1850 lines
53 KiB
JavaScript
1850 lines
53 KiB
JavaScript
// Copyright (c) 2013 - 2015 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.
|
|
|
|
var Snap = (function(root) {
|
|
Snap.version = "0.4.1";
|
|
/*\
|
|
* Snap
|
|
[ method ]
|
|
**
|
|
* Creates a drawing surface or wraps existing SVG element.
|
|
**
|
|
- width (number|string) width of surface
|
|
- height (number|string) height of surface
|
|
* or
|
|
- DOM (SVGElement) element to be wrapped into Snap structure
|
|
* or
|
|
- array (array) array of elements (will return set of elements)
|
|
* or
|
|
- query (string) CSS query selector
|
|
= (object) @Element
|
|
\*/
|
|
function Snap(w, h) {
|
|
if (w) {
|
|
if (w.nodeType) {
|
|
return wrap(w);
|
|
}
|
|
if (is(w, "array") && Snap.set) {
|
|
return Snap.set.apply(Snap, w);
|
|
}
|
|
if (w instanceof Element) {
|
|
return w;
|
|
}
|
|
if (h == null) {
|
|
w = glob.doc.querySelector(String(w));
|
|
return wrap(w);
|
|
}
|
|
}
|
|
w = w == null ? "100%" : w;
|
|
h = h == null ? "100%" : h;
|
|
return new Paper(w, h);
|
|
}
|
|
Snap.toString = function () {
|
|
return "Snap v" + this.version;
|
|
};
|
|
Snap._ = {};
|
|
var glob = {
|
|
win: root.window,
|
|
doc: root.window.document
|
|
};
|
|
Snap._.glob = glob;
|
|
var has = "hasOwnProperty",
|
|
Str = String,
|
|
toFloat = parseFloat,
|
|
toInt = parseInt,
|
|
math = Math,
|
|
mmax = math.max,
|
|
mmin = math.min,
|
|
abs = math.abs,
|
|
pow = math.pow,
|
|
PI = math.PI,
|
|
round = math.round,
|
|
E = "",
|
|
S = " ",
|
|
objectToString = Object.prototype.toString,
|
|
ISURL = /^url\(['"]?([^\)]+?)['"]?\)$/i,
|
|
colourRegExp = /^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgba?\(\s*([\d\.]+%?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+%?(?:\s*,\s*[\d\.]+%?)?)\s*\)|hsba?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?%?)\s*\)|hsla?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?%?)\s*\))\s*$/i,
|
|
bezierrg = /^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,
|
|
reURLValue = /^url\(#?([^)]+)\)$/,
|
|
separator = Snap._.separator = /[,\s]+/,
|
|
whitespace = /[\s]/g,
|
|
commaSpaces = /[\s]*,[\s]*/,
|
|
hsrg = {hs: 1, rg: 1},
|
|
pathCommand = /([a-z])[\s,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\s]*,?[\s]*)+)/ig,
|
|
tCommand = /([rstm])[\s,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\s]*,?[\s]*)+)/ig,
|
|
pathValues = /(-?\d*\.?\d*(?:e[\-+]?\\d+)?)[\s]*,?[\s]*/ig,
|
|
idgen = 0,
|
|
idprefix = "S" + (+new Date).toString(36),
|
|
ID = function (el) {
|
|
return (el && el.type ? el.type : E) + idprefix + (idgen++).toString(36);
|
|
},
|
|
xlink = "http://www.w3.org/1999/xlink",
|
|
xmlns = "http://www.w3.org/2000/svg",
|
|
hub = {},
|
|
URL = Snap.url = function (url) {
|
|
return "url('#" + url + "')";
|
|
};
|
|
|
|
function $(el, attr) {
|
|
if (attr) {
|
|
if (el == "#text") {
|
|
el = glob.doc.createTextNode(attr.text || attr["#text"] || "");
|
|
}
|
|
if (el == "#comment") {
|
|
el = glob.doc.createComment(attr.text || attr["#text"] || "");
|
|
}
|
|
if (typeof el == "string") {
|
|
el = $(el);
|
|
}
|
|
if (typeof attr == "string") {
|
|
if (el.nodeType == 1) {
|
|
if (attr.substring(0, 6) == "xlink:") {
|
|
return el.getAttributeNS(xlink, attr.substring(6));
|
|
}
|
|
if (attr.substring(0, 4) == "xml:") {
|
|
return el.getAttributeNS(xmlns, attr.substring(4));
|
|
}
|
|
return el.getAttribute(attr);
|
|
} else if (attr == "text") {
|
|
return el.nodeValue;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
if (el.nodeType == 1) {
|
|
for (var key in attr) if (attr[has](key)) {
|
|
var val = Str(attr[key]);
|
|
if (val) {
|
|
if (key.substring(0, 6) == "xlink:") {
|
|
el.setAttributeNS(xlink, key.substring(6), val);
|
|
} else if (key.substring(0, 4) == "xml:") {
|
|
el.setAttributeNS(xmlns, key.substring(4), val);
|
|
} else {
|
|
el.setAttribute(key, val);
|
|
}
|
|
} else {
|
|
el.removeAttribute(key);
|
|
}
|
|
}
|
|
} else if ("text" in attr) {
|
|
el.nodeValue = attr.text;
|
|
}
|
|
} else {
|
|
el = glob.doc.createElementNS(xmlns, el);
|
|
}
|
|
return el;
|
|
}
|
|
Snap._.$ = $;
|
|
Snap._.id = ID;
|
|
function getAttrs(el) {
|
|
var attrs = el.attributes,
|
|
name,
|
|
out = {};
|
|
for (var i = 0; i < attrs.length; i++) {
|
|
if (attrs[i].namespaceURI == xlink) {
|
|
name = "xlink:";
|
|
} else {
|
|
name = "";
|
|
}
|
|
name += attrs[i].name;
|
|
out[name] = attrs[i].textContent;
|
|
}
|
|
return out;
|
|
}
|
|
function is(o, type) {
|
|
type = Str.prototype.toLowerCase.call(type);
|
|
if (type == "finite") {
|
|
return isFinite(o);
|
|
}
|
|
if (type == "array" &&
|
|
(o instanceof Array || Array.isArray && Array.isArray(o))) {
|
|
return true;
|
|
}
|
|
return (type == "null" && o === null) ||
|
|
(type == typeof o && o !== null) ||
|
|
(type == "object" && o === Object(o)) ||
|
|
objectToString.call(o).slice(8, -1).toLowerCase() == type;
|
|
}
|
|
/*\
|
|
* Snap.format
|
|
[ method ]
|
|
**
|
|
* Replaces construction of type `{<name>}` to the corresponding argument
|
|
**
|
|
- token (string) string to format
|
|
- json (object) object which properties are used as a replacement
|
|
= (string) formatted string
|
|
> Usage
|
|
| // this draws a rectangular shape equivalent to "M10,20h40v50h-40z"
|
|
| paper.path(Snap.format("M{x},{y}h{dim.width}v{dim.height}h{dim['negative width']}z", {
|
|
| x: 10,
|
|
| y: 20,
|
|
| dim: {
|
|
| width: 40,
|
|
| height: 50,
|
|
| "negative width": -40
|
|
| }
|
|
| }));
|
|
\*/
|
|
Snap.format = (function () {
|
|
var tokenRegex = /\{([^\}]+)\}/g,
|
|
objNotationRegex = /(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g, // matches .xxxxx or ["xxxxx"] to run over object properties
|
|
replacer = function (all, key, obj) {
|
|
var res = obj;
|
|
key.replace(objNotationRegex, function (all, name, quote, quotedName, isFunc) {
|
|
name = name || quotedName;
|
|
if (res) {
|
|
if (name in res) {
|
|
res = res[name];
|
|
}
|
|
typeof res == "function" && isFunc && (res = res());
|
|
}
|
|
});
|
|
res = (res == null || res == obj ? all : res) + "";
|
|
return res;
|
|
};
|
|
return function (str, obj) {
|
|
return Str(str).replace(tokenRegex, function (all, key) {
|
|
return replacer(all, key, obj);
|
|
});
|
|
};
|
|
})();
|
|
function clone(obj) {
|
|
if (typeof obj == "function" || Object(obj) !== obj) {
|
|
return obj;
|
|
}
|
|
var res = new obj.constructor;
|
|
for (var key in obj) if (obj[has](key)) {
|
|
res[key] = clone(obj[key]);
|
|
}
|
|
return res;
|
|
}
|
|
Snap._.clone = clone;
|
|
function repush(array, item) {
|
|
for (var i = 0, ii = array.length; i < ii; i++) if (array[i] === item) {
|
|
return array.push(array.splice(i, 1)[0]);
|
|
}
|
|
}
|
|
function cacher(f, scope, postprocessor) {
|
|
function newf() {
|
|
var arg = Array.prototype.slice.call(arguments, 0),
|
|
args = arg.join("\u2400"),
|
|
cache = newf.cache = newf.cache || {},
|
|
count = newf.count = newf.count || [];
|
|
if (cache[has](args)) {
|
|
repush(count, args);
|
|
return postprocessor ? postprocessor(cache[args]) : cache[args];
|
|
}
|
|
count.length >= 1e3 && delete cache[count.shift()];
|
|
count.push(args);
|
|
cache[args] = f.apply(scope, arg);
|
|
return postprocessor ? postprocessor(cache[args]) : cache[args];
|
|
}
|
|
return newf;
|
|
}
|
|
Snap._.cacher = cacher;
|
|
function angle(x1, y1, x2, y2, x3, y3) {
|
|
if (x3 == null) {
|
|
var x = x1 - x2,
|
|
y = y1 - y2;
|
|
if (!x && !y) {
|
|
return 0;
|
|
}
|
|
return (180 + math.atan2(-y, -x) * 180 / PI + 360) % 360;
|
|
} else {
|
|
return angle(x1, y1, x3, y3) - angle(x2, y2, x3, y3);
|
|
}
|
|
}
|
|
function rad(deg) {
|
|
return deg % 360 * PI / 180;
|
|
}
|
|
function deg(rad) {
|
|
return rad * 180 / PI % 360;
|
|
}
|
|
function x_y() {
|
|
return this.x + S + this.y;
|
|
}
|
|
function x_y_w_h() {
|
|
return this.x + S + this.y + S + this.width + " \xd7 " + this.height;
|
|
}
|
|
|
|
/*\
|
|
* Snap.rad
|
|
[ method ]
|
|
**
|
|
* Transform angle to radians
|
|
- deg (number) angle in degrees
|
|
= (number) angle in radians
|
|
\*/
|
|
Snap.rad = rad;
|
|
/*\
|
|
* Snap.deg
|
|
[ method ]
|
|
**
|
|
* Transform angle to degrees
|
|
- rad (number) angle in radians
|
|
= (number) angle in degrees
|
|
\*/
|
|
Snap.deg = deg;
|
|
/*\
|
|
* Snap.sin
|
|
[ method ]
|
|
**
|
|
* Equivalent to `Math.sin()` only works with degrees, not radians.
|
|
- angle (number) angle in degrees
|
|
= (number) sin
|
|
\*/
|
|
Snap.sin = function (angle) {
|
|
return math.sin(Snap.rad(angle));
|
|
};
|
|
/*\
|
|
* Snap.tan
|
|
[ method ]
|
|
**
|
|
* Equivalent to `Math.tan()` only works with degrees, not radians.
|
|
- angle (number) angle in degrees
|
|
= (number) tan
|
|
\*/
|
|
Snap.tan = function (angle) {
|
|
return math.tan(Snap.rad(angle));
|
|
};
|
|
/*\
|
|
* Snap.cos
|
|
[ method ]
|
|
**
|
|
* Equivalent to `Math.cos()` only works with degrees, not radians.
|
|
- angle (number) angle in degrees
|
|
= (number) cos
|
|
\*/
|
|
Snap.cos = function (angle) {
|
|
return math.cos(Snap.rad(angle));
|
|
};
|
|
/*\
|
|
* Snap.asin
|
|
[ method ]
|
|
**
|
|
* Equivalent to `Math.asin()` only works with degrees, not radians.
|
|
- num (number) value
|
|
= (number) asin in degrees
|
|
\*/
|
|
Snap.asin = function (num) {
|
|
return Snap.deg(math.asin(num));
|
|
};
|
|
/*\
|
|
* Snap.acos
|
|
[ method ]
|
|
**
|
|
* Equivalent to `Math.acos()` only works with degrees, not radians.
|
|
- num (number) value
|
|
= (number) acos in degrees
|
|
\*/
|
|
Snap.acos = function (num) {
|
|
return Snap.deg(math.acos(num));
|
|
};
|
|
/*\
|
|
* Snap.atan
|
|
[ method ]
|
|
**
|
|
* Equivalent to `Math.atan()` only works with degrees, not radians.
|
|
- num (number) value
|
|
= (number) atan in degrees
|
|
\*/
|
|
Snap.atan = function (num) {
|
|
return Snap.deg(math.atan(num));
|
|
};
|
|
/*\
|
|
* Snap.atan2
|
|
[ method ]
|
|
**
|
|
* Equivalent to `Math.atan2()` only works with degrees, not radians.
|
|
- num (number) value
|
|
= (number) atan2 in degrees
|
|
\*/
|
|
Snap.atan2 = function (num) {
|
|
return Snap.deg(math.atan2(num));
|
|
};
|
|
/*\
|
|
* Snap.angle
|
|
[ method ]
|
|
**
|
|
* Returns an angle between two or three points
|
|
> Parameters
|
|
- x1 (number) x coord of first point
|
|
- y1 (number) y coord of first point
|
|
- x2 (number) x coord of second point
|
|
- y2 (number) y coord of second point
|
|
- x3 (number) #optional x coord of third point
|
|
- y3 (number) #optional y coord of third point
|
|
= (number) angle in degrees
|
|
\*/
|
|
Snap.angle = angle;
|
|
/*\
|
|
* Snap.len
|
|
[ method ]
|
|
**
|
|
* Returns distance between two points
|
|
> Parameters
|
|
- x1 (number) x coord of first point
|
|
- y1 (number) y coord of first point
|
|
- x2 (number) x coord of second point
|
|
- y2 (number) y coord of second point
|
|
= (number) distance
|
|
\*/
|
|
Snap.len = function (x1, y1, x2, y2) {
|
|
return Math.sqrt(Snap.len2(x1, y1, x2, y2));
|
|
};
|
|
/*\
|
|
* Snap.len2
|
|
[ method ]
|
|
**
|
|
* Returns squared distance between two points
|
|
> Parameters
|
|
- x1 (number) x coord of first point
|
|
- y1 (number) y coord of first point
|
|
- x2 (number) x coord of second point
|
|
- y2 (number) y coord of second point
|
|
= (number) distance
|
|
\*/
|
|
Snap.len2 = function (x1, y1, x2, y2) {
|
|
return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2);
|
|
};
|
|
/*\
|
|
* Snap.closestPoint
|
|
[ method ]
|
|
**
|
|
* Returns closest point to a given one on a given path.
|
|
> Parameters
|
|
- path (Element) path element
|
|
- x (number) x coord of a point
|
|
- y (number) y coord of a point
|
|
= (object) in format
|
|
{
|
|
x (number) x coord of the point on the path
|
|
y (number) y coord of the point on the path
|
|
length (number) length of the path to the point
|
|
distance (number) distance from the given point to the path
|
|
}
|
|
\*/
|
|
// Copied from http://bl.ocks.org/mbostock/8027637
|
|
Snap.closestPoint = function (path, x, y) {
|
|
function distance2(p) {
|
|
var dx = p.x - x,
|
|
dy = p.y - y;
|
|
return dx * dx + dy * dy;
|
|
}
|
|
var pathNode = path.node,
|
|
pathLength = pathNode.getTotalLength(),
|
|
precision = pathLength / pathNode.pathSegList.numberOfItems * .125,
|
|
best,
|
|
bestLength,
|
|
bestDistance = Infinity;
|
|
|
|
// linear scan for coarse approximation
|
|
for (var scan, scanLength = 0, scanDistance; scanLength <= pathLength; scanLength += precision) {
|
|
if ((scanDistance = distance2(scan = pathNode.getPointAtLength(scanLength))) < bestDistance) {
|
|
best = scan, bestLength = scanLength, bestDistance = scanDistance;
|
|
}
|
|
}
|
|
|
|
// binary search for precise estimate
|
|
precision *= .5;
|
|
while (precision > .5) {
|
|
var before,
|
|
after,
|
|
beforeLength,
|
|
afterLength,
|
|
beforeDistance,
|
|
afterDistance;
|
|
if ((beforeLength = bestLength - precision) >= 0 && (beforeDistance = distance2(before = pathNode.getPointAtLength(beforeLength))) < bestDistance) {
|
|
best = before, bestLength = beforeLength, bestDistance = beforeDistance;
|
|
} else if ((afterLength = bestLength + precision) <= pathLength && (afterDistance = distance2(after = pathNode.getPointAtLength(afterLength))) < bestDistance) {
|
|
best = after, bestLength = afterLength, bestDistance = afterDistance;
|
|
} else {
|
|
precision *= .5;
|
|
}
|
|
}
|
|
|
|
best = {
|
|
x: best.x,
|
|
y: best.y,
|
|
length: bestLength,
|
|
distance: Math.sqrt(bestDistance)
|
|
};
|
|
return best;
|
|
}
|
|
/*\
|
|
* Snap.is
|
|
[ method ]
|
|
**
|
|
* Handy replacement for the `typeof` operator
|
|
- o (…) any object or primitive
|
|
- type (string) name of the type, e.g., `string`, `function`, `number`, etc.
|
|
= (boolean) `true` if given value is of given type
|
|
\*/
|
|
Snap.is = is;
|
|
/*\
|
|
* Snap.snapTo
|
|
[ method ]
|
|
**
|
|
* Snaps given value to given grid
|
|
- values (array|number) given array of values or step of the grid
|
|
- value (number) value to adjust
|
|
- tolerance (number) #optional maximum distance to the target value that would trigger the snap. Default is `10`.
|
|
= (number) adjusted value
|
|
\*/
|
|
Snap.snapTo = function (values, value, tolerance) {
|
|
tolerance = is(tolerance, "finite") ? tolerance : 10;
|
|
if (is(values, "array")) {
|
|
var i = values.length;
|
|
while (i--) if (abs(values[i] - value) <= tolerance) {
|
|
return values[i];
|
|
}
|
|
} else {
|
|
values = +values;
|
|
var rem = value % values;
|
|
if (rem < tolerance) {
|
|
return value - rem;
|
|
}
|
|
if (rem > values - tolerance) {
|
|
return value - rem + values;
|
|
}
|
|
}
|
|
return value;
|
|
};
|
|
// Colour
|
|
/*\
|
|
* Snap.getRGB
|
|
[ method ]
|
|
**
|
|
* Parses color string as RGB object
|
|
- color (string) color string in one of the following formats:
|
|
# <ul>
|
|
# <li>Color name (<code>red</code>, <code>green</code>, <code>cornflowerblue</code>, etc)</li>
|
|
# <li>#••• — shortened HTML color: (<code>#000</code>, <code>#fc0</code>, etc.)</li>
|
|
# <li>#•••••• — full length HTML color: (<code>#000000</code>, <code>#bd2300</code>)</li>
|
|
# <li>rgb(•••, •••, •••) — red, green and blue channels values: (<code>rgb(200, 100, 0)</code>)</li>
|
|
# <li>rgba(•••, •••, •••, •••) — also with opacity</li>
|
|
# <li>rgb(•••%, •••%, •••%) — same as above, but in %: (<code>rgb(100%, 175%, 0%)</code>)</li>
|
|
# <li>rgba(•••%, •••%, •••%, •••%) — also with opacity</li>
|
|
# <li>hsb(•••, •••, •••) — hue, saturation and brightness values: (<code>hsb(0.5, 0.25, 1)</code>)</li>
|
|
# <li>hsba(•••, •••, •••, •••) — also with opacity</li>
|
|
# <li>hsb(•••%, •••%, •••%) — same as above, but in %</li>
|
|
# <li>hsba(•••%, •••%, •••%, •••%) — also with opacity</li>
|
|
# <li>hsl(•••, •••, •••) — hue, saturation and luminosity values: (<code>hsb(0.5, 0.25, 0.5)</code>)</li>
|
|
# <li>hsla(•••, •••, •••, •••) — also with opacity</li>
|
|
# <li>hsl(•••%, •••%, •••%) — same as above, but in %</li>
|
|
# <li>hsla(•••%, •••%, •••%, •••%) — also with opacity</li>
|
|
# </ul>
|
|
* Note that `%` can be used any time: `rgb(20%, 255, 50%)`.
|
|
= (object) RGB object in the following format:
|
|
o {
|
|
o r (number) red,
|
|
o g (number) green,
|
|
o b (number) blue,
|
|
o hex (string) color in HTML/CSS format: #••••••,
|
|
o error (boolean) true if string can't be parsed
|
|
o }
|
|
\*/
|
|
Snap.getRGB = cacher(function (colour) {
|
|
if (!colour || !!((colour = Str(colour)).indexOf("-") + 1)) {
|
|
return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: rgbtoString};
|
|
}
|
|
if (colour == "none") {
|
|
return {r: -1, g: -1, b: -1, hex: "none", toString: rgbtoString};
|
|
}
|
|
!(hsrg[has](colour.toLowerCase().substring(0, 2)) || colour.charAt() == "#") && (colour = toHex(colour));
|
|
if (!colour) {
|
|
return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: rgbtoString};
|
|
}
|
|
var res,
|
|
red,
|
|
green,
|
|
blue,
|
|
opacity,
|
|
t,
|
|
values,
|
|
rgb = colour.match(colourRegExp);
|
|
if (rgb) {
|
|
if (rgb[2]) {
|
|
blue = toInt(rgb[2].substring(5), 16);
|
|
green = toInt(rgb[2].substring(3, 5), 16);
|
|
red = toInt(rgb[2].substring(1, 3), 16);
|
|
}
|
|
if (rgb[3]) {
|
|
blue = toInt((t = rgb[3].charAt(3)) + t, 16);
|
|
green = toInt((t = rgb[3].charAt(2)) + t, 16);
|
|
red = toInt((t = rgb[3].charAt(1)) + t, 16);
|
|
}
|
|
if (rgb[4]) {
|
|
values = rgb[4].split(commaSpaces);
|
|
red = toFloat(values[0]);
|
|
values[0].slice(-1) == "%" && (red *= 2.55);
|
|
green = toFloat(values[1]);
|
|
values[1].slice(-1) == "%" && (green *= 2.55);
|
|
blue = toFloat(values[2]);
|
|
values[2].slice(-1) == "%" && (blue *= 2.55);
|
|
rgb[1].toLowerCase().slice(0, 4) == "rgba" && (opacity = toFloat(values[3]));
|
|
values[3] && values[3].slice(-1) == "%" && (opacity /= 100);
|
|
}
|
|
if (rgb[5]) {
|
|
values = rgb[5].split(commaSpaces);
|
|
red = toFloat(values[0]);
|
|
values[0].slice(-1) == "%" && (red /= 100);
|
|
green = toFloat(values[1]);
|
|
values[1].slice(-1) == "%" && (green /= 100);
|
|
blue = toFloat(values[2]);
|
|
values[2].slice(-1) == "%" && (blue /= 100);
|
|
(values[0].slice(-3) == "deg" || values[0].slice(-1) == "\xb0") && (red /= 360);
|
|
rgb[1].toLowerCase().slice(0, 4) == "hsba" && (opacity = toFloat(values[3]));
|
|
values[3] && values[3].slice(-1) == "%" && (opacity /= 100);
|
|
return Snap.hsb2rgb(red, green, blue, opacity);
|
|
}
|
|
if (rgb[6]) {
|
|
values = rgb[6].split(commaSpaces);
|
|
red = toFloat(values[0]);
|
|
values[0].slice(-1) == "%" && (red /= 100);
|
|
green = toFloat(values[1]);
|
|
values[1].slice(-1) == "%" && (green /= 100);
|
|
blue = toFloat(values[2]);
|
|
values[2].slice(-1) == "%" && (blue /= 100);
|
|
(values[0].slice(-3) == "deg" || values[0].slice(-1) == "\xb0") && (red /= 360);
|
|
rgb[1].toLowerCase().slice(0, 4) == "hsla" && (opacity = toFloat(values[3]));
|
|
values[3] && values[3].slice(-1) == "%" && (opacity /= 100);
|
|
return Snap.hsl2rgb(red, green, blue, opacity);
|
|
}
|
|
red = mmin(math.round(red), 255);
|
|
green = mmin(math.round(green), 255);
|
|
blue = mmin(math.round(blue), 255);
|
|
opacity = mmin(mmax(opacity, 0), 1);
|
|
rgb = {r: red, g: green, b: blue, toString: rgbtoString};
|
|
rgb.hex = "#" + (16777216 | blue | (green << 8) | (red << 16)).toString(16).slice(1);
|
|
rgb.opacity = is(opacity, "finite") ? opacity : 1;
|
|
return rgb;
|
|
}
|
|
return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: rgbtoString};
|
|
}, Snap);
|
|
/*\
|
|
* Snap.hsb
|
|
[ method ]
|
|
**
|
|
* Converts HSB values to a hex representation of the color
|
|
- h (number) hue
|
|
- s (number) saturation
|
|
- b (number) value or brightness
|
|
= (string) hex representation of the color
|
|
\*/
|
|
Snap.hsb = cacher(function (h, s, b) {
|
|
return Snap.hsb2rgb(h, s, b).hex;
|
|
});
|
|
/*\
|
|
* Snap.hsl
|
|
[ method ]
|
|
**
|
|
* Converts HSL values to a hex representation of the color
|
|
- h (number) hue
|
|
- s (number) saturation
|
|
- l (number) luminosity
|
|
= (string) hex representation of the color
|
|
\*/
|
|
Snap.hsl = cacher(function (h, s, l) {
|
|
return Snap.hsl2rgb(h, s, l).hex;
|
|
});
|
|
/*\
|
|
* Snap.rgb
|
|
[ method ]
|
|
**
|
|
* Converts RGB values to a hex representation of the color
|
|
- r (number) red
|
|
- g (number) green
|
|
- b (number) blue
|
|
= (string) hex representation of the color
|
|
\*/
|
|
Snap.rgb = cacher(function (r, g, b, o) {
|
|
if (is(o, "finite")) {
|
|
var round = math.round;
|
|
return "rgba(" + [round(r), round(g), round(b), +o.toFixed(2)] + ")";
|
|
}
|
|
return "#" + (16777216 | b | (g << 8) | (r << 16)).toString(16).slice(1);
|
|
});
|
|
var toHex = function (color) {
|
|
var i = glob.doc.getElementsByTagName("head")[0] || glob.doc.getElementsByTagName("svg")[0],
|
|
red = "rgb(255, 0, 0)";
|
|
toHex = cacher(function (color) {
|
|
if (color.toLowerCase() == "red") {
|
|
return red;
|
|
}
|
|
i.style.color = red;
|
|
i.style.color = color;
|
|
var out = glob.doc.defaultView.getComputedStyle(i, E).getPropertyValue("color");
|
|
return out == red ? null : out;
|
|
});
|
|
return toHex(color);
|
|
},
|
|
hsbtoString = function () {
|
|
return "hsb(" + [this.h, this.s, this.b] + ")";
|
|
},
|
|
hsltoString = function () {
|
|
return "hsl(" + [this.h, this.s, this.l] + ")";
|
|
},
|
|
rgbtoString = function () {
|
|
return this.opacity == 1 || this.opacity == null ?
|
|
this.hex :
|
|
"rgba(" + [this.r, this.g, this.b, this.opacity] + ")";
|
|
},
|
|
prepareRGB = function (r, g, b) {
|
|
if (g == null && is(r, "object") && "r" in r && "g" in r && "b" in r) {
|
|
b = r.b;
|
|
g = r.g;
|
|
r = r.r;
|
|
}
|
|
if (g == null && is(r, string)) {
|
|
var clr = Snap.getRGB(r);
|
|
r = clr.r;
|
|
g = clr.g;
|
|
b = clr.b;
|
|
}
|
|
if (r > 1 || g > 1 || b > 1) {
|
|
r /= 255;
|
|
g /= 255;
|
|
b /= 255;
|
|
}
|
|
|
|
return [r, g, b];
|
|
},
|
|
packageRGB = function (r, g, b, o) {
|
|
r = math.round(r * 255);
|
|
g = math.round(g * 255);
|
|
b = math.round(b * 255);
|
|
var rgb = {
|
|
r: r,
|
|
g: g,
|
|
b: b,
|
|
opacity: is(o, "finite") ? o : 1,
|
|
hex: Snap.rgb(r, g, b),
|
|
toString: rgbtoString
|
|
};
|
|
is(o, "finite") && (rgb.opacity = o);
|
|
return rgb;
|
|
};
|
|
/*\
|
|
* Snap.color
|
|
[ method ]
|
|
**
|
|
* Parses the color string and returns an object featuring the color's component values
|
|
- clr (string) color string in one of the supported formats (see @Snap.getRGB)
|
|
= (object) Combined RGB/HSB object in the following format:
|
|
o {
|
|
o r (number) red,
|
|
o g (number) green,
|
|
o b (number) blue,
|
|
o hex (string) color in HTML/CSS format: #••••••,
|
|
o error (boolean) `true` if string can't be parsed,
|
|
o h (number) hue,
|
|
o s (number) saturation,
|
|
o v (number) value (brightness),
|
|
o l (number) lightness
|
|
o }
|
|
\*/
|
|
Snap.color = function (clr) {
|
|
var rgb;
|
|
if (is(clr, "object") && "h" in clr && "s" in clr && "b" in clr) {
|
|
rgb = Snap.hsb2rgb(clr);
|
|
clr.r = rgb.r;
|
|
clr.g = rgb.g;
|
|
clr.b = rgb.b;
|
|
clr.opacity = 1;
|
|
clr.hex = rgb.hex;
|
|
} else if (is(clr, "object") && "h" in clr && "s" in clr && "l" in clr) {
|
|
rgb = Snap.hsl2rgb(clr);
|
|
clr.r = rgb.r;
|
|
clr.g = rgb.g;
|
|
clr.b = rgb.b;
|
|
clr.opacity = 1;
|
|
clr.hex = rgb.hex;
|
|
} else {
|
|
if (is(clr, "string")) {
|
|
clr = Snap.getRGB(clr);
|
|
}
|
|
if (is(clr, "object") && "r" in clr && "g" in clr && "b" in clr && !("error" in clr)) {
|
|
rgb = Snap.rgb2hsl(clr);
|
|
clr.h = rgb.h;
|
|
clr.s = rgb.s;
|
|
clr.l = rgb.l;
|
|
rgb = Snap.rgb2hsb(clr);
|
|
clr.v = rgb.b;
|
|
} else {
|
|
clr = {hex: "none"};
|
|
clr.r = clr.g = clr.b = clr.h = clr.s = clr.v = clr.l = -1;
|
|
clr.error = 1;
|
|
}
|
|
}
|
|
clr.toString = rgbtoString;
|
|
return clr;
|
|
};
|
|
/*\
|
|
* Snap.hsb2rgb
|
|
[ method ]
|
|
**
|
|
* Converts HSB values to an RGB object
|
|
- h (number) hue
|
|
- s (number) saturation
|
|
- v (number) value or brightness
|
|
= (object) RGB object in the following format:
|
|
o {
|
|
o r (number) red,
|
|
o g (number) green,
|
|
o b (number) blue,
|
|
o hex (string) color in HTML/CSS format: #••••••
|
|
o }
|
|
\*/
|
|
Snap.hsb2rgb = function (h, s, v, o) {
|
|
if (is(h, "object") && "h" in h && "s" in h && "b" in h) {
|
|
v = h.b;
|
|
s = h.s;
|
|
o = h.o;
|
|
h = h.h;
|
|
}
|
|
h *= 360;
|
|
var R, G, B, X, C;
|
|
h = (h % 360) / 60;
|
|
C = v * s;
|
|
X = C * (1 - abs(h % 2 - 1));
|
|
R = G = B = v - C;
|
|
|
|
h = ~~h;
|
|
R += [C, X, 0, 0, X, C][h];
|
|
G += [X, C, C, X, 0, 0][h];
|
|
B += [0, 0, X, C, C, X][h];
|
|
return packageRGB(R, G, B, o);
|
|
};
|
|
/*\
|
|
* Snap.hsl2rgb
|
|
[ method ]
|
|
**
|
|
* Converts HSL values to an RGB object
|
|
- h (number) hue
|
|
- s (number) saturation
|
|
- l (number) luminosity
|
|
= (object) RGB object in the following format:
|
|
o {
|
|
o r (number) red,
|
|
o g (number) green,
|
|
o b (number) blue,
|
|
o hex (string) color in HTML/CSS format: #••••••
|
|
o }
|
|
\*/
|
|
Snap.hsl2rgb = function (h, s, l, o) {
|
|
if (is(h, "object") && "h" in h && "s" in h && "l" in h) {
|
|
l = h.l;
|
|
s = h.s;
|
|
h = h.h;
|
|
}
|
|
if (h > 1 || s > 1 || l > 1) {
|
|
h /= 360;
|
|
s /= 100;
|
|
l /= 100;
|
|
}
|
|
h *= 360;
|
|
var R, G, B, X, C;
|
|
h = (h % 360) / 60;
|
|
C = 2 * s * (l < .5 ? l : 1 - l);
|
|
X = C * (1 - abs(h % 2 - 1));
|
|
R = G = B = l - C / 2;
|
|
|
|
h = ~~h;
|
|
R += [C, X, 0, 0, X, C][h];
|
|
G += [X, C, C, X, 0, 0][h];
|
|
B += [0, 0, X, C, C, X][h];
|
|
return packageRGB(R, G, B, o);
|
|
};
|
|
/*\
|
|
* Snap.rgb2hsb
|
|
[ method ]
|
|
**
|
|
* Converts RGB values to an HSB object
|
|
- r (number) red
|
|
- g (number) green
|
|
- b (number) blue
|
|
= (object) HSB object in the following format:
|
|
o {
|
|
o h (number) hue,
|
|
o s (number) saturation,
|
|
o b (number) brightness
|
|
o }
|
|
\*/
|
|
Snap.rgb2hsb = function (r, g, b) {
|
|
b = prepareRGB(r, g, b);
|
|
r = b[0];
|
|
g = b[1];
|
|
b = b[2];
|
|
|
|
var H, S, V, C;
|
|
V = mmax(r, g, b);
|
|
C = V - mmin(r, g, b);
|
|
H = (C == 0 ? null :
|
|
V == r ? (g - b) / C :
|
|
V == g ? (b - r) / C + 2 :
|
|
(r - g) / C + 4
|
|
);
|
|
H = ((H + 360) % 6) * 60 / 360;
|
|
S = C == 0 ? 0 : C / V;
|
|
return {h: H, s: S, b: V, toString: hsbtoString};
|
|
};
|
|
/*\
|
|
* Snap.rgb2hsl
|
|
[ method ]
|
|
**
|
|
* Converts RGB values to an HSL object
|
|
- r (number) red
|
|
- g (number) green
|
|
- b (number) blue
|
|
= (object) HSL object in the following format:
|
|
o {
|
|
o h (number) hue,
|
|
o s (number) saturation,
|
|
o l (number) luminosity
|
|
o }
|
|
\*/
|
|
Snap.rgb2hsl = function (r, g, b) {
|
|
b = prepareRGB(r, g, b);
|
|
r = b[0];
|
|
g = b[1];
|
|
b = b[2];
|
|
|
|
var H, S, L, M, m, C;
|
|
M = mmax(r, g, b);
|
|
m = mmin(r, g, b);
|
|
C = M - m;
|
|
H = (C == 0 ? null :
|
|
M == r ? (g - b) / C :
|
|
M == g ? (b - r) / C + 2 :
|
|
(r - g) / C + 4);
|
|
H = ((H + 360) % 6) * 60 / 360;
|
|
L = (M + m) / 2;
|
|
S = (C == 0 ? 0 :
|
|
L < .5 ? C / (2 * L) :
|
|
C / (2 - 2 * L));
|
|
return {h: H, s: S, l: L, toString: hsltoString};
|
|
};
|
|
|
|
// Transformations
|
|
/*\
|
|
* Snap.parsePathString
|
|
[ method ]
|
|
**
|
|
* Utility method
|
|
**
|
|
* Parses given path string into an array of arrays of path segments
|
|
- pathString (string|array) path string or array of segments (in the last case it is returned straight away)
|
|
= (array) array of segments
|
|
\*/
|
|
Snap.parsePathString = function (pathString) {
|
|
if (!pathString) {
|
|
return null;
|
|
}
|
|
var pth = Snap.path(pathString);
|
|
if (pth.arr) {
|
|
return Snap.path.clone(pth.arr);
|
|
}
|
|
|
|
var paramCounts = {a: 7, c: 6, o: 2, h: 1, l: 2, m: 2, r: 4, q: 4, s: 4, t: 2, v: 1, u: 3, z: 0},
|
|
data = [];
|
|
if (is(pathString, "array") && is(pathString[0], "array")) { // rough assumption
|
|
data = Snap.path.clone(pathString);
|
|
}
|
|
if (!data.length) {
|
|
Str(pathString).replace(pathCommand, function (a, b, c) {
|
|
var params = [],
|
|
name = b.toLowerCase();
|
|
c.replace(pathValues, function (a, b) {
|
|
b && params.push(+b);
|
|
});
|
|
if (name == "m" && params.length > 2) {
|
|
data.push([b].concat(params.splice(0, 2)));
|
|
name = "l";
|
|
b = b == "m" ? "l" : "L";
|
|
}
|
|
if (name == "o" && params.length == 1) {
|
|
data.push([b, params[0]]);
|
|
}
|
|
if (name == "r") {
|
|
data.push([b].concat(params));
|
|
} else while (params.length >= paramCounts[name]) {
|
|
data.push([b].concat(params.splice(0, paramCounts[name])));
|
|
if (!paramCounts[name]) {
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
data.toString = Snap.path.toString;
|
|
pth.arr = Snap.path.clone(data);
|
|
return data;
|
|
};
|
|
/*\
|
|
* Snap.parseTransformString
|
|
[ method ]
|
|
**
|
|
* Utility method
|
|
**
|
|
* Parses given transform string into an array of transformations
|
|
- TString (string|array) transform string or array of transformations (in the last case it is returned straight away)
|
|
= (array) array of transformations
|
|
\*/
|
|
var parseTransformString = Snap.parseTransformString = function (TString) {
|
|
if (!TString) {
|
|
return null;
|
|
}
|
|
var paramCounts = {r: 3, s: 4, t: 2, m: 6},
|
|
data = [];
|
|
if (is(TString, "array") && is(TString[0], "array")) { // rough assumption
|
|
data = Snap.path.clone(TString);
|
|
}
|
|
if (!data.length) {
|
|
Str(TString).replace(tCommand, function (a, b, c) {
|
|
var params = [],
|
|
name = b.toLowerCase();
|
|
c.replace(pathValues, function (a, b) {
|
|
b && params.push(+b);
|
|
});
|
|
data.push([b].concat(params));
|
|
});
|
|
}
|
|
data.toString = Snap.path.toString;
|
|
return data;
|
|
};
|
|
function svgTransform2string(tstr) {
|
|
var res = [];
|
|
tstr = tstr.replace(/(?:^|\s)(\w+)\(([^)]+)\)/g, function (all, name, params) {
|
|
params = params.split(/\s*,\s*|\s+/);
|
|
if (name == "rotate" && params.length == 1) {
|
|
params.push(0, 0);
|
|
}
|
|
if (name == "scale") {
|
|
if (params.length > 2) {
|
|
params = params.slice(0, 2);
|
|
} else if (params.length == 2) {
|
|
params.push(0, 0);
|
|
}
|
|
if (params.length == 1) {
|
|
params.push(params[0], 0, 0);
|
|
}
|
|
}
|
|
if (name == "skewX") {
|
|
res.push(["m", 1, 0, math.tan(rad(params[0])), 1, 0, 0]);
|
|
} else if (name == "skewY") {
|
|
res.push(["m", 1, math.tan(rad(params[0])), 0, 1, 0, 0]);
|
|
} else {
|
|
res.push([name.charAt(0)].concat(params));
|
|
}
|
|
return all;
|
|
});
|
|
return res;
|
|
}
|
|
Snap._.svgTransform2string = svgTransform2string;
|
|
Snap._.rgTransform = /^[a-z][\s]*-?\.?\d/i;
|
|
function transform2matrix(tstr, bbox) {
|
|
var tdata = parseTransformString(tstr),
|
|
m = new Snap.Matrix;
|
|
if (tdata) {
|
|
for (var i = 0, ii = tdata.length; i < ii; i++) {
|
|
var t = tdata[i],
|
|
tlen = t.length,
|
|
command = Str(t[0]).toLowerCase(),
|
|
absolute = t[0] != command,
|
|
inver = absolute ? m.invert() : 0,
|
|
x1,
|
|
y1,
|
|
x2,
|
|
y2,
|
|
bb;
|
|
if (command == "t" && tlen == 2){
|
|
m.translate(t[1], 0);
|
|
} else if (command == "t" && tlen == 3) {
|
|
if (absolute) {
|
|
x1 = inver.x(0, 0);
|
|
y1 = inver.y(0, 0);
|
|
x2 = inver.x(t[1], t[2]);
|
|
y2 = inver.y(t[1], t[2]);
|
|
m.translate(x2 - x1, y2 - y1);
|
|
} else {
|
|
m.translate(t[1], t[2]);
|
|
}
|
|
} else if (command == "r") {
|
|
if (tlen == 2) {
|
|
bb = bb || bbox;
|
|
m.rotate(t[1], bb.x + bb.width / 2, bb.y + bb.height / 2);
|
|
} else if (tlen == 4) {
|
|
if (absolute) {
|
|
x2 = inver.x(t[2], t[3]);
|
|
y2 = inver.y(t[2], t[3]);
|
|
m.rotate(t[1], x2, y2);
|
|
} else {
|
|
m.rotate(t[1], t[2], t[3]);
|
|
}
|
|
}
|
|
} else if (command == "s") {
|
|
if (tlen == 2 || tlen == 3) {
|
|
bb = bb || bbox;
|
|
m.scale(t[1], t[tlen - 1], bb.x + bb.width / 2, bb.y + bb.height / 2);
|
|
} else if (tlen == 4) {
|
|
if (absolute) {
|
|
x2 = inver.x(t[2], t[3]);
|
|
y2 = inver.y(t[2], t[3]);
|
|
m.scale(t[1], t[1], x2, y2);
|
|
} else {
|
|
m.scale(t[1], t[1], t[2], t[3]);
|
|
}
|
|
} else if (tlen == 5) {
|
|
if (absolute) {
|
|
x2 = inver.x(t[3], t[4]);
|
|
y2 = inver.y(t[3], t[4]);
|
|
m.scale(t[1], t[2], x2, y2);
|
|
} else {
|
|
m.scale(t[1], t[2], t[3], t[4]);
|
|
}
|
|
}
|
|
} else if (command == "m" && tlen == 7) {
|
|
m.add(t[1], t[2], t[3], t[4], t[5], t[6]);
|
|
}
|
|
}
|
|
}
|
|
return m;
|
|
}
|
|
Snap._.transform2matrix = transform2matrix;
|
|
Snap._unit2px = unit2px;
|
|
var contains = glob.doc.contains || glob.doc.compareDocumentPosition ?
|
|
function (a, b) {
|
|
var adown = a.nodeType == 9 ? a.documentElement : a,
|
|
bup = b && b.parentNode;
|
|
return a == bup || !!(bup && bup.nodeType == 1 && (
|
|
adown.contains ?
|
|
adown.contains(bup) :
|
|
a.compareDocumentPosition && a.compareDocumentPosition(bup) & 16
|
|
));
|
|
} :
|
|
function (a, b) {
|
|
if (b) {
|
|
while (b) {
|
|
b = b.parentNode;
|
|
if (b == a) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
function getSomeDefs(el) {
|
|
var p = (el.node.ownerSVGElement && wrap(el.node.ownerSVGElement)) ||
|
|
(el.node.parentNode && wrap(el.node.parentNode)) ||
|
|
Snap.select("svg") ||
|
|
Snap(0, 0),
|
|
pdefs = p.select("defs"),
|
|
defs = pdefs == null ? false : pdefs.node;
|
|
if (!defs) {
|
|
defs = make("defs", p.node).node;
|
|
}
|
|
return defs;
|
|
}
|
|
function getSomeSVG(el) {
|
|
return el.node.ownerSVGElement && wrap(el.node.ownerSVGElement) || Snap.select("svg");
|
|
}
|
|
Snap._.getSomeDefs = getSomeDefs;
|
|
Snap._.getSomeSVG = getSomeSVG;
|
|
function unit2px(el, name, value) {
|
|
var svg = getSomeSVG(el).node,
|
|
out = {},
|
|
mgr = svg.querySelector(".svg---mgr");
|
|
if (!mgr) {
|
|
mgr = $("rect");
|
|
$(mgr, {x: -9e9, y: -9e9, width: 10, height: 10, "class": "svg---mgr", fill: "none"});
|
|
svg.appendChild(mgr);
|
|
}
|
|
function getW(val) {
|
|
if (val == null) {
|
|
return E;
|
|
}
|
|
if (val == +val) {
|
|
return val;
|
|
}
|
|
$(mgr, {width: val});
|
|
try {
|
|
return mgr.getBBox().width;
|
|
} catch (e) {
|
|
return 0;
|
|
}
|
|
}
|
|
function getH(val) {
|
|
if (val == null) {
|
|
return E;
|
|
}
|
|
if (val == +val) {
|
|
return val;
|
|
}
|
|
$(mgr, {height: val});
|
|
try {
|
|
return mgr.getBBox().height;
|
|
} catch (e) {
|
|
return 0;
|
|
}
|
|
}
|
|
function set(nam, f) {
|
|
if (name == null) {
|
|
out[nam] = f(el.attr(nam) || 0);
|
|
} else if (nam == name) {
|
|
out = f(value == null ? el.attr(nam) || 0 : value);
|
|
}
|
|
}
|
|
switch (el.type) {
|
|
case "rect":
|
|
set("rx", getW);
|
|
set("ry", getH);
|
|
case "image":
|
|
set("width", getW);
|
|
set("height", getH);
|
|
case "text":
|
|
set("x", getW);
|
|
set("y", getH);
|
|
break;
|
|
case "circle":
|
|
set("cx", getW);
|
|
set("cy", getH);
|
|
set("r", getW);
|
|
break;
|
|
case "ellipse":
|
|
set("cx", getW);
|
|
set("cy", getH);
|
|
set("rx", getW);
|
|
set("ry", getH);
|
|
break;
|
|
case "line":
|
|
set("x1", getW);
|
|
set("x2", getW);
|
|
set("y1", getH);
|
|
set("y2", getH);
|
|
break;
|
|
case "marker":
|
|
set("refX", getW);
|
|
set("markerWidth", getW);
|
|
set("refY", getH);
|
|
set("markerHeight", getH);
|
|
break;
|
|
case "radialGradient":
|
|
set("fx", getW);
|
|
set("fy", getH);
|
|
break;
|
|
case "tspan":
|
|
set("dx", getW);
|
|
set("dy", getH);
|
|
break;
|
|
default:
|
|
set(name, getW);
|
|
}
|
|
svg.removeChild(mgr);
|
|
return out;
|
|
}
|
|
/*\
|
|
* Snap.select
|
|
[ method ]
|
|
**
|
|
* Wraps a DOM element specified by CSS selector as @Element
|
|
- query (string) CSS selector of the element
|
|
= (Element) the current element
|
|
\*/
|
|
Snap.select = function (query) {
|
|
query = Str(query).replace(/([^\\]):/g, "$1\\:");
|
|
return wrap(glob.doc.querySelector(query));
|
|
};
|
|
/*\
|
|
* Snap.selectAll
|
|
[ method ]
|
|
**
|
|
* Wraps DOM elements specified by CSS selector as set or array of @Element
|
|
- query (string) CSS selector of the element
|
|
= (Element) the current element
|
|
\*/
|
|
Snap.selectAll = function (query) {
|
|
var nodelist = glob.doc.querySelectorAll(query),
|
|
set = (Snap.set || Array)();
|
|
for (var i = 0; i < nodelist.length; i++) {
|
|
set.push(wrap(nodelist[i]));
|
|
}
|
|
return set;
|
|
};
|
|
|
|
function add2group(list) {
|
|
if (!is(list, "array")) {
|
|
list = Array.prototype.slice.call(arguments, 0);
|
|
}
|
|
var i = 0,
|
|
j = 0,
|
|
node = this.node;
|
|
while (this[i]) delete this[i++];
|
|
for (i = 0; i < list.length; i++) {
|
|
if (list[i].type == "set") {
|
|
list[i].forEach(function (el) {
|
|
node.appendChild(el.node);
|
|
});
|
|
} else {
|
|
node.appendChild(list[i].node);
|
|
}
|
|
}
|
|
var children = node.childNodes;
|
|
for (i = 0; i < children.length; i++) {
|
|
this[j++] = wrap(children[i]);
|
|
}
|
|
return this;
|
|
}
|
|
// Hub garbage collector every 10s
|
|
setInterval(function () {
|
|
for (var key in hub) if (hub[has](key)) {
|
|
var el = hub[key],
|
|
node = el.node;
|
|
if (el.type != "svg" && !node.ownerSVGElement || el.type == "svg" && (!node.parentNode || "ownerSVGElement" in node.parentNode && !node.ownerSVGElement)) {
|
|
delete hub[key];
|
|
}
|
|
}
|
|
}, 1e4);
|
|
function Element(el) {
|
|
if (el.snap in hub) {
|
|
return hub[el.snap];
|
|
}
|
|
var svg;
|
|
try {
|
|
svg = el.ownerSVGElement;
|
|
} catch(e) {}
|
|
/*\
|
|
* Element.node
|
|
[ property (object) ]
|
|
**
|
|
* Gives you a reference to the DOM object, so you can assign event handlers or just mess around.
|
|
> Usage
|
|
| // draw a circle at coordinate 10,10 with radius of 10
|
|
| var c = paper.circle(10, 10, 10);
|
|
| c.node.onclick = function () {
|
|
| c.attr("fill", "red");
|
|
| };
|
|
\*/
|
|
this.node = el;
|
|
if (svg) {
|
|
this.paper = new Paper(svg);
|
|
}
|
|
/*\
|
|
* Element.type
|
|
[ property (string) ]
|
|
**
|
|
* SVG tag name of the given element.
|
|
\*/
|
|
this.type = el.tagName || el.nodeName;
|
|
var id = this.id = ID(this);
|
|
this.anims = {};
|
|
this._ = {
|
|
transform: []
|
|
};
|
|
el.snap = id;
|
|
hub[id] = this;
|
|
if (this.type == "g") {
|
|
this.add = add2group;
|
|
}
|
|
if (this.type in {g: 1, mask: 1, pattern: 1, symbol: 1}) {
|
|
for (var method in Paper.prototype) if (Paper.prototype[has](method)) {
|
|
this[method] = Paper.prototype[method];
|
|
}
|
|
}
|
|
}
|
|
/*\
|
|
* Element.attr
|
|
[ method ]
|
|
**
|
|
* Gets or sets given attributes of the element.
|
|
**
|
|
- params (object) contains key-value pairs of attributes you want to set
|
|
* or
|
|
- param (string) name of the attribute
|
|
= (Element) the current element
|
|
* or
|
|
= (string) value of attribute
|
|
> Usage
|
|
| el.attr({
|
|
| fill: "#fc0",
|
|
| stroke: "#000",
|
|
| strokeWidth: 2, // CamelCase...
|
|
| "fill-opacity": 0.5, // or dash-separated names
|
|
| width: "*=2" // prefixed values
|
|
| });
|
|
| console.log(el.attr("fill")); // #fc0
|
|
* Prefixed values in format `"+=10"` supported. All four operations
|
|
* (`+`, `-`, `*` and `/`) could be used. Optionally you can use units for `+`
|
|
* and `-`: `"+=2em"`.
|
|
\*/
|
|
Element.prototype.attr = function (params, value) {
|
|
var el = this,
|
|
node = el.node;
|
|
if (!params) {
|
|
if (node.nodeType != 1) {
|
|
return {
|
|
text: node.nodeValue
|
|
};
|
|
}
|
|
var attr = node.attributes,
|
|
out = {};
|
|
for (var i = 0, ii = attr.length; i < ii; i++) {
|
|
out[attr[i].nodeName] = attr[i].nodeValue;
|
|
}
|
|
return out;
|
|
}
|
|
if (is(params, "string")) {
|
|
if (arguments.length > 1) {
|
|
var json = {};
|
|
json[params] = value;
|
|
params = json;
|
|
} else {
|
|
return eve("snap.util.getattr." + params, el).firstDefined();
|
|
}
|
|
}
|
|
for (var att in params) {
|
|
if (params[has](att)) {
|
|
eve("snap.util.attr." + att, el, params[att]);
|
|
}
|
|
}
|
|
return el;
|
|
};
|
|
/*\
|
|
* Snap.parse
|
|
[ method ]
|
|
**
|
|
* Parses SVG fragment and converts it into a @Fragment
|
|
**
|
|
- svg (string) SVG string
|
|
= (Fragment) the @Fragment
|
|
\*/
|
|
Snap.parse = function (svg) {
|
|
var f = glob.doc.createDocumentFragment(),
|
|
full = true,
|
|
div = glob.doc.createElement("div");
|
|
svg = Str(svg);
|
|
if (!svg.match(/^\s*<\s*svg(?:\s|>)/)) {
|
|
svg = "<svg>" + svg + "</svg>";
|
|
full = false;
|
|
}
|
|
div.innerHTML = svg;
|
|
svg = div.getElementsByTagName("svg")[0];
|
|
if (svg) {
|
|
if (full) {
|
|
f = svg;
|
|
} else {
|
|
while (svg.firstChild) {
|
|
f.appendChild(svg.firstChild);
|
|
}
|
|
}
|
|
}
|
|
return new Fragment(f);
|
|
};
|
|
function Fragment(frag) {
|
|
this.node = frag;
|
|
}
|
|
/*\
|
|
* Snap.fragment
|
|
[ method ]
|
|
**
|
|
* Creates a DOM fragment from a given list of elements or strings
|
|
**
|
|
- varargs (…) SVG string
|
|
= (Fragment) the @Fragment
|
|
\*/
|
|
Snap.fragment = function () {
|
|
var args = Array.prototype.slice.call(arguments, 0),
|
|
f = glob.doc.createDocumentFragment();
|
|
for (var i = 0, ii = args.length; i < ii; i++) {
|
|
var item = args[i];
|
|
if (item.node && item.node.nodeType) {
|
|
f.appendChild(item.node);
|
|
}
|
|
if (item.nodeType) {
|
|
f.appendChild(item);
|
|
}
|
|
if (typeof item == "string") {
|
|
f.appendChild(Snap.parse(item).node);
|
|
}
|
|
}
|
|
return new Fragment(f);
|
|
};
|
|
|
|
function make(name, parent) {
|
|
var res = $(name);
|
|
parent.appendChild(res);
|
|
var el = wrap(res);
|
|
return el;
|
|
}
|
|
function Paper(w, h) {
|
|
var res,
|
|
desc,
|
|
defs,
|
|
proto = Paper.prototype;
|
|
if (w && w.tagName == "svg") {
|
|
if (w.snap in hub) {
|
|
return hub[w.snap];
|
|
}
|
|
var doc = w.ownerDocument;
|
|
res = new Element(w);
|
|
desc = w.getElementsByTagName("desc")[0];
|
|
defs = w.getElementsByTagName("defs")[0];
|
|
if (!desc) {
|
|
desc = $("desc");
|
|
desc.appendChild(doc.createTextNode("Created with Snap"));
|
|
res.node.appendChild(desc);
|
|
}
|
|
if (!defs) {
|
|
defs = $("defs");
|
|
res.node.appendChild(defs);
|
|
}
|
|
res.defs = defs;
|
|
for (var key in proto) if (proto[has](key)) {
|
|
res[key] = proto[key];
|
|
}
|
|
res.paper = res.root = res;
|
|
} else {
|
|
res = make("svg", glob.doc.body);
|
|
$(res.node, {
|
|
height: h,
|
|
version: 1.1,
|
|
width: w,
|
|
xmlns: xmlns
|
|
});
|
|
}
|
|
return res;
|
|
}
|
|
function wrap(dom) {
|
|
if (!dom) {
|
|
return dom;
|
|
}
|
|
if (dom instanceof Element || dom instanceof Fragment) {
|
|
return dom;
|
|
}
|
|
if (dom.tagName && dom.tagName.toLowerCase() == "svg") {
|
|
return new Paper(dom);
|
|
}
|
|
if (dom.tagName && dom.tagName.toLowerCase() == "object" && dom.type == "image/svg+xml") {
|
|
return new Paper(dom.contentDocument.getElementsByTagName("svg")[0]);
|
|
}
|
|
return new Element(dom);
|
|
}
|
|
|
|
Snap._.make = make;
|
|
Snap._.wrap = wrap;
|
|
/*\
|
|
* Paper.el
|
|
[ method ]
|
|
**
|
|
* Creates an element on paper with a given name and no attributes
|
|
**
|
|
- name (string) tag name
|
|
- attr (object) attributes
|
|
= (Element) the current element
|
|
> Usage
|
|
| var c = paper.circle(10, 10, 10); // is the same as...
|
|
| var c = paper.el("circle").attr({
|
|
| cx: 10,
|
|
| cy: 10,
|
|
| r: 10
|
|
| });
|
|
| // and the same as
|
|
| var c = paper.el("circle", {
|
|
| cx: 10,
|
|
| cy: 10,
|
|
| r: 10
|
|
| });
|
|
\*/
|
|
Paper.prototype.el = function (name, attr) {
|
|
var el = make(name, this.node);
|
|
attr && el.attr(attr);
|
|
return el;
|
|
};
|
|
/*\
|
|
* Element.children
|
|
[ method ]
|
|
**
|
|
* Returns array of all the children of the element.
|
|
= (array) array of Elements
|
|
\*/
|
|
Element.prototype.children = function () {
|
|
var out = [],
|
|
ch = this.node.childNodes;
|
|
for (var i = 0, ii = ch.length; i < ii; i++) {
|
|
out[i] = Snap(ch[i]);
|
|
}
|
|
return out;
|
|
};
|
|
function jsonFiller(root, o) {
|
|
for (var i = 0, ii = root.length; i < ii; i++) {
|
|
var item = {
|
|
type: root[i].type,
|
|
attr: root[i].attr()
|
|
},
|
|
children = root[i].children();
|
|
o.push(item);
|
|
if (children.length) {
|
|
jsonFiller(children, item.childNodes = []);
|
|
}
|
|
}
|
|
}
|
|
/*\
|
|
* Element.toJSON
|
|
[ method ]
|
|
**
|
|
* Returns object representation of the given element and all its children.
|
|
= (object) in format
|
|
o {
|
|
o type (string) this.type,
|
|
o attr (object) attributes map,
|
|
o childNodes (array) optional array of children in the same format
|
|
o }
|
|
\*/
|
|
Element.prototype.toJSON = function () {
|
|
var out = [];
|
|
jsonFiller([this], out);
|
|
return out[0];
|
|
};
|
|
// default
|
|
eve.on("snap.util.getattr", function () {
|
|
var att = eve.nt();
|
|
att = att.substring(att.lastIndexOf(".") + 1);
|
|
var css = att.replace(/[A-Z]/g, function (letter) {
|
|
return "-" + letter.toLowerCase();
|
|
});
|
|
if (cssAttr[has](css)) {
|
|
return this.node.ownerDocument.defaultView.getComputedStyle(this.node, null).getPropertyValue(css);
|
|
} else {
|
|
return $(this.node, att);
|
|
}
|
|
});
|
|
var cssAttr = {
|
|
"alignment-baseline": 0,
|
|
"baseline-shift": 0,
|
|
"clip": 0,
|
|
"clip-path": 0,
|
|
"clip-rule": 0,
|
|
"color": 0,
|
|
"color-interpolation": 0,
|
|
"color-interpolation-filters": 0,
|
|
"color-profile": 0,
|
|
"color-rendering": 0,
|
|
"cursor": 0,
|
|
"direction": 0,
|
|
"display": 0,
|
|
"dominant-baseline": 0,
|
|
"enable-background": 0,
|
|
"fill": 0,
|
|
"fill-opacity": 0,
|
|
"fill-rule": 0,
|
|
"filter": 0,
|
|
"flood-color": 0,
|
|
"flood-opacity": 0,
|
|
"font": 0,
|
|
"font-family": 0,
|
|
"font-size": 0,
|
|
"font-size-adjust": 0,
|
|
"font-stretch": 0,
|
|
"font-style": 0,
|
|
"font-variant": 0,
|
|
"font-weight": 0,
|
|
"glyph-orientation-horizontal": 0,
|
|
"glyph-orientation-vertical": 0,
|
|
"image-rendering": 0,
|
|
"kerning": 0,
|
|
"letter-spacing": 0,
|
|
"lighting-color": 0,
|
|
"marker": 0,
|
|
"marker-end": 0,
|
|
"marker-mid": 0,
|
|
"marker-start": 0,
|
|
"mask": 0,
|
|
"opacity": 0,
|
|
"overflow": 0,
|
|
"pointer-events": 0,
|
|
"shape-rendering": 0,
|
|
"stop-color": 0,
|
|
"stop-opacity": 0,
|
|
"stroke": 0,
|
|
"stroke-dasharray": 0,
|
|
"stroke-dashoffset": 0,
|
|
"stroke-linecap": 0,
|
|
"stroke-linejoin": 0,
|
|
"stroke-miterlimit": 0,
|
|
"stroke-opacity": 0,
|
|
"stroke-width": 0,
|
|
"text-anchor": 0,
|
|
"text-decoration": 0,
|
|
"text-rendering": 0,
|
|
"unicode-bidi": 0,
|
|
"visibility": 0,
|
|
"word-spacing": 0,
|
|
"writing-mode": 0
|
|
};
|
|
|
|
eve.on("snap.util.attr", function (value) {
|
|
var att = eve.nt(),
|
|
attr = {};
|
|
att = att.substring(att.lastIndexOf(".") + 1);
|
|
attr[att] = value;
|
|
var style = att.replace(/-(\w)/gi, function (all, letter) {
|
|
return letter.toUpperCase();
|
|
}),
|
|
css = att.replace(/[A-Z]/g, function (letter) {
|
|
return "-" + letter.toLowerCase();
|
|
});
|
|
if (cssAttr[has](css)) {
|
|
this.node.style[style] = value == null ? E : value;
|
|
} else {
|
|
$(this.node, attr);
|
|
}
|
|
});
|
|
(function (proto) {}(Paper.prototype));
|
|
|
|
// simple ajax
|
|
/*\
|
|
* Snap.ajax
|
|
[ method ]
|
|
**
|
|
* Simple implementation of Ajax
|
|
**
|
|
- url (string) URL
|
|
- postData (object|string) data for post request
|
|
- callback (function) callback
|
|
- scope (object) #optional scope of callback
|
|
* or
|
|
- url (string) URL
|
|
- callback (function) callback
|
|
- scope (object) #optional scope of callback
|
|
= (XMLHttpRequest) the XMLHttpRequest object, just in case
|
|
\*/
|
|
Snap.ajax = function (url, postData, callback, scope){
|
|
var req = new XMLHttpRequest,
|
|
id = ID();
|
|
if (req) {
|
|
if (is(postData, "function")) {
|
|
scope = callback;
|
|
callback = postData;
|
|
postData = null;
|
|
} else if (is(postData, "object")) {
|
|
var pd = [];
|
|
for (var key in postData) if (postData.hasOwnProperty(key)) {
|
|
pd.push(encodeURIComponent(key) + "=" + encodeURIComponent(postData[key]));
|
|
}
|
|
postData = pd.join("&");
|
|
}
|
|
req.open((postData ? "POST" : "GET"), url, true);
|
|
if (postData) {
|
|
req.setRequestHeader("X-Requested-With", "XMLHttpRequest");
|
|
req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
|
|
}
|
|
if (callback) {
|
|
eve.once("snap.ajax." + id + ".0", callback);
|
|
eve.once("snap.ajax." + id + ".200", callback);
|
|
eve.once("snap.ajax." + id + ".304", callback);
|
|
}
|
|
req.onreadystatechange = function() {
|
|
if (req.readyState != 4) return;
|
|
eve("snap.ajax." + id + "." + req.status, scope, req);
|
|
};
|
|
if (req.readyState == 4) {
|
|
return req;
|
|
}
|
|
req.send(postData);
|
|
return req;
|
|
}
|
|
};
|
|
/*\
|
|
* Snap.load
|
|
[ method ]
|
|
**
|
|
* Loads external SVG file as a @Fragment (see @Snap.ajax for more advanced AJAX)
|
|
**
|
|
- url (string) URL
|
|
- callback (function) callback
|
|
- scope (object) #optional scope of callback
|
|
\*/
|
|
Snap.load = function (url, callback, scope) {
|
|
Snap.ajax(url, function (req) {
|
|
var f = Snap.parse(req.responseText);
|
|
scope ? callback.call(scope, f) : callback(f);
|
|
});
|
|
};
|
|
var getOffset = function (elem) {
|
|
var box = elem.getBoundingClientRect(),
|
|
doc = elem.ownerDocument,
|
|
body = doc.body,
|
|
docElem = doc.documentElement,
|
|
clientTop = docElem.clientTop || body.clientTop || 0, clientLeft = docElem.clientLeft || body.clientLeft || 0,
|
|
top = box.top + (g.win.pageYOffset || docElem.scrollTop || body.scrollTop ) - clientTop,
|
|
left = box.left + (g.win.pageXOffset || docElem.scrollLeft || body.scrollLeft) - clientLeft;
|
|
return {
|
|
y: top,
|
|
x: left
|
|
};
|
|
};
|
|
/*\
|
|
* Snap.getElementByPoint
|
|
[ method ]
|
|
**
|
|
* Returns you topmost element under given point.
|
|
**
|
|
= (object) Snap element object
|
|
- x (number) x coordinate from the top left corner of the window
|
|
- y (number) y coordinate from the top left corner of the window
|
|
> Usage
|
|
| Snap.getElementByPoint(mouseX, mouseY).attr({stroke: "#f00"});
|
|
\*/
|
|
Snap.getElementByPoint = function (x, y) {
|
|
var paper = this,
|
|
svg = paper.canvas,
|
|
target = glob.doc.elementFromPoint(x, y);
|
|
if (glob.win.opera && target.tagName == "svg") {
|
|
var so = getOffset(target),
|
|
sr = target.createSVGRect();
|
|
sr.x = x - so.x;
|
|
sr.y = y - so.y;
|
|
sr.width = sr.height = 1;
|
|
var hits = target.getIntersectionList(sr, null);
|
|
if (hits.length) {
|
|
target = hits[hits.length - 1];
|
|
}
|
|
}
|
|
if (!target) {
|
|
return null;
|
|
}
|
|
return wrap(target);
|
|
};
|
|
/*\
|
|
* Snap.plugin
|
|
[ method ]
|
|
**
|
|
* Let you write plugins. You pass in a function with five arguments, like this:
|
|
| Snap.plugin(function (Snap, Element, Paper, global, Fragment) {
|
|
| Snap.newmethod = function () {};
|
|
| Element.prototype.newmethod = function () {};
|
|
| Paper.prototype.newmethod = function () {};
|
|
| });
|
|
* Inside the function you have access to all main objects (and their
|
|
* prototypes). This allow you to extend anything you want.
|
|
**
|
|
- f (function) your plugin body
|
|
\*/
|
|
Snap.plugin = function (f) {
|
|
f(Snap, Element, Paper, glob, Fragment);
|
|
};
|
|
glob.win.Snap = Snap;
|
|
return Snap;
|
|
}(window || this));
|