snapsvg/src/set.js

322 lines
7.4 KiB
JavaScript

/**
* {}
* @author yutent<yutent.io@gmail.com>
* @date 2024/03/07 15:33:05
*/
import eve from './eve.js'
import { Animation } from './animation.js'
import mina from './mina.js'
import { is } from './utils.js'
let mmax = Math.max,
mmin = Math.min
// Set
let Set = function (items) {
this.items = []
this.bindings = {}
this.length = 0
this.type = 'set'
if (items) {
for (let i = 0, ii = items.length; i < ii; i++) {
if (items[i]) {
this[this.items.length] = this.items[this.items.length] = items[i]
this.length++
}
}
}
},
setproto = Set.prototype
/*\
* Set.push
[ method ]
**
* Adds each argument to the current set
= (object) original element
\*/
setproto.push = function () {
let item, len
for (let i = 0, ii = arguments.length; i < ii; i++) {
item = arguments[i]
if (item) {
len = this.items.length
this[len] = this.items[len] = item
this.length++
}
}
return this
}
/*\
* Set.pop
[ method ]
**
* Removes last element and returns it
= (object) element
\*/
setproto.pop = function () {
this.length && delete this[this.length--]
return this.items.pop()
}
/*\
* Set.forEach
[ method ]
**
* Executes given function for each element in the set
*
* If the function returns `false`, the loop stops running.
**
- callback (function) function to run
- thisArg (object) context object for the callback
= (object) Set object
\*/
setproto.forEach = function (callback, thisArg) {
for (let i = 0, ii = this.items.length; i < ii; i++) {
if (callback.call(thisArg, this.items[i], i) === false) {
return this
}
}
return this
}
/**
* Animates each element in set in sync.
*
**
- attrs (object) key-value pairs of destination attributes
- duration (number) duration of the animation in milliseconds
- easing (function) #optional easing function from @mina or custom
- callback (function) #optional callback function that executes when the animation ends
* or
- animation (array) array of animation parameter for each element in set in format `[attrs, duration, easing, callback]`
> Usage
| // animate all elements in set to radius 10
| set.animate({r: 10}, 500, mina.easein);
| // or
| // animate first element to radius 10, but second to radius 20 and in different time
| set.animate([{r: 10}, 500, mina.easein], [{r: 20}, 1500, mina.easein]);
= (SnapElement) the current element
*/
setproto.animate = function (attrs, ms, easing, callback) {
if (typeof easing == 'function' && !easing.length) {
callback = easing
easing = mina.linear
}
if (attrs instanceof Animation) {
callback = attrs.callback
easing = attrs.easing
ms = easing.dur
attrs = attrs.attr
}
let args = arguments
if (is(attrs, 'array') && is(args[args.length - 1], 'array')) {
let each = true
}
let begin,
handler = function () {
if (begin) {
this.b = begin
} else {
begin = this.b
}
},
cb = 0,
set = this,
callbacker =
callback &&
function () {
if (++cb == set.length) {
callback.call(this)
}
}
return this.forEach(function (el, i) {
eve.once('snap.animcreated.' + el.id, handler)
if (each) {
args[i] && el.animate.apply(el, args[i])
} else {
el.animate(attrs, ms, easing, callbacker)
}
})
}
/**
* Removes all children of the set.
*
*/
setproto.remove = function () {
while (this.length) {
this.pop().remove()
}
return this
}
/**
* Specifies how to handle a specific attribute when applied
* to a set.
*
**
- attr (string) attribute name
- callback (function) function to run
* or
- attr (string) attribute name
- element (SnapElement) specific element in the set to apply the attribute to
* or
- attr (string) attribute name
- element (SnapElement) specific element in the set to apply the attribute to
- eattr (string) attribute on the element to bind the attribute to
= (object) Set object
*/
setproto.bind = function (attr, a, b) {
let data = {}
if (typeof a == 'function') {
this.bindings[attr] = a
} else {
let aname = b || attr
this.bindings[attr] = function (v) {
data[aname] = v
a.attr(data)
}
}
return this
}
/**
* Equivalent of @SnapElement.attr.
*/
setproto.attr = function (value) {
let unbound = {}
for (let k in value) {
if (this.bindings[k]) {
this.bindings[k](value[k])
} else {
unbound[k] = value[k]
}
}
for (let i = 0, ii = this.items.length; i < ii; i++) {
this.items[i].attr(unbound)
}
return this
}
/**
* Removes all elements from the set
*/
setproto.clear = function () {
while (this.length) {
this.pop()
}
}
/**
* Removes range of elements from the set
**
* index (number) position of the deletion
* count (number) number of element to remove
* insertion… (object) #optional elements to insert
*/
setproto.splice = function (index, count, insertion) {
index = index < 0 ? mmax(this.length + index, 0) : index
count = mmax(0, mmin(this.length - index, count))
let tail = [],
todel = [],
args = [],
i
for (i = 2; i < arguments.length; i++) {
args.push(arguments[i])
}
for (i = 0; i < count; i++) {
todel.push(this[index + i])
}
for (; i < this.length - index; i++) {
tail.push(this[index + i])
}
let arglen = args.length
for (i = 0; i < arglen + tail.length; i++) {
this.items[index + i] = this[index + i] =
i < arglen ? args[i] : tail[i - arglen]
}
i = this.items.length = this.length -= count - arglen
while (this[i]) {
delete this[i++]
}
return new Set(todel)
}
/**
* Removes given element from the set
**
* element (object) element to remove
*/
setproto.exclude = function (el) {
for (let i = 0, ii = this.length; i < ii; i++)
if (this[i] == el) {
this.splice(i, 1)
return true
}
return false
}
/**
* Inserts set elements after given element.
**
* element (object) set will be inserted after this element
*/
setproto.insertAfter = function (el) {
let i = this.items.length
while (i--) {
this.items[i].insertAfter(el)
}
return this
}
/**
* Union of all bboxes of the set. See @SnapElement.getBBox.
*/
setproto.getBBox = function () {
let x = [],
y = [],
x2 = [],
y2 = []
for (let i = this.items.length; i--; )
if (!this.items[i].removed) {
let box = this.items[i].getBBox()
x.push(box.x)
y.push(box.y)
x2.push(box.x + box.width)
y2.push(box.y + box.height)
}
x = mmin.apply(0, x)
y = mmin.apply(0, y)
x2 = mmax.apply(0, x2)
y2 = mmax.apply(0, y2)
return {
x: x,
y: y,
x2: x2,
y2: y2,
width: x2 - x,
height: y2 - y,
cx: x + (x2 - x) / 2,
cy: y + (y2 - y) / 2
}
}
/**
* Creates a clone of the set.
*/
setproto.clone = function (s) {
s = new Set()
for (let i = 0, ii = this.items.length; i < ii; i++) {
s.push(this.items[i].clone())
}
return s
}
setproto.type = 'set'
/**
* Creates a set and fills it with list of arguments.
**
= (object) New Set object
| let r = paper.rect(0, 0, 10, 10),
| s1 = createSet(), // empty set
| s2 = createSet(r, paper.circle(100, 100, 20)); // prefilled set
*/
export function createSet(...args) {
let set = new Set()
if (args.length) {
set.push.apply(set, args)
}
return set
}
JavaScript 100%