UNPKG

snapsvg

Version:
774 lines (770 loc) 25.4 kB
// 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; } }; }()); });