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 @@ + + +
+ + + +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.
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.
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.
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.
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.
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.
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.
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:
+
+
+ nodeSep a number of pixels representing the separation between adjacent nodes in the same rank
+ edgeSep a number of pixels representing the separation between adjacent edges in the same rank
+ rankSep a number of pixels representing the separation between ranks
+ rankDir direction of the layout (one of "TB"
(top-to-bottom) / "BT"
(bottom-to-top) / "LR"
(left-to-right) / "RL"
(right-to-left))
+ marginX number of pixels to use as a margin around the left and right of the graph.
+ marginY number of pixels to use as a margin around the top and bottom of the graph.
+ resizeClusters set 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 specific
+ The 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);
+ }
+ });
+});