Browse Source

Merge branch 'develop'

v3_develop v1.0.2
jos 10 years ago
parent
commit
1e0c8c3059
22 changed files with 725 additions and 284 deletions
  1. +57
    -26
      HISTORY.md
  2. +1
    -1
      bower.json
  3. +258
    -119
      dist/vis.js
  4. +11
    -11
      dist/vis.min.js
  5. +16
    -0
      docs/graph.html
  6. +32
    -7
      docs/timeline.html
  7. +88
    -0
      examples/timeline/16_navigation_menu.html
  8. +1
    -0
      examples/timeline/index.html
  9. +1
    -1
      package.json
  10. +82
    -66
      src/graph/Graph.js
  11. +4
    -4
      src/graph/graphMixins/ManipulationMixin.js
  12. +4
    -4
      src/graph/graphMixins/SelectionMixin.js
  13. +13
    -11
      src/graph/graphMixins/physics/PhysicsMixin.js
  14. +1
    -0
      src/module/exports.js
  15. +14
    -2
      src/timeline/TimeStep.js
  16. +58
    -6
      src/timeline/Timeline.js
  17. +22
    -1
      src/timeline/component/Group.js
  18. +3
    -3
      src/timeline/component/ItemSet.js
  19. +1
    -0
      src/timeline/component/RootPanel.js
  20. +20
    -20
      src/timeline/component/TimeAxis.js
  21. +34
    -0
      src/util.js
  22. +4
    -2
      test/timeline.html

+ 57
- 26
HISTORY.md View File

@ -2,6 +2,29 @@
http://visjs.org http://visjs.org
## 2014-05-28, version 1.0.2
### Timeline
- Implemented option `minHeight`, similar to option `maxHeight`.
- Implemented a method `clear([what])`, to clear items, groups, and configuration
of a Timeline instance.
- Added function `repaint()` to force a repaint of the Timeline.
- Some tweaks in snapping dragged items to nice dates.
- Made the instance of moment.js packaged with vis.js accessibly via `vis.moment`.
- A newly created item is initialized with `end` property when option `type`
is `"range"` or `"rangeoverflow"`.
- Fixed a bug in replacing the DataSet of groups via `Timeline.setGroups(groups)`.
- Fixed a bug when rendering the Timeline inside a hidden container.
- Fixed axis scale being determined wrongly for a second Timeline in a single page.
### Graph
- Added zoomable and moveable options.
- Changes setOptions to avoid resetting view.
- Interchanged canvasToDOM and DOMtoCanvas to correspond with the docs.
## 2014-05-09, version 1.0.1 ## 2014-05-09, version 1.0.1
### Timeline ### Timeline
@ -12,9 +35,9 @@ http://visjs.org
### Graph ### Graph
- Added coordinate conversion from DOM to Canvas. - Added coordinate conversion from DOM to Canvas.
- fixed bug where the graph stopped animation after settling in playing with physics.
- fixed bug where hierarchical physics properties were not handled.
- added events for change of view and zooming.
- Fixed bug where the graph stopped animation after settling in playing with physics.
- Fixed bug where hierarchical physics properties were not handled.
- Added events for change of view and zooming.
## 2014-05-02, version 1.0.0 ## 2014-05-02, version 1.0.0
@ -44,8 +67,8 @@ http://visjs.org
### Graph ### Graph
- added recalculate hierarchical layout to update node event.
- added arrowScaleFactor to scale the arrows on the edges.
- Added recalculate hierarchical layout to update node event.
- Added arrowScaleFactor to scale the arrows on the edges.
### DataSet ### DataSet
@ -57,48 +80,53 @@ http://visjs.org
### Graph ### Graph
- fixed IE9 bug.
- style fixes.
- minor bug fixes.
- Fixed IE9 bug.
- Style fixes.
- Minor bug fixes.
## 2014-04-16, version 0.7.3 ## 2014-04-16, version 0.7.3
### Graph ### Graph
- fixed color bug.
- added pull requests from kannonboy and vierja: tooltip styling, label fill color
- Fixed color bug.
- Added pull requests from kannonboy and vierja: tooltip styling, label fill
color.
## 2014-04-09, version 0.7.2 ## 2014-04-09, version 0.7.2
### Graph ### Graph
- fixed edge select bug.
- fixed zoom bug on empty initialization.
- Fixed edge select bug.
- Fixed zoom bug on empty initialization.
## 2014-03-27, version 0.7.1 ## 2014-03-27, version 0.7.1
### Graph ### Graph
- fixed edge color bug.
- fixed select event bug.
- clarified docs, stressing importance of css inclusion for correct display of navigation an manipulation icons.
- improved and expanded playing with physics (configurePhysics option).
- added highlights to navigation icons if the corresponding key is pressed.
- added freezeForStabilization option to improve stabilization with cached positions.
- Fixed edge color bug.
- Fixed select event bug.
- Clarified docs, stressing importance of css inclusion for correct display of
navigation an manipulation icons.
- Improved and expanded playing with physics (configurePhysics option).
- Added highlights to navigation icons if the corresponding key is pressed.
- Added freezeForStabilization option to improve stabilization with cached
positions.
## 2014-03-07, version 0.7.0 ## 2014-03-07, version 0.7.0
### Graph ### Graph
- changed navigation CSS. Icons are now always correctly positioned.
- added stabilizationIterations option to graph.
- added storePosition() method to save the XY positions of nodes in the DataSet.
- separated allowedToMove into allowedToMoveX and allowedToMoveY. This is required for initializing nodes from hierarchical layouts after storePosition().
- added color options for the edges.
- Changed navigation CSS. Icons are now always correctly positioned.
- Added stabilizationIterations option to graph.
- Added storePosition() method to save the XY positions of nodes in the DataSet.
- Separated allowedToMove into allowedToMoveX and allowedToMoveY. This is
required for initializing nodes from hierarchical layouts after
storePosition().
- Added color options for the edges.
## 2014-03-06, version 0.6.1 ## 2014-03-06, version 0.6.1
@ -112,7 +140,8 @@ http://visjs.org
### Timeline ### Timeline
- Fixed a bug with options `margin.axis` and `margin.item` being ignored when setting them to zero.
- Fixed a bug with options `margin.axis` and `margin.item` being ignored when
setting them to zero.
- Some clarifications in the documentation. - Some clarifications in the documentation.
@ -120,7 +149,8 @@ http://visjs.org
### Graph ### Graph
- Added Physics Configuration option. This makes tweaking the physics system to suit your needs easier.
- Added Physics Configuration option. This makes tweaking the physics system to
suit your needs easier.
- Click and doubleClick events. - Click and doubleClick events.
- Initial zoom bugfix. - Initial zoom bugfix.
- Directions for Hierarchical layout. - Directions for Hierarchical layout.
@ -150,7 +180,8 @@ http://visjs.org
- Performance improvements. - Performance improvements.
- Fixed scroll to zoom not working on IE in standards mode. - Fixed scroll to zoom not working on IE in standards mode.
- Added hierarchical layout option. - Added hierarchical layout option.
- Overhauled physics system, now using Barnes-Hut simulation by default. Great performance gains.
- Overhauled physics system, now using Barnes-Hut simulation by default. Great
performance gains.
- Modified clustering system to give better results. - Modified clustering system to give better results.
- Adaptive performance system to increase visual performance (60fps target). - Adaptive performance system to increase visual performance (60fps target).

+ 1
- 1
bower.json View File

