diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml new file mode 100644 index 0000000..8d2f957 --- /dev/null +++ b/.github/workflows/tests.yaml @@ -0,0 +1,26 @@ +name: Node.js CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [20.x, 18.x] + + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v2 + with: + node-version: ${{ matrix.node-version }} + - run: npm ci + - run: npm run build --if-present + - run: npm test diff --git a/.gitignore b/.gitignore index 2867fd8..180ddfc 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ *.swp *.launch .DS_Store -node_modules \ No newline at end of file +node_modules +npm-debug.log diff --git a/.jshintrc b/.jshintrc index b654edb..2fad102 100644 --- a/.jshintrc +++ b/.jshintrc @@ -1,18 +1,16 @@ { "node": true, "browser": true, - "es5": true, "esnext": true, "bitwise": false, - "camelcase": true, + "camelcase": false, "curly": true, "eqeqeq": true, "immed": true, - "indent": 4, - "latedef": true, + "latedef": false, "newcap": true, "noarg": true, - "quotmark": true, + "quotmark": false, "regexp": true, "undef": true, "unused": true, diff --git a/CHANGELOG b/CHANGELOG index 2075369..5674eed 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,114 @@ -v.0.4.0 +v0.8.1 + date Sep 9, 2015 + - Improved layout performance in some cases + - Extended WebGL API to transform from/to webgl coordinates space + - Custom shaders now receive layout's view of node position. This means + that `y` coordinate needs to be replaced to `-y` in your custom shaders + code + - Bug fixes + - Documentation updates +v0.7.7 + date Mar 24, 2015 + - Upgraded to latest `ngraph` modules + - Bug fixes +v0.7.0 + date Feb 4, 2015 + - All files are now CommonJS Modules + - `Viva.Graph.svg` is replaced with npm module `simplesvg` + - `Viva.Graph.graph` is replaced with `ngraph.graph` + - `Viva.Graph.generator` is replaced with `ngraph.generators`. This module + contains all original graph + new graphs. + Note: `randomNoLinks` is called `noLinks`. + - `Viva.graph.Layout.forceDirected` is replaced with npm module + 'ngraph.forcelayout'. This module is faster than older one, and has better + test coverage. Unfortunately this also means breaking changes: + 1. Instead of passing `link` object to `layout.getLinkPosition(link)`, you + need to pas linkId: `layout.getLinkPosition(link.id)`. + 2. Instead of passing `node` object to `layout.setNodePosition(node, x, y)` + use `layout.setNodePosition(node.id, x, y)`. + 3. Force based layout settings can be now accessed from `layout.simulator`: + `layout.drag()` is now known as `simulator.dragCoeff()` + `layout.springCoeff()` -> `simulator.springCoeff()` + `layout.springLength()` -> `simulator.springLength()` + `layout.gravity()` -> `simulator.gravity()` + `layout.theta()` -> `simulator.theta()` + - `Viva.Graph.Point2d` is removed. Use plain {x: 42, y: 42} object + - `Viva.Graph.graph.addEventListener` is replaced with `on` method + - `Viva.Graph.View.cssGraphcis` is deprecated + - `Viva.Graph.View.svgNodeFactory` is deprecated + - `geom.convexHull` is deprecated. Use https://github.com/anvaka/cnvx + instead. + - `Viva.Graph.community` is deprecated. Use https://github.com/anvaka/ngraph.slpa + instead. + +v0.6.0 + date Dec 27, 2014 + - Migrated to gulp. First step towards commonjs. + - Fixed spelling, grammar. +v0.5.8 + date: Sep 28, 2014 + - Graphics object no longer waits `init` method to create container. Fixes + https://github.com/anvaka/VivaGraphJS/issues/82 + - When renderer is paused clicking on any node will not reset it. Fixes + https://github.com/anvaka/VivaGraphJS/issues/98 + - Calculating stable threshold based on absolute value. Fixes + https://github.com/anvaka/VivaGraphJS/issues/100#issuecomment-55568513 + - Minor code style improvements + +v0.5.6 + date: Apr 05, 2014 + - Using Object.create where possible to avoid node id collision with + standard object property names (e.g. `constructor`) + - Fixed bug in constant layout + +v0.5.5 + date: Feb 22, 2014 + - Stable threshold can now be configured via options (Tnanks to @grnadav) + +v0.5.4 + date: Feb 19, 2014 + - Webgl renderer supports centering API (Thanks to @sgerard) + +v0.5.3 + date: Dec 27, 2013 + - Small clean up in renderer + - Added pan/zoom API for SVG + +v0.5.2 + date: Nov 23, 2013 + - Euler integrator now supports custom time step + +v0.5.1 + date: Nov 20, 2013 + - Merged changes from v0.4.2: custom links length + fixed bug in arguments + order + +v0.5.0 + date: Oct 20, 2013 + BREAKING CHANGES: + - graph.addNode() no longer augments old data model with whatever passed + to method. Instead it stores direct reference to passed node's model + - node/link objects no longer store layout specific properties. Instead you can + query all layout properties from layout class itself. This allows the same + graph to be layed out by two different layout algorithm, without stepping + on toes of each other + - node/link objects no longer store ui specific properties. Instead you + can query them from specific UI provider + +v0.4.2 + date: Nov 20, 2013 + changes: + - Added customization point to force directed layout to allow specify custom links length. + - Fixed a bug with wrong arguments order in physics simulator. + +v0.4.1 + date: Jul 20, 2013 + changes: + - Layout algorithm now listens to changes from graph directly. Public API + to add/remove nodes or links is dropped from layout algorithms. + - Added small performance test for node.js + +v0.4.0 date: April 7, 2013 changes: - Migrated to grunt.js as a build system diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index c7cdb18..0000000 --- a/Gruntfile.js +++ /dev/null @@ -1,145 +0,0 @@ -// Generated on 2013-03-27 using generator-webapp 0.1.5 -"use strict"; - -module.exports = function (grunt) { - var libConfig = { - src: "src", - dist: "dist" - }; - // This needs to be changed. It"s just copies structure of - // my original builder, which was not really well organized: - var fileGroups = { - core: [ - "vivagraph.js", - "version.js", - "Utils/etc.js", - "Utils/browserInfo.js", - "Utils/indexOf.js", - "Utils/getDimensions.js", - "Utils/events.js", - "Input/dragndrop.js", - "Input/domInputManager.js", - "Input/spatialIndex.js", // TODO: Do I need this for SVG? - "Utils/timer.js", - "Utils/geom.js", - - "Core/primitives.js", - "Core/graph.js", - "Core/operations.js", - - "Physics/primitives.js", - "Physics/eulerIntegrator.js", - "Physics/Forces/nbodyForce.js", - "Physics/Forces/dragForce.js", - "Physics/Forces/springForce.js", - "Physics/forceSimulator.js", - "Layout/forceDirected.js", - "Layout/constant.js", - "View/renderer.js" - ], - extra: [ - "Core/serializer.js", - "Algorithms/centrality.js", - "Algorithms/Community/community.js", - "Algorithms/Community/slpa.js", - "Core/generator.js", - "View/cssGraphics.js" - ], - svg: [ - "Svg/svg.js", - "View/svgGraphics.js", - "View/svgNodeFactory.js", - ], - webgl: [ - "WebGL/webgl.js", - "WebGL/webglUIModels.js", - "WebGL/webglNodeProgram.js", - "WebGL/webglLinkProgram.js", - "WebGL/webglImageNodeProgram.js", - "View/webglGraphics.js", - "WebGL/webglInputEvents.js", - "Input/webglInputManager.js", - ] - }; - Object.keys(fileGroups).forEach(function (key) { - fileGroups[key] = fileGroups[key].map(function (path) { - return libConfig.src + "/" + path; - }); - }); - grunt.initConfig({ - lib: libConfig, - watch: { - js: { - files: "<%= lib.src %>/**/*.js", - tasks: ["build"], - spawn: true - } - }, - jshint: { - all: [ - "Gruntfile.js", - "<%= lib.src %>/{,*/}*.js" - ], - options: { - jshintrc: ".jshintrc" - } - }, - clean: { - dist: ["<%= lib.dist %>/*"], - }, - concat: { - options: { - separator: "" - }, - all: { - src: [ - fileGroups.core, - fileGroups.extra, - fileGroups.svg, - fileGroups.webgl - ], - dest: "<%= lib.dist %>/vivagraph.js" - }, - svg: { - src: [ - fileGroups.core, - fileGroups.svg - ], - dest: "<%= lib.dist %>/vivagraph.svg.js" - }, - webgl: { - src: [ - fileGroups.core, - fileGroups.webgl - ], - dest: "<%= lib.dist %>/vivagraph.webgl.js" - }, - }, - uglify: { - dist: { - files: { - "<%= lib.dist %>/vivagraph.min.js": ["<%= lib.dist %>/vivagraph.js"] - } - } - } - }); - - grunt.loadNpmTasks("grunt-contrib-clean"); - grunt.loadNpmTasks("grunt-contrib-jshint"); - grunt.loadNpmTasks("grunt-contrib-concat"); - grunt.loadNpmTasks("grunt-contrib-uglify"); - grunt.loadNpmTasks("grunt-regarde"); - - grunt.renameTask("regarde", "watch"); - - grunt.registerTask("build", [ - "clean:dist", - "concat:all", - "uglify" - ]); - grunt.registerTask("server", [ - "build", - "watch" - ]); - grunt.registerTask("default", ["jshint", "build"]); -}; diff --git a/LICENSE b/LICENSE index add93fc..1295245 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2011, Andrei Kashcha +Copyright (c) 2011 - 2026, Andrei Kashcha All rights reserved. Redistribution and use in source and binary forms, with or without @@ -23,4 +23,4 @@ BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index 65dbc33..99b481c 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,22 @@ -VivaGraphJS - JavaScript Graph Drawing Library +VivaGraph [![build status](https://github.com/anvaka/VivaGraphJS/actions/workflows/tests.yaml/badge.svg)](https://github.com/anvaka/VivaGraphJS/actions/workflows/tests.yaml) ================================================== -**VivaGraphJS** is a free [graph drawing](http://en.wikipedia.org/wiki/Graph_drawing) library for JavaScript. -It is designed to be extensible and to support different rendering engines and layout algorithms. At the moment -it supports rendering graphs using WebGL, SVG or CSS formats. Layout algorithms currently implemented are: +**VivaGraphJS** is designed to be extensible and to support different +rendering engines and layout algorithms. Underlying algorithms have been broken out into [ngraph](https://github.com/anvaka/ngraph). -* [Force Directed](http://en.wikipedia.org/wiki/Force-based_algorithms_\(graph_drawing\)) - based on Barnes-Hut -simulation and optimized for JavaScript language this algorithm gives `N * lg(N) + V` performance per iteration. - -Library provides API which tracks graph changes and reflect changes on the rendering surface -accordingly. +The larger family of modules can be found by [querying npm for "ngraph"](https://www.npmjs.com/search?q=ngraph). +[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/anvaka/VivaGraphJS?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) Enough talking. Show me the demo! ---------------------------------------------------- Some examples of library usage in the real projects: * [Amazon Visualization](http://www.yasiv.com/amazon#/Search?q=graph%20drawing&category=Books&lang=US) Shows related products on Amazon.com, uses SVG as graph output -* [YouTube Visualization](http://www.yasiv.com/youtube#/Search?q=write%20in%20c) Shows related videos from YouTube. SVG based. -* [Facebook Visualization](http://www.yasiv.com/facebook) friendship visualization on Facebook. WebGL based. * [Graph Viewer](http://www.yasiv.com/graphs#Bai/rw496) visualization of sparse matrices collection of the University of Florida. WebGL based. -* [Vkontakte Visualization](http://www.yasiv.com/vk) friendship visualization of the largest social network in Russia [vk.com](vk.com). WebGL based. +* [Vkontakte Visualization](http://www.yasiv.com/vk) friendship visualization of the largest social network in Russia [vk.com](https://vk.com). WebGL based. -To start using the library include `vivagraph.js` script from the [dist](https://github.com/anvaka/VivaGraphJS/tree/master/dist) folder. The following code is the minimum required to render a graph with two nodes and one edge: +To start using the library include `vivagraph.js` script from the [dist](https://github.com/anvaka/VivaGraphJS/tree/master/dist) +folder. The following code is the minimum required to render a graph with two nodes and one edge: ```javascript var graph = Viva.Graph.graph(); @@ -31,12 +26,25 @@ var renderer = Viva.Graph.View.renderer(graph); renderer.run(); ``` -This will produce the following layout: +This will instantiate a graph inside `document.body`: ![Simple graph](https://github.com/anvaka/VivaGraphJS/raw/master/packages/Images/mingraph.png) -The code above adds a link to the graph between nodes `1` and `2`. Since nodes are not yet in the graph -they will be created. It's equivalent to +If you want to render graph in your own DOM element: + +```javascript +var graph = Viva.Graph.graph(); +graph.addLink(1, 2); + +// specify where it should be rendered: +var renderer = Viva.Graph.View.renderer(graph, { + container: document.getElementById('graphDiv') +}); +renderer.run(); +``` + +The code above adds a link to the graph between nodes `1` and `2`. Since nodes +are not yet in the graph they will be created. It's equivalent to ```javascript var graph = Viva.Graph.graph(); @@ -48,18 +56,20 @@ var renderer = Viva.Graph.View.renderer(graph); renderer.run(); ``` - Customization ---------------------------------------------------- -VivaGraphJS is all about customization. You can easily change the appearance of nodes and links. You can also change the layouting algorithm and medium that displays elements of the graph. For example: The following code allows you to use CSS-based rendering, instead of the default SVG. +VivaGraphJS is all about customization. You can easily change the appearance of +nodes and links. You can also change the layouting algorithm and medium that +displays elements of the graph. For example: The following code allows you to +use WebGL-based rendering, instead of the default SVG. ```javascript var graph = Viva.Graph.graph(); graph.addLink(1, 2); -var graphics = Viva.Graph.View.cssGraphics(); +var graphics = Viva.Graph.View.webglGraphics(); -var renderer = Viva.Graph.View.renderer(graph, +var renderer = Viva.Graph.View.renderer(graph, { graphics : graphics }); @@ -101,8 +111,7 @@ graphics.node(function(node) { nodeUI.attr('x', pos.x - 12).attr('y', pos.y - 12); }); -var renderer = Viva.Graph.View.renderer(graph, - { +var renderer = Viva.Graph.View.renderer(graph, { graphics : graphics }); renderer.run(); @@ -125,7 +134,9 @@ var renderer = Viva.Graph.View.renderer(graph); renderer.run(); ``` -Graph generators are part of the library, which can produce classic graphs. `grid` generator creates a grid with given number of columns and rows. But with default parameters the rendering is pretty ugly: +Graph generators are part of the library, which can produce classic graphs. +`grid` generator creates a grid with given number of columns and rows. But with +default parameters the rendering is pretty ugly: ![Grid 3x3 bad](https://github.com/anvaka/VivaGraphJS/raw/master/packages/Images/gridBad.png) @@ -152,28 +163,58 @@ Now the result is much better: ![Grid 3x3](https://github.com/anvaka/VivaGraphJS/raw/master/packages/Images/gridGood.png) -Tuning layout algorithm is definitely one of the hardest part of using this library. It has to be improved in future to simplify usage. Each of the force directed algorithm parameters are described in the source code. +You can tune values during simulation with `layout.simulator.springLength(newValue)`, `layout.simulator.springCoeff(newValue)`, etc. See all the values that you can tune in [this source file](https://github.com/anvaka/ngraph.physics.simulator/blob/master/index.js#L12). + +Tuning layout algorithm is definitely one of the hardest part of using this library. +It has to be improved in future to simplify usage. Each of the force directed +algorithm parameters are described in the source code. + +Design philosophy/roadmap +------------------------- + +Until version 0.7.x VivaGraph was a single monolithic code base. Starting from +0.7.x the library is bundled from small npm modules into `Viva` namespace. +All these modules are part of a larger [ngraph](https://github.com/anvaka/ngraph) +family. `ngraph` modules support rendering graphs into images, 3D rendering, +integration with [gephi](https://gephi.org/), pagerank calculation and many more. + +Version 0.7 is a compromise between maximum backward compatibility and ngraph +flexibility. Eventually I hope to further simplify API and provide interface +for custom builds. +Upgrade guide +------------- + +Please refer the [upgrade guide](https://github.com/anvaka/VivaGraphJS/blob/master/docs/upgrade_guide.md) to see how to update older versions of the library to the latest one. Local Build ----------- Run the following script: ``` -git clone git://github.com/anvaka/VivaGraphJS.git +git clone https://github.com/anvaka/VivaGraphJS.git cd ./VivaGraphJS npm install -grunt +gulp release ``` The combined/minified code should be stored in ```dist``` folder. +Looking for alternatives? +------------------------- + +I'm trying to put up a list of all known graph drawing libraries. +Please [find it here](http://anvaka.github.io/graph-drawing-libraries/#/all) I need your feedback ---------------------------------------------------- -Disclaimer: I wrote this library to learn JavaScript. By no means I pretend to be an expert in the language and chosen approach to design may not be the optimal. I would love to hear your feedback and suggestions. - -Though I implemented this library from scratch, I went through many existing libraries to pick the best (at my view) out of them. If you are evaluating libraries for your project make sure to check them out as well: - -* [Dracula Graph Library](https://github.com/strathausen/dracula) - written by [Johann Philipp Strathausen](https://github.com/strathausen) and uses [Raphaƫl](http://raphaeljs.com/) library to render graphs. Has very simple API. -* [D3](http://mbostock.github.com/d3/ex/force.html) - one of the best data visualization library in JavaScript world. From [Mike Bostock](https://github.com/mbostock). - -My goal is to create highly performant javascript library, which serves in the field of graph drawing. To certain extent I achieved it. But I have no doubt there is much more to improve here. \ No newline at end of file +Disclaimer: I wrote this library to learn JavaScript. By no means I pretend to +be an expert in the language and chosen approach to design may not be the optimal. +I would love to hear your feedback and suggestions. + +Though I implemented this library from scratch, I went through many existing +libraries to pick the best (at my view) out of them. If you are evaluating libraries +for your project make sure to [check them out](http://anvaka.github.io/graph-drawing-libraries/#/all) +as well. + +My goal is to create highly performant javascript library, which serves in the +field of graph drawing. To certain extent I achieved it. But I have no doubt +there is much more to improve here. diff --git a/bower.json b/bower.json new file mode 100644 index 0000000..c937a4d --- /dev/null +++ b/bower.json @@ -0,0 +1,27 @@ +{ + "name": "vivagraphjs", + "homepage": "https://github.com/anvaka/VivaGraphJS", + "authors": [ + "anvaka " + ], + "description": "Graph drawing library", + "main": "dist/vivagraph.js", + "moduleType": [ + "amd", + "globals", + "node" + ], + "keywords": [ + "graph", + "drawing", + "vivagraph" + ], + "license": "BSD-3", + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ] +} diff --git a/demos/other/communityDetection.html b/demos/other/communityDetection.html deleted file mode 100644 index 84172e5..0000000 --- a/demos/other/communityDetection.html +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - VivaGraphs test page - - - - - - -
-
-
- - diff --git a/demos/other/constantLayout.html b/demos/other/constantLayout.html index 962dd1e..b50a80c 100644 --- a/demos/other/constantLayout.html +++ b/demos/other/constantLayout.html @@ -5,7 +5,7 @@ - + - + diff --git a/demos/other/convexHull.html b/demos/other/convexHull.html deleted file mode 100644 index 39e26bc..0000000 --- a/demos/other/convexHull.html +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - VivaGraph Convex Hull test page - - - - - -
- - - - -
- Refresh - - diff --git a/demos/other/customLinkLength.html b/demos/other/customLinkLength.html new file mode 100644 index 0000000..268c46f --- /dev/null +++ b/demos/other/customLinkLength.html @@ -0,0 +1,61 @@ + + + + + VivaGraphs test page + + + + + + + Each link has custom length + + diff --git a/demos/other/d3layout/index.html b/demos/other/d3layout/index.html new file mode 100644 index 0000000..80982bf --- /dev/null +++ b/demos/other/d3layout/index.html @@ -0,0 +1,31 @@ + + + + + + VivaGraph and d3 layout + + + + + + + + + + + +
+ + + diff --git a/demos/other/d3layout/index.js b/demos/other/d3layout/index.js new file mode 100644 index 0000000..f37d280 --- /dev/null +++ b/demos/other/d3layout/index.js @@ -0,0 +1,117 @@ +var graphGenerator = Viva.Graph.generator(); +var graph = graphGenerator.grid(20, 20); + +var layout = d3Force(graph, { + springLength : 20, + springCoeff : 1, + gravity: -30, + springIterations: 10 +}); + +var graphics = Viva.Graph.View.webglGraphics(); + +var renderer = Viva.Graph.View.renderer(graph, { + layout: layout, + graphics: graphics, + renderLinks: true, + prerender: true +}); + +renderer.run(); + +// TODO: extract into module +function d3Force(graph, options) { + // todo: check input + var nodes = [], links = []; + var nodeIdToIdx = Object.create(null); + var linkIdToD3Link = Object.create(null); + + graph.forEachNode(function(n) { + var index = nodes.length; + nodeIdToIdx[n.id] = index; + var node = { + index: index + } + nodes.push(node); + }); + + graph.forEachLink(function(l) { + var source = nodeIdToIdx[l.fromId]; + var target = nodeIdToIdx[l.toId]; + + var index = links.length; + var link = {source: source, target: target, index: index} + links.push(link); + linkIdToD3Link[l.id] = link; + }); + + var simulation = d3.forceSimulation(nodes) + .force("charge", d3.forceManyBody().strength(options.gravity)) + .force("link", d3.forceLink(links) + .strength(options.springCoeff) + .distance(options.springLength) + .iterations(options.springIterations) + ); + + simulation.stop(); + + return { + step: function() { + simulation.tick(); + }, + + getNodePosition: getNodePosition, + + getLinkPosition: function(linkId) { + var link = linkIdToD3Link[linkId]; + return { + from: link.source, + to: link.target + }; + }, + + getGraphRect: function() { + var minX = Number.POSITIVE_INFINITY; + var minY = Number.POSITIVE_INFINITY; + var maxX = Number.NEGATIVE_INFINITY; + var maxY = Number.NEGATIVE_INFINITY; + + nodes.forEach(function(node) { + if (node.x < minX) minX = node.x; + if (node.x > maxX) maxX = node.x; + + if (node.y < minY) minY = node.y; + if (node.y > maxY) maxY = node.y; + }) + + return { + x1: minX, + x2: maxX, + y1: minY, + y2: maxY + } + }, + + isNodePinned: function() { + // TODO: implement + return false; + }, + + pinNode: function() { + // TODO: implement me + }, + + dispose: function() { + }, + + setNodePosition: function(nodeId, x, y) { + var pos = getNodePosition(nodeId); + pos.x = x; + pos.y = y; + } + } + + function getNodePosition(nodeId) { + return nodes[nodeIdToIdx[nodeId]]; + } +} diff --git a/demos/other/d3sampleTest.html b/demos/other/d3sampleTest.html index 4fea8fc..dc94d53 100644 --- a/demos/other/d3sampleTest.html +++ b/demos/other/d3sampleTest.html @@ -4,7 +4,7 @@ VivaGraphs test page - + diff --git a/demos/other/dynamic.html b/demos/other/dynamic.html index ccacd23..62d8aa6 100644 --- a/demos/other/dynamic.html +++ b/demos/other/dynamic.html @@ -18,24 +18,24 @@ "#bcbd22", "#dbdb8d", "#17becf", "#9edae5" ]; - + function beginRemoveNodesLoop(graph){ var nodesLeft = []; graph.forEachNode(function(node){ nodesLeft.push(node.id); }); - + var removeInterval = setInterval(function(){ var nodesCount = nodesLeft.length; - + if (nodesCount > 0){ var nodeToRemove = Math.min((Math.random() * nodesCount) << 0, nodesCount - 1); - + graph.removeNode(nodesLeft[nodeToRemove]); nodesLeft.splice(nodeToRemove, 1); } - - if (nodesCount === 0) { + + if (nodesCount === 0) { clearInterval(removeInterval); setTimeout(function(){ beginAddNodesLoop(graph); @@ -43,12 +43,12 @@ } }, 100); } - + function beginAddNodesLoop(graph){ var i = 0, m = 10, n = 20; var addInterval = setInterval(function(){ graph.beginUpdate(); - + for (var j = 0; j < m; ++j){ var node = i + j * n; if (i > 0) { graph.addLink(node, i - 1 + j * n); } @@ -56,16 +56,16 @@ } i++; graph.endUpdate(); - - if (i >= n) { + + if (i >= n) { clearInterval(addInterval); setTimeout(function() { beginRemoveNodesLoop(graph); - }, 10000); + }, 10000); } }, 100); } - + function onLoad() { var graph = Viva.Graph.graph(); @@ -75,55 +75,43 @@ dragCoeff : 0.02, gravity : -1.2 }); - - // var graphics = Viva.Graph.View.cssGraphics(); + var graphics = Viva.Graph.View.svgGraphics(); graphics.node(function(node){ return Viva.Graph.svg('rect') .attr('width', 10) .attr('height', 10) - .attr('fill', node.data ? node.data : '#00a2e8'); + .attr('fill', node.data ? node.data : '#00a2e8'); }); - + var renderer = Viva.Graph.View.renderer(graph, { layout : layout, graphics : graphics, - container : document.getElementById('graph1'), + container : document.getElementById('graphConainer'), renderLinks : true }); - + renderer.run(50); - + beginAddNodesLoop(graph); l = layout; } - - -
- + + +
+ diff --git a/demos/other/dynamicNodeChange.html b/demos/other/dynamicNodeChange.html new file mode 100644 index 0000000..379e5af --- /dev/null +++ b/demos/other/dynamicNodeChange.html @@ -0,0 +1,90 @@ + + + + + VivaGraphs test page + + + + + +
+ + diff --git a/demos/other/empty.html b/demos/other/empty.html index 16154a9..7c245da 100644 --- a/demos/other/empty.html +++ b/demos/other/empty.html @@ -5,15 +5,12 @@ - diff --git a/demos/other/graphInsideGraph.html b/demos/other/graphInsideGraph.html new file mode 100644 index 0000000..56edf29 --- /dev/null +++ b/demos/other/graphInsideGraph.html @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + diff --git a/demos/other/input-override/README.md b/demos/other/input-override/README.md new file mode 100644 index 0000000..93700f9 --- /dev/null +++ b/demos/other/input-override/README.md @@ -0,0 +1,77 @@ +# no interaction + +This demo shows how to disable automatic processing of user input by VivaGraph. +For example, if you want to just render a graph, and let users scroll your page +this is the right place to start from. + +[See online demo](http://anvaka.github.io/VivaGraphJS/demos/other/input-override/) + +# How? + +When you create a renderer you can explicitly allow or forbid vivagraph to handle +user interaction. By default VivaGraph will allows user to: + +* Zoom in/zoom out using scroll event; +* Drag individual nodes with left mouse; +* Drag entire graph by dragging background canvas. + +To disable all interactions use: + +``` js +Viva.Graph.View.renderer(graph, { + interactive: false +}); +``` + +`intractive` argument can accept either boolean or string type. Boolean is a +global switch for all features. String allows you fine control over features +that you'd like to keep, and can accept any combination of the following words: + +* `node` - enable node interaction +* `drag` - enable background dragging +* `scroll` - enable scrolling + +If you pass a string with just one word it will automatically disable remaining +features. For example, the following code will allow node dragging, but will +forbid vivagraph from controlling scroll-zoom, and canvas dragging: + +``` js +Viva.Graph.View.renderer(graph, { + interactive: 'node' +}); +``` + +To allow both node dragging and canvas dragging: + +``` js +Viva.Graph.View.renderer(graph, { + interactive: 'node drag' // scroll is disabled! +}); +``` + +To allow only scroll-zoom, but disable the rest, use: + +``` js +Viva.Graph.View.renderer(graph, { + interactive: 'scroll' +}); +``` + +Finally, to explicitly enable all interactions: + +``` js +Viva.Graph.View.renderer(graph, { + interactive: 'scroll drag node' // order of words does not matter +}); +``` + +This is equivalent to: + +``` js +Viva.Graph.View.renderer(graph, { + interactive: true +}); + +// or even shorter, since renderer interactive by default: +Viva.Graph.View.renderer(graph); +``` diff --git a/demos/other/input-override/index.html b/demos/other/input-override/index.html new file mode 100644 index 0000000..2ff917b --- /dev/null +++ b/demos/other/input-override/index.html @@ -0,0 +1,16 @@ + + + + No interaction - VivaGraph + + + +

+ VivaGraph is allowed to process interaction with individual nodes only. It + will not handle scroll events in this example. +

+ + + + + diff --git a/demos/other/input-override/index.js b/demos/other/input-override/index.js new file mode 100644 index 0000000..fc3d8d3 --- /dev/null +++ b/demos/other/input-override/index.js @@ -0,0 +1,54 @@ +// let's just rend all possible graphs on this page: +var generator = Viva.Graph.generator(); +var graphNames = Object.keys(generator); + +for (var i = 0; i < graphNames.length; ++i) { + renderGraph(graphNames[i]); +} + +function renderGraph(name) { + var graph = generateGraphByName(name); + var layout = Viva.Graph.Layout.forceDirected(graph, { + springLength: 30, + springCoeff: 0.0008, + dragCoeff: 0.01, + gravity: -1.2, + theta: 1 + }); + + var graphics = Viva.Graph.View.webglGraphics(); + var renderer = Viva.Graph.View.renderer(graph, { + // This prevents vivagraph from handling scrolling, but will let user interact + // with individual nodes: + interactive: 'nodes', + // if you want to completely disable interaction, set `interactive` to false. + // interactive: false, + layout: layout, + graphics: graphics, + container: createContainer() + }); + + renderer.run(); +} + +function createContainer() { + var container = document.createElement('div'); + container.className = 'graph-container'; + document.body.appendChild(container); + return container; +} + +// you can safely ignore this function for the purpose of this demo. It just +// generates a random graph an duses custom nodes/links count based on generator +// name. +function generateGraphByName(name) { + if (name === 'wattsStrogatz') { + // wattsStrogatz has different default arguments, let's respect it: + return generator.wattsStrogatz(20, 4, 0.02); + } else if (name === 'completeBipartite') { + // it's more stable this way: + return generator.completeBipartite(3, 3); + } + return generator[name](5, 5, 5); +} + diff --git a/demos/other/input-override/style.css b/demos/other/input-override/style.css new file mode 100644 index 0000000..21b1521 --- /dev/null +++ b/demos/other/input-override/style.css @@ -0,0 +1,25 @@ +body, +html { + width: 100%; + height: 100%; + margin: 0; + padding: 0; + background: black; +} +.graph-container { + width: 480px; + height: 640px; + margin: 0 auto; + border: 1px solid gray; +} +#message { + color: deepskyblue; + font-size: 24px; + position: absolute; + max-width: 320px; + display: block; + font-family: monospace; + color: #999; + top: 10px; + left: 10px; +} diff --git a/demos/other/mostly-fixed/README.md b/demos/other/mostly-fixed/README.md new file mode 100644 index 0000000..f2181f9 --- /dev/null +++ b/demos/other/mostly-fixed/README.md @@ -0,0 +1,9 @@ +# Mostly Fixed + +This is just a small demo which makes all nodes of the graph fixed (they are not +moved during layout), and then adds more nodes which can fly anywhere. + +[Demo](http://anvaka.github.io/VivaGraphJS/demos/other/mostly-fixed/) + +**Note:** If you already know all positions for nodes, and there is no dynamic +movement required, consider using [constant layout](https://github.com/anvaka/VivaGraphJS/blob/master/demos/other/constantLayout.html) diff --git a/demos/other/mostly-fixed/index.html b/demos/other/mostly-fixed/index.html new file mode 100644 index 0000000..5ed9039 --- /dev/null +++ b/demos/other/mostly-fixed/index.html @@ -0,0 +1,53 @@ + + + + + + VivaGraph fixing almost all nodes + + + + + + + + + diff --git a/demos/other/physicsLayout.html b/demos/other/physicsLayout.html index b3b9e3b..d75b12d 100644 --- a/demos/other/physicsLayout.html +++ b/demos/other/physicsLayout.html @@ -16,21 +16,21 @@ //var graph = graphGenerator.completeBipartite(10, 10); //var graph = graphGenerator.complete(100); //var graph = graphGenerator.balancedBinTree(10); - + var layout = Viva.Graph.Layout.forceDirected(graph, { springLength : 10, springCoeff : 0.0008, dragCoeff : 0.02, gravity : -1.2 }); - + var graphics = Viva.Graph.View.svgGraphics(); var renderer = Viva.Graph.View.renderer(graph, { layout : layout, graphics : graphics }); - + renderer.run(); } @@ -44,6 +44,6 @@ - + diff --git a/demos/other/pinNode.html b/demos/other/pinNode.html index b4a22b5..289c416 100644 --- a/demos/other/pinNode.html +++ b/demos/other/pinNode.html @@ -16,26 +16,22 @@ dragCoeff : 0.02, gravity : -1.2 }); - - var graphics = Viva.Graph.View.svgGraphics(); - // Node pinning is an attribute, which tells layout algorithm - // to not update node's position. Effectively the node looks - // like it is pinned to the canvas. + var graphics = Viva.Graph.View.svgGraphics(); - // One way to achieve pinning is to have "isPinned" attribute on - // node object: - // var node = graph.addNode('yourNodeId', {some: data}); - // node.isPinned = true; // now the node will not move. + // Node pinning is an attribute, which tells layout algorithm + // to not update node's position. Effectively the node looks + // like it is pinned to the canvas. - // You can also pin/unpin node any time you want: + // You can pin/unpin node any time you want: graphics.node(function (node) { var ui = Viva.Graph.svg("rect") .attr("width", 10) .attr("height", 10) .attr("fill", "#00a2e8"); ui.addEventListener('click', function () { - node.isPinned = !node.isPinned + // toggle pinned mode + layout.pinNode(node, !layout.isNodePinned(node)); }); return ui; }); @@ -45,7 +41,7 @@ graphics : graphics, container : document.getElementById('graphContainer') }); - + renderer.run(); } diff --git a/demos/other/precompute-advanced.html b/demos/other/precompute-advanced.html new file mode 100644 index 0000000..49892e7 --- /dev/null +++ b/demos/other/precompute-advanced.html @@ -0,0 +1,100 @@ + + + + + VivaGraphs WebGL Precomupte - Advanced + + + + + + +
+ + diff --git a/demos/other/precompute-naive.html b/demos/other/precompute-naive.html new file mode 100644 index 0000000..fc4a6ec --- /dev/null +++ b/demos/other/precompute-naive.html @@ -0,0 +1,62 @@ + + + + + VivaGraphs WebGL Precomupte - Naive + + + + + + + + + diff --git a/demos/other/remove-graph/README.md b/demos/other/remove-graph/README.md new file mode 100644 index 0000000..c16a02f --- /dev/null +++ b/demos/other/remove-graph/README.md @@ -0,0 +1,13 @@ +# Remove graph + +This demo shows how to completely remove graph from the DOM. + +# How it is done? + +``` js +// renderer is created as Viva.Graph.View.renderer() +// if you no longer need it just call: +renderer.dispose(); +``` + +[See online demo](http://anvaka.github.io/VivaGraphJS/demos/other/remove-graph/) diff --git a/demos/other/remove-graph/index.html b/demos/other/remove-graph/index.html new file mode 100644 index 0000000..ac66259 --- /dev/null +++ b/demos/other/remove-graph/index.html @@ -0,0 +1,38 @@ + + + + + + VivaGraph remove graph + + + + + + + + +
+ Remove graph + Create a new graph +
+ + + diff --git a/demos/other/remove-graph/index.js b/demos/other/remove-graph/index.js new file mode 100644 index 0000000..c686ca2 --- /dev/null +++ b/demos/other/remove-graph/index.js @@ -0,0 +1,32 @@ +/** + * This demo shows how to dispose renderer and create a new one. + */ +function removeGraph() { + if (!window.renderer) { + return; // already removed + } + window.renderer.dispose(); // remove the graph + window.renderer = null; +} + +function createNewGraph() { + removeGraph(); + // just a random size for a grid graph [1, 9): + var n = Math.random() * 10|0 + 1; + var m = Math.random() * 10|0 + 1; + var graph = Viva.Graph.generator().grid(n, m); + + window.renderer = Viva.Graph.View.renderer(graph, { + layout : createPhysicsLayout(graph) + }); + window.renderer.run(); +} + +function createPhysicsLayout(graph) { + return Viva.Graph.Layout.forceDirected(graph, { + springLength : 10, + springCoeff : 0.0005, + dragCoeff : 0.02, + gravity : -1.2 + }); +} diff --git a/demos/other/resetScale.html b/demos/other/resetScale.html index 54af7f1..b6a7908 100644 --- a/demos/other/resetScale.html +++ b/demos/other/resetScale.html @@ -16,7 +16,7 @@ dragCoeff : 0.02, gravity : -1.2 }); - + var graphics = Viva.Graph.View.svgGraphics(); var renderer = Viva.Graph.View.renderer(graph, { @@ -24,7 +24,7 @@ graphics : graphics, container : document.getElementById('graphContainer') }); - + renderer.run(); document.body.addEventListener('keydown', function (e) { if (e.which === 32) { // spacebar diff --git a/demos/other/svg-circle-images/README.md b/demos/other/svg-circle-images/README.md new file mode 100644 index 0000000..dea07c3 --- /dev/null +++ b/demos/other/svg-circle-images/README.md @@ -0,0 +1,23 @@ +# Render nodes as circular images + +This demo shows how to implement circular images with SVG renderer. [Click here](http://anvaka.github.io/VivaGraphJS/demos/other/svg-circle-images/) to see it in action. + +# How it is done? + +We are using [SVG Patterns](http://www.w3.org/TR/SVG/pservers.html#Patterns) to +fill a circle with Image brush. The `graphics.node()` function reconstructs +structure similar to the following: + +``` html + + + + + + + + +``` + +See [source code](https://github.com/anvaka/VivaGraphJS/blob/9075454b4924c6a441d6d456a32ccf5c70f13f19/demos/other/svg-circle-images/index.js#L23) for exact steps. diff --git a/demos/other/svg-circle-images/index.html b/demos/other/svg-circle-images/index.html new file mode 100644 index 0000000..eb01487 --- /dev/null +++ b/demos/other/svg-circle-images/index.html @@ -0,0 +1,28 @@ + + + + + + VivaGraphs SVG circle images + + + + + + + + +
+ + + diff --git a/demos/other/svg-circle-images/index.js b/demos/other/svg-circle-images/index.js new file mode 100644 index 0000000..cd8b525 --- /dev/null +++ b/demos/other/svg-circle-images/index.js @@ -0,0 +1,92 @@ +/** + * This demo shows one possible way of implementing nodes as circular images + */ +function onLoad() { + var graphics = Viva.Graph.View.svgGraphics(); + + // we will use SVG patterns to fill circle with image brush: + // http://stackoverflow.com/questions/11496734/add-a-background-image-png-to-a-svg-circle-shape + var defs = Viva.Graph.svg('defs'); + graphics.getSvgRoot().append(defs); + + graphics.node(createNodeWithImage) + .placeNode(placeNodeWithTransform); + + var graph = constructGraph(); + var renderer = Viva.Graph.View.renderer(graph, { + graphics: graphics, + container: document.getElementById('graph-container') + }); + + renderer.run(); + + function createNodeWithImage(node) { + var radius = 12; + // First, we create a fill pattern and add it to SVG's defs: + var pattern = Viva.Graph.svg('pattern') + .attr('id', "imageFor_" + node.id) + .attr('patternUnits', "userSpaceOnUse") + .attr('width', 100) + .attr('height', 100) + var image = Viva.Graph.svg('image') + .attr('x', '0') + .attr('y', '0') + .attr('height', radius * 2) + .attr('width', radius * 2) + .link(node.data.url); + pattern.append(image); + defs.append(pattern); + + // now create actual node and reference created fill pattern: + var ui = Viva.Graph.svg('g'); + var circle = Viva.Graph.svg('circle') + .attr('cx', radius) + .attr('cy', radius) + .attr('fill', 'url(#imageFor_' + node.id + ')') + .attr('r', radius); + + ui.append(circle); + return ui; + } + + function placeNodeWithTransform(nodeUI, pos) { + // Shift image to let links go to the center: + nodeUI.attr('transform', 'translate(' + (pos.x - 12) + ',' + (pos.y - 12) + ')'); + } +} + + +function constructGraph() { + var graph = Viva.Graph.graph(); + + graph.addNode('anvaka', { + url: 'https://secure.gravatar.com/avatar/91bad8ceeec43ae303790f8fe238164b' + }); + graph.addNode('manunt', { + url: 'https://secure.gravatar.com/avatar/c81bfc2cf23958504617dd4fada3afa8' + }); + graph.addNode('thlorenz', { + url: 'https://secure.gravatar.com/avatar/1c9054d6242bffd5fd25ec652a2b79cc' + }); + graph.addNode('bling', { + url: 'https://secure.gravatar.com/avatar/24a5b6e62e9a486743a71e0a0a4f71af' + }); + graph.addNode('diyan', { + url: 'https://secure.gravatar.com/avatar/01bce7702975191fdc402565bd1045a8?' + }); + graph.addNode('pocheptsov', { + url: 'https://secure.gravatar.com/avatar/13da974fc9716b42f5d62e3c8056c718' + }); + graph.addNode('dimapasko', { + url: 'https://secure.gravatar.com/avatar/8e587a4232502a9f1ca14e2810e3c3dd' + }); + + graph.addLink('anvaka', 'manunt'); + graph.addLink('anvaka', 'thlorenz'); + graph.addLink('anvaka', 'bling'); + graph.addLink('anvaka', 'diyan'); + graph.addLink('anvaka', 'pocheptsov'); + graph.addLink('anvaka', 'dimapasko'); + + return graph; +} diff --git a/demos/other/webGLRenderer.html b/demos/other/webGLRenderer.html index 530306a..d20edbc 100644 --- a/demos/other/webGLRenderer.html +++ b/demos/other/webGLRenderer.html @@ -13,22 +13,22 @@ //var graph = graphGenerator.randomNoLinks(500); //var graph = graphGenerator.completeBipartite(100, 1); //var graph = graphGenerator.complete(2); - var graph = graphGenerator.grid(50, 50); + var graph = graphGenerator.grid(100, 100); //var graph = graphGenerator.balancedBinTree(10); //var graph = graphGenerator.ladder(1000); //var graph = Viva.Graph.graph(); //graph.addLink(1, 2); - + var layout = Viva.Graph.Layout.forceDirected(graph, { springLength : 30, springCoeff : 0.0008, - dragCoeff : 0.00, + dragCoeff : 0.01, gravity : -1.2, theta : 1 }); - + var graphics = Viva.Graph.View.webglGraphics(); - + var renderer = Viva.Graph.View.renderer(graph, { layout : layout, @@ -36,7 +36,7 @@ renderLinks : true, prerender : true }); - + renderer.run(); } @@ -45,14 +45,15 @@ height: 100%; width: 100%; position: absolute; + overflow: hidden; } - canvas { + canvas { width: 100%; height: 100%; } - + diff --git a/demos/other/webgl-area-select/README.md b/demos/other/webgl-area-select/README.md new file mode 100644 index 0000000..a5fd3c6 --- /dev/null +++ b/demos/other/webgl-area-select/README.md @@ -0,0 +1,24 @@ +# Select nodes inside area + +This demo shows how to implement rectangular selection tool with webgl renderer. [Click here](http://anvaka.github.io/VivaGraphJS/demos/other/webgl-area-select/) to see it in action. + +# How it is done? + +First of all we create a basic div overlay on top of graph container: + +``` html +
+
+``` + +Overlay element should have exactly the same size/position as grpah container. +In this demo we are using absolute position for `.graph-overlay` and explicitly +positioned common parent (`body`). See more details here: +[How to overlay one div over another div](http://stackoverflow.com/questions/2941189/how-to-overlay-one-div-over-another-div). + +After this is done we are using vivagraph methods to track drag'n'drop actions, +and convert [client coordinates into graph coordinates](https://github.com/anvaka/VivaGraphJS/blob/8342dfb9d41fb619ec2e3a505beb508ce7743873/demos/other/webgl-area-select/index.js#L32-L40). + +Finally we are doing linear iteration over all nodes within graph and ask layout +to provide their coordinates. If those coordinates are [within bounding rectangle](https://github.com/anvaka/VivaGraphJS/blob/8342dfb9d41fb619ec2e3a505beb508ce7743873/demos/other/webgl-area-select/index.js#L58-L62), +we higlight the node. diff --git a/demos/other/webgl-area-select/index.html b/demos/other/webgl-area-select/index.html new file mode 100644 index 0000000..abeb3e4 --- /dev/null +++ b/demos/other/webgl-area-select/index.html @@ -0,0 +1,43 @@ + + + + + + VivaGraphs WebGL Mouse Input test page + + + + + + + + +
+
+ + Use Shift + Mouse to draw rectangle and select nodes + + + diff --git a/demos/other/webgl-area-select/index.js b/demos/other/webgl-area-select/index.js new file mode 100644 index 0000000..d5fe1d6 --- /dev/null +++ b/demos/other/webgl-area-select/index.js @@ -0,0 +1,150 @@ +/** + * this demo shows one possible way of implementing "area" selection with webgl + * renderer + */ +function onLoad() { + var graphGenerator = Viva.Graph.generator(); + var graph = graphGenerator.grid(50, 10); + + var layout = Viva.Graph.Layout.forceDirected(graph); + + var graphics = Viva.Graph.View.webglGraphics(); + + var renderer = Viva.Graph.View.renderer(graph, { + layout: layout, + graphics: graphics, + container: document.getElementById('graph-container') + }); + var multiSelectOverlay; + + renderer.run(); + + document.addEventListener('keydown', function(e) { + if (e.which === 16 && !multiSelectOverlay) { // shift key + multiSelectOverlay = startMultiSelect(graph, renderer, layout); + } + }); + document.addEventListener('keyup', function(e) { + if (e.which === 16 && multiSelectOverlay) { + multiSelectOverlay.destroy(); + multiSelectOverlay = null; + } + }); +} + +function startMultiSelect(graph, renderer, layout) { + var graphics = renderer.getGraphics(); + var domOverlay = document.querySelector('.graph-overlay'); + var overlay = createOverlay(domOverlay); + overlay.onAreaSelected(handleAreaSelected); + + return overlay; + + function handleAreaSelected(area) { + // For the sake of this demo we are using silly O(n) implementation. + // Could be improved with spatial indexing if required. + var topLeft = graphics.transformClientToGraphCoordinates({ + x: area.x, + y: area.y + }); + + var bottomRight = graphics.transformClientToGraphCoordinates({ + x: area.x + area.width, + y: area.y + area.height + }); + + graph.forEachNode(higlightIfInside); + renderer.rerender(); + + return; + + function higlightIfInside(node) { + var nodeUI = graphics.getNodeUI(node.id); + if (isInside(node.id, topLeft, bottomRight)) { + nodeUI.color = 0xFFA500ff; + nodeUI.size = 20; + } else { + nodeUI.color = 0x009ee8ff; + nodeUI.size = 10; + } + } + + function isInside(nodeId, topLeft, bottomRight) { + var nodePos = layout.getNodePosition(nodeId); + return (topLeft.x < nodePos.x && nodePos.x < bottomRight.x && + topLeft.y < nodePos.y && nodePos.y < bottomRight.y); + } + } +} + +function createOverlay(overlayDom) { + var selectionClasName = 'graph-selection-indicator'; + var selectionIndicator = overlayDom.querySelector('.' + selectionClasName); + if (!selectionIndicator) { + selectionIndicator = document.createElement('div'); + selectionIndicator.className = selectionClasName; + overlayDom.appendChild(selectionIndicator); + } + + var notify = []; + var dragndrop = Viva.Graph.Utils.dragndrop(overlayDom); + var selectedArea = { + x: 0, + y: 0, + width: 0, + height: 0 + }; + var startX = 0; + var startY = 0; + + dragndrop.onStart(function(e) { + startX = selectedArea.x = e.clientX; + startY = selectedArea.y = e.clientY; + selectedArea.width = selectedArea.height = 0; + + updateSelectedAreaIndicator(); + selectionIndicator.style.display = 'block'; + }); + + dragndrop.onDrag(function(e) { + recalculateSelectedArea(e); + updateSelectedAreaIndicator(); + notifyAreaSelected(); + }); + + dragndrop.onStop(function() { + selectionIndicator.style.display = 'none'; + }); + + overlayDom.style.display = 'block'; + + return { + onAreaSelected: function(cb) { + notify.push(cb); + }, + destroy: function () { + overlayDom.style.display = 'none'; + dragndrop.release(); + } + }; + + function notifyAreaSelected() { + notify.forEach(function(cb) { + cb(selectedArea); + }); + } + + function recalculateSelectedArea(e) { + selectedArea.width = Math.abs(e.clientX - startX); + selectedArea.height = Math.abs(e.clientY - startY); + selectedArea.x = Math.min(e.clientX, startX); + selectedArea.y = Math.min(e.clientY, startY); + } + + function updateSelectedAreaIndicator() { + selectionIndicator.style.left = selectedArea.x + 'px'; + selectionIndicator.style.top = selectedArea.y + 'px'; + selectionIndicator.style.width = selectedArea.width + 'px'; + selectionIndicator.style.height = selectedArea.height + 'px'; + } +} diff --git a/demos/other/webgl-clear-color/README.md b/demos/other/webgl-clear-color/README.md new file mode 100644 index 0000000..3337ed2 --- /dev/null +++ b/demos/other/webgl-clear-color/README.md @@ -0,0 +1,14 @@ +# Clear color + +By default webgl renderer will not clear the scene. This may result in artifacts +![dirty](https://raw.githubusercontent.com/anvaka/VivaGraphJS/master/demos/other/webgl-clear-color/dirty.png) + +Instead of expected + +![clear](https://raw.githubusercontent.com/anvaka/VivaGraphJS/master/demos/other/webgl-clear-color/clear.png) + +This demo shows how to set clear color in webgl renderer. [Click here](http://anvaka.github.io/VivaGraphJS/demos/other/webgl-clear-color/) to see it in action. + +# How it is done? + +See the source code if [index.html](index.html) diff --git a/demos/other/webgl-clear-color/clear.png b/demos/other/webgl-clear-color/clear.png new file mode 100644 index 0000000..cba92ed Binary files /dev/null and b/demos/other/webgl-clear-color/clear.png differ diff --git a/demos/other/webgl-clear-color/dirty.png b/demos/other/webgl-clear-color/dirty.png new file mode 100644 index 0000000..0c68df1 Binary files /dev/null and b/demos/other/webgl-clear-color/dirty.png differ diff --git a/demos/other/webgl-clear-color/index.html b/demos/other/webgl-clear-color/index.html new file mode 100644 index 0000000..762aa51 --- /dev/null +++ b/demos/other/webgl-clear-color/index.html @@ -0,0 +1,52 @@ + + + + + + VivaGraphs WebGL renderer clear color + + + + + + + + + diff --git a/demos/other/webglCustomNode.html b/demos/other/webglCustomNode.html index 3685e4f..4ea6ca9 100644 --- a/demos/other/webglCustomNode.html +++ b/demos/other/webglCustomNode.html @@ -7,7 +7,7 @@ + + + + + + This is webgl renderer. Labels are rendered as DOM nodes + + diff --git a/demos/other/webglDynamic.html b/demos/other/webglDynamic.html index cf6650d..42fd96d 100644 --- a/demos/other/webglDynamic.html +++ b/demos/other/webglDynamic.html @@ -18,24 +18,24 @@ 0xbcbd22ff, 0xdbdb8dff, 0x17becfff, 0x9edae5ff ]; - + function beginRemoveNodesLoop(graph){ var nodesLeft = []; graph.forEachNode(function(node){ nodesLeft.push(node.id); }); - + var removeInterval = setInterval(function(){ var nodesCount = nodesLeft.length; - + if (nodesCount > 0){ var nodeToRemove = Math.min((Math.random() * nodesCount) << 0, nodesCount - 1); - + graph.removeNode(nodesLeft[nodeToRemove]); nodesLeft.splice(nodeToRemove, 1); } - - if (nodesCount === 0) { + + if (nodesCount === 0) { clearInterval(removeInterval); setTimeout(function(){ beginAddNodesLoop(graph); @@ -43,12 +43,12 @@ } }, 100); } - + function beginAddNodesLoop(graph){ var i = 0, m = 10, n = 50; var addInterval = setInterval(function(){ graph.beginUpdate(); - + for (var j = 0; j < m; ++j){ var node = i + j * n; if (i > 0) { graph.addLink(node, i - 1 + j * n); } @@ -56,16 +56,16 @@ } i++; graph.endUpdate(); - - if (i >= n) { + + if (i >= n) { clearInterval(addInterval); setTimeout(function() { beginRemoveNodesLoop(graph); - }, 10000); + }, 10000); } }, 100); } - + function onLoad() { var graph = Viva.Graph.graph(); @@ -75,7 +75,7 @@ dragCoeff : 0.02, gravity : -1.2 }); - + var graphics = Viva.Graph.View.webglGraphics(); graphics .node(function(node){ @@ -84,7 +84,7 @@ .link(function(link) { return Viva.Graph.View.webglLine(colors[(Math.random() * colors.length) << 0]); }); - + var renderer = Viva.Graph.View.renderer(graph, { layout : layout, @@ -92,7 +92,7 @@ container : document.getElementById('graph1'), renderLinks : true }); - + renderer.run(50); //graph.addLink(1, 2) beginAddNodesLoop(graph); @@ -110,7 +110,7 @@ background-color: #dddddd; position: absolute; } - + #graph1{ position: absolute; width: 100%; diff --git a/demos/other/webglInput.html b/demos/other/webglInput.html index 1f4d149..7655d74 100644 --- a/demos/other/webglInput.html +++ b/demos/other/webglInput.html @@ -10,16 +10,16 @@ function onLoad() { var graphGenerator = Viva.Graph.generator(); var graph = graphGenerator.path(4); - + var layout = Viva.Graph.Layout.forceDirected(graph, { springLength : 100, springCoeff : 0.0008, dragCoeff : 0.02, gravity : -1.2 }); - + var graphics = Viva.Graph.View.webglGraphics(); - + var renderer = Viva.Graph.View.renderer(graph, { layout : layout, graphics : graphics @@ -37,9 +37,9 @@ }).dblClick(function (node) { console.log('Double click on node: ' + node.id); }).click(function (node) { - console.log('Sincle click on node: ' + node.id); + console.log('Single click on node: ' + node.id); }); - + renderer.run(); } diff --git a/demos/other/zoomPanAPI.html b/demos/other/zoomPanAPI.html new file mode 100644 index 0000000..695f85f --- /dev/null +++ b/demos/other/zoomPanAPI.html @@ -0,0 +1,84 @@ + + + + VivaGraphs test page + + + + + + +
+ Zoom In | Zoom Out
+ Reset +
+ Center on node (0-99): +
+
+ + + diff --git a/demos/tutorial_svg/01 - Create Graph.html b/demos/tutorial_svg/01 - Create Graph.html index f2e5944..c5a87ab 100644 --- a/demos/tutorial_svg/01 - Create Graph.html +++ b/demos/tutorial_svg/01 - Create Graph.html @@ -11,7 +11,7 @@ // Step 2. We add nodes and edges to the graph: graph.addLink(1, 2); - /* Note: graph.addLink() creates new nodes if they are not yet + /* Note: graph.addLink() creates new nodes if they are not yet present in the graph. Thus calling this method is equivalent to: graph.addNode(1); @@ -24,7 +24,7 @@ renderer.run(); } - + @@ -32,4 +32,4 @@ - \ No newline at end of file + diff --git a/demos/tutorial_svg/02 - Create Custom Node.html b/demos/tutorial_svg/02 - Create Custom Node.html index f136628..49dae2e 100644 --- a/demos/tutorial_svg/02 - Create Custom Node.html +++ b/demos/tutorial_svg/02 - Create Custom Node.html @@ -8,9 +8,9 @@ // Step 1. Create a graph: var graph = Viva.Graph.graph(); - // Step 2. Add graph content. - // graph.addNode(nodeId, yourCustomData) method lets you add new - // nodes to the graph and associate them with custom data. In this + // Step 2. Add graph content. + // graph.addNode(nodeId, yourCustomData) method lets you add new + // nodes to the graph and associate them with custom data. In this // case we are associating GitHub user profiles with their Gravatar's images: graph.addNode('anvaka', '91bad8ceeec43ae303790f8fe238164b'); graph.addNode('indexzero', 'd43e8ea63b61e7669ded5b9d3c2e980f'); @@ -26,19 +26,19 @@ // something better than blue dots: graphics.node(function(node) { // node.data holds custom object passed to graph.addNode(): - var url = 'https://secure.gravatar.com/avatar/' + node.data; - + var url = 'https://secure.gravatar.com/avatar/' + node.data; + return Viva.Graph.svg('image') .attr('width', 24) .attr('height', 24) - .link(url); + .link(url); }); - // Usually when you have custom look for nodes, you might want to + // Usually when you have custom look for nodes, you might want to // set their position in a new way too. placeNode() method serves // this goal: graphics.placeNode(function(nodeUI, pos) { - // nodeUI - is exactly the same object that we returned from + // nodeUI - is exactly the same object that we returned from // node() callback above. // pos - is calculated position for this node. nodeUI.attr('x', pos.x - 12).attr('y', pos.y - 12); @@ -51,7 +51,7 @@ renderer.run(); } - + @@ -59,4 +59,4 @@ - \ No newline at end of file + diff --git a/demos/tutorial_svg/03 - Create Custom Link.html b/demos/tutorial_svg/03 - Create Custom Link.html index a9dbcea..39cd630 100644 --- a/demos/tutorial_svg/03 - Create Custom Link.html +++ b/demos/tutorial_svg/03 - Create Custom Link.html @@ -8,7 +8,7 @@ // Step 1. Create a graph: var graph = Viva.Graph.graph(); - // Step 2. Add graph content. + // Step 2. Add graph content. graph.addNode('anvaka', '91bad8ceeec43ae303790f8fe238164b'); graph.addNode('indexzero', 'd43e8ea63b61e7669ded5b9d3c2e980f'); @@ -16,20 +16,20 @@ // Step 3. Customize node appearance. var graphics = Viva.Graph.View.svgGraphics(); - - // Nothing changed in these lines. They are the same as in Step 2 + + // Nothing changed in these lines. They are the same as in Step 2 // of this tutorial. Except maybe chaining support: graphics.node(function(node) { return Viva.Graph.svg('image') .attr('width', 24) .attr('height', 24) - .link('https://secure.gravatar.com/avatar/' + node.data); + .link('https://secure.gravatar.com/avatar/' + node.data); }).placeNode(function(nodeUI, pos) { nodeUI.attr('x', pos.x - 12).attr('y', pos.y - 12); - }); + }); // Step 4. Customize link appearance: - // As you might have guessed already the link()/placeLink() + // As you might have guessed already the link()/placeLink() // functions complement the node()/placeNode() functions // and let us override default presentation of edges: graphics.link(function(link){ @@ -38,10 +38,10 @@ .attr('stroke-dasharray', '5, 5'); }).placeLink(function(linkUI, fromPos, toPos) { // linkUI - is the object returend from link() callback above. - var data = 'M' + fromPos.x + ',' + fromPos.y + + var data = 'M' + fromPos.x + ',' + fromPos.y + 'L' + toPos.x + ',' + toPos.y; - // 'Path data' (http://www.w3.org/TR/SVG/paths.html#DAttribute ) + // 'Path data' (http://www.w3.org/TR/SVG/paths.html#DAttribute ) // is a common way of rendering paths in SVG: linkUI.attr("d", data); }); @@ -53,7 +53,7 @@ renderer.run(); } - + @@ -61,4 +61,4 @@ - \ No newline at end of file + diff --git a/demos/tutorial_svg/04 - Listen To Mouse Events.html b/demos/tutorial_svg/04 - Listen To Mouse Events.html index b951dbf..723dd3c 100644 --- a/demos/tutorial_svg/04 - Listen To Mouse Events.html +++ b/demos/tutorial_svg/04 - Listen To Mouse Events.html @@ -4,7 +4,7 @@ 04. Listen to mouse events. Vivagraph SVG tutorial. - @@ -25,23 +25,23 @@ highlightRelatedNodes = function(nodeId, isOn) { // just enumerate all realted nodes and update link color: graph.forEachLinkedNode(nodeId, function(node, link){ - if (link && link.ui) { - // link.ui is a special property of each link - // points to the link presentation object. - link.ui.attr('stroke', isOn ? 'red' : 'gray'); + var linkUI = graphics.getLinkUI(link.id); + if (linkUI) { + // linkUI is a UI object created by graphics below + linkUI.attr('stroke', isOn ? 'red' : 'gray'); } }); }; // Since we are using SVG we can easily subscribe to any supported - // events (http://www.w3.org/TR/SVG/interact.html#SVGEvents ), + // events (http://www.w3.org/TR/SVG/interact.html#SVGEvents ), // including mouse events: graphics.node(function(node) { var ui = Viva.Graph.svg('image') .attr('width', nodeSize) .attr('height', nodeSize) - .link('https://secure.gravatar.com/avatar/' + node.data); - + .link('https://secure.gravatar.com/avatar/' + node.data); + $(ui).hover(function() { // mouse over highlightRelatedNodes(node.id, true); }, function() { // mouse out @@ -50,13 +50,13 @@ return ui; }).placeNode(function(nodeUI, pos) { nodeUI.attr('x', pos.x - nodeSize / 2).attr('y', pos.y - nodeSize / 2); - }); + }); graphics.link(function(link){ return Viva.Graph.svg('path') .attr('stroke', 'gray'); }).placeLink(function(linkUI, fromPos, toPos) { - var data = 'M' + fromPos.x + ',' + fromPos.y + + var data = 'M' + fromPos.x + ',' + fromPos.y + 'L' + toPos.x + ',' + toPos.y; linkUI.attr("d", data); @@ -69,7 +69,7 @@ renderer.run(); } - + @@ -77,4 +77,4 @@ - \ No newline at end of file + diff --git a/demos/tutorial_svg/05 - Edges With Arrows.html b/demos/tutorial_svg/05 - Edges With Arrows.html index 4e40236..d9cc622 100644 --- a/demos/tutorial_svg/05 - Edges With Arrows.html +++ b/demos/tutorial_svg/05 - Edges With Arrows.html @@ -8,35 +8,29 @@ // This demo shows how to create a directional arrow in SVG renderer. // Though it might seem wordy it's due to SVG specific operations. // The library has minimal SVG manipulation support. - // Maybe in future some of the following technniques will become part + // Maybe in future some of the following technniques will become part // of the library itself... var graph = Viva.Graph.graph(); var graphics = Viva.Graph.View.svgGraphics(), nodeSize = 24; - // In this example we fire off renderer before anything is added to - // the graph: - var renderer = Viva.Graph.View.renderer(graph, { - graphics : graphics - }); - renderer.run(); - + graphics.node(function(node) { return Viva.Graph.svg('image') .attr('width', nodeSize) .attr('height', nodeSize) - .link('https://secure.gravatar.com/avatar/' + node.data); + .link('https://secure.gravatar.com/avatar/' + node.data); }).placeNode(function(nodeUI, pos) { nodeUI.attr('x', pos.x - nodeSize / 2).attr('y', pos.y - nodeSize / 2); - }); + }); + - // To render an arrow we have to address two problems: - // 1. Links should start/stop at node's bounding box, not at the node center. + // 1. Links should start/stop at node's bounding box, not at the node center. // 2. Render an arrow shape at the end of the link. - // Rendering arrow shape is achieved by using SVG markers, part of the SVG + // Rendering arrow shape is achieved by using SVG markers, part of the SVG // standard: http://www.w3.org/TR/SVG/painting.html#Markers var createMarker = function(id) { return Viva.Graph.svg('marker') @@ -49,30 +43,30 @@ .attr('markerHeight', "5") .attr('orient', "auto"); }, - + marker = createMarker('Triangle'); marker.append('path').attr('d', 'M 0 0 L 10 5 L 0 10 z'); - + // Marker should be defined only once in child element of root element: var defs = graphics.getSvgRoot().append('defs'); defs.append(marker); - - var geom = Viva.Graph.geom(); - + + var geom = Viva.Graph.geom(); + graphics.link(function(link){ // Notice the Triangle marker-end attribe: return Viva.Graph.svg('path') .attr('stroke', 'gray') .attr('marker-end', 'url(#Triangle)'); }).placeLink(function(linkUI, fromPos, toPos) { - // Here we should take care about + // Here we should take care about // "Links should start/stop at node's bounding box, not at the node center." - + // For rectangular nodes Viva.Graph.geom() provides efficient way to find // an intersection point between segment and rectangle var toNodeSize = nodeSize, fromNodeSize = nodeSize; - + var from = geom.intersectRect( // rectangle: fromPos.x - fromNodeSize / 2, // left @@ -80,9 +74,9 @@ fromPos.x + fromNodeSize / 2, // right fromPos.y + fromNodeSize / 2, // bottom // segment: - fromPos.x, fromPos.y, toPos.x, toPos.y) + fromPos.x, fromPos.y, toPos.x, toPos.y) || fromPos; // if no intersection found - return center of the node - + var to = geom.intersectRect( // rectangle: toPos.x - toNodeSize / 2, // left @@ -90,12 +84,12 @@ toPos.x + toNodeSize / 2, // right toPos.y + toNodeSize / 2, // bottom // segment: - toPos.x, toPos.y, fromPos.x, fromPos.y) + toPos.x, toPos.y, fromPos.x, fromPos.y) || toPos; // if no intersection found - return center of the node - + var data = 'M' + from.x + ',' + from.y + 'L' + to.x + ',' + to.y; - + linkUI.attr("d", data); }); @@ -103,15 +97,15 @@ graph.addNode('anvaka', '91bad8ceeec43ae303790f8fe238164b'); graph.addNode('indexzero', 'd43e8ea63b61e7669ded5b9d3c2e980f'); graph.addLink('anvaka', 'indexzero'); - - // You might have noticed that the order in which - // we call renderer.run() and graph.addNode()/addLink() differs from - // previous tutorials. The reason for this is that graphics.getSVGRoot() returns - // null if it was not initialized by the renderer yet. Maybe this behavior will - // be fixed in future. + + // All is ready. Render the graph: + var renderer = Viva.Graph.View.renderer(graph, { + graphics : graphics + }); + renderer.run(); } - + @@ -119,4 +113,4 @@ - \ No newline at end of file + diff --git a/demos/tutorial_svg/06 - Composite Nodes.html b/demos/tutorial_svg/06 - Composite Nodes.html index b2a0714..4aaf33c 100644 --- a/demos/tutorial_svg/06 - Composite Nodes.html +++ b/demos/tutorial_svg/06 - Composite Nodes.html @@ -6,7 +6,7 @@ - + @@ -54,4 +54,4 @@ - \ No newline at end of file + diff --git a/demos/tutorial_svg/07 - Show Dual Links.html b/demos/tutorial_svg/07 - Show Dual Links.html index a895bef..df3d23b 100644 --- a/demos/tutorial_svg/07 - Show Dual Links.html +++ b/demos/tutorial_svg/07 - Show Dual Links.html @@ -18,7 +18,7 @@ ui = Viva.Graph.svg('path') .attr('stroke', isBuy ? 'red' : 'blue') .attr('fill', 'none'); - + ui.isBuy = isBuy; // remember for future. return ui; @@ -26,10 +26,10 @@ // linkUI - is the object returend from link() callback above. var ry = linkUI.isBuy ? 10 : 0, // using arc command: http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands - data = 'M' + fromPos.x + ',' + fromPos.y + + data = 'M' + fromPos.x + ',' + fromPos.y + ' A 10,' + ry + ',-30,0,1,' + toPos.x + ',' + toPos.y; - // 'Path data' (http://www.w3.org/TR/SVG/paths.html#DAttribute ) + // 'Path data' (http://www.w3.org/TR/SVG/paths.html#DAttribute ) // is a common way of rendering paths in SVG: linkUI.attr("d", data); }); @@ -37,11 +37,11 @@ renderer.run(); } - - \ No newline at end of file + diff --git a/dist/vivagraph.js b/dist/vivagraph.js index 28e783d..727b7a2 100644 --- a/dist/vivagraph.js +++ b/dist/vivagraph.js @@ -1,5730 +1,5471 @@ +!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.Viva=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o>> 0; - h -= n; - h *= n; - n = h >>> 0; - h -= n; - n += h * 0x100000000; // 2^32 - } - return (n >>> 0) * 2.3283064365386963e-10; // 2^-32 - }; - - mash.version = 'Mash 0.9'; - return mash; - } - - function LFIB4(args) { - return (function (args) { - // George Marsaglia's LFIB4, - //http://groups.google.com/group/sci.crypt/msg/eb4ddde782b17051 - var k0 = 0, - k1 = 58, - k2 = 119, - k3 = 178, - j, - i, - s = [], - mash = getMash(); - - if (args.length === 0) { - args = [+new Date()]; - } - - for (j = 0; j < 256; j++) { - s[j] = mash(' '); - s[j] -= mash(' ') * 4.76837158203125e-7; // 2^-21 - if (s[j] < 0) { - s[j] += 1; - } - } +var random = require('ngraph.random'); + +var Viva = { + lazyExtend: function() { + return require('ngraph.merge').apply(this, arguments); + }, + randomIterator: function() { + return random.randomIterator.apply(random, arguments); + }, + random: function() { + return random.random.apply(random, arguments); + }, + events: require('ngraph.events') +}; + +Viva.Graph = { + version: require('./version.js'), + graph: require('ngraph.graph'), + + serializer: function() { + return { + loadFromJSON: require('ngraph.fromjson'), + storeToJSON: require('ngraph.tojson') + }; + }, + + centrality: require('./Algorithms/centrality.js'), + operations: require('./Algorithms/operations.js'), + + geom: function() { + return { + intersect: require('gintersect'), + intersectRect: require('./Utils/intersectRect.js') + }; + }, + + webgl: require('./WebGL/webgl.js'), + webglInputEvents: require('./WebGL/webglInputEvents.js'), + + generator: function() { + return require('ngraph.generators'); + }, + + Input: { + domInputManager: require('./Input/domInputManager.js'), + webglInputManager: require('./Input/webglInputManager.js') + }, + + Utils: { + // TODO: move to Input + dragndrop: require('./Input/dragndrop.js'), + findElementPosition: require('./Utils/findElementPosition.js'), + timer: require('./Utils/timer.js'), + getDimension: require('./Utils/getDimensions.js'), + events: require('./Utils/backwardCompatibleEvents.js') + }, + + Layout: { + forceDirected: require('ngraph.forcelayout'), + constant: require('./Layout/constant.js') + }, + + View: { + // TODO: Move `webglXXX` out to webgl namespace + Texture: require('./WebGL/texture.js'), + // TODO: This should not be even exported + webglAtlas: require('./WebGL/webglAtlas.js'), + webglImageNodeProgram: require('./WebGL/webglImageNodeProgram.js'), + webglLinkProgram: require('./WebGL/webglLinkProgram.js'), + webglNodeProgram: require('./WebGL/webglNodeProgram.js'), + webglLine: require('./WebGL/webglLine.js'), + webglSquare: require('./WebGL/webglSquare.js'), + webglImage: require('./WebGL/webglImage.js'), + webglGraphics: require('./View/webglGraphics.js'), + // TODO: Deprecate this: + _webglUtil: { + parseColor: require('./WebGL/parseColor.js') + }, + + // TODO: move to svg namespace + svgGraphics: require('./View/svgGraphics.js'), + + renderer: require('./View/renderer.js'), + + // deprecated + cssGraphics: function() { + throw new Error('cssGraphics is deprecated. Please use older version of vivagraph (< 0.7) if you need it'); + }, + + svgNodeFactory: function() { + throw new Error('svgNodeFactory is deprecated. Please use older version of vivagraph (< 0.7) if you need it'); + }, + + community: function() { + throw new Error('community is deprecated. Please use vivagraph < 0.7 if you need it, or `https://github.com/anvaka/ngraph.slpa` module'); + } + }, + + Rect: require('./Utils/rect.js'), + + svg: require('simplesvg'), + + // TODO: should be camelCase + BrowserInfo: require('./Utils/browserInfo.js') +}; + +module.exports = Viva; - for (i = 0; i < args.length; i++) { - for (j = 0; j < 256; j++) { - s[j] -= mash(args[i]); - s[j] -= mash(args[i]) * 4.76837158203125e-7; // 2^-21 - if (s[j] < 0) { - s[j] += 1; - } - } - } +},{"./Algorithms/centrality.js":36,"./Algorithms/operations.js":37,"./Input/domInputManager.js":38,"./Input/dragndrop.js":39,"./Input/webglInputManager.js":40,"./Layout/constant.js":41,"./Utils/backwardCompatibleEvents.js":42,"./Utils/browserInfo.js":43,"./Utils/findElementPosition.js":45,"./Utils/getDimensions.js":46,"./Utils/intersectRect.js":47,"./Utils/rect.js":49,"./Utils/timer.js":50,"./View/renderer.js":52,"./View/svgGraphics.js":53,"./View/webglGraphics.js":54,"./WebGL/parseColor.js":55,"./WebGL/texture.js":56,"./WebGL/webgl.js":57,"./WebGL/webglAtlas.js":58,"./WebGL/webglImage.js":59,"./WebGL/webglImageNodeProgram.js":60,"./WebGL/webglInputEvents.js":61,"./WebGL/webglLine.js":62,"./WebGL/webglLinkProgram.js":63,"./WebGL/webglNodeProgram.js":64,"./WebGL/webglSquare.js":65,"./version.js":66,"gintersect":3,"ngraph.events":9,"ngraph.forcelayout":11,"ngraph.fromjson":13,"ngraph.generators":14,"ngraph.graph":16,"ngraph.merge":17,"ngraph.random":30,"ngraph.tojson":31,"simplesvg":32}],2:[function(require,module,exports){ +addEventListener.removeEventListener = removeEventListener +addEventListener.addEventListener = addEventListener - mash = null; +module.exports = addEventListener - var random = function () { - var x; +var Events = null - k0 = (k0 + 1) & 255; - k1 = (k1 + 1) & 255; - k2 = (k2 + 1) & 255; - k3 = (k3 + 1) & 255; +function addEventListener(el, eventName, listener, useCapture) { + Events = Events || ( + document.addEventListener ? + {add: stdAttach, rm: stdDetach} : + {add: oldIEAttach, rm: oldIEDetach} + ) + + return Events.add(el, eventName, listener, useCapture) +} - x = s[k0] - s[k1]; - if (x < 0) { - x += 1; - } - x -= s[k2]; - if (x < 0) { - x += 1; - } - x -= s[k3]; - if (x < 0) { - x += 1; - } +function removeEventListener(el, eventName, listener, useCapture) { + Events = Events || ( + document.addEventListener ? + {add: stdAttach, rm: stdDetach} : + {add: oldIEAttach, rm: oldIEDetach} + ) + + return Events.rm(el, eventName, listener, useCapture) +} - s[k0] = x; - return x; - }; +function stdAttach(el, eventName, listener, useCapture) { + el.addEventListener(eventName, listener, useCapture) +} - random.uint32 = function () { - return random() * 0x100000000 >>> 0; // 2^32 - }; - random.fract53 = random; - random.version = 'LFIB4 0.9'; - random.args = args; +function stdDetach(el, eventName, listener, useCapture) { + el.removeEventListener(eventName, listener, useCapture) +} - return random; - }(args)); - } +function oldIEAttach(el, eventName, listener, useCapture) { + if(useCapture) { + throw new Error('cannot useCapture in oldIE') + } - var randomFunc = new LFIB4(Array.prototype.slice.call(arguments)); + el.attachEvent('on' + eventName, listener) +} - return { - /** - * Generates random integer number in the range from 0 (inclusive) to maxValue (exclusive) - * - * @param maxValue is REQUIRED. Ommitit this numbe will result in NaN values from PRNG. - */ - next : function (maxValue) { - return Math.floor(randomFunc() * maxValue); - }, +function oldIEDetach(el, eventName, listener, useCapture) { + el.detachEvent('on' + eventName, listener) +} - /** - * Generates random double number in the range from 0 (inclusive) to 1 (exclusive) - * This function is the same as Math.random() (except that it could be seeded) - */ - nextDouble : function () { - return randomFunc(); - } - }; -}; +},{}],3:[function(require,module,exports){ +module.exports = intersect; /** - * Iterates over array in arbitrary order. The iterator modifies actual array content. - * It's based on modern version of Fisher–Yates shuffle algorithm. + * Original authors: Mukesh Prasad, Appeared in Graphics Gem II book + * http://www.opensource.apple.com/source/graphviz/graphviz-498/graphviz/dynagraph/common/xlines.c + * and adopted to javascript version by Andrei Kashcha. + * + * This function computes whether two line segments, + * respectively joining the input points (x1,y1) -- (x2,y2) + * and the input points (x3,y3) -- (x4,y4) intersect. + * If the lines intersect, the output variables x, y are + * set to coordinates of the point of intersection. * - * @see http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm + * @param {Number} x1 First line segment coordinates + * @param {Number} y1 First line segment coordinates + * @param {Number} x2 First line segment coordinates + * @param {Number} x2 First line segment coordinates * - * @param array to be shuffled - * @param random - a [seeded] random number generator to produce same sequences. This parameter - * is optional. If you don't need determenistic randomness keep it blank. + * @param {Number} x3 Second line segment coordinates + * @param {Number} y3 Second line segment coordinates + * @param {Number} x4 Second line segment coordinates + * @param {Number} x4 Second line segment coordinates + * + * @return {Object} x, y coordinates of intersection point or falsy value if no + * intersection found.. */ -Viva.randomIterator = function (array, random) { - random = random || Viva.random(); - - return { - forEach : function (callback) { - var i, j, t; - for (i = array.length - 1; i > 0; --i) { - j = random.next(i + 1); // i inclusive - t = array[j]; - array[j] = array[i]; - array[i] = t; - - callback(t); - } - - if (array.length) { - callback(array[0]); - } - }, +function intersect( + x1, y1, x2, y2, // first line segment + x3, y3, x4, y4 // second line segment +) { + + var a1, a2, b1, b2, c1, c2, /* Coefficients of line eqns. */ + r1, r2, r3, r4, /* 'Sign' values */ + denom, offset, num, /* Intermediate values */ + result = { + x: 0, + y: 0 + }; - /** - * Shuffles array randomly. - */ - shuffle : function () { - var i, j, t; - for (i = array.length - 1; i > 0; --i) { - j = random.next(i + 1); // i inclusive - t = array[j]; - array[j] = array[i]; - array[i] = t; - } + /* Compute a1, b1, c1, where line joining points 1 and 2 + * is "a1 x + b1 y + c1 = 0". + */ + a1 = y2 - y1; + b1 = x1 - x2; + c1 = x2 * y1 - x1 * y2; + + /* Compute r3 and r4. + */ + r3 = a1 * x3 + b1 * y3 + c1; + r4 = a1 * x4 + b1 * y4 + c1; + + /* Check signs of r3 and r4. If both point 3 and point 4 lie on + * same side of line 1, the line segments do not intersect. + */ + + if (r3 !== 0 && r4 !== 0 && ((r3 >= 0) === (r4 >= 4))) { + return null; //no intersection. + } + + /* Compute a2, b2, c2 */ + a2 = y4 - y3; + b2 = x3 - x4; + c2 = x4 * y3 - x3 * y4; + + /* Compute r1 and r2 */ + + r1 = a2 * x1 + b2 * y1 + c2; + r2 = a2 * x2 + b2 * y2 + c2; + + /* Check signs of r1 and r2. If both point 1 and point 2 lie + * on same side of second line segment, the line segments do + * not intersect. + */ + if (r1 !== 0 && r2 !== 0 && ((r1 >= 0) === (r2 >= 0))) { + return null; // no intersection; + } + /* Line segments intersect: compute intersection point. + */ + + denom = a1 * b2 - a2 * b1; + if (denom === 0) { + return null; // Actually collinear.. + } + + offset = denom < 0 ? -denom / 2 : denom / 2; + offset = 0.0; + + /* The denom/2 is to get rounding instead of truncating. It + * is added or subtracted to the numerator, depending upon the + * sign of the numerator. + */ + num = b1 * c2 - b2 * c1; + result.x = (num < 0 ? num - offset : num + offset) / denom; + + num = a2 * c1 - a1 * c2; + result.y = (num < 0 ? num - offset : num + offset) / denom; + + return result; +} + +},{}],4:[function(require,module,exports){ +module.exports.degree = require('./src/degree.js'); +module.exports.betweenness = require('./src/betweenness.js'); +module.exports.closeness = require('./src/closeness.js'); +module.exports.eccentricity = require('./src/eccentricity.js'); + +},{"./src/betweenness.js":5,"./src/closeness.js":6,"./src/degree.js":7,"./src/eccentricity.js":8}],5:[function(require,module,exports){ +module.exports = betweennes; - return array; - } - }; -}; -Viva.BrowserInfo = (function () { - if (typeof window === "undefined" || !window.hasOwnProperty("navigator")) { - return { - browser : "", - version : "0" - }; - } - - var ua = window.navigator.userAgent.toLowerCase(), - // Useragent RegExp - rwebkit = /(webkit)[ \/]([\w.]+)/, - ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/, - rmsie = /(msie) ([\w.]+)/, - rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/, - match = rwebkit.exec(ua) || - ropera.exec(ua) || - rmsie.exec(ua) || - (ua.indexOf("compatible") < 0 && rmozilla.exec(ua)) || - []; - - return { - browser: match[1] || "", - version: match[2] || "0" - }; -}()); /** - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com + * I'm using http://www.inf.uni-konstanz.de/algo/publications/b-vspbc-08.pdf + * as a reference for this implementation */ +function betweennes(graph, oriented) { + var Q = [], + S = []; // Queue and Stack + // list of predcessors on shorteest paths from source + var pred = Object.create(null); + // distance from source + var dist = Object.create(null); + // number of shortest paths from source to key + var sigma = Object.create(null); + // dependency of source on key + var delta = Object.create(null); + + var currentNode; + var centrality = Object.create(null); + + graph.forEachNode(setCentralityToZero); + graph.forEachNode(calculateCentrality); + + if (!oriented) { + // The centrality scores need to be divided by two if the graph is not oriented, + // since all shortest paths are considered twice + Object.keys(centrality).forEach(divideByTwo); + } + + return centrality; + + function divideByTwo(key) { + centrality[key] /= 2; + } + + function setCentralityToZero(node) { + centrality[node.id] = 0; + } + + function calculateCentrality(node) { + currentNode = node.id; + singleSourceShortestPath(currentNode); + accumulate(); + } + + function accumulate() { + graph.forEachNode(setDeltaToZero); + while (S.length) { + var w = S.pop(); + var coeff = (1 + delta[w])/sigma[w]; + var predcessors = pred[w]; + for (var idx = 0; idx < predcessors.length; ++idx) { + var v = predcessors[idx]; + delta[v] += sigma[v] * coeff; + } + if (w !== currentNode) { + centrality[w] += delta[w]; + } + } + } + + function setDeltaToZero(node) { + delta[node.id] = 0; + } + + function singleSourceShortestPath(source) { + graph.forEachNode(initNode); + dist[source] = 0; + sigma[source] = 1; + Q.push(source); + + while (Q.length) { + var v = Q.shift(); + S.push(v); + graph.forEachLinkedNode(v, toId, oriented); + } -Viva.Graph.Utils = Viva.Graph.Utils || {}; - -Viva.Graph.Utils.indexOfElementInArray = function (element, array) { - if (array.indexOf) { - return array.indexOf(element); + function toId(otherNode) { + // NOTE: This code will also consider multi-edges, which are often + // ignored by popular software (Gephi/NetworkX). Depending on your use + // case this may not be desired and deduping needs to be performed. To + // save memory I'm not deduping here... + processNode(otherNode.id); } - var len = array.length, - i; + function initNode(node) { + var nodeId = node.id; + pred[nodeId] = []; // empty list + dist[nodeId] = -1; + sigma[nodeId] = 0; + } - for (i = 0; i < len; i += 1) { - if (array.hasOwnProperty(i) && (array[i] === element)) { - return i; - } + function processNode(w) { + // path discovery + if (dist[w] === -1) { + // Node w is found for the first time + dist[w] = dist[v] + 1; + Q.push(w); + } + // path counting + if (dist[w] === dist[v] + 1) { + // edge (v, w) on a shortest path + sigma[w] += sigma[v]; + pred[w].push(v); + } } + } +} - return -1; -}; -Viva.Graph.Utils = Viva.Graph.Utils || {}; +},{}],6:[function(require,module,exports){ +module.exports = closeness; -Viva.Graph.Utils.getDimension = function (container) { - if (!container) { - throw { - message : 'Cannot get dimensions of undefined container' - }; +/** + * In a connected graph, the normalized closeness centrality of a node is the average + * length of the shortest path between the node and all other nodes in the + * graph. Thus the more central a node is, the closer it is to all other nodes. + */ +function closeness(graph, oriented) { + var Q = []; + // list of predcessors on shortest paths from source + // distance from source + var dist = Object.create(null); + + var currentNode; + var centrality = Object.create(null); + + graph.forEachNode(setCentralityToZero); + graph.forEachNode(calculateCentrality); + + return centrality; + + function setCentralityToZero(node) { + centrality[node.id] = 0; + } + + function calculateCentrality(node) { + currentNode = node.id; + singleSourceShortestPath(currentNode); + accumulate(); + } + + function accumulate() { + // Add all distances for node to array, excluding -1s + var distances = Object.keys(dist).map(function(key) {return dist[key]}).filter(function(val){return val !== -1}); + // Set number of reachable nodes + var reachableNodesTotal = distances.length; + // Compute sum of all distances for node + var totalDistance = distances.reduce(function(a,b) { return a + b }); + if (totalDistance > 0) { + centrality[currentNode] = ((reachableNodesTotal - 1) / totalDistance); + } else { + centrality[currentNode] = 0; } + } - // TODO: Potential cross browser bug. - var width = container.clientWidth; - var height = container.clientHeight; + function singleSourceShortestPath(source) { + graph.forEachNode(initNode); + dist[source] = 0; + Q.push(source); - return { - left : 0, - top : 0, - width : width, - height : height - }; -}; + while (Q.length) { + var v = Q.shift(); + graph.forEachLinkedNode(v, processNode, oriented); + } + + function initNode(node) { + var nodeId = node.id; + dist[nodeId] = -1; + } + + function processNode(otherNode) { + var w = otherNode.id + if (dist[w] === -1) { + // Node w is found for the first time + dist[w] = dist[v] + 1; + Q.push(w); + } + } + } +} + +},{}],7:[function(require,module,exports){ +module.exports = degree; + +/** + * Calculates graph nodes degree centrality (in/out or both). + * + * @see http://en.wikipedia.org/wiki/Centrality#Degree_centrality + * + * @param {ngraph.graph} graph object for which we are calculating centrality. + * @param {string} [kind=both] What kind of degree centrality needs to be calculated: + * 'in' - calculate in-degree centrality + * 'out' - calculate out-degree centrality + * 'inout' - (default) generic degree centrality is calculated + */ +function degree(graph, kind) { + var getNodeDegree; + var result = Object.create(null); + + kind = (kind || 'both').toLowerCase(); + if (kind === 'both' || kind === 'inout') { + getNodeDegree = inoutDegreeCalculator; + } else if (kind === 'in') { + getNodeDegree = inDegreeCalculator; + } else if (kind === 'out') { + getNodeDegree = outDegreeCalculator; + } else { + throw new Error('Expected centrality degree kind is: in, out or both'); + } + + graph.forEachNode(calculateNodeDegree); + + return result; + + function calculateNodeDegree(node) { + var links = graph.getLinks(node.id); + result[node.id] = getNodeDegree(links, node.id); + } +} + +function inDegreeCalculator(links, nodeId) { + var total = 0; + if (!links) return total; + + for (var i = 0; i < links.length; i += 1) { + total += (links[i].toId === nodeId) ? 1 : 0; + } + return total; +} + +function outDegreeCalculator(links, nodeId) { + var total = 0; + if (!links) return total; + + for (var i = 0; i < links.length; i += 1) { + total += (links[i].fromId === nodeId) ? 1 : 0; + } + return total; +} + +function inoutDegreeCalculator(links) { + if (!links) return 0; + + return links.length; +} + +},{}],8:[function(require,module,exports){ +module.exports = eccentricity; /** - * Finds the absolute position of an element on a page + * The eccentricity centrality of a node is the greatest distance between that node and + * any other node in the network. */ -Viva.Graph.Utils.findElementPosition = function (obj) { - var curleft = 0, - curtop = 0; - if (obj.offsetParent) { - do { - curleft += obj.offsetLeft; - curtop += obj.offsetTop; - } while ((obj = obj.offsetParent) !== null); +function eccentricity(graph, oriented) { + var Q = []; + // distance from source + var dist = Object.create(null); + + var currentNode; + var centrality = Object.create(null); + + graph.forEachNode(setCentralityToZero); + graph.forEachNode(calculateCentrality); + + return centrality; + + function setCentralityToZero(node) { + centrality[node.id] = 0; + } + + function calculateCentrality(node) { + currentNode = node.id; + singleSourceShortestPath(currentNode); + accumulate(); + } + + function accumulate() { + var maxDist = 0; + Object.keys(dist).forEach(function (key) { + var val = dist[key]; + if (maxDist < val) maxDist = val; + }); + + centrality[currentNode] = maxDist; + } + + function singleSourceShortestPath(source) { + graph.forEachNode(initNode); + dist[source] = 0; + Q.push(source); + + while (Q.length) { + var v = Q.shift(); + graph.forEachLinkedNode(v, processNode, oriented); } - return [curleft, curtop]; -};/** - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com - */ + function initNode(node) { + var nodeId = node.id; + dist[nodeId] = -1; + } + + function processNode(otherNode) { + var w = otherNode.id + if (dist[w] === -1) { + // Node w is found for the first time + dist[w] = dist[v] + 1; + Q.push(w); + } + } + } +} -Viva.Graph.Utils = Viva.Graph.Utils || {}; +},{}],9:[function(require,module,exports){ +module.exports = function(subject) { + validateSubject(subject); + + var eventsStorage = createEventsStorage(subject); + subject.on = eventsStorage.on; + subject.off = eventsStorage.off; + subject.fire = eventsStorage.fire; + return subject; +}; + +function createEventsStorage(subject) { + // Store all event listeners to this hash. Key is event name, value is array + // of callback records. + // + // A callback record consists of callback function and its optional context: + // { 'eventName' => [{callback: function, ctx: object}] } + var registeredEvents = Object.create(null); + + return { + on: function (eventName, callback, ctx) { + if (typeof callback !== 'function') { + throw new Error('callback is expected to be a function'); + } + var handlers = registeredEvents[eventName]; + if (!handlers) { + handlers = registeredEvents[eventName] = []; + } + handlers.push({callback: callback, ctx: ctx}); + + return subject; + }, + + off: function (eventName, callback) { + var wantToRemoveAll = (typeof eventName === 'undefined'); + if (wantToRemoveAll) { + // Killing old events storage should be enough in this case: + registeredEvents = Object.create(null); + return subject; + } + + if (registeredEvents[eventName]) { + var deleteAllCallbacksForEvent = (typeof callback !== 'function'); + if (deleteAllCallbacksForEvent) { + delete registeredEvents[eventName]; + } else { + var callbacks = registeredEvents[eventName]; + for (var i = 0; i < callbacks.length; ++i) { + if (callbacks[i].callback === callback) { + callbacks.splice(i, 1); + } + } + } + } + + return subject; + }, + + fire: function (eventName) { + var callbacks = registeredEvents[eventName]; + if (!callbacks) { + return subject; + } + + var fireArguments; + if (arguments.length > 1) { + fireArguments = Array.prototype.splice.call(arguments, 1); + } + for(var i = 0; i < callbacks.length; ++i) { + var callbackInfo = callbacks[i]; + callbackInfo.callback.apply(callbackInfo.ctx, fireArguments); + } + + return subject; + } + }; +} + +function validateSubject(subject) { + if (!subject) { + throw new Error('Eventify cannot use falsy object as events subject'); + } + var reservedWords = ['on', 'fire', 'off']; + for (var i = 0; i < reservedWords.length; ++i) { + if (subject.hasOwnProperty(reservedWords[i])) { + throw new Error("Subject cannot be eventified, since it already has property '" + reservedWords[i] + "'"); + } + } +} + +},{}],10:[function(require,module,exports){ +module.exports = exposeProperties; + +/** + * Augments `target` object with getter/setter functions, which modify settings + * + * @example + * var target = {}; + * exposeProperties({ age: 42}, target); + * target.age(); // returns 42 + * target.age(24); // make age 24; + * + * var filteredTarget = {}; + * exposeProperties({ age: 42, name: 'John'}, filteredTarget, ['name']); + * filteredTarget.name(); // returns 'John' + * filteredTarget.age === undefined; // true + */ +function exposeProperties(settings, target, filter) { + var needsFilter = Object.prototype.toString.call(filter) === '[object Array]'; + if (needsFilter) { + for (var i = 0; i < filter.length; ++i) { + augment(settings, target, filter[i]); + } + } else { + for (var key in settings) { + augment(settings, target, key); + } + } +} + +function augment(source, target, key) { + if (source.hasOwnProperty(key)) { + if (typeof target[key] === 'function') { + // this accessor is already defined. Ignore it + return; + } + target[key] = function (value) { + if (value !== undefined) { + source[key] = value; + return target; + } + return source[key]; + } + } +} -// TODO: I don't really like the way I implemented events. It looks clumsy and -// hard to understand. Refactor it. +},{}],11:[function(require,module,exports){ +module.exports = createLayout; +module.exports.simulator = require('ngraph.physics.simulator'); -// TODO: This is really painful. Please don't use this class anymore, I will -// definitely depricate it or update its interface. +var eventify = require('ngraph.events'); /** - * Allows to start/stop listen to element's events. An element can be arbitrary - * DOM element, or object with eventuality behavior. + * Creates force based layout for a given graph. * - * To add eventuality behavior to arbitrary object 'obj' call - * Viva.Graph.Utils.event(obj).extend() method. - * After this call is made the object can use obj.fire(eventName, params) method, and listeners - * can listen to event by Viva.Graph.Utils.events(obj).on(eventName, callback) method. + * @param {ngraph.graph} graph which needs to be laid out + * @param {object} physicsSettings if you need custom settings + * for physics simulator you can pass your own settings here. If it's not passed + * a default one will be created. */ -Viva.Graph.Utils.events = function (element) { +function createLayout(graph, physicsSettings) { + if (!graph) { + throw new Error('Graph structure cannot be undefined'); + } + + var createSimulator = require('ngraph.physics.simulator'); + var physicsSimulator = createSimulator(physicsSettings); + + var nodeMass = defaultNodeMass + if (physicsSettings && typeof physicsSettings.nodeMass === 'function') { + nodeMass = physicsSettings.nodeMass + } + + var nodeBodies = Object.create(null); + var springs = {}; + var bodiesCount = 0; + + var springTransform = physicsSimulator.settings.springTransform || noop; + + // Initialize physics with what we have in the graph: + initPhysics(); + listenToEvents(); + + var wasStable = false; + var api = { /** - * Extends arbitrary object with fire method and allows it to be used with on/stop methods. + * Performs one step of iterative layout algorithm * - * This behavior is based on Crockford's eventuality example, but with a minor changes: - * - fire() method accepts parameters to pass to callbacks (instead of setting them in 'on' method) - * - on() method is replaced with addEventListener(), to let objects be used as a DOM objects. - * - behavoir contract is simplified to "string as event name"/"function as callback" convention. - * - removeEventListener() method added to let unsubscribe from events. + * @returns {boolean} true if the system should be considered stable; Flase otherwise. + * The system is stable if no further call to `step()` can improve the layout. */ - var eventuality = function (that) { - var registry = {}; + step: function() { + if (bodiesCount === 0) return true; // TODO: This will never fire 'stable' - /** - * Fire an event on an object. The event is a string containing the name of the event - * Handlers registered by the 'addEventListener' method that match the event name - * will be invoked. - */ - that.fire = function (eventName, parameters) { - var registeredHandlers, - callback, - handler, - i; - - if (typeof eventName !== "string") { - throw "Only strings can be used as even type"; - } + var lastMove = physicsSimulator.step(); - // If an array of handlers exist for this event, then - // loop through it and execute the handlers in order. - if (registry.hasOwnProperty(eventName)) { - registeredHandlers = registry[eventName]; - for (i = 0; i < registeredHandlers.length; ++i) { - handler = registeredHandlers[i]; - callback = handler.method; - callback(parameters); - } - } + // Save the movement in case if someone wants to query it in the step + // callback. + api.lastMove = lastMove; - return this; - }; + // Allow listeners to perform low-level actions after nodes are updated. + api.fire('step'); - that.addEventListener = function (eventName, callback) { - if (typeof callback !== "function") { - throw "Only functions allowed to be callbacks"; - } + var ratio = lastMove/bodiesCount; + var isStableNow = ratio <= 0.01; // TODO: The number is somewhat arbitrary... - var handler = { - method: callback - }; - if (registry.hasOwnProperty(eventName)) { - registry[eventName].push(handler); - } else { - registry[eventName] = [handler]; - } + if (wasStable !== isStableNow) { + wasStable = isStableNow; + onStableChanged(isStableNow); + } - return this; + return isStableNow; + }, + + /** + * For a given `nodeId` returns position + */ + getNodePosition: function (nodeId) { + return getInitializedBody(nodeId).pos; + }, + + /** + * Sets position of a node to a given coordinates + * @param {string} nodeId node identifier + * @param {number} x position of a node + * @param {number} y position of a node + * @param {number=} z position of node (only if applicable to body) + */ + setNodePosition: function (nodeId) { + var body = getInitializedBody(nodeId); + body.setPosition.apply(body, Array.prototype.slice.call(arguments, 1)); + physicsSimulator.invalidateBBox(); + }, + + /** + * @returns {Object} Link position by link id + * @returns {Object.from} {x, y} coordinates of link start + * @returns {Object.to} {x, y} coordinates of link end + */ + getLinkPosition: function (linkId) { + var spring = springs[linkId]; + if (spring) { + return { + from: spring.from.pos, + to: spring.to.pos }; + } + }, - that.removeEventListener = function (eventName, callback) { - if (typeof callback !== "function") { - throw "Only functions allowed to be callbacks"; - } + /** + * @returns {Object} area required to fit in the graph. Object contains + * `x1`, `y1` - top left coordinates + * `x2`, `y2` - bottom right coordinates + */ + getGraphRect: function () { + return physicsSimulator.getBBox(); + }, - if (registry.hasOwnProperty(eventName)) { - var handlers = registry[eventName], - i; + /** + * Iterates over each body in the layout simulator and performs a callback(body, nodeId) + */ + forEachBody: forEachBody, - for (i = 0; i < handlers.length; ++i) { - if (handlers[i].callback === callback) { - handlers.splice(i); - break; - } - } - } + /* + * Requests layout algorithm to pin/unpin node to its current position + * Pinned nodes should not be affected by layout algorithm and always + * remain at their position + */ + pinNode: function (node, isPinned) { + var body = getInitializedBody(node.id); + body.isPinned = !!isPinned; + }, - return this; - }; + /** + * Checks whether given graph's node is currently pinned + */ + isNodePinned: function (node) { + return getInitializedBody(node.id).isPinned; + }, - that.removeAllListeners = function () { - var eventName; - for (eventName in registry) { - if (registry.hasOwnProperty(eventName)) { - delete registry[eventName]; - } - } - }; + /** + * Request to release all resources + */ + dispose: function() { + graph.off('changed', onGraphChanged); + api.fire('disposed'); + }, - return that; - }; + /** + * Gets physical body for a given node id. If node is not found undefined + * value is returned. + */ + getBody: getBody, - return { - /** - * Registes callback to be called when element fires event with given event name. - */ - on : function (eventName, callback) { - if (element.addEventListener) {// W3C DOM and eventuality objecets. - element.addEventListener(eventName, callback, false); - } else if (element.attachEvent) { // IE DOM - element.attachEvent("on" + eventName, callback); - } + /** + * Gets spring for a given edge. + * + * @param {string} linkId link identifer. If two arguments are passed then + * this argument is treated as formNodeId + * @param {string=} toId when defined this parameter denotes head of the link + * and first argument is trated as tail of the link (fromId) + */ + getSpring: getSpring, - return this; - }, + /** + * [Read only] Gets current physics simulator + */ + simulator: physicsSimulator, - /** - * Unsubcribes from object's events. - */ - stop : function (eventName, callback) { - if (element.removeEventListener) { - element.removeEventListener(eventName, callback, false); - } else if (element.detachEvent) { - element.detachEvent("on" + eventName, callback); - } - }, + /** + * Gets the graph that was used for layout + */ + graph: graph, - /** - * Adds eventuality to arbitrary JavaScript object. Eventuality adds - * fire(), addEventListner() and removeEventListners() to the target object. - * - * This is required if you want to use object with on(), stop() methods. - */ - extend : function () { - return eventuality(element); - } - }; -};/** - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com - */ + /** + * Gets amount of movement performed during last step opeartion + */ + lastMove: 0 + }; -Viva.Graph.Utils = Viva.Graph.Utils || {}; + eventify(api); -// TODO: Move to input namespace -// TODO: Methods should be extracted into the prototype. This class -// does not need to consume so much memory for every tracked element -Viva.Graph.Utils.dragndrop = function (element) { - var start, - drag, - end, - scroll, - prevSelectStart, - prevDragStart, - documentEvents = Viva.Graph.Utils.events(window.document), - elementEvents = Viva.Graph.Utils.events(element), - findElementPosition = Viva.Graph.Utils.findElementPosition, + return api; - startX = 0, - startY = 0, - dragObject, - touchInProgress = false, - pinchZoomLength = 0, + function forEachBody(cb) { + Object.keys(nodeBodies).forEach(function(bodyId) { + cb(nodeBodies[bodyId], bodyId); + }); + } + + function getSpring(fromId, toId) { + var linkId; + if (toId === undefined) { + if (typeof fromId !== 'object') { + // assume fromId as a linkId: + linkId = fromId; + } else { + // assume fromId to be a link object: + linkId = fromId.id; + } + } else { + // toId is defined, should grab link: + var link = graph.hasLink(fromId, toId); + if (!link) return; + linkId = link.id; + } - getMousePos = function (e) { - var posx = 0, - posy = 0; + return springs[linkId]; + } - e = e || window.event; + function getBody(nodeId) { + return nodeBodies[nodeId]; + } - if (e.pageX || e.pageY) { - posx = e.pageX; - posy = e.pageY; - } else if (e.clientX || e.clientY) { - posx = e.clientX + window.document.body.scrollLeft + window.document.documentElement.scrollLeft; - posy = e.clientY + window.document.body.scrollTop + window.document.documentElement.scrollTop; - } + function listenToEvents() { + graph.on('changed', onGraphChanged); + } - return [posx, posy]; - }, + function onStableChanged(isStable) { + api.fire('stable', isStable); + } - move = function (e, clientX, clientY) { - if (drag) { - drag(e, {x : clientX - startX, y : clientY - startY }); - } + function onGraphChanged(changes) { + for (var i = 0; i < changes.length; ++i) { + var change = changes[i]; + if (change.changeType === 'add') { + if (change.node) { + initBody(change.node.id); + } + if (change.link) { + initLink(change.link); + } + } else if (change.changeType === 'remove') { + if (change.node) { + releaseNode(change.node); + } + if (change.link) { + releaseLink(change.link); + } + } + } + bodiesCount = graph.getNodesCount(); + } - startX = clientX; - startY = clientY; - }, + function initPhysics() { + bodiesCount = 0; - stopPropagation = function (e) { - if (e.stopPropagation) { e.stopPropagation(); } else { e.cancelBubble = true; } - }, - preventDefault = function (e) { - if (e.preventDefault) { e.preventDefault(); } - }, + graph.forEachNode(function (node) { + initBody(node.id); + bodiesCount += 1; + }); - handleDisabledEvent = function (e) { - stopPropagation(e); - return false; - }, + graph.forEachLink(initLink); + } - handleMouseMove = function (e) { - e = e || window.event; + function initBody(nodeId) { + var body = nodeBodies[nodeId]; + if (!body) { + var node = graph.getNode(nodeId); + if (!node) { + throw new Error('initBody() was called with unknown node id'); + } - move(e, e.clientX, e.clientY); - }, + var pos = node.position; + if (!pos) { + var neighbors = getNeighborBodies(node); + pos = physicsSimulator.getBestNewBodyPosition(neighbors); + } - handleMouseDown = function (e) { - e = e || window.event; - if (touchInProgress) { - // modern browsers will fire mousedown for touch events too - // we do not want this, since touch is handled separately. - stopPropagation(e); - return false; - } - // for IE, left click == 1 - // for Firefox, left click == 0 - var isLeftButton = ((e.button === 1 && window.event !== null) || e.button === 0); + body = physicsSimulator.addBodyAt(pos); + body.id = nodeId; - if (isLeftButton) { - startX = e.clientX; - startY = e.clientY; + nodeBodies[nodeId] = body; + updateBodyMass(nodeId); - // TODO: bump zIndex? - dragObject = e.target || e.srcElement; + if (isNodeOriginallyPinned(node)) { + body.isPinned = true; + } + } + } - if (start) { start(e, {x: startX, y : startY}); } + function releaseNode(node) { + var nodeId = node.id; + var body = nodeBodies[nodeId]; + if (body) { + nodeBodies[nodeId] = null; + delete nodeBodies[nodeId]; - documentEvents.on('mousemove', handleMouseMove); - documentEvents.on('mouseup', handleMouseUp); + physicsSimulator.removeBody(body); + } + } + function initLink(link) { + updateBodyMass(link.fromId); + updateBodyMass(link.toId); - stopPropagation(e); - // TODO: What if event already there? Not bullet proof: - prevSelectStart = window.document.onselectstart; - prevDragStart = window.document.ondragstart; + var fromBody = nodeBodies[link.fromId], + toBody = nodeBodies[link.toId], + spring = physicsSimulator.addSpring(fromBody, toBody, link.length); - window.document.onselectstart = handleDisabledEvent; - dragObject.ondragstart = handleDisabledEvent; + springTransform(link, spring); - // prevent text selection (except IE) - return false; - } - }, + springs[link.id] = spring; + } - handleMouseUp = function (e) { - e = e || window.event; + function releaseLink(link) { + var spring = springs[link.id]; + if (spring) { + var from = graph.getNode(link.fromId), + to = graph.getNode(link.toId); - documentEvents.stop('mousemove', handleMouseMove); - documentEvents.stop('mouseup', handleMouseUp); + if (from) updateBodyMass(from.id); + if (to) updateBodyMass(to.id); - window.document.onselectstart = prevSelectStart; - dragObject.ondragstart = prevDragStart; - dragObject = null; - if (end) { end(e); } - }, + delete springs[link.id]; - handleMouseWheel = function (e) { - if (typeof scroll !== 'function') { - return; - } + physicsSimulator.removeSpring(spring); + } + } - e = e || window.event; - if (e.preventDefault) { - e.preventDefault(); - } + function getNeighborBodies(node) { + // TODO: Could probably be done better on memory + var neighbors = []; + if (!node.links) { + return neighbors; + } + var maxNeighbors = Math.min(node.links.length, 2); + for (var i = 0; i < maxNeighbors; ++i) { + var link = node.links[i]; + var otherBody = link.fromId !== node.id ? nodeBodies[link.fromId] : nodeBodies[link.toId]; + if (otherBody && otherBody.pos) { + neighbors.push(otherBody); + } + } - e.returnValue = false; - var delta, - mousePos = getMousePos(e), - elementOffset = findElementPosition(element), - relMousePos = { - x: mousePos[0] - elementOffset[0], - y: mousePos[1] - elementOffset[1] - }; + return neighbors; + } - if (e.wheelDelta) { - delta = e.wheelDelta / 360; // Chrome/Safari - } else { - delta = e.detail / -9; // Mozilla - } + function updateBodyMass(nodeId) { + var body = nodeBodies[nodeId]; + body.mass = nodeMass(nodeId); + if (Number.isNaN(body.mass)) { + throw new Error('Node mass should be a number') + } + } + + /** + * Checks whether graph node has in its settings pinned attribute, + * which means layout algorithm cannot move it. Node can be preconfigured + * as pinned, if it has "isPinned" attribute, or when node.data has it. + * + * @param {Object} node a graph node to check + * @return {Boolean} true if node should be treated as pinned; false otherwise. + */ + function isNodeOriginallyPinned(node) { + return (node && (node.isPinned || (node.data && node.data.isPinned))); + } + + function getInitializedBody(nodeId) { + var body = nodeBodies[nodeId]; + if (!body) { + initBody(nodeId); + body = nodeBodies[nodeId]; + } + return body; + } + + /** + * Calculates mass of a body, which corresponds to node with given id. + * + * @param {String|Number} nodeId identifier of a node, for which body mass needs to be calculated + * @returns {Number} recommended mass of the body; + */ + function defaultNodeMass(nodeId) { + var links = graph.getLinks(nodeId); + if (!links) return 1; + return 1 + links.length / 3.0; + } +} + +function noop() { } + +},{"ngraph.events":12,"ngraph.physics.simulator":19}],12:[function(require,module,exports){ +arguments[4][9][0].apply(exports,arguments) +},{"dup":9}],13:[function(require,module,exports){ +module.exports = load; + +var createGraph = require('ngraph.graph'); + +function load(jsonGraph, nodeTransform, linkTransform) { + var stored; + nodeTransform = nodeTransform || id; + linkTransform = linkTransform || id; + if (typeof jsonGraph === 'string') { + stored = JSON.parse(jsonGraph); + } else { + stored = jsonGraph; + } + + var graph = createGraph(), + i; + + if (stored.links === undefined || stored.nodes === undefined) { + throw new Error('Cannot load graph without links and nodes'); + } + + for (i = 0; i < stored.nodes.length; ++i) { + var parsedNode = nodeTransform(stored.nodes[i]); + if (!parsedNode.hasOwnProperty('id')) { + throw new Error('Graph node format is invalid: Node id is missing'); + } - scroll(e, delta, relMousePos); - }, + graph.addNode(parsedNode.id, parsedNode.data); + } - updateScrollEvents = function (scrollCallback) { - if (!scroll && scrollCallback) { - // client is interested in scrolling. Start listening to events: - if (Viva.BrowserInfo.browser === 'webkit') { - element.addEventListener('mousewheel', handleMouseWheel, false); // Chrome/Safari - } else { - element.addEventListener('DOMMouseScroll', handleMouseWheel, false); // Others - } - } else if (scroll && !scrollCallback) { - if (Viva.BrowserInfo.browser === 'webkit') { - element.removeEventListener('mousewheel', handleMouseWheel, false); // Chrome/Safari - } else { - element.removeEventListener('DOMMouseScroll', handleMouseWheel, false); // Others - } - } + for (i = 0; i < stored.links.length; ++i) { + var link = linkTransform(stored.links[i]); + if (!link.hasOwnProperty('fromId') || !link.hasOwnProperty('toId')) { + throw new Error('Graph link format is invalid. Both fromId and toId are required'); + } - scroll = scrollCallback; - }, + graph.addLink(link.fromId, link.toId, link.data); + } + + return graph; +} + +function id(x) { return x; } + +},{"ngraph.graph":16}],14:[function(require,module,exports){ +var createGraph = require('ngraph.graph'); + +module.exports = factory(createGraph); + +// Allow other developers have their own createGraph +module.exports.factory = factory; + +function factory(createGraph) { + return { + ladder: ladder, + complete: complete, + completeBipartite: completeBipartite, + balancedBinTree: balancedBinTree, + path: path, + circularLadder: circularLadder, + grid: grid, + grid3: grid3, + noLinks: noLinks, + wattsStrogatz: wattsStrogatz, + cliqueCircle: cliqueCircle + }; + + + function ladder(n) { + /** + * Ladder graph is a graph in form of ladder + * @param {Number} n Represents number of steps in the ladder + */ + if (!n || n < 0) { + throw new Error("Invalid number of nodes"); + } - getPinchZoomLength = function(finger1, finger2) { - return (finger1.clientX - finger2.clientX) * (finger1.clientX - finger2.clientX) + - (finger1.clientY - finger2.clientY) * (finger1.clientY - finger2.clientY); - }, + var g = createGraph(), + i; - handleTouchMove = function (e) { - if (e.touches.length === 1) { - stopPropagation(e); + for (i = 0; i < n - 1; ++i) { + g.addLink(i, i + 1); + // first row + g.addLink(n + i, n + i + 1); + // second row + g.addLink(i, n + i); + // ladder's step + } - var touch = e.touches[0]; - move(e, touch.clientX, touch.clientY); - } else if (e.touches.length === 2) { - // it's a zoom: - var currentPinchLength = getPinchZoomLength(e.touches[0], e.touches[1]); - var delta = 0; - if (currentPinchLength < pinchZoomLength) { - delta = -1; - } else if (currentPinchLength > pinchZoomLength) { - delta = 1; - } - scroll(e, delta, {x: e.touches[0].clientX, y: e.touches[0].clientY}); - pinchZoomLength = currentPinchLength; - stopPropagation(e); - preventDefault(e); - } - }, + g.addLink(n - 1, 2 * n - 1); + // last step in the ladder; + + return g; + } + + function circularLadder(n) { + /** + * Circular ladder with n steps. + * + * @param {Number} n of steps in the ladder. + */ + if (!n || n < 0) { + throw new Error("Invalid number of nodes"); + } + + var g = ladder(n); + + g.addLink(0, n - 1); + g.addLink(n, 2 * n - 1); + return g; + } + + function complete(n) { + /** + * Complete graph Kn. + * + * @param {Number} n represents number of nodes in the complete graph. + */ + if (!n || n < 1) { + throw new Error("At least two nodes are expected for complete graph"); + } - handleTouchEnd = function (e) { - touchInProgress = false; - documentEvents.stop('touchmove', handleTouchMove); - documentEvents.stop('touchend', handleTouchEnd); - documentEvents.stop('touchcancel', handleTouchEnd); - dragObject = null; - if (end) { end(e); } - }, + var g = createGraph(), + i, + j; - handleSignleFingerTouch = function (e, touch) { - stopPropagation(e); - preventDefault(e); + for (i = 0; i < n; ++i) { + for (j = i + 1; j < n; ++j) { + if (i !== j) { + g.addLink(i, j); + } + } + } - startX = touch.clientX; - startY = touch.clientY; + return g; + } + + function completeBipartite (n, m) { + /** + * Complete bipartite graph K n,m. Each node in the + * first partition is connected to all nodes in the second partition. + * + * @param {Number} n represents number of nodes in the first graph partition + * @param {Number} m represents number of nodes in the second graph partition + */ + if (!n || !m || n < 0 || m < 0) { + throw new Error("Graph dimensions are invalid. Number of nodes in each partition should be greater than 0"); + } - dragObject = e.target || e.srcElement; + var g = createGraph(), + i, j; - if (start) { start(e, {x: startX, y : startY}); } - // TODO: can I enter into the state when touch is in progress - // but it's still a single finger touch? - if (!touchInProgress) { - touchInProgress = true; - documentEvents.on('touchmove', handleTouchMove); - documentEvents.on('touchend', handleTouchEnd); - documentEvents.on('touchcancel', handleTouchEnd); - } - }, + for (i = 0; i < n; ++i) { + for (j = n; j < n + m; ++j) { + g.addLink(i, j); + } + } - handleTouchStart = function (e) { - console.log('Touch start for ', element); - if (e.touches.length === 1) { - return handleSignleFingerTouch(e, e.touches[0]); - } else if (e.touches.length === 2) { - // handleTouchMove() will care about pinch zoom. - stopPropagation(e); - preventDefault(e); + return g; + } + + function path(n) { + /** + * Path graph with n steps. + * + * @param {Number} n number of nodes in the path + */ + if (!n || n < 0) { + throw new Error("Invalid number of nodes"); + } - pinchZoomLength = getPinchZoomLength(e.touches[0], e.touches[1]); + var g = createGraph(), + i; - } - // don't care about the rest. - }; + g.addNode(0); + for (i = 1; i < n; ++i) { + g.addLink(i - 1, i); + } - elementEvents.on('mousedown', handleMouseDown); - elementEvents.on('touchstart', handleTouchStart); + return g; + } - return { - onStart : function (callback) { - start = callback; - return this; - }, - onDrag : function (callback) { - drag = callback; - return this; - }, + function grid(n, m) { + /** + * Grid graph with n rows and m columns. + * + * @param {Number} n of rows in the graph. + * @param {Number} m of columns in the graph. + */ + if (n < 1 || m < 1) { + throw new Error("Invalid number of nodes in grid graph"); + } + var g = createGraph(), + i, + j; + if (n === 1 && m === 1) { + g.addNode(0); + return g; + } - onStop : function (callback) { - end = callback; - return this; - }, + for (i = 0; i < n; ++i) { + for (j = 0; j < m; ++j) { + var node = i + j * n; + if (i > 0) { g.addLink(node, i - 1 + j * n); } + if (j > 0) { g.addLink(node, i + (j - 1) * n); } + } + } - /** - * Occurs when mouse wheel event happens. callback = function(e, scrollDelta, scrollPoint); - */ - onScroll : function (callback) { - updateScrollEvents(callback); - return this; - }, + return g; + } + + function grid3(n, m, z) { + /** + * 3D grid with n rows and m columns and z levels. + * + * @param {Number} n of rows in the graph. + * @param {Number} m of columns in the graph. + * @param {Number} z of levels in the graph. + */ + if (n < 1 || m < 1 || z < 1) { + throw new Error("Invalid number of nodes in grid3 graph"); + } + var g = createGraph(), + i, j, k; - release : function () { - // TODO: could be unsafe. We might wanna release dragObject, etc. - documentEvents.stop('mousemove', handleMouseMove); - documentEvents.stop('mousedown', handleMouseDown); - documentEvents.stop('mouseup', handleMouseUp); - documentEvents.stop('touchmove', handleTouchMove); - documentEvents.stop('touchend', handleTouchEnd); - documentEvents.stop('touchcancel', handleTouchEnd); + if (n === 1 && m === 1 && z === 1) { + g.addNode(0); + return g; + } - updateScrollEvents(null); + for (k = 0; k < z; ++k) { + for (i = 0; i < n; ++i) { + for (j = 0; j < m; ++j) { + var level = k * n * m; + var node = i + j * n + level; + if (i > 0) { g.addLink(node, i - 1 + j * n + level); } + if (j > 0) { g.addLink(node, i + (j - 1) * n + level); } + if (k > 0) { g.addLink(node, i + j * n + (k - 1) * n * m ); } } - }; -}; -/** - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com - */ + } + } -Viva.Input = Viva.Input || {}; -Viva.Input.domInputManager = function () { - return { - /** - * Called by renderer to listen to drag-n-drop events from node. E.g. for CSS/SVG - * graphics we may listen to DOM events, whereas for WebGL the graphics - * should provide custom eventing mechanism. - * - * @param node - to be monitored. - * @param handlers - object with set of three callbacks: - * onStart: function(), - * onDrag: function(e, offset), - * onStop: function() - */ - bindDragNDrop : function (node, handlers) { - if (handlers) { - var events = Viva.Graph.Utils.dragndrop(node.ui); - if (typeof handlers.onStart === 'function') { - events.onStart(handlers.onStart); - } - if (typeof handlers.onDrag === 'function') { - events.onDrag(handlers.onDrag); - } - if (typeof handlers.onStop === 'function') { - events.onStop(handlers.onStop); - } + return g; + } + + function balancedBinTree(n) { + /** + * Balanced binary tree with n levels. + * + * @param {Number} n of levels in the binary tree + */ + if (n < 0) { + throw new Error("Invalid number of nodes in balanced tree"); + } + var g = createGraph(), + count = Math.pow(2, n), + level; - node.events = events; - } else if (node.events) { - // TODO: i'm not sure if this is required in JS world... - node.events.release(); - node.events = null; - delete node.events; - } + if (n === 0) { + g.addNode(1); + } + + for (level = 1; level < count; ++level) { + var root = level, + left = root * 2, + right = root * 2 + 1; + + g.addLink(root, left); + g.addLink(root, right); + } + + return g; + } + + function noLinks(n) { + /** + * Graph with no links + * + * @param {Number} n of nodes in the graph + */ + if (n < 0) { + throw new Error("Number of nodes should be >= 0"); + } + + var g = createGraph(), i; + for (i = 0; i < n; ++i) { + g.addNode(i); + } + + return g; + } + + function cliqueCircle(cliqueCount, cliqueSize) { + /** + * A circular graph with cliques instead of individual nodes + * + * @param {Number} cliqueCount number of cliques inside circle + * @param {Number} cliqueSize number of nodes inside each clique + */ + + if (cliqueCount < 1) throw new Error('Invalid number of cliqueCount in cliqueCircle'); + if (cliqueSize < 1) throw new Error('Invalid number of cliqueSize in cliqueCircle'); + + var graph = createGraph(); + + for (var i = 0; i < cliqueCount; ++i) { + appendClique(cliqueSize, i * cliqueSize) + + if (i > 0) { + graph.addLink(i * cliqueSize, i * cliqueSize - 1); + } + } + graph.addLink(0, graph.getNodesCount() - 1); + + return graph; + + function appendClique(size, from) { + for (var i = 0; i < size; ++i) { + graph.addNode(i + from) + } + + for (var i = 0; i < size; ++i) { + for (var j = i + 1; j < size; ++j) { + graph.addLink(i + from, j + from) } - }; -}; -/** - * Allows querying graph nodes position at given point. - * - * @param graph - graph to be queried. - * @param toleranceOrCheckCallback - if it's a number then it represents offest - * in pixels from any node position to be considered a part of the node. - * if it's a function then it's called for every node to check intersection - * - * TODO: currently it performes linear search. Use real spatial index to improve performance. - */ -Viva.Graph.spatialIndex = function (graph, toleranceOrCheckCallback) { - var getNodeFunction, - preciseCheckCallback, - tolerance = 16; - - if (typeof toleranceOrCheckCallback === 'function') { - preciseCheckCallback = toleranceOrCheckCallback; - getNodeFunction = function (x, y) { - var foundNode = null; - graph.forEachNode(function (node) { - if (preciseCheckCallback(node, x, y)) { - foundNode = node; - return true; - } - }); + } + } + } - return foundNode; - }; - } else if (typeof toleranceOrCheckCallback === 'number') { - tolerance = toleranceOrCheckCallback; - getNodeFunction = function (x, y) { - var foundNode = null; + function wattsStrogatz(n, k, p, seed) { + /** + * Watts-Strogatz small-world graph. + * + * @param {Number} n The number of nodes + * @param {Number} k Each node is connected to k nearest neighbors in ring topology + * @param {Number} p The probability of rewiring each edge - graph.forEachNode(function (node) { - var pos = node.position; + * @see https://github.com/networkx/networkx/blob/master/networkx/generators/random_graphs.py + */ + if (k >= n) throw new Error('Choose smaller `k`. It cannot be larger than number of nodes `n`'); - if (pos.x - tolerance < x && x < pos.x + tolerance && - pos.y - tolerance < y && y < pos.y + tolerance) { - foundNode = node; - return true; - } - }); + var random = require('ngraph.random').random(seed || 42); - return foundNode; - }; + var g = createGraph(), i, to; + for (i = 0; i < n; ++i) { + g.addNode(i); } + // connect each node to k/2 neighbors + var neighborsSize = Math.floor(k/2 + 1); + for (var j = 1; j < neighborsSize; ++j) { + for (i = 0; i < n; ++i) { + to = (j + i) % n; + g.addLink(i, to); + } + } - return { - getNodeAt : getNodeFunction - }; -}; -/** - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com - */ - -Viva.Graph.Utils = Viva.Graph.Utils || {}; - -(function () { - var lastTime = 0, - vendors = ['ms', 'moz', 'webkit', 'o'], - i, - scope; - - if (typeof window !== 'undefined') { - scope = window; - } else if (typeof global !== 'undefined') { - scope = global; - } else { - scope = { - setTimeout: function () {}, - clearTimeout: function () {} - }; - } - for (i = 0; i < vendors.length && !scope.requestAnimationFrame; ++i) { - var vendorPrefix = vendors[i]; - scope.requestAnimationFrame = scope[vendorPrefix + 'RequestAnimationFrame']; - scope.cancelAnimationFrame = - scope[vendorPrefix + 'CancelAnimationFrame'] || scope[vendorPrefix + 'CancelRequestAnimationFrame']; - } - - if (!scope.requestAnimationFrame) { - scope.requestAnimationFrame = function (callback) { - var currTime = new Date().getTime(); - var timeToCall = Math.max(0, 16 - (currTime - lastTime)); - var id = scope.setTimeout(function () { callback(currTime + timeToCall); }, timeToCall); - lastTime = currTime + timeToCall; - return id; - }; - } - - if (!scope.cancelAnimationFrame) { - scope.cancelAnimationFrame = function (id) { - scope.clearTimeout(id); - }; - } - - /** - * Timer that fires callback with given interval (in ms) until - * callback returns true; - */ - Viva.Graph.Utils.timer = function (callback) { - var intervalId, - stopTimer = function () { - scope.cancelAnimationFrame(intervalId); - intervalId = 0; - }, - - startTimer = function () { - intervalId = scope.requestAnimationFrame(startTimer); - if (!callback()) { - stopTimer(); - } - }; - - startTimer(); // start it right away. - - return { - /** - * Stops execution of the callback - */ - stop: stopTimer, - - restart : function () { - if (!intervalId) { - startTimer(); - } - } - }; - }; -}());Viva.Graph.geom = function () { + // rewire edges from each node + // loop over all nodes in order (label) and neighbors in order (distance) + // no self loops or multiple edges allowed + for (j = 1; j < neighborsSize; ++j) { + for (i = 0; i < n; ++i) { + if (random.nextDouble() < p) { + var from = i; + to = (j + i) % n; + + var newTo = random.next(n); + var needsRewire = (newTo === from || g.hasLink(from, newTo)); + if (needsRewire && g.getLinks(from).length === n - 1) { + // we cannot rewire this node, it has too many links. + continue; + } + // Enforce no self-loops or multiple edges + while (needsRewire) { + newTo = random.next(n); + needsRewire = (newTo === from || g.hasLink(from, newTo)); + } + var link = g.hasLink(from, to); + g.removeLink(link); + g.addLink(from, newTo); + } + } + } - return { - // function from Graphics GEM to determine lines intersection: - // http://www.opensource.apple.com/source/graphviz/graphviz-498/graphviz/dynagraph/common/xlines.c - intersect : function (x1, y1, x2, y2, // first line segment - x3, y3, x4, y4) { // second line segment - var a1, a2, b1, b2, c1, c2, /* Coefficients of line eqns. */ - r1, r2, r3, r4, /* 'Sign' values */ - denom, offset, num, /* Intermediate values */ - result = { x: 0, y : 0}; - - /* Compute a1, b1, c1, where line joining points 1 and 2 - * is "a1 x + b1 y + c1 = 0". - */ - a1 = y2 - y1; - b1 = x1 - x2; - c1 = x2 * y1 - x1 * y2; - - /* Compute r3 and r4. - */ - r3 = a1 * x3 + b1 * y3 + c1; - r4 = a1 * x4 + b1 * y4 + c1; - - /* Check signs of r3 and r4. If both point 3 and point 4 lie on - * same side of line 1, the line segments do not intersect. - */ - - if (r3 !== 0 && r4 !== 0 && ((r3 >= 0) === (r4 >= 4))) { - return null; //no itersection. - } + return g; + } +} - /* Compute a2, b2, c2 */ - a2 = y4 - y3; - b2 = x3 - x4; - c2 = x4 * y3 - x3 * y4; +},{"ngraph.graph":16,"ngraph.random":15}],15:[function(require,module,exports){ +module.exports = random; - /* Compute r1 and r2 */ +// TODO: Deprecate? +module.exports.random = random, +module.exports.randomIterator = randomIterator - r1 = a2 * x1 + b2 * y1 + c2; - r2 = a2 * x2 + b2 * y2 + c2; +/** + * Creates seeded PRNG with two methods: + * next() and nextDouble() + */ +function random(inputSeed) { + var seed = typeof inputSeed === 'number' ? inputSeed : (+new Date()); + return new Generator(seed) +} - /* Check signs of r1 and r2. If both point 1 and point 2 lie - * on same side of second line segment, the line segments do - * not intersect. - */ - if (r1 !== 0 && r2 !== 0 && ((r1 >= 0) === (r2 >= 0))) { - return null; // no intersection; - } - /* Line segments intersect: compute intersection point. - */ +function Generator(seed) { + this.seed = seed; +} - denom = a1 * b2 - a2 * b1; - if (denom === 0) { - return null; // Actually collinear.. - } +/** + * Generates random integer number in the range from 0 (inclusive) to maxValue (exclusive) + * + * @param maxValue Number REQUIRED. Omitting this number will result in NaN values from PRNG. + */ +Generator.prototype.next = next; - offset = denom < 0 ? -denom / 2 : denom / 2; - offset = 0.0; +/** + * Generates random double number in the range from 0 (inclusive) to 1 (exclusive) + * This function is the same as Math.random() (except that it could be seeded) + */ +Generator.prototype.nextDouble = nextDouble; - /* The denom/2 is to get rounding instead of truncating. It - * is added or subtracted to the numerator, depending upon the - * sign of the numerator. - */ +/** + * Returns a random real number uniformly in [0, 1) + */ +Generator.prototype.uniform = nextDouble; + +Generator.prototype.gaussian = gaussian; + +function gaussian() { + // use the polar form of the Box-Muller transform + // based on https://introcs.cs.princeton.edu/java/23recursion/StdRandom.java + var r, x, y; + do { + x = this.nextDouble() * 2 - 1; + y = this.nextDouble() * 2 - 1; + r = x * x + y * y; + } while (r >= 1 || r === 0); + + return x * Math.sqrt(-2 * Math.log(r)/r); +} + +function nextDouble() { + var seed = this.seed; + // Robert Jenkins' 32 bit integer hash function. + seed = ((seed + 0x7ed55d16) + (seed << 12)) & 0xffffffff; + seed = ((seed ^ 0xc761c23c) ^ (seed >>> 19)) & 0xffffffff; + seed = ((seed + 0x165667b1) + (seed << 5)) & 0xffffffff; + seed = ((seed + 0xd3a2646c) ^ (seed << 9)) & 0xffffffff; + seed = ((seed + 0xfd7046c5) + (seed << 3)) & 0xffffffff; + seed = ((seed ^ 0xb55a4f09) ^ (seed >>> 16)) & 0xffffffff; + this.seed = seed; + return (seed & 0xfffffff) / 0x10000000; +} + +function next(maxValue) { + return Math.floor(this.nextDouble() * maxValue); +} + +/* + * Creates iterator over array, which returns items of array in random order + * Time complexity is guaranteed to be O(n); + */ +function randomIterator(array, customRandom) { + var localRandom = customRandom || random(); + if (typeof localRandom.next !== 'function') { + throw new Error('customRandom does not match expected API: next() function is missing'); + } + return { + forEach: forEach, - num = b1 * c2 - b2 * c1; - result.x = (num < 0 ? num - offset : num + offset) / denom; + /** + * Shuffles array randomly, in place. + */ + shuffle: shuffle + }; + + function shuffle() { + var i, j, t; + for (i = array.length - 1; i > 0; --i) { + j = localRandom.next(i + 1); // i inclusive + t = array[j]; + array[j] = array[i]; + array[i] = t; + } - num = a2 * c1 - a1 * c2; - result.y = (num < 0 ? num - offset : num + offset) / denom; + return array; + } - return result; - }, + function forEach(callback) { + var i, j, t; + for (i = array.length - 1; i > 0; --i) { + j = localRandom.next(i + 1); // i inclusive + t = array[j]; + array[j] = array[i]; + array[i] = t; - /** - * Returns intersection point of the rectangle defined by - * left, top, right, bottom and a line starting in x1, y1 - * and ending in x2, y2; - */ - intersectRect : function (left, top, right, bottom, x1, y1, x2, y2) { - return this.intersect(left, top, left, bottom, x1, y1, x2, y2) || - this.intersect(left, bottom, right, bottom, x1, y1, x2, y2) || - this.intersect(right, bottom, right, top, x1, y1, x2, y2) || - this.intersect(right, top, left, top, x1, y1, x2, y2); - }, + callback(t); + } - convexHull : function (points) { - var polarAngleSort = function (basePoint, points) { - var cosAngle = function (p) { - var dx = p.x - basePoint.x, - dy = p.y - basePoint.y, - sign = dx > 0 ? 1 : -1; - - // We use squared dx, to avoid Sqrt opertion and improve performance. - // To avoid sign loss during dx * dx operation we precompute its sign: - return sign * dx * dx / (dx * dx + dy * dy); - }, - - sortedPoints = points.sort(function (p1, p2) { - return cosAngle(p2) - cosAngle(p1); - }), - - // If more than one point has the same angle, remove all but the one that is farthest from basePoint: - lastPoint = sortedPoints[0], - lastAngle = cosAngle(lastPoint), - dx = lastPoint.x - basePoint.x, - dy = lastPoint.y - basePoint.y, - lastDistance = dx * dx + dy * dy, - curDistance, - i; - - for (i = 1; i < sortedPoints.length; ++i) { - lastPoint = sortedPoints[i]; - var angle = cosAngle(lastPoint); - if (angle === lastAngle) { - dx = lastPoint.x - basePoint.x; - dy = lastPoint.y - basePoint.y; - curDistance = dx * dx + dy * dy; - - if (curDistance < lastDistance) { - sortedPoints.splice(i, 1); - } else { - sortedPoints.splice(i - 1, 1); - } - } else { - lastAngle = angle; - } - } + if (array.length) { + callback(array[0]); + } + } +} +},{}],16:[function(require,module,exports){ +/** + * @fileOverview Contains definition of the core graph object. + */ - return sortedPoints; - }, +// TODO: need to change storage layer: +// 1. Be able to get all nodes O(1) +// 2. Be able to get number of links O(1) - /** - * Returns true if angle formed by points p0, p1, p2 makes left turn. - * (counterclockwise) - */ - ccw = function (p0, p1, p2) { - return ((p2.x - p0.x) * (p1.y - p0.y) - (p2.y - p0.y) * (p1.x - p0.x)) < 0; - }; +/** + * @example + * var graph = require('ngraph.graph')(); + * graph.addNode(1); // graph has one node. + * graph.addLink(2, 3); // now graph contains three nodes and one link. + * + */ +module.exports = createGraph; - if (points.length < 3) { - return points; // This one is easy... Not precise, but should be enough for now. - } +var eventify = require('ngraph.events'); - // let p0 be the point in Q with the minimum y-coordinate, or the leftmost - // such point in case of a tie - var p0Idx = 0, - i; - for (i = 0; i < points.length; ++i) { - if (points[i].y < points[p0Idx].y) { - p0Idx = i; - } else if (points[i].y === points[p0Idx].y && points[i].x < points[p0Idx].x) { - p0Idx = i; - } - } +/** + * Creates a new graph + */ +function createGraph(options) { + // Graph structure is maintained as dictionary of nodes + // and array of links. Each node has 'links' property which + // hold all links related to that node. And general links + // array is used to speed up all links enumeration. This is inefficient + // in terms of memory, but simplifies coding. + options = options || {}; + if ('uniqueLinkId' in options) { + console.warn( + 'ngraph.graph: Starting from version 0.14 `uniqueLinkId` is deprecated.\n' + + 'Use `multigraph` option instead\n', + '\n', + 'Note: there is also change in default behavior: From now own each graph\n'+ + 'is considered to be not a multigraph by default (each edge is unique).' + ); + + options.multigraph = options.uniqueLinkId; + } + + // Dear reader, the non-multigraphs do not guarantee that there is only + // one link for a given pair of node. When this option is set to false + // we can save some memory and CPU (18% faster for non-multigraph); + if (options.multigraph === undefined) options.multigraph = false; + + var nodes = typeof Object.create === 'function' ? Object.create(null) : {}, + links = [], + // Hash of multi-edges. Used to track ids of edges between same nodes + multiEdges = {}, + nodesCount = 0, + suspendEvents = 0, + + forEachNode = createNodeIterator(), + createLink = options.multigraph ? createUniqueLink : createSingleLink, + + // Our graph API provides means to listen to graph changes. Users can subscribe + // to be notified about changes in the graph by using `on` method. However + // in some cases they don't use it. To avoid unnecessary memory consumption + // we will not record graph changes until we have at least one subscriber. + // Code below supports this optimization. + // + // Accumulates all changes made during graph updates. + // Each change element contains: + // changeType - one of the strings: 'add', 'remove' or 'update'; + // node - if change is related to node this property is set to changed graph's node; + // link - if change is related to link this property is set to changed graph's link; + changes = [], + recordLinkChange = noop, + recordNodeChange = noop, + enterModification = noop, + exitModification = noop; + + // this is our public API: + var graphPart = { + /** + * Adds node to the graph. If node with given id already exists in the graph + * its data is extended with whatever comes in 'data' argument. + * + * @param nodeId the node's identifier. A string or number is preferred. + * @param [data] additional data for the node being added. If node already + * exists its data object is augmented with the new one. + * + * @return {node} The newly added node or node with given id if it already exists. + */ + addNode: addNode, - var p0 = points[p0Idx]; - // let be the remaining points - points.splice(p0Idx, 1); - // sorted by polar angle in counterclockwise order around p0 - var sortedPoints = polarAngleSort(p0, points); - if (sortedPoints.length < 2) { - return sortedPoints; - } + /** + * Adds a link to the graph. The function always create a new + * link between two nodes. If one of the nodes does not exists + * a new node is created. + * + * @param fromId link start node id; + * @param toId link end node id; + * @param [data] additional data to be set on the new link; + * + * @return {link} The newly created link + */ + addLink: addLink, - // let S be empty stack - var s = []; - s.push(p0); - s.push(sortedPoints[0]); - s.push(sortedPoints[1]); - var sLength = s.length; - for (i = 2; i < sortedPoints.length; ++i) { - while (!ccw(s[sLength - 2], s[sLength - 1], sortedPoints[i])) { - s.pop(); - sLength -= 1; - } + /** + * Removes link from the graph. If link does not exist does nothing. + * + * @param link - object returned by addLink() or getLinks() methods. + * + * @returns true if link was removed; false otherwise. + */ + removeLink: removeLink, - s.push(sortedPoints[i]); - sLength += 1; - } + /** + * Removes node with given id from the graph. If node does not exist in the graph + * does nothing. + * + * @param nodeId node's identifier passed to addNode() function. + * + * @returns true if node was removed; false otherwise. + */ + removeNode: removeNode, + + /** + * Gets node with given identifier. If node does not exist undefined value is returned. + * + * @param nodeId requested node identifier; + * + * @return {node} in with requested identifier or undefined if no such node exists. + */ + getNode: getNode, + + /** + * Gets number of nodes in this graph. + * + * @return number of nodes in the graph. + */ + getNodesCount: function () { + return nodesCount; + }, + + /** + * Gets total number of links in the graph. + */ + getLinksCount: function () { + return links.length; + }, + + /** + * Gets all links (inbound and outbound) from the node with given id. + * If node with given id is not found null is returned. + * + * @param nodeId requested node identifier. + * + * @return Array of links from and to requested node if such node exists; + * otherwise null is returned. + */ + getLinks: getLinks, + + /** + * Invokes callback on each node of the graph. + * + * @param {Function(node)} callback Function to be invoked. The function + * is passed one argument: visited node. + */ + forEachNode: forEachNode, + + /** + * Invokes callback on every linked (adjacent) node to the given one. + * + * @param nodeId Identifier of the requested node. + * @param {Function(node, link)} callback Function to be called on all linked nodes. + * The function is passed two parameters: adjacent node and link object itself. + * @param oriented if true graph treated as oriented. + */ + forEachLinkedNode: forEachLinkedNode, + + /** + * Enumerates all links in the graph + * + * @param {Function(link)} callback Function to be called on all links in the graph. + * The function is passed one parameter: graph's link object. + * + * Link object contains at least the following fields: + * fromId - node id where link starts; + * toId - node id where link ends, + * data - additional data passed to graph.addLink() method. + */ + forEachLink: forEachLink, + + /** + * Suspend all notifications about graph changes until + * endUpdate is called. + */ + beginUpdate: enterModification, + + /** + * Resumes all notifications about graph changes and fires + * graph 'changed' event in case there are any pending changes. + */ + endUpdate: exitModification, + + /** + * Removes all nodes and links from the graph. + */ + clear: clear, + + /** + * Detects whether there is a link between two nodes. + * Operation complexity is O(n) where n - number of links of a node. + * NOTE: this function is synonim for getLink() + * + * @returns link if there is one. null otherwise. + */ + hasLink: getLink, + + /** + * Detects whether there is a node with given id + * + * Operation complexity is O(1) + * NOTE: this function is synonim for getNode() + * + * @returns node if there is one; Falsy value otherwise. + */ + hasNode: getNode, + + /** + * Gets an edge between two nodes. + * Operation complexity is O(n) where n - number of links of a node. + * + * @param {string} fromId link start identifier + * @param {string} toId link end identifier + * + * @returns link if there is one. null otherwise. + */ + getLink: getLink + }; - return s; + // this will add `on()` and `fire()` methods. + eventify(graphPart); + + monitorSubscribers(); + + return graphPart; + + function monitorSubscribers() { + var realOn = graphPart.on; + + // replace real `on` with our temporary on, which will trigger change + // modification monitoring: + graphPart.on = on; + + function on() { + // now it's time to start tracking stuff: + graphPart.beginUpdate = enterModification = enterModificationReal; + graphPart.endUpdate = exitModification = exitModificationReal; + recordLinkChange = recordLinkChangeReal; + recordNodeChange = recordNodeChangeReal; + + // this will replace current `on` method with real pub/sub from `eventify`. + graphPart.on = realOn; + // delegate to real `on` handler: + return realOn.apply(graphPart, arguments); + } + } + + function recordLinkChangeReal(link, changeType) { + changes.push({ + link: link, + changeType: changeType + }); + } + + function recordNodeChangeReal(node, changeType) { + changes.push({ + node: node, + changeType: changeType + }); + } + + function addNode(nodeId, data) { + if (nodeId === undefined) { + throw new Error('Invalid node identifier'); + } + + enterModification(); + + var node = getNode(nodeId); + if (!node) { + node = new Node(nodeId, data); + nodesCount++; + recordNodeChange(node, 'add'); + } else { + node.data = data; + recordNodeChange(node, 'update'); + } + + nodes[nodeId] = node; + + exitModification(); + return node; + } + + function getNode(nodeId) { + return nodes[nodeId]; + } + + function removeNode(nodeId) { + var node = getNode(nodeId); + if (!node) { + return false; + } + + enterModification(); + + var prevLinks = node.links; + if (prevLinks) { + node.links = null; + for(var i = 0; i < prevLinks.length; ++i) { + removeLink(prevLinks[i]); + } + } + + delete nodes[nodeId]; + nodesCount--; + + recordNodeChange(node, 'remove'); + + exitModification(); + + return true; + } + + + function addLink(fromId, toId, data) { + enterModification(); + + var fromNode = getNode(fromId) || addNode(fromId); + var toNode = getNode(toId) || addNode(toId); + + var link = createLink(fromId, toId, data); + + links.push(link); + + // TODO: this is not cool. On large graphs potentially would consume more memory. + addLinkToNode(fromNode, link); + if (fromId !== toId) { + // make sure we are not duplicating links for self-loops + addLinkToNode(toNode, link); + } + + recordLinkChange(link, 'add'); + + exitModification(); + + return link; + } + + function createSingleLink(fromId, toId, data) { + var linkId = makeLinkId(fromId, toId); + return new Link(fromId, toId, data, linkId); + } + + function createUniqueLink(fromId, toId, data) { + // TODO: Get rid of this method. + var linkId = makeLinkId(fromId, toId); + var isMultiEdge = multiEdges.hasOwnProperty(linkId); + if (isMultiEdge || getLink(fromId, toId)) { + if (!isMultiEdge) { + multiEdges[linkId] = 0; + } + var suffix = '@' + (++multiEdges[linkId]); + linkId = makeLinkId(fromId + suffix, toId + suffix); + } + + return new Link(fromId, toId, data, linkId); + } + + function getLinks(nodeId) { + var node = getNode(nodeId); + return node ? node.links : null; + } + + function removeLink(link) { + if (!link) { + return false; + } + var idx = indexOfElementInArray(link, links); + if (idx < 0) { + return false; + } + + enterModification(); + + links.splice(idx, 1); + + var fromNode = getNode(link.fromId); + var toNode = getNode(link.toId); + + if (fromNode) { + idx = indexOfElementInArray(link, fromNode.links); + if (idx >= 0) { + fromNode.links.splice(idx, 1); + } + } + + if (toNode) { + idx = indexOfElementInArray(link, toNode.links); + if (idx >= 0) { + toNode.links.splice(idx, 1); + } + } + + recordLinkChange(link, 'remove'); + + exitModification(); + + return true; + } + + function getLink(fromNodeId, toNodeId) { + // TODO: Use sorted links to speed this up + var node = getNode(fromNodeId), + i; + if (!node || !node.links) { + return null; + } + + for (i = 0; i < node.links.length; ++i) { + var link = node.links[i]; + if (link.fromId === fromNodeId && link.toId === toNodeId) { + return link; + } + } + + return null; // no link. + } + + function clear() { + enterModification(); + forEachNode(function(node) { + removeNode(node.id); + }); + exitModification(); + } + + function forEachLink(callback) { + var i, length; + if (typeof callback === 'function') { + for (i = 0, length = links.length; i < length; ++i) { + callback(links[i]); + } + } + } + + function forEachLinkedNode(nodeId, callback, oriented) { + var node = getNode(nodeId); + + if (node && node.links && typeof callback === 'function') { + if (oriented) { + return forEachOrientedLink(node.links, nodeId, callback); + } else { + return forEachNonOrientedLink(node.links, nodeId, callback); + } + } + } + + function forEachNonOrientedLink(links, nodeId, callback) { + var quitFast; + for (var i = 0; i < links.length; ++i) { + var link = links[i]; + var linkedNodeId = link.fromId === nodeId ? link.toId : link.fromId; + + quitFast = callback(nodes[linkedNodeId], link); + if (quitFast) { + return true; // Client does not need more iterations. Break now. + } + } + } + + function forEachOrientedLink(links, nodeId, callback) { + var quitFast; + for (var i = 0; i < links.length; ++i) { + var link = links[i]; + if (link.fromId === nodeId) { + quitFast = callback(nodes[link.toId], link); + if (quitFast) { + return true; // Client does not need more iterations. Break now. } - }; -};/** - * Very generic rectangle. + } + } + } + + // we will not fire anything until users of this library explicitly call `on()` + // method. + function noop() {} + + // Enter, Exit modification allows bulk graph updates without firing events. + function enterModificationReal() { + suspendEvents += 1; + } + + function exitModificationReal() { + suspendEvents -= 1; + if (suspendEvents === 0 && changes.length > 0) { + graphPart.fire('changed', changes); + changes.length = 0; + } + } + + function createNodeIterator() { + // Object.keys iterator is 1.3x faster than `for in` loop. + // See `https://github.com/anvaka/ngraph.graph/tree/bench-for-in-vs-obj-keys` + // branch for perf test + return Object.keys ? objectKeysIterator : forInIterator; + } + + function objectKeysIterator(callback) { + if (typeof callback !== 'function') { + return; + } + + var keys = Object.keys(nodes); + for (var i = 0; i < keys.length; ++i) { + if (callback(nodes[keys[i]])) { + return true; // client doesn't want to proceed. Return. + } + } + } + + function forInIterator(callback) { + if (typeof callback !== 'function') { + return; + } + var node; + + for (node in nodes) { + if (callback(nodes[node])) { + return true; // client doesn't want to proceed. Return. + } + } + } +} + +// need this for old browsers. Should this be a separate module? +function indexOfElementInArray(element, array) { + if (!array) return -1; + + if (array.indexOf) { + return array.indexOf(element); + } + + var len = array.length, + i; + + for (i = 0; i < len; i += 1) { + if (array[i] === element) { + return i; + } + } + + return -1; +} + +/** + * Internal structure to represent node; */ -Viva.Graph.Rect = function (x1, y1, x2, y2) { - this.x1 = x1 || 0; - this.y1 = y1 || 0; - this.x2 = x2 || 0; - this.y2 = y2 || 0; -}; +function Node(id, data) { + this.id = id; + this.links = null; + this.data = data; +} + +function addLinkToNode(node, link) { + if (node.links) { + node.links.push(link); + } else { + node.links = [link]; + } +} /** - * Very generic two-dimensional point. + * Internal structure to represent links; */ -Viva.Graph.Point2d = function (x, y) { - this.x = x || 0; - this.y = y || 0; -}; +function Link(fromId, toId, data, id) { + this.fromId = fromId; + this.toId = toId; + this.data = data; + this.id = id; +} + +function hashCode(str) { + var hash = 0, i, chr, len; + if (str.length == 0) return hash; + for (i = 0, len = str.length; i < len; i++) { + chr = str.charCodeAt(i); + hash = ((hash << 5) - hash) + chr; + hash |= 0; // Convert to 32bit integer + } + return hash; +} + +function makeLinkId(fromId, toId) { + return fromId.toString() + 'šŸ‘‰ ' + toId.toString(); +} + +},{"ngraph.events":9}],17:[function(require,module,exports){ +module.exports = merge; /** - * Internal structure to represent node; + * Augments `target` with properties in `options`. Does not override + * target's properties if they are defined and matches expected type in + * options + * + * @returns {Object} merged object */ -Viva.Graph.Node = function (id) { - this.id = id; - this.links = []; - this.data = null; +function merge(target, options) { + var key; + if (!target) { target = {}; } + if (options) { + for (key in options) { + if (options.hasOwnProperty(key)) { + var targetHasIt = target.hasOwnProperty(key), + optionsValueType = typeof options[key], + shouldReplace = !targetHasIt || (typeof target[key] !== optionsValueType); + + if (shouldReplace) { + target[key] = options[key]; + } else if (optionsValueType === 'object') { + // go deep, don't care about loops here, we are simple API!: + target[key] = merge(target[key], options[key]); + } + } + } + } + + return target; +} + +},{}],18:[function(require,module,exports){ +module.exports = { + Body: Body, + Vector2d: Vector2d, + Body3d: Body3d, + Vector3d: Vector3d +}; + +function Body(x, y) { + this.pos = new Vector2d(x, y); + this.prevPos = new Vector2d(x, y); + this.force = new Vector2d(); + this.velocity = new Vector2d(); + this.mass = 1; +} + +Body.prototype.setPosition = function (x, y) { + this.prevPos.x = this.pos.x = x; + this.prevPos.y = this.pos.y = y; +}; + +function Vector2d(x, y) { + if (x && typeof x !== 'number') { + // could be another vector + this.x = typeof x.x === 'number' ? x.x : 0; + this.y = typeof x.y === 'number' ? x.y : 0; + } else { + this.x = typeof x === 'number' ? x : 0; + this.y = typeof y === 'number' ? y : 0; + } +} + +Vector2d.prototype.reset = function () { + this.x = this.y = 0; }; +function Body3d(x, y, z) { + this.pos = new Vector3d(x, y, z); + this.prevPos = new Vector3d(x, y, z); + this.force = new Vector3d(); + this.velocity = new Vector3d(); + this.mass = 1; +} + +Body3d.prototype.setPosition = function (x, y, z) { + this.prevPos.x = this.pos.x = x; + this.prevPos.y = this.pos.y = y; + this.prevPos.z = this.pos.z = z; +}; + +function Vector3d(x, y, z) { + if (x && typeof x !== 'number') { + // could be another vector + this.x = typeof x.x === 'number' ? x.x : 0; + this.y = typeof x.y === 'number' ? x.y : 0; + this.z = typeof x.z === 'number' ? x.z : 0; + } else { + this.x = typeof x === 'number' ? x : 0; + this.y = typeof y === 'number' ? y : 0; + this.z = typeof z === 'number' ? z : 0; + } +}; + +Vector3d.prototype.reset = function () { + this.x = this.y = this.z = 0; +}; + +},{}],19:[function(require,module,exports){ /** - * Internal structure to represent links; + * Manages a simulation of physical forces acting on bodies and springs. */ -Viva.Graph.Link = function (fromId, toId, data) { - this.fromId = fromId; - this.toId = toId; - this.data = data; -};/** - * @fileOverview Contains definition of the core graph object. - * - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com - */ - -/** - * @namespace Represents a graph data structure. - * - * @example - * var g = Viva.Graph.graph(); - * g.addNode(1); // g has one node. - * g.addLink(2, 3); // now g contains three nodes and one link. - * - */ -Viva.Graph.graph = function () { - - // Graph structure is maintained as dictionary of nodes - // and array of links. Each node has 'links' property which - // hold all links related to that node. And general links - // array is used to speed up all links enumeration. This is inefficient - // in terms of memory, but simplifies coding. Furthermore, the graph structure - // is isolated from outter world, and can be changed to adjacency matrix later. - - var nodes = {}, - links = [], - nodesCount = 0, - suspendEvents = 0, - - // Accumlates all changes made during graph updates. - // Each change element contains: - // changeType - one of the strings: 'add', 'remove' or 'update'; - // node - if change is related to node this property is set to changed graph's node; - // link - if change is related to link this property is set to changed graph's link; - changes = [], - - fireGraphChanged = function (graph) { - // TODO: maybe we shall copy changes? - graph.fire('changed', changes); - }, - - // Enter, Exit Mofidication allows bulk graph updates without firing events. - enterModification = function () { - suspendEvents += 1; - }, - - exitModification = function (graph) { - suspendEvents -= 1; - if (suspendEvents === 0 && changes.length > 0) { - fireGraphChanged(graph); - changes.length = 0; - } - }, - - recordNodeChange = function (node, changeType) { - // TODO: Could add changeType verification. - changes.push({node : node, changeType : changeType}); - }, - - recordLinkChange = function (link, changeType) { - // TODO: Could add change type verification; - changes.push({link : link, changeType : changeType}); - }, - - isArray = function (value) { - return value && - typeof value === 'object' && - typeof value.length === 'number' && - typeof value.splice === 'function' && - !(value.propertyIsEnumerable('length')); - }; - - /** @scope Viva.Graph.graph */ - var graphPart = { - - /** - * Adds node to the graph. If node with given id already exists in the graph - * its data is extended with whatever comes in 'data' argument. - * - * @param nodeId the node's identifier. A string is preferred. - * @param [data] additional data for the node being added. If node already - * exists its data object is augmented with the new one. - * - * @return {node} The newly added node or node with given id if it already exists. - */ - addNode : function (nodeId, data) { - if (typeof nodeId === 'undefined') { - throw { - message: 'Invalid node identifier' - }; - } - - enterModification(); - - var node = this.getNode(nodeId); - if (!node) { - node = new Viva.Graph.Node(nodeId); - nodesCount++; - - recordNodeChange(node, 'add'); - } else { - recordNodeChange(node, 'update'); - } - - if (data) { - var augmentedData = node.data || {}, - dataType = typeof data, - name; - - if (dataType === 'string' || isArray(data) || - dataType === 'number' || dataType === 'boolean') { - augmentedData = data; - } else if (dataType === 'undefined') { - augmentedData = null; - } else { - for (name in data) { - if (data.hasOwnProperty(name)) { - augmentedData[name] = data[name]; - } - } - } - - node.data = augmentedData; - } - - nodes[nodeId] = node; - - exitModification(this); - return node; - }, - - /** - * Adds a link to the graph. The function always create a new - * link between two nodes. If one of the nodes does not exists - * a new node is created. - * - * @param fromId link start node id; - * @param toId link end node id; - * @param [data] additional data to be set on the new link; - * - * @return {link} The newly created link - */ - addLink : function (fromId, toId, data) { - enterModification(); - - var fromNode = this.getNode(fromId) || this.addNode(fromId); - var toNode = this.getNode(toId) || this.addNode(toId); - - var link = new Viva.Graph.Link(fromId, toId, data); - - links.push(link); - - // TODO: this is not cool. On large graphs potentially would consume more memory. - fromNode.links.push(link); - toNode.links.push(link); - - recordLinkChange(link, 'add'); - - exitModification(this); - - return link; - }, - - /** - * Removes link from the graph. If link does not exist does nothing. - * - * @param link - object returned by addLink() or getLinks() methods. - * - * @returns true if link was removed; false otherwise. - */ - removeLink : function (link) { - if (!link) { return false; } - var idx = Viva.Graph.Utils.indexOfElementInArray(link, links); - if (idx < 0) { return false; } - - enterModification(); - - links.splice(idx, 1); - - var fromNode = this.getNode(link.fromId); - var toNode = this.getNode(link.toId); - - if (fromNode) { - idx = Viva.Graph.Utils.indexOfElementInArray(link, fromNode.links); - if (idx >= 0) { - fromNode.links.splice(idx, 1); - } - } - - if (toNode) { - idx = Viva.Graph.Utils.indexOfElementInArray(link, toNode.links); - if (idx >= 0) { - toNode.links.splice(idx, 1); - } - } - - recordLinkChange(link, 'remove'); - - exitModification(this); - - return true; - }, - - /** - * Removes node with given id from the graph. If node does not exist in the graph - * does nothing. - * - * @param nodeId node's identifier passed to addNode() function. - * - * @returns true if node was removed; false otherwise. - */ - removeNode: function (nodeId) { - var node = this.getNode(nodeId); - if (!node) { return false; } - - enterModification(); - - while (node.links.length) { - var link = node.links[0]; - this.removeLink(link); - } - - nodes[nodeId] = null; - delete nodes[nodeId]; - nodesCount--; - - recordNodeChange(node, 'remove'); - - exitModification(this); - }, - - /** - * Gets node with given identifier. If node does not exist undefined value is returned. - * - * @param nodeId requested node identifier; - * - * @return {node} in with requested identifier or undefined if no such node exists. - */ - getNode : function (nodeId) { - return nodes[nodeId]; - }, - - /** - * Gets number of nodes in this graph. - * - * @return number of nodes in the graph. - */ - getNodesCount : function () { - return nodesCount; - }, - - /** - * Gets total number of links in the graph. - */ - getLinksCount : function () { - return links.length; - }, - - /** - * Gets all links (inbound and outbound) from the node with given id. - * If node with given id is not found null is returned. - * - * @param nodeId requested node identifier. - * - * @return Array of links from and to requested node if such node exists; - * otherwise null is returned. - */ - getLinks : function (nodeId) { - var node = this.getNode(nodeId); - return node ? node.links : null; - }, - - /** - * Invokes callback on each node of the graph. - * - * @param {Function(node)} callback Function to be invoked. The function - * is passed one argument: visited node. - */ - forEachNode : function (callback) { - if (typeof callback !== 'function') { - return; - } - var node; - - // TODO: could it be faster for nodes iteration if we had indexed access? - // I.e. use array + 'for' iterator instead of dictionary + 'for .. in'? - for (node in nodes) { - // For performance reasons you might want to sacrifice this sanity check: - if (nodes.hasOwnProperty(node)) { - if (callback(nodes[node])) { - return; // client doesn't want to proceed. return. - } - } - } - }, - - /** - * Invokes callback on every linked (adjacent) node to the given one. - * - * @param nodeId Identifier of the requested node. - * @param {Function(node, link)} callback Function to be called on all linked nodes. - * The function is passed two parameters: adjacent node and link object itself. - * @param oriented if true graph treated as oriented. - */ - forEachLinkedNode : function (nodeId, callback, oriented) { - var node = this.getNode(nodeId), - i, - link, - linkedNodeId; - - if (node && node.links && typeof callback === 'function') { - // Extraced orientation check out of the loop to increase performance - if (oriented) { - for (i = 0; i < node.links.length; ++i) { - link = node.links[i]; - if (link.fromId === nodeId) { - callback(nodes[link.toId], link); - } - } - } else { - for (i = 0; i < node.links.length; ++i) { - link = node.links[i]; - linkedNodeId = link.fromId === nodeId ? link.toId : link.fromId; - - callback(nodes[linkedNodeId], link); - } - } - } - }, - - /** - * Enumerates all links in the graph - * - * @param {Function(link)} callback Function to be called on all links in the graph. - * The function is passed one parameter: graph's link object. - * - * Link object contains at least the following fields: - * fromId - node id where link starts; - * toId - node id where link ends, - * data - additional data passed to graph.addLink() method. - */ - forEachLink : function (callback) { - var i; - if (typeof callback === 'function') { - for (i = 0; i < links.length; ++i) { - callback(links[i]); - } - } - }, - - /** - * Suspend all notifications about graph changes until - * endUpdate is called. - */ - beginUpdate : function () { - enterModification(); - }, - - /** - * Resumes all notifications about graph changes and fires - * graph 'changed' event in case there are any pending changes. - */ - endUpdate : function () { - exitModification(this); - }, - - /** - * Removes all nodes and links from the graph. - */ - clear : function () { - var that = this; - that.beginUpdate(); - that.forEachNode(function (node) { that.removeNode(node.id); }); - that.endUpdate(); - }, - - /** - * Detects whether there is a link between two nodes. - * Operation complexity is O(n) where n - number of links of a node. - * - * @returns link if there is one. null otherwise. - */ - hasLink : function (fromNodeId, toNodeId) { - // TODO: Use adjacency matrix to speed up this operation. - var node = this.getNode(fromNodeId), - i; - if (!node) { - return null; - } - - for (i = 0; i < node.links.length; ++i) { - var link = node.links[i]; - if (link.fromId === fromNodeId && link.toId === toNodeId) { - return link; - } - } - - return null; // no link. - } - }; - - // Let graph fire events before we return it to the caller. - Viva.Graph.Utils.events(graphPart).extend(); - - return graphPart; -};/** - * @fileOverview Contains collection of primitve operations under graph. - * - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com - */ - -Viva.Graph.operations = function () { - - return { - /** - * Gets graph density, which is a ratio of actual number of edges to maximum - * number of edges. I.e. graph density 1 means all nodes are connected with each other with an edge. - * Density 0 - graph has no edges. Runtime: O(1) - * - * @param graph represents oriented graph structure. - * - * @returns density of the graph if graph has nodes. NaN otherwise - */ - density : function (graph) { - var nodes = graph.getNodesCount(); - if (nodes === 0) { - return NaN; - } - - return 2 * graph.getLinksCount() / (nodes * (nodes - 1)); - } - }; -}; -Viva.Graph.Physics = Viva.Graph.Physics || {}; - -Viva.Graph.Physics.Vector = function (x, y) { - this.x = x || 0; - this.y = y || 0; -}; - -Viva.Graph.Physics.Vector.prototype = { - multiply : function (scalar) { - return new Viva.Graph.Physics.Vector(this.x * scalar, this.y * scalar); - } -}; - -Viva.Graph.Physics.Point = function (x, y) { - this.x = x || 0; - this.y = y || 0; -}; - -Viva.Graph.Physics.Point.prototype = { - add : function (point) { - return new Viva.Graph.Physics.Point(this.x + point.x, this.y + point.y); - } -}; - -Viva.Graph.Physics.Body = function () { - this.mass = 1; - this.force = new Viva.Graph.Physics.Vector(); - this.velocity = new Viva.Graph.Physics.Vector(); // For chained call use vel() method. - this.location = new Viva.Graph.Physics.Point(); // For chained calls use loc() method instead. - this.prevLocation = new Viva.Graph.Physics.Point(); // TODO: might be not always needed -}; - -Viva.Graph.Physics.Body.prototype = { - loc : function (location) { - if (location) { - this.location.x = location.x; - this.location.y = location.y; - - return this; - } - - return this.location; - }, - vel : function (velocity) { - if (velocity) { - this.velocity.x = velocity.x; - this.velocity.y = velocity.y; - - return this; - } - - return this.velocity; - } -}; - -Viva.Graph.Physics.Spring = function (body1, body2, length, coeff, weight) { - this.body1 = body1; - this.body2 = body2; - this.length = length; - this.coeff = coeff; - this.weight = weight; -}; - -Viva.Graph.Physics.QuadTreeNode = function () { - this.centerOfMass = new Viva.Graph.Physics.Point(); - this.children = []; - this.body = null; - this.hasChildren = false; - this.x1 = 0; - this.y1 = 0; - this.x2 = 0; - this.y2 = 0; -}; -Viva.Graph.Physics = Viva.Graph.Physics || {}; - -/** - * Updates velocity and position data using the Euler's method. - * It is faster than RK4 but may produce less accurate results. - * - * http://en.wikipedia.org/wiki/Euler_method - */ -Viva.Graph.Physics.eulerIntegrator = function () { - return { - /** - * Performs forces integration, using given timestep and force simulator. - * - * @returns squared distance of total position updates. - */ - integrate : function (simulator, timeStep) { - var speedLimit = simulator.speedLimit, - tx = 0, - ty = 0, - i, - max = simulator.bodies.length; - - for (i = 0; i < max; ++i) { - var body = simulator.bodies[i], - coeff = timeStep / body.mass; - - body.velocity.x += coeff * body.force.x; - body.velocity.y += coeff * body.force.y; - var vx = body.velocity.x, - vy = body.velocity.y, - v = Math.sqrt(vx * vx + vy * vy); - - if (v > speedLimit) { - body.velocity.x = speedLimit * vx / v; - body.velocity.y = speedLimit * vy / v; - } - - tx = timeStep * body.velocity.x; - ty = timeStep * body.velocity.y; - body.location.x += tx; - body.location.y += ty; - } - - return tx * tx + ty * ty; - } - }; -}; -/** - * This is Barnes Hut simulation algorithm. Implementation - * is adopted to non-recursive solution, since certain browsers - * handle recursion extremly bad. - * - * http://www.cs.princeton.edu/courses/archive/fall03/cs126/assignments/barnes-hut.html - */ -Viva.Graph.Physics.nbodyForce = function (options) { - options = Viva.lazyExtend(options || { - gravity : -1, - theta : 0.8 - }); - - // the following structures are here to reduce memory pressure - // when constructing BH-tree. - function InsertStackElement(node, body) { - this.node = node; - this.body = body; - } - - function InsertStack () { - this.stack = []; - this.popIdx = 0; - } - - InsertStack.prototype = { - isEmpty: function() { - return this.popIdx === 0; - }, - push: function (node, body) { - var item = this.stack[this.popIdx]; - if (!item) { - this.stack[this.popIdx] = new InsertStackElement(node, body); - } else { - item.node = node; - item.body = body; - } - ++this.popIdx; - }, - pop: function () { - if (this.popIdx > 0) { - return this.stack[--this.popIdx]; - } - }, - reset: function () { - this.popIdx = 0; - } - }; - - - var gravity = options.gravity, - updateQueue = [], - insertStack = new InsertStack(), - theta = options.theta, - random = Viva.random('5f4dcc3b5aa765d61d8327deb882cf99', 75, 20, 63, 0x6c, 65, 76, 65, 72), - - Node = function () { - this.body = null; - this.quads = []; - this.mass = 0; - this.massX = 0; - this.massY = 0; - this.left = 0; - this.top = 0; - this.bottom = 0; - this.right = 0; - this.isInternal = false; - }, - - nodesCache = [], - currentInCache = 0, - newNode = function () { - // To avoid pressure on GC we reuse nodes. - var node; - if (nodesCache[currentInCache]) { - node = nodesCache[currentInCache]; - node.quads[0] = null; - node.quads[1] = null; - node.quads[2] = null; - node.quads[3] = null; - node.body = null; - node.mass = node.massX = node.massY = 0; - node.left = node.right = node.top = node.bottom = 0; - node.isInternal = false; - } else { - node = new Node(); - nodesCache[currentInCache] = node; - } - - ++currentInCache; - return node; - }, - - root = newNode(), - - isSamePosition = function (point1, point2) { - // TODO: inline it? - var dx = Math.abs(point1.x - point2.x); - var dy = Math.abs(point1.y - point2.y); - - return (dx < 0.01 && dy < 0.01); - }, - - // Inserts body to the tree - insert = function (newBody) { - insertStack.reset(); - insertStack.push(root, newBody); - - while (!insertStack.isEmpty()) { - var stackItem = insertStack.pop(), - node = stackItem.node, - body = stackItem.body; - - if (node.isInternal) { - // This is internal node. Update the total mass of the node and center-of-mass. - var x = body.location.x; - var y = body.location.y; - node.mass = node.mass + body.mass; - node.massX = node.massX + body.mass * x; - node.massY = node.massY + body.mass * y; - - // Recursively insert the body in the appropriate quadrant. - // But first find the appropriate quadrant. - var quadIdx = 0, // Assume we are in the 0's quad. - left = node.left, - right = (node.right + left) / 2, - top = node.top, - bottom = (node.bottom + top) / 2; - - if (x > right) {// somewhere in the eastern part. - quadIdx = quadIdx + 1; - var oldLeft = left; - left = right; - right = right + (right - oldLeft); - } - if (y > bottom) {// and in south. - quadIdx = quadIdx + 2; - var oldTop = top; - top = bottom; - bottom = bottom + (bottom - oldTop); - } - - var child = node.quads[quadIdx]; - if (!child) { - // The node is internal but this quadrant is not taken. Add - // subnode to it. - child = newNode(); - child.left = left; - child.top = top; - child.right = right; - child.bottom = bottom; - - node.quads[quadIdx] = child; - } - - // continue searching in this quadrant. - insertStack.push(child, body); - } else if (node.body) { - // We are trying to add to the leaf node. - // To achieve this we have to convert current leaf into internal node - // and continue adding two nodes. - var oldBody = node.body; - node.body = null; // internal nodes do not cary bodies - node.isInternal = true; - - if (isSamePosition(oldBody.location, body.location)) { - // Prevent infinite subdivision by bumping one node - // slightly. I assume this operation should be quite - // rare, that's why usage of cos()/sin() shouldn't hit performance. - var newX, newY; - do { - var angle = random.nextDouble() * 2 * Math.PI; - var dx = (node.right - node.left) * 0.006 * Math.cos(angle); - var dy = (node.bottom - node.top) * 0.006 * Math.sin(angle); - - newX = oldBody.location.x + dx; - newY = oldBody.location.y + dy; - // Make sure we don't bump it out of the box. If we do, next iteration should fix it - } while (newX < node.left || newX > node.right || - newY < node.top || newY > node.bottom); - - oldBody.location.x = newX; - oldBody.location.y = newY; - } - // Next iteration should subdivide node further. - insertStack.push(node, oldBody); - insertStack.push(node, body); - } else { - // Node has no body. Put it in here. - node.body = body; - } - } - }, - - update = function (sourceBody) { - var queue = updateQueue, - v, - dx, - dy, - r, - queueLength = 1, - shiftIdx = 0, - pushIdx = 1; - - queue[0] = root; - - // TODO: looks like in rare cases this guy has infinite loop bug. To reproduce - // render K1000 (complete(1000)) with the settings: {springLength : 3, springCoeff : 0.0005, - // dragCoeff : 0.02, gravity : -1.2 } - while (queueLength) { - var node = queue[shiftIdx], - body = node.body; - - queueLength -= 1; - shiftIdx += 1; - - if (body && body !== sourceBody) { - // If the current node is an external node (and it is not source body), - // calculate the force exerted by the current node on body, and add this - // amount to body's net force. - dx = body.location.x - sourceBody.location.x; - dy = body.location.y - sourceBody.location.y; - r = Math.sqrt(dx * dx + dy * dy); - - if (r === 0) { - // Poor man's protection agains zero distance. - dx = (random.nextDouble() - 0.5) / 50; - dy = (random.nextDouble() - 0.5) / 50; - r = Math.sqrt(dx * dx + dy * dy); - } - - // This is standard gravition force calculation but we divide - // by r^3 to save two operations when normalizing force vector. - v = gravity * body.mass * sourceBody.mass / (r * r * r); - sourceBody.force.x = sourceBody.force.x + v * dx; - sourceBody.force.y = sourceBody.force.y + v * dy; - } else { - // Otherwise, calculate the ratio s / r, where s is the width of the region - // represented by the internal node, and r is the distance between the body - // and the node's center-of-mass - dx = node.massX / node.mass - sourceBody.location.x; - dy = node.massY / node.mass - sourceBody.location.y; - r = Math.sqrt(dx * dx + dy * dy); - - if (r === 0) { - // Sorry about code duplucation. I don't want to create many functions - // right away. Just want to see performance first. - dx = (random.nextDouble() - 0.5) / 50; - dy = (random.nextDouble() - 0.5) / 50; - r = Math.sqrt(dx * dx + dy * dy); - } - // If s / r < Īø, treat this internal node as a single body, and calculate the - // force it exerts on body b, and add this amount to b's net force. - if ((node.right - node.left) / r < theta) { - // in the if statement above we consider node's width only - // because the region was sqaurified during tree creation. - // Thus there is no difference between using width or height. - v = gravity * node.mass * sourceBody.mass / (r * r * r); - sourceBody.force.x = sourceBody.force.x + v * dx; - sourceBody.force.y = sourceBody.force.y + v * dy; - } else { - // Otherwise, run the procedure recursively on each of the current node's children. - - // I intentionally unfolded this loop, to save several CPU cycles. - if (node.quads[0]) { queue[pushIdx] = node.quads[0]; queueLength += 1; pushIdx += 1; } - if (node.quads[1]) { queue[pushIdx] = node.quads[1]; queueLength += 1; pushIdx += 1; } - if (node.quads[2]) { queue[pushIdx] = node.quads[2]; queueLength += 1; pushIdx += 1; } - if (node.quads[3]) { queue[pushIdx] = node.quads[3]; queueLength += 1; pushIdx += 1; } - } - } - } - }, - - init = function (forceSimulator) { - var x1 = Number.MAX_VALUE, - y1 = Number.MAX_VALUE, - x2 = Number.MIN_VALUE, - y2 = Number.MIN_VALUE, - i, - bodies = forceSimulator.bodies, - max = bodies.length; - - // To reduce quad tree depth we are looking for exact bounding box of all particles. - i = max; - while (i--) { - var x = bodies[i].location.x; - var y = bodies[i].location.y; - if (x < x1) { x1 = x; } - if (x > x2) { x2 = x; } - if (y < y1) { y1 = y; } - if (y > y2) { y2 = y; } - } - - // Squarify the bounds. - var dx = x2 - x1, - dy = y2 - y1; - if (dx > dy) { y2 = y1 + dx; } else { x2 = x1 + dy; } - - currentInCache = 0; - root = newNode(); - root.left = x1; - root.right = x2; - root.top = y1; - root.bottom = y2; - - i = max; - while (i--) { - insert(bodies[i], root); - } - }; - - return { - insert : insert, - init : init, - update : update, - options : function (newOptions) { - if (newOptions) { - if (typeof newOptions.gravity === 'number') { gravity = newOptions.gravity; } - if (typeof newOptions.theta === 'number') { theta = newOptions.theta; } - - return this; - } - - return {gravity : gravity, theta : theta}; - } - }; -};Viva.Graph.Physics.dragForce = function (options) { - if (!options) { - options = {}; - } - - var currentOptions = { - coeff : options.coeff || 0.01 - }; - - return { - init : function (forceSimulator) {}, - update : function (body) { - body.force.x -= currentOptions.coeff * body.velocity.x; - body.force.y -= currentOptions.coeff * body.velocity.y; - }, - options : function (newOptions) { - if (newOptions) { - if (typeof newOptions.coeff === 'number') { currentOptions.coeff = newOptions.coeff; } - - return this; - } - - return currentOptions; - } - }; -}; -Viva.Graph.Physics.springForce = function (currentOptions) { - currentOptions = Viva.lazyExtend(currentOptions, { - length : 50, - coeff : 0.00022 - }); - - var random = Viva.random('Random number 4.', 'Chosen by fair dice roll'); - - return { - init : function (forceSimulator) {}, - - update : function (spring) { - var body1 = spring.body1, - body2 = spring.body2, - length = spring.length < 0 ? currentOptions.length : spring.length, - dx = body2.location.x - body1.location.x, - dy = body2.location.y - body1.location.y, - r = Math.sqrt(dx * dx + dy * dy); - - if (r === 0) { - dx = (random.nextDouble() - 0.5) / 50; - dy = (random.nextDouble() - 0.5) / 50; - r = Math.sqrt(dx * dx + dy * dy); - } - - var d = r - length; - var coeff = ((!spring.coeff || spring.coeff < 0) ? currentOptions.coeff : spring.coeff) * d / r * spring.weight; - - body1.force.x += coeff * dx; - body1.force.y += coeff * dy; - - body2.force.x += -coeff * dx; - body2.force.y += -coeff * dy; - }, - - options : function (newOptions) { - if (newOptions) { - if (typeof newOptions.length === 'number') { currentOptions.length = newOptions.length; } - if (typeof newOptions.coeff === 'number') { currentOptions.coeff = newOptions.coeff; } - - return this; - } - return currentOptions; - } - }; -}; -Viva.Graph.Physics = Viva.Graph.Physics || {}; - -/** - * Manages a simulation of physical forces acting on bodies. - * To create a custom force simulator register forces of the system - * via addForce() method, choos appropriate integrator and register - * bodies. - * - * // TODO: Show example. - */ -Viva.Graph.Physics.forceSimulator = function (forceIntegrator) { - var integrator = forceIntegrator, - bodies = [], // Bodies in this simulation. - springs = [], // Springs in this simulation. - bodyForces = [], // Forces acting on bodies. - springForces = []; // Forces acting on springs. - - return { - - /** - * The speed limit allowed by this simulator. - */ - speedLimit : 1.0, - - /** - * Bodies in this simulation - */ - bodies : bodies, - - /** - * Accumulates all forces acting on the bodies and springs. - */ - accumulate : function () { - var i, j, body; - - // Reinitialize all forces - i = bodyForces.length; - while (i--) { - bodyForces[i].init(this); - } - - i = springForces.length; - while (i--) { - springForces[i].init(this); - } - - // Accumulate forces acting on bodies. - i = bodies.length; - while (i--) { - body = bodies[i]; - body.force.x = 0; - body.force.y = 0; - - for (j = 0; j < bodyForces.length; j++) { - bodyForces[j].update(body); - } - } - - // Accumulate forces acting on springs. - for (i = 0; i < springs.length; ++i) { - for (j = 0; j < springForces.length; j++) { - springForces[j].update(springs[i]); - } - } - }, - - /** - * Runs simulation for one time step. - */ - run : function (timeStep) { - this.accumulate(); - return integrator.integrate(this, timeStep); - }, - - /** - * Adds body to this simulation - * - * @param body - a new body. Bodies expected to have - * mass, force, velocity, location and prevLocation properties. - * the method does not check all this properties, for the sake of performance. - * // TODO: maybe it should check it? - */ - addBody : function (body) { - if (!body) { - throw { - message : 'Cannot add null body to force simulator' - }; - } - - bodies.push(body); // TODO: could mark simulator as dirty... - - return body; - }, - - removeBody : function (body) { - if (!body) { return false; } - - var idx = Viva.Graph.Utils.indexOfElementInArray(body, bodies); - if (idx < 0) { return false; } - - return bodies.splice(idx, 1); - }, - - /** - * Adds a spring to this simulation. - */ - addSpring: function (body1, body2, springLength, springCoefficient, springWeight) { - if (!body1 || !body2) { - throw { - message : 'Cannot add null spring to force simulator' - }; - } - - if (typeof springLength !== 'number') { - throw { - message : 'Spring length should be a number' - }; - } - springWeight = typeof springWeight === 'number' ? springWeight : 1; - - var spring = new Viva.Graph.Physics.Spring(body1, body2, springLength, springCoefficient >= 0 ? springCoefficient : -1, springWeight); - springs.push(spring); - - // TODO: could mark simulator as dirty. - return spring; - }, - - removeSpring : function (spring) { - if (!spring) { return false; } - - var idx = Viva.Graph.Utils.indexOfElementInArray(spring, springs); - if (idx < 0) { return false; } - - return springs.splice(idx, 1); - }, - - /** - * Adds a force acting on all bodies in this simulation - */ - addBodyForce: function (force) { - if (!force) { - throw { - message : 'Cannot add mighty (unknown) force to the simulator' - }; - } - - bodyForces.push(force); - }, - - /** - * Adds a spring force acting on all springs in this simulation. - */ - addSpringForce : function (force) { - if (!force) { - throw { - message : 'Cannot add unknown force to the simulator' - }; - } - - springForces.push(force); - } - }; -};// I don't like to suppress this, but I'm afraid 'force_directed_body' -// could already be used by someone. Don't want to break it now. -/* jshint camelcase:false */ - -Viva.Graph.Layout = Viva.Graph.Layout || {}; - -Viva.Graph.Layout.forceDirected = function (graph, settings) { - var STABLE_THRESHOLD = 0.001; // Maximum movement of the system which can be considered as stabilized - - if (!graph) { - throw { - message : "Graph structure cannot be undefined" - }; - } - - settings = Viva.lazyExtend(settings, { - /** - * Ideal length for links (springs in physical model). - */ - springLength : 80, - - /** - * Hook's law coefficient. 1 - solid spring. - */ - springCoeff : 0.0002, - - /** - * Coulomb's law coefficient. It's used to repel nodes thus should be negative - * if you make it positive nodes start attract each other :). - */ - gravity: -1.2, - - /** - * Theta coeffiecient from Barnes Hut simulation. Ranged between (0, 1). - * The closer it's to 1 the more nodes algorithm will have to go through. - * Setting it to one makes Barnes Hut simulation no different from - * brute-force forces calculation (each node is considered). - */ - theta : 0.8, - - /** - * Drag force coefficient. Used to slow down system, thus should be less than 1. - * The closer it is to 0 the less tight system will be. - */ - dragCoeff : 0.02 - }); - - var forceSimulator = Viva.Graph.Physics.forceSimulator(Viva.Graph.Physics.eulerIntegrator()), - - nbodyForce = Viva.Graph.Physics.nbodyForce({gravity : settings.gravity, theta: settings.theta}), - - springForce = Viva.Graph.Physics.springForce({length : settings.springLength, coeff: settings.springCoeff }), - - dragForce = Viva.Graph.Physics.dragForce({coeff: settings.dragCoeff}), - - initializationRequired = true, - - graphRect = new Viva.Graph.Rect(), - - random = Viva.random("ted.com", 103, 114, 101, 97, 116), - - getBestNodePosition = function (node) { - // TODO: Initial position could be picked better, e.g. take into - // account all neighbouring nodes/links, not only one. - // TODO: this is the same as in gem layout. consider refactoring. - var baseX = (graphRect.x1 + graphRect.x2) / 2, - baseY = (graphRect.y1 + graphRect.y2) / 2, - springLength = settings.springLength; - - if (node.links && node.links.length > 0) { - var firstLink = node.links[0], - otherNode = firstLink.fromId !== node.id ? graph.getNode(firstLink.fromId) : graph.getNode(firstLink.toId); - if (otherNode.position) { - baseX = otherNode.position.x; - baseY = otherNode.position.y; - } - } - - return { - x : baseX + random.next(springLength) - springLength / 2, - y : baseY + random.next(springLength) - springLength / 2 - }; - }, - - updateNodeMass = function (node) { - var body = node.force_directed_body; - body.mass = 1 + graph.getLinks(node.id).length / 3.0; - }, - - initNode = function (node) { - var body = node.force_directed_body; - if (!body) { - // TODO: rename position to location or location to position to be consistent with - // other places. - node.position = node.position || getBestNodePosition(node); - - body = new Viva.Graph.Physics.Body(); - node.force_directed_body = body; - updateNodeMass(node); - - body.loc(node.position); - forceSimulator.addBody(body); - } - }, - - releaseNode = function (node) { - var body = node.force_directed_body; - if (body) { - node.force_directed_body = null; - delete node.force_directed_body; - - forceSimulator.removeBody(body); - } - }, - - initLink = function (link) { - // TODO: what if bodies are not initialized? - var from = graph.getNode(link.fromId), - to = graph.getNode(link.toId); - - updateNodeMass(from); - updateNodeMass(to); - link.force_directed_spring = forceSimulator.addSpring(from.force_directed_body, to.force_directed_body, -1.0, link.weight); - }, - - releaseLink = function (link) { - var spring = link.force_directed_spring; - if (spring) { - var from = graph.getNode(link.fromId), - to = graph.getNode(link.toId); - if (from) { updateNodeMass(from); } - if (to) { updateNodeMass(to); } - - link.force_directed_spring = null; - delete link.force_directed_spring; - - forceSimulator.removeSpring(spring); - } - }, - - initSimulator = function () { - graph.forEachNode(initNode); - graph.forEachLink(initLink); - }, - - isNodePinned = function (node) { - if (!node) { - return true; - } - - return node.isPinned || (node.data && node.data.isPinned); - }, - - updateNodePositions = function () { - var x1 = Number.MAX_VALUE, - y1 = Number.MAX_VALUE, - x2 = Number.MIN_VALUE, - y2 = Number.MIN_VALUE; - if (graph.getNodesCount() === 0) { return; } - - graph.forEachNode(function (node) { - var body = node.force_directed_body; - if (!body) { - return; // TODO: maybe we shall initialize it? - } - - if (isNodePinned(node)) { - body.loc(node.position); - } - - // TODO: once again: use one name to be consistent (position vs location) - node.position.x = body.location.x; - node.position.y = body.location.y; - - if (node.position.x < x1) { x1 = node.position.x; } - if (node.position.x > x2) { x2 = node.position.x; } - if (node.position.y < y1) { y1 = node.position.y; } - if (node.position.y > y2) { y2 = node.position.y; } - }); - - graphRect.x1 = x1; - graphRect.x2 = x2; - graphRect.y1 = y1; - graphRect.y2 = y2; - }; - - forceSimulator.addSpringForce(springForce); - forceSimulator.addBodyForce(nbodyForce); - forceSimulator.addBodyForce(dragForce); - - return { - /** - * Attempts to layout graph within given number of iterations. - * - * @param {integer} [iterationsCount] number of algorithm's iterations. - */ - run : function (iterationsCount) { - var i; - iterationsCount = iterationsCount || 50; - - for (i = 0; i < iterationsCount; ++i) { - this.step(); - } - }, - - step : function () { - // we assume graph was not modified between calls. If it was - // we will have to reinitialize force simulator. - if (initializationRequired) { - initSimulator(); - initializationRequired = false; - } - - var energy = forceSimulator.run(20); - updateNodePositions(); - - return energy < STABLE_THRESHOLD; - }, - - /** - * Returns rectangle structure {x1, y1, x2, y2}, which represents - * current space occupied by graph. - */ - getGraphRect : function () { - return graphRect; - }, - - addNode : function (node) { - initNode(node); - }, - - removeNode : function (node) { - releaseNode(node); - }, - - addLink : function (link) { - initLink(link); - }, - - removeLink : function (link) { - releaseLink(link); - }, - - /** - * Request to release all resources - */ - dispose : function () { - // Because I do not have reference to all nodes - // they should be disposed externally. Probably this will change - // In future. For now just reset this flag. - initializationRequired = true; - }, - - // Layout specific methods - /** - * Gets or sets current desired length of the edge. - * - * @param length new desired length of the springs (aka edge, aka link). - * if this parameter is empty then old spring length is returned. - */ - springLength : function (length) { - if (arguments.length === 1) { - springForce.options({ length : length }); - return this; - } - - return springForce.options().length; - }, - - /** - * Gets or sets current spring coeffiсient. - * - * @param coeff new spring coeffiсient. - * if this parameter is empty then its old value returned. - */ - springCoeff : function (coeff) { - if (arguments.length === 1) { - springForce.options({ coeff : coeff }); - return this; - } - - return springForce.options().coeff; - }, - - /** - * Gets or sets current gravity in the nbody simulation. - * - * @param g new gravity constant. - * if this parameter is empty then its old value returned. - */ - gravity : function (g) { - if (arguments.length === 1) { - nbodyForce.options({ gravity : g }); - return this; - } - - return nbodyForce.options().gravity; - }, - - /** - * Gets or sets current theta value in the nbody simulation. - * - * @param t new theta coeffiсient. - * if this parameter is empty then its old value returned. - */ - theta : function (t) { - if (arguments.length === 1) { - nbodyForce.options({ theta : t }); - return this; - } - - return nbodyForce.options().theta; - }, - - /** - * Gets or sets current theta value in the nbody simulation. - * - * @param dragCoeff new drag coeffiсient. - * if this parameter is empty then its old value returned. - */ - drag : function (dragCoeff) { - if (arguments.length === 1) { - dragForce.options({ coeff : dragCoeff }); - return this; - } - - return dragForce.options().coeff; - } - }; -};Viva.Graph.Layout = Viva.Graph.Layout || {}; - -/** - * Does not really perform any layouting algorithm but is compliant - * with renderer interface. Allowing clients to provide specific positioning - * callback and get static layout of the graph - * - * @param {Viva.Graph.graph} graph to layout - * @param {Object} userSettings - */ -Viva.Graph.Layout.constant = function (graph, userSettings) { - userSettings = Viva.lazyExtend(userSettings, { - maxX : 1024, - maxY : 1024, - seed : 'Deterministic randomness made me do this' - }); - // This class simply follows API, it does not use some of the arguments: - /*jshint unused: false */ - var rand = Viva.random(userSettings.seed), - graphRect = new Viva.Graph.Rect(Number.MAX_VALUE, Number.MAX_VALUE, Number.MIN_VALUE, Number.MIN_VALUE), - - placeNodeCallback = function (node) { - return new Viva.Graph.Point2d(rand.next(userSettings.maxX), rand.next(userSettings.maxY)); - }, - - updateGraphRect = function (node, graphRect) { - if (node.position.x < graphRect.x1) { graphRect.x1 = node.position.x; } - if (node.position.x > graphRect.x2) { graphRect.x2 = node.position.x; } - if (node.position.y < graphRect.y1) { graphRect.y1 = node.position.y; } - if (node.position.y > graphRect.y2) { graphRect.y2 = node.position.y; } - }, - - ensureNodeInitialized = function (node) { - if (!node.hasOwnProperty('position')) { - node.position = placeNodeCallback(node); - } - updateGraphRect(node, graphRect); - }, - - updateNodePositions = function () { - if (graph.getNodesCount() === 0) { return; } - - graphRect.x1 = Number.MAX_VALUE; - graphRect.y1 = Number.MAX_VALUE; - graphRect.x2 = Number.MIN_VALUE; - graphRect.y2 = Number.MIN_VALUE; - - graph.forEachNode(ensureNodeInitialized); - }; - - return { - /** - * Attempts to layout graph within given number of iterations. - * - * @param {integer} [iterationsCount] number of algorithm's iterations. - * The constant layout ignores this parameter. - */ - run : function (iterationsCount) { - this.step(); - }, - - /** - * One step of layout algorithm. - */ - step : function () { - updateNodePositions(); - - return false; // no need to continue. - }, - - /** - * Returns rectangle structure {x1, y1, x2, y2}, which represents - * current space occupied by graph. - */ - getGraphRect : function () { - return graphRect; - }, - - addNode : ensureNodeInitialized, - - removeNode : function (node) { /* nop */ }, - - addLink : function (link) { /* nop */ }, - - removeLink : function (link) { /* nop */ }, - - /** - * Request to release all resources - */ - dispose : function () { /* nop */ }, - - // Layout specific methods: - - /** - * Based on argument either update default node placement callback or - * attempts to place given node using current placement callback. - * Setting new node callback triggers position update for all nodes. - * - * @param {Object} newPlaceNodeCallbackOrNode - if it is a function then - * default node placement callback is replaced with new one. Node placement - * callback has a form of function (node) {}, and is expected to return an - * object with x and y properties set to numbers. - * - * Otherwise if it's not a function the argument is treated as graph node - * and current node placement callback will be used to place it. - */ - placeNode : function (newPlaceNodeCallbackOrNode) { - if (typeof newPlaceNodeCallbackOrNode === 'function') { - placeNodeCallback = newPlaceNodeCallbackOrNode; - updateNodePositions(); - return this; - } - - // it is not a request to update placeNodeCallback, trying to place - // a node using current callback: - return placeNodeCallback(newPlaceNodeCallbackOrNode); - } - - }; -};/** - * @fileOverview Defines a graph renderer that uses CSS based drawings. - * - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com - */ - -Viva.Graph.View = Viva.Graph.View || {}; - -/** - * This is heart of the rendering. Class accepts graph to be rendered and rendering settings. - * It monitors graph changes and depicts them accordingly. - * - * @param graph - Viva.Graph.graph() object to be rendered. - * @param settings - rendering settings, composed from the following parts (with their defaults shown): - * settings = { - * // Represents a module that is capable of displaying graph nodes and links. - * // all graphics has to correspond to defined interface and can be later easily - * // replaced for specific needs (e.g. adding WebGL should be piece of cake as long - * // as WebGL has implemented required interface). See svgGraphics for example. - * // NOTE: current version supports Viva.Graph.View.cssGraphics() as well. - * graphics : Viva.Graph.View.svgGraphics(), - * - * // Where the renderer should draw graph. Container size matters, because - * // renderer will attempt center graph to that size. Also graphics modules - * // might depend on it. - * container : document.body, - * - * // Layout algorithm to be used. The algorithm is expected to comply with defined - * // interface and is expected to be iterative. Renderer will use it then to calculate - * // grpaph's layout. For examples of the interface refer to Viva.Graph.Layout.forceDirected() - * // and Viva.Graph.Layout.gem() algorithms. - * layout : Viva.Graph.Layout.forceDirected(), - * - * // Directs renderer to display links. Usually rendering links is the slowest part of this - * // library. So if you don't need to display links, consider settings this property to false. - * renderLinks : true, - * - * // Number of layout iterations to run before displaying the graph. The bigger you set this number - * // the closer to ideal position graph will apper first time. But be careful: for large graphs - * // it can freeze the browser. - * prerender : 0 - * } - */ -Viva.Graph.View.renderer = function (graph, settings) { - // TODO: This class is getting hard to understand. Consider refactoring. - // TODO: I have a technical debt here: fix scaling/recentring! Currently it's total mess. - var FRAME_INTERVAL = 30; - - settings = settings || {}; - - var layout = settings.layout, - graphics = settings.graphics, - container = settings.container, - inputManager, - animationTimer, - rendererInitialized = false, - updateCenterRequired = true, - - currentStep = 0, - totalIterationsCount = 0, - isStable = false, - userInteraction = false, - - viewPortOffset = { - x : 0, - y : 0 - }, - - transform = { - offsetX : 0, - offsetY : 0, - scale : 1 - }; - - var prepareSettings = function () { - container = container || window.document.body; - layout = layout || Viva.Graph.Layout.forceDirected(graph); - graphics = graphics || Viva.Graph.View.svgGraphics(graph, {container : container}); - - if (!settings.hasOwnProperty('renderLinks')) { - settings.renderLinks = true; - } - - settings.prerender = settings.prerender || 0; - inputManager = (graphics.inputManager || Viva.Input.domInputManager)(graph, graphics); - }, - // Cache positions object to prevent GC pressure - cachedFromPos = {x : 0, y : 0, node: null}, - cachedToPos = {x : 0, y : 0, node: null}, - cachedNodePos = { x: 0, y: 0}, - windowEvents = Viva.Graph.Utils.events(window), - publicEvents = Viva.Graph.Utils.events({}).extend(), - graphEvents, - containerDrag, - - - renderLink = function (link) { - var fromNode = graph.getNode(link.fromId), - toNode = graph.getNode(link.toId); - - if (!fromNode || !toNode) { - return; - } - - cachedFromPos.x = fromNode.position.x; - cachedFromPos.y = fromNode.position.y; - cachedFromPos.node = fromNode; - - cachedToPos.x = toNode.position.x; - cachedToPos.y = toNode.position.y; - cachedToPos.node = toNode; - - graphics.updateLinkPosition(link.ui, cachedFromPos, cachedToPos); - }, - - renderNode = function (node) { - cachedNodePos.x = node.position.x; - cachedNodePos.y = node.position.y; - - graphics.updateNodePosition(node.ui, cachedNodePos); - }, - - renderGraph = function () { - graphics.beginRender(); - if (settings.renderLinks && !graphics.omitLinksRendering) { - graph.forEachLink(renderLink); - } - - graph.forEachNode(renderNode); - graphics.endRender(); - }, - - onRenderFrame = function () { - isStable = layout.step() && !userInteraction; - renderGraph(); - - return !isStable; - }, - - renderIterations = function (iterationsCount) { - if (animationTimer) { - totalIterationsCount += iterationsCount; - return; - } - - if (iterationsCount) { - totalIterationsCount += iterationsCount; - - animationTimer = Viva.Graph.Utils.timer(function () { - return onRenderFrame(); - }, FRAME_INTERVAL); - } else { - currentStep = 0; - totalIterationsCount = 0; - animationTimer = Viva.Graph.Utils.timer(onRenderFrame, FRAME_INTERVAL); - } - }, - - resetStable = function () { - isStable = false; - animationTimer.restart(); - }, - - prerender = function () { - // To get good initial positions for the graph - // perform several prerender steps in background. - var i; - if (typeof settings.prerender === 'number' && settings.prerender > 0) { - for (i = 0; i < settings.prerender; i += 1) { - layout.step(); - } - } else { - layout.step(); // make one step to init positions property. - } - }, - - updateCenter = function () { - var graphRect = layout.getGraphRect(), - containerSize = Viva.Graph.Utils.getDimension(container); - - viewPortOffset.x = viewPortOffset.y = 0; - transform.offsetX = containerSize.width / 2 - (graphRect.x2 + graphRect.x1) / 2; - transform.offsetY = containerSize.height / 2 - (graphRect.y2 + graphRect.y1) / 2; - graphics.graphCenterChanged(transform.offsetX + viewPortOffset.x, transform.offsetY + viewPortOffset.y); - - updateCenterRequired = false; - }, - - createNodeUi = function (node) { - var nodeUI = graphics.node(node); - node.ui = nodeUI; - graphics.initNode(nodeUI); - layout.addNode(node); - - renderNode(node); - }, - - removeNodeUi = function (node) { - if (node.hasOwnProperty('ui')) { - graphics.releaseNode(node.ui); - - node.ui = null; - delete node.ui; - } - - layout.removeNode(node); - }, - - createLinkUi = function (link) { - var linkUI = graphics.link(link); - link.ui = linkUI; - graphics.initLink(linkUI); - - if (!graphics.omitLinksRendering) { - renderLink(link); - } - }, - - removeLinkUi = function (link) { - if (link.hasOwnProperty('ui')) { - graphics.releaseLink(link.ui); - link.ui = null; - delete link.ui; - } - }, - - listenNodeEvents = function (node) { - var wasPinned = false; - - // TODO: This may not be memory efficient. Consider reusing handlers object. - inputManager.bindDragNDrop(node, { - onStart : function () { - wasPinned = node.isPinned; - node.isPinned = true; - userInteraction = true; - resetStable(); - }, - onDrag : function (e, offset) { - node.position.x += offset.x / transform.scale; - node.position.y += offset.y / transform.scale; - userInteraction = true; - - renderGraph(); - }, - onStop : function () { - node.isPinned = wasPinned; - userInteraction = false; - } - }); - }, - - releaseNodeEvents = function (node) { - inputManager.bindDragNDrop(node, null); - }, - - initDom = function () { - graphics.init(container); - - graph.forEachNode(createNodeUi); - - if (settings.renderLinks) { - graph.forEachLink(createLinkUi); - } - }, - - releaseDom = function () { - graphics.release(container); - }, - - processNodeChange = function (change) { - var node = change.node; - - if (change.changeType === 'add') { - createNodeUi(node); - listenNodeEvents(node); - if (updateCenterRequired) { - updateCenter(); - } - } else if (change.changeType === 'remove') { - releaseNodeEvents(node); - removeNodeUi(node); - if (graph.getNodesCount() === 0) { - updateCenterRequired = true; // Next time when node is added - center the graph. - } - } else if (change.changeType === 'update') { - - // releaseNodeEvents(node); - // removeNodeUi(node); - - // createNodeUi(node); - // listenNodeEvents(node); - throw 'Update type is not implemented. TODO: Implement me!'; - } - }, - - processLinkChange = function (change) { - var link = change.link; - if (change.changeType === 'add') { - if (settings.renderLinks) { createLinkUi(link); } - layout.addLink(link); - } else if (change.changeType === 'remove') { - if (settings.renderLinks) { removeLinkUi(link); } - layout.removeLink(link); - } else if (change.changeType === 'update') { - // if (settings.renderLinks) { removeLinkUi(link); } - // layout.removeLink(link); - - // if (settings.renderLinks) { createLinkUi(link); } - // layout.addLink(link); - throw 'Update type is not implemented. TODO: Implement me!'; - } - }, - - onGraphChanged = function (changes) { - var i, change; - for (i = 0; i < changes.length; i += 1) { - change = changes[i]; - if (change.node) { - processNodeChange(change); - } else if (change.link) { - processLinkChange(change); - } - } - - resetStable(); - }, - - onWindowResized = function () { - updateCenter(); - onRenderFrame(); - }, - - releaseContainerDragManager = function () { - if (containerDrag) { - containerDrag.release(); - containerDrag = null; - } - }, - - releaseGraphEvents = function () { - if (graphEvents) { - // Interesting.. why is it not null? Anyway: - graphEvents.stop('changed', onGraphChanged); - graphEvents = null; - } - }, - - listenToEvents = function () { - windowEvents.on('resize', onWindowResized); - - releaseContainerDragManager(); - containerDrag = Viva.Graph.Utils.dragndrop(container); - containerDrag.onDrag(function (e, offset) { - viewPortOffset.x += offset.x; - viewPortOffset.y += offset.y; - graphics.translateRel(offset.x, offset.y); - - renderGraph(); - }); - - containerDrag.onScroll(function (e, scaleOffset, scrollPoint) { - var scaleFactor = Math.pow(1 + 0.4, scaleOffset < 0 ? -0.2 : 0.2); - transform.scale = graphics.scale(scaleFactor, scrollPoint); - - renderGraph(); - publicEvents.fire('scale', transform.scale); - }); - - graph.forEachNode(listenNodeEvents); - - releaseGraphEvents(); - graphEvents = Viva.Graph.Utils.events(graph); - graphEvents.on('changed', onGraphChanged); - }, - - stopListenToEvents = function () { - rendererInitialized = false; - releaseGraphEvents(); - releaseContainerDragManager(); - windowEvents.stop('resize', onWindowResized); - publicEvents.removeAllListeners(); - animationTimer.stop(); - - graph.forEachLink(function (link) { - if (settings.renderLinks) { removeLinkUi(link); } - layout.removeLink(link); - }); - - graph.forEachNode(function (node) { - releaseNodeEvents(node); - removeNodeUi(node); - }); - - layout.dispose(); - releaseDom(); - }; - - return { - /** - * Performs rendering of the graph. - * - * @param iterationsCount if specified renderer will run only given number of iterations - * and then stop. Otherwise graph rendering is performed infinitely. - * - * Note: if rendering stopped by used started dragging nodes or new nodes were added to the - * graph renderer will give run more iterations to reflect changes. - */ - run : function (iterationsCount) { - - if (!rendererInitialized) { - prepareSettings(); - prerender(); - - updateCenter(); - initDom(); - listenToEvents(); - - rendererInitialized = true; - } - - renderIterations(iterationsCount); - - return this; - }, - - reset : function () { - graphics.resetScale(); - updateCenter(); - transform.scale = 1; - }, - - pause : function () { - animationTimer.stop(); - }, - - resume : function () { - animationTimer.restart(); - }, - - rerender : function () { - renderGraph(); - return this; - }, - - /** - * Removes this renderer and deallocates all resources/timers - */ - dispose : function () { - stopListenToEvents(); // I quit! - }, - - on : function (eventName, callback) { - publicEvents.addEventListener(eventName, callback); - return this; - }, - - off : function (eventName, callback) { - publicEvents.removeEventListener(eventName, callback); - return this; - } - }; -}; -Viva.Graph.serializer = function () { - var checkJSON = function () { - if (typeof JSON === 'undefined' || !JSON.stringify || !JSON.parse) { - throw 'JSON serializer is not defined.'; - } - }, - - nodeTransformStore = function (node) { - return { id : node.id, data: node.data }; - }, - - linkTransformStore = function (link) { - return { - fromId : link.fromId, - toId: link.toId, - data : link.data - }; - }, - - nodeTransformLoad = function (node) { - return node; - }, - - linkTransformLoad = function (link) { - return link; - }; - - return { - /** - * Saves graph to JSON format. - * - * NOTE: ECMAScript 5 (or alike) JSON object is required to be defined - * to get proper output. - * - * @param graph to be saved in JSON format. - * @param nodeTransform optional callback function(node) which returns what should be passed into nodes collection - * @param linkTransform optional callback functions(link) which returns what should be passed into the links collection - */ - storeToJSON : function (graph, nodeTransform, linkTransform) { - if (!graph) { throw 'Graph is not defined'; } - checkJSON(); - - nodeTransform = nodeTransform || nodeTransformStore; - linkTransform = linkTransform || linkTransformStore; - - var store = { - nodes : [], - links : [] - }; - - graph.forEachNode(function (node) { store.nodes.push(nodeTransform(node)); }); - graph.forEachLink(function (link) { store.links.push(linkTransform(link)); }); - - return JSON.stringify(store); - }, - - /** - * Restores graph from JSON string created by storeToJSON() method. - * - * NOTE: ECMAScript 5 (or alike) JSON object is required to be defined - * to get proper output. - * - * @param jsonString is a string produced by storeToJSON() method. - * @param nodeTransform optional callback function(node) which accepts deserialized node and returns object with - * 'id' and 'data' properties. - * @param linkTransform optional callback functions(link) which accepts deserialized link and returns link object with - * 'fromId', 'toId' and 'data' properties. - */ - loadFromJSON : function (jsonString, nodeTransform, linkTransform) { - if (typeof jsonString !== 'string') { throw 'String expected in loadFromJSON() method'; } - checkJSON(); - - nodeTransform = nodeTransform || nodeTransformLoad; - linkTransform = linkTransform || linkTransformLoad; - - var store = JSON.parse(jsonString), - graph = Viva.Graph.graph(), - i; - - if (!store || !store.nodes || !store.links) { throw 'Passed json string does not represent valid graph'; } - - for (i = 0; i < store.nodes.length; ++i) { - var parsedNode = nodeTransform(store.nodes[i]); - if (!parsedNode.hasOwnProperty('id')) { throw 'Graph node format is invalid. Node.id is missing'; } - - graph.addNode(parsedNode.id, parsedNode.data); - } - - for (i = 0; i < store.links.length; ++i) { - var link = linkTransform(store.links[i]); - if (!link.hasOwnProperty('fromId') || !link.hasOwnProperty('toId')) { throw 'Graph link format is invalid. Both fromId and toId are required'; } - - graph.addLink(link.fromId, link.toId, link.data); - } - - return graph; - } - }; -}; -/** - * @fileOverview Centrality calcuation algorithms. - * - * @see http://en.wikipedia.org/wiki/Centrality - * - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com - */ - -Viva.Graph.centrality = function () { - var singleSourceShortestPath = function (graph, node, oriented) { - // I'm using the same naming convention used in http://www.inf.uni-konstanz.de/algo/publications/b-fabc-01.pdf - // sorry about cryptic names. - var P = {}, // predcessors lists. - S = [], - sigma = {}, - d = {}, - Q = [node.id], - v, - dV, - sigmaV, - processNode = function (w) { - // w found for the first time? - if (!d.hasOwnProperty(w.id)) { - Q.push(w.id); - d[w.id] = dV + 1; - } - // Shortest path to w via v? - if (d[w.id] === dV + 1) { - sigma[w.id] += sigmaV; - P[w.id].push(v); - } - }; - - graph.forEachNode(function (t) { - P[t.id] = []; - sigma[t.id] = 0; - }); - - d[node.id] = 0; - sigma[node.id] = 1; - - while (Q.length) { // Using BFS to find shortest paths - v = Q.shift(); - dV = d[v]; - sigmaV = sigma[v]; - - S.push(v); - graph.forEachLinkedNode(v, processNode, oriented); - } - - return { - S : S, - P : P, - sigma : sigma - }; - }, - - accumulate = function (betweenness, shortestPath, s) { - var delta = {}, - S = shortestPath.S, - i, - w, - coeff, - pW, - v; - - for (i = 0; i < S.length; i += 1) { - delta[S[i]] = 0; - } - - // S returns vertices in order of non-increasing distance from s - while (S.length) { - w = S.pop(); - coeff = (1 + delta[w]) / shortestPath.sigma[w]; - pW = shortestPath.P[w]; - - for (i = 0; i < pW.length; i += 1) { - v = pW[i]; - delta[v] += shortestPath.sigma[v] * coeff; - } - - if (w !== s) { - betweenness[w] += delta[w]; - } - } - }, - - sortBetweennes = function (b) { - var sorted = [], - key; - for (key in b) { - if (b.hasOwnProperty(key)) { - sorted.push({ key : key, value : b[key]}); - } - } - - return sorted.sort(function (x, y) { return y.value - x.value; }); - }; - - return { - - /** - * Compute the shortest-path betweenness centrality for all nodes in a graph. - * - * Betweenness centrality of a node `n` is the sum of the fraction of all-pairs - * shortest paths that pass through `n`. Runtime O(n * v) for non-weighted graphs. - * - * @see http://en.wikipedia.org/wiki/Centrality#Betweenness_centrality - * - * @see A Faster Algorithm for Betweenness Centrality. - * Ulrik Brandes, Journal of Mathematical Sociology 25(2):163-177, 2001. - * http://www.inf.uni-konstanz.de/algo/publications/b-fabc-01.pdf - * - * @see Ulrik Brandes: On Variants of Shortest-Path Betweenness - * Centrality and their Generic Computation. - * Social Networks 30(2):136-145, 2008. - * http://www.inf.uni-konstanz.de/algo/publications/b-vspbc-08.pdf - * - * @see Ulrik Brandes and Christian Pich: Centrality Estimation in Large Networks. - * International Journal of Bifurcation and Chaos 17(7):2303-2318, 2007. - * http://www.inf.uni-konstanz.de/algo/publications/bp-celn-06.pdf - * - * @param graph for which we are calculating betweenness centrality. Non-weighted graphs are only supported - */ - betweennessCentrality : function (graph) { - var betweennes = {}, - shortestPath; - graph.forEachNode(function (node) { - betweennes[node.id] = 0; - }); - - graph.forEachNode(function (node) { - shortestPath = singleSourceShortestPath(graph, node); - accumulate(betweennes, shortestPath, node); - }); - - return sortBetweennes(betweennes); - }, - - /** - * Calculates graph nodes degree centrality (in/out or both). - * - * @see http://en.wikipedia.org/wiki/Centrality#Degree_centrality - * - * @param graph for which we are calculating centrality. - * @param kind optional parameter. Valid values are - * 'in' - calculate in-degree centrality - * 'out' - calculate out-degree centrality - * - if it's not set generic degree centrality is calculated - */ - degreeCentrality : function (graph, kind) { - var calcDegFunction, - sortedDegrees = [], - result = [], - degree; - - kind = (kind || 'both').toLowerCase(); - if (kind === 'in') { - calcDegFunction = function (links, nodeId) { - var total = 0, - i; - for (i = 0; i < links.length; i += 1) { - total += (links[i].toId === nodeId) ? 1 : 0; - } - return total; - }; - } else if (kind === 'out') { - calcDegFunction = function (links, nodeId) { - var total = 0, - i; - for (i = 0; i < links.length; i += 1) { - total += (links[i].fromId === nodeId) ? 1 : 0; - } - return total; - }; - } else if (kind === 'both') { - calcDegFunction = function (links) { - return links.length; - }; - } else { - throw 'Expected centrality degree kind is: in, out or both'; - } - - graph.forEachNode(function (node) { - var links = graph.getLinks(node.id), - nodeDeg = calcDegFunction(links, node.id); - - if (!sortedDegrees.hasOwnProperty(nodeDeg)) { - sortedDegrees[nodeDeg] = [node.id]; - } else { - sortedDegrees[nodeDeg].push(node.id); - } - }); - - for (degree in sortedDegrees) { - if (sortedDegrees.hasOwnProperty(degree)) { - var nodes = sortedDegrees[degree], - j; - if (nodes) { - for (j = 0; j < nodes.length; ++j) { - result.unshift({key : nodes[j], value : parseInt(degree, 10)}); - } - } - } - } - - return result; - } - }; -};/** - * @fileOverview Community structure detection algorithms - * - * @see http://en.wikipedia.org/wiki/Community_structure - * - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com - */ - -Viva.Graph.community = function () { - return { - /** - * Implementation of Speaker-listener Label Propagation Algorithm (SLPA) of - * Jierui Xie and Boleslaw K. Szymanski. - * - * @see http://arxiv.org/pdf/1109.5720v3.pdf - * @see https://sites.google.com/site/communitydetectionslpa/ - */ - slpa : function (graph, T, r) { - var algorithm = Viva.Graph._community.slpaAlgorithm(graph, T, r); - return algorithm.run(); - } - }; -};Viva.Graph._community = {}; - -/** - * Implementation of Speaker-listener Label Propagation Algorithm (SLPA) of - * Jierui Xie and Boleslaw K. Szymanski. - * - * @see http://arxiv.org/pdf/1109.5720v3.pdf - * @see https://sites.google.com/site/communitydetectionslpa/ - */ -Viva.Graph._community.slpaAlgorithm = function (graph, T, r) { - T = T || 100; // number of evaluation iterations. Should be at least 20. Influence memory consumption by O(n * T); - r = r || 0.3; // community threshold on scale from 0 to 1. Value greater than 0.5 result in disjoint communities. - - var random = Viva.random(1331782216905), - shuffleRandom = Viva.random('Greeting goes to you, ', 'dear reader'), - - calculateCommunities = function (nodeMemory, threshold) { - var communities = []; - nodeMemory.forEachUniqueWord(function (word, count) { - if (count > threshold) { - communities.push({name : word, probability : count / T }); - } else { - return true; // stop enumeration, nothing more popular after this word. - } - }); - - return communities; - }, - - init = function (graph) { - var algoNodes = []; - graph.forEachNode(function (node) { - var memory = Viva.Graph._community.occuranceMap(random); - memory.add(node.id); - - node.slpa = { memory : memory }; - algoNodes.push(node.id); - }); - - return algoNodes; - }, - - evaluate = function (graph, nodes) { - var shuffle = Viva.randomIterator(nodes, shuffleRandom), - t, - - /** - * One iteration of SLPA. - */ - processNode = function (nodeId) { - var listner = graph.getNode(nodeId), - saidWords = Viva.Graph._community.occuranceMap(random); - - graph.forEachLinkedNode(nodeId, function (speakerNode) { - var word = speakerNode.slpa.memory.getRandomWord(); - saidWords.add(word); - }); - - // selecting the most popular label from what it observed in the current step - var heard = saidWords.getMostPopularFair(); - listner.slpa.memory.add(heard); - }; - - for (t = 0; t < T - 1; ++t) { // -1 is because one 'step' was during init phase - shuffle.forEach(processNode); - } - }, - - postProcess = function (graph) { - var communities = {}; - - graph.forEachNode(function (node) { - var nodeCommunities = calculateCommunities(node.slpa.memory, r * T), - i; - - for (i = 0; i < nodeCommunities.length; ++i) { - var communityName = nodeCommunities[i].name; - if (communities.hasOwnProperty(communityName)) { - communities[communityName].push(node.id); - } else { - communities[communityName] = [node.id]; - } - } - - node.communities = nodeCommunities; // TODO: I doesn't look right to augment node's properties. No? - - // Speaking of memory. Node memory created by slpa is really expensive. Release it: - node.slpa = null; - delete node.slpa; - }); - - return communities; - }; - - return { - - /** - * Executes SLPA algorithm. The function returns dictionary of discovered communities: - * { - * 'communityName1' : [nodeId1, nodeId2, .., nodeIdN], - * 'communityName2' : [nodeIdK1, nodeIdK2, .., nodeIdKN], - * ... - * }; - * - * After algorithm is done each node is also augmented with new property 'communities': - * - * node.communities = [ - * {name: 'communityName1', probability: 0.78}, - * {name: 'communityName2', probability: 0.63}, - * ... - * ]; - * - * 'probability' is always higher than 'r' parameter and denotes level of confidence - * with which we think node belongs to community. - * - * Runtime is O(T * m), where m is total number of edges, and T - number of algorithm iterations. - * - */ - run : function () { - var nodes = init(graph); - - evaluate(graph, nodes); - - return postProcess(graph); - } - }; -}; - -/** - * A data structure which serves as node memory during SLPA execution. The main idea is to - * simplify operations on memory such as - * - add word to memory, - * - get random word from memory, with probablity proportional to word occurrence in the memory - * - get the most popular word in memory - * - * TODO: currently this structure is extremely inefficient in terms of memory. I think it could be - * optimized. - */ -Viva.Graph._community.occuranceMap = function (random) { - random = random || Viva.random(); - - var wordsCount = {}, - allWords = [], - dirtyPopularity = false, - uniqueWords = [], - - rebuildPopularityArray = function () { - var key; - - uniqueWords.length = 0; - for (key in wordsCount) { - if (wordsCount.hasOwnProperty(key)) { - uniqueWords.push(key); - } - } - - uniqueWords.sort(function (x, y) { - var result = wordsCount[y] - wordsCount[x]; - if (result) { - return result; - } - - // Not only number of occurances matters but order of keys also does. - // for ... in implementation in different browsers results in different - // order, and if we want to have same categories accross all browsers - // we should order words by key names too: - if (x < y) { return -1; } - if (x > y) { return 1; } - - return 0; - }); - }, - - ensureUniqueWordsUpdated = function () { - if (dirtyPopularity) { - rebuildPopularityArray(); - dirtyPopularity = false; - } - }; - - return { - - /** - * Adds a new word to the collection of words. - */ - add : function (word) { - word = String(word); - if (wordsCount.hasOwnProperty(word)) { - wordsCount[word] += 1; - } else { - wordsCount[word] = 1; - } - - allWords.push(word); - dirtyPopularity = true; - }, - - /** - * Gets number of occurances for a given word. If word is not present in the dictionary - * zero is returned. - */ - getWordCount : function (word) { - return wordsCount[word] || 0; - }, - - /** - * Gets the most popular word in the map. If multiple words are at the same position - * random word among them is choosen. - * - */ - getMostPopularFair : function () { - if (allWords.length === 1) { - return allWords[0]; // optimizes speed for simple case. - } - - ensureUniqueWordsUpdated(); - - var maxCount = 0, - i; - - for (i = 1; i < uniqueWords.length; ++i) { - if (wordsCount[uniqueWords[i - 1]] !== wordsCount[uniqueWords[i]]) { - break; // other words are less popular... not interested. - } else { - maxCount += 1; - } - } - - maxCount += 1; // to include upper bound. i.e. random words between [0, maxCount] (not [0, maxCount) ). - return uniqueWords[random.next(maxCount)]; - }, - - /** - * Selects a random word from map with probability proportional - * to the occurrence frequency of words. - */ - getRandomWord : function () { - if (allWords.length === 0) { - throw 'The occurance map is empty. Cannot get empty word'; - } - - return allWords[random.next(allWords.length)]; - }, - - /** - * Enumerates all unique words in the map, and calls - * callback(word, occuranceCount) function on each word. Callback - * can return true value to stop enumeration. - * - * Note: enumeration is guaranteed in to run in decreasing order. - */ - forEachUniqueWord : function (callback) { - if (typeof callback !== 'function') { - throw 'Function callback is expected to enumerate all words'; - } - var i; - - ensureUniqueWordsUpdated(); - - for (i = 0; i < uniqueWords.length; ++i) { - var word = uniqueWords[i], - count = wordsCount[word]; - - var stop = callback(word, count); - if (stop) { - break; - } - } - } - }; -};/** - * @fileOverview Contains collection of graph generators. - * - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com - */ - -Viva.Graph.generator = function () { - - return { - /** - * Generates complete graph Kn. - * - * @param n represents number of nodes in the complete graph. - */ - complete : function (n) { - if (!n || n < 1) { - throw { message: "At least two nodes expected for complete graph" }; - } - - var g = Viva.Graph.graph(), - i, - j; - - g.Name = "Complete K" + n; - - for (i = 0; i < n; ++i) { - for (j = i + 1; j < n; ++j) { - if (i !== j) { - g.addLink(i, j); - } - } - } - - return g; - }, - - /** - * Generates complete bipartite graph K n,m. Each node in the - * first partition is connected to all nodes in the second partition. - * - * @param n represents number of nodes in the first graph partition - * @param m represents number of nodes in the second graph partition - */ - completeBipartite : function (n, m) { - if (!n || !m || n < 0 || m < 0) { - throw { message: "Graph dimensions are invalid. Number of nodes in each partition should be greate than 0" }; - } - - var g = Viva.Graph.graph(), - i, - j; - - g.Name = "Complete K " + n + "," + m; - for (i = 0; i < n; ++i) { - for (j = n; j < n + m; ++j) { - g.addLink(i, j); - } - } - - return g; - }, - /** - * Generates a graph in a form of a ladder with n steps. - * - * @param n number of steps in the ladder. - */ - ladder : function (n) { - if (!n || n < 0) { - throw { message: "Invalid number of nodes" }; - } - - var g = Viva.Graph.graph(), - i; - g.Name = "Ladder graph " + n; - - for (i = 0; i < n - 1; ++i) { - g.addLink(i, i + 1); - // first row - g.addLink(n + i, n + i + 1); - // second row - g.addLink(i, n + i); - // ladder"s step - } - - g.addLink(n - 1, 2 * n - 1); - // last step in the ladder; - - return g; - }, - - /** - * Generates a graph in a form of a circular ladder with n steps. - * - * @param n number of steps in the ladder. - */ - circularLadder : function (n) { - if (!n || n < 0) { - throw { message: "Invalid number of nodes" }; - } - - var g = this.ladder(n); - g.Name = "Circular ladder graph " + n; - - g.addLink(0, n - 1); - g.addLink(n, 2 * n - 1); - return g; - }, - /** - * Generates a graph in a form of a grid with n rows and m columns. - * - * @param n number of rows in the graph. - * @param m number of columns in the graph. - */ - grid: function (n, m) { - var g = Viva.Graph.graph(), - i, - j; - g.Name = "Grid graph " + n + "x" + m; - for (i = 0; i < n; ++i) { - for (j = 0; j < m; ++j) { - var node = i + j * n; - if (i > 0) { g.addLink(node, i - 1 + j * n); } - if (j > 0) { g.addLink(node, i + (j - 1) * n); } - } - } - - return g; - }, - - path: function (n) { - if (!n || n < 0) { - throw { message: "Invalid number of nodes" }; - } - - var g = Viva.Graph.graph(), - i; - g.Name = "Path graph " + n; - g.addNode(0); - - for (i = 1; i < n; ++i) { - g.addLink(i - 1, i); - } - - return g; - }, - - lollipop: function (m, n) { - if (!n || n < 0 || !m || m < 0) { - throw { message: "Invalid number of nodes" }; - } - - var g = this.complete(m), - i; - g.Name = "Lollipop graph. Head x Path " + m + "x" + n; - - for (i = 0; i < n; ++i) { - g.addLink(m + i - 1, m + i); - } - - return g; - }, - - /** - * Creates balanced binary tree with n levels. - */ - balancedBinTree: function (n) { - var g = Viva.Graph.graph(), - count = Math.pow(2, n), - level; - g.Name = "Balanced bin tree graph " + n; - - for (level = 1; level < count; ++level) { - var root = level, - left = root * 2, - right = root * 2 + 1; - - g.addLink(root, left); - g.addLink(root, right); - } - - return g; - }, - /** - * Generates a graph with n nodes and 0 links. - * - * @param n number of nodes in the graph. - */ - randomNoLinks : function (n) { - if (!n || n < 0) { - throw { message: "Invalid number of nodes" }; - } - - var g = Viva.Graph.graph(), - i; - g.Name = "Random graph, no Links: " + n; - for (i = 0; i < n; ++i) { - g.addNode(i); - } - - return g; - } - }; -}; -/** - * @fileOverview Defines a graph renderer that uses CSS based drawings. - * - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com - */ -// The file tries to conform generic interface: -/*jshint unused: false */ - -Viva.Graph.View = Viva.Graph.View || {}; - -/** - * Performs css-based graph rendering. This module does not perform - * layout, but only visualizes nodes and edeges of the graph. - * - * NOTE: Most likely I will remove this graphics engine due to superior svg support. - * In certain cases it doesn't work and require further imporvments: - * * does not properly work for dragging. - * * does not support scaling. - * * does not support IE versions prior to IE9. - * - */ -Viva.Graph.View.cssGraphics = function () { - var container, // Where graph will be rendered - OLD_IE = "OLD_IE", - offsetX, - offsetY, - scaleX = 1, - scaleY = 1, - - transformName = (function () { - var browserName = Viva.BrowserInfo.browser, - prefix, - version; - - switch (browserName) { - case "mozilla": - prefix = "Moz"; - break; - case "webkit": - prefix = "webkit"; - break; - case "opera": - prefix = "O"; - break; - case "msie": - version = Viva.BrowserInfo.version.split(".")[0]; - if (version > 8) { - prefix = "ms"; - } else { - return OLD_IE; - } - break; - } - - if (prefix) { // CSS3 - return prefix + "Transform"; - } - // Unknown browser - return null; - }()), - - /** - * Returns a function (ui, x, y, angleRad). - * - * The function attempts to rotate "ui" dom element on "angleRad" radians - * and position it to "x" "y" coordinates. - * - * Operation works in most modern browsers that support transform css style - * and IE. - * */ - positionLink = (function () { - if (transformName === OLD_IE) { // This is old IE, use filters - return function (ui, x, y, angleRad) { - var cos = Math.cos(angleRad), - sin = Math.sin(angleRad); - - // IE 6, 7 and 8 are screwed up when it comes to transforms; - // I could not find justification for their choice of "floating" - // matrix transform origin. The following ugly code was written - // out of complete dispair. - if (angleRad < 0) { - angleRad = 2 * Math.PI + angleRad; - } - - if (angleRad < Math.PI / 2) { - ui.style.left = x + "px"; - ui.style.top = y + "px"; - } else if (angleRad < Math.PI) { - ui.style.left = x - (ui.clientWidth) * Math.cos(Math.PI - angleRad); - ui.style.top = y; - } else if (angleRad < (Math.PI + Math.PI / 2)) { - ui.style.left = x - (ui.clientWidth) * Math.cos(Math.PI - angleRad); - ui.style.top = y + (ui.clientWidth) * Math.sin(Math.PI - angleRad); - } else { - ui.style.left = x; - ui.style.top = y + ui.clientWidth * Math.sin(Math.PI - angleRad); - } - ui.style.filter = "progid:DXImageTransform.Microsoft.Matrix(sizingMethod=\"auto expand\"," + "M11=" + cos + ", M12=" + (-sin) + "," + "M21=" + sin + ", M22=" + cos + ");"; - }; - } - - if (transformName) { // Modern CSS3 browser - return function (ui, x, y, angleRad) { - ui.style.left = x + "px"; - ui.style.top = y + "px"; - - ui.style[transformName] = "rotate(" + angleRad + "rad)"; - ui.style[transformName + "Origin"] = "left"; - }; - } - - return function (ui, x, y, angleRad) { - // Don't know how to rotate links in other browsers. - }; - }()), - - nodeBuilder = function (node) { - var nodeUI = window.document.createElement("div"); - nodeUI.setAttribute("class", "node"); - return nodeUI; - }, - - nodePositionCallback = function (nodeUI, pos) { - // TODO: Remove magic 5. It should be half of the width or height of the node. - nodeUI.style.left = pos.x - 5 + "px"; - nodeUI.style.top = pos.y - 5 + "px"; - }, - - linkPositionCallback = function (linkUI, fromPos, toPos) { - var dx = fromPos.x - toPos.x, - dy = fromPos.y - toPos.y, - length = Math.sqrt(dx * dx + dy * dy); - - linkUI.style.height = "1px"; - linkUI.style.width = length + "px"; - - positionLink(linkUI, toPos.x, toPos.y, Math.atan2(dy, dx)); - }, - - linkBuilder = function (link) { - var linkUI = window.document.createElement("div"); - linkUI.setAttribute("class", "link"); - - return linkUI; - }, - - updateTransform = function () { - if (container) { - if (transformName && transformName !== OLD_IE) { - var transform = "matrix(" + scaleX + ", 0, 0," + scaleY + "," + offsetX + "," + offsetY + ")"; - container.style[transformName] = transform; - } else { - throw "Not implemented. TODO: Implement OLD_IE Filter based transform"; - } - } - }; - - return { - /** - * Sets the collback that creates node representation or creates a new node - * presentation if builderCallbackOrNode is not a function. - * - * @param builderCallbackOrNode a callback function that accepts graph node - * as a parameter and must return an element representing this node. OR - * if it's not a function it's treated as a node to which DOM element should be created. - * - * @returns If builderCallbackOrNode is a valid callback function, instance of this is returned; - * Otherwise a node representation is returned for the passed parameter. - */ - node : function (builderCallbackOrNode) { - if (builderCallbackOrNode && typeof builderCallbackOrNode !== "function") { - return nodeBuilder(builderCallbackOrNode); - } - - nodeBuilder = builderCallbackOrNode; - - return this; - }, - - /** - * Sets the collback that creates link representation or creates a new link - * presentation if builderCallbackOrLink is not a function. - * - * @param builderCallbackOrLink a callback function that accepts graph link - * as a parameter and must return an element representing this link. OR - * if it's not a function it's treated as a link to which DOM element should be created. - * - * @returns If builderCallbackOrLink is a valid callback function, instance of this is returned; - * Otherwise a link representation is returned for the passed parameter. - */ - link : function (builderCallbackOrLink) { - if (builderCallbackOrLink && typeof builderCallbackOrLink !== "function") { - return linkBuilder(builderCallbackOrLink); - } - - linkBuilder = builderCallbackOrLink; - return this; - }, - - /** - * Default input manager listens to DOM events to process nodes drag-n-drop - */ - inputManager : Viva.Input.domInputManager, - - /** - * Sets translate operation that should be applied to all nodes and links. - */ - graphCenterChanged : function (x, y) { - offsetX = x; - offsetY = y; - updateTransform(); - }, - - translateRel : function (dx, dy) { - offsetX += dx; - offsetY += dy; - updateTransform(); - }, - - scale : function (x, y) { - // TODO: implement me - return 1; - }, - - resetScale : function () { - // TODO: implement me - return this; - }, - - /** - * Called every before renderer starts rendering. - */ - beginRender : function () {}, - - /** - * Called every time when renderer finishes one step of rendering. - */ - endRender : function () {}, - /** - * Allows to override default position setter for the node with a new - * function. newPlaceCallback(node, position) is function which - * is used by updateNode(). - */ - placeNode : function (newPlaceCallback) { - nodePositionCallback = newPlaceCallback; - return this; - }, - - placeLink : function (newPlaceLinkCallback) { - linkPositionCallback = newPlaceLinkCallback; - return this; - }, - - /** - * Called by Viva.Graph.View.renderer to let concrete graphic output - * providers prepare to render. - */ - init : function (parentContainer) { - container = parentContainer; - updateTransform(); - }, - - /** - * Called by Viva.Graph.View.renderer to let concrete graphic output - * provider prepare to render given link of the graph - * - * @param linkUI visual representation of the link created by link() execution. - */ - initLink : function (linkUI) { - if (container.childElementCount > 0) { - container.insertBefore(linkUI, container.firstChild); - } else { - container.appendChild(linkUI); - } - }, - - /** - * Called by Viva.Graph.View.renderer to let concrete graphic output - * provider remove link from rendering surface. - * - * @param linkUI visual representation of the link created by link() execution. - **/ - releaseLink : function (linkUI) { - container.removeChild(linkUI); - }, - - /** - * Called by Viva.Graph.View.renderer to let concrete graphic output - * provider prepare to render given node of the graph. - * - * @param nodeUI visual representation of the node created by node() execution. - **/ - initNode : function (nodeUI) { - container.appendChild(nodeUI); - }, - - /** - * Called by Viva.Graph.View.renderer to let concrete graphic output - * provider remove node from rendering surface. - * - * @param nodeUI visual representation of the node created by node() execution. - **/ - releaseNode : function (nodeUI) { - container.removeChild(nodeUI); - }, - - /** - * Called by Viva.Graph.View.renderer to let concrete graphic output - * provider place given node to recommended position pos {x, y}; - */ - updateNodePosition : function (node, pos) { - nodePositionCallback(node, pos); - }, - - /** - * Called by Viva.Graph.View.renderer to let concrete graphic output - * provider place given link of the graph - */ - updateLinkPosition : function (link, fromPos, toPos) { - linkPositionCallback(link, fromPos, toPos); - } - }; -}; +module.exports = physicsSimulator; + +function physicsSimulator(settings) { + var Spring = require('./lib/spring'); + var expose = require('ngraph.expose'); + var merge = require('ngraph.merge'); + var eventify = require('ngraph.events'); + + settings = merge(settings, { + /** + * Ideal length for links (springs in physical model). + */ + springLength: 30, + + /** + * Hook's law coefficient. 1 - solid spring. + */ + springCoeff: 0.0008, + + /** + * Coulomb's law coefficient. It's used to repel nodes thus should be negative + * if you make it positive nodes start attract each other :). + */ + gravity: -1.2, + + /** + * Theta coefficient from Barnes Hut simulation. Ranged between (0, 1). + * The closer it's to 1 the more nodes algorithm will have to go through. + * Setting it to one makes Barnes Hut simulation no different from + * brute-force forces calculation (each node is considered). + */ + theta: 0.8, + + /** + * Drag force coefficient. Used to slow down system, thus should be less than 1. + * The closer it is to 0 the less tight system will be. + */ + dragCoeff: 0.02, + + /** + * Default time step (dt) for forces integration + */ + timeStep : 20, + }); + + // We allow clients to override basic factory methods: + var createQuadTree = settings.createQuadTree || require('ngraph.quadtreebh'); + var createBounds = settings.createBounds || require('./lib/bounds'); + var createDragForce = settings.createDragForce || require('./lib/dragForce'); + var createSpringForce = settings.createSpringForce || require('./lib/springForce'); + var integrate = settings.integrator || require('./lib/eulerIntegrator'); + var createBody = settings.createBody || require('./lib/createBody'); + + var bodies = [], // Bodies in this simulation. + springs = [], // Springs in this simulation. + quadTree = createQuadTree(settings), + bounds = createBounds(bodies, settings), + springForce = createSpringForce(settings), + dragForce = createDragForce(settings); + + var bboxNeedsUpdate = true; + var totalMovement = 0; // how much movement we made on last step + + var publicApi = { + /** + * Array of bodies, registered with current simulator + * + * Note: To add new body, use addBody() method. This property is only + * exposed for testing/performance purposes. + */ + bodies: bodies, + + quadTree: quadTree, + + /** + * Array of springs, registered with current simulator + * + * Note: To add new spring, use addSpring() method. This property is only + * exposed for testing/performance purposes. + */ + springs: springs, + + /** + * Returns settings with which current simulator was initialized + */ + settings: settings, + + /** + * Performs one step of force simulation. + * + * @returns {boolean} true if system is considered stable; False otherwise. + */ + step: function () { + accumulateForces(); + + var movement = integrate(bodies, settings.timeStep); + bounds.update(); + + return movement; + }, + + /** + * Adds body to the system + * + * @param {ngraph.physics.primitives.Body} body physical body + * + * @returns {ngraph.physics.primitives.Body} added body + */ + addBody: function (body) { + if (!body) { + throw new Error('Body is required'); + } + bodies.push(body); + + return body; + }, + + /** + * Adds body to the system at given position + * + * @param {Object} pos position of a body + * + * @returns {ngraph.physics.primitives.Body} added body + */ + addBodyAt: function (pos) { + if (!pos) { + throw new Error('Body position is required'); + } + var body = createBody(pos); + bodies.push(body); + + return body; + }, + + /** + * Removes body from the system + * + * @param {ngraph.physics.primitives.Body} body to remove + * + * @returns {Boolean} true if body found and removed. falsy otherwise; + */ + removeBody: function (body) { + if (!body) { return; } + + var idx = bodies.indexOf(body); + if (idx < 0) { return; } + + bodies.splice(idx, 1); + if (bodies.length === 0) { + bounds.reset(); + } + return true; + }, + + /** + * Adds a spring to this simulation. + * + * @returns {Object} - a handle for a spring. If you want to later remove + * spring pass it to removeSpring() method. + */ + addSpring: function (body1, body2, springLength, springWeight, springCoefficient) { + if (!body1 || !body2) { + throw new Error('Cannot add null spring to force simulator'); + } + + if (typeof springLength !== 'number') { + springLength = -1; // assume global configuration + } + + var spring = new Spring(body1, body2, springLength, springCoefficient >= 0 ? springCoefficient : -1, springWeight); + springs.push(spring); + + // TODO: could mark simulator as dirty. + return spring; + }, + + /** + * Returns amount of movement performed on last step() call + */ + getTotalMovement: function () { + return totalMovement; + }, + + /** + * Removes spring from the system + * + * @param {Object} spring to remove. Spring is an object returned by addSpring + * + * @returns {Boolean} true if spring found and removed. falsy otherwise; + */ + removeSpring: function (spring) { + if (!spring) { return; } + var idx = springs.indexOf(spring); + if (idx > -1) { + springs.splice(idx, 1); + return true; + } + }, + + getBestNewBodyPosition: function (neighbors) { + return bounds.getBestNewPosition(neighbors); + }, + + /** + * Returns bounding box which covers all bodies + */ + getBBox: function () { + if (bboxNeedsUpdate) { + bounds.update(); + bboxNeedsUpdate = false; + } + return bounds.box; + }, + + invalidateBBox: function () { + bboxNeedsUpdate = true; + }, + + gravity: function (value) { + if (value !== undefined) { + settings.gravity = value; + quadTree.options({gravity: value}); + return this; + } else { + return settings.gravity; + } + }, + + theta: function (value) { + if (value !== undefined) { + settings.theta = value; + quadTree.options({theta: value}); + return this; + } else { + return settings.theta; + } + } + }; + + // allow settings modification via public API: + expose(settings, publicApi); + + eventify(publicApi); + + return publicApi; + + function accumulateForces() { + // Accumulate forces acting on bodies. + var body, + i = bodies.length; + + if (i) { + // only add bodies if there the array is not empty: + quadTree.insertBodies(bodies); // performance: O(n * log n) + while (i--) { + body = bodies[i]; + // If body is pinned there is no point updating its forces - it should + // never move: + if (!body.isPinned) { + body.force.reset(); + + quadTree.updateBodyForce(body); + dragForce.update(body); + } + } + } + + i = springs.length; + while(i--) { + springForce.update(springs[i]); + } + } +}; + +},{"./lib/bounds":20,"./lib/createBody":21,"./lib/dragForce":22,"./lib/eulerIntegrator":23,"./lib/spring":24,"./lib/springForce":25,"ngraph.events":9,"ngraph.expose":10,"ngraph.merge":17,"ngraph.quadtreebh":26}],20:[function(require,module,exports){ +module.exports = function (bodies, settings) { + var random = require('ngraph.random').random(42); + var boundingBox = { x1: 0, y1: 0, x2: 0, y2: 0 }; + + return { + box: boundingBox, + + update: updateBoundingBox, + + reset : function () { + boundingBox.x1 = boundingBox.y1 = 0; + boundingBox.x2 = boundingBox.y2 = 0; + }, + + getBestNewPosition: function (neighbors) { + var graphRect = boundingBox; + + var baseX = 0, baseY = 0; + + if (neighbors.length) { + for (var i = 0; i < neighbors.length; ++i) { + baseX += neighbors[i].pos.x; + baseY += neighbors[i].pos.y; + } + + baseX /= neighbors.length; + baseY /= neighbors.length; + } else { + baseX = (graphRect.x1 + graphRect.x2) / 2; + baseY = (graphRect.y1 + graphRect.y2) / 2; + } + + var springLength = settings.springLength; + return { + x: baseX + random.next(springLength) - springLength / 2, + y: baseY + random.next(springLength) - springLength / 2 + }; + } + }; + + function updateBoundingBox() { + var i = bodies.length; + if (i === 0) { return; } // don't have to wory here. + + var x1 = Number.MAX_VALUE, + y1 = Number.MAX_VALUE, + x2 = Number.MIN_VALUE, + y2 = Number.MIN_VALUE; + + while(i--) { + // this is O(n), could it be done faster with quadtree? + // how about pinned nodes? + var body = bodies[i]; + if (body.isPinned) { + body.pos.x = body.prevPos.x; + body.pos.y = body.prevPos.y; + } else { + body.prevPos.x = body.pos.x; + body.prevPos.y = body.pos.y; + } + if (body.pos.x < x1) { + x1 = body.pos.x; + } + if (body.pos.x > x2) { + x2 = body.pos.x; + } + if (body.pos.y < y1) { + y1 = body.pos.y; + } + if (body.pos.y > y2) { + y2 = body.pos.y; + } + } + + boundingBox.x1 = x1; + boundingBox.x2 = x2; + boundingBox.y1 = y1; + boundingBox.y2 = y2; + } +} + +},{"ngraph.random":30}],21:[function(require,module,exports){ +var physics = require('ngraph.physics.primitives'); + +module.exports = function(pos) { + return new physics.Body(pos); +} + +},{"ngraph.physics.primitives":18}],22:[function(require,module,exports){ /** - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com + * Represents drag force, which reduces force value on each step by given + * coefficient. + * + * @param {Object} options for the drag force + * @param {Number=} options.dragCoeff drag force coefficient. 0.1 by default */ +module.exports = function (options) { + var merge = require('ngraph.merge'), + expose = require('ngraph.expose'); + + options = merge(options, { + dragCoeff: 0.02 + }); + + var api = { + update : function (body) { + body.force.x -= options.dragCoeff * body.velocity.x; + body.force.y -= options.dragCoeff * body.velocity.y; + } + }; + + // let easy access to dragCoeff: + expose(options, api, ['dragCoeff']); + return api; +}; + +},{"ngraph.expose":10,"ngraph.merge":17}],23:[function(require,module,exports){ /** - * Simple wrapper over svg object model API, to shorten the usage syntax. + * Performs forces integration, using given timestep. Uses Euler method to solve + * differential equation (http://en.wikipedia.org/wiki/Euler_method ). + * + * @returns {Number} squared distance of total position updates. */ -Viva.Graph.svg = function (element) { - var svgns = "http://www.w3.org/2000/svg", - xlinkns = "http://www.w3.org/1999/xlink", - svgElement = element; - if (typeof element === "string") { - svgElement = window.document.createElementNS(svgns, element); - } +module.exports = integrate; - if (svgElement.vivagraphAugmented) { - return svgElement; +function integrate(bodies, timeStep) { + var dx = 0, tx = 0, + dy = 0, ty = 0, + i, + max = bodies.length; + + if (max === 0) { + return 0; + } + + for (i = 0; i < max; ++i) { + var body = bodies[i], + coeff = timeStep / body.mass; + + body.velocity.x += coeff * body.force.x; + body.velocity.y += coeff * body.force.y; + var vx = body.velocity.x, + vy = body.velocity.y, + v = Math.sqrt(vx * vx + vy * vy); + + if (v > 1) { + body.velocity.x = vx / v; + body.velocity.y = vy / v; } - svgElement.vivagraphAugmented = true; + dx = timeStep * body.velocity.x; + dy = timeStep * body.velocity.y; + + body.pos.x += dx; + body.pos.y += dy; + + tx += Math.abs(dx); ty += Math.abs(dy); + } + + return (tx * tx + ty * ty)/max; +} + +},{}],24:[function(require,module,exports){ +module.exports = Spring; + +/** + * Represents a physical spring. Spring connects two bodies, has rest length + * stiffness coefficient and optional weight + */ +function Spring(fromBody, toBody, length, coeff, weight) { + this.from = fromBody; + this.to = toBody; + this.length = length; + this.coeff = coeff; + + this.weight = typeof weight === 'number' ? weight : 1; +}; + +},{}],25:[function(require,module,exports){ +/** + * Represents spring force, which updates forces acting on two bodies, conntected + * by a spring. + * + * @param {Object} options for the spring force + * @param {Number=} options.springCoeff spring force coefficient. + * @param {Number=} options.springLength desired length of a spring at rest. + */ +module.exports = function (options) { + var merge = require('ngraph.merge'); + var random = require('ngraph.random').random(42); + var expose = require('ngraph.expose'); - // Augment svg element (TODO: it's not safe - what if some function already exists on the prototype?): + options = merge(options, { + springCoeff: 0.0002, + springLength: 80 + }); + var api = { /** - * Gets an svg attribute from an element if value is not specified. - * OR sets a new value to the given attribute. - * - * @param name - svg attribute name; - * @param value - value to be set; - * - * @returns svg element if both name and value are specified; or value of the given attribute - * if value parameter is missing. + * Upsates forces acting on a spring */ - svgElement.attr = function (name, value) { - if (arguments.length === 2) { - if (value !== null) { - svgElement.setAttributeNS(null, name, value); - } else { - svgElement.removeAttributeNS(null, name); - } + update : function (spring) { + var body1 = spring.from, + body2 = spring.to, + length = spring.length < 0 ? options.springLength : spring.length, + dx = body2.pos.x - body1.pos.x, + dy = body2.pos.y - body1.pos.y, + r = Math.sqrt(dx * dx + dy * dy); + + if (r === 0) { + dx = (random.nextDouble() - 0.5) / 50; + dy = (random.nextDouble() - 0.5) / 50; + r = Math.sqrt(dx * dx + dy * dy); + } + + var d = r - length; + var coeff = ((!spring.coeff || spring.coeff < 0) ? options.springCoeff : spring.coeff) * d / r * spring.weight; + + body1.force.x += coeff * dx; + body1.force.y += coeff * dy; + + body2.force.x -= coeff * dx; + body2.force.y -= coeff * dy; + } + }; + + expose(options, api, ['springCoeff', 'springLength']); + return api; +} + +},{"ngraph.expose":10,"ngraph.merge":17,"ngraph.random":30}],26:[function(require,module,exports){ +/** + * This is Barnes Hut simulation algorithm for 2d case. Implementation + * is highly optimized (avoids recusion and gc pressure) + * + * http://www.cs.princeton.edu/courses/archive/fall03/cs126/assignments/barnes-hut.html + */ + +module.exports = function(options) { + options = options || {}; + options.gravity = typeof options.gravity === 'number' ? options.gravity : -1; + options.theta = typeof options.theta === 'number' ? options.theta : 0.8; - return svgElement; + // we require deterministic randomness here + var random = require('ngraph.random').random(1984), + Node = require('./node'), + InsertStack = require('./insertStack'), + isSamePosition = require('./isSamePosition'); + + var gravity = options.gravity, + updateQueue = [], + insertStack = new InsertStack(), + theta = options.theta, + + nodesCache = [], + currentInCache = 0, + root = newNode(); + + return { + insertBodies: insertBodies, + /** + * Gets root node if its present + */ + getRoot: function() { + return root; + }, + updateBodyForce: update, + options: function(newOptions) { + if (newOptions) { + if (typeof newOptions.gravity === 'number') { + gravity = newOptions.gravity; + } + if (typeof newOptions.theta === 'number') { + theta = newOptions.theta; } - return svgElement.getAttributeNS(null, name); - }; + return this; + } - svgElement.append = function (element) { - var child = Viva.Graph.svg(element); - svgElement.appendChild(child); - return child; - }; + return { + gravity: gravity, + theta: theta + }; + } + }; + + function newNode() { + // To avoid pressure on GC we reuse nodes. + var node = nodesCache[currentInCache]; + if (node) { + node.quad0 = null; + node.quad1 = null; + node.quad2 = null; + node.quad3 = null; + node.body = null; + node.mass = node.massX = node.massY = 0; + node.left = node.right = node.top = node.bottom = 0; + } else { + node = new Node(); + nodesCache[currentInCache] = node; + } - svgElement.text = function (textContent) { - if (typeof textContent !== "undefined") { - svgElement.textContent = textContent; - return svgElement; + ++currentInCache; + return node; + } + + function update(sourceBody) { + var queue = updateQueue, + v, + dx, + dy, + r, fx = 0, + fy = 0, + queueLength = 1, + shiftIdx = 0, + pushIdx = 1; + + queue[0] = root; + + while (queueLength) { + var node = queue[shiftIdx], + body = node.body; + + queueLength -= 1; + shiftIdx += 1; + var differentBody = (body !== sourceBody); + if (body && differentBody) { + // If the current node is a leaf node (and it is not source body), + // calculate the force exerted by the current node on body, and add this + // amount to body's net force. + dx = body.pos.x - sourceBody.pos.x; + dy = body.pos.y - sourceBody.pos.y; + r = Math.sqrt(dx * dx + dy * dy); + + if (r === 0) { + // Poor man's protection against zero distance. + dx = (random.nextDouble() - 0.5) / 50; + dy = (random.nextDouble() - 0.5) / 50; + r = Math.sqrt(dx * dx + dy * dy); } - return svgElement.textContent; - }; - svgElement.link = function (target) { - if (arguments.length) { - svgElement.setAttributeNS(xlinkns, "xlink:href", target); - return svgElement; + // This is standard gravition force calculation but we divide + // by r^3 to save two operations when normalizing force vector. + v = gravity * body.mass * sourceBody.mass / (r * r * r); + fx += v * dx; + fy += v * dy; + } else if (differentBody) { + // Otherwise, calculate the ratio s / r, where s is the width of the region + // represented by the internal node, and r is the distance between the body + // and the node's center-of-mass + dx = node.massX / node.mass - sourceBody.pos.x; + dy = node.massY / node.mass - sourceBody.pos.y; + r = Math.sqrt(dx * dx + dy * dy); + + if (r === 0) { + // Sorry about code duplucation. I don't want to create many functions + // right away. Just want to see performance first. + dx = (random.nextDouble() - 0.5) / 50; + dy = (random.nextDouble() - 0.5) / 50; + r = Math.sqrt(dx * dx + dy * dy); + } + // If s / r < Īø, treat this internal node as a single body, and calculate the + // force it exerts on sourceBody, and add this amount to sourceBody's net force. + if ((node.right - node.left) / r < theta) { + // in the if statement above we consider node's width only + // because the region was squarified during tree creation. + // Thus there is no difference between using width or height. + v = gravity * node.mass * sourceBody.mass / (r * r * r); + fx += v * dx; + fy += v * dy; + } else { + // Otherwise, run the procedure recursively on each of the current node's children. + + // I intentionally unfolded this loop, to save several CPU cycles. + if (node.quad0) { + queue[pushIdx] = node.quad0; + queueLength += 1; + pushIdx += 1; + } + if (node.quad1) { + queue[pushIdx] = node.quad1; + queueLength += 1; + pushIdx += 1; + } + if (node.quad2) { + queue[pushIdx] = node.quad2; + queueLength += 1; + pushIdx += 1; + } + if (node.quad3) { + queue[pushIdx] = node.quad3; + queueLength += 1; + pushIdx += 1; + } + } + } + } + + sourceBody.force.x += fx; + sourceBody.force.y += fy; + } + + function insertBodies(bodies) { + var x1 = Number.MAX_VALUE, + y1 = Number.MAX_VALUE, + x2 = Number.MIN_VALUE, + y2 = Number.MIN_VALUE, + i, + max = bodies.length; + + // To reduce quad tree depth we are looking for exact bounding box of all particles. + i = max; + while (i--) { + var x = bodies[i].pos.x; + var y = bodies[i].pos.y; + if (x < x1) { + x1 = x; + } + if (x > x2) { + x2 = x; + } + if (y < y1) { + y1 = y; + } + if (y > y2) { + y2 = y; + } + } + + // Squarify the bounds. + var dx = x2 - x1, + dy = y2 - y1; + if (dx > dy) { + y2 = y1 + dx; + } else { + x2 = x1 + dy; + } + + currentInCache = 0; + root = newNode(); + root.left = x1; + root.right = x2; + root.top = y1; + root.bottom = y2; + + i = max - 1; + if (i >= 0) { + root.body = bodies[i]; + } + while (i--) { + insert(bodies[i], root); + } + } + + function insert(newBody) { + insertStack.reset(); + insertStack.push(root, newBody); + + while (!insertStack.isEmpty()) { + var stackItem = insertStack.pop(), + node = stackItem.node, + body = stackItem.body; + + if (!node.body) { + // This is internal node. Update the total mass of the node and center-of-mass. + var x = body.pos.x; + var y = body.pos.y; + node.mass = node.mass + body.mass; + node.massX = node.massX + body.mass * x; + node.massY = node.massY + body.mass * y; + + // Recursively insert the body in the appropriate quadrant. + // But first find the appropriate quadrant. + var quadIdx = 0, // Assume we are in the 0's quad. + left = node.left, + right = (node.right + left) / 2, + top = node.top, + bottom = (node.bottom + top) / 2; + + if (x > right) { // somewhere in the eastern part. + quadIdx = quadIdx + 1; + left = right; + right = node.right; + } + if (y > bottom) { // and in south. + quadIdx = quadIdx + 2; + top = bottom; + bottom = node.bottom; } - return svgElement.getAttributeNS(xlinkns, "xlink:href"); - }; - - svgElement.children = function (selector) { - var wrappedChildren = [], - childrenCount = svgElement.childNodes.length, - i, - j; - - if (selector === undefined && svgElement.hasChildNodes()) { - for (i = 0; i < childrenCount; i++) { - wrappedChildren.push(Viva.Graph.svg(svgElement.childNodes[i])); - } - } else if (typeof selector === "string") { - var classSelector = (selector[0] === "."), - idSelector = (selector[0] === "#"), - tagSelector = !classSelector && !idSelector; - - for (i = 0; i < childrenCount; i++) { - var el = svgElement.childNodes[i]; - - // pass comments, text nodes etc. - if (el.nodeType === 1) { - var classes = el.attr("class"), - id = el.attr("id"), - tagName = el.nodeName; - - if (classSelector && classes) { - classes = classes.replace(/\s+/g, " ").split(" "); - for (j = 0; j < classes.length; j++) { - if (classSelector && classes[j] === selector.substr(1)) { - wrappedChildren.push(Viva.Graph.svg(el)); - break; - } - } - } else if (idSelector && id === selector.substr(1)) { - wrappedChildren.push(Viva.Graph.svg(el)); - break; - } else if (tagSelector && tagName === selector) { - wrappedChildren.push(Viva.Graph.svg(el)); - } + var child = getChild(node, quadIdx); + if (!child) { + // The node is internal but this quadrant is not taken. Add + // subnode to it. + child = newNode(); + child.left = left; + child.top = top; + child.right = right; + child.bottom = bottom; + child.body = body; + + setChild(node, quadIdx, child); + } else { + // continue searching in this quadrant. + insertStack.push(child, body); + } + } else { + // We are trying to add to the leaf node. + // We have to convert current leaf into internal node + // and continue adding two nodes. + var oldBody = node.body; + node.body = null; // internal nodes do not cary bodies + + if (isSamePosition(oldBody.pos, body.pos)) { + // Prevent infinite subdivision by bumping one node + // anywhere in this quadrant + var retriesCount = 3; + do { + var offset = random.nextDouble(); + var dx = (node.right - node.left) * offset; + var dy = (node.bottom - node.top) * offset; + + oldBody.pos.x = node.left + dx; + oldBody.pos.y = node.top + dy; + retriesCount -= 1; + // Make sure we don't bump it out of the box. If we do, next iteration should fix it + } while (retriesCount > 0 && isSamePosition(oldBody.pos, body.pos)); + + if (retriesCount === 0 && isSamePosition(oldBody.pos, body.pos)) { + // This is very bad, we ran out of precision. + // if we do not return from the method we'll get into + // infinite loop here. So we sacrifice correctness of layout, and keep the app running + // Next layout iteration should get larger bounding box in the first step and fix this + return; + } + } + // Next iteration should subdivide node further. + insertStack.push(node, oldBody); + insertStack.push(node, body); + } + } + } +}; - wrappedChildren = wrappedChildren.concat(Viva.Graph.svg(el).children(selector)); - } - } +function getChild(node, idx) { + if (idx === 0) return node.quad0; + if (idx === 1) return node.quad1; + if (idx === 2) return node.quad2; + if (idx === 3) return node.quad3; + return null; +} - if (idSelector && wrappedChildren.length === 1) { - return wrappedChildren[0]; - } - } +function setChild(node, idx, child) { + if (idx === 0) node.quad0 = child; + else if (idx === 1) node.quad1 = child; + else if (idx === 2) node.quad2 = child; + else if (idx === 3) node.quad3 = child; +} - return wrappedChildren; - }; +},{"./insertStack":27,"./isSamePosition":28,"./node":29,"ngraph.random":30}],27:[function(require,module,exports){ +module.exports = InsertStack; - return svgElement; -}; -/** - * @fileOverview Defines a graph renderer that uses SVG based drawings. - * - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com - */ - -Viva.Graph.View = Viva.Graph.View || {}; - -/** - * Performs svg-based graph rendering. This module does not perform - * layout, but only visualizes nodes and edeges of the graph. - */ -Viva.Graph.View.svgGraphics = function () { - var svgContainer, - svgRoot, - offsetX, - offsetY, - actualScale = 1, -/*jshint unused: false */ - nodeBuilder = function (node) { - return Viva.Graph.svg("rect") - .attr("width", 10) - .attr("height", 10) - .attr("fill", "#00a2e8"); - }, - - nodePositionCallback = function (nodeUI, pos) { - // TODO: Remove magic 5. It should be halfo of the width or height of the node. - nodeUI.attr("x", pos.x - 5) - .attr("y", pos.y - 5); - }, - - linkBuilder = function (link) { - return Viva.Graph.svg("line") - .attr("stroke", "#999"); - }, - - linkPositionCallback = function (linkUI, fromPos, toPos) { - linkUI.attr("x1", fromPos.x) - .attr("y1", fromPos.y) - .attr("x2", toPos.x) - .attr("y2", toPos.y); - }, - - fireRescaled = function (graphics) { - // TODO: maybe we shall copy changes? - graphics.fire("rescaled"); - }, - - updateTransform = function () { - if (svgContainer) { - var transform = "matrix(" + actualScale + ", 0, 0," + actualScale + "," + offsetX + "," + offsetY + ")"; - svgContainer.attr("transform", transform); - } - }; - - var graphics = { - /** - * Sets the collback that creates node representation or creates a new node - * presentation if builderCallbackOrNode is not a function. - * - * @param builderCallbackOrNode a callback function that accepts graph node - * as a parameter and must return an element representing this node. OR - * if it's not a function it's treated as a node to which DOM element should be created. - * - * @returns If builderCallbackOrNode is a valid callback function, instance of this is returned; - * Otherwise a node representation is returned for the passed parameter. - */ - node : function (builderCallbackOrNode) { - - if (builderCallbackOrNode && typeof builderCallbackOrNode !== "function") { - return nodeBuilder(builderCallbackOrNode); - } - - nodeBuilder = builderCallbackOrNode; - - return this; - }, - - /** - * Sets the collback that creates link representation or creates a new link - * presentation if builderCallbackOrLink is not a function. - * - * @param builderCallbackOrLink a callback function that accepts graph link - * as a parameter and must return an element representing this link. OR - * if it's not a function it's treated as a link to which DOM element should be created. - * - * @returns If builderCallbackOrLink is a valid callback function, instance of this is returned; - * Otherwise a link representation is returned for the passed parameter. - */ - link : function (builderCallbackOrLink) { - if (builderCallbackOrLink && typeof builderCallbackOrLink !== "function") { - return linkBuilder(builderCallbackOrLink); - } - - linkBuilder = builderCallbackOrLink; - return this; - }, - - /** - * Allows to override default position setter for the node with a new - * function. newPlaceCallback(nodeUI, position) is function which - * is used by updateNodePosition(). - */ - placeNode : function (newPlaceCallback) { - nodePositionCallback = newPlaceCallback; - return this; - }, - - placeLink : function (newPlaceLinkCallback) { - linkPositionCallback = newPlaceLinkCallback; - return this; - }, - - /** - * Called every before renderer starts rendering. - */ - beginRender : function () {}, - - /** - * Called every time when renderer finishes one step of rendering. - */ - endRender : function () {}, - - /** - * Sets translate operation that should be applied to all nodes and links. - */ - graphCenterChanged : function (x, y) { - offsetX = x; - offsetY = y; - updateTransform(); - }, - - /** - * Default input manager listens to DOM events to process nodes drag-n-drop - */ - inputManager : Viva.Input.domInputManager, - - translateRel : function (dx, dy) { - var p = svgRoot.createSVGPoint(), - t = svgContainer.getCTM(), - origin = svgRoot.createSVGPoint().matrixTransform(t.inverse()); - - p.x = dx; - p.y = dy; - - p = p.matrixTransform(t.inverse()); - p.x = (p.x - origin.x) * t.a; - p.y = (p.y - origin.y) * t.d; - - t.e += p.x; - t.f += p.y; - - var transform = "matrix(" + t.a + ", 0, 0," + t.d + "," + t.e + "," + t.f + ")"; - svgContainer.attr("transform", transform); - }, - - scale : function (scaleFactor, scrollPoint) { - var p = svgRoot.createSVGPoint(); - p.x = scrollPoint.x; - p.y = scrollPoint.y; - - p = p.matrixTransform(svgContainer.getCTM().inverse()); // translate to svg coordinates - - // Compute new scale matrix in current mouse position - var k = svgRoot.createSVGMatrix().translate(p.x, p.y).scale(scaleFactor).translate(-p.x, -p.y), - t = svgContainer.getCTM().multiply(k); - - actualScale = t.a; - offsetX = t.e; - offsetY = t.f; - var transform = "matrix(" + t.a + ", 0, 0," + t.d + "," + t.e + "," + t.f + ")"; - svgContainer.attr("transform", transform); - - fireRescaled(this); - return actualScale; - }, - - resetScale : function () { - actualScale = 1; - var transform = "matrix(1, 0, 0, 1, 0, 0)"; - svgContainer.attr("transform", transform); - fireRescaled(this); - return this; - }, - - /** - * Called by Viva.Graph.View.renderer to let concrete graphic output - * provider prepare to render. - */ - init : function (container) { - svgRoot = Viva.Graph.svg("svg"); - - svgContainer = Viva.Graph.svg("g") - .attr("buffered-rendering", "dynamic"); - - svgRoot.appendChild(svgContainer); - container.appendChild(svgRoot); - updateTransform(); - }, - - /** - * Called by Viva.Graph.View.renderer to let concrete graphic output - * provider release occupied resources. - */ - release : function (container) { - if (svgRoot && container) { - container.removeChild(svgRoot); - } - }, - - /** - * Called by Viva.Graph.View.renderer to let concrete graphic output - * provider prepare to render given link of the graph - * - * @param linkUI visual representation of the link created by link() execution. - */ - initLink : function (linkUI) { - if (!linkUI) { return; } - if (svgContainer.childElementCount > 0) { - svgContainer.insertBefore(linkUI, svgContainer.firstChild); - } else { - svgContainer.appendChild(linkUI); - } - }, - - /** - * Called by Viva.Graph.View.renderer to let concrete graphic output - * provider remove link from rendering surface. - * - * @param linkUI visual representation of the link created by link() execution. - **/ - releaseLink : function (linkUI) { - svgContainer.removeChild(linkUI); - }, - - /** - * Called by Viva.Graph.View.renderer to let concrete graphic output - * provider prepare to render given node of the graph. - * - * @param nodeUI visual representation of the node created by node() execution. - **/ - initNode : function (nodeUI) { - svgContainer.appendChild(nodeUI); - }, - - /** - * Called by Viva.Graph.View.renderer to let concrete graphic output - * provider remove node from rendering surface. - * - * @param nodeUI visual representation of the node created by node() execution. - **/ - releaseNode : function (nodeUI) { - svgContainer.removeChild(nodeUI); - }, - - /** - * Called by Viva.Graph.View.renderer to let concrete graphic output - * provider place given node UI to recommended position pos {x, y}; - */ - updateNodePosition : function (nodeUI, pos) { - nodePositionCallback(nodeUI, pos); - }, - - /** - * Called by Viva.Graph.View.renderer to let concrete graphic output - * provider place given link of the graph. Pos objects are {x, y}; - */ - updateLinkPosition : function (link, fromPos, toPos) { - linkPositionCallback(link, fromPos, toPos); - }, - - /** - * Returns root svg element. - * - * Note: This is internal method specific to this renderer - * TODO: Rename this to getGraphicsRoot() to be uniform accross graphics classes - */ - getSvgRoot : function () { - return svgRoot; - } - }; - - // Let graphics fire events before we return it to the caller. - Viva.Graph.Utils.events(graphics).extend(); - - return graphics; -}; /** - * @fileOverview I used this class to render links UI within - * node. Lesser SVG elements is proven to improve performance - * but I'm not happy with the code result here. Probably this class - * will be removed from future versions. - * - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com + * Our implmentation of QuadTree is non-recursive to avoid GC hit + * This data structure represent stack of elements + * which we are trying to insert into quad tree. */ +function InsertStack () { + this.stack = []; + this.popIdx = 0; +} + +InsertStack.prototype = { + isEmpty: function() { + return this.popIdx === 0; + }, + push: function (node, body) { + var item = this.stack[this.popIdx]; + if (!item) { + // we are trying to avoid memory pressue: create new element + // only when absolutely necessary + this.stack[this.popIdx] = new InsertStackElement(node, body); + } else { + item.node = node; + item.body = body; + } + ++this.popIdx; + }, + pop: function () { + if (this.popIdx > 0) { + return this.stack[--this.popIdx]; + } + }, + reset: function () { + this.popIdx = 0; + } +}; -Viva.Graph.View.svgNodeFactory = function (graph) { - var defaultColor = "#999", - geom = Viva.Graph.geom(), - - attachCustomContent = function (nodeUI) { - nodeUI.size = {w: 10, h: 10}; - nodeUI.append("rect") - .attr("width", nodeUI.size.w) - .attr("height", nodeUI.size.h) - .attr("stroke", "orange") - .attr("fill", "orange"); - }, +function InsertStackElement(node, body) { + this.node = node; // QuadTree node + this.body = body; // physical body which needs to be inserted to node +} - nodeSize = function (nodeUI) { - return nodeUI.size; - }; +},{}],28:[function(require,module,exports){ +module.exports = function isSamePosition(point1, point2) { + var dx = Math.abs(point1.x - point2.x); + var dy = Math.abs(point1.y - point2.y); + return (dx < 1e-8 && dy < 1e-8); +}; - return { - node : function (node) { - var nodeUI = Viva.Graph.svg("g"); +},{}],29:[function(require,module,exports){ +/** + * Internal data structure to represent 2D QuadTree node + */ +module.exports = function Node() { + // body stored inside this node. In quad tree only leaf nodes (by construction) + // contain boides: + this.body = null; + + // Child nodes are stored in quads. Each quad is presented by number: + // 0 | 1 + // ----- + // 2 | 3 + this.quad0 = null; + this.quad1 = null; + this.quad2 = null; + this.quad3 = null; + + // Total mass of current node + this.mass = 0; + + // Center of mass coordinates + this.massX = 0; + this.massY = 0; + + // bounding box coordinates + this.left = 0; + this.top = 0; + this.bottom = 0; + this.right = 0; +}; - attachCustomContent(nodeUI, node); - nodeUI.nodeId = node.id; - return nodeUI; - }, +},{}],30:[function(require,module,exports){ +module.exports = { + random: random, + randomIterator: randomIterator +}; + +/** + * Creates seeded PRNG with two methods: + * next() and nextDouble() + */ +function random(inputSeed) { + var seed = typeof inputSeed === 'number' ? inputSeed : (+ new Date()); + var randomFunc = function() { + // Robert Jenkins' 32 bit integer hash function. + seed = ((seed + 0x7ed55d16) + (seed << 12)) & 0xffffffff; + seed = ((seed ^ 0xc761c23c) ^ (seed >>> 19)) & 0xffffffff; + seed = ((seed + 0x165667b1) + (seed << 5)) & 0xffffffff; + seed = ((seed + 0xd3a2646c) ^ (seed << 9)) & 0xffffffff; + seed = ((seed + 0xfd7046c5) + (seed << 3)) & 0xffffffff; + seed = ((seed ^ 0xb55a4f09) ^ (seed >>> 16)) & 0xffffffff; + return (seed & 0xfffffff) / 0x10000000; + }; + + return { + /** + * Generates random integer number in the range from 0 (inclusive) to maxValue (exclusive) + * + * @param maxValue Number REQUIRED. Ommitting this number will result in NaN values from PRNG. + */ + next : function (maxValue) { + return Math.floor(randomFunc() * maxValue); + }, + + /** + * Generates random double number in the range from 0 (inclusive) to 1 (exclusive) + * This function is the same as Math.random() (except that it could be seeded) + */ + nextDouble : function () { + return randomFunc(); + } + }; +} + +/* + * Creates iterator over array, which returns items of array in random order + * Time complexity is guaranteed to be O(n); + */ +function randomIterator(array, customRandom) { + var localRandom = customRandom || random(); + if (typeof localRandom.next !== 'function') { + throw new Error('customRandom does not match expected API: next() function is missing'); + } - link : function (link) { - var fromNode = graph.getNode(link.fromId), - nodeUI = fromNode && fromNode.ui; + return { + forEach : function (callback) { + var i, j, t; + for (i = array.length - 1; i > 0; --i) { + j = localRandom.next(i + 1); // i inclusive + t = array[j]; + array[j] = array[i]; + array[i] = t; - if (nodeUI && !nodeUI.linksContainer) { - var nodeLinks = Viva.Graph.svg("path") - .attr("stroke", defaultColor); - nodeUI.linksContainer = nodeLinks; - return nodeLinks; + callback(t); } - return null; + if (array.length) { + callback(array[0]); + } }, /** - * Sets a callback function for custom nodes contnet. - * @param conentCreator(nodeUI, node) - callback function which returns a node content UI. - * Image, for example. - * @param sizeProvider(nodeUI) - a callback function which accepts nodeUI returned by - * contentCreator and returns it"s custom rectangular size. - * + * Shuffles array randomly, in place. */ - customContent : function (contentCreator, sizeProvider) { - if (typeof contentCreator !== "function" || - typeof sizeProvider !== "function") { - throw "Two functions expected: contentCreator(nodeUI, node) and size(nodeUI)"; + shuffle : function () { + var i, j, t; + for (i = array.length - 1; i > 0; --i) { + j = localRandom.next(i + 1); // i inclusive + t = array[j]; + array[j] = array[i]; + array[i] = t; } - attachCustomContent = contentCreator; - nodeSize = sizeProvider; - }, + return array; + } + }; +} - placeNode : function (nodeUI, fromNodePos) { - var linksPath = "", - fromNodeSize = nodeSize(nodeUI); +},{}],31:[function(require,module,exports){ +module.exports = save; - graph.forEachLinkedNode(nodeUI.nodeId, function (linkedNode, link) { - if (!linkedNode.position || !linkedNode.ui) { - return; // not yet defined - ignore. - } +function save(graph, customNodeTransform, customLinkTransform) { + // Object contains `nodes` and `links` arrays. + var result = { + nodes: [], + links: [] + }; - if (linkedNode.ui === nodeUI) { - return; // incoming link - ignore; - } - if (link.fromId !== nodeUI.nodeId) { - return; // we process only outgoing links. - } + var nodeTransform = customNodeTransform || defaultTransformForNode; + var linkTransform = customLinkTransform || defaultTransformForLink; - var toNodeSize = nodeSize(linkedNode.ui), - toNodePos = linkedNode.position; - - var from = geom.intersectRect( - fromNodePos.x - fromNodeSize.w / 2, // left - fromNodePos.y - fromNodeSize.h / 2, // top - fromNodePos.x + fromNodeSize.w / 2, // right - fromNodePos.y + fromNodeSize.h / 2, // bottom - fromNodePos.x, - fromNodePos.y, - toNodePos.x, - toNodePos.y - ) || fromNodePos; - - var to = geom.intersectRect( - toNodePos.x - toNodeSize.w / 2, // left - toNodePos.y - toNodeSize.h / 2, // top - toNodePos.x + toNodeSize.w / 2, // right - toNodePos.y + toNodeSize.h / 2, // bottom - toNodePos.x, - toNodePos.y, - fromNodePos.x, - fromNodePos.y - ) || toNodePos; - - linksPath += "M" + Math.round(from.x) + " " + Math.round(from.y) + - "L" + Math.round(to.x) + " " + Math.round(to.y); - }); - - nodeUI.attr("transform", - "translate(" + (fromNodePos.x - fromNodeSize.w / 2) + ", " + - (fromNodePos.y - fromNodeSize.h / 2) + ")"); - if (linksPath !== "" && nodeUI.linksContainer) { - nodeUI.linksContainer.attr("d", linksPath); - } - } + graph.forEachNode(saveNode); + graph.forEachLink(saveLink); + + return JSON.stringify(result); + function saveNode(node) { + // Each node of the graph is processed to take only required fields + // `id` and `data` + result.nodes.push(nodeTransform(node)); + } + + function saveLink(link) { + // Each link of the graph is also processed to take `fromId`, `toId` and + // `data` + result.links.push(linkTransform(link)); + } + + function defaultTransformForNode(node) { + var result = { + id: node.id }; -}; -/** - * @fileOverview Utility functions for webgl rendering. - * - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com - */ + // We don't want to store undefined fields when it's not necessary: + if (node.data !== undefined) { + result.data = node.data; + } -Viva.Graph.webgl = function (gl) { - var createShader = function (shaderText, type) { - var shader = gl.createShader(type); - gl.shaderSource(shader, shaderText); - gl.compileShader(shader); + return result; + } - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { - var msg = gl.getShaderInfoLog(shader); - window.alert(msg); - throw msg; - } + function defaultTransformForLink(link) { + var result = { + fromId: link.fromId, + toId: link.toId, + }; - return shader; - }; + if (link.data !== undefined) { + result.data = link.data; + } - return { - createProgram : function (vertexShaderSrc, fragmentShaderSrc) { - var program = gl.createProgram(), - vs = createShader(vertexShaderSrc, gl.VERTEX_SHADER), - fs = createShader(fragmentShaderSrc, gl.FRAGMENT_SHADER); - - gl.attachShader(program, vs); - gl.attachShader(program, fs); - gl.linkProgram(program); - - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { - var msg = gl.getShaderInfoLog(program); - window.alert(msg); - throw msg; - } + return result; + } +} - return program; - }, +},{}],32:[function(require,module,exports){ +module.exports = svg; - extendArray : function (buffer, itemsInBuffer, elementsPerItem) { - if ((itemsInBuffer + 1) * elementsPerItem > buffer.length) { - // Every time we run out of space create new array twice bigger. - // TODO: it seems buffer size is limited. Consider using multiple arrays for huge graphs - var extendedArray = new Float32Array(buffer.length * elementsPerItem * 2); - extendedArray.set(buffer); +svg.compile = require('./lib/compile'); - return extendedArray; - } +var compileTemplate = svg.compileTemplate = require('./lib/compile_template'); - return buffer; - }, +var domEvents = require('add-event-listener'); - copyArrayPart : function (array, to, from, elementsCount) { - var i; - for (i = 0; i < elementsCount; ++i) { - array[to + i] = array[from + i]; - } - }, +var svgns = "http://www.w3.org/2000/svg"; +var xlinkns = "http://www.w3.org/1999/xlink"; - swapArrayPart : function (array, from, to, elementsCount) { - var i; - for (i = 0; i < elementsCount; ++i) { - var tmp = array[from + i]; - array[from + i] = array[to + i]; - array[to + i] = tmp; - } - }, +function svg(element, attrBag) { + var svgElement = augment(element); + if (attrBag === undefined) { + return svgElement; + } + + var attributes = Object.keys(attrBag); + for (var i = 0; i < attributes.length; ++i) { + var attributeName = attributes[i]; + var value = attrBag[attributeName]; + if (attributeName === 'link') { + svgElement.link(value); + } else { + svgElement.attr(attributeName, value); + } + } - getLocations : function (program, uniformOrAttributeNames) { - var foundLocations = {}, - i; - for (i = 0; i < uniformOrAttributeNames.length; ++i) { - var name = uniformOrAttributeNames[i], - location = -1; - if (name.indexOf("a_") === 0) { - location = gl.getAttribLocation(program, name); - if (location === -1) { - throw "Program doesn't have required attribute: " + name; - } + return svgElement; +} - foundLocations[name.slice(2)] = location; - } else if (name.indexOf("u_") === 0) { - location = gl.getUniformLocation(program, name); - if (location === null) { - throw "Program doesn't have required uniform: " + name; - } +function augment(element) { + var svgElement = element; - foundLocations[name.slice(2)] = location; - } else { - throw "Couldn't figure out your intent. All uniforms should start with 'u_' prefix, and attributes with 'a_'"; - } - } + if (typeof element === "string") { + svgElement = window.document.createElementNS(svgns, element); + } else if (element.simplesvg) { + return element; + } - return foundLocations; - }, + var compiledTempalte; - context : gl - }; -}; -/** - * @fileOverview Defines a model objects to represents graph rendering - * primitives in webglGraphics. - * - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com - */ + svgElement.simplesvg = true; // this is not good, since we are monkey patching svg + svgElement.attr = attr; + svgElement.append = append; + svgElement.link = link; + svgElement.text = text; -Viva.Graph.View.WebglUtils = function () { }; + // add easy eventing + svgElement.on = on; + svgElement.off = off; -/** - * Parses various color strings and returns color value used in webgl shaders. - */ -Viva.Graph.View.WebglUtils.prototype.parseColor = function (color) { - var parsedColor = 0x009ee8ff; + // data binding: + svgElement.dataSource = dataSource; - if (typeof color === 'string' && color) { - if (color.length === 4) { // #rgb - color = color.replace(/([^#])/g, '$1$1'); // duplicate each letter except first #. - } - if (color.length === 9) { // #rrggbbaa - parsedColor = parseInt(color.substr(1), 16); - } else if (color.length === 7) { // or #rrggbb. - parsedColor = (parseInt(color.substr(1), 16) << 8) | 0xff; - } else { - throw 'Color expected in hex format with preceding "#". E.g. #00ff00. Got value: ' + color; - } - } else if (typeof color === 'number') { - parsedColor = color; + return svgElement; + + function dataSource(model) { + if (!compiledTempalte) compiledTempalte = compileTemplate(svgElement); + compiledTempalte.link(model); + return svgElement; + } + + function on(name, cb, useCapture) { + domEvents.addEventListener(svgElement, name, cb, useCapture); + return svgElement; + } + + function off(name, cb, useCapture) { + domEvents.removeEventListener(svgElement, name, cb, useCapture); + return svgElement; + } + + function append(content) { + var child = svg(content); + svgElement.appendChild(child); + + return child; + } + + function attr(name, value) { + if (arguments.length === 2) { + if (value !== null) { + svgElement.setAttributeNS(null, name, value); + } else { + svgElement.removeAttributeNS(null, name); + } + + return svgElement; } - return parsedColor; -}; + return svgElement.getAttributeNS(null, name); + } + + function link(target) { + if (arguments.length) { + svgElement.setAttributeNS(xlinkns, "xlink:href", target); + return svgElement; + } -Viva.Graph.View._webglUtil = new Viva.Graph.View.WebglUtils(); // reuse this instance internally. + return svgElement.getAttributeNS(xlinkns, "xlink:href"); + } -/** - * Defines a webgl line. This class has no rendering logic at all, - * it's just passed to corresponding shader and the shader should - * figure out how to render it. - * - * @see Viva.Graph.View.webglLinkShader.position - */ -Viva.Graph.View.webglLine = function (color) { + function text(textContent) { + if (textContent !== undefined) { + svgElement.textContent = textContent; + return svgElement; + } + return svgElement.textContent; + } +} + +},{"./lib/compile":33,"./lib/compile_template":34,"add-event-listener":2}],33:[function(require,module,exports){ +var parser = require('./domparser.js'); +var svg = require('../'); + +module.exports = compile; + +function compile(svgText) { + try { + svgText = addNamespaces(svgText); + return svg(parser.parseFromString(svgText, "text/xml").documentElement); + } catch (e) { + throw e; + } +} + +function addNamespaces(text) { + if (!text) return; + + var namespaces = 'xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"'; + var match = text.match(/^<\w+/); + if (match) { + var tagLength = match[0].length; + return text.substr(0, tagLength) + ' ' + namespaces + ' ' + text.substr(tagLength); + } else { + throw new Error('Cannot parse input text: invalid xml?'); + } +} + +},{"../":32,"./domparser.js":35}],34:[function(require,module,exports){ +module.exports = template; + +var BINDING_EXPR = /{{(.+?)}}/; + +function template(domNode) { + var allBindings = Object.create(null); + extractAllBindings(domNode, allBindings); + + return { + link: function(model) { + Object.keys(allBindings).forEach(function(key) { + var setter = allBindings[key]; + setter.forEach(changeModel); + }); + + function changeModel(setter) { + setter(model); + } + } + }; +} + +function extractAllBindings(domNode, allBindings) { + var nodeType = domNode.nodeType; + var typeSupported = (nodeType === 1) || (nodeType === 3); + if (!typeSupported) return; + var i; + if (domNode.hasChildNodes()) { + var domChildren = domNode.childNodes; + for (i = 0; i < domChildren.length; ++i) { + extractAllBindings(domChildren[i], allBindings); + } + } + + if (nodeType === 3) { // text: + bindTextContent(domNode, allBindings); + } + + if (!domNode.attributes) return; // this might be a text. Need to figure out what to do in that case + + var attrs = domNode.attributes; + for (i = 0; i < attrs.length; ++i) { + bindDomAttribute(attrs[i], domNode, allBindings); + } +} + +function bindDomAttribute(domAttribute, element, allBindings) { + var value = domAttribute.value; + if (!value) return; // unary attribute? + + var modelNameMatch = value.match(BINDING_EXPR); + if (!modelNameMatch) return; // does not look like a binding + + var attrName = domAttribute.localName; + var modelPropertyName = modelNameMatch[1]; + var isSimpleValue = modelPropertyName.indexOf('.') < 0; + + if (!isSimpleValue) throw new Error('simplesvg currently does not support nested bindings'); + + var propertyBindings = allBindings[modelPropertyName]; + if (!propertyBindings) { + propertyBindings = allBindings[modelPropertyName] = [attributeSetter]; + } else { + propertyBindings.push(attributeSetter); + } + + function attributeSetter(model) { + element.setAttributeNS(null, attrName, model[modelPropertyName]); + } +} +function bindTextContent(element, allBindings) { + // todo reduce duplication + var value = element.nodeValue; + if (!value) return; // unary attribute? + + var modelNameMatch = value.match(BINDING_EXPR); + if (!modelNameMatch) return; // does not look like a binding + + var modelPropertyName = modelNameMatch[1]; + var isSimpleValue = modelPropertyName.indexOf('.') < 0; + + var propertyBindings = allBindings[modelPropertyName]; + if (!propertyBindings) { + propertyBindings = allBindings[modelPropertyName] = [textSetter]; + } else { + propertyBindings.push(textSetter); + } + + function textSetter(model) { + element.nodeValue = model[modelPropertyName]; + } +} + +},{}],35:[function(require,module,exports){ +module.exports = createDomparser(); + +function createDomparser() { + if (typeof DOMParser === 'undefined') { return { - /** - * Gets or sets color of the line. If you set this property externally - * make sure it always come as integer of 0xRRGGBBAA format - */ - color : Viva.Graph.View._webglUtil.parseColor(color) + parseFromString: fail }; -}; + } + return new DOMParser(); +} -/** - * Can be used as a callback in the webglGraphics.node() function, to - * create a custom looking node. - * - * @param size - size of the node in pixels. - * @param color - color of the node in '#rrggbbaa' or '#rgb' format. - */ -Viva.Graph.View.webglSquare = function (size, color) { - return { - /** - * Gets or sets size of the sqare side. - */ - size : typeof size === 'number' ? size : 10, +function fail() { + throw new Error('DOMParser is not supported by this platform. Please open issue here https://github.com/anvaka/simplesvg'); +} - /** - * Gets or sets color of the square. - */ - color : Viva.Graph.View._webglUtil.parseColor(color) +},{}],36:[function(require,module,exports){ +var centrality = require('ngraph.centrality'); + +module.exports = centralityWrapper; + +function centralityWrapper() { + // TODO: This should not be a function + return { + betweennessCentrality: betweennessCentrality, + degreeCentrality: degreeCentrality + }; +} + +function betweennessCentrality(g) { + var betweenness = centrality.betweenness(g); + return toVivaGraphCentralityFormat(betweenness); +} + +function degreeCentrality(g, kind) { + var degree = centrality.degree(g, kind); + return toVivaGraphCentralityFormat(degree); +} + +function toVivaGraphCentralityFormat(centrality) { + return Object.keys(centrality).sort(byValue).map(toKeyValue); + + function byValue(x, y) { + return centrality[y] - centrality[x]; + } + + function toKeyValue(key) { + return { + key: key, + value: centrality[key] }; -}; + } +} + +},{"ngraph.centrality":4}],37:[function(require,module,exports){ +/** + * @fileOverview Contains collection of primitive operations under graph. + * + * @author Andrei Kashcha (aka anvaka) / https://github.com/anvaka + */ +module.exports = operations; + +function operations() { + + return { + /** + * Gets graph density, which is a ratio of actual number of edges to maximum + * number of edges. I.e. graph density 1 means all nodes are connected with each other with an edge. + * Density 0 - graph has no edges. Runtime: O(1) + * + * @param graph represents oriented graph structure. + * @param directed (optional boolean) represents if the graph should be treated as a directed graph. + * + * @returns density of the graph if graph has nodes. NaN otherwise. Returns density for undirected graph by default but returns density for directed graph if a boolean 'true' is passed along with the graph. + */ + density : function (graph,directed) { + var nodes = graph.getNodesCount(); + if (nodes === 0) { + return NaN; + } + if(directed){ + return graph.getLinksCount() / (nodes * (nodes - 1)); + } else { + return 2 * graph.getLinksCount() / (nodes * (nodes - 1)); + } + } + }; +}; +},{}],38:[function(require,module,exports){ /** - * Represents a model for image. + * @author Andrei Kashcha (aka anvaka) / https://github.com/anvaka */ -Viva.Graph.View.webglImage = function (size, src) { - return { - /** - * Gets texture index where current image is placed.s - */ - _texture : 0, - /** - * Gets offset in the texture where current image is placed. - */ - _offset : 0, +module.exports = domInputManager; - /** - * Gets size of the square with the image. - */ - size : typeof size === 'number' ? size : 32, +var dragndrop = require('./dragndrop.js'); - /** - * Source of the image. If image is comming not from your domain - * certain origin restrictions applies. - * See http://www.khronos.org/registry/webgl/specs/latest/#4.2 for more details. - */ - src : src - }; -};/** - * @fileOverview Defines a naive form of nodes for webglGraphics class. - * This form allows to change color of node. Shape of nodes is rectangular. - * - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com - */ +function domInputManager(graph, graphics) { + var nodeEvents = {}; + return { + /** + * Called by renderer to listen to drag-n-drop events from node. E.g. for SVG + * graphics we may listen to DOM events, whereas for WebGL the graphics + * should provide custom eventing mechanism. + * + * @param node - to be monitored. + * @param handlers - object with set of three callbacks: + * onStart: function(), + * onDrag: function(e, offset), + * onStop: function() + */ + bindDragNDrop: bindDragNDrop + }; + + function bindDragNDrop(node, handlers) { + var events; + if (handlers) { + var nodeUI = graphics.getNodeUI(node.id); + events = dragndrop(nodeUI); + if (typeof handlers.onStart === 'function') { + events.onStart(handlers.onStart); + } + if (typeof handlers.onDrag === 'function') { + events.onDrag(handlers.onDrag); + } + if (typeof handlers.onStop === 'function') { + events.onStop(handlers.onStop); + } + + nodeEvents[node.id] = events; + } else if ((events = nodeEvents[node.id])) { + events.release(); + delete nodeEvents[node.id]; + } + } +} +},{"./dragndrop.js":39}],39:[function(require,module,exports){ /** - * Defines simple UI for nodes in webgl renderer. Each node is rendered as square. Color and size can be changed. + * @author Andrei Kashcha (aka anvaka) / https://github.com/anvaka */ -Viva.Graph.View.webglNodeProgram = function () { - var ATTRIBUTES_PER_PRIMITIVE = 4, // Primitive is point, x, y, size, color - // x, y, z - floats, color = uint. - BYTES_PER_NODE = 3 * Float32Array.BYTES_PER_ELEMENT + Uint32Array.BYTES_PER_ELEMENT, - nodesFS = [ - 'precision mediump float;', - 'varying vec4 color;', - 'void main(void) {', - ' gl_FragColor = color;', - '}' - ].join('\n'), - nodesVS = [ - 'attribute vec3 a_vertexPos;', - 'attribute vec4 a_color;', - 'uniform vec2 u_screenSize;', - 'uniform mat4 u_transform;', - 'varying vec4 color;', +module.exports = dragndrop; - 'void main(void) {', - ' gl_Position = u_transform * vec4(a_vertexPos.xy/u_screenSize, 0, 1);', - ' gl_PointSize = a_vertexPos.z * u_transform[0][0];', - ' color = a_color.abgr;', - '}' - ].join('\n'), +var documentEvents = require('../Utils/documentEvents.js'); +var browserInfo = require('../Utils/browserInfo.js'); +var findElementPosition = require('../Utils/findElementPosition.js'); - program, - gl, - buffer, - locations, - utils, - storage = new ArrayBuffer(16 * BYTES_PER_NODE), - positions = new Float32Array(storage), - colors = new Uint32Array(storage), - nodesCount = 0, - width, - height, - transform, - sizeDirty, +// TODO: Move to input namespace +// TODO: Methods should be extracted into the prototype. This class +// does not need to consume so much memory for every tracked element +function dragndrop(element) { + var start, + drag, + end, + scroll, + prevSelectStart, + prevDragStart, + + startX = 0, + startY = 0, + dragObject, + touchInProgress = false, + pinchZoomLength = 0, + + getMousePos = function (e) { + var posx = 0, + posy = 0; + + e = e || window.event; + + if (e.pageX || e.pageY) { + posx = e.pageX; + posy = e.pageY; + } else if (e.clientX || e.clientY) { + posx = e.clientX + window.document.body.scrollLeft + window.document.documentElement.scrollLeft; + posy = e.clientY + window.document.body.scrollTop + window.document.documentElement.scrollTop; + } + + return [posx, posy]; + }, + + move = function (e, clientX, clientY) { + if (drag) { + drag(e, {x : clientX - startX, y : clientY - startY }); + } + + startX = clientX; + startY = clientY; + }, + + stopPropagation = function (e) { + if (e.stopPropagation) { e.stopPropagation(); } else { e.cancelBubble = true; } + }, + preventDefault = function (e) { + if (e.preventDefault) { e.preventDefault(); } + }, - ensureEnoughStorage = function () { - if ((nodesCount + 1) * BYTES_PER_NODE >= storage.byteLength) { - // Every time we run out of space create new array twice bigger. - // TODO: it seems buffer size is limited. Consider using multiple arrays for huge graphs - var extendedStorage = new ArrayBuffer(storage.byteLength * 2), - extendedPositions = new Float32Array(extendedStorage), - extendedColors = new Uint32Array(extendedStorage); + handleDisabledEvent = function (e) { + stopPropagation(e); + return false; + }, - extendedColors.set(colors); // should be enough to copy just one view. - positions = extendedPositions; - colors = extendedColors; - storage = extendedStorage; + handleMouseMove = function (e) { + e = e || window.event; + + move(e, e.clientX, e.clientY); + }, + + handleMouseDown = function (e) { + e = e || window.event; + if (touchInProgress) { + // modern browsers will fire mousedown for touch events too + // we do not want this, since touch is handled separately. + stopPropagation(e); + return false; } - }; + // for IE, left click == 1 + // for Firefox, left click == 0 + var isLeftButton = ((e.button === 1 && window.event !== null) || e.button === 0); - return { - load : function (glContext) { - gl = glContext; - utils = Viva.Graph.webgl(glContext); + if (isLeftButton) { + startX = e.clientX; + startY = e.clientY; - program = utils.createProgram(nodesVS, nodesFS); - gl.useProgram(program); - locations = utils.getLocations(program, ['a_vertexPos', 'a_color', 'u_screenSize', 'u_transform']); + // TODO: bump zIndex? + dragObject = e.target || e.srcElement; - gl.enableVertexAttribArray(locations.vertexPos); - gl.enableVertexAttribArray(locations.color); + if (start) { start(e, {x: startX, y : startY}); } - buffer = gl.createBuffer(); - }, + documentEvents.on('mousemove', handleMouseMove); + documentEvents.on('mouseup', handleMouseUp); - /** - * Updates position of node in the buffer of nodes. - * - * @param idx - index of current node. - * @param pos - new position of the node. - */ - position : function (nodeUI, pos) { - var idx = nodeUI.id; - positions[idx * ATTRIBUTES_PER_PRIMITIVE] = pos.x; - positions[idx * ATTRIBUTES_PER_PRIMITIVE + 1] = pos.y; - positions[idx * ATTRIBUTES_PER_PRIMITIVE + 2] = nodeUI.size; + stopPropagation(e); + // TODO: What if event already there? Not bullet proof: + prevSelectStart = window.document.onselectstart; + prevDragStart = window.document.ondragstart; - colors[idx * ATTRIBUTES_PER_PRIMITIVE + 3] = nodeUI.color; - }, + window.document.onselectstart = handleDisabledEvent; + dragObject.ondragstart = handleDisabledEvent; - updateTransform : function (newTransform) { - sizeDirty = true; - transform = newTransform; + // prevent text selection (except IE) + return false; + } }, - updateSize : function (w, h) { - width = w; - height = h; - sizeDirty = true; + handleMouseUp = function (e) { + e = e || window.event; + + documentEvents.off('mousemove', handleMouseMove); + documentEvents.off('mouseup', handleMouseUp); + + window.document.onselectstart = prevSelectStart; + dragObject.ondragstart = prevDragStart; + dragObject = null; + if (end) { end(e); } }, - removeNode : function (node) { - if (nodesCount > 0) { nodesCount -= 1; } + handleMouseWheel = function (e) { + if (typeof scroll !== 'function') { + return; + } - if (node.id < nodesCount && nodesCount > 0) { - // we can use colors as a 'view' into array array buffer. - utils.copyArrayPart(colors, node.id * ATTRIBUTES_PER_PRIMITIVE, nodesCount * ATTRIBUTES_PER_PRIMITIVE, ATTRIBUTES_PER_PRIMITIVE); + e = e || window.event; + if (e.preventDefault) { + e.preventDefault(); } - }, -/*jshint unused:false */ - createNode : function (node) { - ensureEnoughStorage(); - nodesCount += 1; - }, - replaceProperties : function (replacedNode, newNode) {}, -/*jshint unused:true */ + e.returnValue = false; + var delta = -e.deltaY, + mousePos = getMousePos(e), + elementOffset = findElementPosition(element), + relMousePos = { + x: mousePos[0] - elementOffset[0], + y: mousePos[1] - elementOffset[1] + }; - render : function () { - gl.useProgram(program); - gl.bindBuffer(gl.ARRAY_BUFFER, buffer); - gl.bufferData(gl.ARRAY_BUFFER, storage, gl.DYNAMIC_DRAW); + scroll(e, delta, relMousePos); + }, - if (sizeDirty) { - sizeDirty = false; - gl.uniformMatrix4fv(locations.transform, false, transform); - gl.uniform2f(locations.screenSize, width, height); + updateScrollEvents = function (scrollCallback) { + if (!scroll && scrollCallback) { + // client is interested in scrolling. Start listening to events: + element.addEventListener('wheel', handleMouseWheel, false); + } else if (scroll && !scrollCallback) { + element.removeEventListener('wheel', handleMouseWheel, false); } - gl.vertexAttribPointer(locations.vertexPos, 3, gl.FLOAT, false, ATTRIBUTES_PER_PRIMITIVE * Float32Array.BYTES_PER_ELEMENT, 0); - gl.vertexAttribPointer(locations.color, 4, gl.UNSIGNED_BYTE, true, ATTRIBUTES_PER_PRIMITIVE * Float32Array.BYTES_PER_ELEMENT, 3 * 4); + scroll = scrollCallback; + }, - gl.drawArrays(gl.POINTS, 0, nodesCount); - } - }; -};/** - * @fileOverview Defines a naive form of links for webglGraphics class. - * This form allows to change color of links. - * - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com - */ + getPinchZoomLength = function(finger1, finger2) { + return (finger1.clientX - finger2.clientX) * (finger1.clientX - finger2.clientX) + + (finger1.clientY - finger2.clientY) * (finger1.clientY - finger2.clientY); + }, -/** - * Defines UI for links in webgl renderer. - */ -Viva.Graph.View.webglLinkProgram = function () { - var ATTRIBUTES_PER_PRIMITIVE = 6, // primitive is Line with two points. Each has x,y and color = 3 * 2 attributes. - BYTES_PER_LINK = 2 * (2 * Float32Array.BYTES_PER_ELEMENT + Uint32Array.BYTES_PER_ELEMENT), // two nodes * (x, y + color) - linksFS = [ - 'precision mediump float;', - 'varying vec4 color;', - 'void main(void) {', - ' gl_FragColor = color;', - '}' - ].join('\n'), + handleTouchMove = function (e) { + if (e.touches.length === 1) { + stopPropagation(e); - linksVS = [ - 'attribute vec2 a_vertexPos;', - 'attribute vec4 a_color;', + var touch = e.touches[0]; + move(e, touch.clientX, touch.clientY); + } else if (e.touches.length === 2) { + // it's a zoom: + var currentPinchLength = getPinchZoomLength(e.touches[0], e.touches[1]); + var delta = 0; + if (currentPinchLength < pinchZoomLength) { + delta = -1; + } else if (currentPinchLength > pinchZoomLength) { + delta = 1; + } + scroll(e, delta, {x: e.touches[0].clientX, y: e.touches[0].clientY}); + pinchZoomLength = currentPinchLength; + stopPropagation(e); + preventDefault(e); + } + }, - 'uniform vec2 u_screenSize;', - 'uniform mat4 u_transform;', + handleTouchEnd = function (e) { + touchInProgress = false; + documentEvents.off('touchmove', handleTouchMove); + documentEvents.off('touchend', handleTouchEnd); + documentEvents.off('touchcancel', handleTouchEnd); + dragObject = null; + if (end) { end(e); } + }, - 'varying vec4 color;', + handleSignleFingerTouch = function (e, touch) { + stopPropagation(e); + preventDefault(e); - 'void main(void) {', - ' gl_Position = u_transform * vec4(a_vertexPos/u_screenSize, 0.0, 1.0);', - ' color = a_color.abgr;', - '}' - ].join('\n'), + startX = touch.clientX; + startY = touch.clientY; - program, - gl, - buffer, - utils, - locations, - linksCount = 0, - frontLinkId, // used to track z-index of links. - storage = new ArrayBuffer(16 * BYTES_PER_LINK), - positions = new Float32Array(storage), - colors = new Uint32Array(storage), - width, - height, - transform, - sizeDirty, + dragObject = e.target || e.srcElement; - ensureEnoughStorage = function () { - // TODO: this is a duplicate of webglNodeProgram code. Extract it to webgl.js - if ((linksCount+1)*BYTES_PER_LINK > storage.byteLength) { - // Every time we run out of space create new array twice bigger. - // TODO: it seems buffer size is limited. Consider using multiple arrays for huge graphs - var extendedStorage = new ArrayBuffer(storage.byteLength * 2), - extendedPositions = new Float32Array(extendedStorage), - extendedColors = new Uint32Array(extendedStorage); + if (start) { start(e, {x: startX, y : startY}); } + // TODO: can I enter into the state when touch is in progress + // but it's still a single finger touch? + if (!touchInProgress) { + touchInProgress = true; + documentEvents.on('touchmove', handleTouchMove); + documentEvents.on('touchend', handleTouchEnd); + documentEvents.on('touchcancel', handleTouchEnd); + } + }, + + handleTouchStart = function (e) { + if (e.touches.length === 1) { + return handleSignleFingerTouch(e, e.touches[0]); + } else if (e.touches.length === 2) { + // handleTouchMove() will care about pinch zoom. + stopPropagation(e); + preventDefault(e); + + pinchZoomLength = getPinchZoomLength(e.touches[0], e.touches[1]); - extendedColors.set(colors); // should be enough to copy just one view. - positions = extendedPositions; - colors = extendedColors; - storage = extendedStorage; } + // don't care about the rest. }; - return { - load : function (glContext) { - gl = glContext; - utils = Viva.Graph.webgl(glContext); - program = utils.createProgram(linksVS, linksFS); - gl.useProgram(program); - locations = utils.getLocations(program, ['a_vertexPos', 'a_color', 'u_screenSize', 'u_transform']); + element.addEventListener('mousedown', handleMouseDown); + element.addEventListener('touchstart', handleTouchStart); - gl.enableVertexAttribArray(locations.vertexPos); - gl.enableVertexAttribArray(locations.color); + return { + onStart : function (callback) { + start = callback; + return this; + }, - buffer = gl.createBuffer(); + onDrag : function (callback) { + drag = callback; + return this; }, - position: function (linkUi, fromPos, toPos) { - var linkIdx = linkUi.id, - offset = linkIdx * ATTRIBUTES_PER_PRIMITIVE; - positions[offset] = fromPos.x; - positions[offset + 1] = fromPos.y; - colors[offset + 2] = linkUi.color; + onStop : function (callback) { + end = callback; + return this; + }, - positions[offset + 3] = toPos.x; - positions[offset + 4] = toPos.y; - colors[offset + 5] = linkUi.color; + /** + * Occurs when mouse wheel event happens. callback = function(e, scrollDelta, scrollPoint); + */ + onScroll : function (callback) { + updateScrollEvents(callback); + return this; }, - createLink : function (ui) { - ensureEnoughStorage(); + release : function () { + // TODO: could be unsafe. We might wanna release dragObject, etc. + element.removeEventListener('mousedown', handleMouseDown); + element.removeEventListener('touchstart', handleTouchStart); - linksCount += 1; - frontLinkId = ui.id; - }, + documentEvents.off('mousemove', handleMouseMove); + documentEvents.off('mouseup', handleMouseUp); + documentEvents.off('touchmove', handleTouchMove); + documentEvents.off('touchend', handleTouchEnd); + documentEvents.off('touchcancel', handleTouchEnd); - removeLink : function (ui) { - if (linksCount > 0) { linksCount -= 1; } - // swap removed link with the last link. This will give us O(1) performance for links removal: - if (ui.id < linksCount && linksCount > 0) { - // using colors as a view to array buffer is okay here. - utils.copyArrayPart(colors, ui.id * ATTRIBUTES_PER_PRIMITIVE, linksCount * ATTRIBUTES_PER_PRIMITIVE, ATTRIBUTES_PER_PRIMITIVE); - } - }, + updateScrollEvents(null); + } + }; +} - updateTransform : function (newTransform) { - sizeDirty = true; - transform = newTransform; - }, +},{"../Utils/browserInfo.js":43,"../Utils/documentEvents.js":44,"../Utils/findElementPosition.js":45}],40:[function(require,module,exports){ +/** + * @author Andrei Kashcha (aka anvaka) / https://github.com/anvaka + */ - updateSize : function (w, h) { - width = w; - height = h; - sizeDirty = true; - }, +module.exports = webglInputManager; - render : function () { - gl.useProgram(program); - gl.bindBuffer(gl.ARRAY_BUFFER, buffer); - gl.bufferData(gl.ARRAY_BUFFER, storage, gl.DYNAMIC_DRAW); +var createInputEvents = require('../WebGL/webglInputEvents.js'); - if (sizeDirty) { - sizeDirty = false; - gl.uniformMatrix4fv(locations.transform, false, transform); - gl.uniform2f(locations.screenSize, width, height); - } +function webglInputManager(graph, graphics) { + var inputEvents = createInputEvents(graphics), + draggedNode = null, + internalHandlers = {}, + pos = {x : 0, y : 0}; - gl.vertexAttribPointer(locations.vertexPos, 2, gl.FLOAT, false, 3 * Float32Array.BYTES_PER_ELEMENT, 0); - gl.vertexAttribPointer(locations.color, 4, gl.UNSIGNED_BYTE, true, 3 * Float32Array.BYTES_PER_ELEMENT, 2 * 4); + inputEvents.mouseDown(function (node, e) { + draggedNode = node; + pos.x = e.clientX; + pos.y = e.clientY; - gl.drawArrays(gl.LINES, 0, linksCount * 2); + inputEvents.mouseCapture(draggedNode); - frontLinkId = linksCount - 1; - }, + var handlers = internalHandlers[node.id]; + if (handlers && handlers.onStart) { + handlers.onStart(e, pos); + } - bringToFront : function (link) { - if (frontLinkId > link.id) { - utils.swapArrayPart(positions, link.id * ATTRIBUTES_PER_PRIMITIVE, frontLinkId * ATTRIBUTES_PER_PRIMITIVE, ATTRIBUTES_PER_PRIMITIVE); - } - if (frontLinkId > 0) { - frontLinkId -= 1; + return true; + }).mouseUp(function (node) { + inputEvents.releaseMouseCapture(draggedNode); + + draggedNode = null; + var handlers = internalHandlers[node.id]; + if (handlers && handlers.onStop) { + handlers.onStop(); + } + return true; + }).mouseMove(function (node, e) { + if (draggedNode) { + var handlers = internalHandlers[draggedNode.id]; + if (handlers && handlers.onDrag) { + handlers.onDrag(e, {x : e.clientX - pos.x, y : e.clientY - pos.y }); } - }, - getFrontLinkId : function () { - return frontLinkId; + pos.x = e.clientX; + pos.y = e.clientY; + return true; + } + }); + + return { + /** + * Called by renderer to listen to drag-n-drop events from node. E.g. for SVG + * graphics we may listen to DOM events, whereas for WebGL we graphics + * should provide custom eventing mechanism. + * + * @param node - to be monitored. + * @param handlers - object with set of three callbacks: + * onStart: function(), + * onDrag: function(e, offset), + * onStop: function() + */ + bindDragNDrop : function (node, handlers) { + internalHandlers[node.id] = handlers; + if (!handlers) { + delete internalHandlers[node.id]; + } } }; -}; -/** - * @fileOverview Defines an image nodes for webglGraphics class. - * Shape of nodes is sqare. - * - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com - */ +} -/** - * Single texture in the webglAtlas. - */ -Viva.Graph.View.Texture = function (size) { - this.canvas = window.document.createElement("canvas"); - this.ctx = this.canvas.getContext("2d"); - this.isDirty = false; - this.canvas.width = this.canvas.height = size; -}; +},{"../WebGL/webglInputEvents.js":61}],41:[function(require,module,exports){ +module.exports = constant; + +var merge = require('ngraph.merge'); +var random = require('ngraph.random').random; +var Rect = require('../Utils/rect.js'); /** - * My naive implementation of textures atlas. It allows clients to load - * multimple images into atlas and get canvas representing all of them. + * Does not really perform any layouting algorithm but is compliant + * with renderer interface. Allowing clients to provide specific positioning + * callback and get static layout of the graph * - * @param tilesPerTexture - indicates how many images can be loaded to one - * texture of the atlas. If number of loaded images exceeds this - * parameter a new canvas will be created. + * @param {Viva.Graph.graph} graph to layout + * @param {Object} userSettings */ -Viva.Graph.View.webglAtlas = function (tilesPerTexture) { - var tilesPerRow = Math.sqrt(tilesPerTexture || 1024) << 0, - tileSize = tilesPerRow, - lastLoadedIdx = 1, - loadedImages = {}, - dirtyTimeoutId, - skipedDirty = 0, - textures = [], - trackedUrls = [], - that, - - isPowerOf2 = function (n) { - return (n & (n - 1)) === 0; - }, - createTexture = function () { - var texture = new Viva.Graph.View.Texture(tilesPerRow * tileSize); - textures.push(texture); - }, - getTileCoordinates = function (absolutePosition) { - var textureNumber = (absolutePosition / tilesPerTexture) << 0, - localTileNumber = (absolutePosition % tilesPerTexture), - row = (localTileNumber / tilesPerRow) << 0, - col = (localTileNumber % tilesPerRow); +function constant(graph, userSettings) { + userSettings = merge(userSettings, { + maxX : 1024, + maxY : 1024, + seed : 'Deterministic randomness made me do this' + }); + // This class simply follows API, it does not use some of the arguments: + /*jshint unused: false */ + var rand = random(userSettings.seed), + graphRect = new Rect(Number.MAX_VALUE, Number.MAX_VALUE, Number.MIN_VALUE, Number.MIN_VALUE), + layoutLinks = {}, - return {textureNumber : textureNumber, row : row, col: col}; - }, - markDirtyNow = function () { - that.isDirty = true; - skipedDirty = 0; - dirtyTimeoutId = null; + placeNodeCallback = function (node) { + return { + x: rand.next(userSettings.maxX), + y: rand.next(userSettings.maxY) + }; }, - markDirty = function () { - // delay this call, since it results in texture reload - if (dirtyTimeoutId) { - window.clearTimeout(dirtyTimeoutId); - skipedDirty += 1; - dirtyTimeoutId = null; - } - if (skipedDirty > 10) { - markDirtyNow(); - } else { - dirtyTimeoutId = window.setTimeout(markDirtyNow, 400); - } + updateGraphRect = function (position, graphRect) { + if (position.x < graphRect.x1) { graphRect.x1 = position.x; } + if (position.x > graphRect.x2) { graphRect.x2 = position.x; } + if (position.y < graphRect.y1) { graphRect.y1 = position.y; } + if (position.y > graphRect.y2) { graphRect.y2 = position.y; } }, - copy = function (from, to) { - var fromCanvas = textures[from.textureNumber].canvas, - toCtx = textures[to.textureNumber].ctx, - x = to.col * tileSize, - y = to.row * tileSize; + layoutNodes = typeof Object.create === 'function' ? Object.create(null) : {}, - toCtx.drawImage(fromCanvas, from.col * tileSize, from.row * tileSize, tileSize, tileSize, x, y, tileSize, tileSize); - textures[from.textureNumber].isDirty = true; - textures[to.textureNumber].isDirty = true; + ensureNodeInitialized = function (node) { + layoutNodes[node.id] = placeNodeCallback(node); + updateGraphRect(layoutNodes[node.id], graphRect); }, - drawAt = function (tileNumber, img, callback) { - var tilePosition = getTileCoordinates(tileNumber), - coordinates = { offset : tileNumber }; + updateNodePositions = function () { + if (graph.getNodesCount() === 0) { return; } - if (tilePosition.textureNumber >= textures.length) { - createTexture(); - } - var currentTexture = textures[tilePosition.textureNumber]; + graphRect.x1 = Number.MAX_VALUE; + graphRect.y1 = Number.MAX_VALUE; + graphRect.x2 = Number.MIN_VALUE; + graphRect.y2 = Number.MIN_VALUE; - currentTexture.ctx.drawImage(img, tilePosition.col * tileSize, tilePosition.row * tileSize, tileSize, tileSize); - trackedUrls[tileNumber] = img.src; + graph.forEachNode(ensureNodeInitialized); + }, - loadedImages[img.src] = coordinates; - currentTexture.isDirty = true; + ensureLinkInitialized = function (link) { + layoutLinks[link.id] = link; + }, - callback(coordinates); + onGraphChanged = function(changes) { + for (var i = 0; i < changes.length; ++i) { + var change = changes[i]; + if (change.node) { + if (change.changeType === 'add') { + ensureNodeInitialized(change.node); + } else { + delete layoutNodes[change.node.id]; + } + } if (change.link) { + if (change.changeType === 'add') { + ensureLinkInitialized(change.link); + } else { + delete layoutLinks[change.link.id]; + } + } + } }; - if (!isPowerOf2(tilesPerTexture)) { - throw "Tiles per texture should be power of two."; - } + graph.forEachNode(ensureNodeInitialized); + graph.forEachLink(ensureLinkInitialized); + graph.on('changed', onGraphChanged); - // this is the return object - that = { + return { /** - * indicates whether atlas has changed texture in it. If true then - * some of the textures has isDirty flag set as well. + * Attempts to layout graph within given number of iterations. + * + * @param {integer} [iterationsCount] number of algorithm's iterations. + * The constant layout ignores this parameter. */ - isDirty : false, + run : function (iterationsCount) { + this.step(); + }, /** - * Clears any signs of atlas changes. + * One step of layout algorithm. */ - clearDirty : function () { - var i; - this.isDirty = false; - for (i = 0; i < textures.length; ++i) { - textures[i].isDirty = false; - } + step : function () { + updateNodePositions(); + + return true; // no need to continue. }, /** - * Removes given url from colleciton of tiles in the atlas. + * Returns rectangle structure {x1, y1, x2, y2}, which represents + * current space occupied by graph. */ - remove : function (imgUrl) { - var coordinates = loadedImages[imgUrl]; - if (!coordinates) { return false; } - delete loadedImages[imgUrl]; - lastLoadedIdx -= 1; - - - if (lastLoadedIdx === coordinates.offset) { - return true; // Ignore if it's last image in the whole set. - } - - var tileToRemove = getTileCoordinates(coordinates.offset), - lastTileInSet = getTileCoordinates(lastLoadedIdx); - - copy(lastTileInSet, tileToRemove); + getGraphRect : function () { + return graphRect; + }, - var replacedOffset = loadedImages[trackedUrls[lastLoadedIdx]]; - replacedOffset.offset = coordinates.offset; - trackedUrls[coordinates.offset] = trackedUrls[lastLoadedIdx]; + /** + * Request to release all resources + */ + dispose : function () { + graph.off('change', onGraphChanged); + }, - markDirty(); + /* + * Checks whether given node is pinned; all nodes in this layout are pinned. + */ + isNodePinned: function (node) { return true; }, - /** - * Gets all textures in the atlas. + /* + * Requests layout algorithm to pin/unpin node to its current position + * Pinned nodes should not be affected by layout algorithm and always + * remain at their position */ - getTextures : function () { - return textures; // I trust you... + pinNode: function (node, isPinned) { + // noop }, + /* + * Gets position of a node by its id. If node was not seen by this + * layout algorithm undefined value is returned; + */ + getNodePosition: getNodePosition, + /** - * Gets coordinates of the given image in the atlas. Coordinates is an object: - * {offset : int } - where offset is an absolute position of the image in the - * atlas. - * - * Absolute means it can be larger than tilesPerTexture parameter, and in that - * case clients should get next texture in getTextures() collection. + * Returns {from, to} position of a link. */ - getCoordinates : function (imgUrl) { - return loadedImages[imgUrl]; + getLinkPosition: function (linkId) { + var link = layoutLinks[linkId]; + return { + from : getNodePosition(link.fromId), + to : getNodePosition(link.toId) + }; }, /** - * Asynchronously Loads the image to the atlas. Cross-domain security - * limitation applies. + * Sets position of a node to a given coordinates */ - load : function (imgUrl, callback) { - if (loadedImages.hasOwnProperty(imgUrl)) { - callback(loadedImages[imgUrl]); - } else { - var img = new window.Image(), - imgId = lastLoadedIdx; - - lastLoadedIdx += 1; - img.crossOrigin = "anonymous"; - img.onload = function () { - markDirty(); - drawAt(imgId, img, callback); - }; + setNodePosition: function (nodeId, x, y) { + var pos = layoutNodes[nodeId]; + if (pos) { + pos.x = x; + pos.y = y; + } + }, + + // Layout specific methods: - img.src = imgUrl; + /** + * Based on argument either update default node placement callback or + * attempts to place given node using current placement callback. + * Setting new node callback triggers position update for all nodes. + * + * @param {Object} newPlaceNodeCallbackOrNode - if it is a function then + * default node placement callback is replaced with new one. Node placement + * callback has a form of function (node) {}, and is expected to return an + * object with x and y properties set to numbers. + * + * Otherwise if it's not a function the argument is treated as graph node + * and current node placement callback will be used to place it. + */ + placeNode : function (newPlaceNodeCallbackOrNode) { + if (typeof newPlaceNodeCallbackOrNode === 'function') { + placeNodeCallback = newPlaceNodeCallbackOrNode; + updateNodePositions(); + return this; } + + // it is not a request to update placeNodeCallback, trying to place + // a node using current callback: + return placeNodeCallback(newPlaceNodeCallbackOrNode); } + }; - return that; -}; + function getNodePosition(nodeId) { + return layoutNodes[nodeId]; + } +} +},{"../Utils/rect.js":49,"ngraph.merge":17,"ngraph.random":30}],42:[function(require,module,exports){ /** - * Defines simple UI for nodes in webgl renderer. Each node is rendered as an image. + * This module provides compatibility layer with 0.6.x library. It will be + * removed in the next version */ -Viva.Graph.View.webglImageNodeProgram = function () { - var ATTRIBUTES_PER_PRIMITIVE = 18, - nodesFS = [ - "precision mediump float;", - "varying vec4 color;", - "varying vec3 vTextureCoord;", - "uniform sampler2D u_sampler0;", - "uniform sampler2D u_sampler1;", - "uniform sampler2D u_sampler2;", - "uniform sampler2D u_sampler3;", - - "void main(void) {", - " if (vTextureCoord.z == 0.) {", - " gl_FragColor = texture2D(u_sampler0, vTextureCoord.xy);", - " } else if (vTextureCoord.z == 1.) {", - " gl_FragColor = texture2D(u_sampler1, vTextureCoord.xy);", - " } else if (vTextureCoord.z == 2.) {", - " gl_FragColor = texture2D(u_sampler2, vTextureCoord.xy);", - " } else if (vTextureCoord.z == 3.) {", - " gl_FragColor = texture2D(u_sampler3, vTextureCoord.xy);", - " } else { gl_FragColor = vec4(0, 1, 0, 1); }", - "}" - ].join("\n"), - - nodesVS = [ - "attribute vec2 a_vertexPos;", - - "attribute float a_customAttributes;", - "uniform vec2 u_screenSize;", - "uniform mat4 u_transform;", - "uniform float u_tilesPerTexture;", - "varying vec3 vTextureCoord;", - - "void main(void) {", - " gl_Position = u_transform * vec4(a_vertexPos/u_screenSize, 0, 1);", - "float corner = mod(a_customAttributes, 4.);", - "float tileIndex = mod(floor(a_customAttributes / 4.), u_tilesPerTexture);", - "float tilesPerRow = sqrt(u_tilesPerTexture);", - "float tileSize = 1./tilesPerRow;", - "float tileColumn = mod(tileIndex, tilesPerRow);", - "float tileRow = floor(tileIndex/tilesPerRow);", - - "if(corner == 0.0) {", - " vTextureCoord.xy = vec2(0, 1);", - "} else if(corner == 1.0) {", - " vTextureCoord.xy = vec2(1, 1);", - "} else if(corner == 2.0) {", - " vTextureCoord.xy = vec2(0, 0);", - "} else {", - " vTextureCoord.xy = vec2(1, 0);", - "}", - - "vTextureCoord *= tileSize;", - "vTextureCoord.x += tileColumn * tileSize;", - "vTextureCoord.y += tileRow * tileSize;", - "vTextureCoord.z = floor(floor(a_customAttributes / 4.)/u_tilesPerTexture);", - "}" - ].join("\n"), - - tilesPerTexture = 1024, // TODO: Get based on max texture size - atlas; - - var program, - gl, - buffer, - utils, - locations, - nodesCount = 0, - nodes = new Float32Array(64), - width, - height, - transform, - sizeDirty, - refreshTexture = function (texture, idx) { - if (texture.nativeObject) { - gl.deleteTexture(texture.nativeObject); - } +var events = require('ngraph.events'); - var nativeObject = gl.createTexture(); - gl.activeTexture(gl["TEXTURE" + idx]); - gl.bindTexture(gl.TEXTURE_2D, nativeObject); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.canvas); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST); +module.exports = backwardCompatibleEvents; - gl.generateMipmap(gl.TEXTURE_2D); - gl.uniform1i(locations["sampler" + idx], idx); +function backwardCompatibleEvents(g) { + console.log("This method is deprecated. Please use Viva.events() instead"); - texture.nativeObject = nativeObject; - }, + if (!g) { + return g; + } - ensureAtlasTextureUpdated = function () { - if (atlas.isDirty) { - var textures = atlas.getTextures(), - i; - for (i = 0; i < textures.length; ++i) { - if (textures[i].isDirty || !textures[i].nativeObject) { - refreshTexture(textures[i], i); - } - } + var eventsDefined = (g.on !== undefined) || + (g.off !== undefined) || + (g.fire !== undefined); - atlas.clearDirty(); - } - }; + if (eventsDefined) { + // events already defined, ignore + return { + extend: function() { + return g; + }, + on: g.on, + stop: g.off + }; + } + + return { + extend: extend, + on: g.on, + stop: g.off + }; + + function extend() { + var backwardCompatible = events(g); + backwardCompatible.addEventListener = backwardCompatible.on; + return backwardCompatible; + } +} + +},{"ngraph.events":9}],43:[function(require,module,exports){ +module.exports = browserInfo(); + +function browserInfo() { + if (typeof window === "undefined" || !window.hasOwnProperty("navigator")) { + return { + browser : "", + version : "0" + }; + } + + var ua = window.navigator.userAgent.toLowerCase(), + // Useragent RegExp + rwebkit = /(webkit)[ \/]([\w.]+)/, + ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/, + rmsie = /(msie) ([\w.]+)/, + rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/, + match = rwebkit.exec(ua) || + ropera.exec(ua) || + rmsie.exec(ua) || + (ua.indexOf("compatible") < 0 && rmozilla.exec(ua)) || + []; + + return { + browser: match[1] || "", + version: match[2] || "0" + }; +} + +},{}],44:[function(require,module,exports){ +var nullEvents = require('./nullEvents.js'); + +module.exports = createDocumentEvents(); + +function createDocumentEvents() { + if (typeof document === undefined) { + return nullEvents; + } + + return { + on: on, + off: off + }; +} + +function on(eventName, handler) { + document.addEventListener(eventName, handler); +} + +function off(eventName, handler) { + document.removeEventListener(eventName, handler); +} + +},{"./nullEvents.js":48}],45:[function(require,module,exports){ +/** + * Finds the absolute position of an element on a page + */ +module.exports = findElementPosition; + +function findElementPosition(obj) { + var curleft = 0, + curtop = 0; + if (obj.offsetParent) { + do { + curleft += obj.offsetLeft; + curtop += obj.offsetTop; + } while ((obj = obj.offsetParent) !== null); + } + + return [curleft, curtop]; +} + +},{}],46:[function(require,module,exports){ +module.exports = getDimension; + +function getDimension(container) { + if (!container) { + throw { + message : 'Cannot get dimensions of undefined container' + }; + } + + // TODO: Potential cross browser bug. + var width = container.clientWidth; + var height = container.clientHeight; + + return { + left : 0, + top : 0, + width : width, + height : height + }; +} - return { - load : function (glContext) { - gl = glContext; - utils = Viva.Graph.webgl(glContext); +},{}],47:[function(require,module,exports){ +var intersect = require('gintersect'); - atlas = new Viva.Graph.View.webglAtlas(tilesPerTexture); +module.exports = intersectRect; - program = utils.createProgram(nodesVS, nodesFS); - gl.useProgram(program); - locations = utils.getLocations(program, ["a_vertexPos", "a_customAttributes", "u_screenSize", "u_transform", "u_sampler0", "u_sampler1", "u_sampler2", "u_sampler3", "u_tilesPerTexture"]); +function intersectRect(left, top, right, bottom, x1, y1, x2, y2) { + return intersect(left, top, left, bottom, x1, y1, x2, y2) || + intersect(left, bottom, right, bottom, x1, y1, x2, y2) || + intersect(right, bottom, right, top, x1, y1, x2, y2) || + intersect(right, top, left, top, x1, y1, x2, y2); +} - gl.uniform1f(locations.tilesPerTexture, tilesPerTexture); +},{"gintersect":3}],48:[function(require,module,exports){ +module.exports = createNullEvents(); - gl.enableVertexAttribArray(locations.vertexPos); - gl.enableVertexAttribArray(locations.customAttributes); +function createNullEvents() { + return { + on: noop, + off: noop, + stop: noop + }; +} - buffer = gl.createBuffer(); - }, +function noop() { } - /** - * Updates position of current node in the buffer of nodes. - * - * @param idx - index of current node. - * @param pos - new position of the node. - */ - position : function (nodeUI, pos) { - var idx = nodeUI.id * ATTRIBUTES_PER_PRIMITIVE; - nodes[idx] = pos.x - nodeUI.size; - nodes[idx + 1] = pos.y - nodeUI.size; - nodes[idx + 2] = nodeUI._offset * 4; - - nodes[idx + 3] = pos.x + nodeUI.size; - nodes[idx + 4] = pos.y - nodeUI.size; - nodes[idx + 5] = nodeUI._offset * 4 + 1; - - nodes[idx + 6] = pos.x - nodeUI.size; - nodes[idx + 7] = pos.y + nodeUI.size; - nodes[idx + 8] = nodeUI._offset * 4 + 2; - - nodes[idx + 9] = pos.x - nodeUI.size; - nodes[idx + 10] = pos.y + nodeUI.size; - nodes[idx + 11] = nodeUI._offset * 4 + 2; - - nodes[idx + 12] = pos.x + nodeUI.size; - nodes[idx + 13] = pos.y - nodeUI.size; - nodes[idx + 14] = nodeUI._offset * 4 + 1; - - nodes[idx + 15] = pos.x + nodeUI.size; - nodes[idx + 16] = pos.y + nodeUI.size; - nodes[idx + 17] = nodeUI._offset * 4 + 3; - }, +},{}],49:[function(require,module,exports){ +module.exports = Rect; - createNode : function (ui) { - nodes = utils.extendArray(nodes, nodesCount, ATTRIBUTES_PER_PRIMITIVE); - nodesCount += 1; - - var coordinates = atlas.getCoordinates(ui.src); - if (coordinates) { - ui._offset = coordinates.offset; - } else { - ui._offset = 0; - // Image is not yet loaded into the atlas. Reload it: - atlas.load(ui.src, function (coordinates) { - ui._offset = coordinates.offset; - }); - } - }, +/** + * Very generic rectangle. + */ +function Rect (x1, y1, x2, y2) { + this.x1 = x1 || 0; + this.y1 = y1 || 0; + this.x2 = x2 || 0; + this.y2 = y2 || 0; +} - removeNode : function (nodeUI) { - if (nodesCount > 0) { nodesCount -= 1; } +},{}],50:[function(require,module,exports){ +(function (global){ +/** + * @author Andrei Kashcha (aka anvaka) / https://github.com/anvaka + */ + +module.exports = createTimer(); + +function createTimer() { + var lastTime = 0, + vendors = ['ms', 'moz', 'webkit', 'o'], + i, + scope; + + if (typeof window !== 'undefined') { + scope = window; + } else if (typeof global !== 'undefined') { + scope = global; + } else { + scope = { + setTimeout: noop, + clearTimeout: noop + }; + } + + for (i = 0; i < vendors.length && !scope.requestAnimationFrame; ++i) { + var vendorPrefix = vendors[i]; + scope.requestAnimationFrame = scope[vendorPrefix + 'RequestAnimationFrame']; + scope.cancelAnimationFrame = + scope[vendorPrefix + 'CancelAnimationFrame'] || scope[vendorPrefix + 'CancelRequestAnimationFrame']; + } + + if (!scope.requestAnimationFrame) { + scope.requestAnimationFrame = rafPolyfill; + } + + if (!scope.cancelAnimationFrame) { + scope.cancelAnimationFrame = cancelRafPolyfill; + } + + return timer; + + /** + * Timer that fires callback with given interval (in ms) until + * callback returns true; + */ + function timer(callback) { + var intervalId; + startTimer(); // start it right away. + + return { + /** + * Stops execution of the callback + */ + stop: stopTimer, + + restart: restart + }; + + function startTimer() { + intervalId = scope.requestAnimationFrame(startTimer); + if (!callback()) { + stopTimer(); + } + } + + function stopTimer() { + scope.cancelAnimationFrame(intervalId); + intervalId = 0; + } + + function restart() { + if (!intervalId) { + startTimer(); + } + } + } + + function rafPolyfill(callback) { + var currTime = new Date().getTime(); + var timeToCall = Math.max(0, 16 - (currTime - lastTime)); + var id = scope.setTimeout(function() { + callback(currTime + timeToCall); + }, timeToCall); + lastTime = currTime + timeToCall; + return id; + } + + function cancelRafPolyfill(id) { + scope.clearTimeout(id); + } +} + +function noop() {} - if (nodeUI.id < nodesCount && nodesCount > 0) { - if (nodeUI.src) { - atlas.remove(nodeUI.src); - } +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],51:[function(require,module,exports){ +var nullEvents = require('./nullEvents.js'); - utils.copyArrayPart(nodes, nodeUI.id * ATTRIBUTES_PER_PRIMITIVE, nodesCount * ATTRIBUTES_PER_PRIMITIVE, ATTRIBUTES_PER_PRIMITIVE); - } - }, +module.exports = createDocumentEvents(); - replaceProperties : function (replacedNode, newNode) { - newNode._offset = replacedNode._offset; - }, +function createDocumentEvents() { + if (typeof window === 'undefined') { + return nullEvents; + } - updateTransform : function (newTransform) { - sizeDirty = true; - transform = newTransform; - }, + return { + on: on, + off: off + }; +} - updateSize : function (w, h) { - width = w; - height = h; - sizeDirty = true; - }, +function on(eventName, handler) { + window.addEventListener(eventName, handler); +} - render : function () { - gl.useProgram(program); - gl.bindBuffer(gl.ARRAY_BUFFER, buffer); - gl.bufferData(gl.ARRAY_BUFFER, nodes, gl.DYNAMIC_DRAW); +function off(eventName, handler) { + window.removeEventListener(eventName, handler); +} - if (sizeDirty) { - sizeDirty = false; - gl.uniformMatrix4fv(locations.transform, false, transform); - gl.uniform2f(locations.screenSize, width, height); - } - gl.vertexAttribPointer(locations.vertexPos, 2, gl.FLOAT, false, 3 * Float32Array.BYTES_PER_ELEMENT, 0); - gl.vertexAttribPointer(locations.customAttributes, 1, gl.FLOAT, false, 3 * Float32Array.BYTES_PER_ELEMENT, 2 * 4); +},{"./nullEvents.js":48}],52:[function(require,module,exports){ +/** + * @fileOverview Defines a graph renderer that uses CSS based drawings. + * + * @author Andrei Kashcha (aka anvaka) / https://github.com/anvaka + */ + +module.exports = renderer; + +var eventify = require('ngraph.events'); +var forceDirected = require('ngraph.forcelayout'); +var svgGraphics = require('./svgGraphics.js'); +var windowEvents = require('../Utils/windowEvents.js'); +var domInputManager = require('../Input/domInputManager.js'); +var timer = require('../Utils/timer.js'); +var getDimension = require('../Utils/getDimensions.js'); +var dragndrop = require('../Input/dragndrop.js'); + +/** + * This is heart of the rendering. Class accepts graph to be rendered and rendering settings. + * It monitors graph changes and depicts them accordingly. + * + * @param graph - Viva.Graph.graph() object to be rendered. + * @param settings - rendering settings, composed from the following parts (with their defaults shown): + * settings = { + * // Represents a module that is capable of displaying graph nodes and links. + * // all graphics has to correspond to defined interface and can be later easily + * // replaced for specific needs (e.g. adding WebGL should be piece of cake as long + * // as WebGL has implemented required interface). See svgGraphics for example. + * graphics : Viva.Graph.View.svgGraphics(), + * + * // Where the renderer should draw graph. Container size matters, because + * // renderer will attempt center graph to that size. Also graphics modules + * // might depend on it. + * container : document.body, + * + * // Defines whether graph can respond to use input + * interactive: true, + * + * // Layout algorithm to be used. The algorithm is expected to comply with defined + * // interface and is expected to be iterative. Renderer will use it then to calculate + * // graph's layout. For examples of the interface refer to Viva.Graph.Layout.forceDirected() + * layout : Viva.Graph.Layout.forceDirected(), + * + * // Directs renderer to display links. Usually rendering links is the slowest part of this + * // library. So if you don't need to display links, consider settings this property to false. + * renderLinks : true, + * + * // Number of layout iterations to run before displaying the graph. The bigger you set this number + * // the closer to ideal position graph will appear first time. But be careful: for large graphs + * // it can freeze the browser. + * prerender : 0 + * } + */ +function renderer(graph, settings) { + // TODO: This class is getting hard to understand. Consider refactoring. + // TODO: I have a technical debt here: fix scaling/recentering! Currently it's a total mess. + var FRAME_INTERVAL = 30; + + settings = settings || {}; + + var layout = settings.layout, + graphics = settings.graphics, + container = settings.container, + interactive = settings.interactive !== undefined ? settings.interactive : true, + inputManager, + animationTimer, + rendererInitialized = false, + updateCenterRequired = true, + + isStable = false, + userInteraction = false, + isPaused = false, + + transform = { + offsetX: 0, + offsetY: 0, + scale: 1 + }, + + publicEvents = eventify({}), + containerDrag; + + return { + /** + * Performs rendering of the graph. + * + * @param iterationsCount if specified renderer will run only given number of iterations + * and then stop. Otherwise graph rendering is performed indefinitely. + * + * Note: if rendering stopped by used started dragging nodes or new nodes were added to the + * graph renderer will give run more iterations to reflect changes. + */ + run: function(iterationsCount) { + + if (!rendererInitialized) { + prepareSettings(); + prerender(); + + initDom(); + updateCenter(); + listenToEvents(); + + rendererInitialized = true; + } + + renderIterations(iterationsCount); + + return this; + }, + + reset: function() { + graphics.resetScale(); + updateCenter(); + transform.scale = 1; + }, + + pause: function() { + isPaused = true; + animationTimer.stop(); + }, + + resume: function() { + isPaused = false; + animationTimer.restart(); + }, + + rerender: function() { + renderGraph(); + return this; + }, + + zoomOut: function() { + return scale(true); + }, + + zoomIn: function() { + return scale(false); + }, + + /** + * Returns current transformation matrix. + */ + getTransform: function() { + return transform; + }, + + /** + * Centers renderer at x,y graph's coordinates + */ + moveTo: function(x, y) { + graphics.graphCenterChanged(transform.offsetX - x * transform.scale, transform.offsetY - y * transform.scale); + renderGraph(); + }, + + /** + * Gets current graphics object + */ + getGraphics: function() { + return graphics; + }, + + /** + * Gets current layout. + */ + getLayout: function() { + return layout; + }, + + /** + * Removes this renderer and deallocates all resources/timers + */ + dispose: function() { + stopListenToEvents(); // I quit! + }, + + on: function(eventName, callback) { + publicEvents.on(eventName, callback); + return this; + }, + + off: function(eventName, callback) { + publicEvents.off(eventName, callback); + return this; + } + }; + + /** + * Checks whether given interaction (node/scroll) is enabled + */ + function isInteractive(interactionName) { + if (typeof interactive === 'string') { + return interactive.indexOf(interactionName) >= 0; + } else if (typeof interactive === 'boolean') { + return interactive; + } + return true; // default setting + } + + function prepareSettings() { + container = container || window.document.body; + layout = layout || forceDirected(graph, { + springLength: 80, + springCoeff: 0.0002, + }); + graphics = graphics || svgGraphics(graph, { + container: container + }); + + if (!settings.hasOwnProperty('renderLinks')) { + settings.renderLinks = true; + } + + settings.prerender = settings.prerender || 0; + inputManager = (graphics.inputManager || domInputManager)(graph, graphics); + } + + function renderGraph() { + graphics.beginRender(); + + // todo: move this check graphics + if (settings.renderLinks) { + graphics.renderLinks(); + } + graphics.renderNodes(); + graphics.endRender(); + } + + function onRenderFrame() { + isStable = layout.step() && !userInteraction; + renderGraph(); + + return !isStable; + } + + function renderIterations(iterationsCount) { + if (animationTimer) { + return; + } + + if (iterationsCount !== undefined) { + animationTimer = timer(function() { + iterationsCount -= 1; + if (iterationsCount < 0) { + var needMoreFrames = false; + return needMoreFrames; + } + + return onRenderFrame(); + }, FRAME_INTERVAL); + } else { + animationTimer = timer(onRenderFrame, FRAME_INTERVAL); + } + } + + function resetStable() { + if (isPaused) { + return; + } + + isStable = false; + animationTimer.restart(); + } + + function prerender() { + // To get good initial positions for the graph + // perform several prerender steps in background. + if (typeof settings.prerender === 'number' && settings.prerender > 0) { + for (var i = 0; i < settings.prerender; i += 1) { + layout.step(); + } + } + } + + function updateCenter() { + var graphRect = layout.getGraphRect(), + containerSize = getDimension(container); + + var cx = (graphRect.x2 + graphRect.x1) / 2; + var cy = (graphRect.y2 + graphRect.y1) / 2; + transform.offsetX = containerSize.width / 2 - (cx * transform.scale - cx); + transform.offsetY = containerSize.height / 2 - (cy * transform.scale - cy); + graphics.graphCenterChanged(transform.offsetX, transform.offsetY); + + updateCenterRequired = false; + } + + function createNodeUi(node) { + var nodePosition = layout.getNodePosition(node.id); + graphics.addNode(node, nodePosition); + } + + function removeNodeUi(node) { + graphics.releaseNode(node); + } + + function createLinkUi(link) { + var linkPosition = layout.getLinkPosition(link.id); + graphics.addLink(link, linkPosition); + } + + function removeLinkUi(link) { + graphics.releaseLink(link); + } + + function listenNodeEvents(node) { + if (!isInteractive('node')) { + return; + } + + var wasPinned = false; + + // TODO: This may not be memory efficient. Consider reusing handlers object. + inputManager.bindDragNDrop(node, { + onStart: function() { + wasPinned = layout.isNodePinned(node); + layout.pinNode(node, true); + userInteraction = true; + resetStable(); + }, + onDrag: function(e, offset) { + var oldPos = layout.getNodePosition(node.id); + layout.setNodePosition(node.id, + oldPos.x + offset.x / transform.scale, + oldPos.y + offset.y / transform.scale); + + userInteraction = true; + + renderGraph(); + }, + onStop: function() { + layout.pinNode(node, wasPinned); + userInteraction = false; + } + }); + } + + function releaseNodeEvents(node) { + inputManager.bindDragNDrop(node, null); + } + + function initDom() { + graphics.init(container); + + graph.forEachNode(createNodeUi); + + if (settings.renderLinks) { + graph.forEachLink(createLinkUi); + } + } + + function releaseDom() { + graphics.release(container); + } + + function processNodeChange(change) { + var node = change.node; + + if (change.changeType === 'add') { + createNodeUi(node); + listenNodeEvents(node); + if (updateCenterRequired) { + updateCenter(); + } + } else if (change.changeType === 'remove') { + releaseNodeEvents(node); + removeNodeUi(node); + if (graph.getNodesCount() === 0) { + updateCenterRequired = true; // Next time when node is added - center the graph. + } + } else if (change.changeType === 'update') { + releaseNodeEvents(node); + removeNodeUi(node); + + createNodeUi(node); + listenNodeEvents(node); + } + } + + function processLinkChange(change) { + var link = change.link; + if (change.changeType === 'add') { + if (settings.renderLinks) { + createLinkUi(link); + } + } else if (change.changeType === 'remove') { + if (settings.renderLinks) { + removeLinkUi(link); + } + } else if (change.changeType === 'update') { + throw 'Update type is not implemented. TODO: Implement me!'; + } + } + + function onGraphChanged(changes) { + var i, change; + for (i = 0; i < changes.length; i += 1) { + change = changes[i]; + if (change.node) { + processNodeChange(change); + } else if (change.link) { + processLinkChange(change); + } + } + + resetStable(); + } + + function onWindowResized() { + updateCenter(); + onRenderFrame(); + } + + function releaseContainerDragManager() { + if (containerDrag) { + containerDrag.release(); + containerDrag = null; + } + } + + function releaseGraphEvents() { + graph.off('changed', onGraphChanged); + } + + function scale(out, scrollPoint) { + if (!scrollPoint) { + var containerSize = getDimension(container); + scrollPoint = { + x: containerSize.width / 2, + y: containerSize.height / 2 + }; + } + var scaleFactor = Math.pow(1 + 0.4, out ? -0.2 : 0.2); + transform.scale = graphics.scale(scaleFactor, scrollPoint); + + renderGraph(); + publicEvents.fire('scale', transform.scale); + + return transform.scale; + } + + function listenToEvents() { + windowEvents.on('resize', onWindowResized); + + releaseContainerDragManager(); + if (isInteractive('drag')) { + containerDrag = dragndrop(container); + containerDrag.onDrag(function(e, offset) { + graphics.translateRel(offset.x, offset.y); + + renderGraph(); + publicEvents.fire('drag', offset); + }); + } + + if (isInteractive('scroll')) { + if (!containerDrag) { + containerDrag = dragndrop(container); + } + containerDrag.onScroll(function(e, scaleOffset, scrollPoint) { + scale(scaleOffset < 0, scrollPoint); + }); + } + + graph.forEachNode(listenNodeEvents); + + releaseGraphEvents(); + graph.on('changed', onGraphChanged); + } + + function stopListenToEvents() { + rendererInitialized = false; + releaseGraphEvents(); + releaseContainerDragManager(); + windowEvents.off('resize', onWindowResized); + publicEvents.off(); + animationTimer.stop(); + + graph.forEachLink(function(link) { + if (settings.renderLinks) { + removeLinkUi(link); + } + }); + + graph.forEachNode(function(node) { + releaseNodeEvents(node); + removeNodeUi(node); + }); + + layout.dispose(); + releaseDom(); + } +} - ensureAtlasTextureUpdated(); +},{"../Input/domInputManager.js":38,"../Input/dragndrop.js":39,"../Utils/getDimensions.js":46,"../Utils/timer.js":50,"../Utils/windowEvents.js":51,"./svgGraphics.js":53,"ngraph.events":9,"ngraph.forcelayout":11}],53:[function(require,module,exports){ +/** + * @fileOverview Defines a graph renderer that uses SVG based drawings. + * + * @author Andrei Kashcha (aka anvaka) / https://github.com/anvaka + */ + +module.exports = svgGraphics; + +var svg = require('simplesvg'); +var eventify = require('ngraph.events'); +var domInputManager = require('../Input/domInputManager.js'); + +/** + * Performs svg-based graph rendering. This module does not perform + * layout, but only visualizes nodes and edges of the graph. + */ +function svgGraphics() { + var svgContainer, + svgRoot, + offsetX = 0, + offsetY = 0, + initCallback, + actualScale = 1, + allNodes = {}, + allLinks = {}, +/*jshint unused: false */ + nodeBuilder = function (node) { + return svg("rect") + .attr("width", 10) + .attr("height", 10) + .attr("fill", "#00a2e8"); + }, + + nodePositionCallback = function (nodeUI, pos) { + // TODO: Remove magic 5. It should be half of the width or height of the node. + nodeUI.attr("x", pos.x - 5) + .attr("y", pos.y - 5); + }, + + linkBuilder = function (link) { + return svg("line").attr("stroke", "#999"); + }, + + linkPositionCallback = function (linkUI, fromPos, toPos) { + linkUI.attr("x1", fromPos.x) + .attr("y1", fromPos.y) + .attr("x2", toPos.x) + .attr("y2", toPos.y); + }, + + fireRescaled = function (graphics) { + // TODO: maybe we shall copy changes? + graphics.fire("rescaled"); + }, + + cachedPos = {x : 0, y: 0}, + cachedFromPos = {x : 0, y: 0}, + cachedToPos = {x : 0, y: 0}, + + updateTransform = function () { + if (svgContainer) { + var transform = "matrix(" + actualScale + ", 0, 0," + actualScale + "," + offsetX + "," + offsetY + ")"; + svgContainer.attr("transform", transform); + } + }; + + svgRoot = createSvgRoot(); + + var graphics = { + getNodeUI: function (nodeId) { + return allNodes[nodeId]; + }, + + getLinkUI: function (linkId) { + return allLinks[linkId]; + }, + + /** + * Sets the callback that creates node representation. + * + * @param builderCallback a callback function that accepts graph node + * as a parameter and must return an element representing this node. + * + * @returns If builderCallbackOrNode is a valid callback function, instance of this is returned; + * Otherwise undefined value is returned + */ + node : function (builderCallback) { + if (typeof builderCallback !== "function") { + return; // todo: throw? This is not compatible with old versions + } + + nodeBuilder = builderCallback; + + return this; + }, + + /** + * Sets the callback that creates link representation + * + * @param builderCallback a callback function that accepts graph link + * as a parameter and must return an element representing this link. + * + * @returns If builderCallback is a valid callback function, instance of this is returned; + * Otherwise undefined value is returned. + */ + link : function (builderCallback) { + if (typeof builderCallback !== "function") { + return; // todo: throw? This is not compatible with old versions + } + + linkBuilder = builderCallback; + return this; + }, + + /** + * Allows to override default position setter for the node with a new + * function. newPlaceCallback(nodeUI, position, node) is function which + * is used by updateNodePosition(). + */ + placeNode : function (newPlaceCallback) { + nodePositionCallback = newPlaceCallback; + return this; + }, + + placeLink : function (newPlaceLinkCallback) { + linkPositionCallback = newPlaceLinkCallback; + return this; + }, + + /** + * Called every before renderer starts rendering. + */ + beginRender : function () {}, + + /** + * Called every time when renderer finishes one step of rendering. + */ + endRender : function () {}, + + /** + * Sets translate operation that should be applied to all nodes and links. + */ + graphCenterChanged : function (x, y) { + offsetX = x; + offsetY = y; + updateTransform(); + }, + + /** + * Default input manager listens to DOM events to process nodes drag-n-drop + */ + inputManager : domInputManager, + + translateRel : function (dx, dy) { + var p = svgRoot.createSVGPoint(), + t = svgContainer.getCTM(), + origin = svgRoot.createSVGPoint().matrixTransform(t.inverse()); + + p.x = dx; + p.y = dy; + + p = p.matrixTransform(t.inverse()); + p.x = (p.x - origin.x) * t.a; + p.y = (p.y - origin.y) * t.d; + + t.e += p.x; + t.f += p.y; + + var transform = "matrix(" + t.a + ", 0, 0," + t.d + "," + t.e + "," + t.f + ")"; + svgContainer.attr("transform", transform); + }, + + scale : function (scaleFactor, scrollPoint) { + var p = svgRoot.createSVGPoint(); + p.x = scrollPoint.x; + p.y = scrollPoint.y; + + p = p.matrixTransform(svgContainer.getCTM().inverse()); // translate to SVG coordinates + + // Compute new scale matrix in current mouse position + var k = svgRoot.createSVGMatrix().translate(p.x, p.y).scale(scaleFactor).translate(-p.x, -p.y), + t = svgContainer.getCTM().multiply(k); + + actualScale = t.a; + offsetX = t.e; + offsetY = t.f; + var transform = "matrix(" + t.a + ", 0, 0," + t.d + "," + t.e + "," + t.f + ")"; + svgContainer.attr("transform", transform); + + fireRescaled(this); + return actualScale; + }, + + resetScale : function () { + actualScale = 1; + var transform = "matrix(1, 0, 0, 1, 0, 0)"; + svgContainer.attr("transform", transform); + fireRescaled(this); + return this; + }, + + /** + * Called by Viva.Graph.View.renderer to let concrete graphic output + * provider prepare to render. + */ + init : function (container) { + container.appendChild(svgRoot); + updateTransform(); + // Notify the world if someone waited for update. TODO: should send an event + if (typeof initCallback === "function") { + initCallback(svgRoot); + } + }, + + /** + * Called by Viva.Graph.View.renderer to let concrete graphic output + * provider release occupied resources. + */ + release : function (container) { + if (svgRoot && container) { + container.removeChild(svgRoot); + } + }, + + /** + * Called by Viva.Graph.View.renderer to let concrete graphic output + * provider prepare to render given link of the graph + * + * @param link - model of a link + */ + addLink: function (link, pos) { + var linkUI = linkBuilder(link); + if (!linkUI) { return; } + linkUI.position = pos; + linkUI.link = link; + allLinks[link.id] = linkUI; + if (svgContainer.childElementCount > 0) { + svgContainer.insertBefore(linkUI, svgContainer.firstChild); + } else { + svgContainer.appendChild(linkUI); + } + return linkUI; + }, + + /** + * Called by Viva.Graph.View.renderer to let concrete graphic output + * provider remove link from rendering surface. + * + * @param linkUI visual representation of the link created by link() execution. + **/ + releaseLink : function (link) { + var linkUI = allLinks[link.id]; + if (linkUI) { + svgContainer.removeChild(linkUI); + delete allLinks[link.id]; + } + }, + + /** + * Called by Viva.Graph.View.renderer to let concrete graphic output + * provider prepare to render given node of the graph. + * + * @param nodeUI visual representation of the node created by node() execution. + **/ + addNode : function (node, pos) { + var nodeUI = nodeBuilder(node); + if (!nodeUI) { + return; + } + nodeUI.position = pos; + nodeUI.node = node; + allNodes[node.id] = nodeUI; + + svgContainer.appendChild(nodeUI); + + return nodeUI; + }, + + /** + * Called by Viva.Graph.View.renderer to let concrete graphic output + * provider remove node from rendering surface. + * + * @param node graph's node + **/ + releaseNode : function (node) { + var nodeUI = allNodes[node.id]; + if (nodeUI) { + svgContainer.removeChild(nodeUI); + delete allNodes[node.id]; + } + }, + + renderNodes : function () { + for (var key in allNodes) { + if (allNodes.hasOwnProperty(key)) { + var nodeUI = allNodes[key]; + cachedPos.x = nodeUI.position.x; + cachedPos.y = nodeUI.position.y; + nodePositionCallback(nodeUI, cachedPos, nodeUI.node); + } + } + }, + + renderLinks : function () { + for (var key in allLinks) { + if (allLinks.hasOwnProperty(key)) { + var linkUI = allLinks[key]; + cachedFromPos.x = linkUI.position.from.x; + cachedFromPos.y = linkUI.position.from.y; + cachedToPos.x = linkUI.position.to.x; + cachedToPos.y = linkUI.position.to.y; + linkPositionCallback(linkUI, cachedFromPos, cachedToPos, linkUI.link); + } + } + }, + + /** + * Returns root element which hosts graphics. + */ + getGraphicsRoot : function (callbackWhenReady) { + // todo: should fire an event, instead of having this context. + if (typeof callbackWhenReady === "function") { + if (svgRoot) { + callbackWhenReady(svgRoot); + } else { + initCallback = callbackWhenReady; + } + } + return svgRoot; + }, + /** + * Returns root SVG element. + * + * Note: This is internal method specific to this renderer + */ + getSvgRoot : function () { + return svgRoot; + } + }; + + + // Let graphics fire events before we return it to the caller. + eventify(graphics); + + return graphics; + + function createSvgRoot() { + var svgRoot = svg("svg"); + + svgContainer = svg("g") + .attr("buffered-rendering", "dynamic"); + + svgRoot.appendChild(svgContainer); + return svgRoot; + } +} - gl.drawArrays(gl.TRIANGLES, 0, nodesCount * 6); - } - }; -};/** +},{"../Input/domInputManager.js":38,"ngraph.events":9,"simplesvg":32}],54:[function(require,module,exports){ +/** * @fileOverview Defines a graph renderer that uses WebGL based drawings. * - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com + * @author Andrei Kashcha (aka anvaka) / https://github.com/anvaka */ -Viva.Graph.View = Viva.Graph.View || {}; +module.exports = webglGraphics; + +var webglInputManager = require('../Input/webglInputManager.js'); +var webglLinkProgram = require('../WebGL/webglLinkProgram.js'); +var webglNodeProgram = require('../WebGL/webglNodeProgram.js'); +var webglSquare = require('../WebGL/webglSquare.js'); +var webglLine = require('../WebGL/webglLine.js'); +var eventify = require('ngraph.events'); +var merge = require('ngraph.merge'); /** * Performs webgl-based graph rendering. This module does not perform - * layout, but only visualizes nodes and edeges of the graph. + * layout, but only visualizes nodes and edges of the graph. * * @param options - to customize graphics behavior. Currently supported parameter * enableBlending - true by default, allows to use transparency in node/links colors. - * preserveDrawingBuffer - false by default, tells webgl to preserve drawing buffer. + * preserveDrawingBuffer - false by default, tells webgl to preserve drawing buffer. * See https://www.khronos.org/registry/webgl/specs/1.0/#5.2 */ -Viva.Graph.View.webglGraphics = function (options) { - options = Viva.lazyExtend(options, { +function webglGraphics(options) { + options = merge(options, { enableBlending : true, preserveDrawingBuffer : false, clearColor: false, @@ -5743,22 +5484,29 @@ Viva.Graph.View.webglGraphics = function (options) { height, nodesCount = 0, linksCount = 0, - transform, + transform = [ + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + ], userPlaceNodeCallback, userPlaceLinkCallback, nodes = [], links = [], initCallback, - linkProgram = Viva.Graph.View.webglLinkProgram(), - nodeProgram = Viva.Graph.View.webglNodeProgram(), + allNodes = {}, + allLinks = {}, + linkProgram = webglLinkProgram(), + nodeProgram = webglNodeProgram(), /*jshint unused: false */ nodeUIBuilder = function (node) { - return Viva.Graph.View.webglSquare(); // Just make a square, using provided gl context (a nodeProgram); + return webglSquare(); // Just make a square, using provided gl context (a nodeProgram); }, linkUIBuilder = function (link) { - return Viva.Graph.View.webglLine(0xb3b3b3ff); + return webglLine(0xb3b3b3ff); }, /*jshint unused: true */ updateTransformUniform = function () { @@ -5783,75 +5531,59 @@ Viva.Graph.View.webglGraphics = function (options) { } }, - nodeBuilderInternal = function (node) { - var nodeId = nodesCount++, - ui = nodeUIBuilder(node); - ui.id = nodeId; + fireRescaled = function (graphics) { + graphics.fire("rescaled"); + }; - nodeProgram.createNode(ui); + graphicsRoot = window.document.createElement("canvas"); - nodes[nodeId] = node; - return ui; + var graphics = { + getLinkUI: function (linkId) { + return allLinks[linkId]; }, - linkBuilderInternal = function (link) { - var linkId = linksCount++, - ui = linkUIBuilder(link); - ui.id = linkId; - - linkProgram.createLink(ui); - - links[linkId] = link; - return ui; + getNodeUI: function (nodeId) { + return allNodes[nodeId]; }, - fireRescaled = function (graphics) { - graphics.fire("rescaled"); - }; - - var graphics = { /** - * Sets the collback that creates node representation or creates a new node - * presentation if builderCallbackOrNode is not a function. + * Sets the callback that creates node representation. * - * @param builderCallbackOrNode a callback function that accepts graph node - * as a parameter and must return an element representing this node. OR - * if it's not a function it's treated as a node to which DOM element should be created. + * @param builderCallback a callback function that accepts graph node + * as a parameter and must return an element representing this node. * * @returns If builderCallbackOrNode is a valid callback function, instance of this is returned; - * Otherwise a node representation is returned for the passed parameter. + * Otherwise undefined value is returned */ - node : function (builderCallbackOrNode) { - if (builderCallbackOrNode && typeof builderCallbackOrNode !== "function") { - return nodeBuilderInternal(builderCallbackOrNode); // create ui for node using current nodeUIBuilder + node : function (builderCallback) { + if (typeof builderCallback !== "function") { + return; // todo: throw? This is not compatible with old versions } - nodeUIBuilder = builderCallbackOrNode; // else replace ui builder with provided function. + nodeUIBuilder = builderCallback; return this; }, /** - * Sets the collback that creates link representation or creates a new link - * presentation if builderCallbackOrLink is not a function. + * Sets the callback that creates link representation * - * @param builderCallbackOrLink a callback function that accepts graph link - * as a parameter and must return an element representing this link. OR - * if it's not a function it's treated as a link to which DOM element should be created. + * @param builderCallback a callback function that accepts graph link + * as a parameter and must return an element representing this link. * - * @returns If builderCallbackOrLink is a valid callback function, instance of this is returned; - * Otherwise a link representation is returned for the passed parameter. + * @returns If builderCallback is a valid callback function, instance of this is returned; + * Otherwise undefined value is returned. */ - link : function (builderCallbackOrLink) { - - if (builderCallbackOrLink && typeof builderCallbackOrLink !== "function") { - return linkBuilderInternal(builderCallbackOrLink); + link : function (builderCallback) { + if (typeof builderCallback !== "function") { + return; // todo: throw? This is not compatible with old versions } - linkUIBuilder = builderCallbackOrLink; + linkUIBuilder = builderCallback; return this; }, + /** * Allows to override default position setter for the node with a new * function. newPlaceCallback(nodeUI, position) is function which @@ -5870,13 +5602,13 @@ Viva.Graph.View.webglGraphics = function (options) { /** * Custom input manager listens to mouse events to process nodes drag-n-drop inside WebGL canvas */ - inputManager : Viva.Input.webglInputManager, + inputManager : webglInputManager, /** * Called every time before renderer starts rendering. */ beginRender : function () { - // this function could be replaced by this.init, + // this function could be replaced by this.init, // based on user options. }, @@ -5904,36 +5636,60 @@ Viva.Graph.View.webglGraphics = function (options) { temp = links[frontLinkId]; links[frontLinkId] = links[srcLinkId]; - links[frontLinkId].ui.id = frontLinkId; + links[frontLinkId].id = frontLinkId; links[srcLinkId] = temp; - links[srcLinkId].ui.id = srcLinkId; + links[srcLinkId].id = srcLinkId; } }, -/*jshint unused: false */ + /** * Sets translate operation that should be applied to all nodes and links. */ graphCenterChanged : function (x, y) { - updateSize(); + transform[12] = (2 * x / width) - 1; + transform[13] = 1 - (2 * y / height); + updateTransformUniform(); }, - /** - * Called by Viva.Graph.View.renderer to let concrete graphic output - * provider prepare to render given link of the graph - * - * @param linkUI visual representation of the link created by link() execution. - */ - initLink : function (linkUI) { + /** + * Called by Viva.Graph.View.renderer to let concrete graphic output + * provider prepare to render given link of the graph + * + * @param link - model of a link + */ + addLink: function (link, boundPosition) { + var uiid = linksCount++, + ui = linkUIBuilder(link); + ui.id = uiid; + ui.pos = boundPosition; + + linkProgram.createLink(ui); + + links[uiid] = ui; + allLinks[link.id] = ui; + return ui; }, + /** * Called by Viva.Graph.View.renderer to let concrete graphic output * provider prepare to render given node of the graph. * * @param nodeUI visual representation of the node created by node() execution. **/ - initNode : function (nodeUI) { + addNode : function (node, boundPosition) { + var uiid = nodesCount++, + ui = nodeUIBuilder(node); + + ui.id = uiid; + ui.position = boundPosition; + ui.node = node; + + nodeProgram.createNode(ui); + + nodes[uiid] = ui; + allNodes[node.id] = ui; + return ui; }, -/*jshint unused: true */ translateRel : function (dx, dy) { transform[12] += (2 * transform[0] * dx / width) / transform[0]; @@ -5974,6 +5730,12 @@ Viva.Graph.View.webglGraphics = function (options) { return this; }, + /** + * Resizes the graphic without resetting the scale. + * Useful with viva graph in a dynamic container + */ + updateSize: updateSize, + /** * Called by Viva.Graph.View.renderer to let concrete graphic output * provider prepare to render. @@ -5987,7 +5749,6 @@ Viva.Graph.View.webglGraphics = function (options) { container = c; - graphicsRoot = window.document.createElement("canvas"); updateSize(); resetScaleInternal(); container.appendChild(graphicsRoot); @@ -6007,7 +5768,7 @@ Viva.Graph.View.webglGraphics = function (options) { var color = options.clearColorValue; gl.clearColor(color.r, color.g, color.b, color.a); // TODO: not the best way, really. Should come up with something better - // what if we need more updates inisde beginRender, like depth buffer? + // what if we need more updates inside beginRender, like depth buffer? this.beginRender = function () { gl.clear(gl.COLOR_BUFFER_BIT); }; @@ -6021,7 +5782,7 @@ Viva.Graph.View.webglGraphics = function (options) { updateTransformUniform(); - // Notify the world if someoen waited for update. TODO: should send an event + // Notify the world if someone waited for update. TODO: should send an event if (typeof initCallback === "function") { initCallback(graphicsRoot); } @@ -6053,21 +5814,22 @@ Viva.Graph.View.webglGraphics = function (options) { * * @param linkUI visual representation of the link created by link() execution. **/ - releaseLink : function (linkToRemove) { + releaseLink : function (link) { if (linksCount > 0) { linksCount -= 1; } + var linkUI = allLinks[link.id]; + delete allLinks[link.id]; - linkProgram.removeLink(linkToRemove); + linkProgram.removeLink(linkUI); - var linkIdToRemove = linkToRemove.id; + var linkIdToRemove = linkUI.id; if (linkIdToRemove < linksCount) { if (linksCount === 0 || linksCount === linkIdToRemove) { return; // no more links or removed link is the last one. } - // TODO: consider getting rid of this. The only reason why it's here is to update 'ui' property - // so that renderer will pass proper id in updateLinkPosition. - links[linkIdToRemove] = links[linksCount]; - links[linkIdToRemove].ui.id = linkIdToRemove; + var lastLinkUI = links[linksCount]; + links[linkIdToRemove] = lastLinkUI; + lastLinkUI.id = linkIdToRemove; } }, @@ -6077,61 +5839,67 @@ Viva.Graph.View.webglGraphics = function (options) { * * @param nodeUI visual representation of the node created by node() execution. **/ - releaseNode : function (nodeUI) { + releaseNode : function (node) { if (nodesCount > 0) { nodesCount -= 1; } + var nodeUI = allNodes[node.id]; + delete allNodes[node.id]; nodeProgram.removeNode(nodeUI); - if (nodeUI.id < nodesCount) { - var nodeIdToRemove = nodeUI.id; + var nodeIdToRemove = nodeUI.id; + if (nodeIdToRemove < nodesCount) { if (nodesCount === 0 || nodesCount === nodeIdToRemove) { return; // no more nodes or removed node is the last in the list. } - var lastNode = nodes[nodesCount], - replacedNode = nodes[nodeIdToRemove]; + var lastNodeUI = nodes[nodesCount]; - nodes[nodeIdToRemove] = lastNode; - nodes[nodeIdToRemove].ui.id = nodeIdToRemove; + nodes[nodeIdToRemove] = lastNodeUI; + lastNodeUI.id = nodeIdToRemove; - // Since concrete shaders may cache properties in the ui element + // Since concrete shaders may cache properties in the UI element // we are letting them to make this swap (e.g. image node shader // uses this approach to update node's offset in the atlas) - nodeProgram.replaceProperties(replacedNode.ui, lastNode.ui); + nodeProgram.replaceProperties(nodeUI, lastNodeUI); } }, - /** - * Called by Viva.Graph.View.renderer to let concrete graphic output - * provider place given node UI to recommended position pos {x, y}; - */ - updateNodePosition : function (nodeUI, pos) { + renderNodes: function () { + var pos = {x : 0, y : 0}; // WebGL coordinate system is different. Would be better // to have this transform in the shader code, but it would // require every shader to be updated.. - pos.y = -pos.y; - if (userPlaceNodeCallback) { - userPlaceNodeCallback(nodeUI, pos); - } + for (var i = 0; i < nodesCount; ++i) { + var ui = nodes[i]; + pos.x = ui.position.x; + pos.y = ui.position.y; + if (userPlaceNodeCallback) { + userPlaceNodeCallback(ui, pos); + } - nodeProgram.position(nodeUI, pos); + nodeProgram.position(ui, pos); + } }, - /** - * Called by Viva.Graph.View.renderer to let concrete graphic output - * provider place given link of the graph. Pos objects are {x, y}; - */ - updateLinkPosition : function (link, fromPos, toPos) { - // WebGL coordinate system is different. Would be better - // to have this transform in the shader code, but it would - // require every shader to be updated.. - fromPos.y = -fromPos.y; - toPos.y = -toPos.y; - if (userPlaceLinkCallback) { - userPlaceLinkCallback(link, fromPos, toPos); - } + renderLinks: function () { + if (this.omitLinksRendering) { return; } + + var toPos = {x : 0, y : 0}; + var fromPos = {x : 0, y : 0}; + for (var i = 0; i < linksCount; ++i) { + var ui = links[i]; + var pos = ui.pos.from; + fromPos.x = pos.x; + fromPos.y = -pos.y; + pos = ui.pos.to; + toPos.x = pos.x; + toPos.y = -pos.y; + if (userPlaceLinkCallback) { + userPlaceLinkCallback(ui, fromPos, toPos); + } - linkProgram.position(link, fromPos, toPos); + linkProgram.position(ui, fromPos, toPos); + } }, /** @@ -6160,7 +5928,7 @@ Viva.Graph.View.webglGraphics = function (options) { // and let initialization logic take care about the rest. nodeProgram = newProgram; } else if (newProgram) { - throw "Not implemented. Cannot swap shader on the fly... yet."; + throw "Not implemented. Cannot swap shader on the fly... Yet."; // TODO: unload old shader and reinit. } }, @@ -6176,310 +5944,1370 @@ Viva.Graph.View.webglGraphics = function (options) { // and let initialization logic take care about the rest. linkProgram = newProgram; } else if (newProgram) { - throw "Not implemented. Cannot swap shader on the fly... yet."; + throw "Not implemented. Cannot swap shader on the fly... Yet."; // TODO: unload old shader and reinit. } }, - getGraphCoordinates : function (graphicsRootPos) { - // TODO: could be a problem when container has margins? - // to save memory we modify incoming parameter: - // point in clipspace coordinates: - graphicsRootPos.x = 2 * graphicsRootPos.x / width - 1; - graphicsRootPos.y = 1 - (2 * graphicsRootPos.y) / height; - // apply transform: - graphicsRootPos.x = (graphicsRootPos.x - transform[12]) / transform[0]; - graphicsRootPos.y = (graphicsRootPos.y - transform[13]) / transform[5]; - // now transform to graph coordinates: - graphicsRootPos.x *= width / 2; - graphicsRootPos.y *= -height / 2; - - return graphicsRootPos; + + /** + * Transforms client coordinates into layout coordinates. Client coordinates + * are DOM coordinates relative to the rendering container. Layout + * coordinates are those assigned by by layout algorithm to each node. + * + * @param {Object} p - a point object with `x` and `y` attributes. + * This method mutates p. + */ + transformClientToGraphCoordinates: function (p) { + // TODO: could be a problem when container has margins? + // normalize + p.x = ((2 * p.x) / width) - 1; + p.y = 1 - ((2 * p.y) / height); + + // apply transform + p.x = (p.x - transform[12]) / transform[0]; + p.y = (p.y - transform[13]) / transform[5]; + + // transform to graph coordinates + p.x = p.x * (width / 2); + p.y = p.y * (-height / 2); + + return p; + }, + + /** + * Transforms WebGL coordinates into client coordinates. Reverse of + * `transformClientToGraphCoordinates()` + * + * @param {Object} p - a point object with `x` and `y` attributes, which + * represents a layout coordinate. This method mutates p. + */ + transformGraphToClientCoordinates: function (p) { + // TODO: could be a problem when container has margins? + // transform from graph coordinates + p.x = p.x / (width / 2); + p.y = p.y / (-height / 2); + + // apply transform + p.x = (p.x * transform[0]) + transform[12]; + p.y = (p.y * transform[5]) + transform[13]; + + // denormalize + p.x = ((p.x + 1) * width) / 2; + p.y = ((1 - p.y) * height) / 2; + + return p; + }, + + getNodeAtClientPos: function (clientPos, preciseCheck) { + if (typeof preciseCheck !== "function") { + // we don't know anything about your node structure here :( + // potentially this could be delegated to node program, but for + // right now, we are giving up if you don't pass boundary check + // callback. It answers to a question is nodeUI covers (x, y) + return null; + } + // first transform to graph coordinates: + this.transformClientToGraphCoordinates(clientPos); + // now using precise check iterate over each node and find one within box: + // TODO: This is poor O(N) performance. + for (var i = 0; i < nodesCount; ++i) { + if (preciseCheck(nodes[i], clientPos.x, clientPos.y)) { + return nodes[i].node; + } + } + return null; } }; // Let graphics fire events before we return it to the caller. - Viva.Graph.Utils.events(graphics).extend(); + eventify(graphics); return graphics; -};/** +} + +},{"../Input/webglInputManager.js":40,"../WebGL/webglLine.js":62,"../WebGL/webglLinkProgram.js":63,"../WebGL/webglNodeProgram.js":64,"../WebGL/webglSquare.js":65,"ngraph.events":9,"ngraph.merge":17}],55:[function(require,module,exports){ +module.exports = parseColor; + +function parseColor(color) { + var parsedColor = 0x009ee8ff; + + if (typeof color === 'string' && color) { + if (color.length === 4) { // #rgb + color = color.replace(/([^#])/g, '$1$1'); // duplicate each letter except first #. + } + if (color.length === 9) { // #rrggbbaa + parsedColor = parseInt(color.substr(1), 16); + } else if (color.length === 7) { // or #rrggbb. + parsedColor = (parseInt(color.substr(1), 16) << 8) | 0xff; + } else { + throw 'Color expected in hex format with preceding "#". E.g. #00ff00. Got value: ' + color; + } + } else if (typeof color === 'number') { + parsedColor = color; + } + + return parsedColor; +} + +},{}],56:[function(require,module,exports){ +module.exports = Texture; + +/** + * Single texture in the webglAtlas. + */ +function Texture(size) { + this.canvas = window.document.createElement("canvas"); + this.ctx = this.canvas.getContext("2d"); + this.isDirty = false; + this.canvas.width = this.canvas.height = size; +} + +},{}],57:[function(require,module,exports){ +/** + * @fileOverview Utility functions for webgl rendering. + * + * @author Andrei Kashcha (aka anvaka) / https://github.com/anvaka + */ + +module.exports = webgl; + +function webgl(gl) { + + return { + createProgram: createProgram, + extendArray: extendArray, + copyArrayPart: copyArrayPart, + swapArrayPart: swapArrayPart, + getLocations: getLocations, + context: gl + }; + + function createShader(shaderText, type) { + var shader = gl.createShader(type); + gl.shaderSource(shader, shaderText); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { + var msg = gl.getShaderInfoLog(shader); + window.alert(msg); + throw msg; + } + + return shader; + } + + function createProgram(vertexShaderSrc, fragmentShaderSrc) { + var program = gl.createProgram(); + var vs = createShader(vertexShaderSrc, gl.VERTEX_SHADER); + var fs = createShader(fragmentShaderSrc, gl.FRAGMENT_SHADER); + + gl.attachShader(program, vs); + gl.attachShader(program, fs); + gl.linkProgram(program); + + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { + var msg = gl.getShaderInfoLog(program); + window.alert(msg); + throw msg; + } + + return program; + } + + function extendArray(buffer, itemsInBuffer, elementsPerItem) { + if ((itemsInBuffer + 1) * elementsPerItem > buffer.length) { + // Every time we run out of space create new array twice bigger. + // TODO: it seems buffer size is limited. Consider using multiple arrays for huge graphs + var extendedArray = new Float32Array(buffer.length * elementsPerItem * 2); + extendedArray.set(buffer); + + return extendedArray; + } + + return buffer; + } + + function getLocations(program, uniformOrAttributeNames) { + var foundLocations = {}; + for (var i = 0; i < uniformOrAttributeNames.length; ++i) { + var name = uniformOrAttributeNames[i]; + var location = -1; + if (name[0] === 'a' && name[1] === '_') { + location = gl.getAttribLocation(program, name); + if (location === -1) { + throw new Error("Program doesn't have required attribute: " + name); + } + + foundLocations[name.slice(2)] = location; + } else if (name[0] === 'u' && name[1] === '_') { + location = gl.getUniformLocation(program, name); + if (location === null) { + throw new Error("Program doesn't have required uniform: " + name); + } + + foundLocations[name.slice(2)] = location; + } else { + throw new Error("Couldn't figure out your intent. All uniforms should start with 'u_' prefix, and attributes with 'a_'"); + } + } + + return foundLocations; + } +} + +function copyArrayPart(array, to, from, elementsCount) { + for (var i = 0; i < elementsCount; ++i) { + array[to + i] = array[from + i]; + } +} + +function swapArrayPart(array, from, to, elementsCount) { + for (var i = 0; i < elementsCount; ++i) { + var tmp = array[from + i]; + array[from + i] = array[to + i]; + array[to + i] = tmp; + } +} + +},{}],58:[function(require,module,exports){ +var Texture = require('./texture.js'); + +module.exports = webglAtlas; + +/** + * My naive implementation of textures atlas. It allows clients to load + * multiple images into atlas and get canvas representing all of them. + * + * @param tilesPerTexture - indicates how many images can be loaded to one + * texture of the atlas. If number of loaded images exceeds this + * parameter a new canvas will be created. + */ +function webglAtlas(tilesPerTexture) { + var tilesPerRow = Math.sqrt(tilesPerTexture || 1024) << 0, + tileSize = tilesPerRow, + lastLoadedIdx = 1, + loadedImages = {}, + dirtyTimeoutId, + skipedDirty = 0, + textures = [], + trackedUrls = []; + + if (!isPowerOf2(tilesPerTexture)) { + throw "Tiles per texture should be power of two."; + } + + // this is the return object + var api = { + /** + * indicates whether atlas has changed texture in it. If true then + * some of the textures has isDirty flag set as well. + */ + isDirty: false, + + /** + * Clears any signs of atlas changes. + */ + clearDirty: clearDirty, + + /** + * Removes given url from collection of tiles in the atlas. + */ + remove: remove, + + /** + * Gets all textures in the atlas. + */ + getTextures: getTextures, + + /** + * Gets coordinates of the given image in the atlas. Coordinates is an object: + * {offset : int } - where offset is an absolute position of the image in the + * atlas. + * + * Absolute means it can be larger than tilesPerTexture parameter, and in that + * case clients should get next texture in getTextures() collection. + */ + getCoordinates: getCoordinates, + + /** + * Asynchronously Loads the image to the atlas. Cross-domain security + * limitation applies. + */ + load: load + }; + + return api; + + function clearDirty() { + var i; + api.isDirty = false; + for (i = 0; i < textures.length; ++i) { + textures[i].isDirty = false; + } + } + + function remove(imgUrl) { + var coordinates = loadedImages[imgUrl]; + if (!coordinates) { + return false; + } + delete loadedImages[imgUrl]; + lastLoadedIdx -= 1; + + + if (lastLoadedIdx === coordinates.offset) { + return true; // Ignore if it's last image in the whole set. + } + + var tileToRemove = getTileCoordinates(coordinates.offset), + lastTileInSet = getTileCoordinates(lastLoadedIdx); + + copy(lastTileInSet, tileToRemove); + + var replacedOffset = loadedImages[trackedUrls[lastLoadedIdx]]; + replacedOffset.offset = coordinates.offset; + trackedUrls[coordinates.offset] = trackedUrls[lastLoadedIdx]; + + markDirty(); + return true; + } + + function getTextures() { + return textures; // I trust you... + } + + function getCoordinates(imgUrl) { + return loadedImages[imgUrl]; + } + + function load(imgUrl, callback) { + if (loadedImages.hasOwnProperty(imgUrl)) { + callback(loadedImages[imgUrl]); + } else { + var img = new window.Image(), + imgId = lastLoadedIdx; + + lastLoadedIdx += 1; + img.crossOrigin = "anonymous"; + img.onload = function() { + markDirty(); + drawAt(imgId, img, callback); + }; + + img.src = imgUrl; + } + } + + function createTexture() { + var texture = new Texture(tilesPerRow * tileSize); + textures.push(texture); + } + + function drawAt(tileNumber, img, callback) { + var tilePosition = getTileCoordinates(tileNumber), + coordinates = { + offset: tileNumber + }; + + if (tilePosition.textureNumber >= textures.length) { + createTexture(); + } + var currentTexture = textures[tilePosition.textureNumber]; + + currentTexture.ctx.drawImage(img, tilePosition.col * tileSize, tilePosition.row * tileSize, tileSize, tileSize); + trackedUrls[tileNumber] = img.src; + + loadedImages[img.src] = coordinates; + currentTexture.isDirty = true; + + callback(coordinates); + } + + function getTileCoordinates(absolutePosition) { + var textureNumber = (absolutePosition / tilesPerTexture) << 0, + localTileNumber = (absolutePosition % tilesPerTexture), + row = (localTileNumber / tilesPerRow) << 0, + col = (localTileNumber % tilesPerRow); + + return { + textureNumber: textureNumber, + row: row, + col: col + }; + } + + function markDirtyNow() { + api.isDirty = true; + skipedDirty = 0; + dirtyTimeoutId = null; + } + + function markDirty() { + // delay this call, since it results in texture reload + if (dirtyTimeoutId) { + window.clearTimeout(dirtyTimeoutId); + skipedDirty += 1; + dirtyTimeoutId = null; + } + + if (skipedDirty > 10) { + markDirtyNow(); + } else { + dirtyTimeoutId = window.setTimeout(markDirtyNow, 400); + } + } + + function copy(from, to) { + var fromCanvas = textures[from.textureNumber].canvas, + toCtx = textures[to.textureNumber].ctx, + x = to.col * tileSize, + y = to.row * tileSize; + + toCtx.drawImage(fromCanvas, from.col * tileSize, from.row * tileSize, tileSize, tileSize, x, y, tileSize, tileSize); + textures[from.textureNumber].isDirty = true; + textures[to.textureNumber].isDirty = true; + } +} + +function isPowerOf2(n) { + return (n & (n - 1)) === 0; +} + +},{"./texture.js":56}],59:[function(require,module,exports){ +module.exports = webglImage; + +/** + * Represents a model for image. + */ +function webglImage(size, src) { + return { + /** + * Gets texture index where current image is placed. + */ + _texture : 0, + + /** + * Gets offset in the texture where current image is placed. + */ + _offset : 0, + + /** + * Gets size of the square with the image. + */ + size : typeof size === 'number' ? size : 32, + + /** + * Source of the image. If image is coming not from your domain + * certain origin restrictions applies. + * See http://www.khronos.org/registry/webgl/specs/latest/#4.2 for more details. + */ + src : src + }; +} + +},{}],60:[function(require,module,exports){ +/** + * @fileOverview Defines an image nodes for webglGraphics class. + * Shape of nodes is square. + * + * @author Andrei Kashcha (aka anvaka) / https://github.com/anvaka + */ + +var WebglAtlas = require('./webglAtlas.js'); +var glUtils = require('./webgl.js'); + +module.exports = webglImageNodeProgram; + +/** + * Defines simple UI for nodes in webgl renderer. Each node is rendered as an image. + * + * @param {number} tilesPerTexture + * + */ +function webglImageNodeProgram(tilesPerTexture) { + // WebGL is gian state machine, we store some properties of the state here: + var ATTRIBUTES_PER_PRIMITIVE = 18; + var nodesFS = createNodeFragmentShader(); + var nodesVS = createNodeVertexShader(); + var tilesPerTexture = tilesPerTexture || 1024; // TODO: Get based on max texture size + var atlas; + var program; + var gl; + var buffer; + var utils; + var locations; + var nodesCount = 0; + var nodes = new Float32Array(64); + var width; + var height; + var transform; + var sizeDirty; + + + return { + load: load, + + /** + * Updates position of current node in the buffer of nodes. + * + * @param idx - index of current node. + * @param pos - new position of the node. + */ + position: position, + + createNode: createNode, + + removeNode: removeNode, + + replaceProperties: replaceProperties, + + updateTransform: updateTransform, + + updateSize: updateSize, + + render: render + }; + + function refreshTexture(texture, idx) { + if (texture.nativeObject) { + gl.deleteTexture(texture.nativeObject); + } + + var nativeObject = gl.createTexture(); + gl.activeTexture(gl["TEXTURE" + idx]); + gl.bindTexture(gl.TEXTURE_2D, nativeObject); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.canvas); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST); + + gl.generateMipmap(gl.TEXTURE_2D); + gl.uniform1i(locations["sampler" + idx], idx); + + texture.nativeObject = nativeObject; + } + + function ensureAtlasTextureUpdated() { + if (atlas.isDirty) { + var textures = atlas.getTextures(), + i; + for (i = 0; i < textures.length; ++i) { + if (textures[i].isDirty || !textures[i].nativeObject) { + refreshTexture(textures[i], i); + } + } + + atlas.clearDirty(); + } + } + + function load(glContext) { + gl = glContext; + utils = glUtils(glContext); + + atlas = new WebglAtlas(tilesPerTexture); + + program = utils.createProgram(nodesVS, nodesFS); + gl.useProgram(program); + locations = utils.getLocations(program, ["a_vertexPos", "a_customAttributes", "u_screenSize", "u_transform", "u_sampler0", "u_sampler1", "u_sampler2", "u_sampler3", "u_tilesPerTexture"]); + + gl.uniform1f(locations.tilesPerTexture, tilesPerTexture); + + gl.enableVertexAttribArray(locations.vertexPos); + gl.enableVertexAttribArray(locations.customAttributes); + + buffer = gl.createBuffer(); + } + + function position(nodeUI, pos) { + var idx = nodeUI.id * ATTRIBUTES_PER_PRIMITIVE; + nodes[idx] = pos.x - nodeUI.size; + nodes[idx + 1] = -pos.y - nodeUI.size; + nodes[idx + 2] = nodeUI._offset * 4; + + nodes[idx + 3] = pos.x + nodeUI.size; + nodes[idx + 4] = -pos.y - nodeUI.size; + nodes[idx + 5] = nodeUI._offset * 4 + 1; + + nodes[idx + 6] = pos.x - nodeUI.size; + nodes[idx + 7] = -pos.y + nodeUI.size; + nodes[idx + 8] = nodeUI._offset * 4 + 2; + + nodes[idx + 9] = pos.x - nodeUI.size; + nodes[idx + 10] = -pos.y + nodeUI.size; + nodes[idx + 11] = nodeUI._offset * 4 + 2; + + nodes[idx + 12] = pos.x + nodeUI.size; + nodes[idx + 13] = -pos.y - nodeUI.size; + nodes[idx + 14] = nodeUI._offset * 4 + 1; + + nodes[idx + 15] = pos.x + nodeUI.size; + nodes[idx + 16] = -pos.y + nodeUI.size; + nodes[idx + 17] = nodeUI._offset * 4 + 3; + } + + function createNode(ui) { + nodes = utils.extendArray(nodes, nodesCount, ATTRIBUTES_PER_PRIMITIVE); + nodesCount += 1; + + var coordinates = atlas.getCoordinates(ui.src); + if (coordinates) { + ui._offset = coordinates.offset; + } else { + ui._offset = 0; + // Image is not yet loaded into the atlas. Reload it: + atlas.load(ui.src, function(coordinates) { + ui._offset = coordinates.offset; + }); + } + } + + function removeNode(nodeUI) { + if (nodesCount > 0) { + nodesCount -= 1; + } + + if (nodeUI.id < nodesCount && nodesCount > 0) { + if (nodeUI.src) { + atlas.remove(nodeUI.src); + } + + utils.copyArrayPart(nodes, nodeUI.id * ATTRIBUTES_PER_PRIMITIVE, nodesCount * ATTRIBUTES_PER_PRIMITIVE, ATTRIBUTES_PER_PRIMITIVE); + } + } + + function replaceProperties(replacedNode, newNode) { + newNode._offset = replacedNode._offset; + } + + function updateTransform(newTransform) { + sizeDirty = true; + transform = newTransform; + } + + function updateSize(w, h) { + width = w; + height = h; + sizeDirty = true; + } + + function render() { + gl.useProgram(program); + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + gl.bufferData(gl.ARRAY_BUFFER, nodes, gl.DYNAMIC_DRAW); + + if (sizeDirty) { + sizeDirty = false; + gl.uniformMatrix4fv(locations.transform, false, transform); + gl.uniform2f(locations.screenSize, width, height); + } + + gl.vertexAttribPointer(locations.vertexPos, 2, gl.FLOAT, false, 3 * Float32Array.BYTES_PER_ELEMENT, 0); + gl.vertexAttribPointer(locations.customAttributes, 1, gl.FLOAT, false, 3 * Float32Array.BYTES_PER_ELEMENT, 2 * 4); + + ensureAtlasTextureUpdated(); + + gl.drawArrays(gl.TRIANGLES, 0, nodesCount * 6); + } +} + +// TODO: Use glslify for shaders +function createNodeFragmentShader() { + return [ + "precision mediump float;", + "varying vec4 color;", + "varying vec3 vTextureCoord;", + "uniform sampler2D u_sampler0;", + "uniform sampler2D u_sampler1;", + "uniform sampler2D u_sampler2;", + "uniform sampler2D u_sampler3;", + + "void main(void) {", + " if (vTextureCoord.z == 0.) {", + " gl_FragColor = texture2D(u_sampler0, vTextureCoord.xy);", + " } else if (vTextureCoord.z == 1.) {", + " gl_FragColor = texture2D(u_sampler1, vTextureCoord.xy);", + " } else if (vTextureCoord.z == 2.) {", + " gl_FragColor = texture2D(u_sampler2, vTextureCoord.xy);", + " } else if (vTextureCoord.z == 3.) {", + " gl_FragColor = texture2D(u_sampler3, vTextureCoord.xy);", + " } else { gl_FragColor = vec4(0, 1, 0, 1); }", + "}" + ].join("\n"); +} + +function createNodeVertexShader() { + return [ + "attribute vec2 a_vertexPos;", + + "attribute float a_customAttributes;", + "uniform vec2 u_screenSize;", + "uniform mat4 u_transform;", + "uniform float u_tilesPerTexture;", + "varying vec3 vTextureCoord;", + + "void main(void) {", + " gl_Position = u_transform * vec4(a_vertexPos/u_screenSize, 0, 1);", + "float corner = mod(a_customAttributes, 4.);", + "float tileIndex = mod(floor(a_customAttributes / 4.), u_tilesPerTexture);", + "float tilesPerRow = sqrt(u_tilesPerTexture);", + "float tileSize = 1./tilesPerRow;", + "float tileColumn = mod(tileIndex, tilesPerRow);", + "float tileRow = floor(tileIndex/tilesPerRow);", + + "if(corner == 0.0) {", + " vTextureCoord.xy = vec2(0, 1);", + "} else if(corner == 1.0) {", + " vTextureCoord.xy = vec2(1, 1);", + "} else if(corner == 2.0) {", + " vTextureCoord.xy = vec2(0, 0);", + "} else {", + " vTextureCoord.xy = vec2(1, 0);", + "}", + + "vTextureCoord *= tileSize;", + "vTextureCoord.x += tileColumn * tileSize;", + "vTextureCoord.y += tileRow * tileSize;", + "vTextureCoord.z = floor(floor(a_customAttributes / 4.)/u_tilesPerTexture);", + "}" + ].join("\n"); +} + +},{"./webgl.js":57,"./webglAtlas.js":58}],61:[function(require,module,exports){ +var documentEvents = require('../Utils/documentEvents.js'); + +module.exports = webglInputEvents; + +/** * Monitors graph-related mouse input in webgl graphics and notifies subscribers. * * @param {Viva.Graph.View.webglGraphics} webglGraphics - * @param {Viva.Graph.graph} graph */ -Viva.Graph.webglInputEvents = function (webglGraphics, graph) { - if (webglGraphics.webglInputEvents) { - // Don't listen twice, if we are already attached to this graphics: - return webglGraphics.webglInputEvents; +function webglInputEvents(webglGraphics) { + if (webglGraphics.webglInputEvents) { + // Don't listen twice, if we are already attached to this graphics: + return webglGraphics.webglInputEvents; + } + + var mouseCapturedNode = null, + mouseEnterCallback = [], + mouseLeaveCallback = [], + mouseDownCallback = [], + mouseUpCallback = [], + mouseMoveCallback = [], + clickCallback = [], + dblClickCallback = [], + prevSelectStart, + boundRect; + + var root = webglGraphics.getGraphicsRoot(); + startListen(root); + + var api = { + mouseEnter: mouseEnter, + mouseLeave: mouseLeave, + mouseDown: mouseDown, + mouseUp: mouseUp, + mouseMove: mouseMove, + click: click, + dblClick: dblClick, + mouseCapture: mouseCapture, + releaseMouseCapture: releaseMouseCapture + }; + + // TODO I don't remember why this is needed: + webglGraphics.webglInputEvents = api; + + return api; + + function releaseMouseCapture() { + mouseCapturedNode = null; + } + + function mouseCapture(node) { + mouseCapturedNode = node; + } + + function dblClick(callback) { + if (typeof callback === 'function') { + dblClickCallback.push(callback); } + return api; + } - var preciseCheck = function (node, x, y) { - if (node.ui && node.ui.size) { - var pos = node.position, - half = node.ui.size; + function click(callback) { + if (typeof callback === 'function') { + clickCallback.push(callback); + } + return api; + } - return pos.x - half < x && x < pos.x + half && - pos.y - half < y && y < pos.y + half; - } + function mouseMove(callback) { + if (typeof callback === 'function') { + mouseMoveCallback.push(callback); + } + return api; + } - return true; - }, - mouseCapturedNode = null, - - spatialIndex = Viva.Graph.spatialIndex(graph, preciseCheck), - mouseEnterCallback = [], - mouseLeaveCallback = [], - mouseDownCallback = [], - mouseUpCallback = [], - mouseMoveCallback = [], - clickCallback = [], - dblClickCallback = [], - documentEvents = Viva.Graph.Utils.events(window.document), - prevSelectStart, - boundRect, + function mouseUp(callback) { + if (typeof callback === 'function') { + mouseUpCallback.push(callback); + } + return api; + } - stopPropagation = function (e) { - if (e.stopPropagation) { - e.stopPropagation(); - } else { - e.cancelBubble = true; - } - }, + function mouseDown(callback) { + if (typeof callback === 'function') { + mouseDownCallback.push(callback); + } + return api; + } + + function mouseLeave(callback) { + if (typeof callback === 'function') { + mouseLeaveCallback.push(callback); + } + return api; + } + + function mouseEnter(callback) { + if (typeof callback === 'function') { + mouseEnterCallback.push(callback); + } + return api; + } + + function preciseCheck(nodeUI, x, y) { + if (nodeUI && nodeUI.size) { + var pos = nodeUI.position, + half = nodeUI.size; + + return pos.x - half < x && x < pos.x + half && + pos.y - half < y && y < pos.y + half; + } + + return true; + } + + function getNodeAtClientPos(pos) { + return webglGraphics.getNodeAtClientPos(pos, preciseCheck); + } + + function stopPropagation(e) { + if (e.stopPropagation) { + e.stopPropagation(); + } else { + e.cancelBubble = true; + } + } + + function handleDisabledEvent(e) { + stopPropagation(e); + return false; + } + + function invoke(callbacksChain, args) { + var i, stopPropagation; + for (i = 0; i < callbacksChain.length; i += 1) { + stopPropagation = callbacksChain[i].apply(undefined, args); + if (stopPropagation) { + return true; + } + } + } + + function startListen(root) { + var pos = { + x: 0, + y: 0 + }, + lastFound = null, + lastUpdate = 1, + lastClickTime = +new Date(), + + handleMouseMove = function(e) { + invoke(mouseMoveCallback, [lastFound, e]); + pos.x = e.clientX; + pos.y = e.clientY; + }, + + handleMouseUp = function() { + documentEvents.off('mousemove', handleMouseMove); + documentEvents.off('mouseup', handleMouseUp); + }, + + updateBoundRect = function() { + boundRect = root.getBoundingClientRect(); + }; + + window.addEventListener('resize', updateBoundRect); + updateBoundRect(); + + // mouse move inside container serves only to track mouse enter/leave events. + root.addEventListener('mousemove', + function(e) { + if (mouseCapturedNode) { + return; + } + if (lastUpdate++ % 7 === 0) { + // since there is no bullet proof method to detect resize + // event, we preemptively update the bounding rectangle + updateBoundRect(); + lastUpdate = 1; + } + var cancelBubble = false, + node; + + pos.x = e.clientX - boundRect.left; + pos.y = e.clientY - boundRect.top; + + node = getNodeAtClientPos(pos); + + if (node && lastFound !== node) { + if(lastFound){ + invoke(mouseLeaveCallback, [lastFound]); + } + lastFound = node; + cancelBubble = cancelBubble || invoke(mouseEnterCallback, [lastFound]); + } else if (node === null && lastFound !== node) { + cancelBubble = cancelBubble || invoke(mouseLeaveCallback, [lastFound]); + lastFound = null; + } + + if (cancelBubble) { + stopPropagation(e); + } + }); + + root.addEventListener('mousedown', + function(e) { + var cancelBubble = false, + args; + updateBoundRect(); + pos.x = e.clientX - boundRect.left; + pos.y = e.clientY - boundRect.top; + + args = [getNodeAtClientPos(pos), e]; + if (args[0]) { + cancelBubble = invoke(mouseDownCallback, args); + // we clicked on a node. Following drag should be handled on document events: + documentEvents.on('mousemove', handleMouseMove); + documentEvents.on('mouseup', handleMouseUp); - handleDisabledEvent = function (e) { + prevSelectStart = window.document.onselectstart; + + window.document.onselectstart = handleDisabledEvent; + + lastFound = args[0]; + } else { + lastFound = null; + } + if (cancelBubble) { + stopPropagation(e); + } + }); + + root.addEventListener('mouseup', + function(e) { + var clickTime = +new Date(), + args; + + pos.x = e.clientX - boundRect.left; + pos.y = e.clientY - boundRect.top; + + var nodeAtClientPos = getNodeAtClientPos(pos); + var sameNode = nodeAtClientPos === lastFound; + args = [nodeAtClientPos || lastFound, e]; + if (args[0]) { + window.document.onselectstart = prevSelectStart; + + if (clickTime - lastClickTime < 400 && sameNode) { + invoke(dblClickCallback, args); + } else { + invoke(clickCallback, args); + } + lastClickTime = clickTime; + + if (invoke(mouseUpCallback, args)) { stopPropagation(e); - return false; - }, + } + } + }); + } +} - invoke = function (callbacksChain, args) { - var i, stopPropagation; - for (i = 0; i < callbacksChain.length; i += 1) { - stopPropagation = callbacksChain[i].apply(undefined, args); - if (stopPropagation) { return true; } - } - }, +},{"../Utils/documentEvents.js":44}],62:[function(require,module,exports){ +var parseColor = require('./parseColor.js'); - startListen = function (root) { - var pos = {x : 0, y : 0}, - lastFound = null, - lastClickTime = +new Date(), +module.exports = webglLine; - handleMouseMove = function (e) { - invoke(mouseMoveCallback, [lastFound, e]); - pos.x = e.clientX; - pos.y = e.clientY; - }, +/** + * Defines a webgl line. This class has no rendering logic at all, + * it's just passed to corresponding shader and the shader should + * figure out how to render it. + * + */ +function webglLine(color) { + return { + /** + * Gets or sets color of the line. If you set this property externally + * make sure it always come as integer of 0xRRGGBBAA format + */ + color: parseColor(color) + }; +} - handleMouseUp = function () { - documentEvents.stop('mousemove', handleMouseMove); - documentEvents.stop('mouseup', handleMouseUp); - }, +},{"./parseColor.js":55}],63:[function(require,module,exports){ +/** + * @fileOverview Defines a naive form of links for webglGraphics class. + * This form allows to change color of links. + **/ - updateBoundRect = function () { - boundRect = root.getBoundingClientRect(); - }; +var glUtils = require('./webgl.js'); - window.addEventListener('resize', updateBoundRect); - updateBoundRect(); +module.exports = webglLinkProgram; - // mouse move inside container serves only to track mouse enter/leave events. - root.addEventListener('mousemove', - function (e) { - if (mouseCapturedNode) { - return; - } +/** + * Defines UI for links in webgl renderer. + */ +function webglLinkProgram() { + var ATTRIBUTES_PER_PRIMITIVE = 6, // primitive is Line with two points. Each has x,y and color = 3 * 2 attributes. + BYTES_PER_LINK = 2 * (2 * Float32Array.BYTES_PER_ELEMENT + Uint32Array.BYTES_PER_ELEMENT), // two nodes * (x, y + color) + linksFS = [ + 'precision mediump float;', + 'varying vec4 color;', + 'void main(void) {', + ' gl_FragColor = color;', + '}' + ].join('\n'), - var cancelBubble = false, - node; + linksVS = [ + 'attribute vec2 a_vertexPos;', + 'attribute vec4 a_color;', - pos.x = e.clientX - boundRect.left; - pos.y = e.clientY - boundRect.top; + 'uniform vec2 u_screenSize;', + 'uniform mat4 u_transform;', - webglGraphics.getGraphCoordinates(pos); - node = spatialIndex.getNodeAt(pos.x, pos.y); + 'varying vec4 color;', - if (node && lastFound !== node) { - lastFound = node; - cancelBubble = cancelBubble || invoke(mouseEnterCallback, [lastFound]); - } else if (node === null && lastFound !== node) { - cancelBubble = cancelBubble || invoke(mouseLeaveCallback, [lastFound]); - lastFound = null; - } + 'void main(void) {', + ' gl_Position = u_transform * vec4(a_vertexPos/u_screenSize, 0.0, 1.0);', + ' color = a_color.abgr;', + '}' + ].join('\n'), - if (cancelBubble) { stopPropagation(e); } - }); + program, + gl, + buffer, + utils, + locations, + linksCount = 0, + frontLinkId, // used to track z-index of links. + storage = new ArrayBuffer(16 * BYTES_PER_LINK), + positions = new Float32Array(storage), + colors = new Uint32Array(storage), + width, + height, + transform, + sizeDirty, - root.addEventListener('mousedown', - function (e) { - var cancelBubble = false, - args; + ensureEnoughStorage = function () { + // TODO: this is a duplicate of webglNodeProgram code. Extract it to webgl.js + if ((linksCount+1)*BYTES_PER_LINK > storage.byteLength) { + // Every time we run out of space create new array twice bigger. + // TODO: it seems buffer size is limited. Consider using multiple arrays for huge graphs + var extendedStorage = new ArrayBuffer(storage.byteLength * 2), + extendedPositions = new Float32Array(extendedStorage), + extendedColors = new Uint32Array(extendedStorage); - pos.x = e.clientX - boundRect.left; - pos.y = e.clientY - boundRect.top; - webglGraphics.getGraphCoordinates(pos); + extendedColors.set(colors); // should be enough to copy just one view. + positions = extendedPositions; + colors = extendedColors; + storage = extendedStorage; + } + }; - args = [spatialIndex.getNodeAt(pos.x, pos.y), e]; - if (args[0]) { - cancelBubble = invoke(mouseDownCallback, args); - // we clicked on a node. Following drag should be handled on document events: - documentEvents.on('mousemove', handleMouseMove); - documentEvents.on('mouseup', handleMouseUp); + return { + load : function (glContext) { + gl = glContext; + utils = glUtils(glContext); - prevSelectStart = window.document.onselectstart; + program = utils.createProgram(linksVS, linksFS); + gl.useProgram(program); + locations = utils.getLocations(program, ['a_vertexPos', 'a_color', 'u_screenSize', 'u_transform']); - window.document.onselectstart = handleDisabledEvent; + gl.enableVertexAttribArray(locations.vertexPos); + gl.enableVertexAttribArray(locations.color); - lastFound = args[0]; - } else { - lastFound = null; - } - if (cancelBubble) { stopPropagation(e); } - }); - - root.addEventListener('mouseup', - function (e) { - var clickTime = +new Date(), - args; - - pos.x = e.clientX - boundRect.left; - pos.y = e.clientY - boundRect.top; - webglGraphics.getGraphCoordinates(pos); - - args = [spatialIndex.getNodeAt(pos.x, pos.y), e]; - if (args[0]) { - window.document.onselectstart = prevSelectStart; - - if (clickTime - lastClickTime < 400 && args[0] === lastFound) { - invoke(dblClickCallback, args); - } else { - invoke(clickCallback, args); - } - lastClickTime = clickTime; - - if (invoke(mouseUpCallback, args)) { - stopPropagation(e); - } - } - }); - }; + buffer = gl.createBuffer(); + }, - // webgl may not be initialized at this point. Pass callback - // to start listen after graphics root is ready. - webglGraphics.getGraphicsRoot(startListen); + position: function (linkUi, fromPos, toPos) { + var linkIdx = linkUi.id, + offset = linkIdx * ATTRIBUTES_PER_PRIMITIVE; + positions[offset] = fromPos.x; + positions[offset + 1] = fromPos.y; + colors[offset + 2] = linkUi.color; - webglGraphics.webglInputEvents = { - mouseEnter : function (callback) { - if (typeof callback === 'function') { - mouseEnterCallback.push(callback); - } - return this; + positions[offset + 3] = toPos.x; + positions[offset + 4] = toPos.y; + colors[offset + 5] = linkUi.color; }, - mouseLeave : function (callback) { - if (typeof callback === 'function') { - mouseLeaveCallback.push(callback); - } - return this; + + createLink : function (ui) { + ensureEnoughStorage(); + + linksCount += 1; + frontLinkId = ui.id; }, - mouseDown : function (callback) { - if (typeof callback === 'function') { - mouseDownCallback.push(callback); + + removeLink : function (ui) { + if (linksCount > 0) { linksCount -= 1; } + // swap removed link with the last link. This will give us O(1) performance for links removal: + if (ui.id < linksCount && linksCount > 0) { + // using colors as a view to array buffer is okay here. + utils.copyArrayPart(colors, ui.id * ATTRIBUTES_PER_PRIMITIVE, linksCount * ATTRIBUTES_PER_PRIMITIVE, ATTRIBUTES_PER_PRIMITIVE); } - return this; }, - mouseUp : function (callback) { - if (typeof callback === 'function') { - mouseUpCallback.push(callback); - } - return this; + + updateTransform : function (newTransform) { + sizeDirty = true; + transform = newTransform; }, - mouseMove : function (callback) { - if (typeof callback === 'function') { - mouseMoveCallback.push(callback); - } - return this; + + updateSize : function (w, h) { + width = w; + height = h; + sizeDirty = true; }, - click : function (callback) { - if (typeof callback === 'function') { - clickCallback.push(callback); + + render : function () { + gl.useProgram(program); + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + gl.bufferData(gl.ARRAY_BUFFER, storage, gl.DYNAMIC_DRAW); + + if (sizeDirty) { + sizeDirty = false; + gl.uniformMatrix4fv(locations.transform, false, transform); + gl.uniform2f(locations.screenSize, width, height); } - return this; + + gl.vertexAttribPointer(locations.vertexPos, 2, gl.FLOAT, false, 3 * Float32Array.BYTES_PER_ELEMENT, 0); + gl.vertexAttribPointer(locations.color, 4, gl.UNSIGNED_BYTE, true, 3 * Float32Array.BYTES_PER_ELEMENT, 2 * 4); + + gl.drawArrays(gl.LINES, 0, linksCount * 2); + + frontLinkId = linksCount - 1; }, - dblClick : function (callback) { - if (typeof callback === 'function') { - dblClickCallback.push(callback); + + bringToFront : function (link) { + if (frontLinkId > link.id) { + utils.swapArrayPart(positions, link.id * ATTRIBUTES_PER_PRIMITIVE, frontLinkId * ATTRIBUTES_PER_PRIMITIVE, ATTRIBUTES_PER_PRIMITIVE); + } + if (frontLinkId > 0) { + frontLinkId -= 1; } - return this; - }, - mouseCapture : function (node) { - mouseCapturedNode = node; }, - releaseMouseCapture : function () { - mouseCapturedNode = null; + + getFrontLinkId : function () { + return frontLinkId; } }; +} - return webglGraphics.webglInputEvents; -}; +},{"./webgl.js":57}],64:[function(require,module,exports){ /** - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com + * @fileOverview Defines a naive form of nodes for webglGraphics class. + * This form allows to change color of node. Shape of nodes is rectangular. + * + * @author Andrei Kashcha (aka anvaka) / https://github.com/anvaka */ -Viva.Input = Viva.Input || {}; -Viva.Input.webglInputManager = function (graph, graphics) { - var inputEvents = Viva.Graph.webglInputEvents(graphics, graph), - draggedNode = null, - internalHandlers = {}, - pos = {x : 0, y : 0}; +var glUtils = require('./webgl.js'); - inputEvents.mouseDown(function (node, e) { - draggedNode = node; - pos.x = e.clientX; - pos.y = e.clientY; +module.exports = webglNodeProgram; - inputEvents.mouseCapture(draggedNode); +/** + * Defines simple UI for nodes in webgl renderer. Each node is rendered as square. Color and size can be changed. + */ +function webglNodeProgram() { + var ATTRIBUTES_PER_PRIMITIVE = 4; // Primitive is point, x, y, size, color + // x, y, z - floats, color = uint. + var BYTES_PER_NODE = 3 * Float32Array.BYTES_PER_ELEMENT + Uint32Array.BYTES_PER_ELEMENT; + var nodesFS = [ + 'precision mediump float;', + 'varying vec4 color;', + + 'void main(void) {', + ' gl_FragColor = color;', + '}' + ].join('\n'); + var nodesVS = [ + 'attribute vec3 a_vertexPos;', + 'attribute vec4 a_color;', + 'uniform vec2 u_screenSize;', + 'uniform mat4 u_transform;', + 'varying vec4 color;', + + 'void main(void) {', + ' gl_Position = u_transform * vec4(a_vertexPos.xy/u_screenSize, 0, 1);', + ' gl_PointSize = a_vertexPos.z * u_transform[0][0];', + ' color = a_color.abgr;', + '}' + ].join('\n'); + + var program; + var gl; + var buffer; + var locations; + var utils; + var storage = new ArrayBuffer(16 * BYTES_PER_NODE); + var positions = new Float32Array(storage); + var colors = new Uint32Array(storage); + var nodesCount = 0; + var width; + var height; + var transform; + var sizeDirty; + + return { + load: load, - var handlers = internalHandlers[node.ui.id]; - if (handlers && handlers.onStart) { - handlers.onStart(e, pos); - } + /** + * Updates position of node in the buffer of nodes. + * + * @param idx - index of current node. + * @param pos - new position of the node. + */ + position: position, - return true; - }).mouseUp(function (node) { - inputEvents.releaseMouseCapture(draggedNode); + updateTransform: updateTransform, - draggedNode = null; - var handlers = internalHandlers[node.ui.id]; - if (handlers && handlers.onStop) { - handlers.onStop(); - } - return true; - }).mouseMove(function (node, e) { - if (draggedNode) { - var handlers = internalHandlers[draggedNode.ui.id]; - if (handlers && handlers.onDrag) { - handlers.onDrag(e, {x : e.clientX - pos.x, y : e.clientY - pos.y }); - } + updateSize: updateSize, - pos.x = e.clientX; - pos.y = e.clientY; - return true; - } - }); + removeNode: removeNode, - return { - /** - * Called by renderer to listen to drag-n-drop events from node. E.g. for CSS/SVG - * graphics we may listen to DOM events, whereas for WebGL we graphics - * should provide custom eventing mechanism. - * - * @param node - to be monitored. - * @param handlers - object with set of three callbacks: - * onStart: function(), - * onDrag: function(e, offset), - * onStop: function() - */ - bindDragNDrop : function (node, handlers) { - internalHandlers[node.ui.id] = handlers; - } - }; -}; + createNode: createNode, + + replaceProperties: replaceProperties, + + render: render + }; + + function ensureEnoughStorage() { + if ((nodesCount + 1) * BYTES_PER_NODE >= storage.byteLength) { + // Every time we run out of space create new array twice bigger. + // TODO: it seems buffer size is limited. Consider using multiple arrays for huge graphs + var extendedStorage = new ArrayBuffer(storage.byteLength * 2), + extendedPositions = new Float32Array(extendedStorage), + extendedColors = new Uint32Array(extendedStorage); + + extendedColors.set(colors); // should be enough to copy just one view. + positions = extendedPositions; + colors = extendedColors; + storage = extendedStorage; + } + } + + function load(glContext) { + gl = glContext; + utils = glUtils(glContext); + + program = utils.createProgram(nodesVS, nodesFS); + gl.useProgram(program); + locations = utils.getLocations(program, ['a_vertexPos', 'a_color', 'u_screenSize', 'u_transform']); + + gl.enableVertexAttribArray(locations.vertexPos); + gl.enableVertexAttribArray(locations.color); + + buffer = gl.createBuffer(); + } + + function position(nodeUI, pos) { + var idx = nodeUI.id; + + positions[idx * ATTRIBUTES_PER_PRIMITIVE] = pos.x; + positions[idx * ATTRIBUTES_PER_PRIMITIVE + 1] = -pos.y; + positions[idx * ATTRIBUTES_PER_PRIMITIVE + 2] = nodeUI.size; + + colors[idx * ATTRIBUTES_PER_PRIMITIVE + 3] = nodeUI.color; + } + + function updateTransform(newTransform) { + sizeDirty = true; + transform = newTransform; + } + + function updateSize(w, h) { + width = w; + height = h; + sizeDirty = true; + } + + function removeNode(node) { + if (nodesCount > 0) { + nodesCount -= 1; + } + + if (node.id < nodesCount && nodesCount > 0) { + // we can use colors as a 'view' into array array buffer. + utils.copyArrayPart(colors, node.id * ATTRIBUTES_PER_PRIMITIVE, nodesCount * ATTRIBUTES_PER_PRIMITIVE, ATTRIBUTES_PER_PRIMITIVE); + } + } + + function createNode() { + ensureEnoughStorage(); + nodesCount += 1; + } + + function replaceProperties(/* replacedNode, newNode */) {} + + function render() { + gl.useProgram(program); + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + gl.bufferData(gl.ARRAY_BUFFER, storage, gl.DYNAMIC_DRAW); + + if (sizeDirty) { + sizeDirty = false; + gl.uniformMatrix4fv(locations.transform, false, transform); + gl.uniform2f(locations.screenSize, width, height); + } + + gl.vertexAttribPointer(locations.vertexPos, 3, gl.FLOAT, false, ATTRIBUTES_PER_PRIMITIVE * Float32Array.BYTES_PER_ELEMENT, 0); + gl.vertexAttribPointer(locations.color, 4, gl.UNSIGNED_BYTE, true, ATTRIBUTES_PER_PRIMITIVE * Float32Array.BYTES_PER_ELEMENT, 3 * 4); + + gl.drawArrays(gl.POINTS, 0, nodesCount); + } +} + +},{"./webgl.js":57}],65:[function(require,module,exports){ +var parseColor = require('./parseColor.js'); + +module.exports = webglSquare; + +/** + * Can be used as a callback in the webglGraphics.node() function, to + * create a custom looking node. + * + * @param size - size of the node in pixels. + * @param color - color of the node in '#rrggbbaa' or '#rgb' format. + */ +function webglSquare(size, color) { + return { + /** + * Gets or sets size of the square side. + */ + size: typeof size === 'number' ? size : 10, + + /** + * Gets or sets color of the square. + */ + color: parseColor(color) + }; +} + +},{"./parseColor.js":55}],66:[function(require,module,exports){ +// todo: this should be generated at build time. +module.exports = '0.10.1'; + +},{}]},{},[1])(1) +}); \ No newline at end of file diff --git a/dist/vivagraph.min.js b/dist/vivagraph.min.js index f58034d..b73a6f0 100644 --- a/dist/vivagraph.min.js +++ b/dist/vivagraph.min.js @@ -1,2 +1 @@ -var Viva=Viva||{};Viva.Graph=Viva.Graph||{},"undefined"!=typeof module&&module.exports&&(module.exports=Viva),Viva.Graph.version="0.4.0",Viva.lazyExtend=function(e,t){var n;if(e||(e={}),t)for(n in t)if(t.hasOwnProperty(n)){var r=e.hasOwnProperty(n),i=typeof t[n],o=!r||typeof e[n]!==i;o?e[n]=t[n]:"object"===i&&(e[n]=Viva.lazyExtend(e[n],t[n]))}return e},Viva.random=function(){function e(){var e=4022871197,t=function(t){var n;for(t=""+t,n=0;t.length>n;n++){e+=t.charCodeAt(n);var r=.02519603282416938*e;e=r>>>0,r-=e,r*=e,e=r>>>0,r-=e,e+=4294967296*r}return 2.3283064365386963e-10*(e>>>0)};return t.version="Mash 0.9",t}function t(t){return function(t){var n,r,i=0,o=58,a=119,u=178,s=[],f=e();for(0===t.length&&(t=[+new Date]),n=0;256>n;n++)s[n]=f(" "),s[n]-=4.76837158203125e-7*f(" "),0>s[n]&&(s[n]+=1);for(r=0;t.length>r;r++)for(n=0;256>n;n++)s[n]-=f(t[r]),s[n]-=4.76837158203125e-7*f(t[r]),0>s[n]&&(s[n]+=1);f=null;var c=function(){var e;return i=255&i+1,o=255&o+1,a=255&a+1,u=255&u+1,e=s[i]-s[o],0>e&&(e+=1),e-=s[a],0>e&&(e+=1),e-=s[u],0>e&&(e+=1),s[i]=e,e};return c.uint32=function(){return 4294967296*c()>>>0},c.fract53=c,c.version="LFIB4 0.9",c.args=t,c}(t)}var n=new t(Array.prototype.slice.call(arguments));return{next:function(e){return Math.floor(n()*e)},nextDouble:function(){return n()}}},Viva.randomIterator=function(e,t){return t=t||Viva.random(),{forEach:function(n){var r,i,o;for(r=e.length-1;r>0;--r)i=t.next(r+1),o=e[i],e[i]=e[r],e[r]=o,n(o);e.length&&n(e[0])},shuffle:function(){var n,r,i;for(n=e.length-1;n>0;--n)r=t.next(n+1),i=e[r],e[r]=e[n],e[n]=i;return e}}},Viva.BrowserInfo=function(){if("undefined"==typeof window||!window.hasOwnProperty("navigator"))return{browser:"",version:"0"};var e=window.navigator.userAgent.toLowerCase(),t=/(webkit)[ \/]([\w.]+)/,n=/(opera)(?:.*version)?[ \/]([\w.]+)/,r=/(msie) ([\w.]+)/,i=/(mozilla)(?:.*? rv:([\w.]+))?/,o=t.exec(e)||n.exec(e)||r.exec(e)||0>e.indexOf("compatible")&&i.exec(e)||[];return{browser:o[1]||"",version:o[2]||"0"}}(),Viva.Graph.Utils=Viva.Graph.Utils||{},Viva.Graph.Utils.indexOfElementInArray=function(e,t){if(t.indexOf)return t.indexOf(e);var n,r=t.length;for(n=0;r>n;n+=1)if(t.hasOwnProperty(n)&&t[n]===e)return n;return-1},Viva.Graph.Utils=Viva.Graph.Utils||{},Viva.Graph.Utils.getDimension=function(e){if(!e)throw{message:"Cannot get dimensions of undefined container"};var t=e.clientWidth,n=e.clientHeight;return{left:0,top:0,width:t,height:n}},Viva.Graph.Utils.findElementPosition=function(e){var t=0,n=0;if(e.offsetParent)do t+=e.offsetLeft,n+=e.offsetTop;while(null!==(e=e.offsetParent));return[t,n]},Viva.Graph.Utils=Viva.Graph.Utils||{},Viva.Graph.Utils.events=function(e){var t=function(e){var t={};return e.fire=function(e,n){var r,i,o,a;if("string"!=typeof e)throw"Only strings can be used as even type";if(t.hasOwnProperty(e))for(r=t[e],a=0;r.length>a;++a)o=r[a],i=o.method,i(n);return this},e.addEventListener=function(e,n){if("function"!=typeof n)throw"Only functions allowed to be callbacks";var r={method:n};return t.hasOwnProperty(e)?t[e].push(r):t[e]=[r],this},e.removeEventListener=function(e,n){if("function"!=typeof n)throw"Only functions allowed to be callbacks";if(t.hasOwnProperty(e)){var r,i=t[e];for(r=0;i.length>r;++r)if(i[r].callback===n){i.splice(r);break}}return this},e.removeAllListeners=function(){var e;for(e in t)t.hasOwnProperty(e)&&delete t[e]},e};return{on:function(t,n){return e.addEventListener?e.addEventListener(t,n,!1):e.attachEvent&&e.attachEvent("on"+t,n),this},stop:function(t,n){e.removeEventListener?e.removeEventListener(t,n,!1):e.detachEvent&&e.detachEvent("on"+t,n)},extend:function(){return t(e)}}},Viva.Graph.Utils=Viva.Graph.Utils||{},Viva.Graph.Utils.dragndrop=function(e){var t,n,r,i,o,a,u,s=Viva.Graph.Utils.events(window.document),f=Viva.Graph.Utils.events(e),c=Viva.Graph.Utils.findElementPosition,l=0,d=0,h=!1,p=0,v=function(e){var t=0,n=0;return e=e||window.event,e.pageX||e.pageY?(t=e.pageX,n=e.pageY):(e.clientX||e.clientY)&&(t=e.clientX+window.document.body.scrollLeft+window.document.documentElement.scrollLeft,n=e.clientY+window.document.body.scrollTop+window.document.documentElement.scrollTop),[t,n]},m=function(e,t,r){n&&n(e,{x:t-l,y:r-d}),l=t,d=r},y=function(e){e.stopPropagation?e.stopPropagation():e.cancelBubble=!0},g=function(e){e.preventDefault&&e.preventDefault()},x=function(e){return y(e),!1},w=function(e){e=e||window.event,m(e,e.clientX,e.clientY)},V=function(e){if(e=e||window.event,h)return y(e),!1;var n=1===e.button&&null!==window.event||0===e.button;return n?(l=e.clientX,d=e.clientY,u=e.target||e.srcElement,t&&t(e,{x:l,y:d}),s.on("mousemove",w),s.on("mouseup",b),y(e),o=window.document.onselectstart,a=window.document.ondragstart,window.document.onselectstart=x,u.ondragstart=x,!1):void 0},b=function(e){e=e||window.event,s.stop("mousemove",w),s.stop("mouseup",b),window.document.onselectstart=o,u.ondragstart=a,u=null,r&&r(e)},E=function(t){if("function"==typeof i){t=t||window.event,t.preventDefault&&t.preventDefault(),t.returnValue=!1;var n,r=v(t),o=c(e),a={x:r[0]-o[0],y:r[1]-o[1]};n=t.wheelDelta?t.wheelDelta/360:t.detail/-9,i(t,n,a)}},G=function(t){!i&&t?"webkit"===Viva.BrowserInfo.browser?e.addEventListener("mousewheel",E,!1):e.addEventListener("DOMMouseScroll",E,!1):i&&!t&&("webkit"===Viva.BrowserInfo.browser?e.removeEventListener("mousewheel",E,!1):e.removeEventListener("DOMMouseScroll",E,!1)),i=t},N=function(e,t){return(e.clientX-t.clientX)*(e.clientX-t.clientX)+(e.clientY-t.clientY)*(e.clientY-t.clientY)},P=function(e){if(1===e.touches.length){y(e);var t=e.touches[0];m(e,t.clientX,t.clientY)}else if(2===e.touches.length){var n=N(e.touches[0],e.touches[1]),r=0;p>n?r=-1:n>p&&(r=1),i(e,r,{x:e.touches[0].clientX,y:e.touches[0].clientY}),p=n,y(e),g(e)}},_=function(e){h=!1,s.stop("touchmove",P),s.stop("touchend",_),s.stop("touchcancel",_),u=null,r&&r(e)},L=function(e,n){y(e),g(e),l=n.clientX,d=n.clientY,u=e.target||e.srcElement,t&&t(e,{x:l,y:d}),h||(h=!0,s.on("touchmove",P),s.on("touchend",_),s.on("touchcancel",_))},A=function(t){return console.log("Touch start for ",e),1===t.touches.length?L(t,t.touches[0]):(2===t.touches.length&&(y(t),g(t),p=N(t.touches[0],t.touches[1])),void 0)};return f.on("mousedown",V),f.on("touchstart",A),{onStart:function(e){return t=e,this},onDrag:function(e){return n=e,this},onStop:function(e){return r=e,this},onScroll:function(e){return G(e),this},release:function(){s.stop("mousemove",w),s.stop("mousedown",V),s.stop("mouseup",b),s.stop("touchmove",P),s.stop("touchend",_),s.stop("touchcancel",_),G(null)}}},Viva.Input=Viva.Input||{},Viva.Input.domInputManager=function(){return{bindDragNDrop:function(e,t){if(t){var n=Viva.Graph.Utils.dragndrop(e.ui);"function"==typeof t.onStart&&n.onStart(t.onStart),"function"==typeof t.onDrag&&n.onDrag(t.onDrag),"function"==typeof t.onStop&&n.onStop(t.onStop),e.events=n}else e.events&&(e.events.release(),e.events=null,delete e.events)}}},Viva.Graph.spatialIndex=function(e,t){var n,r,i=16;return"function"==typeof t?(r=t,n=function(t,n){var i=null;return e.forEachNode(function(e){return r(e,t,n)?(i=e,!0):void 0}),i}):"number"==typeof t&&(i=t,n=function(t,n){var r=null;return e.forEachNode(function(e){var o=e.position;return t>o.x-i&&o.x+i>t&&n>o.y-i&&o.y+i>n?(r=e,!0):void 0}),r}),{getNodeAt:n}},Viva.Graph.Utils=Viva.Graph.Utils||{},function(){var e,t,n=0,r=["ms","moz","webkit","o"];for(t="undefined"!=typeof window?window:"undefined"!=typeof global?global:{setTimeout:function(){},clearTimeout:function(){}},e=0;r.length>e&&!t.requestAnimationFrame;++e){var i=r[e];t.requestAnimationFrame=t[i+"RequestAnimationFrame"],t.cancelAnimationFrame=t[i+"CancelAnimationFrame"]||t[i+"CancelRequestAnimationFrame"]}t.requestAnimationFrame||(t.requestAnimationFrame=function(e){var r=(new Date).getTime(),i=Math.max(0,16-(r-n)),o=t.setTimeout(function(){e(r+i)},i);return n=r+i,o}),t.cancelAnimationFrame||(t.cancelAnimationFrame=function(e){t.clearTimeout(e)}),Viva.Graph.Utils.timer=function(e){var n,r=function(){t.cancelAnimationFrame(n),n=0},i=function(){n=t.requestAnimationFrame(i),e()||r()};return i(),{stop:r,restart:function(){n||i()}}}}(),Viva.Graph.geom=function(){return{intersect:function(e,t,n,r,i,o,a,u){var s,f,c,l,d,h,p,v,m,y,g,x,w,V={x:0,y:0};return s=r-t,c=e-n,d=n*t-e*r,m=s*i+c*o+d,y=s*a+c*u+d,0!==m&&0!==y&&m>=0==y>=4?null:(f=u-o,l=i-a,h=a*o-i*u,p=f*e+l*t+h,v=f*n+l*r+h,0!==p&&0!==v&&p>=0==v>=0?null:(g=s*l-f*c,0===g?null:(x=0>g?-g/2:g/2,x=0,w=c*h-l*d,V.x=(0>w?w-x:w+x)/g,w=f*d-s*h,V.y=(0>w?w-x:w+x)/g,V)))},intersectRect:function(e,t,n,r,i,o,a,u){return this.intersect(e,t,e,r,i,o,a,u)||this.intersect(e,r,n,r,i,o,a,u)||this.intersect(n,r,n,t,i,o,a,u)||this.intersect(n,t,e,t,i,o,a,u)},convexHull:function(e){var t=function(e,t){var n,r,i=function(t){var n=t.x-e.x,r=t.y-e.y,i=n>0?1:-1;return i*n*n/(n*n+r*r)},o=t.sort(function(e,t){return i(t)-i(e)}),a=o[0],u=i(a),s=a.x-e.x,f=a.y-e.y,c=s*s+f*f;for(r=1;o.length>r;++r){a=o[r];var l=i(a);l===u?(s=a.x-e.x,f=a.y-e.y,n=s*s+f*f,c>n?o.splice(r,1):o.splice(r-1,1)):u=l}return o},n=function(e,t,n){return 0>(n.x-e.x)*(t.y-e.y)-(n.y-e.y)*(t.x-e.x)};if(3>e.length)return e;var r,i=0;for(r=0;e.length>r;++r)e[r].ya.length)return a;var u=[];u.push(o),u.push(a[0]),u.push(a[1]);var s=u.length;for(r=2;a.length>r;++r){for(;!n(u[s-2],u[s-1],a[r]);)u.pop(),s-=1;u.push(a[r]),s+=1}return u}}},Viva.Graph.Rect=function(e,t,n,r){this.x1=e||0,this.y1=t||0,this.x2=n||0,this.y2=r||0},Viva.Graph.Point2d=function(e,t){this.x=e||0,this.y=t||0},Viva.Graph.Node=function(e){this.id=e,this.links=[],this.data=null},Viva.Graph.Link=function(e,t,n){this.fromId=e,this.toId=t,this.data=n},Viva.Graph.graph=function(){var e={},t=[],n=0,r=0,i=[],o=function(e){e.fire("changed",i)},a=function(){r+=1},u=function(e){r-=1,0===r&&i.length>0&&(o(e),i.length=0)},s=function(e,t){i.push({node:e,changeType:t})},f=function(e,t){i.push({link:e,changeType:t})},c=function(e){return e&&"object"==typeof e&&"number"==typeof e.length&&"function"==typeof e.splice&&!e.propertyIsEnumerable("length")},l={addNode:function(t,r){if(t===void 0)throw{message:"Invalid node identifier"};a();var i=this.getNode(t);if(i?s(i,"update"):(i=new Viva.Graph.Node(t),n++,s(i,"add")),r){var o,f=i.data||{},l=typeof r;if("string"===l||c(r)||"number"===l||"boolean"===l)f=r;else if("undefined"===l)f=null;else for(o in r)r.hasOwnProperty(o)&&(f[o]=r[o]);i.data=f}return e[t]=i,u(this),i},addLink:function(e,n,r){a();var i=this.getNode(e)||this.addNode(e),o=this.getNode(n)||this.addNode(n),s=new Viva.Graph.Link(e,n,r);return t.push(s),i.links.push(s),o.links.push(s),f(s,"add"),u(this),s},removeLink:function(e){if(!e)return!1;var n=Viva.Graph.Utils.indexOfElementInArray(e,t);if(0>n)return!1;a(),t.splice(n,1);var r=this.getNode(e.fromId),i=this.getNode(e.toId);return r&&(n=Viva.Graph.Utils.indexOfElementInArray(e,r.links),n>=0&&r.links.splice(n,1)),i&&(n=Viva.Graph.Utils.indexOfElementInArray(e,i.links),n>=0&&i.links.splice(n,1)),f(e,"remove"),u(this),!0},removeNode:function(t){var r=this.getNode(t);if(!r)return!1;for(a();r.links.length;){var i=r.links[0];this.removeLink(i)}e[t]=null,delete e[t],n--,s(r,"remove"),u(this)},getNode:function(t){return e[t]},getNodesCount:function(){return n},getLinksCount:function(){return t.length},getLinks:function(e){var t=this.getNode(e);return t?t.links:null},forEachNode:function(t){if("function"==typeof t){var n;for(n in e)if(e.hasOwnProperty(n)&&t(e[n]))return}},forEachLinkedNode:function(t,n,r){var i,o,a,u=this.getNode(t);if(u&&u.links&&"function"==typeof n)if(r)for(i=0;u.links.length>i;++i)o=u.links[i],o.fromId===t&&n(e[o.toId],o);else for(i=0;u.links.length>i;++i)o=u.links[i],a=o.fromId===t?o.toId:o.fromId,n(e[a],o)},forEachLink:function(e){var n;if("function"==typeof e)for(n=0;t.length>n;++n)e(t[n])},beginUpdate:function(){a()},endUpdate:function(){u(this)},clear:function(){var e=this;e.beginUpdate(),e.forEachNode(function(t){e.removeNode(t.id)}),e.endUpdate()},hasLink:function(e,t){var n,r=this.getNode(e);if(!r)return null;for(n=0;r.links.length>n;++n){var i=r.links[n];if(i.fromId===e&&i.toId===t)return i}return null}};return Viva.Graph.Utils.events(l).extend(),l},Viva.Graph.operations=function(){return{density:function(e){var t=e.getNodesCount();return 0===t?0/0:2*e.getLinksCount()/(t*(t-1))}}},Viva.Graph.Physics=Viva.Graph.Physics||{},Viva.Graph.Physics.Vector=function(e,t){this.x=e||0,this.y=t||0},Viva.Graph.Physics.Vector.prototype={multiply:function(e){return new Viva.Graph.Physics.Vector(this.x*e,this.y*e)}},Viva.Graph.Physics.Point=function(e,t){this.x=e||0,this.y=t||0},Viva.Graph.Physics.Point.prototype={add:function(e){return new Viva.Graph.Physics.Point(this.x+e.x,this.y+e.y)}},Viva.Graph.Physics.Body=function(){this.mass=1,this.force=new Viva.Graph.Physics.Vector,this.velocity=new Viva.Graph.Physics.Vector,this.location=new Viva.Graph.Physics.Point,this.prevLocation=new Viva.Graph.Physics.Point},Viva.Graph.Physics.Body.prototype={loc:function(e){return e?(this.location.x=e.x,this.location.y=e.y,this):this.location},vel:function(e){return e?(this.velocity.x=e.x,this.velocity.y=e.y,this):this.velocity}},Viva.Graph.Physics.Spring=function(e,t,n,r,i){this.body1=e,this.body2=t,this.length=n,this.coeff=r,this.weight=i},Viva.Graph.Physics.QuadTreeNode=function(){this.centerOfMass=new Viva.Graph.Physics.Point,this.children=[],this.body=null,this.hasChildren=!1,this.x1=0,this.y1=0,this.x2=0,this.y2=0},Viva.Graph.Physics=Viva.Graph.Physics||{},Viva.Graph.Physics.eulerIntegrator=function(){return{integrate:function(e,t){var n,r=e.speedLimit,i=0,o=0,a=e.bodies.length;for(n=0;a>n;++n){var u=e.bodies[n],s=t/u.mass;u.velocity.x+=s*u.force.x,u.velocity.y+=s*u.force.y;var f=u.velocity.x,c=u.velocity.y,l=Math.sqrt(f*f+c*c);l>r&&(u.velocity.x=r*f/l,u.velocity.y=r*c/l),i=t*u.velocity.x,o=t*u.velocity.y,u.location.x+=i,u.location.y+=o}return i*i+o*o}}},Viva.Graph.Physics.nbodyForce=function(e){function t(e,t){this.node=e,this.body=t}function n(){this.stack=[],this.popIdx=0}e=Viva.lazyExtend(e||{gravity:-1,theta:.8}),n.prototype={isEmpty:function(){return 0===this.popIdx},push:function(e,n){var r=this.stack[this.popIdx];r?(r.node=e,r.body=n):this.stack[this.popIdx]=new t(e,n),++this.popIdx},pop:function(){return this.popIdx>0?this.stack[--this.popIdx]:void 0},reset:function(){this.popIdx=0}};var r=e.gravity,i=[],o=new n,a=e.theta,u=Viva.random("5f4dcc3b5aa765d61d8327deb882cf99",75,20,63,108,65,76,65,72),s=function(){this.body=null,this.quads=[],this.mass=0,this.massX=0,this.massY=0,this.left=0,this.top=0,this.bottom=0,this.right=0,this.isInternal=!1},f=[],c=0,l=function(){var e;return f[c]?(e=f[c],e.quads[0]=null,e.quads[1]=null,e.quads[2]=null,e.quads[3]=null,e.body=null,e.mass=e.massX=e.massY=0,e.left=e.right=e.top=e.bottom=0,e.isInternal=!1):(e=new s,f[c]=e),++c,e},d=l(),h=function(e,t){var n=Math.abs(e.x-t.x),r=Math.abs(e.y-t.y);return.01>n&&.01>r},p=function(e){for(o.reset(),o.push(d,e);!o.isEmpty();){var t=o.pop(),n=t.node,r=t.body;if(n.isInternal){var i=r.location.x,a=r.location.y;n.mass=n.mass+r.mass,n.massX=n.massX+r.mass*i,n.massY=n.massY+r.mass*a;var s=0,f=n.left,c=(n.right+f)/2,p=n.top,v=(n.bottom+p)/2;if(i>c){s+=1;var m=f;f=c,c+=c-m}if(a>v){s+=2;var y=p;p=v,v+=v-y}var g=n.quads[s];g||(g=l(),g.left=f,g.top=p,g.right=c,g.bottom=v,n.quads[s]=g),o.push(g,r)}else if(n.body){var x=n.body;if(n.body=null,n.isInternal=!0,h(x.location,r.location)){var w,V;do{var b=2*u.nextDouble()*Math.PI,E=.006*(n.right-n.left)*Math.cos(b),G=.006*(n.bottom-n.top)*Math.sin(b);w=x.location.x+E,V=x.location.y+G}while(n.left>w||w>n.right||n.top>V||V>n.bottom);x.location.x=w,x.location.y=V}o.push(n,x),o.push(n,r)}else n.body=r}},v=function(e){var t,n,o,s,f=i,c=1,l=0,h=1;for(f[0]=d;c;){var p=f[l],v=p.body;c-=1,l+=1,v&&v!==e?(n=v.location.x-e.location.x,o=v.location.y-e.location.y,s=Math.sqrt(n*n+o*o),0===s&&(n=(u.nextDouble()-.5)/50,o=(u.nextDouble()-.5)/50,s=Math.sqrt(n*n+o*o)),t=r*v.mass*e.mass/(s*s*s),e.force.x=e.force.x+t*n,e.force.y=e.force.y+t*o):(n=p.massX/p.mass-e.location.x,o=p.massY/p.mass-e.location.y,s=Math.sqrt(n*n+o*o),0===s&&(n=(u.nextDouble()-.5)/50,o=(u.nextDouble()-.5)/50,s=Math.sqrt(n*n+o*o)),a>(p.right-p.left)/s?(t=r*p.mass*e.mass/(s*s*s),e.force.x=e.force.x+t*n,e.force.y=e.force.y+t*o):(p.quads[0]&&(f[h]=p.quads[0],c+=1,h+=1),p.quads[1]&&(f[h]=p.quads[1],c+=1,h+=1),p.quads[2]&&(f[h]=p.quads[2],c+=1,h+=1),p.quads[3]&&(f[h]=p.quads[3],c+=1,h+=1)))}},m=function(e){var t,n=Number.MAX_VALUE,r=Number.MAX_VALUE,i=Number.MIN_VALUE,o=Number.MIN_VALUE,a=e.bodies,u=a.length;for(t=u;t--;){var s=a[t].location.x,f=a[t].location.y;n>s&&(n=s),s>i&&(i=s),r>f&&(r=f),f>o&&(o=f)}var h=i-n,v=o-r;for(h>v?o=r+h:i=n+v,c=0,d=l(),d.left=n,d.right=i,d.top=r,d.bottom=o,t=u;t--;)p(a[t],d)};return{insert:p,init:m,update:v,options:function(e){return e?("number"==typeof e.gravity&&(r=e.gravity),"number"==typeof e.theta&&(a=e.theta),this):{gravity:r,theta:a}}}},Viva.Graph.Physics.dragForce=function(e){e||(e={});var t={coeff:e.coeff||.01};return{init:function(){},update:function(e){e.force.x-=t.coeff*e.velocity.x,e.force.y-=t.coeff*e.velocity.y},options:function(e){return e?("number"==typeof e.coeff&&(t.coeff=e.coeff),this):t}}},Viva.Graph.Physics.springForce=function(e){e=Viva.lazyExtend(e,{length:50,coeff:22e-5});var t=Viva.random("Random number 4.","Chosen by fair dice roll");return{init:function(){},update:function(n){var r=n.body1,i=n.body2,o=0>n.length?e.length:n.length,a=i.location.x-r.location.x,u=i.location.y-r.location.y,s=Math.sqrt(a*a+u*u);0===s&&(a=(t.nextDouble()-.5)/50,u=(t.nextDouble()-.5)/50,s=Math.sqrt(a*a+u*u));var f=s-o,c=(!n.coeff||0>n.coeff?e.coeff:n.coeff)*f/s*n.weight;r.force.x+=c*a,r.force.y+=c*u,i.force.x+=-c*a,i.force.y+=-c*u},options:function(t){return t?("number"==typeof t.length&&(e.length=t.length),"number"==typeof t.coeff&&(e.coeff=t.coeff),this):e}}},Viva.Graph.Physics=Viva.Graph.Physics||{},Viva.Graph.Physics.forceSimulator=function(e){var t=e,n=[],r=[],i=[],o=[];return{speedLimit:1,bodies:n,accumulate:function(){var e,t,a;for(e=i.length;e--;)i[e].init(this);for(e=o.length;e--;)o[e].init(this);for(e=n.length;e--;)for(a=n[e],a.force.x=0,a.force.y=0,t=0;i.length>t;t++)i[t].update(a);for(e=0;r.length>e;++e)for(t=0;o.length>t;t++)o[t].update(r[e])},run:function(e){return this.accumulate(),t.integrate(this,e)},addBody:function(e){if(!e)throw{message:"Cannot add null body to force simulator"};return n.push(e),e},removeBody:function(e){if(!e)return!1;var t=Viva.Graph.Utils.indexOfElementInArray(e,n);return 0>t?!1:n.splice(t,1)},addSpring:function(e,t,n,i,o){if(!e||!t)throw{message:"Cannot add null spring to force simulator"};if("number"!=typeof n)throw{message:"Spring length should be a number"};o="number"==typeof o?o:1;var a=new Viva.Graph.Physics.Spring(e,t,n,i>=0?i:-1,o);return r.push(a),a},removeSpring:function(e){if(!e)return!1;var t=Viva.Graph.Utils.indexOfElementInArray(e,r);return 0>t?!1:r.splice(t,1)},addBodyForce:function(e){if(!e)throw{message:"Cannot add mighty (unknown) force to the simulator"};i.push(e)},addSpringForce:function(e){if(!e)throw{message:"Cannot add unknown force to the simulator"};o.push(e)}}},Viva.Graph.Layout=Viva.Graph.Layout||{},Viva.Graph.Layout.forceDirected=function(e,t){var n=.001;if(!e)throw{message:"Graph structure cannot be undefined"};t=Viva.lazyExtend(t,{springLength:80,springCoeff:2e-4,gravity:-1.2,theta:.8,dragCoeff:.02});var r=Viva.Graph.Physics.forceSimulator(Viva.Graph.Physics.eulerIntegrator()),i=Viva.Graph.Physics.nbodyForce({gravity:t.gravity,theta:t.theta}),o=Viva.Graph.Physics.springForce({length:t.springLength,coeff:t.springCoeff}),a=Viva.Graph.Physics.dragForce({coeff:t.dragCoeff}),u=!0,s=new Viva.Graph.Rect,f=Viva.random("ted.com",103,114,101,97,116),c=function(n){var r=(s.x1+s.x2)/2,i=(s.y1+s.y2)/2,o=t.springLength;if(n.links&&n.links.length>0){var a=n.links[0],u=a.fromId!==n.id?e.getNode(a.fromId):e.getNode(a.toId);u.position&&(r=u.position.x,i=u.position.y)}return{x:r+f.next(o)-o/2,y:i+f.next(o)-o/2}},l=function(t){var n=t.force_directed_body;n.mass=1+e.getLinks(t.id).length/3},d=function(e){var t=e.force_directed_body;t||(e.position=e.position||c(e),t=new Viva.Graph.Physics.Body,e.force_directed_body=t,l(e),t.loc(e.position),r.addBody(t))},h=function(e){var t=e.force_directed_body;t&&(e.force_directed_body=null,delete e.force_directed_body,r.removeBody(t))},p=function(t){var n=e.getNode(t.fromId),i=e.getNode(t.toId);l(n),l(i),t.force_directed_spring=r.addSpring(n.force_directed_body,i.force_directed_body,-1,t.weight)},v=function(t){var n=t.force_directed_spring;if(n){var i=e.getNode(t.fromId),o=e.getNode(t.toId);i&&l(i),o&&l(o),t.force_directed_spring=null,delete t.force_directed_spring,r.removeSpring(n)}},m=function(){e.forEachNode(d),e.forEachLink(p)},y=function(e){return e?e.isPinned||e.data&&e.data.isPinned:!0},g=function(){var t=Number.MAX_VALUE,n=Number.MAX_VALUE,r=Number.MIN_VALUE,i=Number.MIN_VALUE;0!==e.getNodesCount()&&(e.forEachNode(function(e){var o=e.force_directed_body;o&&(y(e)&&o.loc(e.position),e.position.x=o.location.x,e.position.y=o.location.y,t>e.position.x&&(t=e.position.x),e.position.x>r&&(r=e.position.x),n>e.position.y&&(n=e.position.y),e.position.y>i&&(i=e.position.y))}),s.x1=t,s.x2=r,s.y1=n,s.y2=i)};return r.addSpringForce(o),r.addBodyForce(i),r.addBodyForce(a),{run:function(e){var t;for(e=e||50,t=0;e>t;++t)this.step()},step:function(){u&&(m(),u=!1);var e=r.run(20);return g(),n>e},getGraphRect:function(){return s},addNode:function(e){d(e)},removeNode:function(e){h(e)},addLink:function(e){p(e)},removeLink:function(e){v(e)},dispose:function(){u=!0},springLength:function(e){return 1===arguments.length?(o.options({length:e}),this):o.options().length},springCoeff:function(e){return 1===arguments.length?(o.options({coeff:e}),this):o.options().coeff},gravity:function(e){return 1===arguments.length?(i.options({gravity:e}),this):i.options().gravity},theta:function(e){return 1===arguments.length?(i.options({theta:e}),this):i.options().theta},drag:function(e){return 1===arguments.length?(a.options({coeff:e}),this):a.options().coeff}}},Viva.Graph.Layout=Viva.Graph.Layout||{},Viva.Graph.Layout.constant=function(e,t){t=Viva.lazyExtend(t,{maxX:1024,maxY:1024,seed:"Deterministic randomness made me do this"});var n=Viva.random(t.seed),r=new Viva.Graph.Rect(Number.MAX_VALUE,Number.MAX_VALUE,Number.MIN_VALUE,Number.MIN_VALUE),i=function(){return new Viva.Graph.Point2d(n.next(t.maxX),n.next(t.maxY))},o=function(e,t){e.position.xt.x2&&(t.x2=e.position.x),e.position.yt.y2&&(t.y2=e.position.y)},a=function(e){e.hasOwnProperty("position")||(e.position=i(e)),o(e,r)},u=function(){0!==e.getNodesCount()&&(r.x1=Number.MAX_VALUE,r.y1=Number.MAX_VALUE,r.x2=Number.MIN_VALUE,r.y2=Number.MIN_VALUE,e.forEachNode(a))};return{run:function(){this.step()},step:function(){return u(),!1},getGraphRect:function(){return r},addNode:a,removeNode:function(){},addLink:function(){},removeLink:function(){},dispose:function(){},placeNode:function(e){return"function"==typeof e?(i=e,u(),this):i(e)}}},Viva.Graph.View=Viva.Graph.View||{},Viva.Graph.View.renderer=function(e,t){var n=30;t=t||{};var r,i,o,a,u=t.layout,s=t.graphics,f=t.container,c=!1,l=!0,d=0,h=0,p=!1,v=!1,m={x:0,y:0},y={offsetX:0,offsetY:0,scale:1},g=function(){f=f||window.document.body,u=u||Viva.Graph.Layout.forceDirected(e),s=s||Viva.Graph.View.svgGraphics(e,{container:f}),t.hasOwnProperty("renderLinks")||(t.renderLinks=!0),t.prerender=t.prerender||0,r=(s.inputManager||Viva.Input.domInputManager)(e,s)},x={x:0,y:0,node:null},w={x:0,y:0,node:null},V={x:0,y:0},b=Viva.Graph.Utils.events(window),E=Viva.Graph.Utils.events({}).extend(),G=function(t){var n=e.getNode(t.fromId),r=e.getNode(t.toId);n&&r&&(x.x=n.position.x,x.y=n.position.y,x.node=n,w.x=r.position.x,w.y=r.position.y,w.node=r,s.updateLinkPosition(t.ui,x,w))},N=function(e){V.x=e.position.x,V.y=e.position.y,s.updateNodePosition(e.ui,V)},P=function(){s.beginRender(),t.renderLinks&&!s.omitLinksRendering&&e.forEachLink(G),e.forEachNode(N),s.endRender()},_=function(){return p=u.step()&&!v,P(),!p},L=function(e){return i?(h+=e,void 0):(e?(h+=e,i=Viva.Graph.Utils.timer(function(){return _()},n)):(d=0,h=0,i=Viva.Graph.Utils.timer(_,n)),void 0)},A=function(){p=!1,i.restart()},I=function(){var e;if("number"==typeof t.prerender&&t.prerender>0)for(e=0;t.prerender>e;e+=1)u.step();else u.step()},k=function(){var e=u.getGraphRect(),t=Viva.Graph.Utils.getDimension(f);m.x=m.y=0,y.offsetX=t.width/2-(e.x2+e.x1)/2,y.offsetY=t.height/2-(e.y2+e.y1)/2,s.graphCenterChanged(y.offsetX+m.x,y.offsetY+m.y),l=!1},C=function(e){var t=s.node(e);e.ui=t,s.initNode(t),u.addNode(e),N(e)},T=function(e){e.hasOwnProperty("ui")&&(s.releaseNode(e.ui),e.ui=null,delete e.ui),u.removeNode(e)},M=function(e){var t=s.link(e);e.ui=t,s.initLink(t),s.omitLinksRendering||G(e)},S=function(e){e.hasOwnProperty("ui")&&(s.releaseLink(e.ui),e.ui=null,delete e.ui)},U=function(e){var t=!1;r.bindDragNDrop(e,{onStart:function(){t=e.isPinned,e.isPinned=!0,v=!0,A()},onDrag:function(t,n){e.position.x+=n.x/y.scale,e.position.y+=n.y/y.scale,v=!0,P()},onStop:function(){e.isPinned=t,v=!1}})},R=function(e){r.bindDragNDrop(e,null)},D=function(){s.init(f),e.forEachNode(C),t.renderLinks&&e.forEachLink(M)},F=function(){s.release(f)},O=function(t){var n=t.node;if("add"===t.changeType)C(n),U(n),l&&k();else if("remove"===t.changeType)R(n),T(n),0===e.getNodesCount()&&(l=!0);else if("update"===t.changeType)throw"Update type is not implemented. TODO: Implement me!"},z=function(e){var n=e.link;if("add"===e.changeType)t.renderLinks&&M(n),u.addLink(n);else if("remove"===e.changeType)t.renderLinks&&S(n),u.removeLink(n);else if("update"===e.changeType)throw"Update type is not implemented. TODO: Implement me!"},B=function(e){var t,n;for(t=0;e.length>t;t+=1)n=e[t],n.node?O(n):n.link&&z(n);A()},Y=function(){k(),_()},X=function(){a&&(a.release(),a=null)},q=function(){o&&(o.stop("changed",B),o=null)},W=function(){b.on("resize",Y),X(),a=Viva.Graph.Utils.dragndrop(f),a.onDrag(function(e,t){m.x+=t.x,m.y+=t.y,s.translateRel(t.x,t.y),P()}),a.onScroll(function(e,t,n){var r=Math.pow(1.4,0>t?-.2:.2);y.scale=s.scale(r,n),P(),E.fire("scale",y.scale)}),e.forEachNode(U),q(),o=Viva.Graph.Utils.events(e),o.on("changed",B)},j=function(){c=!1,q(),X(),b.stop("resize",Y),E.removeAllListeners(),i.stop(),e.forEachLink(function(e){t.renderLinks&&S(e),u.removeLink(e)}),e.forEachNode(function(e){R(e),T(e)}),u.dispose(),F()};return{run:function(e){return c||(g(),I(),k(),D(),W(),c=!0),L(e),this},reset:function(){s.resetScale(),k(),y.scale=1},pause:function(){i.stop()},resume:function(){i.restart()},rerender:function(){return P(),this},dispose:function(){j()},on:function(e,t){return E.addEventListener(e,t),this},off:function(e,t){return E.removeEventListener(e,t),this}}},Viva.Graph.serializer=function(){var e=function(){if("undefined"==typeof JSON||!JSON.stringify||!JSON.parse)throw"JSON serializer is not defined."},t=function(e){return{id:e.id,data:e.data}},n=function(e){return{fromId:e.fromId,toId:e.toId,data:e.data}},r=function(e){return e},i=function(e){return e};return{storeToJSON:function(r,i,o){if(!r)throw"Graph is not defined";e(),i=i||t,o=o||n;var a={nodes:[],links:[]};return r.forEachNode(function(e){a.nodes.push(i(e))}),r.forEachLink(function(e){a.links.push(o(e))}),JSON.stringify(a)},loadFromJSON:function(t,n,o){if("string"!=typeof t)throw"String expected in loadFromJSON() method";e(),n=n||r,o=o||i;var a,u=JSON.parse(t),s=Viva.Graph.graph();if(!u||!u.nodes||!u.links)throw"Passed json string does not represent valid graph";for(a=0;u.nodes.length>a;++a){var f=n(u.nodes[a]);if(!f.hasOwnProperty("id"))throw"Graph node format is invalid. Node.id is missing";s.addNode(f.id,f.data)}for(a=0;u.links.length>a;++a){var c=o(u.links[a]);if(!c.hasOwnProperty("fromId")||!c.hasOwnProperty("toId"))throw"Graph link format is invalid. Both fromId and toId are required";s.addLink(c.fromId,c.toId,c.data)}return s}}},Viva.Graph.centrality=function(){var e=function(e,t,n){var r,i,o,a={},u=[],s={},f={},c=[t.id],l=function(e){f.hasOwnProperty(e.id)||(c.push(e.id),f[e.id]=i+1),f[e.id]===i+1&&(s[e.id]+=o,a[e.id].push(r))};for(e.forEachNode(function(e){a[e.id]=[],s[e.id]=0}),f[t.id]=0,s[t.id]=1;c.length;)r=c.shift(),i=f[r],o=s[r],u.push(r),e.forEachLinkedNode(r,l,n);return{S:u,P:a,sigma:s}},t=function(e,t,n){var r,i,o,a,u,s={},f=t.S;for(r=0;f.length>r;r+=1)s[f[r]]=0;for(;f.length;){for(i=f.pop(),o=(1+s[i])/t.sigma[i],a=t.P[i],r=0;a.length>r;r+=1)u=a[r],s[u]+=t.sigma[u]*o;i!==n&&(e[i]+=s[i])}},n=function(e){var t,n=[];for(t in e)e.hasOwnProperty(t)&&n.push({key:t,value:e[t]});return n.sort(function(e,t){return t.value-e.value})};return{betweennessCentrality:function(r){var i,o={};return r.forEachNode(function(e){o[e.id]=0}),r.forEachNode(function(n){i=e(r,n),t(o,i,n)}),n(o)},degreeCentrality:function(e,t){var n,r,i=[],o=[];if(t=(t||"both").toLowerCase(),"in"===t)n=function(e,t){var n,r=0;for(n=0;e.length>n;n+=1)r+=e[n].toId===t?1:0;return r};else if("out"===t)n=function(e,t){var n,r=0;for(n=0;e.length>n;n+=1)r+=e[n].fromId===t?1:0;return r};else{if("both"!==t)throw"Expected centrality degree kind is: in, out or both";n=function(e){return e.length}}e.forEachNode(function(t){var r=e.getLinks(t.id),o=n(r,t.id);i.hasOwnProperty(o)?i[o].push(t.id):i[o]=[t.id]});for(r in i)if(i.hasOwnProperty(r)){var a,u=i[r];if(u)for(a=0;u.length>a;++a)o.unshift({key:u[a],value:parseInt(r,10)})}return o}}},Viva.Graph.community=function(){return{slpa:function(e,t,n){var r=Viva.Graph._community.slpaAlgorithm(e,t,n);return r.run()}}},Viva.Graph._community={},Viva.Graph._community.slpaAlgorithm=function(e,t,n){t=t||100,n=n||.3;var r=Viva.random(1331782216905),i=Viva.random("Greeting goes to you, ","dear reader"),o=function(e,n){var r=[];return e.forEachUniqueWord(function(e,i){return i>n?(r.push({name:e,probability:i/t}),void 0):!0}),r},a=function(e){var t=[];return e.forEachNode(function(e){var n=Viva.Graph._community.occuranceMap(r);n.add(e.id),e.slpa={memory:n},t.push(e.id)}),t},u=function(e,n){var o,a=Viva.randomIterator(n,i),u=function(t){var n=e.getNode(t),i=Viva.Graph._community.occuranceMap(r);e.forEachLinkedNode(t,function(e){var t=e.slpa.memory.getRandomWord();i.add(t)});var o=i.getMostPopularFair();n.slpa.memory.add(o)};for(o=0;t-1>o;++o)a.forEach(u)},s=function(e){var r={};return e.forEachNode(function(e){var i,a=o(e.slpa.memory,n*t);for(i=0;a.length>i;++i){var u=a[i].name;r.hasOwnProperty(u)?r[u].push(e.id):r[u]=[e.id]}e.communities=a,e.slpa=null,delete e.slpa}),r};return{run:function(){var t=a(e);return u(e,t),s(e)}}},Viva.Graph._community.occuranceMap=function(e){e=e||Viva.random();var t={},n=[],r=!1,i=[],o=function(){var e;i.length=0;for(e in t)t.hasOwnProperty(e)&&i.push(e);i.sort(function(e,n){var r=t[n]-t[e];return r?r:n>e?-1:e>n?1:0})},a=function(){r&&(o(),r=!1)};return{add:function(e){e+="",t.hasOwnProperty(e)?t[e]+=1:t[e]=1,n.push(e),r=!0},getWordCount:function(e){return t[e]||0},getMostPopularFair:function(){if(1===n.length)return n[0];a();var r,o=0;for(r=1;i.length>r&&t[i[r-1]]===t[i[r]];++r)o+=1;return o+=1,i[e.next(o)]},getRandomWord:function(){if(0===n.length)throw"The occurance map is empty. Cannot get empty word";return n[e.next(n.length)]},forEachUniqueWord:function(e){if("function"!=typeof e)throw"Function callback is expected to enumerate all words";var n;for(a(),n=0;i.length>n;++n){var r=i[n],o=t[r],u=e(r,o);if(u)break}}}},Viva.Graph.generator=function(){return{complete:function(e){if(!e||1>e)throw{message:"At least two nodes expected for complete graph"};var t,n,r=Viva.Graph.graph();for(r.Name="Complete K"+e,t=0;e>t;++t)for(n=t+1;e>n;++n)t!==n&&r.addLink(t,n);return r},completeBipartite:function(e,t){if(!e||!t||0>e||0>t)throw{message:"Graph dimensions are invalid. Number of nodes in each partition should be greate than 0"};var n,r,i=Viva.Graph.graph();for(i.Name="Complete K "+e+","+t,n=0;e>n;++n)for(r=e;e+t>r;++r)i.addLink(n,r); -return i},ladder:function(e){if(!e||0>e)throw{message:"Invalid number of nodes"};var t,n=Viva.Graph.graph();for(n.Name="Ladder graph "+e,t=0;e-1>t;++t)n.addLink(t,t+1),n.addLink(e+t,e+t+1),n.addLink(t,e+t);return n.addLink(e-1,2*e-1),n},circularLadder:function(e){if(!e||0>e)throw{message:"Invalid number of nodes"};var t=this.ladder(e);return t.Name="Circular ladder graph "+e,t.addLink(0,e-1),t.addLink(e,2*e-1),t},grid:function(e,t){var n,r,i=Viva.Graph.graph();for(i.Name="Grid graph "+e+"x"+t,n=0;e>n;++n)for(r=0;t>r;++r){var o=n+r*e;n>0&&i.addLink(o,n-1+r*e),r>0&&i.addLink(o,n+(r-1)*e)}return i},path:function(e){if(!e||0>e)throw{message:"Invalid number of nodes"};var t,n=Viva.Graph.graph();for(n.Name="Path graph "+e,n.addNode(0),t=1;e>t;++t)n.addLink(t-1,t);return n},lollipop:function(e,t){if(!t||0>t||!e||0>e)throw{message:"Invalid number of nodes"};var n,r=this.complete(e);for(r.Name="Lollipop graph. Head x Path "+e+"x"+t,n=0;t>n;++n)r.addLink(e+n-1,e+n);return r},balancedBinTree:function(e){var t,n=Viva.Graph.graph(),r=Math.pow(2,e);for(n.Name="Balanced bin tree graph "+e,t=1;r>t;++t){var i=t,o=2*i,a=2*i+1;n.addLink(i,o),n.addLink(i,a)}return n},randomNoLinks:function(e){if(!e||0>e)throw{message:"Invalid number of nodes"};var t,n=Viva.Graph.graph();for(n.Name="Random graph, no Links: "+e,t=0;e>t;++t)n.addNode(t);return n}}},Viva.Graph.View=Viva.Graph.View||{},Viva.Graph.View.cssGraphics=function(){var e,t,n,r="OLD_IE",i=1,o=1,a=function(){var e,t,n=Viva.BrowserInfo.browser;switch(n){case"mozilla":e="Moz";break;case"webkit":e="webkit";break;case"opera":e="O";break;case"msie":if(t=Viva.BrowserInfo.version.split(".")[0],!(t>8))return r;e="ms"}return e?e+"Transform":null}(),u=function(){return a===r?function(e,t,n,r){var i=Math.cos(r),o=Math.sin(r);0>r&&(r=2*Math.PI+r),Math.PI/2>r?(e.style.left=t+"px",e.style.top=n+"px"):Math.PI>r?(e.style.left=t-e.clientWidth*Math.cos(Math.PI-r),e.style.top=n):Math.PI+Math.PI/2>r?(e.style.left=t-e.clientWidth*Math.cos(Math.PI-r),e.style.top=n+e.clientWidth*Math.sin(Math.PI-r)):(e.style.left=t,e.style.top=n+e.clientWidth*Math.sin(Math.PI-r)),e.style.filter='progid:DXImageTransform.Microsoft.Matrix(sizingMethod="auto expand",M11='+i+", M12="+-o+","+"M21="+o+", M22="+i+");"}:a?function(e,t,n,r){e.style.left=t+"px",e.style.top=n+"px",e.style[a]="rotate("+r+"rad)",e.style[a+"Origin"]="left"}:function(){}}(),s=function(){var e=window.document.createElement("div");return e.setAttribute("class","node"),e},f=function(e,t){e.style.left=t.x-5+"px",e.style.top=t.y-5+"px"},c=function(e,t,n){var r=t.x-n.x,i=t.y-n.y,o=Math.sqrt(r*r+i*i);e.style.height="1px",e.style.width=o+"px",u(e,n.x,n.y,Math.atan2(i,r))},l=function(){var e=window.document.createElement("div");return e.setAttribute("class","link"),e},d=function(){if(e){if(!a||a===r)throw"Not implemented. TODO: Implement OLD_IE Filter based transform";var u="matrix("+i+", 0, 0,"+o+","+t+","+n+")";e.style[a]=u}};return{node:function(e){return e&&"function"!=typeof e?s(e):(s=e,this)},link:function(e){return e&&"function"!=typeof e?l(e):(l=e,this)},inputManager:Viva.Input.domInputManager,graphCenterChanged:function(e,r){t=e,n=r,d()},translateRel:function(e,r){t+=e,n+=r,d()},scale:function(){return 1},resetScale:function(){return this},beginRender:function(){},endRender:function(){},placeNode:function(e){return f=e,this},placeLink:function(e){return c=e,this},init:function(t){e=t,d()},initLink:function(t){e.childElementCount>0?e.insertBefore(t,e.firstChild):e.appendChild(t)},releaseLink:function(t){e.removeChild(t)},initNode:function(t){e.appendChild(t)},releaseNode:function(t){e.removeChild(t)},updateNodePosition:function(e,t){f(e,t)},updateLinkPosition:function(e,t,n){c(e,t,n)}}},Viva.Graph.svg=function(e){var t="http://www.w3.org/2000/svg",n="http://www.w3.org/1999/xlink",r=e;return"string"==typeof e&&(r=window.document.createElementNS(t,e)),r.vivagraphAugmented?r:(r.vivagraphAugmented=!0,r.attr=function(e,t){return 2===arguments.length?(null!==t?r.setAttributeNS(null,e,t):r.removeAttributeNS(null,e),r):r.getAttributeNS(null,e)},r.append=function(e){var t=Viva.Graph.svg(e);return r.appendChild(t),t},r.text=function(e){return e!==void 0?(r.textContent=e,r):r.textContent},r.link=function(e){return arguments.length?(r.setAttributeNS(n,"xlink:href",e),r):r.getAttributeNS(n,"xlink:href")},r.children=function(e){var t,n,i=[],o=r.childNodes.length;if(void 0===e&&r.hasChildNodes())for(t=0;o>t;t++)i.push(Viva.Graph.svg(r.childNodes[t]));else if("string"==typeof e){var a="."===e[0],u="#"===e[0],s=!a&&!u;for(t=0;o>t;t++){var f=r.childNodes[t];if(1===f.nodeType){var c=f.attr("class"),l=f.attr("id"),d=f.nodeName;if(a&&c){for(c=c.replace(/\s+/g," ").split(" "),n=0;c.length>n;n++)if(a&&c[n]===e.substr(1)){i.push(Viva.Graph.svg(f));break}}else{if(u&&l===e.substr(1)){i.push(Viva.Graph.svg(f));break}s&&d===e&&i.push(Viva.Graph.svg(f))}i=i.concat(Viva.Graph.svg(f).children(e))}}if(u&&1===i.length)return i[0]}return i},r)},Viva.Graph.View=Viva.Graph.View||{},Viva.Graph.View.svgGraphics=function(){var e,t,n,r,i=1,o=function(){return Viva.Graph.svg("rect").attr("width",10).attr("height",10).attr("fill","#00a2e8")},a=function(e,t){e.attr("x",t.x-5).attr("y",t.y-5)},u=function(){return Viva.Graph.svg("line").attr("stroke","#999")},s=function(e,t,n){e.attr("x1",t.x).attr("y1",t.y).attr("x2",n.x).attr("y2",n.y)},f=function(e){e.fire("rescaled")},c=function(){if(e){var t="matrix("+i+", 0, 0,"+i+","+n+","+r+")";e.attr("transform",t)}},l={node:function(e){return e&&"function"!=typeof e?o(e):(o=e,this)},link:function(e){return e&&"function"!=typeof e?u(e):(u=e,this)},placeNode:function(e){return a=e,this},placeLink:function(e){return s=e,this},beginRender:function(){},endRender:function(){},graphCenterChanged:function(e,t){n=e,r=t,c()},inputManager:Viva.Input.domInputManager,translateRel:function(n,r){var i=t.createSVGPoint(),o=e.getCTM(),a=t.createSVGPoint().matrixTransform(o.inverse());i.x=n,i.y=r,i=i.matrixTransform(o.inverse()),i.x=(i.x-a.x)*o.a,i.y=(i.y-a.y)*o.d,o.e+=i.x,o.f+=i.y;var u="matrix("+o.a+", 0, 0,"+o.d+","+o.e+","+o.f+")";e.attr("transform",u)},scale:function(o,a){var u=t.createSVGPoint();u.x=a.x,u.y=a.y,u=u.matrixTransform(e.getCTM().inverse());var s=t.createSVGMatrix().translate(u.x,u.y).scale(o).translate(-u.x,-u.y),c=e.getCTM().multiply(s);i=c.a,n=c.e,r=c.f;var l="matrix("+c.a+", 0, 0,"+c.d+","+c.e+","+c.f+")";return e.attr("transform",l),f(this),i},resetScale:function(){i=1;var t="matrix(1, 0, 0, 1, 0, 0)";return e.attr("transform",t),f(this),this},init:function(n){t=Viva.Graph.svg("svg"),e=Viva.Graph.svg("g").attr("buffered-rendering","dynamic"),t.appendChild(e),n.appendChild(t),c()},release:function(e){t&&e&&e.removeChild(t)},initLink:function(t){t&&(e.childElementCount>0?e.insertBefore(t,e.firstChild):e.appendChild(t))},releaseLink:function(t){e.removeChild(t)},initNode:function(t){e.appendChild(t)},releaseNode:function(t){e.removeChild(t)},updateNodePosition:function(e,t){a(e,t)},updateLinkPosition:function(e,t,n){s(e,t,n)},getSvgRoot:function(){return t}};return Viva.Graph.Utils.events(l).extend(),l},Viva.Graph.View.svgNodeFactory=function(e){var t="#999",n=Viva.Graph.geom(),r=function(e){e.size={w:10,h:10},e.append("rect").attr("width",e.size.w).attr("height",e.size.h).attr("stroke","orange").attr("fill","orange")},i=function(e){return e.size};return{node:function(e){var t=Viva.Graph.svg("g");return r(t,e),t.nodeId=e.id,t},link:function(n){var r=e.getNode(n.fromId),i=r&&r.ui;if(i&&!i.linksContainer){var o=Viva.Graph.svg("path").attr("stroke",t);return i.linksContainer=o,o}return null},customContent:function(e,t){if("function"!=typeof e||"function"!=typeof t)throw"Two functions expected: contentCreator(nodeUI, node) and size(nodeUI)";r=e,i=t},placeNode:function(t,r){var o="",a=i(t);e.forEachLinkedNode(t.nodeId,function(e,u){if(e.position&&e.ui&&e.ui!==t&&u.fromId===t.nodeId){var s=i(e.ui),f=e.position,c=n.intersectRect(r.x-a.w/2,r.y-a.h/2,r.x+a.w/2,r.y+a.h/2,r.x,r.y,f.x,f.y)||r,l=n.intersectRect(f.x-s.w/2,f.y-s.h/2,f.x+s.w/2,f.y+s.h/2,f.x,f.y,r.x,r.y)||f;o+="M"+Math.round(c.x)+" "+Math.round(c.y)+"L"+Math.round(l.x)+" "+Math.round(l.y)}}),t.attr("transform","translate("+(r.x-a.w/2)+", "+(r.y-a.h/2)+")"),""!==o&&t.linksContainer&&t.linksContainer.attr("d",o)}}},Viva.Graph.webgl=function(e){var t=function(t,n){var r=e.createShader(n);if(e.shaderSource(r,t),e.compileShader(r),!e.getShaderParameter(r,e.COMPILE_STATUS)){var i=e.getShaderInfoLog(r);throw window.alert(i),i}return r};return{createProgram:function(n,r){var i=e.createProgram(),o=t(n,e.VERTEX_SHADER),a=t(r,e.FRAGMENT_SHADER);if(e.attachShader(i,o),e.attachShader(i,a),e.linkProgram(i),!e.getProgramParameter(i,e.LINK_STATUS)){var u=e.getShaderInfoLog(i);throw window.alert(u),u}return i},extendArray:function(e,t,n){if((t+1)*n>e.length){var r=new Float32Array(2*e.length*n);return r.set(e),r}return e},copyArrayPart:function(e,t,n,r){var i;for(i=0;r>i;++i)e[t+i]=e[n+i]},swapArrayPart:function(e,t,n,r){var i;for(i=0;r>i;++i){var o=e[t+i];e[t+i]=e[n+i],e[n+i]=o}},getLocations:function(t,n){var r,i={};for(r=0;n.length>r;++r){var o=n[r],a=-1;if(0===o.indexOf("a_")){if(a=e.getAttribLocation(t,o),-1===a)throw"Program doesn't have required attribute: "+o;i[o.slice(2)]=a}else{if(0!==o.indexOf("u_"))throw"Couldn't figure out your intent. All uniforms should start with 'u_' prefix, and attributes with 'a_'";if(a=e.getUniformLocation(t,o),null===a)throw"Program doesn't have required uniform: "+o;i[o.slice(2)]=a}}return i},context:e}},Viva.Graph.View.WebglUtils=function(){},Viva.Graph.View.WebglUtils.prototype.parseColor=function(e){var t=10414335;if("string"==typeof e&&e)if(4===e.length&&(e=e.replace(/([^#])/g,"$1$1")),9===e.length)t=parseInt(e.substr(1),16);else{if(7!==e.length)throw'Color expected in hex format with preceding "#". E.g. #00ff00. Got value: '+e;t=255|parseInt(e.substr(1),16)<<8}else"number"==typeof e&&(t=e);return t},Viva.Graph.View._webglUtil=new Viva.Graph.View.WebglUtils,Viva.Graph.View.webglLine=function(e){return{color:Viva.Graph.View._webglUtil.parseColor(e)}},Viva.Graph.View.webglSquare=function(e,t){return{size:"number"==typeof e?e:10,color:Viva.Graph.View._webglUtil.parseColor(t)}},Viva.Graph.View.webglImage=function(e,t){return{_texture:0,_offset:0,size:"number"==typeof e?e:32,src:t}},Viva.Graph.View.webglNodeProgram=function(){var e,t,n,r,i,o,a,u,s,f=4,c=3*Float32Array.BYTES_PER_ELEMENT+Uint32Array.BYTES_PER_ELEMENT,l=["precision mediump float;","varying vec4 color;","void main(void) {"," gl_FragColor = color;","}"].join("\n"),d=["attribute vec3 a_vertexPos;","attribute vec4 a_color;","uniform vec2 u_screenSize;","uniform mat4 u_transform;","varying vec4 color;","void main(void) {"," gl_Position = u_transform * vec4(a_vertexPos.xy/u_screenSize, 0, 1);"," gl_PointSize = a_vertexPos.z * u_transform[0][0];"," color = a_color.abgr;","}"].join("\n"),h=new ArrayBuffer(16*c),p=new Float32Array(h),v=new Uint32Array(h),m=0,y=function(){if((m+1)*c>=h.byteLength){var e=new ArrayBuffer(2*h.byteLength),t=new Float32Array(e),n=new Uint32Array(e);n.set(v),p=t,v=n,h=e}};return{load:function(o){t=o,i=Viva.Graph.webgl(o),e=i.createProgram(d,l),t.useProgram(e),r=i.getLocations(e,["a_vertexPos","a_color","u_screenSize","u_transform"]),t.enableVertexAttribArray(r.vertexPos),t.enableVertexAttribArray(r.color),n=t.createBuffer()},position:function(e,t){var n=e.id;p[n*f]=t.x,p[n*f+1]=t.y,p[n*f+2]=e.size,v[n*f+3]=e.color},updateTransform:function(e){s=!0,u=e},updateSize:function(e,t){o=e,a=t,s=!0},removeNode:function(e){m>0&&(m-=1),m>e.id&&m>0&&i.copyArrayPart(v,e.id*f,m*f,f)},createNode:function(){y(),m+=1},replaceProperties:function(){},render:function(){t.useProgram(e),t.bindBuffer(t.ARRAY_BUFFER,n),t.bufferData(t.ARRAY_BUFFER,h,t.DYNAMIC_DRAW),s&&(s=!1,t.uniformMatrix4fv(r.transform,!1,u),t.uniform2f(r.screenSize,o,a)),t.vertexAttribPointer(r.vertexPos,3,t.FLOAT,!1,f*Float32Array.BYTES_PER_ELEMENT,0),t.vertexAttribPointer(r.color,4,t.UNSIGNED_BYTE,!0,f*Float32Array.BYTES_PER_ELEMENT,12),t.drawArrays(t.POINTS,0,m)}}},Viva.Graph.View.webglLinkProgram=function(){var e,t,n,r,i,o,a,u,s,f,c=6,l=2*(2*Float32Array.BYTES_PER_ELEMENT+Uint32Array.BYTES_PER_ELEMENT),d=["precision mediump float;","varying vec4 color;","void main(void) {"," gl_FragColor = color;","}"].join("\n"),h=["attribute vec2 a_vertexPos;","attribute vec4 a_color;","uniform vec2 u_screenSize;","uniform mat4 u_transform;","varying vec4 color;","void main(void) {"," gl_Position = u_transform * vec4(a_vertexPos/u_screenSize, 0.0, 1.0);"," color = a_color.abgr;","}"].join("\n"),p=0,v=new ArrayBuffer(16*l),m=new Float32Array(v),y=new Uint32Array(v),g=function(){if((p+1)*l>v.byteLength){var e=new ArrayBuffer(2*v.byteLength),t=new Float32Array(e),n=new Uint32Array(e);n.set(y),m=t,y=n,v=e}};return{load:function(o){t=o,r=Viva.Graph.webgl(o),e=r.createProgram(h,d),t.useProgram(e),i=r.getLocations(e,["a_vertexPos","a_color","u_screenSize","u_transform"]),t.enableVertexAttribArray(i.vertexPos),t.enableVertexAttribArray(i.color),n=t.createBuffer()},position:function(e,t,n){var r=e.id,i=r*c;m[i]=t.x,m[i+1]=t.y,y[i+2]=e.color,m[i+3]=n.x,m[i+4]=n.y,y[i+5]=e.color},createLink:function(e){g(),p+=1,o=e.id},removeLink:function(e){p>0&&(p-=1),p>e.id&&p>0&&r.copyArrayPart(y,e.id*c,p*c,c)},updateTransform:function(e){f=!0,s=e},updateSize:function(e,t){a=e,u=t,f=!0},render:function(){t.useProgram(e),t.bindBuffer(t.ARRAY_BUFFER,n),t.bufferData(t.ARRAY_BUFFER,v,t.DYNAMIC_DRAW),f&&(f=!1,t.uniformMatrix4fv(i.transform,!1,s),t.uniform2f(i.screenSize,a,u)),t.vertexAttribPointer(i.vertexPos,2,t.FLOAT,!1,3*Float32Array.BYTES_PER_ELEMENT,0),t.vertexAttribPointer(i.color,4,t.UNSIGNED_BYTE,!0,3*Float32Array.BYTES_PER_ELEMENT,8),t.drawArrays(t.LINES,0,2*p),o=p-1},bringToFront:function(e){o>e.id&&r.swapArrayPart(m,e.id*c,o*c,c),o>0&&(o-=1)},getFrontLinkId:function(){return o}}},Viva.Graph.View.Texture=function(e){this.canvas=window.document.createElement("canvas"),this.ctx=this.canvas.getContext("2d"),this.isDirty=!1,this.canvas.width=this.canvas.height=e},Viva.Graph.View.webglAtlas=function(e){var t,n,r=Math.sqrt(e||1024)<<0,i=r,o=1,a={},u=0,s=[],f=[],c=function(e){return 0===(e&e-1)},l=function(){var e=new Viva.Graph.View.Texture(r*i);s.push(e)},d=function(t){var n=t/e<<0,i=t%e,o=i/r<<0,a=i%r;return{textureNumber:n,row:o,col:a}},h=function(){n.isDirty=!0,u=0,t=null},p=function(){t&&(window.clearTimeout(t),u+=1,t=null),u>10?h():t=window.setTimeout(h,400)},v=function(e,t){var n=s[e.textureNumber].canvas,r=s[t.textureNumber].ctx,o=t.col*i,a=t.row*i;r.drawImage(n,e.col*i,e.row*i,i,i,o,a,i,i),s[e.textureNumber].isDirty=!0,s[t.textureNumber].isDirty=!0},m=function(e,t,n){var r=d(e),o={offset:e};r.textureNumber>=s.length&&l();var u=s[r.textureNumber];u.ctx.drawImage(t,r.col*i,r.row*i,i,i),f[e]=t.src,a[t.src]=o,u.isDirty=!0,n(o)};if(!c(e))throw"Tiles per texture should be power of two.";return n={isDirty:!1,clearDirty:function(){var e;for(this.isDirty=!1,e=0;s.length>e;++e)s[e].isDirty=!1},remove:function(e){var t=a[e];if(!t)return!1;if(delete a[e],o-=1,o===t.offset)return!0;var n=d(t.offset),r=d(o);v(r,n);var i=a[f[o]];return i.offset=t.offset,f[t.offset]=f[o],p(),!0},getTextures:function(){return s},getCoordinates:function(e){return a[e]},load:function(e,t){if(a.hasOwnProperty(e))t(a[e]);else{var n=new window.Image,r=o;o+=1,n.crossOrigin="anonymous",n.onload=function(){p(),m(r,n,t)},n.src=e}}}},Viva.Graph.View.webglImageNodeProgram=function(){var e,t,n,r,i,o,a,u,s,f,c=18,l=["precision mediump float;","varying vec4 color;","varying vec3 vTextureCoord;","uniform sampler2D u_sampler0;","uniform sampler2D u_sampler1;","uniform sampler2D u_sampler2;","uniform sampler2D u_sampler3;","void main(void) {"," if (vTextureCoord.z == 0.) {"," gl_FragColor = texture2D(u_sampler0, vTextureCoord.xy);"," } else if (vTextureCoord.z == 1.) {"," gl_FragColor = texture2D(u_sampler1, vTextureCoord.xy);"," } else if (vTextureCoord.z == 2.) {"," gl_FragColor = texture2D(u_sampler2, vTextureCoord.xy);"," } else if (vTextureCoord.z == 3.) {"," gl_FragColor = texture2D(u_sampler3, vTextureCoord.xy);"," } else { gl_FragColor = vec4(0, 1, 0, 1); }","}"].join("\n"),d=["attribute vec2 a_vertexPos;","attribute float a_customAttributes;","uniform vec2 u_screenSize;","uniform mat4 u_transform;","uniform float u_tilesPerTexture;","varying vec3 vTextureCoord;","void main(void) {"," gl_Position = u_transform * vec4(a_vertexPos/u_screenSize, 0, 1);","float corner = mod(a_customAttributes, 4.);","float tileIndex = mod(floor(a_customAttributes / 4.), u_tilesPerTexture);","float tilesPerRow = sqrt(u_tilesPerTexture);","float tileSize = 1./tilesPerRow;","float tileColumn = mod(tileIndex, tilesPerRow);","float tileRow = floor(tileIndex/tilesPerRow);","if(corner == 0.0) {"," vTextureCoord.xy = vec2(0, 1);","} else if(corner == 1.0) {"," vTextureCoord.xy = vec2(1, 1);","} else if(corner == 2.0) {"," vTextureCoord.xy = vec2(0, 0);","} else {"," vTextureCoord.xy = vec2(1, 0);","}","vTextureCoord *= tileSize;","vTextureCoord.x += tileColumn * tileSize;","vTextureCoord.y += tileRow * tileSize;","vTextureCoord.z = floor(floor(a_customAttributes / 4.)/u_tilesPerTexture);","}"].join("\n"),h=1024,p=0,v=new Float32Array(64),m=function(e,t){e.nativeObject&&n.deleteTexture(e.nativeObject);var r=n.createTexture();n.activeTexture(n["TEXTURE"+t]),n.bindTexture(n.TEXTURE_2D,r),n.texImage2D(n.TEXTURE_2D,0,n.RGBA,n.RGBA,n.UNSIGNED_BYTE,e.canvas),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_MAG_FILTER,n.LINEAR),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_MIN_FILTER,n.LINEAR_MIPMAP_NEAREST),n.generateMipmap(n.TEXTURE_2D),n.uniform1i(o["sampler"+t],t),e.nativeObject=r},y=function(){if(e.isDirty){var t,n=e.getTextures();for(t=0;n.length>t;++t)(n[t].isDirty||!n[t].nativeObject)&&m(n[t],t);e.clearDirty()}};return{load:function(a){n=a,i=Viva.Graph.webgl(a),e=new Viva.Graph.View.webglAtlas(h),t=i.createProgram(d,l),n.useProgram(t),o=i.getLocations(t,["a_vertexPos","a_customAttributes","u_screenSize","u_transform","u_sampler0","u_sampler1","u_sampler2","u_sampler3","u_tilesPerTexture"]),n.uniform1f(o.tilesPerTexture,h),n.enableVertexAttribArray(o.vertexPos),n.enableVertexAttribArray(o.customAttributes),r=n.createBuffer()},position:function(e,t){var n=e.id*c;v[n]=t.x-e.size,v[n+1]=t.y-e.size,v[n+2]=4*e._offset,v[n+3]=t.x+e.size,v[n+4]=t.y-e.size,v[n+5]=4*e._offset+1,v[n+6]=t.x-e.size,v[n+7]=t.y+e.size,v[n+8]=4*e._offset+2,v[n+9]=t.x-e.size,v[n+10]=t.y+e.size,v[n+11]=4*e._offset+2,v[n+12]=t.x+e.size,v[n+13]=t.y-e.size,v[n+14]=4*e._offset+1,v[n+15]=t.x+e.size,v[n+16]=t.y+e.size,v[n+17]=4*e._offset+3},createNode:function(t){v=i.extendArray(v,p,c),p+=1;var n=e.getCoordinates(t.src);n?t._offset=n.offset:(t._offset=0,e.load(t.src,function(e){t._offset=e.offset}))},removeNode:function(t){p>0&&(p-=1),p>t.id&&p>0&&(t.src&&e.remove(t.src),i.copyArrayPart(v,t.id*c,p*c,c))},replaceProperties:function(e,t){t._offset=e._offset},updateTransform:function(e){f=!0,s=e},updateSize:function(e,t){a=e,u=t,f=!0},render:function(){n.useProgram(t),n.bindBuffer(n.ARRAY_BUFFER,r),n.bufferData(n.ARRAY_BUFFER,v,n.DYNAMIC_DRAW),f&&(f=!1,n.uniformMatrix4fv(o.transform,!1,s),n.uniform2f(o.screenSize,a,u)),n.vertexAttribPointer(o.vertexPos,2,n.FLOAT,!1,3*Float32Array.BYTES_PER_ELEMENT,0),n.vertexAttribPointer(o.customAttributes,1,n.FLOAT,!1,3*Float32Array.BYTES_PER_ELEMENT,8),y(),n.drawArrays(n.TRIANGLES,0,6*p)}}},Viva.Graph.View=Viva.Graph.View||{},Viva.Graph.View.webglGraphics=function(e){e=Viva.lazyExtend(e,{enableBlending:!0,preserveDrawingBuffer:!1,clearColor:!1,clearColorValue:{r:1,g:1,b:1,a:1}});var t,n,r,i,o,a,u,s,f,c=0,l=0,d=[],h=[],p=Viva.Graph.View.webglLinkProgram(),v=Viva.Graph.View.webglNodeProgram(),m=function(){return Viva.Graph.View.webglSquare()},y=function(){return Viva.Graph.View.webglLine(3014898687)},g=function(){p.updateTransform(a),v.updateTransform(a)},x=function(){a=[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]},w=function(){t&&n&&(i=n.width=Math.max(t.offsetWidth,1),o=n.height=Math.max(t.offsetHeight,1),r&&r.viewport(0,0,i,o),p&&p.updateSize(i/2,o/2),v&&v.updateSize(i/2,o/2))},V=function(e){var t=c++,n=m(e);return n.id=t,v.createNode(n),d[t]=e,n},b=function(e){var t=l++,n=y(e);return n.id=t,p.createLink(n),h[t]=e,n},E=function(e){e.fire("rescaled")},G={node:function(e){return e&&"function"!=typeof e?V(e):(m=e,this)},link:function(e){return e&&"function"!=typeof e?b(e):(y=e,this)},placeNode:function(e){return u=e,this},placeLink:function(e){return s=e,this},inputManager:Viva.Input.webglInputManager,beginRender:function(){},endRender:function(){l>0&&p.render(),c>0&&v.render()},bringLinkToFront:function(e){var t,n,r=p.getFrontLinkId();p.bringToFront(e),r>e.id&&(t=e.id,n=h[r],h[r]=h[t],h[r].ui.id=r,h[t]=n,h[t].ui.id=t)},graphCenterChanged:function(){w()},initLink:function(){},initNode:function(){},translateRel:function(e,t){a[12]+=2*a[0]*e/i/a[0],a[13]-=2*a[5]*t/o/a[5],g()},scale:function(e,t){var n=2*t.x/i-1,r=1-2*t.y/o;return n-=a[12],r-=a[13],a[12]+=n*(1-e),a[13]+=r*(1-e),a[0]*=e,a[5]*=e,g(),E(this),a[0]},resetScale:function(){return x(),r&&(w(),g()),this},init:function(a){var u={};if(e.preserveDrawingBuffer&&(u.preserveDrawingBuffer=!0),t=a,n=window.document.createElement("canvas"),w(),x(),t.appendChild(n),r=n.getContext("experimental-webgl",u),!r){var s="Could not initialize WebGL. Seems like the browser doesn't support it.";throw window.alert(s),s}if(e.enableBlending&&(r.blendFunc(r.SRC_ALPHA,r.ONE_MINUS_SRC_ALPHA),r.enable(r.BLEND)),e.clearColor){var c=e.clearColorValue;r.clearColor(c.r,c.g,c.b,c.a),this.beginRender=function(){r.clear(r.COLOR_BUFFER_BIT)}}p.load(r),p.updateSize(i/2,o/2),v.load(r),v.updateSize(i/2,o/2),g(),"function"==typeof f&&f(n)},release:function(e){n&&e&&e.removeChild(n)},isSupported:function(){var e=window.document.createElement("canvas"),t=e&&e.getContext&&e.getContext("experimental-webgl");return t},releaseLink:function(e){l>0&&(l-=1),p.removeLink(e);var t=e.id;if(l>t){if(0===l||l===t)return;h[t]=h[l],h[t].ui.id=t}},releaseNode:function(e){if(c>0&&(c-=1),v.removeNode(e),c>e.id){var t=e.id;if(0===c||c===t)return;var n=d[c],r=d[t];d[t]=n,d[t].ui.id=t,v.replaceProperties(r.ui,n.ui)}},updateNodePosition:function(e,t){t.y=-t.y,u&&u(e,t),v.position(e,t)},updateLinkPosition:function(e,t,n){t.y=-t.y,n.y=-n.y,s&&s(e,t,n),p.position(e,t,n)},getGraphicsRoot:function(e){return"function"==typeof e&&(n?e(n):f=e),n},setNodeProgram:function(e){if(!r&&e)v=e;else if(e)throw"Not implemented. Cannot swap shader on the fly... yet."},setLinkProgram:function(e){if(!r&&e)p=e;else if(e)throw"Not implemented. Cannot swap shader on the fly... yet."},getGraphCoordinates:function(e){return e.x=2*e.x/i-1,e.y=1-2*e.y/o,e.x=(e.x-a[12])/a[0],e.y=(e.y-a[13])/a[5],e.x*=i/2,e.y*=-o/2,e}};return Viva.Graph.Utils.events(G).extend(),G},Viva.Graph.webglInputEvents=function(e,t){if(e.webglInputEvents)return e.webglInputEvents;var n,r,i=function(e,t,n){if(e.ui&&e.ui.size){var r=e.position,i=e.ui.size;return t>r.x-i&&r.x+i>t&&n>r.y-i&&r.y+i>n}return!0},o=null,a=Viva.Graph.spatialIndex(t,i),u=[],s=[],f=[],c=[],l=[],d=[],h=[],p=Viva.Graph.Utils.events(window.document),v=function(e){e.stopPropagation?e.stopPropagation():e.cancelBubble=!0},m=function(e){return v(e),!1},y=function(e,t){var n,r;for(n=0;e.length>n;n+=1)if(r=e[n].apply(void 0,t))return!0},g=function(t){var i={x:0,y:0},g=null,x=+new Date,w=function(e){y(l,[g,e]),i.x=e.clientX,i.y=e.clientY},V=function(){p.stop("mousemove",w),p.stop("mouseup",V)},b=function(){r=t.getBoundingClientRect()};window.addEventListener("resize",b),b(),t.addEventListener("mousemove",function(t){if(!o){var n,f=!1;i.x=t.clientX-r.left,i.y=t.clientY-r.top,e.getGraphCoordinates(i),n=a.getNodeAt(i.x,i.y),n&&g!==n?(g=n,f=f||y(u,[g])):null===n&&g!==n&&(f=f||y(s,[g]),g=null),f&&v(t)}}),t.addEventListener("mousedown",function(t){var o,u=!1;i.x=t.clientX-r.left,i.y=t.clientY-r.top,e.getGraphCoordinates(i),o=[a.getNodeAt(i.x,i.y),t],o[0]?(u=y(f,o),p.on("mousemove",w),p.on("mouseup",V),n=window.document.onselectstart,window.document.onselectstart=m,g=o[0]):g=null,u&&v(t)}),t.addEventListener("mouseup",function(t){var o,u=+new Date;i.x=t.clientX-r.left,i.y=t.clientY-r.top,e.getGraphCoordinates(i),o=[a.getNodeAt(i.x,i.y),t],o[0]&&(window.document.onselectstart=n,400>u-x&&o[0]===g?y(h,o):y(d,o),x=u,y(c,o)&&v(t))})};return e.getGraphicsRoot(g),e.webglInputEvents={mouseEnter:function(e){return"function"==typeof e&&u.push(e),this},mouseLeave:function(e){return"function"==typeof e&&s.push(e),this},mouseDown:function(e){return"function"==typeof e&&f.push(e),this},mouseUp:function(e){return"function"==typeof e&&c.push(e),this},mouseMove:function(e){return"function"==typeof e&&l.push(e),this},click:function(e){return"function"==typeof e&&d.push(e),this},dblClick:function(e){return"function"==typeof e&&h.push(e),this},mouseCapture:function(e){o=e},releaseMouseCapture:function(){o=null}},e.webglInputEvents},Viva.Input=Viva.Input||{},Viva.Input.webglInputManager=function(e,t){var n=Viva.Graph.webglInputEvents(t,e),r=null,i={},o={x:0,y:0};return n.mouseDown(function(e,t){r=e,o.x=t.clientX,o.y=t.clientY,n.mouseCapture(r);var a=i[e.ui.id];return a&&a.onStart&&a.onStart(t,o),!0}).mouseUp(function(e){n.releaseMouseCapture(r),r=null;var t=i[e.ui.id];return t&&t.onStop&&t.onStop(),!0}).mouseMove(function(e,t){if(r){var n=i[r.ui.id];return n&&n.onDrag&&n.onDrag(t,{x:t.clientX-o.x,y:t.clientY-o.y}),o.x=t.clientX,o.y=t.clientY,!0}}),{bindDragNDrop:function(e,t){i[e.ui.id]=t}}}; \ No newline at end of file +!function(e){var n;"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):("undefined"!=typeof window?n=window:"undefined"!=typeof global?n=global:"undefined"!=typeof self&&(n=self),n.Viva=e())}(function(){return function r(o,i,a){function u(t,e){if(!i[t]){if(!o[t]){var n="function"==typeof require&&require;if(!e&&n)return n(t,!0);if(s)return s(t,!0);n=new Error("Cannot find module '"+t+"'");throw n.code="MODULE_NOT_FOUND",n}n=i[t]={exports:{}};o[t][0].call(n.exports,function(e){var n=o[t][1][e];return u(n||e)},n,n.exports,r,o,i,a)}return i[t].exports}for(var s="function"==typeof require&&require,e=0;e= 0");var n,t=h();for(n=0;n>>19))+(e<<5)&4294967295)^e<<9))+(e<<3)&4294967295)^e>>>16),(268435455&(this.seed=e))/268435456}n.exports=r,n.exports.random=r,n.exports.randomIterator=function(o,e){var i=e||r();if("function"==typeof i.next)return{forEach:function(e){var n,t,r;for(n=o.length-1;0r&&(r=i.pos.x),i.pos.yo&&(o=i.pos.y)}s.x1=n,s.x2=r,s.y1=t,s.y2=o},reset:function(){s.x1=s.y1=0,s.x2=s.y2=0},getBestNewPosition:function(e){var n=s,t=0,r=0;if(e.length){for(var o=0;o>>19))+374761393+(t<<5)&4294967295)+3550635116^t<<9))+4251993797+(t<<3)&4294967295)^t>>>16)))/268435456}var t="number"==typeof e?e:+new Date;return{next:function(e){return Math.floor(n()*e)},nextDouble:n}}n.exports={random:r,randomIterator:function(o,e){var i=e||r();if("function"==typeof i.next)return{forEach:function(e){for(var n,t,r=o.length-1;0e.x2&&(e.x2=n.x),n.ye.y2&&(e.y2=n.y)}function t(){0!==e.getNodesCount()&&(u.x1=Number.MAX_VALUE,u.y1=Number.MAX_VALUE,u.x2=Number.MIN_VALUE,u.y2=Number.MIN_VALUE,e.forEachNode(r))}function o(e){s[e.id]=e}function i(e){for(var n=0;ne.id&&(n=e.id,e=x[t],x[t]=x[n],x[t].id=t,x[n]=e,x[n].id=n)},graphCenterChanged:function(e,n){m[12]=2*e/c-1,m[13]=1-2*n/d,o()},addLink:function(e,n){var t=v++,r=P(e);return r.id=t,r.pos=n,E.createLink(r),x[t]=r,b[e.id]=r},addNode:function(e,n){var t=g++,r=N(e);return r.id=t,r.position=n,r.node=e,L.createNode(r),y[t]=r,w[e.id]=r},translateRel:function(e,n){m[12]+=2*m[0]*e/c/m[0],m[13]-=2*m[5]*n/d/m[5],o()},scale:function(e,n){var t=2*n.x/c-1,n=1-2*n.y/d;return t-=m[12],n-=m[13],m[12]+=t*(1-e),m[13]+=n*(1-e),m[0]*=e,m[5]*=e,o(),this.fire("rescaled"),m[0]},resetScale:function(){return i(),f&&(a(),o()),this},updateSize:a,init:function(e){var n={};if(r.preserveDrawingBuffer&&(n.preserveDrawingBuffer=!0),u=e,a(),i(),u.appendChild(s),!(f=s.getContext("experimental-webgl",n))){var t="Could not initialize WebGL. Seems like the browser doesn't support it.";throw window.alert(t),t}r.enableBlending&&(f.blendFunc(f.SRC_ALPHA,f.ONE_MINUS_SRC_ALPHA),f.enable(f.BLEND)),r.clearColor&&(t=r.clearColorValue,f.clearColor(t.r,t.g,t.b,t.a),this.beginRender=function(){f.clear(f.COLOR_BUFFER_BIT)}),E.load(f),E.updateSize(c/2,d/2),L.load(f),L.updateSize(c/2,d/2),o(),"function"==typeof h&&h(s)},release:function(e){s&&e&&e.removeChild(s)},isSupported:function(){var e=window.document.createElement("canvas");return e&&e.getContext&&e.getContext("experimental-webgl")},releaseLink:function(e){0e.length){t=new Float32Array(e.length*t*2);return t.set(e),t}return e},copyArrayPart:o,swapArrayPart:i,getLocations:function(e,n){for(var t={},r=0;r=f.length&&function(){var e=new p(a*u);f.push(e)}();var i=f[r.textureNumber];i.ctx.drawImage(n,r.col*u,r.row*u,u,u),c[e]=n.src,s[n.src]=o,i.isDirty=!0,t(o)}(r,t,n)},t.src=e)}}};return r;function d(e){var n=e%t;return{textureNumber:e/t<<0,row:n/a<<0,col:n%a}}function i(){r.isDirty=!0,n=0,e=null}function l(){e&&(window.clearTimeout(e),n+=1,e=null),10g.byteLength&&(n=new ArrayBuffer(2*g.byteLength),t=new Float32Array(n),(r=new Uint32Array(n)).set(m),v=t,m=r,g=n),h+=1,a=e.id},removeLink:function(e){0e.id&&o.swapArrayPart(v,6*e.id,6*a,6),0=p.byteLength&&(e=new ArrayBuffer(2*p.byteLength),n=new Float32Array(e),(t=new Uint32Array(e)).set(g),h=n,g=t,p=e)})(),v+=1},replaceProperties:function(){},render:function(){t.useProgram(n),t.bindBuffer(t.ARRAY_BUFFER,r),t.bufferData(t.ARRAY_BUFFER,p,t.DYNAMIC_DRAW),f&&(f=!1,t.uniformMatrix4fv(o.transform,!1,s),t.uniform2f(o.screenSize,a,u));t.vertexAttribPointer(o.vertexPos,3,t.FLOAT,!1,4*Float32Array.BYTES_PER_ELEMENT,0),t.vertexAttribPointer(o.color,4,t.UNSIGNED_BYTE,!0,4*Float32Array.BYTES_PER_ELEMENT,12),t.drawArrays(t.POINTS,0,v)}}}},{"./webgl.js":57}],65:[function(e,n,t){var r=e("./parseColor.js");n.exports=function(e,n){return{size:"number"==typeof e?e:10,color:r(n)}}},{"./parseColor.js":55}],66:[function(e,n,t){n.exports="0.10.1"},{}]},{},[1])(1)}); \ No newline at end of file diff --git a/docs/graph_intro.md b/docs/graph_intro.md index 89b0d3f..a93e158 100644 --- a/docs/graph_intro.md +++ b/docs/graph_intro.md @@ -107,4 +107,4 @@ g.forEachLinkedNode('hello', function(linkedNode, link){ }); ``` -Stop now and play with [interactive fiddle](http://jsfiddle.net/anvaka/JA76K/2/) for this article. \ No newline at end of file +Stop now and play with [interactive fiddle](http://jsfiddle.net/JA76K/182/) for this article. diff --git a/docs/upgrade_guide.md b/docs/upgrade_guide.md new file mode 100644 index 0000000..193e54a --- /dev/null +++ b/docs/upgrade_guide.md @@ -0,0 +1,163 @@ +Migrating from 0.6.x to 0.7.x +----------------------------- +Main reason for breaking changes is shift from monolithic architecture to +modular design. Now VivaGraph consists of small modules from `ngraph` family. +`ngraph` modules are usually very well documented and tested. + +## Layout API changes + +`Viva.graph.Layout.forceDirected` is replaced with [ngraph.forcelayout](https://github.com/anvaka/ngraph.forcelayout). +This module is faster than older one, and has better test coverage. + +### layout.getLinkPosition() +_v.0.6.*_ +``` js +var linkPosition = layout.getLinkPosition(link); +``` + +_v.0.7.*_ +``` js +var linkPosition = layout.getLinkPosition(link.id); +``` + +### layout.setNodePosition() + +_v.0.6.*_ +``` js +layout.setNodePosition(node, x, y); +``` + +_v.0.7.*_ +``` js +layout.setNodePosition(node.id, x, y); +``` + +### layout settings +Force based layout settings can be now accessed from `layout.simulator`: + +* `layout.drag()` is now known as `simulator.dragCoeff()` +* `layout.springCoeff()` -> `simulator.springCoeff()` +* `layout.springLength()` -> `simulator.springLength()` +* `layout.gravity()` -> `simulator.gravity()` +* `layout.theta()` -> `simulator.theta()` + +## Generator changes + +The module is replaced with [ngraph.generators](https://github.com/anvaka/ngraph.generators) +which contains all original graphs + new graphs. + +_v.0.6.*_ +``` js +Viva.Graph.generator().randomNoLinks(42); +``` + +_v.0.7.*_ +``` js +Viva.Graph.generator().noLinks(42); +``` + +## Deprecated API + +* `Viva.Graph.Point2d` is removed. Use plain {x: 42, y: 42} object instead. +* `Viva.Graph.graph.addEventListener` is replaced with `on()` method. +* `Viva.Graph.View.cssGraphcis` is deprecated +* `Viva.Graph.View.svgNodeFactory` is deprecated +* `geom.convexHull` is deprecated. Use https://github.com/anvaka/cnvx +instead. +* `Viva.Graph.community` is deprecated. Use https://github.com/anvaka/ngraph.slpa +instead. + +Migrating from 0.5.x to 0.6.x +----------------------------- + +Version `0.5.x` and `0.6.x` are almost identical and do not have any breaking +API changes. Primary reason for version bump was that 0.6 had changed build +system from grunt to gulp. Also v.0.6 had lots of small changes with fixed +typos/documentation. + +Migrating from 0.4.x to 0.5.x +----------------------------- + +Main reason for breaking changes below is that v.0.4 does not allow to render +the same graph by multiple renderers. Please feel free to email me and ask for +help, if you find this migration guide not sufficient. + +## node.position + +`position` attribute is moved out from the node object into layout provider. + +**Why?** Having shared node `position` makes impossible rendering of the same graph by two different layouters. + +_v.0.4.*_ +``` js + // each node object has "position" on it: + graph.forEachNode(function (node) { + var position = node.position; + position.x += 1; // move by one pixel + }); +``` + +_v.0.5.*_ +``` js + // "position" is now part of layouter: + graph.forEachNode(function (node) { + // layout here is instance of Viva.Graph.Layout.forceDirected or Viva.Graph.Layout.constant: + var position = layout.getNodePosition(node.id); + position.x += 1; + }); +``` + +To give initial positions to nodes in v.0.5.* simply call: `layout.setNodePosition(node, x, y)`. + +## node.ui + +`ui` attribute is moved out from the node/link objects into graphics provider. + +**Why?** having shared `ui` attribute makes impossible rendering of the same graph by multiple renderers. + +_v.0.4.*_ +``` js + // each node object has "position" on it: + graph.forEachNode(function (node) { + console.log(node.ui); + }); + + // each link object has "position" on it: + graph.forEachLink(function (link) { + console.log(link.ui); + }); +``` + +_v.0.5.*_ +``` js + // "ui" is now part of graphics: + graph.forEachNode(function (node) { + // graphics here can be instance of Viva.Graph.View.svgGraphics or Viva.Graph.View.webglGraphics: + console.dir(graphics.getNodeUI(node.id)); + }); + // "ui" is now part of graphics: + graph.forEachLink(function (link) { + // graphics here can be instance of Viva.Graph.View.svgGraphics or Viva.Graph.View.webglGraphics: + console.dir(graphics.getLinkUI(link.id)); + }); +``` + +## node.isPinned + +`node.isPinned` is no longer used to determine whether node is pinned or not. This responsibility is moved to layouter. + +**Why?** Same reasons as above. Disabling node movement in one renderer should not affect movement of the same node in other renderers. + +_v.0.4.*_ +``` js +// toggle node pinning: +node.data.isPinned = !node.data.isPinned; +``` + +_v.0.5.*_ +``` js +// toggle node pinning: +var wasPinned = layout.isNodePinned(node); +layout.pinNode(node, !wasPinned); +// layout here is instance of Viva.Graph.Layout.forceDirected or Viva.Graph.Layout.constant. +``` diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..e8b3157 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,47 @@ +var gulp = require('gulp'); +var buffer = require('vinyl-buffer'); +var source = require('vinyl-source-stream'); + +var uglify = require('gulp-uglify'); +var rename = require('gulp-rename'); +var del = require('del'); +var run = require('gulp-run'); + +gulp.task('clean', clean); +gulp.task('build', build); +gulp.task('test', test); +gulp.task('release', gulp.series('clean', 'build', test)); +gulp.task('default', watch); + +function watch() { + gulp.watch('src/**/*.js', ['build']); +} + +function clean(cb) { + del(['dist'], cb); +} + +function test(cb) { + new run.Command('npm test').exec('', cb); +} + +function build(cb) { + var bundler = require('browserify')('./src/viva.js', { + standalone: 'Viva' + }); + var bundle = bundler.bundle() + .on('error', showError); + + bundle.pipe(source('vivagraph.js')) + .pipe(buffer()) + .pipe(gulp.dest('./dist/')) + .pipe(rename('vivagraph.min.js')) + .pipe(uglify()) + .pipe(gulp.dest('./dist/')); + + bundle.on('end', cb); + + function showError(err) { + console.log('Failed to browserify', err.message); + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..71d2c09 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,14044 @@ +{ + "name": "vivagraphjs", + "version": "0.12.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "vivagraphjs", + "version": "0.12.0", + "license": "BSD-3-Clause", + "dependencies": { + "gintersect": "0.1.0", + "ngraph.centrality": "0.3.0", + "ngraph.events": "0.0.3", + "ngraph.forcelayout": "0.5.0", + "ngraph.fromjson": "0.1.9", + "ngraph.generators": "0.0.19", + "ngraph.graph": "0.0.14", + "ngraph.merge": "0.0.1", + "ngraph.random": "0.0.1", + "ngraph.tojson": "0.1.4", + "simplesvg": "0.0.10" + }, + "devDependencies": { + "browserify": "^8.0.3", + "del": "^1.1.1", + "gulp": "^4.0.2", + "gulp-rename": "^2.0.0", + "gulp-run": "^1.7.1", + "gulp-uglify": "^3.0.2", + "ngraph.remove-overlaps": "^1.0.0", + "tap": "^0.4.13", + "vinyl-buffer": "^1.0.1", + "vinyl-source-stream": "^2.0.0" + } + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "node_modules/acorn": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-node": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", + "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", + "dev": true, + "dependencies": { + "acorn": "^7.0.0", + "acorn-walk": "^7.0.0", + "xtend": "^4.0.2" + } + }, + "node_modules/acorn-node/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-node/node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/add-event-listener": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/add-event-listener/-/add-event-listener-0.0.1.tgz", + "integrity": "sha1-p2Ip68ZMiu+uIEoWJzovJVq+otA=" + }, + "node_modules/amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true, + "engines": { + "node": ">=0.4.2" + } + }, + "node_modules/ansi-colors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "dev": true, + "dependencies": { + "ansi-wrap": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-gray": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", + "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", + "dev": true, + "dependencies": { + "ansi-wrap": "0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "dependencies": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "node_modules/anymatch/node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/append-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", + "integrity": "sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE=", + "dev": true, + "dependencies": { + "buffer-equal": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/append-buffer/node_modules/buffer-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", + "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, + "node_modules/arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-filter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/arr-filter/-/arr-filter-1.1.2.tgz", + "integrity": "sha1-Q/3d0JHo7xGqTEXZzcGOLf8XEe4=", + "dev": true, + "dependencies": { + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/arr-map/-/arr-map-2.0.2.tgz", + "integrity": "sha1-Onc0X/wc814qkYJWAfnljy4kysQ=", + "dev": true, + "dependencies": { + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-differ": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", + "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", + "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-initial": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", + "integrity": "sha1-L6dLJnOTccOUe9enrcc74zSz15U=", + "dev": true, + "dependencies": { + "array-slice": "^1.0.0", + "is-number": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-initial/node_modules/is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-last": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array-last/-/array-last-1.3.0.tgz", + "integrity": "sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==", + "dev": true, + "dependencies": { + "is-number": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-last/node_modules/is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-slice": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", + "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-sort": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-1.0.0.tgz", + "integrity": "sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==", + "dev": true, + "dependencies": { + "default-compare": "^1.0.0", + "get-value": "^2.0.6", + "kind-of": "^5.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "dependencies": { + "array-uniq": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "dev": true, + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/asn1.js/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/assert": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.3.0.tgz", + "integrity": "sha1-A5OaYiWCqBLMICMgoLmlbJuBWEk=", + "dev": true, + "dependencies": { + "util": "0.10.3" + } + }, + "node_modules/assert/node_modules/inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + }, + "node_modules/assert/node_modules/util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, + "dependencies": { + "inherits": "2.0.1" + } + }, + "node_modules/assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/astw": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/astw/-/astw-2.2.0.tgz", + "integrity": "sha1-e9QXhNMkk5h66yOba04cV6hzuRc=", + "dev": true, + "dependencies": { + "acorn": "^4.0.3" + } + }, + "node_modules/async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "node_modules/async-done": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz", + "integrity": "sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.2", + "process-nextick-args": "^2.0.0", + "stream-exhaust": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", + "dev": true + }, + "node_modules/async-settle": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", + "integrity": "sha1-HQqRS7Aldb7IqPOnTlCA9yssDGs=", + "dev": true, + "dependencies": { + "async-done": "^1.2.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true, + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/bach": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", + "integrity": "sha1-Szzpa/JxNPeaG0FKUcFONMO9mIA=", + "dev": true, + "dependencies": { + "arr-filter": "^1.1.1", + "arr-flatten": "^1.0.1", + "arr-map": "^2.0.0", + "array-each": "^1.0.0", + "array-initial": "^1.0.0", + "array-last": "^1.1.1", + "async-done": "^1.2.2", + "async-settle": "^1.0.0", + "now-and-later": "^2.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "node_modules/base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "dependencies": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/Base64": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/Base64/-/Base64-0.2.1.tgz", + "integrity": "sha1-ujpCMHCOGGcFBl5mur3Uw1z2ACg=", + "dev": true + }, + "node_modules/base64-js": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz", + "integrity": "sha1-EQHpVE9KdrG8OybUUsqW16NeeXg=", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/beeper": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.1.tgz", + "integrity": "sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dev": true, + "optional": true, + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", + "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", + "dev": true, + "dependencies": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/bl/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/bl/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/bl/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true + }, + "node_modules/browser-pack": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/browser-pack/-/browser-pack-3.2.0.tgz", + "integrity": "sha512-BHla5EbbxjNyLMFUMamVjeTY+q1QwHbrYNXlWOkw71QcBqAQF7maJyNh3OI/V0d5YyNdMYD6tiPhJB9ukBo99Q==", + "dev": true, + "dependencies": { + "combine-source-map": "~0.3.0", + "concat-stream": "~1.4.1", + "defined": "~0.0.0", + "JSONStream": "~0.8.4", + "through2": "~0.5.1", + "umd": "^2.1.0" + }, + "bin": { + "browser-pack": "bin/cmd.js" + } + }, + "node_modules/browser-pack/node_modules/readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/browser-pack/node_modules/through2": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.5.1.tgz", + "integrity": "sha1-390BLrnHAOIyP9M084rGIqs3Lac=", + "dev": true, + "dependencies": { + "readable-stream": "~1.0.17", + "xtend": "~3.0.0" + } + }, + "node_modules/browser-resolve": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", + "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", + "dev": true, + "dependencies": { + "resolve": "1.1.7" + } + }, + "node_modules/browser-resolve/node_modules/resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + }, + "node_modules/browserify": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/browserify/-/browserify-8.1.3.tgz", + "integrity": "sha512-0KKOkCQXCvP64qm73CtESE92pVnB5MkjA2W/Tmf+S4uPHVY4/SeCwO7wdr4mkke5ySoVyMqsOcMa5zo6uh9c9A==", + "dev": true, + "dependencies": { + "assert": "~1.3.0", + "browser-pack": "^3.2.0", + "browser-resolve": "^1.3.0", + "browserify-zlib": "~0.1.2", + "buffer": "^3.0.0", + "builtins": "~0.0.3", + "commondir": "0.0.1", + "concat-stream": "~1.4.1", + "console-browserify": "^1.1.0", + "constants-browserify": "~0.0.1", + "crypto-browserify": "^3.0.0", + "deep-equal": "~0.2.1", + "defined": "~0.0.0", + "deps-sort": "^1.3.5", + "domain-browser": "~1.1.0", + "duplexer2": "~0.0.2", + "events": "~1.0.0", + "glob": "^4.0.5", + "http-browserify": "^1.4.0", + "https-browserify": "~0.0.0", + "inherits": "~2.0.1", + "insert-module-globals": "^6.2.0", + "isarray": "0.0.1", + "JSONStream": "~0.8.3", + "labeled-stream-splicer": "^1.0.0", + "module-deps": "^3.6.3", + "os-browserify": "~0.1.1", + "parents": "^1.0.1", + "path-browserify": "~0.0.0", + "process": "^0.10.0", + "punycode": "~1.2.3", + "querystring-es3": "~0.2.0", + "readable-stream": "^1.0.33-1", + "resolve": "~0.7.1", + "shallow-copy": "0.0.1", + "shasum": "^1.0.0", + "shell-quote": "~0.0.1", + "stream-browserify": "^1.0.0", + "string_decoder": "~0.10.0", + "subarg": "^1.0.0", + "syntax-error": "^1.1.1", + "through2": "^1.0.0", + "timers-browserify": "^1.0.1", + "tty-browserify": "~0.0.0", + "umd": "~2.1.0", + "url": "~0.10.1", + "util": "~0.10.1", + "vm-browserify": "~0.0.1", + "xtend": "^3.0.0" + }, + "bin": { + "browserify": "bin/cmd.js" + } + }, + "node_modules/browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "dependencies": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "dependencies": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "node_modules/browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/browserify-rsa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", + "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", + "dev": true, + "dependencies": { + "bn.js": "^5.0.0", + "randombytes": "^2.0.1" + } + }, + "node_modules/browserify-sign": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.2.tgz", + "integrity": "sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg==", + "dev": true, + "dependencies": { + "bn.js": "^5.2.1", + "browserify-rsa": "^4.1.0", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.4", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.6", + "readable-stream": "^3.6.2", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/browserify-sign/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/browserify-sign/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/browserify-zlib": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz", + "integrity": "sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0=", + "dev": true, + "dependencies": { + "pako": "~0.2.0" + } + }, + "node_modules/buffer": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-3.6.2.tgz", + "integrity": "sha512-c3M77NkHJxS0zx/ErxXhDLr1v3y2MDXPeTJPvLNOaIYJ4ymHBUFQ9EXzt9HYuqAJllMoNb/EZ8hIiulnQFAUuQ==", + "dev": true, + "dependencies": { + "base64-js": "0.0.8", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "node_modules/buffer-equal": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.2.tgz", + "integrity": "sha1-7Lt5D1aNQAmKYkK1SAXHWAXrk48=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "node_modules/buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", + "dev": true + }, + "node_modules/buffer/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "node_modules/builtins": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-0.0.7.tgz", + "integrity": "sha1-NVIZzWzxjb58Acx/0tznZc/cVJo=", + "dev": true + }, + "node_modules/bunker": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/bunker/-/bunker-0.1.2.tgz", + "integrity": "sha512-YnahkcXBNT522S46k5LUA9P18lzvgkunbMl0qIJQ8oeRMQ+dAg3YI3k32q5TnO+AAUErFHO6R768To6jslgYmQ==", + "dev": true, + "dependencies": { + "burrito": ">=0.2.5 <0.3" + }, + "engines": { + "node": "*" + } + }, + "node_modules/burrito": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/burrito/-/burrito-0.2.12.tgz", + "integrity": "sha512-ZhhT5iVTAgzQ+s8rily7m45Swxe/cU3dVCHTzqmHVWD/cc0Ds3W4Q4MExbkevY+fm0Me3lEwpehIy6TH7p+ehw==", + "dev": true, + "dependencies": { + "traverse": "~0.5.1", + "uglify-js": "~1.1.1" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/burrito/node_modules/traverse": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.5.2.tgz", + "integrity": "sha1-4gPFjV9/DjfbbnTArLkpuwm2HYU=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/burrito/node_modules/uglify-js": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-1.1.1.tgz", + "integrity": "sha512-YYY9Dle1leC+btgrHnAR05eq0aRdcPJsXlYYD+SYw2lqc5HFuFNHg3wWEW4SNE0iXXEUl0fz43gTQ3r1YK76rg==", + "dev": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": "*" + } + }, + "node_modules/cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "dependencies": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/charm": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/charm/-/charm-0.1.2.tgz", + "integrity": "sha1-BsIe7RobBq62dVPNxT4jJ0usIpY=", + "dev": true + }, + "node_modules/chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "deprecated": "Chokidar 2 will break on node v14+. Upgrade to chokidar 3 with 15x less dependencies.", + "dev": true, + "dependencies": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + }, + "optionalDependencies": { + "fsevents": "^1.2.7" + } + }, + "node_modules/cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "dependencies": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, + "dependencies": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + } + }, + "node_modules/clone": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz", + "integrity": "sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/clone-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", + "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/clone-stats": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", + "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", + "dev": true + }, + "node_modules/cloneable-readable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", + "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "process-nextick-args": "^2.0.0", + "readable-stream": "^2.3.5" + } + }, + "node_modules/cloneable-readable/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "node_modules/cloneable-readable/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/cloneable-readable/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/cloneable-readable/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/collection-map": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-map/-/collection-map-1.0.0.tgz", + "integrity": "sha1-rqDwb40mx4DCt1SUOFVEsiVa8Yw=", + "dev": true, + "dependencies": { + "arr-map": "^2.0.2", + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "dependencies": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true, + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/combine-source-map": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.3.0.tgz", + "integrity": "sha1-2edPWT2c1DgHMSy12EbUUe+qnrc=", + "dev": true, + "dependencies": { + "convert-source-map": "~0.3.0", + "inline-source-map": "~0.3.0", + "source-map": "~0.1.31" + } + }, + "node_modules/commondir": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-0.0.1.tgz", + "integrity": "sha1-ifAP3NUbUZxXhzP+xWPmptp/W+I=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/concat-stream": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.4.11.tgz", + "integrity": "sha512-X3JMh8+4je3U1cQpG87+f9lXHDrqcb2MVLg9L7o8b1UZ0DzhRrUpdn65ttzu10PpJPPI3MQNkis+oha6TSA9Mw==", + "dev": true, + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "inherits": "~2.0.1", + "readable-stream": "~1.1.9", + "typedarray": "~0.0.5" + } + }, + "node_modules/console-browserify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", + "dev": true + }, + "node_modules/constants-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-0.0.1.tgz", + "integrity": "sha1-kld9tSe6bEzwpFaNhLwDH0QeIfI=", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-0.3.5.tgz", + "integrity": "sha1-8dgClQr33SYxof6+BZZVDIarMZA=", + "dev": true + }, + "node_modules/copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/copy-props": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.5.tgz", + "integrity": "sha512-XBlx8HSqrT0ObQwmSzM7WE5k8FxTV75h1DX1Z3n6NhQ/UYYAvInWYmG06vFt7hQZArE2fuO62aihiWIVQwh1sw==", + "dev": true, + "dependencies": { + "each-props": "^1.3.2", + "is-plain-object": "^5.0.0" + } + }, + "node_modules/copy-props/node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "node_modules/create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + } + }, + "node_modules/create-ecdh/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "node_modules/crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "dependencies": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + }, + "engines": { + "node": "*" + } + }, + "node_modules/d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dev": true, + "dependencies": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "node_modules/dateformat": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", + "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/deep-equal": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-0.2.2.tgz", + "integrity": "sha1-hLdFiW80xoTpjyzg5Cq69Du6AX0=", + "dev": true + }, + "node_modules/deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "node_modules/default-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", + "integrity": "sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==", + "dev": true, + "dependencies": { + "kind-of": "^5.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-resolution": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz", + "integrity": "sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ=", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "dependencies": { + "object-keys": "^1.0.12" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/defined": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-0.0.0.tgz", + "integrity": "sha1-817qfXBekzuvE7LwOz+D2SFAOz4=", + "dev": true + }, + "node_modules/del": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/del/-/del-1.2.1.tgz", + "integrity": "sha1-rtblvNfLcyXfNPVjEl+iZbLBoBQ=", + "dev": true, + "dependencies": { + "each-async": "^1.0.0", + "globby": "^2.0.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "object-assign": "^3.0.0", + "rimraf": "^2.2.8" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deps-sort": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/deps-sort/-/deps-sort-1.3.9.tgz", + "integrity": "sha1-Kd//U+F7Nq7K51MK27v2IsLtGnE=", + "dev": true, + "dependencies": { + "JSONStream": "^1.0.3", + "shasum": "^1.0.0", + "subarg": "^1.0.0", + "through2": "^1.0.0" + }, + "bin": { + "deps-sort": "bin/cmd.js" + } + }, + "node_modules/deps-sort/node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "dev": true, + "engines": [ + "node >= 0.2.0" + ] + }, + "node_modules/deps-sort/node_modules/JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "dependencies": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + }, + "bin": { + "JSONStream": "bin.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/des.js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", + "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detective": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/detective/-/detective-4.7.1.tgz", + "integrity": "sha512-H6PmeeUcZloWtdt4DAkFyzFL94arpHr3NOwwmVILFiy+9Qd4JTxxXrzfyGk/lmct2qVGBwTSwSXagqu2BxmWig==", + "dev": true, + "dependencies": { + "acorn": "^5.2.1", + "defined": "^1.0.0" + } + }, + "node_modules/detective/node_modules/acorn": { + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detective/node_modules/defined": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", + "dev": true + }, + "node_modules/diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "node_modules/diffie-hellman/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/difflet": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/difflet/-/difflet-0.2.6.tgz", + "integrity": "sha1-qyOzH1ZJtvqo49KsvTNEZzZcpvo=", + "dev": true, + "dependencies": { + "charm": "0.1.x", + "deep-is": "0.1.x", + "traverse": "0.6.x" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/domain-browser": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.1.7.tgz", + "integrity": "sha1-hnqksJP6oF8d4IwG9NeyH9+GmLw=", + "dev": true, + "engines": { + "node": ">=0.4", + "npm": ">=1.2" + } + }, + "node_modules/duplexer2": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", + "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", + "dev": true, + "dependencies": { + "readable-stream": "~1.1.9" + } + }, + "node_modules/duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "node_modules/duplexify/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "node_modules/duplexify/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/duplexify/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/duplexify/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/each-async": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/each-async/-/each-async-1.1.1.tgz", + "integrity": "sha1-3uUim98KtrogEqOV4bhpq/iBNHM=", + "dev": true, + "dependencies": { + "onetime": "^1.0.0", + "set-immediate-shim": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/each-props": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/each-props/-/each-props-1.3.2.tgz", + "integrity": "sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.1", + "object.defaults": "^1.1.0" + } + }, + "node_modules/elliptic": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", + "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es5-ext": { + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "dev": true, + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "dev": true, + "dependencies": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "node_modules/es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "dev": true, + "dependencies": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "dev": true, + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esniff/node_modules/type": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==", + "dev": true + }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "dev": true, + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "node_modules/events": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/events/-/events-1.0.2.tgz", + "integrity": "sha1-dYSdz+k9EPsFfDAFWv29UdBqjiQ=", + "dev": true, + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "dependencies": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "dependencies": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", + "dev": true, + "dependencies": { + "homedir-polyfill": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ext": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", + "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", + "dev": true, + "dependencies": { + "type": "^2.0.0" + } + }, + "node_modules/ext/node_modules/type": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/type/-/type-2.3.0.tgz", + "integrity": "sha512-rgPIqOdfK/4J9FhiVrZ3cveAjRRo5rsQBAIhnylX874y1DX/kEKSVdLsnuHB6l1KTjHyU01VjiMBHgU2adejyg==", + "dev": true + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "dependencies": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fancy-log": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", + "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", + "dev": true, + "dependencies": { + "ansi-gray": "^0.1.1", + "color-support": "^1.1.3", + "parse-node-version": "^1.0.0", + "time-stamp": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/fast-levenshtein": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.1.4.tgz", + "integrity": "sha1-5qdUzI8V5YmHqpy9J69m/W9OWvk=", + "dev": true + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true, + "optional": true + }, + "node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "dependencies": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", + "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", + "dev": true, + "dependencies": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/fined": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", + "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", + "dev": true, + "dependencies": { + "expand-tilde": "^2.0.2", + "is-plain-object": "^2.0.3", + "object.defaults": "^1.1.0", + "object.pick": "^1.2.0", + "parse-filepath": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/flagged-respawn": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", + "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" + } + }, + "node_modules/flush-write-stream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "node_modules/flush-write-stream/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/flush-write-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/flush-write-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "dev": true, + "dependencies": { + "for-in": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "dependencies": { + "map-cache": "^0.2.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fs-mkdirp-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", + "integrity": "sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes=", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.11", + "through2": "^2.0.3" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/fs-mkdirp-stream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "node_modules/fs-mkdirp-stream/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/fs-mkdirp-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/fs-mkdirp-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/fs-mkdirp-stream/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/fs-mkdirp-stream/node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "deprecated": "fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "node_modules/get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gintersect": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/gintersect/-/gintersect-0.1.0.tgz", + "integrity": "sha1-moy2qAt9bpVawzUVSVsSEmJ7GBY=" + }, + "node_modules/glob": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-4.5.3.tgz", + "integrity": "sha512-I0rTWUKSZKxPSIAIaqhSXTM/DiII6wame+rEC3cFA5Lqmr9YmdL7z6Hj9+bdWtTvoY1Su4/OiMLmb37Y7JzvJQ==", + "dev": true, + "dependencies": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^2.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", + "dev": true, + "dependencies": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + } + }, + "node_modules/glob-parent/node_modules/is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-stream": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", + "integrity": "sha512-uMbLGAP3S2aDOHUDfdoYcdIePUCfysbAd0IAoWVZbeGU/oNQ8asHVSshLDJUPWxfzj8zsCG7/XeHPHTtow0nsw==", + "dev": true, + "dependencies": { + "extend": "^3.0.0", + "glob": "^7.1.1", + "glob-parent": "^3.1.0", + "is-negated-glob": "^1.0.0", + "ordered-read-streams": "^1.0.0", + "pumpify": "^1.3.5", + "readable-stream": "^2.1.5", + "remove-trailing-separator": "^1.0.1", + "to-absolute-glob": "^2.0.0", + "unique-stream": "^2.0.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/glob-stream/node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-stream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "node_modules/glob-stream/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/glob-stream/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/glob-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/glob-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/glob-watcher": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.5.tgz", + "integrity": "sha512-zOZgGGEHPklZNjZQaZ9f41i7F2YwE+tS5ZHrDhbBCk3stwahn5vQxnFmBJZHoYdusR6R1bLSXeGUy/BhctwKzw==", + "dev": true, + "dependencies": { + "anymatch": "^2.0.0", + "async-done": "^1.2.0", + "chokidar": "^2.0.0", + "is-negated-glob": "^1.0.0", + "just-debounce": "^1.0.0", + "normalize-path": "^3.0.0", + "object.defaults": "^1.1.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dev": true, + "dependencies": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", + "dev": true, + "dependencies": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/globby": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-2.1.0.tgz", + "integrity": "sha1-npGSvNM/Srak+JTl5+qLcTITxII=", + "dev": true, + "dependencies": { + "array-union": "^1.0.1", + "async": "^1.2.1", + "glob": "^5.0.3", + "object-assign": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/globby/node_modules/glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "dev": true, + "dependencies": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/glogg": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", + "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", + "dev": true, + "dependencies": { + "sparkles": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", + "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", + "dev": true + }, + "node_modules/gulp": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", + "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==", + "dev": true, + "dependencies": { + "glob-watcher": "^5.0.3", + "gulp-cli": "^2.2.0", + "undertaker": "^1.2.1", + "vinyl-fs": "^3.0.0" + }, + "bin": { + "gulp": "bin/gulp.js" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gulp-cli": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.3.0.tgz", + "integrity": "sha512-zzGBl5fHo0EKSXsHzjspp3y5CONegCm8ErO5Qh0UzFzk2y4tMvzLWhoDokADbarfZRL2pGpRp7yt6gfJX4ph7A==", + "dev": true, + "dependencies": { + "ansi-colors": "^1.0.1", + "archy": "^1.0.0", + "array-sort": "^1.0.0", + "color-support": "^1.1.3", + "concat-stream": "^1.6.0", + "copy-props": "^2.0.1", + "fancy-log": "^1.3.2", + "gulplog": "^1.0.0", + "interpret": "^1.4.0", + "isobject": "^3.0.1", + "liftoff": "^3.1.0", + "matchdep": "^2.0.0", + "mute-stdout": "^1.0.0", + "pretty-hrtime": "^1.0.0", + "replace-homedir": "^1.0.0", + "semver-greatest-satisfied-range": "^1.1.0", + "v8flags": "^3.2.0", + "yargs": "^7.1.0" + }, + "bin": { + "gulp": "bin/gulp.js" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gulp-cli/node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/gulp-cli/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "node_modules/gulp-cli/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/gulp-cli/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/gulp-cli/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/gulp-rename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/gulp-rename/-/gulp-rename-2.0.0.tgz", + "integrity": "sha512-97Vba4KBzbYmR5VBs9mWmK+HwIf5mj+/zioxfZhOKeXtx5ZjBk57KFlePf5nxq9QsTtFl0ejnHE3zTC9MHXqyQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/gulp-run": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/gulp-run/-/gulp-run-1.7.1.tgz", + "integrity": "sha512-4OcXhBE5xpRWmbcKzE0EQWEqpLRAkX3ju6k85qkYLfmnCVGK6nPmu/sbVgDiGg6mjuzsF2b9nHFbImZBZPH3zg==", + "dev": true, + "dependencies": { + "gulp-util": "^3.0.0", + "lodash.defaults": "^4.0.1", + "lodash.template": "^4.0.2", + "vinyl": "^0.4.6" + } + }, + "node_modules/gulp-uglify": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gulp-uglify/-/gulp-uglify-3.0.2.tgz", + "integrity": "sha512-gk1dhB74AkV2kzqPMQBLA3jPoIAPd/nlNzP2XMDSG8XZrqnlCiDGAqC+rZOumzFvB5zOphlFh6yr3lgcAb/OOg==", + "dev": true, + "dependencies": { + "array-each": "^1.0.1", + "extend-shallow": "^3.0.2", + "gulplog": "^1.0.0", + "has-gulplog": "^0.1.0", + "isobject": "^3.0.1", + "make-error-cause": "^1.1.1", + "safe-buffer": "^5.1.2", + "through2": "^2.0.0", + "uglify-js": "^3.0.5", + "vinyl-sourcemaps-apply": "^0.2.0" + } + }, + "node_modules/gulp-uglify/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-uglify/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-uglify/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "node_modules/gulp-uglify/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/gulp-uglify/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/gulp-uglify/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/gulp-uglify/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/gulp-uglify/node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/gulp-util": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", + "integrity": "sha512-q5oWPc12lwSFS9h/4VIjG+1NuNDlJ48ywV2JKItY4Ycc/n1fXJeYPVQsfu5ZrhQi7FGSDBalwUCLar/GyHXKGw==", + "deprecated": "gulp-util is deprecated - replace it, following the guidelines at https://medium.com/gulpjs/gulp-util-ca3b1f9f9ac5", + "dev": true, + "dependencies": { + "array-differ": "^1.0.0", + "array-uniq": "^1.0.2", + "beeper": "^1.0.0", + "chalk": "^1.0.0", + "dateformat": "^2.0.0", + "fancy-log": "^1.1.0", + "gulplog": "^1.0.0", + "has-gulplog": "^0.1.0", + "lodash._reescape": "^3.0.0", + "lodash._reevaluate": "^3.0.0", + "lodash._reinterpolate": "^3.0.0", + "lodash.template": "^3.0.0", + "minimist": "^1.1.0", + "multipipe": "^0.1.2", + "object-assign": "^3.0.0", + "replace-ext": "0.0.1", + "through2": "^2.0.0", + "vinyl": "^0.5.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/gulp-util/node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/gulp-util/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "node_modules/gulp-util/node_modules/lodash.template": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", + "integrity": "sha512-0B4Y53I0OgHUJkt+7RmlDFWKjVAI/YUpWNiL9GQz5ORDr4ttgfQGo+phBWKFLJbBdtOwgMuUkdOHOnPg45jKmQ==", + "dev": true, + "dependencies": { + "lodash._basecopy": "^3.0.0", + "lodash._basetostring": "^3.0.0", + "lodash._basevalues": "^3.0.0", + "lodash._isiterateecall": "^3.0.0", + "lodash._reinterpolate": "^3.0.0", + "lodash.escape": "^3.0.0", + "lodash.keys": "^3.0.0", + "lodash.restparam": "^3.0.0", + "lodash.templatesettings": "^3.0.0" + } + }, + "node_modules/gulp-util/node_modules/lodash.templatesettings": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", + "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=", + "dev": true, + "dependencies": { + "lodash._reinterpolate": "^3.0.0", + "lodash.escape": "^3.0.0" + } + }, + "node_modules/gulp-util/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/gulp-util/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/gulp-util/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/gulp-util/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/gulp-util/node_modules/vinyl": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", + "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", + "dev": true, + "dependencies": { + "clone": "^1.0.0", + "clone-stats": "^0.0.1", + "replace-ext": "0.0.1" + }, + "engines": { + "node": ">= 0.9" + } + }, + "node_modules/gulp-util/node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/gulplog": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", + "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", + "dev": true, + "dependencies": { + "glogg": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-gulplog": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", + "integrity": "sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=", + "dev": true, + "dependencies": { + "sparkles": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "dependencies": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/hash-base/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/hash-base/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dev": true, + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "dependencies": { + "parse-passwd": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/http-browserify": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/http-browserify/-/http-browserify-1.7.0.tgz", + "integrity": "sha1-M3la3nLfiKz7/TZ3PO/tp2RzWyA=", + "dev": true, + "dependencies": { + "Base64": "~0.2.0", + "inherits": "~2.0.1" + } + }, + "node_modules/https-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-0.0.1.tgz", + "integrity": "sha1-P5E2XKvmC3ftDruiS0VOPgnZWoI=", + "dev": true + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", + "dev": true + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/inline-source-map": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.3.1.tgz", + "integrity": "sha1-pSi1FOaJ/OkNswiehw2S9Sestes=", + "dev": true, + "dependencies": { + "source-map": "~0.3.0" + } + }, + "node_modules/inline-source-map/node_modules/source-map": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.3.0.tgz", + "integrity": "sha1-hYb7mloAXltQHiHNGLbyG0V60fk=", + "dev": true, + "dependencies": { + "amdefine": ">=0.0.4" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/insert-module-globals": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/insert-module-globals/-/insert-module-globals-6.6.3.tgz", + "integrity": "sha1-IGOOKaMPntHKLjqCX7wsulJG3fw=", + "dev": true, + "dependencies": { + "combine-source-map": "~0.6.1", + "concat-stream": "~1.4.1", + "is-buffer": "^1.1.0", + "JSONStream": "^1.0.3", + "lexical-scope": "^1.2.0", + "process": "~0.11.0", + "through2": "^1.0.0", + "xtend": "^4.0.0" + }, + "bin": { + "insert-module-globals": "bin/cmd.js" + } + }, + "node_modules/insert-module-globals/node_modules/combine-source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.6.1.tgz", + "integrity": "sha1-m0oJwxYDPXaODxHgKfonMOB5rZY=", + "dev": true, + "dependencies": { + "convert-source-map": "~1.1.0", + "inline-source-map": "~0.5.0", + "lodash.memoize": "~3.0.3", + "source-map": "~0.4.2" + } + }, + "node_modules/insert-module-globals/node_modules/convert-source-map": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", + "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=", + "dev": true + }, + "node_modules/insert-module-globals/node_modules/inline-source-map": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.5.0.tgz", + "integrity": "sha1-Skxd2OT7Xps82mDIIt+tyu5m4K8=", + "dev": true, + "dependencies": { + "source-map": "~0.4.0" + } + }, + "node_modules/insert-module-globals/node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "dev": true, + "engines": [ + "node >= 0.2.0" + ] + }, + "node_modules/insert-module-globals/node_modules/JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "dependencies": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + }, + "bin": { + "JSONStream": "bin.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/insert-module-globals/node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/insert-module-globals/node_modules/source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true, + "dependencies": { + "amdefine": ">=0.0.4" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/insert-module-globals/node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "dev": true, + "dependencies": { + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "dependencies": { + "binary-extensions": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "node_modules/is-core-module": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", + "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-descriptor/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "dependencies": { + "number-is-nan": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-negated-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", + "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-path-in-cwd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", + "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", + "dev": true, + "dependencies": { + "is-path-inside": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "dependencies": { + "path-is-inside": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-relative": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "dev": true, + "dependencies": { + "is-unc-path": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-unc-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "dev": true, + "dependencies": { + "unc-path-regex": "^0.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "node_modules/is-valid-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", + "integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/json-stable-stringify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz", + "integrity": "sha1-YRwj6BTbN1Un34URk9tZ3Sryf0U=", + "dev": true, + "dependencies": { + "jsonify": "~0.0.0" + } + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "node_modules/jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/jsonparse": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-0.0.5.tgz", + "integrity": "sha1-MwVCrT8KZUZlt3jz6y2an6UHrGQ=", + "dev": true, + "engines": [ + "node >= 0.2.0" + ] + }, + "node_modules/JSONStream": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-0.8.4.tgz", + "integrity": "sha1-kWV9/m/4V0gwZhMrRhi2Lo9Ih70=", + "dev": true, + "dependencies": { + "jsonparse": "0.0.5", + "through": ">=2.2.7 <3" + }, + "bin": { + "JSONStream": "index.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/just-debounce": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.1.0.tgz", + "integrity": "sha512-qpcRocdkUmf+UTNBYx5w6dexX5J31AKK1OmPwH630a83DdVVUIngk55RSAiIGpQyoH0dlr872VHfPjnQnK1qDQ==", + "dev": true + }, + "node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/labeled-stream-splicer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-1.0.2.tgz", + "integrity": "sha1-RhUzFTd4SYHo/SZOHzpDTE4N3WU=", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "isarray": "~0.0.1", + "stream-splicer": "^1.1.0" + } + }, + "node_modules/last-run": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/last-run/-/last-run-1.1.1.tgz", + "integrity": "sha1-RblpQsF7HHnHchmCWbqUO+v4yls=", + "dev": true, + "dependencies": { + "default-resolution": "^2.0.0", + "es6-weak-map": "^2.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/lazystream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", + "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", + "dev": true, + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "dev": true, + "dependencies": { + "invert-kv": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lead": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lead/-/lead-1.0.0.tgz", + "integrity": "sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI=", + "dev": true, + "dependencies": { + "flush-write-stream": "^1.0.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/lexical-scope": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/lexical-scope/-/lexical-scope-1.2.0.tgz", + "integrity": "sha1-/Ope3HBKSzqHls3KQZw6CvryLfQ=", + "dev": true, + "dependencies": { + "astw": "^2.0.0" + } + }, + "node_modules/liftoff": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", + "integrity": "sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog==", + "dev": true, + "dependencies": { + "extend": "^3.0.0", + "findup-sync": "^3.0.0", + "fined": "^1.0.1", + "flagged-respawn": "^1.0.0", + "is-plain-object": "^2.0.4", + "object.map": "^1.0.0", + "rechoir": "^0.6.2", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/liftoff/node_modules/resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "dependencies": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", + "dev": true + }, + "node_modules/lodash._basetostring": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz", + "integrity": "sha1-0YYdh3+CSlL2aYMtyvPuFVZqB9U=", + "dev": true + }, + "node_modules/lodash._basevalues": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz", + "integrity": "sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc=", + "dev": true + }, + "node_modules/lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", + "dev": true + }, + "node_modules/lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", + "dev": true + }, + "node_modules/lodash._reescape": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz", + "integrity": "sha1-Kx1vXf4HyKNVdT5fJ/rH8c3hYWo=", + "dev": true + }, + "node_modules/lodash._reevaluate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz", + "integrity": "sha1-WLx0xAZklTrgsSTYBpltrKQx4u0=", + "dev": true + }, + "node_modules/lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", + "dev": true + }, + "node_modules/lodash._root": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", + "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=", + "dev": true + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=", + "dev": true + }, + "node_modules/lodash.escape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", + "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", + "dev": true, + "dependencies": { + "lodash._root": "^3.0.0" + } + }, + "node_modules/lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", + "dev": true + }, + "node_modules/lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", + "dev": true + }, + "node_modules/lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "dev": true, + "dependencies": { + "lodash._getnative": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" + } + }, + "node_modules/lodash.memoize": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz", + "integrity": "sha1-LcvSwofLwKVcxCMovQxzYVDVPj8=", + "dev": true + }, + "node_modules/lodash.restparam": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", + "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=", + "dev": true + }, + "node_modules/lodash.template": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", + "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", + "dev": true, + "dependencies": { + "lodash._reinterpolate": "^3.0.0", + "lodash.templatesettings": "^4.0.0" + } + }, + "node_modules/lodash.templatesettings": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", + "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", + "dev": true, + "dependencies": { + "lodash._reinterpolate": "^3.0.0" + } + }, + "node_modules/lru-cache": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", + "dev": true + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/make-error-cause": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/make-error-cause/-/make-error-cause-1.2.2.tgz", + "integrity": "sha1-3wOI/NCzeBbf8KX7gQiTl3fcvJ0=", + "dev": true, + "dependencies": { + "make-error": "^1.2.0" + } + }, + "node_modules/make-iterator": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", + "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/make-iterator/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "dependencies": { + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/matchdep": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", + "integrity": "sha512-LFgVbaHIHMqCRuCZyfCtUOq9/Lnzhi7Z0KFUE2fhD54+JN2jLh3hC02RLkqauJ3U4soU6H1J3tfj/Byk7GoEjA==", + "dev": true, + "dependencies": { + "findup-sync": "^2.0.0", + "micromatch": "^3.0.4", + "resolve": "^1.4.0", + "stack-trace": "0.0.10" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/matchdep/node_modules/findup-sync": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", + "integrity": "sha512-vs+3unmJT45eczmcAZ6zMJtxN3l/QXeccaXQx5cu/MeJMhewVfoWZqibRkOxPnmoR59+Zy5hjabfQc6JLSah4g==", + "dev": true, + "dependencies": { + "detect-file": "^1.0.0", + "is-glob": "^3.1.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/matchdep/node_modules/is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/matchdep/node_modules/resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "dependencies": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/micromatch/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/micromatch/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/micromatch/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "dependencies": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "bin": { + "miller-rabin": "bin/miller-rabin" + } + }, + "node_modules/miller-rabin/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true + }, + "node_modules/minimatch": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz", + "integrity": "sha512-jQo6o1qSVLEWaw3l+bwYA2X0uLuK2KjNh2wjgO7Q/9UJnXr1Q3yQKR8BI0/Bt/rPg75e6SMW4hW/6cBHVTZUjA==", + "deprecated": "Please update to minimatch 3.0.2 or higher to avoid a RegExp DoS issue", + "dev": true, + "dependencies": { + "brace-expansion": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "dependencies": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mixin-deep/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/module-deps": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-3.9.1.tgz", + "integrity": "sha1-6nXK+RmQkNJbDVUStaysuW5/h/M=", + "dev": true, + "dependencies": { + "browser-resolve": "^1.7.0", + "concat-stream": "~1.4.5", + "defined": "^1.0.0", + "detective": "^4.0.0", + "duplexer2": "0.0.2", + "inherits": "^2.0.1", + "JSONStream": "^1.0.3", + "parents": "^1.0.0", + "readable-stream": "^1.1.13", + "resolve": "^1.1.3", + "stream-combiner2": "~1.0.0", + "subarg": "^1.0.0", + "through2": "^1.0.0", + "xtend": "^4.0.0" + }, + "bin": { + "module-deps": "bin/cmd.js" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/module-deps/node_modules/defined": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", + "dev": true + }, + "node_modules/module-deps/node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "dev": true, + "engines": [ + "node >= 0.2.0" + ] + }, + "node_modules/module-deps/node_modules/JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "dependencies": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + }, + "bin": { + "JSONStream": "bin.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/module-deps/node_modules/resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "dependencies": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/module-deps/node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/multipipe": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz", + "integrity": "sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s=", + "dev": true, + "dependencies": { + "duplexer2": "0.0.2" + } + }, + "node_modules/mute-stdout": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.1.tgz", + "integrity": "sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/nan": { + "version": "2.14.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", + "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==", + "dev": true, + "optional": true + }, + "node_modules/nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nanomatch/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nanomatch/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nanomatch/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", + "dev": true + }, + "node_modules/ngraph.centrality": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/ngraph.centrality/-/ngraph.centrality-0.3.0.tgz", + "integrity": "sha1-jMDsAxnvCjdDV/wQRMFpdbF50J0=" + }, + "node_modules/ngraph.events": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/ngraph.events/-/ngraph.events-0.0.3.tgz", + "integrity": "sha1-OPVTFvPSB61jH/lPZiLKjywOh9A=" + }, + "node_modules/ngraph.expose": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/ngraph.expose/-/ngraph.expose-0.0.0.tgz", + "integrity": "sha1-dGw0kDo4SMRdAzsUvGRhnqhf5ao=" + }, + "node_modules/ngraph.forcelayout": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/ngraph.forcelayout/-/ngraph.forcelayout-0.5.0.tgz", + "integrity": "sha1-UVEcPh20XT1UNtp137HWrwl5FvU=", + "dependencies": { + "ngraph.events": "0.0.4", + "ngraph.physics.simulator": "^0.3.0" + } + }, + "node_modules/ngraph.forcelayout/node_modules/ngraph.events": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/ngraph.events/-/ngraph.events-0.0.4.tgz", + "integrity": "sha1-css2RIjdD9fwV0WESfajsXpyLZo=" + }, + "node_modules/ngraph.fromjson": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/ngraph.fromjson/-/ngraph.fromjson-0.1.9.tgz", + "integrity": "sha1-ZpELZkxp+jxQoc553R391bq0b24=", + "dependencies": { + "ngraph.graph": "0.0.14" + } + }, + "node_modules/ngraph.generators": { + "version": "0.0.19", + "resolved": "https://registry.npmjs.org/ngraph.generators/-/ngraph.generators-0.0.19.tgz", + "integrity": "sha1-VSwNCH+UK50NKwxsqarENr76dlk=", + "dependencies": { + "ngraph.graph": "0.0.14", + "ngraph.random": "0.1.0" + } + }, + "node_modules/ngraph.generators/node_modules/ngraph.random": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ngraph.random/-/ngraph.random-0.1.0.tgz", + "integrity": "sha1-G26Vc1KeCAZ32m/6CYeQ12oJSKk=" + }, + "node_modules/ngraph.graph": { + "version": "0.0.14", + "resolved": "https://registry.npmjs.org/ngraph.graph/-/ngraph.graph-0.0.14.tgz", + "integrity": "sha1-1HrJSWfJIKr3aVLYpOczRuHfLbc=", + "dependencies": { + "ngraph.events": "0.0.3" + } + }, + "node_modules/ngraph.merge": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/ngraph.merge/-/ngraph.merge-0.0.1.tgz", + "integrity": "sha1-5OgM43WBo8lrF9VF46Q8hUNLkCU=" + }, + "node_modules/ngraph.physics.primitives": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ngraph.physics.primitives/-/ngraph.physics.primitives-0.0.7.tgz", + "integrity": "sha1-Xcnhebofkubex3SwHNaJFBILeVs=" + }, + "node_modules/ngraph.physics.simulator": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/ngraph.physics.simulator/-/ngraph.physics.simulator-0.3.0.tgz", + "integrity": "sha1-fKb8PjYXxz4QgFcuqo4E27d+AQI=", + "dependencies": { + "ngraph.events": "0.0.3", + "ngraph.expose": "0.0.0", + "ngraph.merge": "0.0.1", + "ngraph.physics.primitives": "0.0.7", + "ngraph.quadtreebh": "0.0.4", + "ngraph.random": "0.0.1" + } + }, + "node_modules/ngraph.quadtreebh": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/ngraph.quadtreebh/-/ngraph.quadtreebh-0.0.4.tgz", + "integrity": "sha1-xwDUTm5K8HttBQAbo5h/9eLc1iw=", + "dependencies": { + "ngraph.random": "0.0.1" + } + }, + "node_modules/ngraph.random": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/ngraph.random/-/ngraph.random-0.0.1.tgz", + "integrity": "sha1-wAji67/f+vF+0Q5LvJE+VnFmvPg=" + }, + "node_modules/ngraph.remove-overlaps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ngraph.remove-overlaps/-/ngraph.remove-overlaps-1.0.0.tgz", + "integrity": "sha1-tMBjVT6kzx/3gWjnt//2JhfA2fQ=", + "dev": true + }, + "node_modules/ngraph.tojson": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/ngraph.tojson/-/ngraph.tojson-0.1.4.tgz", + "integrity": "sha1-OfAEZYhECt5iLVhzTVideXSgs7w=" + }, + "node_modules/nopt": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-2.2.1.tgz", + "integrity": "sha1-KqCbfRdoSHs7ianFqlIzW/8Lrqc=", + "dev": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + } + }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-package-data/node_modules/resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "dependencies": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/now-and-later": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", + "integrity": "sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==", + "dev": true, + "dependencies": { + "once": "^1.3.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", + "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "dependencies": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-descriptor/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "dependencies": { + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.defaults": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", + "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=", + "dev": true, + "dependencies": { + "array-each": "^1.0.1", + "array-slice": "^1.0.0", + "for-own": "^1.0.0", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", + "integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=", + "dev": true, + "dependencies": { + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.reduce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.reduce/-/object.reduce-1.0.1.tgz", + "integrity": "sha1-b+NI8qx/oPlcpiEiZZkJaCW7A60=", + "dev": true, + "dependencies": { + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/optimist": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.3.7.tgz", + "integrity": "sha1-yQlBrVnkJzMokjB00s8ufLxuwNk=", + "dev": true, + "dependencies": { + "wordwrap": "~0.0.2" + } + }, + "node_modules/ordered-read-streams": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", + "integrity": "sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=", + "dev": true, + "dependencies": { + "readable-stream": "^2.0.1" + } + }, + "node_modules/ordered-read-streams/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "node_modules/ordered-read-streams/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/ordered-read-streams/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/ordered-read-streams/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/os-browserify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.1.2.tgz", + "integrity": "sha1-ScoCk+CxlZCl9d4Qx/JlphfY/lQ=", + "dev": true + }, + "node_modules/os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "dev": true, + "dependencies": { + "lcid": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=", + "dev": true + }, + "node_modules/parents": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz", + "integrity": "sha1-/t1NK/GTp3dF/nHjcdc8MwfZx1E=", + "dev": true, + "dependencies": { + "path-platform": "~0.11.15" + } + }, + "node_modules/parse-asn1": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", + "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", + "dev": true, + "dependencies": { + "asn1.js": "^5.2.0", + "browserify-aes": "^1.0.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/parse-filepath": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", + "integrity": "sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=", + "dev": true, + "dependencies": { + "is-absolute": "^1.0.0", + "map-cache": "^0.2.0", + "path-root": "^0.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "dependencies": { + "error-ex": "^1.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", + "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", + "dev": true + }, + "node_modules/path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "node_modules/path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "dependencies": { + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-platform": { + "version": "0.11.15", + "resolved": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz", + "integrity": "sha1-6GQhf3TDaFDwhSt43Hv31KVyG/I=", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/path-root": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", + "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", + "dev": true, + "dependencies": { + "path-root-regex": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-root-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", + "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pbkdf2": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz", + "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==", + "dev": true, + "dependencies": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pretty-hrtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/process": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/process/-/process-0.10.1.tgz", + "integrity": "sha1-hCRXzFHP7XLcd1r+6vuMYDQ3JyU=", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/public-encrypt/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "dependencies": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "node_modules/punycode": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.2.4.tgz", + "integrity": "sha1-VACKyXKux0F13vnLpt9/qdORh0A=", + "dev": true, + "engines": [ + "node", + "rhino" + ] + }, + "node_modules/querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true, + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "dev": true, + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "dependencies": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "node_modules/read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "dependencies": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "dependencies": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/readable-wrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/readable-wrap/-/readable-wrap-1.0.0.tgz", + "integrity": "sha1-O1ohHGMeEjA6VJkcgGwX564ga/8=", + "dev": true, + "dependencies": { + "readable-stream": "^1.1.13-1" + } + }, + "node_modules/readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/readdirp/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "node_modules/readdirp/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readdirp/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/readdirp/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/rechoir/node_modules/resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "dependencies": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "dependencies": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/regex-not/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/regex-not/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/remove-bom-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", + "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5", + "is-utf8": "^0.2.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/remove-bom-stream": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz", + "integrity": "sha1-BfGlk/FuQuH7kOv1nejlaVJflSM=", + "dev": true, + "dependencies": { + "remove-bom-buffer": "^3.0.0", + "safe-buffer": "^5.1.0", + "through2": "^2.0.3" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/remove-bom-stream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "node_modules/remove-bom-stream/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/remove-bom-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/remove-bom-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/remove-bom-stream/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/remove-bom-stream/node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "node_modules/repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/replace-ext": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", + "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/replace-homedir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-1.0.0.tgz", + "integrity": "sha1-6H9tUTuSjd6AgmDBK+f+xv9ueYw=", + "dev": true, + "dependencies": { + "homedir-polyfill": "^1.0.1", + "is-absolute": "^1.0.0", + "remove-trailing-separator": "^1.1.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "node_modules/resolve": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-0.7.4.tgz", + "integrity": "sha1-OVqe+ehz+/4SvRRAi9kbuTYAPWk=", + "dev": true + }, + "node_modules/resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", + "dev": true, + "dependencies": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-options": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-1.1.0.tgz", + "integrity": "sha1-MrueOcBtZzONyTeMDW1gdFZq0TE=", + "dev": true, + "dependencies": { + "value-or-function": "^3.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "deprecated": "https://github.com/lydell/resolve-url#deprecated", + "dev": true + }, + "node_modules/ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/rfile": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rfile/-/rfile-1.0.0.tgz", + "integrity": "sha1-WXCM+Qyh50xUw8/Fw2/bmBBDUmE=", + "dev": true, + "dependencies": { + "callsite": "~1.0.0", + "resolve": "~0.3.0" + } + }, + "node_modules/rfile/node_modules/resolve": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-0.3.1.tgz", + "integrity": "sha1-NMY0R8ZkxwWY0cmxJvxDsqJDEKQ=", + "dev": true + }, + "node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "node_modules/ruglify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ruglify/-/ruglify-1.0.0.tgz", + "integrity": "sha512-XfRj1YJdm/gnZNvmpQ5L+2YGRHglDGMPgJRbitgCxC3GzKVQF/t+ij1aNcNg2AnEXGtLHJDwoSWrAq3TUm0EVg==", + "dev": true, + "dependencies": { + "rfile": "~1.0", + "uglify-js": "~2.2" + } + }, + "node_modules/ruglify/node_modules/uglify-js": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.2.5.tgz", + "integrity": "sha512-viLk+/8G0zm2aKt1JJAVcz5J/5ytdiNaIsKgrre3yvSUjwVG6ZUujGH7E2TiPigZUwLYCe7eaIUEP2Zka2VJPA==", + "dev": true, + "dependencies": { + "optimist": "~0.3.5", + "source-map": "~0.1.7" + }, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/runforcover": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/runforcover/-/runforcover-0.0.2.tgz", + "integrity": "sha512-yarCIK2HcAOadqnKW419+FA38qpWDCKcOr5RZU+jnyLL/hn3No9BHZY+YJDEzvQ0k8Oyl7ffLjZv9ZUxvyKoLQ==", + "dev": true, + "dependencies": { + "bunker": "0.1.X" + }, + "engines": { + "node": "*" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "dependencies": { + "ret": "~0.1.10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/semver-greatest-satisfied-range": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz", + "integrity": "sha1-E+jCZYq5aRywzXEJMkAoDTb3els=", + "dev": true, + "dependencies": { + "sver-compat": "^1.5.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "node_modules/set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, + "node_modules/shallow-copy": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/shallow-copy/-/shallow-copy-0.0.1.tgz", + "integrity": "sha1-QV9CcC1z2BAzApLMXuhurhoRoXA=", + "dev": true + }, + "node_modules/shasum": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/shasum/-/shasum-1.0.2.tgz", + "integrity": "sha1-5wEjENj0F/TetXEhUOVni4euVl8=", + "dev": true, + "dependencies": { + "json-stable-stringify": "~0.0.0", + "sha.js": "~2.4.4" + } + }, + "node_modules/shell-quote": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-0.0.1.tgz", + "integrity": "sha512-uEWz7wa9vnCi9w4mvKZMgbHFk3DCKjLQlZcy0tJxUH4NwZjRrPPHXAYIEt2TmJs600Dcgj0Z3fZLZKVPVdGNbQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", + "dev": true + }, + "node_modules/simplesvg": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/simplesvg/-/simplesvg-0.0.10.tgz", + "integrity": "sha1-N9LsGN4sFU3Ztp956K0gvx4eX90=", + "dependencies": { + "add-event-listener": "0.0.1" + } + }, + "node_modules/slide": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", + "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "dependencies": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "dependencies": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "dependencies": { + "kind-of": "^3.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map": { + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "dev": true, + "dependencies": { + "amdefine": ">=0.0.4" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "dev": true, + "dependencies": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "node_modules/source-map-url": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", + "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", + "dev": true + }, + "node_modules/sparkles": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", + "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz", + "integrity": "sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ==", + "dev": true + }, + "node_modules/split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "dependencies": { + "extend-shallow": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split-string/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split-string/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "dependencies": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stream-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-1.0.0.tgz", + "integrity": "sha1-v5tKv7QrJ011FHnkTg/yZWtvEZM=", + "dev": true, + "dependencies": { + "inherits": "~2.0.1", + "readable-stream": "^1.0.27-1" + } + }, + "node_modules/stream-combiner2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.0.2.tgz", + "integrity": "sha1-unKmtQy/q/qVD8i8h2BL0B62BnE=", + "dev": true, + "dependencies": { + "duplexer2": "~0.0.2", + "through2": "~0.5.1" + } + }, + "node_modules/stream-combiner2/node_modules/readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/stream-combiner2/node_modules/through2": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.5.1.tgz", + "integrity": "sha1-390BLrnHAOIyP9M084rGIqs3Lac=", + "dev": true, + "dependencies": { + "readable-stream": "~1.0.17", + "xtend": "~3.0.0" + } + }, + "node_modules/stream-exhaust": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", + "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==", + "dev": true + }, + "node_modules/stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", + "dev": true + }, + "node_modules/stream-splicer": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/stream-splicer/-/stream-splicer-1.3.2.tgz", + "integrity": "sha1-PARBvhW5v04iYnXm3IOWR0VUZmE=", + "dev": true, + "dependencies": { + "indexof": "0.0.1", + "inherits": "^2.0.1", + "isarray": "~0.0.1", + "readable-stream": "^1.1.13-1", + "readable-wrap": "^1.0.0", + "through2": "^1.0.0" + } + }, + "node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "node_modules/string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "dependencies": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "dependencies": { + "is-utf8": "^0.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/subarg": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", + "integrity": "sha1-9izxdYHplrSPyWVpn1TAauJouNI=", + "dev": true, + "dependencies": { + "minimist": "^1.1.0" + } + }, + "node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/sver-compat": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/sver-compat/-/sver-compat-1.5.0.tgz", + "integrity": "sha1-PPh9/rTQe0o/FIJ7wYaz/QxkXNg=", + "dev": true, + "dependencies": { + "es6-iterator": "^2.0.1", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/syntax-error": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.4.0.tgz", + "integrity": "sha512-YPPlu67mdnHGTup2A8ff7BC2Pjq0e0Yp/IyTFN03zWO0RcK07uLcbi7C2KpGR2FvWbaB0+bfE27a+sBKebSo7w==", + "dev": true, + "dependencies": { + "acorn-node": "^1.2.0" + } + }, + "node_modules/tap": { + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/tap/-/tap-0.4.13.tgz", + "integrity": "sha512-DRPT9T2qqeUQ9nC8nwnZPQQnKA+bVhzaNrIDoFETFqWLXGOvil/JMhuWj5uO6XeNzIdvvPKLqPHQzIkJnjNnDQ==", + "dev": true, + "dependencies": { + "buffer-equal": "~0.0.0", + "deep-equal": "~0.0.0", + "difflet": "~0.2.0", + "glob": "~3.2.1", + "inherits": "*", + "mkdirp": "~0.3 || 0.4 || 0.5", + "nopt": "~2", + "runforcover": "~0.0.2", + "slide": "*", + "yamlish": "*" + }, + "bin": { + "tap": "bin/tap.js" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tap/node_modules/deep-equal": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-0.0.0.tgz", + "integrity": "sha1-mWedO70EcVb81FDT0B7rkGhpHoM=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/tap/node_modules/glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", + "integrity": "sha512-hVb0zwEZwC1FXSKRPFTeOtN7AArJcJlI6ULGLtrstaswKNlrTJqAA+1lYlSUop4vjA423xlBzqfVS3iWGlqJ+g==", + "dev": true, + "dependencies": { + "inherits": "2", + "minimatch": "0.3" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tap/node_modules/minimatch": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", + "integrity": "sha512-WFX1jI1AaxNTZVOHLBVazwTWKaQjoykSzCBNXB72vDTCzopQGtyP91tKdFK5cv1+qMwPyiTu1HqUriqplI8pcA==", + "deprecated": "Please update to minimatch 3.0.2 or higher to avoid a RegExp DoS issue", + "dev": true, + "dependencies": { + "lru-cache": "2", + "sigmund": "~1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "node_modules/through2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-1.1.1.tgz", + "integrity": "sha1-CEfLxESfNAVXTb3M2buEG4OsNUU=", + "dev": true, + "dependencies": { + "readable-stream": ">=1.1.13-1 <1.2.0-0", + "xtend": ">=4.0.0 <4.1.0-0" + } + }, + "node_modules/through2-filter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", + "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", + "dev": true, + "dependencies": { + "through2": "~2.0.0", + "xtend": "~4.0.0" + } + }, + "node_modules/through2-filter/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "node_modules/through2-filter/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/through2-filter/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/through2-filter/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/through2-filter/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/through2-filter/node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/through2/node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/time-stamp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", + "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/timers-browserify": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz", + "integrity": "sha1-ycWLV1voQHN1y14kYtrO50NZ9B0=", + "dev": true, + "dependencies": { + "process": "~0.11.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/timers-browserify/node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/to-absolute-glob": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", + "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=", + "dev": true, + "dependencies": { + "is-absolute": "^1.0.0", + "is-negated-glob": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-object-path/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "dependencies": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-through": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz", + "integrity": "sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY=", + "dev": true, + "dependencies": { + "through2": "^2.0.3" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/to-through/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "node_modules/to-through/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/to-through/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/to-through/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/to-through/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/to-through/node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/traverse": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz", + "integrity": "sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=", + "dev": true + }, + "node_modules/tty-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", + "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==", + "dev": true + }, + "node_modules/type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", + "dev": true + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "node_modules/uglify-js": { + "version": "3.12.8", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.12.8.tgz", + "integrity": "sha512-fvBeuXOsvqjecUtF/l1dwsrrf5y2BCUk9AOJGzGcm6tE7vegku5u/YvqjyDaAGr422PLoLnrxg3EnRvTqsdC1w==", + "dev": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "dev": true + }, + "node_modules/umd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/umd/-/umd-2.1.0.tgz", + "integrity": "sha512-mEAJeceExHnblcAwN3BQtDPYOrTy4ALeBh6nQ9KW0cUCd0UU714jAfil2jvq09b67IizwJIiTVFOjE+/52Dyvw==", + "dev": true, + "dependencies": { + "rfile": "~1.0.0", + "ruglify": "~1.0.0", + "through": "~2.3.4", + "uglify-js": "~2.4.0" + }, + "bin": { + "umd": "bin/cli.js" + } + }, + "node_modules/umd/node_modules/async": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", + "integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E=", + "dev": true + }, + "node_modules/umd/node_modules/camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/umd/node_modules/source-map": { + "version": "0.1.34", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.34.tgz", + "integrity": "sha1-p8/omux7FoLDsZjQrPtH19CQVms=", + "dev": true, + "dependencies": { + "amdefine": ">=0.0.4" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/umd/node_modules/uglify-js": { + "version": "2.4.24", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.4.24.tgz", + "integrity": "sha512-tktIjwackfZLd893KGJmXc1hrRHH1vH9Po3xFh1XBjjeGAnN02xJ3SuoA+n1L29/ZaCA18KzCFlckS+vfPugiA==", + "dev": true, + "dependencies": { + "async": "~0.2.6", + "source-map": "0.1.34", + "uglify-to-browserify": "~1.0.0", + "yargs": "~3.5.4" + }, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/umd/node_modules/wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/umd/node_modules/yargs": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.5.4.tgz", + "integrity": "sha1-2K/49mXpTDS9JZvevRv68N3TU2E=", + "dev": true, + "dependencies": { + "camelcase": "^1.0.2", + "decamelize": "^1.0.0", + "window-size": "0.1.0", + "wordwrap": "0.0.2" + } + }, + "node_modules/unc-path-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/undertaker": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.3.0.tgz", + "integrity": "sha512-/RXwi5m/Mu3H6IHQGww3GNt1PNXlbeCuclF2QYR14L/2CHPz3DFZkvB5hZ0N/QUkiXWCACML2jXViIQEQc2MLg==", + "dev": true, + "dependencies": { + "arr-flatten": "^1.0.1", + "arr-map": "^2.0.0", + "bach": "^1.0.0", + "collection-map": "^1.0.0", + "es6-weak-map": "^2.0.1", + "fast-levenshtein": "^1.0.0", + "last-run": "^1.1.0", + "object.defaults": "^1.0.0", + "object.reduce": "^1.0.0", + "undertaker-registry": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/undertaker-registry": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-1.0.1.tgz", + "integrity": "sha1-XkvaMI5KiirlhPm5pDWaSZglzFA=", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "dependencies": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unique-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", + "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", + "dev": true, + "dependencies": { + "json-stable-stringify-without-jsonify": "^1.0.1", + "through2-filter": "^3.0.0" + } + }, + "node_modules/unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "dependencies": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "dependencies": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "dependencies": { + "isarray": "1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "node_modules/upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "dev": true, + "engines": { + "node": ">=4", + "yarn": "*" + } + }, + "node_modules/urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "deprecated": "Please see https://github.com/lydell/urix#deprecated", + "dev": true + }, + "node_modules/url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "dev": true, + "dependencies": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "node_modules/url/node_modules/punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + }, + "node_modules/use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "dev": true, + "dependencies": { + "inherits": "2.0.3" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "node_modules/util/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "node_modules/v8flags": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", + "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", + "dev": true, + "dependencies": { + "homedir-polyfill": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/value-or-function": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", + "integrity": "sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vinyl": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz", + "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", + "dev": true, + "dependencies": { + "clone": "^0.2.0", + "clone-stats": "^0.0.1" + }, + "engines": { + "node": ">= 0.9" + } + }, + "node_modules/vinyl-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/vinyl-buffer/-/vinyl-buffer-1.0.1.tgz", + "integrity": "sha1-lsGjR5uMU5JULGEgKQE7Wyf4i78=", + "dev": true, + "dependencies": { + "bl": "^1.2.1", + "through2": "^2.0.3" + } + }, + "node_modules/vinyl-buffer/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "node_modules/vinyl-buffer/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/vinyl-buffer/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/vinyl-buffer/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/vinyl-buffer/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/vinyl-buffer/node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/vinyl-fs": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", + "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", + "dev": true, + "dependencies": { + "fs-mkdirp-stream": "^1.0.0", + "glob-stream": "^6.1.0", + "graceful-fs": "^4.0.0", + "is-valid-glob": "^1.0.0", + "lazystream": "^1.0.0", + "lead": "^1.0.0", + "object.assign": "^4.0.4", + "pumpify": "^1.3.5", + "readable-stream": "^2.3.3", + "remove-bom-buffer": "^3.0.0", + "remove-bom-stream": "^1.2.0", + "resolve-options": "^1.1.0", + "through2": "^2.0.0", + "to-through": "^2.0.0", + "value-or-function": "^3.0.0", + "vinyl": "^2.0.0", + "vinyl-sourcemap": "^1.1.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vinyl-fs/node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/vinyl-fs/node_modules/clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "dev": true + }, + "node_modules/vinyl-fs/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "node_modules/vinyl-fs/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/vinyl-fs/node_modules/replace-ext": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", + "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vinyl-fs/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/vinyl-fs/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/vinyl-fs/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/vinyl-fs/node_modules/vinyl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", + "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", + "dev": true, + "dependencies": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vinyl-fs/node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/vinyl-source-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vinyl-source-stream/-/vinyl-source-stream-2.0.0.tgz", + "integrity": "sha1-84pa+53R6Ttl1VBGmsYYKsT1S44=", + "dev": true, + "dependencies": { + "through2": "^2.0.3", + "vinyl": "^2.1.0" + } + }, + "node_modules/vinyl-source-stream/node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/vinyl-source-stream/node_modules/clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "dev": true + }, + "node_modules/vinyl-source-stream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "node_modules/vinyl-source-stream/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/vinyl-source-stream/node_modules/replace-ext": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", + "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vinyl-source-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/vinyl-source-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/vinyl-source-stream/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/vinyl-source-stream/node_modules/vinyl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", + "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", + "dev": true, + "dependencies": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vinyl-source-stream/node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/vinyl-sourcemap": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz", + "integrity": "sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY=", + "dev": true, + "dependencies": { + "append-buffer": "^1.0.2", + "convert-source-map": "^1.5.0", + "graceful-fs": "^4.1.6", + "normalize-path": "^2.1.1", + "now-and-later": "^2.0.0", + "remove-bom-buffer": "^3.0.0", + "vinyl": "^2.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vinyl-sourcemap/node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/vinyl-sourcemap/node_modules/clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "dev": true + }, + "node_modules/vinyl-sourcemap/node_modules/convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, + "node_modules/vinyl-sourcemap/node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vinyl-sourcemap/node_modules/replace-ext": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", + "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vinyl-sourcemap/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/vinyl-sourcemap/node_modules/vinyl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", + "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", + "dev": true, + "dependencies": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vinyl-sourcemaps-apply": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz", + "integrity": "sha1-q2VJ1h0XLCsbh75cUI0jnI74dwU=", + "dev": true, + "dependencies": { + "source-map": "^0.5.1" + } + }, + "node_modules/vinyl-sourcemaps-apply/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vm-browserify": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", + "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", + "dev": true, + "dependencies": { + "indexof": "0.0.1" + } + }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", + "dev": true + }, + "node_modules/window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "dependencies": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/xtend": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-3.0.0.tgz", + "integrity": "sha1-XM50B7r2Qsunvs2laBEcST9ZZlo=", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz", + "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==", + "dev": true + }, + "node_modules/yamlish": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/yamlish/-/yamlish-0.0.7.tgz", + "integrity": "sha1-tK+aHcxjYYhzw9bkUewyE8OaV/s=", + "dev": true + }, + "node_modules/yargs": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.1.tgz", + "integrity": "sha512-huO4Fr1f9PmiJJdll5kwoS2e4GqzGSsMT3PPMpOwoVkOK8ckqAewMTZyA6LXVQWflleb/Z8oPBEvNsMft0XE+g==", + "dev": true, + "dependencies": { + "camelcase": "^3.0.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.2", + "which-module": "^1.0.0", + "y18n": "^3.2.1", + "yargs-parser": "5.0.0-security.0" + } + }, + "node_modules/yargs-parser": { + "version": "5.0.0-security.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0-security.0.tgz", + "integrity": "sha512-T69y4Ps64LNesYxeYGYPvfoMTt/7y1XtfpIslUeK4um+9Hu7hlGoRtaDLvdXb7+/tfq4opVa2HRY5xGip022rQ==", + "dev": true, + "dependencies": { + "camelcase": "^3.0.0", + "object.assign": "^4.1.0" + } + } + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "acorn": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=", + "dev": true + }, + "acorn-node": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", + "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", + "dev": true, + "requires": { + "acorn": "^7.0.0", + "acorn-walk": "^7.0.0", + "xtend": "^4.0.2" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + } + } + }, + "acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true + }, + "add-event-listener": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/add-event-listener/-/add-event-listener-0.0.1.tgz", + "integrity": "sha1-p2Ip68ZMiu+uIEoWJzovJVq+otA=" + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true + }, + "ansi-colors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "dev": true, + "requires": { + "ansi-wrap": "^0.1.0" + } + }, + "ansi-gray": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", + "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", + "dev": true + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "append-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", + "integrity": "sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE=", + "dev": true, + "requires": { + "buffer-equal": "^1.0.0" + }, + "dependencies": { + "buffer-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", + "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=", + "dev": true + } + } + }, + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-filter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/arr-filter/-/arr-filter-1.1.2.tgz", + "integrity": "sha1-Q/3d0JHo7xGqTEXZzcGOLf8XEe4=", + "dev": true, + "requires": { + "make-iterator": "^1.0.0" + } + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/arr-map/-/arr-map-2.0.2.tgz", + "integrity": "sha1-Onc0X/wc814qkYJWAfnljy4kysQ=", + "dev": true, + "requires": { + "make-iterator": "^1.0.0" + } + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-differ": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", + "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=", + "dev": true + }, + "array-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", + "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", + "dev": true + }, + "array-initial": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", + "integrity": "sha1-L6dLJnOTccOUe9enrcc74zSz15U=", + "dev": true, + "requires": { + "array-slice": "^1.0.0", + "is-number": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true + } + } + }, + "array-last": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array-last/-/array-last-1.3.0.tgz", + "integrity": "sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==", + "dev": true, + "requires": { + "is-number": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true + } + } + }, + "array-slice": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", + "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", + "dev": true + }, + "array-sort": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-1.0.0.tgz", + "integrity": "sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==", + "dev": true, + "requires": { + "default-compare": "^1.0.0", + "get-value": "^2.0.6", + "kind-of": "^5.0.2" + } + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } + } + }, + "assert": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.3.0.tgz", + "integrity": "sha1-A5OaYiWCqBLMICMgoLmlbJuBWEk=", + "dev": true, + "requires": { + "util": "0.10.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + }, + "util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, + "requires": { + "inherits": "2.0.1" + } + } + } + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "astw": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/astw/-/astw-2.2.0.tgz", + "integrity": "sha1-e9QXhNMkk5h66yOba04cV6hzuRc=", + "dev": true, + "requires": { + "acorn": "^4.0.3" + } + }, + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "async-done": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz", + "integrity": "sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.2", + "process-nextick-args": "^2.0.0", + "stream-exhaust": "^1.0.1" + } + }, + "async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", + "dev": true + }, + "async-settle": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", + "integrity": "sha1-HQqRS7Aldb7IqPOnTlCA9yssDGs=", + "dev": true, + "requires": { + "async-done": "^1.2.2" + } + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "bach": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", + "integrity": "sha1-Szzpa/JxNPeaG0FKUcFONMO9mIA=", + "dev": true, + "requires": { + "arr-filter": "^1.1.1", + "arr-flatten": "^1.0.1", + "arr-map": "^2.0.0", + "array-each": "^1.0.0", + "array-initial": "^1.0.0", + "array-last": "^1.1.1", + "async-done": "^1.2.2", + "async-settle": "^1.0.0", + "now-and-later": "^2.0.0" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + } + } + }, + "Base64": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/Base64/-/Base64-0.2.1.tgz", + "integrity": "sha1-ujpCMHCOGGcFBl5mur3Uw1z2ACg=", + "dev": true + }, + "base64-js": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz", + "integrity": "sha1-EQHpVE9KdrG8OybUUsqW16NeeXg=", + "dev": true + }, + "beeper": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.1.tgz", + "integrity": "sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak=", + "dev": true + }, + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true + }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dev": true, + "optional": true, + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "bl": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", + "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", + "dev": true, + "requires": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true + }, + "browser-pack": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/browser-pack/-/browser-pack-3.2.0.tgz", + "integrity": "sha512-BHla5EbbxjNyLMFUMamVjeTY+q1QwHbrYNXlWOkw71QcBqAQF7maJyNh3OI/V0d5YyNdMYD6tiPhJB9ukBo99Q==", + "dev": true, + "requires": { + "combine-source-map": "~0.3.0", + "concat-stream": "~1.4.1", + "defined": "~0.0.0", + "JSONStream": "~0.8.4", + "through2": "~0.5.1", + "umd": "^2.1.0" + }, + "dependencies": { + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "through2": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.5.1.tgz", + "integrity": "sha1-390BLrnHAOIyP9M084rGIqs3Lac=", + "dev": true, + "requires": { + "readable-stream": "~1.0.17", + "xtend": "~3.0.0" + } + } + } + }, + "browser-resolve": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", + "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", + "dev": true, + "requires": { + "resolve": "1.1.7" + }, + "dependencies": { + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + } + } + }, + "browserify": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/browserify/-/browserify-8.1.3.tgz", + "integrity": "sha512-0KKOkCQXCvP64qm73CtESE92pVnB5MkjA2W/Tmf+S4uPHVY4/SeCwO7wdr4mkke5ySoVyMqsOcMa5zo6uh9c9A==", + "dev": true, + "requires": { + "assert": "~1.3.0", + "browser-pack": "^3.2.0", + "browser-resolve": "^1.3.0", + "browserify-zlib": "~0.1.2", + "buffer": "^3.0.0", + "builtins": "~0.0.3", + "commondir": "0.0.1", + "concat-stream": "~1.4.1", + "console-browserify": "^1.1.0", + "constants-browserify": "~0.0.1", + "crypto-browserify": "^3.0.0", + "deep-equal": "~0.2.1", + "defined": "~0.0.0", + "deps-sort": "^1.3.5", + "domain-browser": "~1.1.0", + "duplexer2": "~0.0.2", + "events": "~1.0.0", + "glob": "^4.0.5", + "http-browserify": "^1.4.0", + "https-browserify": "~0.0.0", + "inherits": "~2.0.1", + "insert-module-globals": "^6.2.0", + "isarray": "0.0.1", + "JSONStream": "~0.8.3", + "labeled-stream-splicer": "^1.0.0", + "module-deps": "^3.6.3", + "os-browserify": "~0.1.1", + "parents": "^1.0.1", + "path-browserify": "~0.0.0", + "process": "^0.10.0", + "punycode": "~1.2.3", + "querystring-es3": "~0.2.0", + "readable-stream": "^1.0.33-1", + "resolve": "~0.7.1", + "shallow-copy": "0.0.1", + "shasum": "^1.0.0", + "shell-quote": "~0.0.1", + "stream-browserify": "^1.0.0", + "string_decoder": "~0.10.0", + "subarg": "^1.0.0", + "syntax-error": "^1.1.1", + "through2": "^1.0.0", + "timers-browserify": "^1.0.1", + "tty-browserify": "~0.0.0", + "umd": "~2.1.0", + "url": "~0.10.1", + "util": "~0.10.1", + "vm-browserify": "~0.0.1", + "xtend": "^3.0.0" + } + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "requires": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "requires": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "browserify-rsa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", + "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", + "dev": true, + "requires": { + "bn.js": "^5.0.0", + "randombytes": "^2.0.1" + } + }, + "browserify-sign": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.2.tgz", + "integrity": "sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg==", + "dev": true, + "requires": { + "bn.js": "^5.2.1", + "browserify-rsa": "^4.1.0", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.4", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.6", + "readable-stream": "^3.6.2", + "safe-buffer": "^5.2.1" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + } + } + }, + "browserify-zlib": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz", + "integrity": "sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0=", + "dev": true, + "requires": { + "pako": "~0.2.0" + } + }, + "buffer": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-3.6.2.tgz", + "integrity": "sha512-c3M77NkHJxS0zx/ErxXhDLr1v3y2MDXPeTJPvLNOaIYJ4ymHBUFQ9EXzt9HYuqAJllMoNb/EZ8hIiulnQFAUuQ==", + "dev": true, + "requires": { + "base64-js": "0.0.8", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + } + } + }, + "buffer-equal": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.2.tgz", + "integrity": "sha1-7Lt5D1aNQAmKYkK1SAXHWAXrk48=", + "dev": true + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", + "dev": true + }, + "builtins": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-0.0.7.tgz", + "integrity": "sha1-NVIZzWzxjb58Acx/0tznZc/cVJo=", + "dev": true + }, + "bunker": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/bunker/-/bunker-0.1.2.tgz", + "integrity": "sha512-YnahkcXBNT522S46k5LUA9P18lzvgkunbMl0qIJQ8oeRMQ+dAg3YI3k32q5TnO+AAUErFHO6R768To6jslgYmQ==", + "dev": true, + "requires": { + "burrito": ">=0.2.5 <0.3" + } + }, + "burrito": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/burrito/-/burrito-0.2.12.tgz", + "integrity": "sha512-ZhhT5iVTAgzQ+s8rily7m45Swxe/cU3dVCHTzqmHVWD/cc0Ds3W4Q4MExbkevY+fm0Me3lEwpehIy6TH7p+ehw==", + "dev": true, + "requires": { + "traverse": "~0.5.1", + "uglify-js": "~1.1.1" + }, + "dependencies": { + "traverse": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.5.2.tgz", + "integrity": "sha1-4gPFjV9/DjfbbnTArLkpuwm2HYU=", + "dev": true + }, + "uglify-js": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-1.1.1.tgz", + "integrity": "sha512-YYY9Dle1leC+btgrHnAR05eq0aRdcPJsXlYYD+SYw2lqc5HFuFNHg3wWEW4SNE0iXXEUl0fz43gTQ3r1YK76rg==", + "dev": true + } + } + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "callsite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", + "dev": true + }, + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "charm": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/charm/-/charm-0.1.2.tgz", + "integrity": "sha1-BsIe7RobBq62dVPNxT4jJ0usIpY=", + "dev": true + }, + "chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + } + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + } + } + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + } + }, + "clone": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz", + "integrity": "sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8=", + "dev": true + }, + "clone-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", + "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", + "dev": true + }, + "clone-stats": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", + "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", + "dev": true + }, + "cloneable-readable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", + "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "process-nextick-args": "^2.0.0", + "readable-stream": "^2.3.5" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "collection-map": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-map/-/collection-map-1.0.0.tgz", + "integrity": "sha1-rqDwb40mx4DCt1SUOFVEsiVa8Yw=", + "dev": true, + "requires": { + "arr-map": "^2.0.2", + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + } + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true + }, + "combine-source-map": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.3.0.tgz", + "integrity": "sha1-2edPWT2c1DgHMSy12EbUUe+qnrc=", + "dev": true, + "requires": { + "convert-source-map": "~0.3.0", + "inline-source-map": "~0.3.0", + "source-map": "~0.1.31" + } + }, + "commondir": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-0.0.1.tgz", + "integrity": "sha1-ifAP3NUbUZxXhzP+xWPmptp/W+I=", + "dev": true + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.4.11.tgz", + "integrity": "sha512-X3JMh8+4je3U1cQpG87+f9lXHDrqcb2MVLg9L7o8b1UZ0DzhRrUpdn65ttzu10PpJPPI3MQNkis+oha6TSA9Mw==", + "dev": true, + "requires": { + "inherits": "~2.0.1", + "readable-stream": "~1.1.9", + "typedarray": "~0.0.5" + } + }, + "console-browserify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", + "dev": true + }, + "constants-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-0.0.1.tgz", + "integrity": "sha1-kld9tSe6bEzwpFaNhLwDH0QeIfI=", + "dev": true + }, + "convert-source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-0.3.5.tgz", + "integrity": "sha1-8dgClQr33SYxof6+BZZVDIarMZA=", + "dev": true + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "copy-props": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.5.tgz", + "integrity": "sha512-XBlx8HSqrT0ObQwmSzM7WE5k8FxTV75h1DX1Z3n6NhQ/UYYAvInWYmG06vFt7hQZArE2fuO62aihiWIVQwh1sw==", + "dev": true, + "requires": { + "each-props": "^1.3.2", + "is-plain-object": "^5.0.0" + }, + "dependencies": { + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true + } + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "requires": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + } + }, + "d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dev": true, + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "dateformat": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", + "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "dev": true + }, + "deep-equal": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-0.2.2.tgz", + "integrity": "sha1-hLdFiW80xoTpjyzg5Cq69Du6AX0=", + "dev": true + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "default-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", + "integrity": "sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==", + "dev": true, + "requires": { + "kind-of": "^5.0.2" + } + }, + "default-resolution": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz", + "integrity": "sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ=", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + } + }, + "defined": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-0.0.0.tgz", + "integrity": "sha1-817qfXBekzuvE7LwOz+D2SFAOz4=", + "dev": true + }, + "del": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/del/-/del-1.2.1.tgz", + "integrity": "sha1-rtblvNfLcyXfNPVjEl+iZbLBoBQ=", + "dev": true, + "requires": { + "each-async": "^1.0.0", + "globby": "^2.0.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "object-assign": "^3.0.0", + "rimraf": "^2.2.8" + } + }, + "deps-sort": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/deps-sort/-/deps-sort-1.3.9.tgz", + "integrity": "sha1-Kd//U+F7Nq7K51MK27v2IsLtGnE=", + "dev": true, + "requires": { + "JSONStream": "^1.0.3", + "shasum": "^1.0.0", + "subarg": "^1.0.0", + "through2": "^1.0.0" + }, + "dependencies": { + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "dev": true + }, + "JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + } + } + }, + "des.js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", + "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", + "dev": true + }, + "detective": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/detective/-/detective-4.7.1.tgz", + "integrity": "sha512-H6PmeeUcZloWtdt4DAkFyzFL94arpHr3NOwwmVILFiy+9Qd4JTxxXrzfyGk/lmct2qVGBwTSwSXagqu2BxmWig==", + "dev": true, + "requires": { + "acorn": "^5.2.1", + "defined": "^1.0.0" + }, + "dependencies": { + "acorn": { + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", + "dev": true + }, + "defined": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", + "dev": true + } + } + }, + "diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } + } + }, + "difflet": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/difflet/-/difflet-0.2.6.tgz", + "integrity": "sha1-qyOzH1ZJtvqo49KsvTNEZzZcpvo=", + "dev": true, + "requires": { + "charm": "0.1.x", + "deep-is": "0.1.x", + "traverse": "0.6.x" + } + }, + "domain-browser": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.1.7.tgz", + "integrity": "sha1-hnqksJP6oF8d4IwG9NeyH9+GmLw=", + "dev": true + }, + "duplexer2": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", + "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", + "dev": true, + "requires": { + "readable-stream": "~1.1.9" + } + }, + "duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "each-async": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/each-async/-/each-async-1.1.1.tgz", + "integrity": "sha1-3uUim98KtrogEqOV4bhpq/iBNHM=", + "dev": true, + "requires": { + "onetime": "^1.0.0", + "set-immediate-shim": "^1.0.0" + } + }, + "each-props": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/each-props/-/each-props-1.3.2.tgz", + "integrity": "sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.1", + "object.defaults": "^1.1.0" + } + }, + "elliptic": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", + "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", + "dev": true, + "requires": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } + } + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es5-ext": { + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", + "dev": true, + "requires": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "dev": true, + "requires": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "dev": true, + "requires": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "dependencies": { + "type": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==", + "dev": true + } + } + }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "events": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/events/-/events-1.0.2.tgz", + "integrity": "sha1-dYSdz+k9EPsFfDAFWv29UdBqjiQ=", + "dev": true + }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "requires": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + } + } + }, + "expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.1" + } + }, + "ext": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", + "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", + "dev": true, + "requires": { + "type": "^2.0.0" + }, + "dependencies": { + "type": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/type/-/type-2.3.0.tgz", + "integrity": "sha512-rgPIqOdfK/4J9FhiVrZ3cveAjRRo5rsQBAIhnylX874y1DX/kEKSVdLsnuHB6l1KTjHyU01VjiMBHgU2adejyg==", + "dev": true + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + } + } + }, + "fancy-log": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", + "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", + "dev": true, + "requires": { + "ansi-gray": "^0.1.1", + "color-support": "^1.1.3", + "parse-node-version": "^1.0.0", + "time-stamp": "^1.0.0" + } + }, + "fast-levenshtein": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.1.4.tgz", + "integrity": "sha1-5qdUzI8V5YmHqpy9J69m/W9OWvk=", + "dev": true + }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true, + "optional": true + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "findup-sync": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", + "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", + "dev": true, + "requires": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + } + }, + "fined": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", + "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", + "dev": true, + "requires": { + "expand-tilde": "^2.0.2", + "is-plain-object": "^2.0.3", + "object.defaults": "^1.1.0", + "object.pick": "^1.2.0", + "parse-filepath": "^1.0.1" + } + }, + "flagged-respawn": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", + "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", + "dev": true + }, + "flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "dev": true, + "requires": { + "for-in": "^1.0.1" + } + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "fs-mkdirp-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", + "integrity": "sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "through2": "^2.0.3" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + } + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "dev": true, + "optional": true, + "requires": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "gintersect": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/gintersect/-/gintersect-0.1.0.tgz", + "integrity": "sha1-moy2qAt9bpVawzUVSVsSEmJ7GBY=" + }, + "glob": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-4.5.3.tgz", + "integrity": "sha512-I0rTWUKSZKxPSIAIaqhSXTM/DiII6wame+rEC3cFA5Lqmr9YmdL7z6Hj9+bdWtTvoY1Su4/OiMLmb37Y7JzvJQ==", + "dev": true, + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^2.0.1", + "once": "^1.3.0" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "glob-stream": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", + "integrity": "sha512-uMbLGAP3S2aDOHUDfdoYcdIePUCfysbAd0IAoWVZbeGU/oNQ8asHVSshLDJUPWxfzj8zsCG7/XeHPHTtow0nsw==", + "dev": true, + "requires": { + "extend": "^3.0.0", + "glob": "^7.1.1", + "glob-parent": "^3.1.0", + "is-negated-glob": "^1.0.0", + "ordered-read-streams": "^1.0.0", + "pumpify": "^1.3.5", + "readable-stream": "^2.1.5", + "remove-trailing-separator": "^1.0.1", + "to-absolute-glob": "^2.0.0", + "unique-stream": "^2.0.2" + }, + "dependencies": { + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "glob-watcher": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.5.tgz", + "integrity": "sha512-zOZgGGEHPklZNjZQaZ9f41i7F2YwE+tS5ZHrDhbBCk3stwahn5vQxnFmBJZHoYdusR6R1bLSXeGUy/BhctwKzw==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-done": "^1.2.0", + "chokidar": "^2.0.0", + "is-negated-glob": "^1.0.0", + "just-debounce": "^1.0.0", + "normalize-path": "^3.0.0", + "object.defaults": "^1.1.0" + } + }, + "global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dev": true, + "requires": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + } + }, + "global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", + "dev": true, + "requires": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + } + }, + "globby": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-2.1.0.tgz", + "integrity": "sha1-npGSvNM/Srak+JTl5+qLcTITxII=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "async": "^1.2.1", + "glob": "^5.0.3", + "object-assign": "^3.0.0" + }, + "dependencies": { + "glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "dev": true, + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "glogg": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", + "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", + "dev": true, + "requires": { + "sparkles": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", + "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", + "dev": true + }, + "gulp": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", + "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==", + "dev": true, + "requires": { + "glob-watcher": "^5.0.3", + "gulp-cli": "^2.2.0", + "undertaker": "^1.2.1", + "vinyl-fs": "^3.0.0" + } + }, + "gulp-cli": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.3.0.tgz", + "integrity": "sha512-zzGBl5fHo0EKSXsHzjspp3y5CONegCm8ErO5Qh0UzFzk2y4tMvzLWhoDokADbarfZRL2pGpRp7yt6gfJX4ph7A==", + "dev": true, + "requires": { + "ansi-colors": "^1.0.1", + "archy": "^1.0.0", + "array-sort": "^1.0.0", + "color-support": "^1.1.3", + "concat-stream": "^1.6.0", + "copy-props": "^2.0.1", + "fancy-log": "^1.3.2", + "gulplog": "^1.0.0", + "interpret": "^1.4.0", + "isobject": "^3.0.1", + "liftoff": "^3.1.0", + "matchdep": "^2.0.0", + "mute-stdout": "^1.0.0", + "pretty-hrtime": "^1.0.0", + "replace-homedir": "^1.0.0", + "semver-greatest-satisfied-range": "^1.1.0", + "v8flags": "^3.2.0", + "yargs": "^7.1.0" + }, + "dependencies": { + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "gulp-rename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/gulp-rename/-/gulp-rename-2.0.0.tgz", + "integrity": "sha512-97Vba4KBzbYmR5VBs9mWmK+HwIf5mj+/zioxfZhOKeXtx5ZjBk57KFlePf5nxq9QsTtFl0ejnHE3zTC9MHXqyQ==", + "dev": true + }, + "gulp-run": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/gulp-run/-/gulp-run-1.7.1.tgz", + "integrity": "sha512-4OcXhBE5xpRWmbcKzE0EQWEqpLRAkX3ju6k85qkYLfmnCVGK6nPmu/sbVgDiGg6mjuzsF2b9nHFbImZBZPH3zg==", + "dev": true, + "requires": { + "gulp-util": "^3.0.0", + "lodash.defaults": "^4.0.1", + "lodash.template": "^4.0.2", + "vinyl": "^0.4.6" + } + }, + "gulp-uglify": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gulp-uglify/-/gulp-uglify-3.0.2.tgz", + "integrity": "sha512-gk1dhB74AkV2kzqPMQBLA3jPoIAPd/nlNzP2XMDSG8XZrqnlCiDGAqC+rZOumzFvB5zOphlFh6yr3lgcAb/OOg==", + "dev": true, + "requires": { + "array-each": "^1.0.1", + "extend-shallow": "^3.0.2", + "gulplog": "^1.0.0", + "has-gulplog": "^0.1.0", + "isobject": "^3.0.1", + "make-error-cause": "^1.1.1", + "safe-buffer": "^5.1.2", + "through2": "^2.0.0", + "uglify-js": "^3.0.5", + "vinyl-sourcemaps-apply": "^0.2.0" + }, + "dependencies": { + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + } + } + }, + "gulp-util": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", + "integrity": "sha512-q5oWPc12lwSFS9h/4VIjG+1NuNDlJ48ywV2JKItY4Ycc/n1fXJeYPVQsfu5ZrhQi7FGSDBalwUCLar/GyHXKGw==", + "dev": true, + "requires": { + "array-differ": "^1.0.0", + "array-uniq": "^1.0.2", + "beeper": "^1.0.0", + "chalk": "^1.0.0", + "dateformat": "^2.0.0", + "fancy-log": "^1.1.0", + "gulplog": "^1.0.0", + "has-gulplog": "^0.1.0", + "lodash._reescape": "^3.0.0", + "lodash._reevaluate": "^3.0.0", + "lodash._reinterpolate": "^3.0.0", + "lodash.template": "^3.0.0", + "minimist": "^1.1.0", + "multipipe": "^0.1.2", + "object-assign": "^3.0.0", + "replace-ext": "0.0.1", + "through2": "^2.0.0", + "vinyl": "^0.5.0" + }, + "dependencies": { + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "lodash.template": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", + "integrity": "sha512-0B4Y53I0OgHUJkt+7RmlDFWKjVAI/YUpWNiL9GQz5ORDr4ttgfQGo+phBWKFLJbBdtOwgMuUkdOHOnPg45jKmQ==", + "dev": true, + "requires": { + "lodash._basecopy": "^3.0.0", + "lodash._basetostring": "^3.0.0", + "lodash._basevalues": "^3.0.0", + "lodash._isiterateecall": "^3.0.0", + "lodash._reinterpolate": "^3.0.0", + "lodash.escape": "^3.0.0", + "lodash.keys": "^3.0.0", + "lodash.restparam": "^3.0.0", + "lodash.templatesettings": "^3.0.0" + } + }, + "lodash.templatesettings": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", + "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=", + "dev": true, + "requires": { + "lodash._reinterpolate": "^3.0.0", + "lodash.escape": "^3.0.0" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "vinyl": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", + "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", + "dev": true, + "requires": { + "clone": "^1.0.0", + "clone-stats": "^0.0.1", + "replace-ext": "0.0.1" + } + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + } + } + }, + "gulplog": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", + "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", + "dev": true, + "requires": { + "glogg": "^1.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-gulplog": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", + "integrity": "sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=", + "dev": true, + "requires": { + "sparkles": "^1.0.0" + } + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "dev": true, + "requires": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + } + } + }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dev": true, + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "requires": { + "parse-passwd": "^1.0.0" + } + }, + "hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "http-browserify": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/http-browserify/-/http-browserify-1.7.0.tgz", + "integrity": "sha1-M3la3nLfiKz7/TZ3PO/tp2RzWyA=", + "dev": true, + "requires": { + "Base64": "~0.2.0", + "inherits": "~2.0.1" + } + }, + "https-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-0.0.1.tgz", + "integrity": "sha1-P5E2XKvmC3ftDruiS0VOPgnZWoI=", + "dev": true + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, + "indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "inline-source-map": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.3.1.tgz", + "integrity": "sha1-pSi1FOaJ/OkNswiehw2S9Sestes=", + "dev": true, + "requires": { + "source-map": "~0.3.0" + }, + "dependencies": { + "source-map": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.3.0.tgz", + "integrity": "sha1-hYb7mloAXltQHiHNGLbyG0V60fk=", + "dev": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "insert-module-globals": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/insert-module-globals/-/insert-module-globals-6.6.3.tgz", + "integrity": "sha1-IGOOKaMPntHKLjqCX7wsulJG3fw=", + "dev": true, + "requires": { + "combine-source-map": "~0.6.1", + "concat-stream": "~1.4.1", + "is-buffer": "^1.1.0", + "JSONStream": "^1.0.3", + "lexical-scope": "^1.2.0", + "process": "~0.11.0", + "through2": "^1.0.0", + "xtend": "^4.0.0" + }, + "dependencies": { + "combine-source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.6.1.tgz", + "integrity": "sha1-m0oJwxYDPXaODxHgKfonMOB5rZY=", + "dev": true, + "requires": { + "convert-source-map": "~1.1.0", + "inline-source-map": "~0.5.0", + "lodash.memoize": "~3.0.3", + "source-map": "~0.4.2" + } + }, + "convert-source-map": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", + "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=", + "dev": true + }, + "inline-source-map": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.5.0.tgz", + "integrity": "sha1-Skxd2OT7Xps82mDIIt+tyu5m4K8=", + "dev": true, + "requires": { + "source-map": "~0.4.0" + } + }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "dev": true + }, + "JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true + }, + "source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true, + "requires": { + "amdefine": ">=0.0.4" + } + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + } + } + }, + "interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "dev": true + }, + "is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "dev": true, + "requires": { + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + }, + "dependencies": { + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + } + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-core-module": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", + "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + }, + "dependencies": { + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + } + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "dependencies": { + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + } + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-negated-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", + "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=", + "dev": true + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true + }, + "is-path-in-cwd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", + "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", + "dev": true, + "requires": { + "is-path-inside": "^1.0.0" + } + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "requires": { + "path-is-inside": "^1.0.1" + } + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-relative": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "dev": true, + "requires": { + "is-unc-path": "^1.0.0" + } + }, + "is-unc-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "dev": true, + "requires": { + "unc-path-regex": "^0.1.2" + } + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "is-valid-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", + "integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "json-stable-stringify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz", + "integrity": "sha1-YRwj6BTbN1Un34URk9tZ3Sryf0U=", + "dev": true, + "requires": { + "jsonify": "~0.0.0" + } + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true + }, + "jsonparse": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-0.0.5.tgz", + "integrity": "sha1-MwVCrT8KZUZlt3jz6y2an6UHrGQ=", + "dev": true + }, + "JSONStream": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-0.8.4.tgz", + "integrity": "sha1-kWV9/m/4V0gwZhMrRhi2Lo9Ih70=", + "dev": true, + "requires": { + "jsonparse": "0.0.5", + "through": ">=2.2.7 <3" + } + }, + "just-debounce": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.1.0.tgz", + "integrity": "sha512-qpcRocdkUmf+UTNBYx5w6dexX5J31AKK1OmPwH630a83DdVVUIngk55RSAiIGpQyoH0dlr872VHfPjnQnK1qDQ==", + "dev": true + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + }, + "labeled-stream-splicer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-1.0.2.tgz", + "integrity": "sha1-RhUzFTd4SYHo/SZOHzpDTE4N3WU=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "isarray": "~0.0.1", + "stream-splicer": "^1.1.0" + } + }, + "last-run": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/last-run/-/last-run-1.1.1.tgz", + "integrity": "sha1-RblpQsF7HHnHchmCWbqUO+v4yls=", + "dev": true, + "requires": { + "default-resolution": "^2.0.0", + "es6-weak-map": "^2.0.1" + } + }, + "lazystream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", + "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", + "dev": true, + "requires": { + "readable-stream": "^2.0.5" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "dev": true, + "requires": { + "invert-kv": "^1.0.0" + } + }, + "lead": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lead/-/lead-1.0.0.tgz", + "integrity": "sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI=", + "dev": true, + "requires": { + "flush-write-stream": "^1.0.2" + } + }, + "lexical-scope": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/lexical-scope/-/lexical-scope-1.2.0.tgz", + "integrity": "sha1-/Ope3HBKSzqHls3KQZw6CvryLfQ=", + "dev": true, + "requires": { + "astw": "^2.0.0" + } + }, + "liftoff": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", + "integrity": "sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog==", + "dev": true, + "requires": { + "extend": "^3.0.0", + "findup-sync": "^3.0.0", + "fined": "^1.0.1", + "flagged-respawn": "^1.0.0", + "is-plain-object": "^2.0.4", + "object.map": "^1.0.0", + "rechoir": "^0.6.2", + "resolve": "^1.1.7" + }, + "dependencies": { + "resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + } + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + } + }, + "lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", + "dev": true + }, + "lodash._basetostring": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz", + "integrity": "sha1-0YYdh3+CSlL2aYMtyvPuFVZqB9U=", + "dev": true + }, + "lodash._basevalues": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz", + "integrity": "sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc=", + "dev": true + }, + "lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", + "dev": true + }, + "lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", + "dev": true + }, + "lodash._reescape": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz", + "integrity": "sha1-Kx1vXf4HyKNVdT5fJ/rH8c3hYWo=", + "dev": true + }, + "lodash._reevaluate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz", + "integrity": "sha1-WLx0xAZklTrgsSTYBpltrKQx4u0=", + "dev": true + }, + "lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", + "dev": true + }, + "lodash._root": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", + "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=", + "dev": true + }, + "lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=", + "dev": true + }, + "lodash.escape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", + "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", + "dev": true, + "requires": { + "lodash._root": "^3.0.0" + } + }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", + "dev": true + }, + "lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", + "dev": true + }, + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "dev": true, + "requires": { + "lodash._getnative": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" + } + }, + "lodash.memoize": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz", + "integrity": "sha1-LcvSwofLwKVcxCMovQxzYVDVPj8=", + "dev": true + }, + "lodash.restparam": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", + "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=", + "dev": true + }, + "lodash.template": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", + "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", + "dev": true, + "requires": { + "lodash._reinterpolate": "^3.0.0", + "lodash.templatesettings": "^4.0.0" + } + }, + "lodash.templatesettings": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", + "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", + "dev": true, + "requires": { + "lodash._reinterpolate": "^3.0.0" + } + }, + "lru-cache": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", + "dev": true + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "make-error-cause": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/make-error-cause/-/make-error-cause-1.2.2.tgz", + "integrity": "sha1-3wOI/NCzeBbf8KX7gQiTl3fcvJ0=", + "dev": true, + "requires": { + "make-error": "^1.2.0" + } + }, + "make-iterator": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", + "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", + "dev": true, + "requires": { + "kind-of": "^6.0.2" + }, + "dependencies": { + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + } + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "matchdep": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", + "integrity": "sha512-LFgVbaHIHMqCRuCZyfCtUOq9/Lnzhi7Z0KFUE2fhD54+JN2jLh3hC02RLkqauJ3U4soU6H1J3tfj/Byk7GoEjA==", + "dev": true, + "requires": { + "findup-sync": "^2.0.0", + "micromatch": "^3.0.4", + "resolve": "^1.4.0", + "stack-trace": "0.0.10" + }, + "dependencies": { + "findup-sync": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", + "integrity": "sha512-vs+3unmJT45eczmcAZ6zMJtxN3l/QXeccaXQx5cu/MeJMhewVfoWZqibRkOxPnmoR59+Zy5hjabfQc6JLSah4g==", + "dev": true, + "requires": { + "detect-file": "^1.0.0", + "is-glob": "^3.1.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + } + }, + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + }, + "resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + } + } + }, + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "dependencies": { + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + } + } + }, + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } + } + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true + }, + "minimatch": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz", + "integrity": "sha512-jQo6o1qSVLEWaw3l+bwYA2X0uLuK2KjNh2wjgO7Q/9UJnXr1Q3yQKR8BI0/Bt/rPg75e6SMW4hW/6cBHVTZUjA==", + "dev": true, + "requires": { + "brace-expansion": "^1.0.0" + } + }, + "minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "module-deps": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-3.9.1.tgz", + "integrity": "sha1-6nXK+RmQkNJbDVUStaysuW5/h/M=", + "dev": true, + "requires": { + "browser-resolve": "^1.7.0", + "concat-stream": "~1.4.5", + "defined": "^1.0.0", + "detective": "^4.0.0", + "duplexer2": "0.0.2", + "inherits": "^2.0.1", + "JSONStream": "^1.0.3", + "parents": "^1.0.0", + "readable-stream": "^1.1.13", + "resolve": "^1.1.3", + "stream-combiner2": "~1.0.0", + "subarg": "^1.0.0", + "through2": "^1.0.0", + "xtend": "^4.0.0" + }, + "dependencies": { + "defined": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", + "dev": true + }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "dev": true + }, + "JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, + "resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "multipipe": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz", + "integrity": "sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s=", + "dev": true, + "requires": { + "duplexer2": "0.0.2" + } + }, + "mute-stdout": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.1.tgz", + "integrity": "sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg==", + "dev": true + }, + "nan": { + "version": "2.14.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", + "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==", + "dev": true, + "optional": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + } + } + }, + "next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", + "dev": true + }, + "ngraph.centrality": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/ngraph.centrality/-/ngraph.centrality-0.3.0.tgz", + "integrity": "sha1-jMDsAxnvCjdDV/wQRMFpdbF50J0=" + }, + "ngraph.events": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/ngraph.events/-/ngraph.events-0.0.3.tgz", + "integrity": "sha1-OPVTFvPSB61jH/lPZiLKjywOh9A=" + }, + "ngraph.expose": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/ngraph.expose/-/ngraph.expose-0.0.0.tgz", + "integrity": "sha1-dGw0kDo4SMRdAzsUvGRhnqhf5ao=" + }, + "ngraph.forcelayout": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/ngraph.forcelayout/-/ngraph.forcelayout-0.5.0.tgz", + "integrity": "sha1-UVEcPh20XT1UNtp137HWrwl5FvU=", + "requires": { + "ngraph.events": "0.0.4", + "ngraph.physics.simulator": "^0.3.0" + }, + "dependencies": { + "ngraph.events": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/ngraph.events/-/ngraph.events-0.0.4.tgz", + "integrity": "sha1-css2RIjdD9fwV0WESfajsXpyLZo=" + } + } + }, + "ngraph.fromjson": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/ngraph.fromjson/-/ngraph.fromjson-0.1.9.tgz", + "integrity": "sha1-ZpELZkxp+jxQoc553R391bq0b24=", + "requires": { + "ngraph.graph": "0.0.14" + } + }, + "ngraph.generators": { + "version": "0.0.19", + "resolved": "https://registry.npmjs.org/ngraph.generators/-/ngraph.generators-0.0.19.tgz", + "integrity": "sha1-VSwNCH+UK50NKwxsqarENr76dlk=", + "requires": { + "ngraph.graph": "0.0.14", + "ngraph.random": "0.1.0" + }, + "dependencies": { + "ngraph.random": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ngraph.random/-/ngraph.random-0.1.0.tgz", + "integrity": "sha1-G26Vc1KeCAZ32m/6CYeQ12oJSKk=" + } + } + }, + "ngraph.graph": { + "version": "0.0.14", + "resolved": "https://registry.npmjs.org/ngraph.graph/-/ngraph.graph-0.0.14.tgz", + "integrity": "sha1-1HrJSWfJIKr3aVLYpOczRuHfLbc=", + "requires": { + "ngraph.events": "0.0.3" + } + }, + "ngraph.merge": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/ngraph.merge/-/ngraph.merge-0.0.1.tgz", + "integrity": "sha1-5OgM43WBo8lrF9VF46Q8hUNLkCU=" + }, + "ngraph.physics.primitives": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ngraph.physics.primitives/-/ngraph.physics.primitives-0.0.7.tgz", + "integrity": "sha1-Xcnhebofkubex3SwHNaJFBILeVs=" + }, + "ngraph.physics.simulator": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/ngraph.physics.simulator/-/ngraph.physics.simulator-0.3.0.tgz", + "integrity": "sha1-fKb8PjYXxz4QgFcuqo4E27d+AQI=", + "requires": { + "ngraph.events": "0.0.3", + "ngraph.expose": "0.0.0", + "ngraph.merge": "0.0.1", + "ngraph.physics.primitives": "0.0.7", + "ngraph.quadtreebh": "0.0.4", + "ngraph.random": "0.0.1" + } + }, + "ngraph.quadtreebh": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/ngraph.quadtreebh/-/ngraph.quadtreebh-0.0.4.tgz", + "integrity": "sha1-xwDUTm5K8HttBQAbo5h/9eLc1iw=", + "requires": { + "ngraph.random": "0.0.1" + } + }, + "ngraph.random": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/ngraph.random/-/ngraph.random-0.0.1.tgz", + "integrity": "sha1-wAji67/f+vF+0Q5LvJE+VnFmvPg=" + }, + "ngraph.remove-overlaps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ngraph.remove-overlaps/-/ngraph.remove-overlaps-1.0.0.tgz", + "integrity": "sha1-tMBjVT6kzx/3gWjnt//2JhfA2fQ=", + "dev": true + }, + "ngraph.tojson": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/ngraph.tojson/-/ngraph.tojson-0.1.4.tgz", + "integrity": "sha1-OfAEZYhECt5iLVhzTVideXSgs7w=" + }, + "nopt": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-2.2.1.tgz", + "integrity": "sha1-KqCbfRdoSHs7ianFqlIzW/8Lrqc=", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + } + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "now-and-later": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", + "integrity": "sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==", + "dev": true, + "requires": { + "once": "^1.3.2" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "object-assign": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", + "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=", + "dev": true + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + } + }, + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + }, + "object.defaults": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", + "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=", + "dev": true, + "requires": { + "array-each": "^1.0.1", + "array-slice": "^1.0.0", + "for-own": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "object.map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", + "integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=", + "dev": true, + "requires": { + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "object.reduce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.reduce/-/object.reduce-1.0.1.tgz", + "integrity": "sha1-b+NI8qx/oPlcpiEiZZkJaCW7A60=", + "dev": true, + "requires": { + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", + "dev": true + }, + "optimist": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.3.7.tgz", + "integrity": "sha1-yQlBrVnkJzMokjB00s8ufLxuwNk=", + "dev": true, + "requires": { + "wordwrap": "~0.0.2" + } + }, + "ordered-read-streams": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", + "integrity": "sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=", + "dev": true, + "requires": { + "readable-stream": "^2.0.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "os-browserify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.1.2.tgz", + "integrity": "sha1-ScoCk+CxlZCl9d4Qx/JlphfY/lQ=", + "dev": true + }, + "os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "dev": true, + "requires": { + "lcid": "^1.0.0" + } + }, + "pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=", + "dev": true + }, + "parents": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz", + "integrity": "sha1-/t1NK/GTp3dF/nHjcdc8MwfZx1E=", + "dev": true, + "requires": { + "path-platform": "~0.11.15" + } + }, + "parse-asn1": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", + "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", + "dev": true, + "requires": { + "asn1.js": "^5.2.0", + "browserify-aes": "^1.0.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "parse-filepath": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", + "integrity": "sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=", + "dev": true, + "requires": { + "is-absolute": "^1.0.0", + "map-cache": "^0.2.0", + "path-root": "^0.1.1" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + }, + "parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "dev": true + }, + "parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "dev": true + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", + "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", + "dev": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "path-platform": { + "version": "0.11.15", + "resolved": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz", + "integrity": "sha1-6GQhf3TDaFDwhSt43Hv31KVyG/I=", + "dev": true + }, + "path-root": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", + "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", + "dev": true, + "requires": { + "path-root-regex": "^0.1.0" + } + }, + "path-root-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", + "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", + "dev": true + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "pbkdf2": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz", + "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==", + "dev": true, + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "pretty-hrtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", + "dev": true + }, + "process": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/process/-/process-0.10.1.tgz", + "integrity": "sha1-hCRXzFHP7XLcd1r+6vuMYDQ3JyU=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } + } + }, + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "punycode": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.2.4.tgz", + "integrity": "sha1-VACKyXKux0F13vnLpt9/qdORh0A=", + "dev": true + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "requires": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + } + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "readable-wrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/readable-wrap/-/readable-wrap-1.0.0.tgz", + "integrity": "sha1-O1ohHGMeEjA6VJkcgGwX564ga/8=", + "dev": true, + "requires": { + "readable-stream": "^1.1.13-1" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "requires": { + "resolve": "^1.1.6" + }, + "dependencies": { + "resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + } + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "remove-bom-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", + "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5", + "is-utf8": "^0.2.1" + } + }, + "remove-bom-stream": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz", + "integrity": "sha1-BfGlk/FuQuH7kOv1nejlaVJflSM=", + "dev": true, + "requires": { + "remove-bom-buffer": "^3.0.0", + "safe-buffer": "^5.1.0", + "through2": "^2.0.3" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + } + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "replace-ext": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", + "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=", + "dev": true + }, + "replace-homedir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-1.0.0.tgz", + "integrity": "sha1-6H9tUTuSjd6AgmDBK+f+xv9ueYw=", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.1", + "is-absolute": "^1.0.0", + "remove-trailing-separator": "^1.1.0" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "resolve": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-0.7.4.tgz", + "integrity": "sha1-OVqe+ehz+/4SvRRAi9kbuTYAPWk=", + "dev": true + }, + "resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", + "dev": true, + "requires": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + } + }, + "resolve-options": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-1.1.0.tgz", + "integrity": "sha1-MrueOcBtZzONyTeMDW1gdFZq0TE=", + "dev": true, + "requires": { + "value-or-function": "^3.0.0" + } + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "rfile": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rfile/-/rfile-1.0.0.tgz", + "integrity": "sha1-WXCM+Qyh50xUw8/Fw2/bmBBDUmE=", + "dev": true, + "requires": { + "callsite": "~1.0.0", + "resolve": "~0.3.0" + }, + "dependencies": { + "resolve": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-0.3.1.tgz", + "integrity": "sha1-NMY0R8ZkxwWY0cmxJvxDsqJDEKQ=", + "dev": true + } + } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + }, + "dependencies": { + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "ruglify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ruglify/-/ruglify-1.0.0.tgz", + "integrity": "sha512-XfRj1YJdm/gnZNvmpQ5L+2YGRHglDGMPgJRbitgCxC3GzKVQF/t+ij1aNcNg2AnEXGtLHJDwoSWrAq3TUm0EVg==", + "dev": true, + "requires": { + "rfile": "~1.0", + "uglify-js": "~2.2" + }, + "dependencies": { + "uglify-js": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.2.5.tgz", + "integrity": "sha512-viLk+/8G0zm2aKt1JJAVcz5J/5ytdiNaIsKgrre3yvSUjwVG6ZUujGH7E2TiPigZUwLYCe7eaIUEP2Zka2VJPA==", + "dev": true, + "requires": { + "optimist": "~0.3.5", + "source-map": "~0.1.7" + } + } + } + }, + "runforcover": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/runforcover/-/runforcover-0.0.2.tgz", + "integrity": "sha512-yarCIK2HcAOadqnKW419+FA38qpWDCKcOr5RZU+jnyLL/hn3No9BHZY+YJDEzvQ0k8Oyl7ffLjZv9ZUxvyKoLQ==", + "dev": true, + "requires": { + "bunker": "0.1.X" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true + }, + "semver-greatest-satisfied-range": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz", + "integrity": "sha1-E+jCZYq5aRywzXEJMkAoDTb3els=", + "dev": true, + "requires": { + "sver-compat": "^1.5.0" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "dev": true + }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + } + }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "shallow-copy": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/shallow-copy/-/shallow-copy-0.0.1.tgz", + "integrity": "sha1-QV9CcC1z2BAzApLMXuhurhoRoXA=", + "dev": true + }, + "shasum": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/shasum/-/shasum-1.0.2.tgz", + "integrity": "sha1-5wEjENj0F/TetXEhUOVni4euVl8=", + "dev": true, + "requires": { + "json-stable-stringify": "~0.0.0", + "sha.js": "~2.4.4" + } + }, + "shell-quote": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-0.0.1.tgz", + "integrity": "sha512-uEWz7wa9vnCi9w4mvKZMgbHFk3DCKjLQlZcy0tJxUH4NwZjRrPPHXAYIEt2TmJs600Dcgj0Z3fZLZKVPVdGNbQ==", + "dev": true + }, + "sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", + "dev": true + }, + "simplesvg": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/simplesvg/-/simplesvg-0.0.10.tgz", + "integrity": "sha1-N9LsGN4sFU3Ztp956K0gvx4eX90=", + "requires": { + "add-event-listener": "0.0.1" + } + }, + "slide": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", + "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=", + "dev": true + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "source-map": { + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "dev": true, + "requires": { + "amdefine": ">=0.0.4" + } + }, + "source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "dev": true, + "requires": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-url": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", + "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", + "dev": true + }, + "sparkles": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", + "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==", + "dev": true + }, + "spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz", + "integrity": "sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ==", + "dev": true + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + }, + "dependencies": { + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", + "dev": true + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + } + } + }, + "stream-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-1.0.0.tgz", + "integrity": "sha1-v5tKv7QrJ011FHnkTg/yZWtvEZM=", + "dev": true, + "requires": { + "inherits": "~2.0.1", + "readable-stream": "^1.0.27-1" + } + }, + "stream-combiner2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.0.2.tgz", + "integrity": "sha1-unKmtQy/q/qVD8i8h2BL0B62BnE=", + "dev": true, + "requires": { + "duplexer2": "~0.0.2", + "through2": "~0.5.1" + }, + "dependencies": { + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "through2": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.5.1.tgz", + "integrity": "sha1-390BLrnHAOIyP9M084rGIqs3Lac=", + "dev": true, + "requires": { + "readable-stream": "~1.0.17", + "xtend": "~3.0.0" + } + } + } + }, + "stream-exhaust": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", + "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==", + "dev": true + }, + "stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", + "dev": true + }, + "stream-splicer": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/stream-splicer/-/stream-splicer-1.3.2.tgz", + "integrity": "sha1-PARBvhW5v04iYnXm3IOWR0VUZmE=", + "dev": true, + "requires": { + "indexof": "0.0.1", + "inherits": "^2.0.1", + "isarray": "~0.0.1", + "readable-stream": "^1.1.13-1", + "readable-wrap": "^1.0.0", + "through2": "^1.0.0" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "^0.2.0" + } + }, + "subarg": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", + "integrity": "sha1-9izxdYHplrSPyWVpn1TAauJouNI=", + "dev": true, + "requires": { + "minimist": "^1.1.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "sver-compat": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/sver-compat/-/sver-compat-1.5.0.tgz", + "integrity": "sha1-PPh9/rTQe0o/FIJ7wYaz/QxkXNg=", + "dev": true, + "requires": { + "es6-iterator": "^2.0.1", + "es6-symbol": "^3.1.1" + } + }, + "syntax-error": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.4.0.tgz", + "integrity": "sha512-YPPlu67mdnHGTup2A8ff7BC2Pjq0e0Yp/IyTFN03zWO0RcK07uLcbi7C2KpGR2FvWbaB0+bfE27a+sBKebSo7w==", + "dev": true, + "requires": { + "acorn-node": "^1.2.0" + } + }, + "tap": { + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/tap/-/tap-0.4.13.tgz", + "integrity": "sha512-DRPT9T2qqeUQ9nC8nwnZPQQnKA+bVhzaNrIDoFETFqWLXGOvil/JMhuWj5uO6XeNzIdvvPKLqPHQzIkJnjNnDQ==", + "dev": true, + "requires": { + "buffer-equal": "~0.0.0", + "deep-equal": "~0.0.0", + "difflet": "~0.2.0", + "glob": "~3.2.1", + "inherits": "*", + "mkdirp": "~0.3 || 0.4 || 0.5", + "nopt": "~2", + "runforcover": "~0.0.2", + "slide": "*", + "yamlish": "*" + }, + "dependencies": { + "deep-equal": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-0.0.0.tgz", + "integrity": "sha1-mWedO70EcVb81FDT0B7rkGhpHoM=", + "dev": true + }, + "glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", + "integrity": "sha512-hVb0zwEZwC1FXSKRPFTeOtN7AArJcJlI6ULGLtrstaswKNlrTJqAA+1lYlSUop4vjA423xlBzqfVS3iWGlqJ+g==", + "dev": true, + "requires": { + "inherits": "2", + "minimatch": "0.3" + } + }, + "minimatch": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", + "integrity": "sha512-WFX1jI1AaxNTZVOHLBVazwTWKaQjoykSzCBNXB72vDTCzopQGtyP91tKdFK5cv1+qMwPyiTu1HqUriqplI8pcA==", + "dev": true, + "requires": { + "lru-cache": "2", + "sigmund": "~1.0.0" + } + } + } + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "through2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-1.1.1.tgz", + "integrity": "sha1-CEfLxESfNAVXTb3M2buEG4OsNUU=", + "dev": true, + "requires": { + "readable-stream": ">=1.1.13-1 <1.2.0-0", + "xtend": ">=4.0.0 <4.1.0-0" + }, + "dependencies": { + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + } + } + }, + "through2-filter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", + "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", + "dev": true, + "requires": { + "through2": "~2.0.0", + "xtend": "~4.0.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + } + } + }, + "time-stamp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", + "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=", + "dev": true + }, + "timers-browserify": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz", + "integrity": "sha1-ycWLV1voQHN1y14kYtrO50NZ9B0=", + "dev": true, + "requires": { + "process": "~0.11.0" + }, + "dependencies": { + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true + } + } + }, + "to-absolute-glob": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", + "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=", + "dev": true, + "requires": { + "is-absolute": "^1.0.0", + "is-negated-glob": "^1.0.0" + } + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "to-through": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz", + "integrity": "sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY=", + "dev": true, + "requires": { + "through2": "^2.0.3" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + } + } + }, + "traverse": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz", + "integrity": "sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=", + "dev": true + }, + "tty-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", + "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==", + "dev": true + }, + "type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", + "dev": true + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "uglify-js": { + "version": "3.12.8", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.12.8.tgz", + "integrity": "sha512-fvBeuXOsvqjecUtF/l1dwsrrf5y2BCUk9AOJGzGcm6tE7vegku5u/YvqjyDaAGr422PLoLnrxg3EnRvTqsdC1w==", + "dev": true + }, + "uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "dev": true + }, + "umd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/umd/-/umd-2.1.0.tgz", + "integrity": "sha512-mEAJeceExHnblcAwN3BQtDPYOrTy4ALeBh6nQ9KW0cUCd0UU714jAfil2jvq09b67IizwJIiTVFOjE+/52Dyvw==", + "dev": true, + "requires": { + "rfile": "~1.0.0", + "ruglify": "~1.0.0", + "through": "~2.3.4", + "uglify-js": "~2.4.0" + }, + "dependencies": { + "async": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", + "integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E=", + "dev": true + }, + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "dev": true + }, + "source-map": { + "version": "0.1.34", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.34.tgz", + "integrity": "sha1-p8/omux7FoLDsZjQrPtH19CQVms=", + "dev": true, + "requires": { + "amdefine": ">=0.0.4" + } + }, + "uglify-js": { + "version": "2.4.24", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.4.24.tgz", + "integrity": "sha512-tktIjwackfZLd893KGJmXc1hrRHH1vH9Po3xFh1XBjjeGAnN02xJ3SuoA+n1L29/ZaCA18KzCFlckS+vfPugiA==", + "dev": true, + "requires": { + "async": "~0.2.6", + "source-map": "0.1.34", + "uglify-to-browserify": "~1.0.0", + "yargs": "~3.5.4" + } + }, + "wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "dev": true + }, + "yargs": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.5.4.tgz", + "integrity": "sha1-2K/49mXpTDS9JZvevRv68N3TU2E=", + "dev": true, + "requires": { + "camelcase": "^1.0.2", + "decamelize": "^1.0.0", + "window-size": "0.1.0", + "wordwrap": "0.0.2" + } + } + } + }, + "unc-path-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", + "dev": true + }, + "undertaker": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.3.0.tgz", + "integrity": "sha512-/RXwi5m/Mu3H6IHQGww3GNt1PNXlbeCuclF2QYR14L/2CHPz3DFZkvB5hZ0N/QUkiXWCACML2jXViIQEQc2MLg==", + "dev": true, + "requires": { + "arr-flatten": "^1.0.1", + "arr-map": "^2.0.0", + "bach": "^1.0.0", + "collection-map": "^1.0.0", + "es6-weak-map": "^2.0.1", + "fast-levenshtein": "^1.0.0", + "last-run": "^1.1.0", + "object.defaults": "^1.0.0", + "object.reduce": "^1.0.0", + "undertaker-registry": "^1.0.0" + } + }, + "undertaker-registry": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-1.0.1.tgz", + "integrity": "sha1-XkvaMI5KiirlhPm5pDWaSZglzFA=", + "dev": true + }, + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + } + }, + "unique-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", + "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", + "dev": true, + "requires": { + "json-stable-stringify-without-jsonify": "^1.0.1", + "through2-filter": "^3.0.0" + } + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + } + } + }, + "upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "dev": true + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "dev": true, + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + } + } + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true + }, + "util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "dev": true, + "requires": { + "inherits": "2.0.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + } + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "v8flags": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", + "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.1" + } + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "value-or-function": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", + "integrity": "sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=", + "dev": true + }, + "vinyl": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz", + "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", + "dev": true, + "requires": { + "clone": "^0.2.0", + "clone-stats": "^0.0.1" + } + }, + "vinyl-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/vinyl-buffer/-/vinyl-buffer-1.0.1.tgz", + "integrity": "sha1-lsGjR5uMU5JULGEgKQE7Wyf4i78=", + "dev": true, + "requires": { + "bl": "^1.2.1", + "through2": "^2.0.3" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + } + } + }, + "vinyl-fs": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", + "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", + "dev": true, + "requires": { + "fs-mkdirp-stream": "^1.0.0", + "glob-stream": "^6.1.0", + "graceful-fs": "^4.0.0", + "is-valid-glob": "^1.0.0", + "lazystream": "^1.0.0", + "lead": "^1.0.0", + "object.assign": "^4.0.4", + "pumpify": "^1.3.5", + "readable-stream": "^2.3.3", + "remove-bom-buffer": "^3.0.0", + "remove-bom-stream": "^1.2.0", + "resolve-options": "^1.1.0", + "through2": "^2.0.0", + "to-through": "^2.0.0", + "value-or-function": "^3.0.0", + "vinyl": "^2.0.0", + "vinyl-sourcemap": "^1.1.0" + }, + "dependencies": { + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true + }, + "clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "replace-ext": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", + "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", + "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "vinyl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", + "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", + "dev": true, + "requires": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + } + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + } + } + }, + "vinyl-source-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vinyl-source-stream/-/vinyl-source-stream-2.0.0.tgz", + "integrity": "sha1-84pa+53R6Ttl1VBGmsYYKsT1S44=", + "dev": true, + "requires": { + "through2": "^2.0.3", + "vinyl": "^2.1.0" + }, + "dependencies": { + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true + }, + "clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "replace-ext": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", + "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", + "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "vinyl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", + "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", + "dev": true, + "requires": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + } + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + } + } + }, + "vinyl-sourcemap": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz", + "integrity": "sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY=", + "dev": true, + "requires": { + "append-buffer": "^1.0.2", + "convert-source-map": "^1.5.0", + "graceful-fs": "^4.1.6", + "normalize-path": "^2.1.1", + "now-and-later": "^2.0.0", + "remove-bom-buffer": "^3.0.0", + "vinyl": "^2.0.0" + }, + "dependencies": { + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true + }, + "clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "dev": true + }, + "convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "replace-ext": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", + "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", + "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "vinyl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", + "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", + "dev": true, + "requires": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + } + } + } + }, + "vinyl-sourcemaps-apply": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz", + "integrity": "sha1-q2VJ1h0XLCsbh75cUI0jnI74dwU=", + "dev": true, + "requires": { + "source-map": "^0.5.1" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "vm-browserify": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", + "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", + "dev": true, + "requires": { + "indexof": "0.0.1" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", + "dev": true + }, + "window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", + "dev": true + }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "xtend": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-3.0.0.tgz", + "integrity": "sha1-XM50B7r2Qsunvs2laBEcST9ZZlo=", + "dev": true + }, + "y18n": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz", + "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==", + "dev": true + }, + "yamlish": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/yamlish/-/yamlish-0.0.7.tgz", + "integrity": "sha1-tK+aHcxjYYhzw9bkUewyE8OaV/s=", + "dev": true + }, + "yargs": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.1.tgz", + "integrity": "sha512-huO4Fr1f9PmiJJdll5kwoS2e4GqzGSsMT3PPMpOwoVkOK8ckqAewMTZyA6LXVQWflleb/Z8oPBEvNsMft0XE+g==", + "dev": true, + "requires": { + "camelcase": "^3.0.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.2", + "which-module": "^1.0.0", + "y18n": "^3.2.1", + "yargs-parser": "5.0.0-security.0" + } + }, + "yargs-parser": { + "version": "5.0.0-security.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0-security.0.tgz", + "integrity": "sha512-T69y4Ps64LNesYxeYGYPvfoMTt/7y1XtfpIslUeK4um+9Hu7hlGoRtaDLvdXb7+/tfq4opVa2HRY5xGip022rQ==", + "dev": true, + "requires": { + "camelcase": "^3.0.0", + "object.assign": "^4.1.0" + } + } + } +} diff --git a/package.json b/package.json index c85cc4f..b5f1155 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,16 @@ { "name": "vivagraphjs", - "version": "0.4.0", + "version": "0.12.0", "description": "Graph Drawing Library", - "main": "dist/vivagraph", + "main": "src/viva", "directories": { "doc": "docs" }, "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "start": "gulp", + "test": "tap test/*.js", + "release": "gulp release", + "build": "gulp release" }, "repository": { "type": "git", @@ -17,13 +20,30 @@ "graph" ], "author": "Andrei Kashcha", - "license": "BSD", + "license": "BSD-3-Clause", "devDependencies": { - "grunt": "~0.4.1", - "grunt-contrib-uglify": "~0.2.0", - "grunt-contrib-jshint": "~0.4.1", - "grunt-contrib-clean": "~0.4.0", - "grunt-contrib-concat": "~0.1.3", - "grunt-regarde": "~0.1.1" + "browserify": "^8.0.3", + "del": "^1.1.1", + "gulp": "^4.0.2", + "gulp-rename": "^2.0.0", + "gulp-run": "^1.7.1", + "gulp-uglify": "^3.0.2", + "ngraph.remove-overlaps": "^1.0.0", + "tap": "^0.4.13", + "vinyl-buffer": "^1.0.1", + "vinyl-source-stream": "^2.0.0" + }, + "dependencies": { + "gintersect": "0.1.0", + "ngraph.centrality": "0.3.0", + "ngraph.events": "0.0.3", + "ngraph.forcelayout": "0.5.0", + "ngraph.fromjson": "0.1.9", + "ngraph.generators": "0.0.19", + "ngraph.graph": "0.0.14", + "ngraph.merge": "0.0.1", + "ngraph.random": "0.0.1", + "ngraph.tojson": "0.1.4", + "simplesvg": "0.0.10" } } diff --git a/src/Algorithms/Community/community.js b/src/Algorithms/Community/community.js deleted file mode 100644 index c5d182f..0000000 --- a/src/Algorithms/Community/community.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * @fileOverview Community structure detection algorithms - * - * @see http://en.wikipedia.org/wiki/Community_structure - * - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com - */ - -Viva.Graph.community = function () { - return { - /** - * Implementation of Speaker-listener Label Propagation Algorithm (SLPA) of - * Jierui Xie and Boleslaw K. Szymanski. - * - * @see http://arxiv.org/pdf/1109.5720v3.pdf - * @see https://sites.google.com/site/communitydetectionslpa/ - */ - slpa : function (graph, T, r) { - var algorithm = Viva.Graph._community.slpaAlgorithm(graph, T, r); - return algorithm.run(); - } - }; -}; \ No newline at end of file diff --git a/src/Algorithms/Community/slpa.js b/src/Algorithms/Community/slpa.js deleted file mode 100644 index 33259bc..0000000 --- a/src/Algorithms/Community/slpa.js +++ /dev/null @@ -1,271 +0,0 @@ -Viva.Graph._community = {}; - -/** - * Implementation of Speaker-listener Label Propagation Algorithm (SLPA) of - * Jierui Xie and Boleslaw K. Szymanski. - * - * @see http://arxiv.org/pdf/1109.5720v3.pdf - * @see https://sites.google.com/site/communitydetectionslpa/ - */ -Viva.Graph._community.slpaAlgorithm = function (graph, T, r) { - T = T || 100; // number of evaluation iterations. Should be at least 20. Influence memory consumption by O(n * T); - r = r || 0.3; // community threshold on scale from 0 to 1. Value greater than 0.5 result in disjoint communities. - - var random = Viva.random(1331782216905), - shuffleRandom = Viva.random('Greeting goes to you, ', 'dear reader'), - - calculateCommunities = function (nodeMemory, threshold) { - var communities = []; - nodeMemory.forEachUniqueWord(function (word, count) { - if (count > threshold) { - communities.push({name : word, probability : count / T }); - } else { - return true; // stop enumeration, nothing more popular after this word. - } - }); - - return communities; - }, - - init = function (graph) { - var algoNodes = []; - graph.forEachNode(function (node) { - var memory = Viva.Graph._community.occuranceMap(random); - memory.add(node.id); - - node.slpa = { memory : memory }; - algoNodes.push(node.id); - }); - - return algoNodes; - }, - - evaluate = function (graph, nodes) { - var shuffle = Viva.randomIterator(nodes, shuffleRandom), - t, - - /** - * One iteration of SLPA. - */ - processNode = function (nodeId) { - var listner = graph.getNode(nodeId), - saidWords = Viva.Graph._community.occuranceMap(random); - - graph.forEachLinkedNode(nodeId, function (speakerNode) { - var word = speakerNode.slpa.memory.getRandomWord(); - saidWords.add(word); - }); - - // selecting the most popular label from what it observed in the current step - var heard = saidWords.getMostPopularFair(); - listner.slpa.memory.add(heard); - }; - - for (t = 0; t < T - 1; ++t) { // -1 is because one 'step' was during init phase - shuffle.forEach(processNode); - } - }, - - postProcess = function (graph) { - var communities = {}; - - graph.forEachNode(function (node) { - var nodeCommunities = calculateCommunities(node.slpa.memory, r * T), - i; - - for (i = 0; i < nodeCommunities.length; ++i) { - var communityName = nodeCommunities[i].name; - if (communities.hasOwnProperty(communityName)) { - communities[communityName].push(node.id); - } else { - communities[communityName] = [node.id]; - } - } - - node.communities = nodeCommunities; // TODO: I doesn't look right to augment node's properties. No? - - // Speaking of memory. Node memory created by slpa is really expensive. Release it: - node.slpa = null; - delete node.slpa; - }); - - return communities; - }; - - return { - - /** - * Executes SLPA algorithm. The function returns dictionary of discovered communities: - * { - * 'communityName1' : [nodeId1, nodeId2, .., nodeIdN], - * 'communityName2' : [nodeIdK1, nodeIdK2, .., nodeIdKN], - * ... - * }; - * - * After algorithm is done each node is also augmented with new property 'communities': - * - * node.communities = [ - * {name: 'communityName1', probability: 0.78}, - * {name: 'communityName2', probability: 0.63}, - * ... - * ]; - * - * 'probability' is always higher than 'r' parameter and denotes level of confidence - * with which we think node belongs to community. - * - * Runtime is O(T * m), where m is total number of edges, and T - number of algorithm iterations. - * - */ - run : function () { - var nodes = init(graph); - - evaluate(graph, nodes); - - return postProcess(graph); - } - }; -}; - -/** - * A data structure which serves as node memory during SLPA execution. The main idea is to - * simplify operations on memory such as - * - add word to memory, - * - get random word from memory, with probablity proportional to word occurrence in the memory - * - get the most popular word in memory - * - * TODO: currently this structure is extremely inefficient in terms of memory. I think it could be - * optimized. - */ -Viva.Graph._community.occuranceMap = function (random) { - random = random || Viva.random(); - - var wordsCount = {}, - allWords = [], - dirtyPopularity = false, - uniqueWords = [], - - rebuildPopularityArray = function () { - var key; - - uniqueWords.length = 0; - for (key in wordsCount) { - if (wordsCount.hasOwnProperty(key)) { - uniqueWords.push(key); - } - } - - uniqueWords.sort(function (x, y) { - var result = wordsCount[y] - wordsCount[x]; - if (result) { - return result; - } - - // Not only number of occurances matters but order of keys also does. - // for ... in implementation in different browsers results in different - // order, and if we want to have same categories accross all browsers - // we should order words by key names too: - if (x < y) { return -1; } - if (x > y) { return 1; } - - return 0; - }); - }, - - ensureUniqueWordsUpdated = function () { - if (dirtyPopularity) { - rebuildPopularityArray(); - dirtyPopularity = false; - } - }; - - return { - - /** - * Adds a new word to the collection of words. - */ - add : function (word) { - word = String(word); - if (wordsCount.hasOwnProperty(word)) { - wordsCount[word] += 1; - } else { - wordsCount[word] = 1; - } - - allWords.push(word); - dirtyPopularity = true; - }, - - /** - * Gets number of occurances for a given word. If word is not present in the dictionary - * zero is returned. - */ - getWordCount : function (word) { - return wordsCount[word] || 0; - }, - - /** - * Gets the most popular word in the map. If multiple words are at the same position - * random word among them is choosen. - * - */ - getMostPopularFair : function () { - if (allWords.length === 1) { - return allWords[0]; // optimizes speed for simple case. - } - - ensureUniqueWordsUpdated(); - - var maxCount = 0, - i; - - for (i = 1; i < uniqueWords.length; ++i) { - if (wordsCount[uniqueWords[i - 1]] !== wordsCount[uniqueWords[i]]) { - break; // other words are less popular... not interested. - } else { - maxCount += 1; - } - } - - maxCount += 1; // to include upper bound. i.e. random words between [0, maxCount] (not [0, maxCount) ). - return uniqueWords[random.next(maxCount)]; - }, - - /** - * Selects a random word from map with probability proportional - * to the occurrence frequency of words. - */ - getRandomWord : function () { - if (allWords.length === 0) { - throw 'The occurance map is empty. Cannot get empty word'; - } - - return allWords[random.next(allWords.length)]; - }, - - /** - * Enumerates all unique words in the map, and calls - * callback(word, occuranceCount) function on each word. Callback - * can return true value to stop enumeration. - * - * Note: enumeration is guaranteed in to run in decreasing order. - */ - forEachUniqueWord : function (callback) { - if (typeof callback !== 'function') { - throw 'Function callback is expected to enumerate all words'; - } - var i; - - ensureUniqueWordsUpdated(); - - for (i = 0; i < uniqueWords.length; ++i) { - var word = uniqueWords[i], - count = wordsCount[word]; - - var stop = callback(word, count); - if (stop) { - break; - } - } - } - }; -}; \ No newline at end of file diff --git a/src/Algorithms/centrality.js b/src/Algorithms/centrality.js index 783aec5..5e5fe45 100644 --- a/src/Algorithms/centrality.js +++ b/src/Algorithms/centrality.js @@ -1,210 +1,36 @@ -/** - * @fileOverview Centrality calcuation algorithms. - * - * @see http://en.wikipedia.org/wiki/Centrality - * - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com - */ +var centrality = require('ngraph.centrality'); -Viva.Graph.centrality = function () { - var singleSourceShortestPath = function (graph, node, oriented) { - // I'm using the same naming convention used in http://www.inf.uni-konstanz.de/algo/publications/b-fabc-01.pdf - // sorry about cryptic names. - var P = {}, // predcessors lists. - S = [], - sigma = {}, - d = {}, - Q = [node.id], - v, - dV, - sigmaV, - processNode = function (w) { - // w found for the first time? - if (!d.hasOwnProperty(w.id)) { - Q.push(w.id); - d[w.id] = dV + 1; - } - // Shortest path to w via v? - if (d[w.id] === dV + 1) { - sigma[w.id] += sigmaV; - P[w.id].push(v); - } - }; +module.exports = centralityWrapper; - graph.forEachNode(function (t) { - P[t.id] = []; - sigma[t.id] = 0; - }); +function centralityWrapper() { + // TODO: This should not be a function + return { + betweennessCentrality: betweennessCentrality, + degreeCentrality: degreeCentrality + }; +} - d[node.id] = 0; - sigma[node.id] = 1; +function betweennessCentrality(g) { + var betweenness = centrality.betweenness(g); + return toVivaGraphCentralityFormat(betweenness); +} - while (Q.length) { // Using BFS to find shortest paths - v = Q.shift(); - dV = d[v]; - sigmaV = sigma[v]; +function degreeCentrality(g, kind) { + var degree = centrality.degree(g, kind); + return toVivaGraphCentralityFormat(degree); +} - S.push(v); - graph.forEachLinkedNode(v, processNode, oriented); - } +function toVivaGraphCentralityFormat(centrality) { + return Object.keys(centrality).sort(byValue).map(toKeyValue); - return { - S : S, - P : P, - sigma : sigma - }; - }, - - accumulate = function (betweenness, shortestPath, s) { - var delta = {}, - S = shortestPath.S, - i, - w, - coeff, - pW, - v; - - for (i = 0; i < S.length; i += 1) { - delta[S[i]] = 0; - } - - // S returns vertices in order of non-increasing distance from s - while (S.length) { - w = S.pop(); - coeff = (1 + delta[w]) / shortestPath.sigma[w]; - pW = shortestPath.P[w]; - - for (i = 0; i < pW.length; i += 1) { - v = pW[i]; - delta[v] += shortestPath.sigma[v] * coeff; - } - - if (w !== s) { - betweenness[w] += delta[w]; - } - } - }, - - sortBetweennes = function (b) { - var sorted = [], - key; - for (key in b) { - if (b.hasOwnProperty(key)) { - sorted.push({ key : key, value : b[key]}); - } - } - - return sorted.sort(function (x, y) { return y.value - x.value; }); - }; + function byValue(x, y) { + return centrality[y] - centrality[x]; + } + function toKeyValue(key) { return { - - /** - * Compute the shortest-path betweenness centrality for all nodes in a graph. - * - * Betweenness centrality of a node `n` is the sum of the fraction of all-pairs - * shortest paths that pass through `n`. Runtime O(n * v) for non-weighted graphs. - * - * @see http://en.wikipedia.org/wiki/Centrality#Betweenness_centrality - * - * @see A Faster Algorithm for Betweenness Centrality. - * Ulrik Brandes, Journal of Mathematical Sociology 25(2):163-177, 2001. - * http://www.inf.uni-konstanz.de/algo/publications/b-fabc-01.pdf - * - * @see Ulrik Brandes: On Variants of Shortest-Path Betweenness - * Centrality and their Generic Computation. - * Social Networks 30(2):136-145, 2008. - * http://www.inf.uni-konstanz.de/algo/publications/b-vspbc-08.pdf - * - * @see Ulrik Brandes and Christian Pich: Centrality Estimation in Large Networks. - * International Journal of Bifurcation and Chaos 17(7):2303-2318, 2007. - * http://www.inf.uni-konstanz.de/algo/publications/bp-celn-06.pdf - * - * @param graph for which we are calculating betweenness centrality. Non-weighted graphs are only supported - */ - betweennessCentrality : function (graph) { - var betweennes = {}, - shortestPath; - graph.forEachNode(function (node) { - betweennes[node.id] = 0; - }); - - graph.forEachNode(function (node) { - shortestPath = singleSourceShortestPath(graph, node); - accumulate(betweennes, shortestPath, node); - }); - - return sortBetweennes(betweennes); - }, - - /** - * Calculates graph nodes degree centrality (in/out or both). - * - * @see http://en.wikipedia.org/wiki/Centrality#Degree_centrality - * - * @param graph for which we are calculating centrality. - * @param kind optional parameter. Valid values are - * 'in' - calculate in-degree centrality - * 'out' - calculate out-degree centrality - * - if it's not set generic degree centrality is calculated - */ - degreeCentrality : function (graph, kind) { - var calcDegFunction, - sortedDegrees = [], - result = [], - degree; - - kind = (kind || 'both').toLowerCase(); - if (kind === 'in') { - calcDegFunction = function (links, nodeId) { - var total = 0, - i; - for (i = 0; i < links.length; i += 1) { - total += (links[i].toId === nodeId) ? 1 : 0; - } - return total; - }; - } else if (kind === 'out') { - calcDegFunction = function (links, nodeId) { - var total = 0, - i; - for (i = 0; i < links.length; i += 1) { - total += (links[i].fromId === nodeId) ? 1 : 0; - } - return total; - }; - } else if (kind === 'both') { - calcDegFunction = function (links) { - return links.length; - }; - } else { - throw 'Expected centrality degree kind is: in, out or both'; - } - - graph.forEachNode(function (node) { - var links = graph.getLinks(node.id), - nodeDeg = calcDegFunction(links, node.id); - - if (!sortedDegrees.hasOwnProperty(nodeDeg)) { - sortedDegrees[nodeDeg] = [node.id]; - } else { - sortedDegrees[nodeDeg].push(node.id); - } - }); - - for (degree in sortedDegrees) { - if (sortedDegrees.hasOwnProperty(degree)) { - var nodes = sortedDegrees[degree], - j; - if (nodes) { - for (j = 0; j < nodes.length; ++j) { - result.unshift({key : nodes[j], value : parseInt(degree, 10)}); - } - } - } - } - - return result; - } + key: key, + value: centrality[key] }; -}; \ No newline at end of file + } +} diff --git a/src/Algorithms/operations.js b/src/Algorithms/operations.js new file mode 100644 index 0000000..9f516e0 --- /dev/null +++ b/src/Algorithms/operations.js @@ -0,0 +1,33 @@ +/** + * @fileOverview Contains collection of primitive operations under graph. + * + * @author Andrei Kashcha (aka anvaka) / https://github.com/anvaka + */ +module.exports = operations; + +function operations() { + + return { + /** + * Gets graph density, which is a ratio of actual number of edges to maximum + * number of edges. I.e. graph density 1 means all nodes are connected with each other with an edge. + * Density 0 - graph has no edges. Runtime: O(1) + * + * @param graph represents oriented graph structure. + * @param directed (optional boolean) represents if the graph should be treated as a directed graph. + * + * @returns density of the graph if graph has nodes. NaN otherwise. Returns density for undirected graph by default but returns density for directed graph if a boolean 'true' is passed along with the graph. + */ + density : function (graph,directed) { + var nodes = graph.getNodesCount(); + if (nodes === 0) { + return NaN; + } + if(directed){ + return graph.getLinksCount() / (nodes * (nodes - 1)); + } else { + return 2 * graph.getLinksCount() / (nodes * (nodes - 1)); + } + } + }; +}; diff --git a/src/Core/generator.js b/src/Core/generator.js deleted file mode 100644 index 6f507b5..0000000 --- a/src/Core/generator.js +++ /dev/null @@ -1,203 +0,0 @@ -/** - * @fileOverview Contains collection of graph generators. - * - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com - */ - -Viva.Graph.generator = function () { - - return { - /** - * Generates complete graph Kn. - * - * @param n represents number of nodes in the complete graph. - */ - complete : function (n) { - if (!n || n < 1) { - throw { message: "At least two nodes expected for complete graph" }; - } - - var g = Viva.Graph.graph(), - i, - j; - - g.Name = "Complete K" + n; - - for (i = 0; i < n; ++i) { - for (j = i + 1; j < n; ++j) { - if (i !== j) { - g.addLink(i, j); - } - } - } - - return g; - }, - - /** - * Generates complete bipartite graph K n,m. Each node in the - * first partition is connected to all nodes in the second partition. - * - * @param n represents number of nodes in the first graph partition - * @param m represents number of nodes in the second graph partition - */ - completeBipartite : function (n, m) { - if (!n || !m || n < 0 || m < 0) { - throw { message: "Graph dimensions are invalid. Number of nodes in each partition should be greate than 0" }; - } - - var g = Viva.Graph.graph(), - i, - j; - - g.Name = "Complete K " + n + "," + m; - for (i = 0; i < n; ++i) { - for (j = n; j < n + m; ++j) { - g.addLink(i, j); - } - } - - return g; - }, - /** - * Generates a graph in a form of a ladder with n steps. - * - * @param n number of steps in the ladder. - */ - ladder : function (n) { - if (!n || n < 0) { - throw { message: "Invalid number of nodes" }; - } - - var g = Viva.Graph.graph(), - i; - g.Name = "Ladder graph " + n; - - for (i = 0; i < n - 1; ++i) { - g.addLink(i, i + 1); - // first row - g.addLink(n + i, n + i + 1); - // second row - g.addLink(i, n + i); - // ladder"s step - } - - g.addLink(n - 1, 2 * n - 1); - // last step in the ladder; - - return g; - }, - - /** - * Generates a graph in a form of a circular ladder with n steps. - * - * @param n number of steps in the ladder. - */ - circularLadder : function (n) { - if (!n || n < 0) { - throw { message: "Invalid number of nodes" }; - } - - var g = this.ladder(n); - g.Name = "Circular ladder graph " + n; - - g.addLink(0, n - 1); - g.addLink(n, 2 * n - 1); - return g; - }, - /** - * Generates a graph in a form of a grid with n rows and m columns. - * - * @param n number of rows in the graph. - * @param m number of columns in the graph. - */ - grid: function (n, m) { - var g = Viva.Graph.graph(), - i, - j; - g.Name = "Grid graph " + n + "x" + m; - for (i = 0; i < n; ++i) { - for (j = 0; j < m; ++j) { - var node = i + j * n; - if (i > 0) { g.addLink(node, i - 1 + j * n); } - if (j > 0) { g.addLink(node, i + (j - 1) * n); } - } - } - - return g; - }, - - path: function (n) { - if (!n || n < 0) { - throw { message: "Invalid number of nodes" }; - } - - var g = Viva.Graph.graph(), - i; - g.Name = "Path graph " + n; - g.addNode(0); - - for (i = 1; i < n; ++i) { - g.addLink(i - 1, i); - } - - return g; - }, - - lollipop: function (m, n) { - if (!n || n < 0 || !m || m < 0) { - throw { message: "Invalid number of nodes" }; - } - - var g = this.complete(m), - i; - g.Name = "Lollipop graph. Head x Path " + m + "x" + n; - - for (i = 0; i < n; ++i) { - g.addLink(m + i - 1, m + i); - } - - return g; - }, - - /** - * Creates balanced binary tree with n levels. - */ - balancedBinTree: function (n) { - var g = Viva.Graph.graph(), - count = Math.pow(2, n), - level; - g.Name = "Balanced bin tree graph " + n; - - for (level = 1; level < count; ++level) { - var root = level, - left = root * 2, - right = root * 2 + 1; - - g.addLink(root, left); - g.addLink(root, right); - } - - return g; - }, - /** - * Generates a graph with n nodes and 0 links. - * - * @param n number of nodes in the graph. - */ - randomNoLinks : function (n) { - if (!n || n < 0) { - throw { message: "Invalid number of nodes" }; - } - - var g = Viva.Graph.graph(), - i; - g.Name = "Random graph, no Links: " + n; - for (i = 0; i < n; ++i) { - g.addNode(i); - } - - return g; - } - }; -}; diff --git a/src/Core/graph.js b/src/Core/graph.js deleted file mode 100644 index 9cba92c..0000000 --- a/src/Core/graph.js +++ /dev/null @@ -1,406 +0,0 @@ -/** - * @fileOverview Contains definition of the core graph object. - * - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com - */ - -/** - * @namespace Represents a graph data structure. - * - * @example - * var g = Viva.Graph.graph(); - * g.addNode(1); // g has one node. - * g.addLink(2, 3); // now g contains three nodes and one link. - * - */ -Viva.Graph.graph = function () { - - // Graph structure is maintained as dictionary of nodes - // and array of links. Each node has 'links' property which - // hold all links related to that node. And general links - // array is used to speed up all links enumeration. This is inefficient - // in terms of memory, but simplifies coding. Furthermore, the graph structure - // is isolated from outter world, and can be changed to adjacency matrix later. - - var nodes = {}, - links = [], - nodesCount = 0, - suspendEvents = 0, - - // Accumlates all changes made during graph updates. - // Each change element contains: - // changeType - one of the strings: 'add', 'remove' or 'update'; - // node - if change is related to node this property is set to changed graph's node; - // link - if change is related to link this property is set to changed graph's link; - changes = [], - - fireGraphChanged = function (graph) { - // TODO: maybe we shall copy changes? - graph.fire('changed', changes); - }, - - // Enter, Exit Mofidication allows bulk graph updates without firing events. - enterModification = function () { - suspendEvents += 1; - }, - - exitModification = function (graph) { - suspendEvents -= 1; - if (suspendEvents === 0 && changes.length > 0) { - fireGraphChanged(graph); - changes.length = 0; - } - }, - - recordNodeChange = function (node, changeType) { - // TODO: Could add changeType verification. - changes.push({node : node, changeType : changeType}); - }, - - recordLinkChange = function (link, changeType) { - // TODO: Could add change type verification; - changes.push({link : link, changeType : changeType}); - }, - - isArray = function (value) { - return value && - typeof value === 'object' && - typeof value.length === 'number' && - typeof value.splice === 'function' && - !(value.propertyIsEnumerable('length')); - }; - - /** @scope Viva.Graph.graph */ - var graphPart = { - - /** - * Adds node to the graph. If node with given id already exists in the graph - * its data is extended with whatever comes in 'data' argument. - * - * @param nodeId the node's identifier. A string is preferred. - * @param [data] additional data for the node being added. If node already - * exists its data object is augmented with the new one. - * - * @return {node} The newly added node or node with given id if it already exists. - */ - addNode : function (nodeId, data) { - if (typeof nodeId === 'undefined') { - throw { - message: 'Invalid node identifier' - }; - } - - enterModification(); - - var node = this.getNode(nodeId); - if (!node) { - node = new Viva.Graph.Node(nodeId); - nodesCount++; - - recordNodeChange(node, 'add'); - } else { - recordNodeChange(node, 'update'); - } - - if (data) { - var augmentedData = node.data || {}, - dataType = typeof data, - name; - - if (dataType === 'string' || isArray(data) || - dataType === 'number' || dataType === 'boolean') { - augmentedData = data; - } else if (dataType === 'undefined') { - augmentedData = null; - } else { - for (name in data) { - if (data.hasOwnProperty(name)) { - augmentedData[name] = data[name]; - } - } - } - - node.data = augmentedData; - } - - nodes[nodeId] = node; - - exitModification(this); - return node; - }, - - /** - * Adds a link to the graph. The function always create a new - * link between two nodes. If one of the nodes does not exists - * a new node is created. - * - * @param fromId link start node id; - * @param toId link end node id; - * @param [data] additional data to be set on the new link; - * - * @return {link} The newly created link - */ - addLink : function (fromId, toId, data) { - enterModification(); - - var fromNode = this.getNode(fromId) || this.addNode(fromId); - var toNode = this.getNode(toId) || this.addNode(toId); - - var link = new Viva.Graph.Link(fromId, toId, data); - - links.push(link); - - // TODO: this is not cool. On large graphs potentially would consume more memory. - fromNode.links.push(link); - toNode.links.push(link); - - recordLinkChange(link, 'add'); - - exitModification(this); - - return link; - }, - - /** - * Removes link from the graph. If link does not exist does nothing. - * - * @param link - object returned by addLink() or getLinks() methods. - * - * @returns true if link was removed; false otherwise. - */ - removeLink : function (link) { - if (!link) { return false; } - var idx = Viva.Graph.Utils.indexOfElementInArray(link, links); - if (idx < 0) { return false; } - - enterModification(); - - links.splice(idx, 1); - - var fromNode = this.getNode(link.fromId); - var toNode = this.getNode(link.toId); - - if (fromNode) { - idx = Viva.Graph.Utils.indexOfElementInArray(link, fromNode.links); - if (idx >= 0) { - fromNode.links.splice(idx, 1); - } - } - - if (toNode) { - idx = Viva.Graph.Utils.indexOfElementInArray(link, toNode.links); - if (idx >= 0) { - toNode.links.splice(idx, 1); - } - } - - recordLinkChange(link, 'remove'); - - exitModification(this); - - return true; - }, - - /** - * Removes node with given id from the graph. If node does not exist in the graph - * does nothing. - * - * @param nodeId node's identifier passed to addNode() function. - * - * @returns true if node was removed; false otherwise. - */ - removeNode: function (nodeId) { - var node = this.getNode(nodeId); - if (!node) { return false; } - - enterModification(); - - while (node.links.length) { - var link = node.links[0]; - this.removeLink(link); - } - - nodes[nodeId] = null; - delete nodes[nodeId]; - nodesCount--; - - recordNodeChange(node, 'remove'); - - exitModification(this); - }, - - /** - * Gets node with given identifier. If node does not exist undefined value is returned. - * - * @param nodeId requested node identifier; - * - * @return {node} in with requested identifier or undefined if no such node exists. - */ - getNode : function (nodeId) { - return nodes[nodeId]; - }, - - /** - * Gets number of nodes in this graph. - * - * @return number of nodes in the graph. - */ - getNodesCount : function () { - return nodesCount; - }, - - /** - * Gets total number of links in the graph. - */ - getLinksCount : function () { - return links.length; - }, - - /** - * Gets all links (inbound and outbound) from the node with given id. - * If node with given id is not found null is returned. - * - * @param nodeId requested node identifier. - * - * @return Array of links from and to requested node if such node exists; - * otherwise null is returned. - */ - getLinks : function (nodeId) { - var node = this.getNode(nodeId); - return node ? node.links : null; - }, - - /** - * Invokes callback on each node of the graph. - * - * @param {Function(node)} callback Function to be invoked. The function - * is passed one argument: visited node. - */ - forEachNode : function (callback) { - if (typeof callback !== 'function') { - return; - } - var node; - - // TODO: could it be faster for nodes iteration if we had indexed access? - // I.e. use array + 'for' iterator instead of dictionary + 'for .. in'? - for (node in nodes) { - // For performance reasons you might want to sacrifice this sanity check: - if (nodes.hasOwnProperty(node)) { - if (callback(nodes[node])) { - return; // client doesn't want to proceed. return. - } - } - } - }, - - /** - * Invokes callback on every linked (adjacent) node to the given one. - * - * @param nodeId Identifier of the requested node. - * @param {Function(node, link)} callback Function to be called on all linked nodes. - * The function is passed two parameters: adjacent node and link object itself. - * @param oriented if true graph treated as oriented. - */ - forEachLinkedNode : function (nodeId, callback, oriented) { - var node = this.getNode(nodeId), - i, - link, - linkedNodeId; - - if (node && node.links && typeof callback === 'function') { - // Extraced orientation check out of the loop to increase performance - if (oriented) { - for (i = 0; i < node.links.length; ++i) { - link = node.links[i]; - if (link.fromId === nodeId) { - callback(nodes[link.toId], link); - } - } - } else { - for (i = 0; i < node.links.length; ++i) { - link = node.links[i]; - linkedNodeId = link.fromId === nodeId ? link.toId : link.fromId; - - callback(nodes[linkedNodeId], link); - } - } - } - }, - - /** - * Enumerates all links in the graph - * - * @param {Function(link)} callback Function to be called on all links in the graph. - * The function is passed one parameter: graph's link object. - * - * Link object contains at least the following fields: - * fromId - node id where link starts; - * toId - node id where link ends, - * data - additional data passed to graph.addLink() method. - */ - forEachLink : function (callback) { - var i; - if (typeof callback === 'function') { - for (i = 0; i < links.length; ++i) { - callback(links[i]); - } - } - }, - - /** - * Suspend all notifications about graph changes until - * endUpdate is called. - */ - beginUpdate : function () { - enterModification(); - }, - - /** - * Resumes all notifications about graph changes and fires - * graph 'changed' event in case there are any pending changes. - */ - endUpdate : function () { - exitModification(this); - }, - - /** - * Removes all nodes and links from the graph. - */ - clear : function () { - var that = this; - that.beginUpdate(); - that.forEachNode(function (node) { that.removeNode(node.id); }); - that.endUpdate(); - }, - - /** - * Detects whether there is a link between two nodes. - * Operation complexity is O(n) where n - number of links of a node. - * - * @returns link if there is one. null otherwise. - */ - hasLink : function (fromNodeId, toNodeId) { - // TODO: Use adjacency matrix to speed up this operation. - var node = this.getNode(fromNodeId), - i; - if (!node) { - return null; - } - - for (i = 0; i < node.links.length; ++i) { - var link = node.links[i]; - if (link.fromId === fromNodeId && link.toId === toNodeId) { - return link; - } - } - - return null; // no link. - } - }; - - // Let graph fire events before we return it to the caller. - Viva.Graph.Utils.events(graphPart).extend(); - - return graphPart; -}; \ No newline at end of file diff --git a/src/Core/operations.js b/src/Core/operations.js deleted file mode 100644 index da6454e..0000000 --- a/src/Core/operations.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * @fileOverview Contains collection of primitve operations under graph. - * - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com - */ - -Viva.Graph.operations = function () { - - return { - /** - * Gets graph density, which is a ratio of actual number of edges to maximum - * number of edges. I.e. graph density 1 means all nodes are connected with each other with an edge. - * Density 0 - graph has no edges. Runtime: O(1) - * - * @param graph represents oriented graph structure. - * - * @returns density of the graph if graph has nodes. NaN otherwise - */ - density : function (graph) { - var nodes = graph.getNodesCount(); - if (nodes === 0) { - return NaN; - } - - return 2 * graph.getLinksCount() / (nodes * (nodes - 1)); - } - }; -}; diff --git a/src/Core/primitives.js b/src/Core/primitives.js deleted file mode 100644 index b094147..0000000 --- a/src/Core/primitives.js +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Very generic rectangle. - */ -Viva.Graph.Rect = function (x1, y1, x2, y2) { - this.x1 = x1 || 0; - this.y1 = y1 || 0; - this.x2 = x2 || 0; - this.y2 = y2 || 0; -}; - -/** - * Very generic two-dimensional point. - */ -Viva.Graph.Point2d = function (x, y) { - this.x = x || 0; - this.y = y || 0; -}; - -/** - * Internal structure to represent node; - */ -Viva.Graph.Node = function (id) { - this.id = id; - this.links = []; - this.data = null; -}; - -/** - * Internal structure to represent links; - */ -Viva.Graph.Link = function (fromId, toId, data) { - this.fromId = fromId; - this.toId = toId; - this.data = data; -}; \ No newline at end of file diff --git a/src/Core/serializer.js b/src/Core/serializer.js deleted file mode 100644 index 0d628b1..0000000 --- a/src/Core/serializer.js +++ /dev/null @@ -1,99 +0,0 @@ -Viva.Graph.serializer = function () { - var checkJSON = function () { - if (typeof JSON === 'undefined' || !JSON.stringify || !JSON.parse) { - throw 'JSON serializer is not defined.'; - } - }, - - nodeTransformStore = function (node) { - return { id : node.id, data: node.data }; - }, - - linkTransformStore = function (link) { - return { - fromId : link.fromId, - toId: link.toId, - data : link.data - }; - }, - - nodeTransformLoad = function (node) { - return node; - }, - - linkTransformLoad = function (link) { - return link; - }; - - return { - /** - * Saves graph to JSON format. - * - * NOTE: ECMAScript 5 (or alike) JSON object is required to be defined - * to get proper output. - * - * @param graph to be saved in JSON format. - * @param nodeTransform optional callback function(node) which returns what should be passed into nodes collection - * @param linkTransform optional callback functions(link) which returns what should be passed into the links collection - */ - storeToJSON : function (graph, nodeTransform, linkTransform) { - if (!graph) { throw 'Graph is not defined'; } - checkJSON(); - - nodeTransform = nodeTransform || nodeTransformStore; - linkTransform = linkTransform || linkTransformStore; - - var store = { - nodes : [], - links : [] - }; - - graph.forEachNode(function (node) { store.nodes.push(nodeTransform(node)); }); - graph.forEachLink(function (link) { store.links.push(linkTransform(link)); }); - - return JSON.stringify(store); - }, - - /** - * Restores graph from JSON string created by storeToJSON() method. - * - * NOTE: ECMAScript 5 (or alike) JSON object is required to be defined - * to get proper output. - * - * @param jsonString is a string produced by storeToJSON() method. - * @param nodeTransform optional callback function(node) which accepts deserialized node and returns object with - * 'id' and 'data' properties. - * @param linkTransform optional callback functions(link) which accepts deserialized link and returns link object with - * 'fromId', 'toId' and 'data' properties. - */ - loadFromJSON : function (jsonString, nodeTransform, linkTransform) { - if (typeof jsonString !== 'string') { throw 'String expected in loadFromJSON() method'; } - checkJSON(); - - nodeTransform = nodeTransform || nodeTransformLoad; - linkTransform = linkTransform || linkTransformLoad; - - var store = JSON.parse(jsonString), - graph = Viva.Graph.graph(), - i; - - if (!store || !store.nodes || !store.links) { throw 'Passed json string does not represent valid graph'; } - - for (i = 0; i < store.nodes.length; ++i) { - var parsedNode = nodeTransform(store.nodes[i]); - if (!parsedNode.hasOwnProperty('id')) { throw 'Graph node format is invalid. Node.id is missing'; } - - graph.addNode(parsedNode.id, parsedNode.data); - } - - for (i = 0; i < store.links.length; ++i) { - var link = linkTransform(store.links[i]); - if (!link.hasOwnProperty('fromId') || !link.hasOwnProperty('toId')) { throw 'Graph link format is invalid. Both fromId and toId are required'; } - - graph.addLink(link.fromId, link.toId, link.data); - } - - return graph; - } - }; -}; diff --git a/src/Input/domInputManager.js b/src/Input/domInputManager.js index bbbe21e..48f6b5c 100644 --- a/src/Input/domInputManager.js +++ b/src/Input/domInputManager.js @@ -1,41 +1,47 @@ /** - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com + * @author Andrei Kashcha (aka anvaka) / https://github.com/anvaka */ -Viva.Input = Viva.Input || {}; -Viva.Input.domInputManager = function () { - return { - /** - * Called by renderer to listen to drag-n-drop events from node. E.g. for CSS/SVG - * graphics we may listen to DOM events, whereas for WebGL the graphics - * should provide custom eventing mechanism. - * - * @param node - to be monitored. - * @param handlers - object with set of three callbacks: - * onStart: function(), - * onDrag: function(e, offset), - * onStop: function() - */ - bindDragNDrop : function (node, handlers) { - if (handlers) { - var events = Viva.Graph.Utils.dragndrop(node.ui); - if (typeof handlers.onStart === 'function') { - events.onStart(handlers.onStart); - } - if (typeof handlers.onDrag === 'function') { - events.onDrag(handlers.onDrag); - } - if (typeof handlers.onStop === 'function') { - events.onStop(handlers.onStop); - } +module.exports = domInputManager; - node.events = events; - } else if (node.events) { - // TODO: i'm not sure if this is required in JS world... - node.events.release(); - node.events = null; - delete node.events; - } - } - }; -}; +var dragndrop = require('./dragndrop.js'); + +function domInputManager(graph, graphics) { + var nodeEvents = {}; + return { + /** + * Called by renderer to listen to drag-n-drop events from node. E.g. for SVG + * graphics we may listen to DOM events, whereas for WebGL the graphics + * should provide custom eventing mechanism. + * + * @param node - to be monitored. + * @param handlers - object with set of three callbacks: + * onStart: function(), + * onDrag: function(e, offset), + * onStop: function() + */ + bindDragNDrop: bindDragNDrop + }; + + function bindDragNDrop(node, handlers) { + var events; + if (handlers) { + var nodeUI = graphics.getNodeUI(node.id); + events = dragndrop(nodeUI); + if (typeof handlers.onStart === 'function') { + events.onStart(handlers.onStart); + } + if (typeof handlers.onDrag === 'function') { + events.onDrag(handlers.onDrag); + } + if (typeof handlers.onStop === 'function') { + events.onStop(handlers.onStop); + } + + nodeEvents[node.id] = events; + } else if ((events = nodeEvents[node.id])) { + events.release(); + delete nodeEvents[node.id]; + } + } +} diff --git a/src/Input/dragndrop.js b/src/Input/dragndrop.js index cd70c53..b0044b0 100644 --- a/src/Input/dragndrop.js +++ b/src/Input/dragndrop.js @@ -1,22 +1,23 @@ /** - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com + * @author Andrei Kashcha (aka anvaka) / https://github.com/anvaka */ -Viva.Graph.Utils = Viva.Graph.Utils || {}; +module.exports = dragndrop; + +var documentEvents = require('../Utils/documentEvents.js'); +var browserInfo = require('../Utils/browserInfo.js'); +var findElementPosition = require('../Utils/findElementPosition.js'); // TODO: Move to input namespace // TODO: Methods should be extracted into the prototype. This class // does not need to consume so much memory for every tracked element -Viva.Graph.Utils.dragndrop = function (element) { +function dragndrop(element) { var start, drag, end, scroll, prevSelectStart, prevDragStart, - documentEvents = Viva.Graph.Utils.events(window.document), - elementEvents = Viva.Graph.Utils.events(element), - findElementPosition = Viva.Graph.Utils.findElementPosition, startX = 0, startY = 0, @@ -109,8 +110,8 @@ Viva.Graph.Utils.dragndrop = function (element) { handleMouseUp = function (e) { e = e || window.event; - documentEvents.stop('mousemove', handleMouseMove); - documentEvents.stop('mouseup', handleMouseUp); + documentEvents.off('mousemove', handleMouseMove); + documentEvents.off('mouseup', handleMouseUp); window.document.onselectstart = prevSelectStart; dragObject.ondragstart = prevDragStart; @@ -129,7 +130,7 @@ Viva.Graph.Utils.dragndrop = function (element) { } e.returnValue = false; - var delta, + var delta = -e.deltaY, mousePos = getMousePos(e), elementOffset = findElementPosition(element), relMousePos = { @@ -137,29 +138,15 @@ Viva.Graph.Utils.dragndrop = function (element) { y: mousePos[1] - elementOffset[1] }; - if (e.wheelDelta) { - delta = e.wheelDelta / 360; // Chrome/Safari - } else { - delta = e.detail / -9; // Mozilla - } - scroll(e, delta, relMousePos); }, updateScrollEvents = function (scrollCallback) { if (!scroll && scrollCallback) { // client is interested in scrolling. Start listening to events: - if (Viva.BrowserInfo.browser === 'webkit') { - element.addEventListener('mousewheel', handleMouseWheel, false); // Chrome/Safari - } else { - element.addEventListener('DOMMouseScroll', handleMouseWheel, false); // Others - } + element.addEventListener('wheel', handleMouseWheel, false); } else if (scroll && !scrollCallback) { - if (Viva.BrowserInfo.browser === 'webkit') { - element.removeEventListener('mousewheel', handleMouseWheel, false); // Chrome/Safari - } else { - element.removeEventListener('DOMMouseScroll', handleMouseWheel, false); // Others - } + element.removeEventListener('wheel', handleMouseWheel, false); } scroll = scrollCallback; @@ -194,9 +181,9 @@ Viva.Graph.Utils.dragndrop = function (element) { handleTouchEnd = function (e) { touchInProgress = false; - documentEvents.stop('touchmove', handleTouchMove); - documentEvents.stop('touchend', handleTouchEnd); - documentEvents.stop('touchcancel', handleTouchEnd); + documentEvents.off('touchmove', handleTouchMove); + documentEvents.off('touchend', handleTouchEnd); + documentEvents.off('touchcancel', handleTouchEnd); dragObject = null; if (end) { end(e); } }, @@ -222,7 +209,6 @@ Viva.Graph.Utils.dragndrop = function (element) { }, handleTouchStart = function (e) { - console.log('Touch start for ', element); if (e.touches.length === 1) { return handleSignleFingerTouch(e, e.touches[0]); } else if (e.touches.length === 2) { @@ -237,8 +223,8 @@ Viva.Graph.Utils.dragndrop = function (element) { }; - elementEvents.on('mousedown', handleMouseDown); - elementEvents.on('touchstart', handleTouchStart); + element.addEventListener('mousedown', handleMouseDown); + element.addEventListener('touchstart', handleTouchStart); return { onStart : function (callback) { @@ -266,14 +252,16 @@ Viva.Graph.Utils.dragndrop = function (element) { release : function () { // TODO: could be unsafe. We might wanna release dragObject, etc. - documentEvents.stop('mousemove', handleMouseMove); - documentEvents.stop('mousedown', handleMouseDown); - documentEvents.stop('mouseup', handleMouseUp); - documentEvents.stop('touchmove', handleTouchMove); - documentEvents.stop('touchend', handleTouchEnd); - documentEvents.stop('touchcancel', handleTouchEnd); + element.removeEventListener('mousedown', handleMouseDown); + element.removeEventListener('touchstart', handleTouchStart); + + documentEvents.off('mousemove', handleMouseMove); + documentEvents.off('mouseup', handleMouseUp); + documentEvents.off('touchmove', handleTouchMove); + documentEvents.off('touchend', handleTouchEnd); + documentEvents.off('touchcancel', handleTouchEnd); updateScrollEvents(null); } }; -}; +} diff --git a/src/Input/spatialIndex.js b/src/Input/spatialIndex.js deleted file mode 100644 index 3f21399..0000000 --- a/src/Input/spatialIndex.js +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Allows querying graph nodes position at given point. - * - * @param graph - graph to be queried. - * @param toleranceOrCheckCallback - if it's a number then it represents offest - * in pixels from any node position to be considered a part of the node. - * if it's a function then it's called for every node to check intersection - * - * TODO: currently it performes linear search. Use real spatial index to improve performance. - */ -Viva.Graph.spatialIndex = function (graph, toleranceOrCheckCallback) { - var getNodeFunction, - preciseCheckCallback, - tolerance = 16; - - if (typeof toleranceOrCheckCallback === 'function') { - preciseCheckCallback = toleranceOrCheckCallback; - getNodeFunction = function (x, y) { - var foundNode = null; - graph.forEachNode(function (node) { - if (preciseCheckCallback(node, x, y)) { - foundNode = node; - return true; - } - }); - - return foundNode; - }; - } else if (typeof toleranceOrCheckCallback === 'number') { - tolerance = toleranceOrCheckCallback; - getNodeFunction = function (x, y) { - var foundNode = null; - - graph.forEachNode(function (node) { - var pos = node.position; - - if (pos.x - tolerance < x && x < pos.x + tolerance && - pos.y - tolerance < y && y < pos.y + tolerance) { - - foundNode = node; - return true; - } - }); - - return foundNode; - }; - } - - - return { - getNodeAt : getNodeFunction - }; -}; diff --git a/src/Input/webglInputManager.js b/src/Input/webglInputManager.js index 8d7b659..4a5b4ea 100644 --- a/src/Input/webglInputManager.js +++ b/src/Input/webglInputManager.js @@ -1,10 +1,13 @@ /** - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com + * @author Andrei Kashcha (aka anvaka) / https://github.com/anvaka */ -Viva.Input = Viva.Input || {}; -Viva.Input.webglInputManager = function (graph, graphics) { - var inputEvents = Viva.Graph.webglInputEvents(graphics, graph), +module.exports = webglInputManager; + +var createInputEvents = require('../WebGL/webglInputEvents.js'); + +function webglInputManager(graph, graphics) { + var inputEvents = createInputEvents(graphics), draggedNode = null, internalHandlers = {}, pos = {x : 0, y : 0}; @@ -16,7 +19,7 @@ Viva.Input.webglInputManager = function (graph, graphics) { inputEvents.mouseCapture(draggedNode); - var handlers = internalHandlers[node.ui.id]; + var handlers = internalHandlers[node.id]; if (handlers && handlers.onStart) { handlers.onStart(e, pos); } @@ -26,14 +29,14 @@ Viva.Input.webglInputManager = function (graph, graphics) { inputEvents.releaseMouseCapture(draggedNode); draggedNode = null; - var handlers = internalHandlers[node.ui.id]; + var handlers = internalHandlers[node.id]; if (handlers && handlers.onStop) { handlers.onStop(); } return true; }).mouseMove(function (node, e) { if (draggedNode) { - var handlers = internalHandlers[draggedNode.ui.id]; + var handlers = internalHandlers[draggedNode.id]; if (handlers && handlers.onDrag) { handlers.onDrag(e, {x : e.clientX - pos.x, y : e.clientY - pos.y }); } @@ -46,7 +49,7 @@ Viva.Input.webglInputManager = function (graph, graphics) { return { /** - * Called by renderer to listen to drag-n-drop events from node. E.g. for CSS/SVG + * Called by renderer to listen to drag-n-drop events from node. E.g. for SVG * graphics we may listen to DOM events, whereas for WebGL we graphics * should provide custom eventing mechanism. * @@ -57,7 +60,10 @@ Viva.Input.webglInputManager = function (graph, graphics) { * onStop: function() */ bindDragNDrop : function (node, handlers) { - internalHandlers[node.ui.id] = handlers; + internalHandlers[node.id] = handlers; + if (!handlers) { + delete internalHandlers[node.id]; + } } }; -}; +} diff --git a/src/Layout/constant.js b/src/Layout/constant.js index 74c396b..758c51a 100644 --- a/src/Layout/constant.js +++ b/src/Layout/constant.js @@ -1,4 +1,8 @@ -Viva.Graph.Layout = Viva.Graph.Layout || {}; +module.exports = constant; + +var merge = require('ngraph.merge'); +var random = require('ngraph.random').random; +var Rect = require('../Utils/rect.js'); /** * Does not really perform any layouting algorithm but is compliant @@ -8,33 +12,37 @@ Viva.Graph.Layout = Viva.Graph.Layout || {}; * @param {Viva.Graph.graph} graph to layout * @param {Object} userSettings */ -Viva.Graph.Layout.constant = function (graph, userSettings) { - userSettings = Viva.lazyExtend(userSettings, { +function constant(graph, userSettings) { + userSettings = merge(userSettings, { maxX : 1024, maxY : 1024, seed : 'Deterministic randomness made me do this' }); // This class simply follows API, it does not use some of the arguments: /*jshint unused: false */ - var rand = Viva.random(userSettings.seed), - graphRect = new Viva.Graph.Rect(Number.MAX_VALUE, Number.MAX_VALUE, Number.MIN_VALUE, Number.MIN_VALUE), + var rand = random(userSettings.seed), + graphRect = new Rect(Number.MAX_VALUE, Number.MAX_VALUE, Number.MIN_VALUE, Number.MIN_VALUE), + layoutLinks = {}, placeNodeCallback = function (node) { - return new Viva.Graph.Point2d(rand.next(userSettings.maxX), rand.next(userSettings.maxY)); + return { + x: rand.next(userSettings.maxX), + y: rand.next(userSettings.maxY) + }; }, - updateGraphRect = function (node, graphRect) { - if (node.position.x < graphRect.x1) { graphRect.x1 = node.position.x; } - if (node.position.x > graphRect.x2) { graphRect.x2 = node.position.x; } - if (node.position.y < graphRect.y1) { graphRect.y1 = node.position.y; } - if (node.position.y > graphRect.y2) { graphRect.y2 = node.position.y; } + updateGraphRect = function (position, graphRect) { + if (position.x < graphRect.x1) { graphRect.x1 = position.x; } + if (position.x > graphRect.x2) { graphRect.x2 = position.x; } + if (position.y < graphRect.y1) { graphRect.y1 = position.y; } + if (position.y > graphRect.y2) { graphRect.y2 = position.y; } }, + layoutNodes = typeof Object.create === 'function' ? Object.create(null) : {}, + ensureNodeInitialized = function (node) { - if (!node.hasOwnProperty('position')) { - node.position = placeNodeCallback(node); - } - updateGraphRect(node, graphRect); + layoutNodes[node.id] = placeNodeCallback(node); + updateGraphRect(layoutNodes[node.id], graphRect); }, updateNodePositions = function () { @@ -46,8 +54,35 @@ Viva.Graph.Layout.constant = function (graph, userSettings) { graphRect.y2 = Number.MIN_VALUE; graph.forEachNode(ensureNodeInitialized); + }, + + ensureLinkInitialized = function (link) { + layoutLinks[link.id] = link; + }, + + onGraphChanged = function(changes) { + for (var i = 0; i < changes.length; ++i) { + var change = changes[i]; + if (change.node) { + if (change.changeType === 'add') { + ensureNodeInitialized(change.node); + } else { + delete layoutNodes[change.node.id]; + } + } if (change.link) { + if (change.changeType === 'add') { + ensureLinkInitialized(change.link); + } else { + delete layoutLinks[change.link.id]; + } + } + } }; + graph.forEachNode(ensureNodeInitialized); + graph.forEachLink(ensureLinkInitialized); + graph.on('changed', onGraphChanged); + return { /** * Attempts to layout graph within given number of iterations. @@ -65,7 +100,7 @@ Viva.Graph.Layout.constant = function (graph, userSettings) { step : function () { updateNodePositions(); - return false; // no need to continue. + return true; // no need to continue. }, /** @@ -76,18 +111,56 @@ Viva.Graph.Layout.constant = function (graph, userSettings) { return graphRect; }, - addNode : ensureNodeInitialized, + /** + * Request to release all resources + */ + dispose : function () { + graph.off('change', onGraphChanged); + }, + + /* + * Checks whether given node is pinned; all nodes in this layout are pinned. + */ + isNodePinned: function (node) { + return true; + }, - removeNode : function (node) { /* nop */ }, + /* + * Requests layout algorithm to pin/unpin node to its current position + * Pinned nodes should not be affected by layout algorithm and always + * remain at their position + */ + pinNode: function (node, isPinned) { + // noop + }, - addLink : function (link) { /* nop */ }, + /* + * Gets position of a node by its id. If node was not seen by this + * layout algorithm undefined value is returned; + */ + getNodePosition: getNodePosition, - removeLink : function (link) { /* nop */ }, + /** + * Returns {from, to} position of a link. + */ + getLinkPosition: function (linkId) { + var link = layoutLinks[linkId]; + return { + from : getNodePosition(link.fromId), + to : getNodePosition(link.toId) + }; + }, /** - * Request to release all resources + * Sets position of a node to a given coordinates */ - dispose : function () { /* nop */ }, + setNodePosition: function (nodeId, x, y) { + var pos = layoutNodes[nodeId]; + if (pos) { + pos.x = x; + pos.y = y; + } + }, // Layout specific methods: @@ -117,4 +190,8 @@ Viva.Graph.Layout.constant = function (graph, userSettings) { } }; -}; \ No newline at end of file + + function getNodePosition(nodeId) { + return layoutNodes[nodeId]; + } +} diff --git a/src/Layout/forceDirected.js b/src/Layout/forceDirected.js deleted file mode 100644 index 7f8d189..0000000 --- a/src/Layout/forceDirected.js +++ /dev/null @@ -1,330 +0,0 @@ -// I don't like to suppress this, but I'm afraid 'force_directed_body' -// could already be used by someone. Don't want to break it now. -/* jshint camelcase:false */ - -Viva.Graph.Layout = Viva.Graph.Layout || {}; - -Viva.Graph.Layout.forceDirected = function (graph, settings) { - var STABLE_THRESHOLD = 0.001; // Maximum movement of the system which can be considered as stabilized - - if (!graph) { - throw { - message : "Graph structure cannot be undefined" - }; - } - - settings = Viva.lazyExtend(settings, { - /** - * Ideal length for links (springs in physical model). - */ - springLength : 80, - - /** - * Hook's law coefficient. 1 - solid spring. - */ - springCoeff : 0.0002, - - /** - * Coulomb's law coefficient. It's used to repel nodes thus should be negative - * if you make it positive nodes start attract each other :). - */ - gravity: -1.2, - - /** - * Theta coeffiecient from Barnes Hut simulation. Ranged between (0, 1). - * The closer it's to 1 the more nodes algorithm will have to go through. - * Setting it to one makes Barnes Hut simulation no different from - * brute-force forces calculation (each node is considered). - */ - theta : 0.8, - - /** - * Drag force coefficient. Used to slow down system, thus should be less than 1. - * The closer it is to 0 the less tight system will be. - */ - dragCoeff : 0.02 - }); - - var forceSimulator = Viva.Graph.Physics.forceSimulator(Viva.Graph.Physics.eulerIntegrator()), - - nbodyForce = Viva.Graph.Physics.nbodyForce({gravity : settings.gravity, theta: settings.theta}), - - springForce = Viva.Graph.Physics.springForce({length : settings.springLength, coeff: settings.springCoeff }), - - dragForce = Viva.Graph.Physics.dragForce({coeff: settings.dragCoeff}), - - initializationRequired = true, - - graphRect = new Viva.Graph.Rect(), - - random = Viva.random("ted.com", 103, 114, 101, 97, 116), - - getBestNodePosition = function (node) { - // TODO: Initial position could be picked better, e.g. take into - // account all neighbouring nodes/links, not only one. - // TODO: this is the same as in gem layout. consider refactoring. - var baseX = (graphRect.x1 + graphRect.x2) / 2, - baseY = (graphRect.y1 + graphRect.y2) / 2, - springLength = settings.springLength; - - if (node.links && node.links.length > 0) { - var firstLink = node.links[0], - otherNode = firstLink.fromId !== node.id ? graph.getNode(firstLink.fromId) : graph.getNode(firstLink.toId); - if (otherNode.position) { - baseX = otherNode.position.x; - baseY = otherNode.position.y; - } - } - - return { - x : baseX + random.next(springLength) - springLength / 2, - y : baseY + random.next(springLength) - springLength / 2 - }; - }, - - updateNodeMass = function (node) { - var body = node.force_directed_body; - body.mass = 1 + graph.getLinks(node.id).length / 3.0; - }, - - initNode = function (node) { - var body = node.force_directed_body; - if (!body) { - // TODO: rename position to location or location to position to be consistent with - // other places. - node.position = node.position || getBestNodePosition(node); - - body = new Viva.Graph.Physics.Body(); - node.force_directed_body = body; - updateNodeMass(node); - - body.loc(node.position); - forceSimulator.addBody(body); - } - }, - - releaseNode = function (node) { - var body = node.force_directed_body; - if (body) { - node.force_directed_body = null; - delete node.force_directed_body; - - forceSimulator.removeBody(body); - } - }, - - initLink = function (link) { - // TODO: what if bodies are not initialized? - var from = graph.getNode(link.fromId), - to = graph.getNode(link.toId); - - updateNodeMass(from); - updateNodeMass(to); - link.force_directed_spring = forceSimulator.addSpring(from.force_directed_body, to.force_directed_body, -1.0, link.weight); - }, - - releaseLink = function (link) { - var spring = link.force_directed_spring; - if (spring) { - var from = graph.getNode(link.fromId), - to = graph.getNode(link.toId); - if (from) { updateNodeMass(from); } - if (to) { updateNodeMass(to); } - - link.force_directed_spring = null; - delete link.force_directed_spring; - - forceSimulator.removeSpring(spring); - } - }, - - initSimulator = function () { - graph.forEachNode(initNode); - graph.forEachLink(initLink); - }, - - isNodePinned = function (node) { - if (!node) { - return true; - } - - return node.isPinned || (node.data && node.data.isPinned); - }, - - updateNodePositions = function () { - var x1 = Number.MAX_VALUE, - y1 = Number.MAX_VALUE, - x2 = Number.MIN_VALUE, - y2 = Number.MIN_VALUE; - if (graph.getNodesCount() === 0) { return; } - - graph.forEachNode(function (node) { - var body = node.force_directed_body; - if (!body) { - return; // TODO: maybe we shall initialize it? - } - - if (isNodePinned(node)) { - body.loc(node.position); - } - - // TODO: once again: use one name to be consistent (position vs location) - node.position.x = body.location.x; - node.position.y = body.location.y; - - if (node.position.x < x1) { x1 = node.position.x; } - if (node.position.x > x2) { x2 = node.position.x; } - if (node.position.y < y1) { y1 = node.position.y; } - if (node.position.y > y2) { y2 = node.position.y; } - }); - - graphRect.x1 = x1; - graphRect.x2 = x2; - graphRect.y1 = y1; - graphRect.y2 = y2; - }; - - forceSimulator.addSpringForce(springForce); - forceSimulator.addBodyForce(nbodyForce); - forceSimulator.addBodyForce(dragForce); - - return { - /** - * Attempts to layout graph within given number of iterations. - * - * @param {integer} [iterationsCount] number of algorithm's iterations. - */ - run : function (iterationsCount) { - var i; - iterationsCount = iterationsCount || 50; - - for (i = 0; i < iterationsCount; ++i) { - this.step(); - } - }, - - step : function () { - // we assume graph was not modified between calls. If it was - // we will have to reinitialize force simulator. - if (initializationRequired) { - initSimulator(); - initializationRequired = false; - } - - var energy = forceSimulator.run(20); - updateNodePositions(); - - return energy < STABLE_THRESHOLD; - }, - - /** - * Returns rectangle structure {x1, y1, x2, y2}, which represents - * current space occupied by graph. - */ - getGraphRect : function () { - return graphRect; - }, - - addNode : function (node) { - initNode(node); - }, - - removeNode : function (node) { - releaseNode(node); - }, - - addLink : function (link) { - initLink(link); - }, - - removeLink : function (link) { - releaseLink(link); - }, - - /** - * Request to release all resources - */ - dispose : function () { - // Because I do not have reference to all nodes - // they should be disposed externally. Probably this will change - // In future. For now just reset this flag. - initializationRequired = true; - }, - - // Layout specific methods - /** - * Gets or sets current desired length of the edge. - * - * @param length new desired length of the springs (aka edge, aka link). - * if this parameter is empty then old spring length is returned. - */ - springLength : function (length) { - if (arguments.length === 1) { - springForce.options({ length : length }); - return this; - } - - return springForce.options().length; - }, - - /** - * Gets or sets current spring coeffiсient. - * - * @param coeff new spring coeffiсient. - * if this parameter is empty then its old value returned. - */ - springCoeff : function (coeff) { - if (arguments.length === 1) { - springForce.options({ coeff : coeff }); - return this; - } - - return springForce.options().coeff; - }, - - /** - * Gets or sets current gravity in the nbody simulation. - * - * @param g new gravity constant. - * if this parameter is empty then its old value returned. - */ - gravity : function (g) { - if (arguments.length === 1) { - nbodyForce.options({ gravity : g }); - return this; - } - - return nbodyForce.options().gravity; - }, - - /** - * Gets or sets current theta value in the nbody simulation. - * - * @param t new theta coeffiсient. - * if this parameter is empty then its old value returned. - */ - theta : function (t) { - if (arguments.length === 1) { - nbodyForce.options({ theta : t }); - return this; - } - - return nbodyForce.options().theta; - }, - - /** - * Gets or sets current theta value in the nbody simulation. - * - * @param dragCoeff new drag coeffiсient. - * if this parameter is empty then its old value returned. - */ - drag : function (dragCoeff) { - if (arguments.length === 1) { - dragForce.options({ coeff : dragCoeff }); - return this; - } - - return dragForce.options().coeff; - } - }; -}; \ No newline at end of file diff --git a/src/Physics/Forces/dragForce.js b/src/Physics/Forces/dragForce.js deleted file mode 100644 index 590eca8..0000000 --- a/src/Physics/Forces/dragForce.js +++ /dev/null @@ -1,26 +0,0 @@ -Viva.Graph.Physics.dragForce = function (options) { - if (!options) { - options = {}; - } - - var currentOptions = { - coeff : options.coeff || 0.01 - }; - - return { - init : function (forceSimulator) {}, - update : function (body) { - body.force.x -= currentOptions.coeff * body.velocity.x; - body.force.y -= currentOptions.coeff * body.velocity.y; - }, - options : function (newOptions) { - if (newOptions) { - if (typeof newOptions.coeff === 'number') { currentOptions.coeff = newOptions.coeff; } - - return this; - } - - return currentOptions; - } - }; -}; diff --git a/src/Physics/Forces/gravityForce.js b/src/Physics/Forces/gravityForce.js deleted file mode 100644 index 9754939..0000000 --- a/src/Physics/Forces/gravityForce.js +++ /dev/null @@ -1,31 +0,0 @@ -Viva.Graph.Physics.gravityForce = function (gravityConstant, options) { - if (!options) { - options = {}; - } - - var currentOptions = { - direction : options.direction || Math.PI / 2, - gravity : options.gravity || 1e-4 - }; - - - return { - init : function (forceSimulator) {}, - update : function (body) { - var coeff = currentOptions.gravity * body.mass; - - body.force.x += Math.cos(currentOptions.direction) * coeff; - body.force.y += Math.sin(currentOptions.direction) * coeff; - }, - options : function (newOptions) { - if (newOptions) { - if (newOptions.direction) { currentOptions.direction = newOptions.direction; } - if (newOptions.gravity) { currentOptions.gravity = newOptions.gravity; } - - return this; - } - - return currentOptions; - } - }; -}; diff --git a/src/Physics/Forces/nbodyForce.js b/src/Physics/Forces/nbodyForce.js deleted file mode 100644 index 0c3904e..0000000 --- a/src/Physics/Forces/nbodyForce.js +++ /dev/null @@ -1,327 +0,0 @@ -/** - * This is Barnes Hut simulation algorithm. Implementation - * is adopted to non-recursive solution, since certain browsers - * handle recursion extremly bad. - * - * http://www.cs.princeton.edu/courses/archive/fall03/cs126/assignments/barnes-hut.html - */ -Viva.Graph.Physics.nbodyForce = function (options) { - options = Viva.lazyExtend(options || { - gravity : -1, - theta : 0.8 - }); - - // the following structures are here to reduce memory pressure - // when constructing BH-tree. - function InsertStackElement(node, body) { - this.node = node; - this.body = body; - } - - function InsertStack () { - this.stack = []; - this.popIdx = 0; - } - - InsertStack.prototype = { - isEmpty: function() { - return this.popIdx === 0; - }, - push: function (node, body) { - var item = this.stack[this.popIdx]; - if (!item) { - this.stack[this.popIdx] = new InsertStackElement(node, body); - } else { - item.node = node; - item.body = body; - } - ++this.popIdx; - }, - pop: function () { - if (this.popIdx > 0) { - return this.stack[--this.popIdx]; - } - }, - reset: function () { - this.popIdx = 0; - } - }; - - - var gravity = options.gravity, - updateQueue = [], - insertStack = new InsertStack(), - theta = options.theta, - random = Viva.random('5f4dcc3b5aa765d61d8327deb882cf99', 75, 20, 63, 0x6c, 65, 76, 65, 72), - - Node = function () { - this.body = null; - this.quads = []; - this.mass = 0; - this.massX = 0; - this.massY = 0; - this.left = 0; - this.top = 0; - this.bottom = 0; - this.right = 0; - this.isInternal = false; - }, - - nodesCache = [], - currentInCache = 0, - newNode = function () { - // To avoid pressure on GC we reuse nodes. - var node; - if (nodesCache[currentInCache]) { - node = nodesCache[currentInCache]; - node.quads[0] = null; - node.quads[1] = null; - node.quads[2] = null; - node.quads[3] = null; - node.body = null; - node.mass = node.massX = node.massY = 0; - node.left = node.right = node.top = node.bottom = 0; - node.isInternal = false; - } else { - node = new Node(); - nodesCache[currentInCache] = node; - } - - ++currentInCache; - return node; - }, - - root = newNode(), - - isSamePosition = function (point1, point2) { - // TODO: inline it? - var dx = Math.abs(point1.x - point2.x); - var dy = Math.abs(point1.y - point2.y); - - return (dx < 0.01 && dy < 0.01); - }, - - // Inserts body to the tree - insert = function (newBody) { - insertStack.reset(); - insertStack.push(root, newBody); - - while (!insertStack.isEmpty()) { - var stackItem = insertStack.pop(), - node = stackItem.node, - body = stackItem.body; - - if (node.isInternal) { - // This is internal node. Update the total mass of the node and center-of-mass. - var x = body.location.x; - var y = body.location.y; - node.mass = node.mass + body.mass; - node.massX = node.massX + body.mass * x; - node.massY = node.massY + body.mass * y; - - // Recursively insert the body in the appropriate quadrant. - // But first find the appropriate quadrant. - var quadIdx = 0, // Assume we are in the 0's quad. - left = node.left, - right = (node.right + left) / 2, - top = node.top, - bottom = (node.bottom + top) / 2; - - if (x > right) {// somewhere in the eastern part. - quadIdx = quadIdx + 1; - var oldLeft = left; - left = right; - right = right + (right - oldLeft); - } - if (y > bottom) {// and in south. - quadIdx = quadIdx + 2; - var oldTop = top; - top = bottom; - bottom = bottom + (bottom - oldTop); - } - - var child = node.quads[quadIdx]; - if (!child) { - // The node is internal but this quadrant is not taken. Add - // subnode to it. - child = newNode(); - child.left = left; - child.top = top; - child.right = right; - child.bottom = bottom; - - node.quads[quadIdx] = child; - } - - // continue searching in this quadrant. - insertStack.push(child, body); - } else if (node.body) { - // We are trying to add to the leaf node. - // To achieve this we have to convert current leaf into internal node - // and continue adding two nodes. - var oldBody = node.body; - node.body = null; // internal nodes do not cary bodies - node.isInternal = true; - - if (isSamePosition(oldBody.location, body.location)) { - // Prevent infinite subdivision by bumping one node - // slightly. I assume this operation should be quite - // rare, that's why usage of cos()/sin() shouldn't hit performance. - var newX, newY; - do { - var angle = random.nextDouble() * 2 * Math.PI; - var dx = (node.right - node.left) * 0.006 * Math.cos(angle); - var dy = (node.bottom - node.top) * 0.006 * Math.sin(angle); - - newX = oldBody.location.x + dx; - newY = oldBody.location.y + dy; - // Make sure we don't bump it out of the box. If we do, next iteration should fix it - } while (newX < node.left || newX > node.right || - newY < node.top || newY > node.bottom); - - oldBody.location.x = newX; - oldBody.location.y = newY; - } - // Next iteration should subdivide node further. - insertStack.push(node, oldBody); - insertStack.push(node, body); - } else { - // Node has no body. Put it in here. - node.body = body; - } - } - }, - - update = function (sourceBody) { - var queue = updateQueue, - v, - dx, - dy, - r, - queueLength = 1, - shiftIdx = 0, - pushIdx = 1; - - queue[0] = root; - - // TODO: looks like in rare cases this guy has infinite loop bug. To reproduce - // render K1000 (complete(1000)) with the settings: {springLength : 3, springCoeff : 0.0005, - // dragCoeff : 0.02, gravity : -1.2 } - while (queueLength) { - var node = queue[shiftIdx], - body = node.body; - - queueLength -= 1; - shiftIdx += 1; - - if (body && body !== sourceBody) { - // If the current node is an external node (and it is not source body), - // calculate the force exerted by the current node on body, and add this - // amount to body's net force. - dx = body.location.x - sourceBody.location.x; - dy = body.location.y - sourceBody.location.y; - r = Math.sqrt(dx * dx + dy * dy); - - if (r === 0) { - // Poor man's protection agains zero distance. - dx = (random.nextDouble() - 0.5) / 50; - dy = (random.nextDouble() - 0.5) / 50; - r = Math.sqrt(dx * dx + dy * dy); - } - - // This is standard gravition force calculation but we divide - // by r^3 to save two operations when normalizing force vector. - v = gravity * body.mass * sourceBody.mass / (r * r * r); - sourceBody.force.x = sourceBody.force.x + v * dx; - sourceBody.force.y = sourceBody.force.y + v * dy; - } else { - // Otherwise, calculate the ratio s / r, where s is the width of the region - // represented by the internal node, and r is the distance between the body - // and the node's center-of-mass - dx = node.massX / node.mass - sourceBody.location.x; - dy = node.massY / node.mass - sourceBody.location.y; - r = Math.sqrt(dx * dx + dy * dy); - - if (r === 0) { - // Sorry about code duplucation. I don't want to create many functions - // right away. Just want to see performance first. - dx = (random.nextDouble() - 0.5) / 50; - dy = (random.nextDouble() - 0.5) / 50; - r = Math.sqrt(dx * dx + dy * dy); - } - // If s / r < Īø, treat this internal node as a single body, and calculate the - // force it exerts on body b, and add this amount to b's net force. - if ((node.right - node.left) / r < theta) { - // in the if statement above we consider node's width only - // because the region was sqaurified during tree creation. - // Thus there is no difference between using width or height. - v = gravity * node.mass * sourceBody.mass / (r * r * r); - sourceBody.force.x = sourceBody.force.x + v * dx; - sourceBody.force.y = sourceBody.force.y + v * dy; - } else { - // Otherwise, run the procedure recursively on each of the current node's children. - - // I intentionally unfolded this loop, to save several CPU cycles. - if (node.quads[0]) { queue[pushIdx] = node.quads[0]; queueLength += 1; pushIdx += 1; } - if (node.quads[1]) { queue[pushIdx] = node.quads[1]; queueLength += 1; pushIdx += 1; } - if (node.quads[2]) { queue[pushIdx] = node.quads[2]; queueLength += 1; pushIdx += 1; } - if (node.quads[3]) { queue[pushIdx] = node.quads[3]; queueLength += 1; pushIdx += 1; } - } - } - } - }, - - init = function (forceSimulator) { - var x1 = Number.MAX_VALUE, - y1 = Number.MAX_VALUE, - x2 = Number.MIN_VALUE, - y2 = Number.MIN_VALUE, - i, - bodies = forceSimulator.bodies, - max = bodies.length; - - // To reduce quad tree depth we are looking for exact bounding box of all particles. - i = max; - while (i--) { - var x = bodies[i].location.x; - var y = bodies[i].location.y; - if (x < x1) { x1 = x; } - if (x > x2) { x2 = x; } - if (y < y1) { y1 = y; } - if (y > y2) { y2 = y; } - } - - // Squarify the bounds. - var dx = x2 - x1, - dy = y2 - y1; - if (dx > dy) { y2 = y1 + dx; } else { x2 = x1 + dy; } - - currentInCache = 0; - root = newNode(); - root.left = x1; - root.right = x2; - root.top = y1; - root.bottom = y2; - - i = max; - while (i--) { - insert(bodies[i], root); - } - }; - - return { - insert : insert, - init : init, - update : update, - options : function (newOptions) { - if (newOptions) { - if (typeof newOptions.gravity === 'number') { gravity = newOptions.gravity; } - if (typeof newOptions.theta === 'number') { theta = newOptions.theta; } - - return this; - } - - return {gravity : gravity, theta : theta}; - } - }; -}; \ No newline at end of file diff --git a/src/Physics/Forces/springForce.js b/src/Physics/Forces/springForce.js deleted file mode 100644 index 070a608..0000000 --- a/src/Physics/Forces/springForce.js +++ /dev/null @@ -1,46 +0,0 @@ -Viva.Graph.Physics.springForce = function (currentOptions) { - currentOptions = Viva.lazyExtend(currentOptions, { - length : 50, - coeff : 0.00022 - }); - - var random = Viva.random('Random number 4.', 'Chosen by fair dice roll'); - - return { - init : function (forceSimulator) {}, - - update : function (spring) { - var body1 = spring.body1, - body2 = spring.body2, - length = spring.length < 0 ? currentOptions.length : spring.length, - dx = body2.location.x - body1.location.x, - dy = body2.location.y - body1.location.y, - r = Math.sqrt(dx * dx + dy * dy); - - if (r === 0) { - dx = (random.nextDouble() - 0.5) / 50; - dy = (random.nextDouble() - 0.5) / 50; - r = Math.sqrt(dx * dx + dy * dy); - } - - var d = r - length; - var coeff = ((!spring.coeff || spring.coeff < 0) ? currentOptions.coeff : spring.coeff) * d / r * spring.weight; - - body1.force.x += coeff * dx; - body1.force.y += coeff * dy; - - body2.force.x += -coeff * dx; - body2.force.y += -coeff * dy; - }, - - options : function (newOptions) { - if (newOptions) { - if (typeof newOptions.length === 'number') { currentOptions.length = newOptions.length; } - if (typeof newOptions.coeff === 'number') { currentOptions.coeff = newOptions.coeff; } - - return this; - } - return currentOptions; - } - }; -}; diff --git a/src/Physics/eulerIntegrator.js b/src/Physics/eulerIntegrator.js deleted file mode 100644 index 5dec2de..0000000 --- a/src/Physics/eulerIntegrator.js +++ /dev/null @@ -1,47 +0,0 @@ -Viva.Graph.Physics = Viva.Graph.Physics || {}; - -/** - * Updates velocity and position data using the Euler's method. - * It is faster than RK4 but may produce less accurate results. - * - * http://en.wikipedia.org/wiki/Euler_method - */ -Viva.Graph.Physics.eulerIntegrator = function () { - return { - /** - * Performs forces integration, using given timestep and force simulator. - * - * @returns squared distance of total position updates. - */ - integrate : function (simulator, timeStep) { - var speedLimit = simulator.speedLimit, - tx = 0, - ty = 0, - i, - max = simulator.bodies.length; - - for (i = 0; i < max; ++i) { - var body = simulator.bodies[i], - coeff = timeStep / body.mass; - - body.velocity.x += coeff * body.force.x; - body.velocity.y += coeff * body.force.y; - var vx = body.velocity.x, - vy = body.velocity.y, - v = Math.sqrt(vx * vx + vy * vy); - - if (v > speedLimit) { - body.velocity.x = speedLimit * vx / v; - body.velocity.y = speedLimit * vy / v; - } - - tx = timeStep * body.velocity.x; - ty = timeStep * body.velocity.y; - body.location.x += tx; - body.location.y += ty; - } - - return tx * tx + ty * ty; - } - }; -}; diff --git a/src/Physics/forceSimulator.js b/src/Physics/forceSimulator.js deleted file mode 100644 index 3df15c5..0000000 --- a/src/Physics/forceSimulator.js +++ /dev/null @@ -1,163 +0,0 @@ -Viva.Graph.Physics = Viva.Graph.Physics || {}; - -/** - * Manages a simulation of physical forces acting on bodies. - * To create a custom force simulator register forces of the system - * via addForce() method, choos appropriate integrator and register - * bodies. - * - * // TODO: Show example. - */ -Viva.Graph.Physics.forceSimulator = function (forceIntegrator) { - var integrator = forceIntegrator, - bodies = [], // Bodies in this simulation. - springs = [], // Springs in this simulation. - bodyForces = [], // Forces acting on bodies. - springForces = []; // Forces acting on springs. - - return { - - /** - * The speed limit allowed by this simulator. - */ - speedLimit : 1.0, - - /** - * Bodies in this simulation - */ - bodies : bodies, - - /** - * Accumulates all forces acting on the bodies and springs. - */ - accumulate : function () { - var i, j, body; - - // Reinitialize all forces - i = bodyForces.length; - while (i--) { - bodyForces[i].init(this); - } - - i = springForces.length; - while (i--) { - springForces[i].init(this); - } - - // Accumulate forces acting on bodies. - i = bodies.length; - while (i--) { - body = bodies[i]; - body.force.x = 0; - body.force.y = 0; - - for (j = 0; j < bodyForces.length; j++) { - bodyForces[j].update(body); - } - } - - // Accumulate forces acting on springs. - for (i = 0; i < springs.length; ++i) { - for (j = 0; j < springForces.length; j++) { - springForces[j].update(springs[i]); - } - } - }, - - /** - * Runs simulation for one time step. - */ - run : function (timeStep) { - this.accumulate(); - return integrator.integrate(this, timeStep); - }, - - /** - * Adds body to this simulation - * - * @param body - a new body. Bodies expected to have - * mass, force, velocity, location and prevLocation properties. - * the method does not check all this properties, for the sake of performance. - * // TODO: maybe it should check it? - */ - addBody : function (body) { - if (!body) { - throw { - message : 'Cannot add null body to force simulator' - }; - } - - bodies.push(body); // TODO: could mark simulator as dirty... - - return body; - }, - - removeBody : function (body) { - if (!body) { return false; } - - var idx = Viva.Graph.Utils.indexOfElementInArray(body, bodies); - if (idx < 0) { return false; } - - return bodies.splice(idx, 1); - }, - - /** - * Adds a spring to this simulation. - */ - addSpring: function (body1, body2, springLength, springCoefficient, springWeight) { - if (!body1 || !body2) { - throw { - message : 'Cannot add null spring to force simulator' - }; - } - - if (typeof springLength !== 'number') { - throw { - message : 'Spring length should be a number' - }; - } - springWeight = typeof springWeight === 'number' ? springWeight : 1; - - var spring = new Viva.Graph.Physics.Spring(body1, body2, springLength, springCoefficient >= 0 ? springCoefficient : -1, springWeight); - springs.push(spring); - - // TODO: could mark simulator as dirty. - return spring; - }, - - removeSpring : function (spring) { - if (!spring) { return false; } - - var idx = Viva.Graph.Utils.indexOfElementInArray(spring, springs); - if (idx < 0) { return false; } - - return springs.splice(idx, 1); - }, - - /** - * Adds a force acting on all bodies in this simulation - */ - addBodyForce: function (force) { - if (!force) { - throw { - message : 'Cannot add mighty (unknown) force to the simulator' - }; - } - - bodyForces.push(force); - }, - - /** - * Adds a spring force acting on all springs in this simulation. - */ - addSpringForce : function (force) { - if (!force) { - throw { - message : 'Cannot add unknown force to the simulator' - }; - } - - springForces.push(force); - } - }; -}; \ No newline at end of file diff --git a/src/Physics/primitives.js b/src/Physics/primitives.js deleted file mode 100644 index 6f537b2..0000000 --- a/src/Physics/primitives.js +++ /dev/null @@ -1,73 +0,0 @@ -Viva.Graph.Physics = Viva.Graph.Physics || {}; - -Viva.Graph.Physics.Vector = function (x, y) { - this.x = x || 0; - this.y = y || 0; -}; - -Viva.Graph.Physics.Vector.prototype = { - multiply : function (scalar) { - return new Viva.Graph.Physics.Vector(this.x * scalar, this.y * scalar); - } -}; - -Viva.Graph.Physics.Point = function (x, y) { - this.x = x || 0; - this.y = y || 0; -}; - -Viva.Graph.Physics.Point.prototype = { - add : function (point) { - return new Viva.Graph.Physics.Point(this.x + point.x, this.y + point.y); - } -}; - -Viva.Graph.Physics.Body = function () { - this.mass = 1; - this.force = new Viva.Graph.Physics.Vector(); - this.velocity = new Viva.Graph.Physics.Vector(); // For chained call use vel() method. - this.location = new Viva.Graph.Physics.Point(); // For chained calls use loc() method instead. - this.prevLocation = new Viva.Graph.Physics.Point(); // TODO: might be not always needed -}; - -Viva.Graph.Physics.Body.prototype = { - loc : function (location) { - if (location) { - this.location.x = location.x; - this.location.y = location.y; - - return this; - } - - return this.location; - }, - vel : function (velocity) { - if (velocity) { - this.velocity.x = velocity.x; - this.velocity.y = velocity.y; - - return this; - } - - return this.velocity; - } -}; - -Viva.Graph.Physics.Spring = function (body1, body2, length, coeff, weight) { - this.body1 = body1; - this.body2 = body2; - this.length = length; - this.coeff = coeff; - this.weight = weight; -}; - -Viva.Graph.Physics.QuadTreeNode = function () { - this.centerOfMass = new Viva.Graph.Physics.Point(); - this.children = []; - this.body = null; - this.hasChildren = false; - this.x1 = 0; - this.y1 = 0; - this.x2 = 0; - this.y2 = 0; -}; diff --git a/src/Svg/svg.js b/src/Svg/svg.js deleted file mode 100644 index 0a22809..0000000 --- a/src/Svg/svg.js +++ /dev/null @@ -1,124 +0,0 @@ -/** - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com - */ - -/** - * Simple wrapper over svg object model API, to shorten the usage syntax. - */ -Viva.Graph.svg = function (element) { - var svgns = "http://www.w3.org/2000/svg", - xlinkns = "http://www.w3.org/1999/xlink", - svgElement = element; - - if (typeof element === "string") { - svgElement = window.document.createElementNS(svgns, element); - } - - if (svgElement.vivagraphAugmented) { - return svgElement; - } - - svgElement.vivagraphAugmented = true; - - // Augment svg element (TODO: it's not safe - what if some function already exists on the prototype?): - - /** - * Gets an svg attribute from an element if value is not specified. - * OR sets a new value to the given attribute. - * - * @param name - svg attribute name; - * @param value - value to be set; - * - * @returns svg element if both name and value are specified; or value of the given attribute - * if value parameter is missing. - */ - svgElement.attr = function (name, value) { - if (arguments.length === 2) { - if (value !== null) { - svgElement.setAttributeNS(null, name, value); - } else { - svgElement.removeAttributeNS(null, name); - } - - return svgElement; - } - - return svgElement.getAttributeNS(null, name); - }; - - svgElement.append = function (element) { - var child = Viva.Graph.svg(element); - svgElement.appendChild(child); - return child; - }; - - svgElement.text = function (textContent) { - if (typeof textContent !== "undefined") { - svgElement.textContent = textContent; - return svgElement; - } - return svgElement.textContent; - }; - - svgElement.link = function (target) { - if (arguments.length) { - svgElement.setAttributeNS(xlinkns, "xlink:href", target); - return svgElement; - } - - return svgElement.getAttributeNS(xlinkns, "xlink:href"); - }; - - svgElement.children = function (selector) { - var wrappedChildren = [], - childrenCount = svgElement.childNodes.length, - i, - j; - - if (selector === undefined && svgElement.hasChildNodes()) { - for (i = 0; i < childrenCount; i++) { - wrappedChildren.push(Viva.Graph.svg(svgElement.childNodes[i])); - } - } else if (typeof selector === "string") { - var classSelector = (selector[0] === "."), - idSelector = (selector[0] === "#"), - tagSelector = !classSelector && !idSelector; - - for (i = 0; i < childrenCount; i++) { - var el = svgElement.childNodes[i]; - - // pass comments, text nodes etc. - if (el.nodeType === 1) { - var classes = el.attr("class"), - id = el.attr("id"), - tagName = el.nodeName; - - if (classSelector && classes) { - classes = classes.replace(/\s+/g, " ").split(" "); - for (j = 0; j < classes.length; j++) { - if (classSelector && classes[j] === selector.substr(1)) { - wrappedChildren.push(Viva.Graph.svg(el)); - break; - } - } - } else if (idSelector && id === selector.substr(1)) { - wrappedChildren.push(Viva.Graph.svg(el)); - break; - } else if (tagSelector && tagName === selector) { - wrappedChildren.push(Viva.Graph.svg(el)); - } - - wrappedChildren = wrappedChildren.concat(Viva.Graph.svg(el).children(selector)); - } - } - - if (idSelector && wrappedChildren.length === 1) { - return wrappedChildren[0]; - } - } - - return wrappedChildren; - }; - - return svgElement; -}; diff --git a/src/Utils/backwardCompatibleEvents.js b/src/Utils/backwardCompatibleEvents.js new file mode 100644 index 0000000..66f13c6 --- /dev/null +++ b/src/Utils/backwardCompatibleEvents.js @@ -0,0 +1,43 @@ +/** + * This module provides compatibility layer with 0.6.x library. It will be + * removed in the next version + */ + +var events = require('ngraph.events'); + +module.exports = backwardCompatibleEvents; + +function backwardCompatibleEvents(g) { + console.log("This method is deprecated. Please use Viva.events() instead"); + + if (!g) { + return g; + } + + var eventsDefined = (g.on !== undefined) || + (g.off !== undefined) || + (g.fire !== undefined); + + if (eventsDefined) { + // events already defined, ignore + return { + extend: function() { + return g; + }, + on: g.on, + stop: g.off + }; + } + + return { + extend: extend, + on: g.on, + stop: g.off + }; + + function extend() { + var backwardCompatible = events(g); + backwardCompatible.addEventListener = backwardCompatible.on; + return backwardCompatible; + } +} diff --git a/src/Utils/browserInfo.js b/src/Utils/browserInfo.js index 51facb8..691bc34 100644 --- a/src/Utils/browserInfo.js +++ b/src/Utils/browserInfo.js @@ -1,25 +1,27 @@ -Viva.BrowserInfo = (function () { - if (typeof window === "undefined" || !window.hasOwnProperty("navigator")) { - return { - browser : "", - version : "0" - }; - } - - var ua = window.navigator.userAgent.toLowerCase(), - // Useragent RegExp - rwebkit = /(webkit)[ \/]([\w.]+)/, - ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/, - rmsie = /(msie) ([\w.]+)/, - rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/, - match = rwebkit.exec(ua) || - ropera.exec(ua) || - rmsie.exec(ua) || - (ua.indexOf("compatible") < 0 && rmozilla.exec(ua)) || - []; +module.exports = browserInfo(); +function browserInfo() { + if (typeof window === "undefined" || !window.hasOwnProperty("navigator")) { return { - browser: match[1] || "", - version: match[2] || "0" + browser : "", + version : "0" }; -}()); + } + + var ua = window.navigator.userAgent.toLowerCase(), + // Useragent RegExp + rwebkit = /(webkit)[ \/]([\w.]+)/, + ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/, + rmsie = /(msie) ([\w.]+)/, + rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/, + match = rwebkit.exec(ua) || + ropera.exec(ua) || + rmsie.exec(ua) || + (ua.indexOf("compatible") < 0 && rmozilla.exec(ua)) || + []; + + return { + browser: match[1] || "", + version: match[2] || "0" + }; +} diff --git a/src/Utils/documentEvents.js b/src/Utils/documentEvents.js new file mode 100644 index 0000000..df33d0f --- /dev/null +++ b/src/Utils/documentEvents.js @@ -0,0 +1,22 @@ +var nullEvents = require('./nullEvents.js'); + +module.exports = createDocumentEvents(); + +function createDocumentEvents() { + if (typeof document === undefined) { + return nullEvents; + } + + return { + on: on, + off: off + }; +} + +function on(eventName, handler) { + document.addEventListener(eventName, handler); +} + +function off(eventName, handler) { + document.removeEventListener(eventName, handler); +} diff --git a/src/Utils/etc.js b/src/Utils/etc.js deleted file mode 100644 index 90dca2a..0000000 --- a/src/Utils/etc.js +++ /dev/null @@ -1,201 +0,0 @@ -/** - * Extends target object with given fields/values in the options object. - * Unlike jQuery's extend this method does not override target object - * properties if their type matches corresponding type in the options object - */ -Viva.lazyExtend = function (target, options) { - var key; - if (!target) { target = {}; } - if (options) { - for (key in options) { - if (options.hasOwnProperty(key)) { - var targetHasIt = target.hasOwnProperty(key), - optionsValueType = typeof options[key], - shouldReplace = !targetHasIt || (typeof target[key] !== optionsValueType); - - if (shouldReplace) { - target[key] = options[key]; - } else if (optionsValueType === 'object') { - // go deep, don't care about loops here, we are simple API!: - target[key] = Viva.lazyExtend(target[key], options[key]); - } - } - } - } - - return target; -}; -/** - * Implenetation of seeded pseudo random number generator, based on LFIB4 algorithm. - * - * Usage example: - * var random = Viva.random('random seed', 'can', 'be', 'multiple strings'), - * i = random.next(100); // returns random number from [0 .. 100) range. - */ - -Viva.random = function () { - // From http://baagoe.com/en/RandomMusings/javascript/ - function getMash() { - var n = 0xefc8249d; - - var mash = function (data) { - var i; - data = data.toString(); - for (i = 0; i < data.length; i++) { - n += data.charCodeAt(i); - var h = 0.02519603282416938 * n; - n = h >>> 0; - h -= n; - h *= n; - n = h >>> 0; - h -= n; - n += h * 0x100000000; // 2^32 - } - return (n >>> 0) * 2.3283064365386963e-10; // 2^-32 - }; - - mash.version = 'Mash 0.9'; - return mash; - } - - function LFIB4(args) { - return (function (args) { - // George Marsaglia's LFIB4, - //http://groups.google.com/group/sci.crypt/msg/eb4ddde782b17051 - var k0 = 0, - k1 = 58, - k2 = 119, - k3 = 178, - j, - i, - s = [], - mash = getMash(); - - if (args.length === 0) { - args = [+new Date()]; - } - - for (j = 0; j < 256; j++) { - s[j] = mash(' '); - s[j] -= mash(' ') * 4.76837158203125e-7; // 2^-21 - if (s[j] < 0) { - s[j] += 1; - } - } - - for (i = 0; i < args.length; i++) { - for (j = 0; j < 256; j++) { - s[j] -= mash(args[i]); - s[j] -= mash(args[i]) * 4.76837158203125e-7; // 2^-21 - if (s[j] < 0) { - s[j] += 1; - } - } - } - - mash = null; - - var random = function () { - var x; - - k0 = (k0 + 1) & 255; - k1 = (k1 + 1) & 255; - k2 = (k2 + 1) & 255; - k3 = (k3 + 1) & 255; - - x = s[k0] - s[k1]; - if (x < 0) { - x += 1; - } - x -= s[k2]; - if (x < 0) { - x += 1; - } - x -= s[k3]; - if (x < 0) { - x += 1; - } - - s[k0] = x; - return x; - }; - - random.uint32 = function () { - return random() * 0x100000000 >>> 0; // 2^32 - }; - random.fract53 = random; - random.version = 'LFIB4 0.9'; - random.args = args; - - return random; - }(args)); - } - - var randomFunc = new LFIB4(Array.prototype.slice.call(arguments)); - - return { - /** - * Generates random integer number in the range from 0 (inclusive) to maxValue (exclusive) - * - * @param maxValue is REQUIRED. Ommitit this numbe will result in NaN values from PRNG. - */ - next : function (maxValue) { - return Math.floor(randomFunc() * maxValue); - }, - - /** - * Generates random double number in the range from 0 (inclusive) to 1 (exclusive) - * This function is the same as Math.random() (except that it could be seeded) - */ - nextDouble : function () { - return randomFunc(); - } - }; -}; - -/** - * Iterates over array in arbitrary order. The iterator modifies actual array content. - * It's based on modern version of Fisher–Yates shuffle algorithm. - * - * @see http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm - * - * @param array to be shuffled - * @param random - a [seeded] random number generator to produce same sequences. This parameter - * is optional. If you don't need determenistic randomness keep it blank. - */ -Viva.randomIterator = function (array, random) { - random = random || Viva.random(); - - return { - forEach : function (callback) { - var i, j, t; - for (i = array.length - 1; i > 0; --i) { - j = random.next(i + 1); // i inclusive - t = array[j]; - array[j] = array[i]; - array[i] = t; - - callback(t); - } - - if (array.length) { - callback(array[0]); - } - }, - - /** - * Shuffles array randomly. - */ - shuffle : function () { - var i, j, t; - for (i = array.length - 1; i > 0; --i) { - j = random.next(i + 1); // i inclusive - t = array[j]; - array[j] = array[i]; - array[i] = t; - } - - return array; - } - }; -}; diff --git a/src/Utils/events.js b/src/Utils/events.js deleted file mode 100644 index f8f3b40..0000000 --- a/src/Utils/events.js +++ /dev/null @@ -1,149 +0,0 @@ -/** - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com - */ - -Viva.Graph.Utils = Viva.Graph.Utils || {}; - -// TODO: I don't really like the way I implemented events. It looks clumsy and -// hard to understand. Refactor it. - -// TODO: This is really painful. Please don't use this class anymore, I will -// definitely depricate it or update its interface. - -/** - * Allows to start/stop listen to element's events. An element can be arbitrary - * DOM element, or object with eventuality behavior. - * - * To add eventuality behavior to arbitrary object 'obj' call - * Viva.Graph.Utils.event(obj).extend() method. - * After this call is made the object can use obj.fire(eventName, params) method, and listeners - * can listen to event by Viva.Graph.Utils.events(obj).on(eventName, callback) method. - */ -Viva.Graph.Utils.events = function (element) { - - /** - * Extends arbitrary object with fire method and allows it to be used with on/stop methods. - * - * This behavior is based on Crockford's eventuality example, but with a minor changes: - * - fire() method accepts parameters to pass to callbacks (instead of setting them in 'on' method) - * - on() method is replaced with addEventListener(), to let objects be used as a DOM objects. - * - behavoir contract is simplified to "string as event name"/"function as callback" convention. - * - removeEventListener() method added to let unsubscribe from events. - */ - var eventuality = function (that) { - var registry = {}; - - /** - * Fire an event on an object. The event is a string containing the name of the event - * Handlers registered by the 'addEventListener' method that match the event name - * will be invoked. - */ - that.fire = function (eventName, parameters) { - var registeredHandlers, - callback, - handler, - i; - - if (typeof eventName !== "string") { - throw "Only strings can be used as even type"; - } - - // If an array of handlers exist for this event, then - // loop through it and execute the handlers in order. - if (registry.hasOwnProperty(eventName)) { - registeredHandlers = registry[eventName]; - for (i = 0; i < registeredHandlers.length; ++i) { - handler = registeredHandlers[i]; - callback = handler.method; - callback(parameters); - } - } - - return this; - }; - - that.addEventListener = function (eventName, callback) { - if (typeof callback !== "function") { - throw "Only functions allowed to be callbacks"; - } - - var handler = { - method: callback - }; - if (registry.hasOwnProperty(eventName)) { - registry[eventName].push(handler); - } else { - registry[eventName] = [handler]; - } - - return this; - }; - - that.removeEventListener = function (eventName, callback) { - if (typeof callback !== "function") { - throw "Only functions allowed to be callbacks"; - } - - if (registry.hasOwnProperty(eventName)) { - var handlers = registry[eventName], - i; - - for (i = 0; i < handlers.length; ++i) { - if (handlers[i].callback === callback) { - handlers.splice(i); - break; - } - } - } - - return this; - }; - - that.removeAllListeners = function () { - var eventName; - for (eventName in registry) { - if (registry.hasOwnProperty(eventName)) { - delete registry[eventName]; - } - } - }; - - return that; - }; - - return { - /** - * Registes callback to be called when element fires event with given event name. - */ - on : function (eventName, callback) { - if (element.addEventListener) {// W3C DOM and eventuality objecets. - element.addEventListener(eventName, callback, false); - } else if (element.attachEvent) { // IE DOM - element.attachEvent("on" + eventName, callback); - } - - return this; - }, - - /** - * Unsubcribes from object's events. - */ - stop : function (eventName, callback) { - if (element.removeEventListener) { - element.removeEventListener(eventName, callback, false); - } else if (element.detachEvent) { - element.detachEvent("on" + eventName, callback); - } - }, - - /** - * Adds eventuality to arbitrary JavaScript object. Eventuality adds - * fire(), addEventListner() and removeEventListners() to the target object. - * - * This is required if you want to use object with on(), stop() methods. - */ - extend : function () { - return eventuality(element); - } - }; -}; \ No newline at end of file diff --git a/src/Utils/findElementPosition.js b/src/Utils/findElementPosition.js new file mode 100644 index 0000000..ff260da --- /dev/null +++ b/src/Utils/findElementPosition.js @@ -0,0 +1,17 @@ +/** + * Finds the absolute position of an element on a page + */ +module.exports = findElementPosition; + +function findElementPosition(obj) { + var curleft = 0, + curtop = 0; + if (obj.offsetParent) { + do { + curleft += obj.offsetLeft; + curtop += obj.offsetTop; + } while ((obj = obj.offsetParent) !== null); + } + + return [curleft, curtop]; +} diff --git a/src/Utils/geom.js b/src/Utils/geom.js deleted file mode 100644 index 4c4117a..0000000 --- a/src/Utils/geom.js +++ /dev/null @@ -1,186 +0,0 @@ -Viva.Graph.geom = function () { - - return { - // function from Graphics GEM to determine lines intersection: - // http://www.opensource.apple.com/source/graphviz/graphviz-498/graphviz/dynagraph/common/xlines.c - intersect : function (x1, y1, x2, y2, // first line segment - x3, y3, x4, y4) { // second line segment - var a1, a2, b1, b2, c1, c2, /* Coefficients of line eqns. */ - r1, r2, r3, r4, /* 'Sign' values */ - denom, offset, num, /* Intermediate values */ - result = { x: 0, y : 0}; - - /* Compute a1, b1, c1, where line joining points 1 and 2 - * is "a1 x + b1 y + c1 = 0". - */ - a1 = y2 - y1; - b1 = x1 - x2; - c1 = x2 * y1 - x1 * y2; - - /* Compute r3 and r4. - */ - r3 = a1 * x3 + b1 * y3 + c1; - r4 = a1 * x4 + b1 * y4 + c1; - - /* Check signs of r3 and r4. If both point 3 and point 4 lie on - * same side of line 1, the line segments do not intersect. - */ - - if (r3 !== 0 && r4 !== 0 && ((r3 >= 0) === (r4 >= 4))) { - return null; //no itersection. - } - - /* Compute a2, b2, c2 */ - a2 = y4 - y3; - b2 = x3 - x4; - c2 = x4 * y3 - x3 * y4; - - /* Compute r1 and r2 */ - - r1 = a2 * x1 + b2 * y1 + c2; - r2 = a2 * x2 + b2 * y2 + c2; - - /* Check signs of r1 and r2. If both point 1 and point 2 lie - * on same side of second line segment, the line segments do - * not intersect. - */ - if (r1 !== 0 && r2 !== 0 && ((r1 >= 0) === (r2 >= 0))) { - return null; // no intersection; - } - /* Line segments intersect: compute intersection point. - */ - - denom = a1 * b2 - a2 * b1; - if (denom === 0) { - return null; // Actually collinear.. - } - - offset = denom < 0 ? -denom / 2 : denom / 2; - offset = 0.0; - - /* The denom/2 is to get rounding instead of truncating. It - * is added or subtracted to the numerator, depending upon the - * sign of the numerator. - */ - - - num = b1 * c2 - b2 * c1; - result.x = (num < 0 ? num - offset : num + offset) / denom; - - num = a2 * c1 - a1 * c2; - result.y = (num < 0 ? num - offset : num + offset) / denom; - - return result; - }, - - /** - * Returns intersection point of the rectangle defined by - * left, top, right, bottom and a line starting in x1, y1 - * and ending in x2, y2; - */ - intersectRect : function (left, top, right, bottom, x1, y1, x2, y2) { - return this.intersect(left, top, left, bottom, x1, y1, x2, y2) || - this.intersect(left, bottom, right, bottom, x1, y1, x2, y2) || - this.intersect(right, bottom, right, top, x1, y1, x2, y2) || - this.intersect(right, top, left, top, x1, y1, x2, y2); - }, - - convexHull : function (points) { - var polarAngleSort = function (basePoint, points) { - var cosAngle = function (p) { - var dx = p.x - basePoint.x, - dy = p.y - basePoint.y, - sign = dx > 0 ? 1 : -1; - - // We use squared dx, to avoid Sqrt opertion and improve performance. - // To avoid sign loss during dx * dx operation we precompute its sign: - return sign * dx * dx / (dx * dx + dy * dy); - }, - - sortedPoints = points.sort(function (p1, p2) { - return cosAngle(p2) - cosAngle(p1); - }), - - // If more than one point has the same angle, remove all but the one that is farthest from basePoint: - lastPoint = sortedPoints[0], - lastAngle = cosAngle(lastPoint), - dx = lastPoint.x - basePoint.x, - dy = lastPoint.y - basePoint.y, - lastDistance = dx * dx + dy * dy, - curDistance, - i; - - for (i = 1; i < sortedPoints.length; ++i) { - lastPoint = sortedPoints[i]; - var angle = cosAngle(lastPoint); - if (angle === lastAngle) { - dx = lastPoint.x - basePoint.x; - dy = lastPoint.y - basePoint.y; - curDistance = dx * dx + dy * dy; - - if (curDistance < lastDistance) { - sortedPoints.splice(i, 1); - } else { - sortedPoints.splice(i - 1, 1); - } - } else { - lastAngle = angle; - } - } - - return sortedPoints; - }, - - /** - * Returns true if angle formed by points p0, p1, p2 makes left turn. - * (counterclockwise) - */ - ccw = function (p0, p1, p2) { - return ((p2.x - p0.x) * (p1.y - p0.y) - (p2.y - p0.y) * (p1.x - p0.x)) < 0; - }; - - if (points.length < 3) { - return points; // This one is easy... Not precise, but should be enough for now. - } - - // let p0 be the point in Q with the minimum y-coordinate, or the leftmost - // such point in case of a tie - var p0Idx = 0, - i; - for (i = 0; i < points.length; ++i) { - if (points[i].y < points[p0Idx].y) { - p0Idx = i; - } else if (points[i].y === points[p0Idx].y && points[i].x < points[p0Idx].x) { - p0Idx = i; - } - } - - var p0 = points[p0Idx]; - // let be the remaining points - points.splice(p0Idx, 1); - // sorted by polar angle in counterclockwise order around p0 - var sortedPoints = polarAngleSort(p0, points); - if (sortedPoints.length < 2) { - return sortedPoints; - } - - // let S be empty stack - var s = []; - s.push(p0); - s.push(sortedPoints[0]); - s.push(sortedPoints[1]); - var sLength = s.length; - for (i = 2; i < sortedPoints.length; ++i) { - while (!ccw(s[sLength - 2], s[sLength - 1], sortedPoints[i])) { - s.pop(); - sLength -= 1; - } - - s.push(sortedPoints[i]); - sLength += 1; - } - - return s; - } - }; -}; \ No newline at end of file diff --git a/src/Utils/getDimensions.js b/src/Utils/getDimensions.js index 0d547d5..20c4e56 100644 --- a/src/Utils/getDimensions.js +++ b/src/Utils/getDimensions.js @@ -1,6 +1,6 @@ -Viva.Graph.Utils = Viva.Graph.Utils || {}; +module.exports = getDimension; -Viva.Graph.Utils.getDimension = function (container) { +function getDimension(container) { if (!container) { throw { message : 'Cannot get dimensions of undefined container' @@ -17,20 +17,4 @@ Viva.Graph.Utils.getDimension = function (container) { width : width, height : height }; -}; - -/** - * Finds the absolute position of an element on a page - */ -Viva.Graph.Utils.findElementPosition = function (obj) { - var curleft = 0, - curtop = 0; - if (obj.offsetParent) { - do { - curleft += obj.offsetLeft; - curtop += obj.offsetTop; - } while ((obj = obj.offsetParent) !== null); - } - - return [curleft, curtop]; -}; \ No newline at end of file +} diff --git a/src/Utils/indexOf.js b/src/Utils/indexOf.js deleted file mode 100644 index 7f65044..0000000 --- a/src/Utils/indexOf.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com - */ - -Viva.Graph.Utils = Viva.Graph.Utils || {}; - -Viva.Graph.Utils.indexOfElementInArray = function (element, array) { - if (array.indexOf) { - return array.indexOf(element); - } - - var len = array.length, - i; - - for (i = 0; i < len; i += 1) { - if (array.hasOwnProperty(i) && (array[i] === element)) { - return i; - } - } - - return -1; -}; diff --git a/src/Utils/intersectRect.js b/src/Utils/intersectRect.js new file mode 100644 index 0000000..2830f07 --- /dev/null +++ b/src/Utils/intersectRect.js @@ -0,0 +1,10 @@ +var intersect = require('gintersect'); + +module.exports = intersectRect; + +function intersectRect(left, top, right, bottom, x1, y1, x2, y2) { + return intersect(left, top, left, bottom, x1, y1, x2, y2) || + intersect(left, bottom, right, bottom, x1, y1, x2, y2) || + intersect(right, bottom, right, top, x1, y1, x2, y2) || + intersect(right, top, left, top, x1, y1, x2, y2); +} diff --git a/src/Utils/nullEvents.js b/src/Utils/nullEvents.js new file mode 100644 index 0000000..d869675 --- /dev/null +++ b/src/Utils/nullEvents.js @@ -0,0 +1,11 @@ +module.exports = createNullEvents(); + +function createNullEvents() { + return { + on: noop, + off: noop, + stop: noop + }; +} + +function noop() { } diff --git a/src/Utils/rect.js b/src/Utils/rect.js new file mode 100644 index 0000000..dd201e1 --- /dev/null +++ b/src/Utils/rect.js @@ -0,0 +1,11 @@ +module.exports = Rect; + +/** + * Very generic rectangle. + */ +function Rect (x1, y1, x2, y2) { + this.x1 = x1 || 0; + this.y1 = y1 || 0; + this.x2 = x2 || 0; + this.y2 = y2 || 0; +} diff --git a/src/Utils/timer.js b/src/Utils/timer.js index d00cbc2..e4b8399 100644 --- a/src/Utils/timer.js +++ b/src/Utils/timer.js @@ -1,79 +1,92 @@ /** - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com + * @author Andrei Kashcha (aka anvaka) / https://github.com/anvaka */ -Viva.Graph.Utils = Viva.Graph.Utils || {}; - -(function () { - var lastTime = 0, - vendors = ['ms', 'moz', 'webkit', 'o'], - i, - scope; - - if (typeof window !== 'undefined') { - scope = window; - } else if (typeof global !== 'undefined') { - scope = global; - } else { - scope = { - setTimeout: function () {}, - clearTimeout: function () {} - }; - } - for (i = 0; i < vendors.length && !scope.requestAnimationFrame; ++i) { - var vendorPrefix = vendors[i]; - scope.requestAnimationFrame = scope[vendorPrefix + 'RequestAnimationFrame']; - scope.cancelAnimationFrame = - scope[vendorPrefix + 'CancelAnimationFrame'] || scope[vendorPrefix + 'CancelRequestAnimationFrame']; +module.exports = createTimer(); + +function createTimer() { + var lastTime = 0, + vendors = ['ms', 'moz', 'webkit', 'o'], + i, + scope; + + if (typeof window !== 'undefined') { + scope = window; + } else if (typeof global !== 'undefined') { + scope = global; + } else { + scope = { + setTimeout: noop, + clearTimeout: noop + }; + } + + for (i = 0; i < vendors.length && !scope.requestAnimationFrame; ++i) { + var vendorPrefix = vendors[i]; + scope.requestAnimationFrame = scope[vendorPrefix + 'RequestAnimationFrame']; + scope.cancelAnimationFrame = + scope[vendorPrefix + 'CancelAnimationFrame'] || scope[vendorPrefix + 'CancelRequestAnimationFrame']; + } + + if (!scope.requestAnimationFrame) { + scope.requestAnimationFrame = rafPolyfill; + } + + if (!scope.cancelAnimationFrame) { + scope.cancelAnimationFrame = cancelRafPolyfill; + } + + return timer; + + /** + * Timer that fires callback with given interval (in ms) until + * callback returns true; + */ + function timer(callback) { + var intervalId; + startTimer(); // start it right away. + + return { + /** + * Stops execution of the callback + */ + stop: stopTimer, + + restart: restart + }; + + function startTimer() { + intervalId = scope.requestAnimationFrame(startTimer); + if (!callback()) { + stopTimer(); + } } - if (!scope.requestAnimationFrame) { - scope.requestAnimationFrame = function (callback) { - var currTime = new Date().getTime(); - var timeToCall = Math.max(0, 16 - (currTime - lastTime)); - var id = scope.setTimeout(function () { callback(currTime + timeToCall); }, timeToCall); - lastTime = currTime + timeToCall; - return id; - }; + function stopTimer() { + scope.cancelAnimationFrame(intervalId); + intervalId = 0; } - if (!scope.cancelAnimationFrame) { - scope.cancelAnimationFrame = function (id) { - scope.clearTimeout(id); - }; + function restart() { + if (!intervalId) { + startTimer(); + } } + } - /** - * Timer that fires callback with given interval (in ms) until - * callback returns true; - */ - Viva.Graph.Utils.timer = function (callback) { - var intervalId, - stopTimer = function () { - scope.cancelAnimationFrame(intervalId); - intervalId = 0; - }, - - startTimer = function () { - intervalId = scope.requestAnimationFrame(startTimer); - if (!callback()) { - stopTimer(); - } - }; - - startTimer(); // start it right away. - - return { - /** - * Stops execution of the callback - */ - stop: stopTimer, - - restart : function () { - if (!intervalId) { - startTimer(); - } - } - }; - }; -}()); \ No newline at end of file + function rafPolyfill(callback) { + var currTime = new Date().getTime(); + var timeToCall = Math.max(0, 16 - (currTime - lastTime)); + var id = scope.setTimeout(function() { + callback(currTime + timeToCall); + }, timeToCall); + lastTime = currTime + timeToCall; + return id; + } + + function cancelRafPolyfill(id) { + scope.clearTimeout(id); + } +} + +function noop() {} diff --git a/src/Utils/windowEvents.js b/src/Utils/windowEvents.js new file mode 100644 index 0000000..8f01588 --- /dev/null +++ b/src/Utils/windowEvents.js @@ -0,0 +1,23 @@ +var nullEvents = require('./nullEvents.js'); + +module.exports = createDocumentEvents(); + +function createDocumentEvents() { + if (typeof window === 'undefined') { + return nullEvents; + } + + return { + on: on, + off: off + }; +} + +function on(eventName, handler) { + window.addEventListener(eventName, handler); +} + +function off(eventName, handler) { + window.removeEventListener(eventName, handler); +} + diff --git a/src/View/cssGraphics.js b/src/View/cssGraphics.js deleted file mode 100644 index 0a2fe86..0000000 --- a/src/View/cssGraphics.js +++ /dev/null @@ -1,323 +0,0 @@ -/** - * @fileOverview Defines a graph renderer that uses CSS based drawings. - * - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com - */ -// The file tries to conform generic interface: -/*jshint unused: false */ - -Viva.Graph.View = Viva.Graph.View || {}; - -/** - * Performs css-based graph rendering. This module does not perform - * layout, but only visualizes nodes and edeges of the graph. - * - * NOTE: Most likely I will remove this graphics engine due to superior svg support. - * In certain cases it doesn't work and require further imporvments: - * * does not properly work for dragging. - * * does not support scaling. - * * does not support IE versions prior to IE9. - * - */ -Viva.Graph.View.cssGraphics = function () { - var container, // Where graph will be rendered - OLD_IE = "OLD_IE", - offsetX, - offsetY, - scaleX = 1, - scaleY = 1, - - transformName = (function () { - var browserName = Viva.BrowserInfo.browser, - prefix, - version; - - switch (browserName) { - case "mozilla": - prefix = "Moz"; - break; - case "webkit": - prefix = "webkit"; - break; - case "opera": - prefix = "O"; - break; - case "msie": - version = Viva.BrowserInfo.version.split(".")[0]; - if (version > 8) { - prefix = "ms"; - } else { - return OLD_IE; - } - break; - } - - if (prefix) { // CSS3 - return prefix + "Transform"; - } - // Unknown browser - return null; - }()), - - /** - * Returns a function (ui, x, y, angleRad). - * - * The function attempts to rotate "ui" dom element on "angleRad" radians - * and position it to "x" "y" coordinates. - * - * Operation works in most modern browsers that support transform css style - * and IE. - * */ - positionLink = (function () { - if (transformName === OLD_IE) { // This is old IE, use filters - return function (ui, x, y, angleRad) { - var cos = Math.cos(angleRad), - sin = Math.sin(angleRad); - - // IE 6, 7 and 8 are screwed up when it comes to transforms; - // I could not find justification for their choice of "floating" - // matrix transform origin. The following ugly code was written - // out of complete dispair. - if (angleRad < 0) { - angleRad = 2 * Math.PI + angleRad; - } - - if (angleRad < Math.PI / 2) { - ui.style.left = x + "px"; - ui.style.top = y + "px"; - } else if (angleRad < Math.PI) { - ui.style.left = x - (ui.clientWidth) * Math.cos(Math.PI - angleRad); - ui.style.top = y; - } else if (angleRad < (Math.PI + Math.PI / 2)) { - ui.style.left = x - (ui.clientWidth) * Math.cos(Math.PI - angleRad); - ui.style.top = y + (ui.clientWidth) * Math.sin(Math.PI - angleRad); - } else { - ui.style.left = x; - ui.style.top = y + ui.clientWidth * Math.sin(Math.PI - angleRad); - } - ui.style.filter = "progid:DXImageTransform.Microsoft.Matrix(sizingMethod=\"auto expand\"," + "M11=" + cos + ", M12=" + (-sin) + "," + "M21=" + sin + ", M22=" + cos + ");"; - }; - } - - if (transformName) { // Modern CSS3 browser - return function (ui, x, y, angleRad) { - ui.style.left = x + "px"; - ui.style.top = y + "px"; - - ui.style[transformName] = "rotate(" + angleRad + "rad)"; - ui.style[transformName + "Origin"] = "left"; - }; - } - - return function (ui, x, y, angleRad) { - // Don't know how to rotate links in other browsers. - }; - }()), - - nodeBuilder = function (node) { - var nodeUI = window.document.createElement("div"); - nodeUI.setAttribute("class", "node"); - return nodeUI; - }, - - nodePositionCallback = function (nodeUI, pos) { - // TODO: Remove magic 5. It should be half of the width or height of the node. - nodeUI.style.left = pos.x - 5 + "px"; - nodeUI.style.top = pos.y - 5 + "px"; - }, - - linkPositionCallback = function (linkUI, fromPos, toPos) { - var dx = fromPos.x - toPos.x, - dy = fromPos.y - toPos.y, - length = Math.sqrt(dx * dx + dy * dy); - - linkUI.style.height = "1px"; - linkUI.style.width = length + "px"; - - positionLink(linkUI, toPos.x, toPos.y, Math.atan2(dy, dx)); - }, - - linkBuilder = function (link) { - var linkUI = window.document.createElement("div"); - linkUI.setAttribute("class", "link"); - - return linkUI; - }, - - updateTransform = function () { - if (container) { - if (transformName && transformName !== OLD_IE) { - var transform = "matrix(" + scaleX + ", 0, 0," + scaleY + "," + offsetX + "," + offsetY + ")"; - container.style[transformName] = transform; - } else { - throw "Not implemented. TODO: Implement OLD_IE Filter based transform"; - } - } - }; - - return { - /** - * Sets the collback that creates node representation or creates a new node - * presentation if builderCallbackOrNode is not a function. - * - * @param builderCallbackOrNode a callback function that accepts graph node - * as a parameter and must return an element representing this node. OR - * if it's not a function it's treated as a node to which DOM element should be created. - * - * @returns If builderCallbackOrNode is a valid callback function, instance of this is returned; - * Otherwise a node representation is returned for the passed parameter. - */ - node : function (builderCallbackOrNode) { - if (builderCallbackOrNode && typeof builderCallbackOrNode !== "function") { - return nodeBuilder(builderCallbackOrNode); - } - - nodeBuilder = builderCallbackOrNode; - - return this; - }, - - /** - * Sets the collback that creates link representation or creates a new link - * presentation if builderCallbackOrLink is not a function. - * - * @param builderCallbackOrLink a callback function that accepts graph link - * as a parameter and must return an element representing this link. OR - * if it's not a function it's treated as a link to which DOM element should be created. - * - * @returns If builderCallbackOrLink is a valid callback function, instance of this is returned; - * Otherwise a link representation is returned for the passed parameter. - */ - link : function (builderCallbackOrLink) { - if (builderCallbackOrLink && typeof builderCallbackOrLink !== "function") { - return linkBuilder(builderCallbackOrLink); - } - - linkBuilder = builderCallbackOrLink; - return this; - }, - - /** - * Default input manager listens to DOM events to process nodes drag-n-drop - */ - inputManager : Viva.Input.domInputManager, - - /** - * Sets translate operation that should be applied to all nodes and links. - */ - graphCenterChanged : function (x, y) { - offsetX = x; - offsetY = y; - updateTransform(); - }, - - translateRel : function (dx, dy) { - offsetX += dx; - offsetY += dy; - updateTransform(); - }, - - scale : function (x, y) { - // TODO: implement me - return 1; - }, - - resetScale : function () { - // TODO: implement me - return this; - }, - - /** - * Called every before renderer starts rendering. - */ - beginRender : function () {}, - - /** - * Called every time when renderer finishes one step of rendering. - */ - endRender : function () {}, - /** - * Allows to override default position setter for the node with a new - * function. newPlaceCallback(node, position) is function which - * is used by updateNode(). - */ - placeNode : function (newPlaceCallback) { - nodePositionCallback = newPlaceCallback; - return this; - }, - - placeLink : function (newPlaceLinkCallback) { - linkPositionCallback = newPlaceLinkCallback; - return this; - }, - - /** - * Called by Viva.Graph.View.renderer to let concrete graphic output - * providers prepare to render. - */ - init : function (parentContainer) { - container = parentContainer; - updateTransform(); - }, - - /** - * Called by Viva.Graph.View.renderer to let concrete graphic output - * provider prepare to render given link of the graph - * - * @param linkUI visual representation of the link created by link() execution. - */ - initLink : function (linkUI) { - if (container.childElementCount > 0) { - container.insertBefore(linkUI, container.firstChild); - } else { - container.appendChild(linkUI); - } - }, - - /** - * Called by Viva.Graph.View.renderer to let concrete graphic output - * provider remove link from rendering surface. - * - * @param linkUI visual representation of the link created by link() execution. - **/ - releaseLink : function (linkUI) { - container.removeChild(linkUI); - }, - - /** - * Called by Viva.Graph.View.renderer to let concrete graphic output - * provider prepare to render given node of the graph. - * - * @param nodeUI visual representation of the node created by node() execution. - **/ - initNode : function (nodeUI) { - container.appendChild(nodeUI); - }, - - /** - * Called by Viva.Graph.View.renderer to let concrete graphic output - * provider remove node from rendering surface. - * - * @param nodeUI visual representation of the node created by node() execution. - **/ - releaseNode : function (nodeUI) { - container.removeChild(nodeUI); - }, - - /** - * Called by Viva.Graph.View.renderer to let concrete graphic output - * provider place given node to recommended position pos {x, y}; - */ - updateNodePosition : function (node, pos) { - nodePositionCallback(node, pos); - }, - - /** - * Called by Viva.Graph.View.renderer to let concrete graphic output - * provider place given link of the graph - */ - updateLinkPosition : function (link, fromPos, toPos) { - linkPositionCallback(link, fromPos, toPos); - } - }; -}; diff --git a/src/View/renderer.js b/src/View/renderer.js index 68926f8..16a4eb6 100644 --- a/src/View/renderer.js +++ b/src/View/renderer.js @@ -1,10 +1,19 @@ /** * @fileOverview Defines a graph renderer that uses CSS based drawings. * - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com + * @author Andrei Kashcha (aka anvaka) / https://github.com/anvaka */ -Viva.Graph.View = Viva.Graph.View || {}; +module.exports = renderer; + +var eventify = require('ngraph.events'); +var forceDirected = require('ngraph.forcelayout'); +var svgGraphics = require('./svgGraphics.js'); +var windowEvents = require('../Utils/windowEvents.js'); +var domInputManager = require('../Input/domInputManager.js'); +var timer = require('../Utils/timer.js'); +var getDimension = require('../Utils/getDimensions.js'); +var dragndrop = require('../Input/dragndrop.js'); /** * This is heart of the rendering. Class accepts graph to be rendered and rendering settings. @@ -17,7 +26,6 @@ Viva.Graph.View = Viva.Graph.View || {}; * // all graphics has to correspond to defined interface and can be later easily * // replaced for specific needs (e.g. adding WebGL should be piece of cake as long * // as WebGL has implemented required interface). See svgGraphics for example. - * // NOTE: current version supports Viva.Graph.View.cssGraphics() as well. * graphics : Viva.Graph.View.svgGraphics(), * * // Where the renderer should draw graph. Container size matters, because @@ -25,10 +33,12 @@ Viva.Graph.View = Viva.Graph.View || {}; * // might depend on it. * container : document.body, * + * // Defines whether graph can respond to use input + * interactive: true, + * * // Layout algorithm to be used. The algorithm is expected to comply with defined * // interface and is expected to be iterative. Renderer will use it then to calculate - * // grpaph's layout. For examples of the interface refer to Viva.Graph.Layout.forceDirected() - * // and Viva.Graph.Layout.gem() algorithms. + * // graph's layout. For examples of the interface refer to Viva.Graph.Layout.forceDirected() * layout : Viva.Graph.Layout.forceDirected(), * * // Directs renderer to display links. Usually rendering links is the slowest part of this @@ -36,427 +46,447 @@ Viva.Graph.View = Viva.Graph.View || {}; * renderLinks : true, * * // Number of layout iterations to run before displaying the graph. The bigger you set this number - * // the closer to ideal position graph will apper first time. But be careful: for large graphs + * // the closer to ideal position graph will appear first time. But be careful: for large graphs * // it can freeze the browser. * prerender : 0 * } */ -Viva.Graph.View.renderer = function (graph, settings) { - // TODO: This class is getting hard to understand. Consider refactoring. - // TODO: I have a technical debt here: fix scaling/recentring! Currently it's total mess. - var FRAME_INTERVAL = 30; - - settings = settings || {}; - - var layout = settings.layout, - graphics = settings.graphics, - container = settings.container, - inputManager, - animationTimer, - rendererInitialized = false, - updateCenterRequired = true, - - currentStep = 0, - totalIterationsCount = 0, - isStable = false, - userInteraction = false, - - viewPortOffset = { - x : 0, - y : 0 - }, - - transform = { - offsetX : 0, - offsetY : 0, - scale : 1 - }; - - var prepareSettings = function () { - container = container || window.document.body; - layout = layout || Viva.Graph.Layout.forceDirected(graph); - graphics = graphics || Viva.Graph.View.svgGraphics(graph, {container : container}); - - if (!settings.hasOwnProperty('renderLinks')) { - settings.renderLinks = true; - } - - settings.prerender = settings.prerender || 0; - inputManager = (graphics.inputManager || Viva.Input.domInputManager)(graph, graphics); - }, - // Cache positions object to prevent GC pressure - cachedFromPos = {x : 0, y : 0, node: null}, - cachedToPos = {x : 0, y : 0, node: null}, - cachedNodePos = { x: 0, y: 0}, - windowEvents = Viva.Graph.Utils.events(window), - publicEvents = Viva.Graph.Utils.events({}).extend(), - graphEvents, - containerDrag, - - - renderLink = function (link) { - var fromNode = graph.getNode(link.fromId), - toNode = graph.getNode(link.toId); - - if (!fromNode || !toNode) { - return; - } - - cachedFromPos.x = fromNode.position.x; - cachedFromPos.y = fromNode.position.y; - cachedFromPos.node = fromNode; - - cachedToPos.x = toNode.position.x; - cachedToPos.y = toNode.position.y; - cachedToPos.node = toNode; - - graphics.updateLinkPosition(link.ui, cachedFromPos, cachedToPos); - }, - - renderNode = function (node) { - cachedNodePos.x = node.position.x; - cachedNodePos.y = node.position.y; - - graphics.updateNodePosition(node.ui, cachedNodePos); - }, - - renderGraph = function () { - graphics.beginRender(); - if (settings.renderLinks && !graphics.omitLinksRendering) { - graph.forEachLink(renderLink); - } - - graph.forEachNode(renderNode); - graphics.endRender(); - }, - - onRenderFrame = function () { - isStable = layout.step() && !userInteraction; - renderGraph(); - - return !isStable; - }, - - renderIterations = function (iterationsCount) { - if (animationTimer) { - totalIterationsCount += iterationsCount; - return; - } - - if (iterationsCount) { - totalIterationsCount += iterationsCount; - - animationTimer = Viva.Graph.Utils.timer(function () { - return onRenderFrame(); - }, FRAME_INTERVAL); - } else { - currentStep = 0; - totalIterationsCount = 0; - animationTimer = Viva.Graph.Utils.timer(onRenderFrame, FRAME_INTERVAL); - } - }, - - resetStable = function () { - isStable = false; - animationTimer.restart(); - }, - - prerender = function () { - // To get good initial positions for the graph - // perform several prerender steps in background. - var i; - if (typeof settings.prerender === 'number' && settings.prerender > 0) { - for (i = 0; i < settings.prerender; i += 1) { - layout.step(); - } - } else { - layout.step(); // make one step to init positions property. - } - }, - - updateCenter = function () { - var graphRect = layout.getGraphRect(), - containerSize = Viva.Graph.Utils.getDimension(container); - - viewPortOffset.x = viewPortOffset.y = 0; - transform.offsetX = containerSize.width / 2 - (graphRect.x2 + graphRect.x1) / 2; - transform.offsetY = containerSize.height / 2 - (graphRect.y2 + graphRect.y1) / 2; - graphics.graphCenterChanged(transform.offsetX + viewPortOffset.x, transform.offsetY + viewPortOffset.y); - - updateCenterRequired = false; - }, - - createNodeUi = function (node) { - var nodeUI = graphics.node(node); - node.ui = nodeUI; - graphics.initNode(nodeUI); - layout.addNode(node); - - renderNode(node); - }, - - removeNodeUi = function (node) { - if (node.hasOwnProperty('ui')) { - graphics.releaseNode(node.ui); - - node.ui = null; - delete node.ui; - } - - layout.removeNode(node); - }, - - createLinkUi = function (link) { - var linkUI = graphics.link(link); - link.ui = linkUI; - graphics.initLink(linkUI); - - if (!graphics.omitLinksRendering) { - renderLink(link); - } - }, - - removeLinkUi = function (link) { - if (link.hasOwnProperty('ui')) { - graphics.releaseLink(link.ui); - link.ui = null; - delete link.ui; - } - }, - - listenNodeEvents = function (node) { - var wasPinned = false; - - // TODO: This may not be memory efficient. Consider reusing handlers object. - inputManager.bindDragNDrop(node, { - onStart : function () { - wasPinned = node.isPinned; - node.isPinned = true; - userInteraction = true; - resetStable(); - }, - onDrag : function (e, offset) { - node.position.x += offset.x / transform.scale; - node.position.y += offset.y / transform.scale; - userInteraction = true; - - renderGraph(); - }, - onStop : function () { - node.isPinned = wasPinned; - userInteraction = false; - } - }); - }, - - releaseNodeEvents = function (node) { - inputManager.bindDragNDrop(node, null); - }, - - initDom = function () { - graphics.init(container); - - graph.forEachNode(createNodeUi); - - if (settings.renderLinks) { - graph.forEachLink(createLinkUi); - } - }, - - releaseDom = function () { - graphics.release(container); - }, - - processNodeChange = function (change) { - var node = change.node; - - if (change.changeType === 'add') { - createNodeUi(node); - listenNodeEvents(node); - if (updateCenterRequired) { - updateCenter(); - } - } else if (change.changeType === 'remove') { - releaseNodeEvents(node); - removeNodeUi(node); - if (graph.getNodesCount() === 0) { - updateCenterRequired = true; // Next time when node is added - center the graph. - } - } else if (change.changeType === 'update') { - - // releaseNodeEvents(node); - // removeNodeUi(node); - - // createNodeUi(node); - // listenNodeEvents(node); - throw 'Update type is not implemented. TODO: Implement me!'; - } - }, - - processLinkChange = function (change) { - var link = change.link; - if (change.changeType === 'add') { - if (settings.renderLinks) { createLinkUi(link); } - layout.addLink(link); - } else if (change.changeType === 'remove') { - if (settings.renderLinks) { removeLinkUi(link); } - layout.removeLink(link); - } else if (change.changeType === 'update') { - // if (settings.renderLinks) { removeLinkUi(link); } - // layout.removeLink(link); - - // if (settings.renderLinks) { createLinkUi(link); } - // layout.addLink(link); - throw 'Update type is not implemented. TODO: Implement me!'; - } - }, - - onGraphChanged = function (changes) { - var i, change; - for (i = 0; i < changes.length; i += 1) { - change = changes[i]; - if (change.node) { - processNodeChange(change); - } else if (change.link) { - processLinkChange(change); - } - } - - resetStable(); - }, - - onWindowResized = function () { - updateCenter(); - onRenderFrame(); - }, - - releaseContainerDragManager = function () { - if (containerDrag) { - containerDrag.release(); - containerDrag = null; - } - }, - - releaseGraphEvents = function () { - if (graphEvents) { - // Interesting.. why is it not null? Anyway: - graphEvents.stop('changed', onGraphChanged); - graphEvents = null; - } - }, - - listenToEvents = function () { - windowEvents.on('resize', onWindowResized); - - releaseContainerDragManager(); - containerDrag = Viva.Graph.Utils.dragndrop(container); - containerDrag.onDrag(function (e, offset) { - viewPortOffset.x += offset.x; - viewPortOffset.y += offset.y; - graphics.translateRel(offset.x, offset.y); - - renderGraph(); - }); - - containerDrag.onScroll(function (e, scaleOffset, scrollPoint) { - var scaleFactor = Math.pow(1 + 0.4, scaleOffset < 0 ? -0.2 : 0.2); - transform.scale = graphics.scale(scaleFactor, scrollPoint); - - renderGraph(); - publicEvents.fire('scale', transform.scale); - }); - - graph.forEachNode(listenNodeEvents); - - releaseGraphEvents(); - graphEvents = Viva.Graph.Utils.events(graph); - graphEvents.on('changed', onGraphChanged); - }, - - stopListenToEvents = function () { - rendererInitialized = false; - releaseGraphEvents(); - releaseContainerDragManager(); - windowEvents.stop('resize', onWindowResized); - publicEvents.removeAllListeners(); - animationTimer.stop(); - - graph.forEachLink(function (link) { - if (settings.renderLinks) { removeLinkUi(link); } - layout.removeLink(link); - }); - - graph.forEachNode(function (node) { - releaseNodeEvents(node); - removeNodeUi(node); - }); - - layout.dispose(); - releaseDom(); - }; - - return { - /** - * Performs rendering of the graph. - * - * @param iterationsCount if specified renderer will run only given number of iterations - * and then stop. Otherwise graph rendering is performed infinitely. - * - * Note: if rendering stopped by used started dragging nodes or new nodes were added to the - * graph renderer will give run more iterations to reflect changes. - */ - run : function (iterationsCount) { - - if (!rendererInitialized) { - prepareSettings(); - prerender(); - - updateCenter(); - initDom(); - listenToEvents(); - - rendererInitialized = true; - } - - renderIterations(iterationsCount); - - return this; - }, - - reset : function () { - graphics.resetScale(); - updateCenter(); - transform.scale = 1; - }, - - pause : function () { - animationTimer.stop(); - }, - - resume : function () { - animationTimer.restart(); - }, - - rerender : function () { - renderGraph(); - return this; - }, - - /** - * Removes this renderer and deallocates all resources/timers - */ - dispose : function () { - stopListenToEvents(); // I quit! - }, - - on : function (eventName, callback) { - publicEvents.addEventListener(eventName, callback); - return this; - }, - - off : function (eventName, callback) { - publicEvents.removeEventListener(eventName, callback); - return this; +function renderer(graph, settings) { + // TODO: This class is getting hard to understand. Consider refactoring. + // TODO: I have a technical debt here: fix scaling/recentering! Currently it's a total mess. + var FRAME_INTERVAL = 30; + + settings = settings || {}; + + var layout = settings.layout, + graphics = settings.graphics, + container = settings.container, + interactive = settings.interactive !== undefined ? settings.interactive : true, + inputManager, + animationTimer, + rendererInitialized = false, + updateCenterRequired = true, + + isStable = false, + userInteraction = false, + isPaused = false, + + transform = { + offsetX: 0, + offsetY: 0, + scale: 1 + }, + + publicEvents = eventify({}), + containerDrag; + + return { + /** + * Performs rendering of the graph. + * + * @param iterationsCount if specified renderer will run only given number of iterations + * and then stop. Otherwise graph rendering is performed indefinitely. + * + * Note: if rendering stopped by used started dragging nodes or new nodes were added to the + * graph renderer will give run more iterations to reflect changes. + */ + run: function(iterationsCount) { + + if (!rendererInitialized) { + prepareSettings(); + prerender(); + + initDom(); + updateCenter(); + listenToEvents(); + + rendererInitialized = true; + } + + renderIterations(iterationsCount); + + return this; + }, + + reset: function() { + graphics.resetScale(); + updateCenter(); + transform.scale = 1; + }, + + pause: function() { + isPaused = true; + animationTimer.stop(); + }, + + resume: function() { + isPaused = false; + animationTimer.restart(); + }, + + rerender: function() { + renderGraph(); + return this; + }, + + zoomOut: function() { + return scale(true); + }, + + zoomIn: function() { + return scale(false); + }, + + /** + * Returns current transformation matrix. + */ + getTransform: function() { + return transform; + }, + + /** + * Centers renderer at x,y graph's coordinates + */ + moveTo: function(x, y) { + graphics.graphCenterChanged(transform.offsetX - x * transform.scale, transform.offsetY - y * transform.scale); + renderGraph(); + }, + + /** + * Gets current graphics object + */ + getGraphics: function() { + return graphics; + }, + + /** + * Gets current layout. + */ + getLayout: function() { + return layout; + }, + + /** + * Removes this renderer and deallocates all resources/timers + */ + dispose: function() { + stopListenToEvents(); // I quit! + }, + + on: function(eventName, callback) { + publicEvents.on(eventName, callback); + return this; + }, + + off: function(eventName, callback) { + publicEvents.off(eventName, callback); + return this; + } + }; + + /** + * Checks whether given interaction (node/scroll) is enabled + */ + function isInteractive(interactionName) { + if (typeof interactive === 'string') { + return interactive.indexOf(interactionName) >= 0; + } else if (typeof interactive === 'boolean') { + return interactive; + } + return true; // default setting + } + + function prepareSettings() { + container = container || window.document.body; + layout = layout || forceDirected(graph, { + springLength: 80, + springCoeff: 0.0002, + }); + graphics = graphics || svgGraphics(graph, { + container: container + }); + + if (!settings.hasOwnProperty('renderLinks')) { + settings.renderLinks = true; + } + + settings.prerender = settings.prerender || 0; + inputManager = (graphics.inputManager || domInputManager)(graph, graphics); + } + + function renderGraph() { + graphics.beginRender(); + + // todo: move this check graphics + if (settings.renderLinks) { + graphics.renderLinks(); + } + graphics.renderNodes(); + graphics.endRender(); + } + + function onRenderFrame() { + isStable = layout.step() && !userInteraction; + renderGraph(); + + return !isStable; + } + + function renderIterations(iterationsCount) { + if (animationTimer) { + return; + } + + if (iterationsCount !== undefined) { + animationTimer = timer(function() { + iterationsCount -= 1; + if (iterationsCount < 0) { + var needMoreFrames = false; + return needMoreFrames; } - }; -}; + + return onRenderFrame(); + }, FRAME_INTERVAL); + } else { + animationTimer = timer(onRenderFrame, FRAME_INTERVAL); + } + } + + function resetStable() { + if (isPaused) { + return; + } + + isStable = false; + animationTimer.restart(); + } + + function prerender() { + // To get good initial positions for the graph + // perform several prerender steps in background. + if (typeof settings.prerender === 'number' && settings.prerender > 0) { + for (var i = 0; i < settings.prerender; i += 1) { + layout.step(); + } + } + } + + function updateCenter() { + var graphRect = layout.getGraphRect(), + containerSize = getDimension(container); + + var cx = (graphRect.x2 + graphRect.x1) / 2; + var cy = (graphRect.y2 + graphRect.y1) / 2; + transform.offsetX = containerSize.width / 2 - (cx * transform.scale - cx); + transform.offsetY = containerSize.height / 2 - (cy * transform.scale - cy); + graphics.graphCenterChanged(transform.offsetX, transform.offsetY); + + updateCenterRequired = false; + } + + function createNodeUi(node) { + var nodePosition = layout.getNodePosition(node.id); + graphics.addNode(node, nodePosition); + } + + function removeNodeUi(node) { + graphics.releaseNode(node); + } + + function createLinkUi(link) { + var linkPosition = layout.getLinkPosition(link.id); + graphics.addLink(link, linkPosition); + } + + function removeLinkUi(link) { + graphics.releaseLink(link); + } + + function listenNodeEvents(node) { + if (!isInteractive('node')) { + return; + } + + var wasPinned = false; + + // TODO: This may not be memory efficient. Consider reusing handlers object. + inputManager.bindDragNDrop(node, { + onStart: function() { + wasPinned = layout.isNodePinned(node); + layout.pinNode(node, true); + userInteraction = true; + resetStable(); + }, + onDrag: function(e, offset) { + var oldPos = layout.getNodePosition(node.id); + layout.setNodePosition(node.id, + oldPos.x + offset.x / transform.scale, + oldPos.y + offset.y / transform.scale); + + userInteraction = true; + + renderGraph(); + }, + onStop: function() { + layout.pinNode(node, wasPinned); + userInteraction = false; + } + }); + } + + function releaseNodeEvents(node) { + inputManager.bindDragNDrop(node, null); + } + + function initDom() { + graphics.init(container); + + graph.forEachNode(createNodeUi); + + if (settings.renderLinks) { + graph.forEachLink(createLinkUi); + } + } + + function releaseDom() { + graphics.release(container); + } + + function processNodeChange(change) { + var node = change.node; + + if (change.changeType === 'add') { + createNodeUi(node); + listenNodeEvents(node); + if (updateCenterRequired) { + updateCenter(); + } + } else if (change.changeType === 'remove') { + releaseNodeEvents(node); + removeNodeUi(node); + if (graph.getNodesCount() === 0) { + updateCenterRequired = true; // Next time when node is added - center the graph. + } + } else if (change.changeType === 'update') { + releaseNodeEvents(node); + removeNodeUi(node); + + createNodeUi(node); + listenNodeEvents(node); + } + } + + function processLinkChange(change) { + var link = change.link; + if (change.changeType === 'add') { + if (settings.renderLinks) { + createLinkUi(link); + } + } else if (change.changeType === 'remove') { + if (settings.renderLinks) { + removeLinkUi(link); + } + } else if (change.changeType === 'update') { + throw 'Update type is not implemented. TODO: Implement me!'; + } + } + + function onGraphChanged(changes) { + var i, change; + for (i = 0; i < changes.length; i += 1) { + change = changes[i]; + if (change.node) { + processNodeChange(change); + } else if (change.link) { + processLinkChange(change); + } + } + + resetStable(); + } + + function onWindowResized() { + updateCenter(); + onRenderFrame(); + } + + function releaseContainerDragManager() { + if (containerDrag) { + containerDrag.release(); + containerDrag = null; + } + } + + function releaseGraphEvents() { + graph.off('changed', onGraphChanged); + } + + function scale(out, scrollPoint) { + if (!scrollPoint) { + var containerSize = getDimension(container); + scrollPoint = { + x: containerSize.width / 2, + y: containerSize.height / 2 + }; + } + var scaleFactor = Math.pow(1 + 0.4, out ? -0.2 : 0.2); + transform.scale = graphics.scale(scaleFactor, scrollPoint); + + renderGraph(); + publicEvents.fire('scale', transform.scale); + + return transform.scale; + } + + function listenToEvents() { + windowEvents.on('resize', onWindowResized); + + releaseContainerDragManager(); + if (isInteractive('drag')) { + containerDrag = dragndrop(container); + containerDrag.onDrag(function(e, offset) { + graphics.translateRel(offset.x, offset.y); + + renderGraph(); + publicEvents.fire('drag', offset); + }); + } + + if (isInteractive('scroll')) { + if (!containerDrag) { + containerDrag = dragndrop(container); + } + containerDrag.onScroll(function(e, scaleOffset, scrollPoint) { + scale(scaleOffset < 0, scrollPoint); + }); + } + + graph.forEachNode(listenNodeEvents); + + releaseGraphEvents(); + graph.on('changed', onGraphChanged); + } + + function stopListenToEvents() { + rendererInitialized = false; + releaseGraphEvents(); + releaseContainerDragManager(); + windowEvents.off('resize', onWindowResized); + publicEvents.off(); + animationTimer.stop(); + + graph.forEachLink(function(link) { + if (settings.renderLinks) { + removeLinkUi(link); + } + }); + + graph.forEachNode(function(node) { + releaseNodeEvents(node); + removeNodeUi(node); + }); + + layout.dispose(); + releaseDom(); + } +} diff --git a/src/View/svgGraphics.js b/src/View/svgGraphics.js index a0f24d8..7dd77fc 100644 --- a/src/View/svgGraphics.js +++ b/src/View/svgGraphics.js @@ -1,38 +1,44 @@ /** * @fileOverview Defines a graph renderer that uses SVG based drawings. * - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com + * @author Andrei Kashcha (aka anvaka) / https://github.com/anvaka */ -Viva.Graph.View = Viva.Graph.View || {}; +module.exports = svgGraphics; + +var svg = require('simplesvg'); +var eventify = require('ngraph.events'); +var domInputManager = require('../Input/domInputManager.js'); /** * Performs svg-based graph rendering. This module does not perform - * layout, but only visualizes nodes and edeges of the graph. + * layout, but only visualizes nodes and edges of the graph. */ -Viva.Graph.View.svgGraphics = function () { +function svgGraphics() { var svgContainer, svgRoot, - offsetX, - offsetY, + offsetX = 0, + offsetY = 0, + initCallback, actualScale = 1, + allNodes = {}, + allLinks = {}, /*jshint unused: false */ nodeBuilder = function (node) { - return Viva.Graph.svg("rect") + return svg("rect") .attr("width", 10) .attr("height", 10) .attr("fill", "#00a2e8"); }, nodePositionCallback = function (nodeUI, pos) { - // TODO: Remove magic 5. It should be halfo of the width or height of the node. + // TODO: Remove magic 5. It should be half of the width or height of the node. nodeUI.attr("x", pos.x - 5) .attr("y", pos.y - 5); }, linkBuilder = function (link) { - return Viva.Graph.svg("line") - .attr("stroke", "#999"); + return svg("line").attr("stroke", "#999"); }, linkPositionCallback = function (linkUI, fromPos, toPos) { @@ -47,6 +53,10 @@ Viva.Graph.View.svgGraphics = function () { graphics.fire("rescaled"); }, + cachedPos = {x : 0, y: 0}, + cachedFromPos = {x : 0, y: 0}, + cachedToPos = {x : 0, y: 0}, + updateTransform = function () { if (svgContainer) { var transform = "matrix(" + actualScale + ", 0, 0," + actualScale + "," + offsetX + "," + offsetY + ")"; @@ -54,52 +64,57 @@ Viva.Graph.View.svgGraphics = function () { } }; + svgRoot = createSvgRoot(); + var graphics = { + getNodeUI: function (nodeId) { + return allNodes[nodeId]; + }, + + getLinkUI: function (linkId) { + return allLinks[linkId]; + }, + /** - * Sets the collback that creates node representation or creates a new node - * presentation if builderCallbackOrNode is not a function. + * Sets the callback that creates node representation. * - * @param builderCallbackOrNode a callback function that accepts graph node - * as a parameter and must return an element representing this node. OR - * if it's not a function it's treated as a node to which DOM element should be created. + * @param builderCallback a callback function that accepts graph node + * as a parameter and must return an element representing this node. * * @returns If builderCallbackOrNode is a valid callback function, instance of this is returned; - * Otherwise a node representation is returned for the passed parameter. + * Otherwise undefined value is returned */ - node : function (builderCallbackOrNode) { - - if (builderCallbackOrNode && typeof builderCallbackOrNode !== "function") { - return nodeBuilder(builderCallbackOrNode); + node : function (builderCallback) { + if (typeof builderCallback !== "function") { + return; // todo: throw? This is not compatible with old versions } - nodeBuilder = builderCallbackOrNode; + nodeBuilder = builderCallback; return this; }, /** - * Sets the collback that creates link representation or creates a new link - * presentation if builderCallbackOrLink is not a function. + * Sets the callback that creates link representation * - * @param builderCallbackOrLink a callback function that accepts graph link - * as a parameter and must return an element representing this link. OR - * if it's not a function it's treated as a link to which DOM element should be created. + * @param builderCallback a callback function that accepts graph link + * as a parameter and must return an element representing this link. * - * @returns If builderCallbackOrLink is a valid callback function, instance of this is returned; - * Otherwise a link representation is returned for the passed parameter. + * @returns If builderCallback is a valid callback function, instance of this is returned; + * Otherwise undefined value is returned. */ - link : function (builderCallbackOrLink) { - if (builderCallbackOrLink && typeof builderCallbackOrLink !== "function") { - return linkBuilder(builderCallbackOrLink); + link : function (builderCallback) { + if (typeof builderCallback !== "function") { + return; // todo: throw? This is not compatible with old versions } - linkBuilder = builderCallbackOrLink; + linkBuilder = builderCallback; return this; }, /** * Allows to override default position setter for the node with a new - * function. newPlaceCallback(nodeUI, position) is function which + * function. newPlaceCallback(nodeUI, position, node) is function which * is used by updateNodePosition(). */ placeNode : function (newPlaceCallback) { @@ -134,7 +149,7 @@ Viva.Graph.View.svgGraphics = function () { /** * Default input manager listens to DOM events to process nodes drag-n-drop */ - inputManager : Viva.Input.domInputManager, + inputManager : domInputManager, translateRel : function (dx, dy) { var p = svgRoot.createSVGPoint(), @@ -160,7 +175,7 @@ Viva.Graph.View.svgGraphics = function () { p.x = scrollPoint.x; p.y = scrollPoint.y; - p = p.matrixTransform(svgContainer.getCTM().inverse()); // translate to svg coordinates + p = p.matrixTransform(svgContainer.getCTM().inverse()); // translate to SVG coordinates // Compute new scale matrix in current mouse position var k = svgRoot.createSVGMatrix().translate(p.x, p.y).scale(scaleFactor).translate(-p.x, -p.y), @@ -189,14 +204,12 @@ Viva.Graph.View.svgGraphics = function () { * provider prepare to render. */ init : function (container) { - svgRoot = Viva.Graph.svg("svg"); - - svgContainer = Viva.Graph.svg("g") - .attr("buffered-rendering", "dynamic"); - - svgRoot.appendChild(svgContainer); container.appendChild(svgRoot); updateTransform(); + // Notify the world if someone waited for update. TODO: should send an event + if (typeof initCallback === "function") { + initCallback(svgRoot); + } }, /** @@ -213,15 +226,20 @@ Viva.Graph.View.svgGraphics = function () { * Called by Viva.Graph.View.renderer to let concrete graphic output * provider prepare to render given link of the graph * - * @param linkUI visual representation of the link created by link() execution. + * @param link - model of a link */ - initLink : function (linkUI) { + addLink: function (link, pos) { + var linkUI = linkBuilder(link); if (!linkUI) { return; } + linkUI.position = pos; + linkUI.link = link; + allLinks[link.id] = linkUI; if (svgContainer.childElementCount > 0) { svgContainer.insertBefore(linkUI, svgContainer.firstChild); } else { svgContainer.appendChild(linkUI); } + return linkUI; }, /** @@ -230,8 +248,12 @@ Viva.Graph.View.svgGraphics = function () { * * @param linkUI visual representation of the link created by link() execution. **/ - releaseLink : function (linkUI) { - svgContainer.removeChild(linkUI); + releaseLink : function (link) { + var linkUI = allLinks[link.id]; + if (linkUI) { + svgContainer.removeChild(linkUI); + delete allLinks[link.id]; + } }, /** @@ -240,49 +262,95 @@ Viva.Graph.View.svgGraphics = function () { * * @param nodeUI visual representation of the node created by node() execution. **/ - initNode : function (nodeUI) { + addNode : function (node, pos) { + var nodeUI = nodeBuilder(node); + if (!nodeUI) { + return; + } + nodeUI.position = pos; + nodeUI.node = node; + allNodes[node.id] = nodeUI; + svgContainer.appendChild(nodeUI); + + return nodeUI; }, /** * Called by Viva.Graph.View.renderer to let concrete graphic output * provider remove node from rendering surface. * - * @param nodeUI visual representation of the node created by node() execution. + * @param node graph's node **/ - releaseNode : function (nodeUI) { - svgContainer.removeChild(nodeUI); + releaseNode : function (node) { + var nodeUI = allNodes[node.id]; + if (nodeUI) { + svgContainer.removeChild(nodeUI); + delete allNodes[node.id]; + } }, - /** - * Called by Viva.Graph.View.renderer to let concrete graphic output - * provider place given node UI to recommended position pos {x, y}; - */ - updateNodePosition : function (nodeUI, pos) { - nodePositionCallback(nodeUI, pos); + renderNodes : function () { + for (var key in allNodes) { + if (allNodes.hasOwnProperty(key)) { + var nodeUI = allNodes[key]; + cachedPos.x = nodeUI.position.x; + cachedPos.y = nodeUI.position.y; + nodePositionCallback(nodeUI, cachedPos, nodeUI.node); + } + } }, - /** - * Called by Viva.Graph.View.renderer to let concrete graphic output - * provider place given link of the graph. Pos objects are {x, y}; - */ - updateLinkPosition : function (link, fromPos, toPos) { - linkPositionCallback(link, fromPos, toPos); + renderLinks : function () { + for (var key in allLinks) { + if (allLinks.hasOwnProperty(key)) { + var linkUI = allLinks[key]; + cachedFromPos.x = linkUI.position.from.x; + cachedFromPos.y = linkUI.position.from.y; + cachedToPos.x = linkUI.position.to.x; + cachedToPos.y = linkUI.position.to.y; + linkPositionCallback(linkUI, cachedFromPos, cachedToPos, linkUI.link); + } + } }, /** - * Returns root svg element. + * Returns root element which hosts graphics. + */ + getGraphicsRoot : function (callbackWhenReady) { + // todo: should fire an event, instead of having this context. + if (typeof callbackWhenReady === "function") { + if (svgRoot) { + callbackWhenReady(svgRoot); + } else { + initCallback = callbackWhenReady; + } + } + return svgRoot; + }, + /** + * Returns root SVG element. * * Note: This is internal method specific to this renderer - * TODO: Rename this to getGraphicsRoot() to be uniform accross graphics classes */ getSvgRoot : function () { return svgRoot; } }; + // Let graphics fire events before we return it to the caller. - Viva.Graph.Utils.events(graphics).extend(); + eventify(graphics); return graphics; -}; + + function createSvgRoot() { + var svgRoot = svg("svg"); + + svgContainer = svg("g") + .attr("buffered-rendering", "dynamic"); + + svgRoot.appendChild(svgContainer); + return svgRoot; + } +} diff --git a/src/View/svgNodeFactory.js b/src/View/svgNodeFactory.js deleted file mode 100644 index 3edcd92..0000000 --- a/src/View/svgNodeFactory.js +++ /dev/null @@ -1,123 +0,0 @@ -/** - * @fileOverview I used this class to render links UI within - * node. Lesser SVG elements is proven to improve performance - * but I'm not happy with the code result here. Probably this class - * will be removed from future versions. - * - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com - */ - -Viva.Graph.View.svgNodeFactory = function (graph) { - var defaultColor = "#999", - geom = Viva.Graph.geom(), - - attachCustomContent = function (nodeUI) { - nodeUI.size = {w: 10, h: 10}; - nodeUI.append("rect") - .attr("width", nodeUI.size.w) - .attr("height", nodeUI.size.h) - .attr("stroke", "orange") - .attr("fill", "orange"); - }, - - nodeSize = function (nodeUI) { - return nodeUI.size; - }; - - - return { - node : function (node) { - var nodeUI = Viva.Graph.svg("g"); - - attachCustomContent(nodeUI, node); - nodeUI.nodeId = node.id; - return nodeUI; - }, - - link : function (link) { - var fromNode = graph.getNode(link.fromId), - nodeUI = fromNode && fromNode.ui; - - if (nodeUI && !nodeUI.linksContainer) { - var nodeLinks = Viva.Graph.svg("path") - .attr("stroke", defaultColor); - nodeUI.linksContainer = nodeLinks; - return nodeLinks; - } - - return null; - }, - - /** - * Sets a callback function for custom nodes contnet. - * @param conentCreator(nodeUI, node) - callback function which returns a node content UI. - * Image, for example. - * @param sizeProvider(nodeUI) - a callback function which accepts nodeUI returned by - * contentCreator and returns it"s custom rectangular size. - * - */ - customContent : function (contentCreator, sizeProvider) { - if (typeof contentCreator !== "function" || - typeof sizeProvider !== "function") { - throw "Two functions expected: contentCreator(nodeUI, node) and size(nodeUI)"; - } - - attachCustomContent = contentCreator; - nodeSize = sizeProvider; - }, - - placeNode : function (nodeUI, fromNodePos) { - var linksPath = "", - fromNodeSize = nodeSize(nodeUI); - - graph.forEachLinkedNode(nodeUI.nodeId, function (linkedNode, link) { - if (!linkedNode.position || !linkedNode.ui) { - return; // not yet defined - ignore. - } - - if (linkedNode.ui === nodeUI) { - return; // incoming link - ignore; - } - if (link.fromId !== nodeUI.nodeId) { - return; // we process only outgoing links. - } - - var toNodeSize = nodeSize(linkedNode.ui), - toNodePos = linkedNode.position; - - var from = geom.intersectRect( - fromNodePos.x - fromNodeSize.w / 2, // left - fromNodePos.y - fromNodeSize.h / 2, // top - fromNodePos.x + fromNodeSize.w / 2, // right - fromNodePos.y + fromNodeSize.h / 2, // bottom - fromNodePos.x, - fromNodePos.y, - toNodePos.x, - toNodePos.y - ) || fromNodePos; - - var to = geom.intersectRect( - toNodePos.x - toNodeSize.w / 2, // left - toNodePos.y - toNodeSize.h / 2, // top - toNodePos.x + toNodeSize.w / 2, // right - toNodePos.y + toNodeSize.h / 2, // bottom - toNodePos.x, - toNodePos.y, - fromNodePos.x, - fromNodePos.y - ) || toNodePos; - - linksPath += "M" + Math.round(from.x) + " " + Math.round(from.y) + - "L" + Math.round(to.x) + " " + Math.round(to.y); - }); - - nodeUI.attr("transform", - "translate(" + (fromNodePos.x - fromNodeSize.w / 2) + ", " + - (fromNodePos.y - fromNodeSize.h / 2) + ")"); - if (linksPath !== "" && nodeUI.linksContainer) { - nodeUI.linksContainer.attr("d", linksPath); - } - } - - }; -}; diff --git a/src/View/webglGraphics.js b/src/View/webglGraphics.js index 4953832..7066ef0 100644 --- a/src/View/webglGraphics.js +++ b/src/View/webglGraphics.js @@ -1,23 +1,31 @@ /** * @fileOverview Defines a graph renderer that uses WebGL based drawings. * - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com + * @author Andrei Kashcha (aka anvaka) / https://github.com/anvaka */ -Viva.Graph.View = Viva.Graph.View || {}; +module.exports = webglGraphics; + +var webglInputManager = require('../Input/webglInputManager.js'); +var webglLinkProgram = require('../WebGL/webglLinkProgram.js'); +var webglNodeProgram = require('../WebGL/webglNodeProgram.js'); +var webglSquare = require('../WebGL/webglSquare.js'); +var webglLine = require('../WebGL/webglLine.js'); +var eventify = require('ngraph.events'); +var merge = require('ngraph.merge'); /** * Performs webgl-based graph rendering. This module does not perform - * layout, but only visualizes nodes and edeges of the graph. + * layout, but only visualizes nodes and edges of the graph. * * @param options - to customize graphics behavior. Currently supported parameter * enableBlending - true by default, allows to use transparency in node/links colors. - * preserveDrawingBuffer - false by default, tells webgl to preserve drawing buffer. + * preserveDrawingBuffer - false by default, tells webgl to preserve drawing buffer. * See https://www.khronos.org/registry/webgl/specs/1.0/#5.2 */ -Viva.Graph.View.webglGraphics = function (options) { - options = Viva.lazyExtend(options, { +function webglGraphics(options) { + options = merge(options, { enableBlending : true, preserveDrawingBuffer : false, clearColor: false, @@ -36,22 +44,29 @@ Viva.Graph.View.webglGraphics = function (options) { height, nodesCount = 0, linksCount = 0, - transform, + transform = [ + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + ], userPlaceNodeCallback, userPlaceLinkCallback, nodes = [], links = [], initCallback, - linkProgram = Viva.Graph.View.webglLinkProgram(), - nodeProgram = Viva.Graph.View.webglNodeProgram(), + allNodes = {}, + allLinks = {}, + linkProgram = webglLinkProgram(), + nodeProgram = webglNodeProgram(), /*jshint unused: false */ nodeUIBuilder = function (node) { - return Viva.Graph.View.webglSquare(); // Just make a square, using provided gl context (a nodeProgram); + return webglSquare(); // Just make a square, using provided gl context (a nodeProgram); }, linkUIBuilder = function (link) { - return Viva.Graph.View.webglLine(0xb3b3b3ff); + return webglLine(0xb3b3b3ff); }, /*jshint unused: true */ updateTransformUniform = function () { @@ -76,75 +91,59 @@ Viva.Graph.View.webglGraphics = function (options) { } }, - nodeBuilderInternal = function (node) { - var nodeId = nodesCount++, - ui = nodeUIBuilder(node); - ui.id = nodeId; + fireRescaled = function (graphics) { + graphics.fire("rescaled"); + }; - nodeProgram.createNode(ui); + graphicsRoot = window.document.createElement("canvas"); - nodes[nodeId] = node; - return ui; + var graphics = { + getLinkUI: function (linkId) { + return allLinks[linkId]; }, - linkBuilderInternal = function (link) { - var linkId = linksCount++, - ui = linkUIBuilder(link); - ui.id = linkId; - - linkProgram.createLink(ui); - - links[linkId] = link; - return ui; + getNodeUI: function (nodeId) { + return allNodes[nodeId]; }, - fireRescaled = function (graphics) { - graphics.fire("rescaled"); - }; - - var graphics = { /** - * Sets the collback that creates node representation or creates a new node - * presentation if builderCallbackOrNode is not a function. + * Sets the callback that creates node representation. * - * @param builderCallbackOrNode a callback function that accepts graph node - * as a parameter and must return an element representing this node. OR - * if it's not a function it's treated as a node to which DOM element should be created. + * @param builderCallback a callback function that accepts graph node + * as a parameter and must return an element representing this node. * * @returns If builderCallbackOrNode is a valid callback function, instance of this is returned; - * Otherwise a node representation is returned for the passed parameter. + * Otherwise undefined value is returned */ - node : function (builderCallbackOrNode) { - if (builderCallbackOrNode && typeof builderCallbackOrNode !== "function") { - return nodeBuilderInternal(builderCallbackOrNode); // create ui for node using current nodeUIBuilder + node : function (builderCallback) { + if (typeof builderCallback !== "function") { + return; // todo: throw? This is not compatible with old versions } - nodeUIBuilder = builderCallbackOrNode; // else replace ui builder with provided function. + nodeUIBuilder = builderCallback; return this; }, /** - * Sets the collback that creates link representation or creates a new link - * presentation if builderCallbackOrLink is not a function. + * Sets the callback that creates link representation * - * @param builderCallbackOrLink a callback function that accepts graph link - * as a parameter and must return an element representing this link. OR - * if it's not a function it's treated as a link to which DOM element should be created. + * @param builderCallback a callback function that accepts graph link + * as a parameter and must return an element representing this link. * - * @returns If builderCallbackOrLink is a valid callback function, instance of this is returned; - * Otherwise a link representation is returned for the passed parameter. + * @returns If builderCallback is a valid callback function, instance of this is returned; + * Otherwise undefined value is returned. */ - link : function (builderCallbackOrLink) { - - if (builderCallbackOrLink && typeof builderCallbackOrLink !== "function") { - return linkBuilderInternal(builderCallbackOrLink); + link : function (builderCallback) { + if (typeof builderCallback !== "function") { + return; // todo: throw? This is not compatible with old versions } - linkUIBuilder = builderCallbackOrLink; + linkUIBuilder = builderCallback; return this; }, + /** * Allows to override default position setter for the node with a new * function. newPlaceCallback(nodeUI, position) is function which @@ -163,13 +162,13 @@ Viva.Graph.View.webglGraphics = function (options) { /** * Custom input manager listens to mouse events to process nodes drag-n-drop inside WebGL canvas */ - inputManager : Viva.Input.webglInputManager, + inputManager : webglInputManager, /** * Called every time before renderer starts rendering. */ beginRender : function () { - // this function could be replaced by this.init, + // this function could be replaced by this.init, // based on user options. }, @@ -197,36 +196,60 @@ Viva.Graph.View.webglGraphics = function (options) { temp = links[frontLinkId]; links[frontLinkId] = links[srcLinkId]; - links[frontLinkId].ui.id = frontLinkId; + links[frontLinkId].id = frontLinkId; links[srcLinkId] = temp; - links[srcLinkId].ui.id = srcLinkId; + links[srcLinkId].id = srcLinkId; } }, -/*jshint unused: false */ + /** * Sets translate operation that should be applied to all nodes and links. */ graphCenterChanged : function (x, y) { - updateSize(); + transform[12] = (2 * x / width) - 1; + transform[13] = 1 - (2 * y / height); + updateTransformUniform(); }, - /** - * Called by Viva.Graph.View.renderer to let concrete graphic output - * provider prepare to render given link of the graph - * - * @param linkUI visual representation of the link created by link() execution. - */ - initLink : function (linkUI) { + /** + * Called by Viva.Graph.View.renderer to let concrete graphic output + * provider prepare to render given link of the graph + * + * @param link - model of a link + */ + addLink: function (link, boundPosition) { + var uiid = linksCount++, + ui = linkUIBuilder(link); + ui.id = uiid; + ui.pos = boundPosition; + + linkProgram.createLink(ui); + + links[uiid] = ui; + allLinks[link.id] = ui; + return ui; }, + /** * Called by Viva.Graph.View.renderer to let concrete graphic output * provider prepare to render given node of the graph. * * @param nodeUI visual representation of the node created by node() execution. **/ - initNode : function (nodeUI) { + addNode : function (node, boundPosition) { + var uiid = nodesCount++, + ui = nodeUIBuilder(node); + + ui.id = uiid; + ui.position = boundPosition; + ui.node = node; + + nodeProgram.createNode(ui); + + nodes[uiid] = ui; + allNodes[node.id] = ui; + return ui; }, -/*jshint unused: true */ translateRel : function (dx, dy) { transform[12] += (2 * transform[0] * dx / width) / transform[0]; @@ -267,6 +290,12 @@ Viva.Graph.View.webglGraphics = function (options) { return this; }, + /** + * Resizes the graphic without resetting the scale. + * Useful with viva graph in a dynamic container + */ + updateSize: updateSize, + /** * Called by Viva.Graph.View.renderer to let concrete graphic output * provider prepare to render. @@ -280,7 +309,6 @@ Viva.Graph.View.webglGraphics = function (options) { container = c; - graphicsRoot = window.document.createElement("canvas"); updateSize(); resetScaleInternal(); container.appendChild(graphicsRoot); @@ -300,7 +328,7 @@ Viva.Graph.View.webglGraphics = function (options) { var color = options.clearColorValue; gl.clearColor(color.r, color.g, color.b, color.a); // TODO: not the best way, really. Should come up with something better - // what if we need more updates inisde beginRender, like depth buffer? + // what if we need more updates inside beginRender, like depth buffer? this.beginRender = function () { gl.clear(gl.COLOR_BUFFER_BIT); }; @@ -314,7 +342,7 @@ Viva.Graph.View.webglGraphics = function (options) { updateTransformUniform(); - // Notify the world if someoen waited for update. TODO: should send an event + // Notify the world if someone waited for update. TODO: should send an event if (typeof initCallback === "function") { initCallback(graphicsRoot); } @@ -346,21 +374,22 @@ Viva.Graph.View.webglGraphics = function (options) { * * @param linkUI visual representation of the link created by link() execution. **/ - releaseLink : function (linkToRemove) { + releaseLink : function (link) { if (linksCount > 0) { linksCount -= 1; } + var linkUI = allLinks[link.id]; + delete allLinks[link.id]; - linkProgram.removeLink(linkToRemove); + linkProgram.removeLink(linkUI); - var linkIdToRemove = linkToRemove.id; + var linkIdToRemove = linkUI.id; if (linkIdToRemove < linksCount) { if (linksCount === 0 || linksCount === linkIdToRemove) { return; // no more links or removed link is the last one. } - // TODO: consider getting rid of this. The only reason why it's here is to update 'ui' property - // so that renderer will pass proper id in updateLinkPosition. - links[linkIdToRemove] = links[linksCount]; - links[linkIdToRemove].ui.id = linkIdToRemove; + var lastLinkUI = links[linksCount]; + links[linkIdToRemove] = lastLinkUI; + lastLinkUI.id = linkIdToRemove; } }, @@ -370,61 +399,67 @@ Viva.Graph.View.webglGraphics = function (options) { * * @param nodeUI visual representation of the node created by node() execution. **/ - releaseNode : function (nodeUI) { + releaseNode : function (node) { if (nodesCount > 0) { nodesCount -= 1; } + var nodeUI = allNodes[node.id]; + delete allNodes[node.id]; nodeProgram.removeNode(nodeUI); - if (nodeUI.id < nodesCount) { - var nodeIdToRemove = nodeUI.id; + var nodeIdToRemove = nodeUI.id; + if (nodeIdToRemove < nodesCount) { if (nodesCount === 0 || nodesCount === nodeIdToRemove) { return; // no more nodes or removed node is the last in the list. } - var lastNode = nodes[nodesCount], - replacedNode = nodes[nodeIdToRemove]; + var lastNodeUI = nodes[nodesCount]; - nodes[nodeIdToRemove] = lastNode; - nodes[nodeIdToRemove].ui.id = nodeIdToRemove; + nodes[nodeIdToRemove] = lastNodeUI; + lastNodeUI.id = nodeIdToRemove; - // Since concrete shaders may cache properties in the ui element + // Since concrete shaders may cache properties in the UI element // we are letting them to make this swap (e.g. image node shader // uses this approach to update node's offset in the atlas) - nodeProgram.replaceProperties(replacedNode.ui, lastNode.ui); + nodeProgram.replaceProperties(nodeUI, lastNodeUI); } }, - /** - * Called by Viva.Graph.View.renderer to let concrete graphic output - * provider place given node UI to recommended position pos {x, y}; - */ - updateNodePosition : function (nodeUI, pos) { + renderNodes: function () { + var pos = {x : 0, y : 0}; // WebGL coordinate system is different. Would be better // to have this transform in the shader code, but it would // require every shader to be updated.. - pos.y = -pos.y; - if (userPlaceNodeCallback) { - userPlaceNodeCallback(nodeUI, pos); - } + for (var i = 0; i < nodesCount; ++i) { + var ui = nodes[i]; + pos.x = ui.position.x; + pos.y = ui.position.y; + if (userPlaceNodeCallback) { + userPlaceNodeCallback(ui, pos); + } - nodeProgram.position(nodeUI, pos); + nodeProgram.position(ui, pos); + } }, - /** - * Called by Viva.Graph.View.renderer to let concrete graphic output - * provider place given link of the graph. Pos objects are {x, y}; - */ - updateLinkPosition : function (link, fromPos, toPos) { - // WebGL coordinate system is different. Would be better - // to have this transform in the shader code, but it would - // require every shader to be updated.. - fromPos.y = -fromPos.y; - toPos.y = -toPos.y; - if (userPlaceLinkCallback) { - userPlaceLinkCallback(link, fromPos, toPos); - } + renderLinks: function () { + if (this.omitLinksRendering) { return; } + + var toPos = {x : 0, y : 0}; + var fromPos = {x : 0, y : 0}; + for (var i = 0; i < linksCount; ++i) { + var ui = links[i]; + var pos = ui.pos.from; + fromPos.x = pos.x; + fromPos.y = -pos.y; + pos = ui.pos.to; + toPos.x = pos.x; + toPos.y = -pos.y; + if (userPlaceLinkCallback) { + userPlaceLinkCallback(ui, fromPos, toPos); + } - linkProgram.position(link, fromPos, toPos); + linkProgram.position(ui, fromPos, toPos); + } }, /** @@ -453,7 +488,7 @@ Viva.Graph.View.webglGraphics = function (options) { // and let initialization logic take care about the rest. nodeProgram = newProgram; } else if (newProgram) { - throw "Not implemented. Cannot swap shader on the fly... yet."; + throw "Not implemented. Cannot swap shader on the fly... Yet."; // TODO: unload old shader and reinit. } }, @@ -469,29 +504,83 @@ Viva.Graph.View.webglGraphics = function (options) { // and let initialization logic take care about the rest. linkProgram = newProgram; } else if (newProgram) { - throw "Not implemented. Cannot swap shader on the fly... yet."; + throw "Not implemented. Cannot swap shader on the fly... Yet."; // TODO: unload old shader and reinit. } }, - getGraphCoordinates : function (graphicsRootPos) { - // TODO: could be a problem when container has margins? - // to save memory we modify incoming parameter: - // point in clipspace coordinates: - graphicsRootPos.x = 2 * graphicsRootPos.x / width - 1; - graphicsRootPos.y = 1 - (2 * graphicsRootPos.y) / height; - // apply transform: - graphicsRootPos.x = (graphicsRootPos.x - transform[12]) / transform[0]; - graphicsRootPos.y = (graphicsRootPos.y - transform[13]) / transform[5]; - // now transform to graph coordinates: - graphicsRootPos.x *= width / 2; - graphicsRootPos.y *= -height / 2; - - return graphicsRootPos; + + /** + * Transforms client coordinates into layout coordinates. Client coordinates + * are DOM coordinates relative to the rendering container. Layout + * coordinates are those assigned by by layout algorithm to each node. + * + * @param {Object} p - a point object with `x` and `y` attributes. + * This method mutates p. + */ + transformClientToGraphCoordinates: function (p) { + // TODO: could be a problem when container has margins? + // normalize + p.x = ((2 * p.x) / width) - 1; + p.y = 1 - ((2 * p.y) / height); + + // apply transform + p.x = (p.x - transform[12]) / transform[0]; + p.y = (p.y - transform[13]) / transform[5]; + + // transform to graph coordinates + p.x = p.x * (width / 2); + p.y = p.y * (-height / 2); + + return p; + }, + + /** + * Transforms WebGL coordinates into client coordinates. Reverse of + * `transformClientToGraphCoordinates()` + * + * @param {Object} p - a point object with `x` and `y` attributes, which + * represents a layout coordinate. This method mutates p. + */ + transformGraphToClientCoordinates: function (p) { + // TODO: could be a problem when container has margins? + // transform from graph coordinates + p.x = p.x / (width / 2); + p.y = p.y / (-height / 2); + + // apply transform + p.x = (p.x * transform[0]) + transform[12]; + p.y = (p.y * transform[5]) + transform[13]; + + // denormalize + p.x = ((p.x + 1) * width) / 2; + p.y = ((1 - p.y) * height) / 2; + + return p; + }, + + getNodeAtClientPos: function (clientPos, preciseCheck) { + if (typeof preciseCheck !== "function") { + // we don't know anything about your node structure here :( + // potentially this could be delegated to node program, but for + // right now, we are giving up if you don't pass boundary check + // callback. It answers to a question is nodeUI covers (x, y) + return null; + } + // first transform to graph coordinates: + this.transformClientToGraphCoordinates(clientPos); + // now using precise check iterate over each node and find one within box: + // TODO: This is poor O(N) performance. + for (var i = 0; i < nodesCount; ++i) { + if (preciseCheck(nodes[i], clientPos.x, clientPos.y)) { + return nodes[i].node; + } + } + return null; } }; // Let graphics fire events before we return it to the caller. - Viva.Graph.Utils.events(graphics).extend(); + eventify(graphics); return graphics; -}; \ No newline at end of file +} diff --git a/src/WebGL/parseColor.js b/src/WebGL/parseColor.js new file mode 100644 index 0000000..5a83883 --- /dev/null +++ b/src/WebGL/parseColor.js @@ -0,0 +1,22 @@ +module.exports = parseColor; + +function parseColor(color) { + var parsedColor = 0x009ee8ff; + + if (typeof color === 'string' && color) { + if (color.length === 4) { // #rgb + color = color.replace(/([^#])/g, '$1$1'); // duplicate each letter except first #. + } + if (color.length === 9) { // #rrggbbaa + parsedColor = parseInt(color.substr(1), 16); + } else if (color.length === 7) { // or #rrggbb. + parsedColor = (parseInt(color.substr(1), 16) << 8) | 0xff; + } else { + throw 'Color expected in hex format with preceding "#". E.g. #00ff00. Got value: ' + color; + } + } else if (typeof color === 'number') { + parsedColor = color; + } + + return parsedColor; +} diff --git a/src/WebGL/texture.js b/src/WebGL/texture.js new file mode 100644 index 0000000..be26a74 --- /dev/null +++ b/src/WebGL/texture.js @@ -0,0 +1,11 @@ +module.exports = Texture; + +/** + * Single texture in the webglAtlas. + */ +function Texture(size) { + this.canvas = window.document.createElement("canvas"); + this.ctx = this.canvas.getContext("2d"); + this.isDirty = false; + this.canvas.width = this.canvas.height = size; +} diff --git a/src/WebGL/webgl.js b/src/WebGL/webgl.js index 80b6cfb..b9bea5d 100644 --- a/src/WebGL/webgl.js +++ b/src/WebGL/webgl.js @@ -1,100 +1,105 @@ /** * @fileOverview Utility functions for webgl rendering. * - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com + * @author Andrei Kashcha (aka anvaka) / https://github.com/anvaka */ -Viva.Graph.webgl = function (gl) { - var createShader = function (shaderText, type) { - var shader = gl.createShader(type); - gl.shaderSource(shader, shaderText); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { - var msg = gl.getShaderInfoLog(shader); - window.alert(msg); - throw msg; - } - - return shader; - }; - - return { - createProgram : function (vertexShaderSrc, fragmentShaderSrc) { - var program = gl.createProgram(), - vs = createShader(vertexShaderSrc, gl.VERTEX_SHADER), - fs = createShader(fragmentShaderSrc, gl.FRAGMENT_SHADER); - - gl.attachShader(program, vs); - gl.attachShader(program, fs); - gl.linkProgram(program); - - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { - var msg = gl.getShaderInfoLog(program); - window.alert(msg); - throw msg; - } - - return program; - }, - - extendArray : function (buffer, itemsInBuffer, elementsPerItem) { - if ((itemsInBuffer + 1) * elementsPerItem > buffer.length) { - // Every time we run out of space create new array twice bigger. - // TODO: it seems buffer size is limited. Consider using multiple arrays for huge graphs - var extendedArray = new Float32Array(buffer.length * elementsPerItem * 2); - extendedArray.set(buffer); - - return extendedArray; - } - - return buffer; - }, - - copyArrayPart : function (array, to, from, elementsCount) { - var i; - for (i = 0; i < elementsCount; ++i) { - array[to + i] = array[from + i]; - } - }, - - swapArrayPart : function (array, from, to, elementsCount) { - var i; - for (i = 0; i < elementsCount; ++i) { - var tmp = array[from + i]; - array[from + i] = array[to + i]; - array[to + i] = tmp; - } - }, - - getLocations : function (program, uniformOrAttributeNames) { - var foundLocations = {}, - i; - for (i = 0; i < uniformOrAttributeNames.length; ++i) { - var name = uniformOrAttributeNames[i], - location = -1; - if (name.indexOf("a_") === 0) { - location = gl.getAttribLocation(program, name); - if (location === -1) { - throw "Program doesn't have required attribute: " + name; - } - - foundLocations[name.slice(2)] = location; - } else if (name.indexOf("u_") === 0) { - location = gl.getUniformLocation(program, name); - if (location === null) { - throw "Program doesn't have required uniform: " + name; - } - - foundLocations[name.slice(2)] = location; - } else { - throw "Couldn't figure out your intent. All uniforms should start with 'u_' prefix, and attributes with 'a_'"; - } - } - - return foundLocations; - }, - - context : gl - }; -}; +module.exports = webgl; + +function webgl(gl) { + + return { + createProgram: createProgram, + extendArray: extendArray, + copyArrayPart: copyArrayPart, + swapArrayPart: swapArrayPart, + getLocations: getLocations, + context: gl + }; + + function createShader(shaderText, type) { + var shader = gl.createShader(type); + gl.shaderSource(shader, shaderText); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { + var msg = gl.getShaderInfoLog(shader); + window.alert(msg); + throw msg; + } + + return shader; + } + + function createProgram(vertexShaderSrc, fragmentShaderSrc) { + var program = gl.createProgram(); + var vs = createShader(vertexShaderSrc, gl.VERTEX_SHADER); + var fs = createShader(fragmentShaderSrc, gl.FRAGMENT_SHADER); + + gl.attachShader(program, vs); + gl.attachShader(program, fs); + gl.linkProgram(program); + + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { + var msg = gl.getShaderInfoLog(program); + window.alert(msg); + throw msg; + } + + return program; + } + + function extendArray(buffer, itemsInBuffer, elementsPerItem) { + if ((itemsInBuffer + 1) * elementsPerItem > buffer.length) { + // Every time we run out of space create new array twice bigger. + // TODO: it seems buffer size is limited. Consider using multiple arrays for huge graphs + var extendedArray = new Float32Array(buffer.length * elementsPerItem * 2); + extendedArray.set(buffer); + + return extendedArray; + } + + return buffer; + } + + function getLocations(program, uniformOrAttributeNames) { + var foundLocations = {}; + for (var i = 0; i < uniformOrAttributeNames.length; ++i) { + var name = uniformOrAttributeNames[i]; + var location = -1; + if (name[0] === 'a' && name[1] === '_') { + location = gl.getAttribLocation(program, name); + if (location === -1) { + throw new Error("Program doesn't have required attribute: " + name); + } + + foundLocations[name.slice(2)] = location; + } else if (name[0] === 'u' && name[1] === '_') { + location = gl.getUniformLocation(program, name); + if (location === null) { + throw new Error("Program doesn't have required uniform: " + name); + } + + foundLocations[name.slice(2)] = location; + } else { + throw new Error("Couldn't figure out your intent. All uniforms should start with 'u_' prefix, and attributes with 'a_'"); + } + } + + return foundLocations; + } +} + +function copyArrayPart(array, to, from, elementsCount) { + for (var i = 0; i < elementsCount; ++i) { + array[to + i] = array[from + i]; + } +} + +function swapArrayPart(array, from, to, elementsCount) { + for (var i = 0; i < elementsCount; ++i) { + var tmp = array[from + i]; + array[from + i] = array[to + i]; + array[to + i] = tmp; + } +} diff --git a/src/WebGL/webglAtlas.js b/src/WebGL/webglAtlas.js new file mode 100644 index 0000000..28eb755 --- /dev/null +++ b/src/WebGL/webglAtlas.js @@ -0,0 +1,202 @@ +var Texture = require('./texture.js'); + +module.exports = webglAtlas; + +/** + * My naive implementation of textures atlas. It allows clients to load + * multiple images into atlas and get canvas representing all of them. + * + * @param tilesPerTexture - indicates how many images can be loaded to one + * texture of the atlas. If number of loaded images exceeds this + * parameter a new canvas will be created. + */ +function webglAtlas(tilesPerTexture) { + var tilesPerRow = Math.sqrt(tilesPerTexture || 1024) << 0, + tileSize = tilesPerRow, + lastLoadedIdx = 1, + loadedImages = {}, + dirtyTimeoutId, + skipedDirty = 0, + textures = [], + trackedUrls = []; + + if (!isPowerOf2(tilesPerTexture)) { + throw "Tiles per texture should be power of two."; + } + + // this is the return object + var api = { + /** + * indicates whether atlas has changed texture in it. If true then + * some of the textures has isDirty flag set as well. + */ + isDirty: false, + + /** + * Clears any signs of atlas changes. + */ + clearDirty: clearDirty, + + /** + * Removes given url from collection of tiles in the atlas. + */ + remove: remove, + + /** + * Gets all textures in the atlas. + */ + getTextures: getTextures, + + /** + * Gets coordinates of the given image in the atlas. Coordinates is an object: + * {offset : int } - where offset is an absolute position of the image in the + * atlas. + * + * Absolute means it can be larger than tilesPerTexture parameter, and in that + * case clients should get next texture in getTextures() collection. + */ + getCoordinates: getCoordinates, + + /** + * Asynchronously Loads the image to the atlas. Cross-domain security + * limitation applies. + */ + load: load + }; + + return api; + + function clearDirty() { + var i; + api.isDirty = false; + for (i = 0; i < textures.length; ++i) { + textures[i].isDirty = false; + } + } + + function remove(imgUrl) { + var coordinates = loadedImages[imgUrl]; + if (!coordinates) { + return false; + } + delete loadedImages[imgUrl]; + lastLoadedIdx -= 1; + + + if (lastLoadedIdx === coordinates.offset) { + return true; // Ignore if it's last image in the whole set. + } + + var tileToRemove = getTileCoordinates(coordinates.offset), + lastTileInSet = getTileCoordinates(lastLoadedIdx); + + copy(lastTileInSet, tileToRemove); + + var replacedOffset = loadedImages[trackedUrls[lastLoadedIdx]]; + replacedOffset.offset = coordinates.offset; + trackedUrls[coordinates.offset] = trackedUrls[lastLoadedIdx]; + + markDirty(); + return true; + } + + function getTextures() { + return textures; // I trust you... + } + + function getCoordinates(imgUrl) { + return loadedImages[imgUrl]; + } + + function load(imgUrl, callback) { + if (loadedImages.hasOwnProperty(imgUrl)) { + callback(loadedImages[imgUrl]); + } else { + var img = new window.Image(), + imgId = lastLoadedIdx; + + lastLoadedIdx += 1; + img.crossOrigin = "anonymous"; + img.onload = function() { + markDirty(); + drawAt(imgId, img, callback); + }; + + img.src = imgUrl; + } + } + + function createTexture() { + var texture = new Texture(tilesPerRow * tileSize); + textures.push(texture); + } + + function drawAt(tileNumber, img, callback) { + var tilePosition = getTileCoordinates(tileNumber), + coordinates = { + offset: tileNumber + }; + + if (tilePosition.textureNumber >= textures.length) { + createTexture(); + } + var currentTexture = textures[tilePosition.textureNumber]; + + currentTexture.ctx.drawImage(img, tilePosition.col * tileSize, tilePosition.row * tileSize, tileSize, tileSize); + trackedUrls[tileNumber] = img.src; + + loadedImages[img.src] = coordinates; + currentTexture.isDirty = true; + + callback(coordinates); + } + + function getTileCoordinates(absolutePosition) { + var textureNumber = (absolutePosition / tilesPerTexture) << 0, + localTileNumber = (absolutePosition % tilesPerTexture), + row = (localTileNumber / tilesPerRow) << 0, + col = (localTileNumber % tilesPerRow); + + return { + textureNumber: textureNumber, + row: row, + col: col + }; + } + + function markDirtyNow() { + api.isDirty = true; + skipedDirty = 0; + dirtyTimeoutId = null; + } + + function markDirty() { + // delay this call, since it results in texture reload + if (dirtyTimeoutId) { + window.clearTimeout(dirtyTimeoutId); + skipedDirty += 1; + dirtyTimeoutId = null; + } + + if (skipedDirty > 10) { + markDirtyNow(); + } else { + dirtyTimeoutId = window.setTimeout(markDirtyNow, 400); + } + } + + function copy(from, to) { + var fromCanvas = textures[from.textureNumber].canvas, + toCtx = textures[to.textureNumber].ctx, + x = to.col * tileSize, + y = to.row * tileSize; + + toCtx.drawImage(fromCanvas, from.col * tileSize, from.row * tileSize, tileSize, tileSize, x, y, tileSize, tileSize); + textures[from.textureNumber].isDirty = true; + textures[to.textureNumber].isDirty = true; + } +} + +function isPowerOf2(n) { + return (n & (n - 1)) === 0; +} diff --git a/src/WebGL/webglImage.js b/src/WebGL/webglImage.js new file mode 100644 index 0000000..bf1dc5e --- /dev/null +++ b/src/WebGL/webglImage.js @@ -0,0 +1,30 @@ +module.exports = webglImage; + +/** + * Represents a model for image. + */ +function webglImage(size, src) { + return { + /** + * Gets texture index where current image is placed. + */ + _texture : 0, + + /** + * Gets offset in the texture where current image is placed. + */ + _offset : 0, + + /** + * Gets size of the square with the image. + */ + size : typeof size === 'number' ? size : 32, + + /** + * Source of the image. If image is coming not from your domain + * certain origin restrictions applies. + * See http://www.khronos.org/registry/webgl/specs/latest/#4.2 for more details. + */ + src : src + }; +} diff --git a/src/WebGL/webglImageNodeProgram.js b/src/WebGL/webglImageNodeProgram.js index f8741c8..bb0d53d 100644 --- a/src/WebGL/webglImageNodeProgram.js +++ b/src/WebGL/webglImageNodeProgram.js @@ -1,419 +1,265 @@ /** * @fileOverview Defines an image nodes for webglGraphics class. - * Shape of nodes is sqare. + * Shape of nodes is square. * - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com + * @author Andrei Kashcha (aka anvaka) / https://github.com/anvaka */ -/** - * Single texture in the webglAtlas. - */ -Viva.Graph.View.Texture = function (size) { - this.canvas = window.document.createElement("canvas"); - this.ctx = this.canvas.getContext("2d"); - this.isDirty = false; - this.canvas.width = this.canvas.height = size; -}; +var WebglAtlas = require('./webglAtlas.js'); +var glUtils = require('./webgl.js'); + +module.exports = webglImageNodeProgram; /** - * My naive implementation of textures atlas. It allows clients to load - * multimple images into atlas and get canvas representing all of them. - * - * @param tilesPerTexture - indicates how many images can be loaded to one - * texture of the atlas. If number of loaded images exceeds this - * parameter a new canvas will be created. + * Defines simple UI for nodes in webgl renderer. Each node is rendered as an image. + * + * @param {number} tilesPerTexture + * */ -Viva.Graph.View.webglAtlas = function (tilesPerTexture) { - var tilesPerRow = Math.sqrt(tilesPerTexture || 1024) << 0, - tileSize = tilesPerRow, - lastLoadedIdx = 1, - loadedImages = {}, - dirtyTimeoutId, - skipedDirty = 0, - textures = [], - trackedUrls = [], - that, - - isPowerOf2 = function (n) { - return (n & (n - 1)) === 0; - }, - createTexture = function () { - var texture = new Viva.Graph.View.Texture(tilesPerRow * tileSize); - textures.push(texture); - }, - getTileCoordinates = function (absolutePosition) { - var textureNumber = (absolutePosition / tilesPerTexture) << 0, - localTileNumber = (absolutePosition % tilesPerTexture), - row = (localTileNumber / tilesPerRow) << 0, - col = (localTileNumber % tilesPerRow); - - return {textureNumber : textureNumber, row : row, col: col}; - }, - markDirtyNow = function () { - that.isDirty = true; - skipedDirty = 0; - dirtyTimeoutId = null; - }, - markDirty = function () { - // delay this call, since it results in texture reload - if (dirtyTimeoutId) { - window.clearTimeout(dirtyTimeoutId); - skipedDirty += 1; - dirtyTimeoutId = null; - } - - if (skipedDirty > 10) { - markDirtyNow(); - } else { - dirtyTimeoutId = window.setTimeout(markDirtyNow, 400); - } - }, - - copy = function (from, to) { - var fromCanvas = textures[from.textureNumber].canvas, - toCtx = textures[to.textureNumber].ctx, - x = to.col * tileSize, - y = to.row * tileSize; - - toCtx.drawImage(fromCanvas, from.col * tileSize, from.row * tileSize, tileSize, tileSize, x, y, tileSize, tileSize); - textures[from.textureNumber].isDirty = true; - textures[to.textureNumber].isDirty = true; - }, - - drawAt = function (tileNumber, img, callback) { - var tilePosition = getTileCoordinates(tileNumber), - coordinates = { offset : tileNumber }; - - if (tilePosition.textureNumber >= textures.length) { - createTexture(); - } - var currentTexture = textures[tilePosition.textureNumber]; - - currentTexture.ctx.drawImage(img, tilePosition.col * tileSize, tilePosition.row * tileSize, tileSize, tileSize); - trackedUrls[tileNumber] = img.src; - - loadedImages[img.src] = coordinates; - currentTexture.isDirty = true; - - callback(coordinates); - }; - - if (!isPowerOf2(tilesPerTexture)) { - throw "Tiles per texture should be power of two."; +function webglImageNodeProgram(tilesPerTexture) { + // WebGL is gian state machine, we store some properties of the state here: + var ATTRIBUTES_PER_PRIMITIVE = 18; + var nodesFS = createNodeFragmentShader(); + var nodesVS = createNodeVertexShader(); + var tilesPerTexture = tilesPerTexture || 1024; // TODO: Get based on max texture size + var atlas; + var program; + var gl; + var buffer; + var utils; + var locations; + var nodesCount = 0; + var nodes = new Float32Array(64); + var width; + var height; + var transform; + var sizeDirty; + + + return { + load: load, + + /** + * Updates position of current node in the buffer of nodes. + * + * @param idx - index of current node. + * @param pos - new position of the node. + */ + position: position, + + createNode: createNode, + + removeNode: removeNode, + + replaceProperties: replaceProperties, + + updateTransform: updateTransform, + + updateSize: updateSize, + + render: render + }; + + function refreshTexture(texture, idx) { + if (texture.nativeObject) { + gl.deleteTexture(texture.nativeObject); } - // this is the return object - that = { - /** - * indicates whether atlas has changed texture in it. If true then - * some of the textures has isDirty flag set as well. - */ - isDirty : false, - - /** - * Clears any signs of atlas changes. - */ - clearDirty : function () { - var i; - this.isDirty = false; - for (i = 0; i < textures.length; ++i) { - textures[i].isDirty = false; - } - }, - - /** - * Removes given url from colleciton of tiles in the atlas. - */ - remove : function (imgUrl) { - var coordinates = loadedImages[imgUrl]; - if (!coordinates) { return false; } - delete loadedImages[imgUrl]; - lastLoadedIdx -= 1; - - - if (lastLoadedIdx === coordinates.offset) { - return true; // Ignore if it's last image in the whole set. - } - - var tileToRemove = getTileCoordinates(coordinates.offset), - lastTileInSet = getTileCoordinates(lastLoadedIdx); - - copy(lastTileInSet, tileToRemove); - - var replacedOffset = loadedImages[trackedUrls[lastLoadedIdx]]; - replacedOffset.offset = coordinates.offset; - trackedUrls[coordinates.offset] = trackedUrls[lastLoadedIdx]; - - markDirty(); - return true; - }, - - /** - * Gets all textures in the atlas. - */ - getTextures : function () { - return textures; // I trust you... - }, - - /** - * Gets coordinates of the given image in the atlas. Coordinates is an object: - * {offset : int } - where offset is an absolute position of the image in the - * atlas. - * - * Absolute means it can be larger than tilesPerTexture parameter, and in that - * case clients should get next texture in getTextures() collection. - */ - getCoordinates : function (imgUrl) { - return loadedImages[imgUrl]; - }, - - /** - * Asynchronously Loads the image to the atlas. Cross-domain security - * limitation applies. - */ - load : function (imgUrl, callback) { - if (loadedImages.hasOwnProperty(imgUrl)) { - callback(loadedImages[imgUrl]); - } else { - var img = new window.Image(), - imgId = lastLoadedIdx; - - lastLoadedIdx += 1; - img.crossOrigin = "anonymous"; - img.onload = function () { - markDirty(); - drawAt(imgId, img, callback); - }; - - img.src = imgUrl; - } + var nativeObject = gl.createTexture(); + gl.activeTexture(gl["TEXTURE" + idx]); + gl.bindTexture(gl.TEXTURE_2D, nativeObject); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.canvas); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST); + + gl.generateMipmap(gl.TEXTURE_2D); + gl.uniform1i(locations["sampler" + idx], idx); + + texture.nativeObject = nativeObject; + } + + function ensureAtlasTextureUpdated() { + if (atlas.isDirty) { + var textures = atlas.getTextures(), + i; + for (i = 0; i < textures.length; ++i) { + if (textures[i].isDirty || !textures[i].nativeObject) { + refreshTexture(textures[i], i); } - }; + } - return that; -}; + atlas.clearDirty(); + } + } + + function load(glContext) { + gl = glContext; + utils = glUtils(glContext); + + atlas = new WebglAtlas(tilesPerTexture); + + program = utils.createProgram(nodesVS, nodesFS); + gl.useProgram(program); + locations = utils.getLocations(program, ["a_vertexPos", "a_customAttributes", "u_screenSize", "u_transform", "u_sampler0", "u_sampler1", "u_sampler2", "u_sampler3", "u_tilesPerTexture"]); + + gl.uniform1f(locations.tilesPerTexture, tilesPerTexture); + + gl.enableVertexAttribArray(locations.vertexPos); + gl.enableVertexAttribArray(locations.customAttributes); + + buffer = gl.createBuffer(); + } + + function position(nodeUI, pos) { + var idx = nodeUI.id * ATTRIBUTES_PER_PRIMITIVE; + nodes[idx] = pos.x - nodeUI.size; + nodes[idx + 1] = -pos.y - nodeUI.size; + nodes[idx + 2] = nodeUI._offset * 4; + + nodes[idx + 3] = pos.x + nodeUI.size; + nodes[idx + 4] = -pos.y - nodeUI.size; + nodes[idx + 5] = nodeUI._offset * 4 + 1; + + nodes[idx + 6] = pos.x - nodeUI.size; + nodes[idx + 7] = -pos.y + nodeUI.size; + nodes[idx + 8] = nodeUI._offset * 4 + 2; + + nodes[idx + 9] = pos.x - nodeUI.size; + nodes[idx + 10] = -pos.y + nodeUI.size; + nodes[idx + 11] = nodeUI._offset * 4 + 2; + + nodes[idx + 12] = pos.x + nodeUI.size; + nodes[idx + 13] = -pos.y - nodeUI.size; + nodes[idx + 14] = nodeUI._offset * 4 + 1; + + nodes[idx + 15] = pos.x + nodeUI.size; + nodes[idx + 16] = -pos.y + nodeUI.size; + nodes[idx + 17] = nodeUI._offset * 4 + 3; + } + + function createNode(ui) { + nodes = utils.extendArray(nodes, nodesCount, ATTRIBUTES_PER_PRIMITIVE); + nodesCount += 1; + + var coordinates = atlas.getCoordinates(ui.src); + if (coordinates) { + ui._offset = coordinates.offset; + } else { + ui._offset = 0; + // Image is not yet loaded into the atlas. Reload it: + atlas.load(ui.src, function(coordinates) { + ui._offset = coordinates.offset; + }); + } + } -/** - * Defines simple UI for nodes in webgl renderer. Each node is rendered as an image. - */ -Viva.Graph.View.webglImageNodeProgram = function () { - var ATTRIBUTES_PER_PRIMITIVE = 18, - nodesFS = [ - "precision mediump float;", - "varying vec4 color;", - "varying vec3 vTextureCoord;", - "uniform sampler2D u_sampler0;", - "uniform sampler2D u_sampler1;", - "uniform sampler2D u_sampler2;", - "uniform sampler2D u_sampler3;", - - "void main(void) {", - " if (vTextureCoord.z == 0.) {", - " gl_FragColor = texture2D(u_sampler0, vTextureCoord.xy);", - " } else if (vTextureCoord.z == 1.) {", - " gl_FragColor = texture2D(u_sampler1, vTextureCoord.xy);", - " } else if (vTextureCoord.z == 2.) {", - " gl_FragColor = texture2D(u_sampler2, vTextureCoord.xy);", - " } else if (vTextureCoord.z == 3.) {", - " gl_FragColor = texture2D(u_sampler3, vTextureCoord.xy);", - " } else { gl_FragColor = vec4(0, 1, 0, 1); }", - "}" - ].join("\n"), - - nodesVS = [ - "attribute vec2 a_vertexPos;", - - "attribute float a_customAttributes;", - "uniform vec2 u_screenSize;", - "uniform mat4 u_transform;", - "uniform float u_tilesPerTexture;", - "varying vec3 vTextureCoord;", - - "void main(void) {", - " gl_Position = u_transform * vec4(a_vertexPos/u_screenSize, 0, 1);", - "float corner = mod(a_customAttributes, 4.);", - "float tileIndex = mod(floor(a_customAttributes / 4.), u_tilesPerTexture);", - "float tilesPerRow = sqrt(u_tilesPerTexture);", - "float tileSize = 1./tilesPerRow;", - "float tileColumn = mod(tileIndex, tilesPerRow);", - "float tileRow = floor(tileIndex/tilesPerRow);", - - "if(corner == 0.0) {", - " vTextureCoord.xy = vec2(0, 1);", - "} else if(corner == 1.0) {", - " vTextureCoord.xy = vec2(1, 1);", - "} else if(corner == 2.0) {", - " vTextureCoord.xy = vec2(0, 0);", - "} else {", - " vTextureCoord.xy = vec2(1, 0);", - "}", - - "vTextureCoord *= tileSize;", - "vTextureCoord.x += tileColumn * tileSize;", - "vTextureCoord.y += tileRow * tileSize;", - "vTextureCoord.z = floor(floor(a_customAttributes / 4.)/u_tilesPerTexture);", - "}" - ].join("\n"), - - tilesPerTexture = 1024, // TODO: Get based on max texture size - atlas; - - var program, - gl, - buffer, - utils, - locations, - nodesCount = 0, - nodes = new Float32Array(64), - width, - height, - transform, - sizeDirty, - - refreshTexture = function (texture, idx) { - if (texture.nativeObject) { - gl.deleteTexture(texture.nativeObject); - } - - var nativeObject = gl.createTexture(); - gl.activeTexture(gl["TEXTURE" + idx]); - gl.bindTexture(gl.TEXTURE_2D, nativeObject); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.canvas); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST); - - gl.generateMipmap(gl.TEXTURE_2D); - gl.uniform1i(locations["sampler" + idx], idx); - - texture.nativeObject = nativeObject; - }, - - ensureAtlasTextureUpdated = function () { - if (atlas.isDirty) { - var textures = atlas.getTextures(), - i; - for (i = 0; i < textures.length; ++i) { - if (textures[i].isDirty || !textures[i].nativeObject) { - refreshTexture(textures[i], i); - } - } - - atlas.clearDirty(); - } - }; - - return { - load : function (glContext) { - gl = glContext; - utils = Viva.Graph.webgl(glContext); - - atlas = new Viva.Graph.View.webglAtlas(tilesPerTexture); - - program = utils.createProgram(nodesVS, nodesFS); - gl.useProgram(program); - locations = utils.getLocations(program, ["a_vertexPos", "a_customAttributes", "u_screenSize", "u_transform", "u_sampler0", "u_sampler1", "u_sampler2", "u_sampler3", "u_tilesPerTexture"]); - - gl.uniform1f(locations.tilesPerTexture, tilesPerTexture); - - gl.enableVertexAttribArray(locations.vertexPos); - gl.enableVertexAttribArray(locations.customAttributes); - - buffer = gl.createBuffer(); - }, - - /** - * Updates position of current node in the buffer of nodes. - * - * @param idx - index of current node. - * @param pos - new position of the node. - */ - position : function (nodeUI, pos) { - var idx = nodeUI.id * ATTRIBUTES_PER_PRIMITIVE; - nodes[idx] = pos.x - nodeUI.size; - nodes[idx + 1] = pos.y - nodeUI.size; - nodes[idx + 2] = nodeUI._offset * 4; - - nodes[idx + 3] = pos.x + nodeUI.size; - nodes[idx + 4] = pos.y - nodeUI.size; - nodes[idx + 5] = nodeUI._offset * 4 + 1; - - nodes[idx + 6] = pos.x - nodeUI.size; - nodes[idx + 7] = pos.y + nodeUI.size; - nodes[idx + 8] = nodeUI._offset * 4 + 2; - - nodes[idx + 9] = pos.x - nodeUI.size; - nodes[idx + 10] = pos.y + nodeUI.size; - nodes[idx + 11] = nodeUI._offset * 4 + 2; - - nodes[idx + 12] = pos.x + nodeUI.size; - nodes[idx + 13] = pos.y - nodeUI.size; - nodes[idx + 14] = nodeUI._offset * 4 + 1; - - nodes[idx + 15] = pos.x + nodeUI.size; - nodes[idx + 16] = pos.y + nodeUI.size; - nodes[idx + 17] = nodeUI._offset * 4 + 3; - }, - - createNode : function (ui) { - nodes = utils.extendArray(nodes, nodesCount, ATTRIBUTES_PER_PRIMITIVE); - nodesCount += 1; - - var coordinates = atlas.getCoordinates(ui.src); - if (coordinates) { - ui._offset = coordinates.offset; - } else { - ui._offset = 0; - // Image is not yet loaded into the atlas. Reload it: - atlas.load(ui.src, function (coordinates) { - ui._offset = coordinates.offset; - }); - } - }, - - removeNode : function (nodeUI) { - if (nodesCount > 0) { nodesCount -= 1; } - - if (nodeUI.id < nodesCount && nodesCount > 0) { - if (nodeUI.src) { - atlas.remove(nodeUI.src); - } - - utils.copyArrayPart(nodes, nodeUI.id * ATTRIBUTES_PER_PRIMITIVE, nodesCount * ATTRIBUTES_PER_PRIMITIVE, ATTRIBUTES_PER_PRIMITIVE); - } - }, - - replaceProperties : function (replacedNode, newNode) { - newNode._offset = replacedNode._offset; - }, - - updateTransform : function (newTransform) { - sizeDirty = true; - transform = newTransform; - }, - - updateSize : function (w, h) { - width = w; - height = h; - sizeDirty = true; - }, - - render : function () { - gl.useProgram(program); - gl.bindBuffer(gl.ARRAY_BUFFER, buffer); - gl.bufferData(gl.ARRAY_BUFFER, nodes, gl.DYNAMIC_DRAW); - - if (sizeDirty) { - sizeDirty = false; - gl.uniformMatrix4fv(locations.transform, false, transform); - gl.uniform2f(locations.screenSize, width, height); - } - - gl.vertexAttribPointer(locations.vertexPos, 2, gl.FLOAT, false, 3 * Float32Array.BYTES_PER_ELEMENT, 0); - gl.vertexAttribPointer(locations.customAttributes, 1, gl.FLOAT, false, 3 * Float32Array.BYTES_PER_ELEMENT, 2 * 4); - - ensureAtlasTextureUpdated(); - - gl.drawArrays(gl.TRIANGLES, 0, nodesCount * 6); - } - }; -}; \ No newline at end of file + function removeNode(nodeUI) { + if (nodesCount > 0) { + nodesCount -= 1; + } + + if (nodeUI.id < nodesCount && nodesCount > 0) { + if (nodeUI.src) { + atlas.remove(nodeUI.src); + } + + utils.copyArrayPart(nodes, nodeUI.id * ATTRIBUTES_PER_PRIMITIVE, nodesCount * ATTRIBUTES_PER_PRIMITIVE, ATTRIBUTES_PER_PRIMITIVE); + } + } + + function replaceProperties(replacedNode, newNode) { + newNode._offset = replacedNode._offset; + } + + function updateTransform(newTransform) { + sizeDirty = true; + transform = newTransform; + } + + function updateSize(w, h) { + width = w; + height = h; + sizeDirty = true; + } + + function render() { + gl.useProgram(program); + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + gl.bufferData(gl.ARRAY_BUFFER, nodes, gl.DYNAMIC_DRAW); + + if (sizeDirty) { + sizeDirty = false; + gl.uniformMatrix4fv(locations.transform, false, transform); + gl.uniform2f(locations.screenSize, width, height); + } + + gl.vertexAttribPointer(locations.vertexPos, 2, gl.FLOAT, false, 3 * Float32Array.BYTES_PER_ELEMENT, 0); + gl.vertexAttribPointer(locations.customAttributes, 1, gl.FLOAT, false, 3 * Float32Array.BYTES_PER_ELEMENT, 2 * 4); + + ensureAtlasTextureUpdated(); + + gl.drawArrays(gl.TRIANGLES, 0, nodesCount * 6); + } +} + +// TODO: Use glslify for shaders +function createNodeFragmentShader() { + return [ + "precision mediump float;", + "varying vec4 color;", + "varying vec3 vTextureCoord;", + "uniform sampler2D u_sampler0;", + "uniform sampler2D u_sampler1;", + "uniform sampler2D u_sampler2;", + "uniform sampler2D u_sampler3;", + + "void main(void) {", + " if (vTextureCoord.z == 0.) {", + " gl_FragColor = texture2D(u_sampler0, vTextureCoord.xy);", + " } else if (vTextureCoord.z == 1.) {", + " gl_FragColor = texture2D(u_sampler1, vTextureCoord.xy);", + " } else if (vTextureCoord.z == 2.) {", + " gl_FragColor = texture2D(u_sampler2, vTextureCoord.xy);", + " } else if (vTextureCoord.z == 3.) {", + " gl_FragColor = texture2D(u_sampler3, vTextureCoord.xy);", + " } else { gl_FragColor = vec4(0, 1, 0, 1); }", + "}" + ].join("\n"); +} + +function createNodeVertexShader() { + return [ + "attribute vec2 a_vertexPos;", + + "attribute float a_customAttributes;", + "uniform vec2 u_screenSize;", + "uniform mat4 u_transform;", + "uniform float u_tilesPerTexture;", + "varying vec3 vTextureCoord;", + + "void main(void) {", + " gl_Position = u_transform * vec4(a_vertexPos/u_screenSize, 0, 1);", + "float corner = mod(a_customAttributes, 4.);", + "float tileIndex = mod(floor(a_customAttributes / 4.), u_tilesPerTexture);", + "float tilesPerRow = sqrt(u_tilesPerTexture);", + "float tileSize = 1./tilesPerRow;", + "float tileColumn = mod(tileIndex, tilesPerRow);", + "float tileRow = floor(tileIndex/tilesPerRow);", + + "if(corner == 0.0) {", + " vTextureCoord.xy = vec2(0, 1);", + "} else if(corner == 1.0) {", + " vTextureCoord.xy = vec2(1, 1);", + "} else if(corner == 2.0) {", + " vTextureCoord.xy = vec2(0, 0);", + "} else {", + " vTextureCoord.xy = vec2(1, 0);", + "}", + + "vTextureCoord *= tileSize;", + "vTextureCoord.x += tileColumn * tileSize;", + "vTextureCoord.y += tileRow * tileSize;", + "vTextureCoord.z = floor(floor(a_customAttributes / 4.)/u_tilesPerTexture);", + "}" + ].join("\n"); +} diff --git a/src/WebGL/webglInputEvents.js b/src/WebGL/webglInputEvents.js index 653c0f9..381b80b 100644 --- a/src/WebGL/webglInputEvents.js +++ b/src/WebGL/webglInputEvents.js @@ -1,219 +1,261 @@ +var documentEvents = require('../Utils/documentEvents.js'); + +module.exports = webglInputEvents; + /** * Monitors graph-related mouse input in webgl graphics and notifies subscribers. * * @param {Viva.Graph.View.webglGraphics} webglGraphics - * @param {Viva.Graph.graph} graph */ -Viva.Graph.webglInputEvents = function (webglGraphics, graph) { - if (webglGraphics.webglInputEvents) { - // Don't listen twice, if we are already attached to this graphics: - return webglGraphics.webglInputEvents; +function webglInputEvents(webglGraphics) { + if (webglGraphics.webglInputEvents) { + // Don't listen twice, if we are already attached to this graphics: + return webglGraphics.webglInputEvents; + } + + var mouseCapturedNode = null, + mouseEnterCallback = [], + mouseLeaveCallback = [], + mouseDownCallback = [], + mouseUpCallback = [], + mouseMoveCallback = [], + clickCallback = [], + dblClickCallback = [], + prevSelectStart, + boundRect; + + var root = webglGraphics.getGraphicsRoot(); + startListen(root); + + var api = { + mouseEnter: mouseEnter, + mouseLeave: mouseLeave, + mouseDown: mouseDown, + mouseUp: mouseUp, + mouseMove: mouseMove, + click: click, + dblClick: dblClick, + mouseCapture: mouseCapture, + releaseMouseCapture: releaseMouseCapture + }; + + // TODO I don't remember why this is needed: + webglGraphics.webglInputEvents = api; + + return api; + + function releaseMouseCapture() { + mouseCapturedNode = null; + } + + function mouseCapture(node) { + mouseCapturedNode = node; + } + + function dblClick(callback) { + if (typeof callback === 'function') { + dblClickCallback.push(callback); } + return api; + } - var preciseCheck = function (node, x, y) { - if (node.ui && node.ui.size) { - var pos = node.position, - half = node.ui.size; - - return pos.x - half < x && x < pos.x + half && - pos.y - half < y && y < pos.y + half; - } - - return true; - }, - mouseCapturedNode = null, - - spatialIndex = Viva.Graph.spatialIndex(graph, preciseCheck), - mouseEnterCallback = [], - mouseLeaveCallback = [], - mouseDownCallback = [], - mouseUpCallback = [], - mouseMoveCallback = [], - clickCallback = [], - dblClickCallback = [], - documentEvents = Viva.Graph.Utils.events(window.document), - prevSelectStart, - boundRect, - - stopPropagation = function (e) { - if (e.stopPropagation) { - e.stopPropagation(); - } else { - e.cancelBubble = true; - } - }, - - handleDisabledEvent = function (e) { - stopPropagation(e); - return false; - }, - - invoke = function (callbacksChain, args) { - var i, stopPropagation; - for (i = 0; i < callbacksChain.length; i += 1) { - stopPropagation = callbacksChain[i].apply(undefined, args); - if (stopPropagation) { return true; } - } - }, - - startListen = function (root) { - var pos = {x : 0, y : 0}, - lastFound = null, - lastClickTime = +new Date(), - - handleMouseMove = function (e) { - invoke(mouseMoveCallback, [lastFound, e]); - pos.x = e.clientX; - pos.y = e.clientY; - }, - - handleMouseUp = function () { - documentEvents.stop('mousemove', handleMouseMove); - documentEvents.stop('mouseup', handleMouseUp); - }, - - updateBoundRect = function () { - boundRect = root.getBoundingClientRect(); - }; - - window.addEventListener('resize', updateBoundRect); - updateBoundRect(); - - // mouse move inside container serves only to track mouse enter/leave events. - root.addEventListener('mousemove', - function (e) { - if (mouseCapturedNode) { - return; - } - - var cancelBubble = false, - node; - - pos.x = e.clientX - boundRect.left; - pos.y = e.clientY - boundRect.top; - - webglGraphics.getGraphCoordinates(pos); - node = spatialIndex.getNodeAt(pos.x, pos.y); - - if (node && lastFound !== node) { - lastFound = node; - cancelBubble = cancelBubble || invoke(mouseEnterCallback, [lastFound]); - } else if (node === null && lastFound !== node) { - cancelBubble = cancelBubble || invoke(mouseLeaveCallback, [lastFound]); - lastFound = null; - } - - if (cancelBubble) { stopPropagation(e); } - }); - - root.addEventListener('mousedown', - function (e) { - var cancelBubble = false, - args; - - pos.x = e.clientX - boundRect.left; - pos.y = e.clientY - boundRect.top; - webglGraphics.getGraphCoordinates(pos); - - args = [spatialIndex.getNodeAt(pos.x, pos.y), e]; - if (args[0]) { - cancelBubble = invoke(mouseDownCallback, args); - // we clicked on a node. Following drag should be handled on document events: - documentEvents.on('mousemove', handleMouseMove); - documentEvents.on('mouseup', handleMouseUp); - - prevSelectStart = window.document.onselectstart; - - window.document.onselectstart = handleDisabledEvent; - - lastFound = args[0]; - } else { - lastFound = null; - } - if (cancelBubble) { stopPropagation(e); } - }); - - root.addEventListener('mouseup', - function (e) { - var clickTime = +new Date(), - args; - - pos.x = e.clientX - boundRect.left; - pos.y = e.clientY - boundRect.top; - webglGraphics.getGraphCoordinates(pos); - - args = [spatialIndex.getNodeAt(pos.x, pos.y), e]; - if (args[0]) { - window.document.onselectstart = prevSelectStart; - - if (clickTime - lastClickTime < 400 && args[0] === lastFound) { - invoke(dblClickCallback, args); - } else { - invoke(clickCallback, args); - } - lastClickTime = clickTime; - - if (invoke(mouseUpCallback, args)) { - stopPropagation(e); - } - } - }); - }; - - // webgl may not be initialized at this point. Pass callback - // to start listen after graphics root is ready. - webglGraphics.getGraphicsRoot(startListen); - - webglGraphics.webglInputEvents = { - mouseEnter : function (callback) { - if (typeof callback === 'function') { - mouseEnterCallback.push(callback); - } - return this; - }, - mouseLeave : function (callback) { - if (typeof callback === 'function') { - mouseLeaveCallback.push(callback); - } - return this; - }, - mouseDown : function (callback) { - if (typeof callback === 'function') { - mouseDownCallback.push(callback); - } - return this; - }, - mouseUp : function (callback) { - if (typeof callback === 'function') { - mouseUpCallback.push(callback); - } - return this; - }, - mouseMove : function (callback) { - if (typeof callback === 'function') { - mouseMoveCallback.push(callback); - } - return this; - }, - click : function (callback) { - if (typeof callback === 'function') { - clickCallback.push(callback); - } - return this; - }, - dblClick : function (callback) { - if (typeof callback === 'function') { - dblClickCallback.push(callback); - } - return this; - }, - mouseCapture : function (node) { - mouseCapturedNode = node; - }, - releaseMouseCapture : function () { - mouseCapturedNode = null; + function click(callback) { + if (typeof callback === 'function') { + clickCallback.push(callback); + } + return api; + } + + function mouseMove(callback) { + if (typeof callback === 'function') { + mouseMoveCallback.push(callback); + } + return api; + } + + function mouseUp(callback) { + if (typeof callback === 'function') { + mouseUpCallback.push(callback); + } + return api; + } + + function mouseDown(callback) { + if (typeof callback === 'function') { + mouseDownCallback.push(callback); + } + return api; + } + + function mouseLeave(callback) { + if (typeof callback === 'function') { + mouseLeaveCallback.push(callback); + } + return api; + } + + function mouseEnter(callback) { + if (typeof callback === 'function') { + mouseEnterCallback.push(callback); + } + return api; + } + + function preciseCheck(nodeUI, x, y) { + if (nodeUI && nodeUI.size) { + var pos = nodeUI.position, + half = nodeUI.size; + + return pos.x - half < x && x < pos.x + half && + pos.y - half < y && y < pos.y + half; + } + + return true; + } + + function getNodeAtClientPos(pos) { + return webglGraphics.getNodeAtClientPos(pos, preciseCheck); + } + + function stopPropagation(e) { + if (e.stopPropagation) { + e.stopPropagation(); + } else { + e.cancelBubble = true; + } + } + + function handleDisabledEvent(e) { + stopPropagation(e); + return false; + } + + function invoke(callbacksChain, args) { + var i, stopPropagation; + for (i = 0; i < callbacksChain.length; i += 1) { + stopPropagation = callbacksChain[i].apply(undefined, args); + if (stopPropagation) { + return true; + } + } + } + + function startListen(root) { + var pos = { + x: 0, + y: 0 + }, + lastFound = null, + lastUpdate = 1, + lastClickTime = +new Date(), + + handleMouseMove = function(e) { + invoke(mouseMoveCallback, [lastFound, e]); + pos.x = e.clientX; + pos.y = e.clientY; + }, + + handleMouseUp = function() { + documentEvents.off('mousemove', handleMouseMove); + documentEvents.off('mouseup', handleMouseUp); + }, + + updateBoundRect = function() { + boundRect = root.getBoundingClientRect(); + }; + + window.addEventListener('resize', updateBoundRect); + updateBoundRect(); + + // mouse move inside container serves only to track mouse enter/leave events. + root.addEventListener('mousemove', + function(e) { + if (mouseCapturedNode) { + return; + } + if (lastUpdate++ % 7 === 0) { + // since there is no bullet proof method to detect resize + // event, we preemptively update the bounding rectangle + updateBoundRect(); + lastUpdate = 1; + } + var cancelBubble = false, + node; + + pos.x = e.clientX - boundRect.left; + pos.y = e.clientY - boundRect.top; + + node = getNodeAtClientPos(pos); + + if (node && lastFound !== node) { + if(lastFound){ + invoke(mouseLeaveCallback, [lastFound]); + } + lastFound = node; + cancelBubble = cancelBubble || invoke(mouseEnterCallback, [lastFound]); + } else if (node === null && lastFound !== node) { + cancelBubble = cancelBubble || invoke(mouseLeaveCallback, [lastFound]); + lastFound = null; } - }; - return webglGraphics.webglInputEvents; -}; + if (cancelBubble) { + stopPropagation(e); + } + }); + + root.addEventListener('mousedown', + function(e) { + var cancelBubble = false, + args; + updateBoundRect(); + pos.x = e.clientX - boundRect.left; + pos.y = e.clientY - boundRect.top; + + args = [getNodeAtClientPos(pos), e]; + if (args[0]) { + cancelBubble = invoke(mouseDownCallback, args); + // we clicked on a node. Following drag should be handled on document events: + documentEvents.on('mousemove', handleMouseMove); + documentEvents.on('mouseup', handleMouseUp); + + prevSelectStart = window.document.onselectstart; + + window.document.onselectstart = handleDisabledEvent; + + lastFound = args[0]; + } else { + lastFound = null; + } + if (cancelBubble) { + stopPropagation(e); + } + }); + + root.addEventListener('mouseup', + function(e) { + var clickTime = +new Date(), + args; + + pos.x = e.clientX - boundRect.left; + pos.y = e.clientY - boundRect.top; + + var nodeAtClientPos = getNodeAtClientPos(pos); + var sameNode = nodeAtClientPos === lastFound; + args = [nodeAtClientPos || lastFound, e]; + if (args[0]) { + window.document.onselectstart = prevSelectStart; + + if (clickTime - lastClickTime < 400 && sameNode) { + invoke(dblClickCallback, args); + } else { + invoke(clickCallback, args); + } + lastClickTime = clickTime; + + if (invoke(mouseUpCallback, args)) { + stopPropagation(e); + } + } + }); + } +} diff --git a/src/WebGL/webglLine.js b/src/WebGL/webglLine.js new file mode 100644 index 0000000..cef60be --- /dev/null +++ b/src/WebGL/webglLine.js @@ -0,0 +1,19 @@ +var parseColor = require('./parseColor.js'); + +module.exports = webglLine; + +/** + * Defines a webgl line. This class has no rendering logic at all, + * it's just passed to corresponding shader and the shader should + * figure out how to render it. + * + */ +function webglLine(color) { + return { + /** + * Gets or sets color of the line. If you set this property externally + * make sure it always come as integer of 0xRRGGBBAA format + */ + color: parseColor(color) + }; +} diff --git a/src/WebGL/webglLinkProgram.js b/src/WebGL/webglLinkProgram.js index 091dcf9..cf23fad 100644 --- a/src/WebGL/webglLinkProgram.js +++ b/src/WebGL/webglLinkProgram.js @@ -1,14 +1,16 @@ /** * @fileOverview Defines a naive form of links for webglGraphics class. * This form allows to change color of links. - * - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com - */ + **/ + +var glUtils = require('./webgl.js'); + +module.exports = webglLinkProgram; /** * Defines UI for links in webgl renderer. */ -Viva.Graph.View.webglLinkProgram = function () { +function webglLinkProgram() { var ATTRIBUTES_PER_PRIMITIVE = 6, // primitive is Line with two points. Each has x,y and color = 3 * 2 attributes. BYTES_PER_LINK = 2 * (2 * Float32Array.BYTES_PER_ELEMENT + Uint32Array.BYTES_PER_ELEMENT), // two nodes * (x, y + color) linksFS = [ @@ -68,7 +70,7 @@ Viva.Graph.View.webglLinkProgram = function () { return { load : function (glContext) { gl = glContext; - utils = Viva.Graph.webgl(glContext); + utils = glUtils(glContext); program = utils.createProgram(linksVS, linksFS); gl.useProgram(program); @@ -151,4 +153,4 @@ Viva.Graph.View.webglLinkProgram = function () { return frontLinkId; } }; -}; +} diff --git a/src/WebGL/webglNodeProgram.js b/src/WebGL/webglNodeProgram.js index 8f413ff..402cb48 100644 --- a/src/WebGL/webglNodeProgram.js +++ b/src/WebGL/webglNodeProgram.js @@ -2,141 +2,162 @@ * @fileOverview Defines a naive form of nodes for webglGraphics class. * This form allows to change color of node. Shape of nodes is rectangular. * - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com + * @author Andrei Kashcha (aka anvaka) / https://github.com/anvaka */ +var glUtils = require('./webgl.js'); + +module.exports = webglNodeProgram; + /** * Defines simple UI for nodes in webgl renderer. Each node is rendered as square. Color and size can be changed. */ -Viva.Graph.View.webglNodeProgram = function () { - var ATTRIBUTES_PER_PRIMITIVE = 4, // Primitive is point, x, y, size, color - // x, y, z - floats, color = uint. - BYTES_PER_NODE = 3 * Float32Array.BYTES_PER_ELEMENT + Uint32Array.BYTES_PER_ELEMENT, - nodesFS = [ - 'precision mediump float;', - 'varying vec4 color;', - - 'void main(void) {', - ' gl_FragColor = color;', - '}' - ].join('\n'), - nodesVS = [ - 'attribute vec3 a_vertexPos;', - 'attribute vec4 a_color;', - 'uniform vec2 u_screenSize;', - 'uniform mat4 u_transform;', - 'varying vec4 color;', - - 'void main(void) {', - ' gl_Position = u_transform * vec4(a_vertexPos.xy/u_screenSize, 0, 1);', - ' gl_PointSize = a_vertexPos.z * u_transform[0][0];', - ' color = a_color.abgr;', - '}' - ].join('\n'), - - program, - gl, - buffer, - locations, - utils, - storage = new ArrayBuffer(16 * BYTES_PER_NODE), - positions = new Float32Array(storage), - colors = new Uint32Array(storage), - nodesCount = 0, - width, - height, - transform, - sizeDirty, - - ensureEnoughStorage = function () { - if ((nodesCount + 1) * BYTES_PER_NODE >= storage.byteLength) { - // Every time we run out of space create new array twice bigger. - // TODO: it seems buffer size is limited. Consider using multiple arrays for huge graphs - var extendedStorage = new ArrayBuffer(storage.byteLength * 2), - extendedPositions = new Float32Array(extendedStorage), - extendedColors = new Uint32Array(extendedStorage); - - extendedColors.set(colors); // should be enough to copy just one view. - positions = extendedPositions; - colors = extendedColors; - storage = extendedStorage; - } - }; - - return { - load : function (glContext) { - gl = glContext; - utils = Viva.Graph.webgl(glContext); - - program = utils.createProgram(nodesVS, nodesFS); - gl.useProgram(program); - locations = utils.getLocations(program, ['a_vertexPos', 'a_color', 'u_screenSize', 'u_transform']); - - gl.enableVertexAttribArray(locations.vertexPos); - gl.enableVertexAttribArray(locations.color); - - buffer = gl.createBuffer(); - }, - - /** - * Updates position of node in the buffer of nodes. - * - * @param idx - index of current node. - * @param pos - new position of the node. - */ - position : function (nodeUI, pos) { - var idx = nodeUI.id; - - positions[idx * ATTRIBUTES_PER_PRIMITIVE] = pos.x; - positions[idx * ATTRIBUTES_PER_PRIMITIVE + 1] = pos.y; - positions[idx * ATTRIBUTES_PER_PRIMITIVE + 2] = nodeUI.size; - - colors[idx * ATTRIBUTES_PER_PRIMITIVE + 3] = nodeUI.color; - }, - - updateTransform : function (newTransform) { - sizeDirty = true; - transform = newTransform; - }, - - updateSize : function (w, h) { - width = w; - height = h; - sizeDirty = true; - }, - - removeNode : function (node) { - if (nodesCount > 0) { nodesCount -= 1; } - - if (node.id < nodesCount && nodesCount > 0) { - // we can use colors as a 'view' into array array buffer. - utils.copyArrayPart(colors, node.id * ATTRIBUTES_PER_PRIMITIVE, nodesCount * ATTRIBUTES_PER_PRIMITIVE, ATTRIBUTES_PER_PRIMITIVE); - } - }, -/*jshint unused:false */ - createNode : function (node) { - ensureEnoughStorage(); - nodesCount += 1; - }, - - replaceProperties : function (replacedNode, newNode) {}, -/*jshint unused:true */ - - render : function () { - gl.useProgram(program); - gl.bindBuffer(gl.ARRAY_BUFFER, buffer); - gl.bufferData(gl.ARRAY_BUFFER, storage, gl.DYNAMIC_DRAW); - - if (sizeDirty) { - sizeDirty = false; - gl.uniformMatrix4fv(locations.transform, false, transform); - gl.uniform2f(locations.screenSize, width, height); - } - - gl.vertexAttribPointer(locations.vertexPos, 3, gl.FLOAT, false, ATTRIBUTES_PER_PRIMITIVE * Float32Array.BYTES_PER_ELEMENT, 0); - gl.vertexAttribPointer(locations.color, 4, gl.UNSIGNED_BYTE, true, ATTRIBUTES_PER_PRIMITIVE * Float32Array.BYTES_PER_ELEMENT, 3 * 4); - - gl.drawArrays(gl.POINTS, 0, nodesCount); - } - }; -}; \ No newline at end of file +function webglNodeProgram() { + var ATTRIBUTES_PER_PRIMITIVE = 4; // Primitive is point, x, y, size, color + // x, y, z - floats, color = uint. + var BYTES_PER_NODE = 3 * Float32Array.BYTES_PER_ELEMENT + Uint32Array.BYTES_PER_ELEMENT; + var nodesFS = [ + 'precision mediump float;', + 'varying vec4 color;', + + 'void main(void) {', + ' gl_FragColor = color;', + '}' + ].join('\n'); + var nodesVS = [ + 'attribute vec3 a_vertexPos;', + 'attribute vec4 a_color;', + 'uniform vec2 u_screenSize;', + 'uniform mat4 u_transform;', + 'varying vec4 color;', + + 'void main(void) {', + ' gl_Position = u_transform * vec4(a_vertexPos.xy/u_screenSize, 0, 1);', + ' gl_PointSize = a_vertexPos.z * u_transform[0][0];', + ' color = a_color.abgr;', + '}' + ].join('\n'); + + var program; + var gl; + var buffer; + var locations; + var utils; + var storage = new ArrayBuffer(16 * BYTES_PER_NODE); + var positions = new Float32Array(storage); + var colors = new Uint32Array(storage); + var nodesCount = 0; + var width; + var height; + var transform; + var sizeDirty; + + return { + load: load, + + /** + * Updates position of node in the buffer of nodes. + * + * @param idx - index of current node. + * @param pos - new position of the node. + */ + position: position, + + updateTransform: updateTransform, + + updateSize: updateSize, + + removeNode: removeNode, + + createNode: createNode, + + replaceProperties: replaceProperties, + + render: render + }; + + function ensureEnoughStorage() { + if ((nodesCount + 1) * BYTES_PER_NODE >= storage.byteLength) { + // Every time we run out of space create new array twice bigger. + // TODO: it seems buffer size is limited. Consider using multiple arrays for huge graphs + var extendedStorage = new ArrayBuffer(storage.byteLength * 2), + extendedPositions = new Float32Array(extendedStorage), + extendedColors = new Uint32Array(extendedStorage); + + extendedColors.set(colors); // should be enough to copy just one view. + positions = extendedPositions; + colors = extendedColors; + storage = extendedStorage; + } + } + + function load(glContext) { + gl = glContext; + utils = glUtils(glContext); + + program = utils.createProgram(nodesVS, nodesFS); + gl.useProgram(program); + locations = utils.getLocations(program, ['a_vertexPos', 'a_color', 'u_screenSize', 'u_transform']); + + gl.enableVertexAttribArray(locations.vertexPos); + gl.enableVertexAttribArray(locations.color); + + buffer = gl.createBuffer(); + } + + function position(nodeUI, pos) { + var idx = nodeUI.id; + + positions[idx * ATTRIBUTES_PER_PRIMITIVE] = pos.x; + positions[idx * ATTRIBUTES_PER_PRIMITIVE + 1] = -pos.y; + positions[idx * ATTRIBUTES_PER_PRIMITIVE + 2] = nodeUI.size; + + colors[idx * ATTRIBUTES_PER_PRIMITIVE + 3] = nodeUI.color; + } + + function updateTransform(newTransform) { + sizeDirty = true; + transform = newTransform; + } + + function updateSize(w, h) { + width = w; + height = h; + sizeDirty = true; + } + + function removeNode(node) { + if (nodesCount > 0) { + nodesCount -= 1; + } + + if (node.id < nodesCount && nodesCount > 0) { + // we can use colors as a 'view' into array array buffer. + utils.copyArrayPart(colors, node.id * ATTRIBUTES_PER_PRIMITIVE, nodesCount * ATTRIBUTES_PER_PRIMITIVE, ATTRIBUTES_PER_PRIMITIVE); + } + } + + function createNode() { + ensureEnoughStorage(); + nodesCount += 1; + } + + function replaceProperties(/* replacedNode, newNode */) {} + + function render() { + gl.useProgram(program); + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + gl.bufferData(gl.ARRAY_BUFFER, storage, gl.DYNAMIC_DRAW); + + if (sizeDirty) { + sizeDirty = false; + gl.uniformMatrix4fv(locations.transform, false, transform); + gl.uniform2f(locations.screenSize, width, height); + } + + gl.vertexAttribPointer(locations.vertexPos, 3, gl.FLOAT, false, ATTRIBUTES_PER_PRIMITIVE * Float32Array.BYTES_PER_ELEMENT, 0); + gl.vertexAttribPointer(locations.color, 4, gl.UNSIGNED_BYTE, true, ATTRIBUTES_PER_PRIMITIVE * Float32Array.BYTES_PER_ELEMENT, 3 * 4); + + gl.drawArrays(gl.POINTS, 0, nodesCount); + } +} diff --git a/src/WebGL/webglSquare.js b/src/WebGL/webglSquare.js new file mode 100644 index 0000000..abdeee5 --- /dev/null +++ b/src/WebGL/webglSquare.js @@ -0,0 +1,24 @@ +var parseColor = require('./parseColor.js'); + +module.exports = webglSquare; + +/** + * Can be used as a callback in the webglGraphics.node() function, to + * create a custom looking node. + * + * @param size - size of the node in pixels. + * @param color - color of the node in '#rrggbbaa' or '#rgb' format. + */ +function webglSquare(size, color) { + return { + /** + * Gets or sets size of the square side. + */ + size: typeof size === 'number' ? size : 10, + + /** + * Gets or sets color of the square. + */ + color: parseColor(color) + }; +} diff --git a/src/WebGL/webglUIModels.js b/src/WebGL/webglUIModels.js deleted file mode 100644 index ea0ad31..0000000 --- a/src/WebGL/webglUIModels.js +++ /dev/null @@ -1,101 +0,0 @@ -/** - * @fileOverview Defines a model objects to represents graph rendering - * primitives in webglGraphics. - * - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com - */ - -Viva.Graph.View.WebglUtils = function () { }; - -/** - * Parses various color strings and returns color value used in webgl shaders. - */ -Viva.Graph.View.WebglUtils.prototype.parseColor = function (color) { - var parsedColor = 0x009ee8ff; - - if (typeof color === 'string' && color) { - if (color.length === 4) { // #rgb - color = color.replace(/([^#])/g, '$1$1'); // duplicate each letter except first #. - } - if (color.length === 9) { // #rrggbbaa - parsedColor = parseInt(color.substr(1), 16); - } else if (color.length === 7) { // or #rrggbb. - parsedColor = (parseInt(color.substr(1), 16) << 8) | 0xff; - } else { - throw 'Color expected in hex format with preceding "#". E.g. #00ff00. Got value: ' + color; - } - } else if (typeof color === 'number') { - parsedColor = color; - } - - return parsedColor; -}; - -Viva.Graph.View._webglUtil = new Viva.Graph.View.WebglUtils(); // reuse this instance internally. - -/** - * Defines a webgl line. This class has no rendering logic at all, - * it's just passed to corresponding shader and the shader should - * figure out how to render it. - * - * @see Viva.Graph.View.webglLinkShader.position - */ -Viva.Graph.View.webglLine = function (color) { - return { - /** - * Gets or sets color of the line. If you set this property externally - * make sure it always come as integer of 0xRRGGBBAA format - */ - color : Viva.Graph.View._webglUtil.parseColor(color) - }; -}; - -/** - * Can be used as a callback in the webglGraphics.node() function, to - * create a custom looking node. - * - * @param size - size of the node in pixels. - * @param color - color of the node in '#rrggbbaa' or '#rgb' format. - */ -Viva.Graph.View.webglSquare = function (size, color) { - return { - /** - * Gets or sets size of the sqare side. - */ - size : typeof size === 'number' ? size : 10, - - /** - * Gets or sets color of the square. - */ - color : Viva.Graph.View._webglUtil.parseColor(color) - }; -}; - -/** - * Represents a model for image. - */ -Viva.Graph.View.webglImage = function (size, src) { - return { - /** - * Gets texture index where current image is placed.s - */ - _texture : 0, - - /** - * Gets offset in the texture where current image is placed. - */ - _offset : 0, - - /** - * Gets size of the square with the image. - */ - size : typeof size === 'number' ? size : 32, - - /** - * Source of the image. If image is comming not from your domain - * certain origin restrictions applies. - * See http://www.khronos.org/registry/webgl/specs/latest/#4.2 for more details. - */ - src : src - }; -}; \ No newline at end of file diff --git a/src/version.js b/src/version.js index 7d1323d..b977700 100644 --- a/src/version.js +++ b/src/version.js @@ -1 +1,2 @@ -Viva.Graph.version = '0.4.0'; \ No newline at end of file +// todo: this should be generated at build time. +module.exports = '0.10.1'; diff --git a/src/viva.js b/src/viva.js new file mode 100644 index 0000000..0ee7704 --- /dev/null +++ b/src/viva.js @@ -0,0 +1,112 @@ +/** + * This is an entry point for global namespace. If you want to use separate + * modules individually - you are more than welcome to do so. + */ + +var random = require('ngraph.random'); + +var Viva = { + lazyExtend: function() { + return require('ngraph.merge').apply(this, arguments); + }, + randomIterator: function() { + return random.randomIterator.apply(random, arguments); + }, + random: function() { + return random.random.apply(random, arguments); + }, + events: require('ngraph.events') +}; + +Viva.Graph = { + version: require('./version.js'), + graph: require('ngraph.graph'), + + serializer: function() { + return { + loadFromJSON: require('ngraph.fromjson'), + storeToJSON: require('ngraph.tojson') + }; + }, + + centrality: require('./Algorithms/centrality.js'), + operations: require('./Algorithms/operations.js'), + + geom: function() { + return { + intersect: require('gintersect'), + intersectRect: require('./Utils/intersectRect.js') + }; + }, + + webgl: require('./WebGL/webgl.js'), + webglInputEvents: require('./WebGL/webglInputEvents.js'), + + generator: function() { + return require('ngraph.generators'); + }, + + Input: { + domInputManager: require('./Input/domInputManager.js'), + webglInputManager: require('./Input/webglInputManager.js') + }, + + Utils: { + // TODO: move to Input + dragndrop: require('./Input/dragndrop.js'), + findElementPosition: require('./Utils/findElementPosition.js'), + timer: require('./Utils/timer.js'), + getDimension: require('./Utils/getDimensions.js'), + events: require('./Utils/backwardCompatibleEvents.js') + }, + + Layout: { + forceDirected: require('ngraph.forcelayout'), + constant: require('./Layout/constant.js') + }, + + View: { + // TODO: Move `webglXXX` out to webgl namespace + Texture: require('./WebGL/texture.js'), + // TODO: This should not be even exported + webglAtlas: require('./WebGL/webglAtlas.js'), + webglImageNodeProgram: require('./WebGL/webglImageNodeProgram.js'), + webglLinkProgram: require('./WebGL/webglLinkProgram.js'), + webglNodeProgram: require('./WebGL/webglNodeProgram.js'), + webglLine: require('./WebGL/webglLine.js'), + webglSquare: require('./WebGL/webglSquare.js'), + webglImage: require('./WebGL/webglImage.js'), + webglGraphics: require('./View/webglGraphics.js'), + // TODO: Deprecate this: + _webglUtil: { + parseColor: require('./WebGL/parseColor.js') + }, + + // TODO: move to svg namespace + svgGraphics: require('./View/svgGraphics.js'), + + renderer: require('./View/renderer.js'), + + // deprecated + cssGraphics: function() { + throw new Error('cssGraphics is deprecated. Please use older version of vivagraph (< 0.7) if you need it'); + }, + + svgNodeFactory: function() { + throw new Error('svgNodeFactory is deprecated. Please use older version of vivagraph (< 0.7) if you need it'); + }, + + community: function() { + throw new Error('community is deprecated. Please use vivagraph < 0.7 if you need it, or `https://github.com/anvaka/ngraph.slpa` module'); + } + }, + + Rect: require('./Utils/rect.js'), + + svg: require('simplesvg'), + + // TODO: should be camelCase + BrowserInfo: require('./Utils/browserInfo.js') +}; + +module.exports = Viva; diff --git a/src/vivagraph.js b/src/vivagraph.js deleted file mode 100644 index 18a71bb..0000000 --- a/src/vivagraph.js +++ /dev/null @@ -1,9 +0,0 @@ -/** - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com - */ -var Viva = Viva || {}; - -Viva.Graph = Viva.Graph || {}; -if (typeof module !== 'undefined' && module.exports) { - module.exports = Viva; -} diff --git a/test/constantLayout.js b/test/constantLayout.js new file mode 100644 index 0000000..f3e13aa --- /dev/null +++ b/test/constantLayout.js @@ -0,0 +1,57 @@ +/** + * Testing Viva.Graph.Layout.constant behavior. + */ + +var test = require('tap').test; +var Viva = require('../dist/vivagraph.js'); + +test('nodePositionGeneratedByDefault', function(t) { + var graph = Viva.Graph.generator().path(10), + layout = Viva.Graph.Layout.constant(graph); + + layout.run(); + + graph.forEachNode(function(node) { + var position = layout.getNodePosition(node.id); + t.ok(position, 'All nodes expected to have some position'); + t.ok(typeof position.x === 'number', 'Node position does not have a valid x position'); + t.ok(typeof position.y === 'number', 'Node position does not have a valid y position'); + }); + t.end(); +}); + +test('nodePositionUsesCustomCallback', function(t) { + var graph = Viva.Graph.generator().path(10), + layout = Viva.Graph.Layout.constant(graph), + placeNodeCallback = function() { + return { x: 42, y: 42 }; // all nodes should be placed at the same position. + }; + + layout.placeNode(placeNodeCallback); + layout.run(); + + graph.forEachNode(function(node) { + var position = layout.getNodePosition(node.id); + t.equals(position.x, 42, 'Node position does not have a valid x position'); + t.equals(position.y, 42, 'Node position does not have a valid y position'); + }); + t.end(); +}); + +test('getGraphRectReflectsDefaultSettings', function(t) { + var graph = Viva.Graph.generator().path(10), + layoutSettings = { + maxX: 42, + maxY: 42 + }, + layout = Viva.Graph.Layout.constant(graph, layoutSettings); + + layout.run(); + + graph.forEachNode(function(node) { + var position = layout.getNodePosition(node.id); + t.ok(position.x <= layoutSettings.maxX, 'Node position does not have a valid x position'); + t.ok(position.y <= layoutSettings.maxY, 'Node position does not have a valid y position'); + }); + t.end(); +}); diff --git a/test/forceBasedLayout.js b/test/forceBasedLayout.js new file mode 100644 index 0000000..68aa883 --- /dev/null +++ b/test/forceBasedLayout.js @@ -0,0 +1,34 @@ +var test = require('tap').test; +var Viva = require('../dist/vivagraph.js'); + +test('forceAttributesInitialized', function(t) { + var graph = Viva.Graph.generator().path(2); + + var layout = Viva.Graph.Layout.forceDirected(graph); + assertGraphHasPositions(graph, layout, t); + t.end(); +}); + +test('graphIsMonitored', function(t) { + var graph = Viva.Graph.graph(); + + var layout = Viva.Graph.Layout.forceDirected(graph); + graph.addNode(1); + assertGraphHasPositions(graph, layout, t); + graph.addNode(2); + assertGraphHasPositions(graph, layout, t); + graph.addLink(1, 2); + assertGraphHasPositions(graph, layout, t); + graph.removeNode(1); + assertGraphHasPositions(graph, layout, t); + t.end(); +}); + +function assertGraphHasPositions(graph, layout, test) { + graph.forEachNode(function(node) { + var position = layout.getNodePosition(node.id); + test.ok(position, 'All nodes expected to have some position'); + test.ok(typeof position.x === 'number', 'Node position does not have a valid x position'); + test.ok(typeof position.y === 'number', 'Node position does not have a valid y position'); + }); +} diff --git a/test/graphConstructions.js b/test/graphConstructions.js new file mode 100644 index 0000000..0a8ae11 --- /dev/null +++ b/test/graphConstructions.js @@ -0,0 +1,145 @@ +var test = require('tap').test; +var Viva = require('../dist/vivagraph.js'); + +test('addNode', function(t) { + var graph = Viva.Graph.graph(); + var customData = '31337'; + + var node = graph.addNode(1, customData); + + t.equals(graph.getNodesCount(), 1, 'addNode failed'); + t.equals(graph.getNode(1), node, 'invalid node returned by addNode (or getNode)'); + t.equals(node.data, customData, 'data was not set properly'); + t.equals(node.id, 1, 'node id was not set properly'); + t.end(); +}); + +test('addLink', function(t) { + var graph = Viva.Graph.graph(); + + var link = graph.addLink(1, 2), + firstNodeLinks = graph.getLinks(1), + secondNodeLinks = graph.getLinks(2); + + t.equals(graph.getNodesCount(), 2, 'addLink failed'); + t.equals(firstNodeLinks.length, 1, 'number of links of the first node is wrong'); + t.equals(secondNodeLinks.length, 1, 'number of links of the second node is wrong'); + t.equals(link, firstNodeLinks[0], 'invalid link in the first node'); + t.equals(link, secondNodeLinks[0], 'invalid link in the second node'); + t.end(); +}); + +test('addOneNodeFireChanged', function(t) { + var graph = Viva.Graph.graph(); + var testNodeId = 'hello world'; + graph.on('changed', function(changes) { + t.ok(changes && changes.length === 1, "Only one change should be recorded"); + t.equals(changes[0].node.id, testNodeId, "Wrong node change notification"); + t.equals(changes[0].changeType, 'add', "Add change type expected."); + }); + + graph.addNode(testNodeId); + t.end(); +}); + +test('addLinkFireChanged', function(t) { + var graph = Viva.Graph.graph(); + var fromId = 1, toId = 2; + graph.on('changed', function(changes) { + t.ok(changes && changes.length === 3, "Three change should be recorded: node, node and link"); + t.equals(changes[2].link.fromId, fromId, "Wrong link from Id"); + t.equals(changes[2].link.toId, toId, "Wrong link toId"); + t.equals(changes[2].changeType, 'add', "Add change type expected."); + }); + + graph.addLink(fromId, toId); + t.end(); +}); + +test('removeIsolatedNode', function(t) { + var graph = Viva.Graph.graph(); + graph.addNode(1); + + graph.removeNode(1); + + t.equals(graph.getNodesCount(), 0, 'Remove operation failed'); + t.end(); +}); + +test('removeLink', function(t) { + var graph = Viva.Graph.graph(); + var link = graph.addLink(1, 2); + + graph.removeLink(link); + + t.equals(graph.getNodesCount(), 2, 'remove link should not remove nodes'); + t.equals(graph.getLinks(1).length, 0, 'link should be removed from the first node'); + t.equals(graph.getLinks(2).length, 0, 'link should be removed from the second node'); + graph.forEachLink(function() { + t.fail('No links should be in graph'); + }); + t.end(); +}); + +test('removeIsolatedNodeFireChanged', function(t) { + var graph = Viva.Graph.graph(); + graph.addNode(1); + + graph.on('changed', function(changes) { + t.ok(changes && changes.length === 1, "One change should be recorded: node removed"); + t.equals(changes[0].node.id, 1, "Wrong node Id"); + t.equals(changes[0].changeType, 'remove', "'remove' change type expected."); + }); + + graph.removeNode(1); + t.end(); +}); + +test('removeLinkFireChanged', function(t) { + var graph = Viva.Graph.graph(); + var link = graph.addLink(1, 2); + + graph.on('changed', function(changes) { + t.ok(changes && changes.length === 1, "One change should be recorded: link removed"); + t.equals(changes[0].link, link, "Wrong link removed"); + t.equals(changes[0].changeType, 'remove', "'remove' change type expected."); + }); + + graph.removeLink(1); + t.end(); +}); + +test('removeLinkedNodeFireChanged', function(t) { + var graph = Viva.Graph.graph(), + link = graph.addLink(1, 2), + nodeIdToRemove = 1; + + graph.on('changed', function(changes) { + t.ok(changes && changes.length === 2, "Two changes should be recorded: link and node removed"); + t.equals(changes[0].link, link, "Wrong link removed"); + t.equals(changes[0].changeType, 'remove', "'remove' change type expected."); + t.equals(changes[1].node.id, nodeIdToRemove, "Wrong node removed"); + t.equals(changes[1].changeType, 'remove', "'remove' change type expected."); + }); + + graph.removeNode(nodeIdToRemove); + t.end(); +}); + +test('removeNodeWithManyLinks', function(t) { + var graph = Viva.Graph.graph(); + + graph.addLink(1, 2); + graph.addLink(1, 3); + graph.removeNode(1); + + t.equals(graph.getNodesCount(), 2, 'remove link should remove one node only'); + t.equals(graph.getLinks(1), null, 'link should be removed from the first node'); + t.equals(graph.getLinks(2).length, 0, 'link should be removed from the second node'); + t.equals(graph.getLinks(3).length, 0, 'link should be removed from the third node'); + graph.forEachLink(function() { + t.fail('No links should be in graph'); + }); + + t.end(); +}); diff --git a/test/graphOperations.js b/test/graphOperations.js new file mode 100644 index 0000000..095d366 --- /dev/null +++ b/test/graphOperations.js @@ -0,0 +1,56 @@ +var test = require('tap').test; +var Viva = require('../dist/vivagraph.js'); + +test('twoNodeGraphDensity', function(t) { + var graph = Viva.Graph.graph(), + operations = Viva.Graph.operations(); + + graph.addLink(1, 2); + + var density = operations.density(graph); + + t.equals(density, 1, 'Density of two node graph should be 1'); + t.end(); +}); + +test('completeGraphDensity', function(t) { + var operations = Viva.Graph.operations(); + + for (var i = 2; i < 10; ++i) { + var graph = Viva.Graph.generator().complete(i); + var density = operations.density(graph); + t.equals(density, 1, 'Density of complete graph should be 1'); + } + t.end(); +}); + +test('noEdgesGraphDensity', function(t) { + var graph = Viva.Graph.graph(), + operations = Viva.Graph.operations(); + + for (var i = 0; i < 10; ++i) { + graph.addNode(i); + } + + var density = operations.density(graph); + + t.equals(density, 0, 'Density of graph with no edges should be 0'); + t.end(); +}); + +test('degreeCentralityOneEdge', function(t) { + var graph = Viva.Graph.graph(); + graph.addLink(0, 1); + var cd = Viva.Graph.centrality().degreeCentrality(graph); + + t.equals(cd[0].value, 1, 'First node has 1 degree'); + t.end(); +}); + +test('degreeCentralityCompleteGraph', function(t) { + var graph = Viva.Graph.generator().complete(6); + var cd = Viva.Graph.centrality().degreeCentrality(graph); + + t.equals(cd[0].value, 5, 'Node has 5 degree'); + t.end(); +}); diff --git a/test/graphSerialization.js b/test/graphSerialization.js new file mode 100644 index 0000000..c848836 --- /dev/null +++ b/test/graphSerialization.js @@ -0,0 +1,96 @@ +var test = require('tap').test; +var Viva = require('../dist/vivagraph.js'); + +test('jsonStoreLoadProduceSameResult', function(t) { + var s = Viva.Graph.serializer(), + generator = Viva.Graph.generator(), + testGraphs = [ + generator.path(10), + generator.complete(10), + generator.grid(10, 10), + generator.completeBipartite(10, 10), + generator.circularLadder(10), + generator.noLinks(10) + ]; + + + for (var i = 0; i < testGraphs.length; ++i) { + var testGraph = testGraphs[i], + storedGraph = s.storeToJSON(testGraph), + loadedGraph = s.loadFromJSON(storedGraph); + + assertGraphsAreSame(testGraph, loadedGraph, t); + } + t.end(); +}); + +test('jsonStoreWithCustomTransformer', function(t) { + var s = Viva.Graph.serializer(), + g = Viva.Graph.graph(); + + g.addNode(1, 'Custom data'); + g.addLink(1, 2, 'Custom link data'); + + var json = s.storeToJSON(g, + function(node) { + return [node.id, node.data]; + }, // custom node serializer. transofrm it to array + function(link) { + return [link.fromId, link.toId, link.data]; + }), + parsedGraph; + + parsedGraph = JSON.parse(json); + + t.equals(parsedGraph.nodes[0][0], 1, 'First node id should be stored in array'); + t.equals(parsedGraph.nodes[0][1], 'Custom data', 'First node id should be stored in array'); + t.equals(parsedGraph.links[0][1], 2, 'To link id should be stored in array'); + t.end(); +}); + +test('jsonLoadWithCustomTransform', function(t) { + var s = Viva.Graph.serializer(), + g = Viva.Graph.graph(); + + g.addNode(1, 'Custom data'); + g.addLink(1, 2, 'Custom link data'); + + var json = s.storeToJSON(g, + function(node) { + return [node.id, node.data]; + }, // custom node serializer. transofrm it to array + function(link) { + return [link.fromId, link.toId, link.data]; + }); + + var parsed = s.loadFromJSON(json, + function(node) { + return { + id: node[0], + data: node[1] + }; + }, + function(link) { + return { + fromId: link[0], + toId: link[1], + data: link[2] + }; + }); + + assertGraphsAreSame(g, parsed, t); + t.end(); +}); + +function assertGraphsAreSame(expected, actual,t) { + t.equals(expected.getNodesCount(), actual.getNodesCount(), 'Number of nodes does not match for graph: ' + expected.Name); + t.equals(expected.getLinksCount(), actual.getLinksCount(), 'Number of links does not match for graph: ' + expected.Name); + + expected.forEachNode(function(node) { + t.ok(actual.getNode(node.id), 'Actual graph is missing node with id: ' + node.id); + }); + + expected.forEachLink(function(link) { + t.ok(actual.hasLink(link.fromId, link.toId), 'Actual graph is missing link from ' + link.fromId + ' to ' + link.toId); + }); +} diff --git a/test/perf/perf_test.js b/test/perf/perf_test.js new file mode 100644 index 0000000..b38f726 --- /dev/null +++ b/test/perf/perf_test.js @@ -0,0 +1,29 @@ +// When node is compiled with the lates v8 version performance gain is 35% .. 40% +// +// Take a loook: +// V8 version: 3.20.2 (node version v0.11.5-pre+) +// Grid 100x100, Nodes: 10000, Edges: 19800, 100 layout iterations. Layout time: 9141ms +// +// V8 version: 3.14.5.9 (node version v0.10.10) +// Grid 100x100, Nodes: 10000, Edges: 19800, 100 layout iterations. Layout time: 14771ms + +var Viva = require('../../dist/vivagraph'); +var gridSize = 100; +var layoutIterations = 100; + +console.log('This is a very basic performance test of force directed layout'); +console.log('V8 version: ' + process.versions.v8); +console.log('VivaGraph version: ' + Viva.Graph.version); +var generator = Viva.Graph.generator(); +var graph = generator.grid(gridSize, gridSize); +console.log('Grid ' + gridSize + 'x' + gridSize + ', Nodes: ' + graph.getNodesCount() + ', Edges: ' + graph.getLinksCount()); + +console.time('Layout time'); +console.log('Performing ' + layoutIterations + ' iterations of layout'); +var layout = Viva.Graph.Layout.forceDirected(graph); +for (var i = 0; i < layoutIterations; ++i) { + layout.step(); +} +console.timeEnd('Layout time'); +console.log('Done.'); + diff --git a/test/perf/switch_graphs.js b/test/perf/switch_graphs.js new file mode 100644 index 0000000..fb2c456 --- /dev/null +++ b/test/perf/switch_graphs.js @@ -0,0 +1,54 @@ +// Testing behavior of graph cleanup and restart of layout +var Viva = require('../../dist/vivagraph'); +var gridSize = 100; +var layoutIterations = 30; +var repetition = 10; + +console.log('This is a very basic performance test of force directed layout'); +console.log('V8 version: ' + process.versions.v8); +console.log('VivaGraph version: ' + Viva.Graph.version); + +var graph = Viva.Graph.graph(); +var layout = Viva.Graph.Layout.forceDirected(graph); + +var i; +for (var r = 1; r < repetition + 1; ++r) { + console.time('Layout time'); + makeGrid(gridSize, gridSize, graph); + console.log(r + '. Performing ' + layoutIterations + ' iterations of layout'); + for (i = 0; i < layoutIterations; ++i) { + layout.step(); + } + makeComplete(gridSize, graph); + for (i = 0; i < layoutIterations; ++i) { + layout.step(); + } + console.timeEnd('Layout time'); + console.log('Finished step ' + r + '.'); +} + +function makeGrid(n, m, g) { + g.clear(); + g.beginUpdate(); + for (var i = 0; i < n; ++i) { + for (var j = 0; j < m; ++j) { + var node = i + j * n; + if (i > 0) { g.addLink(node, i - 1 + j * n); } + if (j > 0) { g.addLink(node, i + (j - 1) * n); } + } + } + g.endUpdate(); +} + +function makeComplete(n, g) { + g.clear(); + g.beginUpdate(); + for (var i = 0; i < n; ++i) { + for (var j = i + 1; j < n; ++j) { + if (i !== j) { + g.addLink(i, j); + } + } + } + g.endUpdate(); +} diff --git a/test/renderer.js b/test/renderer.js new file mode 100644 index 0000000..cd96c03 --- /dev/null +++ b/test/renderer.js @@ -0,0 +1,10 @@ +var test = require('tap').test; +var Viva = require('../dist/vivagraph.js'); + +test('renderer fires scale events', function(t) { + var graph = Viva.Graph.graph(); + var renderer = Viva.Graph.View.renderer(graph); + renderer.on('scale', function noop() { }); + t.ok(true, 'event registered'); + t.end(); +}); diff --git a/test/utils.js b/test/utils.js new file mode 100644 index 0000000..f2f2357 --- /dev/null +++ b/test/utils.js @@ -0,0 +1,106 @@ +var test = require('tap').test; +var Viva = require('../dist/vivagraph.js'); + +test('randomIteratorReturnsAllItems', function(t) { + var a = [1, 2, 3, 4, 5, 6], + aCopy = a.map(function(i) { + return i; + }), + shuffle = Viva.randomIterator(aCopy), + iterated = []; + shuffle.forEach(function(i) { + iterated.push(i); + t.ok(a.indexOf(i) !== -1, 'Shuffle iterator should return only items from original array. Unexpected ' + i); + }); + + t.equals(iterated.length, a.length, 'Number of iterated items does not match number of original array items'); + t.end(); +}); + +test('lazyExtendDoesNotExtendExistingValues', function(t) { + var options = { + age: 42 + }; + + Viva.lazyExtend(options, { + age: 24 + }); + + t.equals(options.age, 42, 'Should not touch properties when types match'); + t.end(); +}); + +test('lazyExtendUpdatesWhenTypeDoesNotMatch', function(t) { + var options = { + age: '42' + }; + + Viva.lazyExtend(options, { + age: 24 + }); + + t.equals(options.age, 24, 'Should extend, because types are different'); + t.end(); +}); + +test('lazyExtendUpdatesWhenNewProperty', function(t) { + var options = { + age: '42' + }; + + Viva.lazyExtend(options, { + newProperty: 24 + }); + + t.equals(options.age, '42', 'Should preserve old values'); + t.equals(options.newProperty, 24, 'Should extend, because new property'); + t.end(); +}); + +test('lazyExtendDeepNewObjects', function(t) { + var options = { + age: '42' + }; + + Viva.lazyExtend(options, { + nested: { + name: 'deep' + } + }); + + t.equals(options.age, '42', 'Should preserve old values'); + t.equals(options.nested.name, 'deep', 'Should extend deep properties'); + t.end(); +}); + +test('lazyExtendDeepLogic', function(t) { + var options = { + age: '42', + nested: { + first: 'Mark', + age: '22' + } + }; + + Viva.lazyExtend(options, { + nested: { + first: '', + last: 'Twain', + age: 20 + } + }); + + t.equals(options.age, '42', 'Should preserve old values'); + t.equals(options.nested.first, 'Mark', 'Should preserve deep properties with same types'); + t.equals(options.nested.last, 'Twain', 'Should create new deep properties'); + t.equals(options.nested.age, 20, 'Should fix deep properties with wrong types'); + t.end(); +}); + +test('lazyExtendCreatesNewObject', function(t) { + var options, + extended = Viva.lazyExtend(options, {}); + + t.ok(extended, 'New object should be created'); + t.end(); +}); diff --git a/unit_tests/testRunner.html b/unit_tests/testRunner.html deleted file mode 100644 index f68316a..0000000 --- a/unit_tests/testRunner.html +++ /dev/null @@ -1,35 +0,0 @@ - - - - - VivaGraphs test page - - - - - - - - - - - - - - - - diff --git a/unit_tests/test_Utils.js b/unit_tests/test_Utils.js deleted file mode 100644 index 958c813..0000000 --- a/unit_tests/test_Utils.js +++ /dev/null @@ -1,68 +0,0 @@ -var test_Utils = function (test) { - return { - randomIteratorReturnsAllItems : function () { - var a = [1, 2, 3, 4, 5, 6], - aCopy = a.map(function (i) { return i; }), - shuffle = Viva.randomIterator(aCopy), - iterated = []; - shuffle.forEach(function (i) { - iterated.push(i); - test.assert(a.indexOf(i) !== -1, 'Shuffle iterator should return only items from original array. Unexpected ' + i); - }); - - test.assertEqual(iterated.length, a.length, 'Number of iterated items does not match number of original array items'); - }, - - lazyExtendDoesNotExtendExistingValues : function () { - var options = { age : 42 }; - - Viva.lazyExtend(options, { age : 24 }); - - test.assertEqual(options.age, 42, 'Should not touch properties when types match'); - }, - - lazyExtendUpdatesWhenTypeDoesNotMatch : function () { - var options = { age : '42' }; - - Viva.lazyExtend(options, { age : 24 }); - - test.assertEqual(options.age, 24, 'Should extend, because types are different'); - }, - - lazyExtendUpdatesWhenNewProperty : function () { - var options = { age : '42' }; - - Viva.lazyExtend(options, { newProperty : 24 }); - - test.assertEqual(options.age, '42', 'Should preserve old values'); - test.assertEqual(options.newProperty, 24, 'Should extend, because new property'); - }, - - lazyExtendDeepNewObjects : function () { - var options = { age : '42' }; - - Viva.lazyExtend(options, { nested : { name : 'deep'} }); - - test.assertEqual(options.age, '42', 'Should preserve old values'); - test.assertEqual(options.nested.name, 'deep', 'Should extend deep properties'); - }, - - lazyExtendDeepLogic : function () { - var options = { age : '42', nested: { first : 'Mark', age : '22'}}; - - Viva.lazyExtend(options, { nested : { first : '', last : 'Twain', age : 20} }); - - test.assertEqual(options.age, '42', 'Should preserve old values'); - test.assertEqual(options.nested.first, 'Mark', 'Should preserve deep properties with same types'); - test.assertEqual(options.nested.last, 'Twain', 'Should create new deep properties'); - test.assertEqual(options.nested.age, 20, 'Should fix deep properties with wrong types'); - }, - - lazyExtendCreatesNewObject : function () { - var options, - extended = Viva.lazyExtend(options, {}); - - test.assert(extended, 'New object should be created'); - } - }; -}; \ No newline at end of file diff --git a/unit_tests/test_constantLayout.js b/unit_tests/test_constantLayout.js deleted file mode 100644 index 4fcfef3..0000000 --- a/unit_tests/test_constantLayout.js +++ /dev/null @@ -1,53 +0,0 @@ -/*global Viva*/ - -/** - * Testing Viva.Graph.Layout.constant behavior. - */ -var test_constantLayout = function(test){ - return { - nodePositionGeneratedByDefault : function() { - var graph = Viva.Graph.generator().path(10), - layout = Viva.Graph.Layout.constant(graph); - - layout.run(); - - graph.forEachNode(function(node) { - test.assert(node.hasOwnProperty('position'), 'All nodes expected to have some position'); - test.assert(typeof node.position.x === 'number', 'Node position does not have a valid x position'); - test.assert(typeof node.position.y === 'number', 'Node position does not have a valid y position'); - }); - }, - - nodePositionUsesCustomCallback : function() { - var graph = Viva.Graph.generator().path(10), - layout = Viva.Graph.Layout.constant(graph), - placeNodeCallback = function(node) { - return new Viva.Graph.Point2d(42, 42); // all nodes should be placed at the same position. - }; - - layout.placeNode(placeNodeCallback); - layout.run(); - - graph.forEachNode(function(node) { - test.assertEqual(node.position.x, 42, 'Node position does not have a valid x position'); - test.assertEqual(node.position.y, 42, 'Node position does not have a valid y position'); - }); - }, - - getGraphRectReflectsDefaultSettings : function() { - var graph = Viva.Graph.generator().path(10), - layoutSettings = { - maxX : 42, - maxY : 42 - }, - layout = Viva.Graph.Layout.constant(graph, layoutSettings); - - layout.run(); - - graph.forEachNode(function(node) { - test.assert(node.position.x <= layoutSettings.maxX, 'Node position does not have a valid x position'); - test.assert(node.position.y <= layoutSettings.maxY, 'Node position does not have a valid y position'); - }); - } - }; -}; \ No newline at end of file diff --git a/unit_tests/test_graphCommunity.js b/unit_tests/test_graphCommunity.js deleted file mode 100644 index 687d94e..0000000 --- a/unit_tests/test_graphCommunity.js +++ /dev/null @@ -1,94 +0,0 @@ -/*global Viva, console*/ - -var test_graphCommunity = function(test){ - - return { - - occuranceMapCountsWords : function() { - var map = Viva.Graph._community.occuranceMap(); - for (var i = 0; i < 15; ++i) { - map.add('hello'); - } - map.add('world'); - - var helloCount = map.getWordCount('hello'), - worldCount = map.getWordCount('world'), - randomWordCount = map.getWordCount('he-he'); - - test.assertEqual(helloCount, 15, 'Hello word should be added 15 times'); - test.assertEqual(worldCount, 1, 'Only one occurance of world should be in the map'); - test.assertEqual(randomWordCount, 0, 'This word should not be in the map!'); - }, - - occuranceMapFindsMostPopularWord : function() { - var map = Viva.Graph._community.occuranceMap(); - - map.add('hello'); map.add('world'); map.add('!'); - map.add('hello'); map.add('world'); - map.add('hello'); - - var mostPopular = map.getMostPopularFair(); - - test.assertEqual(mostPopular, 'hello', 'Unexpected most popular word'); - }, - - occuranceMapFindsMostPopularWordWithSameRank : function() { - var map = Viva.Graph._community.occuranceMap(); - - for (var i = 0; i < 100; ++i) { - if (i < 50) { - map.add('hello'); - } else { - map.add('world'); - } - } - - var helloFound = false, - worldFound = false; - for(i = 0; i < 10; ++i){ - var word = map.getMostPopularFair(); - if (word === 'hello') { helloFound = true; } - if (word === 'world') { worldFound = true; } - } - - test.assert(helloFound && worldFound, 'Both words should appear. Well. Potentially...This is non-determenistic test'); - }, - - occuranceMapReturnsRandomWord : function() { - var map = Viva.Graph._community.occuranceMap(), - dictionary = {}; - - for (var i = 0; i < 15; ++i) { - var word = 'hello' + i; - map.add(word); - dictionary[word] = 1; - } - - for (i = 0; i < 15; ++i) { - var actual = map.getRandomWord(); - test.assert(dictionary.hasOwnProperty(actual), 'The random word is not expected'); - } - }, - - occuranceMapEnumeratesAllWords : function() { - var map = Viva.Graph._community.occuranceMap(), - dictionary = {}; - - map.add('hello'); map.add('world'); map.add('!'); - map.add('hello'); map.add('world'); - map.add('hello'); - - var prevCount = 4; - map.forEachUniqueWord(function(word, count) { - test.assert(count <= prevCount, "Enumeration should go in non increasing order"); - test.assert(!dictionary.hasOwnProperty(word), "Enumeration should go through unique words only"); - - dictionary[word] = count; - }); - - test.assert(dictionary.hello, 3, "Unexpected number of 'hello'"); - test.assert(dictionary.world, 2, "Unexpected number of 'world'"); - test.assert(dictionary['!'], 1, "Unexpected number of '!'"); - } - }; -}; \ No newline at end of file diff --git a/unit_tests/test_graphConstructions.js b/unit_tests/test_graphConstructions.js deleted file mode 100644 index 6499495..0000000 --- a/unit_tests/test_graphConstructions.js +++ /dev/null @@ -1,145 +0,0 @@ -/*global Viva, console*/ - -var test_GraphConstructions = function(test){ - return { - addNode : function() { - var graph = Viva.Graph.graph(); - var customData = '31337'; - - var node = graph.addNode(1, customData); - - test.assertEqual(graph.getNodesCount(), 1, 'addNode failed'); - test.assertEqual(graph.getNode(1), node, 'invalid node returned by addNode (or getNode)'); - test.assertEqual(node.data, customData, 'data was not set properly'); - test.assertEqual(node.id, 1, 'node id was not set properly'); - }, - - addLink : function() { - var graph = Viva.Graph.graph(); - - var link = graph.addLink(1, 2), - firstNodeLinks = graph.getLinks(1), - secondNodeLinks = graph.getLinks(2); - - test.assertEqual(graph.getNodesCount(), 2, 'addLink failed'); - test.assertEqual(firstNodeLinks.length, 1, 'number of links of the first node is wrong'); - test.assertEqual(secondNodeLinks.length, 1, 'number of links of the second node is wrong'); - test.assertEqual(link, firstNodeLinks[0], 'invalid link in the first node'); - test.assertEqual(link, secondNodeLinks[0], 'invalid link in the second node'); - }, - - addOneNodeFireChanged : function() { - var graph = Viva.Graph.graph(); - var testNodeId = 'hello world'; - var graphEvents = Viva.Graph.Utils.events(graph); - graphEvents.on('changed', function(changes) { - test.assert(changes && changes.length === 1, "Only one change should be recorded"); - test.assertEqual(changes[0].node.id, testNodeId, "Wrong node change notification"); - test.assertEqual(changes[0].changeType, 'add', "Add change type expected."); - }); - - graph.addNode(testNodeId); - }, - - addLinkFireChanged : function() { - var graph = Viva.Graph.graph(); - var fromId = 1, toId = 2; - var graphEvents = Viva.Graph.Utils.events(graph); - graphEvents.on('changed', function(changes) { - test.assert(changes && changes.length === 3, "Three change should be recorded: node, node and link"); - test.assertEqual(changes[2].link.fromId, fromId, "Wrong link from Id"); - test.assertEqual(changes[2].link.toId, toId, "Wrong link toId"); - test.assertEqual(changes[2].changeType, 'add', "Add change type expected."); - }); - - graph.addLink(fromId, toId); - }, - - removeIsolatedNode : function() { - var graph = Viva.Graph.graph(); - graph.addNode(1); - - graph.removeNode(1); - - test.assertEqual(graph.getNodesCount(), 0, 'Remove operation failed'); - }, - - removeLink : function() { - var graph = Viva.Graph.graph(); - var link = graph.addLink(1, 2); - - graph.removeLink(link); - - test.assertEqual(graph.getNodesCount(), 2, 'remove link should not remove nodes'); - test.assertEqual(graph.getLinks(1).length, 0, 'link should be removed from the first node'); - test.assertEqual(graph.getLinks(2).length, 0, 'link should be removed from the second node'); - graph.forEachLink(function(link){ - test.assertFail('No links should be in graph'); - }); - }, - - removeIsolatedNodeFireChanged : function() { - var graph = Viva.Graph.graph(); - var graphEvents = Viva.Graph.Utils.events(graph); - graph.addNode(1); - - graphEvents.on('changed', function(changes) { - test.assert(changes && changes.length === 1, "One change should be recorded: node removed"); - test.assertEqual(changes[0].node.id, 1, "Wrong node Id"); - test.assertEqual(changes[0].changeType, 'remove', "'remove' change type expected."); - }); - - graph.removeNode(1); - }, - - removeLinkFireChanged : function() { - var graph = Viva.Graph.graph(); - var graphEvents = Viva.Graph.Utils.events(graph); - var link = graph.addLink(1, 2); - - graphEvents.on('changed', function(changes) { - test.assert(changes && changes.length === 1, "One change should be recorded: link removed"); - test.assertEqual(changes[0].link, link, "Wrong link removed"); - test.assertEqual(changes[0].changeType, 'remove', "'remove' change type expected."); - }); - - graph.removeLink(1); - }, - - removeLinkedNodeFireChanged : function() { - var graph = Viva.Graph.graph(), - graphEvents = Viva.Graph.Utils.events(graph), - link = graph.addLink(1, 2), - nodeIdToRemove = 1; - - graphEvents.on('changed', function(changes) { - test.assert(changes && changes.length === 2, "Two changes should be recorded: link and node removed"); - test.assertEqual(changes[0].link, link, "Wrong link removed"); - test.assertEqual(changes[0].changeType, 'remove', "'remove' change type expected."); - test.assertEqual(changes[1].node.id, nodeIdToRemove, "Wrong node removed"); - test.assertEqual(changes[1].changeType, 'remove', "'remove' change type expected."); - }); - - graph.removeNode(nodeIdToRemove); - }, - - removeNodeWithManyLinks : function() { - var graph = Viva.Graph.graph(), - graphEvents = Viva.Graph.Utils.events(graph), - link12 = graph.addLink(1, 2), - link13 = graph.addLink(1, 3), - nodeIdToRemove = 1; - - graph.removeNode(1); - - test.assertEqual(graph.getNodesCount(), 2, 'remove link should remove one node only'); - test.assertEqual(graph.getLinks(1), null, 'link should be removed from the first node'); - test.assertEqual(graph.getLinks(2).length, 0, 'link should be removed from the second node'); - test.assertEqual(graph.getLinks(3).length, 0, 'link should be removed from the third node'); - graph.forEachLink(function(link) { - test.assertFail('No links should be in graph'); - }); - - } - }; -}; \ No newline at end of file diff --git a/unit_tests/test_graphOperations.js b/unit_tests/test_graphOperations.js deleted file mode 100644 index 5aee539..0000000 --- a/unit_tests/test_graphOperations.js +++ /dev/null @@ -1,58 +0,0 @@ -/*global Viva, console*/ - -var test_GraphOperations = function(test){ - - return { - twoNodeGraphDensity : function() { - var graph = Viva.Graph.graph(), - operations = Viva.Graph.operations(); - - graph.addLink(1, 2); - - var density = operations.density(graph); - - test.assertEqual(density, 1, 'Density of two node graph should be 1'); - }, - - completeGraphDensity : function() { - var operations = Viva.Graph.operations(); - - for(var i = 2; i < 10; ++i) { - var graph = Viva.Graph.generator().complete(i); - var density = operations.density(graph); - test.assertEqual(density, 1, 'Density of complete graph should be 1'); - } - }, - - noEdgesGraphDensity : function(){ - var graph = Viva.Graph.graph(), - operations = Viva.Graph.operations(); - - for (var i = 0; i < 10; ++i) { - graph.addNode(i); - } - - var density = operations.density(graph); - - test.assertEqual(density, 0, 'Density of graph with no edges should be 0'); - }, - - degreeCentralityOneEdge : function(){ - var graph = Viva.Graph.graph(), - cd; - graph.addLink(0, 1); - cd = Viva.Graph.centrality().degreeCentrality(graph); - - test.assertEqual(cd[0].value, 1, 'Unexpected node degree centrality'); - }, - - degreeCentralityCompleteGraph : function() { - var graph = Viva.Graph.generator().complete(6), - cd; - - cd = Viva.Graph.centrality().degreeCentrality(graph); - - test.assertEqual(cd[0].value, 5, 'Unexpected complete graph node centrality'); - } - }; -}; \ No newline at end of file diff --git a/unit_tests/test_graphSerialization.js b/unit_tests/test_graphSerialization.js deleted file mode 100644 index be33a35..0000000 --- a/unit_tests/test_graphSerialization.js +++ /dev/null @@ -1,77 +0,0 @@ -/*global Viva, console*/ - -var test_GraphSerialization = function(test){ - var assertGraphsAreSame = function(expected, actual) { - test.assertEqual(expected.getNodesCount(), actual.getNodesCount(), 'Number of nodes does not match for graph: ' + expected.Name); - test.assertEqual(expected.getLinksCount(), actual.getLinksCount(), 'Number of links does not match for graph: ' + expected.Name); - - expected.forEachNode(function(node){ - test.assert(actual.getNode(node.id), 'Actual graph is missing node with id: ' + node.id); - }); - - expected.forEachLink(function(link){ - test.assert(actual.hasLink(link.fromId, link.toId), 'Actual graph is missing link from ' + link.fromId + ' to ' + link.toId); - }); - }; - - return { - jsonStoreLoadProduceSameResult : function() { - var s = Viva.Graph.serializer(), - generator = Viva.Graph.generator(), - testGraphs = [ - generator.path(10), - generator.complete(10), - generator.grid(10, 10), - generator.completeBipartite(10, 10), - generator.circularLadder(10), - generator.randomNoLinks(10) - ]; - - - for(var i = 0; i < testGraphs.length; ++i) { - var testGraph = testGraphs[i], - storedGraph = s.storeToJSON(testGraph), - loadedGraph = s.loadFromJSON(storedGraph); - - assertGraphsAreSame(testGraph, loadedGraph); - } - }, - - jsonStoreWithCustomTransformer : function(){ - var s = Viva.Graph.serializer(), - g = Viva.Graph.graph(); - - g.addNode(1, 'Custom data'); - g.addLink(1, 2, 'Custom link data'); - - var json = s.storeToJSON(g, - function(node) { return [node.id, node.data]; }, // custom node serializer. transofrm it to array - function(link) { return [link.fromId, link.toId, link.data]; }), - parsedGraph; - - parsedGraph = JSON.parse(json); - - test.assertEqual(parsedGraph.nodes[0][0], 1, 'First node id should be stored in array'); - test.assertEqual(parsedGraph.nodes[0][1], 'Custom data', 'First node id should be stored in array'); - test.assertEqual(parsedGraph.links[0][1], 2, 'To link id should be stored in array'); - }, - - jsonLoadWithCustomTransform : function() { - var s = Viva.Graph.serializer(), - g = Viva.Graph.graph(); - - g.addNode(1, 'Custom data'); - g.addLink(1, 2, 'Custom link data'); - - var json = s.storeToJSON(g, - function(node) { return [node.id, node.data]; }, // custom node serializer. transofrm it to array - function(link) { return [link.fromId, link.toId, link.data]; }); - - var parsed = s.loadFromJSON(json, - function(node) { return { id : node[0], data : node[1]}; }, - function(link) { return { fromId : link[0], toId : link[1], data : link[2]}; } ); - - assertGraphsAreSame(g, parsed); - } - }; -}; \ No newline at end of file diff --git a/unit_tests/testing.js b/unit_tests/testing.js deleted file mode 100644 index 55cf6db..0000000 --- a/unit_tests/testing.js +++ /dev/null @@ -1,110 +0,0 @@ -/** - * @fileOverview Contains simple testing framework. - * - * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com - */ - -// TODO: I have to learn more about existing unit test frameworks in JS. -// But I'm too lazy at the moment so I wrote this simple micro framework - -/*global Viva, console */ - -var Viva = Viva || {}; - -Viva.testing = function(context){ - 'use strict'; - - var isTestCategoryName = function(propertyName){ - return propertyName && propertyName.indexOf('test_') === 0; - }, - - getAllCategoryNamesFromContext = function(){ - var categoryNames = [], - key; - - for(key in context){ - if (context.hasOwnProperty(key) && isTestCategoryName(key)) { - categoryNames.push(key); - } - } - - return categoryNames; - }, - - framework = { - log : function(level, message) { - console.log(message); - - var out = document.getElementById('output'), - domRecord; - - if (!out){ - out = document.createElement('div'); - out.id = 'output'; - document.body.appendChild(out); - } - - domRecord = document.createElement('div'); - domRecord.className = level; - domRecord.innerHTML = message; - - if (level === 'info') { - out.appendChild(document.createElement('br')); - } - - out.appendChild(domRecord); - }, - - assert : function(expression, message) { - if (!expression) { - throw message; - } - }, - - assertEqual : function(actual, expected, message) { - if (actual !== expected){ - throw message + '. Actual: ' + actual + '; expected: ' + expected; - } - }, - - assertFail : function(message){ - throw message; - }, - - run : function(testName, testFunction) { - framework.log('info', " * " + testName + '...', 3); - try{ - testFunction(); - framework.log('success', 'Success'); - } catch (e){ - framework.log('fail', 'FAILED: ' + e); - } - }, - - runAll : function(){ - var categoryNames = getAllCategoryNamesFromContext(), - i, categoryName, shortName, tests, testName; - framework.log('info', 'Running all tests'); - - for(i = 0; i < categoryNames.length; i += 1) { - categoryName = categoryNames[i]; - shortName = categoryName.match(/.+_(.+)/); - tests = context[categoryName](framework); - - shortName = (shortName && shortName[1]) || categoryName; - - framework.log('info', 'Running ' + shortName + ' category'); - - for(testName in tests) { - if (tests.hasOwnProperty(testName)){ - framework.run(testName, tests[testName]); - } - } - } - - framework.log('info', 'Done'); - } - }; - - return framework; -};