From d57e58a63e9c5d9b67586c5434fb127769269c37 Mon Sep 17 00:00:00 2001 From: yutent Date: Tue, 5 Mar 2024 13:02:24 +0800 Subject: [PATCH] init --- .gitignore | 11 + .gitmodules | 9 + LICENSE | 202 +++++ NOTICE | 190 +++++ README.md | 5 + history.md | 66 ++ package.json | 25 + src/align.js | 94 +++ src/animation.js | 229 ++++++ src/attr.js | 480 +++++++++++ src/attradd.js | 94 +++ src/class.js | 148 ++++ src/colors.js | 149 ++++ src/element.js | 815 +++++++++++++++++++ src/equal.js | 227 ++++++ src/eve.js | 458 +++++++++++ src/filter.js | 348 ++++++++ src/index.js | 17 + src/matrix.js | 413 ++++++++++ src/mina.js | 378 +++++++++ src/mouse.js | 533 +++++++++++++ src/paper.js | 783 ++++++++++++++++++ src/path.js | 1633 ++++++++++++++++++++++++++++++++++++++ src/set.js | 379 +++++++++ src/svg.js | 1984 ++++++++++++++++++++++++++++++++++++++++++++++ 25 files changed, 9670 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 LICENSE create mode 100644 NOTICE create mode 100644 README.md create mode 100644 history.md create mode 100644 package.json create mode 100644 src/align.js create mode 100644 src/animation.js create mode 100644 src/attr.js create mode 100644 src/attradd.js create mode 100644 src/class.js create mode 100644 src/colors.js create mode 100644 src/element.js create mode 100644 src/equal.js create mode 100644 src/eve.js create mode 100644 src/filter.js create mode 100644 src/index.js create mode 100644 src/matrix.js create mode 100644 src/mina.js create mode 100644 src/mouse.js create mode 100644 src/paper.js create mode 100644 src/path.js create mode 100644 src/set.js create mode 100644 src/svg.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0774656 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ + +dist +node_modules + +._* + +.Spotlight-V100 +.Trashes +.DS_Store +.AppleDouble +.LSOverride diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..8f7f8c1 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "third-party/mocha"] + path = third-party/mocha + url = https://github.com/visionmedia/mocha.git +[submodule "third-party/eve"] + path = third-party/eve + url = https://github.com/adobe-webplatform/eve.git +[submodule "third-party/expect"] + path = third-party/expect + url = https://github.com/LearnBoost/expect.js.git diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..86a96fa --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2013 Adobe Systems Incorporated + + 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. \ No newline at end of file diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..b9fb2b2 --- /dev/null +++ b/NOTICE @@ -0,0 +1,190 @@ +Snap.svg is licensed under the Apache license version 2.0, January 2004 (see LICENSE file). + +Snap.svg uses the following third party libraries that may have licenses +differing from that of Snap.svg itself. You can find the libraries and their +respective licenses below. + + - eve ./node_modules/eve + + https://github.com/adobe-webplatform/eve/ + + 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. + + - Mocha ./node_modules/mocha + + https://github.com/visionmedia/mocha/ + + (The MIT License) + + Copyright (c) 2011-2013 TJ Holowaychuk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + 'Software'), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + - Expect ./node_modules/expect.js + + https://github.com/LearnBoost/expect.js + + (The MIT License) + + Copyright (c) 2011 Guillermo Rauch + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + 'Software'), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + - Grunt ./node_modules/grunt + + http://gruntjs.com + + Copyright (c) 2013 "Cowboy" Ben Alman + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + + - Backbone ./demos/animated-game/js/backbone.js + + http://backbonejs.org/ + + (The MIT License) + + Copyright (c) 2010-2013 Jeremy Ashkenas, DocumentCloud + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + 'Software'), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + + - Underscore ./demos/animated-game/js/underscore.js + + http://underscorejs.org + + (The MIT License) + + Copyright (c) 2010-2013 Jeremy Ashkenas, DocumentCloud + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + 'Software'), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + + - jQuery ./demos/animated-game/js/jquery-1.9.0.min.js + + http://http://jquery.com/ + + (The MIT License) + + Copyright 2013 jQuery Foundation and other contributors + http://jquery.com/ + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + 'Software'), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..2fe2f77 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# @bytedo/snapsvg + +一个功能强大的SVG库, fork于 [【Snap.svg】](https://github.com/adobe-webplatform/Snap.svg)。 + +原库已经年久失修, 有不少bug。 \ No newline at end of file diff --git a/history.md b/history.md new file mode 100644 index 0000000..3b771c4 --- /dev/null +++ b/history.md @@ -0,0 +1,66 @@ +#0.5.2 + +* pack fix +* transform into esm + +#0.5.1 + +* Bug fix + +#0.5.0 + +* Added color palettes for Material and FlatUI +* Added methods for gradients: `Element.stops()`, `Element.addStop()`, `Element.setStops()` +* Fixed matrix splitting for better animation of matrices` +* Various bug fixes +* Better integration of tests and ESlint + +#0.4.1 + +* Bug fixes. + +#0.4.0 + +* Moved class and element related code into separate plugins +* Added `Element.align()` and `Element.getAlign()` methods +* Added animation support for `viewBox` +* Added support for `` +* Added method `Paper.toDataURL()` +* Added method `Snap.closest()` +* Added methods to work with degrees instead of radians: `Snap.sin()`, `Snap.cos()`, `Snap.tan()`, `Snap.asin()`, `Snap.acos()`, `Snap.atan()` and `Snap.atan2()` +* Added methods `Snap.len()`, `Snap.len2()` and `Snap.closestPoint()` +* Added methods `Element.children()` and `Element.toJSON()` +* Various bug fixes + +#0.3.0 + +* Added `.addClass()`, `.removeClass()`, `.toggleClass()` and `.hasClass()` APIs +* Added `Paper.mask()`, `Paper.ptrn()`, `Paper.use()`, `Paper.svg()` +* Mask & pattern elements are sharing paper methods (just like group) +* Added `Set.bind()` method +* Added syncronisation for `Set.animate()` +* Added opacity to the shadow filter +* Added ability to specify attributes as `"+=10"` or `"-=1em"` or `"*=2"` +* Fix negative scale +* Fix for `path2curve` +* Fixed shared `` issue +* Various bug fixes + +#0.2.0 + +* Added support for text path +* Added `getBBox` method to the paper object +* Added `Element.appendTo()` and `Element.prependTo()` +* Added `getElementByPoint()` +* Added `Set.remove()` method +* Get rid of internal SVG parser in favor of the browser +* Fix for `xlink:href` setting for images +* Fix `Element.animate()` +* Fix for animate and stroke-dashoffset +* Absolute transforms fix +* Fix for animation of SVG transformations, matrices and polygon points +* Various bug fixes + +#0.1.0 + +* Initial release diff --git a/package.json b/package.json new file mode 100644 index 0000000..7b5de26 --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "name": "@bytedo/snapsvg", + "type": "module", + "version": "0.5.2", + "description": "JavaScript Vector Library", + "repository": { + "type": "git", + "url": "git@git.wkit.fun:bytedo/snapsvg.git" + }, + "scripts": { + "start": "esbuild src/index.js --watch --bundle --format=esm --target=esnext --outfile=dist/index.js", + "pack": "esbuild src/index.js --minify --bundle --format=esm --target=esnext --outfile=dist/index.js" + }, + "author": "Dmitry Baranovskiy", + "contributors": [ + { + "name": "yutent", + "email": "yutent.io@gmail.com" + } + ], + "license": "Apache-2.0", + "devDependencies": { + "esbuild": "^0.20.1" + } +} diff --git a/src/align.js b/src/align.js new file mode 100644 index 0000000..b76591a --- /dev/null +++ b/src/align.js @@ -0,0 +1,94 @@ +// Copyright (c) 2014 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 { Snap } from './svg.js' + +Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { + var box = Snap._.box, + is = Snap.is, + firstLetter = /^[^a-z]*([tbmlrc])/i, + toString = function () { + return 'T' + this.dx + ',' + this.dy + } + /*\ + * Element.getAlign + [ method ] + ** + * Returns shift needed to align the element relatively to given element. + * If no elements specified, parent `` container will be used. + - el (object) @optional alignment element + - way (string) one of six values: `"top"`, `"middle"`, `"bottom"`, `"left"`, `"center"`, `"right"` + = (object|string) Object in format `{dx: , dy: }` also has a string representation as a transformation string + > Usage + | el.transform(el.getAlign(el2, "top")); + * or + | var dy = el.getAlign(el2, "top").dy; + \*/ + Element.prototype.getAlign = function (el, way) { + if (way == null && is(el, 'string')) { + way = el + el = null + } + el = el || this.paper + var bx = el.getBBox ? el.getBBox() : box(el), + bb = this.getBBox(), + out = {} + way = way && way.match(firstLetter) + way = way ? way[1].toLowerCase() : 'c' + switch (way) { + case 't': + out.dx = 0 + out.dy = bx.y - bb.y + break + case 'b': + out.dx = 0 + out.dy = bx.y2 - bb.y2 + break + case 'm': + out.dx = 0 + out.dy = bx.cy - bb.cy + break + case 'l': + out.dx = bx.x - bb.x + out.dy = 0 + break + case 'r': + out.dx = bx.x2 - bb.x2 + out.dy = 0 + break + default: + out.dx = bx.cx - bb.cx + out.dy = 0 + break + } + out.toString = toString + return out + } + /*\ + * Element.align + [ method ] + ** + * Aligns the element relatively to given one via transformation. + * If no elements specified, parent `` container will be used. + - el (object) @optional alignment element + - way (string) one of six values: `"top"`, `"middle"`, `"bottom"`, `"left"`, `"center"`, `"right"` + = (object) this element + > Usage + | el.align(el2, "top"); + * or + | el.align("middle"); + \*/ + Element.prototype.align = function (el, way) { + return this.transform('...' + this.getAlign(el, way)) + } +}) diff --git a/src/animation.js b/src/animation.js new file mode 100644 index 0000000..2fb468b --- /dev/null +++ b/src/animation.js @@ -0,0 +1,229 @@ +// Copyright (c) 2016 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' +import { Snap } from './svg.js' +import mina from './mina.js' + +Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { + var elproto = Element.prototype, + is = Snap.is, + Str = String, + has = 'hasOwnProperty' + function slice(from, to, f) { + return function (arr) { + var res = arr.slice(from, to) + if (res.length == 1) { + res = res[0] + } + return f ? f(res) : res + } + } + var Animation = function (attr, ms, easing, callback) { + if (typeof easing == 'function' && !easing.length) { + callback = easing + easing = mina.linear + } + this.attr = attr + this.dur = ms + easing && (this.easing = easing) + callback && (this.callback = callback) + } + Snap._.Animation = Animation + /*\ + * Snap.animation + [ method ] + ** + * Creates an animation object + ** + - attr (object) attributes of final destination + - duration (number) duration of the animation, in milliseconds + - easing (function) #optional one of easing functions of @mina or custom one + - callback (function) #optional callback function that fires when animation ends + = (object) animation object + \*/ + Snap.animation = function (attr, ms, easing, callback) { + return new Animation(attr, ms, easing, callback) + } + /*\ + * Element.inAnim + [ method ] + ** + * Returns a set of animations that may be able to manipulate the current element + ** + = (object) in format: + o { + o anim (object) animation object, + o mina (object) @mina object, + o curStatus (number) 0..1 — status of the animation: 0 — just started, 1 — just finished, + o status (function) gets or sets the status of the animation, + o stop (function) stops the animation + o } + \*/ + elproto.inAnim = function () { + var el = this, + res = [] + for (var id in el.anims) + if (el.anims[has](id)) { + ;(function (a) { + res.push({ + anim: new Animation(a._attrs, a.dur, a.easing, a._callback), + mina: a, + curStatus: a.status(), + status: function (val) { + return a.status(val) + }, + stop: function () { + a.stop() + } + }) + })(el.anims[id]) + } + return res + } + /*\ + * Snap.animate + [ method ] + ** + * Runs generic animation of one number into another with a caring function + ** + - from (number|array) number or array of numbers + - to (number|array) number or array of numbers + - setter (function) caring function that accepts one number argument + - duration (number) duration, in milliseconds + - easing (function) #optional easing function from @mina or custom + - callback (function) #optional callback function to execute when animation ends + = (object) animation object in @mina format + o { + o id (string) animation id, consider it read-only, + o duration (function) gets or sets the duration of the animation, + o easing (function) easing, + o speed (function) gets or sets the speed of the animation, + o status (function) gets or sets the status of the animation, + o stop (function) stops the animation + o } + | var rect = Snap().rect(0, 0, 10, 10); + | Snap.animate(0, 10, function (val) { + | rect.attr({ + | x: val + | }); + | }, 1000); + | // in given context is equivalent to + | rect.animate({x: 10}, 1000); + \*/ + Snap.animate = function (from, to, setter, ms, easing, callback) { + if (typeof easing == 'function' && !easing.length) { + callback = easing + easing = mina.linear + } + var now = mina.time(), + anim = mina(from, to, now, now + ms, mina.time, setter, easing) + callback && eve.once('mina.finish.' + anim.id, callback) + return anim + } + /*\ + * Element.stop + [ method ] + ** + * Stops all the animations for the current element + ** + = (Element) the current element + \*/ + elproto.stop = function () { + var anims = this.inAnim() + for (var i = 0, ii = anims.length; i < ii; i++) { + anims[i].stop() + } + return this + } + /*\ + * Element.animate + [ method ] + ** + * Animates the given attributes of the element + ** + - 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 + = (Element) the current element + \*/ + elproto.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 = attrs.dur + attrs = attrs.attr + } + var fkeys = [], + tkeys = [], + keys = {}, + from, + to, + f, + eq, + el = this + for (var key in attrs) + if (attrs[has](key)) { + if (el.equal) { + eq = el.equal(key, Str(attrs[key])) + from = eq.from + to = eq.to + f = eq.f + } else { + from = +el.attr(key) + to = +attrs[key] + } + var len = is(from, 'array') ? from.length : 1 + keys[key] = slice(fkeys.length, fkeys.length + len, f) + fkeys = fkeys.concat(from) + tkeys = tkeys.concat(to) + } + var now = mina.time(), + anim = mina( + fkeys, + tkeys, + now, + now + ms, + mina.time, + function (val) { + var attr = {} + for (var key in keys) + if (keys[has](key)) { + attr[key] = keys[key](val) + } + el.attr(attr) + }, + easing + ) + el.anims[anim.id] = anim + anim._attrs = attrs + anim._callback = callback + eve('snap.animcreated.' + el.id, anim) + eve.once('mina.finish.' + anim.id, function () { + eve.off('mina.*.' + anim.id) + delete el.anims[anim.id] + callback && callback.call(el) + }) + eve.once('mina.stop.' + anim.id, function () { + eve.off('mina.*.' + anim.id) + delete el.anims[anim.id] + }) + return el + } +}) diff --git a/src/attr.js b/src/attr.js new file mode 100644 index 0000000..1761d36 --- /dev/null +++ b/src/attr.js @@ -0,0 +1,480 @@ +// 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' +import { Snap } from './svg.js' + +Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { + var has = 'hasOwnProperty', + make = Snap._.make, + wrap = Snap._.wrap, + is = Snap.is, + getSomeDefs = Snap._.getSomeDefs, + reURLValue = /^url\((['"]?)([^)]+)\1\)$/, + $ = Snap._.$, + URL = Snap.url, + Str = String, + separator = Snap._.separator, + E = '' + /*\ + * Snap.deurl + [ method ] + ** + * Unwraps path from `"url()"`. + - value (string) url path + = (string) unwrapped path + \*/ + Snap.deurl = function (value) { + var res = String(value).match(reURLValue) + return res ? res[2] : value + } + // Attributes event handlers + eve.on('snap.util.attr.mask', function (value) { + if (value instanceof Element || value instanceof Fragment) { + eve.stop() + if (value instanceof Fragment && value.node.childNodes.length == 1) { + value = value.node.firstChild + getSomeDefs(this).appendChild(value) + value = wrap(value) + } + if (value.type == 'mask') { + var mask = value + } else { + mask = make('mask', getSomeDefs(this)) + mask.node.appendChild(value.node) + } + !mask.node.id && + $(mask.node, { + id: mask.id + }) + $(this.node, { + mask: URL(mask.id) + }) + } + }) + ;(function (clipIt) { + eve.on('snap.util.attr.clip', clipIt) + eve.on('snap.util.attr.clip-path', clipIt) + eve.on('snap.util.attr.clipPath', clipIt) + })(function (value) { + if (value instanceof Element || value instanceof Fragment) { + eve.stop() + var clip, + node = value.node + while (node) { + if (node.nodeName === 'clipPath') { + clip = new Element(node) + break + } + if (node.nodeName === 'svg') { + clip = undefined + break + } + node = node.parentNode + } + if (!clip) { + clip = make('clipPath', getSomeDefs(this)) + clip.node.appendChild(value.node) + !clip.node.id && + $(clip.node, { + id: clip.id + }) + } + $(this.node, { + 'clip-path': URL(clip.node.id || clip.id) + }) + } + }) + function fillStroke(name) { + return function (value) { + eve.stop() + if ( + value instanceof Fragment && + value.node.childNodes.length == 1 && + (value.node.firstChild.tagName == 'radialGradient' || + value.node.firstChild.tagName == 'linearGradient' || + value.node.firstChild.tagName == 'pattern') + ) { + value = value.node.firstChild + getSomeDefs(this).appendChild(value) + value = wrap(value) + } + if (value instanceof Element) { + if ( + value.type == 'radialGradient' || + value.type == 'linearGradient' || + value.type == 'pattern' + ) { + if (!value.node.id) { + $(value.node, { + id: value.id + }) + } + var fill = URL(value.node.id) + } else { + fill = value.attr(name) + } + } else { + fill = Snap.color(value) + if (fill.error) { + var grad = Snap(getSomeDefs(this).ownerSVGElement).gradient(value) + if (grad) { + if (!grad.node.id) { + $(grad.node, { + id: grad.id + }) + } + fill = URL(grad.node.id) + } else { + fill = value + } + } else { + fill = Str(fill) + } + } + var attrs = {} + attrs[name] = fill + $(this.node, attrs) + this.node.style[name] = E + } + } + eve.on('snap.util.attr.fill', fillStroke('fill')) + eve.on('snap.util.attr.stroke', fillStroke('stroke')) + var gradrg = /^([lr])(?:\(([^)]*)\))?(.*)$/i + eve.on('snap.util.grad.parse', function parseGrad(string) { + string = Str(string) + var tokens = string.match(gradrg) + if (!tokens) { + return null + } + var type = tokens[1], + params = tokens[2], + stops = tokens[3] + params = params.split(/\s*,\s*/).map(function (el) { + return +el == el ? +el : el + }) + if (params.length == 1 && params[0] == 0) { + params = [] + } + stops = stops.split('-') + stops = stops.map(function (el) { + el = el.split(':') + var out = { + color: el[0] + } + if (el[1]) { + out.offset = parseFloat(el[1]) + } + return out + }) + var len = stops.length, + start = 0, + j = 0 + function seed(i, end) { + var step = (end - start) / (i - j) + for (var k = j; k < i; k++) { + stops[k].offset = +(+start + step * (k - j)).toFixed(2) + } + j = i + start = end + } + len-- + for (var i = 0; i < len; i++) + if ('offset' in stops[i]) { + seed(i, stops[i].offset) + } + stops[len].offset = stops[len].offset || 100 + seed(len, stops[len].offset) + return { + type: type, + params: params, + stops: stops + } + }) + + eve.on('snap.util.attr.d', function (value) { + eve.stop() + if (is(value, 'array') && is(value[0], 'array')) { + value = Snap.path.toString.call(value) + } + value = Str(value) + if (value.match(/[ruo]/i)) { + value = Snap.path.toAbsolute(value) + } + $(this.node, { d: value }) + })(-1) + eve.on('snap.util.attr.#text', function (value) { + eve.stop() + value = Str(value) + var txt = glob.doc.createTextNode(value) + while (this.node.firstChild) { + this.node.removeChild(this.node.firstChild) + } + this.node.appendChild(txt) + })(-1) + eve.on('snap.util.attr.path', function (value) { + eve.stop() + this.attr({ d: value }) + })(-1) + eve.on('snap.util.attr.class', function (value) { + eve.stop() + this.node.className.baseVal = value + })(-1) + eve.on('snap.util.attr.viewBox', function (value) { + var vb + if (is(value, 'object') && 'x' in value) { + vb = [value.x, value.y, value.width, value.height].join(' ') + } else if (is(value, 'array')) { + vb = value.join(' ') + } else { + vb = value + } + $(this.node, { + viewBox: vb + }) + eve.stop() + })(-1) + eve.on('snap.util.attr.transform', function (value) { + this.transform(value) + eve.stop() + })(-1) + eve.on('snap.util.attr.r', function (value) { + if (this.type == 'rect') { + eve.stop() + $(this.node, { + rx: value, + ry: value + }) + } + })(-1) + eve.on('snap.util.attr.textpath', function (value) { + eve.stop() + if (this.type == 'text') { + var id, tp, node + if (!value && this.textPath) { + tp = this.textPath + while (tp.node.firstChild) { + this.node.appendChild(tp.node.firstChild) + } + tp.remove() + delete this.textPath + return + } + if (is(value, 'string')) { + var defs = getSomeDefs(this), + path = wrap(defs.parentNode).path(value) + defs.appendChild(path.node) + id = path.id + path.attr({ id: id }) + } else { + value = wrap(value) + if (value instanceof Element) { + id = value.attr('id') + if (!id) { + id = value.id + value.attr({ id: id }) + } + } + } + if (id) { + tp = this.textPath + node = this.node + if (tp) { + tp.attr({ 'xlink:href': '#' + id }) + } else { + tp = $('textPath', { + 'xlink:href': '#' + id + }) + while (node.firstChild) { + tp.appendChild(node.firstChild) + } + node.appendChild(tp) + this.textPath = wrap(tp) + } + } + } + })(-1) + eve.on('snap.util.attr.text', function (value) { + if (this.type == 'text') { + var i = 0, + node = this.node, + tuner = function (chunk) { + var out = $('tspan') + if (is(chunk, 'array')) { + for (var i = 0; i < chunk.length; i++) { + out.appendChild(tuner(chunk[i])) + } + } else { + out.appendChild(glob.doc.createTextNode(chunk)) + } + out.normalize && out.normalize() + return out + } + while (node.firstChild) { + node.removeChild(node.firstChild) + } + var tuned = tuner(value) + while (tuned.firstChild) { + node.appendChild(tuned.firstChild) + } + } + eve.stop() + })(-1) + function setFontSize(value) { + eve.stop() + if (value == +value) { + value += 'px' + } + this.node.style.fontSize = value + } + eve.on('snap.util.attr.fontSize', setFontSize)(-1) + eve.on('snap.util.attr.font-size', setFontSize)(-1) + + eve.on('snap.util.getattr.transform', function () { + eve.stop() + return this.transform() + })(-1) + eve.on('snap.util.getattr.textpath', function () { + eve.stop() + return this.textPath + })(-1) + // Markers + ;(function () { + function getter(end) { + return function () { + eve.stop() + var style = glob.doc.defaultView + .getComputedStyle(this.node, null) + .getPropertyValue('marker-' + end) + if (style == 'none') { + return style + } else { + return Snap(glob.doc.getElementById(style.match(reURLValue)[1])) + } + } + } + function setter(end) { + return function (value) { + eve.stop() + var name = 'marker' + end.charAt(0).toUpperCase() + end.substring(1) + if (value == '' || !value) { + this.node.style[name] = 'none' + return + } + if (value.type == 'marker') { + var id = value.node.id + if (!id) { + $(value.node, { id: value.id }) + } + this.node.style[name] = URL(id) + return + } + } + } + eve.on('snap.util.getattr.marker-end', getter('end'))(-1) + eve.on('snap.util.getattr.markerEnd', getter('end'))(-1) + eve.on('snap.util.getattr.marker-start', getter('start'))(-1) + eve.on('snap.util.getattr.markerStart', getter('start'))(-1) + eve.on('snap.util.getattr.marker-mid', getter('mid'))(-1) + eve.on('snap.util.getattr.markerMid', getter('mid'))(-1) + eve.on('snap.util.attr.marker-end', setter('end'))(-1) + eve.on('snap.util.attr.markerEnd', setter('end'))(-1) + eve.on('snap.util.attr.marker-start', setter('start'))(-1) + eve.on('snap.util.attr.markerStart', setter('start'))(-1) + eve.on('snap.util.attr.marker-mid', setter('mid'))(-1) + eve.on('snap.util.attr.markerMid', setter('mid'))(-1) + })() + eve.on('snap.util.getattr.r', function () { + if (this.type == 'rect' && $(this.node, 'rx') == $(this.node, 'ry')) { + eve.stop() + return $(this.node, 'rx') + } + })(-1) + function textExtract(node) { + var out = [] + var children = node.childNodes + for (var i = 0, ii = children.length; i < ii; i++) { + var chi = children[i] + if (chi.nodeType == 3) { + out.push(chi.nodeValue) + } + if (chi.tagName == 'tspan') { + if (chi.childNodes.length == 1 && chi.firstChild.nodeType == 3) { + out.push(chi.firstChild.nodeValue) + } else { + out.push(textExtract(chi)) + } + } + } + return out + } + eve.on('snap.util.getattr.text', function () { + if (this.type == 'text' || this.type == 'tspan') { + eve.stop() + var out = textExtract(this.node) + return out.length == 1 ? out[0] : out + } + })(-1) + eve.on('snap.util.getattr.#text', function () { + return this.node.textContent + })(-1) + eve.on('snap.util.getattr.fill', function (internal) { + if (internal) { + return + } + eve.stop() + var value = eve('snap.util.getattr.fill', this, true).firstDefined() + return Snap(Snap.deurl(value)) || value + })(-1) + eve.on('snap.util.getattr.stroke', function (internal) { + if (internal) { + return + } + eve.stop() + var value = eve('snap.util.getattr.stroke', this, true).firstDefined() + return Snap(Snap.deurl(value)) || value + })(-1) + eve.on('snap.util.getattr.viewBox', function () { + eve.stop() + var vb = $(this.node, 'viewBox') + if (vb) { + vb = vb.split(separator) + return Snap._.box(+vb[0], +vb[1], +vb[2], +vb[3]) + } else { + return + } + })(-1) + eve.on('snap.util.getattr.points', function () { + var p = $(this.node, 'points') + eve.stop() + if (p) { + return p.split(separator) + } else { + return + } + })(-1) + eve.on('snap.util.getattr.path', function () { + var p = $(this.node, 'd') + eve.stop() + return p + })(-1) + eve.on('snap.util.getattr.class', function () { + return this.node.className.baseVal + })(-1) + function getFontSize() { + eve.stop() + return this.node.style.fontSize + } + eve.on('snap.util.getattr.fontSize', getFontSize)(-1) + eve.on('snap.util.getattr.font-size', getFontSize)(-1) +}) diff --git a/src/attradd.js b/src/attradd.js new file mode 100644 index 0000000..897af3e --- /dev/null +++ b/src/attradd.js @@ -0,0 +1,94 @@ +// 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' +import { Snap } from './svg.js' + +Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { + var operators = { + '+': function (x, y) { + return x + y + }, + '-': function (x, y) { + return x - y + }, + '/': function (x, y) { + return x / y + }, + '*': function (x, y) { + return x * y + } + }, + Str = String, + reUnit = /[a-z]+$/i, + reAddon = /^\s*([+\-\/*])\s*=\s*([\d.eE+\-]+)\s*([^\d\s]+)?\s*$/ + function getNumber(val) { + return val + } + function getUnit(unit) { + return function (val) { + return +val.toFixed(3) + unit + } + } + eve.on('snap.util.attr', function (val) { + var plus = Str(val).match(reAddon) + if (plus) { + var evnt = eve.nt(), + name = evnt.substring(evnt.lastIndexOf('.') + 1), + a = this.attr(name), + atr = {} + eve.stop() + var unit = plus[3] || '', + aUnit = a.match(reUnit), + op = operators[plus[1]] + if (aUnit && aUnit == unit) { + val = op(parseFloat(a), +plus[2]) + } else { + a = this.asPX(name) + val = op(this.asPX(name), this.asPX(name, plus[2] + unit)) + } + if (isNaN(a) || isNaN(val)) { + return + } + atr[name] = val + this.attr(atr) + } + })(-10) + eve.on('snap.util.equal', function (name, b) { + var A, + B, + a = Str(this.attr(name) || ''), + el = this, + bplus = Str(b).match(reAddon) + if (bplus) { + eve.stop() + var unit = bplus[3] || '', + aUnit = a.match(reUnit), + op = operators[bplus[1]] + if (aUnit && aUnit == unit) { + return { + from: parseFloat(a), + to: op(parseFloat(a), +bplus[2]), + f: getUnit(aUnit) + } + } else { + a = this.asPX(name) + return { + from: a, + to: op(a, this.asPX(name, bplus[2] + unit)), + f: getNumber + } + } + } + })(-10) +}) diff --git a/src/class.js b/src/class.js new file mode 100644 index 0000000..1e51f20 --- /dev/null +++ b/src/class.js @@ -0,0 +1,148 @@ +// Copyright (c) 2014 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 { Snap } from './svg.js' +Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { + var rgNotSpace = /\S+/g, + rgBadSpace = /[\t\r\n\f]/g, + rgTrim = /(^\s+|\s+$)/g, + Str = String, + elproto = Element.prototype + /*\ + * Element.addClass + [ method ] + ** + * Adds given class name or list of class names to the element. + - value (string) class name or space separated list of class names + ** + = (Element) original element. + \*/ + elproto.addClass = function (value) { + var classes = Str(value || '').match(rgNotSpace) || [], + elem = this.node, + className = elem.className.baseVal, + curClasses = className.match(rgNotSpace) || [], + j, + pos, + clazz, + finalValue + + if (classes.length) { + j = 0 + while ((clazz = classes[j++])) { + pos = curClasses.indexOf(clazz) + if (!~pos) { + curClasses.push(clazz) + } + } + + finalValue = curClasses.join(' ') + if (className != finalValue) { + elem.className.baseVal = finalValue + } + } + return this + } + /*\ + * Element.removeClass + [ method ] + ** + * Removes given class name or list of class names from the element. + - value (string) class name or space separated list of class names + ** + = (Element) original element. + \*/ + elproto.removeClass = function (value) { + var classes = Str(value || '').match(rgNotSpace) || [], + elem = this.node, + className = elem.className.baseVal, + curClasses = className.match(rgNotSpace) || [], + j, + pos, + clazz, + finalValue + if (curClasses.length) { + j = 0 + while ((clazz = classes[j++])) { + pos = curClasses.indexOf(clazz) + if (~pos) { + curClasses.splice(pos, 1) + } + } + + finalValue = curClasses.join(' ') + if (className != finalValue) { + elem.className.baseVal = finalValue + } + } + return this + } + /*\ + * Element.hasClass + [ method ] + ** + * Checks if the element has a given class name in the list of class names applied to it. + - value (string) class name + ** + = (boolean) `true` if the element has given class + \*/ + elproto.hasClass = function (value) { + var elem = this.node, + className = elem.className.baseVal, + curClasses = className.match(rgNotSpace) || [] + return !!~curClasses.indexOf(value) + } + /*\ + * Element.toggleClass + [ method ] + ** + * Add or remove one or more classes from the element, depending on either + * the class’s presence or the value of the `flag` argument. + - value (string) class name or space separated list of class names + - flag (boolean) value to determine whether the class should be added or removed + ** + = (Element) original element. + \*/ + elproto.toggleClass = function (value, flag) { + if (flag != null) { + if (flag) { + return this.addClass(value) + } else { + return this.removeClass(value) + } + } + var classes = (value || '').match(rgNotSpace) || [], + elem = this.node, + className = elem.className.baseVal, + curClasses = className.match(rgNotSpace) || [], + j, + pos, + clazz, + finalValue + j = 0 + while ((clazz = classes[j++])) { + pos = curClasses.indexOf(clazz) + if (~pos) { + curClasses.splice(pos, 1) + } else { + curClasses.push(clazz) + } + } + + finalValue = curClasses.join(' ') + if (className != finalValue) { + elem.className.baseVal = finalValue + } + return this + } +}) diff --git a/src/colors.js b/src/colors.js new file mode 100644 index 0000000..5e2bc97 --- /dev/null +++ b/src/colors.js @@ -0,0 +1,149 @@ +// Copyright (c) 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 { Snap } from './svg.js' +Snap.plugin(function (Snap, Element, Paper, glob) { + // Colours are from https://www.materialui.co + var red = + '#ffebee#ffcdd2#ef9a9a#e57373#ef5350#f44336#e53935#d32f2f#c62828#b71c1c#ff8a80#ff5252#ff1744#d50000', + pink = + '#FCE4EC#F8BBD0#F48FB1#F06292#EC407A#E91E63#D81B60#C2185B#AD1457#880E4F#FF80AB#FF4081#F50057#C51162', + purple = + '#F3E5F5#E1BEE7#CE93D8#BA68C8#AB47BC#9C27B0#8E24AA#7B1FA2#6A1B9A#4A148C#EA80FC#E040FB#D500F9#AA00FF', + deeppurple = + '#EDE7F6#D1C4E9#B39DDB#9575CD#7E57C2#673AB7#5E35B1#512DA8#4527A0#311B92#B388FF#7C4DFF#651FFF#6200EA', + indigo = + '#E8EAF6#C5CAE9#9FA8DA#7986CB#5C6BC0#3F51B5#3949AB#303F9F#283593#1A237E#8C9EFF#536DFE#3D5AFE#304FFE', + blue = + '#E3F2FD#BBDEFB#90CAF9#64B5F6#64B5F6#2196F3#1E88E5#1976D2#1565C0#0D47A1#82B1FF#448AFF#2979FF#2962FF', + lightblue = + '#E1F5FE#B3E5FC#81D4FA#4FC3F7#29B6F6#03A9F4#039BE5#0288D1#0277BD#01579B#80D8FF#40C4FF#00B0FF#0091EA', + cyan = + '#E0F7FA#B2EBF2#80DEEA#4DD0E1#26C6DA#00BCD4#00ACC1#0097A7#00838F#006064#84FFFF#18FFFF#00E5FF#00B8D4', + teal = + '#E0F2F1#B2DFDB#80CBC4#4DB6AC#26A69A#009688#00897B#00796B#00695C#004D40#A7FFEB#64FFDA#1DE9B6#00BFA5', + green = + '#E8F5E9#C8E6C9#A5D6A7#81C784#66BB6A#4CAF50#43A047#388E3C#2E7D32#1B5E20#B9F6CA#69F0AE#00E676#00C853', + lightgreen = + '#F1F8E9#DCEDC8#C5E1A5#AED581#9CCC65#8BC34A#7CB342#689F38#558B2F#33691E#CCFF90#B2FF59#76FF03#64DD17', + lime = + '#F9FBE7#F0F4C3#E6EE9C#DCE775#D4E157#CDDC39#C0CA33#AFB42B#9E9D24#827717#F4FF81#EEFF41#C6FF00#AEEA00', + yellow = + '#FFFDE7#FFF9C4#FFF59D#FFF176#FFEE58#FFEB3B#FDD835#FBC02D#F9A825#F57F17#FFFF8D#FFFF00#FFEA00#FFD600', + amber = + '#FFF8E1#FFECB3#FFE082#FFD54F#FFCA28#FFC107#FFB300#FFA000#FF8F00#FF6F00#FFE57F#FFD740#FFC400#FFAB00', + orange = + '#FFF3E0#FFE0B2#FFCC80#FFB74D#FFA726#FF9800#FB8C00#F57C00#EF6C00#E65100#FFD180#FFAB40#FF9100#FF6D00', + deeporange = + '#FBE9E7#FFCCBC#FFAB91#FF8A65#FF7043#FF5722#F4511E#E64A19#D84315#BF360C#FF9E80#FF6E40#FF3D00#DD2C00', + brown = + '#EFEBE9#D7CCC8#BCAAA4#A1887F#8D6E63#795548#6D4C41#5D4037#4E342E#3E2723', + grey = + '#FAFAFA#F5F5F5#EEEEEE#E0E0E0#BDBDBD#9E9E9E#757575#616161#424242#212121', + bluegrey = + '#ECEFF1#CFD8DC#B0BEC5#90A4AE#78909C#607D8B#546E7A#455A64#37474F#263238' + /*\ + * Snap.mui + [ property ] + ** + * Contain Material UI colours. + | Snap().rect(0, 0, 10, 10).attr({fill: Snap.mui.deeppurple, stroke: Snap.mui.amber[600]}); + # For colour reference: https://www.materialui.co. + \*/ + Snap.mui = {} + /*\ + * Snap.flat + [ property ] + ** + * Contain Flat UI colours. + | Snap().rect(0, 0, 10, 10).attr({fill: Snap.flat.carrot, stroke: Snap.flat.wetasphalt}); + # For colour reference: https://www.materialui.co. + \*/ + Snap.flat = {} + function saveColor(colors) { + colors = colors.split(/(?=#)/) + var color = new String(colors[5]) + color[50] = colors[0] + color[100] = colors[1] + color[200] = colors[2] + color[300] = colors[3] + color[400] = colors[4] + color[500] = colors[5] + color[600] = colors[6] + color[700] = colors[7] + color[800] = colors[8] + color[900] = colors[9] + if (colors[10]) { + color.A100 = colors[10] + color.A200 = colors[11] + color.A400 = colors[12] + color.A700 = colors[13] + } + return color + } + Snap.mui.red = saveColor(red) + Snap.mui.pink = saveColor(pink) + Snap.mui.purple = saveColor(purple) + Snap.mui.deeppurple = saveColor(deeppurple) + Snap.mui.indigo = saveColor(indigo) + Snap.mui.blue = saveColor(blue) + Snap.mui.lightblue = saveColor(lightblue) + Snap.mui.cyan = saveColor(cyan) + Snap.mui.teal = saveColor(teal) + Snap.mui.green = saveColor(green) + Snap.mui.lightgreen = saveColor(lightgreen) + Snap.mui.lime = saveColor(lime) + Snap.mui.yellow = saveColor(yellow) + Snap.mui.amber = saveColor(amber) + Snap.mui.orange = saveColor(orange) + Snap.mui.deeporange = saveColor(deeporange) + Snap.mui.brown = saveColor(brown) + Snap.mui.grey = saveColor(grey) + Snap.mui.bluegrey = saveColor(bluegrey) + Snap.flat.turquoise = '#1abc9c' + Snap.flat.greensea = '#16a085' + Snap.flat.sunflower = '#f1c40f' + Snap.flat.orange = '#f39c12' + Snap.flat.emerland = '#2ecc71' + Snap.flat.nephritis = '#27ae60' + Snap.flat.carrot = '#e67e22' + Snap.flat.pumpkin = '#d35400' + Snap.flat.peterriver = '#3498db' + Snap.flat.belizehole = '#2980b9' + Snap.flat.alizarin = '#e74c3c' + Snap.flat.pomegranate = '#c0392b' + Snap.flat.amethyst = '#9b59b6' + Snap.flat.wisteria = '#8e44ad' + Snap.flat.clouds = '#ecf0f1' + Snap.flat.silver = '#bdc3c7' + Snap.flat.wetasphalt = '#34495e' + Snap.flat.midnightblue = '#2c3e50' + Snap.flat.concrete = '#95a5a6' + Snap.flat.asbestos = '#7f8c8d' + /*\ + * Snap.importMUIColors + [ method ] + ** + * Imports Material UI colours into global object. + | Snap.importMUIColors(); + | Snap().rect(0, 0, 10, 10).attr({fill: deeppurple, stroke: amber[600]}); + # For colour reference: https://www.materialui.co. + \*/ + Snap.importMUIColors = function () { + for (var color in Snap.mui) { + if (Snap.mui.hasOwnProperty(color)) { + window[color] = Snap.mui[color] + } + } + } +}) diff --git a/src/element.js b/src/element.js new file mode 100644 index 0000000..ef76cad --- /dev/null +++ b/src/element.js @@ -0,0 +1,815 @@ +// 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' +import { Snap } from './svg.js' + +Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { + var elproto = Element.prototype, + is = Snap.is, + Str = String, + unit2px = Snap._unit2px, + $ = Snap._.$, + make = Snap._.make, + getSomeDefs = Snap._.getSomeDefs, + has = 'hasOwnProperty', + wrap = Snap._.wrap + /*\ + * Element.getBBox + [ method ] + ** + * Returns the bounding box descriptor for the given element + ** + = (object) bounding box descriptor: + o { + o cx: (number) x of the center, + o cy: (number) x of the center, + o h: (number) height, + o height: (number) height, + o path: (string) path command for the box, + o r0: (number) radius of a circle that fully encloses the box, + o r1: (number) radius of the smallest circle that can be enclosed, + o r2: (number) radius of the largest circle that can be enclosed, + o vb: (string) box as a viewbox command, + o w: (number) width, + o width: (number) width, + o x2: (number) x of the right side, + o x: (number) x of the left side, + o y2: (number) y of the bottom edge, + o y: (number) y of the top edge + o } + \*/ + elproto.getBBox = function (isWithoutTransform) { + if (this.type == 'tspan') { + return Snap._.box(this.node.getClientRects().item(0)) + } + if (!Snap.Matrix || !Snap.path) { + return this.node.getBBox() + } + var el = this, + m = new Snap.Matrix() + if (el.removed) { + return Snap._.box() + } + while (el.type == 'use') { + if (!isWithoutTransform) { + m = m.add( + el + .transform() + .localMatrix.translate(el.attr('x') || 0, el.attr('y') || 0) + ) + } + if (el.original) { + el = el.original + } else { + var href = el.attr('xlink:href') + el = el.original = el.node.ownerDocument.getElementById( + href.substring(href.indexOf('#') + 1) + ) + } + } + var _ = el._, + pathfinder = Snap.path.get[el.type] || Snap.path.get.deflt + try { + if (isWithoutTransform) { + _.bboxwt = pathfinder + ? Snap.path.getBBox((el.realPath = pathfinder(el))) + : Snap._.box(el.node.getBBox()) + return Snap._.box(_.bboxwt) + } else { + el.realPath = pathfinder(el) + el.matrix = el.transform().localMatrix + _.bbox = Snap.path.getBBox(Snap.path.map(el.realPath, m.add(el.matrix))) + return Snap._.box(_.bbox) + } + } catch (e) { + // Firefox doesn’t give you bbox of hidden element + return Snap._.box() + } + } + var propString = function () { + return this.string + } + function extractTransform(el, tstr) { + if (tstr == null) { + var doReturn = true + if (el.type == 'linearGradient' || el.type == 'radialGradient') { + tstr = el.node.getAttribute('gradientTransform') + } else if (el.type == 'pattern') { + tstr = el.node.getAttribute('patternTransform') + } else { + tstr = el.node.getAttribute('transform') + } + if (!tstr) { + return new Snap.Matrix() + } + tstr = Snap._.svgTransform2string(tstr) + } else { + if (!Snap._.rgTransform.test(tstr)) { + tstr = Snap._.svgTransform2string(tstr) + } else { + tstr = Str(tstr).replace(/\.{3}|\u2026/g, el._.transform || '') + } + if (is(tstr, 'array')) { + tstr = Snap.path ? Snap.path.toString.call(tstr) : Str(tstr) + } + el._.transform = tstr + } + var m = Snap._.transform2matrix(tstr, el.getBBox(1)) + if (doReturn) { + return m + } else { + el.matrix = m + } + } + /*\ + * Element.transform + [ method ] + ** + * Gets or sets transformation of the element + ** + - tstr (string) transform string in Snap or SVG format + = (Element) the current element + * or + = (object) transformation descriptor: + o { + o string (string) transform string, + o globalMatrix (Matrix) matrix of all transformations applied to element or its parents, + o localMatrix (Matrix) matrix of transformations applied only to the element, + o diffMatrix (Matrix) matrix of difference between global and local transformations, + o global (string) global transformation as string, + o local (string) local transformation as string, + o toString (function) returns `string` property + o } + \*/ + elproto.transform = function (tstr) { + var _ = this._ + if (tstr == null) { + var papa = this, + global = new Snap.Matrix(this.node.getCTM()), + local = extractTransform(this), + ms = [local], + m = new Snap.Matrix(), + i, + localString = local.toTransformString(), + string = Str(local) == Str(this.matrix) ? Str(_.transform) : localString + while (papa.type != 'svg' && (papa = papa.parent())) { + ms.push(extractTransform(papa)) + } + i = ms.length + while (i--) { + m.add(ms[i]) + } + return { + string: string, + globalMatrix: global, + totalMatrix: m, + localMatrix: local, + diffMatrix: global.clone().add(local.invert()), + global: global.toTransformString(), + total: m.toTransformString(), + local: localString, + toString: propString + } + } + if (tstr instanceof Snap.Matrix) { + this.matrix = tstr + this._.transform = tstr.toTransformString() + } else { + extractTransform(this, tstr) + } + + if (this.node) { + if (this.type == 'linearGradient' || this.type == 'radialGradient') { + $(this.node, { gradientTransform: this.matrix }) + } else if (this.type == 'pattern') { + $(this.node, { patternTransform: this.matrix }) + } else { + $(this.node, { transform: this.matrix }) + } + } + + return this + } + /*\ + * Element.parent + [ method ] + ** + * Returns the element's parent + ** + = (Element) the parent element + \*/ + elproto.parent = function () { + return wrap(this.node.parentNode) + } + /*\ + * Element.append + [ method ] + ** + * Appends the given element to current one + ** + - el (Element|Set) element to append + = (Element) the parent element + \*/ + /*\ + * Element.add + [ method ] + ** + * See @Element.append + \*/ + elproto.append = elproto.add = function (el) { + if (el) { + if (el.type == 'set') { + var it = this + el.forEach(function (el) { + it.add(el) + }) + return this + } + el = wrap(el) + this.node.appendChild(el.node) + el.paper = this.paper + } + return this + } + /*\ + * Element.appendTo + [ method ] + ** + * Appends the current element to the given one + ** + - el (Element) parent element to append to + = (Element) the child element + \*/ + elproto.appendTo = function (el) { + if (el) { + el = wrap(el) + el.append(this) + } + return this + } + /*\ + * Element.prepend + [ method ] + ** + * Prepends the given element to the current one + ** + - el (Element) element to prepend + = (Element) the parent element + \*/ + elproto.prepend = function (el) { + if (el) { + if (el.type == 'set') { + var it = this, + first + el.forEach(function (el) { + if (first) { + first.after(el) + } else { + it.prepend(el) + } + first = el + }) + return this + } + el = wrap(el) + var parent = el.parent() + this.node.insertBefore(el.node, this.node.firstChild) + this.add && this.add() + el.paper = this.paper + this.parent() && this.parent().add() + parent && parent.add() + } + return this + } + /*\ + * Element.prependTo + [ method ] + ** + * Prepends the current element to the given one + ** + - el (Element) parent element to prepend to + = (Element) the child element + \*/ + elproto.prependTo = function (el) { + el = wrap(el) + el.prepend(this) + return this + } + /*\ + * Element.before + [ method ] + ** + * Inserts given element before the current one + ** + - el (Element) element to insert + = (Element) the parent element + \*/ + elproto.before = function (el) { + if (el.type == 'set') { + var it = this + el.forEach(function (el) { + var parent = el.parent() + it.node.parentNode.insertBefore(el.node, it.node) + parent && parent.add() + }) + this.parent().add() + return this + } + el = wrap(el) + var parent = el.parent() + this.node.parentNode.insertBefore(el.node, this.node) + this.parent() && this.parent().add() + parent && parent.add() + el.paper = this.paper + return this + } + /*\ + * Element.after + [ method ] + ** + * Inserts given element after the current one + ** + - el (Element) element to insert + = (Element) the parent element + \*/ + elproto.after = function (el) { + el = wrap(el) + var parent = el.parent() + if (this.node.nextSibling) { + this.node.parentNode.insertBefore(el.node, this.node.nextSibling) + } else { + this.node.parentNode.appendChild(el.node) + } + this.parent() && this.parent().add() + parent && parent.add() + el.paper = this.paper + return this + } + /*\ + * Element.insertBefore + [ method ] + ** + * Inserts the element after the given one + ** + - el (Element) element next to whom insert to + = (Element) the parent element + \*/ + elproto.insertBefore = function (el) { + el = wrap(el) + var parent = this.parent() + el.node.parentNode.insertBefore(this.node, el.node) + this.paper = el.paper + parent && parent.add() + el.parent() && el.parent().add() + return this + } + /*\ + * Element.insertAfter + [ method ] + ** + * Inserts the element after the given one + ** + - el (Element) element next to whom insert to + = (Element) the parent element + \*/ + elproto.insertAfter = function (el) { + el = wrap(el) + var parent = this.parent() + el.node.parentNode.insertBefore(this.node, el.node.nextSibling) + this.paper = el.paper + parent && parent.add() + el.parent() && el.parent().add() + return this + } + /*\ + * Element.remove + [ method ] + ** + * Removes element from the DOM + = (Element) the detached element + \*/ + elproto.remove = function () { + var parent = this.parent() + this.node.parentNode && this.node.parentNode.removeChild(this.node) + delete this.paper + this.removed = true + parent && parent.add() + return this + } + /*\ + * Element.select + [ method ] + ** + * Gathers the nested @Element matching the given set of CSS selectors + ** + - query (string) CSS selector + = (Element) result of query selection + \*/ + elproto.select = function (query) { + return wrap(this.node.querySelector(query)) + } + /*\ + * Element.selectAll + [ method ] + ** + * Gathers nested @Element objects matching the given set of CSS selectors + ** + - query (string) CSS selector + = (Set|array) result of query selection + \*/ + elproto.selectAll = function (query) { + var nodelist = this.node.querySelectorAll(query), + set = (Snap.set || Array)() + for (var i = 0; i < nodelist.length; i++) { + set.push(wrap(nodelist[i])) + } + return set + } + /*\ + * Element.asPX + [ method ] + ** + * Returns given attribute of the element as a `px` value (not %, em, etc.) + ** + - attr (string) attribute name + - value (string) #optional attribute value + = (Element) result of query selection + \*/ + elproto.asPX = function (attr, value) { + if (value == null) { + value = this.attr(attr) + } + return +unit2px(this, attr, value) + } + // SIERRA Element.use(): I suggest adding a note about how to access the original element the returned instantiates. It's a part of SVG with which ordinary web developers may be least familiar. + /*\ + * Element.use + [ method ] + ** + * Creates a `` element linked to the current element + ** + = (Element) the `` element + \*/ + elproto.use = function () { + var use, + id = this.node.id + if (!id) { + id = this.id + $(this.node, { + id: id + }) + } + if ( + this.type == 'linearGradient' || + this.type == 'radialGradient' || + this.type == 'pattern' + ) { + use = make(this.type, this.node.parentNode) + } else { + use = make('use', this.node.parentNode) + } + $(use.node, { + 'xlink:href': '#' + id + }) + use.original = this + return use + } + function fixids(el) { + var els = el.selectAll('*'), + it, + url = /^\s*url\(("|'|)(.*)\1\)\s*$/, + ids = [], + uses = {} + function urltest(it, name) { + var val = $(it.node, name) + val = val && val.match(url) + val = val && val[2] + if (val && val.charAt() == '#') { + val = val.substring(1) + } else { + return + } + if (val) { + uses[val] = (uses[val] || []).concat(function (id) { + var attr = {} + attr[name] = Snap.url(id) + $(it.node, attr) + }) + } + } + function linktest(it) { + var val = $(it.node, 'xlink:href') + if (val && val.charAt() == '#') { + val = val.substring(1) + } else { + return + } + if (val) { + uses[val] = (uses[val] || []).concat(function (id) { + it.attr('xlink:href', '#' + id) + }) + } + } + for (var i = 0, ii = els.length; i < ii; i++) { + it = els[i] + urltest(it, 'fill') + urltest(it, 'stroke') + urltest(it, 'filter') + urltest(it, 'mask') + urltest(it, 'clip-path') + linktest(it) + var oldid = $(it.node, 'id') + if (oldid) { + $(it.node, { id: it.id }) + ids.push({ + old: oldid, + id: it.id + }) + } + } + for (i = 0, ii = ids.length; i < ii; i++) { + var fs = uses[ids[i].old] + if (fs) { + for (var j = 0, jj = fs.length; j < jj; j++) { + fs[j](ids[i].id) + } + } + } + } + /*\ + * Element.clone + [ method ] + ** + * Creates a clone of the element and inserts it after the element + ** + = (Element) the clone + \*/ + elproto.clone = function () { + var clone = wrap(this.node.cloneNode(true)) + if ($(clone.node, 'id')) { + $(clone.node, { id: clone.id }) + } + fixids(clone) + clone.insertAfter(this) + return clone + } + /*\ + * Element.toDefs + [ method ] + ** + * Moves element to the shared `` area + ** + = (Element) the element + \*/ + elproto.toDefs = function () { + var defs = getSomeDefs(this) + defs.appendChild(this.node) + return this + } + /*\ + * Element.toPattern + [ method ] + ** + * Creates a `` element from the current element + ** + * To create a pattern you have to specify the pattern rect: + - x (string|number) + - y (string|number) + - width (string|number) + - height (string|number) + = (Element) the `` element + * You can use pattern later on as an argument for `fill` attribute: + | var p = paper.path("M10-5-10,15M15,0,0,15M0-5-20,15").attr({ + | fill: "none", + | stroke: "#bada55", + | strokeWidth: 5 + | }).pattern(0, 0, 10, 10), + | c = paper.circle(200, 200, 100); + | c.attr({ + | fill: p + | }); + \*/ + elproto.pattern = elproto.toPattern = function (x, y, width, height) { + var p = make('pattern', getSomeDefs(this)) + if (x == null) { + x = this.getBBox() + } + if (is(x, 'object') && 'x' in x) { + y = x.y + width = x.width + height = x.height + x = x.x + } + $(p.node, { + x: x, + y: y, + width: width, + height: height, + patternUnits: 'userSpaceOnUse', + id: p.id, + viewBox: [x, y, width, height].join(' ') + }) + p.node.appendChild(this.node) + return p + } + // SIERRA Element.marker(): clarify what a reference point is. E.g., helps you offset the object from its edge such as when centering it over a path. + // SIERRA Element.marker(): I suggest the method should accept default reference point values. Perhaps centered with (refX = width/2) and (refY = height/2)? Also, couldn't it assume the element's current _width_ and _height_? And please specify what _x_ and _y_ mean: offsets? If so, from where? Couldn't they also be assigned default values? + /*\ + * Element.marker + [ method ] + ** + * Creates a `` element from the current element + ** + * To create a marker you have to specify the bounding rect and reference point: + - x (number) + - y (number) + - width (number) + - height (number) + - refX (number) + - refY (number) + = (Element) the `` element + * You can specify the marker later as an argument for `marker-start`, `marker-end`, `marker-mid`, and `marker` attributes. The `marker` attribute places the marker at every point along the path, and `marker-mid` places them at every point except the start and end. + \*/ + // TODO add usage for markers + elproto.marker = function (x, y, width, height, refX, refY) { + var p = make('marker', getSomeDefs(this)) + if (x == null) { + x = this.getBBox() + } + if (is(x, 'object') && 'x' in x) { + y = x.y + width = x.width + height = x.height + refX = x.refX || x.cx + refY = x.refY || x.cy + x = x.x + } + $(p.node, { + viewBox: [x, y, width, height].join(' '), + markerWidth: width, + markerHeight: height, + orient: 'auto', + refX: refX || 0, + refY: refY || 0, + id: p.id + }) + p.node.appendChild(this.node) + return p + } + var eldata = {} + /*\ + * Element.data + [ method ] + ** + * Adds or retrieves given value associated with given key. (Don’t confuse + * with `data-` attributes) + * + * See also @Element.removeData + - key (string) key to store data + - value (any) #optional value to store + = (object) @Element + * or, if value is not specified: + = (any) value + > Usage + | for (var i = 0, i < 5, i++) { + | paper.circle(10 + 15 * i, 10, 10) + | .attr({fill: "#000"}) + | .data("i", i) + | .click(function () { + | alert(this.data("i")); + | }); + | } + \*/ + elproto.data = function (key, value) { + var data = (eldata[this.id] = eldata[this.id] || {}) + if (arguments.length == 0) { + eve('snap.data.get.' + this.id, this, data, null) + return data + } + if (arguments.length == 1) { + if (Snap.is(key, 'object')) { + for (var i in key) + if (key[has](i)) { + this.data(i, key[i]) + } + return this + } + eve('snap.data.get.' + this.id, this, data[key], key) + return data[key] + } + data[key] = value + eve('snap.data.set.' + this.id, this, value, key) + return this + } + /*\ + * Element.removeData + [ method ] + ** + * Removes value associated with an element by given key. + * If key is not provided, removes all the data of the element. + - key (string) #optional key + = (object) @Element + \*/ + elproto.removeData = function (key) { + if (key == null) { + eldata[this.id] = {} + } else { + eldata[this.id] && delete eldata[this.id][key] + } + return this + } + /*\ + * Element.outerSVG + [ method ] + ** + * Returns SVG code for the element, equivalent to HTML's `outerHTML`. + * + * See also @Element.innerSVG + = (string) SVG code for the element + \*/ + /*\ + * Element.toString + [ method ] + ** + * See @Element.outerSVG + \*/ + elproto.outerSVG = elproto.toString = toString(1) + /*\ + * Element.innerSVG + [ method ] + ** + * Returns SVG code for the element's contents, equivalent to HTML's `innerHTML` + = (string) SVG code for the element + \*/ + elproto.innerSVG = toString() + function toString(type) { + return function () { + var res = type ? '<' + this.type : '', + attr = this.node.attributes, + chld = this.node.childNodes + if (type) { + for (var i = 0, ii = attr.length; i < ii; i++) { + res += + ' ' + attr[i].name + '="' + attr[i].value.replace(/"/g, '\\"') + '"' + } + } + if (chld.length) { + type && (res += '>') + for (i = 0, ii = chld.length; i < ii; i++) { + if (chld[i].nodeType == 3) { + res += chld[i].nodeValue + } else if (chld[i].nodeType == 1) { + res += wrap(chld[i]).toString() + } + } + type && (res += '') + } else { + type && (res += '/>') + } + return res + } + } + elproto.toDataURL = function () { + if (window && window.btoa) { + var bb = this.getBBox(), + svg = Snap.format( + '{contents}', + { + x: +bb.x.toFixed(3), + y: +bb.y.toFixed(3), + width: +bb.width.toFixed(3), + height: +bb.height.toFixed(3), + contents: this.outerSVG() + } + ) + return ( + 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svg))) + ) + } + } + /*\ + * Fragment.select + [ method ] + ** + * See @Element.select + \*/ + Fragment.prototype.select = elproto.select + /*\ + * Fragment.selectAll + [ method ] + ** + * See @Element.selectAll + \*/ + Fragment.prototype.selectAll = elproto.selectAll +}) diff --git a/src/equal.js b/src/equal.js new file mode 100644 index 0000000..4ffaca7 --- /dev/null +++ b/src/equal.js @@ -0,0 +1,227 @@ +// 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' +import { Snap } from './svg.js' + +Snap.plugin(function (Snap, Element, Paper, glob) { + var names = {}, + reUnit = /[%a-z]+$/i, + Str = String + names.stroke = names.fill = 'colour' + function getEmpty(item) { + var l = item[0] + switch (l.toLowerCase()) { + case 't': + return [l, 0, 0] + case 'm': + return [l, 1, 0, 0, 1, 0, 0] + case 'r': + if (item.length == 4) { + return [l, 0, item[2], item[3]] + } else { + return [l, 0] + } + case 's': + if (item.length == 5) { + return [l, 1, 1, item[3], item[4]] + } else if (item.length == 3) { + return [l, 1, 1] + } else { + return [l, 1] + } + } + } + function equaliseTransform(t1, t2, getBBox) { + t1 = t1 || new Snap.Matrix() + t2 = t2 || new Snap.Matrix() + t1 = Snap.parseTransformString(t1.toTransformString()) || [] + t2 = Snap.parseTransformString(t2.toTransformString()) || [] + var maxlength = Math.max(t1.length, t2.length), + from = [], + to = [], + i = 0, + j, + jj, + tt1, + tt2 + for (; i < maxlength; i++) { + tt1 = t1[i] || getEmpty(t2[i]) + tt2 = t2[i] || getEmpty(tt1) + if ( + tt1[0] != tt2[0] || + (tt1[0].toLowerCase() == 'r' && + (tt1[2] != tt2[2] || tt1[3] != tt2[3])) || + (tt1[0].toLowerCase() == 's' && (tt1[3] != tt2[3] || tt1[4] != tt2[4])) + ) { + t1 = Snap._.transform2matrix(t1, getBBox()) + t2 = Snap._.transform2matrix(t2, getBBox()) + from = [['m', t1.a, t1.b, t1.c, t1.d, t1.e, t1.f]] + to = [['m', t2.a, t2.b, t2.c, t2.d, t2.e, t2.f]] + break + } + from[i] = [] + to[i] = [] + for (j = 0, jj = Math.max(tt1.length, tt2.length); j < jj; j++) { + j in tt1 && (from[i][j] = tt1[j]) + j in tt2 && (to[i][j] = tt2[j]) + } + } + return { + from: path2array(from), + to: path2array(to), + f: getPath(from) + } + } + function getNumber(val) { + return val + } + function getUnit(unit) { + return function (val) { + return +val.toFixed(3) + unit + } + } + function getViewBox(val) { + return val.join(' ') + } + function getColour(clr) { + return Snap.rgb(clr[0], clr[1], clr[2], clr[3]) + } + function getPath(path) { + var k = 0, + i, + ii, + j, + jj, + out, + a, + b = [] + for (i = 0, ii = path.length; i < ii; i++) { + out = '[' + a = ['"' + path[i][0] + '"'] + for (j = 1, jj = path[i].length; j < jj; j++) { + a[j] = 'val[' + k++ + ']' + } + out += a + ']' + b[i] = out + } + return Function('val', 'return Snap.path.toString.call([' + b + '])') + } + function path2array(path) { + var out = [] + for (var i = 0, ii = path.length; i < ii; i++) { + for (var j = 1, jj = path[i].length; j < jj; j++) { + out.push(path[i][j]) + } + } + return out + } + function isNumeric(obj) { + return isFinite(obj) + } + function arrayEqual(arr1, arr2) { + if (!Snap.is(arr1, 'array') || !Snap.is(arr2, 'array')) { + return false + } + return arr1.toString() == arr2.toString() + } + Element.prototype.equal = function (name, b) { + return eve('snap.util.equal', this, name, b).firstDefined() + } + eve.on('snap.util.equal', function (name, b) { + var A, + B, + a = Str(this.attr(name) || ''), + el = this + if (names[name] == 'colour') { + A = Snap.color(a) + B = Snap.color(b) + return { + from: [A.r, A.g, A.b, A.opacity], + to: [B.r, B.g, B.b, B.opacity], + f: getColour + } + } + if (name == 'viewBox') { + A = this.attr(name).vb.split(' ').map(Number) + B = b.split(' ').map(Number) + return { + from: A, + to: B, + f: getViewBox + } + } + if ( + name == 'transform' || + name == 'gradientTransform' || + name == 'patternTransform' + ) { + if (typeof b == 'string') { + b = Str(b).replace(/\.{3}|\u2026/g, a) + } + a = this.matrix + if (!Snap._.rgTransform.test(b)) { + b = Snap._.transform2matrix( + Snap._.svgTransform2string(b), + this.getBBox() + ) + } else { + b = Snap._.transform2matrix(b, this.getBBox()) + } + return equaliseTransform(a, b, function () { + return el.getBBox(1) + }) + } + if (name == 'd' || name == 'path') { + A = Snap.path.toCubic(a, b) + return { + from: path2array(A[0]), + to: path2array(A[1]), + f: getPath(A[0]) + } + } + if (name == 'points') { + A = Str(a).split(Snap._.separator) + B = Str(b).split(Snap._.separator) + return { + from: A, + to: B, + f: function (val) { + return val + } + } + } + if (isNumeric(a) && isNumeric(b)) { + return { + from: parseFloat(a), + to: parseFloat(b), + f: getNumber + } + } + var aUnit = a.match(reUnit), + bUnit = Str(b).match(reUnit) + if (aUnit && arrayEqual(aUnit, bUnit)) { + return { + from: parseFloat(a), + to: parseFloat(b), + f: getUnit(aUnit) + } + } else { + return { + from: this.asPX(name), + to: this.asPX(name, b), + f: getNumber + } + } + }) +}) diff --git a/src/eve.js b/src/eve.js new file mode 100644 index 0000000..18e4ea8 --- /dev/null +++ b/src/eve.js @@ -0,0 +1,458 @@ +// Copyright (c) 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. +// ┌────────────────────────────────────────────────────────────┐ \\ +// │ Eve 0.5.4 - JavaScript Events Library │ \\ +// ├────────────────────────────────────────────────────────────┤ \\ +// │ Author Dmitry Baranovskiy (http://dmitry.baranovskiy.com/) │ \\ +// └────────────────────────────────────────────────────────────┘ \\ + +var version = '0.5.4', + has = 'hasOwnProperty', + separator = /[\.\/]/, + comaseparator = /\s*,\s*/, + wildcard = '*', + numsort = function (a, b) { + return a - b + }, + current_event, + stop, + events = { n: {} }, + firstDefined = function () { + for (var i = 0, ii = this.length; i < ii; i++) { + if (typeof this[i] != 'undefined') { + return this[i] + } + } + }, + lastDefined = function () { + var i = this.length + while (--i) { + if (typeof this[i] != 'undefined') { + return this[i] + } + } + }, + objtos = Object.prototype.toString, + Str = String, + isArray = + Array.isArray || + function (ar) { + return ar instanceof Array || objtos.call(ar) == '[object Array]' + }, + /*\ + * eve + [ method ] + + * Fires event with given `name`, given scope and other parameters. + + - name (string) name of the *event*, dot (`.`) or slash (`/`) separated + - scope (object) context for the event handlers + - varargs (...) the rest of arguments will be sent to event handlers + + = (object) array of returned values from the listeners. Array has two methods `.firstDefined()` and `.lastDefined()` to get first or last not `undefined` value. + \*/ + eve = function (name, scope) { + var oldstop = stop, + args = Array.prototype.slice.call(arguments, 2), + listeners = eve.listeners(name), + z = 0, + l, + indexed = [], + queue = {}, + out = [], + ce = current_event + out.firstDefined = firstDefined + out.lastDefined = lastDefined + current_event = name + stop = 0 + for (var i = 0, ii = listeners.length; i < ii; i++) + if ('zIndex' in listeners[i]) { + indexed.push(listeners[i].zIndex) + if (listeners[i].zIndex < 0) { + queue[listeners[i].zIndex] = listeners[i] + } + } + indexed.sort(numsort) + while (indexed[z] < 0) { + l = queue[indexed[z++]] + out.push(l.apply(scope, args)) + if (stop) { + stop = oldstop + return out + } + } + for (i = 0; i < ii; i++) { + l = listeners[i] + if ('zIndex' in l) { + if (l.zIndex == indexed[z]) { + out.push(l.apply(scope, args)) + if (stop) { + break + } + do { + z++ + l = queue[indexed[z]] + l && out.push(l.apply(scope, args)) + if (stop) { + break + } + } while (l) + } else { + queue[l.zIndex] = l + } + } else { + out.push(l.apply(scope, args)) + if (stop) { + break + } + } + } + stop = oldstop + current_event = ce + return out + } +// Undocumented. Debug only. +eve._events = events +/*\ + * eve.listeners + [ method ] + + * Internal method which gives you array of all event handlers that will be triggered by the given `name`. + + - name (string) name of the event, dot (`.`) or slash (`/`) separated + + = (array) array of event handlers + \*/ +eve.listeners = function (name) { + var names = isArray(name) ? name : name.split(separator), + e = events, + item, + items, + k, + i, + ii, + j, + jj, + nes, + es = [e], + out = [] + for (i = 0, ii = names.length; i < ii; i++) { + nes = [] + for (j = 0, jj = es.length; j < jj; j++) { + e = es[j].n + items = [e[names[i]], e[wildcard]] + k = 2 + while (k--) { + item = items[k] + if (item) { + nes.push(item) + out = out.concat(item.f || []) + } + } + } + es = nes + } + return out +} +/*\ + * eve.separator + [ method ] + + * If for some reasons you don’t like default separators (`.` or `/`) you can specify yours + * here. Be aware that if you pass a string longer than one character it will be treated as + * a list of characters. + + - separator (string) new separator. Empty string resets to default: `.` or `/`. + \*/ +eve.separator = function (sep) { + if (sep) { + sep = Str(sep).replace(/(?=[\.\^\]\[\-])/g, '\\') + sep = '[' + sep + ']' + separator = new RegExp(sep) + } else { + separator = /[\.\/]/ + } +} +/*\ + * eve.on + [ method ] + ** + * Binds given event handler with a given name. You can use wildcards “`*`” for the names: + | eve.on("*.under.*", f); + | eve("mouse.under.floor"); // triggers f + * Use @eve to trigger the listener. + ** + - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards + - f (function) event handler function + ** + - name (array) if you don’t want to use separators, you can use array of strings + - f (function) event handler function + ** + = (function) returned function accepts a single numeric parameter that represents z-index of the handler. It is an optional feature and only used when you need to ensure that some subset of handlers will be invoked in a given order, despite of the order of assignment. + > Example: + | eve.on("mouse", eatIt)(2); + | eve.on("mouse", scream); + | eve.on("mouse", catchIt)(1); + * This will ensure that `catchIt` function will be called before `eatIt`. + * + * If you want to put your handler before non-indexed handlers, specify a negative value. + * Note: I assume most of the time you don’t need to worry about z-index, but it’s nice to have this feature “just in case”. + \*/ +eve.on = function (name, f) { + if (typeof f != 'function') { + return function () {} + } + var names = isArray(name) + ? isArray(name[0]) + ? name + : [name] + : Str(name).split(comaseparator) + for (var i = 0, ii = names.length; i < ii; i++) { + ;(function (name) { + var names = isArray(name) ? name : Str(name).split(separator), + e = events, + exist + for (var i = 0, ii = names.length; i < ii; i++) { + e = e.n + e = + (e.hasOwnProperty(names[i]) && e[names[i]]) || + (e[names[i]] = { n: {} }) + } + e.f = e.f || [] + for (i = 0, ii = e.f.length; i < ii; i++) + if (e.f[i] == f) { + exist = true + break + } + !exist && e.f.push(f) + })(names[i]) + } + return function (zIndex) { + if (+zIndex == +zIndex) { + f.zIndex = +zIndex + } + } +} +/*\ + * eve.f + [ method ] + ** + * Returns function that will fire given event with optional arguments. + * Arguments that will be passed to the result function will be also + * concated to the list of final arguments. + | el.onclick = eve.f("click", 1, 2); + | eve.on("click", function (a, b, c) { + | console.log(a, b, c); // 1, 2, [event object] + | }); + - event (string) event name + - varargs (…) and any other arguments + = (function) possible event handler function + \*/ +eve.f = function (event) { + var attrs = [].slice.call(arguments, 1) + return function () { + eve.apply( + null, + [event, null].concat(attrs).concat([].slice.call(arguments, 0)) + ) + } +} +/*\ + * eve.stop + [ method ] + ** + * Is used inside an event handler to stop the event, preventing any subsequent listeners from firing. + \*/ +eve.stop = function () { + stop = 1 +} +/*\ + * eve.nt + [ method ] + ** + * Could be used inside event handler to figure out actual name of the event. + ** + - subname (string) #optional subname of the event + ** + = (string) name of the event, if `subname` is not specified + * or + = (boolean) `true`, if current event’s name contains `subname` + \*/ +eve.nt = function (subname) { + var cur = isArray(current_event) ? current_event.join('.') : current_event + if (subname) { + return new RegExp('(?:\\.|\\/|^)' + subname + '(?:\\.|\\/|$)').test(cur) + } + return cur +} +/*\ + * eve.nts + [ method ] + ** + * Could be used inside event handler to figure out actual name of the event. + ** + ** + = (array) names of the event + \*/ +eve.nts = function () { + return isArray(current_event) ? current_event : current_event.split(separator) +} +/*\ + * eve.off + [ method ] + ** + * Removes given function from the list of event listeners assigned to given name. + * If no arguments specified all the events will be cleared. + ** + - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards + - f (function) event handler function + \*/ +/*\ + * eve.unbind + [ method ] + ** + * See @eve.off + \*/ +eve.off = eve.unbind = function (name, f) { + if (!name) { + eve._events = events = { n: {} } + return + } + var names = isArray(name) + ? isArray(name[0]) + ? name + : [name] + : Str(name).split(comaseparator) + if (names.length > 1) { + for (var i = 0, ii = names.length; i < ii; i++) { + eve.off(names[i], f) + } + return + } + names = isArray(name) ? name : Str(name).split(separator) + var e, + key, + splice, + i, + ii, + j, + jj, + cur = [events], + inodes = [] + for (i = 0, ii = names.length; i < ii; i++) { + for (j = 0; j < cur.length; j += splice.length - 2) { + splice = [j, 1] + e = cur[j].n + if (names[i] != wildcard) { + if (e[names[i]]) { + splice.push(e[names[i]]) + inodes.unshift({ + n: e, + name: names[i] + }) + } + } else { + for (key in e) + if (e[has](key)) { + splice.push(e[key]) + inodes.unshift({ + n: e, + name: key + }) + } + } + cur.splice.apply(cur, splice) + } + } + for (i = 0, ii = cur.length; i < ii; i++) { + e = cur[i] + while (e.n) { + if (f) { + if (e.f) { + for (j = 0, jj = e.f.length; j < jj; j++) + if (e.f[j] == f) { + e.f.splice(j, 1) + break + } + !e.f.length && delete e.f + } + for (key in e.n) + if (e.n[has](key) && e.n[key].f) { + var funcs = e.n[key].f + for (j = 0, jj = funcs.length; j < jj; j++) + if (funcs[j] == f) { + funcs.splice(j, 1) + break + } + !funcs.length && delete e.n[key].f + } + } else { + delete e.f + for (key in e.n) + if (e.n[has](key) && e.n[key].f) { + delete e.n[key].f + } + } + e = e.n + } + } + // prune inner nodes in path + prune: for (i = 0, ii = inodes.length; i < ii; i++) { + e = inodes[i] + for (key in e.n[e.name].f) { + // not empty (has listeners) + continue prune + } + for (key in e.n[e.name].n) { + // not empty (has children) + continue prune + } + // is empty + delete e.n[e.name] + } +} +/*\ + * eve.once + [ method ] + ** + * Binds given event handler with a given name to only run once then unbind itself. + | eve.once("login", f); + | eve("login"); // triggers f + | eve("login"); // no listeners + * Use @eve to trigger the listener. + ** + - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards + - f (function) event handler function + ** + = (function) same return function as @eve.on + \*/ +eve.once = function (name, f) { + var f2 = function () { + eve.off(name, f2) + return f.apply(this, arguments) + } + return eve.on(name, f2) +} +/*\ + * eve.version + [ property (string) ] + ** + * Current version of the library. + \*/ +eve.version = version +eve.toString = function () { + return 'You are running Eve ' + version +} + +export default eve diff --git a/src/filter.js b/src/filter.js new file mode 100644 index 0000000..3f24a8d --- /dev/null +++ b/src/filter.js @@ -0,0 +1,348 @@ +// 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' +import { Snap } from './svg.js' + +Snap.plugin(function (Snap, Element, Paper, glob) { + var elproto = Element.prototype, + pproto = Paper.prototype, + rgurl = /^\s*url\((.+)\)/, + Str = String, + $ = Snap._.$ + Snap.filter = {} + /*\ + * Paper.filter + [ method ] + ** + * Creates a `` element + ** + - filstr (string) SVG fragment of filter provided as a string + = (object) @Element + * Note: It is recommended to use filters embedded into the page inside an empty SVG element. + > Usage + | var f = paper.filter(''), + | c = paper.circle(10, 10, 10).attr({ + | filter: f + | }); + \*/ + pproto.filter = function (filstr) { + var paper = this + if (paper.type != 'svg') { + paper = paper.paper + } + var f = Snap.parse(Str(filstr)), + id = Snap._.id(), + 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 Element(filter) + } + + eve.on('snap.util.getattr.filter', function () { + eve.stop() + var p = $(this.node, 'filter') + if (p) { + var match = Str(p).match(rgurl) + return match && Snap.select(match[1]) + } + }) + eve.on('snap.util.attr.filter', function (value) { + if (value instanceof Element && value.type == 'filter') { + eve.stop() + var id = value.node.id + if (!id) { + $(value.node, { id: value.id }) + id = value.id + } + $(this.node, { + filter: Snap.url(id) + }) + } + if (!value || value == 'none') { + eve.stop() + this.node.removeAttribute('filter') + } + }) + /*\ + * Snap.filter.blur + [ method ] + ** + * Returns an SVG markup string for the blur filter + ** + - x (number) amount of horizontal blur, in pixels + - y (number) #optional amount of vertical blur, in pixels + = (string) filter representation + > Usage + | var f = paper.filter(Snap.filter.blur(5, 10)), + | c = paper.circle(10, 10, 10).attr({ + | filter: f + | }); + \*/ + Snap.filter.blur = function (x, y) { + if (x == null) { + x = 2 + } + var def = y == null ? x : [x, y] + return Snap.format('', { + def: def + }) + } + Snap.filter.blur.toString = function () { + return this() + } + /*\ + * Snap.filter.shadow + [ method ] + ** + * Returns an SVG markup string for the shadow filter + ** + - dx (number) #optional horizontal shift of the shadow, in pixels + - dy (number) #optional vertical shift of the shadow, in pixels + - blur (number) #optional amount of blur + - color (string) #optional color of the shadow + - opacity (number) #optional `0..1` opacity of the shadow + * or + - dx (number) #optional horizontal shift of the shadow, in pixels + - dy (number) #optional vertical shift of the shadow, in pixels + - color (string) #optional color of the shadow + - opacity (number) #optional `0..1` opacity of the shadow + * which makes blur default to `4`. Or + - dx (number) #optional horizontal shift of the shadow, in pixels + - dy (number) #optional vertical shift of the shadow, in pixels + - opacity (number) #optional `0..1` opacity of the shadow + = (string) filter representation + > Usage + | var f = paper.filter(Snap.filter.shadow(0, 2, .3)), + | c = paper.circle(10, 10, 10).attr({ + | filter: f + | }); + \*/ + Snap.filter.shadow = function (dx, dy, blur, color, opacity) { + if (opacity == null) { + if (color == null) { + opacity = blur + blur = 4 + color = '#000' + } else { + opacity = color + color = blur + blur = 4 + } + } + if (blur == null) { + blur = 4 + } + if (opacity == null) { + opacity = 1 + } + if (dx == null) { + dx = 0 + dy = 2 + } + if (dy == null) { + dy = dx + } + color = Snap.color(color) + return Snap.format( + '', + { + color: color, + dx: dx, + dy: dy, + blur: blur, + opacity: opacity + } + ) + } + Snap.filter.shadow.toString = function () { + return this() + } + /*\ + * Snap.filter.grayscale + [ method ] + ** + * Returns an SVG markup string for the grayscale filter + ** + - amount (number) amount of filter (`0..1`) + = (string) filter representation + \*/ + Snap.filter.grayscale = function (amount) { + if (amount == null) { + amount = 1 + } + return Snap.format( + '', + { + a: 0.2126 + 0.7874 * (1 - amount), + b: 0.7152 - 0.7152 * (1 - amount), + c: 0.0722 - 0.0722 * (1 - amount), + d: 0.2126 - 0.2126 * (1 - amount), + e: 0.7152 + 0.2848 * (1 - amount), + f: 0.0722 - 0.0722 * (1 - amount), + g: 0.2126 - 0.2126 * (1 - amount), + h: 0.0722 + 0.9278 * (1 - amount) + } + ) + } + Snap.filter.grayscale.toString = function () { + return this() + } + /*\ + * Snap.filter.sepia + [ method ] + ** + * Returns an SVG markup string for the sepia filter + ** + - amount (number) amount of filter (`0..1`) + = (string) filter representation + \*/ + Snap.filter.sepia = function (amount) { + if (amount == null) { + amount = 1 + } + return Snap.format( + '', + { + a: 0.393 + 0.607 * (1 - amount), + b: 0.769 - 0.769 * (1 - amount), + c: 0.189 - 0.189 * (1 - amount), + d: 0.349 - 0.349 * (1 - amount), + e: 0.686 + 0.314 * (1 - amount), + f: 0.168 - 0.168 * (1 - amount), + g: 0.272 - 0.272 * (1 - amount), + h: 0.534 - 0.534 * (1 - amount), + i: 0.131 + 0.869 * (1 - amount) + } + ) + } + Snap.filter.sepia.toString = function () { + return this() + } + /*\ + * Snap.filter.saturate + [ method ] + ** + * Returns an SVG markup string for the saturate filter + ** + - amount (number) amount of filter (`0..1`) + = (string) filter representation + \*/ + Snap.filter.saturate = function (amount) { + if (amount == null) { + amount = 1 + } + return Snap.format('', { + amount: 1 - amount + }) + } + Snap.filter.saturate.toString = function () { + return this() + } + /*\ + * Snap.filter.hueRotate + [ method ] + ** + * Returns an SVG markup string for the hue-rotate filter + ** + - angle (number) angle of rotation + = (string) filter representation + \*/ + Snap.filter.hueRotate = function (angle) { + angle = angle || 0 + return Snap.format('', { + angle: angle + }) + } + Snap.filter.hueRotate.toString = function () { + return this() + } + /*\ + * Snap.filter.invert + [ method ] + ** + * Returns an SVG markup string for the invert filter + ** + - amount (number) amount of filter (`0..1`) + = (string) filter representation + \*/ + Snap.filter.invert = function (amount) { + if (amount == null) { + amount = 1 + } + // + return Snap.format( + '', + { + amount: amount, + amount2: 1 - amount + } + ) + } + Snap.filter.invert.toString = function () { + return this() + } + /*\ + * Snap.filter.brightness + [ method ] + ** + * Returns an SVG markup string for the brightness filter + ** + - amount (number) amount of filter (`0..1`) + = (string) filter representation + \*/ + Snap.filter.brightness = function (amount) { + if (amount == null) { + amount = 1 + } + return Snap.format( + '', + { + amount: amount + } + ) + } + Snap.filter.brightness.toString = function () { + return this() + } + /*\ + * Snap.filter.contrast + [ method ] + ** + * Returns an SVG markup string for the contrast filter + ** + - amount (number) amount of filter (`0..1`) + = (string) filter representation + \*/ + Snap.filter.contrast = function (amount) { + if (amount == null) { + amount = 1 + } + return Snap.format( + '', + { + amount: amount, + amount2: 0.5 - amount / 2 + } + ) + } + Snap.filter.contrast.toString = function () { + return this() + } +}) diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..b644b82 --- /dev/null +++ b/src/index.js @@ -0,0 +1,17 @@ +import { Snap } from './svg.js' +import './element.js' +import './animation.js' +import './matrix.js' +import './attr.js' +import './class.js' +import './attradd.js' +import './paper.js' +import './path.js' +import './set.js' +import './equal.js' +import './mouse.js' +import './filter.js' +import './align.js' +import './colors.js' + +export default Snap diff --git a/src/matrix.js b/src/matrix.js new file mode 100644 index 0000000..3d59f58 --- /dev/null +++ b/src/matrix.js @@ -0,0 +1,413 @@ +// 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 { Snap } from './svg.js' +Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { + var objectToString = Object.prototype.toString, + Str = String, + math = Math, + E = '' + function Matrix(a, b, c, d, e, f) { + if (b == null && objectToString.call(a) == '[object SVGMatrix]') { + this.a = a.a + this.b = a.b + this.c = a.c + this.d = a.d + this.e = a.e + this.f = a.f + return + } + if (a != null) { + this.a = +a + this.b = +b + this.c = +c + this.d = +d + this.e = +e + this.f = +f + } else { + this.a = 1 + this.b = 0 + this.c = 0 + this.d = 1 + this.e = 0 + this.f = 0 + } + } + ;(function (matrixproto) { + /*\ + * Matrix.add + [ method ] + ** + * Adds the given matrix to existing one + - a (number) + - b (number) + - c (number) + - d (number) + - e (number) + - f (number) + * or + - matrix (object) @Matrix + \*/ + matrixproto.add = function (a, b, c, d, e, f) { + if (a && a instanceof Matrix) { + return this.add(a.a, a.b, a.c, a.d, a.e, a.f) + } + var aNew = a * this.a + b * this.c, + bNew = a * this.b + b * this.d + this.e += e * this.a + f * this.c + this.f += e * this.b + f * this.d + this.c = c * this.a + d * this.c + this.d = c * this.b + d * this.d + + this.a = aNew + this.b = bNew + return this + } + /*\ + * Matrix.multLeft + [ method ] + ** + * Multiplies a passed affine transform to the left: M * this. + - a (number) + - b (number) + - c (number) + - d (number) + - e (number) + - f (number) + * or + - matrix (object) @Matrix + \*/ + Matrix.prototype.multLeft = function (a, b, c, d, e, f) { + if (a && a instanceof Matrix) { + return this.multLeft(a.a, a.b, a.c, a.d, a.e, a.f) + } + var aNew = a * this.a + c * this.b, + cNew = a * this.c + c * this.d, + eNew = a * this.e + c * this.f + e + this.b = b * this.a + d * this.b + this.d = b * this.c + d * this.d + this.f = b * this.e + d * this.f + f + + this.a = aNew + this.c = cNew + this.e = eNew + return this + } + /*\ + * Matrix.invert + [ method ] + ** + * Returns an inverted version of the matrix + = (object) @Matrix + \*/ + matrixproto.invert = function () { + var me = this, + x = me.a * me.d - me.b * me.c + return new Matrix( + me.d / x, + -me.b / x, + -me.c / x, + me.a / x, + (me.c * me.f - me.d * me.e) / x, + (me.b * me.e - me.a * me.f) / x + ) + } + /*\ + * Matrix.clone + [ method ] + ** + * Returns a copy of the matrix + = (object) @Matrix + \*/ + matrixproto.clone = function () { + return new Matrix(this.a, this.b, this.c, this.d, this.e, this.f) + } + /*\ + * Matrix.translate + [ method ] + ** + * Translate the matrix + - x (number) horizontal offset distance + - y (number) vertical offset distance + \*/ + matrixproto.translate = function (x, y) { + this.e += x * this.a + y * this.c + this.f += x * this.b + y * this.d + return this + } + /*\ + * Matrix.scale + [ method ] + ** + * Scales the matrix + - x (number) amount to be scaled, with `1` resulting in no change + - y (number) #optional amount to scale along the vertical axis. (Otherwise `x` applies to both axes.) + - cx (number) #optional horizontal origin point from which to scale + - cy (number) #optional vertical origin point from which to scale + * Default cx, cy is the middle point of the element. + \*/ + matrixproto.scale = function (x, y, cx, cy) { + y == null && (y = x) + ;(cx || cy) && this.translate(cx, cy) + this.a *= x + this.b *= x + this.c *= y + this.d *= y + ;(cx || cy) && this.translate(-cx, -cy) + return this + } + /*\ + * Matrix.rotate + [ method ] + ** + * Rotates the matrix + - a (number) angle of rotation, in degrees + - x (number) horizontal origin point from which to rotate + - y (number) vertical origin point from which to rotate + \*/ + matrixproto.rotate = function (a, x, y) { + a = Snap.rad(a) + x = x || 0 + y = y || 0 + var cos = +math.cos(a).toFixed(9), + sin = +math.sin(a).toFixed(9) + this.add(cos, sin, -sin, cos, x, y) + return this.add(1, 0, 0, 1, -x, -y) + } + /*\ + * Matrix.skewX + [ method ] + ** + * Skews the matrix along the x-axis + - x (number) Angle to skew along the x-axis (in degrees). + \*/ + matrixproto.skewX = function (x) { + return this.skew(x, 0) + } + /*\ + * Matrix.skewY + [ method ] + ** + * Skews the matrix along the y-axis + - y (number) Angle to skew along the y-axis (in degrees). + \*/ + matrixproto.skewY = function (y) { + return this.skew(0, y) + } + /*\ + * Matrix.skew + [ method ] + ** + * Skews the matrix + - y (number) Angle to skew along the y-axis (in degrees). + - x (number) Angle to skew along the x-axis (in degrees). + \*/ + matrixproto.skew = function (x, y) { + x = x || 0 + y = y || 0 + x = Snap.rad(x) + y = Snap.rad(y) + var c = math.tan(x).toFixed(9) + var b = math.tan(y).toFixed(9) + return this.add(1, b, c, 1, 0, 0) + } + /*\ + * Matrix.x + [ method ] + ** + * Returns x coordinate for given point after transformation described by the matrix. See also @Matrix.y + - x (number) + - y (number) + = (number) x + \*/ + matrixproto.x = function (x, y) { + return x * this.a + y * this.c + this.e + } + /*\ + * Matrix.y + [ method ] + ** + * Returns y coordinate for given point after transformation described by the matrix. See also @Matrix.x + - x (number) + - y (number) + = (number) y + \*/ + matrixproto.y = function (x, y) { + return x * this.b + y * this.d + this.f + } + matrixproto.get = function (i) { + return +this[Str.fromCharCode(97 + i)].toFixed(4) + } + matrixproto.toString = function () { + return ( + 'matrix(' + + [ + this.get(0), + this.get(1), + this.get(2), + this.get(3), + this.get(4), + this.get(5) + ].join() + + ')' + ) + } + matrixproto.offset = function () { + return [this.e.toFixed(4), this.f.toFixed(4)] + } + function norm(a) { + return a[0] * a[0] + a[1] * a[1] + } + function normalize(a) { + var mag = math.sqrt(norm(a)) + a[0] && (a[0] /= mag) + a[1] && (a[1] /= mag) + } + /*\ + * Matrix.determinant + [ method ] + ** + * Finds determinant of the given matrix. + = (number) determinant + \*/ + matrixproto.determinant = function () { + return this.a * this.d - this.b * this.c + } + /*\ + * Matrix.split + [ method ] + ** + * Splits matrix into primitive transformations + = (object) in format: + o dx (number) translation by x + o dy (number) translation by y + o scalex (number) scale by x + o scaley (number) scale by y + o shear (number) shear + o rotate (number) rotation in deg + o isSimple (boolean) could it be represented via simple transformations + \*/ + matrixproto.split = function () { + var out = {} + // translation + out.dx = this.e + out.dy = this.f + + // scale and shear + var row = [ + [this.a, this.b], + [this.c, this.d] + ] + out.scalex = math.sqrt(norm(row[0])) + normalize(row[0]) + + out.shear = row[0][0] * row[1][0] + row[0][1] * row[1][1] + row[1] = [ + row[1][0] - row[0][0] * out.shear, + row[1][1] - row[0][1] * out.shear + ] + + out.scaley = math.sqrt(norm(row[1])) + normalize(row[1]) + out.shear /= out.scaley + + if (this.determinant() < 0) { + out.scalex = -out.scalex + } + + // rotation + var sin = row[0][1], + cos = row[1][1] + if (cos < 0) { + out.rotate = Snap.deg(math.acos(cos)) + if (sin < 0) { + out.rotate = 360 - out.rotate + } + } else { + out.rotate = Snap.deg(math.asin(sin)) + } + + out.isSimple = + !+out.shear.toFixed(9) && + (out.scalex.toFixed(9) == out.scaley.toFixed(9) || !out.rotate) + out.isSuperSimple = + !+out.shear.toFixed(9) && + out.scalex.toFixed(9) == out.scaley.toFixed(9) && + !out.rotate + out.noRotation = !+out.shear.toFixed(9) && !out.rotate + return out + } + /*\ + * Matrix.toTransformString + [ method ] + ** + * Returns transform string that represents given matrix + = (string) transform string + \*/ + matrixproto.toTransformString = function (shorter) { + var s = shorter || this.split() + if (!+s.shear.toFixed(9)) { + s.scalex = +s.scalex.toFixed(4) + s.scaley = +s.scaley.toFixed(4) + s.rotate = +s.rotate.toFixed(4) + return ( + (s.dx || s.dy ? 't' + [+s.dx.toFixed(4), +s.dy.toFixed(4)] : E) + + (s.rotate ? 'r' + [+s.rotate.toFixed(4), 0, 0] : E) + + (s.scalex != 1 || s.scaley != 1 + ? 's' + [s.scalex, s.scaley, 0, 0] + : E) + ) + } else { + return ( + 'm' + + [ + this.get(0), + this.get(1), + this.get(2), + this.get(3), + this.get(4), + this.get(5) + ] + ) + } + } + })(Matrix.prototype) + /*\ + * Snap.Matrix + [ method ] + ** + * Matrix constructor, extend on your own risk. + * To create matrices use @Snap.matrix. + \*/ + Snap.Matrix = Matrix + /*\ + * Snap.matrix + [ method ] + ** + * Utility method + ** + * Returns a matrix based on the given parameters + - a (number) + - b (number) + - c (number) + - d (number) + - e (number) + - f (number) + * or + - svgMatrix (SVGMatrix) + = (object) @Matrix + \*/ + Snap.matrix = function (a, b, c, d, e, f) { + return new Matrix(a, b, c, d, e, f) + } +}) diff --git a/src/mina.js b/src/mina.js new file mode 100644 index 0000000..8c18e53 --- /dev/null +++ b/src/mina.js @@ -0,0 +1,378 @@ +// Copyright (c) 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' +import { Snap } from './svg.js' + +var animations = {}, + requestAnimFrame = + window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function (callback) { + setTimeout(callback, 16, new Date().getTime()) + return true + }, + requestID, + isArray = + Array.isArray || + function (a) { + return ( + a instanceof Array || + Object.prototype.toString.call(a) == '[object Array]' + ) + }, + idgen = 0, + idprefix = 'M' + (+new Date()).toString(36), + ID = function () { + return idprefix + (idgen++).toString(36) + }, + diff = function (a, b, A, B) { + if (isArray(a)) { + res = [] + for (var i = 0, ii = a.length; i < ii; i++) { + res[i] = diff(a[i], b, A[i], B) + } + return res + } + var dif = (A - a) / (B - b) + return function (bb) { + return a + dif * (bb - b) + } + }, + timer = + Date.now || + function () { + return +new Date() + }, + sta = function (val) { + var a = this + if (val == null) { + return a.s + } + var ds = a.s - val + a.b += a.dur * ds + a.B += a.dur * ds + a.s = val + }, + speed = function (val) { + var a = this + if (val == null) { + return a.spd + } + a.spd = val + }, + duration = function (val) { + var a = this + if (val == null) { + return a.dur + } + a.s = (a.s * val) / a.dur + a.dur = val + }, + stopit = function () { + var a = this + delete animations[a.id] + a.update() + eve('mina.stop.' + a.id, a) + }, + pause = function () { + var a = this + if (a.pdif) { + return + } + delete animations[a.id] + a.update() + a.pdif = a.get() - a.b + }, + resume = function () { + var a = this + if (!a.pdif) { + return + } + a.b = a.get() - a.pdif + delete a.pdif + animations[a.id] = a + frame() + }, + update = function () { + var a = this, + res + if (isArray(a.start)) { + res = [] + for (var j = 0, jj = a.start.length; j < jj; j++) { + res[j] = +a.start[j] + (a.end[j] - a.start[j]) * a.easing(a.s) + } + } else { + res = +a.start + (a.end - a.start) * a.easing(a.s) + } + a.set(res) + }, + frame = function (timeStamp) { + // Manual invokation? + if (!timeStamp) { + // Frame loop stopped? + if (!requestID) { + // Start frame loop... + requestID = requestAnimFrame(frame) + } + return + } + var len = 0 + for (var i in animations) + if (animations.hasOwnProperty(i)) { + var a = animations[i], + b = a.get(), + res + len++ + a.s = (b - a.b) / (a.dur / a.spd) + if (a.s >= 1) { + delete animations[i] + a.s = 1 + len-- + ;(function (a) { + setTimeout(function () { + eve('mina.finish.' + a.id, a) + }) + })(a) + } + a.update() + } + requestID = len ? requestAnimFrame(frame) : false + }, + /*\ + * mina + [ method ] + ** + * Generic animation of numbers + ** + - a (number) start _slave_ number + - A (number) end _slave_ number + - b (number) start _master_ number (start time in general case) + - B (number) end _master_ number (end time in general case) + - get (function) getter of _master_ number (see @mina.time) + - set (function) setter of _slave_ number + - easing (function) #optional easing function, default is @mina.linear + = (object) animation descriptor + o { + o id (string) animation id, + o start (number) start _slave_ number, + o end (number) end _slave_ number, + o b (number) start _master_ number, + o s (number) animation status (0..1), + o dur (number) animation duration, + o spd (number) animation speed, + o get (function) getter of _master_ number (see @mina.time), + o set (function) setter of _slave_ number, + o easing (function) easing function, default is @mina.linear, + o status (function) status getter/setter, + o speed (function) speed getter/setter, + o duration (function) duration getter/setter, + o stop (function) animation stopper + o pause (function) pauses the animation + o resume (function) resumes the animation + o update (function) calles setter with the right value of the animation + o } + \*/ + mina = function (a, A, b, B, get, set, easing) { + var anim = { + id: ID(), + start: a, + end: A, + b: b, + s: 0, + dur: B - b, + spd: 1, + get: get, + set: set, + easing: easing || mina.linear, + status: sta, + speed: speed, + duration: duration, + stop: stopit, + pause: pause, + resume: resume, + update: update + } + animations[anim.id] = anim + var len = 0, + i + for (i in animations) + if (animations.hasOwnProperty(i)) { + len++ + if (len == 2) { + break + } + } + len == 1 && frame() + return anim + } +/*\ + * mina.time + [ method ] + ** + * Returns the current time. Equivalent to: + | function () { + | return (new Date).getTime(); + | } + \*/ +mina.time = timer +/*\ + * mina.getById + [ method ] + ** + * Returns an animation by its id + - id (string) animation's id + = (object) See @mina + \*/ +mina.getById = function (id) { + return animations[id] || null +} + +/*\ + * mina.linear + [ method ] + ** + * Default linear easing + - n (number) input 0..1 + = (number) output 0..1 + \*/ +mina.linear = function (n) { + return n +} +/*\ + * mina.easeout + [ method ] + ** + * Easeout easing + - n (number) input 0..1 + = (number) output 0..1 + \*/ +mina.easeout = function (n) { + return Math.pow(n, 1.7) +} +/*\ + * mina.easein + [ method ] + ** + * Easein easing + - n (number) input 0..1 + = (number) output 0..1 + \*/ +mina.easein = function (n) { + return Math.pow(n, 0.48) +} +/*\ + * mina.easeinout + [ method ] + ** + * Easeinout easing + - n (number) input 0..1 + = (number) output 0..1 + \*/ +mina.easeinout = function (n) { + if (n == 1) { + return 1 + } + if (n == 0) { + return 0 + } + var q = 0.48 - n / 1.04, + Q = Math.sqrt(0.1734 + q * q), + x = Q - q, + X = Math.pow(Math.abs(x), 1 / 3) * (x < 0 ? -1 : 1), + y = -Q - q, + Y = Math.pow(Math.abs(y), 1 / 3) * (y < 0 ? -1 : 1), + t = X + Y + 0.5 + return (1 - t) * 3 * t * t + t * t * t +} +/*\ + * mina.backin + [ method ] + ** + * Backin easing + - n (number) input 0..1 + = (number) output 0..1 + \*/ +mina.backin = function (n) { + if (n == 1) { + return 1 + } + var s = 1.70158 + return n * n * ((s + 1) * n - s) +} +/*\ + * mina.backout + [ method ] + ** + * Backout easing + - n (number) input 0..1 + = (number) output 0..1 + \*/ +mina.backout = function (n) { + if (n == 0) { + return 0 + } + n = n - 1 + var s = 1.70158 + return n * n * ((s + 1) * n + s) + 1 +} +/*\ + * mina.elastic + [ method ] + ** + * Elastic easing + - n (number) input 0..1 + = (number) output 0..1 + \*/ +mina.elastic = function (n) { + if (n == !!n) { + return n + } + return ( + Math.pow(2, -10 * n) * Math.sin(((n - 0.075) * (2 * Math.PI)) / 0.3) + 1 + ) +} +/*\ + * mina.bounce + [ method ] + ** + * Bounce easing + - n (number) input 0..1 + = (number) output 0..1 + \*/ +mina.bounce = function (n) { + var s = 7.5625, + p = 2.75, + l + if (n < 1 / p) { + l = s * n * n + } else { + if (n < 2 / p) { + n -= 1.5 / p + l = s * n * n + 0.75 + } else { + if (n < 2.5 / p) { + n -= 2.25 / p + l = s * n * n + 0.9375 + } else { + n -= 2.625 / p + l = s * n * n + 0.984375 + } + } + } + return l +} +export default mina diff --git a/src/mouse.js b/src/mouse.js new file mode 100644 index 0000000..a76b4bd --- /dev/null +++ b/src/mouse.js @@ -0,0 +1,533 @@ +// 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' +import { Snap } from './svg.js' + +Snap.plugin(function (Snap, Element, Paper, glob) { + var elproto = Element.prototype, + has = 'hasOwnProperty', + supportsTouch = + 'ontouchstart' in window || + window.TouchEvent || + (window.DocumentTouch && document instanceof DocumentTouch), + events = [ + 'click', + 'dblclick', + 'mousedown', + 'mousemove', + 'mouseout', + 'mouseover', + 'mouseup', + 'touchstart', + 'touchmove', + 'touchend', + 'touchcancel' + ], + touchMap = { + mousedown: 'touchstart', + mousemove: 'touchmove', + mouseup: 'touchend' + }, + getScroll = function (xy, el) { + var name = xy == 'y' ? 'scrollTop' : 'scrollLeft', + doc = el && el.node ? el.node.ownerDocument : glob.doc + return doc[name in doc.documentElement ? 'documentElement' : 'body'][name] + }, + preventDefault = function () { + this.returnValue = false + }, + preventTouch = function () { + return this.originalEvent.preventDefault() + }, + stopPropagation = function () { + this.cancelBubble = true + }, + stopTouch = function () { + return this.originalEvent.stopPropagation() + }, + addEvent = function (obj, type, fn, element) { + var realName = supportsTouch && touchMap[type] ? touchMap[type] : type, + f = function (e) { + var scrollY = getScroll('y', element), + scrollX = getScroll('x', element) + if (supportsTouch && touchMap[has](type)) { + for ( + var i = 0, ii = e.targetTouches && e.targetTouches.length; + i < ii; + i++ + ) { + if ( + e.targetTouches[i].target == obj || + obj.contains(e.targetTouches[i].target) + ) { + var olde = e + e = e.targetTouches[i] + e.originalEvent = olde + e.preventDefault = preventTouch + e.stopPropagation = stopTouch + break + } + } + } + var x = e.clientX + scrollX, + y = e.clientY + scrollY + return fn.call(element, e, x, y) + } + + if (type !== realName) { + obj.addEventListener(type, f, false) + } + + obj.addEventListener(realName, f, false) + + return function () { + if (type !== realName) { + obj.removeEventListener(type, f, false) + } + + obj.removeEventListener(realName, f, false) + return true + } + }, + drag = [], + dragMove = function (e) { + var x = e.clientX, + y = e.clientY, + scrollY = getScroll('y'), + scrollX = getScroll('x'), + dragi, + j = drag.length + while (j--) { + dragi = drag[j] + if (supportsTouch) { + var i = e.touches && e.touches.length, + touch + while (i--) { + touch = e.touches[i] + if ( + touch.identifier == dragi.el._drag.id || + dragi.el.node.contains(touch.target) + ) { + x = touch.clientX + y = touch.clientY + ;(e.originalEvent ? e.originalEvent : e).preventDefault() + break + } + } + } else { + e.preventDefault() + } + var node = dragi.el.node, + o, + next = node.nextSibling, + parent = node.parentNode, + display = node.style.display + // glob.win.opera && parent.removeChild(node); + // node.style.display = "none"; + // o = dragi.el.paper.getElementByPoint(x, y); + // node.style.display = display; + // glob.win.opera && (next ? parent.insertBefore(node, next) : parent.appendChild(node)); + // o && eve("snap.drag.over." + dragi.el.id, dragi.el, o); + x += scrollX + y += scrollY + eve( + 'snap.drag.move.' + dragi.el.id, + dragi.move_scope || dragi.el, + x - dragi.el._drag.x, + y - dragi.el._drag.y, + x, + y, + e + ) + } + }, + dragUp = function (e) { + Snap.unmousemove(dragMove).unmouseup(dragUp) + var i = drag.length, + dragi + while (i--) { + dragi = drag[i] + dragi.el._drag = {} + eve( + 'snap.drag.end.' + dragi.el.id, + dragi.end_scope || dragi.start_scope || dragi.move_scope || dragi.el, + e + ) + eve.off('snap.drag.*.' + dragi.el.id) + } + drag = [] + } + /*\ + * Element.click + [ method ] + ** + * Adds a click event handler to the element + - handler (function) handler for the event + = (object) @Element + \*/ + /*\ + * Element.unclick + [ method ] + ** + * Removes a click event handler from the element + - handler (function) handler for the event + = (object) @Element + \*/ + + /*\ + * Element.dblclick + [ method ] + ** + * Adds a double click event handler to the element + - handler (function) handler for the event + = (object) @Element + \*/ + /*\ + * Element.undblclick + [ method ] + ** + * Removes a double click event handler from the element + - handler (function) handler for the event + = (object) @Element + \*/ + + /*\ + * Element.mousedown + [ method ] + ** + * Adds a mousedown event handler to the element + - handler (function) handler for the event + = (object) @Element + \*/ + /*\ + * Element.unmousedown + [ method ] + ** + * Removes a mousedown event handler from the element + - handler (function) handler for the event + = (object) @Element + \*/ + + /*\ + * Element.mousemove + [ method ] + ** + * Adds a mousemove event handler to the element + - handler (function) handler for the event + = (object) @Element + \*/ + /*\ + * Element.unmousemove + [ method ] + ** + * Removes a mousemove event handler from the element + - handler (function) handler for the event + = (object) @Element + \*/ + + /*\ + * Element.mouseout + [ method ] + ** + * Adds a mouseout event handler to the element + - handler (function) handler for the event + = (object) @Element + \*/ + /*\ + * Element.unmouseout + [ method ] + ** + * Removes a mouseout event handler from the element + - handler (function) handler for the event + = (object) @Element + \*/ + + /*\ + * Element.mouseover + [ method ] + ** + * Adds a mouseover event handler to the element + - handler (function) handler for the event + = (object) @Element + \*/ + /*\ + * Element.unmouseover + [ method ] + ** + * Removes a mouseover event handler from the element + - handler (function) handler for the event + = (object) @Element + \*/ + + /*\ + * Element.mouseup + [ method ] + ** + * Adds a mouseup event handler to the element + - handler (function) handler for the event + = (object) @Element + \*/ + /*\ + * Element.unmouseup + [ method ] + ** + * Removes a mouseup event handler from the element + - handler (function) handler for the event + = (object) @Element + \*/ + + /*\ + * Element.touchstart + [ method ] + ** + * Adds a touchstart event handler to the element + - handler (function) handler for the event + = (object) @Element + \*/ + /*\ + * Element.untouchstart + [ method ] + ** + * Removes a touchstart event handler from the element + - handler (function) handler for the event + = (object) @Element + \*/ + + /*\ + * Element.touchmove + [ method ] + ** + * Adds a touchmove event handler to the element + - handler (function) handler for the event + = (object) @Element + \*/ + /*\ + * Element.untouchmove + [ method ] + ** + * Removes a touchmove event handler from the element + - handler (function) handler for the event + = (object) @Element + \*/ + + /*\ + * Element.touchend + [ method ] + ** + * Adds a touchend event handler to the element + - handler (function) handler for the event + = (object) @Element + \*/ + /*\ + * Element.untouchend + [ method ] + ** + * Removes a touchend event handler from the element + - handler (function) handler for the event + = (object) @Element + \*/ + + /*\ + * Element.touchcancel + [ method ] + ** + * Adds a touchcancel event handler to the element + - handler (function) handler for the event + = (object) @Element + \*/ + /*\ + * Element.untouchcancel + [ method ] + ** + * Removes a touchcancel event handler from the element + - handler (function) handler for the event + = (object) @Element + \*/ + for (var i = events.length; i--; ) { + ;(function (eventName) { + Snap[eventName] = elproto[eventName] = function (fn, scope) { + if (Snap.is(fn, 'function')) { + this.events = this.events || [] + this.events.push({ + name: eventName, + f: fn, + unbind: addEvent( + this.node || document, + eventName, + fn, + scope || this + ) + }) + } else { + for (var i = 0, ii = this.events.length; i < ii; i++) + if (this.events[i].name == eventName) { + try { + this.events[i].f.call(this) + } catch (e) {} + } + } + return this + } + Snap['un' + eventName] = elproto['un' + eventName] = function (fn) { + var events = this.events || [], + l = events.length + while (l--) + if (events[l].name == eventName && (events[l].f == fn || !fn)) { + events[l].unbind() + events.splice(l, 1) + !events.length && delete this.events + return this + } + return this + } + })(events[i]) + } + /*\ + * Element.hover + [ method ] + ** + * Adds hover event handlers to the element + - f_in (function) handler for hover in + - f_out (function) handler for hover out + - icontext (object) #optional context for hover in handler + - ocontext (object) #optional context for hover out handler + = (object) @Element + \*/ + elproto.hover = function (f_in, f_out, scope_in, scope_out) { + return this.mouseover(f_in, scope_in).mouseout(f_out, scope_out || scope_in) + } + /*\ + * Element.unhover + [ method ] + ** + * Removes hover event handlers from the element + - f_in (function) handler for hover in + - f_out (function) handler for hover out + = (object) @Element + \*/ + elproto.unhover = function (f_in, f_out) { + return this.unmouseover(f_in).unmouseout(f_out) + } + var draggable = [] + // SIERRA unclear what _context_ refers to for starting, ending, moving the drag gesture. + // SIERRA Element.drag(): _x position of the mouse_: Where are the x/y values offset from? + // SIERRA Element.drag(): much of this member's doc appears to be duplicated for some reason. + // SIERRA Unclear about this sentence: _Additionally following drag events will be triggered: drag.start. on start, drag.end. on end and drag.move. on every move._ Is there a global _drag_ object to which you can assign handlers keyed by an element's ID? + /*\ + * Element.drag + [ method ] + ** + * Adds event handlers for an element's drag gesture + ** + - onmove (function) handler for moving + - onstart (function) handler for drag start + - onend (function) handler for drag end + - mcontext (object) #optional context for moving handler + - scontext (object) #optional context for drag start handler + - econtext (object) #optional context for drag end handler + * Additionaly following `drag` events are triggered: `drag.start.` on start, + * `drag.end.` on end and `drag.move.` on every move. When element is dragged over another element + * `drag.over.` fires as well. + * + * Start event and start handler are called in specified context or in context of the element with following parameters: + o x (number) x position of the mouse + o y (number) y position of the mouse + o event (object) DOM event object + * Move event and move handler are called in specified context or in context of the element with following parameters: + o dx (number) shift by x from the start point + o dy (number) shift by y from the start point + o x (number) x position of the mouse + o y (number) y position of the mouse + o event (object) DOM event object + * End event and end handler are called in specified context or in context of the element with following parameters: + o event (object) DOM event object + = (object) @Element + \*/ + elproto.drag = function ( + onmove, + onstart, + onend, + move_scope, + start_scope, + end_scope + ) { + var el = this + if (!arguments.length) { + var origTransform + return el.drag( + function (dx, dy) { + this.attr({ + transform: origTransform + (origTransform ? 'T' : 't') + [dx, dy] + }) + }, + function () { + origTransform = this.transform().local + } + ) + } + function start(e, x, y) { + ;(e.originalEvent || e).preventDefault() + el._drag.x = x + el._drag.y = y + el._drag.id = e.identifier + !drag.length && Snap.mousemove(dragMove).mouseup(dragUp) + drag.push({ + el: el, + move_scope: move_scope, + start_scope: start_scope, + end_scope: end_scope + }) + onstart && eve.on('snap.drag.start.' + el.id, onstart) + onmove && eve.on('snap.drag.move.' + el.id, onmove) + onend && eve.on('snap.drag.end.' + el.id, onend) + eve('snap.drag.start.' + el.id, start_scope || move_scope || el, x, y, e) + } + function init(e, x, y) { + eve('snap.draginit.' + el.id, el, e, x, y) + } + eve.on('snap.draginit.' + el.id, start) + el._drag = {} + draggable.push({ el: el, start: start, init: init }) + el.mousedown(init) + return el + } + /* + * Element.onDragOver + [ method ] + ** + * Shortcut to assign event handler for `drag.over.` event, where `id` is the element's `id` (see @Element.id) + - f (function) handler for event, first argument would be the element you are dragging over + \*/ + // elproto.onDragOver = function (f) { + // f ? eve.on("snap.drag.over." + this.id, f) : eve.unbind("snap.drag.over." + this.id); + // }; + /*\ + * Element.undrag + [ method ] + ** + * Removes all drag event handlers from the given element + \*/ + elproto.undrag = function () { + var i = draggable.length + while (i--) + if (draggable[i].el == this) { + this.unmousedown(draggable[i].init) + draggable.splice(i, 1) + eve.unbind('snap.drag.*.' + this.id) + eve.unbind('snap.draginit.' + this.id) + } + !draggable.length && Snap.unmousemove(dragMove).unmouseup(dragUp) + return this + } +}) diff --git a/src/paper.js b/src/paper.js new file mode 100644 index 0000000..3caedb7 --- /dev/null +++ b/src/paper.js @@ -0,0 +1,783 @@ +// 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' +import { Snap } from './svg.js' + +Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { + var proto = Paper.prototype, + is = Snap.is + /*\ + * Paper.rect + [ method ] + * + * 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 + | var c = paper.rect(10, 10, 50, 50); + | // rectangle with rounded corners + | var c = paper.rect(40, 40, 50, 50, 10); + \*/ + proto.rect = function (x, y, w, h, rx, ry) { + var attr + if (ry == null) { + ry = rx + } + 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 + } + } + return this.el('rect', attr) + } + /*\ + * Paper.circle + [ method ] + ** + * Draws a circle + ** + - x (number) x coordinate of the centre + - y (number) y coordinate of the centre + - r (number) radius + = (object) the `circle` element + ** + > Usage + | var c = paper.circle(50, 50, 40); + \*/ + proto.circle = function (cx, cy, r) { + var 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) + } + + var preload = (function () { + function onerror() { + this.parentNode.removeChild(this) + } + return function (src, f) { + var img = glob.doc.createElement('img'), + body = glob.doc.body + img.style.cssText = 'position:absolute;left:-9999em;top:-9999em' + img.onload = function () { + f.call(img) + img.onload = img.onerror = null + body.removeChild(img) + } + img.onerror = onerror + body.appendChild(img) + img.src = src + } + })() + + /*\ + * Paper.image + [ method ] + ** + * 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 + | var c = paper.image("apple.png", 10, 10, 80, 80); + \*/ + proto.image = function (src, x, y, width, height) { + var el = this.el('image') + if (is(src, 'object') && 'src' in src) { + el.attr(src) + } else if (src != null) { + var 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 () { + Snap._.$(el.node, { + width: this.offsetWidth, + height: this.offsetHeight + }) + }) + } + Snap._.$(el.node, set) + } + return el + } + /*\ + * Paper.ellipse + [ method ] + ** + * 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 + ** + > Usage + | var c = paper.ellipse(50, 50, 40, 20); + \*/ + proto.ellipse = function (cx, cy, rx, ry) { + var attr + if (is(cx, 'object') && cx == '[object Object]') { + attr = cx + } else if (cx != null) { + attr = { + cx: cx, + cy: cy, + rx: rx, + ry: ry + } + } + return this.el('ellipse', attr) + } + // SIERRA Paper.path(): Unclear from the link what a Catmull-Rom curveto is, and why it would make life any easier. + /*\ + * Paper.path + [ method ] + ** + * Creates a `` element using the given string as the path's definition + - pathString (string) #optional path string in SVG format + * Path string consists of one-letter commands, followed by comma seprarated arguments in numerical form. Example: + | "M10,20L30,40" + * This example features two commands: `M`, with arguments `(10, 20)` and `L` with arguments `(30, 40)`. Uppercase letter commands express coordinates in absolute terms, while lowercase commands express them in relative terms from the most recently declared coordinates. + * + #

