snapsvg
Version:
JavaScript Vector Library
774 lines (770 loc) • 25.4 kB
JavaScript
// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
Snap.plugin(function (Snap, Element, Paper, glob, Fragment) {
var proto = Paper.prototype,
is = Snap.is;
/*\
* Paper.rect
[ method ]
*
* Draws a rectangle
**
- x (number) x coordinate of the top left corner
- y (number) y coordinate of the top left corner
- width (number) width
- height (number) height
- rx (number) #optional horizontal radius for rounded corners, default is 0
- ry (number) #optional vertical radius for rounded corners, default is rx or 0
= (object) the `rect` element
**
> Usage
| // regular rectangle
| var c = paper.rect(10, 10, 50, 50);
| // rectangle with rounded corners
| var c = paper.rect(40, 40, 50, 50, 10);
\*/
proto.rect = function (x, y, w, h, rx, ry) {
var attr;
if (ry == null) {
ry = rx;
}
if (is(x, "object") && x == "[object Object]") {
attr = x;
} else if (x != null) {
attr = {
x: x,
y: y,
width: w,
height: h
};
if (rx != null) {
attr.rx = rx;
attr.ry = ry;
}
}
return this.el("rect", attr);
};
/*\
* Paper.circle
[ method ]
**
* Draws a circle
**
- x (number) x coordinate of the centre
- y (number) y coordinate of the centre
- r (number) radius
= (object) the `circle` element
**
> Usage
| var c = paper.circle(50, 50, 40);
\*/
proto.circle = function (cx, cy, r) {
var attr;
if (is(cx, "object") && cx == "[object Object]") {
attr = cx;
} else if (cx != null) {
attr = {
cx: cx,
cy: cy,
r: r
};
}
return this.el("circle", attr);
};
var preload = (function () {
function onerror() {
this.parentNode.removeChild(this);
}
return function (src, f) {
var img = glob.doc.createElement("img"),
body = glob.doc.body;
img.style.cssText = "position:absolute;left:-9999em;top:-9999em";
img.onload = function () {
f.call(img);
img.onload = img.onerror = null;
body.removeChild(img);
};
img.onerror = onerror;
body.appendChild(img);
img.src = src;
};
}());
/*\
* Paper.image
[ method ]
**
* Places an image on the surface
**
- src (string) URI of the source image
- x (number) x offset position
- y (number) y offset position
- width (number) width of the image
- height (number) height of the image
= (object) the `image` element
* or
= (object) Snap element object with type `image`
**
> Usage
| var c = paper.image("apple.png", 10, 10, 80, 80);
\*/
proto.image = function (src, x, y, width, height) {
var el = this.el("image");
if (is(src, "object") && "src" in src) {
el.attr(src);
} else if (src != null) {
var set = {
"xlink:href": src,
preserveAspectRatio: "none"
};
if (x != null && y != null) {
set.x = x;
set.y = y;
}
if (width != null && height != null) {
set.width = width;
set.height = height;
} else {
preload(src, function () {
Snap._.$(el.node, {
width: this.offsetWidth,
height: this.offsetHeight
});
});
}
Snap._.$(el.node, set);
}
return el;
};
/*\
* Paper.ellipse
[ method ]
**
* Draws an ellipse
**
- x (number) x coordinate of the centre
- y (number) y coordinate of the centre
- rx (number) horizontal radius
- ry (number) vertical radius
= (object) the `ellipse` element
**
> Usage
| var c = paper.ellipse(50, 50, 40, 20);
\*/
proto.ellipse = function (cx, cy, rx, ry) {
var attr;
if (is(cx, "object") && cx == "[object Object]") {
attr = cx;
} else if (cx != null) {
attr ={
cx: cx,
cy: cy,
rx: rx,
ry: ry
};
}
return this.el("ellipse", attr);
};
// SIERRA Paper.path(): Unclear from the link what a Catmull-Rom curveto is, and why it would make life any easier.
/*\
* Paper.path
[ method ]
**
* Creates a `<path>` element using the given string as the path's definition
- pathString (string) #optional path string in SVG format
* Path string consists of one-letter commands, followed by comma seprarated arguments in numerical form. Example:
| "M10,20L30,40"
* This example features two commands: `M`, with arguments `(10, 20)` and `L` with arguments `(30, 40)`. Uppercase letter commands express coordinates in absolute terms, while lowercase commands express them in relative terms from the most recently declared coordinates.
*
# <p>Here is short list of commands available, for more details see <a href="http://www.w3.org/TR/SVG/paths.html#PathData" title="Details of a path's data attribute's format are described in the SVG specification.">SVG path string format</a> or <a href="https://developer.mozilla.org/en/SVG/Tutorial/Paths">article about path strings at MDN</a>.</p>
# <table><thead><tr><th>Command</th><th>Name</th><th>Parameters</th></tr></thead><tbody>
# <tr><td>M</td><td>moveto</td><td>(x y)+</td></tr>
# <tr><td>Z</td><td>closepath</td><td>(none)</td></tr>
# <tr><td>L</td><td>lineto</td><td>(x y)+</td></tr>
# <tr><td>H</td><td>horizontal lineto</td><td>x+</td></tr>
# <tr><td>V</td><td>vertical lineto</td><td>y+</td></tr>
# <tr><td>C</td><td>curveto</td><td>(x1 y1 x2 y2 x y)+</td></tr>
# <tr><td>S</td><td>smooth curveto</td><td>(x2 y2 x y)+</td></tr>
# <tr><td>Q</td><td>quadratic Bézier curveto</td><td>(x1 y1 x y)+</td></tr>
# <tr><td>T</td><td>smooth quadratic Bézier curveto</td><td>(x y)+</td></tr>
# <tr><td>A</td><td>elliptical arc</td><td>(rx ry x-axis-rotation large-arc-flag sweep-flag x y)+</td></tr>
# <tr><td>R</td><td><a href="http://en.wikipedia.org/wiki/Catmull–Rom_spline#Catmull.E2.80.93Rom_spline">Catmull-Rom curveto</a>*</td><td>x1 y1 (x y)+</td></tr></tbody></table>
* * _Catmull-Rom curveto_ is a not standard SVG command and added to make life easier.
* Note: there is a special case when a path consists of only three commands: `M10,10R…z`. In this case the path connects back to its starting point.
> Usage
| var c = paper.path("M10 10L90 90");
| // draw a diagonal line:
| // move to 10,10, line to 90,90
\*/
proto.path = function (d) {
var attr;
if (is(d, "object") && !is(d, "array")) {
attr = d;
} else if (d) {
attr = {d: d};
}
return this.el("path", attr);
};
/*\
* Paper.g
[ method ]
**
* Creates a group element
**
- varargs (…) #optional elements to nest within the group
= (object) the `g` element
**
> Usage
| var c1 = paper.circle(),
| c2 = paper.rect(),
| g = paper.g(c2, c1); // note that the order of elements is different
* or
| var c1 = paper.circle(),
| c2 = paper.rect(),
| g = paper.g();
| g.add(c2, c1);
\*/
/*\
* Paper.group
[ method ]
**
* See @Paper.g
\*/
proto.group = proto.g = function (first) {
var attr,
el = this.el("g");
if (arguments.length == 1 && first && !first.type) {
el.attr(first);
} else if (arguments.length) {
el.add(Array.prototype.slice.call(arguments, 0));
}
return el;
};
/*\
* Paper.svg
[ method ]
**
* Creates a nested SVG element.
- x (number) @optional X of the element
- y (number) @optional Y of the element
- width (number) @optional width of the element
- height (number) @optional height of the element
- vbx (number) @optional viewbox X
- vby (number) @optional viewbox Y
- vbw (number) @optional viewbox width
- vbh (number) @optional viewbox height
**
= (object) the `svg` element
**
\*/
proto.svg = function (x, y, width, height, vbx, vby, vbw, vbh) {
var attrs = {};
if (is(x, "object") && y == null) {
attrs = x;
} else {
if (x != null) {
attrs.x = x;
}
if (y != null) {
attrs.y = y;
}
if (width != null) {
attrs.width = width;
}
if (height != null) {
attrs.height = height;
}
if (vbx != null && vby != null && vbw != null && vbh != null) {
attrs.viewBox = [vbx, vby, vbw, vbh];
}
}
return this.el("svg", attrs);
};
/*\
* Paper.mask
[ method ]
**
* Equivalent in behaviour to @Paper.g, except it’s a mask.
**
= (object) the `mask` element
**
\*/
proto.mask = function (first) {
var attr,
el = this.el("mask");
if (arguments.length == 1 && first && !first.type) {
el.attr(first);
} else if (arguments.length) {
el.add(Array.prototype.slice.call(arguments, 0));
}
return el;
};
/*\
* Paper.ptrn
[ method ]
**
* Equivalent in behaviour to @Paper.g, except it’s a pattern.
- x (number) @optional X of the element
- y (number) @optional Y of the element
- width (number) @optional width of the element
- height (number) @optional height of the element
- vbx (number) @optional viewbox X
- vby (number) @optional viewbox Y
- vbw (number) @optional viewbox width
- vbh (number) @optional viewbox height
**
= (object) the `pattern` element
**
\*/
proto.ptrn = function (x, y, width, height, vx, vy, vw, vh) {
if (is(x, "object")) {
var attr = x;
} else {
attr = {patternUnits: "userSpaceOnUse"};
if (x) {
attr.x = x;
}
if (y) {
attr.y = y;
}
if (width != null) {
attr.width = width;
}
if (height != null) {
attr.height = height;
}
if (vx != null && vy != null && vw != null && vh != null) {
attr.viewBox = [vx, vy, vw, vh];
} else {
attr.viewBox = [x || 0, y || 0, width || 0, height || 0];
}
}
return this.el("pattern", attr);
};
/*\
* Paper.use
[ method ]
**
* Creates a <use> element.
- id (string) @optional id of element to link
* or
- id (Element) @optional element to link
**
= (object) the `use` element
**
\*/
proto.use = function (id) {
if (id != null) {
if (id instanceof Element) {
if (!id.attr("id")) {
id.attr({id: Snap._.id(id)});
}
id = id.attr("id");
}
if (String(id).charAt() == "#") {
id = id.substring(1);
}
return this.el("use", {"xlink:href": "#" + id});
} else {
return Element.prototype.use.call(this);
}
};
/*\
* Paper.symbol
[ method ]
**
* Creates a <symbol> element.
- vbx (number) @optional viewbox X
- vby (number) @optional viewbox Y
- vbw (number) @optional viewbox width
- vbh (number) @optional viewbox height
= (object) the `symbol` element
**
\*/
proto.symbol = function (vx, vy, vw, vh) {
var attr = {};
if (vx != null && vy != null && vw != null && vh != null) {
attr.viewBox = [vx, vy, vw, vh];
}
return this.el("symbol", attr);
};
/*\
* Paper.text
[ method ]
**
* Draws a text string
**
- x (number) x coordinate position
- y (number) y coordinate position
- text (string|array) The text string to draw or array of strings to nest within separate `<tspan>` elements
= (object) the `text` element
**
> Usage
| var t1 = paper.text(50, 50, "Snap");
| var t2 = paper.text(50, 50, ["S","n","a","p"]);
| // Text path usage
| t1.attr({textpath: "M10,10L100,100"});
| // or
| var pth = paper.path("M10,10L100,100");
| t1.attr({textpath: pth});
\*/
proto.text = function (x, y, text) {
var attr = {};
if (is(x, "object")) {
attr = x;
} else if (x != null) {
attr = {
x: x,
y: y,
text: text || ""
};
}
return this.el("text", attr);
};
/*\
* Paper.line
[ method ]
**
* Draws a line
**
- x1 (number) x coordinate position of the start
- y1 (number) y coordinate position of the start
- x2 (number) x coordinate position of the end
- y2 (number) y coordinate position of the end
= (object) the `line` element
**
> Usage
| var t1 = paper.line(50, 50, 100, 100);
\*/
proto.line = function (x1, y1, x2, y2) {
var attr = {};
if (is(x1, "object")) {
attr = x1;
} else if (x1 != null) {
attr = {
x1: x1,
x2: x2,
y1: y1,
y2: y2
};
}
return this.el("line", attr);
};
/*\
* Paper.polyline
[ method ]
**
* Draws a polyline
**
- points (array) array of points
* or
- varargs (…) points
= (object) the `polyline` element
**
> Usage
| var p1 = paper.polyline([10, 10, 100, 100]);
| var p2 = paper.polyline(10, 10, 100, 100);
\*/
proto.polyline = function (points) {
if (arguments.length > 1) {
points = Array.prototype.slice.call(arguments, 0);
}
var attr = {};
if (is(points, "object") && !is(points, "array")) {
attr = points;
} else if (points != null) {
attr = {points: points};
}
return this.el("polyline", attr);
};
/*\
* Paper.polygon
[ method ]
**
* Draws a polygon. See @Paper.polyline
\*/
proto.polygon = function (points) {
if (arguments.length > 1) {
points = Array.prototype.slice.call(arguments, 0);
}
var attr = {};
if (is(points, "object") && !is(points, "array")) {
attr = points;
} else if (points != null) {
attr = {points: points};
}
return this.el("polygon", attr);
};
// gradients
(function () {
var $ = Snap._.$;
// gradients' helpers
/*\
* Element.stops
[ method ]
**
* Only for gradients!
* Returns array of gradient stops elements.
= (array) the stops array.
\*/
function Gstops() {
return this.selectAll("stop");
}
/*\
* Element.addStop
[ method ]
**
* Only for gradients!
* Adds another stop to the gradient.
- color (string) stops color
- offset (number) stops offset 0..100
= (object) gradient element
\*/
function GaddStop(color, offset) {
var stop = $("stop"),
attr = {
offset: +offset + "%"
};
color = Snap.color(color);
attr["stop-color"] = color.hex;
if (color.opacity < 1) {
attr["stop-opacity"] = color.opacity;
}
$(stop, attr);
var stops = this.stops(),
inserted;
for (var i = 0; i < stops.length; i++) {
var stopOffset = parseFloat(stops[i].attr("offset"));
if (stopOffset > offset) {
this.node.insertBefore(stop, stops[i].node);
inserted = true;
break;
}
}
if (!inserted) {
this.node.appendChild(stop);
}
return this;
}
function GgetBBox() {
if (this.type == "linearGradient") {
var x1 = $(this.node, "x1") || 0,
x2 = $(this.node, "x2") || 1,
y1 = $(this.node, "y1") || 0,
y2 = $(this.node, "y2") || 0;
return Snap._.box(x1, y1, math.abs(x2 - x1), math.abs(y2 - y1));
} else {
var cx = this.node.cx || .5,
cy = this.node.cy || .5,
r = this.node.r || 0;
return Snap._.box(cx - r, cy - r, r * 2, r * 2);
}
}
/*\
* Element.setStops
[ method ]
**
* Only for gradients!
* Updates stops of the gradient based on passed gradient descriptor. See @Ppaer.gradient
- str (string) gradient descriptor part after `()`.
= (object) gradient element
| var g = paper.gradient("l(0, 0, 1, 1)#000-#f00-#fff");
| g.setStops("#fff-#000-#f00-#fc0");
\*/
function GsetStops(str) {
var grad = str,
stops = this.stops();
if (typeof str == "string") {
grad = eve("snap.util.grad.parse", null, "l(0,0,0,1)" + str).firstDefined().stops;
}
if (!Snap.is(grad, "array")) {
return;
}
for (var i = 0; i < stops.length; i++) {
if (grad[i]) {
var color = Snap.color(grad[i].color),
attr = {"offset": grad[i].offset + "%"};
attr["stop-color"] = color.hex;
if (color.opacity < 1) {
attr["stop-opacity"] = color.opacity;
}
stops[i].attr(attr);
} else {
stops[i].remove();
}
}
for (i = stops.length; i < grad.length; i++) {
this.addStop(grad[i].color, grad[i].offset);
}
return this;
}
function gradient(defs, str) {
var grad = eve("snap.util.grad.parse", null, str).firstDefined(),
el;
if (!grad) {
return null;
}
grad.params.unshift(defs);
if (grad.type.toLowerCase() == "l") {
el = gradientLinear.apply(0, grad.params);
} else {
el = gradientRadial.apply(0, grad.params);
}
if (grad.type != grad.type.toLowerCase()) {
$(el.node, {
gradientUnits: "userSpaceOnUse"
});
}
var stops = grad.stops,
len = stops.length;
for (var i = 0; i < len; i++) {
var stop = stops[i];
el.addStop(stop.color, stop.offset);
}
return el;
}
function gradientLinear(defs, x1, y1, x2, y2) {
var el = Snap._.make("linearGradient", defs);
el.stops = Gstops;
el.addStop = GaddStop;
el.getBBox = GgetBBox;
el.setStops = GsetStops;
if (x1 != null) {
$(el.node, {
x1: x1,
y1: y1,
x2: x2,
y2: y2
});
}
return el;
}
function gradientRadial(defs, cx, cy, r, fx, fy) {
var el = Snap._.make("radialGradient", defs);
el.stops = Gstops;
el.addStop = GaddStop;
el.getBBox = GgetBBox;
if (cx != null) {
$(el.node, {
cx: cx,
cy: cy,
r: r
});
}
if (fx != null && fy != null) {
$(el.node, {
fx: fx,
fy: fy
});
}
return el;
}
/*\
* Paper.gradient
[ method ]
**
* Creates a gradient element
**
- gradient (string) gradient descriptor
> Gradient Descriptor
* The gradient descriptor is an expression formatted as
* follows: `<type>(<coords>)<colors>`. The `<type>` can be
* either linear or radial. The uppercase `L` or `R` letters
* indicate absolute coordinates offset from the SVG surface.
* Lowercase `l` or `r` letters indicate coordinates
* calculated relative to the element to which the gradient is
* applied. Coordinates specify a linear gradient vector as
* `x1`, `y1`, `x2`, `y2`, or a radial gradient as `cx`, `cy`,
* `r` and optional `fx`, `fy` specifying a focal point away
* from the center of the circle. Specify `<colors>` as a list
* of dash-separated CSS color values. Each color may be
* followed by a custom offset value, separated with a colon
* character.
> Examples
* Linear gradient, relative from top-left corner to bottom-right
* corner, from black through red to white:
| var g = paper.gradient("l(0, 0, 1, 1)#000-#f00-#fff");
* Linear gradient, absolute from (0, 0) to (100, 100), from black
* through red at 25% to white:
| var g = paper.gradient("L(0, 0, 100, 100)#000-#f00:25-#fff");
* Radial gradient, relative from the center of the element with radius
* half the width, from black to white:
| var g = paper.gradient("r(0.5, 0.5, 0.5)#000-#fff");
* To apply the gradient:
| paper.circle(50, 50, 40).attr({
| fill: g
| });
= (object) the `gradient` element
\*/
proto.gradient = function (str) {
return gradient(this.defs, str);
};
proto.gradientLinear = function (x1, y1, x2, y2) {
return gradientLinear(this.defs, x1, y1, x2, y2);
};
proto.gradientRadial = function (cx, cy, r, fx, fy) {
return gradientRadial(this.defs, cx, cy, r, fx, fy);
};
/*\
* Paper.toString
[ method ]
**
* Returns SVG code for the @Paper
= (string) SVG code for the @Paper
\*/
proto.toString = function () {
var doc = this.node.ownerDocument,
f = doc.createDocumentFragment(),
d = doc.createElement("div"),
svg = this.node.cloneNode(true),
res;
f.appendChild(d);
d.appendChild(svg);
Snap._.$(svg, {xmlns: "http://www.w3.org/2000/svg"});
res = d.innerHTML;
f.removeChild(f.firstChild);
return res;
};
/*\
* Paper.toDataURL
[ method ]
**
* Returns SVG code for the @Paper as Data URI string.
= (string) Data URI string
\*/
proto.toDataURL = function () {
if (window && window.btoa) {
return "data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(this)));
}
};
/*\
* Paper.clear
[ method ]
**
* Removes all child nodes of the paper, except <defs>.
\*/
proto.clear = function () {
var node = this.node.firstChild,
next;
while (node) {
next = node.nextSibling;
if (node.tagName != "defs") {
node.parentNode.removeChild(node);
} else {
proto.clear.call({node: node});
}
node = next;
}
};
}());
});