@ -1,6 +1,6 @@
{ {
"name": "vis", "name": "vis",
"version": "1.0.1",
"version": "1.0.2",
"description": "A dynamic, browser-based visualization library.", "description": "A dynamic, browser-based visualization library.",
"homepage": "http://visjs.org/", "homepage": "http://visjs.org/",
"repository": { "repository": {

+ 258
- 119
dist/vis.js View File

@ -4,8 +4,8 @@
* *
* A dynamic, browser-based visualization library. * A dynamic, browser-based visualization library.
* *
* @version 1.0.1
* @date 2014-05-09
* @version 1.0.2
* @date 2014-05-28
* *
* @license * @license
* Copyright (C) 2011-2014 Almende B.V, http://almende.com * Copyright (C) 2011-2014 Almende B.V, http://almende.com
@ -407,6 +407,40 @@ util.extend = function (a, b) {
return a; return a;
}; };
/**
* Deep extend an object a with the properties of object b
* @param {Object} a
* @param {Object} b
* @returns {Object}
*/
util.deepExtend = function deepExtend (a, b) {
// TODO: add support for Arrays to deepExtend
if (Array.isArray(b)) {
throw new TypeError('Arrays are not supported by deepExtend');
}
for (var prop in b) {
if (b.hasOwnProperty(prop)) {
if (b[prop] && b[prop].constructor === Object) {
if (a[prop] === undefined) {
a[prop] = {};
}
if (a[prop].constructor === Object) {
deepExtend(a[prop], b[prop]);
}
else {
a[prop] = b[prop];
}
} else if (Array.isArray(b[prop])) {
throw new TypeError('Arrays are not supported by deepExtend');
} else {
a[prop] = b[prop];
}
}
}
return a;
};
/** /**
* Test whether all elements in two arrays are equal. * Test whether all elements in two arrays are equal.
* @param {Array} a * @param {Array} a
@ -2885,8 +2919,7 @@ TimeStep.prototype.snap = function(date) {
clone.setSeconds(0); clone.setSeconds(0);
clone.setMilliseconds(0); clone.setMilliseconds(0);
} }
else if (this.scale == TimeStep.SCALE.DAY ||
this.scale == TimeStep.SCALE.WEEKDAY) {
else if (this.scale == TimeStep.SCALE.DAY) {
//noinspection FallthroughInSwitchStatementJS //noinspection FallthroughInSwitchStatementJS
switch (this.step) { switch (this.step) {
case 5: case 5:
@ -2899,6 +2932,19 @@ TimeStep.prototype.snap = function(date) {
clone.setSeconds(0); clone.setSeconds(0);
clone.setMilliseconds(0); clone.setMilliseconds(0);
} }
else if (this.scale == TimeStep.SCALE.WEEKDAY) {
//noinspection FallthroughInSwitchStatementJS
switch (this.step) {
case 5:
case 2:
clone.setHours(Math.round(clone.getHours() / 12) * 12); break;
default:
clone.setHours(Math.round(clone.getHours() / 6) * 6); break;
}
clone.setMinutes(0);
clone.setSeconds(0);
clone.setMilliseconds(0);
}
else if (this.scale == TimeStep.SCALE.HOUR) { else if (this.scale == TimeStep.SCALE.HOUR) {
switch (this.step) { switch (this.step) {
case 4: case 4:
@ -3905,6 +3951,7 @@ RootPanel.prototype.repaint = function repaint() {
// update frame size // update frame size
this.frame.style.maxHeight = util.option.asSize(this.options.maxHeight, ''); this.frame.style.maxHeight = util.option.asSize(this.options.maxHeight, '');
this.frame.style.minHeight = util.option.asSize(this.options.minHeight, '');
this._updateSize(); this._updateSize();
// if the root panel or any of its childs is resized, repaint again, // if the root panel or any of its childs is resized, repaint again,
@ -4387,32 +4434,32 @@ TimeAxis.prototype._repaintLine = function() {
* @private * @private
*/ */
TimeAxis.prototype._calculateCharSize = function () { TimeAxis.prototype._calculateCharSize = function () {
// determine the char width and height on the minor axis
if (!('minorCharHeight' in this.props)) {
var textMinor = document.createTextNode('0');
var measureCharMinor = document.createElement('DIV');
measureCharMinor.className = 'text minor measure';
measureCharMinor.appendChild(textMinor);
this.frame.appendChild(measureCharMinor);
// Note: We calculate char size with every repaint. Size may change, for
// example when any of the timelines parents had display:none for example.
this.props.minorCharHeight = measureCharMinor.clientHeight;
this.props.minorCharWidth = measureCharMinor.clientWidth;
// determine the char width and height on the minor axis
if (!this.dom.measureCharMinor) {
this.dom.measureCharMinor = document.createElement('DIV');
this.dom.measureCharMinor.className = 'text minor measure';
this.dom.measureCharMinor.style.position = 'absolute';
this.frame.removeChild(measureCharMinor);
this.dom.measureCharMinor.appendChild(document.createTextNode('0'));
this.frame.appendChild(this.dom.measureCharMinor);
} }
this.props.minorCharHeight = this.dom.measureCharMinor.clientHeight;
this.props.minorCharWidth = this.dom.measureCharMinor.clientWidth;
if (!('majorCharHeight' in this.props)) {
var textMajor = document.createTextNode('0');
var measureCharMajor = document.createElement('DIV');
measureCharMajor.className = 'text major measure';
measureCharMajor.appendChild(textMajor);
this.frame.appendChild(measureCharMajor);
// determine the char width and height on the major axis
if (!this.dom.measureCharMajor) {
this.dom.measureCharMajor = document.createElement('DIV');
this.dom.measureCharMajor.className = 'text minor measure';
this.dom.measureCharMajor.style.position = 'absolute';
this.props.majorCharHeight = measureCharMajor.clientHeight;
this.props.majorCharWidth = measureCharMajor.clientWidth;
this.frame.removeChild(measureCharMajor);
this.dom.measureCharMajor.appendChild(document.createTextNode('0'));
this.frame.appendChild(this.dom.measureCharMajor);
} }
this.props.majorCharHeight = this.dom.measureCharMajor.clientHeight;
this.props.majorCharWidth = this.dom.measureCharMajor.clientWidth;
}; };
/** /**
@ -5190,7 +5237,8 @@ ItemSet.prototype.setGroups = function setGroups(groups) {
// remove all drawn groups // remove all drawn groups
ids = this.groupsData.getIds(); ids = this.groupsData.getIds();
this._onRemoveGroups(ids);
this.groupsData = null;
this._onRemoveGroups(ids); // note: this will cause a repaint
} }
// replace the dataset // replace the dataset
@ -5441,8 +5489,7 @@ ItemSet.prototype._orderGroups = function () {
// hide all groups, removes them from the DOM // hide all groups, removes them from the DOM
var groups = this.groups; var groups = this.groups;
groupIds.forEach(function (groupId) { groupIds.forEach(function (groupId) {
var group = groups[groupId];
group.hide();
groups[groupId].hide();
}); });
// show the groups again, attach them to the DOM in correct order // show the groups again, attach them to the DOM in correct order
@ -6742,6 +6789,14 @@ Group.prototype._create = function() {
this.dom.background = document.createElement('div'); this.dom.background = document.createElement('div');
this.dom.axis = document.createElement('div'); this.dom.axis = document.createElement('div');
// create a hidden marker to detect when the Timelines container is attached
// to the DOM, or the style of a parent of the Timeline is changed from
// display:none is changed to visible.
this.dom.marker = document.createElement('div');
this.dom.marker.style.visibility = 'hidden';
this.dom.marker.innerHTML = '?';
this.dom.background.appendChild(this.dom.marker);
}; };
/** /**
@ -6813,6 +6868,20 @@ Group.prototype.repaint = function repaint(range, margin, restack) {
this.visibleItems = this._updateVisibleItems(this.orderedItems, this.visibleItems, range); this.visibleItems = this._updateVisibleItems(this.orderedItems, this.visibleItems, range);
// force recalculation of the height of the items when the marker height changed
// (due to the Timeline being attached to the DOM or changed from display:none to visible)
var markerHeight = this.dom.marker.clientHeight;
if (markerHeight != this.lastMarkerHeight) {
this.lastMarkerHeight = markerHeight;
util.forEach(this.items, function (item) {
item.dirty = true;
if (item.displayed) item.repaint();
});
restack = true;
}
// reposition visible items vertically // reposition visible items vertically
if (this.itemSet.options.stack) { // TODO: ugly way to access options... if (this.itemSet.options.stack) { // TODO: ugly way to access options...
stack.stack(this.visibleItems, margin, restack); stack.stack(this.visibleItems, margin, restack);
@ -6820,7 +6889,6 @@ Group.prototype.repaint = function repaint(range, margin, restack) {
else { // no stacking else { // no stacking
stack.nostack(this.visibleItems, margin); stack.nostack(this.visibleItems, margin);
} }
this.stackDirty = false;
for (var i = 0, ii = this.visibleItems.length; i < ii; i++) { for (var i = 0, ii = this.visibleItems.length; i < ii; i++) {
var item = this.visibleItems[i]; var item = this.visibleItems[i];
item.repositionY(); item.repositionY();
@ -7155,7 +7223,7 @@ function Timeline (container, items, options) {
var me = this; var me = this;
var now = moment().hours(0).minutes(0).seconds(0).milliseconds(0); var now = moment().hours(0).minutes(0).seconds(0).milliseconds(0);
this.options = {
this.defaultOptions = {
orientation: 'bottom', orientation: 'bottom',
direction: 'horizontal', // 'horizontal' or 'vertical' direction: 'horizontal', // 'horizontal' or 'vertical'
autoResize: true, autoResize: true,
@ -7169,7 +7237,6 @@ function Timeline (container, items, options) {
}, },
selectable: true, selectable: true,
snap: null, // will be specified after timeaxis is created
min: null, min: null,
max: null, max: null,
@ -7183,6 +7250,13 @@ function Timeline (container, items, options) {
showCurrentTime: false, showCurrentTime: false,
showCustomTime: false, showCustomTime: false,
groupOrder: null,
width: null,
height: null,
maxHeight: null,
minHeight: null,
type: 'box', type: 'box',
align: 'center', align: 'center',
margin: { margin: {
@ -7202,11 +7276,17 @@ function Timeline (container, items, options) {
}, },
onRemove: function (item, callback) { onRemove: function (item, callback) {
callback(item); callback(item);
},
}
};
this.options = {};
util.deepExtend(this.options, this.defaultOptions);
util.deepExtend(this.options, {
snap: null, // will be specified after timeaxis is created
toScreen: me._toScreen.bind(me), toScreen: me._toScreen.bind(me),
toTime: me._toTime.bind(me) toTime: me._toTime.bind(me)
};
});
// root panel // root panel
var rootOptions = util.extend(Object.create(this.options), { var rootOptions = util.extend(Object.create(this.options), {
@ -7424,7 +7504,7 @@ Emitter(Timeline.prototype);
* @param {Object} options TODO: describe the available options * @param {Object} options TODO: describe the available options
*/ */
Timeline.prototype.setOptions = function (options) { Timeline.prototype.setOptions = function (options) {
util.extend(this.options, options);
util.deepExtend(this.options, options);
if ('editable' in options) { if ('editable' in options) {
var isBoolean = typeof options.editable === 'boolean'; var isBoolean = typeof options.editable === 'boolean';
@ -7583,6 +7663,33 @@ Timeline.prototype.setGroups = function setGroups(groups) {
this.itemSet.setGroups(newDataSet); this.itemSet.setGroups(newDataSet);
}; };
/**
* Clear the Timeline. By Default, items, groups and options are cleared.
* Example usage:
*
* timeline.clear(); // clear items, groups, and options
* timeline.clear({options: true}); // clear options only
*
* @param {Object} [what] Optionally specify what to clear. By default:
* {items: true, groups: true, options: true}
*/
Timeline.prototype.clear = function clear(what) {
// clear items
if (!what || what.items) {
this.setItems(null);
}
// clear groups
if (!what || what.groups) {
this.setGroups(null);
}
// clear options
if (!what || what.options) {
this.setOptions(this.defaultOptions);
}
};
/** /**
* Set Timeline window such that it fits all items * Set Timeline window such that it fits all items
*/ */
@ -7679,7 +7786,7 @@ Timeline.prototype.getSelection = function getSelection() {
* Where start and end can be a Date, number, or string, and range is an * Where start and end can be a Date, number, or string, and range is an
* object with properties start and end. * object with properties start and end.
* *
* @param {Date | Number | String} [start] Start date of visible window
* @param {Date | Number | String | Object} [start] Start date of visible window
* @param {Date | Number | String} [end] End date of visible window * @param {Date | Number | String} [end] End date of visible window
*/ */
Timeline.prototype.setWindow = function setWindow(start, end) { Timeline.prototype.setWindow = function setWindow(start, end) {
@ -7704,6 +7811,14 @@ Timeline.prototype.getWindow = function setWindow() {
}; };
}; };
/**
* Force a repaint of the Timeline. Can be useful to manually repaint when
* option autoResize=false
*/
Timeline.prototype.repaint = function repaint() {
this.rootPanel.repaint();
};
/** /**
* Handle selecting/deselecting an item when tapping it * Handle selecting/deselecting an item when tapping it
* @param {Event} event * @param {Event} event
@ -7770,6 +7885,11 @@ Timeline.prototype._onAddItem = function (event) {
content: 'new item' content: 'new item'
}; };
// when default type is a range, add a default end date to the new item
if (this.options.type === 'range' || this.options.type == 'rangeoverflow') {
newItem.end = this.timeAxis.snap(this._toTime(x + this.rootPanel.width / 5));
}
var id = util.randomUUID(); var id = util.randomUUID();
newItem[this.itemsData.fieldId] = id; newItem[this.itemsData.fieldId] = id;
@ -11091,7 +11211,7 @@ var physicsMixin = {
*/ */
_calculateSpringForces: function () { _calculateSpringForces: function () {
var edgeLength, edge, edgeId; var edgeLength, edge, edgeId;
var dx, dy, fx, fy, springForce, length;
var dx, dy, fx, fy, springForce, distance;
var edges = this.edges; var edges = this.edges;
// forces caused by the edges, modelled as springs // forces caused by the edges, modelled as springs
@ -11107,13 +11227,14 @@ var physicsMixin = {
dx = (edge.from.x - edge.to.x); dx = (edge.from.x - edge.to.x);
dy = (edge.from.y - edge.to.y); dy = (edge.from.y - edge.to.y);
length = Math.sqrt(dx * dx + dy * dy);
distance = Math.sqrt(dx * dx + dy * dy);
if (length == 0) {
length = 0.01;
if (distance == 0) {
distance = 0.01;
} }
springForce = this.constants.physics.springConstant * (edgeLength - length) / length;
// the 1/distance is so the fx and fy can be calculated without sine or cosine.
springForce = this.constants.physics.springConstant * (edgeLength - distance) / distance;
fx = dx * springForce; fx = dx * springForce;
fy = dy * springForce; fy = dy * springForce;
@ -11175,17 +11296,18 @@ var physicsMixin = {
* @private * @private
*/ */
_calculateSpringForce: function (node1, node2, edgeLength) { _calculateSpringForce: function (node1, node2, edgeLength) {
var dx, dy, fx, fy, springForce, length;
var dx, dy, fx, fy, springForce, distance;
dx = (node1.x - node2.x); dx = (node1.x - node2.x);
dy = (node1.y - node2.y); dy = (node1.y - node2.y);
length = Math.sqrt(dx * dx + dy * dy);
distance = Math.sqrt(dx * dx + dy * dy);
if (length == 0) {
length = 0.01;
if (distance == 0) {
distance = 0.01;
} }
springForce = this.constants.physics.springConstant * (edgeLength - length) / length;
// the 1/distance is so the fx and fy can be calculated without sine or cosine.
springForce = this.constants.physics.springConstant * (edgeLength - distance) / distance;
fx = dx * springForce; fx = dx * springForce;
fy = dy * springForce; fy = dy * springForce;
@ -11220,7 +11342,7 @@ var physicsMixin = {
'<table id="graph_BH_table" style="display:none">' + '<table id="graph_BH_table" style="display:none">' +
'<tr><td><b>Barnes Hut</b></td></tr>' + '<tr><td><b>Barnes Hut</b></td></tr>' +
'<tr>' + '<tr>' +
'<td width="150px">gravitationalConstant</td><td>0</td><td><input type="range" min="500" max="20000" value="' + (-1 * this.constants.physics.barnesHut.gravitationalConstant) + '" step="25" style="width:300px" id="graph_BH_gc"></td><td width="50px">-20000</td><td><input value="' + (-1 * this.constants.physics.barnesHut.gravitationalConstant) + '" id="graph_BH_gc_value" style="width:60px"></td>' +
'<td width="150px">gravitationalConstant</td><td>0</td><td><input type="range" min="0" max="20000" value="' + (-1 * this.constants.physics.barnesHut.gravitationalConstant) + '" step="25" style="width:300px" id="graph_BH_gc"></td><td width="50px">-20000</td><td><input value="' + (-1 * this.constants.physics.barnesHut.gravitationalConstant) + '" id="graph_BH_gc_value" style="width:60px"></td>' +
'</tr>' + '</tr>' +
'<tr>' + '<tr>' +
'<td width="150px">centralGravity</td><td>0</td><td><input type="range" min="0" max="3" value="' + this.constants.physics.barnesHut.centralGravity + '" step="0.05" style="width:300px" id="graph_BH_cg"></td><td>3</td><td><input value="' + this.constants.physics.barnesHut.centralGravity + '" id="graph_BH_cg_value" style="width:60px"></td>' + '<td width="150px">centralGravity</td><td>0</td><td><input type="range" min="0" max="3" value="' + this.constants.physics.barnesHut.centralGravity + '" step="0.05" style="width:300px" id="graph_BH_cg"></td><td>3</td><td><input value="' + this.constants.physics.barnesHut.centralGravity + '" id="graph_BH_cg_value" style="width:60px"></td>' +
@ -12681,10 +12803,10 @@ var manipulationMixin = {
this.cachedFunctions["_handleOnDrag"] = this._handleOnDrag; this.cachedFunctions["_handleOnDrag"] = this._handleOnDrag;
this._handleOnDrag = function(event) { this._handleOnDrag = function(event) {
var pointer = this._getPointer(event.gesture.center); var pointer = this._getPointer(event.gesture.center);
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.sectors['support']['nodes']['targetNode'].x = this._XconvertDOMtoCanvas(pointer.x);
this.sectors['support']['nodes']['targetNode'].y = this._YconvertDOMtoCanvas(pointer.y);
this.sectors['support']['nodes']['targetViaNode'].x = 0.5 * (this._XconvertDOMtoCanvas(pointer.x) + this.edges['connectionEdge'].from.x);
this.sectors['support']['nodes']['targetViaNode'].y = this._YconvertDOMtoCanvas(pointer.y);
}; };
this.moving = true; this.moving = true;
@ -14607,8 +14729,8 @@ var SelectionMixin = {
* @private * @private
*/ */
_pointerToPositionObject : function(pointer) { _pointerToPositionObject : function(pointer) {
var x = this._canvasToX(pointer.x);
var y = this._canvasToY(pointer.y);
var x = this._XconvertDOMtoCanvas(pointer.x);
var y = this._YconvertDOMtoCanvas(pointer.y);
return {left: x, return {left: x,
top: y, top: y,
@ -15000,8 +15122,8 @@ var SelectionMixin = {
var node = this._getNodeAt(pointer); var node = this._getNodeAt(pointer);
if (node != null && node !== undefined) { if (node != null && node !== undefined) {
// we reset the areaCenter here so the opening of the node will occur // we reset the areaCenter here so the opening of the node will occur
this.areaCenter = {"x" : this._canvasToX(pointer.x),
"y" : this._canvasToY(pointer.y)};
this.areaCenter = {"x" : this._XconvertDOMtoCanvas(pointer.x),
"y" : this._YconvertDOMtoCanvas(pointer.y)};
this.openCluster(node); this.openCluster(node);
} }
this.emit("doubleClick", this.getSelection()); this.emit("doubleClick", this.getSelection());
@ -15723,7 +15845,9 @@ function Graph (container, data, options) {
border: '#666', border: '#666',
background: '#FFFFC6' background: '#FFFFC6'
} }
}
},
moveable: true,
zoomable: true
}; };
this.editMode = this.constants.dataManipulation.initiallyVisible; this.editMode = this.constants.dataManipulation.initiallyVisible;
@ -15755,8 +15879,11 @@ function Graph (container, data, options) {
this._loadHierarchySystem(); this._loadHierarchySystem();
// apply options // apply options
this._setTranslation(this.frame.clientWidth / 2, this.frame.clientHeight / 2);
this._setScale(1);
this.setOptions(options); this.setOptions(options);
// other vars // other vars
this.freezeSimulation = false;// freeze the simulation this.freezeSimulation = false;// freeze the simulation
this.cachedFunctions = {}; this.cachedFunctions = {};
@ -16034,15 +16161,19 @@ Graph.prototype.setData = function(data, disableStart) {
if (!disableStart) { if (!disableStart) {
// find a stable position or start animating to a stable position // find a stable position or start animating to a stable position
if (this.stabilize) { if (this.stabilize) {
this._stabilize();
var me = this;
setTimeout(function() {me._stabilize(); me.start();},0)
}
else {
this.start();
} }
this.start();
} }
}; };
/** /**
* Set options * Set options
* @param {Object} options * @param {Object} options
* @param {Boolean} [initializeView] | set zoom and translation to default.
*/ */
Graph.prototype.setOptions = function (options) { Graph.prototype.setOptions = function (options) {
if (options) { if (options) {
@ -16056,7 +16187,8 @@ Graph.prototype.setOptions = function (options) {
if (options.freezeForStabilization !== undefined) {this.constants.freezeForStabilization = options.freezeForStabilization;} if (options.freezeForStabilization !== undefined) {this.constants.freezeForStabilization = options.freezeForStabilization;}
if (options.configurePhysics !== undefined){this.constants.configurePhysics = options.configurePhysics;} if (options.configurePhysics !== undefined){this.constants.configurePhysics = options.configurePhysics;}
if (options.stabilizationIterations !== undefined) {this.constants.stabilizationIterations = options.stabilizationIterations;} if (options.stabilizationIterations !== undefined) {this.constants.stabilizationIterations = options.stabilizationIterations;}
if (options.moveable !== undefined) {this.constants.moveable = options.moveable;}
if (options.zoomable !== undefined) {this.constants.zoomable = options.zoomable;}
if (options.labels !== undefined) { if (options.labels !== undefined) {
@ -16271,11 +16403,10 @@ Graph.prototype.setOptions = function (options) {
// bind keys. If disabled, this will not do anything; // bind keys. If disabled, this will not do anything;
this._createKeyBinds(); this._createKeyBinds();
this.setSize(this.width, this.height); this.setSize(this.width, this.height);
this._setTranslation(this.frame.clientWidth / 2, this.frame.clientHeight / 2);
this._setScale(1);
this._redraw();
this.moving = true;
this.start();
}; };
/** /**
@ -16491,11 +16622,11 @@ Graph.prototype._handleOnDrag = function(event) {
var node = s.node; var node = s.node;
if (!s.xFixed) { if (!s.xFixed) {
node.x = me._canvasToX(me._xToCanvas(s.x) + deltaX);
node.x = me._XconvertDOMtoCanvas(me._XconvertCanvasToDOM(s.x) + deltaX);
} }
if (!s.yFixed) { if (!s.yFixed) {
node.y = me._canvasToY(me._yToCanvas(s.y) + deltaY);
node.y = me._YconvertDOMtoCanvas(me._YconvertCanvasToDOM(s.y) + deltaY);
} }
}); });
@ -16506,16 +16637,18 @@ Graph.prototype._handleOnDrag = function(event) {
} }
} }
else { else {
// move the graph
var diffX = pointer.x - this.drag.pointer.x;
var diffY = pointer.y - this.drag.pointer.y;
this._setTranslation(
this.drag.translation.x + diffX,
this.drag.translation.y + diffY);
this._redraw();
this.moving = true;
this.start();
if (this.constants.moveable == true) {
// move the graph
var diffX = pointer.x - this.drag.pointer.x;
var diffY = pointer.y - this.drag.pointer.y;
this._setTranslation(
this.drag.translation.x + diffX,
this.drag.translation.y + diffY);
this._redraw();
this.moving = true;
this.start();
}
} }
}; };
@ -16603,37 +16736,38 @@ Graph.prototype._onPinch = function (event) {
* @private * @private
*/ */
Graph.prototype._zoom = function(scale, pointer) { Graph.prototype._zoom = function(scale, pointer) {
var scaleOld = this._getScale();
if (scale < 0.00001) {
scale = 0.00001;
}
if (scale > 10) {
scale = 10;
}
// + this.frame.canvas.clientHeight / 2
var translation = this._getTranslation();
var scaleFrac = scale / scaleOld;
var tx = (1 - scaleFrac) * pointer.x + translation.x * scaleFrac;
var ty = (1 - scaleFrac) * pointer.y + translation.y * scaleFrac;
if (this.constants.zoomable == true) {
var scaleOld = this._getScale();
if (scale < 0.00001) {
scale = 0.00001;
}
if (scale > 10) {
scale = 10;
}
// + this.frame.canvas.clientHeight / 2
var translation = this._getTranslation();
this.areaCenter = {"x" : this._canvasToX(pointer.x),
"y" : this._canvasToY(pointer.y)};
var scaleFrac = scale / scaleOld;
var tx = (1 - scaleFrac) * pointer.x + translation.x * scaleFrac;
var ty = (1 - scaleFrac) * pointer.y + translation.y * scaleFrac;
this._setScale(scale);
this._setTranslation(tx, ty);
this.updateClustersDefault();
this._redraw();
this.areaCenter = {"x" : this._XconvertDOMtoCanvas(pointer.x),
"y" : this._YconvertDOMtoCanvas(pointer.y)};
if (scaleOld < scale) {
this.emit("zoom", {direction:"+"});
}
else {
this.emit("zoom", {direction:"-"});
}
this._setScale(scale);
this._setTranslation(tx, ty);
this.updateClustersDefault();
this._redraw();
if (scaleOld < scale) {
this.emit("zoom", {direction:"+"});
}
else {
this.emit("zoom", {direction:"-"});
}
return scale;
return scale;
}
}; };
@ -16719,10 +16853,10 @@ Graph.prototype._onMouseMoveTitle = function (event) {
*/ */
Graph.prototype._checkShowPopup = function (pointer) { Graph.prototype._checkShowPopup = function (pointer) {
var obj = { var obj = {
left: this._canvasToX(pointer.x),
top: this._canvasToY(pointer.y),
right: this._canvasToX(pointer.x),
bottom: this._canvasToY(pointer.y)
left: this._XconvertDOMtoCanvas(pointer.x),
top: this._YconvertDOMtoCanvas(pointer.y),
right: this._XconvertDOMtoCanvas(pointer.x),
bottom: this._YconvertDOMtoCanvas(pointer.y)
}; };
var id; var id;
@ -17186,12 +17320,12 @@ Graph.prototype._redraw = function() {
ctx.scale(this.scale, this.scale); ctx.scale(this.scale, this.scale);
this.canvasTopLeft = { this.canvasTopLeft = {
"x": this._canvasToX(0),
"y": this._canvasToY(0)
"x": this._XconvertDOMtoCanvas(0),
"y": this._YconvertDOMtoCanvas(0)
}; };
this.canvasBottomRight = { this.canvasBottomRight = {
"x": this._canvasToX(this.frame.canvas.clientWidth),
"y": this._canvasToY(this.frame.canvas.clientHeight)
"x": this._XconvertDOMtoCanvas(this.frame.canvas.clientWidth),
"y": this._YconvertDOMtoCanvas(this.frame.canvas.clientHeight)
}; };
this._doInAllSectors("_drawAllSectorNodes",ctx); this._doInAllSectors("_drawAllSectorNodes",ctx);
@ -17260,42 +17394,46 @@ Graph.prototype._getScale = function() {
}; };
/** /**
* Convert a horizontal point on the HTML canvas to the x-value of the model
* Convert the X coordinate in DOM-space (coordinate point in browser relative to the container div) to
* the X coordinate in canvas-space (the simulation sandbox, which the camera looks upon)
* @param {number} x * @param {number} x
* @returns {number} * @returns {number}
* @private * @private
*/ */
Graph.prototype._canvasToX = function(x) {
Graph.prototype._XconvertDOMtoCanvas = function(x) {
return (x - this.translation.x) / this.scale; return (x - this.translation.x) / this.scale;
}; };
/** /**
* Convert an x-value in the model to a horizontal point on the HTML canvas
* Convert the X coordinate in canvas-space (the simulation sandbox, which the camera looks upon) to
* the X coordinate in DOM-space (coordinate point in browser relative to the container div)
* @param {number} x * @param {number} x
* @returns {number} * @returns {number}
* @private * @private
*/ */
Graph.prototype._xToCanvas = function(x) {
Graph.prototype._XconvertCanvasToDOM = function(x) {
return x * this.scale + this.translation.x; return x * this.scale + this.translation.x;
}; };
/** /**
* Convert a vertical point on the HTML canvas to the y-value of the model
* Convert the Y coordinate in DOM-space (coordinate point in browser relative to the container div) to
* the Y coordinate in canvas-space (the simulation sandbox, which the camera looks upon)
* @param {number} y * @param {number} y
* @returns {number} * @returns {number}
* @private * @private
*/ */
Graph.prototype._canvasToY = function(y) {
Graph.prototype._YconvertDOMtoCanvas = function(y) {
return (y - this.translation.y) / this.scale; return (y - this.translation.y) / this.scale;
}; };
/** /**
* Convert an y-value in the model to a vertical point on the HTML canvas
* Convert the Y coordinate in canvas-space (the simulation sandbox, which the camera looks upon) to
* the Y coordinate in DOM-space (coordinate point in browser relative to the container div)
* @param {number} y * @param {number} y
* @returns {number} * @returns {number}
* @private * @private
*/ */
Graph.prototype._yToCanvas = function(y) {
Graph.prototype._YconvertCanvasToDOM = function(y) {
return y * this.scale + this.translation.y ; return y * this.scale + this.translation.y ;
}; };
@ -17306,8 +17444,8 @@ Graph.prototype._yToCanvas = function(y) {
* @returns {{x: number, y: number}} * @returns {{x: number, y: number}}
* @constructor * @constructor
*/ */
Graph.prototype.DOMtoCanvas = function(pos) {
return {x:this._xToCanvas(pos.x),y:this._yToCanvas(pos.y)};
Graph.prototype.canvasToDOM = function(pos) {
return {x:this._XconvertCanvasToDOM(pos.x),y:this._YconvertCanvasToDOM(pos.y)};
} }
/** /**
@ -17316,8 +17454,8 @@ Graph.prototype.DOMtoCanvas = function(pos) {
* @returns {{x: number, y: number}} * @returns {{x: number, y: number}}
* @constructor * @constructor
*/ */
Graph.prototype.canvasToDOM = function(pos) {
return {x:this._canvasToX(pos.x),y:this._canvasToY(pos.y)};
Graph.prototype.DOMtoCanvas = function(pos) {
return {x:this._XconvertDOMtoCanvas(pos.x),y:this._YconvertDOMtoCanvas(pos.y)};
} }
/** /**
@ -17727,6 +17865,7 @@ Graph.prototype.storePosition = function() {
*/ */
var vis = { var vis = {
util: util, util: util,
moment: moment,
DataSet: DataSet, DataSet: DataSet,
DataView: DataView, DataView: DataView,

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


+ 16
- 0
docs/graph.html View File

@ -798,6 +798,14 @@ var options = {
Configuration options for shortcuts keys. Sortcut keys are turned off by default. See section <a href="#Keyboard_navigation">Keyboard navigation</a> for an overview of the available options. Configuration options for shortcuts keys. Sortcut keys are turned off by default. See section <a href="#Keyboard_navigation">Keyboard navigation</a> for an overview of the available options.
</td> </td>
</tr> </tr>
<tr>
<td>moveable</td>
<td>Boolean</td>
<td>true</td>
<td>
Toggle if the graph can be dragged. This will not affect the dragging of nodes.
</td>
</tr>
<tr> <tr>
<td><a href="#Navigation_controls">navigation</a></td> <td><a href="#Navigation_controls">navigation</a></td>
@ -854,6 +862,14 @@ var options = {
<td>"400px"</td> <td>"400px"</td>
<td>The width of the graph in pixels or as a percentage.</td> <td>The width of the graph in pixels or as a percentage.</td>
</tr> </tr>
<tr>
<td>zoomable</td>
<td>Boolean</td>
<td>true</td>
<td>
Toggle if the graph can be zoomed.
</td>
</tr>
</table> </table>

+ 32
- 7
docs/timeline.html View File

@ -341,8 +341,7 @@ var options = {
<td>autoResize</td> <td>autoResize</td>
<td>boolean</td> <td>boolean</td>
<td>true</td> <td>true</td>
<td>If true, the Timeline will automatically detect when its
container is resized, and redraw itself accordingly.</td>
<td>If true, the Timeline will automatically detect when its container is resized, and redraw itself accordingly. If false, the Timeline can be forced to repaint after its container has been resized using the function <code>repaint()</code>.</td>
</tr> </tr>
<tr> <tr>
@ -402,7 +401,7 @@ var options = {
<tr> <tr>
<td>height</td> <td>height</td>
<td>String</td>
<td>Number | String</td>
<td>none</td> <td>none</td>
<td>The height of the timeline in pixels or as a percentage. <td>The height of the timeline in pixels or as a percentage.
When height is undefined or null, the height of the timeline is automatically When height is undefined or null, the height of the timeline is automatically
@ -438,10 +437,9 @@ var options = {
<tr> <tr>
<td>maxHeight</td> <td>maxHeight</td>
<td>Number</td>
<td>Number | String</td>
<td>none</td> <td>none</td>
<td>Specifies a maximum height for the Timeline in pixels.
</td>
<td>Specifies the maximum height for the Timeline. Can be a number in pixels or a string like "300px".</td>
</tr> </tr>
<tr> <tr>
@ -453,6 +451,13 @@ var options = {
</td> </td>
</tr> </tr>
<tr>
<td>minHeight</td>
<td>Number | String</td>
<td>none</td>
<td>Specifies the minimum height for the Timeline. Can be a number in pixels or a string like "300px".</td>
</tr>
<tr> <tr>
<td>onAdd</td> <td>onAdd</td>
<td>Function</td> <td>Function</td>
@ -583,7 +588,7 @@ var options = {
<td>type</td> <td>type</td>
<td>String</td> <td>String</td>
<td>'box'</td> <td>'box'</td>
<td>Specifies the type for the timeline items. Choose from 'box', 'point', 'range', and 'rangeoverflow'. Note that individual items can override this global type.
<td>Specifies the default type for the timeline items. Choose from 'box', 'point', 'range', and 'rangeoverflow'. Note that individual items can override this default type.
</td> </td>
</tr> </tr>
@ -628,6 +633,19 @@ var options = {
<th>Description</th> <th>Description</th>
</tr> </tr>
<tr>
<td>clear([what])</td>
<td>none</td>
<td>
Clear the Timeline. An object can be passed specifying which sections to clear: items, groups,
and/or options. By Default, items, groups and options are cleared, i.e. <code>what = {items: true, groups: true, options: true}</code>. Example usage:
<pre class="prettyprint lang-js">timeline.clear(); // clear items, groups, and options
timeline.clear({options: true}); // clear options only
</pre>
</td>
</tr>
<tr> <tr>
<td>fit()</td> <td>fit()</td>
<td>none</td> <td>none</td>
@ -673,6 +691,13 @@ var options = {
<td>Remove an event listener created before via function <code>on(event, callback)</code>. See section <a href="#Events">Events for more information</a>.</td> <td>Remove an event listener created before via function <code>on(event, callback)</code>. See section <a href="#Events">Events for more information</a>.</td>
</tr> </tr>
<tr>
<td>repaint()</td>
<td>none</td>
<td>Force a repaint of the Timeline. Can be useful to manually repaint when option autoResize=false.
</td>
</tr>
<tr> <tr>
<td>setGroups(groups)</td> <td>setGroups(groups)</td>
<td>none</td> <td>none</td>

+ 88
- 0
examples/timeline/16_navigation_menu.html View File

@ -0,0 +1,88 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Timeline | navigation menu</title>
<style type="text/css">
body, html, input {
font-family: sans-serif;
font-size: 12pt;
}
#visualization {
position: relative;
}
.menu {
position: absolute;
top: 0;
right: 0;
margin: 10px;
z-index: 9999;
}
</style>
<script src="../../dist/vis.js"></script>
<link href="../../dist/vis.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div id="visualization">
<div class="menu">
<input type="button" id="zoomIn" value="Zoom in"/>
<input type="button" id="zoomOut" value="Zoom out"/>
<input type="button" id="moveLeft" value="Move left"/>
<input type="button" id="moveRight" value="Move right"/>
</div>
</div>
<script type="text/javascript">
// create a timeline with some data
var container = document.getElementById('visualization');
var items = [
{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', type: 'point'}
];
var options = {};
var timeline = new vis.Timeline(container, items, options);
/**
* Move the timeline a given percentage to left or right
* @param {Number} percentage For example 0.1 (left) or -0.1 (right)
*/
function move (percentage) {
var range = timeline.getWindow();
var interval = range.end - range.start;
timeline.setWindow({
start: range.start.valueOf() - interval * percentage,
end: range.end.valueOf() - interval * percentage
});
}
/**
* Zoom the timeline a given percentage in or out
* @param {Number} percentage For example 0.1 (zoom out) or -0.1 (zoom in)
*/
function zoom (percentage) {
var range = timeline.getWindow();
var interval = range.end - range.start;
timeline.setWindow({
start: range.start.valueOf() - interval * percentage,
end: range.end.valueOf() + interval * percentage
});
}
// attach events to the navigation buttons
document.getElementById('zoomIn').onclick = function () { zoom(-0.2); };
document.getElementById('zoomOut').onclick = function () { zoom( 0.2); };
document.getElementById('moveLeft').onclick = function () { move( 0.2); };
document.getElementById('moveRight').onclick = function () { move(-0.2); };
</script>
</body>
</html>

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

@ -27,6 +27,7 @@
<p><a href="13_past_and_future.html">13_past_and_future.html</a></p> <p><a href="13_past_and_future.html">13_past_and_future.html</a></p>
<p><a href="14_a_lot_of_grouped_data.html">14_a_lot_of_grouped_data.html</a></p> <p><a href="14_a_lot_of_grouped_data.html">14_a_lot_of_grouped_data.html</a></p>
<p><a href="15_item_class_names.html">15_item_class_names.html</a></p> <p><a href="15_item_class_names.html">15_item_class_names.html</a></p>
<p><a href="16_navigation_menu.html">16_navigation_menu.html</a></p>
<p><a href="requirejs/requirejs_example.html">requirejs_example.html</a></p> <p><a href="requirejs/requirejs_example.html">requirejs_example.html</a></p>

+ 1
- 1
package.json View File

@ -1,6 +1,6 @@
{ {
"name": "vis", "name": "vis",
"version": "1.0.1",
"version": "1.0.2",
"description": "A dynamic, browser-based visualization library.", "description": "A dynamic, browser-based visualization library.",
"homepage": "http://visjs.org/", "homepage": "http://visjs.org/",
"repository": { "repository": {

+ 82
- 66
src/graph/Graph.js View File

@ -179,7 +179,9 @@ function Graph (container, data, options) {
border: '#666', border: '#666',
background: '#FFFFC6' background: '#FFFFC6'
} }
}
},
moveable: true,
zoomable: true
}; };
this.editMode = this.constants.dataManipulation.initiallyVisible; this.editMode = this.constants.dataManipulation.initiallyVisible;
@ -211,8 +213,11 @@ function Graph (container, data, options) {
this._loadHierarchySystem(); this._loadHierarchySystem();
// apply options // apply options
this._setTranslation(this.frame.clientWidth / 2, this.frame.clientHeight / 2);
this._setScale(1);
this.setOptions(options); this.setOptions(options);
// other vars // other vars
this.freezeSimulation = false;// freeze the simulation this.freezeSimulation = false;// freeze the simulation
this.cachedFunctions = {}; this.cachedFunctions = {};
@ -490,15 +495,19 @@ Graph.prototype.setData = function(data, disableStart) {
if (!disableStart) { if (!disableStart) {
// find a stable position or start animating to a stable position // find a stable position or start animating to a stable position
if (this.stabilize) { if (this.stabilize) {
this._stabilize();
var me = this;
setTimeout(function() {me._stabilize(); me.start();},0)
}
else {
this.start();
} }
this.start();
} }
}; };
/** /**
* Set options * Set options
* @param {Object} options * @param {Object} options
* @param {Boolean} [initializeView] | set zoom and translation to default.
*/ */
Graph.prototype.setOptions = function (options) { Graph.prototype.setOptions = function (options) {
if (options) { if (options) {
@ -512,7 +521,8 @@ Graph.prototype.setOptions = function (options) {
if (options.freezeForStabilization !== undefined) {this.constants.freezeForStabilization = options.freezeForStabilization;} if (options.freezeForStabilization !== undefined) {this.constants.freezeForStabilization = options.freezeForStabilization;}
if (options.configurePhysics !== undefined){this.constants.configurePhysics = options.configurePhysics;} if (options.configurePhysics !== undefined){this.constants.configurePhysics = options.configurePhysics;}
if (options.stabilizationIterations !== undefined) {this.constants.stabilizationIterations = options.stabilizationIterations;} if (options.stabilizationIterations !== undefined) {this.constants.stabilizationIterations = options.stabilizationIterations;}
if (options.moveable !== undefined) {this.constants.moveable = options.moveable;}
if (options.zoomable !== undefined) {this.constants.zoomable = options.zoomable;}
if (options.labels !== undefined) { if (options.labels !== undefined) {
@ -727,11 +737,10 @@ Graph.prototype.setOptions = function (options) {
// bind keys. If disabled, this will not do anything; // bind keys. If disabled, this will not do anything;
this._createKeyBinds(); this._createKeyBinds();
this.setSize(this.width, this.height); this.setSize(this.width, this.height);
this._setTranslation(this.frame.clientWidth / 2, this.frame.clientHeight / 2);
this._setScale(1);
this._redraw();
this.moving = true;
this.start();
}; };
/** /**
@ -947,11 +956,11 @@ Graph.prototype._handleOnDrag = function(event) {
var node = s.node; var node = s.node;
if (!s.xFixed) { if (!s.xFixed) {
node.x = me._canvasToX(me._xToCanvas(s.x) + deltaX);
node.x = me._XconvertDOMtoCanvas(me._XconvertCanvasToDOM(s.x) + deltaX);
} }
if (!s.yFixed) { if (!s.yFixed) {
node.y = me._canvasToY(me._yToCanvas(s.y) + deltaY);
node.y = me._YconvertDOMtoCanvas(me._YconvertCanvasToDOM(s.y) + deltaY);
} }
}); });
@ -962,16 +971,18 @@ Graph.prototype._handleOnDrag = function(event) {
} }
} }
else { else {
// move the graph
var diffX = pointer.x - this.drag.pointer.x;
var diffY = pointer.y - this.drag.pointer.y;
this._setTranslation(
this.drag.translation.x + diffX,
this.drag.translation.y + diffY);
this._redraw();
this.moving = true;
this.start();
if (this.constants.moveable == true) {
// move the graph
var diffX = pointer.x - this.drag.pointer.x;
var diffY = pointer.y - this.drag.pointer.y;
this._setTranslation(
this.drag.translation.x + diffX,
this.drag.translation.y + diffY);
this._redraw();
this.moving = true;
this.start();
}
} }
}; };
@ -1059,37 +1070,38 @@ Graph.prototype._onPinch = function (event) {
* @private * @private
*/ */
Graph.prototype._zoom = function(scale, pointer) { Graph.prototype._zoom = function(scale, pointer) {
var scaleOld = this._getScale();
if (scale < 0.00001) {
scale = 0.00001;
}
if (scale > 10) {
scale = 10;
}
// + this.frame.canvas.clientHeight / 2
var translation = this._getTranslation();
var scaleFrac = scale / scaleOld;
var tx = (1 - scaleFrac) * pointer.x + translation.x * scaleFrac;
var ty = (1 - scaleFrac) * pointer.y + translation.y * scaleFrac;
if (this.constants.zoomable == true) {
var scaleOld = this._getScale();
if (scale < 0.00001) {
scale = 0.00001;
}
if (scale > 10) {
scale = 10;
}
// + this.frame.canvas.clientHeight / 2
var translation = this._getTranslation();
this.areaCenter = {"x" : this._canvasToX(pointer.x),
"y" : this._canvasToY(pointer.y)};
var scaleFrac = scale / scaleOld;
var tx = (1 - scaleFrac) * pointer.x + translation.x * scaleFrac;
var ty = (1 - scaleFrac) * pointer.y + translation.y * scaleFrac;
this._setScale(scale);
this._setTranslation(tx, ty);
this.updateClustersDefault();
this._redraw();
this.areaCenter = {"x" : this._XconvertDOMtoCanvas(pointer.x),
"y" : this._YconvertDOMtoCanvas(pointer.y)};
if (scaleOld < scale) {
this.emit("zoom", {direction:"+"});
}
else {
this.emit("zoom", {direction:"-"});
}
this._setScale(scale);
this._setTranslation(tx, ty);
this.updateClustersDefault();
this._redraw();
if (scaleOld < scale) {
this.emit("zoom", {direction:"+"});
}
else {
this.emit("zoom", {direction:"-"});
}
return scale;
return scale;
}
}; };
@ -1175,10 +1187,10 @@ Graph.prototype._onMouseMoveTitle = function (event) {
*/ */
Graph.prototype._checkShowPopup = function (pointer) { Graph.prototype._checkShowPopup = function (pointer) {
var obj = { var obj = {
left: this._canvasToX(pointer.x),
top: this._canvasToY(pointer.y),
right: this._canvasToX(pointer.x),
bottom: this._canvasToY(pointer.y)
left: this._XconvertDOMtoCanvas(pointer.x),
top: this._YconvertDOMtoCanvas(pointer.y),
right: this._XconvertDOMtoCanvas(pointer.x),
bottom: this._YconvertDOMtoCanvas(pointer.y)
}; };
var id; var id;
@ -1642,12 +1654,12 @@ Graph.prototype._redraw = function() {
ctx.scale(this.scale, this.scale); ctx.scale(this.scale, this.scale);
this.canvasTopLeft = { this.canvasTopLeft = {
"x": this._canvasToX(0),
"y": this._canvasToY(0)
"x": this._XconvertDOMtoCanvas(0),
"y": this._YconvertDOMtoCanvas(0)
}; };
this.canvasBottomRight = { this.canvasBottomRight = {
"x": this._canvasToX(this.frame.canvas.clientWidth),
"y": this._canvasToY(this.frame.canvas.clientHeight)
"x": this._XconvertDOMtoCanvas(this.frame.canvas.clientWidth),
"y": this._YconvertDOMtoCanvas(this.frame.canvas.clientHeight)
}; };
this._doInAllSectors("_drawAllSectorNodes",ctx); this._doInAllSectors("_drawAllSectorNodes",ctx);
@ -1716,42 +1728,46 @@ Graph.prototype._getScale = function() {
}; };
/** /**
* Convert a horizontal point on the HTML canvas to the x-value of the model
* Convert the X coordinate in DOM-space (coordinate point in browser relative to the container div) to
* the X coordinate in canvas-space (the simulation sandbox, which the camera looks upon)
* @param {number} x * @param {number} x
* @returns {number} * @returns {number}
* @private * @private
*/ */
Graph.prototype._canvasToX = function(x) {
Graph.prototype._XconvertDOMtoCanvas = function(x) {
return (x - this.translation.x) / this.scale; return (x - this.translation.x) / this.scale;
}; };
/** /**
* Convert an x-value in the model to a horizontal point on the HTML canvas
* Convert the X coordinate in canvas-space (the simulation sandbox, which the camera looks upon) to
* the X coordinate in DOM-space (coordinate point in browser relative to the container div)
* @param {number} x * @param {number} x
* @returns {number} * @returns {number}
* @private * @private
*/ */
Graph.prototype._xToCanvas = function(x) {
Graph.prototype._XconvertCanvasToDOM = function(x) {
return x * this.scale + this.translation.x; return x * this.scale + this.translation.x;
}; };
/** /**
* Convert a vertical point on the HTML canvas to the y-value of the model
* Convert the Y coordinate in DOM-space (coordinate point in browser relative to the container div) to
* the Y coordinate in canvas-space (the simulation sandbox, which the camera looks upon)
* @param {number} y * @param {number} y
* @returns {number} * @returns {number}
* @private * @private
*/ */
Graph.prototype._canvasToY = function(y) {
Graph.prototype._YconvertDOMtoCanvas = function(y) {
return (y - this.translation.y) / this.scale; return (y - this.translation.y) / this.scale;
}; };
/** /**
* Convert an y-value in the model to a vertical point on the HTML canvas
* Convert the Y coordinate in canvas-space (the simulation sandbox, which the camera looks upon) to
* the Y coordinate in DOM-space (coordinate point in browser relative to the container div)
* @param {number} y * @param {number} y
* @returns {number} * @returns {number}
* @private * @private
*/ */
Graph.prototype._yToCanvas = function(y) {
Graph.prototype._YconvertCanvasToDOM = function(y) {
return y * this.scale + this.translation.y ; return y * this.scale + this.translation.y ;
}; };
@ -1762,8 +1778,8 @@ Graph.prototype._yToCanvas = function(y) {
* @returns {{x: number, y: number}} * @returns {{x: number, y: number}}
* @constructor * @constructor
*/ */
Graph.prototype.DOMtoCanvas = function(pos) {
return {x:this._xToCanvas(pos.x),y:this._yToCanvas(pos.y)};
Graph.prototype.canvasToDOM = function(pos) {
return {x:this._XconvertCanvasToDOM(pos.x),y:this._YconvertCanvasToDOM(pos.y)};
} }
/** /**
@ -1772,8 +1788,8 @@ Graph.prototype.DOMtoCanvas = function(pos) {
* @returns {{x: number, y: number}} * @returns {{x: number, y: number}}
* @constructor * @constructor
*/ */
Graph.prototype.canvasToDOM = function(pos) {
return {x:this._canvasToX(pos.x),y:this._canvasToY(pos.y)};
Graph.prototype.DOMtoCanvas = function(pos) {
return {x:this._XconvertDOMtoCanvas(pos.x),y:this._YconvertDOMtoCanvas(pos.y)};
} }
/** /**

+ 4
- 4
src/graph/graphMixins/ManipulationMixin.js View File

@ -243,10 +243,10 @@ var manipulationMixin = {
this.cachedFunctions["_handleOnDrag"] = this._handleOnDrag; this.cachedFunctions["_handleOnDrag"] = this._handleOnDrag;
this._handleOnDrag = function(event) { this._handleOnDrag = function(event) {
var pointer = this._getPointer(event.gesture.center); var pointer = this._getPointer(event.gesture.center);
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.sectors['support']['nodes']['targetNode'].x = this._XconvertDOMtoCanvas(pointer.x);
this.sectors['support']['nodes']['targetNode'].y = this._YconvertDOMtoCanvas(pointer.y);
this.sectors['support']['nodes']['targetViaNode'].x = 0.5 * (this._XconvertDOMtoCanvas(pointer.x) + this.edges['connectionEdge'].from.x);
this.sectors['support']['nodes']['targetViaNode'].y = this._YconvertDOMtoCanvas(pointer.y);
}; };
this.moving = true; this.moving = true;

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

@ -40,8 +40,8 @@ var SelectionMixin = {
* @private * @private
*/ */
_pointerToPositionObject : function(pointer) { _pointerToPositionObject : function(pointer) {
var x = this._canvasToX(pointer.x);
var y = this._canvasToY(pointer.y);
var x = this._XconvertDOMtoCanvas(pointer.x);
var y = this._YconvertDOMtoCanvas(pointer.y);
return {left: x, return {left: x,
top: y, top: y,
@ -433,8 +433,8 @@ var SelectionMixin = {
var node = this._getNodeAt(pointer); var node = this._getNodeAt(pointer);
if (node != null && node !== undefined) { if (node != null && node !== undefined) {
// we reset the areaCenter here so the opening of the node will occur // we reset the areaCenter here so the opening of the node will occur
this.areaCenter = {"x" : this._canvasToX(pointer.x),
"y" : this._canvasToY(pointer.y)};
this.areaCenter = {"x" : this._XconvertDOMtoCanvas(pointer.x),
"y" : this._YconvertDOMtoCanvas(pointer.y)};
this.openCluster(node); this.openCluster(node);
} }
this.emit("doubleClick", this.getSelection()); this.emit("doubleClick", this.getSelection());

+ 13
- 11
src/graph/graphMixins/physics/PhysicsMixin.js View File

@ -188,7 +188,7 @@ var physicsMixin = {
*/ */
_calculateSpringForces: function () { _calculateSpringForces: function () {
var edgeLength, edge, edgeId; var edgeLength, edge, edgeId;
var dx, dy, fx, fy, springForce, length;
var dx, dy, fx, fy, springForce, distance;
var edges = this.edges; var edges = this.edges;
// forces caused by the edges, modelled as springs // forces caused by the edges, modelled as springs
@ -204,13 +204,14 @@ var physicsMixin = {
dx = (edge.from.x - edge.to.x); dx = (edge.from.x - edge.to.x);
dy = (edge.from.y - edge.to.y); dy = (edge.from.y - edge.to.y);
length = Math.sqrt(dx * dx + dy * dy);
distance = Math.sqrt(dx * dx + dy * dy);
if (length == 0) {
length = 0.01;
if (distance == 0) {
distance = 0.01;
} }
springForce = this.constants.physics.springConstant * (edgeLength - length) / length;
// the 1/distance is so the fx and fy can be calculated without sine or cosine.
springForce = this.constants.physics.springConstant * (edgeLength - distance) / distance;
fx = dx * springForce; fx = dx * springForce;
fy = dy * springForce; fy = dy * springForce;
@ -272,17 +273,18 @@ var physicsMixin = {
* @private * @private
*/ */
_calculateSpringForce: function (node1, node2, edgeLength) { _calculateSpringForce: function (node1, node2, edgeLength) {
var dx, dy, fx, fy, springForce, length;
var dx, dy, fx, fy, springForce, distance;
dx = (node1.x - node2.x); dx = (node1.x - node2.x);
dy = (node1.y - node2.y); dy = (node1.y - node2.y);
length = Math.sqrt(dx * dx + dy * dy);
distance = Math.sqrt(dx * dx + dy * dy);
if (length == 0) {
length = 0.01;
if (distance == 0) {
distance = 0.01;
} }
springForce = this.constants.physics.springConstant * (edgeLength - length) / length;
// the 1/distance is so the fx and fy can be calculated without sine or cosine.
springForce = this.constants.physics.springConstant * (edgeLength - distance) / distance;
fx = dx * springForce; fx = dx * springForce;
fy = dy * springForce; fy = dy * springForce;
@ -317,7 +319,7 @@ var physicsMixin = {
'<table id="graph_BH_table" style="display:none">' + '<table id="graph_BH_table" style="display:none">' +
'<tr><td><b>Barnes Hut</b></td></tr>' + '<tr><td><b>Barnes Hut</b></td></tr>' +
'<tr>' + '<tr>' +
'<td width="150px">gravitationalConstant</td><td>0</td><td><input type="range" min="500" max="20000" value="' + (-1 * this.constants.physics.barnesHut.gravitationalConstant) + '" step="25" style="width:300px" id="graph_BH_gc"></td><td width="50px">-20000</td><td><input value="' + (-1 * this.constants.physics.barnesHut.gravitationalConstant) + '" id="graph_BH_gc_value" style="width:60px"></td>' +
'<td width="150px">gravitationalConstant</td><td>0</td><td><input type="range" min="0" max="20000" value="' + (-1 * this.constants.physics.barnesHut.gravitationalConstant) + '" step="25" style="width:300px" id="graph_BH_gc"></td><td width="50px">-20000</td><td><input value="' + (-1 * this.constants.physics.barnesHut.gravitationalConstant) + '" id="graph_BH_gc_value" style="width:60px"></td>' +
'</tr>' + '</tr>' +
'<tr>' + '<tr>' +
'<td width="150px">centralGravity</td><td>0</td><td><input type="range" min="0" max="3" value="' + this.constants.physics.barnesHut.centralGravity + '" step="0.05" style="width:300px" id="graph_BH_cg"></td><td>3</td><td><input value="' + this.constants.physics.barnesHut.centralGravity + '" id="graph_BH_cg_value" style="width:60px"></td>' + '<td width="150px">centralGravity</td><td>0</td><td><input type="range" min="0" max="3" value="' + this.constants.physics.barnesHut.centralGravity + '" step="0.05" style="width:300px" id="graph_BH_cg"></td><td>3</td><td><input value="' + this.constants.physics.barnesHut.centralGravity + '" id="graph_BH_cg_value" style="width:60px"></td>' +

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

@ -3,6 +3,7 @@
*/ */
var vis = { var vis = {
util: util, util: util,
moment: moment,
DataSet: DataSet, DataSet: DataSet,
DataView: DataView, DataView: DataView,

+ 14
- 2
src/timeline/TimeStep.js View File

@ -314,8 +314,7 @@ TimeStep.prototype.snap = function(date) {
clone.setSeconds(0); clone.setSeconds(0);
clone.setMilliseconds(0); clone.setMilliseconds(0);
} }
else if (this.scale == TimeStep.SCALE.DAY ||
this.scale == TimeStep.SCALE.WEEKDAY) {
else if (this.scale == TimeStep.SCALE.DAY) {
//noinspection FallthroughInSwitchStatementJS //noinspection FallthroughInSwitchStatementJS
switch (this.step) { switch (this.step) {
case 5: case 5:
@ -328,6 +327,19 @@ TimeStep.prototype.snap = function(date) {
clone.setSeconds(0); clone.setSeconds(0);
clone.setMilliseconds(0); clone.setMilliseconds(0);
} }
else if (this.scale == TimeStep.SCALE.WEEKDAY) {
//noinspection FallthroughInSwitchStatementJS
switch (this.step) {
case 5:
case 2:
clone.setHours(Math.round(clone.getHours() / 12) * 12); break;
default:
clone.setHours(Math.round(clone.getHours() / 6) * 6); break;
}
clone.setMinutes(0);
clone.setSeconds(0);
clone.setMilliseconds(0);
}
else if (this.scale == TimeStep.SCALE.HOUR) { else if (this.scale == TimeStep.SCALE.HOUR) {
switch (this.step) { switch (this.step) {
case 4: case 4:

+ 58
- 6
src/timeline/Timeline.js View File

@ -11,7 +11,7 @@ function Timeline (container, items, options) {
var me = this; var me = this;
var now = moment().hours(0).minutes(0).seconds(0).milliseconds(0); var now = moment().hours(0).minutes(0).seconds(0).milliseconds(0);
this.options = {
this.defaultOptions = {
orientation: 'bottom', orientation: 'bottom',
direction: 'horizontal', // 'horizontal' or 'vertical' direction: 'horizontal', // 'horizontal' or 'vertical'
autoResize: true, autoResize: true,
@ -25,7 +25,6 @@ function Timeline (container, items, options) {
}, },
selectable: true, selectable: true,
snap: null, // will be specified after timeaxis is created
min: null, min: null,
max: null, max: null,
@ -39,6 +38,13 @@ function Timeline (container, items, options) {
showCurrentTime: false, showCurrentTime: false,
showCustomTime: false, showCustomTime: false,
groupOrder: null,
width: null,
height: null,
maxHeight: null,
minHeight: null,
type: 'box', type: 'box',
align: 'center', align: 'center',
margin: { margin: {
@ -58,11 +64,17 @@ function Timeline (container, items, options) {
}, },
onRemove: function (item, callback) { onRemove: function (item, callback) {
callback(item); callback(item);
},
}
};
this.options = {};
util.deepExtend(this.options, this.defaultOptions);
util.deepExtend(this.options, {
snap: null, // will be specified after timeaxis is created
toScreen: me._toScreen.bind(me), toScreen: me._toScreen.bind(me),
toTime: me._toTime.bind(me) toTime: me._toTime.bind(me)
};
});
// root panel // root panel
var rootOptions = util.extend(Object.create(this.options), { var rootOptions = util.extend(Object.create(this.options), {
@ -280,7 +292,7 @@ Emitter(Timeline.prototype);
* @param {Object} options TODO: describe the available options * @param {Object} options TODO: describe the available options
*/ */
Timeline.prototype.setOptions = function (options) { Timeline.prototype.setOptions = function (options) {
util.extend(this.options, options);
util.deepExtend(this.options, options);
if ('editable' in options) { if ('editable' in options) {
var isBoolean = typeof options.editable === 'boolean'; var isBoolean = typeof options.editable === 'boolean';
@ -439,6 +451,33 @@ Timeline.prototype.setGroups = function setGroups(groups) {
this.itemSet.setGroups(newDataSet); this.itemSet.setGroups(newDataSet);
}; };
/**
* Clear the Timeline. By Default, items, groups and options are cleared.
* Example usage:
*
* timeline.clear(); // clear items, groups, and options
* timeline.clear({options: true}); // clear options only
*
* @param {Object} [what] Optionally specify what to clear. By default:
* {items: true, groups: true, options: true}
*/
Timeline.prototype.clear = function clear(what) {
// clear items
if (!what || what.items) {
this.setItems(null);
}
// clear groups
if (!what || what.groups) {
this.setGroups(null);
}
// clear options
if (!what || what.options) {
this.setOptions(this.defaultOptions);
}
};
/** /**
* Set Timeline window such that it fits all items * Set Timeline window such that it fits all items
*/ */
@ -535,7 +574,7 @@ Timeline.prototype.getSelection = function getSelection() {
* Where start and end can be a Date, number, or string, and range is an * Where start and end can be a Date, number, or string, and range is an
* object with properties start and end. * object with properties start and end.
* *
* @param {Date | Number | String} [start] Start date of visible window
* @param {Date | Number | String | Object} [start] Start date of visible window
* @param {Date | Number | String} [end] End date of visible window * @param {Date | Number | String} [end] End date of visible window
*/ */
Timeline.prototype.setWindow = function setWindow(start, end) { Timeline.prototype.setWindow = function setWindow(start, end) {
@ -560,6 +599,14 @@ Timeline.prototype.getWindow = function setWindow() {
}; };
}; };
/**
* Force a repaint of the Timeline. Can be useful to manually repaint when
* option autoResize=false
*/
Timeline.prototype.repaint = function repaint() {
this.rootPanel.repaint();
};
/** /**
* Handle selecting/deselecting an item when tapping it * Handle selecting/deselecting an item when tapping it
* @param {Event} event * @param {Event} event
@ -626,6 +673,11 @@ Timeline.prototype._onAddItem = function (event) {
content: 'new item' content: 'new item'
}; };
// when default type is a range, add a default end date to the new item
if (this.options.type === 'range' || this.options.type == 'rangeoverflow') {
newItem.end = this.timeAxis.snap(this._toTime(x + this.rootPanel.width / 5));
}
var id = util.randomUUID(); var id = util.randomUUID();
newItem[this.itemsData.fieldId] = id; newItem[this.itemsData.fieldId] = id;

+ 22
- 1
src/timeline/component/Group.js View File

@ -51,6 +51,14 @@ Group.prototype._create = function() {
this.dom.background = document.createElement('div'); this.dom.background = document.createElement('div');
this.dom.axis = document.createElement('div'); this.dom.axis = document.createElement('div');
// create a hidden marker to detect when the Timelines container is attached
// to the DOM, or the style of a parent of the Timeline is changed from
// display:none is changed to visible.
this.dom.marker = document.createElement('div');
this.dom.marker.style.visibility = 'hidden';
this.dom.marker.innerHTML = '?';
this.dom.background.appendChild(this.dom.marker);
}; };
/** /**
@ -122,6 +130,20 @@ Group.prototype.repaint = function repaint(range, margin, restack) {
this.visibleItems = this._updateVisibleItems(this.orderedItems, this.visibleItems, range); this.visibleItems = this._updateVisibleItems(this.orderedItems, this.visibleItems, range);
// force recalculation of the height of the items when the marker height changed
// (due to the Timeline being attached to the DOM or changed from display:none to visible)
var markerHeight = this.dom.marker.clientHeight;
if (markerHeight != this.lastMarkerHeight) {
this.lastMarkerHeight = markerHeight;
util.forEach(this.items, function (item) {
item.dirty = true;
if (item.displayed) item.repaint();
});
restack = true;
}
// reposition visible items vertically // reposition visible items vertically
if (this.itemSet.options.stack) { // TODO: ugly way to access options... if (this.itemSet.options.stack) { // TODO: ugly way to access options...
stack.stack(this.visibleItems, margin, restack); stack.stack(this.visibleItems, margin, restack);
@ -129,7 +151,6 @@ Group.prototype.repaint = function repaint(range, margin, restack) {
else { // no stacking else { // no stacking
stack.nostack(this.visibleItems, margin); stack.nostack(this.visibleItems, margin);
} }
this.stackDirty = false;
for (var i = 0, ii = this.visibleItems.length; i < ii; i++) { for (var i = 0, ii = this.visibleItems.length; i < ii; i++) {
var item = this.visibleItems[i]; var item = this.visibleItems[i];
item.repositionY(); item.repositionY();

+ 3
- 3
src/timeline/component/ItemSet.js View File

@ -519,7 +519,8 @@ ItemSet.prototype.setGroups = function setGroups(groups) {
// remove all drawn groups // remove all drawn groups
ids = this.groupsData.getIds(); ids = this.groupsData.getIds();
this._onRemoveGroups(ids);
this.groupsData = null;
this._onRemoveGroups(ids); // note: this will cause a repaint
} }
// replace the dataset // replace the dataset
@ -770,8 +771,7 @@ ItemSet.prototype._orderGroups = function () {
// hide all groups, removes them from the DOM // hide all groups, removes them from the DOM
var groups = this.groups; var groups = this.groups;
groupIds.forEach(function (groupId) { groupIds.forEach(function (groupId) {
var group = groups[groupId];
group.hide();
groups[groupId].hide();
}); });
// show the groups again, attach them to the DOM in correct order // show the groups again, attach them to the DOM in correct order

+ 1
- 0
src/timeline/component/RootPanel.js View File

@ -101,6 +101,7 @@ RootPanel.prototype.repaint = function repaint() {
// update frame size // update frame size
this.frame.style.maxHeight = util.option.asSize(this.options.maxHeight, ''); this.frame.style.maxHeight = util.option.asSize(this.options.maxHeight, '');
this.frame.style.minHeight = util.option.asSize(this.options.minHeight, '');
this._updateSize(); this._updateSize();
// if the root panel or any of its childs is resized, repaint again, // if the root panel or any of its childs is resized, repaint again,

+ 20
- 20
src/timeline/component/TimeAxis.js View File

@ -407,32 +407,32 @@ TimeAxis.prototype._repaintLine = function() {
* @private * @private
*/ */
TimeAxis.prototype._calculateCharSize = function () { TimeAxis.prototype._calculateCharSize = function () {
// determine the char width and height on the minor axis
if (!('minorCharHeight' in this.props)) {
var textMinor = document.createTextNode('0');
var measureCharMinor = document.createElement('DIV');
measureCharMinor.className = 'text minor measure';
measureCharMinor.appendChild(textMinor);
this.frame.appendChild(measureCharMinor);
// Note: We calculate char size with every repaint. Size may change, for
// example when any of the timelines parents had display:none for example.
this.props.minorCharHeight = measureCharMinor.clientHeight;
this.props.minorCharWidth = measureCharMinor.clientWidth;
// determine the char width and height on the minor axis
if (!this.dom.measureCharMinor) {
this.dom.measureCharMinor = document.createElement('DIV');
this.dom.measureCharMinor.className = 'text minor measure';
this.dom.measureCharMinor.style.position = 'absolute';
this.frame.removeChild(measureCharMinor);
this.dom.measureCharMinor.appendChild(document.createTextNode('0'));
this.frame.appendChild(this.dom.measureCharMinor);
} }
this.props.minorCharHeight = this.dom.measureCharMinor.clientHeight;
this.props.minorCharWidth = this.dom.measureCharMinor.clientWidth;
if (!('majorCharHeight' in this.props)) {
var textMajor = document.createTextNode('0');
var measureCharMajor = document.createElement('DIV');
measureCharMajor.className = 'text major measure';
measureCharMajor.appendChild(textMajor);
this.frame.appendChild(measureCharMajor);
this.props.majorCharHeight = measureCharMajor.clientHeight;
this.props.majorCharWidth = measureCharMajor.clientWidth;
// determine the char width and height on the major axis
if (!this.dom.measureCharMajor) {
this.dom.measureCharMajor = document.createElement('DIV');
this.dom.measureCharMajor.className = 'text minor measure';
this.dom.measureCharMajor.style.position = 'absolute';
this.frame.removeChild(measureCharMajor);
this.dom.measureCharMajor.appendChild(document.createTextNode('0'));
this.frame.appendChild(this.dom.measureCharMajor);
} }
this.props.majorCharHeight = this.dom.measureCharMajor.clientHeight;
this.props.majorCharWidth = this.dom.measureCharMajor.clientWidth;
}; };
/** /**

+ 34
- 0
src/util.js View File

@ -97,6 +97,40 @@ util.extend = function (a, b) {
return a; return a;
}; };
/**
* Deep extend an object a with the properties of object b
* @param {Object} a
* @param {Object} b
* @returns {Object}
*/
util.deepExtend = function deepExtend (a, b) {
// TODO: add support for Arrays to deepExtend
if (Array.isArray(b)) {
throw new TypeError('Arrays are not supported by deepExtend');
}
for (var prop in b) {
if (b.hasOwnProperty(prop)) {
if (b[prop] && b[prop].constructor === Object) {
if (a[prop] === undefined) {
a[prop] = {};
}
if (a[prop].constructor === Object) {
deepExtend(a[prop], b[prop]);
}
else {
a[prop] = b[prop];
}
} else if (Array.isArray(b[prop])) {
throw new TypeError('Arrays are not supported by deepExtend');
} else {
a[prop] = b[prop];
}
}
}
return a;
};
/** /**
* Test whether all elements in two arrays are equal. * Test whether all elements in two arrays are equal.
* @param {Array} a * @param {Array} a

+ 4
- 2
test/timeline.html View File

@ -69,9 +69,11 @@
{_id: 1, content: 'item 1<br>start', start: now.clone().add('days', 4).toDate()}, {_id: 1, content: 'item 1<br>start', start: now.clone().add('days', 4).toDate()},
{_id: 2, content: 'item 2', start: now.clone().add('days', -2).toDate() }, {_id: 2, content: 'item 2', start: now.clone().add('days', -2).toDate() },
{_id: 3, content: 'item 3', start: now.clone().add('days', 2).toDate()}, {_id: 3, content: 'item 3', start: now.clone().add('days', 2).toDate()},
{_id: 4, content: 'item 4',
{
_id: 4, content: 'item 4',
start: now.clone().add('days', 0).toDate(), start: now.clone().add('days', 0).toDate(),
end: now.clone().add('days', 7).toDate()},
end: now.clone().add('days', 7).toDate()
},
{_id: 5, content: 'item 5', start: now.clone().add('days', 9).toDate(), type:'point'}, {_id: 5, content: 'item 5', start: now.clone().add('days', 9).toDate(), type:'point'},
{_id: 6, content: 'item 6', start: now.clone().add('days', 11).toDate()} {_id: 6, content: 'item 6', start: now.clone().add('days', 11).toDate()}
]); ]);

Loading…
Cancel
Save