Browse Source

Merge branch 'develop' of github.com:Gillingham/vis into develop

css_transitions
Eric Gillingham 10 years ago
parent
commit
dfe8e3bccf
41 changed files with 2735 additions and 2240 deletions
  1. +13
    -2
      HISTORY.md
  2. +2
    -3
      Jakefile.js
  3. +17
    -9
      dist/vis.css
  4. +1298
    -1081
      dist/vis.js
  5. +1
    -1
      dist/vis.min.css
  6. +10
    -10
      dist/vis.min.js
  7. +12
    -2
      docs/graph.html
  8. +60
    -6
      docs/timeline.html
  9. +6
    -6
      examples/timeline/01_basic.html
  10. +18
    -16
      examples/timeline/02_interactive.html
  11. +41
    -41
      examples/timeline/03_much_data.html
  12. +8
    -7
      examples/timeline/09_order_groups.html
  13. +1
    -1
      examples/timeline/index.html
  14. +8
    -4
      src/graph/Edge.js
  15. +43
    -9
      src/graph/Graph.js
  16. +1
    -0
      src/graph/Node.js
  17. +4
    -2
      src/graph/graphMixins/ClusterMixin.js
  18. +3
    -3
      src/graph/graphMixins/HierarchicalLayoutMixin.js
  19. +0
    -2
      src/graph/graphMixins/ManipulationMixin.js
  20. +6
    -6
      src/graph/graphMixins/MixinLoader.js
  21. +1
    -1
      src/graph/graphMixins/SelectionMixin.js
  22. +23
    -0
      src/graph/graphMixins/physics/BarnesHut.js
  23. +29
    -1
      src/graph/graphMixins/physics/PhysicsMixin.js
  24. +1
    -1
      src/module/exports.js
  25. +0
    -169
      src/timeline/Stack.js
  26. +63
    -76
      src/timeline/Timeline.js
  27. +1
    -1
      src/timeline/component/Component.js
  28. +343
    -104
      src/timeline/component/Group.js
  29. +0
    -465
      src/timeline/component/GroupSet.js
  30. +523
    -162
      src/timeline/component/ItemSet.js
  31. +2
    -1
      src/timeline/component/RootPanel.js
  32. +2
    -2
      src/timeline/component/TimeAxis.js
  33. +15
    -0
      src/timeline/component/css/itemset.css
  34. +2
    -9
      src/timeline/component/css/labelset.css
  35. +34
    -8
      src/timeline/component/item/Item.js
  36. +3
    -4
      src/timeline/component/item/ItemBox.js
  37. +11
    -10
      src/timeline/component/item/ItemPoint.js
  38. +7
    -8
      src/timeline/component/item/ItemRange.js
  39. +3
    -4
      src/timeline/component/item/ItemRangeOverflow.js
  40. +112
    -0
      src/timeline/stack.js
  41. +8
    -3
      test/timeline_groups.html

+ 13
- 2
HISTORY.md View File

@ -7,17 +7,28 @@ http://visjs.org
### Timeline
- Large refactoring of the Timeline, simplifying the code.
- Performance improvements.
- Great performance improvements.
- Improved layout of box-items inside groups.
- Items can now be dragged from one group to another.
- Implemented option `stack` to enable/disable stacking of items.
- Option `editable` can now be used to enable/disable individual manipulation
actions (`add`, `updateTime`, `updateGroup`, `remove`).
- Function `setWindow` now accepts an object with properties `start` and `end`.
- Fixed option `autoResize` forcing a repaint of the Timeline with every check
rather than when the Timeline is actually resized.
- Fixed `select` event fired repeatedly when clicking an empty place on the
Timeline, deselecting selected items).
- Fixed initial visible window in case items exceed `zoomMax`. Thanks @Remper.
- Fixed an offset in newly created items when using groups.
- Fixed height of a group not reckoning with the height of the group label.
- Option `order` is now deprecated. This was needed for performance improvements.
- Minor bug fixes.
- More examples added.
- Minor bug fixes.
### Graph
- added recalculate hierarchical layout to update node event.
- added arrowScaleFactor to scale the arrows on the edges.
### DataSet

+ 2
- 3
Jakefile.js View File

