diff --git a/lib/network/modules/Canvas.js b/lib/network/modules/Canvas.js index e0f15abb..c378c183 100644 --- a/lib/network/modules/Canvas.js +++ b/lib/network/modules/Canvas.js @@ -18,6 +18,7 @@ class Canvas { this.resizeFunction = this._onResize.bind(this); this.cameraState = {}; this.initialized = false; + this.canvasViewCenter = {}; this.options = {}; this.defaultOptions = { @@ -284,6 +285,11 @@ class Canvas { this.options.width = width; this.options.height = height; + this.canvasViewCenter = { + x: 0.5 * this.frame.clientWidth, + y: 0.5 * this.frame.clientHeight + }; + emitEvent = true; } else { diff --git a/lib/network/modules/Clustering.js b/lib/network/modules/Clustering.js index f67d0da1..3de5198e 100644 --- a/lib/network/modules/Clustering.js +++ b/lib/network/modules/Clustering.js @@ -235,7 +235,19 @@ class ClusterEngine { } } } - + var childNodeIDs = Object.keys(childNodesObj).map(function(childNode){ + return childNodesObj[childNode].id; + }) + + for (childNode in childNodesObj) { + var childNode = childNodesObj[childNode]; + for (var y=0; y < childNode.edges.length; y++){ + var childEdge = childNode.edges[y]; + if (childNodeIDs.indexOf(this._getConnectedId(childEdge,childNode.id)) > -1){ + childEdgesObj[childEdge.id] = childEdge; + } + } + } this._cluster(childNodesObj, childEdgesObj, options, refreshData); } @@ -723,7 +735,7 @@ class ClusterEngine { let allEdgeIds = this.getClusteredEdges(startEdgeId); for (let i = 0; i < allEdgeIds.length; i++) { var edge = this.body.edges[allEdgeIds[i]]; - edge.setOptions(newOptions); + edge.setOptions(newOptions); } this.body.emitter.emit('_dataChanged'); } diff --git a/lib/network/modules/components/NavigationHandler.js b/lib/network/modules/components/NavigationHandler.js index 2b081a14..14347814 100644 --- a/lib/network/modules/components/NavigationHandler.js +++ b/lib/network/modules/components/NavigationHandler.js @@ -150,12 +150,29 @@ class NavigationHandler { _moveDown() {this.body.view.translation.y -= this.options.keyboard.speed.y;} _moveLeft() {this.body.view.translation.x += this.options.keyboard.speed.x;} _moveRight(){this.body.view.translation.x -= this.options.keyboard.speed.x;} - _zoomIn() { - this.body.view.scale *= 1+this.options.keyboard.speed.zoom; - this.body.emitter.emit('zoom', {direction: '+', scale: this.body.view.scale});} + _zoomIn() { + var scaleOld = this.body.view.scale; + var scale = this.body.view.scale * (1 + this.options.keyboard.speed.zoom); + var translation = this.body.view.translation; + var scaleFrac = scale / scaleOld; + var tx = (1 - scaleFrac) * this.canvas.canvasViewCenter.x + translation.x * scaleFrac; + var ty = (1 - scaleFrac) * this.canvas.canvasViewCenter.y + translation.y * scaleFrac; + + this.body.view.scale = scale; + this.body.view.translation = { x: tx, y: ty }; + this.body.emitter.emit('zoom', { direction: '+', scale: this.body.view.scale }); + } _zoomOut() { - this.body.view.scale /= 1+this.options.keyboard.speed.zoom; - this.body.emitter.emit('zoom', {direction: '-', scale: this.body.view.scale}); + var scaleOld = this.body.view.scale; + var scale = this.body.view.scale / (1 + this.options.keyboard.speed.zoom); + var translation = this.body.view.translation; + var scaleFrac = scale / scaleOld; + var tx = (1 - scaleFrac) * this.canvas.canvasViewCenter.x + translation.x * scaleFrac; + var ty = (1 - scaleFrac) * this.canvas.canvasViewCenter.y + translation.y * scaleFrac; + + this.body.view.scale = scale; + this.body.view.translation = { x: tx, y: ty }; + this.body.emitter.emit('zoom', { direction: '-', scale: this.body.view.scale }); } diff --git a/lib/timeline/Core.js b/lib/timeline/Core.js index 04c51ad6..1b4f1291 100644 --- a/lib/timeline/Core.js +++ b/lib/timeline/Core.js @@ -1146,4 +1146,4 @@ Core.prototype._createConfigurator = function () { throw new Error('Cannot invoke abstract method _createConfigurator'); }; -module.exports = Core; +module.exports = Core; \ No newline at end of file diff --git a/lib/timeline/Timeline.js b/lib/timeline/Timeline.js index 05da12ac..cc44f3b0 100644 --- a/lib/timeline/Timeline.js +++ b/lib/timeline/Timeline.js @@ -28,8 +28,8 @@ import Validator from '../shared/Validator'; * @constructor * @extends Core */ - function Timeline (container, items, groups, options) { + if (!(this instanceof Timeline)) { throw new SyntaxError('Constructor must be called with the new operator'); } @@ -45,22 +45,21 @@ function Timeline (container, items, groups, options) { this.defaultOptions = { start: null, end: null, - autoResize: true, - orientation: { axis: 'bottom', // axis orientation: 'bottom', 'top', or 'both' item: 'bottom' // not relevant }, moment: moment, - width: null, height: null, maxHeight: null, minHeight: null }; this.options = util.deepExtend({}, this.defaultOptions); - + if (options) { + this.options.rtl = options.rtl + } // Create the DOM, props, and emitter this._create(container); @@ -104,11 +103,6 @@ function Timeline (container, items, groups, options) { // current time bar this.currentTime = new CurrentTime(this.body); this.components.push(this.currentTime); - - // apply options - if (options) { - this.setOptions(options); - } // item set this.itemSet = new ItemSet(this.body, this.options); @@ -149,6 +143,11 @@ function Timeline (container, items, groups, options) { } }); + // apply options + if (options) { + this.setOptions(options); + } + // IMPORTANT: THIS HAPPENS BEFORE SET ITEMS! if (groups) { this.setGroups(groups); @@ -194,7 +193,6 @@ Timeline.prototype.setOptions = function (options) { if (errorFound === true) { console.log('%cErrors have been found in the supplied options object.', printStyle); } - Core.prototype.setOptions.call(this, options); if ('type' in options) { diff --git a/lib/timeline/component/ItemSet.js b/lib/timeline/component/ItemSet.js index 9eb46393..f2020b09 100644 --- a/lib/timeline/component/ItemSet.js +++ b/lib/timeline/component/ItemSet.js @@ -95,7 +95,9 @@ function ItemSet(body, options) { // options is shared by this ItemSet and all its items this.options = util.extend({}, this.defaultOptions); - this.options.rtl = options.rtl; + if (options) { + this.options.rtl = options.rtl; // required to determine from the initial creation if rtl + } // options for getting items from the DataSet with the correct type this.itemOptions = { @@ -318,7 +320,7 @@ ItemSet.prototype.setOptions = function(options) { // copy all options that we know var fields = ['type', 'rtl', 'align', 'order', 'stack', 'selectable', 'multiselect', 'itemsAlwaysDraggable', 'multiselectPerGroup', 'groupOrder', 'dataAttributes', 'template', 'groupTemplate', 'hide', 'snap', 'groupOrderSwap']; util.selectiveExtend(fields, this.options, options); - + if ('orientation' in options) { if (typeof options.orientation === 'string') { this.options.orientation.item = options.orientation === 'top' ? 'top' : 'bottom'; diff --git a/lib/timeline/component/item/Item.js b/lib/timeline/component/item/Item.js index c12a3203..c1762805 100644 --- a/lib/timeline/component/item/Item.js +++ b/lib/timeline/component/item/Item.js @@ -197,7 +197,7 @@ Item.prototype._updateContents = function (element) { content = this.data.content; } - if (content instanceof Object) { + if ((content instanceof Object) && !(content instanceof Element)) { templateFunction(itemData, element) } else { var changed = this._contentToString(this.content) !== this._contentToString(content); diff --git a/test/DataView.test.js b/test/DataView.test.js index 4d3c5f97..11d481c3 100644 --- a/test/DataView.test.js +++ b/test/DataView.test.js @@ -200,7 +200,8 @@ describe('DataView', function () { }] ]); assert.deepEqual(viewUpdates, [ - ['update', {items: [2], data: [{id: 2, title: 'Item 2 (changed)'}]}] + ['update', {items: [2], data: [{id: 2, title: 'Item 2 (changed)'}], + oldData: [{"group": 2, "id": 2, "title": "Item 2"}]}] ]); });