diff --git a/.travis.yml b/.travis.yml index e5933262f..4e6443cfd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: node_js node_js: - - "5" + - "6" - "4" - "0.12" - "0.10" @@ -10,3 +10,6 @@ before_install: - npm install -g bower install: npm install + +before_script: + - grunt install diff --git a/CHANGELOG b/CHANGELOG index a71c51d48..991dafded 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,29 +1,37 @@ -TBD (v0.10.0) - TBD (v0.9.8) - * Changed the link tool event signature (e.g. `link:options`) to be the same as the rest of the cell events. - * Added paper.drawGrid(opt) - draws grid lines on the paper's DOM element. - * Added paper.setGridSize(gridSize) - changes the grid size of the paper. - * Added point.scale(), rect.scale() - scale point/rect with given origin. - * Added point.toJSON(), rect.toJSON() - converts point/rect into JSON object. - * Added dia.Element scale(), dia.Link scale() for transforming cells by providing a scale ratio and origin. - * Added dia.Graph resize(), resizeCells() for a group resizing - * Added dia.Graph getCellsBBox() for getting a group bounding box - * Added blank:mousewheel and cell:mousewheel events to paper - * Added env namespace with env.test(name) and env.addTest(name, fn) methods - * Added highlighters namespace with two highlighters (stroke and opacity). Highlighting now done automatically when: embedding an element, connecting a link to a port or element. - * Added Vectorizer transform() method to apply SVG matrix to SVG element - * Added Vectorizer empty(), before() method - * Vectorizer prepend() method accepts single or multiple nodes - * Vectorizer createSVGTrasform() accepts optionally an SVG matrix - * Added dia.Cell.isElement() - * Added dia.Graph.removeCells() - * Can now use dia.Graph.addCells(), dia.Graph.removeCells(), and dia.Graph.resetCells() with the same method signature. For example: dia.Graph.addCells(cell, cell) dia.Graph.addCells(cell, cell, opt) dia.Graph.addCells([cell, cell]) dia.Graph.addCells([cell, cell], opt) are all valid usage. - * Added layout.DirectedGraph.layout() `align` option for rank nodes alignment. - * Added paper.setInteractivity() for changing interactivity. - * Added link:connect, link:disconnect paper events for easier link source/target change detection - * Added leftMiddle(), rightMiddle(), topMiddle(), bottomMiddle() to geometry for finding middle points of rect sides. - * Added ellipse.fromRect(), rect.fromEllipse() to geometry for rect-ellipse interchangeability. + * JointJS: + * Added env namespace with env.test(name) and env.addTest(name, fn) methods + * Added highlighters namespace with two highlighters (stroke and opacity). Highlighting now done automatically when: embedding an element, connecting a link to a port or element. + * dia.Paper: + * Added paper.drawGrid(opt) - draws grid lines on the paper's DOM element. + * Added paper.setGridSize(gridSize) - changes the grid size of the paper. + * Added paper.setInteractivity() for changing interactivity. + * Added blank:mousewheel and cell:mousewheel events to paper + * Added link:connect, link:disconnect paper events for easier link source/target change detection + * Changed the link tool event signature (e.g. `link:options`) to be the same as the rest of the cell events. + * dia.Cell: + * Added isElement() and isLink() methods + * dia.Element: + * Added scale() for transforming element by providing a scale ratio and origin. + * dia.Link: + * Added scale() for transforming link by providing a scale ratio and origin. + * dia.Graph: + * Added resize(), resizeCells() for a group resizing + * Added getCellsBBox() for getting a group bounding box + * Added removeCells() + * Can now use addCells(), removeCells(), and resetCells() with the same method signature. For example: addCells(cell, cell) addCells(cell, cell, opt) addCells([cell, cell]) addCells([cell, cell], opt) are all valid usage. + * layout.DirectedGraph: + * Added layout() `align` option for rank nodes alignment. + * Vectorizer: + * Added transform() method to apply SVG matrix to SVG element + * Added empty(), before() method + * prepend() method now accepts single or multiple nodes + * createSVGTransform() now accepts optionally an SVG matrix + * Geometry: + * Added point.scale(), rect.scale() - scale point/rect with given origin. + * Added point.toJSON(), rect.toJSON() - converts point/rect into JSON object. + * Added leftMiddle(), rightMiddle(), topMiddle(), bottomMiddle() for finding middle points of rect sides. + * Added ellipse.fromRect(), rect.fromEllipse() for rect-ellipse interchangeability. 19-12-2015 (v0.9.6/v0.9.7) * dia.Graph introduces new functions for traversing graphs: dfs(), bfs(), search(), isSuccessor(), isPredecessor(), getPredecessors(), getSuccessors(), isNeighbor(), isSource(), isSink(), getSources(), getSinks(), getSubgraph(), getFirstCell(), getLastCell() and getCells() diff --git a/Gruntfile.js b/Gruntfile.js index bd6c7d03a..435074cf2 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -288,6 +288,7 @@ module.exports = function(grunt) { cwd: 'build/', src: [ '*', + '!docs', '!min' ], dest: 'dist/' @@ -306,11 +307,47 @@ module.exports = function(grunt) { cwd: 'docs/', src: [ 'css/**/*', + 'demo/**/*', 'js/**/*', 'images/**/*' ], dest: 'build/docs/' }, + { + nonull: true, + src: 'node_modules/backbone/backbone-min.js', + dest: 'build/docs/js/lib/backbone.min.js' + }, + { + nonull: true, + src: 'node_modules/dagre/dist/dagre.min.js', + dest: 'build/docs/js/lib/dagre.min.js' + }, + { + nonull: true, + src: 'node_modules/graphlib/dist/graphlib.min.js', + dest: 'build/docs/js/lib/graphlib.min.js' + }, + { + nonull: true, + src: 'node_modules/jquery/dist/jquery.min.js', + dest: 'build/docs/js/lib/jquery.min.js' + }, + { + nonull: true, + src: 'build/min/lodash.min.js', + dest: 'build/docs/js/lib/lodash.min.js' + }, + { + nonull: true, + src: 'build/joint.min.js', + dest: 'build/docs/js/lib/joint.min.js' + }, + { + nonull: true, + src: 'build/joint.min.css', + dest: 'build/docs/css/lib/joint.min.css' + }, { expand: true, flatten: true, @@ -415,6 +452,11 @@ module.exports = function(grunt) { options: { ASCIIOnly: true }, + deps: { + files: { + 'build/min/lodash.min.js': 'node_modules/lodash/index.js' + } + }, geometry: { src: js.geometry, dest: 'build/min/geometry.min.js' @@ -652,6 +694,7 @@ module.exports = function(grunt) { grunt.registerTask('build:joint', [ 'build:plugins', + 'newer:uglify:deps', 'newer:uglify:geometry', 'newer:uglify:vectorizer', 'newer:uglify:joint', diff --git a/docs/demo/layout/DirectedGraph/clusters.html b/docs/demo/layout/DirectedGraph/clusters.html new file mode 100644 index 000000000..00f9f35f8 --- /dev/null +++ b/docs/demo/layout/DirectedGraph/clusters.html @@ -0,0 +1,28 @@ + + + + + + + Rappid - Demo - layout.DirectedGraph - Clusters + + + + +
+ + + + + + + + + + + + + + + + diff --git a/docs/demo/layout/DirectedGraph/index.html b/docs/demo/layout/DirectedGraph/index.html new file mode 100644 index 000000000..a85c6434c --- /dev/null +++ b/docs/demo/layout/DirectedGraph/index.html @@ -0,0 +1,44 @@ + + + + + + + Rappid - Demo - layout.DirectedGraph + + + + +
+ +
+
+ +
+
+
+
+ + + + + + + + + + + + + + + + diff --git a/docs/demo/layout/DirectedGraph/js/clusters.js b/docs/demo/layout/DirectedGraph/js/clusters.js new file mode 100644 index 000000000..7d1c5ad65 --- /dev/null +++ b/docs/demo/layout/DirectedGraph/js/clusters.js @@ -0,0 +1,69 @@ +'use strict'; + +(function() { + + var graph = new joint.dia.Graph; + var paper = new joint.dia.Paper({ + el: $('#paper'), + width: 600, + height: 400, + gridSize: 1, + model: graph + }); + + function makeLink(el1, el2) { + var l = new joint.dia.Link({ + source: { id: el1.id }, + target: { id: el2.id }, + attrs: { + '.connection': { stroke: 'gray' } + } + }); + return l; + } + + function makeElement(attrs) { + var el = new joint.shapes.basic.Rect({ + size: { width: 30, height: 30 }, + attrs: { rect: { rx: 2, ry: 2, fill: '#31D0C6', stroke: '#4B4A67', 'stroke-width': 2 }, text: { text: 'rect', fill: 'white' } } + }); + el.attr(attrs); + return el; + } + + var topGroup = makeElement({ text: { text: 'TopGroup', 'ref-y': 15 }}); + var bottomGroup = makeElement({ text: { text: 'Bottom Group', 'ref-y': 15 }}); + var group = makeElement({ text: { text: 'Group', 'ref-y': 15 }}); + + var ea = makeElement({ text: { text: 'a' }, rect: { fill: '#FE854F' }}); + var eb = makeElement({ text: { text: 'b' }, rect: { fill: '#FE854F' }}); + var ec = makeElement({ text: { text: 'c' }, rect: { fill: '#FE854F' }}); + var ed = makeElement({ text: { text: 'd' }, rect: { fill: '#FE854F' }}); + var ee = makeElement({ text: { text: 'e' }, rect: { fill: '#FE854F' }}); + var ef = makeElement({ text: { text: 'f' }, rect: { fill: '#FE854F' }}); + var eg = makeElement({ text: { text: 'g' }, rect: { fill: '#FE854F' }}); + var eh = makeElement({ text: { text: 'h' }, rect: { fill: '#FE854F' }}); + + var lab = makeLink(ea, eb); + var lbc = makeLink(eb, ec); + var lbd = makeLink(eb, ed); + var lbe = makeLink(eb, ee); + var lbf = makeLink(eb, ef); + var lbg = makeLink(eb, eg); + var lhg = makeLink(eh, eg); + + group.embed(topGroup).embed(bottomGroup); + topGroup.embed(eb).embed(eh); + bottomGroup.embed(ec).embed(ed).embed(ee).embed(ef); + + graph.addCell([group, topGroup, bottomGroup, ea, eb, ec, ed, ed, ee, ef, eg, eh, lab, lbc, lbd, lbe, lbf, lbg, lhg]); + + joint.layout.DirectedGraph.layout(graph, { + setLinkVertices: false, + rankDir: 'TB', + marginX: 50, + marginY: 50, + clusterPadding: { top: 30, left: 10, right: 10, bottom: 10 } + }); + +})(); diff --git a/docs/demo/layout/DirectedGraph/js/index.js b/docs/demo/layout/DirectedGraph/js/index.js new file mode 100644 index 000000000..90b4ef1a0 --- /dev/null +++ b/docs/demo/layout/DirectedGraph/js/index.js @@ -0,0 +1,101 @@ +'use strict'; + +(function() { + + var graph = new joint.dia.Graph; + var paper = new joint.dia.Paper({ + el: $('#paper'), + width: 400, + height: 400, + gridSize: 1, + model: graph + }); + + $('#btn-layout').on('click', layout); + + function layout() { + + try { + var adjacencyList = JSON.parse($('#adjacency-list').val()); + } catch (error) { + console.log(error); + } + + var cells = adjacencyListToCells(adjacencyList); + + graph.resetCells(cells); + + joint.layout.DirectedGraph.layout(graph, { + setLinkVertices: false + }); + } + + // Helpers. + // -------- + + function adjacencyListToCells(adjacencyList) { + + var elements = []; + var links = []; + + _.each(adjacencyList, function(edges, parentElementLabel) { + elements.push(makeElement(parentElementLabel)); + + _.each(edges, function(childElementLabel) { + links.push(makeLink(parentElementLabel, childElementLabel)); + }); + }); + + // Links must be added after all the elements. This is because when the links + // are added to the graph, link source/target + // elements must be in the graph already. + var cells = elements.concat(links); + + return cells; + } + + function makeLink(parentElementLabel, childElementLabel) { + + return new joint.dia.Link({ + source: { id: parentElementLabel }, + target: { id: childElementLabel }, + attrs: { + '.marker-target': { d: 'M 4 0 L 0 2 L 4 4 z' } + }, + smooth: true + }); + } + + function makeElement(label) { + + var maxLineLength = _.max(label.split('\n'), function(l) { + return l.length; + }).length; + + // Compute width/height of the rectangle based on the number + // of lines in the label and the letter size. 0.6 * letterSize is + // an approximation of the monospace font letter width. + var letterSize = 10; + var width = 2 * (letterSize * (0.6 * maxLineLength + 1)); + var height = 2 * ((label.split('\n').length + 1) * letterSize); + + return new joint.shapes.basic.Rect({ + id: label, + size: { width: width, height: height }, + attrs: { + text: { text: label, 'font-size': letterSize, 'font-family': 'monospace', fill: 'white' }, + rect: { + fill: '#FE854F', + width: width, + height: height, + rx: 5, + ry: 5, + stroke: 'none' + } + } + }); + } + + layout(); + +})(); diff --git a/docs/src/geometry/api/bezier/getCurveControlPoints.html b/docs/src/geometry/api/bezier/getCurveControlPoints.html new file mode 100644 index 000000000..d7f105734 --- /dev/null +++ b/docs/src/geometry/api/bezier/getCurveControlPoints.html @@ -0,0 +1,3 @@ + +
g.bezier.getCurveControlPoints(knots)
+

Get open-ended Bezier Spline Control Points. knots should be Bezier spline points (at least two points!). Returns an array where the first item is an array of the first control points and the second item is an array of second control points.

diff --git a/docs/src/geometry/api/bezier/getCurveDivider.html b/docs/src/geometry/api/bezier/getCurveDivider.html new file mode 100644 index 000000000..aa72bafec --- /dev/null +++ b/docs/src/geometry/api/bezier/getCurveDivider.html @@ -0,0 +1,3 @@ + +
g.bezier.getCurveDivider(p0, p1, p2, p3)
+

Divide a Bezier curve into two at point defined by value t (<0,1>). Uses the deCasteljau algorithm.

diff --git a/docs/src/geometry/api/bezier/getFirstControlPoints.html b/docs/src/geometry/api/bezier/getFirstControlPoints.html new file mode 100644 index 000000000..07982e628 --- /dev/null +++ b/docs/src/geometry/api/bezier/getFirstControlPoints.html @@ -0,0 +1,3 @@ + +
g.bezier.getFirstControlPoints(rhs)
+

Solves a tridiagonal system for one of coordinates (x or y) of first Bezier control points. rhs is a right hand side vector. Returns a solution vector.

diff --git a/docs/src/geometry/api/bezier/getInversionSolver.html b/docs/src/geometry/api/bezier/getInversionSolver.html new file mode 100644 index 000000000..5db7bdd0e --- /dev/null +++ b/docs/src/geometry/api/bezier/getInversionSolver.html @@ -0,0 +1,3 @@ + +
g.bezier.getInversionSolver(p0, p1, p2, p3)
+

Solves an inversion problem -- Given the (x, y) coordinates of a point which lies on a parametric curve x = x(t)/w(t), y = y(t)/w(t), find the parameter value t which corresponds to that point. Returns a function that accepts a point and returns t.

diff --git a/docs/src/geometry/api/ellipse/fromRect.html b/docs/src/geometry/api/ellipse/fromRect.html new file mode 100644 index 000000000..73401f525 --- /dev/null +++ b/docs/src/geometry/api/ellipse/fromRect.html @@ -0,0 +1,3 @@ + +
g.ellipse.fromRect(rect)
+

Returns a new ellipse object from the given rect.

diff --git a/docs/src/geometry/api/ellipse/prototype/clone.html b/docs/src/geometry/api/ellipse/prototype/clone.html index 9b15526dd..43beedbaf 100644 --- a/docs/src/geometry/api/ellipse/prototype/clone.html +++ b/docs/src/geometry/api/ellipse/prototype/clone.html @@ -1,3 +1,3 @@
ellipse.clone()
-

Return another ellipse which is a clone of the ellipse.

\ No newline at end of file +

Return another ellipse which is a clone of the ellipse.

diff --git a/docs/src/geometry/api/ellipse/prototype/equals.html b/docs/src/geometry/api/ellipse/prototype/equals.html new file mode 100644 index 000000000..4a062f547 --- /dev/null +++ b/docs/src/geometry/api/ellipse/prototype/equals.html @@ -0,0 +1,3 @@ + +
ellipse.equals(otherEllipse)
+

Returns true if the ellipse equals the other ellipse.

\ No newline at end of file diff --git a/docs/src/geometry/api/ellipse/prototype/intersectionWithLineFromCenterToPoint.html b/docs/src/geometry/api/ellipse/prototype/intersectionWithLineFromCenterToPoint.html index 919f49b16..ae876ebbf 100644 --- a/docs/src/geometry/api/ellipse/prototype/intersectionWithLineFromCenterToPoint.html +++ b/docs/src/geometry/api/ellipse/prototype/intersectionWithLineFromCenterToPoint.html @@ -1,6 +1,3 @@
ellipse.intersectionWithLineFromCenterToPoint(p, angle)
-

Return the point on the boundary of the ellipse that is the intersection of the ellipse with - a line starting in the center of the ellipse ending in the point p. - If angle is specified, the intersection will take into account - the rotation of the ellipse by angle degrees around its center.

\ No newline at end of file +

Return the point on the boundary of the ellipse that is the intersection of the ellipse with a line starting in the center of the ellipse ending in the point p. If angle is specified, the intersection will take into account the rotation of the ellipse by angle degrees around its center.

diff --git a/docs/src/geometry/api/ellipse/prototype/toString.html b/docs/src/geometry/api/ellipse/prototype/toString.html new file mode 100644 index 000000000..14120d92c --- /dev/null +++ b/docs/src/geometry/api/ellipse/prototype/toString.html @@ -0,0 +1,3 @@ + +
ellipse.toString()
+

Returns the ellipse represented as a string.

\ No newline at end of file diff --git a/docs/src/geometry/api/line/constructor.html b/docs/src/geometry/api/line/constructor.html index 0da7a1af6..3db858248 100644 --- a/docs/src/geometry/api/line/constructor.html +++ b/docs/src/geometry/api/line/constructor.html @@ -1,8 +1,6 @@
g.line(p1, p2)
-

Return a new line object with starting at point p1 ending at point p2. p1 - and p2 are first passed through the point constructor so they can - also be passed in the string forms. Examples:

+

Return a new line object with starting at point p1 ending at point p2. p1 and p2 are first passed through the point constructor so they can also be passed in the string forms. Examples:

var l = g.line(g.point(10, 20), g.point(50, 60));
 var l = new g.line('10 20', '50 60');
diff --git a/docs/src/geometry/api/line/prototype/bearing.html b/docs/src/geometry/api/line/prototype/bearing.html
index 507b1112b..3b1b2a0c1 100644
--- a/docs/src/geometry/api/line/prototype/bearing.html
+++ b/docs/src/geometry/api/line/prototype/bearing.html
@@ -1,4 +1,3 @@
 
         
line.bearing()
-

Return the bearing (cardinal direction) of the line. - The return value is a one of the following strings: 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW' and 'N'.

\ No newline at end of file +

Return the bearing (cardinal direction) of the line. The return value is a one of the following strings: 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW' and 'N'.

\ No newline at end of file diff --git a/docs/src/geometry/api/line/prototype/pointAt.html b/docs/src/geometry/api/line/prototype/pointAt.html index 21fe464bf..c708289e8 100644 --- a/docs/src/geometry/api/line/prototype/pointAt.html +++ b/docs/src/geometry/api/line/prototype/pointAt.html @@ -1,4 +1,3 @@
line.pointAt(t)
-

Return a point on the line at length specified by a floating point number t from the interaval [0,1]. - For example, if t equals 0.5, the function returns the midpoint of the line.

\ No newline at end of file +

Return a point on the line at length specified by a floating point number t from the interaval [0,1]. For example, if t equals 0.5, the function returns the midpoint of the line.

diff --git a/docs/src/geometry/api/line/prototype/toString.html b/docs/src/geometry/api/line/prototype/toString.html new file mode 100644 index 000000000..06287b458 --- /dev/null +++ b/docs/src/geometry/api/line/prototype/toString.html @@ -0,0 +1,3 @@ + +
line.toString()
+

Returns the line represented as a string.

diff --git a/docs/src/geometry/api/point/constructor.html b/docs/src/geometry/api/point/constructor.html index c520c6daa..cd02f1385 100644 --- a/docs/src/geometry/api/point/constructor.html +++ b/docs/src/geometry/api/point/constructor.html @@ -1,8 +1,6 @@
g.point(x [, y])
-

Return a new point object with x and y coordinates. If - x is a string, it is considered to be in the form "[number] [number]" - or "[number]@[number]" where the first number is x coordinate and the second is y coordinate. Examples:

+

Return a new point object with x and y coordinates. If x is a string, it is considered to be in the form "[number] [number]" or "[number]@[number]" where the first number is x coordinate and the second is y coordinate. Examples:

var p = g.point(10, 20);
 var p = new g.point(10, 20);
diff --git a/docs/src/geometry/api/point/fromPolar.html b/docs/src/geometry/api/point/fromPolar.html
new file mode 100644
index 000000000..05bb12442
--- /dev/null
+++ b/docs/src/geometry/api/point/fromPolar.html
@@ -0,0 +1,3 @@
+
+        
g.point.fromPolar(distance, angle, origin)
+

Returns a new point object from the given polar coordinates.

diff --git a/docs/src/geometry/api/point/prototype/fromPolar.html b/docs/src/geometry/api/point/prototype/fromPolar.html deleted file mode 100644 index 2d0c58ce6..000000000 --- a/docs/src/geometry/api/point/prototype/fromPolar.html +++ /dev/null @@ -1,3 +0,0 @@ - -
point.fromPolar(r, angle, o)
-

Construct a point from polar coordinates.

\ No newline at end of file diff --git a/docs/src/geometry/api/point/prototype/random.html b/docs/src/geometry/api/point/prototype/random.html deleted file mode 100644 index b00aee4c0..000000000 --- a/docs/src/geometry/api/point/prototype/random.html +++ /dev/null @@ -1,3 +0,0 @@ - -
point.random(x1, x2, y1, y2)
-

Construct a point with random coordinates that fall into the range [x1, x2] and [y1, y2].

\ No newline at end of file diff --git a/docs/src/geometry/api/point/prototype/round.html b/docs/src/geometry/api/point/prototype/round.html index 9aa8c0ea7..32ab8039a 100644 --- a/docs/src/geometry/api/point/prototype/round.html +++ b/docs/src/geometry/api/point/prototype/round.html @@ -1,3 +1,3 @@ -
point.round([decimals])
-

Round the point (optionally on certain number of decimals decimal places) and return the point itself.

\ No newline at end of file +
point.round([precision])
+

Rounds the point to the given precision.

\ No newline at end of file diff --git a/docs/src/geometry/api/point/prototype/scale.html b/docs/src/geometry/api/point/prototype/scale.html new file mode 100644 index 000000000..4fab990ad --- /dev/null +++ b/docs/src/geometry/api/point/prototype/scale.html @@ -0,0 +1,3 @@ + +
point.scale(sx, sy, origin)
+

Scale point by sx and sy about the given origin.

\ No newline at end of file diff --git a/docs/src/geometry/api/point/prototype/toJSON.html b/docs/src/geometry/api/point/prototype/toJSON.html new file mode 100644 index 000000000..c59c8eca7 --- /dev/null +++ b/docs/src/geometry/api/point/prototype/toJSON.html @@ -0,0 +1,3 @@ + +
point.toJSON()
+

Return the point as a simple JSON object. For example: { "x": 0, "y": 0 }.

\ No newline at end of file diff --git a/docs/src/geometry/api/point/prototype/toString.html b/docs/src/geometry/api/point/prototype/toString.html new file mode 100644 index 000000000..bf1a0935b --- /dev/null +++ b/docs/src/geometry/api/point/prototype/toString.html @@ -0,0 +1,3 @@ + +
point.toString()
+

Returns the point as a string x@y.

\ No newline at end of file diff --git a/docs/src/geometry/api/point/random.html b/docs/src/geometry/api/point/random.html new file mode 100644 index 000000000..764f8032a --- /dev/null +++ b/docs/src/geometry/api/point/random.html @@ -0,0 +1,3 @@ + +
g.point.random(distance, angle, origin)
+

Returns a new point object with random coordinates that fall within the range [x1, x2] and [y1, y2].

diff --git a/docs/src/geometry/api/rect/fromEllipse.html b/docs/src/geometry/api/rect/fromEllipse.html new file mode 100644 index 000000000..7f42e83c6 --- /dev/null +++ b/docs/src/geometry/api/rect/fromEllipse.html @@ -0,0 +1,3 @@ + +
g.rect.fromEllipse(ellipse)
+

Returns a new rectangle object from the given ellipse.

diff --git a/docs/src/geometry/api/rect/prototype/bottomMiddle.html b/docs/src/geometry/api/rect/prototype/bottomMiddle.html new file mode 100644 index 000000000..6233647ce --- /dev/null +++ b/docs/src/geometry/api/rect/prototype/bottomMiddle.html @@ -0,0 +1,3 @@ + +
rect.bottomMiddle()
+

Return the point that is at the bottom middle of the rectangle.

\ No newline at end of file diff --git a/docs/src/geometry/api/rect/prototype/leftMiddle.html b/docs/src/geometry/api/rect/prototype/leftMiddle.html new file mode 100644 index 000000000..74d3e2a55 --- /dev/null +++ b/docs/src/geometry/api/rect/prototype/leftMiddle.html @@ -0,0 +1,3 @@ + +
rect.leftMiddle()
+

Return the point that is at the left middle of the rectangle.

\ No newline at end of file diff --git a/docs/src/geometry/api/rect/prototype/normalize.html b/docs/src/geometry/api/rect/prototype/normalize.html index d77a61e83..42d5449d2 100644 --- a/docs/src/geometry/api/rect/prototype/normalize.html +++ b/docs/src/geometry/api/rect/prototype/normalize.html @@ -1,4 +1,3 @@
rect.normalize()
-

Normalize the rectangle, i.e. make it so that it has non-negative width and height. If width is less than 0, the - function swaps left and right corners and if height is less than 0, the top and bottom corners are swapped.

\ No newline at end of file +

Normalize the rectangle, i.e. make it so that it has non-negative width and height. If width is less than 0, the function swaps left and right corners and if height is less than 0, the top and bottom corners are swapped.

\ No newline at end of file diff --git a/docs/src/geometry/api/rect/prototype/rightMiddle.html b/docs/src/geometry/api/rect/prototype/rightMiddle.html new file mode 100644 index 000000000..8eb1207cc --- /dev/null +++ b/docs/src/geometry/api/rect/prototype/rightMiddle.html @@ -0,0 +1,3 @@ + +
rect.rightMiddle()
+

Return the point that is at the right middle of the rectangle.

\ No newline at end of file diff --git a/docs/src/geometry/api/rect/prototype/round.html b/docs/src/geometry/api/rect/prototype/round.html index f99a098bc..90aa97909 100644 --- a/docs/src/geometry/api/rect/prototype/round.html +++ b/docs/src/geometry/api/rect/prototype/round.html @@ -1,3 +1,3 @@ -
rect.round([decimals])
-

Round the rectangle coordinates and dimensions (optionally on decimal places) and return the rectangle itself.

\ No newline at end of file +
rect.round([precision])
+

Rounds the rectangle to the given precision.

\ No newline at end of file diff --git a/docs/src/geometry/api/rect/prototype/snapToGrid.html b/docs/src/geometry/api/rect/prototype/snapToGrid.html new file mode 100644 index 000000000..9960dd349 --- /dev/null +++ b/docs/src/geometry/api/rect/prototype/snapToGrid.html @@ -0,0 +1,3 @@ + +
rect.snapToGrid(gx, gy)
+

Adjust the position and dimensions of the rectangle such that its edges are on the nearest increment of gx on the x-axis and gy on the y-axis.

\ No newline at end of file diff --git a/docs/src/geometry/api/rect/prototype/toJSON.html b/docs/src/geometry/api/rect/prototype/toJSON.html new file mode 100644 index 000000000..3103f8919 --- /dev/null +++ b/docs/src/geometry/api/rect/prototype/toJSON.html @@ -0,0 +1,3 @@ + +
rect.toJSON()
+

Return the rectangle as a simple JSON object. For example: { "x": 0, "y": 0, "width": 40, "height": 40 }.

\ No newline at end of file diff --git a/docs/src/geometry/api/rect/prototype/toString.html b/docs/src/geometry/api/rect/prototype/toString.html new file mode 100644 index 000000000..c4b338f2e --- /dev/null +++ b/docs/src/geometry/api/rect/prototype/toString.html @@ -0,0 +1,3 @@ + +
point.toString()
+

Returns the rectangle as a string x@y x@y.

\ No newline at end of file diff --git a/docs/src/geometry/api/rect/prototype/topMiddle.html b/docs/src/geometry/api/rect/prototype/topMiddle.html new file mode 100644 index 000000000..ddbfa8f89 --- /dev/null +++ b/docs/src/geometry/api/rect/prototype/topMiddle.html @@ -0,0 +1,3 @@ + +
rect.topMiddle()
+

Return the point that is at the top middle of the rectangle.

\ No newline at end of file diff --git a/docs/src/geometry/api/scale/linear.html b/docs/src/geometry/api/scale/linear.html index e2e6b8764..dc9c71276 100644 --- a/docs/src/geometry/api/scale/linear.html +++ b/docs/src/geometry/api/scale/linear.html @@ -1,4 +1,3 @@
g.scale.linear(domain, range, value)
-

Return the value from the domain interval linearly scaled to the range interval. Both - domain and range intervals must be specified as arrays with two numbers specifying start and end of the interval.

\ No newline at end of file +

Return the value from the domain interval linearly scaled to the range interval. Both domain and range intervals must be specified as arrays with two numbers specifying start and end of the interval.

\ No newline at end of file diff --git a/docs/src/joint/api/dia/Element/prototype/addTo.html b/docs/src/joint/api/dia/Element/prototype/addTo.html index 9993067c5..0eb7ec780 100644 --- a/docs/src/joint/api/dia/Element/prototype/addTo.html +++ b/docs/src/joint/api/dia/Element/prototype/addTo.html @@ -1,3 +1,3 @@ -
element.addTo(graph)

Add the element to the graph (an instance of joint.dia.Graph). - This is equivalent to calling graph.addCell(element). -

\ No newline at end of file + +
element.addTo(graph)
+

Add the element to the graph (an instance of joint.dia.Graph). This is equivalent to calling graph.addCell(element).

diff --git a/docs/src/joint/api/dia/Element/prototype/isElement.html b/docs/src/joint/api/dia/Element/prototype/isElement.html new file mode 100644 index 000000000..5a1a3838f --- /dev/null +++ b/docs/src/joint/api/dia/Element/prototype/isElement.html @@ -0,0 +1,8 @@ +
element.isElement()
+ +

Always returns true. The reason the method is here is that both joint.dia.Element and joint.dia.Link inherit from joint.dia.Cell. This method is useful if you don't know what the cell is. Calling el.Element() is equivalent to el instanceof joint.dia.Element. Example:

+ +
var cell = graph.getCell(myId)
+if (cell.isElement()) {
+    // Do something if the cell is an element.
+}
diff --git a/docs/src/joint/api/dia/Element/prototype/scale.html b/docs/src/joint/api/dia/Element/prototype/scale.html new file mode 100644 index 000000000..746310b53 --- /dev/null +++ b/docs/src/joint/api/dia/Element/prototype/scale.html @@ -0,0 +1,3 @@ + +
element.scale(sx, sy, origin[, opt])
+

Scales the element's position and size relative to the given origin.

diff --git a/docs/src/joint/api/dia/Element/prototype/toJSON.html b/docs/src/joint/api/dia/Element/prototype/toJSON.html index 5bc09e7cf..7630312e9 100644 --- a/docs/src/joint/api/dia/Element/prototype/toJSON.html +++ b/docs/src/joint/api/dia/Element/prototype/toJSON.html @@ -1,2 +1,3 @@ -
element.toJSON()

Return a copy of the element's attributes for JSON serialization. This can be used for persistance or serialization. Note that this method doesn't - return a JSON string but rather an object that can be then serialized to JSON with JSON.stringify().

\ No newline at end of file + +
element.toJSON()
+

Return a copy of the element's attributes for JSON serialization. This can be used for persistance or serialization. Note that this method doesn't return a JSON string but rather an object that can be then serialized to JSON with JSON.stringify().

diff --git a/docs/src/joint/api/dia/Element/prototype/transition.html b/docs/src/joint/api/dia/Element/prototype/transition.html index 0fe917ae6..b80c7642a 100644 --- a/docs/src/joint/api/dia/Element/prototype/transition.html +++ b/docs/src/joint/api/dia/Element/prototype/transition.html @@ -1,4 +1,4 @@ -
element.transition(path, value [, options])
+
element.transition(path, value [, opt])

Allows to change the element's property gradually over a period of time. This method lets you specify what property to change (path), when the transition will start (options.delay), how long the transition will last (options.duration), how the transition will run (options.timingFunction), and how to interpolate the property value (options.valueFunction).

diff --git a/docs/src/joint/api/dia/Graph/JSON.html b/docs/src/joint/api/dia/Graph/JSON.html new file mode 100644 index 000000000..a124ac425 --- /dev/null +++ b/docs/src/joint/api/dia/Graph/JSON.html @@ -0,0 +1,28 @@ +

The JointJS graph JSON representation has the following format:

+
{
+    cells: [// Array of cells (ie. links and elements).
+        {
+            id: '3d90f661-fe5f-45dc-a938-bca137691eeb',// Some randomly generated UUID.
+            type: 'basic.Rect',
+            attrs: {
+                'stroke': '#000'
+            },
+            position: {
+                x: 0,
+                y: 50
+            },
+            angle: 90,
+            size: {
+                width: 100,
+                height: 50
+            },
+            z: 2,
+            embeds: [
+                '0c6bf4f1-d5db-4058-9e85-f2d6c74a7a30',
+                'cdbfe073-b160-4e8f-a9a0-22853f29cc06'
+            ],
+            parent: '31f348fe-f5c6-4438-964e-9fc9273c02cb'
+            // ... and some other, maybe custom, data properties
+        }
+    ]
+}
diff --git a/docs/src/joint/api/dia/Graph/prototype/JSON.html b/docs/src/joint/api/dia/Graph/prototype/JSON.html deleted file mode 100644 index 31ed86d64..000000000 --- a/docs/src/joint/api/dia/Graph/prototype/JSON.html +++ /dev/null @@ -1,29 +0,0 @@ -

The JointJS graph JSON representation has the following format:

-

-{
-    cells: [
-        { // Element
-            id: [string],
-            type: [string: type of shape],
-            attrs: { [attrs] },
-            position: { x: [number], y: [number] },
-            angle: [deg],
-            size: { width: [number], height: [number] },
-            z: [number],
-            embeds: [array: IDs of embedded cells],
-            parent: [string: ID of parent if embedded]
-            ... and some other, maybe custom, data properties
-        },
-        { // Link
-            id: [string],
-            type: [string: type of link, default: 'link'],
-            attrs: { [attrs] },
-            vertices: [{ x: [number], y: [number] }, ...],
-            source: { id: [string], selector: [optional CSS selector], port: [optional port identifier] },
-            target: { id: [string], selector: [optional CSS selector], port: [optional port identifier] },
-            labels: [{ position: [float], attrs: {...} }, ...]
-            z: [number],
-            ... and some other, maybe custom, data properties 
-        }
-    ]
-}
diff --git a/docs/src/joint/api/dia/Graph/prototype/addCells.html b/docs/src/joint/api/dia/Graph/prototype/addCells.html index 1ce0802fd..682c6e4ef 100644 --- a/docs/src/joint/api/dia/Graph/prototype/addCells.html +++ b/docs/src/joint/api/dia/Graph/prototype/addCells.html @@ -1,2 +1,4 @@ -
graph.addCells(cells)

Add new cells to the graph. This is just a syntactic sugar to the addCell - method. Calling addCell with an array of cells is an equivalent to calling addCells.

\ No newline at end of file + +
graph.addCells(cells[, opt])
+
graph.addCells(cell, cell, ..[, opt])
+

Add new cells to the graph. This is just a convenience method that wraps the addCell method.

diff --git a/docs/src/joint/api/dia/Graph/prototype/getBBox.html b/docs/src/joint/api/dia/Graph/prototype/getBBox.html index 23c004eca..1b70cd525 100644 --- a/docs/src/joint/api/dia/Graph/prototype/getBBox.html +++ b/docs/src/joint/api/dia/Graph/prototype/getBBox.html @@ -1,3 +1,5 @@ -
graph.getBBox(elements)

Return the bounding box of all the elements combined. Example:

-
var bbox = graph.getBBox(graph.getElements())
+
+
graph.getBBox(cells[, opt])
+

Returns the bounding box that surrounds all the given cells. Links are ignored. An example:

+
var bbox = graph.getBBox(graph.getElements());
 // { x: Number, y: Number, width: Number, height: Number }
diff --git a/docs/src/joint/api/dia/Graph/prototype/removeCells.html b/docs/src/joint/api/dia/Graph/prototype/removeCells.html new file mode 100644 index 000000000..c2961950d --- /dev/null +++ b/docs/src/joint/api/dia/Graph/prototype/removeCells.html @@ -0,0 +1,4 @@ + +
graph.removeCells(cells[, opt])
+
graph.removeCells(cell, cell, ..[, opt])
+

Removes the given cells from the graph.

diff --git a/docs/src/joint/api/dia/Graph/prototype/resetCells.html b/docs/src/joint/api/dia/Graph/prototype/resetCells.html index 841cd8e2f..4bc1478b0 100644 --- a/docs/src/joint/api/dia/Graph/prototype/resetCells.html +++ b/docs/src/joint/api/dia/Graph/prototype/resetCells.html @@ -1,3 +1,4 @@ -
graph.resetCells(cells, [options])
+
graph.resetCells(cells[, opt])
+
graph.resetCells(cell, cell, ..[, opt])

Reset cells in the graph. Update all the cells in the graph in one bulk. This is a more efficient method of adding cells to the graph if you ou want to replace all the cells in one go. options object can optionally contain additional data that is passed over to the event listeners of the graph reset event.

diff --git a/docs/src/joint/api/dia/Link/prototype/isElement.html b/docs/src/joint/api/dia/Link/prototype/isElement.html new file mode 100644 index 000000000..d225dbbf5 --- /dev/null +++ b/docs/src/joint/api/dia/Link/prototype/isElement.html @@ -0,0 +1,8 @@ +
link.isElement()
+ +

Always returns false. The reason the method is here is that both joint.dia.Element and joint.dia.Link inherit from joint.dia.Cell. This method is useful if you don't know what the cell is. Calling el.Element() is equivalent to el instanceof joint.dia.Element. Example:

+ +
var cell = graph.getCell(myId)
+if (cell.isElement()) {
+    // Do something if the cell is an element.
+}
diff --git a/docs/src/joint/api/dia/Link/prototype/scale.html b/docs/src/joint/api/dia/Link/prototype/scale.html new file mode 100644 index 000000000..1a9e53b74 --- /dev/null +++ b/docs/src/joint/api/dia/Link/prototype/scale.html @@ -0,0 +1,3 @@ + +
link.scale(sx, sy, origin[, opt])
+

Scales the link's points (vertices) relative to the given origin.

diff --git a/docs/src/joint/api/dia/Link/prototype/transition.html b/docs/src/joint/api/dia/Link/prototype/transition.html index c99db8770..729e732e7 100644 --- a/docs/src/joint/api/dia/Link/prototype/transition.html +++ b/docs/src/joint/api/dia/Link/prototype/transition.html @@ -1,4 +1,4 @@ -
link.transition(path, value [, options])

Allows to change the link's property gradually over a period of time. This is a method analogous to transition method of Joint.dia.Element.

+
link.transition(path, value [, opt])

Allows to change the link's property gradually over a period of time. This is a method analogous to transition method of Joint.dia.Element.

link.transition('target', { x: 250, y: 250 }, {
     delay: 100,
diff --git a/docs/src/joint/api/dia/Paper/events.html b/docs/src/joint/api/dia/Paper/events.html
index 96720d652..cfab0bd07 100644
--- a/docs/src/joint/api/dia/Paper/events.html
+++ b/docs/src/joint/api/dia/Paper/events.html
@@ -10,19 +10,19 @@
     
  • blank:pointerdown - triggered when a pointer is pressed on a blank area on the paper. Takes evt, x and y as arguments.
  • blank:pointerdblclick - triggered when the user double-clicks a blank area on the paper. Takes evt, x and y as arguments.
  • blank:pointerclick - triggered when the user clicks a blank area on the paper. Takes evt, x and y as arguments.
  • +
  • cell:mousewheel - triggered when the user turns their mousewheel while the cursor is over a cell in the paper. Takes evt, x, y, delta as arguments.
  • +
  • blank:mousewheel - triggered when the user turns their mousewheel while the cursor is over the blank area of the paper. Takes evt, x, y, delta as arguments.
  • cell:contextmenu - triggered when the user right-clicks a cell in the paper. Takes cellView, evt, x and y as arguments.
  • blank:contextmenu - triggered when the user right-clicks a blank area in the paper. Takes evt, x and y as arguments.
  • render:done - triggered when the paper has finished rendering all the cell views in case async rendering is enabled.
  • cell:highlight - triggered when highlight() method is called on either an element or a link. Note that this method is also called automatically when the user is reconnecting a link and the connection is valid (validateConnection() returns true) or if embeddingMode is enabled on the paper and the dragging element is above another element it could be dropped into (validateEmbedding() returns true). The handler for this event has the following signature: function(cellView, el). The handler defaults to - function(cellView, el) { V(el).addClass('highlighted') }. In other words, the 'higlighted' CSS class is added - and so you can style the highlighted element in CSS. If you want to use a different method for highlighting cells, - call paper.off('cell:highlight') first to unregister the default handler and then paper.on('cell:highlight', myHandler) - to register your own. + function(cellView, el) { V(el).addClass('highlighted') }. In other words, the 'higlighted' CSS class is added and so you can style the highlighted element in CSS. If you want to use a different method for highlighting cells, call paper.off('cell:highlight') first to unregister the default handler and then paper.on('cell:highlight', myHandler) to register your own.
  • -
  • cell:unhighlight - triggered when unhighlight() method is called on either an element or a link. See above - the comments for cell:highlight event for further details.
  • +
  • cell:unhighlight - triggered when unhighlight() method is called on either an element or a link. See above the comments for cell:highlight event for further details.
  • +
  • link:connect - triggered when a link is connected to a cell. Takes evt, cellView, magnet, arrowhead as arguments.
  • +
  • link:disconnect - triggered when a link is disconnected from a cell. Takes evt, cellView, magnet, arrowhead as arguments.
  • paper.on('blank:pointerdown', function(evt, x, y) { 
    diff --git a/docs/src/joint/api/dia/Paper/prototype/drawGrid.html b/docs/src/joint/api/dia/Paper/prototype/drawGrid.html
    new file mode 100644
    index 000000000..531f824db
    --- /dev/null
    +++ b/docs/src/joint/api/dia/Paper/prototype/drawGrid.html
    @@ -0,0 +1,7 @@
    +
    +
    paper.drawGrid([opt])
    +

    Draw visual grid lines on the paper. Possible options:

    +
      +
    • color - the color of the grid line (hex, RGB, etc).
    • +
    • thickness - the thickness of the grid line (pixels).
    • +
    diff --git a/docs/src/joint/api/dia/Paper/prototype/setDimensions.html b/docs/src/joint/api/dia/Paper/prototype/setDimensions.html index ae210bef9..5f71c4d84 100644 --- a/docs/src/joint/api/dia/Paper/prototype/setDimensions.html +++ b/docs/src/joint/api/dia/Paper/prototype/setDimensions.html @@ -1,2 +1,3 @@ -
    paper.setDimensions(width, height)

    Change dimensions of a paper. Dimensions should always be passed to the options object of the joint.dia.Paper constructor. Use setDimensions() to change - dimensions of the paper later on if needed. If the method called a "resize" event is triggered on the paper.

    \ No newline at end of file + +
    paper.setDimensions(width, height)
    +

    Change dimensions of a paper. Dimensions should always be passed to the options object of the joint.dia.Paper constructor. Use setDimensions() to change dimensions of the paper later on if needed. If the method called a "resize" event is triggered on the paper.

    diff --git a/docs/src/joint/api/dia/Paper/prototype/setGridSize.html b/docs/src/joint/api/dia/Paper/prototype/setGridSize.html new file mode 100644 index 000000000..699f0b195 --- /dev/null +++ b/docs/src/joint/api/dia/Paper/prototype/setGridSize.html @@ -0,0 +1,3 @@ + +
    paper.setGridSize(gridSize)
    +

    Set the grid size of the paper.

    diff --git a/docs/src/joint/api/dia/Paper/prototype/setInteractivity.html b/docs/src/joint/api/dia/Paper/prototype/setInteractivity.html new file mode 100644 index 000000000..121f8396c --- /dev/null +++ b/docs/src/joint/api/dia/Paper/prototype/setInteractivity.html @@ -0,0 +1,4 @@ + +
    paper.setInteractivity(interactive)
    +

    Set the interactivity of the paper. For example, to disable interactivity:

    +
    paper.setInteractivity(false);
    diff --git a/docs/src/joint/api/env/addTest.html b/docs/src/joint/api/env/addTest.html new file mode 100644 index 000000000..fddc5a6e6 --- /dev/null +++ b/docs/src/joint/api/env/addTest.html @@ -0,0 +1,12 @@ +
    env.addTest(name, fn)
    + +

    Add a custom feature-detection test where name is a string which uniquely identifies your feature test and fn is a function that returns true if the browser supports the feature, and false if it does not.

    + +
    joint.env.addTest('customTest', function() {
    +	// Just as an example, we will always return true here.
    +	return true;
    +});
    +
    +if (joint.env.test('customTest')) {
    +    // Feature is supported.
    +}
    diff --git a/docs/src/joint/api/env/test.html b/docs/src/joint/api/env/test.html new file mode 100644 index 000000000..92bd4931d --- /dev/null +++ b/docs/src/joint/api/env/test.html @@ -0,0 +1,12 @@ +
    env.test(name)
    + +

    Tests whether the browsers supports the given feature or not. Returns true if the feature is supported, otherwise it returns false.

    + +
    if (joint.env.test('someFeature')) {
    +    // Feature is supported.
    +}
    + +

    JointJS ships with the following tests:

    +
      +
    • svgforeignobject - Tests whether the browser supports foreignObject.
    • +
    diff --git a/docs/src/joint/api/layout/DirectedGraph.html b/docs/src/joint/api/layout/DirectedGraph.html new file mode 100644 index 000000000..19169c8cd --- /dev/null +++ b/docs/src/joint/api/layout/DirectedGraph.html @@ -0,0 +1,97 @@ +

    Automatic layout of directed graphs. This plugin uses the open-source (MIT license) Dagre library internally. It provides a wrapper so that you can call the layout directly on JointJS graphs.

    + +

    Note that you must include both the Dagre and Graphlib libraries as dependencies if you wish to use the layout.DirectedGraph plugin.

    + +

    Usage

    + +

    joint.layout.DirectedGraph plugin exposes the joint.layout.DirectedGraph.layout(graph, opt) function. + The first parameter graph is the graph we want to layout. The second parameter options is an + object that contains various options for configuring the layout. +

    + +
    var res = joint.layout.DirectedGraph.layout(graph, {
    +    nodeSep: 50,
    +    edgeSep: 80,
    +    rankDir: "TB"
    +});
    +console.log(res.width, res.height);
    + + +

    A full blog post explaining this example in more detail can be found here.

    + + +

    Example with clusters

    + + + + +

    Configuration

    + +

    The following table lists options that you can pass to the joint.layout.DirectedGraph.layout(graph, opt) function:

    + + + + + + + + + + + +
    nodeSepa number of pixels representing the separation between adjacent nodes in the same rank
    edgeSepa number of pixels representing the separation between adjacent edges in the same rank
    rankSepa number of pixels representing the separation between ranks
    rankDirdirection of the layout (one of "TB" (top-to-bottom) / "BT" (bottom-to-top) / "LR" (left-to-right) / "RL" (right-to-left))
    marginXnumber of pixels to use as a margin around the left and right of the graph.
    marginYnumber of pixels to use as a margin around the top and bottom of the graph.
    resizeClustersset to false if you don't want parent elements to stretch in order to fit all their embedded children. Default is true.
    setPosition(element, position)a function that will be used to set the position of elements at the end of the layout. This is useful + if you don't want to use the default element.set('position', position) but want to set the position in an animated fashion via transitions.
    setLinkVertices(link, vertices)a function that will be used to set the vertices of links at the end of the layout. This is useful + if you don't want to use the default link.set('vertices', vertices) but want to set the vertices in an animated fashion via transitions.
    + +

    Additionally, the layout engine takes into account some properties on elements/links to fine tune the layout further. These are:

    + + + + + + +
    minLen Link specificThe number of ranks to keep between the source and target of the link.
    + +

    The layout() function returns an object that contains width and height +of the resulting graph.

    + + +

    API

    + +

    The layout.DirectedGraph plugins extends the joint.dia.Graph type with functions +that make it easy to convert graphs to and from the Graphlib graph format. +This allows you to easily use many of the graph algorithms provided by this library.

    + + + + +

    toGraphLib()

    Convert the JointJS joint.dia.Graph object to the Graphlib graph object.

    +
    var graph = new joint.dia.Graph;
    +// ... populated the graph with elements connected with links
    +graphlib.alg.isAcyclic(graph.toGraphLib())      // true if the graph is acyclic
    +

    fromGraphLib(glGraph, opt)

    Convert the Graphlib graph representation to JointJS joint.dia.Graph object. + opt.importNode and opt.importEdge are functions that accept a Graphlib node and edge objects and should return + JointJS element/link.

    +
    var g = new graphlib.Graph();
    +g.setNode(1);
    +g.setNode(2);
    +g.setNode(3);
    +g.setEdge(1, 2);
    +g.setEdge(2, 3);
    +
    +var graph = new joint.dia.Graph;
    +graph.fromGraphLib(g, {
    +    importNode: function(node) {
    +        return new joint.shapes.basic.Rect({
    +            position: { x: node.x, y: node.y },
    +            size: { width: node.width, height: node.height }
    +        });
    +    },
    +    importEdge: function(edge) {
    +        return new joint.dia.Link({
    +            source: { id: edge.v },
    +            target: { id: edge.w }
    +        });
    +    }
    +});
    +
    diff --git a/docs/src/joint/api/util/breakText.html b/docs/src/joint/api/util/breakText.html index 32c0fc834..62136283d 100644 --- a/docs/src/joint/api/util/breakText.html +++ b/docs/src/joint/api/util/breakText.html @@ -1,4 +1,4 @@ -
    joint.util.breakText(text, size [, attrs, opt])
    +
    util.breakText(text, size [, attrs, opt])

    Break the text into lines so that the text fits into the bounding box defined by size.width and, optionally, size.height. attrs is an object with SVG attributes that will be set on the SVG text before measuring it (such as font-weight, font, ...). If opt.svgDocument is specified, that SVG document will be used to append the internally created SVG text element used for measurements (by default, an SVG document is created automatically for you). This function creates a temporary SVG <text> element with the attributes provided and continuously measures its width and height trying to fit as many words as possible on each line. When the text overflows the size.height, it is cut. The return value is a string with automatically added newline characters (\n) so that the text fits into the bounding box specified. Useful for implementing auto-wrapping of text in JointJS shapes.

    diff --git a/docs/src/joint/api/util/cancelFrame.html b/docs/src/joint/api/util/cancelFrame.html index f03b5d1bb..5a0366366 100644 --- a/docs/src/joint/api/util/cancelFrame.html +++ b/docs/src/joint/api/util/cancelFrame.html @@ -1 +1 @@ -
    joint.util.cancelFrame(requestId)

    Cancels an animation frame request identified by requestId previously scheduled through a call to joint.util.nextFrame.

    \ No newline at end of file +
    util.cancelFrame(requestId)

    Cancels an animation frame request identified by requestId previously scheduled through a call to joint.util.nextFrame.

    \ No newline at end of file diff --git a/docs/src/joint/api/util/findIntersection.html b/docs/src/joint/api/util/findIntersection.html index 433963a91..44caed2e5 100644 --- a/docs/src/joint/api/util/findIntersection.html +++ b/docs/src/joint/api/util/findIntersection.html @@ -1,4 +1,4 @@ -
    joint.util.findIntersection(node, ref)

    +

    util.findIntersection(node, ref)

    Find the intersection of a line starting in the center of the SVG node ending in the point ref. The function uses isPointInStroke() and isPointInFill() methods that are diff --git a/docs/src/joint/api/util/flattenObject.html b/docs/src/joint/api/util/flattenObject.html index 95938e814..ff963e9e5 100644 --- a/docs/src/joint/api/util/flattenObject.html +++ b/docs/src/joint/api/util/flattenObject.html @@ -1,4 +1,4 @@ -

    joint.util.flattenObject(object, delim, stop)
    +
    util.flattenObject(object, delim, stop)

    Flatten a nested object up until the stop function returns true. The stop function takes the value of the node currently traversed. delim is a delimiter for the combined keys in the resulting object. Example:

    diff --git a/docs/src/joint/api/util/format/number.html b/docs/src/joint/api/util/format/number.html index 522256370..8f0d92e4b 100644 --- a/docs/src/joint/api/util/format/number.html +++ b/docs/src/joint/api/util/format/number.html @@ -1,4 +1,4 @@ -
    joint.util.format.number(specifier, value)
    +
    util.format.number(specifier, value)

    Format number value according to the specifier defined via the Python Format Specification Mini-language.

    diff --git a/docs/src/joint/api/util/getByPath.html b/docs/src/joint/api/util/getByPath.html index 177134bbf..edfdfe5ef 100644 --- a/docs/src/joint/api/util/getByPath.html +++ b/docs/src/joint/api/util/getByPath.html @@ -1,4 +1,4 @@ -
    joint.util.getByPath(object, path, delim)
    +
    util.getByPath(object, path, delim)

    Return a value at the path in a nested object. delim is the delimiter used in the path Example:

    diff --git a/docs/src/joint/api/util/getElementBBox.html b/docs/src/joint/api/util/getElementBBox.html index 2a61c58b7..e84b7a7a1 100644 --- a/docs/src/joint/api/util/getElementBBox.html +++ b/docs/src/joint/api/util/getElementBBox.html @@ -1,3 +1,3 @@ -
    joint.util.getElementBBox(el)

    Return a bounding box of the element el. The advantage of this method is that it can handle +

    util.getElementBBox(el)

    Return a bounding box of the element el. The advantage of this method is that it can handle both HTML and SVG elements. The resulting object is of the form { x: Number, y: Number, width: Number, height: Number }.

    \ No newline at end of file diff --git a/docs/src/joint/api/util/guid.html b/docs/src/joint/api/util/guid.html index 940d60c25..4097dd3c4 100644 --- a/docs/src/joint/api/util/guid.html +++ b/docs/src/joint/api/util/guid.html @@ -1 +1 @@ -
    joint.util.guid()

    Return an identifier unique for the page.

    \ No newline at end of file +
    util.guid()

    Return an identifier unique for the page.

    \ No newline at end of file diff --git a/docs/src/joint/api/util/imageToDataUri.html b/docs/src/joint/api/util/imageToDataUri.html index c3d76a520..d09fb5875 100644 --- a/docs/src/joint/api/util/imageToDataUri.html +++ b/docs/src/joint/api/util/imageToDataUri.html @@ -1,4 +1,4 @@ -
    joint.util.imageToDataUri(url, callback)

    +

    util.imageToDataUri(url, callback)

    Convert an image at url to the Data URI scheme. This function is able to handle PNG, JPG and SVG formats. The callback function has the following signature function(err, dataUri) {}. Useful if you want to embed images right into your diagrams diff --git a/docs/src/joint/api/util/nextFrame.html b/docs/src/joint/api/util/nextFrame.html index 22c609a2b..3b27b5da5 100644 --- a/docs/src/joint/api/util/nextFrame.html +++ b/docs/src/joint/api/util/nextFrame.html @@ -1,4 +1,4 @@ -

    joint.util.nextFrame(callback [, context])

    Tell the browser to schedule the callback function to be called before the next repaint. +

    util.nextFrame(callback [, context])

    Tell the browser to schedule the callback function to be called before the next repaint. This is a cross-browser version of the window.requestAnimationFrame function. Returns an ID of the frame request. For convenience, you can pass a context for your callback function.

    \ No newline at end of file diff --git a/docs/src/joint/api/util/normalizeSides.html b/docs/src/joint/api/util/normalizeSides.html index 38c9d6997..35deaed1e 100644 --- a/docs/src/joint/api/util/normalizeSides.html +++ b/docs/src/joint/api/util/normalizeSides.html @@ -1,4 +1,4 @@ -
    joint.util.normalizeSides(box)
    +
    util.normalizeSides(box)

    Return a new object of the form { top: Number, bottom: Number, left: Number, right: Number }. The value for each of the side is determined by the argument box. If box is a number, all for sides will have this number. If it is an object with some/all of the right, left, top, bottom properties, these properties will be used. If any of the properties is missing, they will be set to 0 for the related side in the resulting object.

    diff --git a/docs/src/joint/api/util/setAttributesBySelector.html b/docs/src/joint/api/util/setAttributesBySelector.html index 0491bc707..ae1cdc07d 100644 --- a/docs/src/joint/api/util/setAttributesBySelector.html +++ b/docs/src/joint/api/util/setAttributesBySelector.html @@ -1,4 +1,4 @@ -
    joint.util.setAttributesBySelector(el, attrs)
    +
    util.setAttributesBySelector(el, attrs)

    Set attributes on the DOM element (SVG or HTML) el and its descendants based on the selector in the attrs object. The attrs object is of the form: { [selector]: [attributes }. For example:

    diff --git a/docs/src/joint/api/util/setByPath.html b/docs/src/joint/api/util/setByPath.html index e31473111..3970bb5d2 100644 --- a/docs/src/joint/api/util/setByPath.html +++ b/docs/src/joint/api/util/setByPath.html @@ -1,4 +1,4 @@ -
    joint.util.setByPath(object, path, value, delim)

    Set a value at the path in a nested object. delim is the +

    util.setByPath(object, path, value, delim)

    Set a value at the path in a nested object. delim is the delimiter used in the path. Returns the augmented object.

    joint.util.setByPath({ a: 1 }, 'b/bb/bbb', 2, '/');
    diff --git a/docs/src/joint/api/util/shapePerimeterConnectionPoint.html b/docs/src/joint/api/util/shapePerimeterConnectionPoint.html
    index 5ca80961a..4a49e3ab3 100644
    --- a/docs/src/joint/api/util/shapePerimeterConnectionPoint.html
    +++ b/docs/src/joint/api/util/shapePerimeterConnectionPoint.html
    @@ -1,4 +1,4 @@
    -
    joint.util.shapePerimeterConnectionPoint(linkView, view, magnet, ref)
    +
    util.shapePerimeterConnectionPoint(linkView, view, magnet, ref)

    This function can be directly used in the dia.Paper linkConnectionPoint parameter. When used, links will try to find the best connection point right on the perimeter of the connected shape rather than only on the bounding box. See the image below.

    diff --git a/docs/src/joint/api/util/sortElements.html b/docs/src/joint/api/util/sortElements.html index 15acf5ca4..43f0a9a93 100644 --- a/docs/src/joint/api/util/sortElements.html +++ b/docs/src/joint/api/util/sortElements.html @@ -1,4 +1,4 @@ -
    joint.util.sortElements(elements, comparator)

    Change the order of elements (a collection of HTML elements or a selector or jQuery object) in the DOM +

    util.sortElements(elements, comparator)

    Change the order of elements (a collection of HTML elements or a selector or jQuery object) in the DOM according to the comparator(elementA, elementB) function. The comparator function has the exact same meaning as in Array.prototype.sort(comparator).

    \ No newline at end of file diff --git a/docs/src/joint/api/util/unsetByPath.html b/docs/src/joint/api/util/unsetByPath.html index d376bde12..a59f29b9d 100644 --- a/docs/src/joint/api/util/unsetByPath.html +++ b/docs/src/joint/api/util/unsetByPath.html @@ -1,4 +1,4 @@ -
    joint.util.unsetByPath(object, path, delim)
    +
    util.unsetByPath(object, path, delim)

    Unset (delete) a property at the path in a nested object. delim is the delimiter used in the path. Returns the augmented object.

    diff --git a/docs/src/joint/api/util/uuid.html b/docs/src/joint/api/util/uuid.html index 8cc4d6ae1..2a64670f4 100644 --- a/docs/src/joint/api/util/uuid.html +++ b/docs/src/joint/api/util/uuid.html @@ -1 +1 @@ -
    joint.util.uuid()

    Return a pseudo-UUID.

    \ No newline at end of file +
    util.uuid()

    Return a pseudo-UUID.

    \ No newline at end of file diff --git a/docs/src/vectorizer/api/createSVGMatrix.html b/docs/src/vectorizer/api/createSVGMatrix.html index 70c8dc7a0..499a37262 100644 --- a/docs/src/vectorizer/api/createSVGMatrix.html +++ b/docs/src/vectorizer/api/createSVGMatrix.html @@ -1,3 +1,3 @@ -
    V.createSVGMatrix(extension)
    -

    Return the SVG transformation matrix initialized with the matrix extension. extension is an object of the form: { a: [number], b: [number], c: [number], d: [number], e: [number], f: [number]}.

    \ No newline at end of file +
    V.createSVGMatrix(extension)
    +

    Return the SVG transformation matrix initialized with the matrix extension. extension is an object of the form: { a: [number], b: [number], c: [number], d: [number], e: [number], f: [number]}.

    \ No newline at end of file diff --git a/docs/src/vectorizer/api/createSVGTransform.html b/docs/src/vectorizer/api/createSVGTransform.html index cc9809e31..0948360ae 100644 --- a/docs/src/vectorizer/api/createSVGTransform.html +++ b/docs/src/vectorizer/api/createSVGTransform.html @@ -1,3 +1,3 @@ -
    V.createSVGTransform()
    -

    Return the SVG transform object.

    \ No newline at end of file +
    V.createSVGTransform([matrix])
    +

    Returns a SVG transform object.

    \ No newline at end of file diff --git a/docs/src/vectorizer/api/prototype/append.html b/docs/src/vectorizer/api/prototype/append.html index 95cda6e43..9321b4649 100644 --- a/docs/src/vectorizer/api/prototype/append.html +++ b/docs/src/vectorizer/api/prototype/append.html @@ -1,4 +1,3 @@ -
    vel.append(el)
    -

    Append another element el as the last child of the element. el can be either - a Vectorizer object or an SVG DOM element.

    \ No newline at end of file +
    vel.append(els)
    +

    Append another element (or elements) els as the last child of the element. els can be Vectorizer object(s) or SVG DOM element(s).

    diff --git a/docs/src/vectorizer/api/prototype/before.html b/docs/src/vectorizer/api/prototype/before.html new file mode 100644 index 000000000..e2708dc70 --- /dev/null +++ b/docs/src/vectorizer/api/prototype/before.html @@ -0,0 +1,3 @@ + +
    vel.before(els)
    +

    Adds the given element (or elements) els before the Vectorizer element vel as a child of the parent node. els can be Vectorizer object(s) or SVG DOM element(s).

    diff --git a/docs/src/vectorizer/api/prototype/clone.html b/docs/src/vectorizer/api/prototype/clone.html index 9d85ddaf2..1ec7fdf67 100644 --- a/docs/src/vectorizer/api/prototype/clone.html +++ b/docs/src/vectorizer/api/prototype/clone.html @@ -1,3 +1,3 @@ -
    vel.clone()
    -

    Clone the Vectorizer object creating a brand new copy of the element. This clone is not automatically added to the DOM.

    \ No newline at end of file +
    vel.clone()
    +

    Clone the Vectorizer object creating a brand new copy of the element. This clone is not automatically added to the DOM.

    \ No newline at end of file diff --git a/docs/src/vectorizer/api/prototype/empty.html b/docs/src/vectorizer/api/prototype/empty.html new file mode 100644 index 000000000..c638156a2 --- /dev/null +++ b/docs/src/vectorizer/api/prototype/empty.html @@ -0,0 +1,3 @@ + +
    vel.empty()
    +

    Removes all the child nodes from the Vectorizer element.

    diff --git a/docs/src/vectorizer/api/prototype/prepend.html b/docs/src/vectorizer/api/prototype/prepend.html index 8add819d4..afa99d1d3 100644 --- a/docs/src/vectorizer/api/prototype/prepend.html +++ b/docs/src/vectorizer/api/prototype/prepend.html @@ -1,4 +1,3 @@ -
    vel.prepend(el)
    -

    Prepend another element el as the first child of the element. el can be either - a Vectorizer object or an SVG DOM element.

    \ No newline at end of file +
    vel.prepend(els)
    +

    Prepend another element (or elements) els as the first child of the element. els can be Vectorizer object(s) or SVG DOM element(s).

    \ No newline at end of file diff --git a/docs/src/vectorizer/api/prototype/transform.html b/docs/src/vectorizer/api/prototype/transform.html new file mode 100644 index 000000000..91d9c8558 --- /dev/null +++ b/docs/src/vectorizer/api/prototype/transform.html @@ -0,0 +1,4 @@ + +
    vel.transform([matrix])
    +

    When matrix is not provided, returns the current transformation matrix of the Vectorizer element.

    +

    When matrix is provided, applies the provided transformation matrix to the Vectorizer element.

    diff --git a/docs/src/vectorizer/api/prototype/translate.html b/docs/src/vectorizer/api/prototype/translate.html index 2233ac2fe..474402ea7 100644 --- a/docs/src/vectorizer/api/prototype/translate.html +++ b/docs/src/vectorizer/api/prototype/translate.html @@ -1,4 +1,3 @@ -
    vel.translate(tx [, ty])
    -

    Translate the element by tx pixels in x axis and ty pixels in y axis. - ty is optional in which case the translation in y axis is considered zero.

    \ No newline at end of file +
    vel.translate(tx [, ty])
    +

    Translate the element by tx pixels in x axis and ty pixels in y axis. ty is optional in which case the translation in y axis is considered zero.

    diff --git a/index.js b/index.js index 238d47971..9be7c17e2 100644 --- a/index.js +++ b/index.js @@ -1 +1,25 @@ -module.exports = require('./build/joint'); +'use strict'; + +var fs = require('fs'); + +var possiblePaths = ['./build/joint.js', './dist/joint.js']; +var filePath; + +while ((filePath = possiblePaths.shift())) { + + try { + fs.statSync(__dirname + '/' + filePath); + } catch (error) { + // Try another path. + continue; + } + + // Found a path that exists. + break; +} + +if (!filePath) { + throw new Error('JointJS build file not found.'); +} + +module.exports = require(filePath); diff --git a/package.json b/package.json index c46098cec..01fba28e3 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,6 @@ "visualization" ], "scripts": { - "postinstall": "node ./scripts/postinstall.js", "test": "grunt test" }, "version": "0.9.7", diff --git a/scripts/postinstall.js b/scripts/postinstall.js deleted file mode 100644 index 7dbad225f..000000000 --- a/scripts/postinstall.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -if (process.env.NODE_ENV !== 'production') { - - var grunt = require('grunt'); - - grunt.cli.tasks = ['install']; - grunt.cli(); -} diff --git a/src/geometry.js b/src/geometry.js index 9107f8c0a..979d10cb4 100644 --- a/src/geometry.js +++ b/src/geometry.js @@ -3,6 +3,8 @@ var g = (function() { + var g = {}; + // Declare shorthands to the most used math functions. var math = Math; var abs = math.abs; @@ -18,29 +20,363 @@ var g = (function() { var floor = math.floor; var PI = math.PI; var random = math.random; - var toDeg = function(rad) { return (180 * rad / PI) % 360; }; - var toRad = function(deg, over360) { - over360 = over360 || false; - deg = over360 ? deg : (deg % 360); - return deg * PI / 180; + + var bezier = g.bezier = { + + // Cubic Bezier curve path through points. + // Ported from C# implementation by Oleg V. Polikarpotchkin and Peter Lee (http://www.codeproject.com/KB/graphics/BezierSpline.aspx). + // @param {array} points Array of points through which the smooth line will go. + // @return {array} SVG Path commands as an array + curveThroughPoints: function(points) { + + var controlPoints = this.getCurveControlPoints(points); + var path = ['M', points[0].x, points[0].y]; + + for (var i = 0; i < controlPoints[0].length; i++) { + path.push('C', controlPoints[0][i].x, controlPoints[0][i].y, controlPoints[1][i].x, controlPoints[1][i].y, points[i + 1].x, points[i + 1].y); + } + + return path; + }, + + // Get open-ended Bezier Spline Control Points. + // @param knots Input Knot Bezier spline points (At least two points!). + // @param firstControlPoints Output First Control points. Array of knots.length - 1 length. + // @param secondControlPoints Output Second Control points. Array of knots.length - 1 length. + getCurveControlPoints: function(knots) { + + var firstControlPoints = []; + var secondControlPoints = []; + var n = knots.length - 1; + var i; + + // Special case: Bezier curve should be a straight line. + if (n == 1) { + // 3P1 = 2P0 + P3 + firstControlPoints[0] = Point((2 * knots[0].x + knots[1].x) / 3, + (2 * knots[0].y + knots[1].y) / 3); + // P2 = 2P1 – P0 + secondControlPoints[0] = Point(2 * firstControlPoints[0].x - knots[0].x, + 2 * firstControlPoints[0].y - knots[0].y); + return [firstControlPoints, secondControlPoints]; + } + + // Calculate first Bezier control points. + // Right hand side vector. + var rhs = []; + + // Set right hand side X values. + for (i = 1; i < n - 1; i++) { + rhs[i] = 4 * knots[i].x + 2 * knots[i + 1].x; + } + rhs[0] = knots[0].x + 2 * knots[1].x; + rhs[n - 1] = (8 * knots[n - 1].x + knots[n].x) / 2.0; + // Get first control points X-values. + var x = this.getFirstControlPoints(rhs); + + // Set right hand side Y values. + for (i = 1; i < n - 1; ++i) { + rhs[i] = 4 * knots[i].y + 2 * knots[i + 1].y; + } + rhs[0] = knots[0].y + 2 * knots[1].y; + rhs[n - 1] = (8 * knots[n - 1].y + knots[n].y) / 2.0; + // Get first control points Y-values. + var y = this.getFirstControlPoints(rhs); + + // Fill output arrays. + for (i = 0; i < n; i++) { + // First control point. + firstControlPoints.push(Point(x[i], y[i])); + // Second control point. + if (i < n - 1) { + secondControlPoints.push(Point(2 * knots [i + 1].x - x[i + 1], + 2 * knots[i + 1].y - y[i + 1])); + } else { + secondControlPoints.push(Point((knots[n].x + x[n - 1]) / 2, + (knots[n].y + y[n - 1]) / 2)); + } + } + return [firstControlPoints, secondControlPoints]; + }, + + // Divide a Bezier curve into two at point defined by value 't' <0,1>. + // Using deCasteljau algorithm. http://math.stackexchange.com/a/317867 + // @param control points (start, control start, control end, end) + // @return a function accepts t and returns 2 curves each defined by 4 control points. + getCurveDivider: function(p0, p1, p2, p3) { + + return function divideCurve(t) { + + var l = Line(p0, p1).pointAt(t); + var m = Line(p1, p2).pointAt(t); + var n = Line(p2, p3).pointAt(t); + var p = Line(l, m).pointAt(t); + var q = Line(m, n).pointAt(t); + var r = Line(p, q).pointAt(t); + return [{ p0: p0, p1: l, p2: p, p3: r }, { p0: r, p1: q, p2: n, p3: p3 }]; + }; + }, + + // Solves a tridiagonal system for one of coordinates (x or y) of first Bezier control points. + // @param rhs Right hand side vector. + // @return Solution vector. + getFirstControlPoints: function(rhs) { + + var n = rhs.length; + // `x` is a solution vector. + var x = []; + var tmp = []; + var b = 2.0; + + x[0] = rhs[0] / b; + // Decomposition and forward substitution. + for (var i = 1; i < n; i++) { + tmp[i] = 1 / b; + b = (i < n - 1 ? 4.0 : 3.5) - tmp[i]; + x[i] = (rhs[i] - x[i - 1]) / b; + } + for (i = 1; i < n; i++) { + // Backsubstitution. + x[n - i - 1] -= tmp[n - i] * x[n - i]; + } + return x; + }, + + // Solves an inversion problem -- Given the (x, y) coordinates of a point which lies on + // a parametric curve x = x(t)/w(t), y = y(t)/w(t), find the parameter value t + // which corresponds to that point. + // @param control points (start, control start, control end, end) + // @return a function accepts a point and returns t. + getInversionSolver: function(p0, p1, p2, p3) { + + var pts = arguments; + function l(i, j) { + // calculates a determinant 3x3 + // [p.x p.y 1] + // [pi.x pi.y 1] + // [pj.x pj.y 1] + var pi = pts[i]; + var pj = pts[j]; + return function(p) { + var w = (i % 3 ? 3 : 1) * (j % 3 ? 3 : 1); + var lij = p.x * (pi.y - pj.y) + p.y * (pj.x - pi.x) + pi.x * pj.y - pi.y * pj.x; + return w * lij; + }; + } + return function solveInversion(p) { + var ct = 3 * l(2, 3)(p1); + var c1 = l(1, 3)(p0) / ct; + var c2 = -l(2, 3)(p0) / ct; + var la = c1 * l(3, 1)(p) + c2 * (l(3, 0)(p) + l(2, 1)(p)) + l(2, 0)(p); + var lb = c1 * l(3, 0)(p) + c2 * l(2, 0)(p) + l(1, 0)(p); + return lb / (lb - la); + }; + } + }; + + var Ellipse = g.Ellipse = function(c, a, b) { + + if (!(this instanceof Ellipse)) { + return new Ellipse(c, a, b); + } + + if (c instanceof Ellipse) { + return new Ellipse(Point(c), c.a, c.b); + } + + c = Point(c); + this.x = c.x; + this.y = c.y; + this.a = a; + this.b = b; }; - var snapToGrid = function(val, gridSize) { return gridSize * Math.round(val / gridSize); }; - var normalizeAngle = function(angle) { return (angle % 360) + (angle < 0 ? 360 : 0); }; - // Point - // ----- + g.Ellipse.fromRect = function(rect) { + + rect = Rect(rect); + return Ellipse(rect.center(), rect.width / 2, rect.height / 2); + }; - // Point is the most basic object consisting of x/y coordinate,. + g.Ellipse.prototype = { - // Possible instantiations are: + bbox: function() { + + return Rect(this.x - this.a, this.y - this.b, 2 * this.a, 2 * this.b); + }, + + clone: function() { + + return Ellipse(this); + }, + + equals: function(ellipse) { + + ellipse = Ellipse(ellipse); + return ellipse.x === this.x && + ellipse.y === this.y && + ellipse.a === this.a && + ellipse.b === this.b; + }, + + // Find point on me where line from my center to + // point p intersects my boundary. + // @param {number} angle If angle is specified, intersection with rotated ellipse is computed. + intersectionWithLineFromCenterToPoint: function(p, angle) { + + p = Point(p); + if (angle) p.rotate(Point(this.x, this.y), angle); + var dx = p.x - this.x; + var dy = p.y - this.y; + var result; + if (dx === 0) { + result = this.bbox().pointNearestToPoint(p); + if (angle) return result.rotate(Point(this.x, this.y), -angle); + return result; + } + var m = dy / dx; + var mSquared = m * m; + var aSquared = this.a * this.a; + var bSquared = this.b * this.b; + var x = sqrt(1 / ((1 / aSquared) + (mSquared / bSquared))); + + x = dx < 0 ? -x : x; + var y = m * x; + result = Point(this.x + x, this.y + y); + if (angle) return result.rotate(Point(this.x, this.y), -angle); + return result; + }, + + toString: function() { + + return Point(this.x, this.y).toString() + ' ' + this.a + ' ' + this.b; + } + }; + + var Line = g.Line = function(p1, p2) { + + if (!(this instanceof Line)) { + return new Line(p1, p2); + } + + this.start = Point(p1); + this.end = Point(p2); + }; + + g.Line.prototype = { + + // @return the bearing (cardinal direction) of the line. For example N, W, or SE. + // @returns {String} One of the following bearings : NE, E, SE, S, SW, W, NW, N. + bearing: function() { + + var lat1 = toRad(this.start.y); + var lat2 = toRad(this.end.y); + var lon1 = this.start.x; + var lon2 = this.end.x; + var dLon = toRad(lon2 - lon1); + var y = sin(dLon) * cos(lat2); + var x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dLon); + var brng = toDeg(atan2(y, x)); + + var bearings = ['NE', 'E', 'SE', 'S', 'SW', 'W', 'NW', 'N']; + + var index = brng - 22.5; + if (index < 0) + index += 360; + index = parseInt(index / 45); + + return bearings[index]; + }, + + clone: function() { + + return Line(this); + }, + + // @return {point} Point where I'm intersecting l. + // @see Squeak Smalltalk, LineSegment>>intersectionWith: + intersection: function(l) { + var pt1Dir = Point(this.end.x - this.start.x, this.end.y - this.start.y); + var pt2Dir = Point(l.end.x - l.start.x, l.end.y - l.start.y); + var det = (pt1Dir.x * pt2Dir.y) - (pt1Dir.y * pt2Dir.x); + var deltaPt = Point(l.start.x - this.start.x, l.start.y - this.start.y); + var alpha = (deltaPt.x * pt2Dir.y) - (deltaPt.y * pt2Dir.x); + var beta = (deltaPt.x * pt1Dir.y) - (deltaPt.y * pt1Dir.x); + + if (det === 0 || + alpha * det < 0 || + beta * det < 0) { + // No intersection found. + return null; + } + if (det > 0) { + if (alpha > det || beta > det) { + return null; + } + } else { + if (alpha < det || beta < det) { + return null; + } + } + return Point(this.start.x + (alpha * pt1Dir.x / det), + this.start.y + (alpha * pt1Dir.y / det)); + }, + + // @return {double} length of the line + length: function() { + return sqrt(this.squaredLength()); + }, + + // @return {point} my midpoint + midpoint: function() { + return Point((this.start.x + this.end.x) / 2, + (this.start.y + this.end.y) / 2); + }, + + // @return {point} my point at 't' <0,1> + pointAt: function(t) { + + var x = (1 - t) * this.start.x + t * this.end.x; + var y = (1 - t) * this.start.y + t * this.end.y; + return Point(x, y); + }, + + // @return {number} the offset of the point `p` from the line. + if the point `p` is on the right side of the line, - if on the left and 0 if on the line. + pointOffset: function(p) { + + // Find the sign of the determinant of vectors (start,end), where p is the query point. + return ((this.end.x - this.start.x) * (p.y - this.start.y) - (this.end.y - this.start.y) * (p.x - this.start.x)) / 2; + }, + + // @return {integer} length without sqrt + // @note for applications where the exact length is not necessary (e.g. compare only) + squaredLength: function() { + var x0 = this.start.x; + var y0 = this.start.y; + var x1 = this.end.x; + var y1 = this.end.y; + return (x0 -= x1) * x0 + (y0 -= y1) * y0; + }, + + toString: function() { + return this.start.toString() + ' ' + this.end.toString(); + } + }; + + /* + Point is the most basic object consisting of x/y coordinate. + + Possible instantiations are: + * `Point(10, 20)` + * `new Point(10, 20)` + * `Point('10 20')` + * `Point(Point(10, 20))` + */ + var Point = g.Point = function(x, y) { + + if (!(this instanceof Point)) { + return new Point(x, y); + } - // * `point(10, 20)` - // * `new point(10, 20)` - // * `point('10 20')` - // * `point(point(10, 20))` - function point(x, y) { - if (!(this instanceof point)) - return new point(x, y); if (typeof x === 'string') { var xy = x.split(x.indexOf('@') === -1 ? ' ' : '@'); x = parseInt(xy[0], 10); @@ -49,405 +385,289 @@ var g = (function() { y = x.y; x = x.x; } + this.x = x === undefined ? 0 : x; this.y = y === undefined ? 0 : y; - } + }; + + // Alternative constructor, from polar coordinates. + // @param {number} Distance. + // @param {number} Angle in radians. + // @param {point} [optional] Origin. + g.Point.fromPolar = function(distance, angle, origin) { + + origin = (origin && Point(origin)) || Point(0, 0); + var x = abs(distance * cos(angle)); + var y = abs(distance * sin(angle)); + var deg = normalizeAngle(toDeg(angle)); + + if (deg < 90) { + y = -y; + } else if (deg < 180) { + x = -x; + y = -y; + } else if (deg < 270) { + x = -x; + } + + return Point(origin.x + x, origin.y + y); + }; + + // Create a point with random coordinates that fall into the range `[x1, x2]` and `[y1, y2]`. + g.Point.random = function(x1, x2, y1, y2) { + + return Point(floor(random() * (x2 - x1 + 1) + x1), floor(random() * (y2 - y1 + 1) + y1)); + }; + + g.Point.prototype = { - point.prototype = { - toString: function() { - return this.x + '@' + this.y; - }, // If point lies outside rectangle `r`, return the nearest point on the boundary of rect `r`, // otherwise return point itself. // (see Squeak Smalltalk, Point>>adhereTo:) adhereToRect: function(r) { + if (r.containsPoint(this)) { return this; } + this.x = mmin(mmax(this.x, r.x), r.x + r.width); this.y = mmin(mmax(this.y, r.y), r.y + r.height); return this; }, - // Compute the angle between me and `p` and the x axis. - // (cartesian-to-polar coordinates conversion) - // Return theta angle in degrees. - theta: function(p) { - p = point(p); - // Invert the y-axis. - var y = -(p.y - this.y); - var x = p.x - this.x; - // Makes sure that the comparison with zero takes rounding errors into account. - var PRECISION = 10; - // Note that `atan2` is not defined for `x`, `y` both equal zero. - var rad = (y.toFixed(PRECISION) == 0 && x.toFixed(PRECISION) == 0) ? 0 : atan2(y, x); - // Correction for III. and IV. quadrant. - if (rad < 0) { - rad = 2 * PI + rad; - } - return 180 * rad / PI; + // Return the bearing between me and the given point. + bearing: function(point) { + + return Line(this, point).bearing(); + }, + + // Returns change in angle from my previous position (-dx, -dy) to my new position + // relative to ref point. + changeInAngle: function(dx, dy, ref) { + + // Revert the translation and measure the change in angle around x-axis. + return Point(this).offset(-dx, -dy).theta(ref) - this.theta(ref); }, + + clone: function() { + + return Point(this); + }, + + difference: function(p) { + + return Point(this.x - p.x, this.y - p.y); + }, + // Returns distance between me and point `p`. distance: function(p) { - return line(this, p).length(); - }, - // Returns a manhattan (taxi-cab) distance between me and point `p`. - manhattanDistance: function(p) { - return abs(p.x - this.x) + abs(p.y - this.y); + + return Line(this, p).length(); }, - // Offset me by the specified amount. - offset: function(dx, dy) { - this.x += dx || 0; - this.y += dy || 0; - return this; + + equals: function(p) { + + return this.x === p.x && this.y === p.y; }, + magnitude: function() { + return sqrt((this.x * this.x) + (this.y * this.y)) || 0.01; }, - update: function(x, y) { - this.x = x || 0; - this.y = y || 0; - return this; - }, - round: function(decimals) { - this.x = decimals ? this.x.toFixed(decimals) : round(this.x); - this.y = decimals ? this.y.toFixed(decimals) : round(this.y); - return this; - }, - // Scale the line segment between (0,0) and me to have a length of len. - normalize: function(len) { - var s = (len || 1) / this.magnitude(); - this.x = s * this.x; - this.y = s * this.y; - return this; - }, - difference: function(p) { - return point(this.x - p.x, this.y - p.y); - }, - // Return the bearing between me and point `p`. - bearing: function(p) { - return line(this, p).bearing(); - }, - // Converts rectangular to polar coordinates. - // An origin can be specified, otherwise it's 0@0. - toPolar: function(o) { - o = (o && point(o)) || point(0, 0); - var x = this.x; - var y = this.y; - this.x = sqrt((x - o.x) * (x - o.x) + (y - o.y) * (y - o.y)); // r - this.y = toRad(o.theta(point(x, y))); - return this; - }, - // Rotate point by angle around origin o. - rotate: function(o, angle) { - angle = (angle + 360) % 360; - this.toPolar(o); - this.y += toRad(angle); - var p = point.fromPolar(this.x, this.y, o); - this.x = p.x; - this.y = p.y; - return this; + + // Returns a manhattan (taxi-cab) distance between me and point `p`. + manhattanDistance: function(p) { + + return abs(p.x - this.x) + abs(p.y - this.y); }, + // Move point on line starting from ref ending at me by // distance distance. move: function(ref, distance) { - var theta = toRad(point(ref).theta(this)); + + var theta = toRad(Point(ref).theta(this)); return this.offset(cos(theta) * distance, -sin(theta) * distance); }, - // Scale point with origin at point `o`. - scale: function(sx, sy, o) { - o = (o && point(o)) || point(0, 0); - this.x = o.x + sx * (this.x - o.x); - this.y = o.y + sy * (this.y - o.y); - return this; - }, - // Returns change in angle from my previous position (-dx, -dy) to my new position - // relative to ref point. - changeInAngle: function(dx, dy, ref) { - // Revert the translation and measure the change in angle around x-axis. - return point(this).offset(-dx, -dy).theta(ref) - this.theta(ref); - }, - equals: function(p) { - return this.x === p.x && this.y === p.y; + + // Scales x and y such that the distance between the point and the origin (0,0) is equal to the given length. + normalize: function(length) { + + var scale = (length || 1) / this.magnitude(); + return this.scale(scale, scale); }, - snapToGrid: function(gx, gy) { - this.x = snapToGrid(this.x, gx); - this.y = snapToGrid(this.y, gy || gx); + + // Offset me by the specified amount. + offset: function(dx, dy) { + + this.x += dx || 0; + this.y += dy || 0; return this; }, + // Returns a point that is the reflection of me with // the center of inversion in ref point. reflection: function(ref) { - return point(ref).move(this, this.distance(ref)); - }, - clone: function() { - return point(this); - }, - toJSON: function() { - return { x: this.x, y: this.y }; - } - }; - // Alternative constructor, from polar coordinates. - // @param {number} r Distance. - // @param {number} angle Angle in radians. - // @param {point} [optional] o Origin. - point.fromPolar = function(r, angle, o) { - o = (o && point(o)) || point(0, 0); - var x = abs(r * cos(angle)); - var y = abs(r * sin(angle)); - var deg = normalizeAngle(toDeg(angle)); - if (deg < 90) { - y = -y; - } else if (deg < 180) { - x = -x; - y = -y; - } else if (deg < 270) { - x = -x; - } + return Point(ref).move(this, this.distance(ref)); + }, - return point(o.x + x, o.y + y); - }; + // Rotate point by angle around origin. + rotate: function(origin, angle) { - // Create a point with random coordinates that fall into the range `[x1, x2]` and `[y1, y2]`. - point.random = function(x1, x2, y1, y2) { - return point(floor(random() * (x2 - x1 + 1) + x1), floor(random() * (y2 - y1 + 1) + y1)); - }; + angle = (angle + 360) % 360; + this.toPolar(origin); + this.y += toRad(angle); + var point = Point.fromPolar(this.x, this.y, origin); + this.x = point.x; + this.y = point.y; + return this; + }, - // Line. - // ----- - function line(p1, p2) { - if (!(this instanceof line)) - return new line(p1, p2); - this.start = point(p1); - this.end = point(p2); - } + round: function(precision) { - line.prototype = { - toString: function() { - return this.start.toString() + ' ' + this.end.toString(); - }, - // @return {double} length of the line - length: function() { - return sqrt(this.squaredLength()); - }, - // @return {integer} length without sqrt - // @note for applications where the exact length is not necessary (e.g. compare only) - squaredLength: function() { - var x0 = this.start.x; - var y0 = this.start.y; - var x1 = this.end.x; - var y1 = this.end.y; - return (x0 -= x1) * x0 + (y0 -= y1) * y0; + this.x = precision ? this.x.toFixed(precision) : round(this.x); + this.y = precision ? this.y.toFixed(precision) : round(this.y); + return this; }, - // @return {point} my midpoint - midpoint: function() { - return point((this.start.x + this.end.x) / 2, - (this.start.y + this.end.y) / 2); + + // Scale point with origin. + scale: function(sx, sy, origin) { + + origin = (origin && Point(origin)) || Point(0, 0); + this.x = origin.x + sx * (this.x - origin.x); + this.y = origin.y + sy * (this.y - origin.y); + return this; }, - // @return {point} Point where I'm intersecting l. - // @see Squeak Smalltalk, LineSegment>>intersectionWith: - intersection: function(l) { - var pt1Dir = point(this.end.x - this.start.x, this.end.y - this.start.y); - var pt2Dir = point(l.end.x - l.start.x, l.end.y - l.start.y); - var det = (pt1Dir.x * pt2Dir.y) - (pt1Dir.y * pt2Dir.x); - var deltaPt = point(l.start.x - this.start.x, l.start.y - this.start.y); - var alpha = (deltaPt.x * pt2Dir.y) - (deltaPt.y * pt2Dir.x); - var beta = (deltaPt.x * pt1Dir.y) - (deltaPt.y * pt1Dir.x); - if (det === 0 || - alpha * det < 0 || - beta * det < 0) { - // No intersection found. - return null; - } - if (det > 0) { - if (alpha > det || beta > det) { - return null; - } - } else { - if (alpha < det || beta < det) { - return null; - } - } - return point(this.start.x + (alpha * pt1Dir.x / det), - this.start.y + (alpha * pt1Dir.y / det)); + snapToGrid: function(gx, gy) { + + this.x = snapToGrid(this.x, gx); + this.y = snapToGrid(this.y, gy || gx); + return this; }, - // @return the bearing (cardinal direction) of the line. For example N, W, or SE. - // @returns {String} One of the following bearings : NE, E, SE, S, SW, W, NW, N. - bearing: function() { + // Compute the angle between me and `p` and the x axis. + // (cartesian-to-polar coordinates conversion) + // Return theta angle in degrees. + theta: function(p) { - var lat1 = toRad(this.start.y); - var lat2 = toRad(this.end.y); - var lon1 = this.start.x; - var lon2 = this.end.x; - var dLon = toRad(lon2 - lon1); - var y = sin(dLon) * cos(lat2); - var x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dLon); - var brng = toDeg(atan2(y, x)); + p = Point(p); + // Invert the y-axis. + var y = -(p.y - this.y); + var x = p.x - this.x; + // Makes sure that the comparison with zero takes rounding errors into account. + var PRECISION = 10; + // Note that `atan2` is not defined for `x`, `y` both equal zero. + var rad = (y.toFixed(PRECISION) == 0 && x.toFixed(PRECISION) == 0) ? 0 : atan2(y, x); - var bearings = ['NE', 'E', 'SE', 'S', 'SW', 'W', 'NW', 'N']; + // Correction for III. and IV. quadrant. + if (rad < 0) { + rad = 2 * PI + rad; + } + return 180 * rad / PI; + }, - var index = brng - 22.5; - if (index < 0) - index += 360; - index = parseInt(index / 45); + toJSON: function() { - return bearings[index]; + return { x: this.x, y: this.y }; }, - // @return {point} my point at 't' <0,1> - pointAt: function(t) { - var x = (1 - t) * this.start.x + t * this.end.x; - var y = (1 - t) * this.start.y + t * this.end.y; - return point(x, y); + // Converts rectangular to polar coordinates. + // An origin can be specified, otherwise it's 0@0. + toPolar: function(o) { + + o = (o && Point(o)) || Point(0, 0); + var x = this.x; + var y = this.y; + this.x = sqrt((x - o.x) * (x - o.x) + (y - o.y) * (y - o.y)); // r + this.y = toRad(o.theta(Point(x, y))); + return this; }, - // @return {number} the offset of the point `p` from the line. + if the point `p` is on the right side of the line, - if on the left and 0 if on the line. - pointOffset: function(p) { - // Find the sign of the determinant of vectors (start,end), where p is the query point. - return ((this.end.x - this.start.x) * (p.y - this.start.y) - (this.end.y - this.start.y) * (p.x - this.start.x)) / 2; + toString: function() { + + return this.x + '@' + this.y; }, - clone: function() { - return line(this); + + update: function(x, y) { + + this.x = x || 0; + this.y = y || 0; + return this; } }; - // Rectangle. - // ---------- - function rect(x, y, w, h) { - if (!(this instanceof rect)) - return new rect(x, y, w, h); + var Rect = g.Rect = function(x, y, w, h) { + + if (!(this instanceof Rect)) { + return new Rect(x, y, w, h); + } + if ((Object(x) === x)) { y = x.y; w = x.width; h = x.height; x = x.x; } + this.x = x === undefined ? 0 : x; this.y = y === undefined ? 0 : y; this.width = w === undefined ? 0 : w; this.height = h === undefined ? 0 : h; - } + }; + + g.Rect.fromEllipse = function(e) { - rect.fromEllipse = function(e) { - e = ellipse(e); - return rect(e.x - e.a, e.y - e.b, 2 * e.a, 2 * e.b); + e = Ellipse(e); + return Rect(e.x - e.a, e.y - e.b, 2 * e.a, 2 * e.b); }; - rect.prototype = { - toString: function() { - return this.origin().toString() + ' ' + this.corner().toString(); - }, - // @return {boolean} true if rectangles are equal. - equals: function(r) { - var mr = g.rect(this).normalize(); - var nr = g.rect(r).normalize(); - return mr.x === nr.x && mr.y === nr.y && mr.width === nr.width && mr.height === nr.height; - }, - origin: function() { - return point(this.x, this.y); - }, - corner: function() { - return point(this.x + this.width, this.y + this.height); - }, - topRight: function() { - return point(this.x + this.width, this.y); - }, - bottomLeft: function() { - return point(this.x, this.y + this.height); - }, - center: function() { - return point(this.x + this.width / 2, this.y + this.height / 2); - }, - topMiddle: function() { - return point(this.x + this.width / 2, this.y); - }, - bottomMiddle: function() { - return point(this.x + this.width / 2, this.y + this.height); - }, - leftMiddle: function() { - return point(this.x , this.y + this.height / 2); - }, - rightMiddle: function() { - return point(this.x + this.width, this.y + this.height / 2); - }, + g.Rect.prototype = { - // @return {rect} if rectangles intersect, {null} if not. - intersect: function(r) { - var myOrigin = this.origin(); - var myCorner = this.corner(); - var rOrigin = r.origin(); - var rCorner = r.corner(); + // Find my bounding box when I'm rotated with the center of rotation in the center of me. + // @return r {rectangle} representing a bounding box + bbox: function(angle) { - // No intersection found - if (rCorner.x <= myOrigin.x || - rCorner.y <= myOrigin.y || - rOrigin.x >= myCorner.x || - rOrigin.y >= myCorner.y) return null; + var theta = toRad(angle || 0); + var st = abs(sin(theta)); + var ct = abs(cos(theta)); + var w = this.width * ct + this.height * st; + var h = this.width * st + this.height * ct; + return Rect(this.x + (this.width - w) / 2, this.y + (this.height - h) / 2, w, h); + }, - var x = Math.max(myOrigin.x, rOrigin.x); - var y = Math.max(myOrigin.y, rOrigin.y); + bottomLeft: function() { - return rect(x, y, Math.min(myCorner.x, rCorner.x) - x, Math.min(myCorner.y, rCorner.y) - y); + return Point(this.x, this.y + this.height); }, - // @return {rect} representing the union of both rectangles. - union: function(r) { - var myOrigin = this.origin(); - var myCorner = this.corner(); - var rOrigin = r.origin(); - var rCorner = r.corner(); + bottomMiddle: function() { - var originX = Math.min(myOrigin.x, rOrigin.x); - var originY = Math.min(myOrigin.y, rOrigin.y); - var cornerX = Math.max(myCorner.x, rCorner.x); - var cornerY = Math.max(myCorner.y, rCorner.y); + return Point(this.x + this.width / 2, this.y + this.height); + }, + + center: function() { - return rect(originX, originY, cornerX - originX, cornerY - originY); + return Point(this.x + this.width / 2, this.y + this.height / 2); }, - // @return {string} (left|right|top|bottom) side which is nearest to point - // @see Squeak Smalltalk, Rectangle>>sideNearestTo: - sideNearestToPoint: function(p) { - p = point(p); - var distToLeft = p.x - this.x; - var distToRight = (this.x + this.width) - p.x; - var distToTop = p.y - this.y; - var distToBottom = (this.y + this.height) - p.y; - var closest = distToLeft; - var side = 'left'; + clone: function() { - if (distToRight < closest) { - closest = distToRight; - side = 'right'; - } - if (distToTop < closest) { - closest = distToTop; - side = 'top'; - } - if (distToBottom < closest) { - closest = distToBottom; - side = 'bottom'; - } - return side; + return Rect(this); }, + // @return {bool} true if point p is insight me containsPoint: function(p) { - p = point(p); - if (p.x >= this.x && p.x <= this.x + this.width && - p.y >= this.y && p.y <= this.y + this.height) { - return true; - } - return false; + + p = Point(p); + return p.x >= this.x && p.x <= this.x + this.width && p.y >= this.y && p.y <= this.y + this.height; }, // @return {bool} true if rectangle `r` is inside me. containsRect: function(r) { - var r0 = rect(this).normalize(); - var r1 = rect(r).normalize(); + var r0 = Rect(this).normalize(); + var r1 = Rect(r).normalize(); var w0 = r0.width; var h0 = r0.height; var w1 = r1.width; @@ -468,41 +688,60 @@ var g = (function() { h1 += y1; h0 += y0; - return x0 <= x1 && w1 <= w0 && y0 <= y1 && h1 <= h0; + return x0 <= x1 && w1 <= w0 && y0 <= y1 && h1 <= h0; + }, + + corner: function() { + + return Point(this.x + this.width, this.y + this.height); + }, + + // @return {boolean} true if rectangles are equal. + equals: function(r) { + + var mr = Rect(this).normalize(); + var nr = Rect(r).normalize(); + return mr.x === nr.x && mr.y === nr.y && mr.width === nr.width && mr.height === nr.height; + }, + + // @return {rect} if rectangles intersect, {null} if not. + intersect: function(r) { + + var myOrigin = this.origin(); + var myCorner = this.corner(); + var rOrigin = r.origin(); + var rCorner = r.corner(); + + // No intersection found + if (rCorner.x <= myOrigin.x || + rCorner.y <= myOrigin.y || + rOrigin.x >= myCorner.x || + rOrigin.y >= myCorner.y) return null; + + var x = Math.max(myOrigin.x, rOrigin.x); + var y = Math.max(myOrigin.y, rOrigin.y); + + return Rect(x, y, Math.min(myCorner.x, rCorner.x) - x, Math.min(myCorner.y, rCorner.y) - y); }, - // @return {point} a point on my boundary nearest to p - // @see Squeak Smalltalk, Rectangle>>pointNearestTo: - pointNearestToPoint: function(p) { - p = point(p); - if (this.containsPoint(p)) { - var side = this.sideNearestToPoint(p); - switch (side){ - case 'right': return point(this.x + this.width, p.y); - case 'left': return point(this.x, p.y); - case 'bottom': return point(p.x, this.y + this.height); - case 'top': return point(p.x, this.y); - } - } - return p.adhereToRect(this); - }, // Find point on my boundary where line starting // from my center ending in point p intersects me. // @param {number} angle If angle is specified, intersection with rotated rectangle is computed. intersectionWithLineFromCenterToPoint: function(p, angle) { - p = point(p); - var center = point(this.x + this.width / 2, this.y + this.height / 2); + + p = Point(p); + var center = Point(this.x + this.width / 2, this.y + this.height / 2); var result; if (angle) p.rotate(center, angle); // (clockwise, starting from the top side) var sides = [ - line(this.origin(), this.topRight()), - line(this.topRight(), this.corner()), - line(this.corner(), this.bottomLeft()), - line(this.bottomLeft(), this.origin()) + Line(this.origin(), this.topRight()), + Line(this.topRight(), this.corner()), + Line(this.corner(), this.bottomLeft()), + Line(this.bottomLeft(), this.origin()) ]; - var connector = line(center, p); + var connector = Line(center, p); for (var i = sides.length - 1; i >= 0; --i) { var intersection = sides[i].intersection(connector); @@ -514,27 +753,29 @@ var g = (function() { if (result && angle) result.rotate(center, -angle); return result; }, + + leftMiddle: function() { + + return Point(this.x , this.y + this.height / 2); + }, + // Move and expand me. // @param r {rectangle} representing deltas moveAndExpand: function(r) { + this.x += r.x || 0; this.y += r.y || 0; this.width += r.width || 0; this.height += r.height || 0; return this; }, - round: function(decimals) { - this.x = decimals ? this.x.toFixed(decimals) : round(this.x); - this.y = decimals ? this.y.toFixed(decimals) : round(this.y); - this.width = decimals ? this.width.toFixed(decimals) : round(this.width); - this.height = decimals ? this.height.toFixed(decimals) : round(this.height); - return this; - }, + // Normalize the rectangle; i.e., make it so that it has a non-negative width and height. // If width < 0 the function swaps the left and right corners, // and it swaps the top and bottom corners if height < 0 // like in http://qt-project.org/doc/qt-4.8/qrectf.html#normalized normalize: function() { + var newx = this.x; var newy = this.y; var newwidth = this.width; @@ -553,26 +794,83 @@ var g = (function() { this.height = newheight; return this; }, - // Find my bounding box when I'm rotated with the center of rotation in the center of me. - // @return r {rectangle} representing a bounding box - bbox: function(angle) { - var theta = toRad(angle || 0); - var st = abs(sin(theta)); - var ct = abs(cos(theta)); - var w = this.width * ct + this.height * st; - var h = this.width * st + this.height * ct; - return rect(this.x + (this.width - w) / 2, this.y + (this.height - h) / 2, w, h); + + origin: function() { + + return Point(this.x, this.y); + }, + + // @return {point} a point on my boundary nearest to the given point. + // @see Squeak Smalltalk, Rectangle>>pointNearestTo: + pointNearestToPoint: function(point) { + + point = Point(point); + if (this.containsPoint(point)) { + var side = this.sideNearestToPoint(point); + switch (side){ + case 'right': return Point(this.x + this.width, point.y); + case 'left': return Point(this.x, point.y); + case 'bottom': return Point(point.x, this.y + this.height); + case 'top': return Point(point.x, this.y); + } + } + return point.adhereToRect(this); + }, + + rightMiddle: function() { + + return Point(this.x + this.width, this.y + this.height / 2); + }, + + round: function(precision) { + + this.x = precision ? this.x.toFixed(precision) : round(this.x); + this.y = precision ? this.y.toFixed(precision) : round(this.y); + this.width = precision ? this.width.toFixed(precision) : round(this.width); + this.height = precision ? this.height.toFixed(precision) : round(this.height); + return this; }, - // Scale rectangle with origin at point `o` - scale: function(sx, sy, o) { - var origin = this.origin().scale(sx, sy, o); + + // Scale rectangle with origin. + scale: function(sx, sy, origin) { + + var origin = this.origin().scale(sx, sy, origin); this.x = origin.x; this.y = origin.y; this.width *= sx; this.height *= sy; return this; }, + + // @return {string} (left|right|top|bottom) side which is nearest to point + // @see Squeak Smalltalk, Rectangle>>sideNearestTo: + sideNearestToPoint: function(point) { + + point = Point(point); + var distToLeft = point.x - this.x; + var distToRight = (this.x + this.width) - point.x; + var distToTop = point.y - this.y; + var distToBottom = (this.y + this.height) - point.y; + var closest = distToLeft; + var side = 'left'; + + if (distToRight < closest) { + closest = distToRight; + side = 'right'; + } + if (distToTop < closest) { + closest = distToTop; + side = 'top'; + } + if (distToBottom < closest) { + closest = distToBottom; + side = 'bottom'; + } + return side; + }, + snapToGrid: function(gx, gy) { + var origin = this.origin().snapToGrid(gx, gy); var corner = this.corner().snapToGrid(gx, gy); this.x = origin.x; @@ -581,225 +879,50 @@ var g = (function() { this.height = corner.y - origin.y; return this; }, - clone: function() { - return rect(this); - }, - toJSON: function() { - return { x: this.x, y: this.y, width: this.width, height: this.height }; - } - }; - - // Ellipse. - // -------- - function ellipse(c, a, b) { - if (!(this instanceof ellipse)) - return new ellipse(c, a, b); - if (c instanceof ellipse) { - return new ellipse(point(c), c.a, c.b); - } - c = point(c); - this.x = c.x; - this.y = c.y; - this.a = a; - this.b = b; - } - ellipse.fromRect = function(r) { - r = rect(r); - return ellipse(r.center(), r.width / 2, r.height / 2); - }; - - ellipse.prototype = { - toString: function() { - return point(this.x, this.y).toString() + ' ' + this.a + ' ' + this.b; - }, - bbox: function() { - return rect(this.x - this.a, this.y - this.b, 2 * this.a, 2 * this.b); - }, - equals: function(e) { - e = ellipse(e); - return e.x === this.x && e.y === this.y && e.a === this.a && e.b === this.b; - }, - // Find point on me where line from my center to - // point p intersects my boundary. - // @param {number} angle If angle is specified, intersection with rotated ellipse is computed. - intersectionWithLineFromCenterToPoint: function(p, angle) { - p = point(p); - if (angle) p.rotate(point(this.x, this.y), angle); - var dx = p.x - this.x; - var dy = p.y - this.y; - var result; - if (dx === 0) { - result = this.bbox().pointNearestToPoint(p); - if (angle) return result.rotate(point(this.x, this.y), -angle); - return result; - } - var m = dy / dx; - var mSquared = m * m; - var aSquared = this.a * this.a; - var bSquared = this.b * this.b; - var x = sqrt(1 / ((1 / aSquared) + (mSquared / bSquared))); + topMiddle: function() { - x = dx < 0 ? -x : x; - var y = m * x; - result = point(this.x + x, this.y + y); - if (angle) return result.rotate(point(this.x, this.y), -angle); - return result; + return Point(this.x + this.width / 2, this.y); }, - clone: function() { - return ellipse(this); - } - }; - // Bezier curve. - // ------------- - var bezier = { - // Cubic Bezier curve path through points. - // Ported from C# implementation by Oleg V. Polikarpotchkin and Peter Lee (http://www.codeproject.com/KB/graphics/BezierSpline.aspx). - // @param {array} points Array of points through which the smooth line will go. - // @return {array} SVG Path commands as an array - curveThroughPoints: function(points) { - var controlPoints = this.getCurveControlPoints(points); - var path = ['M', points[0].x, points[0].y]; + topRight: function() { - for (var i = 0; i < controlPoints[0].length; i++) { - path.push('C', controlPoints[0][i].x, controlPoints[0][i].y, controlPoints[1][i].x, controlPoints[1][i].y, points[i + 1].x, points[i + 1].y); - } - return path; + return Point(this.x + this.width, this.y); }, - // Get open-ended Bezier Spline Control Points. - // @param knots Input Knot Bezier spline points (At least two points!). - // @param firstControlPoints Output First Control points. Array of knots.length - 1 length. - // @param secondControlPoints Output Second Control points. Array of knots.length - 1 length. - getCurveControlPoints: function(knots) { - var firstControlPoints = []; - var secondControlPoints = []; - var n = knots.length - 1; - var i; - - // Special case: Bezier curve should be a straight line. - if (n == 1) { - // 3P1 = 2P0 + P3 - firstControlPoints[0] = point((2 * knots[0].x + knots[1].x) / 3, - (2 * knots[0].y + knots[1].y) / 3); - // P2 = 2P1 – P0 - secondControlPoints[0] = point(2 * firstControlPoints[0].x - knots[0].x, - 2 * firstControlPoints[0].y - knots[0].y); - return [firstControlPoints, secondControlPoints]; - } - - // Calculate first Bezier control points. - // Right hand side vector. - var rhs = []; + toJSON: function() { - // Set right hand side X values. - for (i = 1; i < n - 1; i++) { - rhs[i] = 4 * knots[i].x + 2 * knots[i + 1].x; - } - rhs[0] = knots[0].x + 2 * knots[1].x; - rhs[n - 1] = (8 * knots[n - 1].x + knots[n].x) / 2.0; - // Get first control points X-values. - var x = this.getFirstControlPoints(rhs); + return { x: this.x, y: this.y, width: this.width, height: this.height }; + }, - // Set right hand side Y values. - for (i = 1; i < n - 1; ++i) { - rhs[i] = 4 * knots[i].y + 2 * knots[i + 1].y; - } - rhs[0] = knots[0].y + 2 * knots[1].y; - rhs[n - 1] = (8 * knots[n - 1].y + knots[n].y) / 2.0; - // Get first control points Y-values. - var y = this.getFirstControlPoints(rhs); + toString: function() { - // Fill output arrays. - for (i = 0; i < n; i++) { - // First control point. - firstControlPoints.push(point(x[i], y[i])); - // Second control point. - if (i < n - 1) { - secondControlPoints.push(point(2 * knots [i + 1].x - x[i + 1], - 2 * knots[i + 1].y - y[i + 1])); - } else { - secondControlPoints.push(point((knots[n].x + x[n - 1]) / 2, - (knots[n].y + y[n - 1]) / 2)); - } - } - return [firstControlPoints, secondControlPoints]; + return this.origin().toString() + ' ' + this.corner().toString(); }, - // Solves a tridiagonal system for one of coordinates (x or y) of first Bezier control points. - // @param rhs Right hand side vector. - // @return Solution vector. - getFirstControlPoints: function(rhs) { - var n = rhs.length; - // `x` is a solution vector. - var x = []; - var tmp = []; - var b = 2.0; + // @return {rect} representing the union of both rectangles. + union: function(rect) { - x[0] = rhs[0] / b; - // Decomposition and forward substitution. - for (var i = 1; i < n; i++) { - tmp[i] = 1 / b; - b = (i < n - 1 ? 4.0 : 3.5) - tmp[i]; - x[i] = (rhs[i] - x[i - 1]) / b; - } - for (i = 1; i < n; i++) { - // Backsubstitution. - x[n - i - 1] -= tmp[n - i] * x[n - i]; - } - return x; - }, + var myOrigin = this.origin(); + var myCorner = this.corner(); + var rOrigin = rect.origin(); + var rCorner = rect.corner(); - // Solves an inversion problem -- Given the (x, y) coordinates of a point which lies on - // a parametric curve x = x(t)/w(t), y = y(t)/w(t), find the parameter value t - // which corresponds to that point. - // @param control points (start, control start, control end, end) - // @return a function accepts a point and returns t. - getInversionSolver: function(p0, p1, p2, p3) { - var pts = arguments; - function l(i, j) { - // calculates a determinant 3x3 - // [p.x p.y 1] - // [pi.x pi.y 1] - // [pj.x pj.y 1] - var pi = pts[i]; - var pj = pts[j]; - return function(p) { - var w = (i % 3 ? 3 : 1) * (j % 3 ? 3 : 1); - var lij = p.x * (pi.y - pj.y) + p.y * (pj.x - pi.x) + pi.x * pj.y - pi.y * pj.x; - return w * lij; - }; - } - return function solveInversion(p) { - var ct = 3 * l(2, 3)(p1); - var c1 = l(1, 3)(p0) / ct; - var c2 = -l(2, 3)(p0) / ct; - var la = c1 * l(3, 1)(p) + c2 * (l(3, 0)(p) + l(2, 1)(p)) + l(2, 0)(p); - var lb = c1 * l(3, 0)(p) + c2 * l(2, 0)(p) + l(1, 0)(p); - return lb / (lb - la); - }; - }, + var originX = Math.min(myOrigin.x, rOrigin.x); + var originY = Math.min(myOrigin.y, rOrigin.y); + var cornerX = Math.max(myCorner.x, rCorner.x); + var cornerY = Math.max(myCorner.y, rCorner.y); - // Divide a Bezier curve into two at point defined by value 't' <0,1>. - // Using deCasteljau algorithm. http://math.stackexchange.com/a/317867 - // @param control points (start, control start, control end, end) - // @return a function accepts t and returns 2 curves each defined by 4 control points. - getCurveDivider: function(p0, p1, p2, p3) { - return function divideCurve(t) { - var l = line(p0, p1).pointAt(t); - var m = line(p1, p2).pointAt(t); - var n = line(p2, p3).pointAt(t); - var p = line(l, m).pointAt(t); - var q = line(m, n).pointAt(t); - var r = line(p, q).pointAt(t); - return [{ p0: p0, p1: l, p2: p, p3: r }, { p0: r, p1: q, p2: n, p3: p3 }]; - }; + return Rect(originX, originY, cornerX - originX, cornerY - originY); } }; - // Scale. - var scale = { + var normalizeAngle = g.normalizeAngle = function(angle) { + + return (angle % 360) + (angle < 0 ? 360 : 0); + }; + + var scale = g.scale = { // Return the `value` from the `domain` interval scaled to the `range` interval. linear: function(domain, range, value) { @@ -810,17 +933,29 @@ var g = (function() { } }; - return { - toDeg: toDeg, - toRad: toRad, - snapToGrid: snapToGrid, - normalizeAngle: normalizeAngle, - point: point, - line: line, - rect: rect, - ellipse: ellipse, - bezier: bezier, - scale: scale + var snapToGrid = g.snapToGrid = function(value, gridSize) { + + return gridSize * Math.round(value / gridSize); + }; + + var toDeg = g.toDeg = function(rad) { + + return (180 * rad / PI) % 360; + }; + + var toRad = g.toRad = function(deg, over360) { + + over360 = over360 || false; + deg = over360 ? deg : (deg % 360); + return deg * PI / 180; }; + // For backwards compatibility: + g.ellipse = g.Ellipse; + g.line = g.Line; + g.point = g.Point; + g.rect = g.Rect; + + return g; + })(); diff --git a/src/joint.dia.graph.js b/src/joint.dia.graph.js index 4a2571579..eea4a967b 100644 --- a/src/joint.dia.graph.js +++ b/src/joint.dia.graph.js @@ -963,6 +963,7 @@ joint.dia.Graph = Backbone.Model.extend({ // Return bounding box of all elements. getBBox: function(cells, opt) { + return this.getCellsBBox(cells || this.getElements(), opt); }, diff --git a/src/vectorizer.js b/src/vectorizer.js index ddc234095..07851a8ea 100644 --- a/src/vectorizer.js +++ b/src/vectorizer.js @@ -489,13 +489,6 @@ V = Vectorizer = (function() { return this; }; - V.prototype.prepend = function(el) { - - this.node.insertBefore(V.isV(el) ? el.node : el, this.node.firstChild); - - return this; - }; - V.prototype.prepend = function(els) { var child = this.node.firstChild; diff --git a/test/geometry/bezier.js b/test/geometry/bezier.js new file mode 100644 index 000000000..deb4fe2db --- /dev/null +++ b/test/geometry/bezier.js @@ -0,0 +1,24 @@ +'use strict'; + +QUnit.module('bezier', function() { + + QUnit.module('curveThroughPoints(points)', function() { + + }); + + QUnit.module('getCurveControlPoints(knots)', function() { + + }); + + QUnit.module('getCurveDivider(p0, p1, p2, p3)', function() { + + }); + + QUnit.module('getFirstControlPoints(rhs)', function() { + + }); + + QUnit.module('getInversionSolver(p0, p1, p2, p3)', function() { + + }); +}); diff --git a/test/geometry/ellipse.js b/test/geometry/ellipse.js new file mode 100644 index 000000000..ef7540008 --- /dev/null +++ b/test/geometry/ellipse.js @@ -0,0 +1,53 @@ +'use strict'; + +QUnit.module('ellipse', function() { + + QUnit.module('constructor', function() { + + QUnit.test('creates a new Ellipse object', function(assert) { + + assert.ok(g.ellipse() instanceof g.ellipse); + assert.ok(g.ellipse({ x: 1, y: 2 }, 3, 4) instanceof g.ellipse); + assert.equal(g.ellipse({ x: 1, y: 2 }, 3, 4).x, 1); + assert.equal(g.ellipse({ x: 1, y: 2 }, 3, 4).y, 2); + assert.equal(g.ellipse({ x: 1, y: 2 }, 3, 4).a, 3); + assert.equal(g.ellipse({ x: 1, y: 2 }, 3, 4).b, 4); + assert.ok(g.ellipse(g.ellipse({ x: 1, y: 2 }, 3, 4)).equals(g.ellipse({ x: 1, y: 2 }, 3, 4))); + // default values + assert.ok(g.ellipse().equals(g.rect({ x: 0, y: 0 }, 0, 0))); + }); + }); + + QUnit.module('fromRect(rect)', function() { + + QUnit.test('creates a new Ellipse object', function(assert) { + + assert.ok(g.ellipse.fromRect(g.rect()) instanceof g.ellipse); + var r = g.rect(100, 50, 150, 70); + assert.ok(g.rect.fromEllipse(g.ellipse.fromRect(r)).equals(r)); + }); + }); + + QUnit.module('prototype', function() { + + QUnit.module('bbox()', function() { + + }); + + QUnit.module('clone()', function() { + + }); + + QUnit.module('equals(ellipse)', function() { + + }); + + QUnit.module('intersectionWithLineFromCenterToPoint(point, angle)', function() { + + }); + + QUnit.module('toString()', function() { + + }); + }); +}); diff --git a/test/geometry/geometry.js b/test/geometry/geometry.js deleted file mode 100644 index 53d244e19..000000000 --- a/test/geometry/geometry.js +++ /dev/null @@ -1,160 +0,0 @@ -QUnit.module('geometry', function() { - - QUnit.module('point', function() { - - QUnit.test('point()', function(assert) { - - assert.ok(g.point() instanceof g.point); - assert.ok(g.point(1, 2) instanceof g.point); - assert.equal(g.point(1, 2).x, 1); - assert.equal(g.point(1, 2).y, 2); - assert.ok(g.point('1 2').equals(g.point(1, 2))); - assert.ok(g.point({ x: 1, y: 2 }).equals(g.point(1, 2))); - assert.ok(g.point(g.point(1, 2)).equals(g.point(1, 2))); - // default values - assert.equal(g.point(10).y, 0); - assert.equal(g.point({ x: 10 }).y, 0); - }); - - QUnit.test('scale()', function(assert) { - assert.equal(g.point(20, 30).scale(2, 3).toString(), g.point(40, 90).toString(), 'scale with no origin provided'); - assert.equal(g.point(20, 30).scale(2, 3, g.point(40, 45)).toString(), g.point(0, 0).toString(), 'scale with origin provided'); - }); - - QUnit.test('toJSON()', function(assert) { - assert.deepEqual(g.point(20, 30).toJSON(), { x: 20, y: 30 }); - }); - - }); - - QUnit.module('line', function() { - - QUnit.test('bearing()', function(assert) { - - assert.equal(g.line('0 0', '0 -10').bearing(), 'S', 'south bearing'); - assert.equal(g.line('0 0', '0 10').bearing(), 'N', 'north bearing'); - assert.equal(g.line('0 0', '10 10').bearing(), 'NE', 'north east bearing'); - assert.equal(g.line('0 0', '-10 10').bearing(), 'NW', 'north west bearing'); - assert.equal(g.line('0 0', '10 0').bearing(), 'E', 'east bearing'); - assert.equal(g.line('0 0', '-10 0').bearing(), 'W', 'west bearing'); - assert.equal(g.line('0 0', '-10 -10').bearing(), 'SW', 'south west bearing'); - assert.equal(g.line('0 0', '10 -10').bearing(), 'SE', 'south east bearing'); - }); - - }); - - QUnit.module('rect', function() { - - QUnit.test('rect()', function(assert) { - - assert.ok(g.rect() instanceof g.rect); - assert.ok(g.rect(1, 2, 3, 4) instanceof g.rect); - assert.equal(g.rect(1, 2, 3, 4).x, 1); - assert.equal(g.rect(1, 2, 3, 4).y, 2); - assert.equal(g.rect(1, 2, 3, 4).width, 3); - assert.equal(g.rect(1, 2, 3, 4).height, 4); - assert.ok(g.rect({ x: 1, y: 2, width: 3, height: 4 }).equals(g.rect(1, 2, 3, 4))); - assert.ok(g.rect(g.rect(1, 2, 3, 4)).equals(g.rect(1, 2, 3, 4))); - // default values - assert.ok(g.rect().equals(g.rect(0, 0, 0, 0))); - }); - - QUnit.test('fromEllipse()', function(assert) { - assert.ok(g.rect.fromEllipse(g.ellipse()) instanceof g.rect); - var e = g.ellipse(g.point(100, 50), 150, 70); - assert.ok(g.ellipse.fromRect(g.rect.fromEllipse(e)).equals(e)); - }); - - QUnit.test('leftMiddle()', function(assert) { - assert.ok(g.rect(10, 20, 30, 40).leftMiddle().equals(g.point(10, 40))); - }); - - QUnit.test('rightMiddle()', function(assert) { - assert.ok(g.rect(10, 20, 30, 40).rightMiddle().equals(g.point(40, 40))); - }); - - QUnit.test('topMiddle()', function(assert) { - assert.ok(g.rect(10, 20, 30, 40).topMiddle().equals(g.point(25, 20))); - }); - - QUnit.test('bottomMiddle()', function(assert) { - assert.ok(g.rect(10, 20, 30, 40).bottomMiddle().equals(g.point(25, 60))); - }); - - QUnit.test('containsRect()', function(assert) { - - assert.notOk(g.rect(50, 50, 100, 100).containsRect(g.rect(20, 20, 200, 200)), 'not inside when surround'); - assert.notOk(g.rect(50, 50, 100, 100).containsRect(g.rect(40, 40, 100, 100)), 'not inside when overlap left and top'); - assert.notOk(g.rect(50, 50, 100, 100).containsRect(g.rect(60, 60, 100, 40)), 'not inside when overlap left'); - assert.notOk(g.rect(50, 50, 100, 100).containsRect(g.rect(60, 60, 100, 100)), 'not inside when overlap right and bottom'); - assert.notOk(g.rect(50, 50, 100, 100).containsRect(g.rect(60, 60, 40, 100)), 'not inside when overlap bottom'); - assert.notOk(g.rect(50, 50, 100, 100).containsRect(g.rect(75, 75, 0, 0)), 'not inside when argument rect has zero width/height'); - assert.notOk(g.rect(50, 50, 0, 0).containsRect(g.rect(50, 50, 0, 0)), 'not inside when both rects have zero width/height'); - assert.ok(g.rect(50, 50, 100, 100).containsRect(g.rect(60, 60, 80, 80)), 'inside'); - assert.ok(g.rect(50, 50, 100, 100).containsRect(g.rect(50, 50, 100, 100)), 'inside when equal'); - }); - - QUnit.test('equals()', function(assert) { - - assert.ok(g.rect(20, 20, 100, 100).equals(g.rect(20, 20, 100, 100)), 'equal'); - assert.ok(g.rect(20, 20, 100, 100).equals(g.rect(120, 120, -100, -100)), 'equal when target not normalized'); - assert.ok(g.rect(120, 120, -100, -100).equals(g.rect(20, 20, 100, 100)), 'equal when source not normalized'); - assert.notOk(g.rect(20, 20, 100, 100).equals(g.rect(10, 10, 110, 110)), 'not equal'); - }); - - QUnit.test('intersect()', function(assert) { - - assert.ok(g.rect(20, 20, 100, 100).intersect(g.rect(40, 40, 20, 20)).equals(g.rect(40, 40, 20, 20)), 'inside'); - assert.ok(g.rect(20, 20, 100, 100).intersect(g.rect(0, 0, 100, 100)).equals(g.rect(20, 20, 80, 80)), 'overlap left and top'); - assert.ok(g.rect(20, 20, 100, 100).intersect(g.rect(40, 40, 100, 100)).equals(g.rect(40, 40, 80, 80)), 'overlap right and bottom'); - assert.equal(g.rect(20, 20, 100, 100).intersect(g.rect(140, 140, 20, 20)), null, 'no intersection'); - }); - - QUnit.test('union()', function(assert) { - - assert.equal(g.rect(20, 20, 50, 50).union(g.rect(100, 100, 50, 50)).toString(), g.rect(20, 20, 130, 130).toString(), 'union of distant rectangles'); - assert.equal(g.rect(20, 20, 150, 150).union(g.rect(50, 50, 20, 20)).toString(), g.rect(20, 20, 150, 150).toString(), 'union of embedded rectangles'); - assert.equal(g.rect(20, 20, 150, 150).union(g.rect(50, 50, 200, 200)).toString(), g.rect(20, 20, 230, 230).toString(), 'union of intersecting rectangles'); - }); - - QUnit.test('scale()', function(assert) { - assert.equal(g.rect(20, 30, 40, 50).scale(2, 3).toString(), g.rect(40, 90, 80, 150).toString(), 'scale with no origin provided'); - assert.equal(g.rect(20, 30, 40, 50).scale(2, 3, g.point(20, 30)).toString(), g.rect(20, 30, 80, 150).toString(), 'scale with origin at rect origin'); - }); - - QUnit.test('toJSON()', function(assert) { - assert.deepEqual(g.rect(20, 30, 40, 50).toJSON(), { x: 20, y: 30, width: 40, height: 50 }); - }); - }); - - QUnit.module('ellipse', function() { - - QUnit.test('ellipse()', function(assert) { - - assert.ok(g.ellipse() instanceof g.ellipse); - assert.ok(g.ellipse({ x: 1, y: 2 }, 3, 4) instanceof g.ellipse); - assert.equal(g.ellipse({ x: 1, y: 2 }, 3, 4).x, 1); - assert.equal(g.ellipse({ x: 1, y: 2 }, 3, 4).y, 2); - assert.equal(g.ellipse({ x: 1, y: 2 }, 3, 4).a, 3); - assert.equal(g.ellipse({ x: 1, y: 2 }, 3, 4).b, 4); - assert.ok(g.ellipse(g.ellipse({ x: 1, y: 2 }, 3, 4)).equals(g.ellipse({ x: 1, y: 2 }, 3, 4))); - // default values - assert.ok(g.ellipse().equals(g.rect({ x: 0, y: 0 }, 0, 0))); - }); - - QUnit.test('fromRect()', function(assert) { - - assert.ok(g.ellipse.fromRect(g.rect()) instanceof g.ellipse); - var r = g.rect(100, 50, 150, 70); - assert.ok(g.rect.fromEllipse(g.ellipse.fromRect(r)).equals(r)); - }); - - }); - - QUnit.test('scale()', function(assert) { - - assert.equal(g.scale.linear([.5, 1], [50, 150], .75), 100, 'linear scale up'); - assert.equal(g.scale.linear([50, 150], [.5, 1], 100), .75, 'linear scale down'); - }); - -}); diff --git a/test/geometry/index.html b/test/geometry/index.html index 72b9d858e..09d35705c 100644 --- a/test/geometry/index.html +++ b/test/geometry/index.html @@ -1,22 +1,30 @@ - + + Geometry | Tests + + + - Geometry test suite - - - +
    +
    -
    -
    + + + - - - + - + + + + + + + + + + - - - + diff --git a/test/geometry/line.js b/test/geometry/line.js new file mode 100644 index 000000000..1589f8cd8 --- /dev/null +++ b/test/geometry/line.js @@ -0,0 +1,70 @@ +'use strict'; + +QUnit.module('line', function() { + + QUnit.module('constructor', function() { + + QUnit.test('creates a new Line object', function(assert) { + + var line = g.line(g.point(), g.point(3, 8)); + assert.ok(line, 'returns instance of g.line'); + assert.ok(typeof line.start !== 'undefined', 'has "start" property'); + assert.ok(typeof line.end !== 'undefined', 'has "end" property'); + assert.equal(line.start.x, 0, 'start.x is correct'); + assert.equal(line.start.y, 0, 'start.y is correct'); + assert.equal(line.end.x, 3, 'end.x is correct'); + assert.equal(line.end.y, 8, 'end.y is correct'); + assert.ok(g.line() instanceof g.line, 'no arguments provided'); + }); + }); + + QUnit.module('prototype', function() { + + QUnit.module('bearing()', function() { + + QUnit.test('should return the line\'s bearing', function(assert) { + + assert.equal(g.line('0 0', '0 -10').bearing(), 'S', 'south bearing'); + assert.equal(g.line('0 0', '0 10').bearing(), 'N', 'north bearing'); + assert.equal(g.line('0 0', '10 10').bearing(), 'NE', 'north east bearing'); + assert.equal(g.line('0 0', '-10 10').bearing(), 'NW', 'north west bearing'); + assert.equal(g.line('0 0', '10 0').bearing(), 'E', 'east bearing'); + assert.equal(g.line('0 0', '-10 0').bearing(), 'W', 'west bearing'); + assert.equal(g.line('0 0', '-10 -10').bearing(), 'SW', 'south west bearing'); + assert.equal(g.line('0 0', '10 -10').bearing(), 'SE', 'south east bearing'); + }); + }); + + QUnit.module('clone()', function() { + + }); + + QUnit.module('intersection(line)', function() { + + }); + + QUnit.module('length()', function() { + + }); + + QUnit.module('midpoint()', function() { + + }); + + QUnit.module('pointAt(t)', function() { + + }); + + QUnit.module('pointOffset(point)', function() { + + }); + + QUnit.module('squaredLength()', function() { + + }); + + QUnit.module('toString()', function() { + + }); + }); +}); diff --git a/test/geometry/normalizeAngle.js b/test/geometry/normalizeAngle.js new file mode 100644 index 000000000..d39639e4b --- /dev/null +++ b/test/geometry/normalizeAngle.js @@ -0,0 +1,28 @@ +'use strict'; + +QUnit.module('normalizeAngle(angle)', function() { + + QUnit.test('returns normalized value of angle (within the range [0,360] degrees)', function(assert) { + + var normalizedAngles = [ + { angle: 720, normalized: 0 }, + { angle: 180, normalized: 180 }, + { angle: 370, normalized: 10 }, + { angle: 1080, normalized: 0 }, + { angle: 1085, normalized: 5 }, + { angle: 0, normalized: 0 }, + { angle: 360, normalized: 0 }, + { angle: -360, normalized: 360 }, + { angle: -180, normalized: 180 }, + { angle: -90, normalized: 270 } + ]; + + var angle, normalized; + + for (var i = 0; i < normalizedAngles.length; i++) { + angle = normalizedAngles[i].angle; + normalized = normalizedAngles[i].normalized; + assert.equal(g.normalizeAngle(angle), normalized, 'angle = ' + angle); + } + }); +}); diff --git a/test/geometry/point.js b/test/geometry/point.js new file mode 100644 index 000000000..90ed0857c --- /dev/null +++ b/test/geometry/point.js @@ -0,0 +1,167 @@ +'use strict'; + +QUnit.module('point', function() { + + QUnit.module('constructor', function() { + + QUnit.test('creates a new Point object', function(assert) { + + assert.ok(g.point() instanceof g.point); + assert.ok(g.point(1, 2) instanceof g.point); + assert.equal(g.point(1, 2).x, 1); + assert.equal(g.point(1, 2).y, 2); + assert.ok(g.point('1 2').equals(g.point(1, 2))); + assert.ok(g.point({ x: 1, y: 2 }).equals(g.point(1, 2))); + assert.ok(g.point(g.point(1, 2)).equals(g.point(1, 2))); + // default values + assert.equal(g.point(10).y, 0); + assert.equal(g.point({ x: 10 }).y, 0); + }); + }); + + QUnit.module('fromPolar(distance, angle, origin)', function() { + + }); + + QUnit.module('random(x1, x2, y1, y2)', function() { + + }); + + QUnit.module('prototype', function() { + + QUnit.module('adhereToRect(rect)', function() { + + }); + + QUnit.module('bearing(point)', function() { + + }); + + QUnit.module('changeInAngle(dx, dy, ref)', function() { + + }); + + QUnit.module('clone()', function() { + + }); + + QUnit.module('difference(point)', function() { + + }); + + QUnit.module('distance(point)', function() { + + }); + + QUnit.module('equals(point)', function() { + + }); + + QUnit.module('magnitude()', function() { + + }); + + QUnit.module('manhattanDistance(point)', function() { + + }); + + QUnit.module('move(ref, distance)', function() { + + }); + + QUnit.module('normalize(length)', function() { + + QUnit.test('scales x and y such that the distance between the point and the origin (0,0) is equal to the given length', function(assert) { + + assert.equal(g.point(0, 10).normalize(20).toString(), '0@20'); + assert.equal(g.point(2, 0).normalize(4).toString(), '4@0'); + }); + }); + + QUnit.module('offset(dx, dy)', function() { + + QUnit.test('changes the x and y values by adding the given dx and dy values respectively', function(assert) { + + var point = g.point(0, 0); + point.offset(2, 3); + assert.equal(point.toString(), '2@3'); + point.offset(-2, 4); + assert.equal(point.toString(), '0@7'); + }); + }); + + QUnit.module('reflection(ref)', function() { + + }); + + QUnit.module('rotate(origin, angle)', function() { + + }); + + QUnit.module('round(precision)', function() { + + QUnit.test('rounds the x and y properties to the given precision', function(assert) { + + var point = g.point(17.231, 4.01); + point.round(2); + assert.equal(point.toString(), '17.23@4.01'); + point.round(0); + assert.equal(point.toString(), '17@4'); + }); + }); + + QUnit.module('scale(sx, sy, origin)', function() { + + QUnit.test('without origin', function(assert) { + + assert.equal(g.point(20, 30).scale(2, 3).toString(), g.point(40, 90).toString()); + }); + + QUnit.test('with origin', function(assert) { + + assert.equal(g.point(20, 30).scale(2, 3, g.point(40, 45)).toString(), g.point(0, 0).toString()); + }); + }); + + QUnit.module('snapToGrid(gx, gy)', function() { + + }); + + QUnit.module('theta(point)', function() { + + }); + + QUnit.module('toJSON()', function() { + + QUnit.test('returns an object with the point\'s coordinates', function(assert) { + + assert.deepEqual(g.point(20, 30).toJSON(), { x: 20, y: 30 }); + }); + }); + + QUnit.module('toPolar(origin)', function() { + + }); + + QUnit.module('toString()', function() { + + QUnit.test('returns string with values of x and y', function(assert) { + + var value = g.point(17, 20).toString(); + + assert.equal(typeof value, 'string'); + assert.equal(value, '17@20'); + }); + }); + + QUnit.module('update(x, y)', function() { + + QUnit.test('changes the values of x and y', function(assert) { + + var point = g.point(4, 17); + point.update(16, 24); + assert.equal(point.toString(), '16@24'); + }); + }); + }); +}); diff --git a/test/geometry/rect.js b/test/geometry/rect.js new file mode 100644 index 000000000..7882d47df --- /dev/null +++ b/test/geometry/rect.js @@ -0,0 +1,199 @@ +'use strict'; + +QUnit.module('rect', function() { + + QUnit.module('constructor', function() { + + QUnit.test('creates a new Rect object', function(assert) { + + assert.ok(g.rect() instanceof g.rect); + assert.ok(g.rect(1, 2, 3, 4) instanceof g.rect); + assert.equal(g.rect(1, 2, 3, 4).x, 1); + assert.equal(g.rect(1, 2, 3, 4).y, 2); + assert.equal(g.rect(1, 2, 3, 4).width, 3); + assert.equal(g.rect(1, 2, 3, 4).height, 4); + assert.ok(g.rect({ x: 1, y: 2, width: 3, height: 4 }).equals(g.rect(1, 2, 3, 4))); + assert.ok(g.rect(g.rect(1, 2, 3, 4)).equals(g.rect(1, 2, 3, 4))); + // default values + assert.ok(g.rect().equals(g.rect(0, 0, 0, 0))); + }); + }); + + QUnit.module('fromEllipse(ellipse)', function() { + + QUnit.test('creates a new Rect object', function(assert) { + + assert.ok(g.rect.fromEllipse(g.ellipse()) instanceof g.rect); + var e = g.ellipse(g.point(100, 50), 150, 70); + assert.ok(g.ellipse.fromRect(g.rect.fromEllipse(e)).equals(e)); + }); + }); + + QUnit.module('prototype', function() { + + QUnit.module('bbox()', function() { + + }); + + QUnit.module('bottomLeft()', function() { + + }); + + QUnit.module('bottomMiddle()', function() { + + QUnit.test('returns the bottom-middle point', function(assert) { + + assert.ok(g.rect(10, 20, 30, 40).bottomMiddle().equals(g.point(25, 60))); + }); + }); + + QUnit.module('center()', function() { + + }); + + QUnit.module('clone()', function() { + + }); + + QUnit.module('containsPoint(point)', function() { + + }); + + QUnit.module('containsRect(rect)', function() { + + QUnit.test('returns TRUE when rect is completely inside the other rect', function(assert) { + + assert.notOk(g.rect(50, 50, 100, 100).containsRect(g.rect(20, 20, 200, 200)), 'not inside when surround'); + assert.notOk(g.rect(50, 50, 100, 100).containsRect(g.rect(40, 40, 100, 100)), 'not inside when overlap left and top'); + assert.notOk(g.rect(50, 50, 100, 100).containsRect(g.rect(60, 60, 100, 40)), 'not inside when overlap left'); + assert.notOk(g.rect(50, 50, 100, 100).containsRect(g.rect(60, 60, 100, 100)), 'not inside when overlap right and bottom'); + assert.notOk(g.rect(50, 50, 100, 100).containsRect(g.rect(60, 60, 40, 100)), 'not inside when overlap bottom'); + assert.notOk(g.rect(50, 50, 100, 100).containsRect(g.rect(75, 75, 0, 0)), 'not inside when argument rect has zero width/height'); + assert.notOk(g.rect(50, 50, 0, 0).containsRect(g.rect(50, 50, 0, 0)), 'not inside when both rects have zero width/height'); + assert.ok(g.rect(50, 50, 100, 100).containsRect(g.rect(60, 60, 80, 80)), 'inside'); + assert.ok(g.rect(50, 50, 100, 100).containsRect(g.rect(50, 50, 100, 100)), 'inside when equal'); + }); + }); + + QUnit.module('corner()', function() { + + }); + + QUnit.module('equals(rect)', function() { + + QUnit.test('returns TRUE when the rect equals the other rect', function(assert) { + + assert.ok(g.rect(20, 20, 100, 100).equals(g.rect(20, 20, 100, 100)), 'equal'); + assert.ok(g.rect(20, 20, 100, 100).equals(g.rect(120, 120, -100, -100)), 'equal when target not normalized'); + assert.ok(g.rect(120, 120, -100, -100).equals(g.rect(20, 20, 100, 100)), 'equal when source not normalized'); + assert.notOk(g.rect(20, 20, 100, 100).equals(g.rect(10, 10, 110, 110)), 'not equal'); + }); + }); + + QUnit.module('intersect(rect)', function() { + + QUnit.test('returns TRUE when the rect intersects with the other rect', function(assert) { + + assert.ok(g.rect(20, 20, 100, 100).intersect(g.rect(40, 40, 20, 20)).equals(g.rect(40, 40, 20, 20)), 'inside'); + assert.ok(g.rect(20, 20, 100, 100).intersect(g.rect(0, 0, 100, 100)).equals(g.rect(20, 20, 80, 80)), 'overlap left and top'); + assert.ok(g.rect(20, 20, 100, 100).intersect(g.rect(40, 40, 100, 100)).equals(g.rect(40, 40, 80, 80)), 'overlap right and bottom'); + assert.equal(g.rect(20, 20, 100, 100).intersect(g.rect(140, 140, 20, 20)), null, 'no intersection'); + }); + }); + + QUnit.module('intersect(rect)', function() { + + }); + + QUnit.module('intersectionWithLineFromCenterToPoint(point, angle)', function() { + + }); + + QUnit.module('leftMiddle()', function() { + + QUnit.test('returns the left-middle point', function(assert) { + + assert.ok(g.rect(10, 20, 30, 40).leftMiddle().equals(g.point(10, 40))); + }); + }); + + QUnit.module('moveAndExpand(rect)', function() { + + }); + + QUnit.module('normalize()', function() { + + }); + + QUnit.module('origin()', function() { + + }); + + QUnit.module('pointNearestToPoint(point)', function() { + + }); + + QUnit.module('rightMiddle()', function() { + + QUnit.test('returns the right-middle point', function(assert) { + + assert.ok(g.rect(10, 20, 30, 40).rightMiddle().equals(g.point(40, 40))); + }); + }); + + QUnit.module('round(precision)', function() { + + }); + + QUnit.module('scale(sx, sy, origin)', function() { + + QUnit.test('correctly scales the rectangle', function(assert) { + + assert.equal(g.rect(20, 30, 40, 50).scale(2, 3).toString(), g.rect(40, 90, 80, 150).toString(), 'scale with no origin provided'); + assert.equal(g.rect(20, 30, 40, 50).scale(2, 3, g.point(20, 30)).toString(), g.rect(20, 30, 80, 150).toString(), 'scale with origin at rect origin'); + }); + }); + + QUnit.module('sideNearestToPoint(point)', function() { + + }); + + QUnit.module('snapToGrid(gx, gy)', function() { + + }); + + QUnit.module('topMiddle()', function() { + + QUnit.test('returns the top-middle point', function(assert) { + + assert.ok(g.rect(10, 20, 30, 40).topMiddle().equals(g.point(25, 20))); + }); + }); + + QUnit.module('topRight()', function() { + + }); + + QUnit.module('toJSON()', function() { + + QUnit.test('returns an object with the rectangle\'s coordinates and dimensions', function(assert) { + + assert.deepEqual(g.rect(20, 30, 40, 50).toJSON(), { x: 20, y: 30, width: 40, height: 50 }); + }); + }); + + QUnit.module('toString()', function() { + + }); + + QUnit.module('union(rect)', function() { + + QUnit.test('returns a new rect that represents the union of the two rects', function(assert) { + + assert.equal(g.rect(20, 20, 50, 50).union(g.rect(100, 100, 50, 50)).toString(), g.rect(20, 20, 130, 130).toString(), 'union of distant rectangles'); + assert.equal(g.rect(20, 20, 150, 150).union(g.rect(50, 50, 20, 20)).toString(), g.rect(20, 20, 150, 150).toString(), 'union of embedded rectangles'); + assert.equal(g.rect(20, 20, 150, 150).union(g.rect(50, 50, 200, 200)).toString(), g.rect(20, 20, 230, 230).toString(), 'union of intersecting rectangles'); + }); + }); + }); +}); diff --git a/test/geometry/requirejs.html b/test/geometry/requirejs.html index a958307be..5bca9150c 100644 --- a/test/geometry/requirejs.html +++ b/test/geometry/requirejs.html @@ -1,17 +1,16 @@ - + + Geometry | Tests | require.js + + + - Geometry | require.js | test suite - - - +
    +
    -
    -
    + + - - - - + diff --git a/test/geometry/scale.js b/test/geometry/scale.js new file mode 100644 index 000000000..5e70dd9f0 --- /dev/null +++ b/test/geometry/scale.js @@ -0,0 +1,13 @@ +'use strict'; + +QUnit.module('scale', function() { + + QUnit.module('linear(domain, range, value)', function() { + + QUnit.test('returns the value from the domain interval scaled to the range interval', function(assert) { + + assert.equal(g.scale.linear([.5, 1], [50, 150], .75), 100, 'linear scale up'); + assert.equal(g.scale.linear([50, 150], [.5, 1], 100), .75, 'linear scale down'); + }); + }); +}); diff --git a/test/geometry/snapToGrid.js b/test/geometry/snapToGrid.js new file mode 100644 index 000000000..71127bbe6 --- /dev/null +++ b/test/geometry/snapToGrid.js @@ -0,0 +1,23 @@ +'use strict'; + +QUnit.module('snapToGrid(value, gridSize)', function() { + + QUnit.test('returns value rounded to the nearest increment of the given grid size', function(assert) { + + var expected = [ + { value: 9, gridSize: 10, newValue: 10 }, + { value: 17, gridSize: 10, newValue: 20 }, + { value: 4, gridSize: 10, newValue: 0 }, + { value: 3, gridSize: 2, newValue: 4 } + ]; + + var value, gridSize, newValue; + + for (var i = 0; i < expected.length; i++) { + value = expected[i].value; + gridSize = expected[i].gridSize; + newValue = expected[i].newValue; + assert.equal(g.snapToGrid(value, gridSize), newValue, 'value = ' + value + ', gridSize = ' + gridSize); + } + }); +}); diff --git a/test/geometry/toDeg.js b/test/geometry/toDeg.js new file mode 100644 index 000000000..357902200 --- /dev/null +++ b/test/geometry/toDeg.js @@ -0,0 +1,29 @@ +'use strict'; + +QUnit.module('toDeg(radians)', function() { + + QUnit.test('should be a function', function(assert) { + + assert.equal(typeof g.toDeg, 'function'); + }); + + QUnit.test('should correctly convert the angle (in radians) to degrees', function(assert) { + + var values = [ + // Values have a maximum precision of 3 decimal places. + { radians: 1, degrees: 57.296 }, + { radians: 2, degrees: 114.592 }, + { radians: 3, degrees: 171.887 }, + { radians: 3.2, degrees: 183.346 }, + { radians: 5, degrees: 286.479 } + ]; + + var degrees, radians; + + for (var i = 0; i < values.length; i++) { + degrees = values[i].degrees; + radians = values[i].radians; + assert.equal(g.toDeg(radians).toFixed(3), degrees, 'radians = ' + radians); + } + }); +}); diff --git a/test/geometry/toRad.js b/test/geometry/toRad.js new file mode 100644 index 000000000..ac30193b7 --- /dev/null +++ b/test/geometry/toRad.js @@ -0,0 +1,29 @@ +'use strict'; + +QUnit.module('toRad(degrees)', function() { + + QUnit.test('should be a function', function(assert) { + + assert.equal(typeof g.toRad, 'function'); + }); + + QUnit.test('should correctly convert the angle (in degrees) to radians', function(assert) { + + var values = [ + // Values have a maximum precision of 3 decimal places. + { radians: 0.785, degrees: 45 }, + { radians: 1.047, degrees: 60 }, + { radians: 1.571, degrees: 90 }, + { radians: 2.793, degrees: 160 }, + { radians: 4.712, degrees: 270 } + ]; + + var degrees, radians; + + for (var i = 0; i < values.length; i++) { + degrees = values[i].degrees; + radians = values[i].radians; + assert.equal(g.toRad(degrees).toFixed(3), radians, 'degrees = ' + degrees); + } + }); +});