snapsvg/src/set.js

379 lines
9.1 KiB
JavaScript
Raw Normal View History

2024-03-05 13:02:24 +08:00
// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import eve from './eve.js'
2024-03-06 18:36:03 +08:00
import { Snap, SnapElement, Paper, Fragment } from './svg.js'
2024-03-05 13:02:24 +08:00
import mina from './mina.js'
2024-03-06 18:36:03 +08:00
import { is } from './utils.js'
2024-03-05 13:02:24 +08:00
2024-03-06 18:36:03 +08:00
let mmax = Math.max,
mmin = Math.min
2024-03-05 13:02:24 +08:00
2024-03-06 18:36:03 +08:00
// 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++
2024-03-05 13:02:24 +08:00
}
}
2024-03-06 18:36:03 +08:00
}
},
setproto = Set.prototype
/*\
2024-03-05 13:02:24 +08:00
* Set.push
[ method ]
**
* Adds each argument to the current set
= (object) original element
\*/
2024-03-06 18:36:03 +08:00
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++
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
* Set.pop
[ method ]
**
* Removes last element and returns it
= (object) element
\*/
2024-03-06 18:36:03 +08:00
setproto.pop = function () {
this.length && delete this[this.length--]
return this.items.pop()
}
/*\
2024-03-05 13:02:24 +08:00
* 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
\*/
2024-03-06 18:36:03 +08:00
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
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
* Set.animate
[ method ]
**
* 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]);
2024-03-06 18:36:03 +08:00
= (SnapElement) the current element
2024-03-05 13:02:24 +08:00
\*/
2024-03-06 18:36:03 +08:00
setproto.animate = function (attrs, ms, easing, callback) {
if (typeof easing == 'function' && !easing.length) {
callback = easing
easing = mina.linear
}
if (attrs instanceof Snap._.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
2024-03-05 13:02:24 +08:00
} else {
2024-03-06 18:36:03 +08:00
begin = this.b
2024-03-05 13:02:24 +08:00
}
2024-03-06 18:36:03 +08:00
},
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)
}
})
}
/*\
2024-03-05 13:02:24 +08:00
* Set.remove
[ method ]
**
* Removes all children of the set.
*
= (object) Set object
\*/
2024-03-06 18:36:03 +08:00
setproto.remove = function () {
while (this.length) {
this.pop().remove()
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
* Set.bind
[ method ]
**
* 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
2024-03-06 18:36:03 +08:00
- element (SnapElement) specific element in the set to apply the attribute to
2024-03-05 13:02:24 +08:00
* or
- attr (string) attribute name
2024-03-06 18:36:03 +08:00
- element (SnapElement) specific element in the set to apply the attribute to
2024-03-05 13:02:24 +08:00
- eattr (string) attribute on the element to bind the attribute to
= (object) Set object
\*/
2024-03-06 18:36:03 +08:00
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)
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
* Set.attr
[ method ]
**
2024-03-06 18:36:03 +08:00
* Equivalent of @SnapElement.attr.
2024-03-05 13:02:24 +08:00
= (object) Set object
\*/
2024-03-06 18:36:03 +08:00
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]
2024-03-05 13:02:24 +08:00
}
}
2024-03-06 18:36:03 +08:00
for (let i = 0, ii = this.items.length; i < ii; i++) {
this.items[i].attr(unbound)
}
return this
}
/*\
2024-03-05 13:02:24 +08:00
* Set.clear
[ method ]
**
* Removes all elements from the set
\*/
2024-03-06 18:36:03 +08:00
setproto.clear = function () {
while (this.length) {
this.pop()
2024-03-05 13:02:24 +08:00
}
2024-03-06 18:36:03 +08:00
}
/*\
2024-03-05 13:02:24 +08:00
* Set.splice
[ method ]
**
* 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
= (object) set elements that were deleted
\*/
2024-03-06 18:36:03 +08:00
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])
2024-03-05 13:02:24 +08:00
}
2024-03-06 18:36:03 +08:00
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)
}
/*\
2024-03-05 13:02:24 +08:00
* Set.exclude
[ method ]
**
* Removes given element from the set
**
- element (object) element to remove
= (boolean) `true` if object was found and removed from the set
\*/
2024-03-06 18:36:03 +08:00
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
}
/*\
2024-03-05 13:02:24 +08:00
* Set.insertAfter
[ method ]
**
* Inserts set elements after given element.
**
- element (object) set will be inserted after this element
= (object) Set object
\*/
2024-03-06 18:36:03 +08:00
setproto.insertAfter = function (el) {
let i = this.items.length
while (i--) {
this.items[i].insertAfter(el)
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
* Set.getBBox
[ method ]
**
2024-03-06 18:36:03 +08:00
* Union of all bboxes of the set. See @SnapElement.getBBox.
= (object) bounding box descriptor. See @SnapElement.getBBox.
2024-03-05 13:02:24 +08:00
\*/
2024-03-06 18:36:03 +08:00
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)
2024-03-05 13:02:24 +08:00
}
2024-03-06 18:36:03 +08:00
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
2024-03-05 13:02:24 +08:00
}
2024-03-06 18:36:03 +08:00
}
/*\
2024-03-05 13:02:24 +08:00
* Set.insertAfter
[ method ]
**
* Creates a clone of the set.
**
= (object) New Set object
\*/
2024-03-06 18:36:03 +08:00
setproto.clone = function (s) {
s = new Set()
for (let i = 0, ii = this.items.length; i < ii; i++) {
s.push(this.items[i].clone())
2024-03-05 13:02:24 +08:00
}
2024-03-06 18:36:03 +08:00
return s
}
setproto.toString = function () {
return 'Snap\u2018s set'
}
setproto.type = 'set'
// export
/*\
2024-03-05 13:02:24 +08:00
* Snap.Set
[ property ]
**
* Set constructor.
\*/
2024-03-06 18:36:03 +08:00
Snap.Set = Set
/*\
2024-03-05 13:02:24 +08:00
* Snap.set
[ method ]
**
* Creates a set and fills it with list of arguments.
**
= (object) New Set object
2024-03-06 18:36:03 +08:00
| let r = paper.rect(0, 0, 10, 10),
2024-03-05 13:02:24 +08:00
| s1 = Snap.set(), // empty set
| s2 = Snap.set(r, paper.circle(100, 100, 20)); // prefilled set
\*/
2024-03-06 18:36:03 +08:00
Snap.set = function () {
let set = new Set()
if (arguments.length) {
set.push.apply(set, Array.prototype.slice.call(arguments, 0))
2024-03-05 13:02:24 +08:00
}
2024-03-06 18:36:03 +08:00
return set
}