snapsvg/src/svg.js

1985 lines
49 KiB
JavaScript
Raw Normal View History

2024-03-05 13:02:24 +08:00
// Copyright (c) 2013 - 2017 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.
import eve from './eve.js'
/*\
* 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
\*/
export 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) {
try {
w = glob.doc.querySelector(String(w))
return wrap(w)
} catch (e) {
return null
}
}
}
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: window,
doc: 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\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,
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]*)+)/gi,
tCommand = /([rstm])[\s,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\s]*,?[\s]*)+)/gi,
pathValues = /(-?\d*\.?\d*(?:e[\-+]?\d+)?)[\s]*,?[\s]*/gi,
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 = {},
/*\
* Snap.url
[ method ]
**
* Wraps path into `"url('<path>')"`.
- value (string) path
= (string) wrapped path
\*/
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.
- x (number) value
- y (number) value
= (number) atan2 in degrees
\*/
Snap.atan2 = function (x, y) {
return Snap.deg(math.atan2(x, y))
}
/*\
* Snap.angle
[ method ]
**
* Returns an angle between two or three points
- 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
- 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
- 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.
- 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) * 0.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 *= 0.5
while (precision > 0.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 *= 0.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,&nbsp;100,&nbsp;0)</code>)</li>
# <li>rgba(•••, •••, •••, •••) — also with opacity</li>
# <li>rgb(•••%, •••%, •••%) — same as above, but in %: (<code>rgb(100%,&nbsp;175%,&nbsp;0%)</code>)</li>
# <li>rgba(•••%, •••%, •••%, •••%) — also with opacity</li>
# <li>hsb(•••, •••, •••) — hue, saturation and brightness values: (<code>hsb(0.5,&nbsp;0.25,&nbsp;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,&nbsp;0.25,&nbsp;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 < 0.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 < 0.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 && w.tagName.toLowerCase() == '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)
}