@ -38,7 +38,7 @@ task('build', {async: true}, function () {
src: [
'./src/timeline/component/css/timeline.css',
'./src/timeline/component/css/panel.css',
'./src/timeline/component/css/groupset.css',
'./src/timeline/component/css/labelset.css',
'./src/timeline/component/css/itemset.css',
'./src/timeline/component/css/item.css',
'./src/timeline/component/css/timeaxis.css',
@ -64,8 +64,8 @@ task('build', {async: true}, function () {
'./src/DataSet.js',
'./src/DataView.js',
'./src/timeline/stack.js',
'./src/timeline/TimeStep.js',
'./src/timeline/Stack.js',
'./src/timeline/Range.js',
'./src/timeline/component/Component.js',
'./src/timeline/component/Panel.js',
@ -76,7 +76,6 @@ task('build', {async: true}, function () {
'./src/timeline/component/ItemSet.js',
'./src/timeline/component/item/*.js',
'./src/timeline/component/Group.js',
'./src/timeline/component/GroupSet.js',
'./src/timeline/Timeline.js',
'./src/graph/dotparser.js',

+ 17
- 9
dist/vis.css View File

@ -32,9 +32,6 @@
display: none;
}
.vis.timeline .groupset {
position: relative;
}
.vis.timeline .labelset {
position: relative;
@ -57,16 +54,12 @@
box-sizing: border-box;
}
.vis.timeline.bottom .labelset .vlabel,
.vis.timeline.top .vpanel.side-content,
.vis.timeline.top .groupset .itemset {
.vis.timeline.top .labelset .vlabel {
border-top: 1px solid #bfbfbf;
border-bottom: none;
}
.vis.timeline.top .labelset .vlabel,
.vis.timeline.bottom .vpanel.side-content,
.vis.timeline.bottom .groupset .itemset {
.vis.timeline.bottom .labelset .vlabel {
border-top: none;
border-bottom: 1px solid #bfbfbf;
}
@ -101,6 +94,21 @@
overflow: visible;
}
.vis.timeline .group {
position: relative;
box-sizing: border-box;
}
.vis.timeline.top .group {
border-top: 1px solid #bfbfbf;
border-bottom: none;
}
.vis.timeline.bottom .group {
border-top: none;
border-bottom: 1px solid #bfbfbf;
}
.vis.timeline .item {
position: absolute;

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


+ 1
- 1
dist/vis.min.css
File diff suppressed because it is too large
View File


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


+ 12
- 2
docs/graph.html View File

@ -490,7 +490,12 @@ var edges = [
<th>Required</th>
<th>Description</th>
</tr>
<tr>
<td>arrowScaleFactor</td>
<td>Number</td>
<td>no</td>
<td>If you are using arrows, this will scale the arrow. Values < 1 give smaller arrows, > 1 larger arrows. Default: 1.</td>
</tr>
<tr>
<td>color</td>
<td>String | Object</td>
@ -1062,7 +1067,12 @@ var options = {
<th>Default</th>
<th>Description</th>
</tr>
<tr>
<td>arrowScaleFactor</td>
<td>Number</td>
<td>1</td>
<td>If you are using arrows, this will scale the arrow. Values < 1 give smaller arrows, > 1 larger arrows. Default: 1.</td>
</tr>
<tr>
<td>color</td>
<td>String | Object</td>

+ 60
- 6
docs/timeline.html View File

@ -347,12 +347,41 @@ var options = {
<tr>
<td>editable</td>
<td>Boolean</td>
<td>Boolean | Object</td>
<td>false</td>
<td>If true, the items on the timeline can be dragged. Only applicable when option <code>selectable</code> is <code>true</code>. See also the callbacks <code>onAdd</code>, <code>onUpdate</code>, <code>onMove</code>, and <code>onRemove</code>, described in detail in section <a href="#Editing_Items">Editing Items</a>.
<td>If true, the items in the timeline can be manipulated. Only applicable when option <code>selectable</code> is <code>true</code>. See also the callbacks <code>onAdd</code>, <code>onUpdate</code>, <code>onMove</code>, and <code>onRemove</code>. When <code>editable</code> is an object, one can enable or disable individual manipulation actions.
See section <a href="#Editing_Items">Editing Items</a> for a detailed explanation.
</td>
</tr>
<tr>
<td>editable.add</td>
<td>Boolean</td>
<td>false</td>
<td>If true, new items can be created by double tapping an empty space in the Timeline. See section <a href="#Editing_Items">Editing Items</a> for a detailed explanation.</td>
</tr>
<tr>
<td>editable.remove</td>
<td>Boolean</td>
<td>false</td>
<td>If true, items can be deleted by first selecting them, and then clicking the delete button on the top right of the item. See section <a href="#Editing_Items">Editing Items</a> for a detailed explanation.</td>
</tr>
<tr>
<td>editable.updateGroup</td>
<td>Boolean</td>
<td>false</td>
<td>If true, items can be dragged from one group to another. Only applicable when the Timeline has groups. See section <a href="#Editing_Items">Editing Items</a> for a detailed explanation.</td>
</tr>
<tr>
<td>editable.updateTime</td>
<td>Boolean</td>
<td>false</td>
<td>If true, items can be dragged to another moment in time. See section <a href="#Editing_Items">Editing Items</a> for a detailed explanation.</td>
</tr>
<tr>
<td>end</td>
<td>Date | Number | String</td>
@ -428,7 +457,7 @@ var options = {
<td>onAdd</td>
<td>Function</td>
<td>none</td>
<td>Callback function triggered when an item is about to be added: when the user double taps an empty space in the Timeline. See section <a href="#Editing_Items">Editing Items</a> for more information. Only applicable when both options <code>selectable</code> and <code>editable</code> are set <code>true</code>.
<td>Callback function triggered when an item is about to be added: when the user double taps an empty space in the Timeline. See section <a href="#Editing_Items">Editing Items</a> for more information. Only applicable when both options <code>selectable</code> and <code>editable.add</code> are set <code>true</code>.
</td>
</tr>
@ -436,7 +465,7 @@ var options = {
<td>onUpdate</td>
<td>Function</td>
<td>none</td>
<td>Callback function triggered when an item is about to be updated, when the user double taps an item in the Timeline. See section <a href="#Editing_Items">Editing Items</a> for more information. Only applicable when both options <code>selectable</code> and <code>editable</code> are set <code>true</code>.
<td>Callback function triggered when an item is about to be updated, when the user double taps an item in the Timeline. See section <a href="#Editing_Items">Editing Items</a> for more information. Only applicable when both options <code>selectable</code> and <code>editable.updateTime</code> or <code>editable.updateGroup</code> are set <code>true</code>.
</td>
</tr>
@ -444,7 +473,7 @@ var options = {
<td>onMove</td>
<td>Function</td>
<td>none</td>
<td>Callback function triggered when an item has been moved: after the user has dragged the item to an other position. See section <a href="#Editing_Items">Editing Items</a> for more information. Only applicable when both options <code>selectable</code> and <code>editable</code> are set <code>true</code>.
<td>Callback function triggered when an item has been moved: after the user has dragged the item to an other position. See section <a href="#Editing_Items">Editing Items</a> for more information. Only applicable when both options <code>selectable</code> and <code>editable.updateTime</code> or <code>editable.updateGroup</code> are set <code>true</code>.
</td>
</tr>
@ -452,7 +481,7 @@ var options = {
<td>onRemove</td>
<td>Function</td>
<td>none</td>
<td>Callback function triggered when an item is about to be removed: when the user tapped the delete button on the top right of a selected item. See section <a href="#Editing_Items">Editing Items</a> for more information. Only applicable when both options <code>selectable</code> and <code>editable</code> are set <code>true</code>.
<td>Callback function triggered when an item is about to be removed: when the user tapped the delete button on the top right of a selected item. See section <a href="#Editing_Items">Editing Items</a> for more information. Only applicable when both options <code>selectable</code> and <code>editable.remove</code> are set <code>true</code>.
</td>
</tr>
@ -535,6 +564,13 @@ var options = {
visible.</td>
</tr>
<tr>
<td>stack</td>
<td>Boolean</td>
<td>true</td>
<td>If true (default), items will be stacked on top of each other such that they are not overlapping.</td>
</tr>
<tr>
<td>start</td>
<td>Date | Number | String</td>
@ -792,6 +828,24 @@ timeline.off('select', onSelect);
When the Timeline is configured to be editable (both options <code>selectable</code> and <code>editable</code> are <code>true</code>), the user can move items by dragging them, can create a new item by double tapping on an empty space, can update an item by double tapping it, and can delete a selected item by clicking the delete button on the top right.
</p>
<p>Option <code>editable</code> accepts a boolean or an object. When <code>editable</code> is a boolean, all manipulation actions will be either enabled or disabled. When <code>editable</code> is an object, one can enable individual manipulation actions:</p>
<pre class="prettyprint lang-js">// enable or disable all manipulation actions
var options = {
editable: true // true or false
};
// enable or disable individual manipulation actions
var options = {
editable: {
add: true, // add new items by double tapping
updateTime: true, // drag items horizontally
updateGroup: true, // drag items from one group to another
remove: true // delete an item by tapping the delete button top right
}
};</pre>
<p>
One can specify callback functions to validate changes made by the user. There are a number of callback functions for this purpose:
</p>

+ 6
- 6
examples/timeline/01_basic.html View File

@ -18,12 +18,12 @@
<script type="text/javascript">
var container = document.getElementById('visualization');
var items = [
{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'}
{id: 1, content: 'item 1', start: '2014-04-20'},
{id: 2, content: 'item 2', start: '2014-04-14'},
{id: 3, content: 'item 3', start: '2014-04-18'},
{id: 4, content: 'item 4', start: '2014-04-16', end: '2014-04-19'},
{id: 5, content: 'item 5', start: '2014-04-25'},
{id: 6, content: 'item 6', start: '2014-04-27'}
];
var options = {};
var timeline = new vis.Timeline(container, items, options);

examples/timeline/02_dataset.html → examples/timeline/02_interactive.html View File

@ -1,36 +1,25 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Timeline | Dataset example</title>
<title>Timeline | Interactive example</title>
<style>
body, html {
font-family: arial, sans-serif;
font-size: 11pt;
height: 100%;
margin: 0;
padding: 0;
}
#visualization {
box-sizing: border-box;
width: 100%;
height: 100%;
}
</style>
<!-- note: moment.js must be loaded before vis.js, else vis.js uses its embedded version of moment.js -->
<script src="http://cdnjs.cloudflare.com/ajax/libs/moment.js/2.3.1/moment.min.js"></script>
<script src="../../dist/vis.js"></script>
<link href="../../dist/vis.css" rel="stylesheet" type="text/css" />
</head>
<body>
<p>Drag items around, create new items, and remove items.</p>
<div id="visualization"></div>
<script>
var now = moment().minutes(0).seconds(0).milliseconds(0);
// create a dataset with items
var items = new vis.DataSet({
convert: {
@ -52,7 +41,20 @@
start: '2014-01-10',
end: '2014-02-10',
orientation: 'top',
height: '100%',
height: '300px',
editable: true,
/* alternatively, enable/disable individual actions:
editable: {
add: true,
updateTime: true,
updateGroup: true,
remove: true
},
*/
showCurrentTime: true
};

+ 41
- 41
examples/timeline/03_much_data.html View File

@ -1,68 +1,68 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Timeline | a lot of data</title>
<title>Timeline | a lot of data</title>
<style>
body, html {
font-family: arial, sans-serif;
font-size: 11pt;
}
</style>
<style>
body, html {
font-family: arial, sans-serif;
font-size: 11pt;
}
</style>
<!-- note: moment.js must be loaded before vis.js, else vis.js uses its embedded version of moment.js -->
<script src="http://cdnjs.cloudflare.com/ajax/libs/moment.js/2.3.1/moment.min.js"></script>
<!-- note: moment.js must be loaded before vis.js, else vis.js uses its embedded version of moment.js -->
<script src="http://cdnjs.cloudflare.com/ajax/libs/moment.js/2.3.1/moment.min.js"></script>
<script src="../../dist/vis.js"></script>
<link href="../../dist/vis.css" rel="stylesheet" type="text/css" />
</head>
<body>
<h1>
Test with a lot of data
Test with a lot of data
</h1>
<p>
<label for="count">Number of items</label>
<input id="count" value="1000">
<input id="count" value="10000">
<input id="draw" type="button" value="draw">
</p>
<div id="visualization"></div>
<script>
// create a dataset with items
var now = moment().minutes(0).seconds(0).milliseconds(0);
var items = new vis.DataSet({
convert: {
start: 'Date',
end: 'Date'
}
});
// create a dataset with items
var now = moment().minutes(0).seconds(0).milliseconds(0);
var items = new vis.DataSet({
convert: {
start: 'Date',
end: 'Date'
}
});
// create data
function createData() {
var count = parseInt(document.getElementById('count').value) || 100;
var newData = [];
for (var i = 0; i < count; i++) {
newData.push({id: i, content: 'item ' + i, start: now.clone().add('days', i)});
}
items.clear();
items.add(newData);
// create data
function createData() {
var count = parseInt(document.getElementById('count').value) || 100;
var newData = [];
for (var i = 0; i < count; i++) {
newData.push({id: i, content: 'item ' + i, start: now.clone().add('days', i)});
}
createData();
items.clear();
items.add(newData);
}
createData();
document.getElementById('draw').onclick = createData;
document.getElementById('draw').onclick = createData;
var container = document.getElementById('visualization');
var options = {
start: now.clone().add('days', -3),
end: now.clone().add('days', 11),
zoomMin: 1000 * 60 * 60 * 24, // a day
zoomMax: 1000 * 60 * 60 * 24 * 30 * 3 // three months
//maxHeight: 300,
//height: '300px',
//orientation: 'top'
};
var container = document.getElementById('visualization');
var options = {
start: now.clone().add('days', -3),
end: now.clone().add('days', 11),
zoomMin: 1000 * 60 * 60 * 24, // a day
zoomMax: 1000 * 60 * 60 * 24 * 30 * 3 // three months
//maxHeight: 300,
//height: '300px',
//orientation: 'top'
};
var timeline = new vis.Timeline(container, items, options);
var timeline = new vis.Timeline(container, items, options);
</script>
</body>
</html>

+ 8
- 7
examples/timeline/09_order_groups.html View File

@ -34,12 +34,12 @@
// create a dataset with items
var items = new vis.DataSet([
{id: 0, group: 0, content: 'item 0', start: new Date(2014, 3, 17)},
{id: 1, group: 0, content: 'item 1', start: new Date(2014, 3, 19)},
{id: 2, group: 1, content: 'item 2', start: new Date(2014, 3, 16)},
{id: 3, group: 1, content: 'item 3', start: new Date(2014, 3, 23)},
{id: 4, group: 1, content: 'item 4', start: new Date(2014, 3, 22)},
{id: 5, group: 2, content: 'item 5', start: new Date(2014, 3, 24)}
{id: 0, group: 0, content: 'item 0', start: new Date(2014, 3, 17), end: new Date(2014, 3, 21)},
{id: 1, group: 0, content: 'item 1', start: new Date(2014, 3, 19), end: new Date(2014, 3, 20)},
{id: 2, group: 1, content: 'item 2', start: new Date(2014, 3, 16), end: new Date(2014, 3, 24)},
{id: 3, group: 1, content: 'item 3', start: new Date(2014, 3, 23), end: new Date(2014, 3, 24)},
{id: 4, group: 1, content: 'item 4', start: new Date(2014, 3, 22), end: new Date(2014, 3, 26)},
{id: 5, group: 2, content: 'item 5', start: new Date(2014, 3, 24), end: new Date(2014, 3, 27)}
]);
// create visualization
@ -52,7 +52,8 @@
// 0 when a == b
groupOrder: function (a, b) {
return a.value - b.value;
}
},
editable: true
};
var timeline = new vis.Timeline(container);

+ 1
- 1
examples/timeline/index.html View File

@ -13,7 +13,7 @@
<h1>vis.js timeline examples</h1>
<p><a href="01_basic.html">01_basic.html</a></p>
<p><a href="02_dataset.html">02_dataset.html</a></p>
<p><a href="02_interactive.html">02_dataset.html</a></p>
<p><a href="03_much_data.html">03_much_data.html</a></p>
<p><a href="04_html_data.html">04_html_data.html</a></p>
<p><a href="05_groups.html">05_groups.html</a></p>

+ 8
- 4
src/graph/Edge.js View File

@ -35,6 +35,7 @@ function Edge (properties, graph, constants) {
this.customLength = false;
this.selected = false;
this.smooth = constants.smoothCurves;
this.arrowScaleFactor = constants.edges.arrowScaleFactor;
this.from = null; // a node
this.to = null; // a node
@ -95,6 +96,9 @@ Edge.prototype.setProperties = function(properties, constants) {
if (properties.length !== undefined) {this.length = properties.length;
this.customLength = true;}
// scale the arrow
if (properties.arrowScaleFactor !== undefined) {this.arrowScaleFactor = properties.arrowScaleFactor;}
// Added to support dashed lines
// David Jordan
// 2012-08-08
@ -511,7 +515,7 @@ Edge.prototype._drawArrowCenter = function(ctx) {
this._line(ctx);
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?
var length = (10 + 5 * this.width) * this.arrowScaleFactor;
// 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));
@ -551,7 +555,7 @@ Edge.prototype._drawArrowCenter = function(ctx) {
// draw all arrows
var angle = 0.2 * Math.PI;
var length = 10 + 5 * this.width; // TODO: make customizable?
var length = (10 + 5 * this.width) * this.arrowScaleFactor;
point = this._pointOnCircle(x, y, radius, 0.5);
ctx.arrow(point.x, point.y, angle, length);
ctx.fill();
@ -625,7 +629,7 @@ Edge.prototype._drawArrow = function(ctx) {
ctx.stroke();
// draw arrow at the end of the line
length = 10 + 5 * this.width;
length = (10 + 5 * this.width) * this.arrowScaleFactor;
ctx.arrow(xTo, yTo, angle, length);
ctx.fill();
ctx.stroke();
@ -676,7 +680,7 @@ Edge.prototype._drawArrow = function(ctx) {
ctx.stroke();
// draw all arrows
length = 10 + 5 * this.width; // TODO: make customizable?
var length = (10 + 5 * this.width) * this.arrowScaleFactor;
ctx.arrow(arrow.x, arrow.y, arrow.angle, length);
ctx.fill();
ctx.stroke();

+ 43
- 9
src/graph/Graph.js View File

@ -73,6 +73,7 @@ function Graph (container, data, options) {
fontSize: 14, // px
fontFace: 'arial',
fontFill: 'white',
arrowScaleFactor: 1,
dash: {
length: 10,
gap: 5,
@ -371,6 +372,7 @@ Graph.prototype._centerGraph = function(range) {
* This function zooms out to fit all data on screen based on amount of nodes
*
* @param {Boolean} [initialZoom] | zoom based on fitted formula or range, true = fitted, default = false;
* @param {Boolean} [disableStart] | If true, start is not called.
*/
Graph.prototype.zoomExtent = function(initialZoom, disableStart) {
if (initialZoom === undefined) {
@ -627,6 +629,7 @@ Graph.prototype.setOptions = function (options) {
}
}
if (options.edges.color !== undefined) {
if (util.isString(options.edges.color)) {
this.constants.edges.color = {};
@ -956,7 +959,7 @@ Graph.prototype._handleOnDrag = function(event) {
this.drag.translation.x + diffX,
this.drag.translation.y + diffY);
this._redraw();
this.moved = true;
this.moving = true;
}
};
@ -1359,12 +1362,13 @@ Graph.prototype._updateNodes = function(ids) {
// create node
node = new Node(properties, this.images, this.groups, this.constants);
nodes[id] = node;
if (!node.isFixed()) {
this.moving = true;
}
}
}
this.moving = true;
if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
this._resetLevels();
this._setupHierarchicalLayout();
}
this._updateNodeIndexList();
this._reconnectEdges();
this._updateValueRange(nodes);
@ -1809,7 +1813,12 @@ Graph.prototype._stabilize = function() {
this.emit("stabilized",{iterations:count});
};
/**
* When initializing and stabilizing, we can freeze nodes with a predefined position. This greatly speeds up stabilization
* because only the supportnodes for the smoothCurves have to settle.
*
* @private
*/
Graph.prototype._freezeDefinedNodes = function() {
var nodes = this.nodes;
for (var id in nodes) {
@ -1824,6 +1833,11 @@ Graph.prototype._freezeDefinedNodes = function() {
}
};
/**
* Unfreezes the nodes that have been frozen by _freezeDefinedNodes.
*
* @private
*/
Graph.prototype._restoreFrozenNodes = function() {
var nodes = this.nodes;
for (var id in nodes) {
@ -1894,7 +1908,11 @@ Graph.prototype._discreteStepNodes = function() {
}
};
/**
* A single simulation step (or "tick") in the physics simulation
*
* @private
*/
Graph.prototype._physicsTick = function() {
if (!this.freezeSimulation) {
if (this.moving) {
@ -2013,7 +2031,12 @@ Graph.prototype.toggleFreeze = function() {
};
/**
* This function cleans the support nodes if they are not needed and adds them when they are.
*
* @param {boolean} [disableStart]
* @private
*/
Graph.prototype._configureSmoothCurves = function(disableStart) {
if (disableStart === undefined) {
disableStart = true;
@ -2039,6 +2062,13 @@ Graph.prototype._configureSmoothCurves = function(disableStart) {
}
};
/**
* Bezier curves require an anchor point to calculate the smooth flow. These points are nodes. These nodes are invisible but
* are used for the force calculation.
*
* @private
*/
Graph.prototype._createBezierNodes = function() {
if (this.constants.smoothCurves == true) {
for (var edgeId in this.edges) {
@ -2063,7 +2093,11 @@ Graph.prototype._createBezierNodes = function() {
}
};
/**
* load the functions that load the mixins into the prototype.
*
* @private
*/
Graph.prototype._initializeMixinLoaders = function () {
for (var mixinFunction in graphMixinLoaders) {
if (graphMixinLoaders.hasOwnProperty(mixinFunction)) {

+ 1
- 0
src/graph/Node.js View File

@ -356,6 +356,7 @@ Node.prototype.discreteStep = function(interval) {
/**
* Perform one discrete step for the node
* @param {number} interval Time interval in seconds
* @param {number} maxVelocity The speed limit imposed on the velocity
*/
Node.prototype.discreteStepLimited = function(interval, maxVelocity) {
if (!this.xFixed) {

+ 4
- 2
src/graph/graphMixins/ClusterMixin.js View File

@ -137,6 +137,7 @@ var ClusterMixin = {
* @param {Number} zoomDirection | -1 / 0 / +1 for zoomOut / determineByZoom / zoomIn
* @param {Boolean} recursive | enabled or disable recursive calling of the opening of clusters
* @param {Boolean} force | enabled or disable forcing
* @param {Boolean} doNotStart | if true do not call start
*
*/
updateClusters : function(zoomDirection,recursive,force,doNotStart) {
@ -987,9 +988,10 @@ var ClusterMixin = {
var maxLevel = 0;
var minLevel = 1e9;
var clusterLevel = 0;
var nodeId;
// we loop over all nodes in the list
for (var nodeId in this.nodes) {
for (nodeId in this.nodes) {
if (this.nodes.hasOwnProperty(nodeId)) {
clusterLevel = this.nodes[nodeId].clusterSessions.length;
if (maxLevel < clusterLevel) {maxLevel = clusterLevel;}
@ -1001,7 +1003,7 @@ var ClusterMixin = {
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) {
for (nodeId in this.nodes) {
if (this.nodes.hasOwnProperty(nodeId)) {
if (this.nodes[nodeId].clusterSessions.length < targetLevel) {
this._clusterToSmallestNeighbour(this.nodes[nodeId]);

+ 3
- 3
src/graph/graphMixins/HierarchicalLayoutMixin.js View File

@ -123,7 +123,7 @@ var HierarchicalLayoutMixin = {
*/
_getDistribution : function() {
var distribution = {};
var nodeId, node;
var nodeId, node, level;
// we fix Y because the hierarchy is vertical, we fix X so we do not give a node an x position for a second time.
// the fix of X is removed after the x value has been set.
@ -148,7 +148,7 @@ var HierarchicalLayoutMixin = {
// determine the largest amount of nodes of all levels
var maxCount = 0;
for (var level in distribution) {
for (level in distribution) {
if (distribution.hasOwnProperty(level)) {
if (maxCount < distribution[level].amount) {
maxCount = distribution[level].amount;
@ -157,7 +157,7 @@ var HierarchicalLayoutMixin = {
}
// set the initial position and spacing of each nodes accordingly
for (var level in distribution) {
for (level in distribution) {
if (distribution.hasOwnProperty(level)) {
distribution[level].nodeSpacing = (maxCount + 1) * this.constants.hierarchicalLayout.nodeSpacing;
distribution[level].nodeSpacing /= (distribution[level].amount + 1);

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

@ -288,8 +288,6 @@ var manipulationMixin = {
/**
* Adds a node on the specified location
*
* @param {Object} pointer
*/
_addNode : function() {
if (this._selectionIsEmpty() && this.editMode == true) {

+ 6
- 6
src/graph/graphMixins/MixinLoader.js View File

@ -67,15 +67,15 @@ var graphMixinLoaders = {
* @private
*/
_loadSectorSystem: function () {
this.sectors = { },
this.activeSector = ["default"];
this.sectors["active"] = { },
this.sectors["active"]["default"] = {"nodes": {},
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["frozen"] = {};
this.sectors["support"] = {"nodes": {},
"edges": {},
"nodeIndices": [],
@ -108,7 +108,7 @@ var graphMixinLoaders = {
_loadManipulationSystem: function () {
// reset global variables -- these are used by the selection of nodes and edges.
this.blockConnectingEdgeSelection = false;
this.forceAppendSelection = false
this.forceAppendSelection = false;
if (this.constants.dataManipulation.enabled == true) {
// load the manipulator HTML elements. All styling done in css.

+ 1
- 1
src/graph/graphMixins/SelectionMixin.js View File

@ -174,7 +174,7 @@ var SelectionMixin = {
}
for(var edgeId in this.selectionObj.edges) {
if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
this.selectionObj.edges[edgeId].unselect();;
this.selectionObj.edges[edgeId].unselect();
}
}

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

@ -156,6 +156,13 @@ var barnesHutMixin = {
},
/**
* this updates the mass of a branch. this is increased by adding a node.
*
* @param parentBranch
* @param node
* @private
*/
_updateBranchMass : function(parentBranch, node) {
var totalMass = parentBranch.mass + node.mass;
var totalMassInv = 1/totalMass;
@ -173,6 +180,14 @@ var barnesHutMixin = {
},
/**
* determine in which branch the node will be placed.
*
* @param parentBranch
* @param node
* @param skipMassUpdate
* @private
*/
_placeInTree : function(parentBranch,node,skipMassUpdate) {
if (skipMassUpdate != true || skipMassUpdate === undefined) {
// update the mass of the branch.
@ -198,6 +213,14 @@ var barnesHutMixin = {
},
/**
* actually place the node in a region (or branch)
*
* @param parentBranch
* @param node
* @param region
* @private
*/
_placeInRegion : function(parentBranch,node,region) {
switch (parentBranch.children[region].childrenCount) {
case 0: // place node here

+ 29
- 1
src/graph/graphMixins/physics/PhysicsMixin.js View File

@ -464,6 +464,13 @@ var physicsMixin = {
}
},
/**
* This overwrites the this.constants.
*
* @param constantsVariableName
* @param value
* @private
*/
_overWriteGraphConstants: function (constantsVariableName, value) {
var nameArray = constantsVariableName.split("_");
if (nameArray.length == 1) {
@ -478,6 +485,9 @@ var physicsMixin = {
}
};
/**
* this function is bound to the toggle smooth curves button. That is also why it is not in the prototype.
*/
function graphToggleSmoothCurves () {
this.constants.smoothCurves = !this.constants.smoothCurves;
var graph_toggleSmooth = document.getElementById("graph_toggleSmooth");
@ -487,6 +497,10 @@ function graphToggleSmoothCurves () {
this._configureSmoothCurves(false);
};
/**
* this function is used to scramble the nodes
*
*/
function graphRepositionNodes () {
for (var nodeId in this.calculationNodes) {
if (this.calculationNodes.hasOwnProperty(nodeId)) {
@ -504,6 +518,9 @@ function graphRepositionNodes () {
this.start();
};
/**
* this is used to generate an options file from the playing with physics system.
*/
function graphGenerateOptions () {
var options = "No options are required, default values used.";
var optionsSpecific = [];
@ -601,7 +618,10 @@ function graphGenerateOptions () {
};
/**
* this is used to switch between barnesHut, repulsion and hierarchical.
*
*/
function switchConfigurations () {
var ids = ["graph_BH_table", "graph_R_table", "graph_H_table"];
var radioButton = document.querySelector('input[name="graph_physicsMethod"]:checked').value;
@ -640,6 +660,14 @@ function switchConfigurations () {
}
/**
* this generates the ranges depending on the iniital values.
*
* @param id
* @param map
* @param constantsVariableName
*/
function showValueOfRange (id,map,constantsVariableName) {
var valueId = id + "_value";
var rangeValue = document.getElementById(id).value;

+ 1
- 1
src/module/exports.js View File

@ -7,7 +7,7 @@ var vis = {
DataSet: DataSet,
DataView: DataView,
Range: Range,
Stack: Stack,
stack: stack,
TimeStep: TimeStep,
components: {

+ 0
- 169
src/timeline/Stack.js View File

@ -1,169 +0,0 @@
// TODO: turn Stack into a Mixin?
/**
* @constructor Stack
* Stacks items on top of each other.
* @param {Object} [options]
*/
function Stack (options) {
this.options = options || {};
this.defaultOptions = {
order: function (a, b) {
// Order: ranges over non-ranges, ranged ordered by width,
// and non-ranges ordered by start.
if (a instanceof ItemRange) {
if (b instanceof ItemRange) {
var aInt = (a.data.end - a.data.start);
var bInt = (b.data.end - b.data.start);
return (aInt - bInt) || (a.data.start - b.data.start);
}
else {
return -1;
}
}
else {
if (b instanceof ItemRange) {
return 1;
}
else {
return (a.data.start - b.data.start);
}
}
},
margin: {
item: 10,
axis: 20
}
};
}
/**
* Set options for the stack
* @param {Object} options Available options:
* {Number} [margin.item=10]
* {Number} [margin.axis=20]
* {function} [order] Stacking order
*/
Stack.prototype.setOptions = function setOptions (options) {
util.extend(this.options, options);
};
/**
* Order an array with items using a predefined order function for items
* @param {Item[]} items
*/
Stack.prototype.order = function order(items) {
//order the items
var order = this.options.order || this.defaultOptions.order;
if (!(typeof order === 'function')) {
throw new Error('Option order must be a function');
}
items.sort(order);
};
/**
* Order items by their start data
* @param {Item[]} items
*/
Stack.prototype.orderByStart = function orderByStart(items) {
items.sort(function (a, b) {
return a.data.start - b.data.start;
});
};
/**
* Order items by their end date. If they have no end date, their start date
* is used.
* @param {Item[]} items
*/
Stack.prototype.orderByEnd = function orderByEnd(items) {
items.sort(function (a, b) {
var aTime = ('end' in a.data) ? a.data.end : a.data.start,
bTime = ('end' in b.data) ? b.data.end : b.data.start;
return aTime - bTime;
});
};
/**
* Adjust vertical positions of the events such that they don't overlap each
* other.
* @param {Item[]} items All visible items
* @param {boolean} [force=false] If true, all items will be re-stacked.
* If false (default), only items having a
* top===null will be re-stacked
* @private
*/
Stack.prototype.stack = function stack (items, force) {
var i,
iMax,
options = this.options,
marginItem,
marginAxis;
if (options.margin && options.margin.item !== undefined) {
marginItem = options.margin.item;
}
else {
marginItem = this.defaultOptions.margin.item
}
if (options.margin && options.margin.axis !== undefined) {
marginAxis = options.margin.axis;
}
else {
marginAxis = this.defaultOptions.margin.axis
}
if (force) {
// reset top position of all items
for (i = 0, iMax = items.length; i < iMax; i++) {
items[i].top = null;
}
}
// calculate new, non-overlapping positions
for (i = 0, iMax = items.length; i < iMax; i++) {
var item = items[i];
if (item.top === null) {
// initialize top position
item.top = marginAxis;
do {
// TODO: optimize checking for overlap. when there is a gap without items,
// you only need to check for items from the next item on, not from zero
var collidingItem = null;
for (var j = 0, jj = items.length; j < jj; j++) {
var other = items[j];
if (other.top !== null && other !== item && this.collision(item, other, marginItem)) {
collidingItem = other;
break;
}
}
if (collidingItem != null) {
// There is a collision. Reposition the event above the colliding element
item.top = collidingItem.top + collidingItem.height + marginItem;
}
} while (collidingItem);
}
}
};
/**
* Test if the two provided items collide
* The items must have parameters left, width, top, and height.
* @param {Component} a The first item
* @param {Component} b The second item
* @param {Number} margin A minimum required margin.
* If margin is provided, the two items will be
* marked colliding when they overlap or
* when the margin between the two is smaller than
* the requested margin.
* @return {boolean} true if a and b collide, else false
*/
Stack.prototype.collision = function collision (a, b, margin) {
return ((a.left - margin) < (b.left + b.width) &&
(a.left + a.width + margin) > b.left &&
(a.top - margin) < (b.top + b.height) &&
(a.top + a.height + margin) > b.top);
};

+ 63
- 76
src/timeline/Timeline.js View File

@ -15,7 +15,15 @@ function Timeline (container, items, options) {
orientation: 'bottom',
direction: 'horizontal', // 'horizontal' or 'vertical'
autoResize: true,
editable: false,
stacking: true,
editable: {
updateTime: false,
updateGroup: false,
add: false,
remove: false
},
selectable: true,
snap: null, // will be specified after timeaxis is created
@ -93,8 +101,8 @@ function Timeline (container, items, options) {
right: null,
height: '100%',
width: function () {
if (me.groupSet) {
return me.groupSet.getLabelsWidth();
if (me.itemSet) {
return me.itemSet.getLabelsWidth();
}
else {
return 0;
@ -236,11 +244,19 @@ function Timeline (container, items, options) {
me.emit('timechanged', time);
});
this.itemSet = null;
this.groupSet = null;
// create groupset
this.setGroups(null);
// itemset containing items and groups
var itemOptions = util.extend(Object.create(this.options), {
left: null,
right: null,
top: null,
bottom: null,
width: null,
height: null
});
this.itemSet = new ItemSet(this.backgroundPanel, this.axisPanel, this.sideContentPanel, itemOptions);
this.itemSet.setRange(this.range);
this.itemSet.on('change', me.rootPanel.repaint.bind(me.rootPanel));
this.contentPanel.appendChild(this.itemSet);
this.itemsData = null; // DataSet
this.groupsData = null; // DataSet
@ -250,7 +266,7 @@ function Timeline (container, items, options) {
this.setOptions(options);
}
// create itemset and groupset
// create itemset
if (items) {
this.setItems(items);
}
@ -266,6 +282,17 @@ Emitter(Timeline.prototype);
Timeline.prototype.setOptions = function (options) {
util.extend(this.options, options);
if ('editable' in options) {
var isBoolean = typeof options.editable === 'boolean';
this.options.editable = {
updateTime: isBoolean ? options.editable : (options.editable.updateTime || false),
updateGroup: isBoolean ? options.editable : (options.editable.updateGroup || false),
add: isBoolean ? options.editable : (options.editable.add || false),
remove: isBoolean ? options.editable : (options.editable.remove || false)
};
}
// force update of range (apply new min/max etc.)
// both start and end are optional
this.range.setRange(options.start, options.end);
@ -281,6 +308,9 @@ Timeline.prototype.setOptions = function (options) {
}
}
// force the itemSet to refresh: options like orientation and margins may be changed
this.itemSet.markDirty();
// validate the callback functions
var validateCallback = (function (fn) {
if (!(this.options[fn] instanceof Function) || this.options[fn].length != 2) {
@ -360,22 +390,22 @@ Timeline.prototype.setItems = function(items) {
if (!items) {
newDataSet = null;
}
else if (items instanceof DataSet) {
else if (items instanceof DataSet || items instanceof DataView) {
newDataSet = items;
}
if (!(items instanceof DataSet)) {
newDataSet = new DataSet({
else {
// turn an array into a dataset
newDataSet = new DataSet(items, {
convert: {
start: 'Date',
end: 'Date'
}
});
newDataSet.add(items);
}
// set items
this.itemsData = newDataSet;
(this.itemSet || this.groupSet).setItems(newDataSet);
this.itemSet.setItems(newDataSet);
if (initialLoad && (this.options.start == undefined || this.options.end == undefined)) {
// apply the data range as range
@ -424,63 +454,24 @@ Timeline.prototype.setItems = function(items) {
/**
* Set groups
* @param {vis.DataSet | Array | google.visualization.DataTable} groupSet
* @param {vis.DataSet | Array | google.visualization.DataTable} groups
*/
Timeline.prototype.setGroups = function(groupSet) {
var me = this;
this.groupsData = groupSet;
// create options for the itemset or groupset
var options = util.extend(Object.create(this.options), {
top: null,
bottom: null,
right: null,
left: null,
width: null,
height: null
});
if (this.groupsData) {
// Create a GroupSet
// remove itemset if existing
if (this.itemSet) {
this.itemSet.hide(); // TODO: not so nice having to hide here
this.contentPanel.removeChild(this.itemSet);
this.itemSet.setItems(); // disconnect from itemset
this.itemSet = null;
}
// create new GroupSet when needed
if (!this.groupSet) {
this.groupSet = new GroupSet(this.contentPanel, this.sideContentPanel, this.backgroundPanel, this.axisPanel, options);
this.groupSet.on('change', this.rootPanel.repaint.bind(this.rootPanel));
this.groupSet.setRange(this.range);
this.groupSet.setItems(this.itemsData);
this.groupSet.setGroups(this.groupsData);
this.contentPanel.appendChild(this.groupSet);
}
else {
this.groupSet.setGroups(this.groupsData);
}
Timeline.prototype.setGroups = function(groups) {
// convert to type DataSet when needed
var newDataSet;
if (!groups) {
newDataSet = null;
}
else if (groups instanceof DataSet || groups instanceof DataView) {
newDataSet = groups;
}
else {
// ItemSet
if (this.groupSet) {
this.groupSet.hide(); // TODO: not so nice having to hide here
//this.groupSet.setGroups(); // disconnect from groupset
this.groupSet.setItems(); // disconnect from itemset
this.contentPanel.removeChild(this.groupSet);
this.groupSet = null;
}
// create new items
this.itemSet = new ItemSet(this.backgroundPanel, this.axisPanel, options);
this.itemSet.setRange(this.range);
this.itemSet.setItems(this.itemsData);
this.itemSet.on('change', me.rootPanel.repaint.bind(me.rootPanel));
this.contentPanel.appendChild(this.itemSet);
// turn an array into a dataset
newDataSet = new DataSet(groups);
}
this.groupsData = newDataSet;
this.itemSet.setGroups(newDataSet);
};
/**
@ -530,9 +521,7 @@ Timeline.prototype.getItemRange = function getItemRange() {
* unselected.
*/
Timeline.prototype.setSelection = function setSelection (ids) {
var itemOrGroupSet = (this.itemSet || this.groupSet);
if (itemOrGroupSet) itemOrGroupSet.setSelection(ids);
this.itemSet.setSelection(ids);
};
/**
@ -540,9 +529,7 @@ Timeline.prototype.setSelection = function setSelection (ids) {
* @return {Array} ids The ids of the selected items
*/
Timeline.prototype.getSelection = function getSelection() {
var itemOrGroupSet = (this.itemSet || this.groupSet);
return itemOrGroupSet ? itemOrGroupSet.getSelection() : [];
return this.itemSet.getSelection();
};
/**
@ -621,7 +608,7 @@ Timeline.prototype._onSelectItem = function (event) {
*/
Timeline.prototype._onAddItem = function (event) {
if (!this.options.selectable) return;
if (!this.options.editable) return;
if (!this.options.editable.add) return;
var me = this,
item = ItemSet.itemFromTarget(event);
@ -639,7 +626,7 @@ Timeline.prototype._onAddItem = function (event) {
}
else {
// add item
var xAbs = vis.util.getAbsoluteLeft(this.rootPanel.frame);
var xAbs = vis.util.getAbsoluteLeft(this.contentPanel.frame);
var x = event.gesture.center.pageX - xAbs;
var newItem = {
start: this.timeAxis.snap(this._toTime(x)),
@ -649,7 +636,7 @@ Timeline.prototype._onAddItem = function (event) {
var id = util.randomUUID();
newItem[this.itemsData.fieldId] = id;
var group = GroupSet.groupFromTarget(event);
var group = ItemSet.groupFromTarget(event);
if (group) {
newItem.group = group.groupId;
}

+ 1
- 1
src/timeline/component/Component.js View File

@ -74,7 +74,7 @@ Component.prototype.repaint = function repaint() {
* Test whether the component is resized since the last time _isResized() was
* called.
* @return {Boolean} Returns true if the component is resized
* @private
* @protected
*/
Component.prototype._isResized = function _isResized() {
var resized = (this._previousWidth !== this.width || this._previousHeight !== this.height);

+ 343
- 104
src/timeline/component/Group.js View File

@ -1,26 +1,15 @@
/**
* @constructor Group
* @param {Panel} groupPanel
* @param {Panel} labelPanel
* @param {Panel} backgroundPanel
* @param {Panel} axisPanel
* @param {Number | String} groupId
* @param {Object} [options] Options to set initial property values