Browse Source

Merge branch 'alex_dev' into develop

Conflicts:
	Jakefile.js
	examples/graph/index.html
	src/graph/Edge.js
	src/graph/Graph.js
	src/graph/graphMixins/ClusterMixin.js
	src/graph/graphMixins/SelectionMixin.js
	src/timeline/Controller.js
	src/timeline/Range.js
	src/timeline/Timeline.js
	src/timeline/component/ItemSet.js
	src/timeline/component/RootPanel.js
	src/timeline/component/item/Item.js
	src/util.js
css_transitions
josdejong 10 years ago
parent
commit
a8a8519e4c
91 changed files with 4233 additions and 21198 deletions
  1. +1
    -0
      .gitignore
  2. +1
    -0
      CNAME
  3. +13
    -4
      Jakefile.js
  4. +94
    -0
      css/style.css
  5. BIN
      dist/img/acceptDeleteIcon.png
  6. BIN
      dist/img/addNodeIcon.png
  7. BIN
      dist/img/backIcon.png
  8. BIN
      dist/img/connectIcon.png
  9. BIN
      dist/img/deleteIcon.png
  10. BIN
      dist/img/editIcon.png
  11. +0
    -19896
      dist/vis.js
  12. +0
    -31
      dist/vis.min.js
  13. +299
    -29
      docs/graph.html
  14. BIN
      download/vis.zip
  15. +1
    -2
      examples/graph/02_random_nodes.html
  16. +0
    -3
      examples/graph/19_scale_free_graph_clustering.html
  17. +6
    -5
      examples/graph/20_navigation.html
  18. +214
    -0
      examples/graph/21_data_manipulation.html
  19. +373
    -0
      examples/graph/22_les_miserables.html
  20. +1
    -0
      examples/graph/index.html
  21. BIN
      favicon.ico
  22. BIN
      img/external-link-icons/external-link-icon.png
  23. +40
    -0
      img/external-link-icons/license.txt
  24. BIN
      img/forkme_right_darkblue_121621.png
  25. BIN
      img/gallery/graph/01_basic_usage.png
  26. BIN
      img/gallery/graph/02_random_nodes.png
  27. BIN
      img/gallery/graph/03_images.png
  28. BIN
      img/gallery/graph/04_shapes.png
  29. BIN
      img/gallery/graph/05_social_network.png
  30. BIN
      img/gallery/graph/06_groups.png
  31. BIN
      img/gallery/graph/07_selections.png
  32. BIN
      img/gallery/graph/08_mobile_friendly.png
  33. BIN
      img/gallery/graph/09_sizing.png
  34. BIN
      img/gallery/graph/10_multiline_text.png
  35. BIN
      img/gallery/graph/11_custom_style.png
  36. BIN
      img/gallery/graph/12_scalable_images.png
  37. BIN
      img/gallery/graph/13_dashed_lines.png
  38. BIN
      img/gallery/graph/14_dot_language.png
  39. BIN
      img/gallery/graph/15_dot_language_playground.png
  40. BIN
      img/gallery/graph/15_dot_language_playground2.png
  41. BIN
      img/gallery/graph/16_dynamic_data.png
  42. BIN
      img/gallery/graph/17_network_info.png
  43. BIN
      img/gallery/graph/graphviz_gallery.png
  44. BIN
      img/gallery/timeline/01_basic.png
  45. BIN
      img/gallery/timeline/02_dataset.png
  46. BIN
      img/gallery/timeline/03_much_data.png
  47. BIN
      img/gallery/timeline/04_html_data.png
  48. BIN
      img/gallery/timeline/05_groups.png
  49. +169
    -0
      img/logo/vis.svg
  50. BIN
      img/logo/vis128.png
  51. BIN
      img/logo/vis16.png
  52. BIN
      img/logo/vis256.png
  53. BIN
      img/logo/vis32.ico
  54. BIN
      img/logo/vis32.png
  55. BIN
      img/logo/vis64.png
  56. +317
    -0
      index.html
  57. +0
    -0
      package.js
  58. +166
    -76
      src/graph/Edge.js
  59. +387
    -594
      src/graph/Graph.js
  60. +0
    -245
      src/graph/NavigationMixin.js
  61. +114
    -79
      src/graph/Node.js
  62. +128
    -0
      src/graph/css/graph-manipulation.css
  63. +62
    -0
      src/graph/css/graph-navigation.css
  64. +200
    -85
      src/graph/graphMixins/ClusterMixin.js
  65. +433
    -0
      src/graph/graphMixins/ManipulationMixin.js
  66. +219
    -0
      src/graph/graphMixins/MixinLoader.js
  67. +173
    -0
      src/graph/graphMixins/NavigationMixin.js
  68. +44
    -39
      src/graph/graphMixins/SectorsMixin.js
  69. +25
    -70
      src/graph/graphMixins/SelectionMixin.js
  70. +373
    -0
      src/graph/graphMixins/physics/BarnesHut.js
  71. +245
    -0
      src/graph/graphMixins/physics/PhysicsMixin.js
  72. +66
    -0
      src/graph/graphMixins/physics/Repulsion.js
  73. BIN
      src/graph/img/acceptDeleteIcon.png
  74. BIN
      src/graph/img/addNodeIcon.png
  75. BIN
      src/graph/img/backIcon.png
  76. BIN
      src/graph/img/connectIcon.png
  77. BIN
      src/graph/img/cross.png
  78. BIN
      src/graph/img/cross2.png
  79. BIN
      src/graph/img/deleteIcon.png
  80. +0
    -0
      src/graph/img/downArrow.png
  81. BIN
      src/graph/img/downarrow.png
  82. BIN
      src/graph/img/editIcon.png
  83. +0
    -0
      src/graph/img/leftArrow.png
  84. BIN
      src/graph/img/leftarrow.png
  85. +0
    -0
      src/graph/img/rightArrow.png
  86. BIN
      src/graph/img/rightarrow.png
  87. +0
    -0
      src/graph/img/upArrow.png
  88. BIN
      src/graph/img/uparrow.png
  89. +5
    -39
      src/timeline/component/item/Item.js
  90. +6
    -1
      src/util.js
  91. +58
    -0
      updateversion.js

+ 1
- 0
.gitignore View File

@ -5,3 +5,4 @@ node_modules
.settings/org.eclipse.wst.jsdt.ui.superType.container
.settings/org.eclipse.wst.jsdt.ui.superType.name
npm-debug.log
dist/

+ 1
- 0
CNAME View File

@ -0,0 +1 @@
visjs.org

+ 13
- 4
Jakefile.js View File

