From 18e1d1f2b474927fcd96ad09f174873a4aad23e0 Mon Sep 17 00:00:00 2001 From: Alex de Mulder Date: Tue, 19 Jan 2016 17:49:58 +0100 Subject: [PATCH 1/2] Fixed 1588, destroy now releases the dataset. --- HISTORY.md | 1 + dist/vis.js | 8 +++++++- lib/network/modules/EdgesHandler.js | 4 ++++ lib/network/modules/NodesHandler.js | 4 ++++ 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index d13c55f3..b5a5e5c8 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -12,6 +12,7 @@ http://visjs.org on the manipulation toolbar. - Fixed #1334 (again): Network now ignores scroll when interaction:zoomView is false. - Added options to customize the hierarchical layout without the use of physics. +- Fixed #1588: destroy now unsubscribed from the dataset. ### Graph2d diff --git a/dist/vis.js b/dist/vis.js index 11bfbc55..f4193f82 100644 --- a/dist/vis.js +++ b/dist/vis.js @@ -5,7 +5,7 @@ * A dynamic, browser-based visualization library. * * @version 4.12.1-SNAPSHOT - * @date 2016-01-18 + * @date 2016-01-19 * * @license * Copyright (C) 2011-2016 Almende B.V, http://almende.com @@ -28577,6 +28577,9 @@ return /******/ (function(modules) { // webpackBootstrap this.body.emitter.on('refreshNodes', this.refresh.bind(this)); this.body.emitter.on('refresh', this.refresh.bind(this)); this.body.emitter.on('destroy', function () { + util.forEach(_this2.nodesListeners, function (callback, event) { + if (_this2.body.data.nodes) _this2.body.data.nodes.off(event, callback); + }); delete _this2.body.functions.createNode; delete _this2.nodesListeners.add; delete _this2.nodesListeners.update; @@ -31517,6 +31520,9 @@ return /******/ (function(modules) { // webpackBootstrap this.body.emitter.on("refreshEdges", this.refresh.bind(this)); this.body.emitter.on("refresh", this.refresh.bind(this)); this.body.emitter.on("destroy", function () { + util.forEach(_this2.edgesListeners, function (callback, event) { + if (_this2.body.data.edges) _this2.body.data.edges.off(event, callback); + }); delete _this2.body.functions.createEdge; delete _this2.edgesListeners.add; delete _this2.edgesListeners.update; diff --git a/lib/network/modules/EdgesHandler.js b/lib/network/modules/EdgesHandler.js index f66fbe1e..4be26d95 100644 --- a/lib/network/modules/EdgesHandler.js +++ b/lib/network/modules/EdgesHandler.js @@ -141,6 +141,10 @@ class EdgesHandler { this.body.emitter.on("refreshEdges", this.refresh.bind(this)); this.body.emitter.on("refresh", this.refresh.bind(this)); this.body.emitter.on("destroy", () => { + util.forEach(this.edgesListeners, (callback, event) => { + if (this.body.data.edges) + this.body.data.edges.off(event, callback); + }); delete this.body.functions.createEdge; delete this.edgesListeners.add; delete this.edgesListeners.update; diff --git a/lib/network/modules/NodesHandler.js b/lib/network/modules/NodesHandler.js index cbf0450a..f7f6d650 100644 --- a/lib/network/modules/NodesHandler.js +++ b/lib/network/modules/NodesHandler.js @@ -115,6 +115,10 @@ class NodesHandler { this.body.emitter.on('refreshNodes', this.refresh.bind(this)); this.body.emitter.on('refresh', this.refresh.bind(this)); this.body.emitter.on('destroy', () => { + util.forEach(this.nodesListeners, (callback, event) => { + if (this.body.data.nodes) + this.body.data.nodes.off(event, callback); + }); delete this.body.functions.createNode; delete this.nodesListeners.add; delete this.nodesListeners.update; From bbf04d3489683cf2938e6870d6e307d5c6f945c8 Mon Sep 17 00:00:00 2001 From: Ludo Stellingwerff Date: Wed, 20 Jan 2016 12:45:30 +0100 Subject: [PATCH 2/2] Fixed #1403: Graph2d change yAxisOrientation, Cleanup of linegraph's event handling Fixed #1557: Make sure we don't default to scientific notation. Couple of minor fixes in group counts and ranges. --- lib/timeline/Core.js | 30 +++-- lib/timeline/DataStep.js | 4 + lib/timeline/Graph2d.js | 7 +- lib/timeline/component/DataAxis.js | 7 +- lib/timeline/component/LineGraph.js | 189 ++++++++++++++-------------- 5 files changed, 130 insertions(+), 107 deletions(-) diff --git a/lib/timeline/Core.js b/lib/timeline/Core.js index 4436ae7c..b1257122 100644 --- a/lib/timeline/Core.js +++ b/lib/timeline/Core.js @@ -91,7 +91,9 @@ Core.prototype._create = function (container) { this.dom.rightContainer.appendChild(this.dom.shadowBottomRight); this.on('rangechange', function () { - this._redraw(); // this allows overriding the _redraw method + if (this.initialDrawDone) { + this._redraw(); // this allows overriding the _redraw method + } }.bind(this)); this.on('touch', this._onTouch.bind(this)); this.on('pan', this._onDrag.bind(this)); @@ -181,6 +183,7 @@ Core.prototype._create = function (container) { this.touch = {}; this.redrawCount = 0; + this.initialDrawDone = false; // attach the root panel to the provided container if (!container) throw new Error('No container provided'); @@ -317,11 +320,12 @@ Core.prototype.setOptions = function (options) { // override redraw with a throttled version if (!this._origRedraw) { this._origRedraw = this._redraw.bind(this); + this._redraw = util.throttle(this._origRedraw, this.options.throttleRedraw); + } else { + // Not the initial run: redraw everything + this._redraw(); } - this._redraw = util.throttle(this._origRedraw, this.options.throttleRedraw); - // redraw everything - this._redraw(); }; /** @@ -621,6 +625,7 @@ Core.prototype.redraw = function() { * @protected */ Core.prototype._redraw = function() { + this.redrawCount++; var resized = false; var options = this.options; var props = this.props; @@ -766,18 +771,19 @@ Core.prototype._redraw = function() { this.components.forEach(function (component) { resized = component.redraw() || resized; }); + var MAX_REDRAW = 5; if (resized) { - // keep repainting until all sizes are settled - var MAX_REDRAWS = 3; // maximum number of consecutive redraws - if (this.redrawCount < MAX_REDRAWS) { - this.redrawCount++; - this._redraw(); + if (this.redrawCount < MAX_REDRAW) { + this.body.emitter.emit('change'); + return; } else { console.log('WARNING: infinite loop in redraw?'); } + } else { this.redrawCount = 0; } + this.initialDrawDone = true; }; // TODO: deprecated since version 1.1.0, remove some day @@ -915,6 +921,12 @@ Core.prototype._startAutoResize = function () { // add event listener to window resize util.addEventListener(window, 'resize', this._onResize); + //Prevent initial unnecessary redraw + if (me.dom.root) { + me.props.lastWidth = me.dom.root.offsetWidth; + me.props.lastHeight = me.dom.root.offsetHeight; + } + this.watchTimer = setInterval(this._onResize, 1000); }; diff --git a/lib/timeline/DataStep.js b/lib/timeline/DataStep.js index 96453387..d533eb43 100644 --- a/lib/timeline/DataStep.js +++ b/lib/timeline/DataStep.js @@ -59,6 +59,10 @@ function DataStep(start, end, minimumStep, containerHeight, customRange, formatt * @param {Number} [minimumStep] Optional. Minimum step size in milliseconds */ DataStep.prototype.setRange = function(start, end, minimumStep, containerHeight, customRange) { + if (customRange === undefined) { + customRange = {}; + } + this._start = customRange.min === undefined ? start : customRange.min; this._end = customRange.max === undefined ? end : customRange.max; if (this._start === this._end) { diff --git a/lib/timeline/Graph2d.js b/lib/timeline/Graph2d.js index cae83d24..9bca8f61 100644 --- a/lib/timeline/Graph2d.js +++ b/lib/timeline/Graph2d.js @@ -93,11 +93,13 @@ function Graph2d (container, items, groups, options) { // item set this.linegraph = new LineGraph(this.body); + this.components.push(this.linegraph); this.itemsData = null; // DataSet this.groupsData = null; // DataSet + this.on('tap', function (event) { me.emit('click', me.getEventProperties(event)) }); @@ -122,9 +124,7 @@ function Graph2d (container, items, groups, options) { if (items) { this.setItems(items); } - else { - this._redraw(); - } + this._redraw(); } // Extend the functionality from Core @@ -173,7 +173,6 @@ Graph2d.prototype.setItems = function(items) { if (this.options.start != undefined || this.options.end != undefined) { var start = this.options.start != undefined ? this.options.start : null; var end = this.options.end != undefined ? this.options.end : null; - this.setWindow(start, end, {animation: false}); } else { diff --git a/lib/timeline/component/DataAxis.js b/lib/timeline/component/DataAxis.js index 269a3f7d..bac4559d 100644 --- a/lib/timeline/component/DataAxis.js +++ b/lib/timeline/component/DataAxis.js @@ -30,12 +30,12 @@ function DataAxis (body, options, svg, linegraphOptions) { alignZeros: true, left:{ range: {min:undefined,max:undefined}, - format: function (value) {return ''+value.toPrecision(3);}, + format: function (value) {return ''+Number.parseFloat(value.toPrecision(3));}, title: {text:undefined,style:undefined} }, right:{ range: {min:undefined,max:undefined}, - format: function (value) {return ''+value.toPrecision(3);}, + format: function (value) {return ''+Number.parseFloat(value.toPrecision(3));}, title: {text:undefined,style:undefined} } }; @@ -96,6 +96,9 @@ DataAxis.prototype.addGroup = function(label, graphOptions) { }; DataAxis.prototype.updateGroup = function(label, graphOptions) { + if (!this.groups.hasOwnProperty(label)) { + this.amountOfGroups += 1; + } this.groups[label] = graphOptions; }; diff --git a/lib/timeline/component/LineGraph.js b/lib/timeline/component/LineGraph.js index 2ea127d5..aadfe7d2 100644 --- a/lib/timeline/component/LineGraph.js +++ b/lib/timeline/component/LineGraph.js @@ -66,6 +66,7 @@ function LineGraph(body, options) { this.abortedGraphUpdate = false; this.updateSVGheight = false; this.updateSVGheightOnResize = false; + this.forceGraphUpdate = true; var me = this; this.itemsData = null; // DataSet @@ -105,18 +106,18 @@ function LineGraph(body, options) { this.svgElements = {}; this.setOptions(options); this.groupsUsingDefaultStyles = [0]; - this.COUNTER = 0; this.body.emitter.on('rangechanged', function () { me.lastStart = me.body.range.start; me.svg.style.left = util.option.asSize(-me.props.width); - me.redraw.call(me, true); + + me.forceGraphUpdate = true; + //Is this local redraw necessary? (Core also does a change event!) + me.redraw.call(me); }); // create the HTML DOM this._create(); this.framework = {svg: this.svg, svgElements: this.svgElements, options: this.options, groups: this.groups}; - this.body.emitter.emit('change'); - } LineGraph.prototype = new Component(); @@ -158,7 +159,7 @@ LineGraph.prototype._create = function () { LineGraph.prototype.setOptions = function (options) { if (options) { var fields = ['sampling', 'defaultGroup', 'stack', 'height', 'graphHeight', 'yAxisOrientation', 'style', 'barChart', 'dataAxis', 'sort', 'groups']; - if (options.graphHeight === undefined && options.height !== undefined && this.body.domProps.centerContainer.height !== undefined) { + if (options.graphHeight === undefined && options.height !== undefined) { this.updateSVGheight = true; this.updateSVGheightOnResize = true; } @@ -210,8 +211,9 @@ LineGraph.prototype.setOptions = function (options) { } // this is used to redraw the graph if the visibility of the groups is changed. - if (this.dom.frame) { - this.redraw(true); + if (this.dom.frame) { //not on initial run? + this.forceGraphUpdate=true; + this.body.emitter.emit("change"); } }; @@ -331,7 +333,6 @@ LineGraph.prototype.setGroups = function (groups) { LineGraph.prototype._onUpdate = function (ids) { this._updateAllGroupData(); - this.redraw(true); }; LineGraph.prototype._onAdd = function (ids) { this._onUpdate(ids); @@ -341,7 +342,6 @@ LineGraph.prototype._onRemove = function (ids) { }; LineGraph.prototype._onUpdateGroups = function (groupIds) { this._updateAllGroupData(); - this.redraw(true); }; LineGraph.prototype._onAddGroups = function (groupIds) { this._onUpdateGroups(groupIds); @@ -356,7 +356,8 @@ LineGraph.prototype._onRemoveGroups = function (groupIds) { for (var i = 0; i < groupIds.length; i++) { this._removeGroup(groupIds[i]); } - this.redraw(true); + this.forceGraphUpdate = true; + this.body.emitter.emit("change"); }; /** @@ -404,10 +405,16 @@ LineGraph.prototype._updateGroup = function (group, groupId) { if (this.groups[groupId].options.yAxisOrientation == 'right') { this.yAxisRight.updateGroup(groupId, this.groups[groupId]); this.legendRight.updateGroup(groupId, this.groups[groupId]); + //If yAxisOrientation changed, clean out the group from the other axis. + this.yAxisLeft.removeGroup(groupId); + this.legendLeft.removeGroup(groupId); } else { this.yAxisLeft.updateGroup(groupId, this.groups[groupId]); this.legendLeft.updateGroup(groupId, this.groups[groupId]); + //If yAxisOrientation changed, clean out the group from the other axis. + this.yAxisRight.removeGroup(groupId); + this.legendRight.removeGroup(groupId); } } this.legendLeft.redraw(); @@ -484,6 +491,8 @@ LineGraph.prototype._updateAllGroupData = function () { } } } + this.forceGraphUpdate = true; + this.body.emitter.emit("change"); } }; @@ -491,7 +500,7 @@ LineGraph.prototype._updateAllGroupData = function () { * Redraw the component, mandatory function * @return {boolean} Returns true if the component is resized */ -LineGraph.prototype.redraw = function (forceGraphUpdate) { +LineGraph.prototype.redraw = function () { var resized = false; // calculate actual size and position @@ -500,9 +509,9 @@ LineGraph.prototype.redraw = function (forceGraphUpdate) { - this.body.domProps.border.top - this.body.domProps.border.bottom; - // update the graph if there is no lastWidth or with, used for the initial draw + // update the graph if there is no lastWidth or width, used for the initial draw if (this.lastWidth === undefined && this.props.width) { - forceGraphUpdate = true; + this.forceGraphUpdate = true; } // check if this component is resized @@ -539,8 +548,9 @@ LineGraph.prototype.redraw = function (forceGraphUpdate) { } // zoomed is here to ensure that animations are shown correctly. - if (resized == true || zoomed == true || this.abortedGraphUpdate == true || forceGraphUpdate == true) { + if (resized == true || zoomed == true || this.abortedGraphUpdate == true || this.forceGraphUpdate == true) { resized = this._updateGraph() || resized; + this.forceGraphUpdate = false; } else { // move the whole svg while dragging @@ -554,7 +564,6 @@ LineGraph.prototype.redraw = function (forceGraphUpdate) { } } } - this.legendLeft.redraw(); this.legendRight.redraw(); return resized; @@ -621,101 +630,97 @@ LineGraph.prototype._updateGraph = function () { this._getYRanges(groupIds, groupsData, groupRanges); // update the Y axis first, we use this data to draw at the correct Y points - // changeCalled is required to clean the SVG on a change emit. changeCalled = this._updateYAxis(groupIds, groupRanges); - var MAX_CYCLES = 5; - if (changeCalled == true && this.COUNTER < MAX_CYCLES) { + + // at changeCalled, abort this update cycle as the graph needs another update with new Width input from the Redraw container. + // Cleanup SVG elements on abort. + if (changeCalled == true) { DOMutil.cleanupElements(this.svgElements); this.abortedGraphUpdate = true; - this.COUNTER++; - this.body.emitter.emit('change'); return true; } - else { - if (this.COUNTER > MAX_CYCLES) { - console.log("WARNING: there may be an infinite loop in the _updateGraph emitter cycle."); - } - this.COUNTER = 0; - this.abortedGraphUpdate = false; - - // With the yAxis scaled correctly, use this to get the Y values of the points. - var below = undefined; - for (i = 0; i < groupIds.length; i++) { - group = this.groups[groupIds[i]]; - if (this.options.stack === true && this.options.style === 'line') { - if (group.options.excludeFromStacking == undefined || !group.options.excludeFromStacking) { - if (below != undefined) { - this._stack(groupsData[group.id], groupsData[below.id]); - if (group.options.shaded.enabled == true && group.options.shaded.orientation !== "group"){ - if (group.options.shaded.orientation == "top" && below.options.shaded.orientation !== "group"){ - below.options.shaded.orientation="group"; - below.options.shaded.groupId=group.id; - } else { - group.options.shaded.orientation="group"; - group.options.shaded.groupId=below.id; - } + this.abortedGraphUpdate = false; + + // With the yAxis scaled correctly, use this to get the Y values of the points. + var below = undefined; + for (i = 0; i < groupIds.length; i++) { + group = this.groups[groupIds[i]]; + if (this.options.stack === true && this.options.style === 'line') { + if (group.options.excludeFromStacking == undefined || !group.options.excludeFromStacking) { + if (below != undefined) { + this._stack(groupsData[group.id], groupsData[below.id]); + if (group.options.shaded.enabled == true && group.options.shaded.orientation !== "group"){ + if (group.options.shaded.orientation == "top" && below.options.shaded.orientation !== "group"){ + below.options.shaded.orientation="group"; + below.options.shaded.groupId=group.id; + } else { + group.options.shaded.orientation="group"; + group.options.shaded.groupId=below.id; } } - below = group; } + below = group; } - this._convertYcoordinates(groupsData[groupIds[i]], group); } + this._convertYcoordinates(groupsData[groupIds[i]], group); + } - //Precalculate paths and draw shading if appropriate. This will make sure the shading is always behind any lines. - var paths = {}; - for (i = 0; i < groupIds.length; i++) { - group = this.groups[groupIds[i]]; - if (group.options.style === 'line' && group.options.shaded.enabled == true) { - var dataset = groupsData[groupIds[i]]; - if (!paths.hasOwnProperty(groupIds[i])) { - paths[groupIds[i]] = Lines.calcPath(dataset, group); - } - if (group.options.shaded.orientation === "group") { - var subGroupId = group.options.shaded.groupId; - if (groupIds.indexOf(subGroupId) === -1) { - console.log(group.id + ": Unknown shading group target given:" + subGroupId); - continue; - } - if (!paths.hasOwnProperty(subGroupId)) { - paths[subGroupId] = Lines.calcPath(groupsData[subGroupId], this.groups[subGroupId]); - } - Lines.drawShading(paths[groupIds[i]], group, paths[subGroupId], this.framework); + //Precalculate paths and draw shading if appropriate. This will make sure the shading is always behind any lines. + var paths = {}; + for (i = 0; i < groupIds.length; i++) { + group = this.groups[groupIds[i]]; + if (group.options.style === 'line' && group.options.shaded.enabled == true) { + var dataset = groupsData[groupIds[i]]; + if (dataset == null || dataset.length == 0) { + continue; + } + if (!paths.hasOwnProperty(groupIds[i])) { + paths[groupIds[i]] = Lines.calcPath(dataset, group); + } + if (group.options.shaded.orientation === "group") { + var subGroupId = group.options.shaded.groupId; + if (groupIds.indexOf(subGroupId) === -1) { + console.log(group.id + ": Unknown shading group target given:" + subGroupId); + continue; } - else { - Lines.drawShading(paths[groupIds[i]], group, undefined, this.framework); + if (!paths.hasOwnProperty(subGroupId)) { + paths[subGroupId] = Lines.calcPath(groupsData[subGroupId], this.groups[subGroupId]); } + Lines.drawShading(paths[groupIds[i]], group, paths[subGroupId], this.framework); + } + else { + Lines.drawShading(paths[groupIds[i]], group, undefined, this.framework); } } + } - // draw the groups, calculating paths if still necessary. - Bars.draw(groupIds, groupsData, this.framework); - for (i = 0; i < groupIds.length; i++) { - group = this.groups[groupIds[i]]; - if (groupsData[groupIds[i]].length > 0) { - switch (group.options.style) { - case "line": - if (!paths.hasOwnProperty(groupIds[i])) { - paths[groupIds[i]] = Lines.calcPath(groupsData[groupIds[i]], group); - } - Lines.draw(paths[groupIds[i]], group, this.framework); - //explicit no break; - case "point": - //explicit no break; - case "points": - if (group.options.style == "point" || group.options.style == "points" || group.options.drawPoints.enabled == true) { - Points.draw(groupsData[groupIds[i]], group, this.framework); - } - break; - case "bar": - // bar needs to be drawn enmasse - //explicit no break - default: - //do nothing... - } + // draw the groups, calculating paths if still necessary. + Bars.draw(groupIds, groupsData, this.framework); + for (i = 0; i < groupIds.length; i++) { + group = this.groups[groupIds[i]]; + if (groupsData[groupIds[i]].length > 0) { + switch (group.options.style) { + case "line": + if (!paths.hasOwnProperty(groupIds[i])) { + paths[groupIds[i]] = Lines.calcPath(groupsData[groupIds[i]], group); + } + Lines.draw(paths[groupIds[i]], group, this.framework); + //explicit no break; + case "point": + //explicit no break; + case "points": + if (group.options.style == "point" || group.options.style == "points" || group.options.drawPoints.enabled == true) { + Points.draw(groupsData[groupIds[i]], group, this.framework); + } + break; + case "bar": + // bar needs to be drawn enmasse + //explicit no break + default: + //do nothing... } - } + } } }