Here is short list of commands available, for more details see SVG path string format or article about path strings at MDN.

+ # + # + # + # + # + # + # + # + # + # + # + #
CommandNameParameters
Mmoveto(x y)+
Zclosepath(none)
Llineto(x y)+
Hhorizontal linetox+
Vvertical linetoy+
Ccurveto(x1 y1 x2 y2 x y)+
Ssmooth curveto(x2 y2 x y)+
Qquadratic Bézier curveto(x1 y1 x y)+
Tsmooth quadratic Bézier curveto(x y)+
Aelliptical arc(rx ry x-axis-rotation large-arc-flag sweep-flag x y)+
RCatmull-Rom curveto*x1 y1 (x y)+
+ * * _Catmull-Rom curveto_ is a not standard SVG command and added to make life easier. + * Note: there is a special case when a path consists of only three commands: `M10,10R…z`. In this case the path connects back to its starting point. + > Usage + | var c = paper.path("M10 10L90 90"); + | // draw a diagonal line: + | // move to 10,10, line to 90,90 + \*/ + proto.path = function (d) { + var attr + if (is(d, 'object') && !is(d, 'array')) { + attr = d + } else if (d) { + attr = { d: d } + } + return this.el('path', attr) + } + /*\ + * Paper.g + [ method ] + ** + * Creates a group element + ** + - varargs (…) #optional elements to nest within the group + = (object) the `g` element + ** + > Usage + | var c1 = paper.circle(), + | c2 = paper.rect(), + | g = paper.g(c2, c1); // note that the order of elements is different + * or + | var c1 = paper.circle(), + | c2 = paper.rect(), + | g = paper.g(); + | g.add(c2, c1); + \*/ + /*\ + * Paper.group + [ method ] + ** + * See @Paper.g + \*/ + proto.group = proto.g = function (first) { + var attr, + el = this.el('g') + if (arguments.length == 1 && first && !first.type) { + el.attr(first) + } else if (arguments.length) { + el.add(Array.prototype.slice.call(arguments, 0)) + } + return el + } + /*\ + * Paper.svg + [ method ] + ** + * 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 + ** + \*/ + proto.svg = function (x, y, width, height, vbx, vby, vbw, vbh) { + var 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) + } + /*\ + * Paper.mask + [ method ] + ** + * Equivalent in behaviour to @Paper.g, except it’s a mask. + ** + = (object) the `mask` element + ** + \*/ + proto.mask = function (first) { + var attr, + el = this.el('mask') + if (arguments.length == 1 && first && !first.type) { + el.attr(first) + } else if (arguments.length) { + el.add(Array.prototype.slice.call(arguments, 0)) + } + return el + } + /*\ + * Paper.ptrn + [ method ] + ** + * 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 + ** + \*/ + proto.ptrn = function (x, y, width, height, vx, vy, vw, vh) { + if (is(x, 'object')) { + var 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) + } + /*\ + * Paper.use + [ method ] + ** + * Creates a element. + - id (string) @optional id of element to link + * or + - id (Element) @optional element to link + ** + = (object) the `use` element + ** + \*/ + proto.use = function (id) { + if (id != null) { + if (id instanceof Element) { + if (!id.attr('id')) { + id.attr({ id: Snap._.id(id) }) + } + id = id.attr('id') + } + if (String(id).charAt() == '#') { + id = id.substring(1) + } + return this.el('use', { 'xlink:href': '#' + id }) + } else { + return Element.prototype.use.call(this) + } + } + /*\ + * Paper.symbol + [ method ] + ** + * Creates a 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 + ** + \*/ + proto.symbol = function (vx, vy, vw, vh) { + var attr = {} + if (vx != null && vy != null && vw != null && vh != null) { + attr.viewBox = [vx, vy, vw, vh] + } + + return this.el('symbol', attr) + } + /*\ + * Paper.text + [ method ] + ** + * 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 `` elements + = (object) the `text` element + ** + > Usage + | var t1 = paper.text(50, 50, "Snap"); + | var t2 = paper.text(50, 50, ["S","n","a","p"]); + | // Text path usage + | t1.attr({textpath: "M10,10L100,100"}); + | // or + | var pth = paper.path("M10,10L100,100"); + | t1.attr({textpath: pth}); + \*/ + proto.text = function (x, y, text) { + var attr = {} + if (is(x, 'object')) { + attr = x + } else if (x != null) { + attr = { + x: x, + y: y, + text: text || '' + } + } + return this.el('text', attr) + } + /*\ + * Paper.line + [ method ] + ** + * 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 + | var t1 = paper.line(50, 50, 100, 100); + \*/ + proto.line = function (x1, y1, x2, y2) { + var 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) + } + /*\ + * Paper.polyline + [ method ] + ** + * Draws a polyline + ** + - points (array) array of points + * or + - varargs (…) points + = (object) the `polyline` element + ** + > Usage + | var p1 = paper.polyline([10, 10, 100, 100]); + | var p2 = paper.polyline(10, 10, 100, 100); + \*/ + proto.polyline = function (points) { + if (arguments.length > 1) { + points = Array.prototype.slice.call(arguments, 0) + } + var attr = {} + if (is(points, 'object') && !is(points, 'array')) { + attr = points + } else if (points != null) { + attr = { points: points } + } + return this.el('polyline', attr) + } + /*\ + * Paper.polygon + [ method ] + ** + * Draws a polygon. See @Paper.polyline + \*/ + proto.polygon = function (points) { + if (arguments.length > 1) { + points = Array.prototype.slice.call(arguments, 0) + } + var attr = {} + if (is(points, 'object') && !is(points, 'array')) { + attr = points + } else if (points != null) { + attr = { points: points } + } + return this.el('polygon', attr) + } + // gradients + ;(function () { + var $ = Snap._.$ + // gradients' helpers + /*\ + * Element.stops + [ method ] + ** + * Only for gradients! + * Returns array of gradient stops elements. + = (array) the stops array. + \*/ + function Gstops() { + return this.selectAll('stop') + } + /*\ + * Element.addStop + [ method ] + ** + * Only for gradients! + * Adds another stop to the gradient. + - color (string) stops color + - offset (number) stops offset 0..100 + = (object) gradient element + \*/ + function GaddStop(color, offset) { + var stop = $('stop'), + attr = { + offset: +offset + '%' + } + color = Snap.color(color) + attr['stop-color'] = color.hex + if (color.opacity < 1) { + attr['stop-opacity'] = color.opacity + } + $(stop, attr) + var stops = this.stops(), + inserted + for (var i = 0; i < stops.length; i++) { + var stopOffset = parseFloat(stops[i].attr('offset')) + if (stopOffset > offset) { + this.node.insertBefore(stop, stops[i].node) + inserted = true + break + } + } + if (!inserted) { + this.node.appendChild(stop) + } + return this + } + function GgetBBox() { + if (this.type == 'linearGradient') { + var x1 = $(this.node, 'x1') || 0, + x2 = $(this.node, 'x2') || 1, + y1 = $(this.node, 'y1') || 0, + y2 = $(this.node, 'y2') || 0 + return Snap._.box(x1, y1, math.abs(x2 - x1), math.abs(y2 - y1)) + } else { + var cx = this.node.cx || 0.5, + cy = this.node.cy || 0.5, + r = this.node.r || 0 + return Snap._.box(cx - r, cy - r, r * 2, r * 2) + } + } + /*\ + * Element.setStops + [ method ] + ** + * Only for gradients! + * Updates stops of the gradient based on passed gradient descriptor. See @Ppaer.gradient + - str (string) gradient descriptor part after `()`. + = (object) gradient element + | var g = paper.gradient("l(0, 0, 1, 1)#000-#f00-#fff"); + | g.setStops("#fff-#000-#f00-#fc0"); + \*/ + function GsetStops(str) { + var grad = str, + stops = this.stops() + if (typeof str == 'string') { + grad = eve( + 'snap.util.grad.parse', + null, + 'l(0,0,0,1)' + str + ).firstDefined().stops + } + if (!Snap.is(grad, 'array')) { + return + } + for (var i = 0; i < stops.length; i++) { + if (grad[i]) { + var color = Snap.color(grad[i].color), + attr = { offset: grad[i].offset + '%' } + attr['stop-color'] = color.hex + if (color.opacity < 1) { + attr['stop-opacity'] = color.opacity + } + stops[i].attr(attr) + } else { + stops[i].remove() + } + } + for (i = stops.length; i < grad.length; i++) { + this.addStop(grad[i].color, grad[i].offset) + } + return this + } + function gradient(defs, str) { + var grad = eve('snap.util.grad.parse', null, str).firstDefined(), + el + if (!grad) { + return null + } + grad.params.unshift(defs) + if (grad.type.toLowerCase() == 'l') { + el = gradientLinear.apply(0, grad.params) + } else { + el = gradientRadial.apply(0, grad.params) + } + if (grad.type != grad.type.toLowerCase()) { + $(el.node, { + gradientUnits: 'userSpaceOnUse' + }) + } + var stops = grad.stops, + len = stops.length + for (var i = 0; i < len; i++) { + var stop = stops[i] + el.addStop(stop.color, stop.offset) + } + return el + } + function gradientLinear(defs, x1, y1, x2, y2) { + var el = Snap._.make('linearGradient', defs) + el.stops = Gstops + el.addStop = GaddStop + el.getBBox = GgetBBox + el.setStops = GsetStops + if (x1 != null) { + $(el.node, { + x1: x1, + y1: y1, + x2: x2, + y2: y2 + }) + } + return el + } + function gradientRadial(defs, cx, cy, r, fx, fy) { + var el = Snap._.make('radialGradient', defs) + el.stops = Gstops + el.addStop = GaddStop + el.getBBox = GgetBBox + if (cx != null) { + $(el.node, { + cx: cx, + cy: cy, + r: r + }) + } + if (fx != null && fy != null) { + $(el.node, { + fx: fx, + fy: fy + }) + } + return el + } + /*\ + * Paper.gradient + [ method ] + ** + * Creates a gradient element + ** + - gradient (string) gradient descriptor + > Gradient Descriptor + * The gradient descriptor is an expression formatted as + * follows: `()`. The `` can be + * either linear or radial. The uppercase `L` or `R` letters + * indicate absolute coordinates offset from the SVG surface. + * Lowercase `l` or `r` letters indicate coordinates + * calculated relative to the element to which the gradient is + * applied. Coordinates specify a linear gradient vector as + * `x1`, `y1`, `x2`, `y2`, or a radial gradient as `cx`, `cy`, + * `r` and optional `fx`, `fy` specifying a focal point away + * from the center of the circle. Specify `` as a list + * of dash-separated CSS color values. Each color may be + * followed by a custom offset value, separated with a colon + * character. + > Examples + * Linear gradient, relative from top-left corner to bottom-right + * corner, from black through red to white: + | var g = paper.gradient("l(0, 0, 1, 1)#000-#f00-#fff"); + * Linear gradient, absolute from (0, 0) to (100, 100), from black + * through red at 25% to white: + | var g = paper.gradient("L(0, 0, 100, 100)#000-#f00:25-#fff"); + * Radial gradient, relative from the center of the element with radius + * half the width, from black to white: + | var g = paper.gradient("r(0.5, 0.5, 0.5)#000-#fff"); + * To apply the gradient: + | paper.circle(50, 50, 40).attr({ + | fill: g + | }); + = (object) the `gradient` element + \*/ + proto.gradient = function (str) { + return gradient(this.defs, str) + } + proto.gradientLinear = function (x1, y1, x2, y2) { + return gradientLinear(this.defs, x1, y1, x2, y2) + } + proto.gradientRadial = function (cx, cy, r, fx, fy) { + return gradientRadial(this.defs, cx, cy, r, fx, fy) + } + /*\ + * Paper.toString + [ method ] + ** + * Returns SVG code for the @Paper + = (string) SVG code for the @Paper + \*/ + proto.toString = function () { + var doc = this.node.ownerDocument, + f = doc.createDocumentFragment(), + d = doc.createElement('div'), + svg = this.node.cloneNode(true), + res + f.appendChild(d) + d.appendChild(svg) + Snap._.$(svg, { xmlns: 'http://www.w3.org/2000/svg' }) + res = d.innerHTML + f.removeChild(f.firstChild) + return res + } + /*\ + * Paper.toDataURL + [ method ] + ** + * Returns SVG code for the @Paper as Data URI string. + = (string) Data URI string + \*/ + proto.toDataURL = function () { + if (window && window.btoa) { + return ( + 'data:image/svg+xml;base64,' + + btoa(unescape(encodeURIComponent(this))) + ) + } + } + /*\ + * Paper.clear + [ method ] + ** + * Removes all child nodes of the paper, except . + \*/ + proto.clear = function () { + var node = this.node.firstChild, + next + while (node) { + next = node.nextSibling + if (node.tagName != 'defs') { + node.parentNode.removeChild(node) + } else { + proto.clear.call({ node: node }) + } + node = next + } + } + })() +}) diff --git a/src/path.js b/src/path.js new file mode 100644 index 0000000..d9cf2bb --- /dev/null +++ b/src/path.js @@ -0,0 +1,1633 @@ +// 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 { Snap } from './svg.js' + +Snap.plugin(function (Snap, Element, Paper, glob) { + var elproto = Element.prototype, + is = Snap.is, + clone = Snap._.clone, + has = 'hasOwnProperty', + p2s = /,?([a-z]),?/gi, + toFloat = parseFloat, + math = Math, + PI = math.PI, + mmin = math.min, + mmax = math.max, + pow = math.pow, + abs = math.abs + function paths(ps) { + var p = (paths.ps = paths.ps || {}) + if (p[ps]) { + p[ps].sleep = 100 + } else { + p[ps] = { + sleep: 100 + } + } + setTimeout(function () { + for (var key in p) + if (p[has](key) && key != ps) { + p[key].sleep-- + !p[key].sleep && delete p[key] + } + }) + return p[ps] + } + function box(x, y, width, height) { + if (x == null) { + x = y = width = height = 0 + } + if (y == null) { + y = x.y + width = x.width + height = x.height + x = x.x + } + return { + x: x, + y: y, + width: width, + w: width, + height: height, + h: height, + x2: x + width, + y2: y + height, + cx: x + width / 2, + cy: y + height / 2, + r1: math.min(width, height) / 2, + r2: math.max(width, height) / 2, + r0: math.sqrt(width * width + height * height) / 2, + path: rectPath(x, y, width, height), + vb: [x, y, width, height].join(' ') + } + } + function toString() { + return this.join(',').replace(p2s, '$1') + } + function pathClone(pathArray) { + var res = clone(pathArray) + res.toString = toString + return res + } + function getPointAtSegmentLength( + p1x, + p1y, + c1x, + c1y, + c2x, + c2y, + p2x, + p2y, + length + ) { + if (length == null) { + return bezlen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) + } else { + return findDotsAtSegment( + p1x, + p1y, + c1x, + c1y, + c2x, + c2y, + p2x, + p2y, + getTotLen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, length) + ) + } + } + function getLengthFactory(istotal, subpath) { + function O(val) { + return +(+val).toFixed(3) + } + return Snap._.cacher( + function (path, length, onlystart) { + if (path instanceof Element) { + path = path.attr('d') + } + path = path2curve(path) + var x, + y, + p, + l, + sp = '', + subpaths = {}, + point, + len = 0 + for (var i = 0, ii = path.length; i < ii; i++) { + p = path[i] + if (p[0] == 'M') { + x = +p[1] + y = +p[2] + } else { + l = getPointAtSegmentLength( + x, + y, + p[1], + p[2], + p[3], + p[4], + p[5], + p[6] + ) + if (len + l > length) { + if (subpath && !subpaths.start) { + point = getPointAtSegmentLength( + x, + y, + p[1], + p[2], + p[3], + p[4], + p[5], + p[6], + length - len + ) + sp += [ + 'C' + O(point.start.x), + O(point.start.y), + O(point.m.x), + O(point.m.y), + O(point.x), + O(point.y) + ] + if (onlystart) { + return sp + } + subpaths.start = sp + sp = [ + 'M' + O(point.x), + O(point.y) + 'C' + O(point.n.x), + O(point.n.y), + O(point.end.x), + O(point.end.y), + O(p[5]), + O(p[6]) + ].join() + len += l + x = +p[5] + y = +p[6] + continue + } + if (!istotal && !subpath) { + point = getPointAtSegmentLength( + x, + y, + p[1], + p[2], + p[3], + p[4], + p[5], + p[6], + length - len + ) + return point + } + } + len += l + x = +p[5] + y = +p[6] + } + sp += p.shift() + p + } + subpaths.end = sp + point = istotal + ? len + : subpath + ? subpaths + : findDotsAtSegment(x, y, p[0], p[1], p[2], p[3], p[4], p[5], 1) + return point + }, + null, + Snap._.clone + ) + } + var getTotalLength = getLengthFactory(1), + getPointAtLength = getLengthFactory(), + getSubpathsAtLength = getLengthFactory(0, 1) + function findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) { + var t1 = 1 - t, + t13 = pow(t1, 3), + t12 = pow(t1, 2), + t2 = t * t, + t3 = t2 * t, + x = t13 * p1x + t12 * 3 * t * c1x + t1 * 3 * t * t * c2x + t3 * p2x, + y = t13 * p1y + t12 * 3 * t * c1y + t1 * 3 * t * t * c2y + t3 * p2y, + mx = p1x + 2 * t * (c1x - p1x) + t2 * (c2x - 2 * c1x + p1x), + my = p1y + 2 * t * (c1y - p1y) + t2 * (c2y - 2 * c1y + p1y), + nx = c1x + 2 * t * (c2x - c1x) + t2 * (p2x - 2 * c2x + c1x), + ny = c1y + 2 * t * (c2y - c1y) + t2 * (p2y - 2 * c2y + c1y), + ax = t1 * p1x + t * c1x, + ay = t1 * p1y + t * c1y, + cx = t1 * c2x + t * p2x, + cy = t1 * c2y + t * p2y, + alpha = 90 - (math.atan2(mx - nx, my - ny) * 180) / PI + // (mx > nx || my < ny) && (alpha += 180); + return { + x: x, + y: y, + m: { x: mx, y: my }, + n: { x: nx, y: ny }, + start: { x: ax, y: ay }, + end: { x: cx, y: cy }, + alpha: alpha + } + } + function bezierBBox(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) { + if (!Snap.is(p1x, 'array')) { + p1x = [p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y] + } + var bbox = curveDim.apply(null, p1x) + return box( + bbox.min.x, + bbox.min.y, + bbox.max.x - bbox.min.x, + bbox.max.y - bbox.min.y + ) + } + function isPointInsideBBox(bbox, x, y) { + return ( + x >= bbox.x && + x <= bbox.x + bbox.width && + y >= bbox.y && + y <= bbox.y + bbox.height + ) + } + function isBBoxIntersect(bbox1, bbox2) { + bbox1 = box(bbox1) + bbox2 = box(bbox2) + return ( + isPointInsideBBox(bbox2, bbox1.x, bbox1.y) || + isPointInsideBBox(bbox2, bbox1.x2, bbox1.y) || + isPointInsideBBox(bbox2, bbox1.x, bbox1.y2) || + isPointInsideBBox(bbox2, bbox1.x2, bbox1.y2) || + isPointInsideBBox(bbox1, bbox2.x, bbox2.y) || + isPointInsideBBox(bbox1, bbox2.x2, bbox2.y) || + isPointInsideBBox(bbox1, bbox2.x, bbox2.y2) || + isPointInsideBBox(bbox1, bbox2.x2, bbox2.y2) || + (((bbox1.x < bbox2.x2 && bbox1.x > bbox2.x) || + (bbox2.x < bbox1.x2 && bbox2.x > bbox1.x)) && + ((bbox1.y < bbox2.y2 && bbox1.y > bbox2.y) || + (bbox2.y < bbox1.y2 && bbox2.y > bbox1.y))) + ) + } + function base3(t, p1, p2, p3, p4) { + var t1 = -3 * p1 + 9 * p2 - 9 * p3 + 3 * p4, + t2 = t * t1 + 6 * p1 - 12 * p2 + 6 * p3 + return t * t2 - 3 * p1 + 3 * p2 + } + function bezlen(x1, y1, x2, y2, x3, y3, x4, y4, z) { + if (z == null) { + z = 1 + } + z = z > 1 ? 1 : z < 0 ? 0 : z + var z2 = z / 2, + n = 12, + Tvalues = [ + -0.1252, 0.1252, -0.3678, 0.3678, -0.5873, 0.5873, -0.7699, 0.7699, + -0.9041, 0.9041, -0.9816, 0.9816 + ], + Cvalues = [ + 0.2491, 0.2491, 0.2335, 0.2335, 0.2032, 0.2032, 0.1601, 0.1601, 0.1069, + 0.1069, 0.0472, 0.0472 + ], + sum = 0 + for (var i = 0; i < n; i++) { + var ct = z2 * Tvalues[i] + z2, + xbase = base3(ct, x1, x2, x3, x4), + ybase = base3(ct, y1, y2, y3, y4), + comb = xbase * xbase + ybase * ybase + sum += Cvalues[i] * math.sqrt(comb) + } + return z2 * sum + } + function getTotLen(x1, y1, x2, y2, x3, y3, x4, y4, ll) { + if (ll < 0 || bezlen(x1, y1, x2, y2, x3, y3, x4, y4) < ll) { + return + } + var t = 1, + step = t / 2, + t2 = t - step, + l, + e = 0.01 + l = bezlen(x1, y1, x2, y2, x3, y3, x4, y4, t2) + while (abs(l - ll) > e) { + step /= 2 + t2 += (l < ll ? 1 : -1) * step + l = bezlen(x1, y1, x2, y2, x3, y3, x4, y4, t2) + } + return t2 + } + function intersect(x1, y1, x2, y2, x3, y3, x4, y4) { + if ( + mmax(x1, x2) < mmin(x3, x4) || + mmin(x1, x2) > mmax(x3, x4) || + mmax(y1, y2) < mmin(y3, y4) || + mmin(y1, y2) > mmax(y3, y4) + ) { + return + } + var nx = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4), + ny = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4), + denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4) + + if (!denominator) { + return + } + var px = nx / denominator, + py = ny / denominator, + px2 = +px.toFixed(2), + py2 = +py.toFixed(2) + if ( + px2 < +mmin(x1, x2).toFixed(2) || + px2 > +mmax(x1, x2).toFixed(2) || + px2 < +mmin(x3, x4).toFixed(2) || + px2 > +mmax(x3, x4).toFixed(2) || + py2 < +mmin(y1, y2).toFixed(2) || + py2 > +mmax(y1, y2).toFixed(2) || + py2 < +mmin(y3, y4).toFixed(2) || + py2 > +mmax(y3, y4).toFixed(2) + ) { + return + } + return { x: px, y: py } + } + function inter(bez1, bez2) { + return interHelper(bez1, bez2) + } + function interCount(bez1, bez2) { + return interHelper(bez1, bez2, 1) + } + function interHelper(bez1, bez2, justCount) { + var bbox1 = bezierBBox(bez1), + bbox2 = bezierBBox(bez2) + if (!isBBoxIntersect(bbox1, bbox2)) { + return justCount ? 0 : [] + } + var l1 = bezlen.apply(0, bez1), + l2 = bezlen.apply(0, bez2), + n1 = ~~(l1 / 8), + n2 = ~~(l2 / 8), + dots1 = [], + dots2 = [], + xy = {}, + res = justCount ? 0 : [] + for (var i = 0; i < n1 + 1; i++) { + var p = findDotsAtSegment.apply(0, bez1.concat(i / n1)) + dots1.push({ x: p.x, y: p.y, t: i / n1 }) + } + for (i = 0; i < n2 + 1; i++) { + p = findDotsAtSegment.apply(0, bez2.concat(i / n2)) + dots2.push({ x: p.x, y: p.y, t: i / n2 }) + } + for (i = 0; i < n1; i++) { + for (var j = 0; j < n2; j++) { + var di = dots1[i], + di1 = dots1[i + 1], + dj = dots2[j], + dj1 = dots2[j + 1], + ci = abs(di1.x - di.x) < 0.001 ? 'y' : 'x', + cj = abs(dj1.x - dj.x) < 0.001 ? 'y' : 'x', + is = intersect(di.x, di.y, di1.x, di1.y, dj.x, dj.y, dj1.x, dj1.y) + if (is) { + if (xy[is.x.toFixed(4)] == is.y.toFixed(4)) { + continue + } + xy[is.x.toFixed(4)] = is.y.toFixed(4) + var t1 = + di.t + + abs((is[ci] - di[ci]) / (di1[ci] - di[ci])) * (di1.t - di.t), + t2 = + dj.t + + abs((is[cj] - dj[cj]) / (dj1[cj] - dj[cj])) * (dj1.t - dj.t) + if (t1 >= 0 && t1 <= 1 && t2 >= 0 && t2 <= 1) { + if (justCount) { + res++ + } else { + res.push({ + x: is.x, + y: is.y, + t1: t1, + t2: t2 + }) + } + } + } + } + } + return res + } + function pathIntersection(path1, path2) { + return interPathHelper(path1, path2) + } + function pathIntersectionNumber(path1, path2) { + return interPathHelper(path1, path2, 1) + } + function interPathHelper(path1, path2, justCount) { + path1 = path2curve(path1) + path2 = path2curve(path2) + var x1, + y1, + x2, + y2, + x1m, + y1m, + x2m, + y2m, + bez1, + bez2, + res = justCount ? 0 : [] + for (var i = 0, ii = path1.length; i < ii; i++) { + var pi = path1[i] + if (pi[0] == 'M') { + x1 = x1m = pi[1] + y1 = y1m = pi[2] + } else { + if (pi[0] == 'C') { + bez1 = [x1, y1].concat(pi.slice(1)) + x1 = bez1[6] + y1 = bez1[7] + } else { + bez1 = [x1, y1, x1, y1, x1m, y1m, x1m, y1m] + x1 = x1m + y1 = y1m + } + for (var j = 0, jj = path2.length; j < jj; j++) { + var pj = path2[j] + if (pj[0] == 'M') { + x2 = x2m = pj[1] + y2 = y2m = pj[2] + } else { + if (pj[0] == 'C') { + bez2 = [x2, y2].concat(pj.slice(1)) + x2 = bez2[6] + y2 = bez2[7] + } else { + bez2 = [x2, y2, x2, y2, x2m, y2m, x2m, y2m] + x2 = x2m + y2 = y2m + } + var intr = interHelper(bez1, bez2, justCount) + if (justCount) { + res += intr + } else { + for (var k = 0, kk = intr.length; k < kk; k++) { + intr[k].segment1 = i + intr[k].segment2 = j + intr[k].bez1 = bez1 + intr[k].bez2 = bez2 + } + res = res.concat(intr) + } + } + } + } + } + return res + } + function isPointInsidePath(path, x, y) { + var bbox = pathBBox(path) + return ( + isPointInsideBBox(bbox, x, y) && + interPathHelper( + path, + [ + ['M', x, y], + ['H', bbox.x2 + 10] + ], + 1 + ) % + 2 == + 1 + ) + } + function pathBBox(path) { + var pth = paths(path) + if (pth.bbox) { + return clone(pth.bbox) + } + if (!path) { + return box() + } + path = path2curve(path) + var x = 0, + y = 0, + X = [], + Y = [], + p + for (var i = 0, ii = path.length; i < ii; i++) { + p = path[i] + if (p[0] == 'M') { + x = p[1] + y = p[2] + X.push(x) + Y.push(y) + } else { + var dim = curveDim(x, y, p[1], p[2], p[3], p[4], p[5], p[6]) + X = X.concat(dim.min.x, dim.max.x) + Y = Y.concat(dim.min.y, dim.max.y) + x = p[5] + y = p[6] + } + } + var xmin = mmin.apply(0, X), + ymin = mmin.apply(0, Y), + xmax = mmax.apply(0, X), + ymax = mmax.apply(0, Y), + bb = box(xmin, ymin, xmax - xmin, ymax - ymin) + pth.bbox = clone(bb) + return bb + } + function rectPath(x, y, w, h, r) { + if (r) { + return [ + ['M', +x + +r, y], + ['l', w - r * 2, 0], + ['a', r, r, 0, 0, 1, r, r], + ['l', 0, h - r * 2], + ['a', r, r, 0, 0, 1, -r, r], + ['l', r * 2 - w, 0], + ['a', r, r, 0, 0, 1, -r, -r], + ['l', 0, r * 2 - h], + ['a', r, r, 0, 0, 1, r, -r], + ['z'] + ] + } + var res = [['M', x, y], ['l', w, 0], ['l', 0, h], ['l', -w, 0], ['z']] + res.toString = toString + return res + } + function ellipsePath(x, y, rx, ry, a) { + if (a == null && ry == null) { + ry = rx + } + x = +x + y = +y + rx = +rx + ry = +ry + if (a != null) { + var rad = Math.PI / 180, + x1 = x + rx * Math.cos(-ry * rad), + x2 = x + rx * Math.cos(-a * rad), + y1 = y + rx * Math.sin(-ry * rad), + y2 = y + rx * Math.sin(-a * rad), + res = [ + ['M', x1, y1], + ['A', rx, rx, 0, +(a - ry > 180), 0, x2, y2] + ] + } else { + res = [ + ['M', x, y], + ['m', 0, -ry], + ['a', rx, ry, 0, 1, 1, 0, 2 * ry], + ['a', rx, ry, 0, 1, 1, 0, -2 * ry], + ['z'] + ] + } + res.toString = toString + return res + } + var unit2px = Snap._unit2px, + getPath = { + path: function (el) { + return el.attr('path') + }, + circle: function (el) { + var attr = unit2px(el) + return ellipsePath(attr.cx, attr.cy, attr.r) + }, + ellipse: function (el) { + var attr = unit2px(el) + return ellipsePath(attr.cx || 0, attr.cy || 0, attr.rx, attr.ry) + }, + rect: function (el) { + var attr = unit2px(el) + return rectPath( + attr.x || 0, + attr.y || 0, + attr.width, + attr.height, + attr.rx, + attr.ry + ) + }, + image: function (el) { + var attr = unit2px(el) + return rectPath(attr.x || 0, attr.y || 0, attr.width, attr.height) + }, + line: function (el) { + return ( + 'M' + + [el.attr('x1') || 0, el.attr('y1') || 0, el.attr('x2'), el.attr('y2')] + ) + }, + polyline: function (el) { + return 'M' + el.attr('points') + }, + polygon: function (el) { + return 'M' + el.attr('points') + 'z' + }, + deflt: function (el) { + var bbox = el.node.getBBox() + return rectPath(bbox.x, bbox.y, bbox.width, bbox.height) + } + } + function pathToRelative(pathArray) { + var pth = paths(pathArray), + lowerCase = String.prototype.toLowerCase + if (pth.rel) { + return pathClone(pth.rel) + } + if ( + !Snap.is(pathArray, 'array') || + !Snap.is(pathArray && pathArray[0], 'array') + ) { + pathArray = Snap.parsePathString(pathArray) + } + var res = [], + x = 0, + y = 0, + mx = 0, + my = 0, + start = 0 + if (pathArray[0][0] == 'M') { + x = pathArray[0][1] + y = pathArray[0][2] + mx = x + my = y + start++ + res.push(['M', x, y]) + } + for (var i = start, ii = pathArray.length; i < ii; i++) { + var r = (res[i] = []), + pa = pathArray[i] + if (pa[0] != lowerCase.call(pa[0])) { + r[0] = lowerCase.call(pa[0]) + switch (r[0]) { + case 'a': + r[1] = pa[1] + r[2] = pa[2] + r[3] = pa[3] + r[4] = pa[4] + r[5] = pa[5] + r[6] = +(pa[6] - x).toFixed(3) + r[7] = +(pa[7] - y).toFixed(3) + break + case 'v': + r[1] = +(pa[1] - y).toFixed(3) + break + case 'm': + mx = pa[1] + my = pa[2] + default: + for (var j = 1, jj = pa.length; j < jj; j++) { + r[j] = +(pa[j] - (j % 2 ? x : y)).toFixed(3) + } + } + } else { + r = res[i] = [] + if (pa[0] == 'm') { + mx = pa[1] + x + my = pa[2] + y + } + for (var k = 0, kk = pa.length; k < kk; k++) { + res[i][k] = pa[k] + } + } + var len = res[i].length + switch (res[i][0]) { + case 'z': + x = mx + y = my + break + case 'h': + x += +res[i][len - 1] + break + case 'v': + y += +res[i][len - 1] + break + default: + x += +res[i][len - 2] + y += +res[i][len - 1] + } + } + res.toString = toString + pth.rel = pathClone(res) + return res + } + function pathToAbsolute(pathArray) { + var pth = paths(pathArray) + if (pth.abs) { + return pathClone(pth.abs) + } + if (!is(pathArray, 'array') || !is(pathArray && pathArray[0], 'array')) { + // rough assumption + pathArray = Snap.parsePathString(pathArray) + } + if (!pathArray || !pathArray.length) { + return [['M', 0, 0]] + } + var res = [], + x = 0, + y = 0, + mx = 0, + my = 0, + start = 0, + pa0 + if (pathArray[0][0] == 'M') { + x = +pathArray[0][1] + y = +pathArray[0][2] + mx = x + my = y + start++ + res[0] = ['M', x, y] + } + var crz = + pathArray.length == 3 && + pathArray[0][0] == 'M' && + pathArray[1][0].toUpperCase() == 'R' && + pathArray[2][0].toUpperCase() == 'Z' + for (var r, pa, i = start, ii = pathArray.length; i < ii; i++) { + res.push((r = [])) + pa = pathArray[i] + pa0 = pa[0] + if (pa0 != pa0.toUpperCase()) { + r[0] = pa0.toUpperCase() + switch (r[0]) { + case 'A': + r[1] = pa[1] + r[2] = pa[2] + r[3] = pa[3] + r[4] = pa[4] + r[5] = pa[5] + r[6] = +pa[6] + x + r[7] = +pa[7] + y + break + case 'V': + r[1] = +pa[1] + y + break + case 'H': + r[1] = +pa[1] + x + break + case 'R': + var dots = [x, y].concat(pa.slice(1)) + for (var j = 2, jj = dots.length; j < jj; j++) { + dots[j] = +dots[j] + x + dots[++j] = +dots[j] + y + } + res.pop() + res = res.concat(catmullRom2bezier(dots, crz)) + break + case 'O': + res.pop() + dots = ellipsePath(x, y, pa[1], pa[2]) + dots.push(dots[0]) + res = res.concat(dots) + break + case 'U': + res.pop() + res = res.concat(ellipsePath(x, y, pa[1], pa[2], pa[3])) + r = ['U'].concat(res[res.length - 1].slice(-2)) + break + case 'M': + mx = +pa[1] + x + my = +pa[2] + y + default: + for (j = 1, jj = pa.length; j < jj; j++) { + r[j] = +pa[j] + (j % 2 ? x : y) + } + } + } else if (pa0 == 'R') { + dots = [x, y].concat(pa.slice(1)) + res.pop() + res = res.concat(catmullRom2bezier(dots, crz)) + r = ['R'].concat(pa.slice(-2)) + } else if (pa0 == 'O') { + res.pop() + dots = ellipsePath(x, y, pa[1], pa[2]) + dots.push(dots[0]) + res = res.concat(dots) + } else if (pa0 == 'U') { + res.pop() + res = res.concat(ellipsePath(x, y, pa[1], pa[2], pa[3])) + r = ['U'].concat(res[res.length - 1].slice(-2)) + } else { + for (var k = 0, kk = pa.length; k < kk; k++) { + r[k] = pa[k] + } + } + pa0 = pa0.toUpperCase() + if (pa0 != 'O') { + switch (r[0]) { + case 'Z': + x = +mx + y = +my + break + case 'H': + x = r[1] + break + case 'V': + y = r[1] + break + case 'M': + mx = r[r.length - 2] + my = r[r.length - 1] + default: + x = r[r.length - 2] + y = r[r.length - 1] + } + } + } + res.toString = toString + pth.abs = pathClone(res) + return res + } + function l2c(x1, y1, x2, y2) { + return [x1, y1, x2, y2, x2, y2] + } + function q2c(x1, y1, ax, ay, x2, y2) { + var _13 = 1 / 3, + _23 = 2 / 3 + return [ + _13 * x1 + _23 * ax, + _13 * y1 + _23 * ay, + _13 * x2 + _23 * ax, + _13 * y2 + _23 * ay, + x2, + y2 + ] + } + function a2c( + x1, + y1, + rx, + ry, + angle, + large_arc_flag, + sweep_flag, + x2, + y2, + recursive + ) { + // for more information of where this math came from visit: + // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes + var _120 = (PI * 120) / 180, + rad = (PI / 180) * (+angle || 0), + res = [], + xy, + rotate = Snap._.cacher(function (x, y, rad) { + var X = x * math.cos(rad) - y * math.sin(rad), + Y = x * math.sin(rad) + y * math.cos(rad) + return { x: X, y: Y } + }) + if (!rx || !ry) { + return [x1, y1, x2, y2, x2, y2] + } + if (!recursive) { + xy = rotate(x1, y1, -rad) + x1 = xy.x + y1 = xy.y + xy = rotate(x2, y2, -rad) + x2 = xy.x + y2 = xy.y + var cos = math.cos((PI / 180) * angle), + sin = math.sin((PI / 180) * angle), + x = (x1 - x2) / 2, + y = (y1 - y2) / 2 + var h = (x * x) / (rx * rx) + (y * y) / (ry * ry) + if (h > 1) { + h = math.sqrt(h) + rx = h * rx + ry = h * ry + } + var rx2 = rx * rx, + ry2 = ry * ry, + k = + (large_arc_flag == sweep_flag ? -1 : 1) * + math.sqrt( + abs( + (rx2 * ry2 - rx2 * y * y - ry2 * x * x) / + (rx2 * y * y + ry2 * x * x) + ) + ), + cx = (k * rx * y) / ry + (x1 + x2) / 2, + cy = (k * -ry * x) / rx + (y1 + y2) / 2, + f1 = math.asin(((y1 - cy) / ry).toFixed(9)), + f2 = math.asin(((y2 - cy) / ry).toFixed(9)) + + f1 = x1 < cx ? PI - f1 : f1 + f2 = x2 < cx ? PI - f2 : f2 + f1 < 0 && (f1 = PI * 2 + f1) + f2 < 0 && (f2 = PI * 2 + f2) + if (sweep_flag && f1 > f2) { + f1 = f1 - PI * 2 + } + if (!sweep_flag && f2 > f1) { + f2 = f2 - PI * 2 + } + } else { + f1 = recursive[0] + f2 = recursive[1] + cx = recursive[2] + cy = recursive[3] + } + var df = f2 - f1 + if (abs(df) > _120) { + var f2old = f2, + x2old = x2, + y2old = y2 + f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1) + x2 = cx + rx * math.cos(f2) + y2 = cy + ry * math.sin(f2) + res = a2c(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [ + f2, + f2old, + cx, + cy + ]) + } + df = f2 - f1 + var c1 = math.cos(f1), + s1 = math.sin(f1), + c2 = math.cos(f2), + s2 = math.sin(f2), + t = math.tan(df / 4), + hx = (4 / 3) * rx * t, + hy = (4 / 3) * ry * t, + m1 = [x1, y1], + m2 = [x1 + hx * s1, y1 - hy * c1], + m3 = [x2 + hx * s2, y2 - hy * c2], + m4 = [x2, y2] + m2[0] = 2 * m1[0] - m2[0] + m2[1] = 2 * m1[1] - m2[1] + if (recursive) { + return [m2, m3, m4].concat(res) + } else { + res = [m2, m3, m4].concat(res).join().split(',') + var newres = [] + for (var i = 0, ii = res.length; i < ii; i++) { + newres[i] = + i % 2 + ? rotate(res[i - 1], res[i], rad).y + : rotate(res[i], res[i + 1], rad).x + } + return newres + } + } + function findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) { + var t1 = 1 - t + return { + x: + pow(t1, 3) * p1x + + pow(t1, 2) * 3 * t * c1x + + t1 * 3 * t * t * c2x + + pow(t, 3) * p2x, + y: + pow(t1, 3) * p1y + + pow(t1, 2) * 3 * t * c1y + + t1 * 3 * t * t * c2y + + pow(t, 3) * p2y + } + } + + // Returns bounding box of cubic bezier curve. + // Source: http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html + // Original version: NISHIO Hirokazu + // Modifications: https://github.com/timo22345 + function curveDim(x0, y0, x1, y1, x2, y2, x3, y3) { + var tvalues = [], + bounds = [[], []], + a, + b, + c, + t, + t1, + t2, + b2ac, + sqrtb2ac + for (var i = 0; i < 2; ++i) { + if (i == 0) { + b = 6 * x0 - 12 * x1 + 6 * x2 + a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3 + c = 3 * x1 - 3 * x0 + } else { + b = 6 * y0 - 12 * y1 + 6 * y2 + a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3 + c = 3 * y1 - 3 * y0 + } + if (abs(a) < 1e-12) { + if (abs(b) < 1e-12) { + continue + } + t = -c / b + if (0 < t && t < 1) { + tvalues.push(t) + } + continue + } + b2ac = b * b - 4 * c * a + sqrtb2ac = math.sqrt(b2ac) + if (b2ac < 0) { + continue + } + t1 = (-b + sqrtb2ac) / (2 * a) + if (0 < t1 && t1 < 1) { + tvalues.push(t1) + } + t2 = (-b - sqrtb2ac) / (2 * a) + if (0 < t2 && t2 < 1) { + tvalues.push(t2) + } + } + + var x, + y, + j = tvalues.length, + jlen = j, + mt + while (j--) { + t = tvalues[j] + mt = 1 - t + bounds[0][j] = + mt * mt * mt * x0 + + 3 * mt * mt * t * x1 + + 3 * mt * t * t * x2 + + t * t * t * x3 + bounds[1][j] = + mt * mt * mt * y0 + + 3 * mt * mt * t * y1 + + 3 * mt * t * t * y2 + + t * t * t * y3 + } + + bounds[0][jlen] = x0 + bounds[1][jlen] = y0 + bounds[0][jlen + 1] = x3 + bounds[1][jlen + 1] = y3 + bounds[0].length = bounds[1].length = jlen + 2 + + return { + min: { x: mmin.apply(0, bounds[0]), y: mmin.apply(0, bounds[1]) }, + max: { x: mmax.apply(0, bounds[0]), y: mmax.apply(0, bounds[1]) } + } + } + + function path2curve(path, path2) { + var pth = !path2 && paths(path) + if (!path2 && pth.curve) { + return pathClone(pth.curve) + } + var p = pathToAbsolute(path), + p2 = path2 && pathToAbsolute(path2), + attrs = { x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null }, + attrs2 = { x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null }, + processPath = function (path, d, pcom) { + var nx, ny + if (!path) { + return ['C', d.x, d.y, d.x, d.y, d.x, d.y] + } + !(path[0] in { T: 1, Q: 1 }) && (d.qx = d.qy = null) + switch (path[0]) { + case 'M': + d.X = path[1] + d.Y = path[2] + break + case 'A': + path = ['C'].concat(a2c.apply(0, [d.x, d.y].concat(path.slice(1)))) + break + case 'S': + if (pcom == 'C' || pcom == 'S') { + // In "S" case we have to take into account, if the previous command is C/S. + nx = d.x * 2 - d.bx // And reflect the previous + ny = d.y * 2 - d.by // command's control point relative to the current point. + } else { + // or some else or nothing + nx = d.x + ny = d.y + } + path = ['C', nx, ny].concat(path.slice(1)) + break + case 'T': + if (pcom == 'Q' || pcom == 'T') { + // In "T" case we have to take into account, if the previous command is Q/T. + d.qx = d.x * 2 - d.qx // And make a reflection similar + d.qy = d.y * 2 - d.qy // to case "S". + } else { + // or something else or nothing + d.qx = d.x + d.qy = d.y + } + path = ['C'].concat(q2c(d.x, d.y, d.qx, d.qy, path[1], path[2])) + break + case 'Q': + d.qx = path[1] + d.qy = path[2] + path = ['C'].concat( + q2c(d.x, d.y, path[1], path[2], path[3], path[4]) + ) + break + case 'L': + path = ['C'].concat(l2c(d.x, d.y, path[1], path[2])) + break + case 'H': + path = ['C'].concat(l2c(d.x, d.y, path[1], d.y)) + break + case 'V': + path = ['C'].concat(l2c(d.x, d.y, d.x, path[1])) + break + case 'Z': + path = ['C'].concat(l2c(d.x, d.y, d.X, d.Y)) + break + } + return path + }, + fixArc = function (pp, i) { + if (pp[i].length > 7) { + pp[i].shift() + var pi = pp[i] + while (pi.length) { + pcoms1[i] = 'A' // if created multiple C:s, their original seg is saved + p2 && (pcoms2[i] = 'A') // the same as above + pp.splice(i++, 0, ['C'].concat(pi.splice(0, 6))) + } + pp.splice(i, 1) + ii = mmax(p.length, (p2 && p2.length) || 0) + } + }, + fixM = function (path1, path2, a1, a2, i) { + if (path1 && path2 && path1[i][0] == 'M' && path2[i][0] != 'M') { + path2.splice(i, 0, ['M', a2.x, a2.y]) + a1.bx = 0 + a1.by = 0 + a1.x = path1[i][1] + a1.y = path1[i][2] + ii = mmax(p.length, (p2 && p2.length) || 0) + } + }, + pcoms1 = [], // path commands of original path p + pcoms2 = [], // path commands of original path p2 + pfirst = '', // temporary holder for original path command + pcom = '' // holder for previous path command of original path + for (var i = 0, ii = mmax(p.length, (p2 && p2.length) || 0); i < ii; i++) { + p[i] && (pfirst = p[i][0]) // save current path command + + if (pfirst != 'C') { + // C is not saved yet, because it may be result of conversion + pcoms1[i] = pfirst // Save current path command + i && (pcom = pcoms1[i - 1]) // Get previous path command pcom + } + p[i] = processPath(p[i], attrs, pcom) // Previous path command is inputted to processPath + + if (pcoms1[i] != 'A' && pfirst == 'C') pcoms1[i] = 'C' // A is the only command + // which may produce multiple C:s + // so we have to make sure that C is also C in original path + + fixArc(p, i) // fixArc adds also the right amount of A:s to pcoms1 + + if (p2) { + // the same procedures is done to p2 + p2[i] && (pfirst = p2[i][0]) + if (pfirst != 'C') { + pcoms2[i] = pfirst + i && (pcom = pcoms2[i - 1]) + } + p2[i] = processPath(p2[i], attrs2, pcom) + + if (pcoms2[i] != 'A' && pfirst == 'C') { + pcoms2[i] = 'C' + } + + fixArc(p2, i) + } + fixM(p, p2, attrs, attrs2, i) + fixM(p2, p, attrs2, attrs, i) + var seg = p[i], + seg2 = p2 && p2[i], + seglen = seg.length, + seg2len = p2 && seg2.length + attrs.x = seg[seglen - 2] + attrs.y = seg[seglen - 1] + attrs.bx = toFloat(seg[seglen - 4]) || attrs.x + attrs.by = toFloat(seg[seglen - 3]) || attrs.y + attrs2.bx = p2 && (toFloat(seg2[seg2len - 4]) || attrs2.x) + attrs2.by = p2 && (toFloat(seg2[seg2len - 3]) || attrs2.y) + attrs2.x = p2 && seg2[seg2len - 2] + attrs2.y = p2 && seg2[seg2len - 1] + } + if (!p2) { + pth.curve = pathClone(p) + } + return p2 ? [p, p2] : p + } + function mapPath(path, matrix) { + if (!matrix) { + return path + } + var x, y, i, j, ii, jj, pathi + path = path2curve(path) + for (i = 0, ii = path.length; i < ii; i++) { + pathi = path[i] + for (j = 1, jj = pathi.length; j < jj; j += 2) { + x = matrix.x(pathi[j], pathi[j + 1]) + y = matrix.y(pathi[j], pathi[j + 1]) + pathi[j] = x + pathi[j + 1] = y + } + } + return path + } + + // http://schepers.cc/getting-to-the-point + function catmullRom2bezier(crp, z) { + var d = [] + for (var i = 0, iLen = crp.length; iLen - 2 * !z > i; i += 2) { + var p = [ + { x: +crp[i - 2], y: +crp[i - 1] }, + { x: +crp[i], y: +crp[i + 1] }, + { x: +crp[i + 2], y: +crp[i + 3] }, + { x: +crp[i + 4], y: +crp[i + 5] } + ] + if (z) { + if (!i) { + p[0] = { x: +crp[iLen - 2], y: +crp[iLen - 1] } + } else if (iLen - 4 == i) { + p[3] = { x: +crp[0], y: +crp[1] } + } else if (iLen - 2 == i) { + p[2] = { x: +crp[0], y: +crp[1] } + p[3] = { x: +crp[2], y: +crp[3] } + } + } else { + if (iLen - 4 == i) { + p[3] = p[2] + } else if (!i) { + p[0] = { x: +crp[i], y: +crp[i + 1] } + } + } + d.push([ + 'C', + (-p[0].x + 6 * p[1].x + p[2].x) / 6, + (-p[0].y + 6 * p[1].y + p[2].y) / 6, + (p[1].x + 6 * p[2].x - p[3].x) / 6, + (p[1].y + 6 * p[2].y - p[3].y) / 6, + p[2].x, + p[2].y + ]) + } + + return d + } + + // export + Snap.path = paths + + /*\ + * Snap.path.getTotalLength + [ method ] + ** + * Returns the length of the given path in pixels + ** + - path (string) SVG path string + ** + = (number) length + \*/ + Snap.path.getTotalLength = getTotalLength + /*\ + * Snap.path.getPointAtLength + [ method ] + ** + * Returns the coordinates of the point located at the given length along the given path + ** + - path (string) SVG path string + - length (number) length, in pixels, from the start of the path, excluding non-rendering jumps + ** + = (object) representation of the point: + o { + o x: (number) x coordinate, + o y: (number) y coordinate, + o alpha: (number) angle of derivative + o } + \*/ + Snap.path.getPointAtLength = getPointAtLength + /*\ + * Snap.path.getSubpath + [ method ] + ** + * Returns the subpath of a given path between given start and end lengths + ** + - path (string) SVG path string + - from (number) length, in pixels, from the start of the path to the start of the segment + - to (number) length, in pixels, from the start of the path to the end of the segment + ** + = (string) path string definition for the segment + \*/ + Snap.path.getSubpath = function (path, from, to) { + if (this.getTotalLength(path) - to < 1e-6) { + return getSubpathsAtLength(path, from).end + } + var a = getSubpathsAtLength(path, to, 1) + return from ? getSubpathsAtLength(a, from).end : a + } + /*\ + * Element.getTotalLength + [ method ] + ** + * Returns the length of the path in pixels (only works for `path` elements) + = (number) length + \*/ + elproto.getTotalLength = function () { + if (this.node.getTotalLength) { + return this.node.getTotalLength() + } + } + // SIERRA Element.getPointAtLength()/Element.getTotalLength(): If a is broken into different segments, is the jump distance to the new coordinates set by the _M_ or _m_ commands calculated as part of the path's total length? + /*\ + * Element.getPointAtLength + [ method ] + ** + * Returns coordinates of the point located at the given length on the given path (only works for `path` elements) + ** + - length (number) length, in pixels, from the start of the path, excluding non-rendering jumps + ** + = (object) representation of the point: + o { + o x: (number) x coordinate, + o y: (number) y coordinate, + o alpha: (number) angle of derivative + o } + \*/ + elproto.getPointAtLength = function (length) { + return getPointAtLength(this.attr('d'), length) + } + // SIERRA Element.getSubpath(): Similar to the problem for Element.getPointAtLength(). Unclear how this would work for a segmented path. Overall, the concept of _subpath_ and what I'm calling a _segment_ (series of non-_M_ or _Z_ commands) is unclear. + /*\ + * Element.getSubpath + [ method ] + ** + * Returns subpath of a given element from given start and end lengths (only works for `path` elements) + ** + - from (number) length, in pixels, from the start of the path to the start of the segment + - to (number) length, in pixels, from the start of the path to the end of the segment + ** + = (string) path string definition for the segment + \*/ + elproto.getSubpath = function (from, to) { + return Snap.path.getSubpath(this.attr('d'), from, to) + } + Snap._.box = box + /*\ + * Snap.path.findDotsAtSegment + [ method ] + ** + * Utility method + ** + * Finds dot coordinates on the given cubic beziér curve at the given t + - p1x (number) x of the first point of the curve + - p1y (number) y of the first point of the curve + - c1x (number) x of the first anchor of the curve + - c1y (number) y of the first anchor of the curve + - c2x (number) x of the second anchor of the curve + - c2y (number) y of the second anchor of the curve + - p2x (number) x of the second point of the curve + - p2y (number) y of the second point of the curve + - t (number) position on the curve (0..1) + = (object) point information in format: + o { + o x: (number) x coordinate of the point, + o y: (number) y coordinate of the point, + o m: { + o x: (number) x coordinate of the left anchor, + o y: (number) y coordinate of the left anchor + o }, + o n: { + o x: (number) x coordinate of the right anchor, + o y: (number) y coordinate of the right anchor + o }, + o start: { + o x: (number) x coordinate of the start of the curve, + o y: (number) y coordinate of the start of the curve + o }, + o end: { + o x: (number) x coordinate of the end of the curve, + o y: (number) y coordinate of the end of the curve + o }, + o alpha: (number) angle of the curve derivative at the point + o } + \*/ + Snap.path.findDotsAtSegment = findDotsAtSegment + /*\ + * Snap.path.bezierBBox + [ method ] + ** + * Utility method + ** + * Returns the bounding box of a given cubic beziér curve + - p1x (number) x of the first point of the curve + - p1y (number) y of the first point of the curve + - c1x (number) x of the first anchor of the curve + - c1y (number) y of the first anchor of the curve + - c2x (number) x of the second anchor of the curve + - c2y (number) y of the second anchor of the curve + - p2x (number) x of the second point of the curve + - p2y (number) y of the second point of the curve + * or + - bez (array) array of six points for beziér curve + = (object) bounding box + o { + o x: (number) x coordinate of the left top point of the box, + o y: (number) y coordinate of the left top point of the box, + o x2: (number) x coordinate of the right bottom point of the box, + o y2: (number) y coordinate of the right bottom point of the box, + o width: (number) width of the box, + o height: (number) height of the box + o } + \*/ + Snap.path.bezierBBox = bezierBBox + /*\ + * Snap.path.isPointInsideBBox + [ method ] + ** + * Utility method + ** + * Returns `true` if given point is inside bounding box + - bbox (string) bounding box + - x (string) x coordinate of the point + - y (string) y coordinate of the point + = (boolean) `true` if point is inside + \*/ + Snap.path.isPointInsideBBox = isPointInsideBBox + Snap.closest = function (x, y, X, Y) { + var r = 100, + b = box(x - r / 2, y - r / 2, r, r), + inside = [], + getter = X[0].hasOwnProperty('x') + ? function (i) { + return { + x: X[i].x, + y: X[i].y + } + } + : function (i) { + return { + x: X[i], + y: Y[i] + } + }, + found = 0 + while (r <= 1e6 && !found) { + for (var i = 0, ii = X.length; i < ii; i++) { + var xy = getter(i) + if (isPointInsideBBox(b, xy.x, xy.y)) { + found++ + inside.push(xy) + break + } + } + if (!found) { + r *= 2 + b = box(x - r / 2, y - r / 2, r, r) + } + } + if (r == 1e6) { + return + } + var len = Infinity, + res + for (i = 0, ii = inside.length; i < ii; i++) { + var l = Snap.len(x, y, inside[i].x, inside[i].y) + if (len > l) { + len = l + inside[i].len = l + res = inside[i] + } + } + return res + } + /*\ + * Snap.path.isBBoxIntersect + [ method ] + ** + * Utility method + ** + * Returns `true` if two bounding boxes intersect + - bbox1 (string) first bounding box + - bbox2 (string) second bounding box + = (boolean) `true` if bounding boxes intersect + \*/ + Snap.path.isBBoxIntersect = isBBoxIntersect + /*\ + * Snap.path.intersection + [ method ] + ** + * Utility method + ** + * Finds intersections of two paths + - path1 (string) path string + - path2 (string) path string + = (array) dots of intersection + o [ + o { + o x: (number) x coordinate of the point, + o y: (number) y coordinate of the point, + o t1: (number) t value for segment of path1, + o t2: (number) t value for segment of path2, + o segment1: (number) order number for segment of path1, + o segment2: (number) order number for segment of path2, + o bez1: (array) eight coordinates representing beziér curve for the segment of path1, + o bez2: (array) eight coordinates representing beziér curve for the segment of path2 + o } + o ] + \*/ + Snap.path.intersection = pathIntersection + Snap.path.intersectionNumber = pathIntersectionNumber + /*\ + * Snap.path.isPointInside + [ method ] + ** + * Utility method + ** + * Returns `true` if given point is inside a given closed path. + * + * Note: fill mode doesn’t affect the result of this method. + - path (string) path string + - x (number) x of the point + - y (number) y of the point + = (boolean) `true` if point is inside the path + \*/ + Snap.path.isPointInside = isPointInsidePath + /*\ + * Snap.path.getBBox + [ method ] + ** + * Utility method + ** + * Returns the bounding box of a given path + - path (string) path string + = (object) bounding box + o { + o x: (number) x coordinate of the left top point of the box, + o y: (number) y coordinate of the left top point of the box, + o x2: (number) x coordinate of the right bottom point of the box, + o y2: (number) y coordinate of the right bottom point of the box, + o width: (number) width of the box, + o height: (number) height of the box + o } + \*/ + Snap.path.getBBox = pathBBox + Snap.path.get = getPath + /*\ + * Snap.path.toRelative + [ method ] + ** + * Utility method + ** + * Converts path coordinates into relative values + - path (string) path string + = (array) path string + \*/ + Snap.path.toRelative = pathToRelative + /*\ + * Snap.path.toAbsolute + [ method ] + ** + * Utility method + ** + * Converts path coordinates into absolute values + - path (string) path string + = (array) path string + \*/ + Snap.path.toAbsolute = pathToAbsolute + /*\ + * Snap.path.toCubic + [ method ] + ** + * Utility method + ** + * Converts path to a new path where all segments are cubic beziér curves + - pathString (string|array) path string or array of segments + = (array) array of segments + \*/ + Snap.path.toCubic = path2curve + /*\ + * Snap.path.map + [ method ] + ** + * Transform the path string with the given matrix + - path (string) path string + - matrix (object) see @Matrix + = (string) transformed path string + \*/ + Snap.path.map = mapPath + Snap.path.toString = toString + Snap.path.clone = pathClone +}) diff --git a/src/set.js b/src/set.js new file mode 100644 index 0000000..92f6f9d --- /dev/null +++ b/src/set.js @@ -0,0 +1,379 @@ +// 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' +import { Snap } from './svg.js' +import mina from './mina.js' + +Snap.plugin(function (Snap, Element, Paper, glob) { + var mmax = Math.max, + mmin = Math.min + + // Set + var Set = function (items) { + this.items = [] + this.bindings = {} + this.length = 0 + this.type = 'set' + if (items) { + for (var 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 () { + var item, len + for (var 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 (var i = 0, ii = this.items.length; i < ii; i++) { + if (callback.call(thisArg, this.items[i], i) === false) { + return this + } + } + return this + } + /*\ + * 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]); + = (Element) the current element + \*/ + 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 + } + var args = arguments + if (Snap.is(attrs, 'array') && Snap.is(args[args.length - 1], 'array')) { + var each = true + } + var 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) + } + }) + } + /*\ + * Set.remove + [ method ] + ** + * Removes all children of the set. + * + = (object) Set object + \*/ + setproto.remove = function () { + while (this.length) { + this.pop().remove() + } + return this + } + /*\ + * 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 + - element (Element) specific element in the set to apply the attribute to + * or + - attr (string) attribute name + - element (Element) 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) { + var data = {} + if (typeof a == 'function') { + this.bindings[attr] = a + } else { + var aname = b || attr + this.bindings[attr] = function (v) { + data[aname] = v + a.attr(data) + } + } + return this + } + /*\ + * Set.attr + [ method ] + ** + * Equivalent of @Element.attr. + = (object) Set object + \*/ + setproto.attr = function (value) { + var unbound = {} + for (var k in value) { + if (this.bindings[k]) { + this.bindings[k](value[k]) + } else { + unbound[k] = value[k] + } + } + for (var i = 0, ii = this.items.length; i < ii; i++) { + this.items[i].attr(unbound) + } + return this + } + /*\ + * Set.clear + [ method ] + ** + * Removes all elements from the set + \*/ + setproto.clear = function () { + while (this.length) { + this.pop() + } + } + /*\ + * 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 + \*/ + setproto.splice = function (index, count, insertion) { + index = index < 0 ? mmax(this.length + index, 0) : index + count = mmax(0, mmin(this.length - index, count)) + var 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]) + } + var 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) + } + /*\ + * 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 + \*/ + setproto.exclude = function (el) { + for (var i = 0, ii = this.length; i < ii; i++) + if (this[i] == el) { + this.splice(i, 1) + return true + } + return false + } + /*\ + * Set.insertAfter + [ method ] + ** + * Inserts set elements after given element. + ** + - element (object) set will be inserted after this element + = (object) Set object + \*/ + setproto.insertAfter = function (el) { + var i = this.items.length + while (i--) { + this.items[i].insertAfter(el) + } + return this + } + /*\ + * Set.getBBox + [ method ] + ** + * Union of all bboxes of the set. See @Element.getBBox. + = (object) bounding box descriptor. See @Element.getBBox. + \*/ + setproto.getBBox = function () { + var x = [], + y = [], + x2 = [], + y2 = [] + for (var i = this.items.length; i--; ) + if (!this.items[i].removed) { + var 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 + } + } + /*\ + * Set.insertAfter + [ method ] + ** + * Creates a clone of the set. + ** + = (object) New Set object + \*/ + setproto.clone = function (s) { + s = new Set() + for (var i = 0, ii = this.items.length; i < ii; i++) { + s.push(this.items[i].clone()) + } + return s + } + setproto.toString = function () { + return 'Snap\u2018s set' + } + setproto.type = 'set' + // export + /*\ + * Snap.Set + [ property ] + ** + * Set constructor. + \*/ + Snap.Set = Set + /*\ + * Snap.set + [ method ] + ** + * Creates a set and fills it with list of arguments. + ** + = (object) New Set object + | var r = paper.rect(0, 0, 10, 10), + | s1 = Snap.set(), // empty set + | s2 = Snap.set(r, paper.circle(100, 100, 20)); // prefilled set + \*/ + Snap.set = function () { + var set = new Set() + if (arguments.length) { + set.push.apply(set, Array.prototype.slice.call(arguments, 0)) + } + return set + } +}) diff --git a/src/svg.js b/src/svg.js new file mode 100644 index 0000000..4708651 --- /dev/null +++ b/src/svg.js @@ -0,0 +1,1984 @@ +// 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('')"`. + - 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 `{}` 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: + #
    + #
  • Color name (red, green, cornflowerblue, etc)
  • + #
  • #••• — shortened HTML color: (#000, #fc0, etc.)
  • + #
  • #•••••• — full length HTML color: (#000000, #bd2300)
  • + #
  • rgb(•••, •••, •••) — red, green and blue channels values: (rgb(200, 100, 0))
  • + #
  • rgba(•••, •••, •••, •••) — also with opacity
  • + #
  • rgb(•••%, •••%, •••%) — same as above, but in %: (rgb(100%, 175%, 0%))
  • + #
  • rgba(•••%, •••%, •••%, •••%) — also with opacity
  • + #
  • hsb(•••, •••, •••) — hue, saturation and brightness values: (hsb(0.5, 0.25, 1))
  • + #
  • hsba(•••, •••, •••, •••) — also with opacity
  • + #
  • hsb(•••%, •••%, •••%) — same as above, but in %
  • + #
  • hsba(•••%, •••%, •••%, •••%) — also with opacity
  • + #
  • hsl(•••, •••, •••) — hue, saturation and luminosity values: (hsb(0.5, 0.25, 0.5))
  • + #
  • hsla(•••, •••, •••, •••) — also with opacity
  • + #
  • hsl(•••%, •••%, •••%) — same as above, but in %
  • + #
  • hsla(•••%, •••%, •••%, •••%) — also with opacity
  • + #
+ * 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 + '' + 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) +}