@ -80,10 +80,15 @@ task('build', {async: true}, function () {
'./src/graph/Popup.js',
'./src/graph/Groups.js',
'./src/graph/Images.js',
'./src/graph/SectorsMixin.js',
'./src/graph/ClusterMixin.js',
'./src/graph/SelectionMixin.js',
'./src/graph/NavigationMixin.js',
'./src/graph/graphMixins/physics/PhysicsMixin.js',
'./src/graph/graphMixins/physics/BarnesHut.js',
'./src/graph/graphMixins/physics/Repulsion.js',
'./src/graph/graphMixins/ManipulationMixin.js',
'./src/graph/graphMixins/SectorsMixin.js',
'./src/graph/graphMixins/ClusterMixin.js',
'./src/graph/graphMixins/SelectionMixin.js',
'./src/graph/graphMixins/NavigationMixin.js',
'./src/graph/graphMixins/MixinLoader.js',
'./src/graph/Graph.js',
'./src/module/exports.js'
@ -99,6 +104,10 @@ task('build', {async: true}, function () {
wrench.copyDirSyncRecursive('./src/timeline/img', DIST+ '/img/timeline', {
forceDelete: true
});
// copy css
wrench.copyDirSyncRecursive('./src/graph/css', DIST+ '/css', {
forceDelete: true
});
var timeStart = Date.now();
// bundle the concatenated script and dependencies into one file

+ 94
- 0
css/style.css View File

@ -0,0 +1,94 @@
#menu {
position: absolute;
left: -170px;
top: 35px;
background-color: #a7c8f9;
padding: 15px;
border-radius: 3px;
}
#forkme {
position: fixed;
top: 0;
right: 0;
border: 0;
}
div.nav {
text-align: center;
}
div.nav ul {
text-decoration: none;
text-transform: uppercase;
margin-bottom: 30px;
padding-left: 0;
}
li.nav {
}
div.nav ul li {
text-decoration: none;
text-transform: uppercase;
font-weight: normal;
font-size: 11pt;
list-style: none;
margin-top: 5px;
}
div.nav ul li ul li {
text-decoration: none;
text-transform: none;
font-weight: normal;
font-size: 11pt;
color: #4D4D4D;
list-style: none;
}
div.nav a {
color: #2B7CE9;
color: white;
font-weight: bold;
}
.subtitle {
color: gray;
text-transform: uppercase;
font-size: 11pt;
}
.download td {
border: none;
padding: 5px 20px 5px 0;
}
.gallery .thumb {
display: inline-block;
text-align: center;
margin-right: 10px;
margin-bottom: 20px;
}
.gallery .thumb img {
border: 1px solid white;
border-radius: 5px;
height: 90px;
margin: 0;
}
.gallery .thumb a:hover img {
border-color: lightgray;
}
.gallery .thumb div {
margin: 0;
}
img {
border: 0;
}

BIN
dist/img/acceptDeleteIcon.png View File

Before After
Width: 24  |  Height: 24  |  Size: 20 KiB

BIN
dist/img/addNodeIcon.png View File

Before After
Width: 24  |  Height: 24  |  Size: 20 KiB

BIN
dist/img/backIcon.png View File

Before After
Width: 24  |  Height: 24  |  Size: 20 KiB

BIN
dist/img/connectIcon.png View File

Before After
Width: 24  |  Height: 24  |  Size: 20 KiB

BIN
dist/img/deleteIcon.png View File

Before After
Width: 24  |  Height: 24  |  Size: 20 KiB

BIN
dist/img/editIcon.png View File

Before After
Width: 24  |  Height: 24  |  Size: 20 KiB

+ 0
- 19896
dist/vis.js
File diff suppressed because it is too large
View File


+ 0
- 31
dist/vis.min.js
File diff suppressed because it is too large
View File


+ 299
- 29
docs/graph.html View File

@ -53,7 +53,9 @@
<li><a href="#Nodes_configuration">Nodes</a></li>
<li><a href="#Edges_configuration">Edges</a></li>
<li><a href="#Groups_configuration">Groups</a></li>
<li><a href="#Clustering">Clustering</a></li>
<li><a href="#Physics">Physics</a></li>
<li><a href="#Data_manipulation">Data_manipulation</a></li>
<li><a href="#Clustering">Clustering</a></li>
<li><a href="#Navigation_controls">Navigation controls</a></li>
<li><a href="#Keyboard_navigation">Keyboard navigation</a></li>
</ul>
@ -529,13 +531,6 @@ var edges = [
type.</td>
</tr>
<tr>
<td>length</td>
<td>number</td>
<td>no</td>
<td>The length of the edge in pixels.</td>
</tr>
<tr>
<td>style</td>
<td>string</td>
@ -647,6 +642,25 @@ var options = {
<th>Description</th>
</tr>
<tr>
<td><a href="#Physics">physics</a></td>
<td>Object</td>
<td>none</td>
<td>
Configuration of the physics system governing the simulation of the nodes and edges.
Barnes-Hut nBody simulation is used by default. See section <a href="#Physics">Physics</a> for an overview of the available options.
</td>
</tr>
<tr>
<td><a href="#Data_manipulation">dataManipulation</a></td>
<td>Object</td>
<td>none</td>
<td>
Settings for manipulating the Dataset. See section <a href="#Data_manipulation">Data manipulation</a> for an overview of the available options.
</td>
</tr>
<tr>
<td><a href="#Clustering">clustering</a></td>
<td>Object</td>
@ -710,6 +724,13 @@ var options = {
</td>
</tr>
<tr>
<td>smoothCurves</td>
<td>Boolean</td>
<td>true</td>
<td>If true, edges are drawn as smooth curves. This is more computationally intensive since the edge now is a quadratic Bezier curve with control points on both nodes and an invisible node in the center of the edge. This support node is also handed by the physics simulation.</td>
</tr>
<tr>
<td>selectable</td>
<td>Boolean</td>
@ -964,12 +985,6 @@ var options = {
Only applicable when the line style is <code>dash-line</code>.</td>
</tr>
<tr>
<td>length</td>
<td>Number</td>
<td>100</td>
<td>The default length of a edge.</td>
</tr>
<tr>
<td>style</td>
<td>String</td>
@ -1122,6 +1137,236 @@ var nodes = [
</table>
<h3 id="Physics">Physics</h3>
<p>
The physics system has been overhauled to increase performance. The original simulation method was based on particel physics with a repulsion field (potential) around each node,
and the edges were modelled as springs. The new system employed the <a href="http://en.wikipedia.org/wiki/Barnes%E2%80%93Hut_simulation">Barnes-Hut</a> gravitational simulation model. The edges are still modelled as springs.
To unify the physics system, the damping, repulsion distance and edge length have been combined in an physics option. To retain good behaviour, both the old repulsion model and the Barnes-Hut model have their own parameters.
If no options for the physics system are supplied, the Barnes-Hut method will be used with the default parameters.
</p>
<pre class="prettyprint">
// These variables must be defined in an options object named physics.
// If a variable is not supplied, the default value is used.
var options = {
physics: {
barnesHut: {
enabled: true,
gravitationalConstant: -2000,
centralGravity: 0.1,
springLength: 100,
springConstant: 0.05,
damping: 0.09
},
repulsion: {
centralGravity: 0.1,
springLength: 50,
springConstant: 0.05,
nodeDistance: 100,
damping: 0.09
},
}
</pre>
<h5>barnesHut:</h5>
<table>
<tr>
<th>Name</th>
<th>Type</th>
<th>Default</th>
<th>Description</th>
</tr>
<tr>
<td>enabled</td>
<td>Boolean</td>
<td>true</td>
<td>This switches the Barnes-Hut simulation on or off. If it is turned off, the old repulsion model is used. Barnes-Hut is generally faster and yields better results.</td>
</tr>
<tr>
<td>gravitationalConstant</td>
<td>Number</td>
<td>-2000</td>
<td>This is the gravitational constand used to calculate the gravity forces. More information is available <a href="http://en.wikipedia.org/wiki/Newton's_law_of_universal_gravitation" target="_blank">here</a>.</td>
</tr>
<tr>
<td>centralGravity</td>
<td>Number</td>
<td>0.1</td>
<td>The central gravity is a force that pulls all nodes to the center. This ensures independent groups do not float apart.</td>
</tr>
<tr>
<td>springLength</td>
<td>Number</td>
<td>100</td>
<td>In the previous versions this was a property of the edges, called length. This is the length of the springs when they are at rest. During the simulation they will be streched by the gravitational fields.
To greatly reduce the edge length, the gravitationalConstant has to be reduced as well.</td>
</tr>
<tr>
<td>springConstant</td>
<td>Number</td>
<td>0.05</td>
<td>This is the spring constant used to calculate the spring forces based on Hooke&prime;s Law. More information is available <a href="http://en.wikipedia.org/wiki/Hooke's_law" target="_blank">here</a>.</td>
</tr>
<tr>
<td>damping</td>
<td>Number</td>
<td>0.09</td>
<td>This is the damping constant. It is used to dissipate energy from the system to have it settle in an equilibrium. More information is available <a href="http://en.wikipedia.org/wiki/Damping" target="_blank">here</a>.</td>
</tr>
</table>
<h5>repulsion:</h5>
<table>
<tr>
<th>Name</th>
<th>Type</th>
<th>Default</th>
<th>Description</th>
</tr>
<tr>
<td>centralGravity</td>
<td>Number</td>
<td>0.1</td>
<td>The central gravity is a force that pulls all nodes to the center. This ensures independent groups do not float apart.</td>
</tr>
<tr>
<td>springLength</td>
<td>Number</td>
<td>50</td>
<td>In the previous versions this was a property of the edges, called length. This is the length of the springs when they are at rest. During the simulation they will be streched by the gravitational fields.
To greatly reduce the edge length, the gravitationalConstant has to be reduced as well.</td>
</tr>
<tr>
<td>nodeDistance</td>
<td>Number</td>
<td>100</td>
<td>This parameter is used to define the distance of influence of the repulsion field of the nodes. Below half this distance, the repulsion is maximal and beyond twice this distance the repulsion is zero.</td>
</tr>
<tr>
<td>springConstant</td>
<td>Number</td>
<td>0.05</td>
<td>This is the spring constant used to calculate the spring forces based on Hooke&prime;s Law. More information is available <a href="http://en.wikipedia.org/wiki/Hooke's_law" target="_blank">here</a>.</td>
</tr>
<tr>
<td>damping</td>
<td>Number</td>
<td>0.09</td>
<td>This is the damping constant. It is used to dissipate energy from the system to have it settle in an equilibrium. More information is available <a href="http://en.wikipedia.org/wiki/Damping" target="_blank">here</a>.</td>
</tr>
</table>
<h3 id="Data_manipulation">Data manipulation</h3>
<p>
By using the data manipulation feature of the graph you can dynamically create nodes, connect nodes with edges, edit nodes or delete nodes and edges.
The toolbar is fully HTML and CSS so the user can style this to their preference. To control the behaviour of the data manipulation, users can insert custom functions
into the data manipulation process. For example, an injected function can show an detailed pop-up when a user wants to add a node. In <a href="../examples/graph/21_data_manipulation.html">example 21</a>,
two functions have been injected into the add and edit functionality. This is described in more detail in the next subsection.
</p>
<pre class="prettyprint">
// These variables must be defined in an options object named dataManipulation.
// If a variable is not supplied, the default value is used.
var options = {
dataManipulation: {
enabled: false,
initiallyVisible: false
}
}
// OR to just load the module with default values:
var options: {
dataManipulation: true
}
</pre>
<table>
<tr>
<th>Name</th>
<th>Type</th>
<th>Default</th>
<th>Description</th>
</tr>
<tr>
<td>enabled</td>
<td>Boolean</td>
<td>false</td>
<td>Enabling or disabling of the data manipulation toolbar. If it is initially hidden, an edit button appears in the top left corner.</td>
</tr>
<tr>
<td>initiallyVisible</td>
<td>Boolean</td>
<td>false</td>
<td>Initially hide or show the data manipulation toolbar.</td>
</tr>
</table>
<h4 id="Data_manipulation_custom">Data manipulation: custom functionality</h4>
<p>
Users can insert custom functions into the add node, edit node, connect nodes, and delete selected operations. This is done by supplying them in the options.
If the callback is NOT called, nothing happens. <a href="../examples/graph/21_data_manipulation.html">Example 21</a> has two working examples
for the add and edit functions. The data the user is supplied with in these functions has been described in the code below.
For the add data, you can add any and all options that are accepted for node creation as described above. The same goes for edit, however only the fields described
in the code below contain information on the selected node. The callback for connect accepts any options that are used for edge creation. Only the callback for delete selected
requires the same data structure that is supplied to the user. <br /><br />
<b>If there is no injected function supplied for the edit operation, the button will not be shown in the toolbar.</b>
</p>
<pre class="prettyprint">
// If a variable is not supplied, the default value is used.
var options: {
dataManipulation: true,
onAdd: function(data,callback) {
// fixed must be false because we define a set x and y position.
// If fixed is not false, the node cannot move.
/** data = {id: random unique id,
* label: new,
* x: x position of click (canvas space),
* y: y position of click (canvas space),
* fixed: false
* };
*/
var newData = {..}; // alter the data as you want.
// all fields normally accepted by a node can be used.
callback(newData); // call the callback to add a node.
},
onEdit: function(data,callback) {
/** data = {id:...,
* label: ...,
* group: ...,
* shape: ...,
* color: {
* background:...,
* border:...,
* highlight: {
* background:...,
* border:...
* }
* }
* };
*/
var newData = {..}; // alter the data as you want.
// all fields normally accepted by a node can be used.
callback(newData); // call the callback with the new data to edit the node.
}
onConnect: function(data,callback) {
// data = {from: nodeId1, to: nodeId2};
var newData = {..}; // check or alter data as you see fit.
callback(newData); // call the callback to connect the nodes.
},
onDelete: function(data,callback) {
// data = {nodes: [selectedNodeIds], edges: [selectedEdgeIds]};
var newData = {..}; // alter the data as you want.
// the same data structure is required.
callback(newData); // call the callback to delete the objects.
}
};
</pre>
<p>
Because the interface elements are CSS and HTML, the user will have to correct for size changes of the canvas. To facilitate this, a new event has been added called frameResize.
A function can be bound to this event. This function is supplied with the new widht and height of the canvas. The CSS can then be updated accordingly.
An code snippet from example 21 is shown below.
</p>
<pre class="prettyprint">
graph.on("frameResize", function(params) {console.log(params.width,params.height)});
</pre>
<h3 id="Clustering">Clustering</h3>
<p>
The graph now supports dynamic clustering of nodes. This allows a user to view a very large dataset (> 50.000 nodes) without
@ -1150,16 +1395,19 @@ var options = {
reduceToNodes:300,
chainThreshold: 0.4,
clusterEdgeThreshold: 20,
sectorThreshold: 50,
sectorThreshold: 100,
screenSizeThreshold: 0.2,
fontSizeMultiplier: 4.0,
forceAmplification: 0.6,
distanceAmplification: 0.2,
edgeGrowth: 11,
nodeScaling: {width: 10,
height: 10,
radius: 10},
activeAreaBoxSize: 100
maxFontSize: 1000,
forceAmplification: 0.1,
distanceAmplification: 0.1,
edgeGrowth: 20,
nodeScaling: {width: 1,
height: 1,
radius: 1},
maxNodeSizeIncrements: 600,
activeAreaBoxSize: 100,
clusterLevelDifference: 2
}
}
// OR to just load the module with default values:
@ -1233,6 +1481,12 @@ var options: {
<td>4.0</td>
<td>This parameter denotes the increase in fontSize of the cluster when a single node is added to it.</td>
</tr>
<tr>
<td>maxFontSize</td>
<td>Number</td>
<td>1000</td>
<td>This parameter denotes the largest allowed font size. If the font becomes too large, some browsers experience problems displaying this.</td>
</tr>
<tr>
<td>forceAmplification</td>
<td>Number</td>
@ -1251,7 +1505,7 @@ var options: {
<tr>
<td>edgeGrowth</td>
<td>Number</td>
<td>11</td>
<td>20</td>
<td>This factor determines the elongation of edges connected to a cluster.</td>
</tr>
<tr>
@ -1272,13 +1526,29 @@ var options: {
<td>10</td>
<td>This factor determines how much the radius of a cluster increases in pixels per added node.</td>
</tr>
<tr>
<td>activeAreaBoxSize</td>
<tr>
<td>maxNodeSizeIncrements</td>
<td>Number</td>
<td>600</td>
<td>This limits the size clusters can grow to. The default value, 600, implies that if a cluster contains more than 600 nodes, it will no longer grow.</td>
</tr>
<tr>
<td>activeAreaBoxSize</td>
<td>Number</td>
<td>100</td>
<td>Imagine a square with an edge length of <code>activeAreaBoxSize</code> pixels around your cursor.
If a cluster is in this box as you zoom in, the cluster can be opened in a seperate sector.
This is regardless of the zoom level.</td>
</tr>
<tr>
<td>clusterLevelDifference</td>
<td>Number</td>
<td>100</td>
<td>Imagine a square with an edge length of <code>activeAreaBoxSize</code> pixels around your cursor.
If a cluster is in this box as you zoom in, the cluster can be opened in a seperate sector.
This is regardless of the zoom level.</td>
<td>2</td>
<td>At every clustering session, Graph will check if the difference between cluster levels is
acceptable. When a cluster is formed when zooming out, that is one cluster level.
If you zoom out further and it encompasses more nodes, that is another level. For example:
If the highest level of your graph at any given time is 3, nodes that have not clustered or
have clustered only once will join their neighbour with the lowest cluster level.</td>
</tr>
</table>

BIN
download/vis.zip View File


+ 1
- 2
examples/graph/02_random_nodes.html View File

@ -57,6 +57,7 @@
j++;
}
var from = i;
var to = j;
edges.push({
@ -87,7 +88,6 @@
*/
var options = {
edges: {
length: 50
},
stabilize: false
};
@ -102,7 +102,6 @@
</head>
<body onload="draw();">
<form onsubmit="draw(); return false;">
<label for="nodeCount">Number of nodes:</label>
<input id="nodeCount" type="text" value="25" style="width: 50px;">

+ 0
- 3
examples/graph/19_scale_free_graph_clustering.html View File

@ -88,9 +88,6 @@
};
*/
var options = {
edges: {
length: 50
},
clustering: {
enabled: clusteringOn,
clusterEdgeThreshold: clusterEdgeThreshold

+ 6
- 5
examples/graph/20_navigation.html View File

@ -34,6 +34,7 @@
</style>
<script type="text/javascript" src="../../dist/vis.js"></script>
<link type="text/css" rel="stylesheet" charset="UTF-8" href="../../dist/css/graph-navigation.css">
<script type="text/javascript">
var nodes = null;
@ -125,14 +126,14 @@
<body onload="draw();">
<h2>Navigation controls and keyboad navigation</h2>
<div style="width: 700px; font-size:14px;">
This example is the same as example 2, except for the navigation controls that has been activated. The navigation controls are described below. <br /><br />
This example is the same as example 2, except for the navigation controls that have been activated. The navigation controls are described below. <br /><br />
<table class="legend_table">
<tr>
<td>Icons: </td>
<td><div class="table_content"><img src="../../dist/img/uparrow.png" /> </div></td>
<td><div class="table_content"><img src="../../dist/img/downarrow.png" /> </div></td>
<td><div class="table_content"><img src="../../dist/img/leftarrow.png" /> </div></td>
<td><div class="table_content"><img src="../../dist/img/rightarrow.png" /> </div></td>
<td><div class="table_content"><img src="../../dist/img/upArrow.png" /> </div></td>
<td><div class="table_content"><img src="../../dist/img/downArrow.png" /> </div></td>
<td><div class="table_content"><img src="../../dist/img/leftArrow.png" /> </div></td>
<td><div class="table_content"><img src="../../dist/img/rightArrow.png" /> </div></td>
<td><div class="table_content"><img src="../../dist/img/plus.png" /> </div></td>
<td><div class="table_content"><img src="../../dist/img/minus.png" /> </div></td>
<td><div class="table_content"><img src="../../dist/img/zoomExtends.png" /> </div></td>

+ 214
- 0
examples/graph/21_data_manipulation.html View File

@ -0,0 +1,214 @@
<!doctype html>
<html>
<head>
<title>Graph | Navigation</title>
<style type="text/css">
body {
font: 10pt sans;
}
#mygraph {
position:relative;
width: 600px;
height: 600px;
border: 1px solid lightgray;
}
table.legend_table {
font-size: 11px;
border-width:1px;
border-color:#d3d3d3;
border-style:solid;
}
table.legend_table,td {
border-width:1px;
border-color:#d3d3d3;
border-style:solid;
padding: 2px;
}
div.table_content {
width:80px;
text-align:center;
}
div.table_description {
width:100px;
}
#operation {
font-size:28px;
}
#graph-popUp {
display:none;
position:absolute;
top:350px;
left:170px;
z-index:299;
width:250px;
height:120px;
background-color: #f9f9f9;
border-style:solid;
border-width:3px;
border-color: #5394ed;
padding:10px;
text-align: center;
}
</style>
<link type="text/css" rel="stylesheet" charset="UTF-8" href="../../dist/css/graph-manipulation.css">
<link type="text/css" rel="stylesheet" charset="UTF-8" href="../../dist/css/graph-navigation.css">
<script type="text/javascript" src="../../dist/vis.js"></script>
<script type="text/javascript">
var nodes = null;
var edges = null;
var graph = null;
function draw() {
nodes = [];
edges = [];
var connectionCount = [];
// randomly create some nodes and edges
var nodeCount = 25;
for (var i = 0; i < nodeCount; i++) {
nodes.push({
id: i,
label: String(i)
});
connectionCount[i] = 0;
// create edges in a scale-free-graph way
if (i == 1) {
var from = i;
var to = 0;
edges.push({
from: from,
to: to
});
connectionCount[from]++;
connectionCount[to]++;
}
else if (i > 1) {
var conn = edges.length * 2;
var rand = Math.floor(Math.random() * conn);
var cum = 0;
var j = 0;
while (j < connectionCount.length && cum < rand) {
cum += connectionCount[j];
j++;
}
var from = i;
var to = j;
edges.push({
from: from,
to: to
});
connectionCount[from]++;
connectionCount[to]++;
}
}
// create a graph
var container = document.getElementById('mygraph');
var data = {
nodes: nodes,
edges: edges
};
var options = {
edges: {
length: 50
},
stabilize: false,
dataManipulation: true,
onAdd: function(data,callback) {
var span = document.getElementById('operation');
var idInput = document.getElementById('node-id');
var labelInput = document.getElementById('node-label');
var saveButton = document.getElementById('saveButton');
var cancelButton = document.getElementById('cancelButton');
var div = document.getElementById('graph-popUp');
span.innerHTML = "Add Node";
idInput.value = data.id;
labelInput.value = data.label;
saveButton.onclick = saveData.bind(this,data,callback);
cancelButton.onclick = clearPopUp.bind();
div.style.display = 'block';
},
onEdit: function(data,callback) {
var span = document.getElementById('operation');
var idInput = document.getElementById('node-id');
var labelInput = document.getElementById('node-label');
var saveButton = document.getElementById('saveButton');
var cancelButton = document.getElementById('cancelButton');
var div = document.getElementById('graph-popUp');
span.innerHTML = "Edit Node";
idInput.value = data.id;
labelInput.value = data.label;
saveButton.onclick = saveData.bind(this,data,callback);
cancelButton.onclick = clearPopUp.bind();
div.style.display = 'block';
}
};
graph = new vis.Graph(container, data, options);
// add event listeners
graph.on('select', function(params) {
document.getElementById('selection').innerHTML = 'Selection: ' + params.nodes;
});
graph.on("frameResize", function(params) {console.log(params.width,params.height)});
function clearPopUp() {
var saveButton = document.getElementById('saveButton');
var cancelButton = document.getElementById('cancelButton');
saveButton.onclick = null;
cancelButton.onclick = null;
var div = document.getElementById('graph-popUp');
div.style.display = 'none';
}
function saveData(data,callback) {
var idInput = document.getElementById('node-id');
var labelInput = document.getElementById('node-label');
var div = document.getElementById('graph-popUp');
data.id = idInput.value;
data.label = labelInput.value;
clearPopUp();
callback(data);
}
}
</script>
</head>
<body onload="draw();">
<h2>Editing the dataset</h2>
<div style="width: 700px; font-size:14px;">
In this example we have enabled the data manipulation setting. If the dataManipulation option is set to true, the edit button will appear.
If you prefer to have the toolbar visible initially, you can set the initiallyVisible option to true. The exact method is described in the docs.
<br /><br />
The data manipulation allows the user to add nodes, connect them, edit them and delete any selected items. In this example we have created trigger functions
for the add and edit operations. By settings these trigger functions the user can direct the way the data is manipulated. In this example we have created a simple
pop-up that allows us to edit some of the properties.
</div>
<br />
<div id="graph-popUp">
<span id="operation">node</span> <br>
<table style="margin:auto;"><tr>
<td>id</td><td><input id="node-id" value="new value"></td>
</tr>
<tr>
<td>label</td><td><input id="node-label" value="new value"> </td>
</tr></table>
<input type="button" value="save" id="saveButton"></button>
<input type="button" value="cancel" id="cancelButton"></button>
</div>
<br />
<div id="mygraph"></div>
<p id="selection"></p>
</body>
</html>

+ 373
- 0
examples/graph/22_les_miserables.html View File

@ -0,0 +1,373 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Graph | Multiline text</title>
<style type="text/css">
#mygraph {
width: 900px;
height: 900px;
border: 1px solid lightgray;
}
</style>
<script type="text/javascript" src="../../dist/vis.js"></script>
<script type="text/javascript">
function draw() {
// create some nodes
var nodes = [
{id:0,"labelIGNORE":"Myriel","group":1},
{id:1,"labelIGNORE":"Napoleon","group":1},
{id:2,"labelIGNORE":"Mlle.Baptistine","group":1},
{id:3,"labelIGNORE":"Mme.Magloire","group":1},
{id:4,"labelIGNORE":"CountessdeLo","group":1},
{id:5,"labelIGNORE":"Geborand","group":1},
{id:6,"labelIGNORE":"Champtercier","group":1},
{id:7,"labelIGNORE":"Cravatte","group":1},
{id:8,"labelIGNORE":"Count","group":1},
{id:9,"labelIGNORE":"OldMan","group":1},
{id:10,"labelIGNORE":"Labarre","group":2},
{id:11,"labelIGNORE":"Valjean","group":2},
{id:12,"labelIGNORE":"Marguerite","group":3},
{id:13,"labelIGNORE":"Mme.deR","group":2},
{id:14,"labelIGNORE":"Isabeau","group":2},
{id:15,"labelIGNORE":"Gervais","group":2},
{id:16,"labelIGNORE":"Tholomyes","group":3},
{id:17,"labelIGNORE":"Listolier","group":3},
{id:18,"labelIGNORE":"Fameuil","group":3},
{id:19,"labelIGNORE":"Blacheville","group":3},
{id:20,"labelIGNORE":"Favourite","group":3},
{id:21,"labelIGNORE":"Dahlia","group":3},
{id:22,"labelIGNORE":"Zephine","group":3},
{id:23,"labelIGNORE":"Fantine","group":3},
{id:24,"labelIGNORE":"Mme.Thenardier","group":4},
{id:25,"labelIGNORE":"Thenardier","group":4},
{id:26,"labelIGNORE":"Cosette","group":5},
{id:27,"labelIGNORE":"Javert","group":4},
{id:28,"labelIGNORE":"Fauchelevent","group":0},
{id:29,"labelIGNORE":"Bamatabois","group":2},
{id:30,"labelIGNORE":"Perpetue","group":3},
{id:31,"labelIGNORE":"Simplice","group":2},
{id:32,"labelIGNORE":"Scaufflaire","group":2},
{id:33,"labelIGNORE":"Woman1","group":2},
{id:34,"labelIGNORE":"Judge","group":2},
{id:35,"labelIGNORE":"Champmathieu","group":2},
{id:36,"labelIGNORE":"Brevet","group":2},
{id:37,"labelIGNORE":"Chenildieu","group":2},
{id:38,"labelIGNORE":"Cochepaille","group":2},
{id:39,"labelIGNORE":"Pontmercy","group":4},
{id:40,"labelIGNORE":"Boulatruelle","group":6},
{id:41,"labelIGNORE":"Eponine","group":4},
{id:42,"labelIGNORE":"Anzelma","group":4},
{id:43,"labelIGNORE":"Woman2","group":5},
{id:44,"labelIGNORE":"MotherInnocent","group":0},
{id:45,"labelIGNORE":"Gribier","group":0},
{id:46,"labelIGNORE":"Jondrette","group":7},
{id:47,"labelIGNORE":"Mme.Burgon","group":7},
{id:48,"labelIGNORE":"Gavroche","group":8},
{id:49,"labelIGNORE":"Gillenormand","group":5},
{id:50,"labelIGNORE":"Magnon","group":5},
{id:51,"labelIGNORE":"Mlle.Gillenormand","group":5},
{id:52,"labelIGNORE":"Mme.Pontmercy","group":5},
{id:53,"labelIGNORE":"Mlle.Vaubois","group":5},
{id:54,"labelIGNORE":"Lt.Gillenormand","group":5},
{id:55,"labelIGNORE":"Marius","group":8},
{id:56,"labelIGNORE":"BaronessT","group":5},
{id:57,"labelIGNORE":"Mabeuf","group":8},
{id:58,"labelIGNORE":"Enjolras","group":8},
{id:59,"labelIGNORE":"Combeferre","group":8},
{id:60,"labelIGNORE":"Prouvaire","group":8},
{id:61,"labelIGNORE":"Feuilly","group":8},
{id:62,"labelIGNORE":"Courfeyrac","group":8},
{id:63,"labelIGNORE":"Bahorel","group":8},
{id:64,"labelIGNORE":"Bossuet","group":8},
{id:65,"labelIGNORE":"Joly","group":8},
{id:66,"labelIGNORE":"Grantaire","group":8},
{id:67,"labelIGNORE":"MotherPlutarch","group":9},
{id:68,"labelIGNORE":"Gueulemer","group":4},
{id:69,"labelIGNORE":"Babet","group":4},
{id:70,"labelIGNORE":"Claquesous","group":4},
{id:71,"labelIGNORE":"Montparnasse","group":4},
{id:72,"labelIGNORE":"Toussaint","group":5},
{id:73,"labelIGNORE":"Child1","group":10},
{id:74,"labelIGNORE":"Child2","group":10},
{id:75,"labelIGNORE":"Brujon","group":4},
{id:76,"labelIGNORE":"Mme.Hucheloup","group":8}
];
// create some edges
var edges = [
{"from":1,"to":0,"valueIGNORED":1},
{"from":2,"to":0,"valueIGNORED":8},
{"from":3,"to":0,"valueIGNORED":10},
{"from":3,"to":2,"valueIGNORED":6},
{"from":4,"to":0,"valueIGNORED":1},
{"from":5,"to":0,"valueIGNORED":1},
{"from":6,"to":0,"valueIGNORED":1},
{"from":7,"to":0,"valueIGNORED":1},
{"from":8,"to":0,"valueIGNORED":2},
{"from":9,"to":0,"valueIGNORED":1},
{"from":11,"to":10,"valueIGNORED":1},
{"from":11,"to":3,"valueIGNORED":3},
{"from":11,"to":2,"valueIGNORED":3},
{"from":11,"to":0,"valueIGNORED":5},
{"from":12,"to":11,"valueIGNORED":1},
{"from":13,"to":11,"valueIGNORED":1},
{"from":14,"to":11,"valueIGNORED":1},
{"from":15,"to":11,"valueIGNORED":1},
{"from":17,"to":16,"valueIGNORED":4},
{"from":18,"to":16,"valueIGNORED":4},
{"from":18,"to":17,"valueIGNORED":4},
{"from":19,"to":16,"valueIGNORED":4},
{"from":19,"to":17,"valueIGNORED":4},
{"from":19,"to":18,"valueIGNORED":4},
{"from":20,"to":16,"valueIGNORED":3},
{"from":20,"to":17,"valueIGNORED":3},
{"from":20,"to":18,"valueIGNORED":3},
{"from":20,"to":19,"valueIGNORED":4},
{"from":21,"to":16,"valueIGNORED":3},
{"from":21,"to":17,"valueIGNORED":3},
{"from":21,"to":18,"valueIGNORED":3},
{"from":21,"to":19,"valueIGNORED":3},
{"from":21,"to":20,"valueIGNORED":5},
{"from":22,"to":16,"valueIGNORED":3},
{"from":22,"to":17,"valueIGNORED":3},
{"from":22,"to":18,"valueIGNORED":3},
{"from":22,"to":19,"valueIGNORED":3},
{"from":22,"to":20,"valueIGNORED":4},
{"from":22,"to":21,"valueIGNORED":4},
{"from":23,"to":16,"valueIGNORED":3},
{"from":23,"to":17,"valueIGNORED":3},
{"from":23,"to":18,"valueIGNORED":3},
{"from":23,"to":19,"valueIGNORED":3},
{"from":23,"to":20,"valueIGNORED":4},
{"from":23,"to":21,"valueIGNORED":4},
{"from":23,"to":22,"valueIGNORED":4},
{"from":23,"to":12,"valueIGNORED":2},
{"from":23,"to":11,"valueIGNORED":9},
{"from":24,"to":23,"valueIGNORED":2},
{"from":24,"to":11,"valueIGNORED":7},
{"from":25,"to":24,"valueIGNORED":13},
{"from":25,"to":23,"valueIGNORED":1},
{"from":25,"to":11,"valueIGNORED":12},
{"from":26,"to":24,"valueIGNORED":4},
{"from":26,"to":11,"valueIGNORED":31},
{"from":26,"to":16,"valueIGNORED":1},
{"from":26,"to":25,"valueIGNORED":1},
{"from":27,"to":11,"valueIGNORED":17},
{"from":27,"to":23,"valueIGNORED":5},
{"from":27,"to":25,"valueIGNORED":5},
{"from":27,"to":24,"valueIGNORED":1},
{"from":27,"to":26,"valueIGNORED":1},
{"from":28,"to":11,"valueIGNORED":8},
{"from":28,"to":27,"valueIGNORED":1},
{"from":29,"to":23,"valueIGNORED":1},
{"from":29,"to":27,"valueIGNORED":1},
{"from":29,"to":11,"valueIGNORED":2},
{"from":30,"to":23,"valueIGNORED":1},
{"from":31,"to":30,"valueIGNORED":2},
{"from":31,"to":11,"valueIGNORED":3},
{"from":31,"to":23,"valueIGNORED":2},
{"from":31,"to":27,"valueIGNORED":1},
{"from":32,"to":11,"valueIGNORED":1},
{"from":33,"to":11,"valueIGNORED":2},
{"from":33,"to":27,"valueIGNORED":1},
{"from":34,"to":11,"valueIGNORED":3},
{"from":34,"to":29,"valueIGNORED":2},
{"from":35,"to":11,"valueIGNORED":3},
{"from":35,"to":34,"valueIGNORED":3},
{"from":35,"to":29,"valueIGNORED":2},
{"from":36,"to":34,"valueIGNORED":2},
{"from":36,"to":35,"valueIGNORED":2},
{"from":36,"to":11,"valueIGNORED":2},
{"from":36,"to":29,"valueIGNORED":1},
{"from":37,"to":34,"valueIGNORED":2},
{"from":37,"to":35,"valueIGNORED":2},
{"from":37,"to":36,"valueIGNORED":2},
{"from":37,"to":11,"valueIGNORED":2},
{"from":37,"to":29,"valueIGNORED":1},
{"from":38,"to":34,"valueIGNORED":2},
{"from":38,"to":35,"valueIGNORED":2},
{"from":38,"to":36,"valueIGNORED":2},
{"from":38,"to":37,"valueIGNORED":2},
{"from":38,"to":11,"valueIGNORED":2},
{"from":38,"to":29,"valueIGNORED":1},
{"from":39,"to":25,"valueIGNORED":1},
{"from":40,"to":25,"valueIGNORED":1},
{"from":41,"to":24,"valueIGNORED":2},
{"from":41,"to":25,"valueIGNORED":3},
{"from":42,"to":41,"valueIGNORED":2},
{"from":42,"to":25,"valueIGNORED":2},
{"from":42,"to":24,"valueIGNORED":1},
{"from":43,"to":11,"valueIGNORED":3},
{"from":43,"to":26,"valueIGNORED":1},
{"from":43,"to":27,"valueIGNORED":1},
{"from":44,"to":28,"valueIGNORED":3},
{"from":44,"to":11,"valueIGNORED":1},
{"from":45,"to":28,"valueIGNORED":2},
{"from":47,"to":46,"valueIGNORED":1},
{"from":48,"to":47,"valueIGNORED":2},
{"from":48,"to":25,"valueIGNORED":1},
{"from":48,"to":27,"valueIGNORED":1},
{"from":48,"to":11,"valueIGNORED":1},
{"from":49,"to":26,"valueIGNORED":3},
{"from":49,"to":11,"valueIGNORED":2},
{"from":50,"to":49,"valueIGNORED":1},
{"from":50,"to":24,"valueIGNORED":1},
{"from":51,"to":49,"valueIGNORED":9},
{"from":51,"to":26,"valueIGNORED":2},
{"from":51,"to":11,"valueIGNORED":2},
{"from":52,"to":51,"valueIGNORED":1},
{"from":52,"to":39,"valueIGNORED":1},
{"from":53,"to":51,"valueIGNORED":1},
{"from":54,"to":51,"valueIGNORED":2},
{"from":54,"to":49,"valueIGNORED":1},
{"from":54,"to":26,"valueIGNORED":1},
{"from":55,"to":51,"valueIGNORED":6},
{"from":55,"to":49,"valueIGNORED":12},
{"from":55,"to":39,"valueIGNORED":1},
{"from":55,"to":54,"valueIGNORED":1},
{"from":55,"to":26,"valueIGNORED":21},
{"from":55,"to":11,"valueIGNORED":19},
{"from":55,"to":16,"valueIGNORED":1},
{"from":55,"to":25,"valueIGNORED":2},
{"from":55,"to":41,"valueIGNORED":5},
{"from":55,"to":48,"valueIGNORED":4},
{"from":56,"to":49,"valueIGNORED":1},
{"from":56,"to":55,"valueIGNORED":1},
{"from":57,"to":55,"valueIGNORED":1},
{"from":57,"to":41,"valueIGNORED":1},
{"from":57,"to":48,"valueIGNORED":1},
{"from":58,"to":55,"valueIGNORED":7},
{"from":58,"to":48,"valueIGNORED":7},
{"from":58,"to":27,"valueIGNORED":6},
{"from":58,"to":57,"valueIGNORED":1},
{"from":58,"to":11,"valueIGNORED":4},
{"from":59,"to":58,"valueIGNORED":15},
{"from":59,"to":55,"valueIGNORED":5},
{"from":59,"to":48,"valueIGNORED":6},
{"from":59,"to":57,"valueIGNORED":2},
{"from":60,"to":48,"valueIGNORED":1},
{"from":60,"to":58,"valueIGNORED":4},
{"from":60,"to":59,"valueIGNORED":2},
{"from":61,"to":48,"valueIGNORED":2},
{"from":61,"to":58,"valueIGNORED":6},
{"from":61,"to":60,"valueIGNORED":2},
{"from":61,"to":59,"valueIGNORED":5},
{"from":61,"to":57,"valueIGNORED":1},
{"from":61,"to":55,"valueIGNORED":1},
{"from":62,"to":55,"valueIGNORED":9},
{"from":62,"to":58,"valueIGNORED":17},
{"from":62,"to":59,"valueIGNORED":13},
{"from":62,"to":48,"valueIGNORED":7},
{"from":62,"to":57,"valueIGNORED":2},
{"from":62,"to":41,"valueIGNORED":1},
{"from":62,"to":61,"valueIGNORED":6},
{"from":62,"to":60,"valueIGNORED":3},
{"from":63,"to":59,"valueIGNORED":5},
{"from":63,"to":48,"valueIGNORED":5},
{"from":63,"to":62,"valueIGNORED":6},
{"from":63,"to":57,"valueIGNORED":2},
{"from":63,"to":58,"valueIGNORED":4},
{"from":63,"to":61,"valueIGNORED":3},
{"from":63,"to":60,"valueIGNORED":2},
{"from":63,"to":55,"valueIGNORED":1},
{"from":64,"to":55,"valueIGNORED":5},
{"from":64,"to":62,"valueIGNORED":12},
{"from":64,"to":48,"valueIGNORED":5},
{"from":64,"to":63,"valueIGNORED":4},
{"from":64,"to":58,"valueIGNORED":10},
{"from":64,"to":61,"valueIGNORED":6},
{"from":64,"to":60,"valueIGNORED":2},
{"from":64,"to":59,"valueIGNORED":9},
{"from":64,"to":57,"valueIGNORED":1},
{"from":64,"to":11,"valueIGNORED":1},
{"from":65,"to":63,"valueIGNORED":5},
{"from":65,"to":64,"valueIGNORED":7},
{"from":65,"to":48,"valueIGNORED":3},
{"from":65,"to":62,"valueIGNORED":5},
{"from":65,"to":58,"valueIGNORED":5},
{"from":65,"to":61,"valueIGNORED":5},
{"from":65,"to":60,"valueIGNORED":2},
{"from":65,"to":59,"valueIGNORED":5},
{"from":65,"to":57,"valueIGNORED":1},
{"from":65,"to":55,"valueIGNORED":2},
{"from":66,"to":64,"valueIGNORED":3},
{"from":66,"to":58,"valueIGNORED":3},
{"from":66,"to":59,"valueIGNORED":1},
{"from":66,"to":62,"valueIGNORED":2},
{"from":66,"to":65,"valueIGNORED":2},
{"from":66,"to":48,"valueIGNORED":1},
{"from":66,"to":63,"valueIGNORED":1},
{"from":66,"to":61,"valueIGNORED":1},
{"from":66,"to":60,"valueIGNORED":1},
{"from":67,"to":57,"valueIGNORED":3},
{"from":68,"to":25,"valueIGNORED":5},
{"from":68,"to":11,"valueIGNORED":1},
{"from":68,"to":24,"valueIGNORED":1},
{"from":68,"to":27,"valueIGNORED":1},
{"from":68,"to":48,"valueIGNORED":1},
{"from":68,"to":41,"valueIGNORED":1},
{"from":69,"to":25,"valueIGNORED":6},
{"from":69,"to":68,"valueIGNORED":6},
{"from":69,"to":11,"valueIGNORED":1},
{"from":69,"to":24,"valueIGNORED":1},
{"from":69,"to":27,"valueIGNORED":2},
{"from":69,"to":48,"valueIGNORED":1},
{"from":69,"to":41,"valueIGNORED":1},
{"from":70,"to":25,"valueIGNORED":4},
{"from":70,"to":69,"valueIGNORED":4},
{"from":70,"to":68,"valueIGNORED":4},
{"from":70,"to":11,"valueIGNORED":1},
{"from":70,"to":24,"valueIGNORED":1},
{"from":70,"to":27,"valueIGNORED":1},
{"from":70,"to":41,"valueIGNORED":1},
{"from":70,"to":58,"valueIGNORED":1},
{"from":71,"to":27,"valueIGNORED":1},
{"from":71,"to":69,"valueIGNORED":2},
{"from":71,"to":68,"valueIGNORED":2},
{"from":71,"to":70,"valueIGNORED":2},
{"from":71,"to":11,"valueIGNORED":1},
{"from":71,"to":48,"valueIGNORED":1},
{"from":71,"to":41,"valueIGNORED":1},
{"from":71,"to":25,"valueIGNORED":1},
{"from":72,"to":26,"valueIGNORED":2},
{"from":72,"to":27,"valueIGNORED":1},
{"from":72,"to":11,"valueIGNORED":1},
{"from":73,"to":48,"valueIGNORED":2},
{"from":74,"to":48,"valueIGNORED":2},
{"from":74,"to":73,"valueIGNORED":3},
{"from":75,"to":69,"valueIGNORED":3},
{"from":75,"to":68,"valueIGNORED":3},
{"from":75,"to":25,"valueIGNORED":3},
{"from":75,"to":48,"valueIGNORED":1},
{"from":75,"to":41,"valueIGNORED":1},
{"from":75,"to":70,"valueIGNORED":1},
{"from":75,"to":71,"valueIGNORED":1},
{"from":76,"to":64,"valueIGNORED":1},
{"from":76,"to":65,"valueIGNORED":1},
{"from":76,"to":66,"valueIGNORED":1},
{"from":76,"to":63,"valueIGNORED":1},
{"from":76,"to":62,"valueIGNORED":1},
{"from":76,"to":48,"valueIGNORED":1},
{"from":76,"to":58,"valueIGNORED":1}
];
// create a graph
var container = document.getElementById('mygraph');
var data = {
nodes: nodes,
edges: edges
};
var options = {nodes: {shape:'circle'},stabilize: false};
var graph = new vis.Graph(container, data, options);
}
</script>
</head>
<body onload="draw()">
<div id="mygraph"></div>
</body>
</html>

+ 1
- 0
examples/graph/index.html View File

@ -33,6 +33,7 @@
<p><a href="19_scale_free_graph_clustering.html">19_scale_free_graph_clustering.html</a></p>
<p><a href="20_navigation.html">20_navigation.html</a></p>
<p><a href="21_data_manipulation.html">21_data_manipulation.html</a></p>
<p><a href="22_les_miserables.html">22_les_miserables.html</a></p>
<p><a href="graphviz/graphviz_gallery.html">graphviz_gallery.html</a></p>
</div>

BIN
favicon.ico View File

Before After

BIN
img/external-link-icons/external-link-icon.png View File

Before After
Width: 9  |  Height: 9  |  Size: 194 B

+ 40
- 0
img/external-link-icons/license.txt View File

@ -0,0 +1,40 @@
/**************************************\
Shapes4FREE License
http://www.shapes4free.com/ - visit us to get free photoshop shapes, read our easy-to-understand shapes tutorials and tips, and view beautiful examples of using shapes in all kinds of design
More about the license: http://www.shapes4free.com/license/
\**************************************/
This resource was created by Oksana Khristenko
This resource has been downloaded from Shapes4FREE.com and is free for personal or commercial projects. You may use it for web and print design.
No attribution or backlinks are required, but we would certainly appreciate it if you bookmarked www.shapes4free.com and shared the link to it with your friends:
www.shapes4free.com - free photoshop shapes
You may not resell or distribute this resource. Uploading it to another website
or offering them for download on another website is not allowed. If you would like to feature this resource on
your website or share them with friends, do not link directly to the resource files,
please link to the appropriate page on Shapes4FREE.com where it is possible to download the freebie.
/**************************************\
Shapes4FREE Ëèöåíçèÿ
http://www.shapes4free.com/ - áåñïëàòíûå ôèãóðû äëÿ Ôîòîøîïà, óðîêè è ïîäñêàçêè, à òàêæå êðàñèâûå ïðèìåðû èñïîëüçîâàíèÿ ôèãóð âî âñåõ âèäàõ äèçàéíà
Ëèöåíçèÿ: http://www.shapes4free.com/license-ru/
\**************************************/
Àâòîð: Îêñàíà Õðèñòåíêî
Âñå áåñïëàòíûå ðåñóðñû êîòîðûå ìîæíî ñêà÷àòü íà Shapes4FREE.com, âêëþ÷àÿ ïðîèçâîëüíûå ôèãóðû äëÿ Ôîòîøîïà
(photoshop custom shapes) áåñïëàòíû äëÿ èñïîëüçîâàíèÿ â ëè÷íûõ è êîììåð÷åñêèõ ïðîåêòàõ. Ðàçðåøåíî èñïîëüçîâàòü
áåñïëàòíûå ðåñóðñû Shapes4FREE â âåá äèçàéíå è ïå÷àòíûõ ìàòåðèàëàõ.
Ññûëêà íà ñàéò Shapes4FREE.com íå òðåáóåòñÿ íî ïðèâåòñòâóåòñÿ. Ìû áóäåì ðàäû åñëè âû ðàññêàæåòå î íàñ äðóçüÿì:
www.shapes4free.com - áåñïëàòíûå ôèãóðû äëÿ Ôîòîøîïà
Çàïðåùåíî ïðîäàâàòü èëè ðàñïðîñòðàíÿòü áåñïëàòíûå ðåñóðñû ñîçäàííûå Shapes4FREE.
Çàïðåùåíî çàãðóæàòü èõ íà äðóãèå ñàéòû è ïîçâîëÿòü ïîëüçîâàòåëÿì èõ ñêà÷èâàòü. Åñëè âû õîòèòå ðàññêàçàòü î íàøåì
áåñïëàòíîì ðåñóðñå íà ñàéòå èëè ïîäåëèòüñÿ ñ äðóçüÿìè, íå ñîçäàâàéòå ïðÿìûõ ññûëîê íà ôàéë, ñîçäàéòå ññûëêó íà
ñîîòâåòñòâóþùóþ ñòðàíèöó ñàéòà Shapes4FREE.com ãäå ìîæíî áóäåò ñêà÷àòü ýòîò ðåñóðñ.

BIN
img/forkme_right_darkblue_121621.png View File

Before After
Width: 149  |  Height: 149  |  Size: 7.6 KiB

BIN
img/gallery/graph/01_basic_usage.png View File

Before After
Width: 400  |  Height: 400  |  Size: 21 KiB

BIN
img/gallery/graph/02_random_nodes.png View File

Before After
Width: 600  |  Height: 600  |  Size: 58 KiB

BIN
img/gallery/graph/03_images.png View File

Before After
Width: 600  |  Height: 600  |  Size: 88 KiB

BIN
img/gallery/graph/04_shapes.png View File

Before After
Width: 797  |  Height: 600  |  Size: 50 KiB

BIN
img/gallery/graph/05_social_network.png View File

Before After
Width: 600  |  Height: 600  |  Size: 76 KiB

BIN
img/gallery/graph/06_groups.png View File

Before After
Width: 600  |  Height: 600  |  Size: 40 KiB

BIN
img/gallery/graph/07_selections.png View File

Before After
Width: 400  |  Height: 400  |  Size: 14 KiB

BIN
img/gallery/graph/08_mobile_friendly.png View File

Before After
Width: 605  |  Height: 551  |  Size: 45 KiB

BIN
img/gallery/graph/09_sizing.png View File

Before After
Width: 600  |  Height: 600  |  Size: 27 KiB

BIN
img/gallery/graph/10_multiline_text.png View File

Before After
Width: 600  |  Height: 600  |  Size: 40 KiB

BIN
img/gallery/graph/11_custom_style.png View File

Before After
Width: 600  |  Height: 600  |  Size: 59 KiB

BIN
img/gallery/graph/12_scalable_images.png View File

Before After
Width: 600  |  Height: 600  |  Size: 104 KiB

BIN
img/gallery/graph/13_dashed_lines.png View File

Before After
Width: 600  |  Height: 600  |  Size: 26 KiB

BIN
img/gallery/graph/14_dot_language.png View File

Before After
Width: 381  |  Height: 330  |  Size: 13 KiB

BIN
img/gallery/graph/15_dot_language_playground.png View File

Before After
Width: 429  |  Height: 384  |  Size: 31 KiB

BIN
img/gallery/graph/15_dot_language_playground2.png View File

Before After
Width: 842  |  Height: 595  |  Size: 41 KiB

BIN
img/gallery/graph/16_dynamic_data.png View File

Before After
Width: 504  |  Height: 370  |  Size: 17 KiB

BIN
img/gallery/graph/17_network_info.png View File

Before After
Width: 871  |  Height: 648  |  Size: 60 KiB

BIN
img/gallery/graph/graphviz_gallery.png View File

Before After
Width: 1237  |  Height: 729  |  Size: 262 KiB

BIN
img/gallery/timeline/01_basic.png View File

Before After
Width: 544  |  Height: 195  |  Size: 9.0 KiB

BIN
img/gallery/timeline/02_dataset.png View File

Before After
Width: 544  |  Height: 195  |  Size: 9.0 KiB

BIN
img/gallery/timeline/03_much_data.png View File

Before After
Width: 544  |  Height: 263  |  Size: 14 KiB

BIN
img/gallery/timeline/04_html_data.png View File

Before After
Width: 544  |  Height: 248  |  Size: 16 KiB

BIN
img/gallery/timeline/05_groups.png View File

Before After
Width: 711  |  Height: 459  |  Size: 24 KiB

+ 169
- 0
img/logo/vis.svg View File

@ -0,0 +1,169 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="128"
height="128"
id="svg2"
version="1.1"
inkscape:version="0.48.3.1 r9886"
sodipodi:docname="vis.svg"
inkscape:export-filename="/home/jos/projects/vis-pages/img/logo/vis256.png"
inkscape:export-xdpi="180"
inkscape:export-ydpi="180">
<defs
id="defs4">
<filter
inkscape:collect="always"
id="filter3765">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="0.098994946"
id="feGaussianBlur3767" />
</filter>
<filter
color-interpolation-filters="sRGB"
inkscape:collect="always"
id="filter3765-8">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="0.098994946"
id="feGaussianBlur3767-2" />
</filter>
<filter
color-interpolation-filters="sRGB"
inkscape:collect="always"
id="filter3765-8-3">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="0.098994946"
id="feGaussianBlur3767-2-9" />
</filter>
<filter
color-interpolation-filters="sRGB"
inkscape:collect="always"
id="filter3765-3">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="0.098994946"
id="feGaussianBlur3767-3" />
</filter>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="2.8284271"
inkscape:cx="13.788283"
inkscape:cy="60.335199"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:snap-global="true"
inkscape:window-width="1600"
inkscape:window-height="850"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:showpageshadow="false"
borderlayer="false"
showborder="true">
<inkscape:grid
type="xygrid"
id="grid2987"
empspacing="5"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-924.36217)">
<path
sodipodi:type="arc"
style="fill:#109618;fill-opacity:1;stroke:none;opacity:0.01587302"
id="path4104"
sodipodi:cx="64.25"
sodipodi:cy="64.5"
sodipodi:rx="63.75"
sodipodi:ry="64"
d="m 128,64.5 a 63.75,64 0 1 1 -127.5,0 63.75,64 0 1 1 127.5,0 z"
transform="matrix(0.93139646,0,0,0.83984375,-189.81956,958.94835)" />
<path
style="fill:#ffcf00;fill-opacity:1;stroke:#ffcf00;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="M 3.3795626,993.2579 C 22.563164,969.55671 64.941101,938.83212 101.8906,932.98778 c 15.01437,32.97153 15.80482,65.24066 11.89745,97.27812 -53.04371,-1.7716 -84.489701,-14.912 -110.4084874,-37.008 z"
id="path2991"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<path
style="fill:#109618;fill-opacity:1;stroke:#109618;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="m 4.586161,1010.1966 c 29.519439,19.0948 61.669847,32.8593 114.396079,29.9232 6.98138,-4.9614 5.91066,-11.2031 3.61936,-17.6388 -37.656342,4.9772 -79.514689,1.6063 -118.015439,-12.2844 z"
id="path3761"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<path
sodipodi:type="arc"
style="fill:#ff9900;fill-opacity:1;stroke:none;filter:url(#filter3765-8)"
id="path3763-6"
sodipodi:cx="66.998367"
sodipodi:cy="63.476505"
sodipodi:rx="10.429825"
sodipodi:ry="9.3691645"
d="m 77.428192,63.476505 a 10.429825,9.3691645 0 1 1 -20.85965,0 10.429825,9.3691645 0 1 1 20.85965,0 z"
transform="matrix(3.7791753,0,0,4.159068,-180.52628,715.8869)" />
<path
sodipodi:type="arc"
style="fill:#dc3912;fill-opacity:1;stroke:none;filter:url(#filter3765-8-3)"
id="path3763-6-3"
sodipodi:cx="66.998367"
sodipodi:cy="63.476505"
sodipodi:rx="10.429825"
sodipodi:ry="9.3691645"
d="m 77.428192,63.476505 a 10.429825,9.3691645 0 1 1 -20.85965,0 10.429825,9.3691645 0 1 1 20.85965,0 z"
transform="matrix(2.1560756,0,0,2.3930971,-65.92105,839.4416)" />
<path
sodipodi:type="arc"
style="fill:#ffffff;fill-opacity:1;stroke:none;filter:url(#filter3765)"
id="path3763"
sodipodi:cx="66.998367"
sodipodi:cy="63.476505"
sodipodi:rx="10.429825"
sodipodi:ry="9.3691645"
d="m 77.428192,63.476505 a 10.429825,9.3691645 0 1 1 -20.85965,0 10.429825,9.3691645 0 1 1 20.85965,0 z"
transform="matrix(1.369772,0,0,1.5215787,-11.897888,900.22297)" />
<path
sodipodi:type="arc"
style="fill:#4d4d4d;fill-opacity:1;stroke:none;filter:url(#filter3765-3)"
id="path3763-9"
sodipodi:cx="66.998367"
sodipodi:cy="63.476505"
sodipodi:rx="10.429825"
sodipodi:ry="9.3691645"
d="m 77.428192,63.476505 a 10.429825,9.3691645 0 1 1 -20.85965,0 10.429825,9.3691645 0 1 1 20.85965,0 z"
transform="matrix(0.68506744,0,0,0.76063592,38.273417,952.729)" />
</g>
</svg>

BIN
img/logo/vis128.png View File

Before After
Width: 128  |  Height: 128  |  Size: 5.5 KiB

BIN
img/logo/vis16.png View File

Before After
Width: 16  |  Height: 16  |  Size: 660 B

BIN
img/logo/vis256.png View File

Before After
Width: 256  |  Height: 256  |  Size: 13 KiB

BIN
img/logo/vis32.ico View File

Before After

BIN
img/logo/vis32.png View File

Before After
Width: 32  |  Height: 32  |  Size: 1.4 KiB

BIN
img/logo/vis64.png View File

Before After
Width: 64  |  Height: 64  |  Size: 2.9 KiB

+ 317
- 0
index.html View File

@ -0,0 +1,317 @@
<!doctype html>
<html>
<head>
<title>vis.js | a dynamic, browser-based visualization library</title>
<meta charset='utf-8' />
<meta name="title" content="vis.js">
<meta name="description" content="vis.js is a dynamic, browser-based visualization library" />
<meta name="keywords" content="vis, visualization, javascript, browser based, web based, chart, linechart, timeline, graph, network, browser" />
<meta name="author" content="Almende B.V.">
<link href="docs/css/prettify.css" type="text/css" rel="stylesheet" />
<link href='docs/css/style.css' type='text/css' rel='stylesheet'>
<link href="css/style.css" type="text/css" rel="stylesheet" >
<script type="text/javascript" src="docs/lib/prettify/prettify.js"></script>
</head>
<body onload="prettyPrint();">
<div id="container">
<div id="menu">
<a href="http://visjs.org/"><img src="img/logo/vis128.png" alt="logo"></a>
<div class="nav">
<ul>
<li><a href="#install">Install</a></li>
<li><a href="#example">Example</a></li>
<li><a href="#gallery">Gallery</a></li>
<li>
<a href="docs/index.html" target="_blank">
Docs
<img src="img/external-link-icons/external-link-icon.png" style="vertical-align: text-top;" title="Docs will open in a new window">
</a>
</li>
<li><a href="#license">License</a></li>
</ul>
</div>
</div>
<h1>
vis.js<br>
<span class="subtitle">a visual interaction system</span>
</h1>
<p>
Vis.js is a dynamic, browser based visualization library.
The library is designed to be easy to use, to handle large amounts
of dynamic data, and to enable manipulation of and interaction with the data.
The library consists of the components DataSet, Timeline, and Graph.
</p>
<p>
The vis.js library is developed by <a href="http://almende.com" target="_blank">Almende B.V</a>,
as part of the <a href="http://chap.almende.com/" target="_blank">CHAP</a>.
</p>
<h2 id="install">Install</h2>
<h3>npm</h3>
<pre class="prettyprint">
npm install vis
</pre>
<h3>bower</h3>
<pre class="prettyprint">
bower install vis
</pre>
<h3>download</h3>
<a href="download/vis.zip">Click here to download vis.js</a>
(version <span class="version">0.4.0</span>)
<h2 id="example">Example</h2>
<p>
A basic example demonstrating how to use the vis.js timeline is shown below.
See the <a href="#gallery">gallery</a> below for more examples.
</p>
<pre class="prettyprint lang-html">&lt;!doctype html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;title&gt;Timeline | Basic demo&lt;/title&gt;
&lt;script src="http://visjs.org/dist/vis.js"&gt;&lt;/script&gt;
&lt;link href="http://visjs.org/dist/vis.css" rel="stylesheet" type="text/css" /&gt;
&lt;style type="text/css"&gt;
body, html {
font-family: sans-serif;
}
&lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;div id="mytimeline"&gt;&lt;/div&gt;
&lt;script type="text/javascript"&gt;
var container = document.getElementById('mytimeline');
var data = [
{id: 1, content: 'item 1', start: '2013-04-20'},
{id: 2, content: 'item 2', start: '2013-04-14'},
{id: 3, content: 'item 3', start: '2013-04-18'},
{id: 4, content: 'item 4', start: '2013-04-16', end: '2013-04-19'},
{id: 5, content: 'item 5', start: '2013-04-25'},
{id: 6, content: 'item 6', start: '2013-04-27'}
];
var options = {};
var timeline = new vis.Timeline(container, data, options);
&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre>
<h2 id="gallery">Gallery</h2>
This gallery gives an idea of the features and possibilities of the library.
The source code of the examples can be found in the
<a href="https://github.com/almende/vis/tree/master/examples" target="_blank">examples directory</a>.
<h3 id="timeline">Timeline</h3>
<p>
The timeline from vis.js displays different types of data on a timeline.
</p>
<div class="gallery">
<div class="thumb">
<a href="examples/timeline/01_basic.html">
<img src="img/gallery/timeline/01_basic.png">
<div>basic usage</div>
</a>
</div>
<div class="thumb">
<a href="examples/timeline/02_dataset.html">
<img src="img/gallery/timeline/02_dataset.png">
<div>dataset</div>
</a>
</div>
<div class="thumb">
<a href="examples/timeline/03_much_data.html">
<img src="img/gallery/timeline/03_much_data.png">
<div>much data</div>
</a>
</div>
<div class="thumb">
<a href="examples/timeline/04_html_data.html">
<img src="img/gallery/timeline/04_html_data.png">
<div>html data</div>
</a>
</div>
<div class="thumb">
<a href="examples/timeline/05_groups.html">
<img src="img/gallery/timeline/05_groups.png">
<div>groups</div>
</a>
</div>
</div>
<h3 id="graph">Graph</h3>
<p>
The graph from vis.js visualizes graphs and networks with
customizable styles.
</p>
<div class="gallery">
<div class="thumb">
<a href="examples/graph/01_basic_usage.html">
<img src="img/gallery/graph/01_basic_usage.png">
<div>basic usage</div>
</a>
</div>
<div class="thumb">
<a href="examples/graph/02_random_nodes.html">
<img src="img/gallery/graph/02_random_nodes.png">
<div>random nodes</div>
</a>
</div>
<div class="thumb">
<a href="examples/graph/03_images.html">
<img src="img/gallery/graph/03_images.png">
<div>images</div>
</a>
</div>
<div class="thumb">
<a href="examples/graph/04_shapes.html">
<img src="img/gallery/graph/04_shapes.png">
<div>shapes</div>
</a>
</div>
<div class="thumb">
<a href="examples/graph/05_social_network.html">
<img src="img/gallery/graph/05_social_network.png">
<div>social network</div>
</a>
</div>
<div class="thumb">
<a href="examples/graph/06_groups.html">
<img src="img/gallery/graph/06_groups.png">
<div>groups</div>
</a>
</div>
<div class="thumb">
<a href="examples/graph/07_selections.html">
<img src="img/gallery/graph/07_selections.png">
<div>selections</div>
</a>
</div>
<div class="thumb">
<a href="examples/graph/08_mobile_friendly.html">
<img src="img/gallery/graph/08_mobile_friendly.png">
<div>mobile friendly</div>
</a>
</div>
<div class="thumb">
<a href="examples/graph/09_sizing.html">
<img src="img/gallery/graph/09_sizing.png">
<div>sizing</div>
</a>
</div>
<div class="thumb">
<a href="examples/graph/10_multiline_text.html">
<img src="img/gallery/graph/10_multiline_text.png">
<div>multiline text</div>
</a>
</div>
<div class="thumb">
<a href="examples/graph/11_custom_style.html">
<img src="img/gallery/graph/11_custom_style.png">
<div>custom style</div>
</a>
</div>
<div class="thumb">
<a href="examples/graph/12_scalable_images.html">
<img src="img/gallery/graph/12_scalable_images.png">
<div>scalable images</div>
</a>
</div>
<div class="thumb">
<a href="examples/graph/13_dashed_lines.html">
<img src="img/gallery/graph/13_dashed_lines.png">
<div>dashed lines</div>
</a>
</div>
<div class="thumb">
<a href="examples/graph/14_dot_language.html">
<img src="img/gallery/graph/14_dot_language.png">
<div>dot language</div>
</a>
</div>
<div class="thumb">
<a href="examples/graph/15_dot_language_playground.html">
<img src="img/gallery/graph/15_dot_language_playground.png">
<div>playground</div>
</a>
</div>
<div class="thumb">
<a href="examples/graph/16_dynamic_data.html">
<img src="img/gallery/graph/16_dynamic_data.png">
<div>dynamic data</div>
</a>
</div>
<div class="thumb">
<a href="examples/graph/17_network_info.html">
<img src="img/gallery/graph/17_network_info.png">
<div>network info</div>
</a>
</div>
<div class="thumb">
<a href="examples/graph/graphviz/graphviz_gallery.html">
<img src="img/gallery/graph/graphviz_gallery.png">
<div>graphviz gallery</div>
</a>
</div>
</div>
<h2 id="docs">Docs</h2>
<p>
Documentation is available here:
<a href="docs/index.html" target="_blank">Documentation</a>
</p>
<h2 id="license">License</h2>
<p>
Copyright (C) 2010-2014 Almende B.V.
</p>
<p>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
</p>
<p>
http://www.apache.org/licenses/LICENSE-2.0
</p>
<p>
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
</p>
<a id="forkme" href="https://github.com/almende/vis/" target="_blank">
<img src="img/forkme_right_darkblue_121621.png" alt="Fork me on GitHub" >
</a>
</div>
</body>
</html>

+ 0
- 0
package.js View File


+ 166
- 76
src/graph/Edge.js View File

@ -31,11 +31,13 @@ function Edge (properties, graph, constants) {
this.title = undefined;
this.width = constants.edges.width;
this.value = undefined;
this.length = constants.edges.length;
this.length = constants.physics.springLength;
this.selected = false;
this.smooth = constants.smoothCurves;
this.from = null; // a node
this.to = null; // a node
this.via = null; // a temp node
// we use this to be able to reconnect the edge to a cluster if its node is put into a cluster
// by storing the original information we can revert to the original connection when the cluser is opened.
@ -49,13 +51,11 @@ function Edge (properties, graph, constants) {
// 2012-08-08
this.dash = util.extend({}, constants.edges.dash); // contains properties length, gap, altLength
this.stiffness = undefined; // depends on the length of the edge
this.color = constants.edges.color;
this.widthFixed = false;
this.lengthFixed = false;
this.setProperties(properties, constants);
}
/**
@ -74,6 +74,7 @@ Edge.prototype.setProperties = function(properties, constants) {
if (properties.id !== undefined) {this.id = properties.id;}
if (properties.style !== undefined) {this.style = properties.style;}
if (properties.label !== undefined) {this.label = properties.label;}
if (this.label) {
this.fontSize = constants.edges.fontSize;
this.fontFace = constants.edges.fontFace;
@ -82,10 +83,11 @@ Edge.prototype.setProperties = function(properties, constants) {
if (properties.fontSize !== undefined) {this.fontSize = properties.fontSize;}
if (properties.fontFace !== undefined) {this.fontFace = properties.fontFace;}
}
if (properties.title !== undefined) {this.title = properties.title;}
if (properties.width !== undefined) {this.width = properties.width;}
if (properties.value !== undefined) {this.value = properties.value;}
if (properties.length !== undefined) {this.length = properties.length;}
if (properties.title !== undefined) {this.title = properties.title;}
if (properties.width !== undefined) {this.width = properties.width;}
if (properties.value !== undefined) {this.value = properties.value;}
if (properties.length !== undefined) {this.length = properties.length;}
// Added to support dashed lines
// David Jordan
@ -103,7 +105,6 @@ Edge.prototype.setProperties = function(properties, constants) {
this.widthFixed = this.widthFixed || (properties.width !== undefined);
this.lengthFixed = this.lengthFixed || (properties.length !== undefined);
this.stiffness = 1 / this.length;
// set draw method based on style
switch (this.style) {
@ -211,8 +212,7 @@ Edge.prototype.isOverlappingWith = function(obj) {
var xObj = obj.left;
var yObj = obj.top;
var dist = Edge._dist(xFrom, yFrom, xTo, yTo, xObj, yObj);
var dist = this._getDistanceToEdge(xFrom, yFrom, xTo, yTo, xObj, yObj);
return (dist < distMax);
};
@ -231,7 +231,7 @@ Edge.prototype._drawLine = function(ctx) {
ctx.lineWidth = this._getLineWidth();
var point;
if (this.from != this.to) {
if (this.from != this.to+9) {
// draw line
this._line(ctx);
@ -286,7 +286,12 @@ Edge.prototype._line = function (ctx) {
// draw a straight line
ctx.beginPath();
ctx.moveTo(this.from.x, this.from.y);
ctx.lineTo(this.to.x, this.to.y);
if (this.smooth == true) {
ctx.quadraticCurveTo(this.via.x,this.via.y,this.to.x, this.to.y);
}
else {
ctx.lineTo(this.to.x, this.to.y);
}
ctx.stroke();
};
@ -348,25 +353,70 @@ Edge.prototype._drawDashLine = function(ctx) {
ctx.strokeStyle = this.color;
ctx.lineWidth = this._getLineWidth();
// draw dashed line
ctx.beginPath();
ctx.lineCap = 'round';
if (this.dash.altLength !== undefined) //If an alt dash value has been set add to the array this value
{
ctx.dashedLine(this.from.x,this.from.y,this.to.x,this.to.y,
[this.dash.length,this.dash.gap,this.dash.altLength,this.dash.gap]);
}
else if (this.dash.length !== undefined && this.dash.gap !== undefined) //If a dash and gap value has been set add to the array this value
{
ctx.dashedLine(this.from.x,this.from.y,this.to.x,this.to.y,
[this.dash.length,this.dash.gap]);
}
else //If all else fails draw a line
{
// only firefox and chrome support this method, else we use the legacy one.
if (ctx.mozDash !== undefined || ctx.setLineDash !== undefined) {
ctx.beginPath();
ctx.moveTo(this.from.x, this.from.y);
ctx.lineTo(this.to.x, this.to.y);
// configure the dash pattern
var pattern = [0];
if (this.dash.length !== undefined && this.dash.gap !== undefined) {
pattern = [this.dash.length,this.dash.gap];
}
else {
pattern = [5,5];
}
// set dash settings for chrome or firefox
if (typeof ctx.setLineDash !== 'undefined') { //Chrome
ctx.setLineDash(pattern);
ctx.lineDashOffset = 0;
} else { //Firefox
ctx.mozDash = pattern;
ctx.mozDashOffset = 0;
}
// draw the line
if (this.smooth == true) {
ctx.quadraticCurveTo(this.via.x,this.via.y,this.to.x, this.to.y);
}
else {
ctx.lineTo(this.to.x, this.to.y);
}
ctx.stroke();
// restore the dash settings.
if (typeof ctx.setLineDash !== 'undefined') { //Chrome
ctx.setLineDash([0]);
ctx.lineDashOffset = 0;
} else { //Firefox
ctx.mozDash = [0];
ctx.mozDashOffset = 0;
}
}
else { // unsupporting smooth lines
// draw dashed line
ctx.beginPath();
ctx.lineCap = 'round';
if (this.dash.altLength !== undefined) //If an alt dash value has been set add to the array this value
{
ctx.dashedLine(this.from.x,this.from.y,this.to.x,this.to.y,
[this.dash.length,this.dash.gap,this.dash.altLength,this.dash.gap]);
}
else if (this.dash.length !== undefined && this.dash.gap !== undefined) //If a dash and gap value has been set add to the array this value
{
ctx.dashedLine(this.from.x,this.from.y,this.to.x,this.to.y,
[this.dash.length,this.dash.gap]);
}
else //If all else fails draw a line
{
ctx.moveTo(this.from.x, this.from.y);
ctx.lineTo(this.to.x, this.to.y);
}
ctx.stroke();
}
ctx.stroke();
// draw label
if (this.label) {
@ -423,10 +473,18 @@ Edge.prototype._drawArrowCenter = function(ctx) {
// draw line
this._line(ctx);
// draw an arrow halfway the line
var angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
var length = 10 + 5 * this.width; // TODO: make customizable?
point = this._pointOnLine(0.5);
// draw an arrow halfway the line
if (this.smooth == true) {
var midpointX = 0.5*(0.5*(this.from.x + this.via.x) + 0.5*(this.to.x + this.via.x));
var midpointY = 0.5*(0.5*(this.from.y + this.via.y) + 0.5*(this.to.y + this.via.y));
point = {x:midpointX, y:midpointY};
}
else {
point = this._pointOnLine(0.5);
}
ctx.arrow(point.x, point.y, angle, length);
ctx.fill();
ctx.stroke();
@ -440,18 +498,18 @@ Edge.prototype._drawArrowCenter = function(ctx) {
else {
// draw circle
var x, y;
var radius = this.length / 4;
var radius = 0.25 * Math.max(100,this.length);
var node = this.from;
if (!node.width) {
node.resize(ctx);
}
if (node.width > node.height) {
x = node.x + node.width / 2;
x = node.x + node.width * 0.5;
y = node.y - radius;
}
else {
x = node.x + radius;
y = node.y - node.height / 2;
y = node.y - node.height * 0.5;
}
this._circle(ctx, x, y, radius);
@ -486,32 +544,43 @@ Edge.prototype._drawArrow = function(ctx) {
ctx.fillStyle = this.color;
ctx.lineWidth = this._getLineWidth();
// draw line
var angle, length;
//draw a line
if (this.from != this.to) {
// calculate length and angle of the line
angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
var dx = (this.to.x - this.from.x);
var dy = (this.to.y - this.from.y);
var lEdge = Math.sqrt(dx * dx + dy * dy);
var lFrom = this.from.distanceToBorder(ctx, angle + Math.PI);
var pFrom = (lEdge - lFrom) / lEdge;
var xFrom = (pFrom) * this.from.x + (1 - pFrom) * this.to.x;
var yFrom = (pFrom) * this.from.y + (1 - pFrom) * this.to.y;
var edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
var fromBorderDist = this.from.distanceToBorder(ctx, angle + Math.PI);
var fromBorderPoint = (edgeSegmentLength - fromBorderDist) / edgeSegmentLength;
var xFrom = (fromBorderPoint) * this.from.x + (1 - fromBorderPoint) * this.to.x;
var yFrom = (fromBorderPoint) * this.from.y + (1 - fromBorderPoint) * this.to.y;
if (this.smooth == true) {
angle = Math.atan2((this.to.y - this.via.y), (this.to.x - this.via.x));
dx = (this.to.x - this.via.x);
dy = (this.to.y - this.via.y);
edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
}
var toBorderDist = this.to.distanceToBorder(ctx, angle);
var toBorderPoint = (edgeSegmentLength - toBorderDist) / edgeSegmentLength;
var xTo = (1 - toBorderPoint) * this.via.x + toBorderPoint * this.to.x;
var yTo = (1 - toBorderPoint) * this.via.y + toBorderPoint * this.to.y;
var lTo = this.to.distanceToBorder(ctx, angle);
var pTo = (lEdge - lTo) / lEdge;
var xTo = (1 - pTo) * this.from.x + pTo * this.to.x;
var yTo = (1 - pTo) * this.from.y + pTo * this.to.y;
ctx.beginPath();
ctx.moveTo(xFrom, yFrom);
ctx.lineTo(xTo, yTo);
ctx.moveTo(xFrom,yFrom);
if (this.smooth == true) {
ctx.quadraticCurveTo(this.via.x,this.via.y,xTo, yTo);
}
else {
ctx.lineTo(xTo, yTo);
}
ctx.stroke();
// draw arrow at the end of the line
length = 10 + 5 * this.width; // TODO: make customizable?
length = 10 + 5 * this.width;
ctx.arrow(xTo, yTo, angle, length);
ctx.fill();
ctx.stroke();
@ -526,12 +595,12 @@ Edge.prototype._drawArrow = function(ctx) {
// draw circle
var node = this.from;
var x, y, arrow;
var radius = this.length / 4;
var radius = 0.25 * Math.max(100,this.length);
if (!node.width) {
node.resize(ctx);
}
if (node.width > node.height) {
x = node.x + node.width / 2;
x = node.x + node.width * 0.5;
y = node.y - radius;
arrow = {
x: x,
@ -541,7 +610,7 @@ Edge.prototype._drawArrow = function(ctx) {
}
else {
x = node.x + radius;
y = node.y - node.height / 2;
y = node.y - node.height * 0.5;
arrow = {
x: node.x,
y: y,
@ -549,7 +618,6 @@ Edge.prototype._drawArrow = function(ctx) {
};
}
ctx.beginPath();
// TODO: do not draw a circle, but an arc
// TODO: similarly, for a line without arrows, draw to the border of the nodes instead of the center
ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
ctx.stroke();
@ -582,31 +650,46 @@ Edge.prototype._drawArrow = function(ctx) {
* @param {number} y3
* @private
*/
Edge._dist = function (x1,y1, x2,y2, x3,y3) { // x3,y3 is the point
var px = x2-x1,
py = y2-y1,
something = px*px + py*py,
u = ((x3 - x1) * px + (y3 - y1) * py) / something;
if (u > 1) {
u = 1;
}
else if (u < 0) {
u = 0;
Edge.prototype._getDistanceToEdge = function (x1,y1, x2,y2, x3,y3) { // x3,y3 is the point
if (this.smooth == true) {
var minDistance = 1e9;
var i,t,x,y,dx,dy;
for (i = 0; i < 10; i++) {
t = 0.1*i;
x = Math.pow(1-t,2)*x1 + (2*t*(1 - t))*this.via.x + Math.pow(t,2)*x2;
y = Math.pow(1-t,2)*y1 + (2*t*(1 - t))*this.via.y + Math.pow(t,2)*y2;
dx = Math.abs(x3-x);
dy = Math.abs(y3-y);
minDistance = Math.min(minDistance,Math.sqrt(dx*dx + dy*dy));
}
return minDistance
}
else {
var px = x2-x1,
py = y2-y1,
something = px*px + py*py,
u = ((x3 - x1) * px + (y3 - y1) * py) / something;
if (u > 1) {
u = 1;
}
else if (u < 0) {
u = 0;
}
var x = x1 + u * px,
y = y1 + u * py,
dx = x - x3,
dy = y - y3;
var x = x1 + u * px,
y = y1 + u * py,
dx = x - x3,
dy = y - y3;
//# Note: If the actual distance does not matter,
//# if you only want to compare what this function
//# returns to other results of this function, you
//# can just return the squared distance instead
//# (i.e. remove the sqrt) to gain a little performance
//# Note: If the actual distance does not matter,
//# if you only want to compare what this function
//# returns to other results of this function, you
//# can just return the squared distance instead
//# (i.e. remove the sqrt) to gain a little performance
return Math.sqrt(dx*dx + dy*dy);
return Math.sqrt(dx*dx + dy*dy);
}
};
@ -623,8 +706,15 @@ Edge.prototype.setScale = function(scale) {
Edge.prototype.select = function() {
this.selected = true;
}
};
Edge.prototype.unselect = function() {
this.selected = false;
}
};
Edge.prototype.positionBezierNode = function() {
if (this.via !== null) {
this.via.x = 0.5 * (this.from.x + this.to.x);
this.via.y = 0.5 * (this.from.y + this.to.y);
}
};

+ 387
- 594
src/graph/Graph.js
File diff suppressed because it is too large
View File


+ 0
- 245
src/graph/NavigationMixin.js View File

@ -1,245 +0,0 @@
/**
* Created by Alex on 1/22/14.
*/
var NavigationMixin = {
/**
* This function moves the navigation controls if the canvas size has been changed. If the arugments
* verticaAlignTop and horizontalAlignLeft are false, the correction will be made
*
* @private
*/
_relocateNavigation : function() {
if (this.sectors !== undefined) {
var xOffset = this.navigationClientWidth - this.frame.canvas.clientWidth;
var yOffset = this.navigationClientHeight - this.frame.canvas.clientHeight;
this.navigationClientWidth = this.frame.canvas.clientWidth;
this.navigationClientHeight = this.frame.canvas.clientHeight;
var node = null;
for (var nodeId in this.sectors["navigation"]["nodes"]) {
if (this.sectors["navigation"]["nodes"].hasOwnProperty(nodeId)) {
node = this.sectors["navigation"]["nodes"][nodeId];
if (!node.horizontalAlignLeft) {
node.x -= xOffset;
}
if (!node.verticalAlignTop) {
node.y -= yOffset;
}
}
}
}
},
/**
* Creation of the navigation controls nodes. They are drawn over the rest of the nodes and are not affected by scale and translation
* they have a triggerFunction which is called on click. If the position of the navigation controls is dependent
* on this.frame.canvas.clientWidth or this.frame.canvas.clientHeight, we flag horizontalAlignLeft and verticalAlignTop false.
* This means that the location will be corrected by the _relocateNavigation function on a size change of the canvas.
*
* @private
*/
_loadNavigationElements : function() {
var DIR = this.constants.navigation.iconPath;
this.navigationClientWidth = this.frame.canvas.clientWidth;
this.navigationClientHeight = this.frame.canvas.clientHeight;
if (this.navigationClientWidth === undefined) {
this.navigationClientWidth = 0;
this.navigationClientHeight = 0;
}
var offset = 15;
var intermediateOffset = 7;
var navigationNodes = [
{id: 'navigation_up', shape: 'image', image: DIR + '/uparrow.png', triggerFunction: "_moveUp",
verticalAlignTop: false, x: 45 + offset + intermediateOffset, y: this.navigationClientHeight - 45 - offset - intermediateOffset},
{id: 'navigation_down', shape: 'image', image: DIR + '/downarrow.png', triggerFunction: "_moveDown",
verticalAlignTop: false, x: 45 + offset + intermediateOffset, y: this.navigationClientHeight - 15 - offset},
{id: 'navigation_left', shape: 'image', image: DIR + '/leftarrow.png', triggerFunction: "_moveLeft",
verticalAlignTop: false, x: 15 + offset, y: this.navigationClientHeight - 15 - offset},
{id: 'navigation_right', shape: 'image', image: DIR + '/rightarrow.png',triggerFunction: "_moveRight",
verticalAlignTop: false, x: 75 + offset + 2 * intermediateOffset, y: this.navigationClientHeight - 15 - offset},
{id: 'navigation_plus', shape: 'image', image: DIR + '/plus.png', triggerFunction: "_zoomIn",
verticalAlignTop: false, horizontalAlignLeft: false,
x: this.navigationClientWidth - 45 - offset - intermediateOffset, y: this.navigationClientHeight - 15 - offset},
{id: 'navigation_min', shape: 'image', image: DIR + '/minus.png', triggerFunction: "_zoomOut",
verticalAlignTop: false, horizontalAlignLeft: false,
x: this.navigationClientWidth - 15 - offset, y: this.navigationClientHeight - 15 - offset},
{id: 'navigation_zoomExtends', shape: 'image', image: DIR + '/zoomExtends.png', triggerFunction: "zoomToFit",
verticalAlignTop: false, horizontalAlignLeft: false,
x: this.navigationClientWidth - 15 - offset, y: this.navigationClientHeight - 45 - offset - intermediateOffset}
];
var nodeObj = null;
for (var i = 0; i < navigationNodes.length; i++) {
nodeObj = this.sectors["navigation"]['nodes'];
nodeObj[navigationNodes[i]['id']] = new Node(navigationNodes[i], this.images, this.groups, this.constants);
}
},
/**
* By setting the clustersize to be larger than 1, we use the clustering drawing method
* to illustrate the buttons are presed. We call this highlighting.
*
* @param {String} elementId
* @private
*/
_highlightNavigationElement : function(elementId) {
if (this.sectors["navigation"]["nodes"].hasOwnProperty(elementId)) {
this.sectors["navigation"]["nodes"][elementId].clusterSize = 2;
}
},
/**
* Reverting back to a normal button
*
* @param {String} elementId
* @private
*/
_unHighlightNavigationElement : function(elementId) {
if (this.sectors["navigation"]["nodes"].hasOwnProperty(elementId)) {
this.sectors["navigation"]["nodes"][elementId].clusterSize = 1;
}
},
/**
* un-highlight (for lack of a better term) all navigation controls elements
* @private
*/
_unHighlightAll : function() {
for (var nodeId in this.sectors['navigation']['nodes']) {
if (this.sectors['navigation']['nodes'].hasOwnProperty(nodeId)) {
this._unHighlightNavigationElement(nodeId);
}
}
},
_preventDefault : function(event) {
if (event !== undefined) {
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
}
},
/**
* move the screen up
* By using the increments, instead of adding a fixed number to the translation, we keep fluent and
* instant movement. The onKeypress event triggers immediately, then pauses, then triggers frequently
* To avoid this behaviour, we do the translation in the start loop.
*
* @private
*/
_moveUp : function(event) {
this._highlightNavigationElement("navigation_up");
this.yIncrement = this.constants.keyboard.speed.y;
this.start(); // if there is no node movement, the calculation wont be done
this._preventDefault(event);
},
/**
* move the screen down
* @private
*/
_moveDown : function(event) {
this._highlightNavigationElement("navigation_down");
this.yIncrement = -this.constants.keyboard.speed.y;
this.start(); // if there is no node movement, the calculation wont be done
this._preventDefault(event);
},
/**
* move the screen left
* @private
*/
_moveLeft : function(event) {
this._highlightNavigationElement("navigation_left");
this.xIncrement = this.constants.keyboard.speed.x;
this.start(); // if there is no node movement, the calculation wont be done
this._preventDefault(event);
},
/**
* move the screen right
* @private
*/
_moveRight : function(event) {
this._highlightNavigationElement("navigation_right");
this.xIncrement = -this.constants.keyboard.speed.y;
this.start(); // if there is no node movement, the calculation wont be done
this._preventDefault(event);
},
/**
* Zoom in, using the same method as the movement.
* @private
*/
_zoomIn : function(event) {
this._highlightNavigationElement("navigation_plus");
this.zoomIncrement = this.constants.keyboard.speed.zoom;
this.start(); // if there is no node movement, the calculation wont be done
this._preventDefault(event);
},
/**
* Zoom out
* @private
*/
_zoomOut : function() {
this._highlightNavigationElement("navigation_min");
this.zoomIncrement = -this.constants.keyboard.speed.zoom;
this.start(); // if there is no node movement, the calculation wont be done
this._preventDefault(event);
},
/**
* Stop zooming and unhighlight the zoom controls
* @private
*/
_stopZoom : function() {
this._unHighlightNavigationElement("navigation_plus");
this._unHighlightNavigationElement("navigation_min");
this.zoomIncrement = 0;
},
/**
* Stop moving in the Y direction and unHighlight the up and down
* @private
*/
_yStopMoving : function() {
this._unHighlightNavigationElement("navigation_up");
this._unHighlightNavigationElement("navigation_down");
this.yIncrement = 0;
},
/**
* Stop moving in the X direction and unHighlight left and right.
* @private
*/
_xStopMoving : function() {
this._unHighlightNavigationElement("navigation_left");
this._unHighlightNavigationElement("navigation_right");
this.xIncrement = 0;
}
};

+ 114
- 79
src/graph/Node.js View File

@ -21,6 +21,7 @@
* retrieving group properties
* @param {Object} constants An object with default values for
* example for the color
*
*/
function Node(properties, imagelist, grouplist, constants) {
this.selected = false;
@ -33,6 +34,7 @@ function Node(properties, imagelist, grouplist, constants) {
this.fontSize = constants.nodes.fontSize;
this.fontFace = constants.nodes.fontFace;
this.fontColor = constants.nodes.fontColor;
this.fontDrawThreshold = 3;
this.color = constants.nodes.color;
@ -53,9 +55,17 @@ function Node(properties, imagelist, grouplist, constants) {
this.radiusMax = constants.nodes.radiusMax;
this.imagelist = imagelist;
this.grouplist = grouplist;
// physics properties
this.fx = 0.0; // external force x
this.fy = 0.0; // external force y
this.vx = 0.0; // velocity x
this.vy = 0.0; // velocity y
this.minForce = constants.minForce;
this.damping = constants.physics.damping;
this.mass = 1; // kg
this.setProperties(properties, constants);
// creating the variables for clustering
@ -65,20 +75,15 @@ function Node(properties, imagelist, grouplist, constants) {
this.clusterSizeWidthFactor = constants.clustering.nodeScaling.width;
this.clusterSizeHeightFactor = constants.clustering.nodeScaling.height;
this.clusterSizeRadiusFactor = constants.clustering.nodeScaling.radius;
this.maxNodeSizeIncrements = constants.clustering.maxNodeSizeIncrements;
this.growthIndicator = 0;
// mass, force, velocity
this.mass = 1; // kg (mass is adjusted for the number of connected edges)
this.fx = 0.0; // external force x
this.fy = 0.0; // external force y
this.vx = 0.0; // velocity x
this.vy = 0.0; // velocity y
this.minForce = constants.minForce;
this.damping = 0.9;
this.dampingFactor = 75;
// variables to tell the node about the graph.
this.graphScaleInv = 1;
this.graphScale = 1;
this.canvasTopLeft = {"x": -300, "y": -300};
this.canvasBottomRight = {"x": 300, "y": 300};
this.parentEdgeId = null;
}
/**
@ -105,7 +110,6 @@ Node.prototype.attachEdge = function(edge) {
this.dynamicEdges.push(edge);
}
this.dynamicEdgesLength = this.dynamicEdges.length;
this._updateMass();
};
/**
@ -119,17 +123,8 @@ Node.prototype.detachEdge = function(edge) {
this.dynamicEdges.splice(index, 1);
}
this.dynamicEdgesLength = this.dynamicEdges.length;
this._updateMass();
};
/**
* Update the nodes mass, which is determined by the number of edges connecting
* to it (more edges -> heavier node).
* @private
*/
Node.prototype._updateMass = function() {
this.mass = 1 + 0.6 * this.edges.length; // kg
};
/**
* Set or overwrite properties for the node
@ -150,6 +145,12 @@ Node.prototype.setProperties = function(properties, constants) {
if (properties.y !== undefined) {this.y = properties.y;}
if (properties.value !== undefined) {this.value = properties.value;}
// physics
if (properties.internalMultiplier !== undefined) {this.internalMultiplier = properties.internalMultiplier;}
if (properties.damping !== undefined) {this.dampingBase = properties.damping;}
if (properties.mass !== undefined) {this.mass = properties.mass;}
// navigation controls properties
if (properties.horizontalAlignLeft !== undefined) {this.horizontalAlignLeft = properties.horizontalAlignLeft;}
if (properties.verticalAlignTop !== undefined) {this.verticalAlignTop = properties.verticalAlignTop;}
@ -226,15 +227,32 @@ Node.prototype.setProperties = function(properties, constants) {
Node.parseColor = function(color) {
var c;
if (util.isString(color)) {
c = {
border: color,
background: color,
highlight: {
if (util.isValidHex(color)) {
var hsv = util.hexToHSV(color);
var lighterColorHSV = {h:hsv.h,s:hsv.s * 0.45,v:Math.min(1,hsv.v * 1.05)};
var darkerColorHSV = {h:hsv.h,s:Math.min(1,hsv.v * 1.25),v:hsv.v*0.6};
var darkerColorHex = util.HSVToHex(darkerColorHSV.h ,darkerColorHSV.h ,darkerColorHSV.v);
var lighterColorHex = util.HSVToHex(lighterColorHSV.h,lighterColorHSV.s,lighterColorHSV.v);
c = {
border: color,
background: color
}
};
// TODO: automatically generate a nice highlight color
border:darkerColorHex,
highlight: {
background:lighterColorHex,
border:darkerColorHex
}
};
}
else {
c = {
border:color,
border:color,
highlight: {
background:color,
border:color
}
};
}
}
else {
c = {};
@ -312,7 +330,6 @@ Node.prototype.distanceToBorder = function (ctx, angle) {
this.resize(ctx);
}
//noinspection FallthroughInSwitchStatementJS
switch (this.shape) {
case 'circle':
case 'dot':
@ -344,7 +361,6 @@ Node.prototype.distanceToBorder = function (ctx, angle) {
}
}
// TODO: implement calculation of distance to border for all shapes
};
@ -375,21 +391,44 @@ Node.prototype._addForce = function(fx, fy) {
*/
Node.prototype.discreteStep = function(interval) {
if (!this.xFixed) {
var dx = -this.damping * this.vx; // damping force
var ax = (this.fx + dx) / this.mass; // acceleration
var dx = this.damping * this.vx; // damping force
var ax = (this.fx - dx) / this.mass; // acceleration
this.vx += ax * interval; // velocity
this.x += this.vx * interval; // position
}
if (!this.yFixed) {
var dy = -this.damping * this.vy; // damping force
var ay = (this.fy + dy) / this.mass; // acceleration
var dy = this.damping * this.vy; // damping force
var ay = (this.fy - dy) / this.mass; // acceleration
this.vy += ay * interval; // velocity
this.y += this.vy * interval; // position
}
};
/**
* Perform one discrete step for the node
* @param {number} interval Time interval in seconds
*/
Node.prototype.discreteStepLimited = function(interval, maxVelocity) {
if (!this.xFixed) {
var dx = this.damping * this.vx; // damping force
var ax = (this.fx - dx) / this.mass; // acceleration
this.vx += ax * interval; // velocity
this.vx = (Math.abs(this.vx) > maxVelocity) ? ((this.vx > 0) ? maxVelocity : -maxVelocity) : this.vx;
this.x += this.vx * interval; // position
}
if (!this.yFixed) {
var dy = this.damping * this.vy; // damping force
var ay = (this.fy - dy) / this.mass; // acceleration
this.vy += ay * interval; // velocity
this.vy = (Math.abs(this.vy) > maxVelocity) ? ((this.vy > 0) ? maxVelocity : -maxVelocity) : this.vy;
this.y += this.vy * interval; // position
}
};
/**
* Check if this node has a fixed x and y position
* @return {boolean} true if fixed, false if not
@ -405,16 +444,7 @@ Node.prototype.isFixed = function() {
*/
// TODO: replace this method with calculating the kinetic energy
Node.prototype.isMoving = function(vmin) {
if (Math.abs(this.vx) > vmin || Math.abs(this.vy) > vmin) {
// console.log(vmin,this.vx,this.vy);
return true;
}
else {
this.vx = 0; this.vy = 0;
return false;
}
//return (Math.abs(this.vx) > vmin || Math.abs(this.vy) > vmin);
return (Math.abs(this.vx) > vmin || Math.abs(this.vy) > vmin);
};
/**
@ -519,10 +549,12 @@ Node.prototype._resizeImage = function (ctx) {
this.width = width;
this.height = height;
this.growthIndicator = 0;
if (this.width > 0 && this.height > 0) {
this.width += (this.clusterSize - 1) * this.clusterSizeWidthFactor;
this.height += (this.clusterSize - 1) * this.clusterSizeHeightFactor;
this.radius += (this.clusterSize - 1) * this.clusterSizeRadiusFactor;
this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
this.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor;
this.growthIndicator = this.width - width;
}
}
@ -567,9 +599,11 @@ Node.prototype._resizeBox = function (ctx) {
this.width = textSize.width + 2 * margin;
this.height = textSize.height + 2 * margin;
this.width += (this.clusterSize - 1) * 0.5 * this.clusterSizeWidthFactor;
this.height += (this.clusterSize - 1) * 0.5 * this.clusterSizeHeightFactor;
// this.radius += (this.clusterSize - 1) * 0.5 * this.clusterSizeRadiusFactor;
this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeWidthFactor;
this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeHeightFactor;
this.growthIndicator = this.width - (textSize.width + 2 * margin);
// this.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeRadiusFactor;
}
};
@ -616,9 +650,10 @@ Node.prototype._resizeDatabase = function (ctx) {
this.height = size;
// scaling used for clustering
this.width += (this.clusterSize - 1) * this.clusterSizeWidthFactor;
this.height += (this.clusterSize - 1) * this.clusterSizeHeightFactor;
this.radius += (this.clusterSize - 1) * this.clusterSizeRadiusFactor;
this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
this.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor;
this.growthIndicator = this.width - size;
}
};
@ -665,9 +700,10 @@ Node.prototype._resizeCircle = function (ctx) {
this.height = diameter;
// scaling used for clustering
// this.width += (this.clusterSize - 1) * 0.5 * this.clusterSizeWidthFactor;
// this.height += (this.clusterSize - 1) * 0.5 * this.clusterSizeHeightFactor;
this.radius += (this.clusterSize - 1) * 0.5 * this.clusterSizeRadiusFactor;
// this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeWidthFactor;
// this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeHeightFactor;
this.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeRadiusFactor;
this.growthIndicator = this.radius - 0.5*diameter;
}
};
@ -711,11 +747,13 @@ Node.prototype._resizeEllipse = function (ctx) {
if (this.width < this.height) {
this.width = this.height;
}
var defaultSize = this.width;
// scaling used for clustering
this.width += (this.clusterSize - 1) * this.clusterSizeWidthFactor;
this.height += (this.clusterSize - 1) * this.clusterSizeHeightFactor;
this.radius += (this.clusterSize - 1) * this.clusterSizeRadiusFactor;
// scaling used for clustering
this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
this.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor;
this.growthIndicator = this.width - defaultSize;
}
};
@ -778,9 +816,10 @@ Node.prototype._resizeShape = function (ctx) {
this.height = size;
// scaling used for clustering
this.width += (this.clusterSize - 1) * this.clusterSizeWidthFactor;
this.height += (this.clusterSize - 1) * this.clusterSizeHeightFactor;
this.radius += (this.clusterSize - 1) * 0.5 * this.clusterSizeRadiusFactor;
this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
this.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeRadiusFactor;
this.growthIndicator = this.width - size;
}
};
@ -837,9 +876,10 @@ Node.prototype._resizeText = function (ctx) {
this.height = textSize.height + 2 * margin;
// scaling used for clustering
this.width += (this.clusterSize - 1) * this.clusterSizeWidthFactor;
this.height += (this.clusterSize - 1) * this.clusterSizeHeightFactor;
this.radius += (this.clusterSize - 1) * this.clusterSizeRadiusFactor;
this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
this.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor;
this.growthIndicator = this.width - (textSize.width + 2 * margin);
}
};
@ -853,7 +893,7 @@ Node.prototype._drawText = function (ctx) {
Node.prototype._label = function (ctx, text, x, y, align, baseline) {
if (text) {
if (text && this.fontSize * this.graphScale > this.fontDrawThreshold) {
ctx.font = (this.selected ? "bold " : "") + this.fontSize + "px " + this.fontFace;
ctx.fillStyle = this.fontColor || "black";
ctx.textAlign = align || "center";
@ -907,7 +947,7 @@ Node.prototype.inArea = function() {
else {
return true;
}
}
};
/**
* checks if the core of the node is in the display area, this is used for opening clusters around zoom
@ -918,7 +958,7 @@ Node.prototype.inView = function() {
this.x < this.canvasBottomRight.x &&
this.y >= this.canvasTopLeft.y &&
this.y < this.canvasBottomRight.y);
}
};
/**
* This allows the zoom level of the graph to influence the rendering
@ -930,6 +970,7 @@ Node.prototype.inView = function() {
*/
Node.prototype.setScaleAndPos = function(scale,canvasTopLeft,canvasBottomRight) {
this.graphScaleInv = 1.0/scale;
this.graphScale = scale;
this.canvasTopLeft = canvasTopLeft;
this.canvasBottomRight = canvasBottomRight;
};
@ -942,17 +983,9 @@ Node.prototype.setScaleAndPos = function(scale,canvasTopLeft,canvasBottomRight)
*/
Node.prototype.setScale = function(scale) {
this.graphScaleInv = 1.0/scale;
this.graphScale = scale;
};
/**
* This function updates the damping parameter for clusters, based ont he
*
* @param {Number} numberOfNodes
*/
Node.prototype.updateDamping = function(numberOfNodes) {
this.damping = (0.8 + 0.1*this.clusterSize * (1 + Math.pow(numberOfNodes,-2)));
this.damping *= this.dampingFactor;
};
/**
@ -971,8 +1004,10 @@ Node.prototype.clearVelocity = function() {
*/
Node.prototype.updateVelocity = function(massBeforeClustering) {
var energyBefore = this.vx * this.vx * massBeforeClustering;
//this.vx = (this.vx < 0) ? -Math.sqrt(energyBefore/this.mass) : Math.sqrt(energyBefore/this.mass);
this.vx = Math.sqrt(energyBefore/this.mass);
energyBefore = this.vy * this.vy * massBeforeClustering;
//this.vy = (this.vy < 0) ? -Math.sqrt(energyBefore/this.mass) : Math.sqrt(energyBefore/this.mass);
this.vy = Math.sqrt(energyBefore/this.mass);
};

+ 128
- 0
src/graph/css/graph-manipulation.css View File

@ -0,0 +1,128 @@
div.graph-manipulationDiv {
border-width:0px;
border-bottom: 1px;
border-style:solid;
border-color: #d6d9d8;
background: #ffffff; /* Old browsers */
background: -moz-linear-gradient(top, #ffffff 0%, #fcfcfc 48%, #fafafa 50%, #fcfcfc 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffffff), color-stop(48%,#fcfcfc), color-stop(50%,#fafafa), color-stop(100%,#fcfcfc)); /* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, #ffffff 0%,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top, #ffffff 0%,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%); /* Opera 11.10+ */
background: -ms-linear-gradient(top, #ffffff 0%,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%); /* IE10+ */
background: linear-gradient(to bottom, #ffffff 0%,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%); /* W3C */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#fcfcfc',GradientType=0 ); /* IE6-9 */
width: 600px;
height:30px;
z-index:10;
position:absolute;
}
div.graph-manipulation-editMode {
height:30px;
z-index:10;
position:absolute;
margin-top:20px;
}
div.graph-manipulation-closeDiv {
height:30px;
width:30px;
z-index:11;
position:absolute;
margin-top:3px;
margin-left:590px;
background-position: 0px 0px;
background-repeat:no-repeat;
background-image: url("../../dist/img/cross.png");
cursor: pointer;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
span.graph-manipulationUI {
font-family: verdana;
font-size: 12px;
-moz-border-radius: 15px;
border-radius: 15px;
display:inline-block;
background-position: 0px 0px;
background-repeat:no-repeat;
height:24px;
margin: -14px 0px 0px 10px;
vertical-align:middle;
cursor: pointer;
padding: 0px 8px 0px 8px;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
span.graph-manipulationUI:hover {
box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.20);
}
span.graph-manipulationUI:active {
box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.50);
}
span.graph-manipulationUI.back {
background-image: url("../../dist/img/backIcon.png");
}
span.graph-manipulationUI.none:hover {
box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.0);
cursor: default;
}
span.graph-manipulationUI.none:active {
box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.0);
}
span.graph-manipulationUI.none {
padding: 0px 0px 0px 0px;
}
span.graph-manipulationUI.notification{
margin: 2px;
font-weight: bold;
}
span.graph-manipulationUI.add {
background-image: url("../../dist/img/addNodeIcon.png");
}
span.graph-manipulationUI.edit {
background-image: url("../../dist/img/editIcon.png");
}
span.graph-manipulationUI.edit.editmode {
background-color: #fcfcfc;
border-style:solid;
border-width:1px;
border-color: #cccccc;
}
span.graph-manipulationUI.connect {
background-image: url("../../dist/img/connectIcon.png");
}
span.graph-manipulationUI.delete {
background-image: url("../../dist/img/deleteIcon.png");
}
/* top right bottom left */
span.graph-manipulationLabel {
margin: 0px 0px 0px 23px;
line-height: 25px;
}
div.graph-seperatorLine {
display:inline-block;
width:1px;
height:20px;
background-color: #bdbdbd;
margin: 5px 7px 0px 15px;
}

+ 62
- 0
src/graph/css/graph-navigation.css View File

@ -0,0 +1,62 @@
div.graph-navigation {
width:34px;
height:34px;
z-index:10;
-moz-border-radius: 17px;
border-radius: 17px;
position:absolute;
display:inline-block;
background-position: 2px 2px;
background-repeat:no-repeat;
cursor: pointer;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
div.graph-navigation:hover {
box-shadow: 0px 0px 3px 3px rgba(56, 207, 21, 0.30);
}
div.graph-navigation:active {
box-shadow: 0px 0px 1px 3px rgba(56, 207, 21, 0.95);
}
div.graph-navigation.up {
background-image: url("../../dist/img/upArrow.png");
margin-top:520px;
margin-left:55px;
}
div.graph-navigation.down {
background-image: url("../../dist/img/downArrow.png");
margin-top:560px;
margin-left:55px;
}
div.graph-navigation.left {
background-image: url("../../dist/img/leftArrow.png");
margin-top:560px;
margin-left:15px;
}
div.graph-navigation.right {
background-image: url("../../dist/img/rightArrow.png");
margin-top:560px;
margin-left:95px;
}
div.graph-navigation.zoomIn {
background-image: url("../../dist/img/plus.png");
margin-top:560px;
margin-left:555px;
}
div.graph-navigation.zoomOut {
background-image: url("../../dist/img/minus.png");
margin-top:560px;
margin-left:515px;
}
div.graph-navigation.zoomExtends {
background-image: url("../../dist/img/zoomExtends.png");
margin-top:520px;
margin-left:555px;
}

src/graph/ClusterMixin.js → src/graph/graphMixins/ClusterMixin.js View File

@ -8,23 +8,24 @@
*/
var ClusterMixin = {
/**
* This is only called in the constructor of the graph object
* */
/**
* This is only called in the constructor of the graph object
*
*/
startWithClustering : function() {
// cluster if the data set is big
this.clusterToFit(this.constants.clustering.initialMaxNodes, true);
// cluster if the data set is big
this.clusterToFit(this.constants.clustering.initialMaxNodes, true);
// updates the lables after clustering
this.updateLabels();
// updates the lables after clustering
this.updateLabels();
// this is called here because if clusterin is disabled, the start and stabilize are called in
// the setData function.
if (this.stabilize) {
this._doStabilize();
}
this.start();
},
// this is called here because if clusterin is disabled, the start and stabilize are called in
// the setData function.
if (this.stabilize) {
this._doStabilize();
}
this.start();
},
/**
* This function clusters until the initialMaxNodes has been reached
@ -41,20 +42,23 @@ var ClusterMixin = {
// we first cluster the hubs, then we pull in the outliers, repeat
while (numberOfNodes > maxNumberOfNodes && level < maxLevels) {
if (level % 3 == 0) {
this.forceAggregateHubs();
this.forceAggregateHubs(true);
this.normalizeClusterLevels();
}
else {
this.increaseClusterLevel();
this.increaseClusterLevel(); // this also includes a cluster normalization
}
numberOfNodes = this.nodeIndices.length;
level += 1;
}
// after the clustering we reposition the nodes to reduce the initial chaos
if (level > 1 && reposition == true) {
if (level > 0 && reposition == true) {
this.repositionNodes();
}
},
this._updateCalculationNodes();
},
/**
* This function can be called to open up a specific cluster. It is only called by
@ -66,12 +70,16 @@ var ClusterMixin = {
var isMovingBeforeClustering = this.moving;
if (node.clusterSize > this.constants.clustering.sectorThreshold && this._nodeInActiveArea(node) &&
!(this._sector() == "default" && this.nodeIndices.length == 1)) {
// this loads a new sector, loads the nodes and edges and nodeIndices of it.
this._addSector(node);
var level = 0;
// we decluster until we reach a decent number of nodes
while ((this.nodeIndices.length < this.constants.clustering.initialMaxNodes) && (level < 10)) {
this.decreaseClusterLevel();
level += 1;
}
}
else {
this._expandClusterNode(node,false,true);
@ -79,6 +87,7 @@ var ClusterMixin = {
// update the index list, dynamic edges and labels
this._updateNodeIndexList();
this._updateDynamicEdges();
this._updateCalculationNodes();
this.updateLabels();
}
@ -86,7 +95,8 @@ var ClusterMixin = {
if (this.moving != isMovingBeforeClustering) {
this.start();
}
},
},
/**
* This calls the updateClustes with default arguments
@ -97,6 +107,7 @@ var ClusterMixin = {
}
},
/**
* This function can be called to increase the cluster level. This means that the nodes with only one edge connection will
* be clustered with their connected node. This can be repeated as many times as needed.
@ -104,8 +115,7 @@ var ClusterMixin = {
*/
increaseClusterLevel : function() {
this.updateClusters(-1,false,true);
},
},
/**
@ -115,7 +125,7 @@ var ClusterMixin = {
*/
decreaseClusterLevel : function() {
this.updateClusters(1,false,true);
},
},
/**
@ -129,7 +139,7 @@ var ClusterMixin = {
* @param {Boolean} force | enabled or disable forcing
*
*/
updateClusters : function(zoomDirection,recursive,force) {
updateClusters : function(zoomDirection,recursive,force,doNotStart) {
var isMovingBeforeClustering = this.moving;
var amountOfNodes = this.nodeIndices.length;
@ -178,13 +188,19 @@ var ClusterMixin = {
// if a cluster was formed, we increase the clusterSession
if (this.nodeIndices.length < amountOfNodes) { // this means a clustering operation has taken place
this.clusterSession += 1;
// if clusters have been made, we normalize the cluster level
this.normalizeClusterLevels();
}
// if the simulation was settled, we restart the simulation if a cluster has been formed or expanded
if (this.moving != isMovingBeforeClustering) {
this.start();
if (doNotStart == false || doNotStart === undefined) {
// if the simulation was settled, we restart the simulation if a cluster has been formed or expanded
if (this.moving != isMovingBeforeClustering) {
this.start();
}
}
},
this._updateCalculationNodes();
},
/**
* This function handles the chains. It is called on every updateClusters().
@ -196,7 +212,7 @@ var ClusterMixin = {
this._reduceAmountOfChains(1 - this.constants.clustering.chainThreshold / chainPercentage)
}
},
},
/**
* this functions starts clustering by hubs
@ -207,14 +223,14 @@ var ClusterMixin = {
_aggregateHubs : function(force) {
this._getHubSize();
this._formClustersByHub(force,false);
},
},
/**
* This function is fired by keypress. It forces hubs to form.
*
*/
forceAggregateHubs : function() {
forceAggregateHubs : function(doNotStart) {
var isMovingBeforeClustering = this.moving;
var amountOfNodes = this.nodeIndices.length;
@ -230,11 +246,13 @@ var ClusterMixin = {
this.clusterSession += 1;
}
// if the simulation was settled, we restart the simulation if a cluster has been formed or expanded
if (this.moving != isMovingBeforeClustering) {
this.start();
if (doNotStart == false || doNotStart === undefined) {
// if the simulation was settled, we restart the simulation if a cluster has been formed or expanded
if (this.moving != isMovingBeforeClustering) {
this.start();
}
}
},
},
/**
* If a cluster takes up more than a set percentage of the screen, open the cluster
@ -253,7 +271,7 @@ var ClusterMixin = {
}
}
}
},
},
/**
@ -266,8 +284,9 @@ var ClusterMixin = {
for (var i = 0; i < this.nodeIndices.length; i++) {
var node = this.nodes[this.nodeIndices[i]];
this._expandClusterNode(node,recursive,force);
this._updateCalculationNodes();
}
},
},
/**
* This function checks if a node has to be opened. This is done by checking the zoom level.
@ -313,7 +332,7 @@ var ClusterMixin = {
}
}
}
},
},
/**
* ONLY CALLED FROM _expandClusterNode
@ -330,14 +349,13 @@ var ClusterMixin = {
* @param {Boolean} openAll | This will recursively force all nodes in the parent to be released
* @private
*/
_expelChildFromParent : function(parentNode, containedNodeId, recursive, force, openAll) {
_expelChildFromParent : function(parentNode, containedNodeId, recursive, force, openAll) {
var childNode = parentNode.containedNodes[containedNodeId];
// if child node has been added on smaller scale than current, kick out
if (childNode.formationScale < this.scale || force == true) {
// remove the selection, first remove the selection from the connected edges
this._unselectConnectedEdges(parentNode);
parentNode.unselect();
// unselect all selected items
this._unselectAll();
// put the child node back in the global nodes object
this.nodes[containedNodeId] = childNode;
@ -352,14 +370,14 @@ var ClusterMixin = {
this._validateEdges(parentNode);
// undo the changes from the clustering operation on the parent node
parentNode.mass -= this.constants.clustering.massTransferCoefficient * childNode.mass;
parentNode.fontSize -= this.constants.clustering.fontSizeMultiplier * childNode.clusterSize;
parentNode.mass -= childNode.mass;
parentNode.clusterSize -= childNode.clusterSize;
parentNode.fontSize = Math.min(this.constants.clustering.maxFontSize, this.constants.nodes.fontSize + this.constants.clustering.fontSizeMultiplier*parentNode.clusterSize);
parentNode.dynamicEdgesLength = parentNode.dynamicEdges.length;
// place the child node near the parent, not at the exact same location to avoid chaos in the system
childNode.x = parentNode.x + this.constants.edges.length * 0.3 * (0.5 - Math.random()) * parentNode.clusterSize;
childNode.y = parentNode.y + this.constants.edges.length * 0.3 * (0.5 - Math.random()) * parentNode.clusterSize;
childNode.x = parentNode.x + parentNode.growthIndicator * (0.5 - Math.random());
childNode.y = parentNode.y + parentNode.growthIndicator * (0.5 - Math.random());
// remove node from the list
delete parentNode.containedNodes[containedNodeId];
@ -379,24 +397,37 @@ var ClusterMixin = {
parentNode.clusterSessions.pop();
}
this._repositionBezierNodes(childNode);
// this._repositionBezierNodes(parentNode);
// remove the clusterSession from the child node
childNode.clusterSession = 0;
// restart the simulation to reorganise all nodes
this.moving = true;
// recalculate the size of the node on the next time the node is rendered
parentNode.clearSizeCache();
// this unselects the rest of the edges
this._unselectConnectedEdges(parentNode);
// restart the simulation to reorganise all nodes
this.moving = true;
}
// check if a further expansion step is possible if recursivity is enabled
if (recursive == true) {
this._expandClusterNode(childNode,recursive,force,openAll);
}
},
},
/**
* position the bezier nodes at the center of the edges
*
* @param node
* @private
*/
_repositionBezierNodes : function(node) {
for (var i = 0; i < node.dynamicEdges.length; i++) {
node.dynamicEdges[i].positionBezierNode();
}
},
/**
@ -415,7 +446,8 @@ var ClusterMixin = {
else {
this._forceClustersByZoom();
}
},
},
/**
* This function handles the clustering by zooming out, this is based on a minimum edge distance
@ -458,7 +490,7 @@ var ClusterMixin = {
}
}
}
},
},
/**
* This function forces the graph to cluster all nodes with only one connecting edge to their
@ -489,8 +521,41 @@ var ClusterMixin = {
}
}
}
},
},
/**
* To keep the nodes of roughly equal size we normalize the cluster levels.
* This function clusters a node to its smallest connected neighbour.
*
* @param node
* @private
*/
_clusterToSmallestNeighbour : function(node) {
var smallestNeighbour = -1;
var smallestNeighbourNode = null;
for (var i = 0; i < node.dynamicEdges.length; i++) {
if (node.dynamicEdges[i] !== undefined) {
var neighbour = null;
if (node.dynamicEdges[i].fromId != node.id) {
neighbour = node.dynamicEdges[i].from;
}
else if (node.dynamicEdges[i].toId != node.id) {
neighbour = node.dynamicEdges[i].to;
}
if (neighbour != null && smallestNeighbour > neighbour.clusterSessions.length) {
smallestNeighbour = neighbour.clusterSessions.length;
smallestNeighbourNode = neighbour;
}
}
}
if (neighbour != null && this.nodes[neighbour.id] !== undefined) {
this._addToCluster(neighbour, node, true);
}
},
/**
@ -508,7 +573,7 @@ var ClusterMixin = {
this._formClusterFromHub(this.nodes[nodeId],force,onlyEqual);
}
}
},
},
/**
* This function forms a cluster from a specific preselected hub node
@ -578,7 +643,7 @@ var ClusterMixin = {
}
}
}
},
},
@ -617,9 +682,9 @@ var ClusterMixin = {
// update the properties of the child and parent
var massBefore = parentNode.mass;
childNode.clusterSession = this.clusterSession;
parentNode.mass += this.constants.clustering.massTransferCoefficient * childNode.mass;
parentNode.mass += childNode.mass;
parentNode.clusterSize += childNode.clusterSize;
parentNode.fontSize += this.constants.clustering.fontSizeMultiplier * childNode.clusterSize;
parentNode.fontSize = Math.min(this.constants.clustering.maxFontSize, this.constants.nodes.fontSize + this.constants.clustering.fontSizeMultiplier*parentNode.clusterSize);
// keep track of the clustersessions so we can open the cluster up as it has been formed.
if (parentNode.clusterSessions[parentNode.clusterSessions.length - 1] != this.clusterSession) {
@ -649,12 +714,12 @@ var ClusterMixin = {
// restart the simulation to reorganise all nodes
this.moving = true;
},
},
/**
* This function will apply the changes made to the remainingEdges during the formation of the clusters.
* This is a seperate function to allow for level-wise collapsing of the node tree.
* This is a seperate function to allow for level-wise collapsing of the node barnesHutTree.
* It has to be called if a level is collapsed. It is called by _formClusters().
* @private
*/
@ -679,7 +744,7 @@ var ClusterMixin = {
}
node.dynamicEdgesLength -= correction;
}
},
},
/**
@ -708,7 +773,7 @@ var ClusterMixin = {
break;
}
}
},
},
/**
* This function connects an edge that was connected to a child node to the parent node.
@ -739,9 +804,17 @@ var ClusterMixin = {
this._addToReroutedEdges(parentNode,childNode,edge);
}
},
},
/**
* If a node is connected to itself, a circular edge is drawn. When clustering we want to contain
* these edges inside of the cluster.
*
* @param parentNode
* @param childNode
* @private
*/
_containCircularEdgesFromNode : function(parentNode, childNode) {
// manage all the edges connected to the child and parent nodes
for (var i = 0; i < parentNode.dynamicEdges.length; i++) {
@ -812,7 +885,7 @@ var ClusterMixin = {
// remove the entry from the rerouted edges
delete parentNode.reroutedEdges[childNode.id];
}
},
},
/**
@ -830,7 +903,7 @@ var ClusterMixin = {
parentNode.dynamicEdges.splice(i,1);
}
}
},
},
/**
@ -855,7 +928,7 @@ var ClusterMixin = {
// remove the entry from the contained edges
delete parentNode.containedEdges[childNode.id];
},
},
@ -894,14 +967,56 @@ var ClusterMixin = {
}
/* Debug Override */
// for (nodeId in this.nodes) {
// if (this.nodes.hasOwnProperty(nodeId)) {
// node = this.nodes[nodeId];
// node.label = String(Math.round(node.width)).concat(":",Math.round(node.width*this.scale));
// }
// }
// for (nodeId in this.nodes) {
// if (this.nodes.hasOwnProperty(nodeId)) {
// node = this.nodes[nodeId];
// node.label = String(node.fx).concat(",",node.fy);
// }
// }
},
/**
* We want to keep the cluster level distribution rather small. This means we do not want unclustered nodes
* if the rest of the nodes are already a few cluster levels in.
* To fix this we use this function. It determines the min and max cluster level and sends nodes that have not
* clustered enough to the clusterToSmallestNeighbours function.
*/
normalizeClusterLevels : function() {
var maxLevel = 0;
var minLevel = 1e9;
var clusterLevel = 0;
// we loop over all nodes in the list
for (var nodeId in this.nodes) {
if (this.nodes.hasOwnProperty(nodeId)) {
clusterLevel = this.nodes[nodeId].clusterSessions.length;
if (maxLevel < clusterLevel) {maxLevel = clusterLevel;}
if (minLevel > clusterLevel) {minLevel = clusterLevel;}
}
}
if (maxLevel - minLevel > this.constants.clustering.clusterLevelDifference) {
var amountOfNodes = this.nodeIndices.length;
var targetLevel = maxLevel - this.constants.clustering.clusterLevelDifference;
// we loop over all nodes in the list
for (var nodeId in this.nodes) {
if (this.nodes.hasOwnProperty(nodeId)) {
if (this.nodes[nodeId].clusterSessions.length < targetLevel) {
this._clusterToSmallestNeighbour(this.nodes[nodeId]);
}
}
}
this._updateNodeIndexList();
this._updateDynamicEdges();
// if a cluster was formed, we increase the clusterSession
if (this.nodeIndices.length != amountOfNodes) {
this.clusterSession += 1;
}
}
},
},
/**
@ -918,7 +1033,7 @@ var ClusterMixin = {
&&
Math.abs(node.y - this.areaCenter.y) <= this.constants.clustering.activeAreaBoxSize/this.scale
)
},
},
/**
@ -929,17 +1044,15 @@ var ClusterMixin = {
repositionNodes : function() {
for (var i = 0; i < this.nodeIndices.length; i++) {
var node = this.nodes[this.nodeIndices[i]];
if (!node.isFixed()) {
var radius = this.constants.edges.length * (1 + 0.6*node.clusterSize);
if ((node.xFixed == false || node.yFixed == false) && this.createNodeOnClick != true) {
var radius = this.constants.physics.springLength * Math.min(100,node.mass);
var angle = 2 * Math.PI * Math.random();
node.x = radius * Math.cos(angle);
node.y = radius * Math.sin(angle);
if (node.xFixed == false) {node.x = radius * Math.cos(angle);}
if (node.yFixed == false) {node.y = radius * Math.sin(angle);}
this._repositionBezierNodes(node);
}
}
},
},
/**
@ -955,6 +1068,7 @@ var ClusterMixin = {
var largestHub = 0;
for (var i = 0; i < this.nodeIndices.length; i++) {
var node = this.nodes[this.nodeIndices[i]];
if (node.dynamicEdgesLength > largestHub) {
largestHub = node.dynamicEdgesLength;
@ -979,7 +1093,7 @@ var ClusterMixin = {
// console.log("average",average,"averageSQ",averageSquared,"var",variance,"std",standardDeviation);
// console.log("hubThreshold:",this.hubThreshold);
},
},
/**
@ -1002,7 +1116,7 @@ var ClusterMixin = {
}
}
}
},
},
/**
* We get the amount of "extension nodes" or chains. These are not quickly clustered with the outliers and hubs methods
@ -1022,5 +1136,6 @@ var ClusterMixin = {
}
}
return chains/total;
}
}
};

+ 433
- 0
src/graph/graphMixins/ManipulationMixin.js View File

@ -0,0 +1,433 @@
/**
* Created by Alex on 2/4/14.
*/
var manipulationMixin = {
/**
* clears the toolbar div element of children
*
* @private
*/
_clearManipulatorBar : function() {
while (this.manipulationDiv.hasChildNodes()) {
this.manipulationDiv.removeChild(this.manipulationDiv.firstChild);
}
},
/**
* Manipulation UI temporarily overloads certain functions to extend or replace them. To be able to restore
* these functions to their original functionality, we saved them in this.cachedFunctions.
* This function restores these functions to their original function.
*
* @private
*/
_restoreOverloadedFunctions : function() {
for (var functionName in this.cachedFunctions) {
if (this.cachedFunctions.hasOwnProperty(functionName)) {
this[functionName] = this.cachedFunctions[functionName];
}
}
},
/**
* Enable or disable edit-mode.
*
* @private
*/
_toggleEditMode : function() {
this.editMode = !this.editMode;
var toolbar = document.getElementById("graph-manipulationDiv");
var closeDiv = document.getElementById("graph-manipulation-closeDiv");
var editModeDiv = document.getElementById("graph-manipulation-editMode");
if (this.editMode == true) {
toolbar.style.display="block";
closeDiv.style.display="block";
editModeDiv.style.display="none";
closeDiv.onclick = this._toggleEditMode.bind(this);
}
else {
toolbar.style.display="none";
closeDiv.style.display="none";
editModeDiv.style.display="block";
closeDiv.onclick = null;
}
this._createManipulatorBar()
},
/**
* main function, creates the main toolbar. Removes functions bound to the select event. Binds all the buttons of the toolbar.
*
* @private
*/
_createManipulatorBar : function() {
// remove bound functions
this.off('select', this.boundFunction);
// restore overloaded functions
this._restoreOverloadedFunctions();
// resume calculation
this.freezeSimulation = false;
// reset global variables
this.blockConnectingEdgeSelection = false;
this.forceAppendSelection = false
if (this.editMode == true) {
while (this.manipulationDiv.hasChildNodes()) {
this.manipulationDiv.removeChild(this.manipulationDiv.firstChild);
}
// add the icons to the manipulator div
this.manipulationDiv.innerHTML = "" +
"<span class='graph-manipulationUI add' id='graph-manipulate-addNode'>" +
"<span class='graph-manipulationLabel'>Add Node</span></span>" +
"<div class='graph-seperatorLine'></div>" +
"<span class='graph-manipulationUI connect' id='graph-manipulate-connectNode'>" +
"<span class='graph-manipulationLabel'>Add Link</span></span>";
if (this._getSelectedNodeCount() == 1 && this.triggerFunctions.edit) {
this.manipulationDiv.innerHTML += "" +
"<div class='graph-seperatorLine'></div>" +
"<span class='graph-manipulationUI edit' id='graph-manipulate-editNode'>" +
"<span class='graph-manipulationLabel'>Edit Node</span></span>";
}
if (this._selectionIsEmpty() == false) {
this.manipulationDiv.innerHTML += "" +
"<div class='graph-seperatorLine'></div>" +
"<span class='graph-manipulationUI delete' id='graph-manipulate-delete'>" +
"<span class='graph-manipulationLabel'>Delete selected</span></span>";
}
// bind the icons
var addNodeButton = document.getElementById("graph-manipulate-addNode");
addNodeButton.onclick = this._createAddNodeToolbar.bind(this);
var addEdgeButton = document.getElementById("graph-manipulate-connectNode");
addEdgeButton.onclick = this._createAddEdgeToolbar.bind(this);
if (this._getSelectedNodeCount() == 1 && this.triggerFunctions.edit) {
var editButton = document.getElementById("graph-manipulate-editNode");
editButton.onclick = this._editNode.bind(this);
}
if (this._selectionIsEmpty() == false) {
var deleteButton = document.getElementById("graph-manipulate-delete");
deleteButton.onclick = this._deleteSelected.bind(this);
}
var closeDiv = document.getElementById("graph-manipulation-closeDiv");
closeDiv.onclick = this._toggleEditMode.bind(this);
this.boundFunction = this._createManipulatorBar.bind(this);
this.on('select', this.boundFunction);
}
else {
this.editModeDiv.innerHTML = "" +
"<span class='graph-manipulationUI edit editmode' id='graph-manipulate-editModeButton'>" +
"<span class='graph-manipulationLabel'>Edit</span></span>"
var editModeButton = document.getElementById("graph-manipulate-editModeButton");
editModeButton.onclick = this._toggleEditMode.bind(this);
}
},
/**
* Create the toolbar for adding Nodes
*
* @private
*/
_createAddNodeToolbar : function() {
// clear the toolbar
this._clearManipulatorBar();
this.off('select', this.boundFunction);
// create the toolbar contents
this.manipulationDiv.innerHTML = "" +
"<span class='graph-manipulationUI back' id='graph-manipulate-back'>" +
"<span class='graph-manipulationLabel'>Back</span></span>" +
"<div class='graph-seperatorLine'></div>" +
"<span class='graph-manipulationUI none' id='graph-manipulate-back'>" +
"<span class='graph-manipulationLabel'>Click in an empty space to place a new node</span></span>";
// bind the icon
var backButton = document.getElementById("graph-manipulate-back");
backButton.onclick = this._createManipulatorBar.bind(this);
// we use the boundFunction so we can reference it when we unbind it from the "select" event.
this.boundFunction = this._addNode.bind(this);
this.on('select', this.boundFunction);
},
/**
* create the toolbar to connect nodes
*
* @private
*/
_createAddEdgeToolbar : function() {
// clear the toolbar
this._clearManipulatorBar();
this._unselectAll(true);
this.freezeSimulation = true;
this.off('select', this.boundFunction);
this._unselectAll();
this.forceAppendSelection = false;
this.blockConnectingEdgeSelection = true;
this.manipulationDiv.innerHTML = "" +
"<span class='graph-manipulationUI back' id='graph-manipulate-back'>" +
"<span class='graph-manipulationLabel'>Back</span></span>" +
"<div class='graph-seperatorLine'></div>" +
"<span class='graph-manipulationUI none' id='graph-manipulate-back'>" +
"<span id='graph-manipulatorLabel' class='graph-manipulationLabel'>Click on a node and drag the edge to another node to connect them.</span></span>";
// bind the icon
var backButton = document.getElementById("graph-manipulate-back");
backButton.onclick = this._createManipulatorBar.bind(this);
// we use the boundFunction so we can reference it when we unbind it from the "select" event.
this.boundFunction = this._handleConnect.bind(this);
this.on('select', this.boundFunction);
// temporarily overload functions
this.cachedFunctions["_handleTouch"] = this._handleTouch;
this.cachedFunctions["_handleOnRelease"] = this._handleOnRelease;
this._handleTouch = this._handleConnect;
this._handleOnRelease = this._finishConnect;
// redraw to show the unselect
this._redraw();
},
/**
* the function bound to the selection event. It checks if you want to connect a cluster and changes the description
* to walk the user through the process.
*
* @private
*/
_handleConnect : function(pointer) {
if (this._getSelectedNodeCount() == 0) {
var node = this._getNodeAt(pointer);
if (node != null) {
if (node.clusterSize > 1) {
alert("Cannot create edges to a cluster.")
}
else {
this._selectObject(node,false);
// create a node the temporary line can look at
this.sectors['support']['nodes']['targetNode'] = new Node({id:'targetNode'},{},{},this.constants);
this.sectors['support']['nodes']['targetNode'].x = node.x;
this.sectors['support']['nodes']['targetNode'].y = node.y;
this.sectors['support']['nodes']['targetViaNode'] = new Node({id:'targetViaNode'},{},{},this.constants);
this.sectors['support']['nodes']['targetViaNode'].x = node.x;
this.sectors['support']['nodes']['targetViaNode'].y = node.y;
this.sectors['support']['nodes']['targetViaNode'].parentEdgeId = "connectionEdge";
// create a temporary edge
this.edges['connectionEdge'] = new Edge({id:"connectionEdge",from:node.id,to:this.sectors['support']['nodes']['targetNode'].id}, this, this.constants);
this.edges['connectionEdge'].from = node;
this.edges['connectionEdge'].connected = true;
this.edges['connectionEdge'].smooth = true;
this.edges['connectionEdge'].selected = true;
this.edges['connectionEdge'].to = this.sectors['support']['nodes']['targetNode'];
this.edges['connectionEdge'].via = this.sectors['support']['nodes']['targetViaNode'];
this.cachedFunctions["_handleOnDrag"] = this._handleOnDrag;
this._handleOnDrag = function(event) {
var pointer = this._getPointer(event.gesture.touches[0]);
this.sectors['support']['nodes']['targetNode'].x = this._canvasToX(pointer.x);
this.sectors['support']['nodes']['targetNode'].y = this._canvasToY(pointer.y);
this.sectors['support']['nodes']['targetViaNode'].x = 0.5 * (this._canvasToX(pointer.x) + this.edges['connectionEdge'].from.x);
this.sectors['support']['nodes']['targetViaNode'].y = this._canvasToY(pointer.y);
};
this.moving = true;
this.start();
}
}
}
},
_finishConnect : function(pointer) {
if (this._getSelectedNodeCount() == 1) {
// restore the drag function
this._handleOnDrag = this.cachedFunctions["_handleOnDrag"];
delete this.cachedFunctions["_handleOnDrag"];
// remember the edge id
var connectFromId = this.edges['connectionEdge'].fromId;
// remove the temporary nodes and edge
delete this.edges['connectionEdge']
delete this.sectors['support']['nodes']['targetNode'];
delete this.sectors['support']['nodes']['targetViaNode'];
var node = this._getNodeAt(pointer);
if (node != null) {
if (node.clusterSize > 1) {
alert("Cannot create edges to a cluster.")
}
else {
this._createEdge(connectFromId,node.id);
this._createManipulatorBar();
}
}
this._unselectAll();
}
},
/**
* Adds a node on the specified location
*
* @param {Object} pointer
*/
_addNode : function() {
if (this._selectionIsEmpty() && this.editMode == true) {
var positionObject = this._pointerToPositionObject(this.pointerPosition);
var defaultData = {id:util.randomUUID(),x:positionObject.left,y:positionObject.top,label:"new",fixed:false};
if (this.triggerFunctions.add) {
if (this.triggerFunctions.add.length == 2) {
var me = this;
this.triggerFunctions.add(defaultData, function(finalizedData) {
me.createNodeOnClick = true;
me.nodesData.add(finalizedData);
me.createNodeOnClick = false;
me._createManipulatorBar();
me.moving = true;
me.start();
});
}
else {
alert("The function for add does not support two arguments (data,callback).");
this._createManipulatorBar();
this.moving = true;
this.start();
}
}
else {
this.createNodeOnClick = true;
this.nodesData.add(defaultData);
this.createNodeOnClick = false;
this._createManipulatorBar();
this.moving = true;
this.start();
}
}
},
/**
* connect two nodes with a new edge.
*
* @private
*/
_createEdge : function(sourceNodeId,targetNodeId) {
if (this.editMode == true) {
var defaultData = {from:sourceNodeId, to:targetNodeId};
if (this.triggerFunctions.connect) {
if (this.triggerFunctions.connect.length == 2) {
var me = this;
this.triggerFunctions.connect(defaultData, function(finalizedData) {
me.edgesData.add(finalizedData)
me.moving = true;
me.start();
});
}
else {
alert("The function for connect does not support two arguments (data,callback).");
this.moving = true;
this.start();
}
}
else {
this.edgesData.add(defaultData)
this.moving = true;
this.start();
}
}
},
/**
* Create the toolbar to edit the selected node. The label and the color can be changed. Other colors are derived from the chosen color.
*
* @private
*/
_editNode : function() {
if (this.triggerFunctions.edit && this.editMode == true) {
var node = this._getSelectedNode();
var data = {id:node.id,
label: node.label,
group: node.group,
shape: node.shape,
color: {
background:node.color.background,
border:node.color.border,
highlight: {
background:node.color.highlight.background,
border:node.color.highlight.border
}
}};
if (this.triggerFunctions.edit.length == 2) {
var me = this;
this.triggerFunctions.edit(data, function (finalizedData) {
me.nodesData.update(finalizedData);
me._createManipulatorBar();
me.moving = true;
me.start();
});
}
else {
alert("The function for edit does not support two arguments (data, callback).")
}
}
else {
alert("No edit function has been bound to this button.")
}
},
/**
* delete everything in the selection
*
* @private
*/
_deleteSelected : function() {
if (!this._selectionIsEmpty() && this.editMode == true) {
if (!this._clusterInSelection()) {
var selectedNodes = this.getSelectedNodes();
var selectedEdges = this.getSelectedEdges();
if (this.triggerFunctions.delete) {
var me = this;
var data = {nodes: selectedNodes, edges: selectedEdges};
if (this.triggerFunctions.delete.length = 2) {
this.triggerFunctions.delete(data, function (finalizedData) {
me.edgesData.remove(finalizedData.edges);
me.nodesData.remove(finalizedData.nodes);
this._unselectAll();
me.moving = true;
me.start();
});
}
else {
alert("The function for edit does not support two arguments (data, callback).")
}
}
else {
this.edgesData.remove(selectedEdges);
this.nodesData.remove(selectedNodes);
this._unselectAll();
this.moving = true;
this.start();
}
}
else {
alert("Clusters cannot be deleted.");
}
}
}
};

+ 219
- 0
src/graph/graphMixins/MixinLoader.js View File

@ -0,0 +1,219 @@
/**
* Created by Alex on 2/10/14.
*/
var graphMixinLoaders = {
/**
* Load a mixin into the graph object
*
* @param {Object} sourceVariable | this object has to contain functions.
* @private
*/
_loadMixin : function(sourceVariable) {
for (var mixinFunction in sourceVariable) {
if (sourceVariable.hasOwnProperty(mixinFunction)) {
Graph.prototype[mixinFunction] = sourceVariable[mixinFunction];
}
}
},
/**
* removes a mixin from the graph object.
*
* @param {Object} sourceVariable | this object has to contain functions.
* @private
*/
_clearMixin : function(sourceVariable) {
for (var mixinFunction in sourceVariable) {
if (sourceVariable.hasOwnProperty(mixinFunction)) {
Graph.prototype[mixinFunction] = undefined;
}
}
},
/**
* Mixin the physics system and initialize the parameters required.
*
* @private
*/
_loadPhysicsSystem : function() {
this._loadMixin(physicsMixin);
this._loadSelectedForceSolver();
},
/**
* This loads the node force solver based on the barnes hut or repulsion algorithm
*
* @private
*/
_loadSelectedForceSolver : function() {
// this overloads the this._calculateNodeForces
if (this.constants.physics.barnesHut.enabled == true) {
this._clearMixin(repulsionMixin);
this.constants.physics.centralGravity = this.constants.physics.barnesHut.centralGravity;
this.constants.physics.springLength = this.constants.physics.barnesHut.springLength;
this.constants.physics.springConstant = this.constants.physics.barnesHut.springConstant;
this.constants.physics.damping = this.constants.physics.barnesHut.damping;
this.constants.physics.springGrowthPerMass = this.constants.physics.barnesHut.springGrowthPerMass;
this._loadMixin(barnesHutMixin);
}
else {
this._clearMixin(barnesHutMixin);
this.barnesHutTree = undefined;
this.constants.physics.centralGravity = this.constants.physics.repulsion.centralGravity;
this.constants.physics.springLength = this.constants.physics.repulsion.springLength;
this.constants.physics.springConstant = this.constants.physics.repulsion.springConstant;
this.constants.physics.damping = this.constants.physics.repulsion.damping;
this.constants.physics.springGrowthPerMass = this.constants.physics.repulsion.springGrowthPerMass;
this._loadMixin(repulsionMixin);
}
},
/**
* Mixin the cluster system and initialize the parameters required.
*
* @private
*/
_loadClusterSystem : function() {
this.clusterSession = 0;
this.hubThreshold = 5;
this._loadMixin(ClusterMixin);
},
/**
* Mixin the sector system and initialize the parameters required
*
* @private
*/
_loadSectorSystem : function() {
this.sectors = { },
this.activeSector = ["default"];
this.sectors["active"] = { },
this.sectors["active"]["default"] = {"nodes":{},
"edges":{},
"nodeIndices":[],
"formationScale": 1.0,
"drawingNode": undefined };
this.sectors["frozen"] = {},
this.sectors["support"] = {"nodes":{},
"edges":{},
"nodeIndices":[],
"formationScale": 1.0,
"drawingNode": undefined };
this.nodeIndices = this.sectors["active"]["default"]["nodeIndices"]; // the node indices list is used to speed up the computation of the repulsion fields
this._loadMixin(SectorMixin);
},
/**
* Mixin the selection system and initialize the parameters required
*
* @private
*/
_loadSelectionSystem : function() {
this.selectionObj = { };
this._loadMixin(SelectionMixin);
},
/**
* Mixin the navigationUI (User Interface) system and initialize the parameters required
*
* @private
*/
_loadManipulationSystem : function() {
// reset global variables -- these are used by the selection of nodes and edges.
this.blockConnectingEdgeSelection = false;
this.forceAppendSelection = false
if (this.constants.dataManipulation.enabled == true) {
// load the manipulator HTML elements. All styling done in css.
if (this.manipulationDiv === undefined) {
this.manipulationDiv = document.createElement('div');
this.manipulationDiv.className = 'graph-manipulationDiv';
this.manipulationDiv.id = 'graph-manipulationDiv';
if (this.editMode == true) {
this.manipulationDiv.style.display = "block";
}
else {
this.manipulationDiv.style.display = "none";
}
this.containerElement.insertBefore(this.manipulationDiv, this.frame);
}
if (this.editModeDiv === undefined) {
this.editModeDiv = document.createElement('div');
this.editModeDiv.className = 'graph-manipulation-editMode';
this.editModeDiv.id = 'graph-manipulation-editMode';
if (this.editMode == true) {
this.editModeDiv.style.display = "none";
}
else {
this.editModeDiv.style.display = "block";
}
this.containerElement.insertBefore(this.editModeDiv, this.frame);
}
if (this.closeDiv === undefined) {
this.closeDiv = document.createElement('div');
this.closeDiv.className = 'graph-manipulation-closeDiv';
this.closeDiv.id = 'graph-manipulation-closeDiv';
this.closeDiv.style.display = this.manipulationDiv.style.display;
this.containerElement.insertBefore(this.closeDiv, this.frame);
}
// load the manipulation functions
this._loadMixin(manipulationMixin);
// create the manipulator toolbar
this._createManipulatorBar();
}
else {
if (this.manipulationDiv !== undefined) {
// removes all the bindings and overloads
this._createManipulatorBar();
// remove the manipulation divs
this.containerElement.removeChild(this.manipulationDiv);
this.containerElement.removeChild(this.editModeDiv);
this.containerElement.removeChild(this.closeDiv);
this.manipulationDiv = undefined;
this.editModeDiv = undefined;
this.closeDiv = undefined;
// remove the mixin functions
this._clearMixin(manipulationMixin);
}
}
},
/**
* Mixin the navigation (User Interface) system and initialize the parameters required
*
* @private
*/
_loadNavigationControls : function() {
this._loadMixin(NavigationMixin);
// the clean function removes the button divs, this is done to remove the bindings.
this._cleanNavigation();
if (this.constants.navigation.enabled == true) {
this._loadNavigationElements();
}
}
}

+ 173
- 0
src/graph/graphMixins/NavigationMixin.js View File

@ -0,0 +1,173 @@
/**
* Created by Alex on 1/22/14.
*/
var NavigationMixin = {
_cleanNavigation : function() {
// clean up previosu navigation items
var wrapper = document.getElementById('graph-navigation_wrapper');
if (wrapper != null) {
this.containerElement.removeChild(wrapper);
}
document.onmouseup = null;
},
/**
* Creation of the navigation controls nodes. They are drawn over the rest of the nodes and are not affected by scale and translation
* they have a triggerFunction which is called on click. If the position of the navigation controls is dependent
* on this.frame.canvas.clientWidth or this.frame.canvas.clientHeight, we flag horizontalAlignLeft and verticalAlignTop false.
* This means that the location will be corrected by the _relocateNavigation function on a size change of the canvas.
*
* @private
*/
_loadNavigationElements : function() {
this._cleanNavigation();
this.navigationDivs = {};
var navigationDivs = ['up','down','left','right','zoomIn','zoomOut','zoomExtends'];
var navigationDivActions = ['_moveUp','_moveDown','_moveLeft','_moveRight','_zoomIn','_zoomOut','zoomToFit'];
this.navigationDivs['wrapper'] = document.createElement('div');
this.navigationDivs['wrapper'].id = "graph-navigation_wrapper";
this.containerElement.insertBefore(this.navigationDivs['wrapper'],this.frame);
for (var i = 0; i < navigationDivs.length; i++) {
this.navigationDivs[navigationDivs[i]] = document.createElement('div');
this.navigationDivs[navigationDivs[i]].id = "graph-navigation_" + navigationDivs[i];
this.navigationDivs[navigationDivs[i]].className = "graph-navigation " + navigationDivs[i];
this.navigationDivs['wrapper'].appendChild(this.navigationDivs[navigationDivs[i]]);
this.navigationDivs[navigationDivs[i]].onmousedown = this[navigationDivActions[i]].bind(this);
}
document.onmouseup = this._stopMovement.bind(this);
},
/**
* this stops all movement induced by the navigation buttons
*
* @private
*/
_stopMovement : function() {
this._xStopMoving();
this._yStopMoving();
this._stopZoom();
},
/**
* stops the actions performed by page up and down etc.
*
* @param event
* @private
*/
_preventDefault : function(event) {
if (event !== undefined) {
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
}
},
/**
* move the screen up
* By using the increments, instead of adding a fixed number to the translation, we keep fluent and
* instant movement. The onKeypress event triggers immediately, then pauses, then triggers frequently
* To avoid this behaviour, we do the translation in the start loop.
*
* @private
*/
_moveUp : function(event) {
console.log("here")
this.yIncrement = this.constants.keyboard.speed.y;
this.start(); // if there is no node movement, the calculation wont be done
this._preventDefault(event);
},
/**
* move the screen down
* @private
*/
_moveDown : function(event) {
this.yIncrement = -this.constants.keyboard.speed.y;
this.start(); // if there is no node movement, the calculation wont be done
this._preventDefault(event);
},
/**
* move the screen left
* @private
*/
_moveLeft : function(event) {
this.xIncrement = this.constants.keyboard.speed.x;
this.start(); // if there is no node movement, the calculation wont be done
this._preventDefault(event);
},
/**
* move the screen right
* @private
*/
_moveRight : function(event) {
this.xIncrement = -this.constants.keyboard.speed.y;
this.start(); // if there is no node movement, the calculation wont be done
this._preventDefault(event);
},
/**
* Zoom in, using the same method as the movement.
* @private
*/
_zoomIn : function(event) {
this.zoomIncrement = this.constants.keyboard.speed.zoom;
this.start(); // if there is no node movement, the calculation wont be done
this._preventDefault(event);
},
/**
* Zoom out
* @private
*/
_zoomOut : function() {
this.zoomIncrement = -this.constants.keyboard.speed.zoom;
this.start(); // if there is no node movement, the calculation wont be done
this._preventDefault(event);
},
/**
* Stop zooming and unhighlight the zoom controls
* @private
*/
_stopZoom : function() {
this.zoomIncrement = 0;
},
/**
* Stop moving in the Y direction and unHighlight the up and down
* @private
*/
_yStopMoving : function() {
this.yIncrement = 0;
},
/**
* Stop moving in the X direction and unHighlight left and right.
* @private
*/
_xStopMoving : function() {
this.xIncrement = 0;
}
};

src/graph/SectorsMixin.js → src/graph/graphMixins/SectorsMixin.js View File

@ -58,28 +58,29 @@ var SectorMixin = {
/**
* This function sets the global references to nodes, edges and nodeIndices back to
* those of the supplied frozen sector.
* those of the supplied active sector.
*
* @param sectorId
* @private
*/
_switchToFrozenSector : function(sectorId) {
this.nodeIndices = this.sectors["frozen"][sectorId]["nodeIndices"];
this.nodes = this.sectors["frozen"][sectorId]["nodes"];
this.edges = this.sectors["frozen"][sectorId]["edges"];
_switchToSupportSector : function() {
this.nodeIndices = this.sectors["support"]["nodeIndices"];
this.nodes = this.sectors["support"]["nodes"];
this.edges = this.sectors["support"]["edges"];
},
/**
* This function sets the global references to nodes, edges and nodeIndices to
* those of the navigation controls sector.
* This function sets the global references to nodes, edges and nodeIndices back to
* those of the supplied frozen sector.
*
* @param sectorId
* @private
*/
_switchToNavigationSector : function() {
this.nodeIndices = this.sectors["navigation"]["nodeIndices"];
this.nodes = this.sectors["navigation"]["nodes"];
this.edges = this.sectors["navigation"]["edges"];
_switchToFrozenSector : function(sectorId) {
this.nodeIndices = this.sectors["frozen"][sectorId]["nodeIndices"];
this.nodes = this.sectors["frozen"][sectorId]["nodes"];
this.edges = this.sectors["frozen"][sectorId]["edges"];
},
@ -349,6 +350,9 @@ var SectorMixin = {
// finally, we update the node index list.
this._updateNodeIndexList();
// we refresh the list with calulation nodes and calculation node indices.
this._updateCalculationNodes();
}
}
},
@ -393,6 +397,35 @@ var SectorMixin = {
},
/**
* This runs a function in all active sectors. This is used in _redraw() and the _initializeForceCalculation().
*
* @param {String} runFunction | This is the NAME of a function we want to call in all active sectors
* | we dont pass the function itself because then the "this" is the window object
* | instead of the Graph object
* @param {*} [argument] | Optional: arguments to pass to the runFunction
* @private
*/
_doInSupportSector : function(runFunction,argument) {
if (argument === undefined) {
this._switchToSupportSector();
this[runFunction]();
}
else {
this._switchToSupportSector();
var args = Array.prototype.splice.call(arguments, 1);
if (args.length > 1) {
this[runFunction](args[0],args[1]);
}
else {
this[runFunction](argument);
}
}
// we revert the global references back to our active sector
this._loadLatestSector();
},
/**
* This runs a function in all frozen sectors. This is used in the _redraw().
*
@ -431,33 +464,6 @@ var SectorMixin = {
},
/**
* This runs a function in the navigation controls sector.
*
* @param {String} runFunction | This is the NAME of a function we want to call in all active sectors
* | we don't pass the function itself because then the "this" is the window object
* | instead of the Graph object
* @param {*} [argument] | Optional: arguments to pass to the runFunction
* @private
*/
_doInNavigationSector : function(runFunction,argument) {
this._switchToNavigationSector();
if (argument === undefined) {
this[runFunction]();
}
else {
var args = Array.prototype.splice.call(arguments, 1);
if (args.length > 1) {
this[runFunction](args[0],args[1]);
}
else {
this[runFunction](argument);
}
}
this._loadLatestSector();
},
/**
* This runs a function in all sectors. This is used in the _redraw().
*
@ -483,7 +489,6 @@ var SectorMixin = {
this._doInAllFrozenSectors(runFunction,argument);
}
}
},

src/graph/SelectionMixin.js → src/graph/graphMixins/SelectionMixin.js View File

@ -32,18 +32,6 @@ var SelectionMixin = {
},
/**
* retrieve all nodes in the navigation controls overlapping with given object
* @param {Object} object An object with parameters left, top, right, bottom
* @return {Number[]} An array with id's of the overlapping nodes
* @private
*/
_getAllNavigationNodesOverlappingWith : function (object) {
var overlappingNodes = [];
this._doInNavigationSector("_getNodesOverlappingWith",object,overlappingNodes);
return overlappingNodes;
},
/**
* Return a position object in canvasspace from a single point in screenspace
*
@ -61,42 +49,6 @@ var SelectionMixin = {
bottom: y};
},
/**
* Return a position object in canvasspace from a single point in screenspace
*
* @param pointer
* @returns {{left: number, top: number, right: number, bottom: number}}
* @private
*/
_pointerToScreenPositionObject : function(pointer) {
var x = pointer.x;
var y = pointer.y;
return {left: x,
top: y,
right: x,
bottom: y};
},
/**
* Get the top navigation controls node at the a specific point (like a click)
*
* @param {{x: Number, y: Number}} pointer
* @return {Node | null} node
* @private
*/
_getNavigationNodeAt : function (pointer) {
var screenPositionObject = this._pointerToScreenPositionObject(pointer);
var overlappingNodes = this._getAllNavigationNodesOverlappingWith(screenPositionObject);
if (overlappingNodes.length > 0) {
return this.sectors["navigation"]["nodes"][overlappingNodes[overlappingNodes.length - 1]];
}
else {
return null;
}
},
/**
* Get the top node at the a specific point (like a click)
@ -213,7 +165,7 @@ var SelectionMixin = {
this.selectionObj = {};
if (doNotTrigger == false) {
this.emit('select', this.getSelection());
this._trigger('select', this.getSelection());
}
},
@ -240,7 +192,7 @@ var SelectionMixin = {
}
if (doNotTrigger == false) {
this.emit('select', this.getSelection());
this._trigger('select', this.getSelection());
}
},
@ -263,6 +215,23 @@ var SelectionMixin = {
return count;
},
/**
* return the number of selected nodes
*
* @returns {number}
* @private
*/
_getSelectedNode : function() {
for (var objectId in this.selectionObj) {
if (this.selectionObj.hasOwnProperty(objectId)) {
if (this.selectionObj[objectId] instanceof Node) {
return this.selectionObj[objectId];
}
}
}
return null;
},
/**
* return the number of selected edges
@ -395,7 +364,7 @@ var SelectionMixin = {
this._removeFromSelection(object);
}
if (doNotTrigger == false) {
this.emit('select', this.getSelection());
this._trigger('select', this.getSelection());
}
},
@ -409,15 +378,7 @@ var SelectionMixin = {
* @private
*/
_handleTouch : function(pointer) {
if (this.constants.navigation.enabled == true) {
this.pointerPosition = pointer;
var node = this._getNavigationNodeAt(pointer);
if (node != null) {
if (this[node.triggerFunction] !== undefined) {
this[node.triggerFunction]();
}
}
}
},
@ -488,11 +449,8 @@ var SelectionMixin = {
*
* @private
*/
_handleOnRelease : function() {
this.xIncrement = 0;
this.yIncrement = 0;
this.zoomIncrement = 0;
this._unHighlightAll();
_handleOnRelease : function(pointer) {
},
@ -506,10 +464,7 @@ var SelectionMixin = {
getSelection : function() {
var nodeIds = this.getSelectedNodes();
var edgeIds = this.getSelectedEdges();
return {
nodes: nodeIds,
edges: edgeIds
};
return {nodes:nodeIds, edges:edgeIds};
},
/**
@ -633,7 +588,7 @@ var SelectionMixin = {
=======
if (changed) {
// fire the select event
this.emit('select', {
this._trigger('select', {
nodes: this.getSelection()
});
}

+ 373
- 0
src/graph/graphMixins/physics/BarnesHut.js View File

@ -0,0 +1,373 @@
/**
* Created by Alex on 2/10/14.
*/
var barnesHutMixin = {
/**
* This function calculates the forces the nodes apply on eachother based on a gravitational model.
* The Barnes Hut method is used to speed up this N-body simulation.
*
* @private
*/
_calculateNodeForces : function() {
var node;
var nodes = this.calculationNodes;
var nodeIndices = this.calculationNodeIndices;
var nodeCount = nodeIndices.length;
this._formBarnesHutTree(nodes,nodeIndices);
var barnesHutTree = this.barnesHutTree;
// place the nodes one by one recursively
for (var i = 0; i < nodeCount; i++) {
node = nodes[nodeIndices[i]];
// starting with root is irrelevant, it never passes the BarnesHut condition
this._getForceContribution(barnesHutTree.root.children.NW,node);
this._getForceContribution(barnesHutTree.root.children.NE,node);
this._getForceContribution(barnesHutTree.root.children.SW,node);
this._getForceContribution(barnesHutTree.root.children.SE,node);
}
},
/**
* This function traverses the barnesHutTree. It checks when it can approximate distant nodes with their center of mass.
* If a region contains a single node, we check if it is not itself, then we apply the force.
*
* @param parentBranch
* @param node
* @private
*/
_getForceContribution : function(parentBranch,node) {
// we get no force contribution from an empty region
if (parentBranch.childrenCount > 0) {
var dx,dy,distance;
// get the distance from the center of mass to the node.
dx = parentBranch.centerOfMass.x - node.x;
dy = parentBranch.centerOfMass.y - node.y;
distance = Math.sqrt(dx * dx + dy * dy);
// BarnesHut condition
// original condition : s/d < theta = passed === d/s > 1/theta = passed
// calcSize = 1/s --> d * 1/s > 1/theta = passed
if (distance * parentBranch.calcSize > this.constants.physics.barnesHut.theta) {
// duplicate code to reduce function calls to speed up program
if (distance == 0) {
distance = 0.5*Math.random();
dx = distance;
}
var gravityForce = this.constants.physics.barnesHut.gravitationalConstant * parentBranch.mass * node.mass / (distance * distance * distance);
var fx = dx * gravityForce;
var fy = dy * gravityForce;
node.fx += fx;
node.fy += fy;
}
else {
// Did not pass the condition, go into children if available
if (parentBranch.childrenCount == 4) {
this._getForceContribution(parentBranch.children.NW,node);
this._getForceContribution(parentBranch.children.NE,node);
this._getForceContribution(parentBranch.children.SW,node);
this._getForceContribution(parentBranch.children.SE,node);
}
else { // parentBranch must have only one node, if it was empty we wouldnt be here
if (parentBranch.children.data.id != node.id) { // if it is not self
// duplicate code to reduce function calls to speed up program
if (distance == 0) {
distance = 0.5*Math.random();
dx = distance;
}
var gravityForce = this.constants.physics.barnesHut.gravitationalConstant * parentBranch.mass * node.mass / (distance * distance * distance);
var fx = dx * gravityForce;
var fy = dy * gravityForce;
node.fx += fx;
node.fy += fy;
}
}
}
}
},
/**
* This function constructs the barnesHut tree recursively. It creates the root, splits it and starts placing the nodes.
*
* @param nodes
* @param nodeIndices
* @private
*/
_formBarnesHutTree : function(nodes,nodeIndices) {
var node;
var nodeCount = nodeIndices.length;
var minX = Number.MAX_VALUE,
minY = Number.MAX_VALUE,
maxX =-Number.MAX_VALUE,
maxY =-Number.MAX_VALUE;
// get the range of the nodes
for (var i = 0; i < nodeCount; i++) {
var x = nodes[nodeIndices[i]].x;
var y = nodes[nodeIndices[i]].y;
if (x < minX) { minX = x; }
if (x > maxX) { maxX = x; }
if (y < minY) { minY = y; }
if (y > maxY) { maxY = y; }
}
// make the range a square
var sizeDiff = Math.abs(maxX - minX) - Math.abs(maxY - minY); // difference between X and Y
if (sizeDiff > 0) {minY -= 0.5 * sizeDiff; maxY += 0.5 * sizeDiff;} // xSize > ySize
else {minX += 0.5 * sizeDiff; maxX -= 0.5 * sizeDiff;} // xSize < ySize
var minimumTreeSize = 1e-5;
var rootSize = Math.max(minimumTreeSize,Math.abs(maxX - minX));
var halfRootSize = 0.5 * rootSize;
var centerX = 0.5 * (minX + maxX), centerY = 0.5 * (minY + maxY);
// construct the barnesHutTree
var barnesHutTree = {root:{
centerOfMass:{x:0,y:0}, // Center of Mass
mass:0,
range: {minX:centerX-halfRootSize,maxX:centerX+halfRootSize,
minY:centerY-halfRootSize,maxY:centerY+halfRootSize},
size: rootSize,
calcSize: 1 / rootSize,
children: {data:null},
maxWidth: 0,
level: 0,
childrenCount: 4
}};
this._splitBranch(barnesHutTree.root);
// place the nodes one by one recursively
for (i = 0; i < nodeCount; i++) {
node = nodes[nodeIndices[i]];
this._placeInTree(barnesHutTree.root,node);
}
// make global
this.barnesHutTree = barnesHutTree
},
_updateBranchMass : function(parentBranch, node) {
var totalMass = parentBranch.mass + node.mass;
var totalMassInv = 1/totalMass;
parentBranch.centerOfMass.x = parentBranch.centerOfMass.x * parentBranch.mass + node.x * node.mass;
parentBranch.centerOfMass.x *= totalMassInv;
parentBranch.centerOfMass.y = parentBranch.centerOfMass.y * parentBranch.mass + node.y * node.mass;
parentBranch.centerOfMass.y *= totalMassInv;
parentBranch.mass = totalMass;
var biggestSize = Math.max(Math.max(node.height,node.radius),node.width);
parentBranch.maxWidth = (parentBranch.maxWidth < biggestSize) ? biggestSize : parentBranch.maxWidth;
},
_placeInTree : function(parentBranch,node,skipMassUpdate) {
if (skipMassUpdate != true || skipMassUpdate === undefined) {
// update the mass of the branch.
this._updateBranchMass(parentBranch,node);
}
if (parentBranch.children.NW.range.maxX > node.x) { // in NW or SW
if (parentBranch.children.NW.range.maxY > node.y) { // in NW
this._placeInRegion(parentBranch,node,"NW");
}
else { // in SW
this._placeInRegion(parentBranch,node,"SW");
}
}
else { // in NE or SE
if (parentBranch.children.NW.range.maxY > node.y) { // in NE
this._placeInRegion(parentBranch,node,"NE");
}
else { // in SE
this._placeInRegion(parentBranch,node,"SE");
}
}
},
_placeInRegion : function(parentBranch,node,region) {
switch (parentBranch.children[region].childrenCount) {
case 0: // place node here
parentBranch.children[region].children.data = node;
parentBranch.children[region].childrenCount = 1;
this._updateBranchMass(parentBranch.children[region],node);
break;
case 1: // convert into children
// if there are two nodes exactly overlapping (on init, on opening of cluster etc.)
// we move one node a pixel and we do not put it in the tree.
if (parentBranch.children[region].children.data.x == node.x &&
parentBranch.children[region].children.data.y == node.y) {
node.x += Math.random();
node.y += Math.random();
this._placeInTree(parentBranch,node, true);
}
else {
this._splitBranch(parentBranch.children[region]);
this._placeInTree(parentBranch.children[region],node);
}
break;
case 4: // place in branch
this._placeInTree(parentBranch.children[region],node);
break;
}
},
/**
* this function splits a branch into 4 sub branches. If the branch contained a node, we place it in the subbranch
* after the split is complete.
*
* @param parentBranch
* @private
*/
_splitBranch : function(parentBranch) {
// if the branch is filled with a node, replace the node in the new subset.
var containedNode = null;
if (parentBranch.childrenCount == 1) {
containedNode = parentBranch.children.data;
parentBranch.mass = 0; parentBranch.centerOfMass.x = 0; parentBranch.centerOfMass.y = 0;
}
parentBranch.childrenCount = 4;
parentBranch.children.data = null;
this._insertRegion(parentBranch,"NW");
this._insertRegion(parentBranch,"NE");
this._insertRegion(parentBranch,"SW");
this._insertRegion(parentBranch,"SE");
if (containedNode != null) {
this._placeInTree(parentBranch,containedNode);
}
},
/**
* This function subdivides the region into four new segments.
* Specifically, this inserts a single new segment.
* It fills the children section of the parentBranch
*
* @param parentBranch
* @param region
* @param parentRange
* @private
*/
_insertRegion : function(parentBranch, region) {
var minX,maxX,minY,maxY;
var childSize = 0.5 * parentBranch.size;
switch (region) {
case "NW":
minX = parentBranch.range.minX;
maxX = parentBranch.range.minX + childSize;
minY = parentBranch.range.minY;
maxY = parentBranch.range.minY + childSize;
break;
case "NE":
minX = parentBranch.range.minX + childSize;
maxX = parentBranch.range.maxX;
minY = parentBranch.range.minY;
maxY = parentBranch.range.minY + childSize;
break;
case "SW":
minX = parentBranch.range.minX;
maxX = parentBranch.range.minX + childSize;
minY = parentBranch.range.minY + childSize;
maxY = parentBranch.range.maxY;
break;
case "SE":
minX = parentBranch.range.minX + childSize;
maxX = parentBranch.range.maxX;
minY = parentBranch.range.minY + childSize;
maxY = parentBranch.range.maxY;
break;
}
parentBranch.children[region] = {
centerOfMass:{x:0,y:0},
mass:0,
range:{minX:minX,maxX:maxX,minY:minY,maxY:maxY},
size: 0.5 * parentBranch.size,
calcSize: 2 * parentBranch.calcSize,
children: {data:null},
maxWidth: 0,
level: parentBranch.level+1,
childrenCount: 0
};
},
/**
* This function is for debugging purposed, it draws the tree.
*
* @param ctx
* @param color
* @private
*/
_drawTree : function(ctx,color) {
if (this.barnesHutTree !== undefined) {
ctx.lineWidth = 1;
this._drawBranch(this.barnesHutTree.root,ctx,color);
}
},
/**
* This function is for debugging purposes. It draws the branches recursively.
*
* @param branch
* @param ctx
* @param color
* @private
*/
_drawBranch : function(branch,ctx,color) {
if (color === undefined) {
color = "#FF0000";
}
if (branch.childrenCount == 4) {
this._drawBranch(branch.children.NW,ctx);
this._drawBranch(branch.children.NE,ctx);
this._drawBranch(branch.children.SE,ctx);
this._drawBranch(branch.children.SW,ctx);
}
ctx.strokeStyle = color;
ctx.beginPath();
ctx.moveTo(branch.range.minX,branch.range.minY);
ctx.lineTo(branch.range.maxX,branch.range.minY);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(branch.range.maxX,branch.range.minY);
ctx.lineTo(branch.range.maxX,branch.range.maxY);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(branch.range.maxX,branch.range.maxY);
ctx.lineTo(branch.range.minX,branch.range.maxY);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(branch.range.minX,branch.range.maxY);
ctx.lineTo(branch.range.minX,branch.range.minY);
ctx.stroke();
/*
if (branch.mass > 0) {
ctx.circle(branch.centerOfMass.x, branch.centerOfMass.y, 3*branch.mass);
ctx.stroke();
}
*/
}
};

+ 245
- 0
src/graph/graphMixins/physics/PhysicsMixin.js View File

@ -0,0 +1,245 @@
/**
* Created by Alex on 2/6/14.
*/
var physicsMixin = {
/**
* Toggling barnes Hut calculation on and off.
*
* @private
*/
_toggleBarnesHut : function() {
this.constants.physics.barnesHut.enabled = !this.constants.physics.barnesHut.enabled;
this._loadSelectedForceSolver();
this.moving = true;
this.start();
},
/**
* Before calculating the forces, we check if we need to cluster to keep up performance and we check
* if there is more than one node. If it is just one node, we dont calculate anything.
*
* @private
*/
_initializeForceCalculation : function() {
// stop calculation if there is only one node
if (this.nodeIndices.length == 1) {
this.nodes[this.nodeIndices[0]]._setForce(0,0);
}
else {
// if there are too many nodes on screen, we cluster without repositioning
if (this.nodeIndices.length > this.constants.clustering.clusterThreshold && this.constants.clustering.enabled == true) {
this.clusterToFit(this.constants.clustering.reduceToNodes, false);
}
// we now start the force calculation
this._calculateForces();
}
},
/**
* Calculate the external forces acting on the nodes
* Forces are caused by: edges, repulsing forces between nodes, gravity
* @private
*/
_calculateForces : function() {
// Gravity is required to keep separated groups from floating off
// the forces are reset to zero in this loop by using _setForce instead
// of _addForce
this._calculateGravitationalForces();
this._calculateNodeForces();
if (this.constants.smoothCurves == true) {
this._calculateSpringForcesWithSupport();
}
else {
this._calculateSpringForces();
}
},
/**
* Smooth curves are created by adding invisible nodes in the center of the edges. These nodes are also
* handled in the calculateForces function. We then use a quadratic curve with the center node as control.
* This function joins the datanodes and invisible (called support) nodes into one object.
* We do this so we do not contaminate this.nodes with the support nodes.
*
* @private
*/
_updateCalculationNodes : function() {
if (this.constants.smoothCurves == true) {
this.calculationNodes = {};
this.calculationNodeIndices = [];
for (var nodeId in this.nodes) {
if (this.nodes.hasOwnProperty(nodeId)) {
this.calculationNodes[nodeId] = this.nodes[nodeId];
}
}
var supportNodes = this.sectors['support']['nodes'];
for (var supportNodeId in supportNodes) {
if (supportNodes.hasOwnProperty(supportNodeId)) {
if (this.edges.hasOwnProperty(supportNodes[supportNodeId].parentEdgeId)) {
this.calculationNodes[supportNodeId] = supportNodes[supportNodeId];
}
else {
supportNodes[supportNodeId]._setForce(0,0);
}
}
}
for (var idx in this.calculationNodes) {
if (this.calculationNodes.hasOwnProperty(idx)) {
this.calculationNodeIndices.push(idx);
}
}
}
else {
this.calculationNodes = this.nodes;
this.calculationNodeIndices = this.nodeIndices;
}
},
/**
* this function applies the central gravity effect to keep groups from floating off
*
* @private
*/
_calculateGravitationalForces : function() {
var dx, dy, distance, node, i;
var nodes = this.calculationNodes;
var gravity = this.constants.physics.centralGravity;
var gravityForce = 0;
for (i = 0; i < this.calculationNodeIndices.length; i++) {
node = nodes[this.calculationNodeIndices[i]];
node.damping = this.constants.physics.damping; // possibly add function to alter damping properties of clusters.
// gravity does not apply when we are in a pocket sector
if (this._sector() == "default") {
dx = -node.x;
dy = -node.y;
distance = Math.sqrt(dx*dx + dy*dy);
gravityForce = gravity / distance;
node.fx = dx * gravityForce;
node.fy = dy * gravityForce;
}
else {
node.fx = 0;
node.fy = 0;
}
}
},
/**
* this function calculates the effects of the springs in the case of unsmooth curves.
*
* @private
*/
_calculateSpringForces : function() {
var edgeLength, edge, edgeId;
var dx, dy, fx, fy, springForce, length;
var edges = this.edges;
// forces caused by the edges, modelled as springs
for (edgeId in edges) {
if (edges.hasOwnProperty(edgeId)) {
edge = edges[edgeId];
if (edge.connected) {
// only calculate forces if nodes are in the same sector
if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) {
edgeLength = edge.length;
// this implies that the edges between big clusters are longer
edgeLength += (edge.to.clusterSize + edge.from.clusterSize - 2) * this.constants.clustering.edgeGrowth;
dx = (edge.from.x - edge.to.x);
dy = (edge.from.y - edge.to.y);
length = Math.sqrt(dx * dx + dy * dy);
springForce = this.constants.physics.springConstant * (edgeLength - length) / length;
fx = dx * springForce;
fy = dy * springForce;
edge.from.fx += fx;
edge.from.fy += fy;
edge.to.fx -= fx;
edge.to.fy -= fy;
}
}
}
}
},
/**
* This function calculates the springforces on the nodes, accounting for the support nodes.
*
* @private
*/
_calculateSpringForcesWithSupport : function() {
var edgeLength, edge, edgeId, combinedClusterSize;
var edges = this.edges;
// forces caused by the edges, modelled as springs
for (edgeId in edges) {
if (edges.hasOwnProperty(edgeId)) {
edge = edges[edgeId];
if (edge.connected) {
// only calculate forces if nodes are in the same sector
if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) {
if (edge.via != null) {
var node1 = edge.to;
var node2 = edge.via;
var node3 = edge.from;
edgeLength = edge.length;
combinedClusterSize = node1.clusterSize + node3.clusterSize - 2;
// this implies that the edges between big clusters are longer
edgeLength += combinedClusterSize * this.constants.clustering.edgeGrowth;
this._calculateSpringForce(node1,node2,0.5*edgeLength);
this._calculateSpringForce(node2,node3,0.5*edgeLength);
}
}
}
}
}
},
/**
* This is the code actually performing the calculation for the function above. It is split out to avoid repetition.
*
* @param node1
* @param node2
* @param edgeLength
* @private
*/
_calculateSpringForce : function(node1,node2,edgeLength) {
var dx, dy, fx, fy, springForce, length;
dx = (node1.x - node2.x);
dy = (node1.y - node2.y);
length = Math.sqrt(dx * dx + dy * dy);
springForce = this.constants.physics.springConstant * (edgeLength - length) / length;
fx = dx * springForce;
fy = dy * springForce;
node1.fx += fx;
node1.fy += fy;
node2.fx -= fx;
node2.fy -= fy;
}
}

+ 66
- 0
src/graph/graphMixins/physics/Repulsion.js View File

@ -0,0 +1,66 @@
/**
* Created by Alex on 2/10/14.
*/
var repulsionMixin = {
/**
* Calculate the forces the nodes apply on eachother based on a repulsion field.
* This field is linearly approximated.
*
* @private
*/
_calculateNodeForces : function() {
var dx, dy, angle, distance, fx, fy, combinedClusterSize,
repulsingForce, node1, node2, i, j;
var nodes = this.calculationNodes;
var nodeIndices = this.calculationNodeIndices;
// approximation constants
var a_base = -2/3;
var b = 4/3;
// repulsing forces between nodes
var nodeDistance = this.constants.physics.repulsion.nodeDistance;
var minimumDistance = nodeDistance;
// we loop from i over all but the last entree in the array
// j loops from i+1 to the last. This way we do not double count any of the indices, nor i == j
for (i = 0; i < nodeIndices.length-1; i++) {
node1 = nodes[nodeIndices[i]];
for (j = i+1; j < nodeIndices.length; j++) {
node2 = nodes[nodeIndices[j]];
combinedClusterSize = node1.clusterSize + node2.clusterSize - 2;
dx = node2.x - node1.x;
dy = node2.y - node1.y;
distance = Math.sqrt(dx * dx + dy * dy);
minimumDistance = (combinedClusterSize == 0) ? nodeDistance : (nodeDistance * (1 + combinedClusterSize * this.constants.clustering.distanceAmplification));
var a = a_base / minimumDistance;
if (distance < 2*minimumDistance) {
if (distance < 0.5*minimumDistance) {
repulsingForce = 1.0;
}
else {
repulsingForce = a * distance + b; // linear approx of 1 / (1 + Math.exp((distance / minimumDistance - 1) * steepness))
}
// amplify the repulsion for clusters.
repulsingForce *= (combinedClusterSize == 0) ? 1 : 1 + combinedClusterSize * this.constants.clustering.forceAmplification;
repulsingForce = repulsingForce/distance;
fx = dx * repulsingForce;
fy = dy * repulsingForce;
node1.fx -= fx;
node1.fy -= fy;
node2.fx += fx;
node2.fy += fy;
}
}
}
}
}

BIN
src/graph/img/acceptDeleteIcon.png View File

Before After
Width: 24  |  Height: 24  |  Size: 20 KiB

BIN
src/graph/img/addNodeIcon.png View File

Before After
Width: 24  |  Height: 24  |  Size: 20 KiB

BIN
src/graph/img/backIcon.png View File

Before After
Width: 24  |  Height: 24  |  Size: 20 KiB

BIN
src/graph/img/connectIcon.png View File

Before After
Width: 24  |  Height: 24  |  Size: 20 KiB

BIN
src/graph/img/cross.png View File

Before After
Width: 7  |  Height: 7  |  Size: 18 KiB

BIN
src/graph/img/cross2.png View File

Before After
Width: 5  |  Height: 5  |  Size: 17 KiB

BIN
src/graph/img/deleteIcon.png View File

Before After
Width: 24  |  Height: 24  |  Size: 20 KiB

dist/img/downarrow.png → src/graph/img/downArrow.png View File


BIN
src/graph/img/downarrow.png View File

Before After
Width: 30  |  Height: 30  |  Size: 4.4 KiB

BIN
src/graph/img/editIcon.png View File

Before After
Width: 24  |  Height: 24  |  Size: 20 KiB

dist/img/leftarrow.png → src/graph/img/leftArrow.png View File


BIN
src/graph/img/leftarrow.png View File

Before After
Width: 30  |  Height: 30  |  Size: 4.4 KiB

dist/img/rightarrow.png → src/graph/img/rightArrow.png View File


BIN
src/graph/img/rightarrow.png View File

Before After
Width: 30  |  Height: 30  |  Size: 4.4 KiB

dist/img/uparrow.png → src/graph/img/upArrow.png View File


BIN
src/graph/img/uparrow.png View File

Before After
Width: 30  |  Height: 30  |  Size: 4.4 KiB

+ 5
- 39
src/timeline/component/item/Item.js View File

@ -74,43 +74,9 @@ Item.prototype.reflow = function reflow() {
};
/**
* Give the item a display offset in pixels
* @param {Number} offset Offset on screen in pixels
* Return the items width
* @return {Integer} width
*/
Item.prototype.setOffset = function setOffset(offset) {
this.offset = offset;
};
/**
* Repaint a delete button on the top right of the item when the item is selected
* @param {HTMLElement} anchor
* @private
*/
Item.prototype._repaintDeleteButton = function (anchor) {
if (this.selected && this.options.editable && !this.dom.deleteButton) {
// create and show button
var parent = this.parent;
var id = this.id;
var deleteButton = document.createElement('div');
deleteButton.className = 'delete';
deleteButton.title = 'Delete this item';
Hammer(deleteButton, {
preventDefault: true
}).on('tap', function (event) {
parent.removeItem(id);
event.stopPropagation();
});
anchor.appendChild(deleteButton);
this.dom.deleteButton = deleteButton;
}
else if (!this.selected && this.dom.deleteButton) {
// remove button
if (this.dom.deleteButton.parentNode) {
this.dom.deleteButton.parentNode.removeChild(this.dom.deleteButton);
}
this.dom.deleteButton = null;
}
};
Item.prototype.getWidth = function getWidth() {
return this.width;
}

+ 6
- 1
src/util.js View File

@ -793,4 +793,9 @@ util.HSVToHex = function HSVToHex(h,s,v) {
util.hexToHSV = function hexToHSV(hex) {
var rgb = util.hexToRGB(hex);
return util.RGBToHSV(rgb.r,rgb.g,rgb.b);
}
}
util.isValidHex = function isValidHex(hex) {
var isOk = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(hex);
return isOk;
}

+ 58
- 0
updateversion.js View File

@ -0,0 +1,58 @@
// Update the version numbers and library sizes in index.html
var fs = require('fs'),
zlib = require('zlib');
var VIS_ZIP = './dist/vis.js',
INDEX = 'index.html';
// read version from dist/vis.js
function version(callback) {
fs.readFile(VIS_ZIP, function (err, data) {
if (!err) {
var match = /@version\s*([\w\.-]*)/i.exec(data);
var version = undefined;
if (match) {
version = match[1];
}
callback(null, version);
}
else {
callback(err);
}
});
}
// update version and library sizes in index.md
function updateVersion(version, callback) {
fs.readFile(INDEX, function (err, data) {
if (!err) {
data = String(data);
data = data.replace(/<span class="version">([\w\.-]*)<\/span>/g,
'<span class="version">' + version + '</span>');
fs.writeFile(INDEX, data, callback);
}
else {
callback(err);
}
});
}
version(function (err, version) {
console.log('version: ' + version);
if (version) {
updateVersion(version, function (err, res) {
if (err) {
console.log(err);
}
else {
console.log('done');
}
});
}
else {
}
});

Loading…
Cancel
Save