flow like the river

This commit is contained in:
root 2025-11-07 00:06:12 +01:00
commit 013fe673f3
42435 changed files with 5764238 additions and 0 deletions

View file

@ -0,0 +1,2 @@
dist
demo/dist

18
VISUALIZACION/node_modules/ngraph.forcelayout/.eslintrc.json generated vendored Executable file
View file

@ -0,0 +1,18 @@
{
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module"
},
"env": {
"browser": true,
"node": true,
"es6": true
},
"rules": {
"semi": "error",
"no-undef": "error",
"no-unused-vars": "error",
"no-shadow": "error"
}
}

View file

@ -0,0 +1,26 @@
name: Node.js CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [12.x, 14.x, 15.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

28
VISUALIZACION/node_modules/ngraph.forcelayout/Changelog.md generated vendored Executable file
View file

@ -0,0 +1,28 @@
# v3.0
The following physics settings are renamed:
* `springCoeff -> springCoefficient`
* `dragCoeff -> dragCoefficient`
A new interactive demo is added to the library: http://anvaka.github.io/ngraph.forcelayout/.
Also the library is now available for consumption via CDN.
# v2.0
Major rework on how library treats dimensions of space. Previous versions of the library
required concrete implementation for the given space (e.g. [3d](https://github.com/anvaka/ngraph.forcelayout3d), [N-d](https://github.com/anvaka/ngraph.forcelayout.nd)).
With version 2.0, `ngraph.forcealyout` generates code on the fly to support layout in the
given dimension. This comes at no extra performance cost to the consumer.
Second big change, is that custom forces can now be added to the library via `simulator.addForce()`
`simulator.removeForce()` api.
With this change, the old `physicsSimulator` factory methods became obsolete and were removed
(like `settings.createQuadTree`, `settings,createBounds`, [etc.](https://github.com/anvaka/ngraph.forcelayout/blob/d2eea4a5dd6913fb0002787d91d211916b56ba01/lib/physicsSimulator.js#L50-L55) )
# 0.xx - V1.0
This was original implementation of the ngraph.forcelayout.

27
VISUALIZACION/node_modules/ngraph.forcelayout/LICENSE generated vendored Executable file
View file

@ -0,0 +1,27 @@
Copyright (c) 2013 - 2022, Andrei Kashcha
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
* Neither the name of the Andrei Kashcha nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, 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.

240
VISUALIZACION/node_modules/ngraph.forcelayout/README.md generated vendored Executable file
View file

@ -0,0 +1,240 @@
# ngraph.forcelayout
[![build status](https://github.com/anvaka/ngraph.forcelayout/actions/workflows/tests.yaml/badge.svg)](https://github.com/anvaka/ngraph.forcelayout/actions/workflows/tests.yaml)
This is a [force directed](http://en.wikipedia.org/wiki/Force-directed_graph_drawing) graph layout algorithm,
that works in any dimension (2D, 3D, and above).
The library uses quad tree to speed up computation of long-distance forces.
This repository is part of [ngraph family](https://github.com/anvaka/ngraph), and operates on
[`ngraph.graph`](https://github.com/anvaka/ngraph.graph) data structure.
# API
All force directed algorithms are iterative. We need to perform multiple iterations of an algorithm,
before graph starts looking good:
``` js
// graph is an instance of `ngraph.graph` object.
var createLayout = require('ngraph.forcelayout');
var layout = createLayout(graph);
for (var i = 0; i < ITERATIONS_COUNT; ++i) {
layout.step();
}
// now we can ask layout where each node/link is best positioned:
graph.forEachNode(function(node) {
console.log(layout.getNodePosition(node.id));
// Node position is pair of x,y coordinates:
// {x: ... , y: ... }
});
graph.forEachLink(function(link) {
console.log(layout.getLinkPosition(link.id));
// link position is a pair of two positions:
// {
// from: {x: ..., y: ...},
// to: {x: ..., y: ...}
// }
});
```
If you'd like to perform graph layout in space with more than two dimensions, just add one
argument to this line:
``` js
let layout = createLayout(graph, {dimensions: 3}); // 3D layout
let nodePosition = layout.getNodePosition(nodeId); // has {x, y, z} attributes
```
Even higher dimensions are not a problem for this library:
``` js
let layout = createLayout(graph, {dimensions: 6}); // 6D layout
// Every layout with more than 3 dimensions, say N, gets additional attributes:
// c4, c5, ... cN
let nodePosition = layout.getNodePosition(nodeId); // has {x, y, z, c4, c5, c6}
```
Note: Higher dimensionality comes at exponential cost of memory for every added
dimension. See a performance section below for more details.
## Node position and object reuse
Recently immutability became a ruling principle of javascript world. This library
doesn't follow the rules, and results of `getNodePosition()`/`getLinkPosition()` will be
always the same for the same node. This is true:
``` js
layout.getNodePosition(1) === layout.getNodePosition(1);
```
Reason for this is performance. If you are interested in storing positions
somewhere else, you can do it and they still will be updated after each force
directed layout iteration.
## "Pin" node and initial position
Sometimes it's desirable to tell layout algorithm not to move certain nodes.
This can be done with `pinNode()` method:
``` js
var nodeToPin = graph.getNode(nodeId);
layout.pinNode(nodeToPin, true); // now layout will not move this node
```
If you want to check whether node is pinned or not you can use `isNodePinned()`
method. Here is an example how to toggle node pinning, without knowing it's
original state:
``` js
var node = graph.getNode(nodeId);
layout.pinNode(node, !layout.isNodePinned(node)); // toggle it
```
What if you still want to move your node according to some external factor (e.g.
you have initial positions, or user drags pinned node)? To do this, call
`setNodePosition()` method:
``` js
layout.setNodePosition(nodeId, x, y);
```
## Monitoring changes
Like many other algorithms in `ngraph` family, force layout monitors graph changes
via [graph events](https://github.com/anvaka/ngraph.graph#listening-to-events).
It keeps layout up to date whenever graph changes:
``` js
var graph = require('ngraph.graph')(); // empty graph
var layout = require('ngraph.layout')(graph); // layout of empty graph
graph.addLink(1, 2); // create node 1 and 2, and make link between them
layout.getNodePosition(1); // returns position.
```
If you want to stop monitoring graph events, call `dispose()` method:
``` js
layout.dispose();
```
## Physics Simulator
Simulator calculates forces acting on each body and then deduces their position via Newton's law.
There are three major forces in the system:
1. Spring force keeps connected nodes together via [Hooke's law](http://en.wikipedia.org/wiki/Hooke's_law)
2. Each body repels each other via [Coulomb's law](http://en.wikipedia.org/wiki/Coulomb's_law)
3. The drag force slows the entire simulation down, helping with convergence.
Body forces are calculated in `n*lg(n)` time with help of Barnes-Hut algorithm implemented with quadtree.
``` js
// Configure
var physicsSettings = {
timeStep: 0.5,
dimensions: 2,
gravity: -12,
theta: 0.8,
springLength: 10,
springCoefficient: 0.8,
dragCoefficient: 0.9,
};
// pass it as second argument to layout:
var layout = require('ngraph.forcelayout')(graph, physicsSettings);
```
You can get current physics simulator from layout by checking `layout.simulator`
property. This is a read only property.
## Space occupied by graph
Finally, it's often desirable to know how much space does our graph occupy. To
quickly get bounding box use `getGraphRect()` method:
``` js
var rect = layout.getGraphRect();
// rect.min_x, rect.min_y - left top coordinates of the bounding box
// rect.max_x, rect.max_y - right bottom coordinates of the bounding box
```
## Manipulating bodies
This is advanced technique to get to internal state of the simulator. If you need
to get a node position use regular `layout.getNodePosition(nodeId)` described
above.
In some cases you really need to manipulate physic attributes on a body level.
To get to a single body by node id:
``` js
var graph = createGraph();
graph.addLink(1, 2);
// Get body that represents node 1:
var body = layout.getBody(1);
assert(
typeof body.pos.x === 'number' &&
typeof body.pos.y === 'number', 'Body has position');
assert(body.mass, 'Body has mass');
```
To iterate over all bodies at once:
``` js
layout.forEachBody(function(body, nodeId) {
assert(
typeof body.pos.x === 'number' &&
typeof body.pos.y === 'number', 'Body has position');
assert(graph.getNode(nodeId), 'NodeId is coming from the graph');
});
```
# Section about performance
This library is focused on performance of physical simulation. We use quad tree data structure
in 2D space to approximate long distance forces, and reduce amount of required computations.
When layout is performed in higher dimensions we use analogues tree data structure. By design
such tree requires to store `2^dimensions_count` child nodes on each node. In practice, performing
layout in 6 dimensional space on a graph with a few thousand nodes yields decent performance
on modern mac book (graph can be both rendered and layed out at 60FPS rate).
Additionally, the vector algebra is optimized by a ad-hoc code generation. Essentially this means
that upon first load of the library, we check the dimension of the space where you want to perform
layout, and generate all required data structure to run fast in this space.
The code generation happens only once when dimension is requested. Any subsequent layouts in the same
space would reuse generated codes. It is pretty fast and cool.
# install
With [npm](https://npmjs.org) do:
```
npm install ngraph.forcelayout
```
Or download from CDN:
``` html
<script src='https://unpkg.com/ngraph.forcelayout@3.0.0/dist/ngraph.forcelayout.min.js'></script>
```
If you download from CDN the library will be available under `ngraphCreateLayout` global name.
# license
MIT
# Feedback?
I'd totally love it! Please email me, open issue here, [tweet](https://twitter.com/anvaka) to me,
or join discussion [on gitter](https://gitter.im/anvaka/VivaGraphJS).
If you love this library, please consider sponsoring it at https://github.com/sponsors/anvaka or at
https://www.patreon.com/anvaka

View file

@ -0,0 +1,2 @@
> 1%
last 2 versions

View file

@ -0,0 +1,18 @@
module.exports = {
root: true,
env: {
node: true
},
'extends': [
'plugin:vue/essential',
'eslint:recommended'
],
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-unused-vars': 'off'
},
parserOptions: {
parser: 'babel-eslint'
}
}

33
VISUALIZACION/node_modules/ngraph.forcelayout/demo/README.md generated vendored Executable file
View file

@ -0,0 +1,33 @@
# ngraph.forcelayout demo
This folder contains a demo of the `ngraph.forcelayout` package.
If you drop any `.dot` file into the browser window the demo will attempt to visualize it.
### Compiles and hot-reloads for development
```
npm start
```
This should render a simple graph and you can do some basic layout. You can drop `.dot` files into it
to load new graphs.
### Compiles and minifies for production
```
npm run build
```
## What's inside?
* [ngraph.graph](https://github.com/anvaka/ngraph.graph) as a graph data structure
* [ngraph.forcelayout](https://github.com/anvaka/ngraph.forcelayout) for the basic graph layout
* [w-gl](https://github.com/anvaka/w-gl) - super duper obscure (and fast) WebGL renderer.
* vue.js powered UI and dev tools.
## Thanks!
* Stay tuned for updates: https://twitter.com/anvaka
* If you like my work and would like to support it - https://www.patreon.com/anvaka

View file

@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

10
VISUALIZACION/node_modules/ngraph.forcelayout/demo/deploy.sh generated vendored Executable file
View file

@ -0,0 +1,10 @@
#!/bin/sh
rm -rf ./dist
npm run build
cd ./dist
git init
git add .
git commit -m 'push to gh-pages'
## Change the line below to deploy to your gh-pages
git push --force git@github.com:anvaka/ngraph.forcelayout.git master:gh-pages
cd ../

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,37 @@
{
"name": "graph-layout-demo",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"start": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"core-js": "^3.6.5",
"d3-color": "^2.0.0",
"miserables": "^2.0.0",
"ngraph.events": "^1.0.0",
"ngraph.forcelayout": "^3.2.0",
"ngraph.fromdot": "^6.0.0",
"ngraph.fromjson": "^3.0.0",
"ngraph.generators": "^20.0.0",
"ngraph.graph": "^20.0.0",
"ngraph.hde": "^1.0.1",
"query-state": "^4.3.0",
"vue": "^2.6.12",
"w-gl": "^0.19.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.5.4",
"@vue/cli-plugin-eslint": "^4.5.4",
"@vue/cli-service": "^4.5.4",
"babel-eslint": "^10.0.3",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2",
"stylus": "^0.54.8",
"stylus-loader": "^3.0.2",
"vue-template-compiler": "^2.6.11"
}
}

View file

@ -0,0 +1,50 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>ngraph.forcelayout demo</title>
<!-- <script src="ngraph.forcelayout2d.js"></script> -->
<style>
* {
box-sizing: border-box;
}
body {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #EEE;
margin: 0;
background: rgb(12, 41, 82);
overflow: hidden;
}
canvas {
position: absolute;
width: 100%;
height: 100%;
left: 0;
right: 0;
top: 0;
bottom: 0;
}
</style>
</head>
<body>
<noscript>
<strong>We're sorry but puller doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<canvas id='cnv'></canvas>
<div id="app"></div>
<!-- built files will be auto injected -->
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-47259320-1', 'anvaka.github.io');
ga('send', 'pageview');
</script>
</body>
</html>

View file

@ -0,0 +1,225 @@
<template>
<div id="app">
<h2><a href='https://github.com/anvaka/ngraph.forcelayout'>ngraph.forcelayout</a> demo
<small class='toggle-settings'><a href='#' @click.prevent='settingsOpen = !settingsOpen'>{{settingsOpen ? 'hide settings' : 'show settings'}}</a></small>
</h2>
<div class='content' v-if='settingsOpen'>
<div class='row'>
<div class='label'>Graph </div>
<select v-model='selectedGraph' :disable='loading' class='value'>
<option v-for="graph in graphs" :key='graph' :value='graph'>{{graph}}</option>
</select>
</div>
<input-value label='Time step' v-model='layoutSettings.timeStep'>
This is integration time step value. The higher it is, the faster nodes will move, but setting it too high
can result in lots of jitter and instability.
</input-value>
<input-value label='Gravity' v-model='layoutSettings.gravity'>
This coefficient defines how strongly each node repels each other.
</input-value>
<input-value label='Ideal spring length' v-model='layoutSettings.springLength'>
What is the ideal length of each spring?
</input-value>
<input-value label='Spring coefficient' v-model='layoutSettings.springCoefficient'>
Higher values makes the spring force stronger, pushing edges closer to the ideal spring length.
</input-value>
<input-value label='Drag coefficient' v-model='layoutSettings.dragCoefficient'>
This coefficient introduces "resistance" from environment. When it is close to 0 the forces
will have a lot of freedom, nothing will be stopping them, and that can result in a very
unstable simulation.
</input-value>
<input-value label='Theta' v-model='layoutSettings.theta'>
This coefficient influences when we apply long distance forces approximation. When this value is
close to 0, the simulation compares forces between every single node (giving O(n^2), slow performance).
Recommended value is 0.8.
</input-value>
<input-value label='Dimensions' v-model='layoutSettings.dimensions' step=1>
Defines number of dimensions of the space where layout is performed. For visualization purpose
2 or 3 dimensions are normally enough. Note: Memory consumptions grows exponentially with number
of dimensions.
</input-value>
<input-flag label='Follow bounding box' v-model='fixedViewBox' step=1>
Setting this to true will disable pan/zoom but will always keep the graph visible. This is not
part of the layout algorithm. Just a view setting of the renderer.
</input-flag>
<div v-if='loading'>Loading graph...</div>
</div>
<div v-if='!loading' class='layout-box'>
<a href="#" @click.prevent='toggleLayoutRun' class='btn'>{{isRunning ? 'Stop layout' : 'Start layout'}}</a>
</div>
</div>
</template>
<script>
import createGraphScene from './lib/createGraphScene';
import getAvailableGraphs from './lib/getAvailableGraphs';
import loadGraph from './lib/loadGraph';
import bus from './lib/bus';
import queryState from 'query-state';
import InputValue from './components/InputValue';
import InputFlag from './components/InputFlag';
let appState = queryState({
graph: 'Miserables',
timeStep: 0.5,
springLength: 10,
springCoefficient: 0.8,
dragCoefficient: 0.9,
dimensions: 2,
theta: 0.8,
gravity: -12,
}, { useSearch: true });
export default {
name: 'app',
components: {
InputValue,
InputFlag
},
methods: {
toggleLayoutRun() {
this.isRunning = !this.isRunning;
this.scene.runLayout(this.isRunning);
},
loadNewGraph(newGraph) {
this.loading = true;
this.stats = null;
this.isRunning = false;
loadGraph(newGraph).then(newGraph => {
bus.fire('load-graph', newGraph, this.selectedLayout);
this.loading = false;
});
},
onGraphLoaded() {
this.isRunning = false;
}
},
watch: {
layoutSettings: {
deep: true,
handler(newValue) {
this.scene.updateLayoutSettings(newValue);
appState.set(newValue);
}
},
fixedViewBox(newValue) {
this.scene.setFixedViewBox(newValue);
},
selectedGraph(newGraph) {
appState.set('graph', newGraph);
this.loadNewGraph(newGraph);
}
},
data() {
let graphs = getAvailableGraphs();
return {
isRunning: false,
fixedViewBox: false,
selectedGraph: appState.get('graph'),
settingsOpen: window.innerWidth > 500,
loading: false,
layoutSettings: {
timeStep: appState.get('timeStep'),
springLength: appState.get('springLength'),
springCoefficient: appState.get('springCoefficient'),
dragCoefficient: appState.get('dragCoefficient'),
dimensions: appState.get('dimensions'),
theta: appState.get('theta'),
gravity: appState.get('gravity'),
},
graphs
}
},
mounted() {
const canvas = document.getElementById('cnv');
this.scene = createGraphScene(canvas, {...this.layoutSettings});
this.loadNewGraph(this.selectedGraph);
bus.on('load-graph', this.onGraphLoaded);
},
beforeDestroy() {
if (this.scene) {
this.scene.dispose();
}
}
}
</script>
<style lang='stylus'>
small-screen = 500px;
#app {
position: absolute;
width: 400px;
background: rgb(12, 41, 82);
border: 1px solid white;
}
a {
text-decoration: none;
}
.content {
padding: 8px;
}
.row {
display: flex;
flex-direction: row;
align-items: baseline;
}
.row .label {
flex: 1;
}
.row .value {
flex: 1;
}
.row select {
width: 100%;
}
a.btn {
color: rgb(244, 244, 244);
text-decoration: none;
justify-content: center;
align-items: center;
border-top: 1px solid;
height: 32px;
width: 100%;
display: flex;
margin: 0;
}
h2 {
margin: 8px;
font-size: 18px;
font-weight: normal;
a {
color: #267fcd;
}
small a {
position: absolute;
right: 8px;
top: 8px;
font-size: 12px;
}
}
.number {
color: yellow;
font-size: 18px;
}
.names {
position: fixed;
font-size: 24px;
top: 18px;
left: 50%;
transform: translateX(-50%);
}
@media (max-width: small-screen) {
#app {
width: 100%;
}
}
</style>

View file

@ -0,0 +1,15 @@
<template>
<a @click.prevent='show' class='help-icon' href='#' title='Click for more details'>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="9" height="14" viewBox="0 0 9 14" fill='#267fcd' >
<path d="M5.5 9.812v1.875q0 0.125-0.094 0.219t-0.219 0.094h-1.875q-0.125 0-0.219-0.094t-0.094-0.219v-1.875q0-0.125 0.094-0.219t0.219-0.094h1.875q0.125 0 0.219 0.094t0.094 0.219zM7.969 5.125q0 0.422-0.121 0.789t-0.273 0.598-0.43 0.465-0.449 0.34-0.477 0.277q-0.32 0.18-0.535 0.508t-0.215 0.523q0 0.133-0.094 0.254t-0.219 0.121h-1.875q-0.117 0-0.199-0.145t-0.082-0.293v-0.352q0-0.648 0.508-1.223t1.117-0.848q0.461-0.211 0.656-0.438t0.195-0.594q0-0.328-0.363-0.578t-0.84-0.25q-0.508 0-0.844 0.227-0.273 0.195-0.836 0.898-0.102 0.125-0.242 0.125-0.094 0-0.195-0.062l-1.281-0.977q-0.102-0.078-0.121-0.195t0.043-0.219q1.25-2.078 3.625-2.078 0.625 0 1.258 0.242t1.141 0.648 0.828 0.996 0.32 1.238z"></path>
</svg>
</a>
</template>
<script>
export default {
methods: {
show() { this.$emit('show');}
}
}
</script>

View file

@ -0,0 +1,102 @@
<template>
<div class='block'>
<div class='row'>
<div class='col'>{{label}}</div>
<div class='col'>
<input type='checkbox' v-model='inputValue' >
</div>
<help-icon @show='helpVisible = !helpVisible' :class='{open: helpVisible}'></help-icon>
</div>
<div class='row help' v-if='helpVisible'>
<slot></slot>
</div>
</div>
</template>
<script>
import HelpIcon from './HelpIcon';
export default {
components: {
HelpIcon
},
props: {
label: String,
value: Boolean,
},
methods: {
selectAll(e) {
e.target.select()
}
},
data() {
return {
helpVisible: false,
inputValue: this.value
}
},
watch: {
inputValue(newValue) {
this.$emit('input', newValue);
}
}
}
</script>
<style lang="stylus">
primary-text = white;
help-background = #004499;
.block {
.row {
display: flex;
flex-direction: row;
margin-top: 1px;
}
.col {
flex: 1;
}
a.help-icon {
display: flex;
align-items: center;
justify-content: center;
align-self: stretch;
width: 25px;
margin-right: -7px;
svg {
fill: secondary-text;
}
&.open {
background: help-background;
svg {
fill: primary-text;
}
}
}
.row.help {
margin-top: 0;
background: help-background;
padding: 8px;
margin: 0 -7px;
}
input[type='text'],
input[type='number'] {
background: transparent;
color: primary-text;
border: 1px solid transparent;
padding: 7px;
font-size: 16px;
width: 100%;
margin-left: 7px;
&:focus {
outline-offset: 0;
outline: none;
border: 1px dashed;
background: #13294f;
}
&:invalid {
box-shadow:none;
}
}
}
</style>

View file

@ -0,0 +1,113 @@
<template>
<div class='block'>
<div class='row'>
<div class='col'>{{label}}</div>
<div class='col'>
<input type='number'
:step='step'
v-model='inputValue'
@focus="selectAll"
autocomplete="off"
autocorrect="off"
autocapitalize="off"
spellcheck="false">
</div>
<help-icon @show='helpVisible = !helpVisible' :class='{open: helpVisible}'></help-icon>
</div>
<div class='row help' v-if='helpVisible'>
<slot></slot>
</div>
</div>
</template>
<script>
import HelpIcon from './HelpIcon';
export default {
components: {
HelpIcon
},
props: {
label: String,
value: Number,
step: {
default: '0.1',
type: String
},
},
methods: {
selectAll(e) {
e.target.select()
}
},
data() {
return {
helpVisible: false,
inputValue: this.value
}
},
watch: {
inputValue(newValue) {
this.$emit('input', parseFloat(newValue));
}
}
}
</script>
<style lang="stylus">
primary-text = white;
help-background = #004499;
.block {
.row {
display: flex;
flex-direction: row;
margin-top: 1px;
}
.col {
flex: 1;
}
a.help-icon {
display: flex;
align-items: center;
justify-content: center;
align-self: stretch;
width: 25px;
margin-right: -7px;
svg {
fill: secondary-text;
}
&.open {
background: help-background;
svg {
fill: primary-text;
}
}
}
.row.help {
margin-top: 0;
background: help-background;
padding: 8px;
margin: 0 -7px;
}
input[type='text'],
input[type='number'] {
background: transparent;
color: primary-text;
border: 1px solid transparent;
padding: 7px;
font-size: 16px;
width: 100%;
margin-left: 7px;
&:focus {
outline-offset: 0;
outline: none;
border: 1px dashed;
background: #13294f;
}
&:invalid {
box-shadow:none;
}
}
}
</style>

View file

@ -0,0 +1,98 @@
import {GLCollection, defineProgram, InstancedAttribute, ColorAttribute} from 'w-gl';
export default class LineCollection extends GLCollection {
constructor(gl, options = {}) {
let program = defineProgram({
gl,
vertex: `
uniform mat4 modelViewProjection;
uniform float width;
uniform vec2 resolution;
attribute vec4 color;
attribute vec3 from, to;
attribute vec2 point;
varying vec4 vColor;
varying vec2 vPoint;
void main() {
vec4 clip0 = modelViewProjection * vec4(from, 1.0);
vec4 clip1 = modelViewProjection * vec4(to, 1.0);
vec2 screen0 = resolution * (0.5 * clip0.xy/clip0.w + 0.5);
vec2 screen1 = resolution * (0.5 * clip1.xy/clip1.w + 0.5);
vec2 xBasis = normalize(screen1 - screen0);
vec2 yBasis = vec2(-xBasis.y, xBasis.x);
// Offset the original points:
vec2 pt0 = screen0 + width * point.x * yBasis;
vec2 pt1 = screen1 + width * point.x * yBasis;
vec2 pt = mix(pt0, pt1, point.y);
vec4 clip = mix(clip0, clip1, point.y);
gl_Position = vec4(clip.w * (2.0 * pt/resolution - 1.0), clip.z, clip.w);
vColor = color.abgr; // mix(.abgr, aToColor.abgr, aPosition.y);
}`,
fragment: `
precision highp float;
varying vec4 vColor;
void main() {
gl_FragColor = vColor;
}`,
attributes: {
color: new ColorAttribute()
},
instanced: {
point: new InstancedAttribute([
-0.5, 0, -0.5, 1, 0.5, 1, // First 2D triangle of the quad
-0.5, 0, 0.5, 1, 0.5, 0 // Second 2D triangle of the quad
])
}
});
super(program);
this.width = options.width || 2;
}
draw(_, drawContext) {
if (!this.uniforms) {
this.uniforms = {
modelViewProjection: this.modelViewProjection,
width: this.width,
resolution: [drawContext.width, drawContext.height]
}
}
this.uniforms.resolution[0] = drawContext.width;
this.uniforms.resolution[1] = drawContext.height;
this.program.draw(this.uniforms);
}
// implement lineRenderTrait to allow SVG export via w-gl
forEachLine(cb) {
let count = this.program.getCount()
for (let i = 0; i < count; ++i) {
let vertex = this.program.get(i);
let from = { x: vertex.from[0], y: vertex.from[1], z: vertex.from[2], color: vertex.color }
let to = { x: vertex.to[0], y: vertex.to[1], z: vertex.to[2], color: vertex.color }
cb(from, to);
}
}
getLineColor(from) {
let count = this.program.getCount()
let c = from ?
from.color :
count > 0 ? this.program.get(0).color : 0xFFFFFFFF;
return [
(c >> 24) & 0xFF / 255,
(c >> 16) & 0xFF / 255,
(c >> 8) & 0xFF / 255,
(c >> 0) & 0xFF / 255,
]
}
}

View file

@ -0,0 +1,56 @@
import {GLCollection, defineProgram, ColorAttribute, InstancedAttribute} from 'w-gl';
export default class PointCollection extends GLCollection {
constructor(gl) {
let program = defineProgram({
gl,
vertex: `
uniform mat4 modelViewProjection;
attribute float size;
attribute vec3 position;
attribute vec4 color;
attribute vec2 point; // instanced
varying vec4 vColor;
varying vec2 vPoint;
void main() {
gl_Position = modelViewProjection * vec4(position + vec3(point * size, 0.), 1.0);
vColor = color.abgr;
vPoint = point;
}`,
fragment: `
precision highp float;
varying vec4 vColor;
varying vec2 vPoint;
void main() {
float dist = length(vPoint);
if (dist >= 0.5) {discard;}
gl_FragColor = vColor;
}`,
// These are just overrides:
attributes: {
color: new ColorAttribute(),
},
instanced: {
point: new InstancedAttribute([
-0.5, -0.5, -0.5, 0.5, 0.5, 0.5,
0.5, 0.5, 0.5, -0.5, -0.5, -0.5,
])
},
preDrawHook(/* programInfo */) {
return `gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);`;
},
postDrawHook() {
return 'gl.disable(gl.DEPTH_TEST);';
},
});
super(program);
}
}

View file

@ -0,0 +1,5 @@
import eventify from 'ngraph.events';
const bus = eventify({});
export default bus;

View file

@ -0,0 +1,15 @@
import createLayout from '../../../';
export default function createForceLayout(graph, layoutSettings) {
// return window.ngraphCreate2dLayout(graph, Object.assign({
return createLayout(graph, Object.assign({
dimensions: 2,
timeStep: 0.5,
springLength: 10,
gravity: -12,
springCoefficient: 0.8,
dragCoefficient: 0.9,
// adaptiveTimeStepWeight: 0.1,
debug: false,
}, layoutSettings));
}

View file

@ -0,0 +1,259 @@
import {createScene, createGuide} from 'w-gl';
import LineCollection from './LineCollection';
import PointCollection from './PointCollection';
import bus from './bus';
import createHighLayout from 'ngraph.hde'
import createForceLayout from './createForceLayout';
import findLargestComponent from './findLargestComponent';
import createGraph from 'ngraph.graph';
export default function createGraphScene(canvas, layoutSettings = {}) {
let drawLinks = true;
// Since graph can be loaded dynamically, we have these uninitialized
// and captured into closure. loadGraph will do the initialization
let graph, layout;
let scene, nodes, lines, guide;
let fixedViewBox = false;
let isRunning = false;
let rafHandle;
bus.on('load-graph', loadGraph);
return {
dispose,
runLayout,
updateLayoutSettings,
setFixedViewBox,
};
function loadGraph(newGraph, desiredLayout) {
if (scene) {
scene.dispose();
layout.dispose();
scene = null
isRunning = false;
cancelAnimationFrame(rafHandle);
}
// newGraph = createGraph(); newGraph.addLink(1, 2)
scene = initScene();
graph = newGraph; //findLargestComponent(newGraph, 1)[0];
// Let them play on console with it!
window.graph = graph;
guide = createGuide(scene, {showGrid: true, lineColor: 0xffffff10, maxAlpha: 0x10, showCursor: false});
// this is a standard force layout
layout = createForceLayout(graph, layoutSettings);
//standardizePositions(layout)
let minX = -42, minY = -42;
let maxX = 42, maxY =42
setSceneSize(Math.max(maxX - minX, maxY - minY) * 1.2);
initUIElements();
rafHandle = requestAnimationFrame(frame);
}
function setSceneSize(sceneSize) {
scene.setViewBox({
left: -sceneSize,
top: -sceneSize,
right: sceneSize,
bottom: sceneSize,
});
}
function runLayout(newIsRunning) {
isRunning = newIsRunning;
}
function updateLayoutSettings(newLayoutSettings) {
let props = ['timeStep', 'springLength', 'springCoefficient', 'dimensions', 'dragCoefficient', 'gravity', 'theta']
let previousDimensions = (layoutSettings && layoutSettings.dimensions) || 2;
layoutSettings = props.reduce((settings, name) => (settings[name] = newLayoutSettings[name], settings), {});
if (!layout) return;
if (layoutSettings.dimensions !== previousDimensions) {
let prevLayout = layout;
layout = createForceLayout(graph, layoutSettings)
graph.forEachNode(node => {
let prevPos = prevLayout.getNodePosition(node.id);
let positions = Object.keys(prevPos).map(name => prevPos[name]);
for (let i = previousDimensions; i < layoutSettings.dimensions; ++i) {
// If new layout has more dimensions than the previous layout, fill those with random values:
positions.push(Math.random());
}
positions.unshift(node.id);
layout.setNodePosition.apply(layout, positions);
});
prevLayout.dispose();
} else {
props.forEach(name => {
layout.simulator[name](layoutSettings[name]);
});
}
}
function setFixedViewBox(isFixed) {
fixedViewBox = isFixed;
}
function initScene() {
let scene = createScene(canvas);
scene.setClearColor(12/255, 41/255, 82/255, 1)
return scene;
}
function initUIElements() {
nodes = new PointCollection(scene.getGL(), {
capacity: graph.getNodesCount()
});
graph.forEachNode(node => {
var point = layout.getNodePosition(node.id);
let size = 1;
if (node.data && node.data.size) {
size = node.data.size;
} else {
if (!node.data) node.data = {};
node.data.size = size;
}
node.ui = {size, position: [point.x, point.y, point.z || 0], color: 0x90f8fcff};
node.uiId = nodes.add(node.ui);
});
lines = new LineCollection(scene.getGL(), { capacity: graph.getLinksCount() });
graph.forEachLink(link => {
var from = layout.getNodePosition(link.fromId);
var to = layout.getNodePosition(link.toId);
var line = { from: [from.x, from.y, from.z || 0], to: [to.x, to.y, to.z || 0], color: 0xFFFFFF10 };
link.ui = line;
link.uiId = lines.add(link.ui);
});
// lines.add({from: [0, 0, 0], to: [0, 10, 0], color: 0xFF0000FF})
scene.appendChild(lines);
scene.appendChild(nodes);
}
function frame() {
rafHandle = requestAnimationFrame(frame);
if (isRunning) {
layout.step();
if (fixedViewBox) {
let rect = layout.getGraphRect();
scene.setViewBox({
left: rect.min_x,
top: rect.min_y,
right: rect.max_x,
bottom: rect.max_y,
});
}
}
drawGraph();
scene.renderFrame();
}
function drawGraph() {
let names = ['x', 'y', 'z']
// let minR = Infinity; let maxR = -minR;
// let minG = Infinity; let maxG = -minG;
// let minB = Infinity; let maxB = -minB;
// graph.forEachNode(node => {
// let pos = layout.getNodePosition(node.id);
// if (pos.c4 < minR) minR = pos.c4;
// if (pos.c4 > maxR) maxR = pos.c4;
// if (pos.c5 < minG) minG = pos.c5;
// if (pos.c5 > maxG) maxG = pos.c5;
// if (pos.c6 < minB) minB = pos.c6;
// if (pos.c6 > maxB) maxB = pos.c6;
// });
graph.forEachNode(node => {
let pos = layout.getNodePosition(node.id);
let uiPosition = node.ui.position;
for (let i = 0; i < 3; ++i) {
uiPosition[i] = pos[names[i]] || 0;
}
// let r = Math.floor(255 * (pos.c4 - minR) / (maxR - minR)) << 24;
// let g = Math.floor(255 * (pos.c5 - minG) / (maxG - minG)) << 16;
// let b = Math.floor(255 * (pos.c6 - minB) / (maxB - minB)) << 8;
// [r, g, b] = lab2rgb(
// (pos.c4 - minR) / (maxR - minR),
// (pos.c5 - minG) / (maxG - minG),
// (pos.c6 - minB) / (maxB - minB)
// );
// node.ui.color = (0x000000FF | r | g | b);
nodes.update(node.uiId, node.ui)
});
if (drawLinks) {
graph.forEachLink(link => {
var fromPos = layout.getNodePosition(link.fromId);
var toPos = layout.getNodePosition(link.toId);
let {from, to} = link.ui;
for (let i = 0; i < 3; ++i) {
from[i] = fromPos[names[i]] || 0;
to[i] = toPos[names[i]] || 0;
}
// from[0] = fromPos.x || 0; from[1] = fromPos.y || 0; from[2] = fromPos.z || 0;
// to[0] = toPos.x || 0; to[1] = toPos.y || 0; to[2] = toPos.z || 0;
// link.ui.color = lerp(graph.getNode(link.fromId).ui.color, graph.getNode(link.toId).ui.color);
lines.update(link.uiId, link.ui);
})
}
}
function lerp(aColor, bColor) {
let ar = (aColor >> 24) & 0xFF;
let ag = (aColor >> 16) & 0xFF;
let ab = (aColor >> 8) & 0xFF;
let br = (bColor >> 24) & 0xFF;
let bg = (bColor >> 16) & 0xFF;
let bb = (bColor >> 8) & 0xFF;
let r = Math.floor((ar + br) / 2);
let g = Math.floor((ag + bg) / 2);
let b = Math.floor((ab + bb) / 2);
return (r << 24) | (g << 16) | (b << 8) | 0xF0;
}
function dispose() {
cancelAnimationFrame(rafHandle);
scene.dispose();
bus.off('load-graph', loadGraph);
}
}
function standardizePositions(layout) {
let arr = [];
let avgX = 0, avgY = 0;
layout.forEachBody(body => {
arr.push(body.pos);
avgX += body.pos.x;
avgY += body.pos.y;
});
let meanX = avgX / arr.length;
let meanY = avgY / arr.length;
let varX = 0, varY = 0;
arr.forEach(pos => {
varX += Math.pow(pos.x - meanX, 2);
varY += Math.pow(pos.y - meanY, 2);
});
varX = Math.sqrt(varX / arr.length);
varY = Math.sqrt(varY / arr.length);
arr.forEach(pos => {
pos.x = 10 * (pos.x - meanX) / varX;
pos.y = 10 * (pos.y - meanY) / varY;
});
}

View file

@ -0,0 +1,78 @@
/**
* Handles dropped files into the browser.
*/
export default function fileDrop(dropHandler, onDropped) {
dropHandler.addEventListener('drop', handleDrop, true);
dropHandler.addEventListener('dragover', handleDragOver);
dropHandler.addEventListener('dragenter', prevent);
dropHandler.addEventListener('dragleave', handleDragEnd)
dropHandler.addEventListener('dragend', handleDragEnd);
return {
dispose
}
function dispose() {
dropHandler.removeEventListener('drop', handleDrop);
dropHandler.removeEventListener('dragover', handleDragOver);
dropHandler.removeEventListener('dragenter', prevent);
dropHandler.removeEventListener('dragleave', handleDragEnd)
dropHandler.removeEventListener('dragend', handleDragEnd);
}
function prevent(e) {
if (!hasFiles(e)) return;
e.preventDefault();
}
function handleDrop(ev) {
handleDragEnd();
ev.preventDefault();
// If dropped items aren't files, reject them
var dt = ev.dataTransfer;
var files = []
var i, file;
if (dt.items) {
// Use DataTransferItemList interface to access the file(s)
for (i = 0; i < dt.items.length; i++) {
if (dt.items[i].kind == "file") {
file = dt.items[i].getAsFile();
files.push(file);
}
}
} else {
// Use DataTransfer interface to access the file(s)
for (i = 0; i < dt.files.length; i++) {
file = dt.files[i];
files.push(file);
}
}
onDropped(files);
}
function handleDragOver(e) {
if (!hasFiles(e)) return;
e.preventDefault();
dropHandler.classList.add('drag-over');
}
function hasFiles(e) {
if (!e.dataTransfer) return false;
if (e.dataTransfer.files && e.dataTransfer.files.length > 0) return true;
var items = e.dataTransfer.items;
if (!items) return false;
for (var i = 0; i < items.length; ++i) {
if (items[i].kind === 'file') return true;
}
return false;
}
function handleDragEnd() {
dropHandler.classList.remove('drag-over');
}
}

View file

@ -0,0 +1,64 @@
import createGraph from 'ngraph.graph';
/**
* Returns array of first `count` largest connected components
* of the `graph`
*/
export default function findLargestComponent(graph, count) {
var nodeIdToComponentId = new Map();
var connectedComponents = [];
var lastComponentId = 0;
graph.forEachNode(function(node) {
if (nodeIdToComponentId.has(node.id)) {
// we already seen this cluster. Ignore it.
return;
}
// We found a new connected component:
nodeIdToComponentId.set(node.id, lastComponentId);
var currentComponent = new Set();
connectedComponents.push(currentComponent);
// Let's find what other nodes belong to this component
bfs(graph, node.id, otherNode => {
currentComponent.add(otherNode);
nodeIdToComponentId.set(otherNode, lastComponentId);
});
lastComponentId += 1;
});
return connectedComponents.sort((a, b) => b.size - a.size)
.slice(0, count)
.map(largestComponent => {
let subGraph = createGraph();
// not the most efficient way, as we iterate over every single link.
// This could be improved, for example by performing bfs from the component
graph.forEachLink(link => {
if (largestComponent.has(link.fromId)) {
subGraph.addLink(link.fromId, link.toId);
}
})
return subGraph;
});
}
function bfs(graph, startFromNodeId, visitor) {
let queue = [startFromNodeId];
let visited = new Set(queue);
while (queue.length) {
let nodeId = queue.shift();
visitor(nodeId);
graph.forEachLinkedNode(nodeId, function(otherNode) {
if (visited.has(otherNode.id)) return;
queue.push(otherNode.id);
visited.add(otherNode.id);
});
}
}

View file

@ -0,0 +1,731 @@
export default function getAvailableGraphs() {
return [
'Miserables',
'Binary',
'HB/blckhole',
'Bai/rw5151',
'HB/bcsstm13',
'HB/lshp1882',
'HB/plat1919',
'HB/bcsstk26',
'Bai/dw256A',
'Bai/tols2000',
'Bai/dw1024',
'Bai/rdb2048',
'Pajek/CSphd',
'GHS_indef/laser',
"Bai/bfwa398",
"Bai/bfwa62",
"Bai/bfwb398",
"Bai/bfwb62",
"Bai/bfwb782",
"Bai/bwm200",
"Bai/cdde1",
"Bai/cdde2",
"Bai/cdde3",
"Bai/cdde4",
"Bai/cdde5",
"Bai/cdde6",
"Bai/ck104",
"Bai/ck400",
"Bai/ck656",
"Bai/dw256B",
"Bai/dwa512",
"Bai/dwb512",
"Bai/dwg961a",
"Bai/lop163",
"Bai/mhdb416",
"Bai/odepa400",
"Bai/olm100",
"Bai/olm1000",
"Bai/olm500",
"Bai/pde225",
"Bai/pde900",
"Bai/qh1484",
"Bai/qh768",
"Bai/qh882",
"Bai/rdb1250",
"Bai/rdb1250l",
"Bai/rdb200",
"Bai/rdb200l",
"Bai/rdb450",
"Bai/rdb450l",
"Bai/rdb800l",
"Bai/rdb968",
"Bai/rw136",
"Bai/rw496",
"Bai/tols1090",
"Bai/tols340",
"Bai/tols90",
"Bai/tub100",
"Bai/tub1000",
"Barabasi/NotreDame_yeast",
"Bates/Chem97ZtZ",
"FEMLAB/poisson2D",
"FEMLAB/problem1",
"FIDAP/ex1",
"FIDAP/ex5",
"Grund/b1_ss",
"Grund/d_ss",
"Grund/poli",
"Gset/G11",
"Gset/G12",
"Gset/G13",
"Gset/G14",
"Gset/G15",
"Gset/G16",
"Gset/G17",
"Gset/G18",
"Gset/G19",
"Gset/G20",
"Gset/G21",
"Gset/G32",
"Gset/G33",
"Gset/G34",
"Gset/G35",
"Gset/G36",
"Gset/G37",
"Gset/G38",
"Gset/G39",
"Gset/G40",
"Gset/G41",
"Gset/G42",
"Gset/G43",
"Gset/G44",
"Gset/G45",
"Gset/G46",
"Gset/G47",
"Gset/G48",
"Gset/G49",
"Gset/G50",
"Gset/G51",
"Gset/G52",
"Gset/G53",
"Gset/G54",
"Gset/G55",
"Gset/G57",
"Hamrle/Hamrle1",
"HB/1138_bus",
"HB/494_bus",
"HB/662_bus",
"HB/685_bus",
"HB/abb313",
"HB/arc130",
"HB/ash219",
"HB/ash292",
"HB/ash331",
"HB/ash608",
"HB/ash85",
"HB/ash958",
"HB/bcspwr01",
"HB/bcspwr02",
"HB/bcspwr03",
"HB/bcspwr04",
"HB/bcspwr05",
"HB/bcspwr06",
"HB/bcspwr07",
"HB/bcspwr08",
"HB/bcspwr09",
"HB/bcsstk01",
"HB/bcsstk02",
"HB/bcsstk03",
"HB/bcsstk04",
"HB/bcsstk05",
"HB/bcsstk06",
"HB/bcsstk07",
"HB/bcsstk19",
"HB/bcsstk20",
"HB/bcsstk22",
"HB/bcsstm01",
"HB/bcsstm02",
"HB/bcsstm03",
"HB/bcsstm04",
"HB/bcsstm05",
"HB/bcsstm06",
"HB/bcsstm07",
"HB/bcsstm08",
"HB/bcsstm09",
"HB/bcsstm11",
"HB/bcsstm19",
"HB/bcsstm20",
"HB/bcsstm21",
"HB/bcsstm22",
"HB/bcsstm23",
"HB/bcsstm24",
"HB/bcsstm26",
"HB/bp_0",
"HB/bp_1000",
"HB/bp_1200",
"HB/bp_1400",
"HB/bp_1600",
"HB/bp_200",
"HB/bp_400",
"HB/bp_600",
"HB/bp_800",
"HB/can_1054",
"HB/can_1072",
"HB/can_144",
"HB/can_161",
"HB/can_187",
"HB/can_229",
"HB/can_24",
"HB/can_256",
"HB/can_268",
"HB/can_292",
"HB/can_445",
"HB/can_61",
"HB/can_62",
"HB/can_634",
"HB/can_715",
"HB/can_73",
"HB/can_838",
"HB/can_96",
"HB/curtis54",
"HB/dwt_1005",
"HB/dwt_1007",
"HB/dwt_1242",
"HB/dwt_162",
"HB/dwt_193",
"HB/dwt_198",
"HB/dwt_209",
"HB/dwt_221",
"HB/dwt_234",
"HB/dwt_245",
"HB/dwt_2680",
"HB/dwt_307",
"HB/dwt_310",
"HB/dwt_346",
"HB/dwt_361",
"HB/dwt_419",
"HB/dwt_492",
"HB/dwt_503",
"HB/dwt_512",
"HB/dwt_59",
"HB/dwt_592",
"HB/dwt_607",
"HB/dwt_66",
"HB/dwt_72",
"HB/dwt_758",
"HB/dwt_869",
"HB/dwt_87",
"HB/dwt_878",
"HB/dwt_918",
"HB/dwt_992",
"HB/eris1176",
"HB/fs_183_1",
"HB/fs_183_3",
"HB/fs_183_4",
"HB/fs_183_6",
"HB/fs_541_1",
"HB/fs_541_2",
"HB/fs_541_3",
"HB/fs_541_4",
"HB/fs_680_1",
"HB/fs_680_2",
"HB/fs_680_3",
"HB/gent113",
"HB/gr_30_30",
"HB/gre_1107",
"HB/gre_115",
"HB/gre_185",
"HB/gre_216a",
"HB/gre_216b",
"HB/gre_343",
"HB/gre_512",
"HB/hor_131",
"HB/ibm32",
"HB/illc1033",
"HB/impcol_a",
"HB/impcol_b",
"HB/impcol_c",
"HB/impcol_d",
"HB/impcol_e",
"HB/jagmesh1",
"HB/jagmesh2",
"HB/jagmesh3",
"HB/jagmesh4",
"HB/jagmesh5",
"HB/jagmesh7",
"HB/jagmesh8",
"HB/jagmesh9",
"HB/jgl009",
"HB/jgl011",
"HB/jpwh_991",
"HB/lap_25",
"HB/lns_131",
"HB/lns_511",
"HB/lnsp_131",
"HB/lnsp_511",
"HB/lock_700",
"HB/lshp1009",
"HB/lshp1270",
"HB/lshp1561",
"HB/lshp2233",
"HB/lshp2614",
"HB/lshp3025",
"HB/lshp3466",
"HB/lshp_265",
"HB/lshp_406",
"HB/lshp_577",
"HB/lshp_778",
"HB/lund_a",
"HB/lund_b",
"HB/mcca",
"HB/nnc261",
"HB/nnc666",
"HB/nos1",
"HB/nos2",
"HB/nos4",
"HB/nos5",
"HB/nos6",
"HB/nos7",
"HB/orsirr_1",
"HB/orsirr_2",
"HB/plat362",
"HB/plskz362",
"HB/pores_1",
"HB/pores_3",
"HB/rgg010",
"HB/saylr1",
"HB/saylr3",
"HB/sherman1",
"HB/sherman4",
"HB/shl_0",
"HB/shl_200",
"HB/shl_400",
"HB/sstmodel",
"HB/steam1",
"HB/steam3",
"HB/str_0",
"HB/str_200",
"HB/str_400",
"HB/str_600",
"HB/well1033",
"HB/west0067",
"HB/west0132",
"HB/west0156",
"HB/west0167",
"HB/west0381",
"HB/west0479",
"HB/west0497",
"HB/west0655",
"HB/west0989",
"HB/west1505",
"HB/west2021",
"HB/will199",
"HB/will57",
"HB/wm1",
"HB/wm2",
"HB/wm3",
"HB/young1c",
"HB/young2c",
"HB/young3c",
"HB/young4c",
"JGD_BIBD/bibd_11_5",
"JGD_BIBD/bibd_12_4",
"JGD_BIBD/bibd_12_5",
"JGD_BIBD/bibd_15_3",
"JGD_BIBD/bibd_17_3",
"JGD_BIBD/bibd_17_4",
"JGD_BIBD/bibd_17_4b",
"JGD_BIBD/bibd_81_2",
"JGD_BIBD/bibd_9_3",
"JGD_BIBD/bibd_9_5",
"JGD_CAG/CAG_mat364",
"JGD_CAG/CAG_mat72",
"JGD_Forest/TF10",
"JGD_Forest/TF11",
"JGD_Forest/TF12",
"JGD_Forest/TF13",
"JGD_Franz/Franz1",
"JGD_Franz/Franz3",
"JGD_G5/IG5-10",
"JGD_G5/IG5-6",
"JGD_G5/IG5-7",
"JGD_G5/IG5-8",
"JGD_G5/IG5-9",
"JGD_GL6/GL6_D_10",
"JGD_GL6/GL6_D_6",
"JGD_GL6/GL6_D_7",
"JGD_GL6/GL6_D_8",
"JGD_GL6/GL6_D_9",
"JGD_GL7d/GL7d10",
"JGD_GL7d/GL7d11",
"JGD_GL7d/GL7d26",
"JGD_Homology/ch3-3-b1",
"JGD_Homology/ch3-3-b2",
"JGD_Homology/ch4-4-b1",
"JGD_Homology/ch4-4-b2",
"JGD_Homology/ch4-4-b3",
"JGD_Homology/ch5-5-b1",
"JGD_Homology/ch5-5-b2",
"JGD_Homology/ch5-5-b3",
"JGD_Homology/ch5-5-b4",
"JGD_Homology/ch6-6-b1",
"JGD_Homology/ch6-6-b2",
"JGD_Homology/ch6-6-b5",
"JGD_Homology/ch7-6-b1",
"JGD_Homology/ch7-7-b1",
"JGD_Homology/ch7-8-b1",
"JGD_Homology/ch7-9-b1",
"JGD_Homology/ch8-8-b1",
"JGD_Homology/cis-n4c6-b1",
"JGD_Homology/cis-n4c6-b15",
"JGD_Homology/cis-n4c6-b2",
"JGD_Homology/klein-b1",
"JGD_Homology/klein-b2",
"JGD_Homology/mk10-b1",
"JGD_Homology/mk10-b2",
"JGD_Homology/mk10-b4",
"JGD_Homology/mk11-b1",
"JGD_Homology/mk12-b1",
"JGD_Homology/mk9-b1",
"JGD_Homology/mk9-b2",
"JGD_Homology/mk9-b3",
"JGD_Homology/n2c6-b1",
"JGD_Homology/n2c6-b10",
"JGD_Homology/n2c6-b2",
"JGD_Homology/n2c6-b3",
"JGD_Homology/n2c6-b9",
"JGD_Homology/n3c4-b1",
"JGD_Homology/n3c4-b2",
"JGD_Homology/n3c4-b3",
"JGD_Homology/n3c4-b4",
"JGD_Homology/n3c5-b1",
"JGD_Homology/n3c5-b2",
"JGD_Homology/n3c5-b3",
"JGD_Homology/n3c5-b4",
"JGD_Homology/n3c5-b5",
"JGD_Homology/n3c5-b6",
"JGD_Homology/n3c5-b7",
"JGD_Homology/n3c6-b1",
"JGD_Homology/n3c6-b10",
"JGD_Homology/n3c6-b11",
"JGD_Homology/n3c6-b2",
"JGD_Homology/n3c6-b3",
"JGD_Homology/n4c5-b1",
"JGD_Homology/n4c5-b10",
"JGD_Homology/n4c5-b11",
"JGD_Homology/n4c5-b2",
"JGD_Homology/n4c5-b3",
"JGD_Homology/n4c5-b9",
"JGD_Homology/n4c6-b1",
"JGD_Homology/n4c6-b15",
"JGD_Homology/n4c6-b2",
"JGD_Kocay/Trec10",
"JGD_Kocay/Trec3",
"JGD_Kocay/Trec4",
"JGD_Kocay/Trec5",
"JGD_Kocay/Trec6",
"JGD_Kocay/Trec7",
"JGD_Kocay/Trec8",
"JGD_Kocay/Trec9",
"JGD_Margulies/cat_ears_2_1",
"JGD_Margulies/cat_ears_2_4",
"JGD_Margulies/cat_ears_3_1",
"JGD_Margulies/cat_ears_4_1",
"JGD_Margulies/flower_4_1",
"JGD_Margulies/flower_5_1",
"JGD_Margulies/flower_7_1",
"JGD_Margulies/flower_8_1",
"JGD_Margulies/kneser_6_2_1",
"JGD_Margulies/wheel_3_1",
"JGD_Margulies/wheel_4_1",
"JGD_Margulies/wheel_5_1",
"JGD_Margulies/wheel_6_1",
"JGD_Margulies/wheel_7_1",
"JGD_Relat/rel3",
"JGD_Relat/rel4",
"JGD_Relat/rel5",
"JGD_Relat/rel6",
"JGD_Relat/relat3",
"JGD_Relat/relat4",
"JGD_Relat/relat5",
"JGD_Relat/relat6",
"JGD_SL6/D_10",
"JGD_SL6/D_11",
"JGD_SL6/D_5",
"JGD_SL6/D_6",
"JGD_SPG/08blocks",
"JGD_SPG/EX1",
"JGD_SPG/EX2",
"JGD_Trefethen/Trefethen_150",
"JGD_Trefethen/Trefethen_20",
"JGD_Trefethen/Trefethen_200",
"JGD_Trefethen/Trefethen_200b",
"JGD_Trefethen/Trefethen_20b",
"JGD_Trefethen/Trefethen_300",
"JGD_Trefethen/Trefethen_500",
"JGD_Trefethen/Trefethen_700",
"LPnetlib/lp_adlittle",
"LPnetlib/lp_afiro",
"LPnetlib/lp_agg",
"LPnetlib/lp_agg2",
"LPnetlib/lp_agg3",
"LPnetlib/lp_bandm",
"LPnetlib/lp_beaconfd",
"LPnetlib/lp_blend",
"LPnetlib/lp_bnl1",
"LPnetlib/lp_bore3d",
"LPnetlib/lp_brandy",
"LPnetlib/lp_capri",
"LPnetlib/lp_czprob",
"LPnetlib/lp_degen2",
"LPnetlib/lp_e226",
"LPnetlib/lp_etamacro",
"LPnetlib/lp_fffff800",
"LPnetlib/lp_finnis",
"LPnetlib/lp_fit1p",
"LPnetlib/lp_ganges",
"LPnetlib/lp_gfrd_pnc",
"LPnetlib/lp_grow15",
"LPnetlib/lp_grow7",
"LPnetlib/lp_israel",
"LPnetlib/lp_kb2",
"LPnetlib/lp_ken_07",
"LPnetlib/lp_lotfi",
"LPnetlib/lp_modszk1",
"LPnetlib/lp_perold",
"LPnetlib/lp_pilot4",
"LPnetlib/lp_qap8",
"LPnetlib/lp_recipe",
"LPnetlib/lp_sc105",
"LPnetlib/lp_sc205",
"LPnetlib/lp_sc50a",
"LPnetlib/lp_sc50b",
"LPnetlib/lp_scagr25",
"LPnetlib/lp_scagr7",
"LPnetlib/lp_scfxm1",
"LPnetlib/lp_scfxm2",
"LPnetlib/lp_scfxm3",
"LPnetlib/lp_scorpion",
"LPnetlib/lp_scrs8",
"LPnetlib/lp_scsd1",
"LPnetlib/lp_scsd6",
"LPnetlib/lp_sctap1",
"LPnetlib/lp_sctap2",
"LPnetlib/lp_sctap3",
"LPnetlib/lp_share1b",
"LPnetlib/lp_share2b",
"LPnetlib/lp_shell",
"LPnetlib/lp_ship04l",
"LPnetlib/lp_ship04s",
"LPnetlib/lp_ship08s",
"LPnetlib/lp_ship12s",
"LPnetlib/lp_sierra",
"LPnetlib/lp_stair",
"LPnetlib/lp_standata",
"LPnetlib/lp_standgub",
"LPnetlib/lp_standmps",
"LPnetlib/lp_stocfor1",
"LPnetlib/lp_tuff",
"LPnetlib/lp_vtp_base",
"LPnetlib/lpi_bgdbg1",
"LPnetlib/lpi_bgetam",
"LPnetlib/lpi_bgprtr",
"LPnetlib/lpi_box1",
"LPnetlib/lpi_chemcom",
"LPnetlib/lpi_cplex2",
"LPnetlib/lpi_ex72a",
"LPnetlib/lpi_ex73a",
"LPnetlib/lpi_forest6",
"LPnetlib/lpi_galenet",
"LPnetlib/lpi_itest2",
"LPnetlib/lpi_itest6",
"LPnetlib/lpi_klein1",
"LPnetlib/lpi_klein2",
"LPnetlib/lpi_mondou2",
"LPnetlib/lpi_pang",
"LPnetlib/lpi_pilot4i",
"LPnetlib/lpi_qual",
"LPnetlib/lpi_reactor",
"LPnetlib/lpi_refinery",
"LPnetlib/lpi_vol1",
"LPnetlib/lpi_woodinfe",
"MathWorks/Harvard500",
"MathWorks/Pd_rhs",
"MathWorks/pivtol",
"MathWorks/QRpivot",
"Meszaros/cep1",
"Meszaros/cr42",
"Meszaros/farm",
"Meszaros/gams10a",
"Meszaros/gams10am",
"Meszaros/gams30a",
"Meszaros/gams30am",
"Meszaros/gams60am",
"Meszaros/gas11",
"Meszaros/iiasa",
"Meszaros/iprob",
"Meszaros/kleemin",
"Meszaros/l9",
"Meszaros/model1",
"Meszaros/model2",
"Meszaros/nemsafm",
"Meszaros/nemscem",
"Meszaros/nsic",
"Meszaros/p0033",
"Meszaros/p0040",
"Meszaros/p0201",
"Meszaros/p0282",
"Meszaros/p0291",
"Meszaros/p0548",
"Meszaros/p2756",
"Meszaros/problem",
"Meszaros/qiulp",
"Meszaros/refine",
"Meszaros/rosen7",
"Meszaros/scagr7-2c",
"Meszaros/scrs8-2b",
"Meszaros/scrs8-2c",
"Meszaros/small",
"Meszaros/zed",
"Morandini/robot",
"Morandini/rotor1",
"Muite/Chebyshev1",
"NYPA/Maragal_1",
"NYPA/Maragal_2",
"Oberwolfach/LF10",
"Oberwolfach/LFAT5",
"Pajek/Cities",
"Pajek/divorce",
"Pajek/EPA",
"Pajek/Erdos02",
"Pajek/Erdos971",
"Pajek/Erdos972",
"Pajek/Erdos981",
"Pajek/Erdos982",
"Pajek/Erdos991",
"Pajek/Erdos992",
"Pajek/EVA",
"Pajek/football",
"Pajek/GD00_a",
"Pajek/GD00_c",
"Pajek/GD01_a",
"Pajek/GD01_A",
"Pajek/GD01_b",
"Pajek/GD01_c",
"Pajek/GD02_a",
"Pajek/GD02_b",
"Pajek/GD06_Java",
"Pajek/GD06_theory",
"Pajek/GD95_a",
"Pajek/GD95_b",
"Pajek/GD95_c",
"Pajek/GD96_a",
"Pajek/GD96_b",
"Pajek/GD96_c",
"Pajek/GD96_d",
"Pajek/GD97_a",
"Pajek/GD97_b",
"Pajek/GD97_c",
"Pajek/GD98_a",
"Pajek/GD98_b",
"Pajek/GD98_c",
"Pajek/GD99_b",
"Pajek/GD99_c",
"Pajek/GlossGT",
"Pajek/Journals",
"Pajek/Kohonen",
"Pajek/Ragusa16",
"Pajek/Ragusa18",
"Pajek/Roget",
"Pajek/Sandi_authors",
"Pajek/Sandi_sandi",
"Pajek/SciMet",
"Pajek/SmaGri",
"Pajek/SmallW",
"Pajek/Stranke94",
"Pajek/Tina_AskCal",
"Pajek/Tina_AskCog",
"Pajek/Tina_DisCal",
"Pajek/Tina_DisCog",
"Pajek/USAir97",
"Pajek/USpowerGrid",
"Pajek/WorldCities",
"Pajek/yeast",
"Pothen/mesh1e1",
"Pothen/mesh1em1",
"Pothen/mesh1em6",
"Pothen/mesh2e1",
"Pothen/mesh2em5",
"Pothen/mesh3e1",
"Pothen/mesh3em5",
"Pothen/sphere2",
"Pothen/sphere3",
"Qaplib/lp_nug05",
"Qaplib/lp_nug06",
"Qaplib/lp_nug07",
"Qaplib/lp_nug08",
"Rajat/rajat02",
"Rajat/rajat05",
"Rajat/rajat11",
"Rajat/rajat14",
"Rajat/rajat19",
"Sandia/oscil_dcop_01",
"Sandia/oscil_dcop_02",
"Sandia/oscil_dcop_03",
"Sandia/oscil_dcop_04",
"Sandia/oscil_dcop_05",
"Sandia/oscil_dcop_06",
"Sandia/oscil_dcop_07",
"Sandia/oscil_dcop_08",
"Sandia/oscil_dcop_09",
"Sandia/oscil_dcop_10",
"Sandia/oscil_dcop_11",
"Sandia/oscil_dcop_12",
"Sandia/oscil_dcop_13",
"Sandia/oscil_dcop_14",
"Sandia/oscil_dcop_15",
"Sandia/oscil_dcop_16",
"Sandia/oscil_dcop_17",
"Sandia/oscil_dcop_18",
"Sandia/oscil_dcop_19",
"Sandia/oscil_dcop_20",
"Sandia/oscil_dcop_21",
"Sandia/oscil_dcop_22",
"Sandia/oscil_dcop_23",
"Sandia/oscil_dcop_24",
"Sandia/oscil_dcop_25",
"Sandia/oscil_dcop_26",
"Sandia/oscil_dcop_27",
"Sandia/oscil_dcop_28",
"Sandia/oscil_dcop_29",
"Sandia/oscil_dcop_30",
"Sandia/oscil_dcop_31",
"Sandia/oscil_dcop_32",
"Sandia/oscil_dcop_33",
"Sandia/oscil_dcop_34",
"Sandia/oscil_dcop_35",
"Sandia/oscil_dcop_36",
"Sandia/oscil_dcop_37",
"Sandia/oscil_dcop_38",
"Sandia/oscil_dcop_39",
"Sandia/oscil_dcop_40",
"Sandia/oscil_dcop_41",
"Sandia/oscil_dcop_42",
"Sandia/oscil_dcop_43",
"Sandia/oscil_dcop_44",
"Sandia/oscil_dcop_45",
"Sandia/oscil_dcop_46",
"Sandia/oscil_dcop_47",
"Sandia/oscil_dcop_48",
"Sandia/oscil_dcop_49",
"Sandia/oscil_dcop_50",
"Sandia/oscil_dcop_51",
"Sandia/oscil_dcop_52",
"Sandia/oscil_dcop_53",
"Sandia/oscil_dcop_54",
"Sandia/oscil_dcop_55",
"Sandia/oscil_dcop_56",
"Sandia/oscil_dcop_57",
"Sandia/oscil_trans_01",
"TOKAMAK/utm300",
"vanHeukelum/cage3",
"vanHeukelum/cage4",
"vanHeukelum/cage5",
"vanHeukelum/cage6",
"vanHeukelum/cage7",
"YZhou/circuit204"
];
}

View file

@ -0,0 +1,24 @@
/**
* Load your graph here.
*/
// https://github.com/anvaka/miserables
import miserables from 'miserables';
// Other loaders:
// https://github.com/anvaka/ngraph.generators
// import generate from 'ngraph.generators';
// https://github.com/anvaka/ngraph.graph
// import createGraph from 'ngraph.graph';
// https://github.com/anvaka/ngraph.fromjson
// import fromjson from 'ngraph.fromjson'
// https://github.com/anvaka/ngraph.fromdot
// import fromdot from 'ngraph.fromdot'
export default function getGraph() {
return miserables.create();
// return generate.wattsStrogatz(20, 5, 0.4);
}

View file

@ -0,0 +1,39 @@
import fromDot from 'ngraph.fromdot';
import fromJson from 'ngraph.fromjson';
import bus from './bus.js';
/**
* Loads graph from a dropped file
*/
export default function loadDroppedGraph(files) {
let file = files[0];
var reader = new FileReader();
reader.readAsText(file, "UTF-8");
reader.onload = e => {
let content = e.target.result;
let graph = tryDot(content) || tryJson(content);
if (graph) bus.fire('load-graph', graph);
}
reader.onerror = (e) => {
//eslint-disable-next-line
console.log('error loading dot file: ', e)
};
function tryDot(fileContent) {
try {
return fromDot(fileContent);
} catch (e) {
//eslint-disable-next-line
console.log('error loading dot file: ', e)
}
}
function tryJson(fileContent) {
try {
return fromJson(JSON.parse(fileContent));
} catch (e) {
//eslint-disable-next-line
console.log('error loading JSON: ', e)
}
}
}

View file

@ -0,0 +1,54 @@
import createGraph from 'ngraph.graph';
import miserables from 'miserables';
import generate from 'ngraph.generators';
let cache = simpleCache();
export default function loadGraph(name) {
if (name === 'Miserables') return Promise.resolve(miserables);
if (name === 'Binary') return Promise.resolve(generate.balancedBinTree(10));
let mtxObject = cache.get(name);
if (mtxObject) return Promise.resolve(renderGraph(mtxObject.links, mtxObject.recordsPerEdge));
return fetch(`https://s3.amazonaws.com/yasiv_uf/out/${name}/index.js`, {
mode: 'cors'
})
.then(x => x.json())
.then(mtxObject => {
cache.put(name, mtxObject);
return renderGraph(mtxObject.links, mtxObject.recordsPerEdge);
});
}
function renderGraph (edges, recordsPerEdge) {
let graph = createGraph();
for(var i = 0; i < edges.length - 1; i += recordsPerEdge) {
graph.addLink(edges[i], edges[i + 1]);
}
return graph
}
function simpleCache() {
var supported = 'localStorage' in window;
return {
get : function(key) {
if (!supported) { return null; }
var graphData = JSON.parse(window.localStorage.getItem(key));
if (!graphData || graphData.recordsPerEdge === undefined) {
// this is old cache. Invalidate it
return null;
}
return graphData;
},
put : function(key, value) {
if (!supported) { return false;}
try {
window.localStorage.setItem(key, JSON.stringify(value));
} catch(err) {
// TODO: make something clever than this in case of quata exceeded.
window.localStorage.clear();
}
}
};
}

View file

@ -0,0 +1,54 @@
/**
* Set of function that I find useful for explorations.
*/
/**
* Performs hermit interpolation of `x` between two edges
*/
export function smoothStep(edge0, edge1, x) {
let t = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
return t * t * (3.0 - 2.0 * t);
}
/**
* Clamp `x` to [min, max] range.
*/
export function clamp(x, min, max) {
return x < min ? min : x > max ? max : x;
}
/**
* Collects main statistical properties of a collection
*/
export function collectStatistics(array) {
if (array.length === 0) {
return {
min: undefined,
max: undefined,
avg: undefined,
sigma: undefined,
mod: undefined,
count: 0
}
}
let min = Infinity;
let max = -Infinity;
let sum = 0;
let counts = new Map();
array.forEach(x => {
if (x < min) min = x;
if (x > max) max = x;
sum += x;
counts.set(x, (counts.get(x) || 0) + 1)
});
let mod = Array.from(counts).sort((a, b) => b[1] - a[1])[0][0]
let avg = sum /= array.length;
let sigma = 0;
array.forEach(x => {
sigma += (x - avg) * (x - avg);
});
sigma = Math.sqrt(sigma / (array.length + 1));
let count = array.length;
return {min, max, avg, sigma, mod, count};
}

View file

@ -0,0 +1,13 @@
import Vue from 'vue'
import App from './App.vue'
import fileDrop from './lib/fileDrop.js';
import loadDroppedGraph from './lib/loadDroppedGraph.js';
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')
// When they drop a `.dot` file into the browser - let's load it.
fileDrop(document.body, loadDroppedGraph);

View file

@ -0,0 +1,3 @@
module.exports = {
publicPath: ''
}

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

374
VISUALIZACION/node_modules/ngraph.forcelayout/index.d.ts generated vendored Executable file
View file

@ -0,0 +1,374 @@
declare module "ngraph.forcelayout" {
import { Graph, NodeId, LinkId, Node, Link } from "ngraph.graph";
import { EventedType } from "ngraph.events";
export type ForceFunction = (iterationNumber: number) => void;
export interface Vector {
x: number;
y: number;
z?: number;
[coord: `c${number}`]: number
}
export interface Body {
isPinned: boolean;
pos: Vector;
force: Vector;
velocity: Vector;
mass: number;
springCount: number;
springLength: number;
reset(): void;
setPosition(x: number, y: number, z?: number, ...c: number[]): void;
}
export interface Spring {
from: Body;
to: Body;
length: number;
coefficient: number;
}
export interface QuadNode {
body: Body | null;
mass: number;
mass_x: number;
mass_y: number;
mass_z?: number;
[mass: `mass_c${number}`]: number | null;
[mass: `min_c${number}`]: number | null;
[mass: `max_c${number}`]: number | null;
[quad: `quad${number}`]: number | null;
}
export interface QuadTree {
insertBodies(bodies: Body[]): void;
getRoot(): QuadNode;
updateBodyForce(sourceBody: Body): void;
options(newOptions: { gravity: number; theta: number }): { gravity: number; theta: number };
}
export interface BoundingBox {
min_x: number;
max_x: number;
min_y: number;
max_y: number;
min_z?: number;
max_z?: number;
[min: `min_c${number}`]: number;
[max: `max_c${number}`]: number;
}
/**
* Settings for a PhysicsSimulator
*/
export interface PhysicsSettings {
/**
* Ideal length for links (springs in physical model).
*/
springLength: number;
/**
* Hook's law coefficient. 1 - solid spring.
*/
springCoefficient: number;
/**
* 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: number;
/**
* 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: number;
/**
* 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.
*/
dragCoefficient: number;
/**
* Default time step (dt) for forces integration
*/
timeStep: number;
/**
* Adaptive time step uses average spring length to compute actual time step:
* See: https://twitter.com/anvaka/status/1293067160755957760
*/
adaptiveTimeStepWeight: number;
/**
* This parameter defines number of dimensions of the space where simulation
* is performed.
*/
dimensions: number;
/**
* In debug mode more checks are performed, this will help you catch errors
* quickly, however for production build it is recommended to turn off this flag
* to speed up computation.
*/
debug: boolean;
}
/**
* Manages a simulation of physical forces acting on bodies and springs.
*/
export interface PhysicsSimulator {
/**
* 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: Body[];
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: Spring[];
/**
* Returns settings with which current simulator was initialized
*/
settings: PhysicsSettings;
/**
* Adds a new force to simulation
* @param forceName force identifier
* @param forceFunction the function to apply
*/
addForce(forceName: string, forceFunction: ForceFunction): void;
/**
* Removes a force from the simulation
* @param forceName force identifier
*/
removeForce(forceName: string): void;
/**
* Returns a map of all registered forces
*/
getForces(): Map<string, ForceFunction>;
/**
* Performs one step of force simulation.
*
* @returns true if system is considered stable; False otherwise.
*/
step(): boolean;
/**
* Adds body to the system
* @param body physical body
* @returns added body
*/
addBody(body: Body): Body;
/**
* Adds body to the system at given position
* @param pos position of a body
* @returns added body
*/
addBodyAt(pos: Vector): Body;
/**
* Removes body from the system
* @param body to remove
* @returns true if body found and removed. falsy otherwise;
*/
removeBody(body: Body): boolean;
/**
* Adds a spring to this simulation
* @param body1 first body
* @param body2 second body
* @param springLength Ideal length for links
* @param springCoefficient Hook's law coefficient. 1 - solid spring
* @returns a handle for a spring. If you want to later remove
* spring pass it to removeSpring() method.
*/
addSpring(body1: Body, body2: Body, springLength: number, springCoefficient: number): Spring;
/**
* Returns amount of movement performed on last step() call
*/
getTotalMovement(): number;
/**
* Removes spring from the system
* @param spring to remove. Spring is an object returned by addSpring
* @returns true if spring found and removed. falsy otherwise;
*/
removeSpring(spring: Spring): boolean;
getBestNewBodyPosition(neighbors: Body[]): Vector;
/**
* Returns bounding box which covers all bodies
*/
getBBox(): BoundingBox;
/**
* Returns bounding box which covers all bodies
*/
getBoundingBox(): BoundingBox;
/** @deprecated invalidateBBox() is deprecated, bounds always recomputed on `getBBox()` call */
invalidateBBox(): void;
/**
* Changes the gravity for the system
* @param value Coulomb's law coefficient
*/
gravity(value: number): number;
/**
* Changes the theta coeffitient for the system
* @param value Theta coefficient from Barnes Hut simulation
*/
theta(value: number): number;
// TODO: create types declaration file for ngraph.random
/**
* Returns pseudo-random number generator instance
*/
random: any;
}
/**
* Force based layout for a given graph.
*/
export interface Layout<T extends Graph> {
/**
* Performs one step of iterative layout algorithm
* @returns true if the system should be considered stable; False otherwise.
* The system is stable if no further call to `step()` can improve the layout.
*/
step(): boolean;
/**
* For a given `nodeId` returns position
* @param nodeId node identifier
*/
getNodePosition(nodeId: NodeId): Vector;
/**
* Sets position of a node to a given coordinates
* @param nodeId node identifier
* @param x position of a node
* @param y position of a node
* @param z position of node (only if applicable to body)
*/
setNodePosition(nodeId: NodeId, x: number, y: number, z?: number, ...c: number[]): void;
/**
* Gets Link position by link id
* @param linkId link identifier
* @returns from: {x, y} coordinates of link start
* @returns to: {x, y} coordinates of link end
*/
getLinkPosition(linkId: LinkId): { from: Vector; to: Vector };
/**
* @returns area required to fit in the graph. Object contains
* `x1`, `y1` - top left coordinates
* `x2`, `y2` - bottom right coordinates
*/
getGraphRect(): { x1: number; y1: number; x2: number; y2: number };
/**
* Iterates over each body in the layout simulator and performs a callback(body, nodeId)
* @param callbackfn the callback function
*/
forEachBody(callbackfn: (value: Body, key: NodeId, map: Map<NodeId, Body>) => void): void;
/**
* 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
* @param node the node to pin/unpin
* @param isPinned true to pin, false to unpin
*/
pinNode(node: Node, isPinned: boolean): void;
/**
* Checks whether given graph's node is currently pinned
* @param node the node to check
*/
isNodePinned(node: Node): boolean;
/**
* Request to release all resources
*/
dispose(): void;
/**
* Gets physical body for a given node id. If node is not found undefined
* value is returned.
* @param nodeId node identifier
*/
getBody(nodeId: NodeId): Body | undefined;
/**
* Gets spring for a given edge.
*
* @param linkId link identifer.
*/
getSpring(linkId: LinkId | Link): Spring;
/**
* Gets spring for a given edge.
*
* @param fromId node identifer - tail of the link
* @param toId head of the link - head of the link
*/
getSpring(fromId: NodeId, toId: NodeId): Spring | undefined;
/**
* Returns length of cumulative force vector. The closer this to zero - the more stable the system is
*/
getForceVectorLength(): number;
/**
* @readonly Gets current physics simulator
*/
readonly simulator: PhysicsSimulator;
/**
* Gets the graph that was used for layout
*/
graph: T;
/**
* Gets amount of movement performed during last step operation
*/
lastMove: number;
}
/**
* Creates force based layout for a given graph.
*
* @param graph which needs to be laid out
* @param 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.
*/
export default function createLayout<T extends Graph>(
graph: T,
physicsSettings?: Partial<PhysicsSettings>
): Layout<T> & EventedType;
}

399
VISUALIZACION/node_modules/ngraph.forcelayout/index.js generated vendored Executable file
View file

@ -0,0 +1,399 @@
module.exports = createLayout;
module.exports.simulator = require('./lib/createPhysicsSimulator');
var eventify = require('ngraph.events');
/**
* Creates force based layout for a given graph.
*
* @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.
*/
function createLayout(graph, physicsSettings) {
if (!graph) {
throw new Error('Graph structure cannot be undefined');
}
var createSimulator = (physicsSettings && physicsSettings.createSimulator) || require('./lib/createPhysicsSimulator');
var physicsSimulator = createSimulator(physicsSettings);
if (Array.isArray(physicsSettings)) throw new Error('Physics settings is expected to be an object');
var nodeMass = graph.version > 19 ? defaultSetNodeMass : defaultArrayNodeMass;
if (physicsSettings && typeof physicsSettings.nodeMass === 'function') {
nodeMass = physicsSettings.nodeMass;
}
var nodeBodies = new Map();
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 = {
/**
* Performs one step of iterative layout algorithm
*
* @returns {boolean} true if the system should be considered stable; False otherwise.
* The system is stable if no further call to `step()` can improve the layout.
*/
step: function() {
if (bodiesCount === 0) {
updateStableStatus(true);
return true;
}
var lastMove = physicsSimulator.step();
// Save the movement in case if someone wants to query it in the step
// callback.
api.lastMove = lastMove;
// Allow listeners to perform low-level actions after nodes are updated.
api.fire('step');
var ratio = lastMove/bodiesCount;
var isStableNow = ratio <= 0.01; // TODO: The number is somewhat arbitrary...
updateStableStatus(isStableNow);
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));
},
/**
* @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
};
}
},
/**
* @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();
},
/**
* Iterates over each body in the layout simulator and performs a callback(body, nodeId)
*/
forEachBody: forEachBody,
/*
* 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;
},
/**
* Checks whether given graph's node is currently pinned
*/
isNodePinned: function (node) {
return getInitializedBody(node.id).isPinned;
},
/**
* Request to release all resources
*/
dispose: function() {
graph.off('changed', onGraphChanged);
api.fire('disposed');
},
/**
* Gets physical body for a given node id. If node is not found undefined
* value is returned.
*/
getBody: getBody,
/**
* 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 treated as tail of the link (fromId)
*/
getSpring: getSpring,
/**
* Returns length of cumulative force vector. The closer this to zero - the more stable the system is
*/
getForceVectorLength: getForceVectorLength,
/**
* [Read only] Gets current physics simulator
*/
simulator: physicsSimulator,
/**
* Gets the graph that was used for layout
*/
graph: graph,
/**
* Gets amount of movement performed during last step operation
*/
lastMove: 0
};
eventify(api);
return api;
function updateStableStatus(isStableNow) {
if (wasStable !== isStableNow) {
wasStable = isStableNow;
onStableChanged(isStableNow);
}
}
function forEachBody(cb) {
nodeBodies.forEach(cb);
}
function getForceVectorLength() {
var fx = 0, fy = 0;
forEachBody(function(body) {
fx += Math.abs(body.force.x);
fy += Math.abs(body.force.y);
});
return Math.sqrt(fx * fx + fy * fy);
}
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;
}
return springs[linkId];
}
function getBody(nodeId) {
return nodeBodies.get(nodeId);
}
function listenToEvents() {
graph.on('changed', onGraphChanged);
}
function onStableChanged(isStable) {
api.fire('stable', isStable);
}
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();
}
function initPhysics() {
bodiesCount = 0;
graph.forEachNode(function (node) {
initBody(node.id);
bodiesCount += 1;
});
graph.forEachLink(initLink);
}
function initBody(nodeId) {
var body = nodeBodies.get(nodeId);
if (!body) {
var node = graph.getNode(nodeId);
if (!node) {
throw new Error('initBody() was called with unknown node id');
}
var pos = node.position;
if (!pos) {
var neighbors = getNeighborBodies(node);
pos = physicsSimulator.getBestNewBodyPosition(neighbors);
}
body = physicsSimulator.addBodyAt(pos);
body.id = nodeId;
nodeBodies.set(nodeId, body);
updateBodyMass(nodeId);
if (isNodeOriginallyPinned(node)) {
body.isPinned = true;
}
}
}
function releaseNode(node) {
var nodeId = node.id;
var body = nodeBodies.get(nodeId);
if (body) {
nodeBodies.delete(nodeId);
physicsSimulator.removeBody(body);
}
}
function initLink(link) {
updateBodyMass(link.fromId);
updateBodyMass(link.toId);
var fromBody = nodeBodies.get(link.fromId),
toBody = nodeBodies.get(link.toId),
spring = physicsSimulator.addSpring(fromBody, toBody, link.length);
springTransform(link, spring);
springs[link.id] = spring;
}
function releaseLink(link) {
var spring = springs[link.id];
if (spring) {
var from = graph.getNode(link.fromId),
to = graph.getNode(link.toId);
if (from) updateBodyMass(from.id);
if (to) updateBodyMass(to.id);
delete springs[link.id];
physicsSimulator.removeSpring(spring);
}
}
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.get(link.fromId) : nodeBodies.get(link.toId);
if (otherBody && otherBody.pos) {
neighbors.push(otherBody);
}
}
return neighbors;
}
function updateBodyMass(nodeId) {
var body = nodeBodies.get(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 marked
* 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.get(nodeId);
if (!body) {
initBody(nodeId);
body = nodeBodies.get(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 defaultArrayNodeMass(nodeId) {
// This function is for older versions of ngraph.graph.
var links = graph.getLinks(nodeId);
if (!links) return 1;
return 1 + links.length / 3.0;
}
function defaultSetNodeMass(nodeId) {
var links = graph.getLinks(nodeId);
if (!links) return 1;
return 1 + links.size / 3.0;
}
}
function noop() { }

369
VISUALIZACION/node_modules/ngraph.forcelayout/index.v43.d.ts generated vendored Executable file
View file

@ -0,0 +1,369 @@
declare module "ngraph.forcelayout" {
import { Graph, NodeId, LinkId, Node, Link } from "ngraph.graph";
import { EventedType } from "ngraph.events";
export type ForceFunction = (iterationNumber: number) => void;
export interface Vector {
x: number;
y: number;
z?: number;
[coord: string]: number | undefined;
}
export interface Body {
isPinned: boolean;
pos: Vector;
force: Vector;
velocity: Vector;
mass: number;
springCount: number;
springLength: number;
reset(): void;
setPosition(x: number, y: number, z?: number, ...c: number[]): void;
}
export interface Spring {
from: Body;
to: Body;
length: number;
coefficient: number;
}
export interface QuadNode {
body: Body | null;
mass: number;
mass_x: number;
mass_y: number;
mass_z?: number;
}
export interface QuadTree {
insertBodies(bodies: Body[]): void;
getRoot(): QuadNode & Record<string, number | null>;
updateBodyForce(sourceBody: Body): void;
options(newOptions: { gravity: number; theta: number }): { gravity: number; theta: number };
}
export interface BoundingBox {
min_x: number;
max_x: number;
min_y: number;
max_y: number;
min_z?: number;
max_z?: number;
[min_max: string]: number | undefined;
}
/**
* Settings for a PhysicsSimulator
*/
export interface PhysicsSettings {
/**
* Ideal length for links (springs in physical model).
*/
springLength: number;
/**
* Hook's law coefficient. 1 - solid spring.
*/
springCoefficient: number;
/**
* 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: number;
/**
* 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: number;
/**
* 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.
*/
dragCoefficient: number;
/**
* Default time step (dt) for forces integration
*/
timeStep: number;
/**
* Adaptive time step uses average spring length to compute actual time step:
* See: https://twitter.com/anvaka/status/1293067160755957760
*/
adaptiveTimeStepWeight: number;
/**
* This parameter defines number of dimensions of the space where simulation
* is performed.
*/
dimensions: number;
/**
* In debug mode more checks are performed, this will help you catch errors
* quickly, however for production build it is recommended to turn off this flag
* to speed up computation.
*/
debug: boolean;
}
/**
* Manages a simulation of physical forces acting on bodies and springs.
*/
export interface PhysicsSimulator {
/**
* 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: Body[];
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: Spring[];
/**
* Returns settings with which current simulator was initialized
*/
settings: PhysicsSettings;
/**
* Adds a new force to simulation
* @param forceName force identifier
* @param forceFunction the function to apply
*/
addForce(forceName: string, forceFunction: ForceFunction): void;
/**
* Removes a force from the simulation
* @param forceName force identifier
*/
removeForce(forceName: string): void;
/**
* Returns a map of all registered forces
*/
getForces(): Map<string, ForceFunction>;
/**
* Performs one step of force simulation.
*
* @returns true if system is considered stable; False otherwise.
*/
step(): boolean;
/**
* Adds body to the system
* @param body physical body
* @returns added body
*/
addBody(body: Body): Body;
/**
* Adds body to the system at given position
* @param pos position of a body
* @returns added body
*/
addBodyAt(pos: Vector): Body;
/**
* Removes body from the system
* @param body to remove
* @returns true if body found and removed. falsy otherwise;
*/
removeBody(body: Body): boolean;
/**
* Adds a spring to this simulation
* @param body1 first body
* @param body2 second body
* @param springLength Ideal length for links
* @param springCoefficient Hook's law coefficient. 1 - solid spring
* @returns a handle for a spring. If you want to later remove
* spring pass it to removeSpring() method.
*/
addSpring(body1: Body, body2: Body, springLength: number, springCoefficient: number): Spring;
/**
* Returns amount of movement performed on last step() call
*/
getTotalMovement(): number;
/**
* Removes spring from the system
* @param spring to remove. Spring is an object returned by addSpring
* @returns true if spring found and removed. falsy otherwise;
*/
removeSpring(spring: Spring): boolean;
getBestNewBodyPosition(neighbors: Body[]): Vector;
/**
* Returns bounding box which covers all bodies
*/
getBBox(): BoundingBox;
/**
* Returns bounding box which covers all bodies
*/
getBoundingBox(): BoundingBox;
/** @deprecated invalidateBBox() is deprecated, bounds always recomputed on `getBBox()` call */
invalidateBBox(): void;
/**
* Changes the gravity for the system
* @param value Coulomb's law coefficient
*/
gravity(value: number): number;
/**
* Changes the theta coeffitient for the system
* @param value Theta coefficient from Barnes Hut simulation
*/
theta(value: number): number;
// TODO: create types declaration file for ngraph.random
/**
* Returns pseudo-random number generator instance
*/
random: any;
}
/**
* Force based layout for a given graph.
*/
export interface Layout<T extends Graph> {
/**
* Performs one step of iterative layout algorithm
* @returns true if the system should be considered stable; False otherwise.
* The system is stable if no further call to `step()` can improve the layout.
*/
step(): boolean;
/**
* For a given `nodeId` returns position
* @param nodeId node identifier
*/
getNodePosition(nodeId: NodeId): Vector;
/**
* Sets position of a node to a given coordinates
* @param nodeId node identifier
* @param x position of a node
* @param y position of a node
* @param z position of node (only if applicable to body)
*/
setNodePosition(nodeId: NodeId, x: number, y: number, z?: number, ...c: number[]): void;
/**
* Gets Link position by link id
* @param linkId link identifier
* @returns from: {x, y} coordinates of link start
* @returns to: {x, y} coordinates of link end
*/
getLinkPosition(linkId: LinkId): { from: Vector; to: Vector };
/**
* @returns area required to fit in the graph. Object contains
* `x1`, `y1` - top left coordinates
* `x2`, `y2` - bottom right coordinates
*/
getGraphRect(): { x1: number; y1: number; x2: number; y2: number };
/**
* Iterates over each body in the layout simulator and performs a callback(body, nodeId)
* @param callbackfn the callback function
*/
forEachBody(callbackfn: (value: Body, key: NodeId, map: Map<NodeId, Body>) => void): void;
/**
* 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
* @param node the node to pin/unpin
* @param isPinned true to pin, false to unpin
*/
pinNode(node: Node, isPinned: boolean): void;
/**
* Checks whether given graph's node is currently pinned
* @param node the node to check
*/
isNodePinned(node: Node): boolean;
/**
* Request to release all resources
*/
dispose(): void;
/**
* Gets physical body for a given node id. If node is not found undefined
* value is returned.
* @param nodeId node identifier
*/
getBody(nodeId: NodeId): Body | undefined;
/**
* Gets spring for a given edge.
*
* @param linkId link identifer.
*/
getSpring(linkId: LinkId | Link): Spring;
/**
* Gets spring for a given edge.
*
* @param fromId node identifer - tail of the link
* @param toId head of the link - head of the link
*/
getSpring(fromId: NodeId, toId: NodeId): Spring | undefined;
/**
* Returns length of cumulative force vector. The closer this to zero - the more stable the system is
*/
getForceVectorLength(): number;
/**
* @readonly Gets current physics simulator
*/
readonly simulator: PhysicsSimulator;
/**
* Gets the graph that was used for layout
*/
graph: T;
/**
* Gets amount of movement performed during last step operation
*/
lastMove: number;
}
/**
* Creates force based layout for a given graph.
*
* @param graph which needs to be laid out
* @param 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.
*/
export default function createLayout<T extends Graph>(
graph: T,
physicsSettings?: Partial<PhysicsSettings>
): Layout<T> & EventedType;
}

View file

@ -0,0 +1,23 @@
var through = require('through2');
module.exports = function (file) {
return through(function (buf, enc, next) {
let originalContent = buf.toString('utf8');
let dimensions = 2; // change this if you need different number of dimensions
if (file.match(/codeGenerators\/generate/)) {
let content = require(file);
let matches = originalContent.match(/^\/\/ InlineTransform: (.+)$/gm);
let additionalTransform = matches ? matches.map(name => {
let f = name.substr('// InlineTransform: '.length);
return content[f](dimensions);
}).join('\n') : '';
let exportCodeMatch = originalContent.match(/^\/\/ InlineTransformExport: (.+)$/m);
let codeExport = exportCodeMatch ? exportCodeMatch[1] :
`module.exports = function() { return ${content(dimensions).toString()} }`;
this.push(`${additionalTransform}\n${codeExport}`);
} else {
this.push(originalContent);
}
next();
});
};

View file

@ -0,0 +1,3 @@
const generateBoundsFunction = require('./codeGenerators/generateBounds');
module.exports = generateBoundsFunction(2);

View file

@ -0,0 +1,19 @@
const getVariableName = require('./getVariableName');
module.exports = function createPatternBuilder(dimension) {
return pattern;
function pattern(template, config) {
let indent = (config && config.indent) || 0;
let join = (config && config.join !== undefined) ? config.join : '\n';
let indentString = Array(indent + 1).join(' ');
let buffer = [];
for (let i = 0; i < dimension; ++i) {
let variableName = getVariableName(i);
let prefix = (i === 0) ? '' : indentString;
buffer.push(prefix + template.replace(/{var}/g, variableName));
}
return buffer.join(join);
}
};

View file

@ -0,0 +1,71 @@
module.exports = generateBoundsFunction;
module.exports.generateFunctionBody = generateBoundsFunctionBody;
const createPatternBuilder = require('./createPatternBuilder');
function generateBoundsFunction(dimension) {
let code = generateBoundsFunctionBody(dimension);
return new Function('bodies', 'settings', 'random', code);
}
function generateBoundsFunctionBody(dimension) {
let pattern = createPatternBuilder(dimension);
let code = `
var boundingBox = {
${pattern('min_{var}: 0, max_{var}: 0,', {indent: 4})}
};
return {
box: boundingBox,
update: updateBoundingBox,
reset: resetBoundingBox,
getBestNewPosition: function (neighbors) {
var ${pattern('base_{var} = 0', {join: ', '})};
if (neighbors.length) {
for (var i = 0; i < neighbors.length; ++i) {
let neighborPos = neighbors[i].pos;
${pattern('base_{var} += neighborPos.{var};', {indent: 10})}
}
${pattern('base_{var} /= neighbors.length;', {indent: 8})}
} else {
${pattern('base_{var} = (boundingBox.min_{var} + boundingBox.max_{var}) / 2;', {indent: 8})}
}
var springLength = settings.springLength;
return {
${pattern('{var}: base_{var} + (random.nextDouble() - 0.5) * springLength,', {indent: 8})}
};
}
};
function updateBoundingBox() {
var i = bodies.length;
if (i === 0) return; // No bodies - no borders.
${pattern('var max_{var} = -Infinity;', {indent: 4})}
${pattern('var min_{var} = Infinity;', {indent: 4})}
while(i--) {
// this is O(n), it could be done faster with quadtree, if we check the root node bounds
var bodyPos = bodies[i].pos;
${pattern('if (bodyPos.{var} < min_{var}) min_{var} = bodyPos.{var};', {indent: 6})}
${pattern('if (bodyPos.{var} > max_{var}) max_{var} = bodyPos.{var};', {indent: 6})}
}
${pattern('boundingBox.min_{var} = min_{var};', {indent: 4})}
${pattern('boundingBox.max_{var} = max_{var};', {indent: 4})}
}
function resetBoundingBox() {
${pattern('boundingBox.min_{var} = boundingBox.max_{var} = 0;', {indent: 4})}
}
`;
return code;
}

View file

@ -0,0 +1,85 @@
const createPatternBuilder = require('./createPatternBuilder');
module.exports = generateCreateBodyFunction;
module.exports.generateCreateBodyFunctionBody = generateCreateBodyFunctionBody;
// InlineTransform: getVectorCode
module.exports.getVectorCode = getVectorCode;
// InlineTransform: getBodyCode
module.exports.getBodyCode = getBodyCode;
// InlineTransformExport: module.exports = function() { return Body; }
function generateCreateBodyFunction(dimension, debugSetters) {
let code = generateCreateBodyFunctionBody(dimension, debugSetters);
let {Body} = (new Function(code))();
return Body;
}
function generateCreateBodyFunctionBody(dimension, debugSetters) {
let code = `
${getVectorCode(dimension, debugSetters)}
${getBodyCode(dimension, debugSetters)}
return {Body: Body, Vector: Vector};
`;
return code;
}
function getBodyCode(dimension) {
let pattern = createPatternBuilder(dimension);
let variableList = pattern('{var}', {join: ', '});
return `
function Body(${variableList}) {
this.isPinned = false;
this.pos = new Vector(${variableList});
this.force = new Vector();
this.velocity = new Vector();
this.mass = 1;
this.springCount = 0;
this.springLength = 0;
}
Body.prototype.reset = function() {
this.force.reset();
this.springCount = 0;
this.springLength = 0;
}
Body.prototype.setPosition = function (${variableList}) {
${pattern('this.pos.{var} = {var} || 0;', {indent: 2})}
};`;
}
function getVectorCode(dimension, debugSetters) {
let pattern = createPatternBuilder(dimension);
let setters = '';
if (debugSetters) {
setters = `${pattern("\n\
var v{var};\n\
Object.defineProperty(this, '{var}', {\n\
set: function(v) { \n\
if (!Number.isFinite(v)) throw new Error('Cannot set non-numbers to {var}');\n\
v{var} = v; \n\
},\n\
get: function() { return v{var}; }\n\
});")}`;
}
let variableList = pattern('{var}', {join: ', '});
return `function Vector(${variableList}) {
${setters}
if (typeof arguments[0] === 'object') {
// could be another vector
let v = arguments[0];
${pattern('if (!Number.isFinite(v.{var})) throw new Error("Expected value is not a finite number at Vector constructor ({var})");', {indent: 4})}
${pattern('this.{var} = v.{var};', {indent: 4})}
} else {
${pattern('this.{var} = typeof {var} === "number" ? {var} : 0;', {indent: 4})}
}
}
Vector.prototype.reset = function () {
${pattern('this.{var} = ', {join: ''})}0;
};`;
}

View file

@ -0,0 +1,23 @@
const createPatternBuilder = require('./createPatternBuilder');
module.exports = generateCreateDragForceFunction;
module.exports.generateCreateDragForceFunctionBody = generateCreateDragForceFunctionBody;
function generateCreateDragForceFunction(dimension) {
let code = generateCreateDragForceFunctionBody(dimension);
return new Function('options', code);
}
function generateCreateDragForceFunctionBody(dimension) {
let pattern = createPatternBuilder(dimension);
let code = `
if (!Number.isFinite(options.dragCoefficient)) throw new Error('dragCoefficient is not a finite number');
return {
update: function(body) {
${pattern('body.force.{var} -= options.dragCoefficient * body.velocity.{var};', {indent: 6})}
}
};
`;
return code;
}

View file

@ -0,0 +1,47 @@
const createPatternBuilder = require('./createPatternBuilder');
module.exports = generateCreateSpringForceFunction;
module.exports.generateCreateSpringForceFunctionBody = generateCreateSpringForceFunctionBody;
function generateCreateSpringForceFunction(dimension) {
let code = generateCreateSpringForceFunctionBody(dimension);
return new Function('options', 'random', code);
}
function generateCreateSpringForceFunctionBody(dimension) {
let pattern = createPatternBuilder(dimension);
let code = `
if (!Number.isFinite(options.springCoefficient)) throw new Error('Spring coefficient is not a number');
if (!Number.isFinite(options.springLength)) throw new Error('Spring length is not a number');
return {
/**
* Updates forces acting on a spring
*/
update: function (spring) {
var body1 = spring.from;
var body2 = spring.to;
var length = spring.length < 0 ? options.springLength : spring.length;
${pattern('var d{var} = body2.pos.{var} - body1.pos.{var};', {indent: 6})}
var r = Math.sqrt(${pattern('d{var} * d{var}', {join: ' + '})});
if (r === 0) {
${pattern('d{var} = (random.nextDouble() - 0.5) / 50;', {indent: 8})}
r = Math.sqrt(${pattern('d{var} * d{var}', {join: ' + '})});
}
var d = r - length;
var coefficient = ((spring.coefficient > 0) ? spring.coefficient : options.springCoefficient) * d / r;
${pattern('body1.force.{var} += coefficient * d{var}', {indent: 6})};
body1.springCount += 1;
body1.springLength += r;
${pattern('body2.force.{var} -= coefficient * d{var}', {indent: 6})};
body2.springCount += 1;
body2.springLength += r;
}
};
`;
return code;
}

View file

@ -0,0 +1,49 @@
const createPatternBuilder = require('./createPatternBuilder');
module.exports = generateIntegratorFunction;
module.exports.generateIntegratorFunctionBody = generateIntegratorFunctionBody;
function generateIntegratorFunction(dimension) {
let code = generateIntegratorFunctionBody(dimension);
return new Function('bodies', 'timeStep', 'adaptiveTimeStepWeight', code);
}
function generateIntegratorFunctionBody(dimension) {
let pattern = createPatternBuilder(dimension);
let code = `
var length = bodies.length;
if (length === 0) return 0;
${pattern('var d{var} = 0, t{var} = 0;', {indent: 2})}
for (var i = 0; i < length; ++i) {
var body = bodies[i];
if (body.isPinned) continue;
if (adaptiveTimeStepWeight && body.springCount) {
timeStep = (adaptiveTimeStepWeight * body.springLength/body.springCount);
}
var coeff = timeStep / body.mass;
${pattern('body.velocity.{var} += coeff * body.force.{var};', {indent: 4})}
${pattern('var v{var} = body.velocity.{var};', {indent: 4})}
var v = Math.sqrt(${pattern('v{var} * v{var}', {join: ' + '})});
if (v > 1) {
// We normalize it so that we move within timeStep range.
// for the case when v <= 1 - we let velocity to fade out.
${pattern('body.velocity.{var} = v{var} / v;', {indent: 6})}
}
${pattern('d{var} = timeStep * body.velocity.{var};', {indent: 4})}
${pattern('body.pos.{var} += d{var};', {indent: 4})}
${pattern('t{var} += Math.abs(d{var});', {indent: 4})}
}
return (${pattern('t{var} * t{var}', {join: ' + '})})/length;
`;
return code;
}

View file

@ -0,0 +1,461 @@
const createPatternBuilder = require('./createPatternBuilder');
const getVariableName = require('./getVariableName');
module.exports = generateQuadTreeFunction;
module.exports.generateQuadTreeFunctionBody = generateQuadTreeFunctionBody;
// These exports are for InlineTransform tool.
// InlineTransform: getInsertStackCode
module.exports.getInsertStackCode = getInsertStackCode;
// InlineTransform: getQuadNodeCode
module.exports.getQuadNodeCode = getQuadNodeCode;
// InlineTransform: isSamePosition
module.exports.isSamePosition = isSamePosition;
// InlineTransform: getChildBodyCode
module.exports.getChildBodyCode = getChildBodyCode;
// InlineTransform: setChildBodyCode
module.exports.setChildBodyCode = setChildBodyCode;
function generateQuadTreeFunction(dimension) {
let code = generateQuadTreeFunctionBody(dimension);
return (new Function(code))();
}
function generateQuadTreeFunctionBody(dimension) {
let pattern = createPatternBuilder(dimension);
let quadCount = Math.pow(2, dimension);
let code = `
${getInsertStackCode()}
${getQuadNodeCode(dimension)}
${isSamePosition(dimension)}
${getChildBodyCode(dimension)}
${setChildBodyCode(dimension)}
function createQuadTree(options, random) {
options = options || {};
options.gravity = typeof options.gravity === 'number' ? options.gravity : -1;
options.theta = typeof options.theta === 'number' ? options.theta : 0.8;
var gravity = options.gravity;
var updateQueue = [];
var insertStack = new InsertStack();
var theta = options.theta;
var nodesCache = [];
var currentInCache = 0;
var root = newNode();
return {
insertBodies: insertBodies,
/**
* Gets root node if it is 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 this;
}
return {
gravity: gravity,
theta: theta
};
}
};
function newNode() {
// To avoid pressure on GC we reuse nodes.
var node = nodesCache[currentInCache];
if (node) {
${assignQuads(' node.')}
node.body = null;
node.mass = ${pattern('node.mass_{var} = ', {join: ''})}0;
${pattern('node.min_{var} = node.max_{var} = ', {join: ''})}0;
} else {
node = new QuadNode();
nodesCache[currentInCache] = node;
}
++currentInCache;
return node;
}
function update(sourceBody) {
var queue = updateQueue;
var v;
${pattern('var d{var};', {indent: 4})}
var r;
${pattern('var f{var} = 0;', {indent: 4})}
var queueLength = 1;
var shiftIdx = 0;
var pushIdx = 1;
queue[0] = root;
while (queueLength) {
var node = queue[shiftIdx];
var 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.
${pattern('d{var} = body.pos.{var} - sourceBody.pos.{var};', {indent: 8})}
r = Math.sqrt(${pattern('d{var} * d{var}', {join: ' + '})});
if (r === 0) {
// Poor man's protection against zero distance.
${pattern('d{var} = (random.nextDouble() - 0.5) / 50;', {indent: 10})}
r = Math.sqrt(${pattern('d{var} * d{var}', {join: ' + '})});
}
// This is standard gravitation 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);
${pattern('f{var} += v * d{var};', {indent: 8})}
} 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
${pattern('d{var} = node.mass_{var} / node.mass - sourceBody.pos.{var};', {indent: 8})}
r = Math.sqrt(${pattern('d{var} * d{var}', {join: ' + '})});
if (r === 0) {
// Sorry about code duplication. I don't want to create many functions
// right away. Just want to see performance first.
${pattern('d{var} = (random.nextDouble() - 0.5) / 50;', {indent: 10})}
r = Math.sqrt(${pattern('d{var} * d{var}', {join: ' + '})});
}
// 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.max_${getVariableName(0)} - node.min_${getVariableName(0)}) / r < theta) {
// in the if statement above we consider node's width only
// because the region was made into square during tree creation.
// Thus there is no difference between using width or height.
v = gravity * node.mass * sourceBody.mass / (r * r * r);
${pattern('f{var} += v * d{var};', {indent: 10})}
} else {
// Otherwise, run the procedure recursively on each of the current node's children.
// I intentionally unfolded this loop, to save several CPU cycles.
${runRecursiveOnChildren()}
}
}
}
${pattern('sourceBody.force.{var} += f{var};', {indent: 4})}
}
function insertBodies(bodies) {
${pattern('var {var}min = Number.MAX_VALUE;', {indent: 4})}
${pattern('var {var}max = Number.MIN_VALUE;', {indent: 4})}
var i = bodies.length;
// To reduce quad tree depth we are looking for exact bounding box of all particles.
while (i--) {
var pos = bodies[i].pos;
${pattern('if (pos.{var} < {var}min) {var}min = pos.{var};', {indent: 6})}
${pattern('if (pos.{var} > {var}max) {var}max = pos.{var};', {indent: 6})}
}
// Makes the bounds square.
var maxSideLength = -Infinity;
${pattern('if ({var}max - {var}min > maxSideLength) maxSideLength = {var}max - {var}min ;', {indent: 4})}
currentInCache = 0;
root = newNode();
${pattern('root.min_{var} = {var}min;', {indent: 4})}
${pattern('root.max_{var} = {var}min + maxSideLength;', {indent: 4})}
i = bodies.length - 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();
var node = stackItem.node;
var body = stackItem.body;
if (!node.body) {
// This is internal node. Update the total mass of the node and center-of-mass.
${pattern('var {var} = body.pos.{var};', {indent: 8})}
node.mass += body.mass;
${pattern('node.mass_{var} += body.mass * {var};', {indent: 8})}
// 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.
${pattern('var min_{var} = node.min_{var};', {indent: 8})}
${pattern('var max_{var} = (min_{var} + node.max_{var}) / 2;', {indent: 8})}
${assignInsertionQuadIndex(8)}
var child = getChild(node, quadIdx);
if (!child) {
// The node is internal but this quadrant is not taken. Add
// subnode to it.
child = newNode();
${pattern('child.min_{var} = min_{var};', {indent: 10})}
${pattern('child.max_{var} = max_{var};', {indent: 10})}
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();
${pattern('var d{var} = (node.max_{var} - node.min_{var}) * offset;', {indent: 12})}
${pattern('oldBody.pos.{var} = node.min_{var} + d{var};', {indent: 12})}
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);
}
}
}
}
return createQuadTree;
`;
return code;
function assignInsertionQuadIndex(indentCount) {
let insertionCode = [];
let indent = Array(indentCount + 1).join(' ');
for (let i = 0; i < dimension; ++i) {
insertionCode.push(indent + `if (${getVariableName(i)} > max_${getVariableName(i)}) {`);
insertionCode.push(indent + ` quadIdx = quadIdx + ${Math.pow(2, i)};`);
insertionCode.push(indent + ` min_${getVariableName(i)} = max_${getVariableName(i)};`);
insertionCode.push(indent + ` max_${getVariableName(i)} = node.max_${getVariableName(i)};`);
insertionCode.push(indent + `}`);
}
return insertionCode.join('\n');
// if (x > max_x) { // somewhere in the eastern part.
// quadIdx = quadIdx + 1;
// left = right;
// right = node.right;
// }
}
function runRecursiveOnChildren() {
let indent = Array(11).join(' ');
let recursiveCode = [];
for (let i = 0; i < quadCount; ++i) {
recursiveCode.push(indent + `if (node.quad${i}) {`);
recursiveCode.push(indent + ` queue[pushIdx] = node.quad${i};`);
recursiveCode.push(indent + ` queueLength += 1;`);
recursiveCode.push(indent + ` pushIdx += 1;`);
recursiveCode.push(indent + `}`);
}
return recursiveCode.join('\n');
// if (node.quad0) {
// queue[pushIdx] = node.quad0;
// queueLength += 1;
// pushIdx += 1;
// }
}
function assignQuads(indent) {
// this.quad0 = null;
// this.quad1 = null;
// this.quad2 = null;
// this.quad3 = null;
let quads = [];
for (let i = 0; i < quadCount; ++i) {
quads.push(`${indent}quad${i} = null;`);
}
return quads.join('\n');
}
}
function isSamePosition(dimension) {
let pattern = createPatternBuilder(dimension);
return `
function isSamePosition(point1, point2) {
${pattern('var d{var} = Math.abs(point1.{var} - point2.{var});', {indent: 2})}
return ${pattern('d{var} < 1e-8', {join: ' && '})};
}
`;
}
function setChildBodyCode(dimension) {
var quadCount = Math.pow(2, dimension);
return `
function setChild(node, idx, child) {
${setChildBody()}
}`;
function setChildBody() {
let childBody = [];
for (let i = 0; i < quadCount; ++i) {
let prefix = (i === 0) ? ' ' : ' else ';
childBody.push(`${prefix}if (idx === ${i}) node.quad${i} = child;`);
}
return childBody.join('\n');
// 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;
}
}
function getChildBodyCode(dimension) {
return `function getChild(node, idx) {
${getChildBody()}
return null;
}`;
function getChildBody() {
let childBody = [];
let quadCount = Math.pow(2, dimension);
for (let i = 0; i < quadCount; ++i) {
childBody.push(` if (idx === ${i}) return node.quad${i};`);
}
return childBody.join('\n');
// if (idx === 0) return node.quad0;
// if (idx === 1) return node.quad1;
// if (idx === 2) return node.quad2;
// if (idx === 3) return node.quad3;
}
}
function getQuadNodeCode(dimension) {
let pattern = createPatternBuilder(dimension);
let quadCount = Math.pow(2, dimension);
var quadNodeCode = `
function QuadNode() {
// body stored inside this node. In quad tree only leaf nodes (by construction)
// contain bodies:
this.body = null;
// Child nodes are stored in quads. Each quad is presented by number:
// 0 | 1
// -----
// 2 | 3
${assignQuads(' this.')}
// Total mass of current node
this.mass = 0;
// Center of mass coordinates
${pattern('this.mass_{var} = 0;', {indent: 2})}
// bounding box coordinates
${pattern('this.min_{var} = 0;', {indent: 2})}
${pattern('this.max_{var} = 0;', {indent: 2})}
}
`;
return quadNodeCode;
function assignQuads(indent) {
// this.quad0 = null;
// this.quad1 = null;
// this.quad2 = null;
// this.quad3 = null;
let quads = [];
for (let i = 0; i < quadCount; ++i) {
quads.push(`${indent}quad${i} = null;`);
}
return quads.join('\n');
}
}
function getInsertStackCode() {
return `
/**
* Our implementation 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 pressure: 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;
}
};
function InsertStackElement(node, body) {
this.node = node; // QuadTree node
this.body = body; // physical body which needs to be inserted to node
}
`;
}

View file

@ -0,0 +1,6 @@
module.exports = function getVariableName(index) {
if (index === 0) return 'x';
if (index === 1) return 'y';
if (index === 2) return 'z';
return 'c' + (index + 1);
};

View file

@ -0,0 +1,399 @@
/**
* Manages a simulation of physical forces acting on bodies and springs.
*/
module.exports = createPhysicsSimulator;
var generateCreateBodyFunction = require('./codeGenerators/generateCreateBody');
var generateQuadTreeFunction = require('./codeGenerators/generateQuadTree');
var generateBoundsFunction = require('./codeGenerators/generateBounds');
var generateCreateDragForceFunction = require('./codeGenerators/generateCreateDragForce');
var generateCreateSpringForceFunction = require('./codeGenerators/generateCreateSpringForce');
var generateIntegratorFunction = require('./codeGenerators/generateIntegrator');
var dimensionalCache = {};
function createPhysicsSimulator(settings) {
var Spring = require('./spring');
var merge = require('ngraph.merge');
var eventify = require('ngraph.events');
if (settings) {
// Check for names from older versions of the layout
if (settings.springCoeff !== undefined) throw new Error('springCoeff was renamed to springCoefficient');
if (settings.dragCoeff !== undefined) throw new Error('dragCoeff was renamed to dragCoefficient');
}
settings = merge(settings, {
/**
* Ideal length for links (springs in physical model).
*/
springLength: 10,
/**
* Hook's law coefficient. 1 - solid spring.
*/
springCoefficient: 0.8,
/**
* 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: -12,
/**
* 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.
*/
dragCoefficient: 0.9, // TODO: Need to rename this to something better. E.g. `dragCoefficient`
/**
* Default time step (dt) for forces integration
*/
timeStep : 0.5,
/**
* Adaptive time step uses average spring length to compute actual time step:
* See: https://twitter.com/anvaka/status/1293067160755957760
*/
adaptiveTimeStepWeight: 0,
/**
* This parameter defines number of dimensions of the space where simulation
* is performed.
*/
dimensions: 2,
/**
* In debug mode more checks are performed, this will help you catch errors
* quickly, however for production build it is recommended to turn off this flag
* to speed up computation.
*/
debug: false
});
var factory = dimensionalCache[settings.dimensions];
if (!factory) {
var dimensions = settings.dimensions;
factory = {
Body: generateCreateBodyFunction(dimensions, settings.debug),
createQuadTree: generateQuadTreeFunction(dimensions),
createBounds: generateBoundsFunction(dimensions),
createDragForce: generateCreateDragForceFunction(dimensions),
createSpringForce: generateCreateSpringForceFunction(dimensions),
integrate: generateIntegratorFunction(dimensions),
};
dimensionalCache[dimensions] = factory;
}
var Body = factory.Body;
var createQuadTree = factory.createQuadTree;
var createBounds = factory.createBounds;
var createDragForce = factory.createDragForce;
var createSpringForce = factory.createSpringForce;
var integrate = factory.integrate;
var createBody = pos => new Body(pos);
var random = require('ngraph.random').random(42);
var bodies = []; // Bodies in this simulation.
var springs = []; // Springs in this simulation.
var quadTree = createQuadTree(settings, random);
var bounds = createBounds(bodies, settings, random);
var springForce = createSpringForce(settings, random);
var dragForce = createDragForce(settings);
var totalMovement = 0; // how much movement we made on last step
var forces = [];
var forceMap = new Map();
var iterationNumber = 0;
addForce('nbody', nbodyForce);
addForce('spring', updateSpringForce);
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,
/**
* Adds a new force to simulation
*/
addForce: addForce,
/**
* Removes a force from the simulation.
*/
removeForce: removeForce,
/**
* Returns a map of all registered forces.
*/
getForces: getForces,
/**
* Performs one step of force simulation.
*
* @returns {boolean} true if system is considered stable; False otherwise.
*/
step: function () {
for (var i = 0; i < forces.length; ++i) {
forces[i](iterationNumber);
}
var movement = integrate(bodies, settings.timeStep, settings.adaptiveTimeStepWeight);
iterationNumber += 1;
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, 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);
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: getBoundingBox,
getBoundingBox: getBoundingBox,
invalidateBBox: function () {
console.warn('invalidateBBox() is deprecated, bounds always recomputed on `getBBox()` call');
},
// TODO: Move the force specific stuff to force
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;
}
},
/**
* Returns pseudo-random number generator instance.
*/
random: random
};
// allow settings modification via public API:
expose(settings, publicApi);
eventify(publicApi);
return publicApi;
function getBoundingBox() {
bounds.update();
return bounds.box;
}
function addForce(forceName, forceFunction) {
if (forceMap.has(forceName)) throw new Error('Force ' + forceName + ' is already added');
forceMap.set(forceName, forceFunction);
forces.push(forceFunction);
}
function removeForce(forceName) {
var forceIndex = forces.indexOf(forceMap.get(forceName));
if (forceIndex < 0) return;
forces.splice(forceIndex, 1);
forceMap.delete(forceName);
}
function getForces() {
// TODO: Should I trust them or clone the forces?
return forceMap;
}
function nbodyForce(/* iterationUmber */) {
if (bodies.length === 0) return;
quadTree.insertBodies(bodies);
var i = bodies.length;
while (i--) {
var body = bodies[i];
if (!body.isPinned) {
body.reset();
quadTree.updateBodyForce(body);
dragForce.update(body);
}
}
}
function updateSpringForce() {
var i = springs.length;
while (i--) {
springForce.update(springs[i]);
}
}
}
function expose(settings, target) {
for (var key in settings) {
augment(settings, target, key);
}
}
function augment(source, target, key) {
if (!source.hasOwnProperty(key)) return;
if (typeof target[key] === 'function') {
// this accessor is already defined. Ignore it
return;
}
var sourceIsNumber = Number.isFinite(source[key]);
if (sourceIsNumber) {
target[key] = function (value) {
if (value !== undefined) {
if (!Number.isFinite(value)) throw new Error('Value of ' + key + ' should be a valid number.');
source[key] = value;
return target;
}
return source[key];
};
} else {
target[key] = function (value) {
if (value !== undefined) {
source[key] = value;
return target;
}
return source[key];
};
}
}

45
VISUALIZACION/node_modules/ngraph.forcelayout/lib/kdForce.js generated vendored Executable file
View file

@ -0,0 +1,45 @@
/**
* This is not used anywhere, but it is a good exploration into kd-tree based
* simulation.
*/
module.exports = createKDForce;
function createKDForce(bodies, settings) {
var KDBush = require('kdbush').default;
var random = require('ngraph.random').random(1984);
return kdForce;
function kdForce(iterationNumber) {
if (iterationNumber < 500) return;
var gravity = settings.gravity;
var points = new KDBush(bodies, p => p.pos.x0, p => p.pos.x1);
var i = bodies.length;
while (i--) {
var body = bodies[i];
body.reset();
var neighbors = points.within(body.pos.x0, body.pos.x1, settings.springLength);
var fx = 0, fy = 0;
for (var j = 0; j < neighbors.length; ++j) {
var other = bodies[neighbors[j]];
if (other === body) continue;
var dx = other.pos.x0 - body.pos.x0;
var dy = other.pos.x1 - body.pos.x1;
var 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);
}
var v = gravity * other.mass * body.mass / (r * r * r);
fx += v * dx;
fy += v * dy;
}
body.force.x0 = fx;
body.force.x1 = fy;
//dragForce.update(body);
}
}
}

12
VISUALIZACION/node_modules/ngraph.forcelayout/lib/spring.js generated vendored Executable file
View file

@ -0,0 +1,12 @@
module.exports = Spring;
/**
* Represents a physical spring. Spring connects two bodies, has rest length
* stiffness coefficient and optional weight
*/
function Spring(fromBody, toBody, length, springCoefficient) {
this.from = fromBody;
this.to = toBody;
this.length = length;
this.coefficient = springCoefficient;
}

51
VISUALIZACION/node_modules/ngraph.forcelayout/package.json generated vendored Executable file
View file

@ -0,0 +1,51 @@
{
"name": "ngraph.forcelayout",
"version": "3.3.1",
"description": "Force directed graph drawing layout",
"main": "index.js",
"jsdelivr": "dist/ngraph.forcelayout.min.js",
"unpkg": "dist/ngraph.forcelayout.min.js",
"types": "./index.d.ts",
"typesVersions": {
"<=4.3": {
"index.d.ts": [
"index.v43.d.ts"
]
}
},
"scripts": {
"test": "tap --branches=80 --lines=80 --statements=80 --functions=80 test/*.js",
"lint": "eslint .",
"perf": "npm version && node perf/test.js",
"build": "browserify index.js -s ngraphCreateLayout -o dist/ngraph.forcelayout.js && terser --compress -o dist/ngraph.forcelayout.min.js -- dist/ngraph.forcelayout.js",
"build2d": "browserify index.js -t ./inline-transform.js -s ngraphCreate2dLayout > dist/ngraph.forcelayout2d.js && terser --compress -o dist/ngraph.forcelayout2d.min.js -- dist/ngraph.forcelayout2d.js"
},
"repository": {
"type": "git",
"url": "https://github.com/anvaka/ngraph.forcelayout.git"
},
"keywords": [
"ngraph",
"ngraphjs"
],
"author": "Andrei Kashcha",
"license": "BSD-3-Clause",
"bugs": {
"url": "https://github.com/anvaka/ngraph.forcelayout/issues"
},
"devDependencies": {
"benchmark": "~1.0.0",
"browserify": "^17.0.0",
"eslint": "^7.12.1",
"ngraph.generators": "^20.0.0",
"ngraph.graph": "^20.0.0",
"tap": "^16.3.0",
"terser": "^5.3.0",
"through2": "^4.0.2"
},
"dependencies": {
"ngraph.events": "^1.0.0",
"ngraph.merge": "^1.0.0",
"ngraph.random": "^1.0.0"
}
}

View file

@ -0,0 +1,2 @@
This folder contains my experiments with performance, trying to figure out the
most efficient way of performing simulation

View file

@ -0,0 +1,110 @@
// Comparing the difference of compiled adhoc vs compiled in runtime function execution
// This should let build functions specific to dimension, without affecting performance.
var Benchmark = require('benchmark');
var suite = new Benchmark.Suite;
let bodies;
let total = 0;
let srcCode = staticCompiled.toString().split('\n').slice(1, -1).join('\n');
let dynamicCompiled = compile(srcCode);
resetBodies();
suite.add('static compiled', function() {
for (var i = 0; i < 2000; ++i) {
if (staticCompiled(bodies, 0.5, 0) > 0) {
total = 1;
}
}
})
.add('dynamic pre-compiled', function() {
for (var i = 0; i < 2000; ++i) {
if (dynamicCompiled(bodies, 0.5, 0) > 0) {
total = 1;
}
}
})
.add('dynamic ad-hoc pre-compiled', function() {
let fn = compile(srcCode);
for (var i = 0; i < 2000; ++i) {
if (fn(bodies, 0.5, 0) > 0) {
total = 1;
}
}
})
.on('cycle', function(event) {
console.log(String(event.target), total);
total = 0;
resetBodies();
})
.on('complete', function() {
console.log('Fastest is ' + this.filter('fastest').pluck('name'));
})
// run async
.run({ 'async': true });
function resetBodies() {
bodies = [];
for (let i = 0; i < 100; ++i) {
bodies.push(createBody(i));
}
}
function createBody(i) {
return {
springCount: 0,
springLength: 10,
mass: 1,
force: {x: i, y: i},
velocity: {x: 0, y: 0},
pos: {x: i, y: i}
};
}
function staticCompiled(bodyCollection, timeStep, adaptiveTimeStepWeight) {
var dx = 0, tx = 0,
dy = 0, ty = 0,
i,
max = bodyCollection.length;
if (max === 0) {
return 0;
}
for (i = 0; i < max; ++i) {
var body = bodyCollection[i];
if (adaptiveTimeStepWeight && body.springCount) {
timeStep = (adaptiveTimeStepWeight * body.springLength/body.springCount);
}
var coefficient = timeStep / body.mass;
body.velocity.x += coefficient * body.force.x;
body.velocity.y += coefficient * body.force.y;
var vx = body.velocity.x,
vy = body.velocity.y,
v = Math.sqrt(vx * vx + vy * vy);
if (v > 1) {
// We normalize it so that we move within timeStep range.
// for the case when v <= 1 - we let velocity to fade out.
body.velocity.x = vx / v;
body.velocity.y = vy / v;
}
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;
}
function compile(body) {
return new Function('bodies', 'timeStep', 'adaptiveTimeStepWeight', body);
}

View file

@ -0,0 +1,23 @@
> npm version && node perf/test.js
{
'ngraph.forcelayout': '1.0.0',
npm: '6.13.1',
ares: '1.15.0',
brotli: '1.0.7',
cldr: '35.1',
icu: '64.2',
llhttp: '1.1.4',
modules: '79',
napi: '5',
nghttp2: '1.40.0',
node: '13.2.0',
openssl: '1.1.1d',
tz: '2019c',
unicode: '12.1',
uv: '1.33.1',
v8: '7.9.317.23-node.20',
zlib: '1.2.11'
}
Run default x 58.06 ops/sec ±2.20% (61 runs sampled)
Fastest is Run default

20
VISUALIZACION/node_modules/ngraph.forcelayout/perf/test.js generated vendored Executable file
View file

@ -0,0 +1,20 @@
var graph = require('ngraph.generators').grid(20, 20);
var Benchmark = require('benchmark');
var suite = new Benchmark.Suite;
// add tests
suite.add('Run default', function() {
var layout = require('../')(graph);
for (var i = 0; i < 20; ++i) {
layout.step();
}
})
.on('cycle', function(event) {
console.log(String(event.target));
})
.on('complete', function() {
console.log('Fastest is ' + this.filter('fastest').pluck('name'));
})
// run async
.run({ 'async': true });

10
VISUALIZACION/node_modules/ngraph.forcelayout/stress.sh generated vendored Executable file
View file

@ -0,0 +1,10 @@
node perf/test.js
node perf/test.js
node perf/test.js
node perf/test.js
node perf/test.js
node perf/test.js
node perf/test.js
node perf/test.js
node perf/test.js
node perf/test.js

View file

@ -0,0 +1,10 @@
var test = require('tap').test;
var dimensions = 2;
test('can debug setters', function (t) {
var Body = require('../lib/codeGenerators/generateCreateBody')(dimensions, true);
let b = new Body();
t.throws(() => b.pos.x = 'foo', /Cannot set non-numbers to x/);
t.end();
});

View file

@ -0,0 +1,21 @@
var test = require('tap').test;
var dimensions = 2;
var createDragForce = require('../lib/codeGenerators/generateCreateDragForce')(dimensions);
var Body = require('../lib/codeGenerators/generateCreateBody')(dimensions);
test('reduces force value', function (t) {
var body = new Body();
body.force.x = 1; body.force.y = 1;
body.velocity.x = 1; body.velocity.y = 1;
var dragForce = createDragForce({ dragCoefficient: 0.1 });
dragForce.update(body);
t.ok(body.force.x < 1 && body.force.y < 1, 'Force value is reduced');
t.end();
});
test('Initialized with default value', function (t) {
t.throws(() => createDragForce());
t.end();
});

View file

@ -0,0 +1,68 @@
var test = require('tap').test;
var dimensions = 2;
var Body = require('../lib/codeGenerators/generateCreateBody')(dimensions);
var integrate = require('../lib/codeGenerators/generateIntegrator')(dimensions);
test('Body preserves velocity without forces', function (t) {
var body = new Body();
var timeStep = 1;
body.mass = 1; body.velocity.x = 1;
integrate([body], timeStep);
t.equal(body.pos.x, 1, 'Should move by 1 pixel on first iteration');
timeStep = 2; // let's increase time step:
integrate([body], timeStep);
t.equal(body.pos.x, 3, 'Should move by 2 pixel on second iteration');
t.end();
});
test('Body gains velocity under force', function (t) {
var body = new Body();
var timeStep = 1;
body.mass = 1; body.force.x = 0.1;
// F = m * a;
// since mass = 1 => F = a = y';
integrate([body], timeStep);
t.equal(body.velocity.x, 0.1, 'Should increase velocity');
integrate([body], timeStep);
t.equal(body.velocity.x, 0.2, 'Should increase velocity');
// floating point math:
t.ok(0.29 < body.pos.x && body.pos.x < 0.31, 'Position should be at 0.3 now');
t.end();
});
test('No bodies yield 0 movement', function (t) {
var movement = integrate([], 2);
t.equal(movement, 0, 'Nothing has moved');
t.end();
});
test('Body does not move faster than 1px', function (t) {
var body = new Body();
var timeStep = 1;
body.mass = 1; body.force.x = 2;
integrate([body], timeStep);
t.ok(body.velocity.x <= 1, 'Velocity should be within speed limit');
integrate([body], timeStep);
t.ok(body.velocity.x <= 1, 'Velocity should be within speed limit');
t.end();
});
test('Can get total system movement', function (t) {
var body = new Body();
var timeStep = 1;
body.mass = 1; body.velocity.x = 0.2;
var movement = integrate([body], timeStep);
// to improve performance, integrator does not take square root, thus
// total movement is .2 * .2 = 0.04;
t.ok(0.04 <= movement && movement <= 0.041, 'System should travel by 0.2 pixels');
t.end();
});

78
VISUALIZACION/node_modules/ngraph.forcelayout/test/insert.js generated vendored Executable file
View file

@ -0,0 +1,78 @@
var test = require('tap').test;
var dimensions = 2;
var createQuadTree = require('../lib/codeGenerators/generateQuadTree')(dimensions);
var Body = require('../lib/codeGenerators/generateCreateBody')(dimensions);
var random = require('ngraph.random').random(42);
test('insert and update update forces', function (t) {
var tree = createQuadTree({}, random);
var body = new Body();
var clone = JSON.parse(JSON.stringify(body));
tree.insertBodies([body]);
tree.updateBodyForce(body);
t.same(body, clone, 'The body should not be changed - there are no forces acting on it');
t.end();
});
test('it can get root', function (t) {
var tree = createQuadTree({}, random);
var body = new Body();
tree.insertBodies([body]);
var root = tree.getRoot();
t.ok(root, 'Root is present');
t.equal(root.body, body, 'Body is initialized');
t.end();
});
test('Two bodies repel each other', function (t) {
var tree = createQuadTree({}, random);
var bodyA = new Body(); bodyA.pos.x = 1; bodyA.pos.y = 0;
var bodyB = new Body(); bodyB.pos.x = 2; bodyB.pos.y = 0;
tree.insertBodies([bodyA, bodyB]);
tree.updateBodyForce(bodyA);
tree.updateBodyForce(bodyB);
// based on our physical model construction forces should be equivalent, with
// opposite sign:
t.ok(bodyA.force.x + bodyB.force.x === 0, 'Forces should be same, with opposite sign');
t.ok(bodyA.force.x !== 0, 'X-force for body A should not be zero');
t.ok(bodyB.force.x !== 0, 'X-force for body B should not be zero');
// On the other hand, our bodies should not move by Y axis:
t.ok(bodyA.force.y === 0, 'Y-force for body A should be zero');
t.ok(bodyB.force.y === 0, 'Y-force for body B should be zero');
t.end();
});
test('Can handle two bodies at the same location', function (t) {
var tree = createQuadTree({}, random);
var bodyA = new Body();
var bodyB = new Body();
tree.insertBodies([bodyA, bodyB]);
tree.updateBodyForce(bodyA);
tree.updateBodyForce(bodyB);
t.end();
});
test('it does not stuck', function(t) {
var count = 60000;
var bodies = [];
for (var i = 0; i < count; ++i) {
bodies.push(new Body(Math.random(), Math.random()));
}
var quadTree = createQuadTree({}, random);
quadTree.insertBodies(bodies);
bodies.forEach(function(body) {
quadTree.updateBodyForce(body);
});
t.ok(1);
t.end();
});

463
VISUALIZACION/node_modules/ngraph.forcelayout/test/layout.js generated vendored Executable file
View file

@ -0,0 +1,463 @@
/* eslint-disable no-shadow */
var test = require('tap').test,
createGraph = require('ngraph.graph'),
createLayout = require('..');
test('it exposes simulator', function(t) {
t.ok(typeof createLayout.simulator === 'function', 'Simulator is exposed');
t.end();
});
test('it returns spring', function(t) {
var g = createGraph();
var layout = createLayout(g);
var link = g.addLink(1, 2);
var springForLink = layout.getSpring(link);
var springForLinkId = layout.getSpring(link.id);
var springForFromTo = layout.getSpring(1, 2);
t.ok(springForLink, 'spring is here');
t.ok(springForLinkId === springForLink, 'Spring is the same');
t.ok(springForFromTo === springForLink, 'Spring is the same');
t.end();
});
test('it returns same position', function(t) {
var g = createGraph();
var layout = createLayout(g);
g.addLink(1, 2);
var firstNodePos = layout.getNodePosition(1);
layout.step();
t.ok(firstNodePos === layout.getNodePosition(1), 'Position is the same object');
layout.step();
t.ok(firstNodePos === layout.getNodePosition(1), 'Position is the same object after multiple steps');
t.end();
});
test('it returns body', function(t) {
var g = createGraph();
var layout = createLayout(g);
g.addLink(1, 2);
t.ok(layout.getBody(1), 'node 1 has body');
t.ok(layout.getBody(2), 'node 2 has body');
t.notOk(layout.getBody(4), 'there is no node 4');
var body = layout.getBody(1);
t.ok(body.pos.x && body.pos.y, 'Body has a position');
t.ok(body.mass, 'Body has a mass');
t.end();
});
test('it can set node mass', function(t) {
var g = createGraph();
g.addNode('anvaka');
var layout = createLayout(g, {
nodeMass: function (nodeId) {
t.equal(nodeId, 'anvaka', 'correct node is called');
return 84; // my mass in kilograms :P
}
});
var body = layout.getBody('anvaka');
t.equal(body.mass, 84, 'Mass is okay');
t.end();
});
test('does not tolerate bad input', function (t) {
t.throws(missingGraph);
t.throws(invalidNodeId);
t.end();
function missingGraph() {
// graph is missing:
createLayout();
}
function invalidNodeId() {
var graph = createGraph();
var layout = createLayout(graph);
// we don't have nodes in the graph. This should throw:
layout.getNodePosition(1);
}
});
test('it fires stable on empty graph', function(t) {
var graph = createGraph();
var layout = createLayout(graph);
layout.on('stable', endTest);
layout.step();
function endTest() {
t.end();
}
});
test('can add bodies which are standard prototype names', function (t) {
var graph = createGraph();
graph.addLink('constructor', 'watch');
var layout = createLayout(graph);
layout.step();
graph.forEachNode(function (node) {
var pos = layout.getNodePosition(node.id);
t.ok(pos && typeof pos.x === 'number' &&
typeof pos.y === 'number', 'Position is defined');
});
t.end();
});
test('it can step when no links present', function (t) {
var graph = createGraph();
graph.addNode('constructor');
graph.addNode('watch');
var layout = createLayout(graph);
layout.step();
graph.forEachNode(function (node) {
var pos = layout.getNodePosition(node.id);
t.ok(pos && typeof pos.x === 'number' &&
typeof pos.y === 'number', 'Position is defined');
});
t.end();
});
test('layout initializes nodes positions', function (t) {
var graph = createGraph();
graph.addLink(1, 2);
var layout = createLayout(graph);
// perform one iteration of layout:
layout.step();
graph.forEachNode(function (node) {
var pos = layout.getNodePosition(node.id);
t.ok(pos && typeof pos.x === 'number' &&
typeof pos.y === 'number', 'Position is defined');
});
graph.forEachLink(function (link) {
var linkPos = layout.getLinkPosition(link.id);
t.ok(linkPos && linkPos.from && linkPos.to, 'Link position is defined');
var fromPos = layout.getNodePosition(link.fromId);
t.ok(linkPos.from === fromPos, '"From" should be identical to getNodePosition');
var toPos = layout.getNodePosition(link.toId);
t.ok(linkPos.to === toPos, '"To" should be identical to getNodePosition');
});
t.end();
});
test('Layout can set node position', function (t) {
var graph = createGraph();
graph.addLink(1, 2);
var layout = createLayout(graph);
layout.pinNode(graph.getNode(1), true);
layout.setNodePosition(1, 42, 42);
// perform one iteration of layout:
layout.step();
// and make sure node 1 was not moved:
var actualPosition = layout.getNodePosition(1);
t.equal(actualPosition.x, 42, 'X has not changed');
t.equal(actualPosition.y, 42, 'Y has not changed');
t.end();
});
test('Layout updates bounding box when it sets node position', function (t) {
var graph = createGraph();
graph.addLink(1, 2);
var layout = createLayout(graph);
layout.setNodePosition(1, 42, 42);
layout.setNodePosition(2, 40, 40);
var rect = layout.getGraphRect();
t.ok(rect.max_x <= 42); t.ok(rect.max_y <= 42);
t.ok(rect.min_x >= 40); t.ok(rect.min_y >= 40);
t.end();
});
test('layout initializes links', function (t) {
var graph = createGraph();
var node1 = graph.addNode(1); node1.position = {x : -1000, y: 0};
var node2 = graph.addNode(2); node2.position = {x : 1000, y: 0};
graph.addLink(1, 2);
var layout = createLayout(graph);
// perform one iteration of layout:
layout.step();
// since both nodes are connected by spring and distance is too large between
// them, they should start attracting each other
var pos1 = layout.getNodePosition(1);
var pos2 = layout.getNodePosition(2);
t.ok(pos1.x > -1000, 'Node 1 moves towards node 2');
t.ok(pos2.x < 1000, 'Node 1 moves towards node 2');
t.end();
});
test('layout respects proposed original position', function (t) {
var graph = createGraph();
var node = graph.addNode(1);
var initialPosition = {x: 100, y: 100};
node.position = copy(initialPosition);
var layout = createLayout(graph);
layout.step();
t.same(layout.getNodePosition(node.id), initialPosition, 'original position preserved');
t.end();
});
test('layout has defined graph rectangle', function (t) {
t.test('empty graph', function (t) {
var graph = createGraph();
var layout = createLayout(graph);
var rect = layout.getGraphRect();
var expectedProperties = ['min_x', 'min_y', 'max_x', 'max_y'];
t.ok(rect && expectedProperties.reduce(hasProperties, true), 'Values are present before step()');
layout.step();
t.ok(rect && expectedProperties.reduce(hasProperties, true), 'Values are present after step()');
t.end();
function hasProperties(result, key) {
return result && typeof rect[key] === 'number';
}
});
t.test('two nodes', function (t) {
var graph = createGraph();
graph.addLink(1, 2);
var layout = createLayout(graph);
layout.step();
var rect = layout.getGraphRect();
t.ok(!rectangleIsEmpty(rect), 'Graph rectangle is not empty');
t.end();
});
t.end();
});
test('it does not move pinned nodes', function (t) {
t.test('respects original data.isPinned attribute', function (t) {
var graph = createGraph();
var testNode = graph.addNode(1, { isPinned: true });
var layout = createLayout(graph);
t.ok(layout.isNodePinned(testNode), 'Node is pinned');
t.end();
});
t.test('respects node.isPinned attribute', function (t) {
var graph = createGraph();
var testNode = graph.addNode(1);
// this was possible in vivagraph. Port it over to ngraph:
testNode.isPinned = true;
var layout = createLayout(graph);
t.ok(layout.isNodePinned(testNode), 'Node is pinned');
t.end();
});
t.test('can pin nodes after graph is initialized', function (t) {
var graph = createGraph();
graph.addLink(1, 2);
var layout = createLayout(graph);
layout.pinNode(graph.getNode(1), true);
layout.step();
var pos1 = copy(layout.getNodePosition(1));
var pos2 = copy(layout.getNodePosition(2));
// make one more step and make sure node 1 did not move:
layout.step();
t.ok(!positionChanged(pos1, layout.getNodePosition(1)), 'Node 1 was not moved');
t.ok(positionChanged(pos2, layout.getNodePosition(2)), 'Node 2 has moved');
t.end();
});
t.end();
});
test('it listens to graph events', function (t) {
// we first initialize with empty graph:
var graph = createGraph();
var layout = createLayout(graph);
// and only then add nodes:
graph.addLink(1, 2);
// make two iterations
layout.step();
var pos1 = copy(layout.getNodePosition(1));
var pos2 = copy(layout.getNodePosition(2));
layout.step();
t.ok(positionChanged(pos1, layout.getNodePosition(1)), 'Node 1 has moved');
t.ok(positionChanged(pos2, layout.getNodePosition(2)), 'Node 2 has moved');
t.end();
});
test('can stop listen to events', function (t) {
// we first initialize with empty graph:
var graph = createGraph();
var layout = createLayout(graph);
layout.dispose();
graph.addLink(1, 2);
layout.step();
t.ok(layout.simulator.bodies.length === 0, 'No bodies in the simulator');
t.end();
});
test('physics simulator', function (t) {
t.test('has default simulator', function (t) {
var graph = createGraph();
var layout = createLayout(graph);
t.ok(layout.simulator, 'physics simulator is present');
t.end();
});
t.test('can override default settings', function (t) {
var graph = createGraph();
var layout = createLayout(graph, {
theta: 1.5
});
t.equal(layout.simulator.theta(), 1.5, 'Simulator settings are overridden');
t.end();
});
t.end();
});
test('it removes removed nodes', function (t) {
var graph = createGraph();
var layout = createLayout(graph);
graph.addLink(1, 2);
layout.step();
graph.clear();
// since we removed everything from graph rect should be empty:
var rect = layout.getGraphRect();
t.ok(rectangleIsEmpty(rect), 'Graph rect is empty');
t.end();
});
test('it can iterate over bodies', function(t) {
var graph = createGraph();
var layout = createLayout(graph);
graph.addLink(1, 2);
var calledCount = 0;
layout.forEachBody(function(body, bodyId) {
t.ok(body.pos, bodyId + ' has position');
t.ok(graph.getNode(bodyId), bodyId + ' matches a graph node');
calledCount += 1;
});
t.equal(calledCount, 2, 'Both bodies are visited');
t.end();
});
test('it handles large graphs', function (t) {
var graph = createGraph();
var layout = createLayout(graph);
var count = 60000;
var i = count;
while (i--) {
graph.addNode(i);
}
// link each node to 2 other random nodes
i = count;
while (i--) {
graph.addLink(i, Math.ceil(Math.random() * count));
graph.addLink(i, Math.ceil(Math.random() * count));
}
layout.step();
t.ok(layout.simulator.bodies.length !== 0, 'Bodies in the simulator');
t.end();
});
test('it can create high dimensional layout', function(t) {
var graph = createGraph();
graph.addLink(1, 2);
var layout = createLayout(graph, {dimensions: 6});
layout.step();
var pos = layout.getNodePosition(1);
t.ok(pos.x !== undefined, 'Position has x');
t.ok(pos.y !== undefined, 'Position has y');
t.ok(pos.z !== undefined, 'Position has z');
t.ok(pos.c4 !== undefined, 'Position has c4');
t.ok(pos.c5 !== undefined, 'Position has c5');
t.ok(pos.c6 !== undefined, 'Position has c6');
t.end();
});
test('it can layout two graphs independently', function(t) {
var graph1 = createGraph();
var graph2 = createGraph();
var layout1 = createLayout(graph1);
var layout2 = createLayout(graph2);
graph1.addLink(1, 2);
graph2.addLink(1, 2);
layout1.step();
layout2.step();
layout2.step();
t.ok(layout1.getNodePosition(1).x !== layout2.getNodePosition(1).x, 'Positions are different');
t.end();
});
function positionChanged(pos1, pos2) {
return (pos1.x !== pos2.x) || (pos1.y !== pos2.y);
}
function copy(obj) {
return JSON.parse(JSON.stringify(obj));
}
function rectangleIsEmpty(rect) {
return rect.min_x === 0 && rect.min_y === 0 && rect.max_x === 0 && rect.max_y === 0;
}

View file

@ -0,0 +1,100 @@
var test = require('tap').test;
var {generateCreateBodyFunctionBody} = require('../lib/codeGenerators/generateCreateBody');
function primitive(dimension) {
let res = (new Function(generateCreateBodyFunctionBody(dimension)))();
return res;
}
test('Body has properties force, pos and mass', function(t) {
debugger;
var body = new (primitive(2).Body)();
t.ok(body.force, 'Force attribute is missing on body');
t.ok(body.pos, 'Pos attribute is missing on body');
t.ok(body.velocity, 'Velocity attribute is missing on body');
t.ok(typeof body.mass === 'number' && body.mass !== 0, 'Body should have a mass');
t.end();
});
test('Vector has x and y', function(t) {
var vector = new (primitive(2).Vector)();
t.ok(typeof vector.x === 'number', 'Vector has x coordinates');
t.ok(typeof vector.y === 'number', 'Vector has y coordinates');
var initialized = new (primitive(2).Vector)(1, 2);
t.equal(initialized.x, 1, 'Vector initialized properly');
t.equal(initialized.y, 2, 'Vector initialized properly');
var badInput = new (primitive(2).Vector)('hello world');
t.equal(badInput.x, 0, 'Vector should be resilient to bed input');
t.equal(badInput.y, 0, 'Vector should be resilient to bed input');
t.end();
});
test('Body3d has properties force, pos and mass', function(t) {
var body = new (primitive(3).Body)();
t.ok(body.force, 'Force attribute is missing on body');
t.ok(body.pos, 'Pos attribute is missing on body');
t.ok(body.velocity, 'Velocity attribute is missing on body');
t.ok(typeof body.mass === 'number' && body.mass !== 0, 'Body should have a mass');
t.end();
});
test('Vector3d has x and y and z', function(t) {
var vector = new (primitive(3).Vector)();
t.ok(typeof vector.x === 'number', 'Vector has x coordinates');
t.ok(typeof vector.y === 'number', 'Vector has y coordinates');
t.ok(typeof vector.z === 'number', 'Vector has z coordinates');
var initialized = new (primitive(3).Vector)(1, 2, 3);
t.equal(initialized.x, 1, 'Vector initialized properly');
t.equal(initialized.y, 2, 'Vector initialized properly');
t.equal(initialized.z, 3, 'Vector initialized properly');
var badInput = new (primitive(3).Vector)('hello world');
t.equal(badInput.x, 0, 'Vector should be resilient to bed input');
t.equal(badInput.y, 0, 'Vector should be resilient to bed input');
t.equal(badInput.z, 0, 'Vector should be resilient to bed input');
t.end();
});
test('reset vector', function(t) {
var v3 = new (primitive(3).Vector)(1, 2, 3);
v3.reset();
t.equal(v3.x, 0, 'Reset to 0');
t.equal(v3.y, 0, 'Reset to 0');
t.equal(v3.z, 0, 'Reset to 0');
var v2 = new (primitive(2).Vector)(1, 2);
v2.reset();
t.equal(v2.x, 0, 'Reset to 0');
t.equal(v2.y, 0, 'Reset to 0');
t.end();
});
test('vector can use copy constructor', function(t) {
var a = new (primitive(3).Vector)(1, 2, 3);
var b = new (primitive(3).Vector)(a);
t.equal(b.x, a.x, 'Value copied');
t.equal(b.y, a.y, 'Value copied');
t.equal(b.z, a.z, 'Value copied');
t.end();
});
test('Body3d can set position', function(t) {
var body = new (primitive(3).Body)();
body.setPosition(10, 11, 12);
t.equal(body.pos.x, 10, 'x is correct');
t.equal(body.pos.y, 11, 'y is correct');
t.equal(body.pos.z, 12, 'z is correct');
t.end();
});
test('Body can set position', function(t) {
var body = new (primitive(2).Body)();
body.setPosition(10, 11);
t.equal(body.pos.x, 10, 'x is correct');
t.equal(body.pos.y, 11, 'y is correct');
t.end();
});

View file

@ -0,0 +1,336 @@
/* eslint-disable no-shadow */
var test = require('tap').test;
var dimensions = 2;
var Body = require('../lib/codeGenerators/generateCreateBody')(dimensions);
var createSimulator = require('../lib/createPhysicsSimulator');
test('Can step without bodies', function (t) {
var simulator = createSimulator();
t.equal(simulator.bodies.length, 0, 'There should be no bodies');
t.equal(simulator.springs.length, 0, 'There should be no springs');
simulator.step();
t.end();
});
test('it has settings exposed', function(t) {
var mySettings = { };
var simulator = createSimulator(mySettings);
t.ok(mySettings === simulator.settings, 'settings are exposed');
t.end();
});
test('it gives amount of total movement', function(t) {
var simulator = createSimulator();
var body1 = new Body(-10, 0);
var body2 = new Body(10, 0);
simulator.addBody(body1);
simulator.addBody(body2);
simulator.step();
var totalMoved = simulator.getTotalMovement();
t.ok(!isNaN(totalMoved), 'Amount of total movement is returned');
t.end();
});
test('it can add a body at given position', function(t) {
var simulator = createSimulator();
var pos1 = {x: -10, y: 0};
var pos2 = {x: 10, y: 0};
simulator.addBodyAt(pos1);
simulator.addBodyAt(pos2);
t.equal(simulator.bodies.length, 2, 'All bodies are added');
var body1 = simulator.bodies[0];
t.equal(body1.pos.x, -10, 'X is there');
t.equal(body1.pos.y, 0, 'Y is there');
var body2 = simulator.bodies[1];
t.equal(body2.pos.x, 10, 'X is there');
t.equal(body2.pos.y, 0, 'Y is there');
t.end();
});
test('Does not update position of one body', function (t) {
var simulator = createSimulator();
var body = new Body(0, 0);
simulator.addBody(body);
simulator.step(1);
t.equal(simulator.bodies.length, 1, 'Number of bodies is 1');
t.equal(simulator.springs.length, 0, 'Number of springs is 0');
t.equal(simulator.bodies[0], body, 'Body points to actual object');
t.equal(body.pos.x, 0, 'X is not changed');
t.equal(body.pos.y, 0, 'Y is not changed');
t.end();
});
test('throws on no body or no pos', t => {
var simulator = createSimulator();
t.throws(() => simulator.addBody(), /Body is required/);
t.throws(() => simulator.addBodyAt(), /Body position is required/);
t.end();
});
test('throws on no spring', t => {
var simulator = createSimulator();
t.throws(() => simulator.addSpring(), /Cannot add null spring to force simulator/);
t.end();
});
test('Can add and remove forces', function (t) {
var simulator = createSimulator();
var testForce = function () {};
simulator.addForce('foo', testForce);
t.equal(simulator.getForces().get('foo'), testForce);
simulator.removeForce('foo');
t.equal(simulator.getForces().get('foo'), undefined);
simulator.removeForce('foo');
// should still be good
t.end();
});
test('Can configure forces', function (t) {
t.test('Gravity', function (t) {
var simulator = createSimulator();
var body1 = new Body(0, 0);
var body2 = new Body(1, 0);
simulator.addBody(body1);
simulator.addBody(body2);
simulator.step();
// by default gravity is negative, bodies should repel each other:
var x1 = body1.pos.x;
var x2 = body2.pos.x;
t.ok(x1 < 0, 'Body 1 moves away from body 2');
t.ok(x2 > 1, 'Body 2 moves away from body 1');
// now reverse gravity, and bodies should attract each other:
simulator.gravity(100);
simulator.step();
t.ok(body1.pos.x > x1, 'Body 1 moved towards body 2');
t.ok(body2.pos.x < x2, 'Body 2 moved towards body 1');
t.end();
});
t.test('Drag', function (t) {
var simulator = createSimulator();
var body1 = new Body(0, 0);
body1.velocity.x = -1; // give it small impulse
simulator.addBody(body1);
simulator.step();
var x1 = body1.velocity.x;
// by default drag force will slow down entire system:
t.ok(x1 > -1, 'Body 1 moves at reduced speed');
// Restore original velocity, but now set drag force to 0
body1.velocity.x = -1;
simulator.dragCoefficient(0);
simulator.step();
t.ok(body1.velocity.x === -1, 'Velocity should remain unchanged');
t.end();
});
t.end();
});
test('Can remove bodies', function (t) {
var simulator = createSimulator();
var body = new Body(0, 0);
simulator.addBody(body);
t.equal(simulator.bodies.length, 1, 'Number of bodies is 1');
var result = simulator.removeBody(body);
t.equal(result, true, 'body successfully removed');
t.equal(simulator.bodies.length, 0, 'Number of bodies is 0');
t.end();
});
test('Updates position for two bodies', function (t) {
var simulator = createSimulator();
var body1 = new Body(-1, 0);
var body2 = new Body(1, 0);
simulator.addBody(body1);
simulator.addBody(body2);
simulator.step();
t.equal(simulator.bodies.length, 2, 'Number of bodies is 2');
t.ok(body1.pos.x !== 0, 'Body1.X has changed');
t.ok(body2.pos.x !== 0, 'Body2.X has changed');
t.equal(body1.pos.y, 0, 'Body1.Y has not changed');
t.equal(body2.pos.y, 0, 'Body2.Y has not changed');
t.end();
});
test('add spring should not add bodies', function (t) {
var simulator = createSimulator();
var body1 = new Body(-1, 0);
var body2 = new Body(1, 0);
simulator.addSpring(body1, body2, 10);
t.equal(simulator.bodies.length, 0, 'Should not add two bodies');
t.equal(simulator.bodies.length, 0, 'Should not add two bodies');
t.equal(simulator.springs.length, 1, 'Should have a spring');
t.end();
});
test('Spring affects bodies positions', function (t) {
var simulator = createSimulator();
var body1 = new Body(-10, 0);
var body2 = new Body(10, 0);
simulator.addBody(body1);
simulator.addBody(body2);
// If you take this out, bodies will repel each other:
simulator.addSpring(body1, body2, 1);
simulator.step();
t.ok(body1.pos.x > -10, 'Body 1 should move towards body 2');
t.ok(body2.pos.x < 10, 'Body 2 should move towards body 1');
t.end();
});
test('Can remove springs', function (t) {
var simulator = createSimulator();
var body1 = new Body(-10, 0);
var body2 = new Body(10, 0);
simulator.addBody(body1);
simulator.addBody(body2);
var spring = simulator.addSpring(body1, body2, 1);
simulator.removeSpring(spring);
simulator.step();
t.ok(body1.pos.x < -10, 'Body 1 should move away from body 2');
t.ok(body2.pos.x > 10, 'Body 2 should move away from body 1');
t.end();
});
test('Get bounding box', function (t) {
var simulator = createSimulator();
var body1 = new Body(0, 0);
var body2 = new Body(10, 10);
simulator.addBody(body1);
simulator.addBody(body2);
simulator.step(); // this will move bodies farther away
var bbox = simulator.getBBox();
t.ok(bbox.min_x <= 0, 'Left is 0');
t.ok(bbox.min_y <= 0, 'Top is 0');
t.ok(bbox.max_x >= 10, 'right is 10');
t.ok(bbox.max_y >= 10, 'bottom is 10');
t.end();
});
test('it updates bounding box', function (t) {
var simulator = createSimulator();
var body1 = new Body(0, 0);
var body2 = new Body(10, 10);
simulator.addBody(body1);
simulator.addBody(body2);
var bbox = simulator.getBBox();
t.ok(bbox.min_x === 0, 'Left is 0');
t.ok(bbox.min_y === 0, 'Top is 0');
t.ok(bbox.max_x === 10, 'right is 10');
t.ok(bbox.max_y === 10, 'bottom is 10');
body1.setPosition(15, 15);
simulator.invalidateBBox();
bbox = simulator.getBBox();
t.ok(bbox.min_x === 10, 'Left is 10');
t.ok(bbox.min_y === 10, 'Top is 10');
t.ok(bbox.max_x === 15, 'right is 15');
t.ok(bbox.max_y === 15, 'bottom is 15');
t.end();
});
test('Get best position', function (t) {
t.test('can get with empty simulator', function (t) {
var simulator = createSimulator();
var empty = simulator.getBestNewBodyPosition([]);
t.ok(typeof empty.x === 'number', 'Has X');
t.ok(typeof empty.y === 'number', 'Has Y');
t.end();
});
t.end();
});
test('it can change settings', function(t) {
var simulator = createSimulator();
var currentTheta = simulator.theta();
t.ok(typeof currentTheta === 'number', 'theta is here');
simulator.theta(1.2);
t.equal(simulator.theta(), 1.2, 'theta is changed');
var currentSpringCoefficient = simulator.springCoefficient();
t.ok(typeof currentSpringCoefficient === 'number', 'springCoefficient is here');
simulator.springCoefficient(0.8);
t.equal(simulator.springCoefficient(), 0.8, 'springCoefficient is changed');
var gravity = simulator.gravity();
t.ok(typeof gravity === 'number', 'gravity is here');
simulator.gravity(-0.8);
t.equal(simulator.gravity(), -0.8, 'gravity is changed');
var springLength = simulator.springLength();
t.ok(typeof springLength === 'number', 'springLength is here');
simulator.springLength(80);
t.equal(simulator.springLength(), 80, 'springLength is changed');
var dragCoefficient = simulator.dragCoefficient();
t.ok(typeof dragCoefficient === 'number', 'dragCoefficient is here');
simulator.dragCoefficient(0.8);
t.equal(simulator.dragCoefficient(), 0.8, 'dragCoefficient is changed');
var timeStep = simulator.timeStep();
t.ok(typeof timeStep === 'number', 'timeStep is here');
simulator.timeStep(8);
t.equal(simulator.timeStep(), 8, 'timeStep is changed');
t.end();
});
test('it can augment string setter values', function (t) {
var simulator = createSimulator({
name: 'John'
});
simulator.name('Alisa');
t.equal(simulator.name(), 'Alisa', 'name is Alisa');
t.end();
});
test('it ignores body that does not exist', function(t) {
var simulator = createSimulator();
var body = new Body(0, 0);
simulator.addBody(body);
simulator.removeBody({});
t.equal(simulator.bodies.length, 1, 'Should ignore body that does not exist');
t.end();
});
test('it throws on springCoeff', function (t) {
t.throws(function () {
createSimulator({springCoeff: 1});
}, 'springCoeff was renamed to springCoefficient');
t.end();
});
test('it throws on dragCoeff', function (t) {
t.throws(function () {
createSimulator({dragCoeff: 1});
}, 'dragCoeff was renamed to dragCoefficient');
t.end();
});

View file

@ -0,0 +1,63 @@
/* eslint-disable no-shadow */
var test = require('tap').test;
var dimensions = 2;
var createSpringForce = require('../lib/codeGenerators/generateCreateSpringForce')(dimensions);
var Body = require('../lib/codeGenerators/generateCreateBody')(dimensions);
var Spring = require('../lib/spring');
var random = require('ngraph.random')(42);
test('Initialized with default value', function (t) {
t.throws(() => createSpringForce());
t.end();
});
test('Should bump bodies at same position', function (t) {
var body1 = new Body(0, 0);
var body2 = new Body(0, 0);
// length between two bodies is 2, while ideal length is 1. Each body
// should start moving towards each other after force update
var idealLength = 1;
var spring = new Spring(body1, body2, idealLength);
var springForce = createSpringForce({springCoefficient: 0.1, springLength: 1}, random);
springForce.update(spring);
t.ok(body1.force.x > 0, 'Body 1 should go right');
t.ok(body2.force.x < 0, 'Body 2 should go left');
t.end();
});
test('Check spring force direction', function (t) {
var springForce = createSpringForce({springCoefficient: 0.1, springLength: 1});
t.test('Should contract two bodies when ideal length is smaller than actual', function (t) {
var body1 = new Body(-1, 0);
var body2 = new Body(+1, 0);
// length between two bodies is 2, while ideal length is 1. Each body
// should start moving towards each other after force update
var idealLength = 1;
var spring = new Spring(body1, body2, idealLength);
springForce.update(spring);
t.ok(body1.force.x > 0, 'Body 1 should go right');
t.ok(body2.force.x < 0, 'Body 2 should go left');
t.end();
});
t.test('Should repel two bodies when ideal length is larger than actual', function (t) {
var body1 = new Body(-1, 0);
var body2 = new Body(+1, 0);
// length between two bodies is 2, while ideal length is 1. Each body
// should start moving towards each other after force update
var idealLength = 3;
var spring = new Spring(body1, body2, idealLength);
springForce.update(spring);
t.ok(body1.force.x < 0, 'Body 1 should go left');
t.ok(body2.force.x > 0, 'Body 2 should go right');
t.end();
});
t.end();
});