2024-03-08 14:05:08 +08:00
|
|
|
|
/**
|
|
|
|
|
* {}
|
|
|
|
|
* @author yutent<yutent.io@gmail.com>
|
|
|
|
|
* @date 2024/03/08 10:59:09
|
|
|
|
|
*/
|
|
|
|
|
|
2024-03-05 13:02:24 +08:00
|
|
|
|
import eve from './eve.js'
|
|
|
|
|
|
2024-03-06 18:36:03 +08:00
|
|
|
|
import {
|
|
|
|
|
doc,
|
|
|
|
|
win,
|
|
|
|
|
CSS_ATTR,
|
|
|
|
|
xmlns,
|
|
|
|
|
T_COMMAND,
|
|
|
|
|
PATH_VALUES
|
|
|
|
|
} from './lib/constants.js'
|
|
|
|
|
|
2024-03-07 15:42:22 +08:00
|
|
|
|
import { Matrix } from './matrix.js'
|
|
|
|
|
|
2024-03-06 18:36:03 +08:00
|
|
|
|
import {
|
|
|
|
|
$,
|
|
|
|
|
is,
|
|
|
|
|
uuid,
|
|
|
|
|
clone,
|
|
|
|
|
preload,
|
|
|
|
|
cacher,
|
|
|
|
|
jsonFiller,
|
|
|
|
|
extend
|
|
|
|
|
} from './utils.js'
|
|
|
|
|
|
|
|
|
|
let has = 'hasOwnProperty',
|
2024-03-05 13:02:24 +08:00
|
|
|
|
math = Math,
|
|
|
|
|
abs = math.abs,
|
|
|
|
|
E = '',
|
|
|
|
|
S = ' ',
|
|
|
|
|
objectToString = Object.prototype.toString,
|
2024-03-06 18:36:03 +08:00
|
|
|
|
hub = {}
|
2024-03-05 13:02:24 +08:00
|
|
|
|
|
2024-03-06 18:36:03 +08:00
|
|
|
|
export function make(name, parent) {
|
|
|
|
|
let res = $(name)
|
|
|
|
|
parent.appendChild(res)
|
|
|
|
|
let el = wrap(res)
|
2024-03-05 13:02:24 +08:00
|
|
|
|
return el
|
|
|
|
|
}
|
2024-03-06 18:36:03 +08:00
|
|
|
|
|
|
|
|
|
export function wrap(dom) {
|
|
|
|
|
if (!dom) {
|
|
|
|
|
return dom
|
2024-03-05 13:02:24 +08:00
|
|
|
|
}
|
2024-03-06 18:36:03 +08:00
|
|
|
|
if (dom instanceof SnapElement || dom instanceof Fragment) {
|
|
|
|
|
return dom
|
|
|
|
|
}
|
|
|
|
|
if (dom.tagName.toLowerCase() === 'svg') {
|
|
|
|
|
return new Paper(dom)
|
2024-03-05 13:02:24 +08:00
|
|
|
|
}
|
|
|
|
|
if (
|
2024-03-06 18:36:03 +08:00
|
|
|
|
dom.tagName &&
|
|
|
|
|
dom.tagName.toLowerCase() == 'object' &&
|
|
|
|
|
dom.type == 'image/svg+xml'
|
2024-03-05 13:02:24 +08:00
|
|
|
|
) {
|
2024-03-06 18:36:03 +08:00
|
|
|
|
return new Paper(dom.contentDocument.getElementsByTagName('svg')[0])
|
2024-03-05 13:02:24 +08:00
|
|
|
|
}
|
2024-03-06 18:36:03 +08:00
|
|
|
|
return new SnapElement(dom)
|
2024-03-05 13:02:24 +08:00
|
|
|
|
}
|
2024-03-06 18:36:03 +08:00
|
|
|
|
|
2024-03-07 16:04:37 +08:00
|
|
|
|
/*\
|
|
|
|
|
**
|
|
|
|
|
* Wraps a DOM element specified by CSS selector as @SnapElement
|
|
|
|
|
- query (string) CSS selector of the element
|
|
|
|
|
= (SnapElement) the current element
|
|
|
|
|
\*/
|
|
|
|
|
export function select(query = '') {
|
|
|
|
|
query = query.replace(/([^\\]):/g, '$1\\:')
|
|
|
|
|
return wrap(doc.querySelector(query))
|
|
|
|
|
}
|
|
|
|
|
/*\
|
|
|
|
|
|
|
|
|
|
**
|
|
|
|
|
* Wraps DOM elements specified by CSS selector as set or array of @SnapElement
|
|
|
|
|
- query (string) CSS selector of the element
|
|
|
|
|
= (SnapElement) the current element
|
|
|
|
|
\*/
|
|
|
|
|
export function selectAll(query = '') {
|
|
|
|
|
let nodelist = doc.querySelectorAll(query),
|
|
|
|
|
set = []
|
|
|
|
|
for (let i = 0; i < nodelist.length; i++) {
|
|
|
|
|
set.push(wrap(nodelist[i]))
|
|
|
|
|
}
|
|
|
|
|
return set
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-06 18:36:03 +08:00
|
|
|
|
export class Fragment {
|
|
|
|
|
constructor(frag) {
|
|
|
|
|
this.node = frag
|
2024-03-05 13:02:24 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-06 18:36:03 +08:00
|
|
|
|
export class Snap {
|
|
|
|
|
static _ = { $ }
|
|
|
|
|
|
2024-03-08 14:05:08 +08:00
|
|
|
|
/*
|
|
|
|
|
**
|
|
|
|
|
* Parses SVG fragment and converts it into a @Fragment
|
|
|
|
|
**
|
|
|
|
|
*/
|
2024-03-06 18:36:03 +08:00
|
|
|
|
static parse(svg) {
|
|
|
|
|
let f = doc.createDocumentFragment(),
|
|
|
|
|
full = true,
|
|
|
|
|
div = doc.createElement('div')
|
|
|
|
|
svg = String(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)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*\
|
2024-03-05 13:02:24 +08:00
|
|
|
|
**
|
2024-03-06 18:36:03 +08:00
|
|
|
|
* Creates a DOM fragment from a given list of elements or strings
|
2024-03-05 13:02:24 +08:00
|
|
|
|
**
|
2024-03-06 18:36:03 +08:00
|
|
|
|
- varargs (…) SVG string
|
2024-03-05 13:02:24 +08:00
|
|
|
|
\*/
|
2024-03-06 18:36:03 +08:00
|
|
|
|
static fragment(...args) {
|
|
|
|
|
let f = doc.createDocumentFragment()
|
|
|
|
|
for (let i = 0, ii = args.length; i < ii; i++) {
|
|
|
|
|
let 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)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*\
|
2024-03-05 13:02:24 +08:00
|
|
|
|
**
|
|
|
|
|
* Returns closest point to a given one on a given path.
|
2024-03-06 18:36:03 +08:00
|
|
|
|
- path (SnapElement) path element
|
2024-03-05 13:02:24 +08:00
|
|
|
|
- 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
|
|
|
|
|
}
|
|
|
|
|
\*/
|
2024-03-06 18:36:03 +08:00
|
|
|
|
// Copied from http://bl.ocks.org/mbostock/8027637
|
|
|
|
|
static closestPoint(path, x, y) {
|
|
|
|
|
function distance2(p) {
|
|
|
|
|
let dx = p.x - x,
|
|
|
|
|
dy = p.y - y
|
|
|
|
|
return dx * dx + dy * dy
|
|
|
|
|
}
|
|
|
|
|
let pathNode = path.node,
|
|
|
|
|
pathLength = pathNode.getTotalLength(),
|
|
|
|
|
precision = (pathLength / pathNode.pathSegList.numberOfItems) * 0.125,
|
|
|
|
|
best,
|
|
|
|
|
bestLength,
|
|
|
|
|
bestDistance = Infinity
|
2024-03-05 13:02:24 +08:00
|
|
|
|
|
2024-03-06 18:36:03 +08:00
|
|
|
|
// linear scan for coarse approximation
|
|
|
|
|
for (
|
|
|
|
|
let scan, scanLength = 0, scanDistance;
|
|
|
|
|
scanLength <= pathLength;
|
|
|
|
|
scanLength += precision
|
2024-03-05 13:02:24 +08:00
|
|
|
|
) {
|
2024-03-06 18:36:03 +08:00
|
|
|
|
if (
|
|
|
|
|
(scanDistance = distance2(
|
|
|
|
|
(scan = pathNode.getPointAtLength(scanLength))
|
|
|
|
|
)) < bestDistance
|
|
|
|
|
) {
|
|
|
|
|
best = scan
|
|
|
|
|
bestLength = scanLength
|
|
|
|
|
bestDistance = scanDistance
|
|
|
|
|
}
|
2024-03-05 13:02:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
2024-03-06 18:36:03 +08:00
|
|
|
|
// binary search for precise estimate
|
|
|
|
|
precision *= 0.5
|
|
|
|
|
while (precision > 0.5) {
|
|
|
|
|
let 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
|
|
|
|
|
}
|
2024-03-05 13:02:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
2024-03-06 18:36:03 +08:00
|
|
|
|
best = {
|
|
|
|
|
x: best.x,
|
|
|
|
|
y: best.y,
|
|
|
|
|
length: bestLength,
|
|
|
|
|
distance: Math.sqrt(bestDistance)
|
|
|
|
|
}
|
|
|
|
|
return best
|
2024-03-05 13:02:24 +08:00
|
|
|
|
}
|
2024-03-06 18:36:03 +08:00
|
|
|
|
|
|
|
|
|
/*\
|
2024-03-05 13:02:24 +08:00
|
|
|
|
**
|
|
|
|
|
* 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
|
|
|
|
|
\*/
|
2024-03-06 18:36:03 +08:00
|
|
|
|
static snapTo(values, value, tolerance) {
|
|
|
|
|
tolerance = is(tolerance, 'finite') ? tolerance : 10
|
|
|
|
|
if (is(values, 'array')) {
|
|
|
|
|
let i = values.length
|
|
|
|
|
while (i--)
|
|
|
|
|
if (abs(values[i] - value) <= tolerance) {
|
|
|
|
|
return values[i]
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
values = +values
|
|
|
|
|
let rem = value % values
|
|
|
|
|
if (rem < tolerance) {
|
|
|
|
|
return value - rem
|
|
|
|
|
}
|
|
|
|
|
if (rem > values - tolerance) {
|
|
|
|
|
return value - rem + values
|
2024-03-05 13:02:24 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
2024-03-06 18:36:03 +08:00
|
|
|
|
return value
|
2024-03-05 13:02:24 +08:00
|
|
|
|
}
|
2024-03-06 18:36:03 +08:00
|
|
|
|
|
2024-03-08 14:05:08 +08:00
|
|
|
|
/*
|
2024-03-05 13:02:24 +08:00
|
|
|
|
**
|
2024-03-06 18:36:03 +08:00
|
|
|
|
* Returns you topmost element under given point.
|
2024-03-05 13:02:24 +08:00
|
|
|
|
**
|
2024-03-06 18:36:03 +08:00
|
|
|
|
= (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"});
|
2024-03-08 14:05:08 +08:00
|
|
|
|
*/
|
2024-03-06 18:36:03 +08:00
|
|
|
|
static getElementByPoint(x, y) {
|
2024-03-08 14:05:08 +08:00
|
|
|
|
let target = doc.elementFromPoint(x, y)
|
2024-03-05 13:02:24 +08:00
|
|
|
|
|
2024-03-06 18:36:03 +08:00
|
|
|
|
if (!target) {
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
return wrap(target)
|
|
|
|
|
}
|
2024-03-05 13:02:24 +08:00
|
|
|
|
|
2024-03-06 18:36:03 +08:00
|
|
|
|
constructor(w, h) {
|
|
|
|
|
if (w) {
|
|
|
|
|
if (w.nodeType) {
|
|
|
|
|
return wrap(w)
|
|
|
|
|
}
|
2024-03-07 15:42:22 +08:00
|
|
|
|
|
2024-03-06 18:36:03 +08:00
|
|
|
|
if (w instanceof SnapElement) {
|
|
|
|
|
return w
|
|
|
|
|
}
|
|
|
|
|
if (h == null) {
|
|
|
|
|
try {
|
|
|
|
|
w = 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)
|
|
|
|
|
}
|
2024-03-05 13:02:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Transformations
|
|
|
|
|
/*\
|
|
|
|
|
**
|
|
|
|
|
* 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
|
|
|
|
|
}
|
2024-03-06 18:36:03 +08:00
|
|
|
|
let pth = Snap.path(pathString)
|
2024-03-05 13:02:24 +08:00
|
|
|
|
if (pth.arr) {
|
|
|
|
|
return Snap.path.clone(pth.arr)
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-06 18:36:03 +08:00
|
|
|
|
let paramCounts = {
|
2024-03-05 13:02:24 +08:00
|
|
|
|
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) {
|
2024-03-06 18:36:03 +08:00
|
|
|
|
String(pathString).replace(pathCommand, function (a, b, c) {
|
|
|
|
|
let params = [],
|
2024-03-05 13:02:24 +08:00
|
|
|
|
name = b.toLowerCase()
|
2024-03-06 18:36:03 +08:00
|
|
|
|
c.replace(PATH_VALUES, function (a, b) {
|
2024-03-05 13:02:24 +08:00
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
/*\
|
|
|
|
|
**
|
|
|
|
|
* 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
|
|
|
|
|
\*/
|
2024-03-06 18:36:03 +08:00
|
|
|
|
let parseTransformString = (Snap.parseTransformString = function (TString) {
|
2024-03-05 13:02:24 +08:00
|
|
|
|
if (!TString) {
|
|
|
|
|
return null
|
|
|
|
|
}
|
2024-03-06 18:36:03 +08:00
|
|
|
|
let paramCounts = { r: 3, s: 4, t: 2, m: 6 },
|
2024-03-05 13:02:24 +08:00
|
|
|
|
data = []
|
|
|
|
|
if (is(TString, 'array') && is(TString[0], 'array')) {
|
|
|
|
|
// rough assumption
|
|
|
|
|
data = Snap.path.clone(TString)
|
|
|
|
|
}
|
|
|
|
|
if (!data.length) {
|
2024-03-06 18:36:03 +08:00
|
|
|
|
String(TString).replace(T_COMMAND, function (a, b, c) {
|
|
|
|
|
let params = [],
|
2024-03-05 13:02:24 +08:00
|
|
|
|
name = b.toLowerCase()
|
2024-03-06 18:36:03 +08:00
|
|
|
|
c.replace(PATH_VALUES, function (a, b) {
|
2024-03-05 13:02:24 +08:00
|
|
|
|
b && params.push(+b)
|
|
|
|
|
})
|
|
|
|
|
data.push([b].concat(params))
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
data.toString = Snap.path.toString
|
|
|
|
|
return data
|
|
|
|
|
})
|
|
|
|
|
function svgTransform2string(tstr) {
|
2024-03-06 18:36:03 +08:00
|
|
|
|
let res = []
|
2024-03-05 13:02:24 +08:00
|
|
|
|
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
|
2024-03-06 18:36:03 +08:00
|
|
|
|
|
2024-03-05 13:02:24 +08:00
|
|
|
|
function transform2matrix(tstr, bbox) {
|
2024-03-06 18:36:03 +08:00
|
|
|
|
let tdata = parseTransformString(tstr),
|
2024-03-07 15:42:22 +08:00
|
|
|
|
m = new Matrix()
|
2024-03-05 13:02:24 +08:00
|
|
|
|
if (tdata) {
|
2024-03-06 18:36:03 +08:00
|
|
|
|
for (let i = 0, ii = tdata.length; i < ii; i++) {
|
|
|
|
|
let t = tdata[i],
|
2024-03-05 13:02:24 +08:00
|
|
|
|
tlen = t.length,
|
2024-03-06 18:36:03 +08:00
|
|
|
|
command = String(t[0]).toLowerCase(),
|
2024-03-05 13:02:24 +08:00
|
|
|
|
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
|
|
|
|
|
}
|
2024-03-06 18:36:03 +08:00
|
|
|
|
|
2024-03-05 13:02:24 +08:00
|
|
|
|
Snap._.transform2matrix = transform2matrix
|
2024-03-06 18:36:03 +08:00
|
|
|
|
|
|
|
|
|
export function getSomeDefs(el) {
|
|
|
|
|
let p =
|
2024-03-05 13:02:24 +08:00
|
|
|
|
(el.node.ownerSVGElement && wrap(el.node.ownerSVGElement)) ||
|
|
|
|
|
(el.node.parentNode && wrap(el.node.parentNode)) ||
|
2024-03-07 16:04:37 +08:00
|
|
|
|
select('svg') ||
|
2024-03-06 18:36:03 +08:00
|
|
|
|
new Snap(0, 0),
|
2024-03-05 13:02:24 +08:00
|
|
|
|
pdefs = p.select('defs'),
|
|
|
|
|
defs = pdefs == null ? false : pdefs.node
|
|
|
|
|
if (!defs) {
|
|
|
|
|
defs = make('defs', p.node).node
|
|
|
|
|
}
|
|
|
|
|
return defs
|
|
|
|
|
}
|
2024-03-06 18:36:03 +08:00
|
|
|
|
|
2024-03-05 13:02:24 +08:00
|
|
|
|
function getSomeSVG(el) {
|
|
|
|
|
return (
|
2024-03-07 16:04:37 +08:00
|
|
|
|
(el.node.ownerSVGElement && wrap(el.node.ownerSVGElement)) || select('svg')
|
2024-03-05 13:02:24 +08:00
|
|
|
|
)
|
|
|
|
|
}
|
2024-03-06 18:36:03 +08:00
|
|
|
|
|
|
|
|
|
export function unit2px(el, name, value) {
|
|
|
|
|
let svg = getSomeSVG(el).node,
|
2024-03-05 13:02:24 +08:00
|
|
|
|
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)
|
|
|
|
|
}
|
2024-03-06 18:36:03 +08:00
|
|
|
|
svg.removeChild(mgr)
|
|
|
|
|
return out
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function add2group(list) {
|
|
|
|
|
if (!is(list, 'array')) {
|
|
|
|
|
list = Array.prototype.slice.call(arguments, 0)
|
|
|
|
|
}
|
|
|
|
|
let 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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let 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 (let key in hub)
|
|
|
|
|
if (hub[has](key)) {
|
|
|
|
|
let 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)
|
|
|
|
|
|
|
|
|
|
export class Paper {
|
|
|
|
|
constructor(w, h) {
|
|
|
|
|
let res, desc, defs
|
|
|
|
|
if (w && w.tagName && w.tagName.toLowerCase() == 'svg') {
|
|
|
|
|
if (w.snap in hub) {
|
|
|
|
|
return hub[w.snap]
|
|
|
|
|
}
|
|
|
|
|
let doc = w.ownerDocument
|
|
|
|
|
res = new SnapElement(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
|
|
|
|
|
extend(res, Paper.prototype)
|
|
|
|
|
res.paper = res.root = res
|
|
|
|
|
} else {
|
|
|
|
|
res = make('svg', doc.body)
|
|
|
|
|
$(res.node, {
|
|
|
|
|
width: w,
|
|
|
|
|
height: h,
|
|
|
|
|
version: 1.1,
|
|
|
|
|
xmlns
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
return res
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*\
|
|
|
|
|
**
|
|
|
|
|
* Creates a nested SVG element.
|
|
|
|
|
- x (number) @optional X of the element
|
|
|
|
|
- y (number) @optional Y of the element
|
|
|
|
|
- width (number) @optional width of the element
|
|
|
|
|
- height (number) @optional height of the element
|
|
|
|
|
- vbx (number) @optional viewbox X
|
|
|
|
|
- vby (number) @optional viewbox Y
|
|
|
|
|
- vbw (number) @optional viewbox width
|
|
|
|
|
- vbh (number) @optional viewbox height
|
|
|
|
|
**
|
|
|
|
|
= (object) the `svg` element
|
|
|
|
|
**
|
|
|
|
|
\*/
|
|
|
|
|
svg(x, y, width, height, vbx, vby, vbw, vbh) {
|
|
|
|
|
let attrs = {}
|
|
|
|
|
if (is(x, 'object') && y == null) {
|
|
|
|
|
attrs = x
|
|
|
|
|
} else {
|
|
|
|
|
if (x != null) {
|
|
|
|
|
attrs.x = x
|
|
|
|
|
}
|
|
|
|
|
if (y != null) {
|
|
|
|
|
attrs.y = y
|
|
|
|
|
}
|
|
|
|
|
if (width != null) {
|
|
|
|
|
attrs.width = width
|
|
|
|
|
}
|
|
|
|
|
if (height != null) {
|
|
|
|
|
attrs.height = height
|
|
|
|
|
}
|
|
|
|
|
if (vbx != null && vby != null && vbw != null && vbh != null) {
|
|
|
|
|
attrs.viewBox = [vbx, vby, vbw, vbh]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return this.el('svg', attrs)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mask(first) {
|
|
|
|
|
let attr,
|
|
|
|
|
el = this.el('mask')
|
|
|
|
|
if (arguments.length == 1 && first && !first.type) {
|
|
|
|
|
el.attr(first)
|
|
|
|
|
} else if (arguments.length) {
|
|
|
|
|
el.add(Array.from(arguments))
|
|
|
|
|
}
|
|
|
|
|
return el
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*\
|
|
|
|
|
**
|
|
|
|
|
* Equivalent in behaviour to @Paper.g, except it’s a pattern.
|
|
|
|
|
- x (number) @optional X of the element
|
|
|
|
|
- y (number) @optional Y of the element
|
|
|
|
|
- width (number) @optional width of the element
|
|
|
|
|
- height (number) @optional height of the element
|
|
|
|
|
- vbx (number) @optional viewbox X
|
|
|
|
|
- vby (number) @optional viewbox Y
|
|
|
|
|
- vbw (number) @optional viewbox width
|
|
|
|
|
- vbh (number) @optional viewbox height
|
|
|
|
|
**
|
|
|
|
|
= (object) the `pattern` element
|
|
|
|
|
**
|
|
|
|
|
\*/
|
|
|
|
|
ptrn(x, y, width, height, vx, vy, vw, vh) {
|
|
|
|
|
if (is(x, 'object')) {
|
|
|
|
|
let attr = x
|
|
|
|
|
} else {
|
|
|
|
|
attr = { patternUnits: 'userSpaceOnUse' }
|
|
|
|
|
if (x) {
|
|
|
|
|
attr.x = x
|
|
|
|
|
}
|
|
|
|
|
if (y) {
|
|
|
|
|
attr.y = y
|
|
|
|
|
}
|
|
|
|
|
if (width != null) {
|
|
|
|
|
attr.width = width
|
|
|
|
|
}
|
|
|
|
|
if (height != null) {
|
|
|
|
|
attr.height = height
|
|
|
|
|
}
|
|
|
|
|
if (vx != null && vy != null && vw != null && vh != null) {
|
|
|
|
|
attr.viewBox = [vx, vy, vw, vh]
|
|
|
|
|
} else {
|
|
|
|
|
attr.viewBox = [x || 0, y || 0, width || 0, height || 0]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return this.el('pattern', attr)
|
|
|
|
|
}
|
|
|
|
|
/*\
|
|
|
|
|
**
|
|
|
|
|
* Creates a <use> element.
|
|
|
|
|
- id (string) @optional id of element to link
|
|
|
|
|
* or
|
|
|
|
|
- id (SnapElement) @optional element to link
|
|
|
|
|
**
|
|
|
|
|
= (object) the `use` element
|
|
|
|
|
**
|
|
|
|
|
\*/
|
|
|
|
|
use(id) {
|
|
|
|
|
if (id != null) {
|
|
|
|
|
if (id instanceof SnapElement) {
|
|
|
|
|
if (!id.attr('id')) {
|
|
|
|
|
id.attr({ id: uuid(id.type + 'S') })
|
|
|
|
|
}
|
|
|
|
|
id = id.attr('id')
|
|
|
|
|
}
|
|
|
|
|
if (String(id).charAt() == '#') {
|
|
|
|
|
id = id.substring(1)
|
|
|
|
|
}
|
|
|
|
|
return this.el('use', { 'xlink:href': '#' + id })
|
|
|
|
|
} else {
|
|
|
|
|
return SnapElement.prototype.use.call(this)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/*\
|
|
|
|
|
**
|
|
|
|
|
* Creates a <symbol> element.
|
|
|
|
|
- vbx (number) @optional viewbox X
|
|
|
|
|
- vby (number) @optional viewbox Y
|
|
|
|
|
- vbw (number) @optional viewbox width
|
|
|
|
|
- vbh (number) @optional viewbox height
|
|
|
|
|
= (object) the `symbol` element
|
|
|
|
|
**
|
|
|
|
|
\*/
|
|
|
|
|
symbol(vx, vy, vw, vh) {
|
|
|
|
|
let attr = {}
|
|
|
|
|
if (vx != null && vy != null && vw != null && vh != null) {
|
|
|
|
|
attr.viewBox = [vx, vy, vw, vh]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return this.el('symbol', attr)
|
|
|
|
|
}
|
|
|
|
|
/*\
|
|
|
|
|
**
|
|
|
|
|
* Draws a text string
|
|
|
|
|
**
|
|
|
|
|
- x (number) x coordinate position
|
|
|
|
|
- y (number) y coordinate position
|
|
|
|
|
- text (string|array) The text string to draw or array of strings to nest within separate `<tspan>` elements
|
|
|
|
|
= (object) the `text` element
|
|
|
|
|
**
|
|
|
|
|
> Usage
|
|
|
|
|
| let t1 = paper.text(50, 50, "Snap");
|
|
|
|
|
| let t2 = paper.text(50, 50, ["S","n","a","p"]);
|
|
|
|
|
| // Text path usage
|
|
|
|
|
| t1.attr({textpath: "M10,10L100,100"});
|
|
|
|
|
| // or
|
|
|
|
|
| let pth = paper.path("M10,10L100,100");
|
|
|
|
|
| t1.attr({textpath: pth});
|
|
|
|
|
\*/
|
|
|
|
|
text(x, y, text) {
|
|
|
|
|
let attr = {}
|
|
|
|
|
if (is(x, 'object')) {
|
|
|
|
|
attr = x
|
|
|
|
|
} else if (x != null) {
|
|
|
|
|
attr = {
|
|
|
|
|
x: x,
|
|
|
|
|
y: y,
|
|
|
|
|
text: text || ''
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return this.el('text', attr)
|
|
|
|
|
}
|
|
|
|
|
/*\
|
|
|
|
|
**
|
|
|
|
|
* Draws a line
|
|
|
|
|
**
|
|
|
|
|
- x1 (number) x coordinate position of the start
|
|
|
|
|
- y1 (number) y coordinate position of the start
|
|
|
|
|
- x2 (number) x coordinate position of the end
|
|
|
|
|
- y2 (number) y coordinate position of the end
|
|
|
|
|
= (object) the `line` element
|
|
|
|
|
**
|
|
|
|
|
> Usage
|
|
|
|
|
| let t1 = paper.line(50, 50, 100, 100);
|
|
|
|
|
\*/
|
|
|
|
|
line(x1, y1, x2, y2) {
|
|
|
|
|
let attr = {}
|
|
|
|
|
if (is(x1, 'object')) {
|
|
|
|
|
attr = x1
|
|
|
|
|
} else if (x1 != null) {
|
|
|
|
|
attr = {
|
|
|
|
|
x1: x1,
|
|
|
|
|
x2: x2,
|
|
|
|
|
y1: y1,
|
|
|
|
|
y2: y2
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return this.el('line', attr)
|
|
|
|
|
}
|
|
|
|
|
/*\
|
|
|
|
|
**
|
|
|
|
|
* Draws a polyline
|
|
|
|
|
**
|
|
|
|
|
- points (array) array of points
|
|
|
|
|
* or
|
|
|
|
|
- varargs (…) points
|
|
|
|
|
= (object) the `polyline` element
|
|
|
|
|
**
|
|
|
|
|
> Usage
|
|
|
|
|
| let p1 = paper.polyline([10, 10, 100, 100]);
|
|
|
|
|
| let p2 = paper.polyline(10, 10, 100, 100);
|
|
|
|
|
\*/
|
|
|
|
|
polyline(points) {
|
|
|
|
|
if (arguments.length > 1) {
|
|
|
|
|
points = Array.from(arguments)
|
|
|
|
|
}
|
|
|
|
|
let attr = {}
|
|
|
|
|
if (is(points, 'object') && !is(points, 'array')) {
|
|
|
|
|
attr = points
|
|
|
|
|
} else if (points != null) {
|
|
|
|
|
attr = { points: points }
|
|
|
|
|
}
|
|
|
|
|
return this.el('polyline', attr)
|
|
|
|
|
}
|
|
|
|
|
/*\
|
|
|
|
|
**
|
|
|
|
|
* Draws a polygon. See @Paper.polyline
|
|
|
|
|
\*/
|
|
|
|
|
polygon(points) {
|
|
|
|
|
if (arguments.length > 1) {
|
|
|
|
|
points = Array.prototype.slice.call(arguments, 0)
|
|
|
|
|
}
|
|
|
|
|
let attr = {}
|
|
|
|
|
if (is(points, 'object') && !is(points, 'array')) {
|
|
|
|
|
attr = points
|
|
|
|
|
} else if (points != null) {
|
|
|
|
|
attr = { points: points }
|
|
|
|
|
}
|
|
|
|
|
return this.el('polygon', attr)
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-08 14:05:08 +08:00
|
|
|
|
/*
|
2024-03-05 13:02:24 +08:00
|
|
|
|
**
|
2024-03-06 18:36:03 +08:00
|
|
|
|
* Creates an element on paper with a given name and no attributes
|
2024-03-05 13:02:24 +08:00
|
|
|
|
**
|
2024-03-06 18:36:03 +08:00
|
|
|
|
- name (string) tag name
|
|
|
|
|
- attr (object) attributes
|
|
|
|
|
= (SnapElement) the current element
|
|
|
|
|
> Usage
|
|
|
|
|
| let c = paper.circle(10, 10, 10); // is the same as...
|
|
|
|
|
| let c = paper.el("circle").attr({
|
|
|
|
|
| cx: 10,
|
|
|
|
|
| cy: 10,
|
|
|
|
|
| r: 10
|
|
|
|
|
| });
|
|
|
|
|
| // and the same as
|
|
|
|
|
| let c = paper.el("circle", {
|
|
|
|
|
| cx: 10,
|
|
|
|
|
| cy: 10,
|
|
|
|
|
| r: 10
|
|
|
|
|
| });
|
2024-03-08 14:05:08 +08:00
|
|
|
|
*/
|
2024-03-06 18:36:03 +08:00
|
|
|
|
el(name, attr) {
|
|
|
|
|
let el = make(name, this.node)
|
|
|
|
|
attr && el.attr(attr)
|
|
|
|
|
return el
|
2024-03-05 13:02:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
2024-03-08 14:05:08 +08:00
|
|
|
|
/*
|
|
|
|
|
*
|
|
|
|
|
* Draws a rectangle
|
|
|
|
|
**
|
|
|
|
|
- x (number) x coordinate of the top left corner
|
|
|
|
|
- y (number) y coordinate of the top left corner
|
|
|
|
|
- width (number) width
|
|
|
|
|
- height (number) height
|
|
|
|
|
- rx (number) #optional horizontal radius for rounded corners, default is 0
|
|
|
|
|
- ry (number) #optional vertical radius for rounded corners, default is rx or 0
|
|
|
|
|
= (object) the `rect` element
|
|
|
|
|
**
|
|
|
|
|
> Usage
|
|
|
|
|
| // regular rectangle
|
|
|
|
|
| let c = paper.rect(10, 10, 50, 50);
|
|
|
|
|
| // rectangle with rounded corners
|
|
|
|
|
| let c = paper.rect(40, 40, 50, 50, 10);
|
|
|
|
|
*/
|
2024-03-06 18:36:03 +08:00
|
|
|
|
rect(x, y, w, h, rx, ry) {
|
|
|
|
|
let attr
|
|
|
|
|
if (ry == null) {
|
|
|
|
|
ry = rx
|
2024-03-05 13:02:24 +08:00
|
|
|
|
}
|
2024-03-06 18:36:03 +08:00
|
|
|
|
if (is(x, 'object') && x == '[object Object]') {
|
|
|
|
|
attr = x
|
|
|
|
|
} else if (x != null) {
|
|
|
|
|
attr = {
|
|
|
|
|
x: x,
|
|
|
|
|
y: y,
|
|
|
|
|
width: w,
|
|
|
|
|
height: h
|
|
|
|
|
}
|
|
|
|
|
if (rx != null) {
|
|
|
|
|
attr.rx = rx
|
|
|
|
|
attr.ry = ry
|
2024-03-05 13:02:24 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
2024-03-06 18:36:03 +08:00
|
|
|
|
return this.el('rect', attr)
|
2024-03-05 13:02:24 +08:00
|
|
|
|
}
|
2024-03-06 18:36:03 +08:00
|
|
|
|
|
2024-03-05 13:02:24 +08:00
|
|
|
|
/*\
|
|
|
|
|
**
|
2024-03-06 18:36:03 +08:00
|
|
|
|
* Draws a circle
|
|
|
|
|
**
|
|
|
|
|
- x (number) x coordinate of the centre
|
|
|
|
|
- y (number) y coordinate of the centre
|
|
|
|
|
- r (number) radius
|
|
|
|
|
= (object) the `circle` element
|
|
|
|
|
**
|
2024-03-05 13:02:24 +08:00
|
|
|
|
> Usage
|
2024-03-06 18:36:03 +08:00
|
|
|
|
| let c = paper.circle(50, 50, 40);
|
2024-03-05 13:02:24 +08:00
|
|
|
|
\*/
|
2024-03-06 18:36:03 +08:00
|
|
|
|
circle(cx, cy, r) {
|
|
|
|
|
let attr
|
|
|
|
|
if (is(cx, 'object') && cx == '[object Object]') {
|
|
|
|
|
attr = cx
|
|
|
|
|
} else if (cx != null) {
|
|
|
|
|
attr = {
|
|
|
|
|
cx: cx,
|
|
|
|
|
cy: cy,
|
|
|
|
|
r: r
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return this.el('circle', attr)
|
2024-03-05 13:02:24 +08:00
|
|
|
|
}
|
2024-03-06 18:36:03 +08:00
|
|
|
|
|
2024-03-05 13:02:24 +08:00
|
|
|
|
/*\
|
|
|
|
|
**
|
2024-03-06 18:36:03 +08:00
|
|
|
|
* Places an image on the surface
|
|
|
|
|
**
|
|
|
|
|
- src (string) URI of the source image
|
|
|
|
|
- x (number) x offset position
|
|
|
|
|
- y (number) y offset position
|
|
|
|
|
- width (number) width of the image
|
|
|
|
|
- height (number) height of the image
|
|
|
|
|
= (object) the `image` element
|
|
|
|
|
* or
|
|
|
|
|
= (object) Snap element object with type `image`
|
|
|
|
|
**
|
|
|
|
|
> Usage
|
|
|
|
|
| let c = paper.image("apple.png", 10, 10, 80, 80);
|
2024-03-05 13:02:24 +08:00
|
|
|
|
\*/
|
2024-03-06 18:36:03 +08:00
|
|
|
|
image(src, x, y, width, height) {
|
|
|
|
|
let el = this.el('image')
|
|
|
|
|
if (is(src, 'object') && 'src' in src) {
|
|
|
|
|
el.attr(src)
|
|
|
|
|
} else if (src != null) {
|
|
|
|
|
let set = {
|
|
|
|
|
'xlink:href': src,
|
|
|
|
|
preserveAspectRatio: 'none'
|
|
|
|
|
}
|
|
|
|
|
if (x != null && y != null) {
|
|
|
|
|
set.x = x
|
|
|
|
|
set.y = y
|
|
|
|
|
}
|
|
|
|
|
if (width != null && height != null) {
|
|
|
|
|
set.width = width
|
|
|
|
|
set.height = height
|
|
|
|
|
} else {
|
|
|
|
|
preload(src, function () {
|
|
|
|
|
$(el.node, {
|
|
|
|
|
width: this.offsetWidth,
|
|
|
|
|
height: this.offsetHeight
|
|
|
|
|
})
|
|
|
|
|
})
|
2024-03-05 13:02:24 +08:00
|
|
|
|
}
|
2024-03-06 18:36:03 +08:00
|
|
|
|
$(el.node, set)
|
|
|
|
|
}
|
|
|
|
|
return el
|
2024-03-05 13:02:24 +08:00
|
|
|
|
}
|
2024-03-06 18:36:03 +08:00
|
|
|
|
|
|
|
|
|
/*\
|
2024-03-05 13:02:24 +08:00
|
|
|
|
**
|
2024-03-06 18:36:03 +08:00
|
|
|
|
* Draws an ellipse
|
|
|
|
|
**
|
|
|
|
|
- x (number) x coordinate of the centre
|
|
|
|
|
- y (number) y coordinate of the centre
|
|
|
|
|
- rx (number) horizontal radius
|
|
|
|
|
- ry (number) vertical radius
|
|
|
|
|
= (object) the `ellipse` element
|
2024-03-05 13:02:24 +08:00
|
|
|
|
**
|
|
|
|
|
> Usage
|
2024-03-06 18:36:03 +08:00
|
|
|
|
| let c = paper.ellipse(50, 50, 40, 20);
|
2024-03-05 13:02:24 +08:00
|
|
|
|
\*/
|
2024-03-06 18:36:03 +08:00
|
|
|
|
ellipse(cx, cy, rx, ry) {
|
|
|
|
|
let attr
|
|
|
|
|
if (is(cx, 'object') && cx == '[object Object]') {
|
|
|
|
|
attr = cx
|
|
|
|
|
} else if (cx != null) {
|
|
|
|
|
attr = {
|
|
|
|
|
cx: cx,
|
|
|
|
|
cy: cy,
|
|
|
|
|
rx: rx,
|
|
|
|
|
ry: ry
|
2024-03-05 13:02:24 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
2024-03-06 18:36:03 +08:00
|
|
|
|
return this.el('ellipse', attr)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
path(d) {
|
|
|
|
|
let attr
|
|
|
|
|
if (is(d, 'object') && !is(d, 'array')) {
|
|
|
|
|
attr = d
|
|
|
|
|
} else if (d) {
|
|
|
|
|
attr = { d: d }
|
2024-03-05 13:02:24 +08:00
|
|
|
|
}
|
2024-03-06 18:36:03 +08:00
|
|
|
|
return this.el('path', attr)
|
2024-03-05 13:02:24 +08:00
|
|
|
|
}
|
2024-03-06 18:36:03 +08:00
|
|
|
|
|
|
|
|
|
group(first) {
|
|
|
|
|
let attr,
|
|
|
|
|
el = this.el('g')
|
|
|
|
|
if (arguments.length == 1 && first && !first.type) {
|
|
|
|
|
el.attr(first)
|
|
|
|
|
} else if (arguments.length) {
|
|
|
|
|
el.add(Array.from(arguments))
|
2024-03-05 13:02:24 +08:00
|
|
|
|
}
|
2024-03-06 18:36:03 +08:00
|
|
|
|
return el
|
2024-03-05 13:02:24 +08:00
|
|
|
|
}
|
2024-03-06 18:36:03 +08:00
|
|
|
|
|
2024-03-08 14:05:08 +08:00
|
|
|
|
/*
|
|
|
|
|
**
|
|
|
|
|
* Creates a `<filter>` element
|
|
|
|
|
**
|
|
|
|
|
- filstr (string) SVG fragment of filter provided as a string
|
|
|
|
|
= (object) @SnapElement
|
|
|
|
|
* Note: It is recommended to use filters embedded into the page inside an empty SVG element.
|
|
|
|
|
> Usage
|
|
|
|
|
| let f = paper.filter('<feGaussianBlur stdDeviation="2"/>'),
|
|
|
|
|
| c = paper.circle(10, 10, 10).attr({
|
|
|
|
|
| filter: f
|
|
|
|
|
| });
|
|
|
|
|
*/
|
2024-03-06 18:36:03 +08:00
|
|
|
|
filter(filstr) {
|
|
|
|
|
let paper = this
|
|
|
|
|
if (paper.type !== 'svg') {
|
|
|
|
|
paper = paper.paper
|
2024-03-05 13:02:24 +08:00
|
|
|
|
}
|
2024-03-06 18:36:03 +08:00
|
|
|
|
let f = Snap.parse(String(filstr)),
|
|
|
|
|
id = uuid((this.type || '') + 'S'),
|
|
|
|
|
width = paper.node.offsetWidth,
|
|
|
|
|
height = paper.node.offsetHeight,
|
|
|
|
|
filter = $('filter')
|
|
|
|
|
$(filter, {
|
|
|
|
|
id: id,
|
|
|
|
|
filterUnits: 'userSpaceOnUse'
|
|
|
|
|
})
|
|
|
|
|
filter.appendChild(f.node)
|
|
|
|
|
paper.defs.appendChild(filter)
|
|
|
|
|
return new SnapElement(filter)
|
2024-03-05 13:02:24 +08:00
|
|
|
|
}
|
2024-03-06 18:36:03 +08:00
|
|
|
|
|
|
|
|
|
toString() {
|
|
|
|
|
let doc = this.node.ownerDocument,
|
|
|
|
|
f = doc.createDocumentFragment(),
|
|
|
|
|
d = doc.createElement('div'),
|
|
|
|
|
svg = this.node.cloneNode(true),
|
|
|
|
|
res
|
|
|
|
|
f.appendChild(d)
|
|
|
|
|
d.appendChild(svg)
|
|
|
|
|
$(svg, { xmlns: 'http://www.w3.org/2000/svg' })
|
|
|
|
|
res = d.innerHTML
|
|
|
|
|
return res
|
2024-03-05 13:02:24 +08:00
|
|
|
|
}
|
2024-03-06 18:36:03 +08:00
|
|
|
|
|
|
|
|
|
/*\
|
|
|
|
|
|
|
|
|
|
**
|
|
|
|
|
* Removes all child nodes of the paper, except <defs>.
|
|
|
|
|
*/
|
|
|
|
|
clear() {
|
|
|
|
|
let node = this.node.firstChild,
|
|
|
|
|
next
|
|
|
|
|
while (node) {
|
|
|
|
|
next = node.nextSibling
|
|
|
|
|
if (node.tagName != 'defs') {
|
|
|
|
|
node.parentNode.removeChild(node)
|
|
|
|
|
} else {
|
|
|
|
|
this.clear.call({ node: node })
|
2024-03-05 13:02:24 +08:00
|
|
|
|
}
|
2024-03-06 18:36:03 +08:00
|
|
|
|
node = next
|
2024-03-05 13:02:24 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-03-06 18:36:03 +08:00
|
|
|
|
|
|
|
|
|
export class SnapElement {
|
|
|
|
|
constructor(el) {
|
|
|
|
|
if (el.snap in hub) {
|
|
|
|
|
return hub[el.snap]
|
|
|
|
|
}
|
|
|
|
|
let svg
|
|
|
|
|
try {
|
|
|
|
|
svg = el.ownerSVGElement
|
|
|
|
|
} catch (e) {}
|
|
|
|
|
/*\
|
|
|
|
|
**
|
|
|
|
|
* 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
|
|
|
|
|
| let c = paper.circle(10, 10, 10);
|
|
|
|
|
| c.node.onclick = function () {
|
|
|
|
|
| c.attr("fill", "red");
|
|
|
|
|
| };
|
|
|
|
|
\*/
|
|
|
|
|
this.node = el
|
|
|
|
|
if (svg) {
|
|
|
|
|
this.paper = new Paper(svg)
|
|
|
|
|
}
|
|
|
|
|
/*\
|
|
|
|
|
* SnapElement.type
|
|
|
|
|
[ property (string) ]
|
|
|
|
|
**
|
|
|
|
|
* SVG tag name of the given element.
|
|
|
|
|
\*/
|
|
|
|
|
this.type = el.tagName || el.nodeName
|
|
|
|
|
let id = (this.id = uuid((this.type || '') + 'S'))
|
|
|
|
|
this.anims = {}
|
|
|
|
|
this._ = {
|
|
|
|
|
transform: []
|
2024-03-05 13:02:24 +08:00
|
|
|
|
}
|
2024-03-06 18:36:03 +08:00
|
|
|
|
el.snap = id
|
|
|
|
|
hub[id] = this
|
|
|
|
|
if (this.type == 'g') {
|
|
|
|
|
this.add = add2group
|
2024-03-05 13:02:24 +08:00
|
|
|
|
}
|
2024-03-06 18:36:03 +08:00
|
|
|
|
if (this.type in { g: 1, mask: 1, pattern: 1, symbol: 1 }) {
|
|
|
|
|
extend(this, Paper.prototype)
|
2024-03-05 13:02:24 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-06 18:36:03 +08:00
|
|
|
|
/*
|
|
|
|
|
**
|
|
|
|
|
* 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
|
|
|
|
|
= (SnapElement) 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"`.
|
|
|
|
|
*/
|
|
|
|
|
attr(params, value) {
|
|
|
|
|
let node = this.node
|
|
|
|
|
|
|
|
|
|
if (!params) {
|
|
|
|
|
if (node.nodeType != 1) {
|
|
|
|
|
return {
|
|
|
|
|
text: node.nodeValue
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let attr = node.attributes,
|
|
|
|
|
out = {}
|
|
|
|
|
for (let i = 0, ii = attr.length; i < ii; i++) {
|
|
|
|
|
out[attr[i].nodeName] = attr[i].nodeValue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return out
|
2024-03-05 13:02:24 +08:00
|
|
|
|
}
|
2024-03-06 18:36:03 +08:00
|
|
|
|
if (is(params, 'string')) {
|
|
|
|
|
if (value !== void 0) {
|
|
|
|
|
params = { [params]: value }
|
|
|
|
|
} else {
|
|
|
|
|
return eve('snap.util.getattr.' + params, this).firstDefined()
|
|
|
|
|
}
|
2024-03-05 13:02:24 +08:00
|
|
|
|
}
|
2024-03-06 18:36:03 +08:00
|
|
|
|
for (let att in params) {
|
|
|
|
|
eve('snap.util.attr.' + att, this, params[att])
|
2024-03-05 13:02:24 +08:00
|
|
|
|
}
|
2024-03-06 18:36:03 +08:00
|
|
|
|
return this
|
2024-03-05 13:02:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
2024-03-06 18:36:03 +08:00
|
|
|
|
/*
|
|
|
|
|
**
|
|
|
|
|
* Returns array of all the children of the element.
|
|
|
|
|
= (array) array of Elements
|
|
|
|
|
*/
|
|
|
|
|
children() {
|
|
|
|
|
let out = [],
|
|
|
|
|
ch = this.node.childNodes
|
|
|
|
|
for (let i = 0, ii = ch.length; i < ii; i++) {
|
|
|
|
|
out[i] = new Snap(ch[i])
|
2024-03-05 13:02:24 +08:00
|
|
|
|
}
|
2024-03-06 18:36:03 +08:00
|
|
|
|
return out
|
2024-03-05 13:02:24 +08:00
|
|
|
|
}
|
2024-03-06 18:36:03 +08:00
|
|
|
|
|
|
|
|
|
/*\
|
2024-03-05 13:02:24 +08:00
|
|
|
|
**
|
|
|
|
|
* 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 }
|
|
|
|
|
\*/
|
2024-03-06 18:36:03 +08:00
|
|
|
|
toJSON() {
|
|
|
|
|
let out = []
|
|
|
|
|
jsonFiller([this], out)
|
|
|
|
|
return out[0]
|
|
|
|
|
}
|
2024-03-07 14:38:37 +08:00
|
|
|
|
|
2024-03-07 15:42:22 +08:00
|
|
|
|
equal(name, b) {
|
|
|
|
|
return eve('snap.util.equal', this, name, b).firstDefined()
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-07 14:38:37 +08:00
|
|
|
|
addClass(value) {
|
|
|
|
|
this.node.classList.add(value)
|
|
|
|
|
return this
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
removeClass(value) {
|
|
|
|
|
this.node.classList.remove(value)
|
|
|
|
|
return this
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hasClass(value) {
|
|
|
|
|
return this.node.classList.contains(value)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
toggleClass(value, flag) {
|
|
|
|
|
this.node.classList.toggle(value, flag)
|
|
|
|
|
return this
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getAlign(way = 'center') {
|
|
|
|
|
let bx = this.paper.getBBox()
|
|
|
|
|
let bb = this.getBBox()
|
|
|
|
|
let out = {
|
|
|
|
|
toString() {
|
|
|
|
|
return 'T' + this.dx + ',' + this.dy
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (way.toLowerCase()) {
|
|
|
|
|
case 'top':
|
|
|
|
|
out.dx = 0
|
|
|
|
|
out.dy = bx.y - bb.y
|
|
|
|
|
break
|
|
|
|
|
case 'bottom':
|
|
|
|
|
out.dx = 0
|
|
|
|
|
out.dy = bx.y2 - bb.y2
|
|
|
|
|
break
|
|
|
|
|
case 'middle':
|
|
|
|
|
out.dx = 0
|
|
|
|
|
out.dy = bx.cy - bb.cy
|
|
|
|
|
break
|
|
|
|
|
case 'left':
|
|
|
|
|
out.dx = bx.x - bb.x
|
|
|
|
|
out.dy = 0
|
|
|
|
|
break
|
|
|
|
|
case 'right':
|
|
|
|
|
out.dx = bx.x2 - bb.x2
|
|
|
|
|
out.dy = 0
|
|
|
|
|
break
|
|
|
|
|
case 'center':
|
|
|
|
|
out.dx = bx.cx - bb.cx
|
|
|
|
|
out.dy = 0
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
return out
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
align(way) {
|
|
|
|
|
return this.transform('' + this.getAlign(way))
|
|
|
|
|
}
|
2024-03-05 13:02:24 +08:00
|
|
|
|
}
|
2024-03-06 18:36:03 +08:00
|
|
|
|
|
2024-03-05 13:02:24 +08:00
|
|
|
|
// default
|
|
|
|
|
eve.on('snap.util.getattr', function () {
|
2024-03-06 18:36:03 +08:00
|
|
|
|
let key = eve.nt().split('.').at(-1)
|
|
|
|
|
let css = key.replace(/[A-Z]/g, function (letter) {
|
2024-03-05 13:02:24 +08:00
|
|
|
|
return '-' + letter.toLowerCase()
|
|
|
|
|
})
|
2024-03-06 18:36:03 +08:00
|
|
|
|
if (CSS_ATTR[css]) {
|
2024-03-05 13:02:24 +08:00
|
|
|
|
return this.node.ownerDocument.defaultView
|
|
|
|
|
.getComputedStyle(this.node, null)
|
|
|
|
|
.getPropertyValue(css)
|
|
|
|
|
} else {
|
2024-03-06 18:36:03 +08:00
|
|
|
|
return $(this.node, key)
|
2024-03-05 13:02:24 +08:00
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
eve.on('snap.util.attr', function (value) {
|
2024-03-06 18:36:03 +08:00
|
|
|
|
let key = eve.nt().split('.').at(-1)
|
|
|
|
|
let attr = { [key]: value }
|
|
|
|
|
|
|
|
|
|
let css = key.replace(/[A-Z]/g, function (letter) {
|
|
|
|
|
return '-' + letter.toLowerCase()
|
|
|
|
|
})
|
|
|
|
|
if (CSS_ATTR[css]) {
|
|
|
|
|
this.node.style[css] = value == null ? E : value
|
2024-03-05 13:02:24 +08:00
|
|
|
|
} else {
|
|
|
|
|
$(this.node, attr)
|
|
|
|
|
}
|
|
|
|
|
})
|