From e96d228ab84144c76f2865709c4259f9eb3eca9e Mon Sep 17 00:00:00 2001 From: Alex de Mulder Date: Tue, 2 Jun 2015 12:35:23 +0200 Subject: [PATCH] Added releaseFunction to openCluster. --- HISTORY.md | 1 + dist/vis.js | 10335 ++++++++-------- docs/network/index.html | 17 +- examples/network/other/clustering.html | 2 +- lib/network/modules/Clustering.js | 42 +- lib/network/modules/components/Node.js | 2 +- .../components/edges/BezierEdgeDynamic.js | 3 +- .../components/physics/BarnesHutSolver.js | 12 +- test/networkTest.html | 74 +- 9 files changed, 5290 insertions(+), 5198 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index c2aa21d8..7c3d9ec9 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -20,6 +20,7 @@ http://visjs.org - Fixed #904, correctly parsing global font options now. - Fixed dataView support for storePositions. - Second click on node is no longer unselect. +- Added releaseFunction to openCluster. ## 2015-05-28, version 4.1.0 diff --git a/dist/vis.js b/dist/vis.js index 6a312236..990bbd4b 100644 --- a/dist/vis.js +++ b/dist/vis.js @@ -84,60 +84,60 @@ return /******/ (function(modules) { // webpackBootstrap // utils 'use strict'; - exports.util = __webpack_require__(2); - exports.DOMutil = __webpack_require__(11); + exports.util = __webpack_require__(3); + exports.DOMutil = __webpack_require__(15); // data - exports.DataSet = __webpack_require__(12); - exports.DataView = __webpack_require__(14); - exports.Queue = __webpack_require__(13); + exports.DataSet = __webpack_require__(16); + exports.DataView = __webpack_require__(18); + exports.Queue = __webpack_require__(17); // Graph3d - exports.Graph3d = __webpack_require__(15); + exports.Graph3d = __webpack_require__(19); exports.graph3d = { - Camera: __webpack_require__(19), - Filter: __webpack_require__(20), - Point2d: __webpack_require__(16), - Point3d: __webpack_require__(18), - Slider: __webpack_require__(21), - StepNumber: __webpack_require__(22) + Camera: __webpack_require__(23), + Filter: __webpack_require__(24), + Point2d: __webpack_require__(20), + Point3d: __webpack_require__(22), + Slider: __webpack_require__(25), + StepNumber: __webpack_require__(26) }; // Timeline - exports.Timeline = __webpack_require__(23); - exports.Graph2d = __webpack_require__(53); + exports.Timeline = __webpack_require__(27); + exports.Graph2d = __webpack_require__(57); exports.timeline = { - DateUtil: __webpack_require__(33), - DataStep: __webpack_require__(56), - Range: __webpack_require__(31), - stack: __webpack_require__(37), - TimeStep: __webpack_require__(40), + DateUtil: __webpack_require__(37), + DataStep: __webpack_require__(60), + Range: __webpack_require__(35), + stack: __webpack_require__(41), + TimeStep: __webpack_require__(44), components: { items: { - Item: __webpack_require__(39), - BackgroundItem: __webpack_require__(44), - BoxItem: __webpack_require__(42), - PointItem: __webpack_require__(43), - RangeItem: __webpack_require__(38) + Item: __webpack_require__(43), + BackgroundItem: __webpack_require__(48), + BoxItem: __webpack_require__(46), + PointItem: __webpack_require__(47), + RangeItem: __webpack_require__(42) }, - Component: __webpack_require__(25), - CurrentTime: __webpack_require__(24), - CustomTime: __webpack_require__(48), - DataAxis: __webpack_require__(55), - GraphGroup: __webpack_require__(57), - Group: __webpack_require__(36), - BackgroundGroup: __webpack_require__(41), - ItemSet: __webpack_require__(35), - Legend: __webpack_require__(61), - LineGraph: __webpack_require__(54), - TimeAxis: __webpack_require__(45) + Component: __webpack_require__(29), + CurrentTime: __webpack_require__(28), + CustomTime: __webpack_require__(52), + DataAxis: __webpack_require__(59), + GraphGroup: __webpack_require__(61), + Group: __webpack_require__(40), + BackgroundGroup: __webpack_require__(45), + ItemSet: __webpack_require__(39), + Legend: __webpack_require__(65), + LineGraph: __webpack_require__(58), + TimeAxis: __webpack_require__(49) } }; // Network - exports.Network = __webpack_require__(63); + exports.Network = __webpack_require__(5); exports.network = { Images: __webpack_require__(112), dotparser: __webpack_require__(110), @@ -157,9 +157,9 @@ return /******/ (function(modules) { // webpackBootstrap }; // bundled external libraries - exports.moment = __webpack_require__(7); - exports.hammer = __webpack_require__(27); // TODO: deprecate exports.hammer some day - exports.Hammer = __webpack_require__(27); + exports.moment = __webpack_require__(11); + exports.hammer = __webpack_require__(31); // TODO: deprecate exports.hammer some day + exports.Hammer = __webpack_require__(31); /***/ }, /* 1 */ @@ -176,6 +176,171 @@ return /******/ (function(modules) { // webpackBootstrap /***/ }, /* 2 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, '__esModule', { + value: true + }); + + var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + + var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + + function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } + + var _utilBezierEdgeBase = __webpack_require__(88); + + var _utilBezierEdgeBase2 = _interopRequireDefault(_utilBezierEdgeBase); + + var BezierEdgeDynamic = (function (_BezierEdgeBase) { + function BezierEdgeDynamic(options, body, labelModule) { + _classCallCheck(this, BezierEdgeDynamic); + + //this.via = undefined; // Here for completeness but not allowed to defined before super() is invoked. + _get(Object.getPrototypeOf(BezierEdgeDynamic.prototype), 'constructor', this).call(this, options, body, labelModule); // --> this calls the setOptions below + } + + _inherits(BezierEdgeDynamic, _BezierEdgeBase); + + _createClass(BezierEdgeDynamic, [{ + key: 'setOptions', + value: function setOptions(options) { + this.options = options; + this.id = this.options.id; + this.setupSupportNode(); + this.connect(); + } + }, { + key: 'connect', + value: function connect() { + this.from = this.body.nodes[this.options.from]; + this.to = this.body.nodes[this.options.to]; + if (this.from === undefined || this.to === undefined) { + this.via.setOptions({ physics: false }); + } else { + // fix weird behaviour where a selfreferencing node has physics enabled + if (this.from.id === this.to.id) { + this.via.setOptions({ physics: false }); + } else { + this.via.setOptions({ physics: true }); + } + } + } + }, { + key: 'cleanup', + value: function cleanup() { + if (this.via !== undefined) { + delete this.body.nodes[this.via.id]; + this.via = undefined; + return true; + } + return false; + } + }, { + key: 'togglePhysics', + value: function togglePhysics(status) { + this.via.setOptions({ physics: status }); + this.positionBezierNode(); + } + }, { + key: 'setupSupportNode', + + /** + * Bezier curves require an anchor point to calculate the smooth flow. These points are nodes. These nodes are invisible but + * are used for the force calculation. + * + * The changed data is not called, if needed, it is returned by the main edge constructor. + * @private + */ + value: function setupSupportNode() { + if (this.via === undefined) { + var nodeId = 'edgeId:' + this.id; + var node = this.body.functions.createNode({ + id: nodeId, + shape: 'circle', + physics: true, + hidden: true + }); + this.body.nodes[nodeId] = node; + this.via = node; + this.via.parentEdgeId = this.id; + this.positionBezierNode(); + } + } + }, { + key: 'positionBezierNode', + value: function positionBezierNode() { + if (this.via !== undefined && this.from !== undefined && this.to !== undefined) { + this.via.x = 0.5 * (this.from.x + this.to.x); + this.via.y = 0.5 * (this.from.y + this.to.y); + } else if (this.via !== undefined) { + this.via.x = 0; + this.via.y = 0; + } + } + }, { + key: '_line', + + /** + * Draw a line between two nodes + * @param {CanvasRenderingContext2D} ctx + * @private + */ + value: function _line(ctx) { + // draw a straight line + ctx.beginPath(); + ctx.moveTo(this.from.x, this.from.y); + ctx.quadraticCurveTo(this.via.x, this.via.y, this.to.x, this.to.y); + // draw shadow if enabled + this.enableShadow(ctx); + ctx.stroke(); + this.disableShadow(ctx); + return this.via; + } + }, { + key: 'getPoint', + + /** + * Combined function of pointOnLine and pointOnBezier. This gives the coordinates of a point on the line at a certain percentage of the way + * @param percentage + * @param via + * @returns {{x: number, y: number}} + * @private + */ + value: function getPoint(percentage) { + var t = percentage; + var x = Math.pow(1 - t, 2) * this.from.x + 2 * t * (1 - t) * this.via.x + Math.pow(t, 2) * this.to.x; + var y = Math.pow(1 - t, 2) * this.from.y + 2 * t * (1 - t) * this.via.y + Math.pow(t, 2) * this.to.y; + + return { x: x, y: y }; + } + }, { + key: '_findBorderPosition', + value: function _findBorderPosition(nearNode, ctx) { + return this._findBorderPositionBezier(nearNode, ctx, this.via); + } + }, { + key: '_getDistanceToEdge', + value: function _getDistanceToEdge(x1, y1, x2, y2, x3, y3) { + // x3,y3 is the point + return this._getDistanceToBezierEdge(x1, y1, x2, y2, x3, y3, this.via); + } + }]); + + return BezierEdgeDynamic; + })(_utilBezierEdgeBase2['default']); + + exports['default'] = BezierEdgeDynamic; + module.exports = exports['default']; + +/***/ }, +/* 3 */ /***/ function(module, exports, __webpack_require__) { // utility functions @@ -185,8 +350,8 @@ return /******/ (function(modules) { // webpackBootstrap 'use strict'; - var moment = __webpack_require__(7); - var uuid = __webpack_require__(10); + var moment = __webpack_require__(11); + var uuid = __webpack_require__(14); /** * Test whether given object is a number @@ -1521,7 +1686,7 @@ return /******/ (function(modules) { // webpackBootstrap }; /***/ }, -/* 3 */ +/* 4 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -1536,39 +1701,39 @@ return /******/ (function(modules) { // webpackBootstrap function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - var _componentsPhysicsBarnesHutSolver = __webpack_require__(90); + var _componentsPhysicsBarnesHutSolver = __webpack_require__(8); var _componentsPhysicsBarnesHutSolver2 = _interopRequireDefault(_componentsPhysicsBarnesHutSolver); - var _componentsPhysicsRepulsionSolver = __webpack_require__(91); + var _componentsPhysicsRepulsionSolver = __webpack_require__(92); var _componentsPhysicsRepulsionSolver2 = _interopRequireDefault(_componentsPhysicsRepulsionSolver); - var _componentsPhysicsHierarchicalRepulsionSolver = __webpack_require__(92); + var _componentsPhysicsHierarchicalRepulsionSolver = __webpack_require__(93); var _componentsPhysicsHierarchicalRepulsionSolver2 = _interopRequireDefault(_componentsPhysicsHierarchicalRepulsionSolver); - var _componentsPhysicsSpringSolver = __webpack_require__(93); + var _componentsPhysicsSpringSolver = __webpack_require__(94); var _componentsPhysicsSpringSolver2 = _interopRequireDefault(_componentsPhysicsSpringSolver); - var _componentsPhysicsHierarchicalSpringSolver = __webpack_require__(94); + var _componentsPhysicsHierarchicalSpringSolver = __webpack_require__(95); var _componentsPhysicsHierarchicalSpringSolver2 = _interopRequireDefault(_componentsPhysicsHierarchicalSpringSolver); - var _componentsPhysicsCentralGravitySolver = __webpack_require__(95); + var _componentsPhysicsCentralGravitySolver = __webpack_require__(96); var _componentsPhysicsCentralGravitySolver2 = _interopRequireDefault(_componentsPhysicsCentralGravitySolver); - var _componentsPhysicsFA2BasedRepulsionSolver = __webpack_require__(96); + var _componentsPhysicsFA2BasedRepulsionSolver = __webpack_require__(97); var _componentsPhysicsFA2BasedRepulsionSolver2 = _interopRequireDefault(_componentsPhysicsFA2BasedRepulsionSolver); - var _componentsPhysicsFA2BasedCentralGravitySolver = __webpack_require__(97); + var _componentsPhysicsFA2BasedCentralGravitySolver = __webpack_require__(98); var _componentsPhysicsFA2BasedCentralGravitySolver2 = _interopRequireDefault(_componentsPhysicsFA2BasedCentralGravitySolver); - var util = __webpack_require__(2); + var util = __webpack_require__(3); var PhysicsEngine = (function () { function PhysicsEngine(body) { @@ -2145,361 +2310,1723 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 4 */ +/* 5 */ /***/ function(module, exports, __webpack_require__) { - "use strict"; + // Load custom shapes into CanvasRenderingContext2D + 'use strict'; - Object.defineProperty(exports, "__esModule", { - value: true - }); + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + var _modulesGroups = __webpack_require__(67); - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + var _modulesGroups2 = _interopRequireDefault(_modulesGroups); - var Node = __webpack_require__(6); - var Edge = __webpack_require__(84); - var util = __webpack_require__(2); + var _modulesNodesHandler = __webpack_require__(68); - var SelectionHandler = (function () { - function SelectionHandler(body, canvas) { - var _this = this; + var _modulesNodesHandler2 = _interopRequireDefault(_modulesNodesHandler); - _classCallCheck(this, SelectionHandler); + var _modulesEdgesHandler = __webpack_require__(86); - this.body = body; - this.canvas = canvas; - this.selectionObj = { nodes: [], edges: [] }; - this.hoverObj = { nodes: {}, edges: {} }; + var _modulesEdgesHandler2 = _interopRequireDefault(_modulesEdgesHandler); - this.options = {}; - this.defaultOptions = { - multiselect: false, - selectable: true, - selectConnectedEdges: true, - hoverConnectedEdges: true - }; - util.extend(this.options, this.defaultOptions); + var _modulesPhysicsEngine = __webpack_require__(4); - this.body.emitter.on("_dataChanged", function () { - _this.updateSelection(); - }); - } + var _modulesPhysicsEngine2 = _interopRequireDefault(_modulesPhysicsEngine); - _createClass(SelectionHandler, [{ - key: "setOptions", - value: function setOptions(options) { - if (options !== undefined) { - var fields = ["multiselect", "hoverConnectedEdges", "selectable", "selectConnectedEdges"]; - util.selectiveDeepExtend(fields, this.options, options); - } - } - }, { - key: "selectOnPoint", + var _modulesClustering = __webpack_require__(6); - /** - * handles the selection part of the tap; - * - * @param {Object} pointer - * @private - */ - value: function selectOnPoint(pointer) { - var selected = false; - if (this.options.selectable === true) { - var obj = this.getNodeAt(pointer) || this.getEdgeAt(pointer); + var _modulesClustering2 = _interopRequireDefault(_modulesClustering); - // unselect after getting the objects in order to restore width and height. - this.unselectAll(); + var _modulesCanvasRenderer = __webpack_require__(100); - if (obj !== undefined) { - selected = this.selectObject(obj); - } - this.body.emitter.emit("_requestRedraw"); - } - return selected; - } - }, { - key: "selectAdditionalOnPoint", - value: function selectAdditionalOnPoint(pointer) { - var selectionChanged = false; - if (this.options.selectable === true) { - var obj = this.getNodeAt(pointer) || this.getEdgeAt(pointer); + var _modulesCanvasRenderer2 = _interopRequireDefault(_modulesCanvasRenderer); - if (obj !== undefined) { - selectionChanged = true; - if (obj.isSelected() === true) { - this.deselectObject(obj); - } else { - this.selectObject(obj); - } + var _modulesCanvas = __webpack_require__(101); - this.body.emitter.emit("_requestRedraw"); - } - } - return selectionChanged; - } - }, { - key: "_generateClickEvent", - value: function _generateClickEvent(eventType, event, pointer, oldSelection) { - var properties = this.getSelection(); - properties["pointer"] = { - DOM: { x: pointer.x, y: pointer.y }, - canvas: this.canvas.DOMtoCanvas(pointer) - }; - properties["event"] = event; + var _modulesCanvas2 = _interopRequireDefault(_modulesCanvas); - if (oldSelection !== undefined) { - properties["previousSelection"] = oldSelection; - } - this.body.emitter.emit(eventType, properties); - } - }, { - key: "selectObject", - value: function selectObject(obj) { - var highlightEdges = arguments[1] === undefined ? this.options.selectConnectedEdges : arguments[1]; + var _modulesView = __webpack_require__(102); - if (obj !== undefined) { - if (obj instanceof Node) { - if (highlightEdges === true) { - this._selectConnectedEdges(obj); - } - } - obj.select(); - this._addToSelection(obj); - return true; - } - return false; - } - }, { - key: "deselectObject", - value: function deselectObject(obj) { - if (obj.isSelected() === true) { - obj.selected = false; - this._removeFromSelection(obj); - } - } - }, { - key: "_getAllNodesOverlappingWith", + var _modulesView2 = _interopRequireDefault(_modulesView); - /** - * retrieve all nodes overlapping with given object - * @param {Object} object An object with parameters left, top, right, bottom - * @return {Number[]} An array with id's of the overlapping nodes - * @private - */ - value: function _getAllNodesOverlappingWith(object) { - var overlappingNodes = []; - var nodes = this.body.nodes; - for (var i = 0; i < this.body.nodeIndices.length; i++) { - var nodeId = this.body.nodeIndices[i]; - if (nodes[nodeId].isOverlappingWith(object)) { - overlappingNodes.push(nodeId); - } - } - return overlappingNodes; - } - }, { - key: "_pointerToPositionObject", + var _modulesInteractionHandler = __webpack_require__(103); - /** - * Return a position object in canvasspace from a single point in screenspace - * - * @param pointer - * @returns {{left: number, top: number, right: number, bottom: number}} - * @private - */ - value: function _pointerToPositionObject(pointer) { - var canvasPos = this.canvas.DOMtoCanvas(pointer); - return { - left: canvasPos.x - 1, - top: canvasPos.y + 1, - right: canvasPos.x + 1, - bottom: canvasPos.y - 1 - }; - } - }, { - key: "getNodeAt", + var _modulesInteractionHandler2 = _interopRequireDefault(_modulesInteractionHandler); - /** - * Get the top node at the a specific point (like a click) - * - * @param {{x: Number, y: Number}} pointer - * @return {Node | undefined} node - * @private - */ - value: function getNodeAt(pointer) { - var returnNode = arguments[1] === undefined ? true : arguments[1]; + var _modulesSelectionHandler = __webpack_require__(7); - // we first check if this is an navigation controls element - var positionObject = this._pointerToPositionObject(pointer); - var overlappingNodes = this._getAllNodesOverlappingWith(positionObject); - // if there are overlapping nodes, select the last one, this is the - // one which is drawn on top of the others - if (overlappingNodes.length > 0) { - if (returnNode === true) { - return this.body.nodes[overlappingNodes[overlappingNodes.length - 1]]; - } else { - return overlappingNodes[overlappingNodes.length - 1]; - } - } else { - return undefined; - } - } - }, { - key: "_getEdgesOverlappingWith", + var _modulesSelectionHandler2 = _interopRequireDefault(_modulesSelectionHandler); - /** - * retrieve all edges overlapping with given object, selector is around center - * @param {Object} object An object with parameters left, top, right, bottom - * @return {Number[]} An array with id's of the overlapping nodes - * @private - */ - value: function _getEdgesOverlappingWith(object, overlappingEdges) { - var edges = this.body.edges; - for (var i = 0; i < this.body.edgeIndices.length; i++) { - var edgeId = this.body.edgeIndices[i]; - if (edges[edgeId].isOverlappingWith(object)) { - overlappingEdges.push(edgeId); - } - } - } - }, { - key: "_getAllEdgesOverlappingWith", + var _modulesLayoutEngine = __webpack_require__(106); - /** - * retrieve all nodes overlapping with given object - * @param {Object} object An object with parameters left, top, right, bottom - * @return {Number[]} An array with id's of the overlapping nodes - * @private - */ - value: function _getAllEdgesOverlappingWith(object) { - var overlappingEdges = []; - this._getEdgesOverlappingWith(object, overlappingEdges); - return overlappingEdges; - } - }, { - key: "getEdgeAt", + var _modulesLayoutEngine2 = _interopRequireDefault(_modulesLayoutEngine); - /** - * Place holder. To implement change the getNodeAt to a _getObjectAt. Have the _getObjectAt call - * getNodeAt and _getEdgesAt, then priortize the selection to user preferences. - * - * @param pointer - * @returns {undefined} - * @private - */ - value: function getEdgeAt(pointer) { - var returnEdge = arguments[1] === undefined ? true : arguments[1]; + var _modulesManipulationSystem = __webpack_require__(107); - var positionObject = this._pointerToPositionObject(pointer); - var overlappingEdges = this._getAllEdgesOverlappingWith(positionObject); + var _modulesManipulationSystem2 = _interopRequireDefault(_modulesManipulationSystem); - if (overlappingEdges.length > 0) { - if (returnEdge === true) { - return this.body.edges[overlappingEdges[overlappingEdges.length - 1]]; - } else { - return overlappingEdges[overlappingEdges.length - 1]; - } - } else { - return undefined; - } - } - }, { - key: "_addToSelection", + var _sharedConfigurator = __webpack_require__(53); - /** - * Add object to the selection array. - * - * @param obj - * @private - */ - value: function _addToSelection(obj) { - if (obj instanceof Node) { - this.selectionObj.nodes[obj.id] = obj; - } else { - this.selectionObj.edges[obj.id] = obj; - } - } - }, { - key: "_addToHover", + var _sharedConfigurator2 = _interopRequireDefault(_sharedConfigurator); - /** - * Add object to the selection array. - * - * @param obj - * @private - */ - value: function _addToHover(obj) { - if (obj instanceof Node) { - this.hoverObj.nodes[obj.id] = obj; - } else { - this.hoverObj.edges[obj.id] = obj; - } - } - }, { - key: "_removeFromSelection", + var _sharedValidator = __webpack_require__(55); - /** - * Remove a single option from selection. - * - * @param {Object} obj - * @private - */ - value: function _removeFromSelection(obj) { - if (obj instanceof Node) { - delete this.selectionObj.nodes[obj.id]; - } else { - delete this.selectionObj.edges[obj.id]; - } - } - }, { - key: "unselectAll", + var _sharedValidator2 = _interopRequireDefault(_sharedValidator); - /** - * Unselect all. The selectionObj is useful for this. - * - * @private - */ - value: function unselectAll() { - for (var nodeId in this.selectionObj.nodes) { - if (this.selectionObj.nodes.hasOwnProperty(nodeId)) { - this.selectionObj.nodes[nodeId].unselect(); - } - } - for (var edgeId in this.selectionObj.edges) { - if (this.selectionObj.edges.hasOwnProperty(edgeId)) { - this.selectionObj.edges[edgeId].unselect(); - } - } + var _optionsJs = __webpack_require__(108); - this.selectionObj = { nodes: {}, edges: {} }; - } - }, { - key: "_getSelectedNodeCount", + __webpack_require__(109); - /** - * return the number of selected nodes - * - * @returns {number} - * @private - */ - value: function _getSelectedNodeCount() { - var count = 0; - for (var nodeId in this.selectionObj.nodes) { - if (this.selectionObj.nodes.hasOwnProperty(nodeId)) { - count += 1; - } - } - return count; - } - }, { - key: "_getSelectedNode", + var Emitter = __webpack_require__(21); + var Hammer = __webpack_require__(31); + var util = __webpack_require__(3); + var DataSet = __webpack_require__(16); + var DataView = __webpack_require__(18); + var dotparser = __webpack_require__(110); + var gephiParser = __webpack_require__(111); + var Images = __webpack_require__(112); + var Activator = __webpack_require__(50); + var locales = __webpack_require__(113); - /** - * return the selected node - * - * @returns {number} + /** + * @constructor Network + * Create a network visualization, displaying nodes and edges. + * + * @param {Element} container The DOM element in which the Network will + * be created. Normally a div element. + * @param {Object} data An object containing parameters + * {Array} nodes + * {Array} edges + * @param {Object} options Options + */ + function Network(container, data, options) { + var _this = this; + + if (!(this instanceof Network)) { + throw new SyntaxError('Constructor must be called with the new operator'); + } + + // set constant values + this.options = {}; + this.defaultOptions = { + locale: 'en', + locales: locales, + clickToUse: false + }; + util.extend(this.options, this.defaultOptions); + + // containers for nodes and edges + this.body = { + container: container, + nodes: {}, + nodeIndices: [], + edges: {}, + edgeIndices: [], + emitter: { + on: this.on.bind(this), + off: this.off.bind(this), + emit: this.emit.bind(this), + once: this.once.bind(this) + }, + eventListeners: { + onTap: function onTap() {}, + onTouch: function onTouch() {}, + onDoubleTap: function onDoubleTap() {}, + onHold: function onHold() {}, + onDragStart: function onDragStart() {}, + onDrag: function onDrag() {}, + onDragEnd: function onDragEnd() {}, + onMouseWheel: function onMouseWheel() {}, + onPinch: function onPinch() {}, + onMouseMove: function onMouseMove() {}, + onRelease: function onRelease() {}, + onContext: function onContext() {} + }, + data: { + nodes: null, // A DataSet or DataView + edges: null // A DataSet or DataView + }, + functions: { + createNode: function createNode() {}, + createEdge: function createEdge() {}, + getPointer: function getPointer() {} + }, + view: { + scale: 1, + translation: { x: 0, y: 0 } + } + }; + + // bind the event listeners + this.bindEventListeners(); + + // setting up all modules + this.images = new Images(function () { + return _this.body.emitter.emit('_requestRedraw'); + }); // object with images + this.groups = new _modulesGroups2['default'](); // object with groups + this.canvas = new _modulesCanvas2['default'](this.body); // DOM handler + this.selectionHandler = new _modulesSelectionHandler2['default'](this.body, this.canvas); // Selection handler + this.interactionHandler = new _modulesInteractionHandler2['default'](this.body, this.canvas, this.selectionHandler); // Interaction handler handles all the hammer bindings (that are bound by canvas), key + this.view = new _modulesView2['default'](this.body, this.canvas); // camera handler, does animations and zooms + this.renderer = new _modulesCanvasRenderer2['default'](this.body, this.canvas); // renderer, starts renderloop, has events that modules can hook into + this.physics = new _modulesPhysicsEngine2['default'](this.body); // physics engine, does all the simulations + this.layoutEngine = new _modulesLayoutEngine2['default'](this.body); // layout engine for inital layout and hierarchical layout + this.clustering = new _modulesClustering2['default'](this.body); // clustering api + this.manipulation = new _modulesManipulationSystem2['default'](this.body, this.canvas, this.selectionHandler); // data manipulation system + + this.nodesHandler = new _modulesNodesHandler2['default'](this.body, this.images, this.groups, this.layoutEngine); // Handle adding, deleting and updating of nodes as well as global options + this.edgesHandler = new _modulesEdgesHandler2['default'](this.body, this.images, this.groups); // Handle adding, deleting and updating of edges as well as global options + + // create the DOM elements + this.canvas._create(); + + // setup configuration system + this.configurator = new _sharedConfigurator2['default'](this, this.body.container, _optionsJs.configureOptions, this.canvas.pixelRatio); + + // apply options + this.setOptions(options); + + // load data (the disable start variable will be the same as the enabled clustering) + this.setData(data); + } + + // Extend Network with an Emitter mixin + Emitter(Network.prototype); + + /** + * Set options + * @param {Object} options + */ + Network.prototype.setOptions = function (options) { + var _this2 = this; + + if (options !== undefined) { + + var errorFound = _sharedValidator2['default'].validate(options, _optionsJs.allOptions); + if (errorFound === true) { + console.log('%cErrors have been found in the supplied options object.', _sharedValidator.printStyle); + } + + // copy the global fields over + var fields = ['locale', 'locales', 'clickToUse']; + util.selectiveDeepExtend(fields, this.options, options); + + // the hierarchical system can adapt the edges and the physics to it's own options because not all combinations work with the hierarichical system. + options = this.layoutEngine.setOptions(options.layout, options); + + this.canvas.setOptions(options); // options for canvas are in globals + + // pass the options to the modules + this.groups.setOptions(options.groups); + this.nodesHandler.setOptions(options.nodes); + this.edgesHandler.setOptions(options.edges); + this.physics.setOptions(options.physics); + this.manipulation.setOptions(options.manipulation, options, this.options); // manipulation uses the locales in the globals + + this.interactionHandler.setOptions(options.interaction); + this.renderer.setOptions(options.interaction); // options for rendering are in interaction + this.selectionHandler.setOptions(options.interaction); // options for selection are in interaction + + // reload the settings of the nodes to apply changes in groups that are not referenced by pointer. + if (options.groups !== undefined) { + this.body.emitter.emit('refreshNodes'); + } + // these two do not have options at the moment, here for completeness + //this.view.setOptions(options.view); + //this.clustering.setOptions(options.clustering); + + this.configurator.setOptions(options.configure); + + // if the configuration system is enabled, copy all options and put them into the config system + if (this.configurator.options.enabled === true) { + var networkOptions = { nodes: {}, edges: {}, layout: {}, interaction: {}, manipulation: {}, physics: {}, global: {} }; + util.deepExtend(networkOptions.nodes, this.nodesHandler.options); + util.deepExtend(networkOptions.edges, this.edgesHandler.options); + util.deepExtend(networkOptions.layout, this.layoutEngine.options); + // load the selectionHandler and rendere default options in to the interaction group + util.deepExtend(networkOptions.interaction, this.selectionHandler.options); + util.deepExtend(networkOptions.interaction, this.renderer.options); + + util.deepExtend(networkOptions.interaction, this.interactionHandler.options); + util.deepExtend(networkOptions.manipulation, this.manipulation.options); + util.deepExtend(networkOptions.physics, this.physics.options); + + // load globals into the global object + util.deepExtend(networkOptions.global, this.canvas.options); + util.deepExtend(networkOptions.global, this.options); + + this.configurator.setModuleOptions(networkOptions); + } + + // handle network global options + if (options.clickToUse !== undefined) { + if (options.clickToUse === true) { + if (this.activator === undefined) { + this.activator = new Activator(this.canvas.frame); + this.activator.on('change', function () { + _this2.body.emitter.emit('activate'); + }); + } + } else { + if (this.activator !== undefined) { + this.activator.destroy(); + delete this.activator; + } + this.body.emitter.emit('activate'); + } + } else { + this.body.emitter.emit('activate'); + } + + this.canvas.setSize(); + // start the physics simulation. Can be safely called multiple times. + this.body.emitter.emit('startSimulation'); + } + }; + + /** + * Update the this.body.nodeIndices with the most recent node index list + * @private + */ + Network.prototype._updateVisibleIndices = function () { + var nodes = this.body.nodes; + var edges = this.body.edges; + this.body.nodeIndices = []; + this.body.edgeIndices = []; + + for (var nodeId in nodes) { + if (nodes.hasOwnProperty(nodeId)) { + if (nodes[nodeId].options.hidden === false) { + this.body.nodeIndices.push(nodeId); + } + } + } + + for (var edgeId in edges) { + if (edges.hasOwnProperty(edgeId)) { + if (edges[edgeId].options.hidden === false) { + this.body.edgeIndices.push(edgeId); + } + } + } + }; + + /** + * Bind all events + */ + Network.prototype.bindEventListeners = function () { + var _this3 = this; + + // this event will trigger a rebuilding of the cache everything. Used when nodes or edges have been added or removed. + this.body.emitter.on('_dataChanged', function () { + // update shortcut lists + _this3._updateVisibleIndices(); + _this3.physics.updatePhysicsData(); + + // call the dataUpdated event because the only difference between the two is the updating of the indices + _this3.body.emitter.emit('_dataUpdated'); + }); + + // this is called when options of EXISTING nodes or edges have changed. + this.body.emitter.on('_dataUpdated', function () { + // update values + _this3._updateValueRange(_this3.body.nodes); + _this3._updateValueRange(_this3.body.edges); + // start simulation (can be called safely, even if already running) + _this3.body.emitter.emit('startSimulation'); + }); + }; + + /** + * Set nodes and edges, and optionally options as well. + * + * @param {Object} data Object containing parameters: + * {Array | DataSet | DataView} [nodes] Array with nodes + * {Array | DataSet | DataView} [edges] Array with edges + * {String} [dot] String containing data in DOT format + * {String} [gephi] String containing data in gephi JSON format + * {Options} [options] Object with options + */ + Network.prototype.setData = function (data) { + // reset the physics engine. + this.body.emitter.emit('resetPhysics'); + this.body.emitter.emit('_resetData'); + + // unselect all to ensure no selections from old data are carried over. + this.selectionHandler.unselectAll(); + + if (data && data.dot && (data.nodes || data.edges)) { + throw new SyntaxError('Data must contain either parameter "dot" or ' + ' parameter pair "nodes" and "edges", but not both.'); + } + + // set options + this.setOptions(data && data.options); + // set all data + if (data && data.dot) { + console.log('The dot property has been depricated. Please use the static convertDot method to convert DOT into vis.network format and use the normal data format with nodes and edges. This converter is used like this: var data = vis.network.convertDot(dotString);'); + // parse DOT file + var dotData = dotparser.DOTToGraph(data.dot); + this.setData(dotData); + return; + } else if (data && data.gephi) { + // parse DOT file + console.log('The gephi property has been depricated. Please use the static convertGephi method to convert gephi into vis.network format and use the normal data format with nodes and edges. This converter is used like this: var data = vis.network.convertGephi(gephiJson);'); + var gephiData = gephiParser.parseGephi(data.gephi); + this.setData(gephiData); + return; + } else { + this.nodesHandler.setData(data && data.nodes, true); + this.edgesHandler.setData(data && data.edges, true); + } + + // emit change in data + this.body.emitter.emit('_dataChanged'); + + // find a stable position or start animating to a stable position + this.body.emitter.emit('initPhysics'); + }; + + /** + * Cleans up all bindings of the network, removing it fully from the memory IF the variable is set to null after calling this function. + * var network = new vis.Network(..); + * network.destroy(); + * network = null; + */ + Network.prototype.destroy = function () { + this.body.emitter.emit('destroy'); + // clear events + this.body.emitter.off(); + this.off(); + + // delete modules + delete this.groups; + delete this.canvas; + delete this.selectionHandler; + delete this.interactionHandler; + delete this.view; + delete this.renderer; + delete this.physics; + delete this.layoutEngine; + delete this.clustering; + delete this.manipulation; + delete this.nodesHandler; + delete this.edgesHandler; + delete this.configurator; + delete this.images; + + for (var nodeId in this.body.nodes) { + delete this.body.nodes[nodeId]; + } + for (var edgeId in this.body.edges) { + delete this.body.edges[edgeId]; + } + + // remove the container and everything inside it recursively + util.recursiveDOMDelete(this.body.container); + }; + + /** + * Update the values of all object in the given array according to the current + * value range of the objects in the array. + * @param {Object} obj An object containing a set of Edges or Nodes + * The objects must have a method getValue() and + * setValueRange(min, max). + * @private + */ + Network.prototype._updateValueRange = function (obj) { + var id; + + // determine the range of the objects + var valueMin = undefined; + var valueMax = undefined; + var valueTotal = 0; + for (id in obj) { + if (obj.hasOwnProperty(id)) { + var value = obj[id].getValue(); + if (value !== undefined) { + valueMin = valueMin === undefined ? value : Math.min(value, valueMin); + valueMax = valueMax === undefined ? value : Math.max(value, valueMax); + valueTotal += value; + } + } + } + + // adjust the range of all objects + if (valueMin !== undefined && valueMax !== undefined) { + for (id in obj) { + if (obj.hasOwnProperty(id)) { + obj[id].setValueRange(valueMin, valueMax, valueTotal); + } + } + } + }; + + /** + * Returns true when the Network is active. + * @returns {boolean} + */ + Network.prototype.isActive = function () { + return !this.activator || this.activator.active; + }; + + Network.prototype.setSize = function () { + return this.canvas.setSize.apply(this.canvas, arguments); + }; + Network.prototype.canvasToDOM = function () { + return this.canvas.canvasToDOM.apply(this.canvas, arguments); + }; + Network.prototype.DOMtoCanvas = function () { + return this.canvas.DOMtoCanvas(this.canvas, arguments); + }; + Network.prototype.findNode = function () { + return this.clustering.findNode.apply(this.clustering, arguments); + }; + Network.prototype.isCluster = function () { + return this.clustering.isCluster.apply(this.clustering, arguments); + }; + Network.prototype.openCluster = function () { + return this.clustering.openCluster.apply(this.clustering, arguments); + }; + Network.prototype.cluster = function () { + return this.clustering.cluster.apply(this.clustering, arguments); + }; + Network.prototype.getNodesInCluster = function () { + return this.clustering.getNodesInCluster.apply(this.clustering, arguments); + }; + Network.prototype.clusterByConnection = function () { + return this.clustering.clusterByConnection.apply(this.clustering, arguments); + }; + Network.prototype.clusterByHubsize = function () { + return this.clustering.clusterByHubsize.apply(this.clustering, arguments); + }; + Network.prototype.clusterOutliers = function () { + return this.clustering.clusterOutliers.apply(this.clustering, arguments); + }; + Network.prototype.getSeed = function () { + return this.layoutEngine.getSeed.apply(this.layoutEngine, arguments); + }; + Network.prototype.enableEditMode = function () { + return this.manipulation.enableEditMode.apply(this.manipulation, arguments); + }; + Network.prototype.disableEditMode = function () { + return this.manipulation.disableEditMode.apply(this.manipulation, arguments); + }; + Network.prototype.addNodeMode = function () { + return this.manipulation.addNodeMode.apply(this.manipulation, arguments); + }; + Network.prototype.editNode = function () { + return this.manipulation.editNode.apply(this.manipulation, arguments); + }; + Network.prototype.editNodeMode = function () { + console.log('Depricated: Please use editNode instead of editNodeMode.');return this.manipulation.editNode.apply(this.manipulation, arguments); + }; + Network.prototype.addEdgeMode = function () { + return this.manipulation.addEdgeMode.apply(this.manipulation, arguments); + }; + Network.prototype.editEdgeMode = function () { + return this.manipulation.editEdgeMode.apply(this.manipulation, arguments); + }; + Network.prototype.deleteSelected = function () { + return this.manipulation.deleteSelected.apply(this.manipulation, arguments); + }; + Network.prototype.getPositions = function () { + return this.nodesHandler.getPositions.apply(this.nodesHandler, arguments); + }; + Network.prototype.storePositions = function () { + return this.nodesHandler.storePositions.apply(this.nodesHandler, arguments); + }; + Network.prototype.getBoundingBox = function () { + return this.nodesHandler.getBoundingBox.apply(this.nodesHandler, arguments); + }; + Network.prototype.getConnectedNodes = function (objectId) { + if (this.body.nodes[objectId] !== undefined) { + return this.nodesHandler.getConnectedNodes.apply(this.nodesHandler, arguments); + } else { + return this.edgesHandler.getConnectedNodes.apply(this.edgesHandler, arguments); + } + }; + Network.prototype.getConnectedEdges = function () { + return this.nodesHandler.getConnectedEdges.apply(this.nodesHandler, arguments); + }; + Network.prototype.startSimulation = function () { + return this.physics.startSimulation.apply(this.physics, arguments); + }; + Network.prototype.stopSimulation = function () { + return this.physics.stopSimulation.apply(this.physics, arguments); + }; + Network.prototype.stabilize = function () { + return this.physics.stabilize.apply(this.physics, arguments); + }; + Network.prototype.getSelection = function () { + return this.selectionHandler.getSelection.apply(this.selectionHandler, arguments); + }; + Network.prototype.getSelectedNodes = function () { + return this.selectionHandler.getSelectedNodes.apply(this.selectionHandler, arguments); + }; + Network.prototype.getSelectedEdges = function () { + return this.selectionHandler.getSelectedEdges.apply(this.selectionHandler, arguments); + }; + Network.prototype.getNodeAt = function () { + var node = this.selectionHandler.getNodeAt.apply(this.selectionHandler, arguments); + if (node !== undefined && node.id !== undefined) { + return node.id; + } + return node; + }; + Network.prototype.getEdgeAt = function () { + var edge = this.selectionHandler.getEdgeAt.apply(this.selectionHandler, arguments); + if (edge !== undefined && edge.id !== undefined) { + return edge.id; + } + return edge; + }; + Network.prototype.selectNodes = function () { + return this.selectionHandler.selectNodes.apply(this.selectionHandler, arguments); + }; + Network.prototype.selectEdges = function () { + return this.selectionHandler.selectEdges.apply(this.selectionHandler, arguments); + }; + Network.prototype.unselectAll = function () { + return this.selectionHandler.unselectAll.apply(this.selectionHandler, arguments); + }; + Network.prototype.redraw = function () { + return this.renderer.redraw.apply(this.renderer, arguments); + }; + Network.prototype.getScale = function () { + return this.view.getScale.apply(this.view, arguments); + }; + Network.prototype.getViewPosition = function () { + return this.view.getViewPosition.apply(this.view, arguments); + }; + Network.prototype.fit = function () { + return this.view.fit.apply(this.view, arguments); + }; + Network.prototype.moveTo = function () { + return this.view.moveTo.apply(this.view, arguments); + }; + Network.prototype.focus = function () { + return this.view.focus.apply(this.view, arguments); + }; + Network.prototype.releaseNode = function () { + return this.view.releaseNode.apply(this.view, arguments); + }; + + module.exports = Network; + +/***/ }, +/* 6 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, '__esModule', { + value: true + }); + + var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + + var _componentsNodesCluster = __webpack_require__(99); + + var _componentsNodesCluster2 = _interopRequireDefault(_componentsNodesCluster); + + var util = __webpack_require__(3); + + var ClusterEngine = (function () { + function ClusterEngine(body) { + var _this = this; + + _classCallCheck(this, ClusterEngine); + + this.body = body; + this.clusteredNodes = {}; + + this.options = {}; + this.defaultOptions = {}; + util.extend(this.options, this.defaultOptions); + + this.body.emitter.on('_resetData', function () { + _this.clusteredNodes = {}; + }); + } + + _createClass(ClusterEngine, [{ + key: 'setOptions', + value: function setOptions(options) { + if (options !== undefined) {} + } + }, { + key: 'clusterByHubsize', + + /** + * + * @param hubsize + * @param options + */ + value: function clusterByHubsize(hubsize, options) { + if (hubsize === undefined) { + hubsize = this._getHubSize(); + } else if (typeof hubsize === 'object') { + options = this._checkOptions(hubsize); + hubsize = this._getHubSize(); + } + + var nodesToCluster = []; + for (var i = 0; i < this.body.nodeIndices.length; i++) { + var node = this.body.nodes[this.body.nodeIndices[i]]; + if (node.edges.length >= hubsize) { + nodesToCluster.push(node.id); + } + } + + for (var i = 0; i < nodesToCluster.length; i++) { + this.clusterByConnection(nodesToCluster[i], options, false); + } + this.body.emitter.emit('_dataChanged'); + } + }, { + key: 'cluster', + + /** + * loop over all nodes, check if they adhere to the condition and cluster if needed. + * @param options + * @param refreshData + */ + value: function cluster() { + var options = arguments[0] === undefined ? {} : arguments[0]; + var refreshData = arguments[1] === undefined ? true : arguments[1]; + + if (options.joinCondition === undefined) { + throw new Error('Cannot call clusterByNodeData without a joinCondition function in the options.'); + } + + // check if the options object is fine, append if needed + options = this._checkOptions(options); + + var childNodesObj = {}; + var childEdgesObj = {}; + + // collect the nodes that will be in the cluster + for (var i = 0; i < this.body.nodeIndices.length; i++) { + var nodeId = this.body.nodeIndices[i]; + var node = this.body.nodes[nodeId]; + var clonedOptions = this._cloneOptions(node); + if (options.joinCondition(clonedOptions) === true) { + childNodesObj[nodeId] = this.body.nodes[nodeId]; + + // collect the nodes that will be in the cluster + for (var _i = 0; _i < node.edges.length; _i++) { + var edge = node.edges[_i]; + childEdgesObj[edge.id] = edge; + } + } + } + + this._cluster(childNodesObj, childEdgesObj, options, refreshData); + } + }, { + key: 'clusterOutliers', + + /** + * Cluster all nodes in the network that have only 1 edge + * @param options + * @param refreshData + */ + value: function clusterOutliers(options) { + var refreshData = arguments[1] === undefined ? true : arguments[1]; + + options = this._checkOptions(options); + var clusters = []; + + // collect the nodes that will be in the cluster + for (var i = 0; i < this.body.nodeIndices.length; i++) { + var childNodesObj = {}; + var childEdgesObj = {}; + var nodeId = this.body.nodeIndices[i]; + var visibleEdges = 0; + var edge = undefined; + for (var j = 0; j < this.body.nodes[nodeId].edges.length; j++) { + if (this.body.nodes[nodeId].edges[j].options.hidden === false) { + visibleEdges++; + edge = this.body.nodes[nodeId].edges[j]; + } + } + + if (visibleEdges === 1) { + // this is an outlier + var childNodeId = this._getConnectedId(edge, nodeId); + if (childNodeId !== nodeId) { + if (options.joinCondition === undefined) { + if (this._checkIfUsed(clusters, nodeId, edge.id) === false && this._checkIfUsed(clusters, childNodeId, edge.id) === false) { + childEdgesObj[edge.id] = edge; + childNodesObj[nodeId] = this.body.nodes[nodeId]; + childNodesObj[childNodeId] = this.body.nodes[childNodeId]; + } + } else { + var clonedOptions = this._cloneOptions(this.body.nodes[nodeId]); + if (options.joinCondition(clonedOptions) === true && this._checkIfUsed(clusters, nodeId, edge.id) === false) { + childEdgesObj[edge.id] = edge; + childNodesObj[nodeId] = this.body.nodes[nodeId]; + } + clonedOptions = this._cloneOptions(this.body.nodes[childNodeId]); + if (options.joinCondition(clonedOptions) === true && this._checkIfUsed(clusters, nodeId, edge.id) === false) { + childEdgesObj[edge.id] = edge; + childNodesObj[childNodeId] = this.body.nodes[childNodeId]; + } + } + + if (Object.keys(childNodesObj).length > 0 && Object.keys(childEdgesObj).length > 0) { + clusters.push({ nodes: childNodesObj, edges: childEdgesObj }); + } + } + } + } + + for (var i = 0; i < clusters.length; i++) { + this._cluster(clusters[i].nodes, clusters[i].edges, options, false); + } + + if (refreshData === true) { + this.body.emitter.emit('_dataChanged'); + } + } + }, { + key: '_checkIfUsed', + value: function _checkIfUsed(clusters, nodeId, edgeId) { + for (var i = 0; i < clusters.length; i++) { + var cluster = clusters[i]; + if (cluster.nodes[nodeId] !== undefined || cluster.edges[edgeId] !== undefined) { + return true; + } + } + return false; + } + }, { + key: 'clusterByConnection', + + /** + * suck all connected nodes of a node into the node. + * @param nodeId + * @param options + * @param refreshData + */ + value: function clusterByConnection(nodeId, options) { + var refreshData = arguments[2] === undefined ? true : arguments[2]; + + // kill conditions + if (nodeId === undefined) { + throw new Error('No nodeId supplied to clusterByConnection!'); + } + if (this.body.nodes[nodeId] === undefined) { + throw new Error('The nodeId given to clusterByConnection does not exist!'); + } + + var node = this.body.nodes[nodeId]; + options = this._checkOptions(options, node); + if (options.clusterNodeProperties.x === undefined) { + options.clusterNodeProperties.x = node.x; + } + if (options.clusterNodeProperties.y === undefined) { + options.clusterNodeProperties.y = node.y; + } + if (options.clusterNodeProperties.fixed === undefined) { + options.clusterNodeProperties.fixed = {}; + options.clusterNodeProperties.fixed.x = node.options.fixed.x; + options.clusterNodeProperties.fixed.y = node.options.fixed.y; + } + + var childNodesObj = {}; + var childEdgesObj = {}; + var parentNodeId = node.id; + var parentClonedOptions = this._cloneOptions(node); + childNodesObj[parentNodeId] = node; + + // collect the nodes that will be in the cluster + for (var i = 0; i < node.edges.length; i++) { + var edge = node.edges[i]; + var childNodeId = this._getConnectedId(edge, parentNodeId); + + if (childNodeId !== parentNodeId) { + if (options.joinCondition === undefined) { + childEdgesObj[edge.id] = edge; + childNodesObj[childNodeId] = this.body.nodes[childNodeId]; + } else { + // clone the options and insert some additional parameters that could be interesting. + var childClonedOptions = this._cloneOptions(this.body.nodes[childNodeId]); + if (options.joinCondition(parentClonedOptions, childClonedOptions) === true) { + childEdgesObj[edge.id] = edge; + childNodesObj[childNodeId] = this.body.nodes[childNodeId]; + } + } + } else { + childEdgesObj[edge.id] = edge; + } + } + + this._cluster(childNodesObj, childEdgesObj, options, refreshData); + } + }, { + key: '_cloneOptions', + + /** + * This returns a clone of the options or options of the edge or node to be used for construction of new edges or check functions for new nodes. + * @param objId + * @param type + * @returns {{}} + * @private + */ + value: function _cloneOptions(item, type) { + var clonedOptions = {}; + if (type === undefined || type === 'node') { + util.deepExtend(clonedOptions, item.options, true); + clonedOptions.x = item.x; + clonedOptions.y = item.y; + clonedOptions.amountOfConnections = item.edges.length; + } else { + util.deepExtend(clonedOptions, item.options, true); + } + return clonedOptions; + } + }, { + key: '_createClusterEdges', + + /** + * This function creates the edges that will be attached to the cluster. + * + * @param childNodesObj + * @param childEdgesObj + * @param newEdges + * @param options + * @private + */ + value: function _createClusterEdges(childNodesObj, childEdgesObj, newEdges, clusterNodeProperties, clusterEdgeProperties) { + var edge = undefined, + childNodeId = undefined, + childNode = undefined, + toId = undefined, + fromId = undefined, + otherNodeId = undefined; + + var childKeys = Object.keys(childNodesObj); + for (var i = 0; i < childKeys.length; i++) { + childNodeId = childKeys[i]; + childNode = childNodesObj[childNodeId]; + + // construct new edges from the cluster to others + for (var j = 0; j < childNode.edges.length; j++) { + edge = childNode.edges[j]; + childEdgesObj[edge.id] = edge; + + // childNodeId position will be replaced by the cluster. + if (edge.toId == childNodeId) { + // this is a double equals because ints and strings can be interchanged here. + toId = clusterNodeProperties.id; + fromId = edge.fromId; + otherNodeId = fromId; + } else { + toId = edge.toId; + fromId = clusterNodeProperties.id; + otherNodeId = toId; + } + + // if the node connected to the cluster is also in the cluster we do not need a new edge. + if (childNodesObj[otherNodeId] === undefined) { + var clonedOptions = this._cloneOptions(edge, 'edge'); + util.deepExtend(clonedOptions, clusterEdgeProperties); + clonedOptions.from = fromId; + clonedOptions.to = toId; + clonedOptions.id = 'clusterEdge:' + util.randomUUID(); + newEdges.push(this.body.functions.createEdge(clonedOptions)); + } + } + } + } + }, { + key: '_checkOptions', + + /** + * This function checks the options that can be supplied to the different cluster functions + * for certain fields and inserts defaults if needed + * @param options + * @returns {*} + * @private + */ + value: function _checkOptions() { + var options = arguments[0] === undefined ? {} : arguments[0]; + + if (options.clusterEdgeProperties === undefined) { + options.clusterEdgeProperties = {}; + } + if (options.clusterNodeProperties === undefined) { + options.clusterNodeProperties = {}; + } + + return options; + } + }, { + key: '_cluster', + + /** + * + * @param {Object} childNodesObj | object with node objects, id as keys, same as childNodes except it also contains a source node + * @param {Object} childEdgesObj | object with edge objects, id as keys + * @param {Array} options | object with {clusterNodeProperties, clusterEdgeProperties, processProperties} + * @param {Boolean} refreshData | when true, do not wrap up + * @private + */ + value: function _cluster(childNodesObj, childEdgesObj, options) { + var refreshData = arguments[3] === undefined ? true : arguments[3]; + + console.log(childNodesObj); + // kill condition: no children so cant cluster + if (Object.keys(childNodesObj).length === 0) { + return; + } + + var clusterNodeProperties = util.deepExtend({}, options.clusterNodeProperties); + + // construct the clusterNodeProperties + if (options.processProperties !== undefined) { + // get the childNode options + var childNodesOptions = []; + for (var nodeId in childNodesObj) { + var clonedOptions = this._cloneOptions(childNodesObj[nodeId]); + childNodesOptions.push(clonedOptions); + } + + // get clusterproperties based on childNodes + var childEdgesOptions = []; + for (var edgeId in childEdgesObj) { + var clonedOptions = this._cloneOptions(childEdgesObj[edgeId], 'edge'); + childEdgesOptions.push(clonedOptions); + } + + clusterNodeProperties = options.processProperties(clusterNodeProperties, childNodesOptions, childEdgesOptions); + if (!clusterNodeProperties) { + throw new Error('The processProperties function does not return properties!'); + } + } + + // check if we have an unique id; + if (clusterNodeProperties.id === undefined) { + clusterNodeProperties.id = 'cluster:' + util.randomUUID(); + } + var clusterId = clusterNodeProperties.id; + + if (clusterNodeProperties.label === undefined) { + clusterNodeProperties.label = 'cluster'; + } + + // give the clusterNode a postion if it does not have one. + var pos = undefined; + if (clusterNodeProperties.x === undefined) { + pos = this._getClusterPosition(childNodesObj); + clusterNodeProperties.x = pos.x; + } + if (clusterNodeProperties.y === undefined) { + if (pos === undefined) { + pos = this._getClusterPosition(childNodesObj); + } + clusterNodeProperties.y = pos.y; + } + + // force the ID to remain the same + clusterNodeProperties.id = clusterId; + + // create the clusterNode + var clusterNode = this.body.functions.createNode(clusterNodeProperties, _componentsNodesCluster2['default']); + clusterNode.isCluster = true; + clusterNode.containedNodes = childNodesObj; + clusterNode.containedEdges = childEdgesObj; + // cache a copy from the cluster edge properties if we have to reconnect others later on + clusterNode.clusterEdgeProperties = options.clusterEdgeProperties; + + // finally put the cluster node into global + this.body.nodes[clusterNodeProperties.id] = clusterNode; + + // create the new edges that will connect to the cluster + var newEdges = []; + this._createClusterEdges(childNodesObj, childEdgesObj, newEdges, clusterNodeProperties, options.clusterEdgeProperties); + + // disable the childEdges + for (var edgeId in childEdgesObj) { + if (childEdgesObj.hasOwnProperty(edgeId)) { + if (this.body.edges[edgeId] !== undefined) { + var edge = this.body.edges[edgeId]; + edge.togglePhysics(false); + edge.options.hidden = true; + } + } + } + + // disable the childNodes + for (var nodeId in childNodesObj) { + if (childNodesObj.hasOwnProperty(nodeId)) { + this.clusteredNodes[nodeId] = { clusterId: clusterNodeProperties.id, node: this.body.nodes[nodeId] }; + this.body.nodes[nodeId].togglePhysics(false); + this.body.nodes[nodeId].options.hidden = true; + } + } + + // push new edges to global + for (var i = 0; i < newEdges.length; i++) { + this.body.edges[newEdges[i].id] = newEdges[i]; + this.body.edges[newEdges[i].id].connect(); + } + + // set ID to undefined so no duplicates arise + clusterNodeProperties.id = undefined; + + // wrap up + if (refreshData === true) { + this.body.emitter.emit('_dataChanged'); + } + } + }, { + key: 'isCluster', + + /** + * Check if a node is a cluster. + * @param nodeId + * @returns {*} + */ + value: function isCluster(nodeId) { + if (this.body.nodes[nodeId] !== undefined) { + return this.body.nodes[nodeId].isCluster === true; + } else { + console.log('Node does not exist.'); + return false; + } + } + }, { + key: '_getClusterPosition', + + /** + * get the position of the cluster node based on what's inside + * @param {object} childNodesObj | object with node objects, id as keys + * @returns {{x: number, y: number}} + * @private + */ + value: function _getClusterPosition(childNodesObj) { + var childKeys = Object.keys(childNodesObj); + var minX = childNodesObj[childKeys[0]].x; + var maxX = childNodesObj[childKeys[0]].x; + var minY = childNodesObj[childKeys[0]].y; + var maxY = childNodesObj[childKeys[0]].y; + var node = undefined; + for (var i = 1; i < childKeys.length; i++) { + node = childNodesObj[childKeys[i]]; + minX = node.x < minX ? node.x : minX; + maxX = node.x > maxX ? node.x : maxX; + minY = node.y < minY ? node.y : minY; + maxY = node.y > maxY ? node.y : maxY; + } + + return { x: 0.5 * (minX + maxX), y: 0.5 * (minY + maxY) }; + } + }, { + key: 'openCluster', + + /** + * Open a cluster by calling this function. + * @param {String} clusterNodeId | the ID of the cluster node + * @param {Boolean} refreshData | wrap up afterwards if not true + */ + value: function openCluster(clusterNodeId, options) { + var refreshData = arguments[2] === undefined ? true : arguments[2]; + + // kill conditions + if (clusterNodeId === undefined) { + throw new Error('No clusterNodeId supplied to openCluster.'); + } + if (this.body.nodes[clusterNodeId] === undefined) { + throw new Error('The clusterNodeId supplied to openCluster does not exist.'); + } + if (this.body.nodes[clusterNodeId].containedNodes === undefined) { + console.log('The node:' + clusterNodeId + ' is not a cluster.'); + return; + } + var clusterNode = this.body.nodes[clusterNodeId]; + var containedNodes = clusterNode.containedNodes; + var containedEdges = clusterNode.containedEdges; + + // allow the user to position the nodes after release. + if (options.releaseFunction !== undefined) { + var positions = {}; + var clusterPosition = { x: clusterNode.x, y: clusterNode.y }; + for (var nodeId in containedNodes) { + if (containedNodes.hasOwnProperty(nodeId)) { + var containedNode = this.body.nodes[nodeId]; + positions[nodeId] = { x: containedNode.x, y: containedNode.y }; + } + } + var newPositions = options.releaseFunction(clusterPosition, positions); + + for (var nodeId in containedNodes) { + if (containedNodes.hasOwnProperty(nodeId)) { + var containedNode = this.body.nodes[nodeId]; + if (newPositions[nodeId] !== undefined) { + containedNode.x = newPositions[nodeId].x || clusterNode.x; + containedNode.y = newPositions[nodeId].y || clusterNode.y; + } + } + } + } else { + // copy the position from the cluster + for (var nodeId in containedNodes) { + if (containedNodes.hasOwnProperty(nodeId)) { + var containedNode = this.body.nodes[nodeId]; + containedNode = containedNodes[nodeId]; + // inherit position + containedNode.x = clusterNode.x; + containedNode.y = clusterNode.y; + } + } + } + + // release nodes + for (var nodeId in containedNodes) { + if (containedNodes.hasOwnProperty(nodeId)) { + var containedNode = this.body.nodes[nodeId]; + + // inherit speed + containedNode.vx = clusterNode.vx; + containedNode.vy = clusterNode.vy; + + containedNode.options.hidden = false; + containedNode.togglePhysics(true); + + delete this.clusteredNodes[nodeId]; + } + } + + // release edges + for (var edgeId in containedEdges) { + if (containedEdges.hasOwnProperty(edgeId)) { + var edge = containedEdges[edgeId]; + // if this edge was a temporary edge and it's connected nodes do not exist anymore, we remove it from the data + if (this.body.nodes[edge.fromId] === undefined || this.body.nodes[edge.toId] === undefined) { + edge.edgeType.cleanup(); + // this removes the edge from node.edges, which is why edgeIds is formed + edge.disconnect(); + delete this.body.edges[edgeId]; + } else { + // one of the nodes connected to this edge is in a cluster. We give the edge to that cluster so it will be released when that cluster is opened. + if (this.clusteredNodes[edge.fromId] !== undefined || this.clusteredNodes[edge.toId] !== undefined) { + var fromId = undefined, + toId = undefined; + var clusteredNode = this.clusteredNodes[edge.fromId] || this.clusteredNodes[edge.toId]; + var clusterId = clusteredNode.clusterId; + var _clusterNode = this.body.nodes[clusterId]; + _clusterNode.containedEdges[edgeId] = edge; + + if (this.clusteredNodes[edge.fromId] !== undefined) { + fromId = clusterId; + toId = edge.toId; + } else { + fromId = edge.fromId; + toId = clusterId; + } + + // if both from and to nodes are visible, we create a new temporary edge + if (this.body.nodes[fromId].options.hidden !== true && this.body.nodes[toId].options.hidden !== true) { + var clonedOptions = this._cloneOptions(edge, 'edge'); + var id = 'clusterEdge:' + util.randomUUID(); + util.deepExtend(clonedOptions, _clusterNode.clusterEdgeProperties); + util.deepExtend(clonedOptions, { from: fromId, to: toId, hidden: false, physics: true, id: id }); + var newEdge = this.body.functions.createEdge(clonedOptions); + + this.body.edges[id] = newEdge; + this.body.edges[id].connect(); + } + } else { + edge.options.hidden = false; + edge.togglePhysics(true); + } + } + } + } + + // remove all temporary edges + for (var i = 0; i < clusterNode.edges.length; i++) { + var edgeId = clusterNode.edges[i].id; + this.body.edges[edgeId].edgeType.cleanup(); + // this removes the edge from node.edges, which is why edgeIds is formed + this.body.edges[edgeId].disconnect(); + delete this.body.edges[edgeId]; + } + + // remove clusterNode + delete this.body.nodes[clusterNodeId]; + + if (refreshData === true) { + this.body.emitter.emit('_dataChanged'); + } + } + }, { + key: 'getNodesInCluster', + value: function getNodesInCluster(clusterId) { + var nodesArray = []; + if (this.isCluster(clusterId) === true) { + var containedNodes = this.body.nodes[clusterId].containedNodes; + for (var nodeId in containedNodes) { + if (containedNodes.hasOwnProperty(nodeId)) { + nodesArray.push(nodeId); + } + } + } + + return nodesArray; + } + }, { + key: 'findNode', + + /** + * Get the stack clusterId's that a certain node resides in. cluster A -> cluster B -> cluster C -> node + * @param nodeId + * @returns {Array} + * @private + */ + value: function findNode(nodeId) { + var stack = []; + var max = 100; + var counter = 0; + + while (this.clusteredNodes[nodeId] !== undefined && counter < max) { + stack.push(this.clusteredNodes[nodeId].node); + nodeId = this.clusteredNodes[nodeId].clusterId; + counter++; + } + stack.push(this.body.nodes[nodeId]); + return stack; + } + }, { + key: '_getConnectedId', + + /** + * Get the Id the node is connected to + * @param edge + * @param nodeId + * @returns {*} + * @private + */ + value: function _getConnectedId(edge, nodeId) { + if (edge.toId != nodeId) { + return edge.toId; + } else if (edge.fromId != nodeId) { + return edge.fromId; + } else { + return edge.fromId; + } + } + }, { + key: '_getHubSize', + + /** + * We determine how many connections denote an important hub. + * We take the mean + 2*std as the important hub size. (Assuming a normal distribution of data, ~2.2%) + * + * @private + */ + value: function _getHubSize() { + var average = 0; + var averageSquared = 0; + var hubCounter = 0; + var largestHub = 0; + + for (var i = 0; i < this.body.nodeIndices.length; i++) { + var node = this.body.nodes[this.body.nodeIndices[i]]; + if (node.edges.length > largestHub) { + largestHub = node.edges.length; + } + average += node.edges.length; + averageSquared += Math.pow(node.edges.length, 2); + hubCounter += 1; + } + average = average / hubCounter; + averageSquared = averageSquared / hubCounter; + + var letiance = averageSquared - Math.pow(average, 2); + var standardDeviation = Math.sqrt(letiance); + + var hubThreshold = Math.floor(average + 2 * standardDeviation); + + // always have at least one to cluster + if (hubThreshold > largestHub) { + hubThreshold = largestHub; + } + + return hubThreshold; + } + }]); + + return ClusterEngine; + })(); + + exports['default'] = ClusterEngine; + module.exports = exports['default']; + +/***/ }, +/* 7 */ +/***/ function(module, exports, __webpack_require__) { + + "use strict"; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + var Node = __webpack_require__(9); + var Edge = __webpack_require__(87); + var util = __webpack_require__(3); + + var SelectionHandler = (function () { + function SelectionHandler(body, canvas) { + var _this = this; + + _classCallCheck(this, SelectionHandler); + + this.body = body; + this.canvas = canvas; + this.selectionObj = { nodes: [], edges: [] }; + this.hoverObj = { nodes: {}, edges: {} }; + + this.options = {}; + this.defaultOptions = { + multiselect: false, + selectable: true, + selectConnectedEdges: true, + hoverConnectedEdges: true + }; + util.extend(this.options, this.defaultOptions); + + this.body.emitter.on("_dataChanged", function () { + _this.updateSelection(); + }); + } + + _createClass(SelectionHandler, [{ + key: "setOptions", + value: function setOptions(options) { + if (options !== undefined) { + var fields = ["multiselect", "hoverConnectedEdges", "selectable", "selectConnectedEdges"]; + util.selectiveDeepExtend(fields, this.options, options); + } + } + }, { + key: "selectOnPoint", + + /** + * handles the selection part of the tap; + * + * @param {Object} pointer + * @private + */ + value: function selectOnPoint(pointer) { + var selected = false; + if (this.options.selectable === true) { + var obj = this.getNodeAt(pointer) || this.getEdgeAt(pointer); + + // unselect after getting the objects in order to restore width and height. + this.unselectAll(); + + if (obj !== undefined) { + selected = this.selectObject(obj); + } + this.body.emitter.emit("_requestRedraw"); + } + return selected; + } + }, { + key: "selectAdditionalOnPoint", + value: function selectAdditionalOnPoint(pointer) { + var selectionChanged = false; + if (this.options.selectable === true) { + var obj = this.getNodeAt(pointer) || this.getEdgeAt(pointer); + + if (obj !== undefined) { + selectionChanged = true; + if (obj.isSelected() === true) { + this.deselectObject(obj); + } else { + this.selectObject(obj); + } + + this.body.emitter.emit("_requestRedraw"); + } + } + return selectionChanged; + } + }, { + key: "_generateClickEvent", + value: function _generateClickEvent(eventType, event, pointer, oldSelection) { + var properties = this.getSelection(); + properties["pointer"] = { + DOM: { x: pointer.x, y: pointer.y }, + canvas: this.canvas.DOMtoCanvas(pointer) + }; + properties["event"] = event; + + if (oldSelection !== undefined) { + properties["previousSelection"] = oldSelection; + } + this.body.emitter.emit(eventType, properties); + } + }, { + key: "selectObject", + value: function selectObject(obj) { + var highlightEdges = arguments[1] === undefined ? this.options.selectConnectedEdges : arguments[1]; + + if (obj !== undefined) { + if (obj instanceof Node) { + if (highlightEdges === true) { + this._selectConnectedEdges(obj); + } + } + obj.select(); + this._addToSelection(obj); + return true; + } + return false; + } + }, { + key: "deselectObject", + value: function deselectObject(obj) { + if (obj.isSelected() === true) { + obj.selected = false; + this._removeFromSelection(obj); + } + } + }, { + key: "_getAllNodesOverlappingWith", + + /** + * retrieve all nodes overlapping with given object + * @param {Object} object An object with parameters left, top, right, bottom + * @return {Number[]} An array with id's of the overlapping nodes + * @private + */ + value: function _getAllNodesOverlappingWith(object) { + var overlappingNodes = []; + var nodes = this.body.nodes; + for (var i = 0; i < this.body.nodeIndices.length; i++) { + var nodeId = this.body.nodeIndices[i]; + if (nodes[nodeId].isOverlappingWith(object)) { + overlappingNodes.push(nodeId); + } + } + return overlappingNodes; + } + }, { + key: "_pointerToPositionObject", + + /** + * Return a position object in canvasspace from a single point in screenspace + * + * @param pointer + * @returns {{left: number, top: number, right: number, bottom: number}} + * @private + */ + value: function _pointerToPositionObject(pointer) { + var canvasPos = this.canvas.DOMtoCanvas(pointer); + return { + left: canvasPos.x - 1, + top: canvasPos.y + 1, + right: canvasPos.x + 1, + bottom: canvasPos.y - 1 + }; + } + }, { + key: "getNodeAt", + + /** + * Get the top node at the a specific point (like a click) + * + * @param {{x: Number, y: Number}} pointer + * @return {Node | undefined} node + * @private + */ + value: function getNodeAt(pointer) { + var returnNode = arguments[1] === undefined ? true : arguments[1]; + + // we first check if this is an navigation controls element + var positionObject = this._pointerToPositionObject(pointer); + var overlappingNodes = this._getAllNodesOverlappingWith(positionObject); + // if there are overlapping nodes, select the last one, this is the + // one which is drawn on top of the others + if (overlappingNodes.length > 0) { + if (returnNode === true) { + return this.body.nodes[overlappingNodes[overlappingNodes.length - 1]]; + } else { + return overlappingNodes[overlappingNodes.length - 1]; + } + } else { + return undefined; + } + } + }, { + key: "_getEdgesOverlappingWith", + + /** + * retrieve all edges overlapping with given object, selector is around center + * @param {Object} object An object with parameters left, top, right, bottom + * @return {Number[]} An array with id's of the overlapping nodes + * @private + */ + value: function _getEdgesOverlappingWith(object, overlappingEdges) { + var edges = this.body.edges; + for (var i = 0; i < this.body.edgeIndices.length; i++) { + var edgeId = this.body.edgeIndices[i]; + if (edges[edgeId].isOverlappingWith(object)) { + overlappingEdges.push(edgeId); + } + } + } + }, { + key: "_getAllEdgesOverlappingWith", + + /** + * retrieve all nodes overlapping with given object + * @param {Object} object An object with parameters left, top, right, bottom + * @return {Number[]} An array with id's of the overlapping nodes + * @private + */ + value: function _getAllEdgesOverlappingWith(object) { + var overlappingEdges = []; + this._getEdgesOverlappingWith(object, overlappingEdges); + return overlappingEdges; + } + }, { + key: "getEdgeAt", + + /** + * Place holder. To implement change the getNodeAt to a _getObjectAt. Have the _getObjectAt call + * getNodeAt and _getEdgesAt, then priortize the selection to user preferences. + * + * @param pointer + * @returns {undefined} + * @private + */ + value: function getEdgeAt(pointer) { + var returnEdge = arguments[1] === undefined ? true : arguments[1]; + + var positionObject = this._pointerToPositionObject(pointer); + var overlappingEdges = this._getAllEdgesOverlappingWith(positionObject); + + if (overlappingEdges.length > 0) { + if (returnEdge === true) { + return this.body.edges[overlappingEdges[overlappingEdges.length - 1]]; + } else { + return overlappingEdges[overlappingEdges.length - 1]; + } + } else { + return undefined; + } + } + }, { + key: "_addToSelection", + + /** + * Add object to the selection array. + * + * @param obj + * @private + */ + value: function _addToSelection(obj) { + if (obj instanceof Node) { + this.selectionObj.nodes[obj.id] = obj; + } else { + this.selectionObj.edges[obj.id] = obj; + } + } + }, { + key: "_addToHover", + + /** + * Add object to the selection array. + * + * @param obj + * @private + */ + value: function _addToHover(obj) { + if (obj instanceof Node) { + this.hoverObj.nodes[obj.id] = obj; + } else { + this.hoverObj.edges[obj.id] = obj; + } + } + }, { + key: "_removeFromSelection", + + /** + * Remove a single option from selection. + * + * @param {Object} obj + * @private + */ + value: function _removeFromSelection(obj) { + if (obj instanceof Node) { + delete this.selectionObj.nodes[obj.id]; + } else { + delete this.selectionObj.edges[obj.id]; + } + } + }, { + key: "unselectAll", + + /** + * Unselect all. The selectionObj is useful for this. + * + * @private + */ + value: function unselectAll() { + for (var nodeId in this.selectionObj.nodes) { + if (this.selectionObj.nodes.hasOwnProperty(nodeId)) { + this.selectionObj.nodes[nodeId].unselect(); + } + } + for (var edgeId in this.selectionObj.edges) { + if (this.selectionObj.edges.hasOwnProperty(edgeId)) { + this.selectionObj.edges[edgeId].unselect(); + } + } + + this.selectionObj = { nodes: {}, edges: {} }; + } + }, { + key: "_getSelectedNodeCount", + + /** + * return the number of selected nodes + * + * @returns {number} + * @private + */ + value: function _getSelectedNodeCount() { + var count = 0; + for (var nodeId in this.selectionObj.nodes) { + if (this.selectionObj.nodes.hasOwnProperty(nodeId)) { + count += 1; + } + } + return count; + } + }, { + key: "_getSelectedNode", + + /** + * return the selected node + * + * @returns {number} * @private */ value: function _getSelectedNode() { @@ -2756,227 +4283,619 @@ return /******/ (function(modules) { // webpackBootstrap } } } - return idArray; + return idArray; + } + }, { + key: "getSelectedEdges", + + /** + * + * retrieve the currently selected edges + * @return {Array} selection An array with the ids of the + * selected nodes. + */ + value: function getSelectedEdges() { + var idArray = []; + if (this.options.selectable === true) { + for (var edgeId in this.selectionObj.edges) { + if (this.selectionObj.edges.hasOwnProperty(edgeId)) { + idArray.push(edgeId); + } + } + } + return idArray; + } + }, { + key: "selectNodes", + + /** + * select zero or more nodes with the option to highlight edges + * @param {Number[] | String[]} selection An array with the ids of the + * selected nodes. + * @param {boolean} [highlightEdges] + */ + value: function selectNodes(selection) { + var highlightEdges = arguments[1] === undefined ? true : arguments[1]; + + var i = undefined, + id = undefined; + + if (!selection || selection.length === undefined) throw "Selection must be an array with ids"; + + // first unselect any selected node + this.unselectAll(); + + for (i = 0; i < selection.length; i++) { + id = selection[i]; + + var node = this.body.nodes[id]; + if (!node) { + throw new RangeError("Node with id \"" + id + "\" not found"); + } + this.selectObject(node, highlightEdges); + } + this.body.emitter.emit("_requestRedraw"); + } + }, { + key: "selectEdges", + + /** + * select zero or more edges + * @param {Number[] | String[]} selection An array with the ids of the + * selected nodes. + */ + value: function selectEdges(selection) { + var i = undefined, + id = undefined; + + if (!selection || selection.length === undefined) throw "Selection must be an array with ids"; + + // first unselect any selected objects + this.unselectAll(); + + for (i = 0; i < selection.length; i++) { + id = selection[i]; + + var edge = this.body.edges[id]; + if (!edge) { + throw new RangeError("Edge with id \"" + id + "\" not found"); + } + this.selectObject(edge); + } + this.body.emitter.emit("_requestRedraw"); + } + }, { + key: "updateSelection", + + /** + * Validate the selection: remove ids of nodes which no longer exist + * @private + */ + value: function updateSelection() { + for (var nodeId in this.selectionObj.nodes) { + if (this.selectionObj.nodes.hasOwnProperty(nodeId)) { + if (!this.body.nodes.hasOwnProperty(nodeId)) { + delete this.selectionObj.nodes[nodeId]; + } + } + } + for (var edgeId in this.selectionObj.edges) { + if (this.selectionObj.edges.hasOwnProperty(edgeId)) { + if (!this.body.edges.hasOwnProperty(edgeId)) { + delete this.selectionObj.edges[edgeId]; + } + } + } + } + }]); + + return SelectionHandler; + })(); + + exports["default"] = SelectionHandler; + module.exports = exports["default"]; + +/***/ }, +/* 8 */ +/***/ function(module, exports, __webpack_require__) { + + "use strict"; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + var BarnesHutSolver = (function () { + function BarnesHutSolver(body, physicsBody, options) { + _classCallCheck(this, BarnesHutSolver); + + this.body = body; + this.physicsBody = physicsBody; + this.barnesHutTree; + this.setOptions(options); + this.randomSeed = 5; + } + + _createClass(BarnesHutSolver, [{ + key: "setOptions", + value: function setOptions(options) { + this.options = options; + this.thetaInversed = 1 / this.options.theta; + this.overlapAvoidanceFactor = 1 - Math.max(0, Math.min(1, this.options.avoidOverlap)); // if 1 then min distance = 0.5, if 0.5 then min distance = 0.5 + 0.5*node.shape.radius + } + }, { + key: "seededRandom", + value: function seededRandom() { + var x = Math.sin(this.randomSeed++) * 10000; + return x - Math.floor(x); + } + }, { + key: "solve", + + /** + * This function calculates the forces the nodes apply on eachother based on a gravitational model. + * The Barnes Hut method is used to speed up this N-body simulation. + * + * @private + */ + value: function solve() { + if (this.options.gravitationalConstant !== 0 && this.physicsBody.physicsNodeIndices.length > 0) { + var node = undefined; + var nodes = this.body.nodes; + var nodeIndices = this.physicsBody.physicsNodeIndices; + var nodeCount = nodeIndices.length; + + // create the tree + var barnesHutTree = this._formBarnesHutTree(nodes, nodeIndices); + + // for debugging + this.barnesHutTree = barnesHutTree; + + // place the nodes one by one recursively + for (var i = 0; i < nodeCount; i++) { + node = nodes[nodeIndices[i]]; + if (node.options.mass > 0) { + // starting with root is irrelevant, it never passes the BarnesHutSolver condition + this._getForceContribution(barnesHutTree.root.children.NW, node); + this._getForceContribution(barnesHutTree.root.children.NE, node); + this._getForceContribution(barnesHutTree.root.children.SW, node); + this._getForceContribution(barnesHutTree.root.children.SE, node); + } + } + } + } + }, { + key: "_getForceContribution", + + /** + * This function traverses the barnesHutTree. It checks when it can approximate distant nodes with their center of mass. + * If a region contains a single node, we check if it is not itself, then we apply the force. + * + * @param parentBranch + * @param node + * @private + */ + value: function _getForceContribution(parentBranch, node) { + // we get no force contribution from an empty region + if (parentBranch.childrenCount > 0) { + var dx = undefined, + dy = undefined, + distance = undefined; + + // get the distance from the center of mass to the node. + dx = parentBranch.centerOfMass.x - node.x; + dy = parentBranch.centerOfMass.y - node.y; + distance = Math.sqrt(dx * dx + dy * dy); + + // BarnesHutSolver condition + // original condition : s/d < theta = passed === d/s > 1/theta = passed + // calcSize = 1/s --> d * 1/s > 1/theta = passed + if (distance * parentBranch.calcSize > this.thetaInversed) { + this._calculateForces(distance, dx, dy, node, parentBranch); + } else { + // Did not pass the condition, go into children if available + if (parentBranch.childrenCount === 4) { + this._getForceContribution(parentBranch.children.NW, node); + this._getForceContribution(parentBranch.children.NE, node); + this._getForceContribution(parentBranch.children.SW, node); + this._getForceContribution(parentBranch.children.SE, node); + } else { + // parentBranch must have only one node, if it was empty we wouldnt be here + if (parentBranch.children.data.id != node.id) { + // if it is not self + this._calculateForces(distance, dx, dy, node, parentBranch); + } + } + } + } + } + }, { + key: "_calculateForces", + + /** + * Calculate the forces based on the distance. + * + * @param distance + * @param dx + * @param dy + * @param node + * @param parentBranch + * @private + */ + value: function _calculateForces(distance, dx, dy, node, parentBranch) { + if (distance === 0) { + distance = 0.1; + dx = distance; + } + + if (this.overlapAvoidanceFactor < 1) { + distance = Math.max(0.1 + this.overlapAvoidanceFactor * node.shape.radius, distance - node.shape.radius); + } + + // the dividing by the distance cubed instead of squared allows us to get the fx and fy components without sines and cosines + // it is shorthand for gravityforce with distance squared and fx = dx/distance * gravityForce + var gravityForce = this.options.gravitationalConstant * parentBranch.mass * node.options.mass / Math.pow(distance, 3); + var fx = dx * gravityForce; + var fy = dy * gravityForce; + + this.physicsBody.forces[node.id].x += fx; + this.physicsBody.forces[node.id].y += fy; + } + }, { + key: "_formBarnesHutTree", + + /** + * This function constructs the barnesHut tree recursively. It creates the root, splits it and starts placing the nodes. + * + * @param nodes + * @param nodeIndices + * @private + */ + value: function _formBarnesHutTree(nodes, nodeIndices) { + var node = undefined; + var nodeCount = nodeIndices.length; + + var minX = nodes[nodeIndices[0]].x; + var minY = nodes[nodeIndices[0]].y; + var maxX = nodes[nodeIndices[0]].x; + var maxY = nodes[nodeIndices[0]].y; + + // get the range of the nodes + for (var i = 1; i < nodeCount; i++) { + var x = nodes[nodeIndices[i]].x; + var y = nodes[nodeIndices[i]].y; + if (nodes[nodeIndices[i]].options.mass > 0) { + if (x < minX) { + minX = x; + } + if (x > maxX) { + maxX = x; + } + if (y < minY) { + minY = y; + } + if (y > maxY) { + maxY = y; + } + } + } + // make the range a square + var sizeDiff = Math.abs(maxX - minX) - Math.abs(maxY - minY); // difference between X and Y + if (sizeDiff > 0) { + minY -= 0.5 * sizeDiff; + maxY += 0.5 * sizeDiff; + } // xSize > ySize + else { + minX += 0.5 * sizeDiff; + maxX -= 0.5 * sizeDiff; + } // xSize < ySize + + var minimumTreeSize = 0.00001; + var rootSize = Math.max(minimumTreeSize, Math.abs(maxX - minX)); + var halfRootSize = 0.5 * rootSize; + var centerX = 0.5 * (minX + maxX), + centerY = 0.5 * (minY + maxY); + + // construct the barnesHutTree + var barnesHutTree = { + root: { + centerOfMass: { x: 0, y: 0 }, + mass: 0, + range: { + minX: centerX - halfRootSize, maxX: centerX + halfRootSize, + minY: centerY - halfRootSize, maxY: centerY + halfRootSize + }, + size: rootSize, + calcSize: 1 / rootSize, + children: { data: null }, + maxWidth: 0, + level: 0, + childrenCount: 4 + } + }; + this._splitBranch(barnesHutTree.root); + + // place the nodes one by one recursively + for (var i = 0; i < nodeCount; i++) { + node = nodes[nodeIndices[i]]; + if (node.options.mass > 0) { + this._placeInTree(barnesHutTree.root, node); + } + } + + // make global + return barnesHutTree; + } + }, { + key: "_updateBranchMass", + + /** + * this updates the mass of a branch. this is increased by adding a node. + * + * @param parentBranch + * @param node + * @private + */ + value: function _updateBranchMass(parentBranch, node) { + var totalMass = parentBranch.mass + node.options.mass; + var totalMassInv = 1 / totalMass; + + parentBranch.centerOfMass.x = parentBranch.centerOfMass.x * parentBranch.mass + node.x * node.options.mass; + parentBranch.centerOfMass.x *= totalMassInv; + + parentBranch.centerOfMass.y = parentBranch.centerOfMass.y * parentBranch.mass + node.y * node.options.mass; + parentBranch.centerOfMass.y *= totalMassInv; + + parentBranch.mass = totalMass; + var biggestSize = Math.max(Math.max(node.height, node.radius), node.width); + parentBranch.maxWidth = parentBranch.maxWidth < biggestSize ? biggestSize : parentBranch.maxWidth; + } + }, { + key: "_placeInTree", + + /** + * determine in which branch the node will be placed. + * + * @param parentBranch + * @param node + * @param skipMassUpdate + * @private + */ + value: function _placeInTree(parentBranch, node, skipMassUpdate) { + if (skipMassUpdate != true || skipMassUpdate === undefined) { + // update the mass of the branch. + this._updateBranchMass(parentBranch, node); + } + + if (parentBranch.children.NW.range.maxX > node.x) { + // in NW or SW + if (parentBranch.children.NW.range.maxY > node.y) { + // in NW + this._placeInRegion(parentBranch, node, "NW"); + } else { + // in SW + this._placeInRegion(parentBranch, node, "SW"); + } + } else { + // in NE or SE + if (parentBranch.children.NW.range.maxY > node.y) { + // in NE + this._placeInRegion(parentBranch, node, "NE"); + } else { + // in SE + this._placeInRegion(parentBranch, node, "SE"); + } + } + } + }, { + key: "_placeInRegion", + + /** + * actually place the node in a region (or branch) + * + * @param parentBranch + * @param node + * @param region + * @private + */ + value: function _placeInRegion(parentBranch, node, region) { + switch (parentBranch.children[region].childrenCount) { + case 0: + // place node here + parentBranch.children[region].children.data = node; + parentBranch.children[region].childrenCount = 1; + this._updateBranchMass(parentBranch.children[region], node); + break; + case 1: + // convert into children + // if there are two nodes exactly overlapping (on init, on opening of cluster etc.) + // we move one node a pixel and we do not put it in the tree. + if (parentBranch.children[region].children.data.x === node.x && parentBranch.children[region].children.data.y === node.y) { + node.x += this.seededRandom(); + node.y += this.seededRandom(); + } else { + this._splitBranch(parentBranch.children[region]); + this._placeInTree(parentBranch.children[region], node); + } + break; + case 4: + // place in branch + this._placeInTree(parentBranch.children[region], node); + break; + } } }, { - key: "getSelectedEdges", + key: "_splitBranch", /** + * this function splits a branch into 4 sub branches. If the branch contained a node, we place it in the subbranch + * after the split is complete. * - * retrieve the currently selected edges - * @return {Array} selection An array with the ids of the - * selected nodes. + * @param parentBranch + * @private */ - value: function getSelectedEdges() { - var idArray = []; - if (this.options.selectable === true) { - for (var edgeId in this.selectionObj.edges) { - if (this.selectionObj.edges.hasOwnProperty(edgeId)) { - idArray.push(edgeId); - } - } + value: function _splitBranch(parentBranch) { + // if the branch is shaded with a node, replace the node in the new subset. + var containedNode = null; + if (parentBranch.childrenCount === 1) { + containedNode = parentBranch.children.data; + parentBranch.mass = 0; + parentBranch.centerOfMass.x = 0; + parentBranch.centerOfMass.y = 0; + } + parentBranch.childrenCount = 4; + parentBranch.children.data = null; + this._insertRegion(parentBranch, "NW"); + this._insertRegion(parentBranch, "NE"); + this._insertRegion(parentBranch, "SW"); + this._insertRegion(parentBranch, "SE"); + + if (containedNode != null) { + this._placeInTree(parentBranch, containedNode); } - return idArray; } }, { - key: "selectNodes", + key: "_insertRegion", /** - * select zero or more nodes with the option to highlight edges - * @param {Number[] | String[]} selection An array with the ids of the - * selected nodes. - * @param {boolean} [highlightEdges] + * This function subdivides the region into four new segments. + * Specifically, this inserts a single new segment. + * It fills the children section of the parentBranch + * + * @param parentBranch + * @param region + * @param parentRange + * @private */ - value: function selectNodes(selection) { - var highlightEdges = arguments[1] === undefined ? true : arguments[1]; - - var i = undefined, - id = undefined; - - if (!selection || selection.length === undefined) throw "Selection must be an array with ids"; - - // first unselect any selected node - this.unselectAll(); - - for (i = 0; i < selection.length; i++) { - id = selection[i]; - - var node = this.body.nodes[id]; - if (!node) { - throw new RangeError("Node with id \"" + id + "\" not found"); - } - this.selectObject(node, highlightEdges); + value: function _insertRegion(parentBranch, region) { + var minX = undefined, + maxX = undefined, + minY = undefined, + maxY = undefined; + var childSize = 0.5 * parentBranch.size; + switch (region) { + case "NW": + minX = parentBranch.range.minX; + maxX = parentBranch.range.minX + childSize; + minY = parentBranch.range.minY; + maxY = parentBranch.range.minY + childSize; + break; + case "NE": + minX = parentBranch.range.minX + childSize; + maxX = parentBranch.range.maxX; + minY = parentBranch.range.minY; + maxY = parentBranch.range.minY + childSize; + break; + case "SW": + minX = parentBranch.range.minX; + maxX = parentBranch.range.minX + childSize; + minY = parentBranch.range.minY + childSize; + maxY = parentBranch.range.maxY; + break; + case "SE": + minX = parentBranch.range.minX + childSize; + maxX = parentBranch.range.maxX; + minY = parentBranch.range.minY + childSize; + maxY = parentBranch.range.maxY; + break; } - this.body.emitter.emit("_requestRedraw"); + + parentBranch.children[region] = { + centerOfMass: { x: 0, y: 0 }, + mass: 0, + range: { minX: minX, maxX: maxX, minY: minY, maxY: maxY }, + size: 0.5 * parentBranch.size, + calcSize: 2 * parentBranch.calcSize, + children: { data: null }, + maxWidth: 0, + level: parentBranch.level + 1, + childrenCount: 0 + }; } }, { - key: "selectEdges", + key: "_debug", + + //--------------------------- DEBUGGING BELOW ---------------------------// /** - * select zero or more edges - * @param {Number[] | String[]} selection An array with the ids of the - * selected nodes. + * This function is for debugging purposed, it draws the tree. + * + * @param ctx + * @param color + * @private */ - value: function selectEdges(selection) { - var i = undefined, - id = undefined; - - if (!selection || selection.length === undefined) throw "Selection must be an array with ids"; - - // first unselect any selected objects - this.unselectAll(); + value: function _debug(ctx, color) { + if (this.barnesHutTree !== undefined) { - for (i = 0; i < selection.length; i++) { - id = selection[i]; + ctx.lineWidth = 1; - var edge = this.body.edges[id]; - if (!edge) { - throw new RangeError("Edge with id \"" + id + "\" not found"); - } - this.selectObject(edge); + this._drawBranch(this.barnesHutTree.root, ctx, color); } - this.body.emitter.emit("_requestRedraw"); } }, { - key: "updateSelection", + key: "_drawBranch", /** - * Validate the selection: remove ids of nodes which no longer exist + * This function is for debugging purposes. It draws the branches recursively. + * + * @param branch + * @param ctx + * @param color * @private */ - value: function updateSelection() { - for (var nodeId in this.selectionObj.nodes) { - if (this.selectionObj.nodes.hasOwnProperty(nodeId)) { - if (!this.body.nodes.hasOwnProperty(nodeId)) { - delete this.selectionObj.nodes[nodeId]; - } - } - } - for (var edgeId in this.selectionObj.edges) { - if (this.selectionObj.edges.hasOwnProperty(edgeId)) { - if (!this.body.edges.hasOwnProperty(edgeId)) { - delete this.selectionObj.edges[edgeId]; - } - } + value: function _drawBranch(branch, ctx, color) { + if (color === undefined) { + color = "#FF0000"; } - } - }]); - - return SelectionHandler; - })(); - - exports["default"] = SelectionHandler; - module.exports = exports["default"]; - -/***/ }, -/* 5 */ -/***/ function(module, exports, __webpack_require__) { - - 'use strict'; - - Object.defineProperty(exports, '__esModule', { - value: true - }); - - var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); - - var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; - - function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - - function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } - - var _utilNodeBase = __webpack_require__(68); - - var _utilNodeBase2 = _interopRequireDefault(_utilNodeBase); - - var Ellipse = (function (_NodeBase) { - function Ellipse(options, body, labelModule) { - _classCallCheck(this, Ellipse); - - _get(Object.getPrototypeOf(Ellipse.prototype), 'constructor', this).call(this, options, body, labelModule); - } - - _inherits(Ellipse, _NodeBase); - - _createClass(Ellipse, [{ - key: 'resize', - value: function resize(ctx, selected) { - if (this.width === undefined) { - var textSize = this.labelModule.getTextSize(ctx, selected); - this.width = textSize.width * 1.5; - this.height = textSize.height * 2; - if (this.width < this.height) { - this.width = this.height; - } - this.radius = 0.5 * this.width; + if (branch.childrenCount === 4) { + this._drawBranch(branch.children.NW, ctx); + this._drawBranch(branch.children.NE, ctx); + this._drawBranch(branch.children.SE, ctx); + this._drawBranch(branch.children.SW, ctx); } - } - }, { - key: 'draw', - value: function draw(ctx, x, y, selected, hover) { - this.resize(ctx, selected); - this.left = x - this.width * 0.5; - this.top = y - this.height * 0.5; - - var borderWidth = this.options.borderWidth; - var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth; - - ctx.strokeStyle = selected ? this.options.color.highlight.border : hover ? this.options.color.hover.border : this.options.color.border; - - ctx.lineWidth = selected ? selectionLineWidth : borderWidth; - ctx.lineWidth /= this.body.view.scale; - ctx.lineWidth = Math.min(this.width, ctx.lineWidth); - - ctx.fillStyle = selected ? this.options.color.highlight.background : hover ? this.options.color.hover.background : this.options.color.background; - ctx.ellipse(this.left, this.top, this.width, this.height); - - // draw shadow if enabled - this.enableShadow(ctx); - ctx.fill(); + ctx.strokeStyle = color; + ctx.beginPath(); + ctx.moveTo(branch.range.minX, branch.range.minY); + ctx.lineTo(branch.range.maxX, branch.range.minY); + ctx.stroke(); - // disable shadows for other elements. - this.disableShadow(ctx); + ctx.beginPath(); + ctx.moveTo(branch.range.maxX, branch.range.minY); + ctx.lineTo(branch.range.maxX, branch.range.maxY); + ctx.stroke(); + ctx.beginPath(); + ctx.moveTo(branch.range.maxX, branch.range.maxY); + ctx.lineTo(branch.range.minX, branch.range.maxY); ctx.stroke(); - this.updateBoundingBox(x, y); - this.labelModule.draw(ctx, x, y, selected); - } - }, { - key: 'updateBoundingBox', - value: function updateBoundingBox(x, y) { - this.left = x - this.width * 0.5; - this.top = y - this.height * 0.5; + ctx.beginPath(); + ctx.moveTo(branch.range.minX, branch.range.maxY); + ctx.lineTo(branch.range.minX, branch.range.minY); + ctx.stroke(); - this.boundingBox.left = this.left; - this.boundingBox.top = this.top; - this.boundingBox.bottom = this.top + this.height; - this.boundingBox.right = this.left + this.width; - } - }, { - key: 'distanceToBorder', - value: function distanceToBorder(ctx, angle) { - this.resize(ctx); - var a = this.width * 0.5; - var b = this.height * 0.5; - var w = Math.sin(angle) * a; - var h = Math.cos(angle) * b; - return a * b / Math.sqrt(w * w + h * h); + /* + if (branch.mass > 0) { + ctx.circle(branch.centerOfMass.x, branch.centerOfMass.y, 3*branch.mass); + ctx.stroke(); + } + */ } }]); - return Ellipse; - })(_utilNodeBase2['default']); + return BarnesHutSolver; + })(); - exports['default'] = Ellipse; - module.exports = exports['default']; + exports["default"] = BarnesHutSolver; + module.exports = exports["default"]; /***/ }, -/* 6 */ +/* 9 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -2991,71 +4910,71 @@ return /******/ (function(modules) { // webpackBootstrap function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - var _sharedLabel = __webpack_require__(66); + var _sharedLabel = __webpack_require__(69); var _sharedLabel2 = _interopRequireDefault(_sharedLabel); - var _nodesShapesBox = __webpack_require__(67); + var _nodesShapesBox = __webpack_require__(70); var _nodesShapesBox2 = _interopRequireDefault(_nodesShapesBox); - var _nodesShapesCircle = __webpack_require__(69); + var _nodesShapesCircle = __webpack_require__(72); var _nodesShapesCircle2 = _interopRequireDefault(_nodesShapesCircle); - var _nodesShapesCircularImage = __webpack_require__(71); + var _nodesShapesCircularImage = __webpack_require__(74); var _nodesShapesCircularImage2 = _interopRequireDefault(_nodesShapesCircularImage); - var _nodesShapesDatabase = __webpack_require__(72); + var _nodesShapesDatabase = __webpack_require__(75); var _nodesShapesDatabase2 = _interopRequireDefault(_nodesShapesDatabase); - var _nodesShapesDiamond = __webpack_require__(73); + var _nodesShapesDiamond = __webpack_require__(76); var _nodesShapesDiamond2 = _interopRequireDefault(_nodesShapesDiamond); - var _nodesShapesDot = __webpack_require__(75); + var _nodesShapesDot = __webpack_require__(78); var _nodesShapesDot2 = _interopRequireDefault(_nodesShapesDot); - var _nodesShapesEllipse = __webpack_require__(5); + var _nodesShapesEllipse = __webpack_require__(10); var _nodesShapesEllipse2 = _interopRequireDefault(_nodesShapesEllipse); - var _nodesShapesIcon = __webpack_require__(76); + var _nodesShapesIcon = __webpack_require__(79); var _nodesShapesIcon2 = _interopRequireDefault(_nodesShapesIcon); - var _nodesShapesImage = __webpack_require__(77); + var _nodesShapesImage = __webpack_require__(80); var _nodesShapesImage2 = _interopRequireDefault(_nodesShapesImage); - var _nodesShapesSquare = __webpack_require__(78); + var _nodesShapesSquare = __webpack_require__(81); var _nodesShapesSquare2 = _interopRequireDefault(_nodesShapesSquare); - var _nodesShapesStar = __webpack_require__(79); + var _nodesShapesStar = __webpack_require__(82); var _nodesShapesStar2 = _interopRequireDefault(_nodesShapesStar); - var _nodesShapesText = __webpack_require__(80); + var _nodesShapesText = __webpack_require__(83); var _nodesShapesText2 = _interopRequireDefault(_nodesShapesText); - var _nodesShapesTriangle = __webpack_require__(81); + var _nodesShapesTriangle = __webpack_require__(84); var _nodesShapesTriangle2 = _interopRequireDefault(_nodesShapesTriangle); - var _nodesShapesTriangleDown = __webpack_require__(82); + var _nodesShapesTriangleDown = __webpack_require__(85); var _nodesShapesTriangleDown2 = _interopRequireDefault(_nodesShapesTriangleDown); - var _sharedValidator = __webpack_require__(51); + var _sharedValidator = __webpack_require__(55); var _sharedValidator2 = _interopRequireDefault(_sharedValidator); - var util = __webpack_require__(2); + var util = __webpack_require__(3); /** * @class Node @@ -3177,7 +5096,7 @@ return /******/ (function(modules) { // webpackBootstrap this.baseSize = options.size; } if (options.value !== undefined) { - options.value = parseInt(options.value); + options.value = parseFloat(options.value); } // copy group options @@ -3496,17 +5415,124 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 7 */ +/* 10 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, '__esModule', { + value: true + }); + + var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + + var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + + function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } + + var _utilNodeBase = __webpack_require__(71); + + var _utilNodeBase2 = _interopRequireDefault(_utilNodeBase); + + var Ellipse = (function (_NodeBase) { + function Ellipse(options, body, labelModule) { + _classCallCheck(this, Ellipse); + + _get(Object.getPrototypeOf(Ellipse.prototype), 'constructor', this).call(this, options, body, labelModule); + } + + _inherits(Ellipse, _NodeBase); + + _createClass(Ellipse, [{ + key: 'resize', + value: function resize(ctx, selected) { + if (this.width === undefined) { + var textSize = this.labelModule.getTextSize(ctx, selected); + + this.width = textSize.width * 1.5; + this.height = textSize.height * 2; + if (this.width < this.height) { + this.width = this.height; + } + this.radius = 0.5 * this.width; + } + } + }, { + key: 'draw', + value: function draw(ctx, x, y, selected, hover) { + this.resize(ctx, selected); + this.left = x - this.width * 0.5; + this.top = y - this.height * 0.5; + + var borderWidth = this.options.borderWidth; + var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth; + + ctx.strokeStyle = selected ? this.options.color.highlight.border : hover ? this.options.color.hover.border : this.options.color.border; + + ctx.lineWidth = selected ? selectionLineWidth : borderWidth; + ctx.lineWidth /= this.body.view.scale; + ctx.lineWidth = Math.min(this.width, ctx.lineWidth); + + ctx.fillStyle = selected ? this.options.color.highlight.background : hover ? this.options.color.hover.background : this.options.color.background; + ctx.ellipse(this.left, this.top, this.width, this.height); + + // draw shadow if enabled + this.enableShadow(ctx); + ctx.fill(); + + // disable shadows for other elements. + this.disableShadow(ctx); + + ctx.stroke(); + + this.updateBoundingBox(x, y); + this.labelModule.draw(ctx, x, y, selected); + } + }, { + key: 'updateBoundingBox', + value: function updateBoundingBox(x, y) { + this.left = x - this.width * 0.5; + this.top = y - this.height * 0.5; + + this.boundingBox.left = this.left; + this.boundingBox.top = this.top; + this.boundingBox.bottom = this.top + this.height; + this.boundingBox.right = this.left + this.width; + } + }, { + key: 'distanceToBorder', + value: function distanceToBorder(ctx, angle) { + this.resize(ctx); + var a = this.width * 0.5; + var b = this.height * 0.5; + var w = Math.sin(angle) * a; + var h = Math.cos(angle) * b; + return a * b / Math.sqrt(w * w + h * h); + } + }]); + + return Ellipse; + })(_utilNodeBase2['default']); + + exports['default'] = Ellipse; + module.exports = exports['default']; + +/***/ }, +/* 11 */ /***/ function(module, exports, __webpack_require__) { // first check if moment.js is already loaded in the browser window, if so, // use this instance. Else, load via commonjs. 'use strict'; - module.exports = typeof window !== 'undefined' && window['moment'] || __webpack_require__(8); + module.exports = typeof window !== 'undefined' && window['moment'] || __webpack_require__(12); /***/ }, -/* 8 */ +/* 12 */ /***/ function(module, exports, __webpack_require__) { /* WEBPACK VAR INJECTION */(function(module) {//! moment.js @@ -6620,10 +8646,10 @@ return /******/ (function(modules) { // webpackBootstrap return _moment; })); - /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(9)(module))) + /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(13)(module))) /***/ }, -/* 9 */ +/* 13 */ /***/ function(module, exports, __webpack_require__) { module.exports = function(module) { @@ -6639,7 +8665,7 @@ return /******/ (function(modules) { // webpackBootstrap /***/ }, -/* 10 */ +/* 14 */ /***/ function(module, exports, __webpack_require__) { /* WEBPACK VAR INJECTION */(function(global) {'use strict'; @@ -6855,7 +8881,7 @@ return /******/ (function(modules) { // webpackBootstrap /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()))) /***/ }, -/* 11 */ +/* 15 */ /***/ function(module, exports, __webpack_require__) { // DOM utility methods @@ -7057,13 +9083,13 @@ return /******/ (function(modules) { // webpackBootstrap }; /***/ }, -/* 12 */ +/* 16 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var util = __webpack_require__(2); - var Queue = __webpack_require__(13); + var util = __webpack_require__(3); + var Queue = __webpack_require__(17); /** * DataSet @@ -7952,7 +9978,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = DataSet; /***/ }, -/* 13 */ +/* 17 */ /***/ function(module, exports, __webpack_require__) { /** @@ -8157,13 +10183,13 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = Queue; /***/ }, -/* 14 */ +/* 18 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var util = __webpack_require__(2); - var DataSet = __webpack_require__(12); + var util = __webpack_require__(3); + var DataSet = __webpack_require__(16); /** * DataView @@ -8505,21 +10531,21 @@ return /******/ (function(modules) { // webpackBootstrap // nothing interesting for me :-( /***/ }, -/* 15 */ +/* 19 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var Emitter = __webpack_require__(17); - var DataSet = __webpack_require__(12); - var DataView = __webpack_require__(14); - var util = __webpack_require__(2); - var Point3d = __webpack_require__(18); - var Point2d = __webpack_require__(16); - var Camera = __webpack_require__(19); - var Filter = __webpack_require__(20); - var Slider = __webpack_require__(21); - var StepNumber = __webpack_require__(22); + var Emitter = __webpack_require__(21); + var DataSet = __webpack_require__(16); + var DataView = __webpack_require__(18); + var util = __webpack_require__(3); + var Point3d = __webpack_require__(22); + var Point2d = __webpack_require__(20); + var Camera = __webpack_require__(23); + var Filter = __webpack_require__(24); + var Slider = __webpack_require__(25); + var StepNumber = __webpack_require__(26); /** * @constructor Graph3d @@ -10723,7 +12749,7 @@ return /******/ (function(modules) { // webpackBootstrap // use use defaults /***/ }, -/* 16 */ +/* 20 */ /***/ function(module, exports, __webpack_require__) { /** @@ -10741,7 +12767,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = Point2d; /***/ }, -/* 17 */ +/* 21 */ /***/ function(module, exports, __webpack_require__) { @@ -10911,7 +12937,7 @@ return /******/ (function(modules) { // webpackBootstrap /***/ }, -/* 18 */ +/* 22 */ /***/ function(module, exports, __webpack_require__) { /** @@ -10994,12 +13020,12 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = Point3d; /***/ }, -/* 19 */ +/* 23 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var Point3d = __webpack_require__(18); + var Point3d = __webpack_require__(22); /** * @class Camera @@ -11135,12 +13161,12 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = Camera; /***/ }, -/* 20 */ +/* 24 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var DataView = __webpack_require__(14); + var DataView = __webpack_require__(18); /** * @class Filter @@ -11346,12 +13372,12 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = Filter; /***/ }, -/* 21 */ +/* 25 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var util = __webpack_require__(2); + var util = __webpack_require__(3); /** * @constructor Slider @@ -11694,7 +13720,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = Slider; /***/ }, -/* 22 */ +/* 26 */ /***/ function(module, exports, __webpack_require__) { /** @@ -11838,28 +13864,28 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = StepNumber; /***/ }, -/* 23 */ +/* 27 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var Emitter = __webpack_require__(17); - var Hammer = __webpack_require__(27); - var util = __webpack_require__(2); - var DataSet = __webpack_require__(12); - var DataView = __webpack_require__(14); - var Range = __webpack_require__(31); - var Core = __webpack_require__(34); - var TimeAxis = __webpack_require__(45); - var CurrentTime = __webpack_require__(24); - var CustomTime = __webpack_require__(48); - var ItemSet = __webpack_require__(35); - - var Configurator = __webpack_require__(49); - var Validator = __webpack_require__(51)['default']; - var printStyle = __webpack_require__(51).printStyle; - var allOptions = __webpack_require__(52).allOptions; - var configureOptions = __webpack_require__(52).configureOptions; + var Emitter = __webpack_require__(21); + var Hammer = __webpack_require__(31); + var util = __webpack_require__(3); + var DataSet = __webpack_require__(16); + var DataView = __webpack_require__(18); + var Range = __webpack_require__(35); + var Core = __webpack_require__(38); + var TimeAxis = __webpack_require__(49); + var CurrentTime = __webpack_require__(28); + var CustomTime = __webpack_require__(52); + var ItemSet = __webpack_require__(39); + + var Configurator = __webpack_require__(53); + var Validator = __webpack_require__(55)['default']; + var printStyle = __webpack_require__(55).printStyle; + var allOptions = __webpack_require__(56).allOptions; + var configureOptions = __webpack_require__(56).configureOptions; /** * Create a timeline visualization @@ -12362,15 +14388,15 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = Timeline; /***/ }, -/* 24 */ +/* 28 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var util = __webpack_require__(2); - var Component = __webpack_require__(25); - var moment = __webpack_require__(7); - var locales = __webpack_require__(26); + var util = __webpack_require__(3); + var Component = __webpack_require__(29); + var moment = __webpack_require__(11); + var locales = __webpack_require__(30); /** * A current time bar @@ -12538,7 +14564,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = CurrentTime; /***/ }, -/* 25 */ +/* 29 */ /***/ function(module, exports, __webpack_require__) { /** @@ -12598,7 +14624,7 @@ return /******/ (function(modules) { // webpackBootstrap // should be implemented by the component /***/ }, -/* 26 */ +/* 30 */ /***/ function(module, exports, __webpack_require__) { // English @@ -12620,7 +14646,7 @@ return /******/ (function(modules) { // webpackBootstrap exports['nl_BE'] = exports['nl']; /***/ }, -/* 27 */ +/* 31 */ /***/ function(module, exports, __webpack_require__) { // Only load hammer.js when in a browser environment @@ -12628,8 +14654,8 @@ return /******/ (function(modules) { // webpackBootstrap 'use strict'; if (typeof window !== 'undefined') { - var propagating = __webpack_require__(28); - var Hammer = window['Hammer'] || __webpack_require__(29); + var propagating = __webpack_require__(32); + var Hammer = window['Hammer'] || __webpack_require__(33); module.exports = propagating(Hammer, { preventDefault: 'mouse' }); @@ -12640,7 +14666,7 @@ return /******/ (function(modules) { // webpackBootstrap } /***/ }, -/* 28 */ +/* 32 */ /***/ function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;'use strict'; @@ -12868,7 +14894,7 @@ return /******/ (function(modules) { // webpackBootstrap /***/ }, -/* 29 */ +/* 33 */ /***/ function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_RESULT__;/*! Hammer.JS - v2.0.4 - 2014-09-28 @@ -15323,7 +17349,7 @@ return /******/ (function(modules) { // webpackBootstrap prefixed: prefixed }); - if ("function" == TYPE_FUNCTION && __webpack_require__(30)) { + if ("function" == TYPE_FUNCTION && __webpack_require__(34)) { !(__WEBPACK_AMD_DEFINE_RESULT__ = function() { return Hammer; }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); @@ -15337,7 +17363,7 @@ return /******/ (function(modules) { // webpackBootstrap /***/ }, -/* 30 */ +/* 34 */ /***/ function(module, exports, __webpack_require__) { /* WEBPACK VAR INJECTION */(function(__webpack_amd_options__) {module.exports = __webpack_amd_options__; @@ -15345,16 +17371,16 @@ return /******/ (function(modules) { // webpackBootstrap /* WEBPACK VAR INJECTION */}.call(exports, {})) /***/ }, -/* 31 */ +/* 35 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var util = __webpack_require__(2); - var hammerUtil = __webpack_require__(32); - var moment = __webpack_require__(7); - var Component = __webpack_require__(25); - var DateUtil = __webpack_require__(33); + var util = __webpack_require__(3); + var hammerUtil = __webpack_require__(36); + var moment = __webpack_require__(11); + var Component = __webpack_require__(29); + var DateUtil = __webpack_require__(37); /** * @constructor Range @@ -16021,12 +18047,12 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = Range; /***/ }, -/* 32 */ +/* 36 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var Hammer = __webpack_require__(27); + var Hammer = __webpack_require__(31); /** * Register a touch event, taking place before a gesture @@ -16093,12 +18119,12 @@ return /******/ (function(modules) { // webpackBootstrap exports.offRelease = exports.offTouch; /***/ }, -/* 33 */ +/* 37 */ /***/ function(module, exports, __webpack_require__) { "use strict"; - var moment = __webpack_require__(7); + var moment = __webpack_require__(11); /** * used in Core to convert the options into a volatile variable @@ -16553,23 +18579,23 @@ return /******/ (function(modules) { // webpackBootstrap }; /***/ }, -/* 34 */ +/* 38 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var Emitter = __webpack_require__(17); - var Hammer = __webpack_require__(27); - var hammerUtil = __webpack_require__(32); - var util = __webpack_require__(2); - var DataSet = __webpack_require__(12); - var DataView = __webpack_require__(14); - var Range = __webpack_require__(31); - var ItemSet = __webpack_require__(35); - var TimeAxis = __webpack_require__(45); - var Activator = __webpack_require__(46); - var DateUtil = __webpack_require__(33); - var CustomTime = __webpack_require__(48); + var Emitter = __webpack_require__(21); + var Hammer = __webpack_require__(31); + var hammerUtil = __webpack_require__(36); + var util = __webpack_require__(3); + var DataSet = __webpack_require__(16); + var DataView = __webpack_require__(18); + var Range = __webpack_require__(35); + var ItemSet = __webpack_require__(39); + var TimeAxis = __webpack_require__(49); + var Activator = __webpack_require__(50); + var DateUtil = __webpack_require__(37); + var CustomTime = __webpack_require__(52); /** * Create a timeline visualization @@ -17524,23 +19550,23 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = Core; /***/ }, -/* 35 */ +/* 39 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var Hammer = __webpack_require__(27); - var util = __webpack_require__(2); - var DataSet = __webpack_require__(12); - var DataView = __webpack_require__(14); - var TimeStep = __webpack_require__(40); - var Component = __webpack_require__(25); - var Group = __webpack_require__(36); - var BackgroundGroup = __webpack_require__(41); - var BoxItem = __webpack_require__(42); - var PointItem = __webpack_require__(43); - var RangeItem = __webpack_require__(38); - var BackgroundItem = __webpack_require__(44); + var Hammer = __webpack_require__(31); + var util = __webpack_require__(3); + var DataSet = __webpack_require__(16); + var DataView = __webpack_require__(18); + var TimeStep = __webpack_require__(44); + var Component = __webpack_require__(29); + var Group = __webpack_require__(40); + var BackgroundGroup = __webpack_require__(45); + var BoxItem = __webpack_require__(46); + var PointItem = __webpack_require__(47); + var RangeItem = __webpack_require__(42); + var BackgroundItem = __webpack_require__(48); var UNGROUPED = '__ungrouped__'; // reserved group id for ungrouped items var BACKGROUND = '__background__'; // reserved group id for background items without group @@ -19135,14 +21161,14 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = ItemSet; /***/ }, -/* 36 */ +/* 40 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var util = __webpack_require__(2); - var stack = __webpack_require__(37); - var RangeItem = __webpack_require__(38); + var util = __webpack_require__(3); + var stack = __webpack_require__(41); + var RangeItem = __webpack_require__(42); /** * @constructor Group @@ -19721,7 +21747,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = Group; /***/ }, -/* 37 */ +/* 41 */ /***/ function(module, exports, __webpack_require__) { // Utility functions for ordering and stacking of items @@ -19845,13 +21871,13 @@ return /******/ (function(modules) { // webpackBootstrap }; /***/ }, -/* 38 */ +/* 42 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var Hammer = __webpack_require__(27); - var Item = __webpack_require__(39); + var Hammer = __webpack_require__(31); + var Item = __webpack_require__(43); /** * @constructor RangeItem @@ -20139,13 +22165,13 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = RangeItem; /***/ }, -/* 39 */ +/* 43 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var Hammer = __webpack_require__(27); - var util = __webpack_require__(2); + var Hammer = __webpack_require__(31); + var util = __webpack_require__(3); /** * @constructor Item @@ -20429,14 +22455,14 @@ return /******/ (function(modules) { // webpackBootstrap // should be implemented by the item /***/ }, -/* 40 */ +/* 44 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var moment = __webpack_require__(7); - var DateUtil = __webpack_require__(33); - var util = __webpack_require__(2); + var moment = __webpack_require__(11); + var DateUtil = __webpack_require__(37); + var util = __webpack_require__(3); /** * @constructor TimeStep @@ -21119,13 +23145,13 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = TimeStep; /***/ }, -/* 41 */ +/* 45 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var util = __webpack_require__(2); - var Group = __webpack_require__(36); + var util = __webpack_require__(3); + var Group = __webpack_require__(40); /** * @constructor BackgroundGroup @@ -21183,13 +23209,13 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = BackgroundGroup; /***/ }, -/* 42 */ +/* 46 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var Item = __webpack_require__(39); - var util = __webpack_require__(2); + var Item = __webpack_require__(43); + var util = __webpack_require__(3); /** * @constructor BoxItem @@ -21421,12 +23447,12 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = BoxItem; /***/ }, -/* 43 */ +/* 47 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var Item = __webpack_require__(39); + var Item = __webpack_require__(43); /** * @constructor PointItem @@ -21624,15 +23650,15 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = PointItem; /***/ }, -/* 44 */ +/* 48 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var Hammer = __webpack_require__(27); - var Item = __webpack_require__(39); - var BackgroundGroup = __webpack_require__(41); - var RangeItem = __webpack_require__(38); + var Hammer = __webpack_require__(31); + var Item = __webpack_require__(43); + var BackgroundGroup = __webpack_require__(45); + var RangeItem = __webpack_require__(42); /** * @constructor BackgroundItem @@ -21845,16 +23871,16 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = BackgroundItem; /***/ }, -/* 45 */ +/* 49 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var util = __webpack_require__(2); - var Component = __webpack_require__(25); - var TimeStep = __webpack_require__(40); - var DateUtil = __webpack_require__(33); - var moment = __webpack_require__(7); + var util = __webpack_require__(3); + var Component = __webpack_require__(29); + var TimeStep = __webpack_require__(44); + var DateUtil = __webpack_require__(37); + var moment = __webpack_require__(11); /** * A horizontal time axis @@ -22285,15 +24311,15 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = TimeAxis; /***/ }, -/* 46 */ +/* 50 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var keycharm = __webpack_require__(47); - var Emitter = __webpack_require__(17); - var Hammer = __webpack_require__(27); - var util = __webpack_require__(2); + var keycharm = __webpack_require__(51); + var Emitter = __webpack_require__(21); + var Hammer = __webpack_require__(31); + var util = __webpack_require__(3); /** * Turn an element into an clickToUse element. @@ -22438,7 +24464,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = Activator; /***/ }, -/* 47 */ +/* 51 */ /***/ function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;"use strict"; @@ -22637,16 +24663,16 @@ return /******/ (function(modules) { // webpackBootstrap /***/ }, -/* 48 */ +/* 52 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var Hammer = __webpack_require__(27); - var util = __webpack_require__(2); - var Component = __webpack_require__(25); - var moment = __webpack_require__(7); - var locales = __webpack_require__(26); + var Hammer = __webpack_require__(31); + var util = __webpack_require__(3); + var Component = __webpack_require__(29); + var moment = __webpack_require__(11); + var locales = __webpack_require__(30); /** * A custom time bar @@ -22876,7 +24902,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = CustomTime; /***/ }, -/* 49 */ +/* 53 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -22891,11 +24917,11 @@ return /******/ (function(modules) { // webpackBootstrap function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - var _ColorPicker = __webpack_require__(50); + var _ColorPicker = __webpack_require__(54); var _ColorPicker2 = _interopRequireDefault(_ColorPicker); - var util = __webpack_require__(2); + var util = __webpack_require__(3); /** * The way this works is for all properties of this.possible options, you can supply the property name in any form to list the options. @@ -23557,7 +25583,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 50 */ +/* 54 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -23570,9 +25596,9 @@ return /******/ (function(modules) { // webpackBootstrap function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - var Hammer = __webpack_require__(27); - var hammerUtil = __webpack_require__(32); - var util = __webpack_require__(2); + var Hammer = __webpack_require__(31); + var hammerUtil = __webpack_require__(36); + var util = __webpack_require__(3); var ColorPicker = (function () { function ColorPicker() { @@ -24137,7 +26163,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 51 */ +/* 55 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -24150,7 +26176,7 @@ return /******/ (function(modules) { // webpackBootstrap function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - var util = __webpack_require__(2); + var util = __webpack_require__(3); var errorFound = false; var allOptions = undefined; @@ -24456,7 +26482,7 @@ return /******/ (function(modules) { // webpackBootstrap // item is a function, which is allowed /***/ }, -/* 52 */ +/* 56 */ /***/ function(module, exports, __webpack_require__) { /** @@ -24673,28 +26699,28 @@ return /******/ (function(modules) { // webpackBootstrap exports.configureOptions = configureOptions; /***/ }, -/* 53 */ +/* 57 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var Emitter = __webpack_require__(17); - var Hammer = __webpack_require__(27); - var util = __webpack_require__(2); - var DataSet = __webpack_require__(12); - var DataView = __webpack_require__(14); - var Range = __webpack_require__(31); - var Core = __webpack_require__(34); - var TimeAxis = __webpack_require__(45); - var CurrentTime = __webpack_require__(24); - var CustomTime = __webpack_require__(48); - var LineGraph = __webpack_require__(54); - - var Configurator = __webpack_require__(49); - var Validator = __webpack_require__(51)['default']; - var printStyle = __webpack_require__(51).printStyle; - var allOptions = __webpack_require__(62).allOptions; - var configureOptions = __webpack_require__(62).configureOptions; + var Emitter = __webpack_require__(21); + var Hammer = __webpack_require__(31); + var util = __webpack_require__(3); + var DataSet = __webpack_require__(16); + var DataView = __webpack_require__(18); + var Range = __webpack_require__(35); + var Core = __webpack_require__(38); + var TimeAxis = __webpack_require__(49); + var CurrentTime = __webpack_require__(28); + var CustomTime = __webpack_require__(52); + var LineGraph = __webpack_require__(58); + + var Configurator = __webpack_require__(53); + var Validator = __webpack_require__(55)['default']; + var printStyle = __webpack_require__(55).printStyle; + var allOptions = __webpack_require__(66).allOptions; + var configureOptions = __webpack_require__(66).configureOptions; /** * Create a timeline visualization @@ -25003,21 +27029,21 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = Graph2d; /***/ }, -/* 54 */ +/* 58 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var util = __webpack_require__(2); - var DOMutil = __webpack_require__(11); - var DataSet = __webpack_require__(12); - var DataView = __webpack_require__(14); - var Component = __webpack_require__(25); - var DataAxis = __webpack_require__(55); - var GraphGroup = __webpack_require__(57); - var Legend = __webpack_require__(61); - var BarFunctions = __webpack_require__(60); - var LineFunctions = __webpack_require__(58); + var util = __webpack_require__(3); + var DOMutil = __webpack_require__(15); + var DataSet = __webpack_require__(16); + var DataView = __webpack_require__(18); + var Component = __webpack_require__(29); + var DataAxis = __webpack_require__(59); + var GraphGroup = __webpack_require__(61); + var Legend = __webpack_require__(65); + var BarFunctions = __webpack_require__(64); + var LineFunctions = __webpack_require__(62); var UNGROUPED = '__ungrouped__'; // reserved group id for ungrouped items @@ -25726,1824 +27752,1320 @@ return /******/ (function(modules) { // webpackBootstrap }; /** - * - * @param groupIds - * @param groupsData - * @private - */ - LineGraph.prototype._applySampling = function (groupIds, groupsData) { - var group; - if (groupIds.length > 0) { - for (var i = 0; i < groupIds.length; i++) { - group = this.groups[groupIds[i]]; - if (group.options.sampling == true) { - var dataContainer = groupsData[groupIds[i]]; - if (dataContainer.length > 0) { - var increment = 1; - var amountOfPoints = dataContainer.length; - - // the global screen is used because changing the width of the yAxis may affect the increment, resulting in an endless loop - // of width changing of the yAxis. - var xDistance = this.body.util.toGlobalScreen(dataContainer[dataContainer.length - 1].x) - this.body.util.toGlobalScreen(dataContainer[0].x); - var pointsPerPixel = amountOfPoints / xDistance; - increment = Math.min(Math.ceil(0.2 * amountOfPoints), Math.max(1, Math.round(pointsPerPixel))); - - var sampledData = []; - for (var j = 0; j < amountOfPoints; j += increment) { - sampledData.push(dataContainer[j]); - } - groupsData[groupIds[i]] = sampledData; - } - } - } - } - }; - - /** - * - * - * @param {array} groupIds - * @param {object} groupsData - * @param {object} groupRanges | this is being filled here - * @private - */ - LineGraph.prototype._getYRanges = function (groupIds, groupsData, groupRanges) { - var groupData, group, i; - var combinedDataLeft = []; - var combinedDataRight = []; - var options; - if (groupIds.length > 0) { - for (i = 0; i < groupIds.length; i++) { - groupData = groupsData[groupIds[i]]; - options = this.groups[groupIds[i]].options; - if (groupData.length > 0) { - group = this.groups[groupIds[i]]; - // if bar graphs are stacked, their range need to be handled differently and accumulated over all groups. - if (options.stack === true && options.style === 'bar') { - if (options.yAxisOrientation === 'left') { - combinedDataLeft = combinedDataLeft.concat(group.getData(groupData)); - } else { - combinedDataRight = combinedDataRight.concat(group.getData(groupData)); - } - } else { - groupRanges[groupIds[i]] = group.getYRange(groupData, groupIds[i]); - } - } - } - - // if bar graphs are stacked, their range need to be handled differently and accumulated over all groups. - BarFunctions.getStackedYRange(combinedDataLeft, groupRanges, groupIds, '__barStackLeft', 'left'); - BarFunctions.getStackedYRange(combinedDataRight, groupRanges, groupIds, '__barStackRight', 'right'); - // if line graphs are stacked, their range need to be handled differently and accumulated over all groups. - //LineFunctions.getStackedYRange(combinedDataLeft , groupRanges, groupIds, '__lineStackLeft' , 'left' ); - //LineFunctions.getStackedYRange(combinedDataRight, groupRanges, groupIds, '__lineStackRight', 'right'); - } - }; - - /** - * this sets the Y ranges for the Y axis. It also determines which of the axis should be shown or hidden. - * @param {Array} groupIds - * @param {Object} groupRanges - * @private - */ - LineGraph.prototype._updateYAxis = function (groupIds, groupRanges) { - var resized = false; - var yAxisLeftUsed = false; - var yAxisRightUsed = false; - var minLeft = 1000000000, - minRight = 1000000000, - maxLeft = -1000000000, - maxRight = -1000000000, - minVal, - maxVal; - // if groups are present - if (groupIds.length > 0) { - // this is here to make sure that if there are no items in the axis but there are groups, that there is no infinite draw/redraw loop. - for (var i = 0; i < groupIds.length; i++) { - var group = this.groups[groupIds[i]]; - if (group && group.options.yAxisOrientation != 'right') { - yAxisLeftUsed = true; - minLeft = 0; - maxLeft = 0; - } else if (group && group.options.yAxisOrientation) { - yAxisRightUsed = true; - minRight = 0; - maxRight = 0; - } - } - - // if there are items: - for (var i = 0; i < groupIds.length; i++) { - if (groupRanges.hasOwnProperty(groupIds[i])) { - if (groupRanges[groupIds[i]].ignore !== true) { - minVal = groupRanges[groupIds[i]].min; - maxVal = groupRanges[groupIds[i]].max; - - if (groupRanges[groupIds[i]].yAxisOrientation != 'right') { - yAxisLeftUsed = true; - minLeft = minLeft > minVal ? minVal : minLeft; - maxLeft = maxLeft < maxVal ? maxVal : maxLeft; - } else { - yAxisRightUsed = true; - minRight = minRight > minVal ? minVal : minRight; - maxRight = maxRight < maxVal ? maxVal : maxRight; - } - } - } - } - - if (yAxisLeftUsed == true) { - this.yAxisLeft.setRange(minLeft, maxLeft); - } - if (yAxisRightUsed == true) { - this.yAxisRight.setRange(minRight, maxRight); - } - } - resized = this._toggleAxisVisiblity(yAxisLeftUsed, this.yAxisLeft) || resized; - resized = this._toggleAxisVisiblity(yAxisRightUsed, this.yAxisRight) || resized; - - if (yAxisRightUsed == true && yAxisLeftUsed == true) { - this.yAxisLeft.drawIcons = true; - this.yAxisRight.drawIcons = true; - } else { - this.yAxisLeft.drawIcons = false; - this.yAxisRight.drawIcons = false; - } - this.yAxisRight.master = !yAxisLeftUsed; - if (this.yAxisRight.master == false) { - if (yAxisRightUsed == true) { - this.yAxisLeft.lineOffset = this.yAxisRight.width; - } else { - this.yAxisLeft.lineOffset = 0; - } - - resized = this.yAxisLeft.redraw() || resized; - this.yAxisRight.stepPixels = this.yAxisLeft.stepPixels; - this.yAxisRight.zeroCrossing = this.yAxisLeft.zeroCrossing; - this.yAxisRight.amountOfSteps = this.yAxisLeft.amountOfSteps; - resized = this.yAxisRight.redraw() || resized; - } else { - resized = this.yAxisRight.redraw() || resized; - } - - // clean the accumulated lists - var tempGroups = ['__barStackLeft', '__barStackRight', '__lineStackLeft', '__lineStackRight']; - for (var i = 0; i < tempGroups.length; i++) { - if (groupIds.indexOf(tempGroups[i]) != -1) { - groupIds.splice(groupIds.indexOf(tempGroups[i]), 1); - } - } - - return resized; - }; - - /** - * This shows or hides the Y axis if needed. If there is a change, the changed event is emitted by the updateYAxis function - * - * @param {boolean} axisUsed - * @returns {boolean} - * @private - * @param axis - */ - LineGraph.prototype._toggleAxisVisiblity = function (axisUsed, axis) { - var changed = false; - if (axisUsed == false) { - if (axis.dom.frame.parentNode && axis.hidden == false) { - axis.hide(); - changed = true; - } - } else { - if (!axis.dom.frame.parentNode && axis.hidden == true) { - axis.show(); - changed = true; - } - } - return changed; - }; - - /** - * This uses the DataAxis object to generate the correct X coordinate on the SVG window. It uses the - * util function toScreen to get the x coordinate from the timestamp. It also pre-filters the data and get the minMax ranges for - * the yAxis. - * - * @param datapoints - * @returns {Array} - * @private - */ - LineGraph.prototype._convertXcoordinates = function (datapoints) { - var extractedData = []; - var xValue, yValue; - var toScreen = this.body.util.toScreen; - - for (var i = 0; i < datapoints.length; i++) { - xValue = toScreen(datapoints[i].x) + this.props.width; - yValue = datapoints[i].y; - extractedData.push({ x: xValue, y: yValue }); - } - - return extractedData; - }; - - /** - * This uses the DataAxis object to generate the correct X coordinate on the SVG window. It uses the - * util function toScreen to get the x coordinate from the timestamp. It also pre-filters the data and get the minMax ranges for - * the yAxis. - * - * @param datapoints - * @param group - * @returns {Array} - * @private - */ - LineGraph.prototype._convertYcoordinates = function (datapoints, group) { - var extractedData = []; - var xValue, yValue; - var toScreen = this.body.util.toScreen; - var axis = this.yAxisLeft; - var svgHeight = Number(this.svg.style.height.replace('px', '')); - if (group.options.yAxisOrientation == 'right') { - axis = this.yAxisRight; - } - - for (var i = 0; i < datapoints.length; i++) { - var labelValue = datapoints[i].label ? datapoints[i].label : null; - xValue = toScreen(datapoints[i].x) + this.props.width; - yValue = Math.round(axis.convertValue(datapoints[i].y)); - extractedData.push({ x: xValue, y: yValue, label: labelValue }); - } - - group.setZeroPosition(Math.min(svgHeight, axis.convertValue(0))); - - return extractedData; - }; - - module.exports = LineGraph; - -/***/ }, -/* 55 */ -/***/ function(module, exports, __webpack_require__) { - - 'use strict'; - - var util = __webpack_require__(2); - var DOMutil = __webpack_require__(11); - var Component = __webpack_require__(25); - var DataStep = __webpack_require__(56); - - /** - * A horizontal time axis - * @param {Object} [options] See DataAxis.setOptions for the available - * options. - * @constructor DataAxis - * @extends Component - * @param body - */ - function DataAxis(body, options, svg, linegraphOptions) { - this.id = util.randomUUID(); - this.body = body; - - this.defaultOptions = { - orientation: 'left', // supported: 'left', 'right' - showMinorLabels: true, - showMajorLabels: true, - icons: true, - majorLinesOffset: 7, - minorLinesOffset: 4, - labelOffsetX: 10, - labelOffsetY: 2, - iconWidth: 20, - width: '40px', - visible: true, - alignZeros: true, - left: { - range: { min: undefined, max: undefined }, - format: function format(value) { - return value; - }, - title: { text: undefined, style: undefined } - }, - right: { - range: { min: undefined, max: undefined }, - format: function format(value) { - return value; - }, - title: { text: undefined, style: undefined } - } - }; - - this.linegraphOptions = linegraphOptions; - this.linegraphSVG = svg; - this.props = {}; - this.DOMelements = { // dynamic elements - lines: {}, - labels: {}, - title: {} - }; - - this.dom = {}; - - this.range = { start: 0, end: 0 }; - - this.options = util.extend({}, this.defaultOptions); - this.conversionFactor = 1; - - this.setOptions(options); - this.width = Number(('' + this.options.width).replace('px', '')); - this.minWidth = this.width; - this.height = this.linegraphSVG.offsetHeight; - this.hidden = false; - - this.stepPixels = 25; - this.zeroCrossing = -1; - this.amountOfSteps = -1; - - this.lineOffset = 0; - this.master = true; - this.svgElements = {}; - this.iconsRemoved = false; - - this.groups = {}; - this.amountOfGroups = 0; - - // create the HTML DOM - this._create(); - - var me = this; - this.body.emitter.on('verticalDrag', function () { - me.dom.lineContainer.style.top = me.body.domProps.scrollTop + 'px'; - }); - } - - DataAxis.prototype = new Component(); - - DataAxis.prototype.addGroup = function (label, graphOptions) { - if (!this.groups.hasOwnProperty(label)) { - this.groups[label] = graphOptions; - } - this.amountOfGroups += 1; - }; - - DataAxis.prototype.updateGroup = function (label, graphOptions) { - this.groups[label] = graphOptions; - }; - - DataAxis.prototype.removeGroup = function (label) { - if (this.groups.hasOwnProperty(label)) { - delete this.groups[label]; - this.amountOfGroups -= 1; - } - }; - - DataAxis.prototype.setOptions = function (options) { - if (options) { - var redraw = false; - if (this.options.orientation != options.orientation && options.orientation !== undefined) { - redraw = true; - } - var fields = ['orientation', 'showMinorLabels', 'showMajorLabels', 'icons', 'majorLinesOffset', 'minorLinesOffset', 'labelOffsetX', 'labelOffsetY', 'iconWidth', 'width', 'visible', 'left', 'right', 'alignZeros']; - util.selectiveExtend(fields, this.options, options); - - this.minWidth = Number(('' + this.options.width).replace('px', '')); - - if (redraw === true && this.dom.frame) { - this.hide(); - this.show(); - } - } - }; - - /** - * Create the HTML DOM for the DataAxis - */ - DataAxis.prototype._create = function () { - this.dom.frame = document.createElement('div'); - this.dom.frame.style.width = this.options.width; - this.dom.frame.style.height = this.height; - - this.dom.lineContainer = document.createElement('div'); - this.dom.lineContainer.style.width = '100%'; - this.dom.lineContainer.style.height = this.height; - this.dom.lineContainer.style.position = 'relative'; - - // create svg element for graph drawing. - this.svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); - this.svg.style.position = 'absolute'; - this.svg.style.top = '0px'; - this.svg.style.height = '100%'; - this.svg.style.width = '100%'; - this.svg.style.display = 'block'; - this.dom.frame.appendChild(this.svg); - }; - - DataAxis.prototype._redrawGroupIcons = function () { - DOMutil.prepareElements(this.svgElements); - - var x; - var iconWidth = this.options.iconWidth; - var iconHeight = 15; - var iconOffset = 4; - var y = iconOffset + 0.5 * iconHeight; - - if (this.options.orientation === 'left') { - x = iconOffset; - } else { - x = this.width - iconWidth - iconOffset; - } - - var groupArray = Object.keys(this.groups); - groupArray.sort(function (a, b) { - return a < b ? -1 : 1; - }); - - for (var i = 0; i < groupArray.length; i++) { - var groupId = groupArray[i]; - if (this.groups[groupId].visible === true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] === true)) { - this.groups[groupId].drawIcon(x, y, this.svgElements, this.svg, iconWidth, iconHeight); - y += iconHeight + iconOffset; - } - } - - DOMutil.cleanupElements(this.svgElements); - this.iconsRemoved = false; - }; - - DataAxis.prototype._cleanupIcons = function () { - if (this.iconsRemoved === false) { - DOMutil.prepareElements(this.svgElements); - DOMutil.cleanupElements(this.svgElements); - this.iconsRemoved = true; - } - }; - - /** - * Create the HTML DOM for the DataAxis - */ - DataAxis.prototype.show = function () { - this.hidden = false; - if (!this.dom.frame.parentNode) { - if (this.options.orientation === 'left') { - this.body.dom.left.appendChild(this.dom.frame); - } else { - this.body.dom.right.appendChild(this.dom.frame); - } - } - - if (!this.dom.lineContainer.parentNode) { - this.body.dom.backgroundHorizontal.appendChild(this.dom.lineContainer); - } - }; - - /** - * Create the HTML DOM for the DataAxis - */ - DataAxis.prototype.hide = function () { - this.hidden = true; - if (this.dom.frame.parentNode) { - this.dom.frame.parentNode.removeChild(this.dom.frame); - } - - if (this.dom.lineContainer.parentNode) { - this.dom.lineContainer.parentNode.removeChild(this.dom.lineContainer); - } - }; - - /** - * Set a range (start and end) - * @param end - * @param start - * @param end - */ - DataAxis.prototype.setRange = function (start, end) { - if (this.master === false && this.options.alignZeros === true && this.zeroCrossing != -1) { - if (start > 0) { - start = 0; - } - } - this.range.start = start; - this.range.end = end; - }; - - /** - * Repaint the component - * @return {boolean} Returns true if the component is resized + * + * @param groupIds + * @param groupsData + * @private */ - DataAxis.prototype.redraw = function () { - var resized = false; - var activeGroups = 0; + LineGraph.prototype._applySampling = function (groupIds, groupsData) { + var group; + if (groupIds.length > 0) { + for (var i = 0; i < groupIds.length; i++) { + group = this.groups[groupIds[i]]; + if (group.options.sampling == true) { + var dataContainer = groupsData[groupIds[i]]; + if (dataContainer.length > 0) { + var increment = 1; + var amountOfPoints = dataContainer.length; - // Make sure the line container adheres to the vertical scrolling. - this.dom.lineContainer.style.top = this.body.domProps.scrollTop + 'px'; + // the global screen is used because changing the width of the yAxis may affect the increment, resulting in an endless loop + // of width changing of the yAxis. + var xDistance = this.body.util.toGlobalScreen(dataContainer[dataContainer.length - 1].x) - this.body.util.toGlobalScreen(dataContainer[0].x); + var pointsPerPixel = amountOfPoints / xDistance; + increment = Math.min(Math.ceil(0.2 * amountOfPoints), Math.max(1, Math.round(pointsPerPixel))); - for (var groupId in this.groups) { - if (this.groups.hasOwnProperty(groupId)) { - if (this.groups[groupId].visible === true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] === true)) { - activeGroups++; + var sampledData = []; + for (var j = 0; j < amountOfPoints; j += increment) { + sampledData.push(dataContainer[j]); + } + groupsData[groupIds[i]] = sampledData; + } } } } - if (this.amountOfGroups === 0 || activeGroups === 0) { - this.hide(); - } else { - this.show(); - this.height = Number(this.linegraphSVG.style.height.replace('px', '')); - - // svg offsetheight did not work in firefox and explorer... - this.dom.lineContainer.style.height = this.height + 'px'; - this.width = this.options.visible === true ? Number(('' + this.options.width).replace('px', '')) : 0; - - var props = this.props; - var frame = this.dom.frame; - - // update classname - frame.className = 'vis-data-axis'; - - // calculate character width and height - this._calculateCharSize(); - - var orientation = this.options.orientation; - var showMinorLabels = this.options.showMinorLabels; - var showMajorLabels = this.options.showMajorLabels; - - // determine the width and height of the elements for the axis - props.minorLabelHeight = showMinorLabels ? props.minorCharHeight : 0; - props.majorLabelHeight = showMajorLabels ? props.majorCharHeight : 0; - - props.minorLineWidth = this.body.dom.backgroundHorizontal.offsetWidth - this.lineOffset - this.width + 2 * this.options.minorLinesOffset; - props.minorLineHeight = 1; - props.majorLineWidth = this.body.dom.backgroundHorizontal.offsetWidth - this.lineOffset - this.width + 2 * this.options.majorLinesOffset; - props.majorLineHeight = 1; - - // take frame offline while updating (is almost twice as fast) - if (orientation === 'left') { - frame.style.top = '0'; - frame.style.left = '0'; - frame.style.bottom = ''; - frame.style.width = this.width + 'px'; - frame.style.height = this.height + 'px'; - this.props.width = this.body.domProps.left.width; - this.props.height = this.body.domProps.left.height; - } else { - // right - frame.style.top = ''; - frame.style.bottom = '0'; - frame.style.left = '0'; - frame.style.width = this.width + 'px'; - frame.style.height = this.height + 'px'; - this.props.width = this.body.domProps.right.width; - this.props.height = this.body.domProps.right.height; - } - - resized = this._redrawLabels(); - resized = this._isResized() || resized; + }; - if (this.options.icons === true) { - this._redrawGroupIcons(); - } else { - this._cleanupIcons(); + /** + * + * + * @param {array} groupIds + * @param {object} groupsData + * @param {object} groupRanges | this is being filled here + * @private + */ + LineGraph.prototype._getYRanges = function (groupIds, groupsData, groupRanges) { + var groupData, group, i; + var combinedDataLeft = []; + var combinedDataRight = []; + var options; + if (groupIds.length > 0) { + for (i = 0; i < groupIds.length; i++) { + groupData = groupsData[groupIds[i]]; + options = this.groups[groupIds[i]].options; + if (groupData.length > 0) { + group = this.groups[groupIds[i]]; + // if bar graphs are stacked, their range need to be handled differently and accumulated over all groups. + if (options.stack === true && options.style === 'bar') { + if (options.yAxisOrientation === 'left') { + combinedDataLeft = combinedDataLeft.concat(group.getData(groupData)); + } else { + combinedDataRight = combinedDataRight.concat(group.getData(groupData)); + } + } else { + groupRanges[groupIds[i]] = group.getYRange(groupData, groupIds[i]); + } + } } - this._redrawTitle(orientation); + // if bar graphs are stacked, their range need to be handled differently and accumulated over all groups. + BarFunctions.getStackedYRange(combinedDataLeft, groupRanges, groupIds, '__barStackLeft', 'left'); + BarFunctions.getStackedYRange(combinedDataRight, groupRanges, groupIds, '__barStackRight', 'right'); + // if line graphs are stacked, their range need to be handled differently and accumulated over all groups. + //LineFunctions.getStackedYRange(combinedDataLeft , groupRanges, groupIds, '__lineStackLeft' , 'left' ); + //LineFunctions.getStackedYRange(combinedDataRight, groupRanges, groupIds, '__lineStackRight', 'right'); } - return resized; }; /** - * Repaint major and minor text labels and vertical grid lines + * this sets the Y ranges for the Y axis. It also determines which of the axis should be shown or hidden. + * @param {Array} groupIds + * @param {Object} groupRanges * @private */ - DataAxis.prototype._redrawLabels = function () { + LineGraph.prototype._updateYAxis = function (groupIds, groupRanges) { var resized = false; - DOMutil.prepareElements(this.DOMelements.lines); - DOMutil.prepareElements(this.DOMelements.labels); - var orientation = this.options['orientation']; - - // get the range for the slaved axis - var step; - if (this.master === false) { - var stepSize, rangeStart, rangeEnd, minimumStep; - if (this.zeroCrossing !== -1 && this.options.alignZeros === true) { - if (this.range.end > 0) { - stepSize = this.range.end / this.zeroCrossing; // size of one step - rangeStart = this.range.end - this.amountOfSteps * stepSize; - rangeEnd = this.range.end; - } else { - // all of the range (including start) has to be done before the zero crossing. - stepSize = -1 * this.range.start / (this.amountOfSteps - this.zeroCrossing); // absolute size of a step - rangeStart = this.range.start; - rangeEnd = this.range.start + stepSize * this.amountOfSteps; + var yAxisLeftUsed = false; + var yAxisRightUsed = false; + var minLeft = 1000000000, + minRight = 1000000000, + maxLeft = -1000000000, + maxRight = -1000000000, + minVal, + maxVal; + // if groups are present + if (groupIds.length > 0) { + // this is here to make sure that if there are no items in the axis but there are groups, that there is no infinite draw/redraw loop. + for (var i = 0; i < groupIds.length; i++) { + var group = this.groups[groupIds[i]]; + if (group && group.options.yAxisOrientation != 'right') { + yAxisLeftUsed = true; + minLeft = 0; + maxLeft = 0; + } else if (group && group.options.yAxisOrientation) { + yAxisRightUsed = true; + minRight = 0; + maxRight = 0; } - } else { - rangeStart = this.range.start; - rangeEnd = this.range.end; - } - minimumStep = this.stepPixels; - } else { - // calculate range and step (step such that we have space for 7 characters per label) - minimumStep = this.props.majorCharHeight; - rangeStart = this.range.start; - rangeEnd = this.range.end; - } - - this.step = step = new DataStep(rangeStart, rangeEnd, minimumStep, this.dom.frame.offsetHeight, this.options[this.options.orientation].range, this.options[this.options.orientation].format, this.master === false && this.options.alignZeros // does the step have to align zeros? only if not master and the options is on - ); - - // the slave axis needs to use the same horizontal lines as the master axis. - if (this.master === true) { - this.stepPixels = this.dom.frame.offsetHeight / step.marginRange * step.step; - this.amountOfSteps = Math.ceil(this.dom.frame.offsetHeight / this.stepPixels); - } else { - // align with zero - if (this.options.alignZeros === true && this.zeroCrossing !== -1) { - // distance is the amount of steps away from the zero crossing we are. - var distance = (step.current - this.zeroCrossing * step.step) / step.step; - this.step.shift(distance); } - } - - // value at the bottom of the SVG - this.valueAtBottom = step.marginEnd; - - this.maxLabelSize = 0; - var y = 0; // init value - var stepIndex = 0; // init value - var isMajor = false; // init value - while (stepIndex < this.amountOfSteps) { - y = Math.round(stepIndex * this.stepPixels); - isMajor = step.isMajor(); - if (stepIndex > 0 && stepIndex !== this.amountOfSteps) { - if (this.options['showMinorLabels'] && isMajor === false || this.master === false && this.options['showMinorLabels'] === true) { - this._redrawLabel(y - 2, step.getCurrent(), orientation, 'vis-y-axis vis-minor', this.props.minorCharHeight); - } + // if there are items: + for (var i = 0; i < groupIds.length; i++) { + if (groupRanges.hasOwnProperty(groupIds[i])) { + if (groupRanges[groupIds[i]].ignore !== true) { + minVal = groupRanges[groupIds[i]].min; + maxVal = groupRanges[groupIds[i]].max; - if (isMajor && this.options['showMajorLabels'] && this.master === true || this.options['showMinorLabels'] === false && this.master === false && isMajor === true) { - if (y >= 0) { - this._redrawLabel(y - 2, step.getCurrent(), orientation, 'vis-y-axis vis-major', this.props.majorCharHeight); + if (groupRanges[groupIds[i]].yAxisOrientation != 'right') { + yAxisLeftUsed = true; + minLeft = minLeft > minVal ? minVal : minLeft; + maxLeft = maxLeft < maxVal ? maxVal : maxLeft; + } else { + yAxisRightUsed = true; + minRight = minRight > minVal ? minVal : minRight; + maxRight = maxRight < maxVal ? maxVal : maxRight; + } } - this._redrawLine(y, orientation, 'vis-grid vis-horizontal vis-major', this.options.majorLinesOffset, this.props.majorLineWidth); - } else { - this._redrawLine(y, orientation, 'vis-grid vis-horizontal vis-minor', this.options.minorLinesOffset, this.props.minorLineWidth); } } - // get zero crossing - if (this.master === true && step.current === 0) { - this.zeroCrossing = stepIndex; + if (yAxisLeftUsed == true) { + this.yAxisLeft.setRange(minLeft, maxLeft); + } + if (yAxisRightUsed == true) { + this.yAxisRight.setRange(minRight, maxRight); } - - step.next(); - stepIndex += 1; } + resized = this._toggleAxisVisiblity(yAxisLeftUsed, this.yAxisLeft) || resized; + resized = this._toggleAxisVisiblity(yAxisRightUsed, this.yAxisRight) || resized; - // get zero crossing if it's the last step - if (this.master === true && step.current === 0) { - this.zeroCrossing = stepIndex; + if (yAxisRightUsed == true && yAxisLeftUsed == true) { + this.yAxisLeft.drawIcons = true; + this.yAxisRight.drawIcons = true; + } else { + this.yAxisLeft.drawIcons = false; + this.yAxisRight.drawIcons = false; } + this.yAxisRight.master = !yAxisLeftUsed; + if (this.yAxisRight.master == false) { + if (yAxisRightUsed == true) { + this.yAxisLeft.lineOffset = this.yAxisRight.width; + } else { + this.yAxisLeft.lineOffset = 0; + } - this.conversionFactor = this.stepPixels / step.step; - - // Note that title is rotated, so we're using the height, not width! - var titleWidth = 0; - if (this.options[orientation].title !== undefined && this.options[orientation].title.text !== undefined) { - titleWidth = this.props.titleCharHeight; + resized = this.yAxisLeft.redraw() || resized; + this.yAxisRight.stepPixels = this.yAxisLeft.stepPixels; + this.yAxisRight.zeroCrossing = this.yAxisLeft.zeroCrossing; + this.yAxisRight.amountOfSteps = this.yAxisLeft.amountOfSteps; + resized = this.yAxisRight.redraw() || resized; + } else { + resized = this.yAxisRight.redraw() || resized; } - var offset = this.options.icons === true ? Math.max(this.options.iconWidth, titleWidth) + this.options.labelOffsetX + 15 : titleWidth + this.options.labelOffsetX + 15; - // this will resize the yAxis to accommodate the labels. - if (this.maxLabelSize > this.width - offset && this.options.visible === true) { - this.width = this.maxLabelSize + offset; - this.options.width = this.width + 'px'; - DOMutil.cleanupElements(this.DOMelements.lines); - DOMutil.cleanupElements(this.DOMelements.labels); - this.redraw(); - resized = true; - } - // this will resize the yAxis if it is too big for the labels. - else if (this.maxLabelSize < this.width - offset && this.options.visible === true && this.width > this.minWidth) { - this.width = Math.max(this.minWidth, this.maxLabelSize + offset); - this.options.width = this.width + 'px'; - DOMutil.cleanupElements(this.DOMelements.lines); - DOMutil.cleanupElements(this.DOMelements.labels); - this.redraw(); - resized = true; - } else { - DOMutil.cleanupElements(this.DOMelements.lines); - DOMutil.cleanupElements(this.DOMelements.labels); - resized = false; + // clean the accumulated lists + var tempGroups = ['__barStackLeft', '__barStackRight', '__lineStackLeft', '__lineStackRight']; + for (var i = 0; i < tempGroups.length; i++) { + if (groupIds.indexOf(tempGroups[i]) != -1) { + groupIds.splice(groupIds.indexOf(tempGroups[i]), 1); + } } return resized; }; - DataAxis.prototype.convertValue = function (value) { - var invertedValue = this.valueAtBottom - value; - var convertedValue = invertedValue * this.conversionFactor; - return convertedValue; - }; - - DataAxis.prototype.screenToValue = function (x) { - return this.valueAtBottom - x / this.conversionFactor; - }; - /** - * Create a label for the axis at position x + * This shows or hides the Y axis if needed. If there is a change, the changed event is emitted by the updateYAxis function + * + * @param {boolean} axisUsed + * @returns {boolean} * @private - * @param y - * @param text - * @param orientation - * @param className - * @param characterHeight + * @param axis */ - DataAxis.prototype._redrawLabel = function (y, text, orientation, className, characterHeight) { - // reuse redundant label - var label = DOMutil.getDOMElement('div', this.DOMelements.labels, this.dom.frame); //this.dom.redundant.labels.shift(); - label.className = className; - label.innerHTML = text; - if (orientation === 'left') { - label.style.left = '-' + this.options.labelOffsetX + 'px'; - label.style.textAlign = 'right'; + LineGraph.prototype._toggleAxisVisiblity = function (axisUsed, axis) { + var changed = false; + if (axisUsed == false) { + if (axis.dom.frame.parentNode && axis.hidden == false) { + axis.hide(); + changed = true; + } } else { - label.style.right = '-' + this.options.labelOffsetX + 'px'; - label.style.textAlign = 'left'; - } - - label.style.top = y - 0.5 * characterHeight + this.options.labelOffsetY + 'px'; - - text += ''; - - var largestWidth = Math.max(this.props.majorCharWidth, this.props.minorCharWidth); - if (this.maxLabelSize < text.length * largestWidth) { - this.maxLabelSize = text.length * largestWidth; - } - }; - - /** - * Create a minor line for the axis at position y - * @param y - * @param orientation - * @param className - * @param offset - * @param width - */ - DataAxis.prototype._redrawLine = function (y, orientation, className, offset, width) { - if (this.master === true) { - var line = DOMutil.getDOMElement('div', this.DOMelements.lines, this.dom.lineContainer); //this.dom.redundant.lines.shift(); - line.className = className; - line.innerHTML = ''; - - if (orientation === 'left') { - line.style.left = this.width - offset + 'px'; - } else { - line.style.right = this.width - offset + 'px'; + if (!axis.dom.frame.parentNode && axis.hidden == true) { + axis.show(); + changed = true; } - - line.style.width = width + 'px'; - line.style.top = y + 'px'; } + return changed; }; /** - * Create a title for the axis + * This uses the DataAxis object to generate the correct X coordinate on the SVG window. It uses the + * util function toScreen to get the x coordinate from the timestamp. It also pre-filters the data and get the minMax ranges for + * the yAxis. + * + * @param datapoints + * @returns {Array} * @private - * @param orientation */ - DataAxis.prototype._redrawTitle = function (orientation) { - DOMutil.prepareElements(this.DOMelements.title); - - // Check if the title is defined for this axes - if (this.options[orientation].title !== undefined && this.options[orientation].title.text !== undefined) { - var title = DOMutil.getDOMElement('div', this.DOMelements.title, this.dom.frame); - title.className = 'vis-y-axis vis-title vis-' + orientation; - title.innerHTML = this.options[orientation].title.text; - - // Add style - if provided - if (this.options[orientation].title.style !== undefined) { - util.addCssText(title, this.options[orientation].title.style); - } - - if (orientation === 'left') { - title.style.left = this.props.titleCharHeight + 'px'; - } else { - title.style.right = this.props.titleCharHeight + 'px'; - } + LineGraph.prototype._convertXcoordinates = function (datapoints) { + var extractedData = []; + var xValue, yValue; + var toScreen = this.body.util.toScreen; - title.style.width = this.height + 'px'; + for (var i = 0; i < datapoints.length; i++) { + xValue = toScreen(datapoints[i].x) + this.props.width; + yValue = datapoints[i].y; + extractedData.push({ x: xValue, y: yValue }); } - // we need to clean up in case we did not use all elements. - DOMutil.cleanupElements(this.DOMelements.title); + return extractedData; }; /** - * Determine the size of text on the axis (both major and minor axis). - * The size is calculated only once and then cached in this.props. + * This uses the DataAxis object to generate the correct X coordinate on the SVG window. It uses the + * util function toScreen to get the x coordinate from the timestamp. It also pre-filters the data and get the minMax ranges for + * the yAxis. + * + * @param datapoints + * @param group + * @returns {Array} * @private */ - DataAxis.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 = 'vis-y-axis vis-minor vis-measure'; - measureCharMinor.appendChild(textMinor); - this.dom.frame.appendChild(measureCharMinor); - - this.props.minorCharHeight = measureCharMinor.clientHeight; - this.props.minorCharWidth = measureCharMinor.clientWidth; + LineGraph.prototype._convertYcoordinates = function (datapoints, group) { + var extractedData = []; + var xValue, yValue; + var toScreen = this.body.util.toScreen; + var axis = this.yAxisLeft; + var svgHeight = Number(this.svg.style.height.replace('px', '')); + if (group.options.yAxisOrientation == 'right') { + axis = this.yAxisRight; + } - this.dom.frame.removeChild(measureCharMinor); + for (var i = 0; i < datapoints.length; i++) { + var labelValue = datapoints[i].label ? datapoints[i].label : null; + xValue = toScreen(datapoints[i].x) + this.props.width; + yValue = Math.round(axis.convertValue(datapoints[i].y)); + extractedData.push({ x: xValue, y: yValue, label: labelValue }); } - if (!('majorCharHeight' in this.props)) { - var textMajor = document.createTextNode('0'); - var measureCharMajor = document.createElement('div'); - measureCharMajor.className = 'vis-y-axis vis-major vis-measure'; - measureCharMajor.appendChild(textMajor); - this.dom.frame.appendChild(measureCharMajor); + group.setZeroPosition(Math.min(svgHeight, axis.convertValue(0))); - this.props.majorCharHeight = measureCharMajor.clientHeight; - this.props.majorCharWidth = measureCharMajor.clientWidth; + return extractedData; + }; - this.dom.frame.removeChild(measureCharMajor); - } + module.exports = LineGraph; - if (!('titleCharHeight' in this.props)) { - var textTitle = document.createTextNode('0'); - var measureCharTitle = document.createElement('div'); - measureCharTitle.className = 'vis-y-axis vis-title vis-measure'; - measureCharTitle.appendChild(textTitle); - this.dom.frame.appendChild(measureCharTitle); +/***/ }, +/* 59 */ +/***/ function(module, exports, __webpack_require__) { - this.props.titleCharHeight = measureCharTitle.clientHeight; - this.props.titleCharWidth = measureCharTitle.clientWidth; + 'use strict'; + + var util = __webpack_require__(3); + var DOMutil = __webpack_require__(15); + var Component = __webpack_require__(29); + var DataStep = __webpack_require__(60); + + /** + * A horizontal time axis + * @param {Object} [options] See DataAxis.setOptions for the available + * options. + * @constructor DataAxis + * @extends Component + * @param body + */ + function DataAxis(body, options, svg, linegraphOptions) { + this.id = util.randomUUID(); + this.body = body; + + this.defaultOptions = { + orientation: 'left', // supported: 'left', 'right' + showMinorLabels: true, + showMajorLabels: true, + icons: true, + majorLinesOffset: 7, + minorLinesOffset: 4, + labelOffsetX: 10, + labelOffsetY: 2, + iconWidth: 20, + width: '40px', + visible: true, + alignZeros: true, + left: { + range: { min: undefined, max: undefined }, + format: function format(value) { + return value; + }, + title: { text: undefined, style: undefined } + }, + right: { + range: { min: undefined, max: undefined }, + format: function format(value) { + return value; + }, + title: { text: undefined, style: undefined } + } + }; - this.dom.frame.removeChild(measureCharTitle); - } - }; + this.linegraphOptions = linegraphOptions; + this.linegraphSVG = svg; + this.props = {}; + this.DOMelements = { // dynamic elements + lines: {}, + labels: {}, + title: {} + }; - module.exports = DataAxis; + this.dom = {}; -/***/ }, -/* 56 */ -/***/ function(module, exports, __webpack_require__) { + this.range = { start: 0, end: 0 }; - /** - * @constructor DataStep - * The class DataStep is an iterator for data for the lineGraph. You provide a start data point and an - * end data point. The class itself determines the best scale (step size) based on the - * provided start Date, end Date, and minimumStep. - * - * If minimumStep is provided, the step size is chosen as close as possible - * to the minimumStep but larger than minimumStep. If minimumStep is not - * provided, the scale is set to 1 DAY. - * The minimumStep should correspond with the onscreen size of about 6 characters - * - * Alternatively, you can set a scale by hand. - * After creation, you can initialize the class by executing first(). Then you - * can iterate from the start date to the end date via next(). You can check if - * the end date is reached with the function hasNext(). After each step, you can - * retrieve the current date via getCurrent(). - * The DataStep has scales ranging from milliseconds, seconds, minutes, hours, - * days, to years. - * - * Version: 1.2 - * - * @param {Date} [start] The start date, for example new Date(2010, 9, 21) - * or new Date(2010, 9, 21, 23, 45, 00) - * @param {Date} [end] The end date - * @param {Number} [minimumStep] Optional. Minimum step size in milliseconds - */ - 'use strict'; + this.options = util.extend({}, this.defaultOptions); + this.conversionFactor = 1; - function DataStep(start, end, minimumStep, containerHeight, customRange, formattingFunction, alignZeros) { - // variables - this.current = 0; + this.setOptions(options); + this.width = Number(('' + this.options.width).replace('px', '')); + this.minWidth = this.width; + this.height = this.linegraphSVG.offsetHeight; + this.hidden = false; - this.autoScale = true; - this.stepIndex = 0; - this.step = 1; - this.scale = 1; - this.formattingFunction = formattingFunction; + this.stepPixels = 25; + this.zeroCrossing = -1; + this.amountOfSteps = -1; - this.marginStart; - this.marginEnd; - this.deadSpace = 0; + this.lineOffset = 0; + this.master = true; + this.svgElements = {}; + this.iconsRemoved = false; - this.majorSteps = [1, 2, 5, 10]; - this.minorSteps = [0.25, 0.5, 1, 2]; + this.groups = {}; + this.amountOfGroups = 0; - this.alignZeros = alignZeros; + // create the HTML DOM + this._create(); - this.setRange(start, end, minimumStep, containerHeight, customRange); + var me = this; + this.body.emitter.on('verticalDrag', function () { + me.dom.lineContainer.style.top = me.body.domProps.scrollTop + 'px'; + }); } - /** - * Set a new range - * If minimumStep is provided, the step size is chosen as close as possible - * to the minimumStep but larger than minimumStep. If minimumStep is not - * provided, the scale is set to 1 DAY. - * The minimumStep should correspond with the onscreen size of about 6 characters - * @param {Number} [start] The start date and time. - * @param {Number} [end] The end date and time. - * @param {Number} [minimumStep] Optional. Minimum step size in milliseconds - */ - DataStep.prototype.setRange = function (start, end, minimumStep, containerHeight, customRange) { - this._start = customRange.min === undefined ? start : customRange.min; - this._end = customRange.max === undefined ? end : customRange.max; - if (this._start === this._end) { - this._start = customRange.min === undefined ? this._start - 0.75 : this._start; - this._end = customRange.max === undefined ? this._end + 1 : this._end;; - } + DataAxis.prototype = new Component(); - if (this.autoScale === true) { - this.setMinimumStep(minimumStep, containerHeight); + DataAxis.prototype.addGroup = function (label, graphOptions) { + if (!this.groups.hasOwnProperty(label)) { + this.groups[label] = graphOptions; } - - this.setFirst(customRange); + this.amountOfGroups += 1; }; - /** - * Automatically determine the scale that bests fits the provided minimum step - * @param {Number} [minimumStep] The minimum step size in pixels - */ - DataStep.prototype.setMinimumStep = function (minimumStep, containerHeight) { - // round to floor - var range = this._end - this._start; - var safeRange = range * 1.2; - var minimumStepValue = minimumStep * (safeRange / containerHeight); - var orderOfMagnitude = Math.round(Math.log(safeRange) / Math.LN10); - - var minorStepIdx = -1; - var magnitudefactor = Math.pow(10, orderOfMagnitude); + DataAxis.prototype.updateGroup = function (label, graphOptions) { + this.groups[label] = graphOptions; + }; - var start = 0; - if (orderOfMagnitude < 0) { - start = orderOfMagnitude; + DataAxis.prototype.removeGroup = function (label) { + if (this.groups.hasOwnProperty(label)) { + delete this.groups[label]; + this.amountOfGroups -= 1; } + }; - var solutionFound = false; - for (var i = start; Math.abs(i) <= Math.abs(orderOfMagnitude); i++) { - magnitudefactor = Math.pow(10, i); - for (var j = 0; j < this.minorSteps.length; j++) { - var stepSize = magnitudefactor * this.minorSteps[j]; - if (stepSize >= minimumStepValue) { - solutionFound = true; - minorStepIdx = j; - break; - } + DataAxis.prototype.setOptions = function (options) { + if (options) { + var redraw = false; + if (this.options.orientation != options.orientation && options.orientation !== undefined) { + redraw = true; } - if (solutionFound === true) { - break; + var fields = ['orientation', 'showMinorLabels', 'showMajorLabels', 'icons', 'majorLinesOffset', 'minorLinesOffset', 'labelOffsetX', 'labelOffsetY', 'iconWidth', 'width', 'visible', 'left', 'right', 'alignZeros']; + util.selectiveExtend(fields, this.options, options); + + this.minWidth = Number(('' + this.options.width).replace('px', '')); + + if (redraw === true && this.dom.frame) { + this.hide(); + this.show(); } } - this.stepIndex = minorStepIdx; - this.scale = magnitudefactor; - this.step = magnitudefactor * this.minorSteps[minorStepIdx]; }; /** - * Round the current date to the first minor date value - * This must be executed once when the current date is set to start Date + * Create the HTML DOM for the DataAxis */ - DataStep.prototype.setFirst = function (customRange) { - if (customRange === undefined) { - customRange = {}; - } - - var niceStart = customRange.min === undefined ? this._start - this.scale * 2 * this.minorSteps[this.stepIndex] : customRange.min; - var niceEnd = customRange.max === undefined ? this._end + this.scale * this.minorSteps[this.stepIndex] : customRange.max; + DataAxis.prototype._create = function () { + this.dom.frame = document.createElement('div'); + this.dom.frame.style.width = this.options.width; + this.dom.frame.style.height = this.height; - this.marginEnd = customRange.max === undefined ? this.roundToMinor(niceEnd) : customRange.max; - this.marginStart = customRange.min === undefined ? this.roundToMinor(niceStart) : customRange.min; + this.dom.lineContainer = document.createElement('div'); + this.dom.lineContainer.style.width = '100%'; + this.dom.lineContainer.style.height = this.height; + this.dom.lineContainer.style.position = 'relative'; - // if we need to align the zero's we need to make sure that there is a zero to use. - if (this.alignZeros === true && (this.marginEnd - this.marginStart) % this.step != 0) { - this.marginEnd += this.marginEnd % this.step; - } + // create svg element for graph drawing. + this.svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + this.svg.style.position = 'absolute'; + this.svg.style.top = '0px'; + this.svg.style.height = '100%'; + this.svg.style.width = '100%'; + this.svg.style.display = 'block'; + this.dom.frame.appendChild(this.svg); + }; - this.deadSpace = this.roundToMinor(niceEnd) - niceEnd + this.roundToMinor(niceStart) - niceStart; - this.marginRange = this.marginEnd - this.marginStart; + DataAxis.prototype._redrawGroupIcons = function () { + DOMutil.prepareElements(this.svgElements); - this.current = this.marginEnd; - }; + var x; + var iconWidth = this.options.iconWidth; + var iconHeight = 15; + var iconOffset = 4; + var y = iconOffset + 0.5 * iconHeight; - DataStep.prototype.roundToMinor = function (value) { - var rounded = value - value % (this.scale * this.minorSteps[this.stepIndex]); - if (value % (this.scale * this.minorSteps[this.stepIndex]) > 0.5 * (this.scale * this.minorSteps[this.stepIndex])) { - return rounded + this.scale * this.minorSteps[this.stepIndex]; + if (this.options.orientation === 'left') { + x = iconOffset; } else { - return rounded; + x = this.width - iconWidth - iconOffset; } - }; - - /** - * Check if the there is a next step - * @return {boolean} true if the current date has not passed the end date - */ - DataStep.prototype.hasNext = function () { - return this.current >= this.marginStart; - }; - /** - * Do the next step - */ - DataStep.prototype.next = function () { - var prev = this.current; - this.current -= this.step; + var groupArray = Object.keys(this.groups); + groupArray.sort(function (a, b) { + return a < b ? -1 : 1; + }); - // safety mechanism: if current time is still unchanged, move to the end - if (this.current === prev) { - this.current = this._end; + for (var i = 0; i < groupArray.length; i++) { + var groupId = groupArray[i]; + if (this.groups[groupId].visible === true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] === true)) { + this.groups[groupId].drawIcon(x, y, this.svgElements, this.svg, iconWidth, iconHeight); + y += iconHeight + iconOffset; + } } - }; - /** - * Do the next step - */ - DataStep.prototype.previous = function () { - this.current += this.step; - this.marginEnd += this.step; - this.marginRange = this.marginEnd - this.marginStart; + DOMutil.cleanupElements(this.svgElements); + this.iconsRemoved = false; }; - /** - * Get the current datetime - * @return {String} current The current date - */ - DataStep.prototype.getCurrent = function () { - // prevent round-off errors when close to zero - var current = Math.abs(this.current) < this.step / 2 ? 0 : this.current; - var returnValue = current.toPrecision(5); - if (typeof this.formattingFunction === 'function') { - returnValue = this.formattingFunction(current); - } - - if (typeof returnValue === 'number') { - return '' + returnValue; - } else if (typeof returnValue === 'string') { - return returnValue; - } else { - return current.toPrecision(5); + DataAxis.prototype._cleanupIcons = function () { + if (this.iconsRemoved === false) { + DOMutil.prepareElements(this.svgElements); + DOMutil.cleanupElements(this.svgElements); + this.iconsRemoved = true; } }; /** - * Check if the current value is a major value (for example when the step - * is DAY, a major value is each first day of the MONTH) - * @return {boolean} true if current date is major, else false. + * Create the HTML DOM for the DataAxis */ - DataStep.prototype.isMajor = function () { - return this.current % (this.scale * this.majorSteps[this.stepIndex]) === 0; - }; - - DataStep.prototype.shift = function (steps) { - if (steps < 0) { - for (var i = 0; i < -steps; i++) { - this.previous(); - } - } else if (steps > 0) { - for (var i = 0; i < steps; i++) { - this.next(); + DataAxis.prototype.show = function () { + this.hidden = false; + if (!this.dom.frame.parentNode) { + if (this.options.orientation === 'left') { + this.body.dom.left.appendChild(this.dom.frame); + } else { + this.body.dom.right.appendChild(this.dom.frame); } } - }; - - module.exports = DataStep; - -/***/ }, -/* 57 */ -/***/ function(module, exports, __webpack_require__) { - - 'use strict'; - var util = __webpack_require__(2); - var DOMutil = __webpack_require__(11); - var Line = __webpack_require__(58); - var Bar = __webpack_require__(60); - var Points = __webpack_require__(59); + if (!this.dom.lineContainer.parentNode) { + this.body.dom.backgroundHorizontal.appendChild(this.dom.lineContainer); + } + }; /** - * /** - * @param {object} group | the object of the group from the dataset - * @param {string} groupId | ID of the group - * @param {object} options | the default options - * @param {array} groupsUsingDefaultStyles | this array has one entree. - * It is passed as an array so it is passed by reference. - * It enumerates through the default styles - * @constructor + * Create the HTML DOM for the DataAxis */ - function GraphGroup(group, groupId, options, groupsUsingDefaultStyles) { - this.id = groupId; - var fields = ['sampling', 'style', 'sort', 'yAxisOrientation', 'barChart', 'drawPoints', 'shaded', 'interpolation']; - this.options = util.selectiveBridgeObject(fields, options); - this.usingDefaultStyle = group.className === undefined; - this.groupsUsingDefaultStyles = groupsUsingDefaultStyles; - this.zeroPosition = 0; - this.update(group); - if (this.usingDefaultStyle == true) { - this.groupsUsingDefaultStyles[0] += 1; + DataAxis.prototype.hide = function () { + this.hidden = true; + if (this.dom.frame.parentNode) { + this.dom.frame.parentNode.removeChild(this.dom.frame); } - this.itemsData = []; - this.visible = group.visible === undefined ? true : group.visible; - } - /** - * this loads a reference to all items in this group into this group. - * @param {array} items - */ - GraphGroup.prototype.setItems = function (items) { - if (items != null) { - this.itemsData = items; - if (this.options.sort == true) { - this.itemsData.sort(function (a, b) { - return a.x - b.x; - }); - } - } else { - this.itemsData = []; + if (this.dom.lineContainer.parentNode) { + this.dom.lineContainer.parentNode.removeChild(this.dom.lineContainer); } }; /** - * this is used for plotting barcharts, this way, we only have to calculate it once. - * @param pos + * Set a range (start and end) + * @param end + * @param start + * @param end */ - GraphGroup.prototype.setZeroPosition = function (pos) { - this.zeroPosition = pos; + DataAxis.prototype.setRange = function (start, end) { + if (this.master === false && this.options.alignZeros === true && this.zeroCrossing != -1) { + if (start > 0) { + start = 0; + } + } + this.range.start = start; + this.range.end = end; }; /** - * set the options of the graph group over the default options. - * @param options + * Repaint the component + * @return {boolean} Returns true if the component is resized */ - GraphGroup.prototype.setOptions = function (options) { - if (options !== undefined) { - var fields = ['sampling', 'style', 'sort', 'yAxisOrientation', 'barChart']; - util.selectiveDeepExtend(fields, this.options, options); + DataAxis.prototype.redraw = function () { + var resized = false; + var activeGroups = 0; - util.mergeOptions(this.options, options, 'interpolation'); - util.mergeOptions(this.options, options, 'drawPoints'); - util.mergeOptions(this.options, options, 'shaded'); + // Make sure the line container adheres to the vertical scrolling. + this.dom.lineContainer.style.top = this.body.domProps.scrollTop + 'px'; - if (options.interpolation) { - if (typeof options.interpolation == 'object') { - if (options.interpolation.parametrization) { - if (options.interpolation.parametrization == 'uniform') { - this.options.interpolation.alpha = 0; - } else if (options.interpolation.parametrization == 'chordal') { - this.options.interpolation.alpha = 1; - } else { - this.options.interpolation.parametrization = 'centripetal'; - this.options.interpolation.alpha = 0.5; - } - } + for (var groupId in this.groups) { + if (this.groups.hasOwnProperty(groupId)) { + if (this.groups[groupId].visible === true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] === true)) { + activeGroups++; } } } + if (this.amountOfGroups === 0 || activeGroups === 0) { + this.hide(); + } else { + this.show(); + this.height = Number(this.linegraphSVG.style.height.replace('px', '')); - if (this.options.style == 'line') { - this.type = new Line(this.id, this.options); - } else if (this.options.style == 'bar') { - this.type = new Bar(this.id, this.options); - } else if (this.options.style == 'points') { - this.type = new Points(this.id, this.options); - } - }; + // svg offsetheight did not work in firefox and explorer... + this.dom.lineContainer.style.height = this.height + 'px'; + this.width = this.options.visible === true ? Number(('' + this.options.width).replace('px', '')) : 0; - /** - * this updates the current group class with the latest group dataset entree, used in _updateGroup in linegraph - * @param group - */ - GraphGroup.prototype.update = function (group) { - this.group = group; - this.content = group.content || 'graph'; - this.className = group.className || this.className || 'vis-graph-group' + this.groupsUsingDefaultStyles[0] % 10; - this.visible = group.visible === undefined ? true : group.visible; - this.style = group.style; - this.setOptions(group.options); - }; + var props = this.props; + var frame = this.dom.frame; - /** - * draw the icon for the legend. - * - * @param x - * @param y - * @param JSONcontainer - * @param SVGcontainer - * @param iconWidth - * @param iconHeight - */ - GraphGroup.prototype.drawIcon = function (x, y, JSONcontainer, SVGcontainer, iconWidth, iconHeight) { - var fillHeight = iconHeight * 0.5; - var path, fillPath; + // update classname + frame.className = 'vis-data-axis'; - var outline = DOMutil.getSVGElement('rect', JSONcontainer, SVGcontainer); - outline.setAttributeNS(null, 'x', x); - outline.setAttributeNS(null, 'y', y - fillHeight); - outline.setAttributeNS(null, 'width', iconWidth); - outline.setAttributeNS(null, 'height', 2 * fillHeight); - outline.setAttributeNS(null, 'class', 'vis-outline'); + // calculate character width and height + this._calculateCharSize(); - if (this.options.style == 'line') { - path = DOMutil.getSVGElement('path', JSONcontainer, SVGcontainer); - path.setAttributeNS(null, 'class', this.className); - if (this.style !== undefined) { - path.setAttributeNS(null, 'style', this.style); - } + var orientation = this.options.orientation; + var showMinorLabels = this.options.showMinorLabels; + var showMajorLabels = this.options.showMajorLabels; - path.setAttributeNS(null, 'd', 'M' + x + ',' + y + ' L' + (x + iconWidth) + ',' + y + ''); - if (this.options.shaded.enabled == true) { - fillPath = DOMutil.getSVGElement('path', JSONcontainer, SVGcontainer); - if (this.options.shaded.orientation == 'top') { - fillPath.setAttributeNS(null, 'd', 'M' + x + ', ' + (y - fillHeight) + 'L' + x + ',' + y + ' L' + (x + iconWidth) + ',' + y + ' L' + (x + iconWidth) + ',' + (y - fillHeight)); - } else { - fillPath.setAttributeNS(null, 'd', 'M' + x + ',' + y + ' ' + 'L' + x + ',' + (y + fillHeight) + ' ' + 'L' + (x + iconWidth) + ',' + (y + fillHeight) + 'L' + (x + iconWidth) + ',' + y); - } - fillPath.setAttributeNS(null, 'class', this.className + ' vis-icon-fill'); - } + // determine the width and height of the elements for the axis + props.minorLabelHeight = showMinorLabels ? props.minorCharHeight : 0; + props.majorLabelHeight = showMajorLabels ? props.majorCharHeight : 0; - if (this.options.drawPoints.enabled == true) { - DOMutil.drawPoint(x + 0.5 * iconWidth, y, this, JSONcontainer, SVGcontainer); + props.minorLineWidth = this.body.dom.backgroundHorizontal.offsetWidth - this.lineOffset - this.width + 2 * this.options.minorLinesOffset; + props.minorLineHeight = 1; + props.majorLineWidth = this.body.dom.backgroundHorizontal.offsetWidth - this.lineOffset - this.width + 2 * this.options.majorLinesOffset; + props.majorLineHeight = 1; + + // take frame offline while updating (is almost twice as fast) + if (orientation === 'left') { + frame.style.top = '0'; + frame.style.left = '0'; + frame.style.bottom = ''; + frame.style.width = this.width + 'px'; + frame.style.height = this.height + 'px'; + this.props.width = this.body.domProps.left.width; + this.props.height = this.body.domProps.left.height; + } else { + // right + frame.style.top = ''; + frame.style.bottom = '0'; + frame.style.left = '0'; + frame.style.width = this.width + 'px'; + frame.style.height = this.height + 'px'; + this.props.width = this.body.domProps.right.width; + this.props.height = this.body.domProps.right.height; } - } else { - var barWidth = Math.round(0.3 * iconWidth); - var bar1Height = Math.round(0.4 * iconHeight); - var bar2Height = Math.round(0.75 * iconHeight); - var offset = Math.round((iconWidth - 2 * barWidth) / 3); + resized = this._redrawLabels(); + resized = this._isResized() || resized; - DOMutil.drawBar(x + 0.5 * barWidth + offset, y + fillHeight - bar1Height - 1, barWidth, bar1Height, this.className + ' vis-bar', JSONcontainer, SVGcontainer, this.style); - DOMutil.drawBar(x + 1.5 * barWidth + offset + 2, y + fillHeight - bar2Height - 1, barWidth, bar2Height, this.className + ' vis-bar', JSONcontainer, SVGcontainer, this.style); + if (this.options.icons === true) { + this._redrawGroupIcons(); + } else { + this._cleanupIcons(); + } + + this._redrawTitle(orientation); } + return resized; }; /** - * return the legend entree for this group. - * - * @param iconWidth - * @param iconHeight - * @returns {{icon: HTMLElement, label: (group.content|*|string), orientation: (.options.yAxisOrientation|*)}} + * Repaint major and minor text labels and vertical grid lines + * @private */ - GraphGroup.prototype.getLegend = function (iconWidth, iconHeight) { - var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); - this.drawIcon(0, 0.5 * iconHeight, [], svg, iconWidth, iconHeight); - return { icon: svg, label: this.content, orientation: this.options.yAxisOrientation }; - }; + DataAxis.prototype._redrawLabels = function () { + var resized = false; + DOMutil.prepareElements(this.DOMelements.lines); + DOMutil.prepareElements(this.DOMelements.labels); + var orientation = this.options['orientation']; - GraphGroup.prototype.getYRange = function (groupData) { - return this.type.getYRange(groupData); - }; + // get the range for the slaved axis + var step; + if (this.master === false) { + var stepSize, rangeStart, rangeEnd, minimumStep; + if (this.zeroCrossing !== -1 && this.options.alignZeros === true) { + if (this.range.end > 0) { + stepSize = this.range.end / this.zeroCrossing; // size of one step + rangeStart = this.range.end - this.amountOfSteps * stepSize; + rangeEnd = this.range.end; + } else { + // all of the range (including start) has to be done before the zero crossing. + stepSize = -1 * this.range.start / (this.amountOfSteps - this.zeroCrossing); // absolute size of a step + rangeStart = this.range.start; + rangeEnd = this.range.start + stepSize * this.amountOfSteps; + } + } else { + rangeStart = this.range.start; + rangeEnd = this.range.end; + } + minimumStep = this.stepPixels; + } else { + // calculate range and step (step such that we have space for 7 characters per label) + minimumStep = this.props.majorCharHeight; + rangeStart = this.range.start; + rangeEnd = this.range.end; + } - GraphGroup.prototype.getData = function (groupData) { - return this.type.getData(groupData); - }; + this.step = step = new DataStep(rangeStart, rangeEnd, minimumStep, this.dom.frame.offsetHeight, this.options[this.options.orientation].range, this.options[this.options.orientation].format, this.master === false && this.options.alignZeros // does the step have to align zeros? only if not master and the options is on + ); - GraphGroup.prototype.draw = function (dataset, group, framework) { - this.type.draw(dataset, group, framework); - }; + // the slave axis needs to use the same horizontal lines as the master axis. + if (this.master === true) { + this.stepPixels = this.dom.frame.offsetHeight / step.marginRange * step.step; + this.amountOfSteps = Math.ceil(this.dom.frame.offsetHeight / this.stepPixels); + } else { + // align with zero + if (this.options.alignZeros === true && this.zeroCrossing !== -1) { + // distance is the amount of steps away from the zero crossing we are. + var distance = (step.current - this.zeroCrossing * step.step) / step.step; + this.step.shift(distance); + } + } - module.exports = GraphGroup; + // value at the bottom of the SVG + this.valueAtBottom = step.marginEnd; -/***/ }, -/* 58 */ -/***/ function(module, exports, __webpack_require__) { + this.maxLabelSize = 0; + var y = 0; // init value + var stepIndex = 0; // init value + var isMajor = false; // init value + while (stepIndex < this.amountOfSteps) { + y = Math.round(stepIndex * this.stepPixels); + isMajor = step.isMajor(); - 'use strict'; + if (stepIndex > 0 && stepIndex !== this.amountOfSteps) { + if (this.options['showMinorLabels'] && isMajor === false || this.master === false && this.options['showMinorLabels'] === true) { + this._redrawLabel(y - 2, step.getCurrent(), orientation, 'vis-y-axis vis-minor', this.props.minorCharHeight); + } - var DOMutil = __webpack_require__(11); - var Points = __webpack_require__(59); + if (isMajor && this.options['showMajorLabels'] && this.master === true || this.options['showMinorLabels'] === false && this.master === false && isMajor === true) { + if (y >= 0) { + this._redrawLabel(y - 2, step.getCurrent(), orientation, 'vis-y-axis vis-major', this.props.majorCharHeight); + } + this._redrawLine(y, orientation, 'vis-grid vis-horizontal vis-major', this.options.majorLinesOffset, this.props.majorLineWidth); + } else { + this._redrawLine(y, orientation, 'vis-grid vis-horizontal vis-minor', this.options.minorLinesOffset, this.props.minorLineWidth); + } + } - function Line(groupId, options) { - this.groupId = groupId; - this.options = options; - } + // get zero crossing + if (this.master === true && step.current === 0) { + this.zeroCrossing = stepIndex; + } - Line.prototype.getData = function (groupData) { - var combinedData = []; - for (var j = 0; j < groupData.length; j++) { - combinedData.push({ - x: groupData[j].x, - y: groupData[j].y, - groupId: this.groupId - }); + step.next(); + stepIndex += 1; } - return combinedData; - }; - Line.prototype.getYRange = function (groupData) { - var yMin = groupData[0].y; - var yMax = groupData[0].y; - for (var j = 0; j < groupData.length; j++) { - yMin = yMin > groupData[j].y ? groupData[j].y : yMin; - yMax = yMax < groupData[j].y ? groupData[j].y : yMax; + // get zero crossing if it's the last step + if (this.master === true && step.current === 0) { + this.zeroCrossing = stepIndex; } - return { min: yMin, max: yMax, yAxisOrientation: this.options.yAxisOrientation }; - }; - Line.getStackedYRange = function (combinedData, groupRanges, groupIds, groupLabel, orientation) { - if (combinedData.length > 0) { - // sort by time and by group - combinedData.sort(function (a, b) { - if (a.x === b.x) { - return a.groupId < b.groupId ? -1 : 1; - } else { - return a.x - b.x; - } - }); - var intersections = {}; + this.conversionFactor = this.stepPixels / step.step; - Line._getDataIntersections(intersections, combinedData); - groupRanges[groupLabel] = Line._getStackedYRange(intersections, combinedData); - groupRanges[groupLabel].yAxisOrientation = orientation; - groupIds.push(groupLabel); + // Note that title is rotated, so we're using the height, not width! + var titleWidth = 0; + if (this.options[orientation].title !== undefined && this.options[orientation].title.text !== undefined) { + titleWidth = this.props.titleCharHeight; } - }; + var offset = this.options.icons === true ? Math.max(this.options.iconWidth, titleWidth) + this.options.labelOffsetX + 15 : titleWidth + this.options.labelOffsetX + 15; - Line._getStackedYRange = function (intersections, combinedData) { - var key; - var yMin = combinedData[0].y; - var yMax = combinedData[0].y; - for (var i = 0; i < combinedData.length; i++) { - key = combinedData[i].x; - if (intersections[key] === undefined) { - yMin = yMin > combinedData[i].y ? combinedData[i].y : yMin; - yMax = yMax < combinedData[i].y ? combinedData[i].y : yMax; - } else { - if (combinedData[i].y < 0) { - intersections[key].accumulatedNegative += combinedData[i].y; - } else { - intersections[key].accumulatedPositive += combinedData[i].y; - } - } + // this will resize the yAxis to accommodate the labels. + if (this.maxLabelSize > this.width - offset && this.options.visible === true) { + this.width = this.maxLabelSize + offset; + this.options.width = this.width + 'px'; + DOMutil.cleanupElements(this.DOMelements.lines); + DOMutil.cleanupElements(this.DOMelements.labels); + this.redraw(); + resized = true; } - for (var xpos in intersections) { - if (intersections.hasOwnProperty(xpos)) { - yMin = yMin > intersections[xpos].accumulatedNegative ? intersections[xpos].accumulatedNegative : yMin; - yMin = yMin > intersections[xpos].accumulatedPositive ? intersections[xpos].accumulatedPositive : yMin; - yMax = yMax < intersections[xpos].accumulatedNegative ? intersections[xpos].accumulatedNegative : yMax; - yMax = yMax < intersections[xpos].accumulatedPositive ? intersections[xpos].accumulatedPositive : yMax; - } + // this will resize the yAxis if it is too big for the labels. + else if (this.maxLabelSize < this.width - offset && this.options.visible === true && this.width > this.minWidth) { + this.width = Math.max(this.minWidth, this.maxLabelSize + offset); + this.options.width = this.width + 'px'; + DOMutil.cleanupElements(this.DOMelements.lines); + DOMutil.cleanupElements(this.DOMelements.labels); + this.redraw(); + resized = true; + } else { + DOMutil.cleanupElements(this.DOMelements.lines); + DOMutil.cleanupElements(this.DOMelements.labels); + resized = false; } - return { min: yMin, max: yMax }; + return resized; + }; + + DataAxis.prototype.convertValue = function (value) { + var invertedValue = this.valueAtBottom - value; + var convertedValue = invertedValue * this.conversionFactor; + return convertedValue; + }; + + DataAxis.prototype.screenToValue = function (x) { + return this.valueAtBottom - x / this.conversionFactor; }; /** - * Fill the intersections object with counters of how many datapoints share the same x coordinates - * @param intersections - * @param combinedData + * Create a label for the axis at position x * @private + * @param y + * @param text + * @param orientation + * @param className + * @param characterHeight */ - Line._getDataIntersections = function (intersections, combinedData) { - // get intersections - var coreDistance; - for (var i = 0; i < combinedData.length; i++) { - if (i + 1 < combinedData.length) { - coreDistance = Math.abs(combinedData[i + 1].x - combinedData[i].x); - } - if (i > 0) { - coreDistance = Math.min(coreDistance, Math.abs(combinedData[i - 1].x - combinedData[i].x)); - } - if (coreDistance === 0) { - if (intersections[combinedData[i].x] === undefined) { - intersections[combinedData[i].x] = { amount: 0, resolved: 0, accumulatedPositive: 0, accumulatedNegative: 0 }; - } - intersections[combinedData[i].x].amount += 1; + DataAxis.prototype._redrawLabel = function (y, text, orientation, className, characterHeight) { + // reuse redundant label + var label = DOMutil.getDOMElement('div', this.DOMelements.labels, this.dom.frame); //this.dom.redundant.labels.shift(); + label.className = className; + label.innerHTML = text; + if (orientation === 'left') { + label.style.left = '-' + this.options.labelOffsetX + 'px'; + label.style.textAlign = 'right'; + } else { + label.style.right = '-' + this.options.labelOffsetX + 'px'; + label.style.textAlign = 'left'; + } + + label.style.top = y - 0.5 * characterHeight + this.options.labelOffsetY + 'px'; + + text += ''; + + var largestWidth = Math.max(this.props.majorCharWidth, this.props.minorCharWidth); + if (this.maxLabelSize < text.length * largestWidth) { + this.maxLabelSize = text.length * largestWidth; + } + }; + + /** + * Create a minor line for the axis at position y + * @param y + * @param orientation + * @param className + * @param offset + * @param width + */ + DataAxis.prototype._redrawLine = function (y, orientation, className, offset, width) { + if (this.master === true) { + var line = DOMutil.getDOMElement('div', this.DOMelements.lines, this.dom.lineContainer); //this.dom.redundant.lines.shift(); + line.className = className; + line.innerHTML = ''; + + if (orientation === 'left') { + line.style.left = this.width - offset + 'px'; + } else { + line.style.right = this.width - offset + 'px'; } + + line.style.width = width + 'px'; + line.style.top = y + 'px'; } }; /** - * draw a line graph - * - * @param dataset - * @param group + * Create a title for the axis + * @private + * @param orientation */ - Line.prototype.draw = function (dataset, group, framework) { - if (dataset != null) { - if (dataset.length > 0) { - var path, d; - var svgHeight = Number(framework.svg.style.height.replace('px', '')); - path = DOMutil.getSVGElement('path', framework.svgElements, framework.svg); - path.setAttributeNS(null, 'class', group.className); - if (group.style !== undefined) { - path.setAttributeNS(null, 'style', group.style); - } + DataAxis.prototype._redrawTitle = function (orientation) { + DOMutil.prepareElements(this.DOMelements.title); - // construct path from dataset - if (group.options.interpolation.enabled == true) { - d = Line._catmullRom(dataset, group); - } else { - d = Line._linear(dataset); - } + // Check if the title is defined for this axes + if (this.options[orientation].title !== undefined && this.options[orientation].title.text !== undefined) { + var title = DOMutil.getDOMElement('div', this.DOMelements.title, this.dom.frame); + title.className = 'vis-y-axis vis-title vis-' + orientation; + title.innerHTML = this.options[orientation].title.text; - // append with points for fill and finalize the path - if (group.options.shaded.enabled == true) { - var fillPath = DOMutil.getSVGElement('path', framework.svgElements, framework.svg); - var dFill; - if (group.options.shaded.orientation == 'top') { - dFill = 'M' + dataset[0].x + ',' + 0 + ' ' + d + 'L' + dataset[dataset.length - 1].x + ',' + 0; - } else { - dFill = 'M' + dataset[0].x + ',' + svgHeight + ' ' + d + 'L' + dataset[dataset.length - 1].x + ',' + svgHeight; - } - fillPath.setAttributeNS(null, 'class', group.className + ' vis-fill'); - if (group.options.shaded.style !== undefined) { - fillPath.setAttributeNS(null, 'style', group.options.shaded.style); - } - fillPath.setAttributeNS(null, 'd', dFill); - } - // copy properties to path for drawing. - path.setAttributeNS(null, 'd', 'M' + d); + // Add style - if provided + if (this.options[orientation].title.style !== undefined) { + util.addCssText(title, this.options[orientation].title.style); + } - // draw points - if (group.options.drawPoints.enabled == true) { - Points.draw(dataset, group, framework); - } + if (orientation === 'left') { + title.style.left = this.props.titleCharHeight + 'px'; + } else { + title.style.right = this.props.titleCharHeight + 'px'; } + + title.style.width = this.height + 'px'; } + + // we need to clean up in case we did not use all elements. + DOMutil.cleanupElements(this.DOMelements.title); }; /** - * This uses an uniform parametrization of the interpolation algorithm: - * 'On the Parameterization of Catmull-Rom Curves' by Cem Yuksel et al. - * @param data - * @returns {string} + * Determine the size of text on the axis (both major and minor axis). + * The size is calculated only once and then cached in this.props. * @private */ - Line._catmullRomUniform = function (data) { - // catmull rom - var p0, p1, p2, p3, bp1, bp2; - var d = Math.round(data[0].x) + ',' + Math.round(data[0].y) + ' '; - var normalization = 1 / 6; - var length = data.length; - for (var i = 0; i < length - 1; i++) { + DataAxis.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 = 'vis-y-axis vis-minor vis-measure'; + measureCharMinor.appendChild(textMinor); + this.dom.frame.appendChild(measureCharMinor); - p0 = i == 0 ? data[0] : data[i - 1]; - p1 = data[i]; - p2 = data[i + 1]; - p3 = i + 2 < length ? data[i + 2] : p2; + this.props.minorCharHeight = measureCharMinor.clientHeight; + this.props.minorCharWidth = measureCharMinor.clientWidth; - // Catmull-Rom to Cubic Bezier conversion matrix - // 0 1 0 0 - // -1/6 1 1/6 0 - // 0 1/6 1 -1/6 - // 0 0 1 0 + this.dom.frame.removeChild(measureCharMinor); + } - // bp0 = { x: p1.x, y: p1.y }; - bp1 = { x: (-p0.x + 6 * p1.x + p2.x) * normalization, y: (-p0.y + 6 * p1.y + p2.y) * normalization }; - bp2 = { x: (p1.x + 6 * p2.x - p3.x) * normalization, y: (p1.y + 6 * p2.y - p3.y) * normalization }; - // bp0 = { x: p2.x, y: p2.y }; + if (!('majorCharHeight' in this.props)) { + var textMajor = document.createTextNode('0'); + var measureCharMajor = document.createElement('div'); + measureCharMajor.className = 'vis-y-axis vis-major vis-measure'; + measureCharMajor.appendChild(textMajor); + this.dom.frame.appendChild(measureCharMajor); - d += 'C' + bp1.x + ',' + bp1.y + ' ' + bp2.x + ',' + bp2.y + ' ' + p2.x + ',' + p2.y + ' '; + this.props.majorCharHeight = measureCharMajor.clientHeight; + this.props.majorCharWidth = measureCharMajor.clientWidth; + + this.dom.frame.removeChild(measureCharMajor); } - return d; + if (!('titleCharHeight' in this.props)) { + var textTitle = document.createTextNode('0'); + var measureCharTitle = document.createElement('div'); + measureCharTitle.className = 'vis-y-axis vis-title vis-measure'; + measureCharTitle.appendChild(textTitle); + this.dom.frame.appendChild(measureCharTitle); + + this.props.titleCharHeight = measureCharTitle.clientHeight; + this.props.titleCharWidth = measureCharTitle.clientWidth; + + this.dom.frame.removeChild(measureCharTitle); + } }; + module.exports = DataAxis; + +/***/ }, +/* 60 */ +/***/ function(module, exports, __webpack_require__) { + /** - * This uses either the chordal or centripetal parameterization of the catmull-rom algorithm. - * By default, the centripetal parameterization is used because this gives the nicest results. - * These parameterizations are relatively heavy because the distance between 4 points have to be calculated. + * @constructor DataStep + * The class DataStep is an iterator for data for the lineGraph. You provide a start data point and an + * end data point. The class itself determines the best scale (step size) based on the + * provided start Date, end Date, and minimumStep. * - * One optimization can be used to reuse distances since this is a sliding window approach. - * @param data - * @param group - * @returns {string} - * @private + * If minimumStep is provided, the step size is chosen as close as possible + * to the minimumStep but larger than minimumStep. If minimumStep is not + * provided, the scale is set to 1 DAY. + * The minimumStep should correspond with the onscreen size of about 6 characters + * + * Alternatively, you can set a scale by hand. + * After creation, you can initialize the class by executing first(). Then you + * can iterate from the start date to the end date via next(). You can check if + * the end date is reached with the function hasNext(). After each step, you can + * retrieve the current date via getCurrent(). + * The DataStep has scales ranging from milliseconds, seconds, minutes, hours, + * days, to years. + * + * Version: 1.2 + * + * @param {Date} [start] The start date, for example new Date(2010, 9, 21) + * or new Date(2010, 9, 21, 23, 45, 00) + * @param {Date} [end] The end date + * @param {Number} [minimumStep] Optional. Minimum step size in milliseconds */ - Line._catmullRom = function (data, group) { - var alpha = group.options.interpolation.alpha; - if (alpha == 0 || alpha === undefined) { - return this._catmullRomUniform(data); - } else { - var p0, p1, p2, p3, bp1, bp2, d1, d2, d3, A, B, N, M; - var d3powA, d2powA, d3pow2A, d2pow2A, d1pow2A, d1powA; - var d = Math.round(data[0].x) + ',' + Math.round(data[0].y) + ' '; - var length = data.length; - for (var i = 0; i < length - 1; i++) { + 'use strict'; - p0 = i == 0 ? data[0] : data[i - 1]; - p1 = data[i]; - p2 = data[i + 1]; - p3 = i + 2 < length ? data[i + 2] : p2; + function DataStep(start, end, minimumStep, containerHeight, customRange, formattingFunction, alignZeros) { + // variables + this.current = 0; - d1 = Math.sqrt(Math.pow(p0.x - p1.x, 2) + Math.pow(p0.y - p1.y, 2)); - d2 = Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2)); - d3 = Math.sqrt(Math.pow(p2.x - p3.x, 2) + Math.pow(p2.y - p3.y, 2)); + this.autoScale = true; + this.stepIndex = 0; + this.step = 1; + this.scale = 1; + this.formattingFunction = formattingFunction; - // Catmull-Rom to Cubic Bezier conversion matrix + this.marginStart; + this.marginEnd; + this.deadSpace = 0; - // A = 2d1^2a + 3d1^a * d2^a + d3^2a - // B = 2d3^2a + 3d3^a * d2^a + d2^2a + this.majorSteps = [1, 2, 5, 10]; + this.minorSteps = [0.25, 0.5, 1, 2]; - // [ 0 1 0 0 ] - // [ -d2^2a /N A/N d1^2a /N 0 ] - // [ 0 d3^2a /M B/M -d2^2a /M ] - // [ 0 0 1 0 ] + this.alignZeros = alignZeros; + + this.setRange(start, end, minimumStep, containerHeight, customRange); + } + + /** + * Set a new range + * If minimumStep is provided, the step size is chosen as close as possible + * to the minimumStep but larger than minimumStep. If minimumStep is not + * provided, the scale is set to 1 DAY. + * The minimumStep should correspond with the onscreen size of about 6 characters + * @param {Number} [start] The start date and time. + * @param {Number} [end] The end date and time. + * @param {Number} [minimumStep] Optional. Minimum step size in milliseconds + */ + DataStep.prototype.setRange = function (start, end, minimumStep, containerHeight, customRange) { + this._start = customRange.min === undefined ? start : customRange.min; + this._end = customRange.max === undefined ? end : customRange.max; + if (this._start === this._end) { + this._start = customRange.min === undefined ? this._start - 0.75 : this._start; + this._end = customRange.max === undefined ? this._end + 1 : this._end;; + } + + if (this.autoScale === true) { + this.setMinimumStep(minimumStep, containerHeight); + } - d3powA = Math.pow(d3, alpha); - d3pow2A = Math.pow(d3, 2 * alpha); - d2powA = Math.pow(d2, alpha); - d2pow2A = Math.pow(d2, 2 * alpha); - d1powA = Math.pow(d1, alpha); - d1pow2A = Math.pow(d1, 2 * alpha); + this.setFirst(customRange); + }; - A = 2 * d1pow2A + 3 * d1powA * d2powA + d2pow2A; - B = 2 * d3pow2A + 3 * d3powA * d2powA + d2pow2A; - N = 3 * d1powA * (d1powA + d2powA); - if (N > 0) { - N = 1 / N; - } - M = 3 * d3powA * (d3powA + d2powA); - if (M > 0) { - M = 1 / M; - } + /** + * Automatically determine the scale that bests fits the provided minimum step + * @param {Number} [minimumStep] The minimum step size in pixels + */ + DataStep.prototype.setMinimumStep = function (minimumStep, containerHeight) { + // round to floor + var range = this._end - this._start; + var safeRange = range * 1.2; + var minimumStepValue = minimumStep * (safeRange / containerHeight); + var orderOfMagnitude = Math.round(Math.log(safeRange) / Math.LN10); - bp1 = { x: (-d2pow2A * p0.x + A * p1.x + d1pow2A * p2.x) * N, - y: (-d2pow2A * p0.y + A * p1.y + d1pow2A * p2.y) * N }; + var minorStepIdx = -1; + var magnitudefactor = Math.pow(10, orderOfMagnitude); - bp2 = { x: (d3pow2A * p1.x + B * p2.x - d2pow2A * p3.x) * M, - y: (d3pow2A * p1.y + B * p2.y - d2pow2A * p3.y) * M }; + var start = 0; + if (orderOfMagnitude < 0) { + start = orderOfMagnitude; + } - if (bp1.x == 0 && bp1.y == 0) { - bp1 = p1; - } - if (bp2.x == 0 && bp2.y == 0) { - bp2 = p2; + var solutionFound = false; + for (var i = start; Math.abs(i) <= Math.abs(orderOfMagnitude); i++) { + magnitudefactor = Math.pow(10, i); + for (var j = 0; j < this.minorSteps.length; j++) { + var stepSize = magnitudefactor * this.minorSteps[j]; + if (stepSize >= minimumStepValue) { + solutionFound = true; + minorStepIdx = j; + break; } - d += 'C' + bp1.x + ',' + bp1.y + ' ' + bp2.x + ',' + bp2.y + ' ' + p2.x + ',' + p2.y + ' '; } - - return d; + if (solutionFound === true) { + break; + } } + this.stepIndex = minorStepIdx; + this.scale = magnitudefactor; + this.step = magnitudefactor * this.minorSteps[minorStepIdx]; }; /** - * this generates the SVG path for a linear drawing between datapoints. - * @param data - * @returns {string} - * @private + * Round the current date to the first minor date value + * This must be executed once when the current date is set to start Date */ - Line._linear = function (data) { - // linear - var d = ''; - for (var i = 0; i < data.length; i++) { - if (i == 0) { - d += data[i].x + ',' + data[i].y; - } else { - d += ' ' + data[i].x + ',' + data[i].y; - } + DataStep.prototype.setFirst = function (customRange) { + if (customRange === undefined) { + customRange = {}; } - return d; - }; - module.exports = Line; + var niceStart = customRange.min === undefined ? this._start - this.scale * 2 * this.minorSteps[this.stepIndex] : customRange.min; + var niceEnd = customRange.max === undefined ? this._end + this.scale * this.minorSteps[this.stepIndex] : customRange.max; -/***/ }, -/* 59 */ -/***/ function(module, exports, __webpack_require__) { + this.marginEnd = customRange.max === undefined ? this.roundToMinor(niceEnd) : customRange.max; + this.marginStart = customRange.min === undefined ? this.roundToMinor(niceStart) : customRange.min; - 'use strict'; + // if we need to align the zero's we need to make sure that there is a zero to use. + if (this.alignZeros === true && (this.marginEnd - this.marginStart) % this.step != 0) { + this.marginEnd += this.marginEnd % this.step; + } - var DOMutil = __webpack_require__(11); + this.deadSpace = this.roundToMinor(niceEnd) - niceEnd + this.roundToMinor(niceStart) - niceStart; + this.marginRange = this.marginEnd - this.marginStart; - function Points(groupId, options) { - this.groupId = groupId; - this.options = options; - } + this.current = this.marginEnd; + }; - Points.prototype.getYRange = function (groupData) { - var yMin = groupData[0].y; - var yMax = groupData[0].y; - for (var j = 0; j < groupData.length; j++) { - yMin = yMin > groupData[j].y ? groupData[j].y : yMin; - yMax = yMax < groupData[j].y ? groupData[j].y : yMax; + DataStep.prototype.roundToMinor = function (value) { + var rounded = value - value % (this.scale * this.minorSteps[this.stepIndex]); + if (value % (this.scale * this.minorSteps[this.stepIndex]) > 0.5 * (this.scale * this.minorSteps[this.stepIndex])) { + return rounded + this.scale * this.minorSteps[this.stepIndex]; + } else { + return rounded; } - return { min: yMin, max: yMax, yAxisOrientation: this.options.yAxisOrientation }; }; - Points.prototype.draw = function (dataset, group, framework, offset) { - Points.draw(dataset, group, framework, offset); + /** + * Check if the there is a next step + * @return {boolean} true if the current date has not passed the end date + */ + DataStep.prototype.hasNext = function () { + return this.current >= this.marginStart; }; /** - * draw the data points - * - * @param {Array} dataset - * @param {Object} JSONcontainer - * @param {Object} svg | SVG DOM element - * @param {GraphGroup} group - * @param {Number} [offset] + * Do the next step */ - Points.draw = function (dataset, group, framework, offset) { - if (offset === undefined) { - offset = 0; + DataStep.prototype.next = function () { + var prev = this.current; + this.current -= this.step; + + // safety mechanism: if current time is still unchanged, move to the end + if (this.current === prev) { + this.current = this._end; } - for (var i = 0; i < dataset.length; i++) { - DOMutil.drawPoint(dataset[i].x + offset, dataset[i].y, group, framework.svgElements, framework.svg, dataset[i].label); + }; + + /** + * Do the next step + */ + DataStep.prototype.previous = function () { + this.current += this.step; + this.marginEnd += this.step; + this.marginRange = this.marginEnd - this.marginStart; + }; + + /** + * Get the current datetime + * @return {String} current The current date + */ + DataStep.prototype.getCurrent = function () { + // prevent round-off errors when close to zero + var current = Math.abs(this.current) < this.step / 2 ? 0 : this.current; + var returnValue = current.toPrecision(5); + if (typeof this.formattingFunction === 'function') { + returnValue = this.formattingFunction(current); + } + + if (typeof returnValue === 'number') { + return '' + returnValue; + } else if (typeof returnValue === 'string') { + return returnValue; + } else { + return current.toPrecision(5); } }; - module.exports = Points; + /** + * Check if the current value is a major value (for example when the step + * is DAY, a major value is each first day of the MONTH) + * @return {boolean} true if current date is major, else false. + */ + DataStep.prototype.isMajor = function () { + return this.current % (this.scale * this.majorSteps[this.stepIndex]) === 0; + }; + + DataStep.prototype.shift = function (steps) { + if (steps < 0) { + for (var i = 0; i < -steps; i++) { + this.previous(); + } + } else if (steps > 0) { + for (var i = 0; i < steps; i++) { + this.next(); + } + } + }; + + module.exports = DataStep; /***/ }, -/* 60 */ +/* 61 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var DOMutil = __webpack_require__(11); - var Points = __webpack_require__(59); + var util = __webpack_require__(3); + var DOMutil = __webpack_require__(15); + var Line = __webpack_require__(62); + var Bar = __webpack_require__(64); + var Points = __webpack_require__(63); - function Bargraph(groupId, options) { - this.groupId = groupId; - this.options = options; + /** + * /** + * @param {object} group | the object of the group from the dataset + * @param {string} groupId | ID of the group + * @param {object} options | the default options + * @param {array} groupsUsingDefaultStyles | this array has one entree. + * It is passed as an array so it is passed by reference. + * It enumerates through the default styles + * @constructor + */ + function GraphGroup(group, groupId, options, groupsUsingDefaultStyles) { + this.id = groupId; + var fields = ['sampling', 'style', 'sort', 'yAxisOrientation', 'barChart', 'drawPoints', 'shaded', 'interpolation']; + this.options = util.selectiveBridgeObject(fields, options); + this.usingDefaultStyle = group.className === undefined; + this.groupsUsingDefaultStyles = groupsUsingDefaultStyles; + this.zeroPosition = 0; + this.update(group); + if (this.usingDefaultStyle == true) { + this.groupsUsingDefaultStyles[0] += 1; + } + this.itemsData = []; + this.visible = group.visible === undefined ? true : group.visible; } - Bargraph.prototype.getYRange = function (groupData) { - var yMin = groupData[0].y; - var yMax = groupData[0].y; - for (var j = 0; j < groupData.length; j++) { - yMin = yMin > groupData[j].y ? groupData[j].y : yMin; - yMax = yMax < groupData[j].y ? groupData[j].y : yMax; + /** + * this loads a reference to all items in this group into this group. + * @param {array} items + */ + GraphGroup.prototype.setItems = function (items) { + if (items != null) { + this.itemsData = items; + if (this.options.sort == true) { + this.itemsData.sort(function (a, b) { + return a.x - b.x; + }); + } + } else { + this.itemsData = []; } - return { min: yMin, max: yMax, yAxisOrientation: this.options.yAxisOrientation }; }; - Bargraph.prototype.getData = function (groupData) { - var combinedData = []; - for (var j = 0; j < groupData.length; j++) { - combinedData.push({ - x: groupData[j].x, - y: groupData[j].y, - groupId: this.groupId - }); - } - return combinedData; + /** + * this is used for plotting barcharts, this way, we only have to calculate it once. + * @param pos + */ + GraphGroup.prototype.setZeroPosition = function (pos) { + this.zeroPosition = pos; }; /** - * draw a bar graph - * - * @param groupIds - * @param processedGroupData + * set the options of the graph group over the default options. + * @param options */ - Bargraph.draw = function (groupIds, processedGroupData, framework) { - var combinedData = []; - var intersections = {}; - var coreDistance; - var key, drawData; - var group; - var i, j; - var barPoints = 0; + GraphGroup.prototype.setOptions = function (options) { + if (options !== undefined) { + var fields = ['sampling', 'style', 'sort', 'yAxisOrientation', 'barChart']; + util.selectiveDeepExtend(fields, this.options, options); - // combine all barchart data - for (i = 0; i < groupIds.length; i++) { - group = framework.groups[groupIds[i]]; - if (group.options.style === 'bar') { - if (group.visible === true && (framework.options.groups.visibility[groupIds[i]] === undefined || framework.options.groups.visibility[groupIds[i]] === true)) { - for (j = 0; j < processedGroupData[groupIds[i]].length; j++) { - combinedData.push({ - x: processedGroupData[groupIds[i]][j].x, - y: processedGroupData[groupIds[i]][j].y, - groupId: groupIds[i], - label: processedGroupData[groupIds[i]][j].label - }); - barPoints += 1; + util.mergeOptions(this.options, options, 'interpolation'); + util.mergeOptions(this.options, options, 'drawPoints'); + util.mergeOptions(this.options, options, 'shaded'); + + if (options.interpolation) { + if (typeof options.interpolation == 'object') { + if (options.interpolation.parametrization) { + if (options.interpolation.parametrization == 'uniform') { + this.options.interpolation.alpha = 0; + } else if (options.interpolation.parametrization == 'chordal') { + this.options.interpolation.alpha = 1; + } else { + this.options.interpolation.parametrization = 'centripetal'; + this.options.interpolation.alpha = 0.5; + } } } } } - if (barPoints === 0) { - return; + if (this.options.style == 'line') { + this.type = new Line(this.id, this.options); + } else if (this.options.style == 'bar') { + this.type = new Bar(this.id, this.options); + } else if (this.options.style == 'points') { + this.type = new Points(this.id, this.options); } + }; - // sort by time and by group - combinedData.sort(function (a, b) { - if (a.x === b.x) { - return a.groupId < b.groupId ? -1 : 1; - } else { - return a.x - b.x; - } - }); + /** + * this updates the current group class with the latest group dataset entree, used in _updateGroup in linegraph + * @param group + */ + GraphGroup.prototype.update = function (group) { + this.group = group; + this.content = group.content || 'graph'; + this.className = group.className || this.className || 'vis-graph-group' + this.groupsUsingDefaultStyles[0] % 10; + this.visible = group.visible === undefined ? true : group.visible; + this.style = group.style; + this.setOptions(group.options); + }; - // get intersections - Bargraph._getDataIntersections(intersections, combinedData); + /** + * draw the icon for the legend. + * + * @param x + * @param y + * @param JSONcontainer + * @param SVGcontainer + * @param iconWidth + * @param iconHeight + */ + GraphGroup.prototype.drawIcon = function (x, y, JSONcontainer, SVGcontainer, iconWidth, iconHeight) { + var fillHeight = iconHeight * 0.5; + var path, fillPath; - // plot barchart - for (i = 0; i < combinedData.length; i++) { - group = framework.groups[combinedData[i].groupId]; - var minWidth = 0.1 * group.options.barChart.width; + var outline = DOMutil.getSVGElement('rect', JSONcontainer, SVGcontainer); + outline.setAttributeNS(null, 'x', x); + outline.setAttributeNS(null, 'y', y - fillHeight); + outline.setAttributeNS(null, 'width', iconWidth); + outline.setAttributeNS(null, 'height', 2 * fillHeight); + outline.setAttributeNS(null, 'class', 'vis-outline'); - key = combinedData[i].x; - var heightOffset = 0; - if (intersections[key] === undefined) { - if (i + 1 < combinedData.length) { - coreDistance = Math.abs(combinedData[i + 1].x - key); - } - if (i > 0) { - coreDistance = Math.min(coreDistance, Math.abs(combinedData[i - 1].x - key)); - } - drawData = Bargraph._getSafeDrawData(coreDistance, group, minWidth); - } else { - var nextKey = i + (intersections[key].amount - intersections[key].resolved); - var prevKey = i - (intersections[key].resolved + 1); - if (nextKey < combinedData.length) { - coreDistance = Math.abs(combinedData[nextKey].x - key); - } - if (prevKey > 0) { - coreDistance = Math.min(coreDistance, Math.abs(combinedData[prevKey].x - key)); - } - drawData = Bargraph._getSafeDrawData(coreDistance, group, minWidth); - intersections[key].resolved += 1; + if (this.options.style == 'line') { + path = DOMutil.getSVGElement('path', JSONcontainer, SVGcontainer); + path.setAttributeNS(null, 'class', this.className); + if (this.style !== undefined) { + path.setAttributeNS(null, 'style', this.style); + } - if (group.options.stack === true) { - if (combinedData[i].y < group.zeroPosition) { - heightOffset = intersections[key].accumulatedNegative; - intersections[key].accumulatedNegative += group.zeroPosition - combinedData[i].y; - } else { - heightOffset = intersections[key].accumulatedPositive; - intersections[key].accumulatedPositive += group.zeroPosition - combinedData[i].y; - } - } else if (group.options.barChart.sideBySide === true) { - drawData.width = drawData.width / intersections[key].amount; - drawData.offset += intersections[key].resolved * drawData.width - 0.5 * drawData.width * (intersections[key].amount + 1); - if (group.options.barChart.align === 'left') { - drawData.offset -= 0.5 * drawData.width; - } else if (group.options.barChart.align === 'right') { - drawData.offset += 0.5 * drawData.width; - } + path.setAttributeNS(null, 'd', 'M' + x + ',' + y + ' L' + (x + iconWidth) + ',' + y + ''); + if (this.options.shaded.enabled == true) { + fillPath = DOMutil.getSVGElement('path', JSONcontainer, SVGcontainer); + if (this.options.shaded.orientation == 'top') { + fillPath.setAttributeNS(null, 'd', 'M' + x + ', ' + (y - fillHeight) + 'L' + x + ',' + y + ' L' + (x + iconWidth) + ',' + y + ' L' + (x + iconWidth) + ',' + (y - fillHeight)); + } else { + fillPath.setAttributeNS(null, 'd', 'M' + x + ',' + y + ' ' + 'L' + x + ',' + (y + fillHeight) + ' ' + 'L' + (x + iconWidth) + ',' + (y + fillHeight) + 'L' + (x + iconWidth) + ',' + y); } + fillPath.setAttributeNS(null, 'class', this.className + ' vis-icon-fill'); } - DOMutil.drawBar(combinedData[i].x + drawData.offset, combinedData[i].y - heightOffset, drawData.width, group.zeroPosition - combinedData[i].y, group.className + ' vis-bar', framework.svgElements, framework.svg, group.style); - // draw points - if (group.options.drawPoints.enabled === true) { - Points.draw([combinedData[i]], group, framework, drawData.offset); - //DOMutil.drawPoint(combinedData[i].x + drawData.offset, combinedData[i].y, group, framework.svgElements, framework.svg); - } - } - }; - /** - * Fill the intersections object with counters of how many datapoints share the same x coordinates - * @param intersections - * @param combinedData - * @private - */ - Bargraph._getDataIntersections = function (intersections, combinedData) { - // get intersections - var coreDistance; - for (var i = 0; i < combinedData.length; i++) { - if (i + 1 < combinedData.length) { - coreDistance = Math.abs(combinedData[i + 1].x - combinedData[i].x); - } - if (i > 0) { - coreDistance = Math.min(coreDistance, Math.abs(combinedData[i - 1].x - combinedData[i].x)); - } - if (coreDistance === 0) { - if (intersections[combinedData[i].x] === undefined) { - intersections[combinedData[i].x] = { amount: 0, resolved: 0, accumulatedPositive: 0, accumulatedNegative: 0 }; - } - intersections[combinedData[i].x].amount += 1; + if (this.options.drawPoints.enabled == true) { + DOMutil.drawPoint(x + 0.5 * iconWidth, y, this, JSONcontainer, SVGcontainer); } + } else { + var barWidth = Math.round(0.3 * iconWidth); + var bar1Height = Math.round(0.4 * iconHeight); + var bar2Height = Math.round(0.75 * iconHeight); + + var offset = Math.round((iconWidth - 2 * barWidth) / 3); + + DOMutil.drawBar(x + 0.5 * barWidth + offset, y + fillHeight - bar1Height - 1, barWidth, bar1Height, this.className + ' vis-bar', JSONcontainer, SVGcontainer, this.style); + DOMutil.drawBar(x + 1.5 * barWidth + offset + 2, y + fillHeight - bar2Height - 1, barWidth, bar2Height, this.className + ' vis-bar', JSONcontainer, SVGcontainer, this.style); } }; /** - * Get the width and offset for bargraphs based on the coredistance between datapoints + * return the legend entree for this group. * - * @param coreDistance - * @param group - * @param minWidth - * @returns {{width: Number, offset: Number}} - * @private + * @param iconWidth + * @param iconHeight + * @returns {{icon: HTMLElement, label: (group.content|*|string), orientation: (.options.yAxisOrientation|*)}} */ - Bargraph._getSafeDrawData = function (coreDistance, group, minWidth) { - var width, offset; - if (coreDistance < group.options.barChart.width && coreDistance > 0) { - width = coreDistance < minWidth ? minWidth : coreDistance; + GraphGroup.prototype.getLegend = function (iconWidth, iconHeight) { + var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + this.drawIcon(0, 0.5 * iconHeight, [], svg, iconWidth, iconHeight); + return { icon: svg, label: this.content, orientation: this.options.yAxisOrientation }; + }; - offset = 0; // recalculate offset with the new width; - if (group.options.barChart.align === 'left') { - offset -= 0.5 * coreDistance; - } else if (group.options.barChart.align === 'right') { - offset += 0.5 * coreDistance; - } - } else { - // default settings - width = group.options.barChart.width; - offset = 0; - if (group.options.barChart.align === 'left') { - offset -= 0.5 * group.options.barChart.width; - } else if (group.options.barChart.align === 'right') { - offset += 0.5 * group.options.barChart.width; - } + GraphGroup.prototype.getYRange = function (groupData) { + return this.type.getYRange(groupData); + }; + + GraphGroup.prototype.getData = function (groupData) { + return this.type.getData(groupData); + }; + + GraphGroup.prototype.draw = function (dataset, group, framework) { + this.type.draw(dataset, group, framework); + }; + + module.exports = GraphGroup; + +/***/ }, +/* 62 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var DOMutil = __webpack_require__(15); + var Points = __webpack_require__(63); + + function Line(groupId, options) { + this.groupId = groupId; + this.options = options; + } + + Line.prototype.getData = function (groupData) { + var combinedData = []; + for (var j = 0; j < groupData.length; j++) { + combinedData.push({ + x: groupData[j].x, + y: groupData[j].y, + groupId: this.groupId + }); } + return combinedData; + }; - return { width: width, offset: offset }; + Line.prototype.getYRange = function (groupData) { + var yMin = groupData[0].y; + var yMax = groupData[0].y; + for (var j = 0; j < groupData.length; j++) { + yMin = yMin > groupData[j].y ? groupData[j].y : yMin; + yMax = yMax < groupData[j].y ? groupData[j].y : yMax; + } + return { min: yMin, max: yMax, yAxisOrientation: this.options.yAxisOrientation }; }; - Bargraph.getStackedYRange = function (combinedData, groupRanges, groupIds, groupLabel, orientation) { + Line.getStackedYRange = function (combinedData, groupRanges, groupIds, groupLabel, orientation) { if (combinedData.length > 0) { // sort by time and by group combinedData.sort(function (a, b) { @@ -27555,14 +29077,14 @@ return /******/ (function(modules) { // webpackBootstrap }); var intersections = {}; - Bargraph._getDataIntersections(intersections, combinedData); - groupRanges[groupLabel] = Bargraph._getStackedYRange(intersections, combinedData); + Line._getDataIntersections(intersections, combinedData); + groupRanges[groupLabel] = Line._getStackedYRange(intersections, combinedData); groupRanges[groupLabel].yAxisOrientation = orientation; groupIds.push(groupLabel); } }; - Bargraph._getStackedYRange = function (intersections, combinedData) { + Line._getStackedYRange = function (intersections, combinedData) { var key; var yMin = combinedData[0].y; var yMax = combinedData[0].y; @@ -27591,1101 +29113,999 @@ return /******/ (function(modules) { // webpackBootstrap return { min: yMin, max: yMax }; }; - module.exports = Bargraph; - -/***/ }, -/* 61 */ -/***/ function(module, exports, __webpack_require__) { - - 'use strict'; - - var util = __webpack_require__(2); - var DOMutil = __webpack_require__(11); - var Component = __webpack_require__(25); - - /** - * Legend for Graph2d - */ - function Legend(body, options, side, linegraphOptions) { - this.body = body; - this.defaultOptions = { - enabled: true, - icons: true, - iconSize: 20, - iconSpacing: 6, - left: { - visible: true, - position: 'top-left' // top/bottom - left,center,right - }, - right: { - visible: true, - position: 'top-left' // top/bottom - left,center,right - } - }; - this.side = side; - this.options = util.extend({}, this.defaultOptions); - this.linegraphOptions = linegraphOptions; - - this.svgElements = {}; - this.dom = {}; - this.groups = {}; - this.amountOfGroups = 0; - this._create(); - - this.setOptions(options); - } - - Legend.prototype = new Component(); - - Legend.prototype.clear = function () { - this.groups = {}; - this.amountOfGroups = 0; - }; - - Legend.prototype.addGroup = function (label, graphOptions) { - - if (!this.groups.hasOwnProperty(label)) { - this.groups[label] = graphOptions; - } - this.amountOfGroups += 1; - }; - - Legend.prototype.updateGroup = function (label, graphOptions) { - this.groups[label] = graphOptions; - }; - - Legend.prototype.removeGroup = function (label) { - if (this.groups.hasOwnProperty(label)) { - delete this.groups[label]; - this.amountOfGroups -= 1; - } - }; - - Legend.prototype._create = function () { - this.dom.frame = document.createElement('div'); - this.dom.frame.className = 'vis-legend'; - this.dom.frame.style.position = 'absolute'; - this.dom.frame.style.top = '10px'; - this.dom.frame.style.display = 'block'; - - this.dom.textArea = document.createElement('div'); - this.dom.textArea.className = 'vis-legend-text'; - this.dom.textArea.style.position = 'relative'; - this.dom.textArea.style.top = '0px'; - - this.svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); - this.svg.style.position = 'absolute'; - this.svg.style.top = 0 + 'px'; - this.svg.style.width = this.options.iconSize + 5 + 'px'; - this.svg.style.height = '100%'; - - this.dom.frame.appendChild(this.svg); - this.dom.frame.appendChild(this.dom.textArea); - }; - - /** - * Hide the component from the DOM - */ - Legend.prototype.hide = function () { - // remove the frame containing the items - if (this.dom.frame.parentNode) { - this.dom.frame.parentNode.removeChild(this.dom.frame); - } - }; - /** - * Show the component in the DOM (when not already visible). - * @return {Boolean} changed + * Fill the intersections object with counters of how many datapoints share the same x coordinates + * @param intersections + * @param combinedData + * @private */ - Legend.prototype.show = function () { - // show frame containing the items - if (!this.dom.frame.parentNode) { - this.body.dom.center.appendChild(this.dom.frame); - } - }; - - Legend.prototype.setOptions = function (options) { - var fields = ['enabled', 'orientation', 'icons', 'left', 'right']; - util.selectiveDeepExtend(fields, this.options, options); - }; - - Legend.prototype.redraw = function () { - var activeGroups = 0; - var groupArray = Object.keys(this.groups); - groupArray.sort(function (a, b) { - return a < b ? -1 : 1; - }); - - for (var i = 0; i < groupArray.length; i++) { - var groupId = groupArray[i]; - if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) { - activeGroups++; - } - } - - if (this.options[this.side].visible == false || this.amountOfGroups == 0 || this.options.enabled == false || activeGroups == 0) { - this.hide(); - } else { - this.show(); - if (this.options[this.side].position == 'top-left' || this.options[this.side].position == 'bottom-left') { - this.dom.frame.style.left = '4px'; - this.dom.frame.style.textAlign = 'left'; - this.dom.textArea.style.textAlign = 'left'; - this.dom.textArea.style.left = this.options.iconSize + 15 + 'px'; - this.dom.textArea.style.right = ''; - this.svg.style.left = 0 + 'px'; - this.svg.style.right = ''; - } else { - this.dom.frame.style.right = '4px'; - this.dom.frame.style.textAlign = 'right'; - this.dom.textArea.style.textAlign = 'right'; - this.dom.textArea.style.right = this.options.iconSize + 15 + 'px'; - this.dom.textArea.style.left = ''; - this.svg.style.right = 0 + 'px'; - this.svg.style.left = ''; - } - - if (this.options[this.side].position == 'top-left' || this.options[this.side].position == 'top-right') { - this.dom.frame.style.top = 4 - Number(this.body.dom.center.style.top.replace('px', '')) + 'px'; - this.dom.frame.style.bottom = ''; - } else { - var scrollableHeight = this.body.domProps.center.height - this.body.domProps.centerContainer.height; - this.dom.frame.style.bottom = 4 + scrollableHeight + Number(this.body.dom.center.style.top.replace('px', '')) + 'px'; - this.dom.frame.style.top = ''; - } - - if (this.options.icons == false) { - this.dom.frame.style.width = this.dom.textArea.offsetWidth + 10 + 'px'; - this.dom.textArea.style.right = ''; - this.dom.textArea.style.left = ''; - this.svg.style.width = '0px'; - } else { - this.dom.frame.style.width = this.options.iconSize + 15 + this.dom.textArea.offsetWidth + 10 + 'px'; - this.drawLegendIcons(); + Line._getDataIntersections = function (intersections, combinedData) { + // get intersections + var coreDistance; + for (var i = 0; i < combinedData.length; i++) { + if (i + 1 < combinedData.length) { + coreDistance = Math.abs(combinedData[i + 1].x - combinedData[i].x); } - - var content = ''; - for (var i = 0; i < groupArray.length; i++) { - var groupId = groupArray[i]; - if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) { - content += this.groups[groupId].content + '
'; - } + if (i > 0) { + coreDistance = Math.min(coreDistance, Math.abs(combinedData[i - 1].x - combinedData[i].x)); } - this.dom.textArea.innerHTML = content; - this.dom.textArea.style.lineHeight = 0.75 * this.options.iconSize + this.options.iconSpacing + 'px'; - } - }; - - Legend.prototype.drawLegendIcons = function () { - if (this.dom.frame.parentNode) { - var groupArray = Object.keys(this.groups); - groupArray.sort(function (a, b) { - return a < b ? -1 : 1; - }); - - DOMutil.prepareElements(this.svgElements); - var padding = window.getComputedStyle(this.dom.frame).paddingTop; - var iconOffset = Number(padding.replace('px', '')); - var x = iconOffset; - var iconWidth = this.options.iconSize; - var iconHeight = 0.75 * this.options.iconSize; - var y = iconOffset + 0.5 * iconHeight + 3; - - this.svg.style.width = iconWidth + 5 + iconOffset + 'px'; - - for (var i = 0; i < groupArray.length; i++) { - var groupId = groupArray[i]; - if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) { - this.groups[groupId].drawIcon(x, y, this.svgElements, this.svg, iconWidth, iconHeight); - y += iconHeight + this.options.iconSpacing; + if (coreDistance === 0) { + if (intersections[combinedData[i].x] === undefined) { + intersections[combinedData[i].x] = { amount: 0, resolved: 0, accumulatedPositive: 0, accumulatedNegative: 0 }; } + intersections[combinedData[i].x].amount += 1; } - - DOMutil.cleanupElements(this.svgElements); } }; - module.exports = Legend; - -/***/ }, -/* 62 */ -/***/ function(module, exports, __webpack_require__) { - /** - * This object contains all possible options. It will check if the types are correct, if required if the option is one - * of the allowed values. + * draw a line graph * - * __any__ means that the name of the property does not matter. - * __type__ is a required field for all objects and contains the allowed types of all objects + * @param dataset + * @param group */ - 'use strict'; - - Object.defineProperty(exports, '__esModule', { - value: true - }); - var string = 'string'; - var boolean = 'boolean'; - var number = 'number'; - var array = 'array'; - var date = 'date'; - var object = 'object'; // should only be in a __type__ property - var dom = 'dom'; - var moment = 'moment'; - var fn = 'function'; - var nada = 'null'; - var undef = 'undefined'; - var any = 'any'; - - var allOptions = { - configure: { - enabled: { boolean: boolean }, - filter: { boolean: boolean, fn: fn }, - container: { dom: dom }, - __type__: { object: object, boolean: boolean, fn: fn } - }, - - //globals : - yAxisOrientation: { string: ['left', 'right'] }, - defaultGroup: { string: string }, - sort: { boolean: boolean }, - sampling: { boolean: boolean }, - stack: { boolean: boolean }, - graphHeight: { string: string, number: number }, - shaded: { - enabled: { boolean: boolean }, - orientation: { string: ['bottom', 'top'] }, // top, bottom - __type__: { boolean: boolean, object: object } - }, - style: { string: ['line', 'bar', 'points'] }, // line, bar - barChart: { - width: { number: number }, - sideBySide: { boolean: boolean }, - align: { string: ['left', 'center', 'right'] }, - __type__: { object: object } - }, - interpolation: { - enabled: { boolean: boolean }, - parametrization: { string: ['centripetal', 'chordal', 'uniform'] }, // uniform (alpha = 0.0), chordal (alpha = 1.0), centripetal (alpha = 0.5) - alpha: { number: number }, - __type__: { object: object, boolean: boolean } - }, - drawPoints: { - enabled: { boolean: boolean }, - size: { number: number }, - style: { string: ['square', 'circle'] }, // square, circle - __type__: { object: object, boolean: boolean } - }, - dataAxis: { - showMinorLabels: { boolean: boolean }, - showMajorLabels: { boolean: boolean }, - icons: { boolean: boolean }, - width: { string: string, number: number }, - visible: { boolean: boolean }, - alignZeros: { boolean: boolean }, - left: { - range: { min: { number: number }, max: { number: number }, __type__: { object: object } }, - format: { fn: fn }, - title: { text: { string: string, number: number }, style: { string: string }, __type__: { object: object } }, - __type__: { object: object } - }, - right: { - range: { min: { number: number }, max: { number: number }, __type__: { object: object } }, - format: { fn: fn }, - title: { text: { string: string, number: number }, style: { string: string }, __type__: { object: object } }, - __type__: { object: object } - }, - __type__: { object: object } - }, - legend: { - enabled: { boolean: boolean }, - icons: { boolean: boolean }, - left: { - visible: { boolean: boolean }, - position: { string: ['top-right', 'bottom-right', 'top-left', 'bottom-left'] }, - __type__: { object: object } - }, - right: { - visible: { boolean: boolean }, - position: { string: ['top-right', 'bottom-right', 'top-left', 'bottom-left'] }, - __type__: { object: object } - }, - __type__: { object: object, boolean: boolean } - }, - groups: { - visibility: { any: any }, - __type__: { object: object } - }, - - autoResize: { boolean: boolean }, - clickToUse: { boolean: boolean }, - end: { number: number, date: date, string: string, moment: moment }, - format: { - minorLabels: { - millisecond: { string: string, undef: undef }, - second: { string: string, undef: undef }, - minute: { string: string, undef: undef }, - hour: { string: string, undef: undef }, - weekday: { string: string, undef: undef }, - day: { string: string, undef: undef }, - month: { string: string, undef: undef }, - year: { string: string, undef: undef }, - __type__: { object: object } - }, - majorLabels: { - millisecond: { string: string, undef: undef }, - second: { string: string, undef: undef }, - minute: { string: string, undef: undef }, - hour: { string: string, undef: undef }, - weekday: { string: string, undef: undef }, - day: { string: string, undef: undef }, - month: { string: string, undef: undef }, - year: { string: string, undef: undef }, - __type__: { object: object } - }, - __type__: { object: object } - }, - height: { string: string, number: number }, - hiddenDates: { object: object, array: array }, - locale: { string: string }, - locales: { - __any__: { object: object }, - __type__: { object: object } - }, - max: { date: date, number: number, string: string, moment: moment }, - maxHeight: { number: number, string: string }, - min: { date: date, number: number, string: string, moment: moment }, - minHeight: { number: number, string: string }, - moveable: { boolean: boolean }, - multiselect: { boolean: boolean }, - orientation: { string: string }, - showCurrentTime: { boolean: boolean }, - showMajorLabels: { boolean: boolean }, - showMinorLabels: { boolean: boolean }, - start: { date: date, number: number, string: string, moment: moment }, - timeAxis: { - scale: { string: string, undef: undef }, - step: { number: number, undef: undef }, - __type__: { object: object } - }, - width: { string: string, number: number }, - zoomable: { boolean: boolean }, - zoomMax: { number: number }, - zoomMin: { number: number }, - __type__: { object: object } - }; - - var configureOptions = { - global: { - //yAxisOrientation: ['left','right'], // TDOO: enable as soon as Grahp2d doesn't crash when changing this on the fly - sort: true, - sampling: true, - stack: false, - shaded: { - enabled: false, - orientation: ['top', 'bottom'] // top, bottom - }, - style: ['line', 'bar', 'points'], // line, bar - barChart: { - width: [50, 5, 100, 5], - sideBySide: false, - align: ['left', 'center', 'right'] // left, center, right - }, - interpolation: { - enabled: true, - parametrization: ['centripetal', 'chordal', 'uniform'] // uniform (alpha = 0.0), chordal (alpha = 1.0), centripetal (alpha = 0.5) - }, - drawPoints: { - enabled: true, - size: [6, 2, 30, 1], - style: ['square', 'circle'] // square, circle - }, - dataAxis: { - showMinorLabels: true, - showMajorLabels: true, - icons: false, - width: [40, 0, 200, 1], - visible: true, - alignZeros: true, - left: { - //range: {min:undefined,max:undefined}, - //format: function (value) {return value;}, - title: { text: '', style: '' } - }, - right: { - //range: {min:undefined,max:undefined}, - //format: function (value) {return value;}, - title: { text: '', style: '' } + Line.prototype.draw = function (dataset, group, framework) { + if (dataset != null) { + if (dataset.length > 0) { + var path, d; + var svgHeight = Number(framework.svg.style.height.replace('px', '')); + path = DOMutil.getSVGElement('path', framework.svgElements, framework.svg); + path.setAttributeNS(null, 'class', group.className); + if (group.style !== undefined) { + path.setAttributeNS(null, 'style', group.style); } - }, - legend: { - enabled: false, - icons: true, - left: { - visible: true, - position: ['top-right', 'bottom-right', 'top-left', 'bottom-left'] // top/bottom - left,right - }, - right: { - visible: true, - position: ['top-right', 'bottom-right', 'top-left', 'bottom-left'] // top/bottom - left,right + + // construct path from dataset + if (group.options.interpolation.enabled == true) { + d = Line._catmullRom(dataset, group); + } else { + d = Line._linear(dataset); } - }, - autoResize: true, - clickToUse: false, - end: '', - format: { - minorLabels: { - millisecond: 'SSS', - second: 's', - minute: 'HH:mm', - hour: 'HH:mm', - weekday: 'ddd D', - day: 'D', - month: 'MMM', - year: 'YYYY' - }, - majorLabels: { - millisecond: 'HH:mm:ss', - second: 'D MMMM HH:mm', - minute: 'ddd D MMMM', - hour: 'ddd D MMMM', - weekday: 'MMMM YYYY', - day: 'MMMM YYYY', - month: 'YYYY', - year: '' + // append with points for fill and finalize the path + if (group.options.shaded.enabled == true) { + var fillPath = DOMutil.getSVGElement('path', framework.svgElements, framework.svg); + var dFill; + if (group.options.shaded.orientation == 'top') { + dFill = 'M' + dataset[0].x + ',' + 0 + ' ' + d + 'L' + dataset[dataset.length - 1].x + ',' + 0; + } else { + dFill = 'M' + dataset[0].x + ',' + svgHeight + ' ' + d + 'L' + dataset[dataset.length - 1].x + ',' + svgHeight; + } + fillPath.setAttributeNS(null, 'class', group.className + ' vis-fill'); + if (group.options.shaded.style !== undefined) { + fillPath.setAttributeNS(null, 'style', group.options.shaded.style); + } + fillPath.setAttributeNS(null, 'd', dFill); } - }, + // copy properties to path for drawing. + path.setAttributeNS(null, 'd', 'M' + d); - height: '', - locale: '', - max: '', - maxHeight: '', - min: '', - minHeight: '', - moveable: true, - orientation: ['both', 'bottom', 'top'], - showCurrentTime: false, - showMajorLabels: true, - showMinorLabels: true, - start: '', - width: '100%', - zoomable: true, - zoomMax: [315360000000000, 10, 315360000000000, 1], - zoomMin: [10, 10, 315360000000000, 1] + // draw points + if (group.options.drawPoints.enabled == true) { + Points.draw(dataset, group, framework); + } + } } }; - exports.allOptions = allOptions; - exports.configureOptions = configureOptions; - -/***/ }, -/* 63 */ -/***/ function(module, exports, __webpack_require__) { + /** + * This uses an uniform parametrization of the interpolation algorithm: + * 'On the Parameterization of Catmull-Rom Curves' by Cem Yuksel et al. + * @param data + * @returns {string} + * @private + */ + Line._catmullRomUniform = function (data) { + // catmull rom + var p0, p1, p2, p3, bp1, bp2; + var d = Math.round(data[0].x) + ',' + Math.round(data[0].y) + ' '; + var normalization = 1 / 6; + var length = data.length; + for (var i = 0; i < length - 1; i++) { - // Load custom shapes into CanvasRenderingContext2D - 'use strict'; + p0 = i == 0 ? data[0] : data[i - 1]; + p1 = data[i]; + p2 = data[i + 1]; + p3 = i + 2 < length ? data[i + 2] : p2; - function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + // Catmull-Rom to Cubic Bezier conversion matrix + // 0 1 0 0 + // -1/6 1 1/6 0 + // 0 1/6 1 -1/6 + // 0 0 1 0 - var _modulesGroups = __webpack_require__(64); + // bp0 = { x: p1.x, y: p1.y }; + bp1 = { x: (-p0.x + 6 * p1.x + p2.x) * normalization, y: (-p0.y + 6 * p1.y + p2.y) * normalization }; + bp2 = { x: (p1.x + 6 * p2.x - p3.x) * normalization, y: (p1.y + 6 * p2.y - p3.y) * normalization }; + // bp0 = { x: p2.x, y: p2.y }; - var _modulesGroups2 = _interopRequireDefault(_modulesGroups); + d += 'C' + bp1.x + ',' + bp1.y + ' ' + bp2.x + ',' + bp2.y + ' ' + p2.x + ',' + p2.y + ' '; + } - var _modulesNodesHandler = __webpack_require__(65); + return d; + }; - var _modulesNodesHandler2 = _interopRequireDefault(_modulesNodesHandler); + /** + * This uses either the chordal or centripetal parameterization of the catmull-rom algorithm. + * By default, the centripetal parameterization is used because this gives the nicest results. + * These parameterizations are relatively heavy because the distance between 4 points have to be calculated. + * + * One optimization can be used to reuse distances since this is a sliding window approach. + * @param data + * @param group + * @returns {string} + * @private + */ + Line._catmullRom = function (data, group) { + var alpha = group.options.interpolation.alpha; + if (alpha == 0 || alpha === undefined) { + return this._catmullRomUniform(data); + } else { + var p0, p1, p2, p3, bp1, bp2, d1, d2, d3, A, B, N, M; + var d3powA, d2powA, d3pow2A, d2pow2A, d1pow2A, d1powA; + var d = Math.round(data[0].x) + ',' + Math.round(data[0].y) + ' '; + var length = data.length; + for (var i = 0; i < length - 1; i++) { - var _modulesEdgesHandler = __webpack_require__(83); + p0 = i == 0 ? data[0] : data[i - 1]; + p1 = data[i]; + p2 = data[i + 1]; + p3 = i + 2 < length ? data[i + 2] : p2; - var _modulesEdgesHandler2 = _interopRequireDefault(_modulesEdgesHandler); + d1 = Math.sqrt(Math.pow(p0.x - p1.x, 2) + Math.pow(p0.y - p1.y, 2)); + d2 = Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2)); + d3 = Math.sqrt(Math.pow(p2.x - p3.x, 2) + Math.pow(p2.y - p3.y, 2)); - var _modulesPhysicsEngine = __webpack_require__(3); + // Catmull-Rom to Cubic Bezier conversion matrix - var _modulesPhysicsEngine2 = _interopRequireDefault(_modulesPhysicsEngine); + // A = 2d1^2a + 3d1^a * d2^a + d3^2a + // B = 2d3^2a + 3d3^a * d2^a + d2^2a - var _modulesClustering = __webpack_require__(98); + // [ 0 1 0 0 ] + // [ -d2^2a /N A/N d1^2a /N 0 ] + // [ 0 d3^2a /M B/M -d2^2a /M ] + // [ 0 0 1 0 ] - var _modulesClustering2 = _interopRequireDefault(_modulesClustering); + d3powA = Math.pow(d3, alpha); + d3pow2A = Math.pow(d3, 2 * alpha); + d2powA = Math.pow(d2, alpha); + d2pow2A = Math.pow(d2, 2 * alpha); + d1powA = Math.pow(d1, alpha); + d1pow2A = Math.pow(d1, 2 * alpha); - var _modulesCanvasRenderer = __webpack_require__(100); + A = 2 * d1pow2A + 3 * d1powA * d2powA + d2pow2A; + B = 2 * d3pow2A + 3 * d3powA * d2powA + d2pow2A; + N = 3 * d1powA * (d1powA + d2powA); + if (N > 0) { + N = 1 / N; + } + M = 3 * d3powA * (d3powA + d2powA); + if (M > 0) { + M = 1 / M; + } - var _modulesCanvasRenderer2 = _interopRequireDefault(_modulesCanvasRenderer); + bp1 = { x: (-d2pow2A * p0.x + A * p1.x + d1pow2A * p2.x) * N, + y: (-d2pow2A * p0.y + A * p1.y + d1pow2A * p2.y) * N }; - var _modulesCanvas = __webpack_require__(101); + bp2 = { x: (d3pow2A * p1.x + B * p2.x - d2pow2A * p3.x) * M, + y: (d3pow2A * p1.y + B * p2.y - d2pow2A * p3.y) * M }; - var _modulesCanvas2 = _interopRequireDefault(_modulesCanvas); + if (bp1.x == 0 && bp1.y == 0) { + bp1 = p1; + } + if (bp2.x == 0 && bp2.y == 0) { + bp2 = p2; + } + d += 'C' + bp1.x + ',' + bp1.y + ' ' + bp2.x + ',' + bp2.y + ' ' + p2.x + ',' + p2.y + ' '; + } - var _modulesView = __webpack_require__(102); + return d; + } + }; - var _modulesView2 = _interopRequireDefault(_modulesView); + /** + * this generates the SVG path for a linear drawing between datapoints. + * @param data + * @returns {string} + * @private + */ + Line._linear = function (data) { + // linear + var d = ''; + for (var i = 0; i < data.length; i++) { + if (i == 0) { + d += data[i].x + ',' + data[i].y; + } else { + d += ' ' + data[i].x + ',' + data[i].y; + } + } + return d; + }; - var _modulesInteractionHandler = __webpack_require__(103); + module.exports = Line; - var _modulesInteractionHandler2 = _interopRequireDefault(_modulesInteractionHandler); +/***/ }, +/* 63 */ +/***/ function(module, exports, __webpack_require__) { - var _modulesSelectionHandler = __webpack_require__(4); + 'use strict'; - var _modulesSelectionHandler2 = _interopRequireDefault(_modulesSelectionHandler); + var DOMutil = __webpack_require__(15); - var _modulesLayoutEngine = __webpack_require__(106); + function Points(groupId, options) { + this.groupId = groupId; + this.options = options; + } - var _modulesLayoutEngine2 = _interopRequireDefault(_modulesLayoutEngine); + Points.prototype.getYRange = function (groupData) { + var yMin = groupData[0].y; + var yMax = groupData[0].y; + for (var j = 0; j < groupData.length; j++) { + yMin = yMin > groupData[j].y ? groupData[j].y : yMin; + yMax = yMax < groupData[j].y ? groupData[j].y : yMax; + } + return { min: yMin, max: yMax, yAxisOrientation: this.options.yAxisOrientation }; + }; - var _modulesManipulationSystem = __webpack_require__(107); + Points.prototype.draw = function (dataset, group, framework, offset) { + Points.draw(dataset, group, framework, offset); + }; - var _modulesManipulationSystem2 = _interopRequireDefault(_modulesManipulationSystem); + /** + * draw the data points + * + * @param {Array} dataset + * @param {Object} JSONcontainer + * @param {Object} svg | SVG DOM element + * @param {GraphGroup} group + * @param {Number} [offset] + */ + Points.draw = function (dataset, group, framework, offset) { + if (offset === undefined) { + offset = 0; + } + for (var i = 0; i < dataset.length; i++) { + DOMutil.drawPoint(dataset[i].x + offset, dataset[i].y, group, framework.svgElements, framework.svg, dataset[i].label); + } + }; - var _sharedConfigurator = __webpack_require__(49); + module.exports = Points; - var _sharedConfigurator2 = _interopRequireDefault(_sharedConfigurator); +/***/ }, +/* 64 */ +/***/ function(module, exports, __webpack_require__) { - var _sharedValidator = __webpack_require__(51); + 'use strict'; - var _sharedValidator2 = _interopRequireDefault(_sharedValidator); + var DOMutil = __webpack_require__(15); + var Points = __webpack_require__(63); - var _optionsJs = __webpack_require__(108); + function Bargraph(groupId, options) { + this.groupId = groupId; + this.options = options; + } - __webpack_require__(109); + Bargraph.prototype.getYRange = function (groupData) { + var yMin = groupData[0].y; + var yMax = groupData[0].y; + for (var j = 0; j < groupData.length; j++) { + yMin = yMin > groupData[j].y ? groupData[j].y : yMin; + yMax = yMax < groupData[j].y ? groupData[j].y : yMax; + } + return { min: yMin, max: yMax, yAxisOrientation: this.options.yAxisOrientation }; + }; - var Emitter = __webpack_require__(17); - var Hammer = __webpack_require__(27); - var util = __webpack_require__(2); - var DataSet = __webpack_require__(12); - var DataView = __webpack_require__(14); - var dotparser = __webpack_require__(110); - var gephiParser = __webpack_require__(111); - var Images = __webpack_require__(112); - var Activator = __webpack_require__(46); - var locales = __webpack_require__(113); + Bargraph.prototype.getData = function (groupData) { + var combinedData = []; + for (var j = 0; j < groupData.length; j++) { + combinedData.push({ + x: groupData[j].x, + y: groupData[j].y, + groupId: this.groupId + }); + } + return combinedData; + }; /** - * @constructor Network - * Create a network visualization, displaying nodes and edges. + * draw a bar graph * - * @param {Element} container The DOM element in which the Network will - * be created. Normally a div element. - * @param {Object} data An object containing parameters - * {Array} nodes - * {Array} edges - * @param {Object} options Options + * @param groupIds + * @param processedGroupData */ - function Network(container, data, options) { - var _this = this; + Bargraph.draw = function (groupIds, processedGroupData, framework) { + var combinedData = []; + var intersections = {}; + var coreDistance; + var key, drawData; + var group; + var i, j; + var barPoints = 0; - if (!(this instanceof Network)) { - throw new SyntaxError('Constructor must be called with the new operator'); + // combine all barchart data + for (i = 0; i < groupIds.length; i++) { + group = framework.groups[groupIds[i]]; + if (group.options.style === 'bar') { + if (group.visible === true && (framework.options.groups.visibility[groupIds[i]] === undefined || framework.options.groups.visibility[groupIds[i]] === true)) { + for (j = 0; j < processedGroupData[groupIds[i]].length; j++) { + combinedData.push({ + x: processedGroupData[groupIds[i]][j].x, + y: processedGroupData[groupIds[i]][j].y, + groupId: groupIds[i], + label: processedGroupData[groupIds[i]][j].label + }); + barPoints += 1; + } + } + } } - // set constant values - this.options = {}; - this.defaultOptions = { - locale: 'en', - locales: locales, - clickToUse: false - }; - util.extend(this.options, this.defaultOptions); + if (barPoints === 0) { + return; + } - // containers for nodes and edges - this.body = { - container: container, - nodes: {}, - nodeIndices: [], - edges: {}, - edgeIndices: [], - emitter: { - on: this.on.bind(this), - off: this.off.bind(this), - emit: this.emit.bind(this), - once: this.once.bind(this) - }, - eventListeners: { - onTap: function onTap() {}, - onTouch: function onTouch() {}, - onDoubleTap: function onDoubleTap() {}, - onHold: function onHold() {}, - onDragStart: function onDragStart() {}, - onDrag: function onDrag() {}, - onDragEnd: function onDragEnd() {}, - onMouseWheel: function onMouseWheel() {}, - onPinch: function onPinch() {}, - onMouseMove: function onMouseMove() {}, - onRelease: function onRelease() {}, - onContext: function onContext() {} - }, - data: { - nodes: null, // A DataSet or DataView - edges: null // A DataSet or DataView - }, - functions: { - createNode: function createNode() {}, - createEdge: function createEdge() {}, - getPointer: function getPointer() {} - }, - view: { - scale: 1, - translation: { x: 0, y: 0 } + // sort by time and by group + combinedData.sort(function (a, b) { + if (a.x === b.x) { + return a.groupId < b.groupId ? -1 : 1; + } else { + return a.x - b.x; } - }; + }); - // bind the event listeners - this.bindEventListeners(); + // get intersections + Bargraph._getDataIntersections(intersections, combinedData); - // setting up all modules - this.images = new Images(function () { - return _this.body.emitter.emit('_requestRedraw'); - }); // object with images - this.groups = new _modulesGroups2['default'](); // object with groups - this.canvas = new _modulesCanvas2['default'](this.body); // DOM handler - this.selectionHandler = new _modulesSelectionHandler2['default'](this.body, this.canvas); // Selection handler - this.interactionHandler = new _modulesInteractionHandler2['default'](this.body, this.canvas, this.selectionHandler); // Interaction handler handles all the hammer bindings (that are bound by canvas), key - this.view = new _modulesView2['default'](this.body, this.canvas); // camera handler, does animations and zooms - this.renderer = new _modulesCanvasRenderer2['default'](this.body, this.canvas); // renderer, starts renderloop, has events that modules can hook into - this.physics = new _modulesPhysicsEngine2['default'](this.body); // physics engine, does all the simulations - this.layoutEngine = new _modulesLayoutEngine2['default'](this.body); // layout engine for inital layout and hierarchical layout - this.clustering = new _modulesClustering2['default'](this.body); // clustering api - this.manipulation = new _modulesManipulationSystem2['default'](this.body, this.canvas, this.selectionHandler); // data manipulation system + // plot barchart + for (i = 0; i < combinedData.length; i++) { + group = framework.groups[combinedData[i].groupId]; + var minWidth = 0.1 * group.options.barChart.width; - this.nodesHandler = new _modulesNodesHandler2['default'](this.body, this.images, this.groups, this.layoutEngine); // Handle adding, deleting and updating of nodes as well as global options - this.edgesHandler = new _modulesEdgesHandler2['default'](this.body, this.images, this.groups); // Handle adding, deleting and updating of edges as well as global options + key = combinedData[i].x; + var heightOffset = 0; + if (intersections[key] === undefined) { + if (i + 1 < combinedData.length) { + coreDistance = Math.abs(combinedData[i + 1].x - key); + } + if (i > 0) { + coreDistance = Math.min(coreDistance, Math.abs(combinedData[i - 1].x - key)); + } + drawData = Bargraph._getSafeDrawData(coreDistance, group, minWidth); + } else { + var nextKey = i + (intersections[key].amount - intersections[key].resolved); + var prevKey = i - (intersections[key].resolved + 1); + if (nextKey < combinedData.length) { + coreDistance = Math.abs(combinedData[nextKey].x - key); + } + if (prevKey > 0) { + coreDistance = Math.min(coreDistance, Math.abs(combinedData[prevKey].x - key)); + } + drawData = Bargraph._getSafeDrawData(coreDistance, group, minWidth); + intersections[key].resolved += 1; + + if (group.options.stack === true) { + if (combinedData[i].y < group.zeroPosition) { + heightOffset = intersections[key].accumulatedNegative; + intersections[key].accumulatedNegative += group.zeroPosition - combinedData[i].y; + } else { + heightOffset = intersections[key].accumulatedPositive; + intersections[key].accumulatedPositive += group.zeroPosition - combinedData[i].y; + } + } else if (group.options.barChart.sideBySide === true) { + drawData.width = drawData.width / intersections[key].amount; + drawData.offset += intersections[key].resolved * drawData.width - 0.5 * drawData.width * (intersections[key].amount + 1); + if (group.options.barChart.align === 'left') { + drawData.offset -= 0.5 * drawData.width; + } else if (group.options.barChart.align === 'right') { + drawData.offset += 0.5 * drawData.width; + } + } + } + DOMutil.drawBar(combinedData[i].x + drawData.offset, combinedData[i].y - heightOffset, drawData.width, group.zeroPosition - combinedData[i].y, group.className + ' vis-bar', framework.svgElements, framework.svg, group.style); + // draw points + if (group.options.drawPoints.enabled === true) { + Points.draw([combinedData[i]], group, framework, drawData.offset); + //DOMutil.drawPoint(combinedData[i].x + drawData.offset, combinedData[i].y, group, framework.svgElements, framework.svg); + } + } + }; + + /** + * Fill the intersections object with counters of how many datapoints share the same x coordinates + * @param intersections + * @param combinedData + * @private + */ + Bargraph._getDataIntersections = function (intersections, combinedData) { + // get intersections + var coreDistance; + for (var i = 0; i < combinedData.length; i++) { + if (i + 1 < combinedData.length) { + coreDistance = Math.abs(combinedData[i + 1].x - combinedData[i].x); + } + if (i > 0) { + coreDistance = Math.min(coreDistance, Math.abs(combinedData[i - 1].x - combinedData[i].x)); + } + if (coreDistance === 0) { + if (intersections[combinedData[i].x] === undefined) { + intersections[combinedData[i].x] = { amount: 0, resolved: 0, accumulatedPositive: 0, accumulatedNegative: 0 }; + } + intersections[combinedData[i].x].amount += 1; + } + } + }; + + /** + * Get the width and offset for bargraphs based on the coredistance between datapoints + * + * @param coreDistance + * @param group + * @param minWidth + * @returns {{width: Number, offset: Number}} + * @private + */ + Bargraph._getSafeDrawData = function (coreDistance, group, minWidth) { + var width, offset; + if (coreDistance < group.options.barChart.width && coreDistance > 0) { + width = coreDistance < minWidth ? minWidth : coreDistance; + + offset = 0; // recalculate offset with the new width; + if (group.options.barChart.align === 'left') { + offset -= 0.5 * coreDistance; + } else if (group.options.barChart.align === 'right') { + offset += 0.5 * coreDistance; + } + } else { + // default settings + width = group.options.barChart.width; + offset = 0; + if (group.options.barChart.align === 'left') { + offset -= 0.5 * group.options.barChart.width; + } else if (group.options.barChart.align === 'right') { + offset += 0.5 * group.options.barChart.width; + } + } + + return { width: width, offset: offset }; + }; + + Bargraph.getStackedYRange = function (combinedData, groupRanges, groupIds, groupLabel, orientation) { + if (combinedData.length > 0) { + // sort by time and by group + combinedData.sort(function (a, b) { + if (a.x === b.x) { + return a.groupId < b.groupId ? -1 : 1; + } else { + return a.x - b.x; + } + }); + var intersections = {}; + + Bargraph._getDataIntersections(intersections, combinedData); + groupRanges[groupLabel] = Bargraph._getStackedYRange(intersections, combinedData); + groupRanges[groupLabel].yAxisOrientation = orientation; + groupIds.push(groupLabel); + } + }; + + Bargraph._getStackedYRange = function (intersections, combinedData) { + var key; + var yMin = combinedData[0].y; + var yMax = combinedData[0].y; + for (var i = 0; i < combinedData.length; i++) { + key = combinedData[i].x; + if (intersections[key] === undefined) { + yMin = yMin > combinedData[i].y ? combinedData[i].y : yMin; + yMax = yMax < combinedData[i].y ? combinedData[i].y : yMax; + } else { + if (combinedData[i].y < 0) { + intersections[key].accumulatedNegative += combinedData[i].y; + } else { + intersections[key].accumulatedPositive += combinedData[i].y; + } + } + } + for (var xpos in intersections) { + if (intersections.hasOwnProperty(xpos)) { + yMin = yMin > intersections[xpos].accumulatedNegative ? intersections[xpos].accumulatedNegative : yMin; + yMin = yMin > intersections[xpos].accumulatedPositive ? intersections[xpos].accumulatedPositive : yMin; + yMax = yMax < intersections[xpos].accumulatedNegative ? intersections[xpos].accumulatedNegative : yMax; + yMax = yMax < intersections[xpos].accumulatedPositive ? intersections[xpos].accumulatedPositive : yMax; + } + } - // create the DOM elements - this.canvas._create(); + return { min: yMin, max: yMax }; + }; - // setup configuration system - this.configurator = new _sharedConfigurator2['default'](this, this.body.container, _optionsJs.configureOptions, this.canvas.pixelRatio); + module.exports = Bargraph; - // apply options - this.setOptions(options); +/***/ }, +/* 65 */ +/***/ function(module, exports, __webpack_require__) { - // load data (the disable start variable will be the same as the enabled clustering) - this.setData(data); - } + 'use strict'; - // Extend Network with an Emitter mixin - Emitter(Network.prototype); + var util = __webpack_require__(3); + var DOMutil = __webpack_require__(15); + var Component = __webpack_require__(29); /** - * Set options - * @param {Object} options + * Legend for Graph2d */ - Network.prototype.setOptions = function (options) { - var _this2 = this; - - if (options !== undefined) { - - var errorFound = _sharedValidator2['default'].validate(options, _optionsJs.allOptions); - if (errorFound === true) { - console.log('%cErrors have been found in the supplied options object.', _sharedValidator.printStyle); + function Legend(body, options, side, linegraphOptions) { + this.body = body; + this.defaultOptions = { + enabled: true, + icons: true, + iconSize: 20, + iconSpacing: 6, + left: { + visible: true, + position: 'top-left' // top/bottom - left,center,right + }, + right: { + visible: true, + position: 'top-left' // top/bottom - left,center,right } + }; + this.side = side; + this.options = util.extend({}, this.defaultOptions); + this.linegraphOptions = linegraphOptions; - // copy the global fields over - var fields = ['locale', 'locales', 'clickToUse']; - util.selectiveDeepExtend(fields, this.options, options); + this.svgElements = {}; + this.dom = {}; + this.groups = {}; + this.amountOfGroups = 0; + this._create(); - // the hierarchical system can adapt the edges and the physics to it's own options because not all combinations work with the hierarichical system. - options = this.layoutEngine.setOptions(options.layout, options); + this.setOptions(options); + } - this.canvas.setOptions(options); // options for canvas are in globals + Legend.prototype = new Component(); - // pass the options to the modules - this.groups.setOptions(options.groups); - this.nodesHandler.setOptions(options.nodes); - this.edgesHandler.setOptions(options.edges); - this.physics.setOptions(options.physics); - this.manipulation.setOptions(options.manipulation, options, this.options); // manipulation uses the locales in the globals + Legend.prototype.clear = function () { + this.groups = {}; + this.amountOfGroups = 0; + }; - this.interactionHandler.setOptions(options.interaction); - this.renderer.setOptions(options.interaction); // options for rendering are in interaction - this.selectionHandler.setOptions(options.interaction); // options for selection are in interaction + Legend.prototype.addGroup = function (label, graphOptions) { - // reload the settings of the nodes to apply changes in groups that are not referenced by pointer. - if (options.groups !== undefined) { - this.body.emitter.emit('refreshNodes'); - } - // these two do not have options at the moment, here for completeness - //this.view.setOptions(options.view); - //this.clustering.setOptions(options.clustering); + if (!this.groups.hasOwnProperty(label)) { + this.groups[label] = graphOptions; + } + this.amountOfGroups += 1; + }; - this.configurator.setOptions(options.configure); + Legend.prototype.updateGroup = function (label, graphOptions) { + this.groups[label] = graphOptions; + }; - // if the configuration system is enabled, copy all options and put them into the config system - if (this.configurator.options.enabled === true) { - var networkOptions = { nodes: {}, edges: {}, layout: {}, interaction: {}, manipulation: {}, physics: {}, global: {} }; - util.deepExtend(networkOptions.nodes, this.nodesHandler.options); - util.deepExtend(networkOptions.edges, this.edgesHandler.options); - util.deepExtend(networkOptions.layout, this.layoutEngine.options); - // load the selectionHandler and rendere default options in to the interaction group - util.deepExtend(networkOptions.interaction, this.selectionHandler.options); - util.deepExtend(networkOptions.interaction, this.renderer.options); + Legend.prototype.removeGroup = function (label) { + if (this.groups.hasOwnProperty(label)) { + delete this.groups[label]; + this.amountOfGroups -= 1; + } + }; - util.deepExtend(networkOptions.interaction, this.interactionHandler.options); - util.deepExtend(networkOptions.manipulation, this.manipulation.options); - util.deepExtend(networkOptions.physics, this.physics.options); + Legend.prototype._create = function () { + this.dom.frame = document.createElement('div'); + this.dom.frame.className = 'vis-legend'; + this.dom.frame.style.position = 'absolute'; + this.dom.frame.style.top = '10px'; + this.dom.frame.style.display = 'block'; - // load globals into the global object - util.deepExtend(networkOptions.global, this.canvas.options); - util.deepExtend(networkOptions.global, this.options); + this.dom.textArea = document.createElement('div'); + this.dom.textArea.className = 'vis-legend-text'; + this.dom.textArea.style.position = 'relative'; + this.dom.textArea.style.top = '0px'; - this.configurator.setModuleOptions(networkOptions); - } + this.svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + this.svg.style.position = 'absolute'; + this.svg.style.top = 0 + 'px'; + this.svg.style.width = this.options.iconSize + 5 + 'px'; + this.svg.style.height = '100%'; - // handle network global options - if (options.clickToUse !== undefined) { - if (options.clickToUse === true) { - if (this.activator === undefined) { - this.activator = new Activator(this.canvas.frame); - this.activator.on('change', function () { - _this2.body.emitter.emit('activate'); - }); - } - } else { - if (this.activator !== undefined) { - this.activator.destroy(); - delete this.activator; - } - this.body.emitter.emit('activate'); - } - } else { - this.body.emitter.emit('activate'); - } + this.dom.frame.appendChild(this.svg); + this.dom.frame.appendChild(this.dom.textArea); + }; - this.canvas.setSize(); - // start the physics simulation. Can be safely called multiple times. - this.body.emitter.emit('startSimulation'); + /** + * Hide the component from the DOM + */ + Legend.prototype.hide = function () { + // remove the frame containing the items + if (this.dom.frame.parentNode) { + this.dom.frame.parentNode.removeChild(this.dom.frame); } }; /** - * Update the this.body.nodeIndices with the most recent node index list - * @private + * Show the component in the DOM (when not already visible). + * @return {Boolean} changed */ - Network.prototype._updateVisibleIndices = function () { - var nodes = this.body.nodes; - var edges = this.body.edges; - this.body.nodeIndices = []; - this.body.edgeIndices = []; - - for (var nodeId in nodes) { - if (nodes.hasOwnProperty(nodeId)) { - if (nodes[nodeId].options.hidden === false) { - this.body.nodeIndices.push(nodeId); - } - } + Legend.prototype.show = function () { + // show frame containing the items + if (!this.dom.frame.parentNode) { + this.body.dom.center.appendChild(this.dom.frame); } + }; - for (var edgeId in edges) { - if (edges.hasOwnProperty(edgeId)) { - if (edges[edgeId].options.hidden === false) { - this.body.edgeIndices.push(edgeId); - } + Legend.prototype.setOptions = function (options) { + var fields = ['enabled', 'orientation', 'icons', 'left', 'right']; + util.selectiveDeepExtend(fields, this.options, options); + }; + + Legend.prototype.redraw = function () { + var activeGroups = 0; + var groupArray = Object.keys(this.groups); + groupArray.sort(function (a, b) { + return a < b ? -1 : 1; + }); + + for (var i = 0; i < groupArray.length; i++) { + var groupId = groupArray[i]; + if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) { + activeGroups++; } } - }; - /** - * Bind all events - */ - Network.prototype.bindEventListeners = function () { - var _this3 = this; + if (this.options[this.side].visible == false || this.amountOfGroups == 0 || this.options.enabled == false || activeGroups == 0) { + this.hide(); + } else { + this.show(); + if (this.options[this.side].position == 'top-left' || this.options[this.side].position == 'bottom-left') { + this.dom.frame.style.left = '4px'; + this.dom.frame.style.textAlign = 'left'; + this.dom.textArea.style.textAlign = 'left'; + this.dom.textArea.style.left = this.options.iconSize + 15 + 'px'; + this.dom.textArea.style.right = ''; + this.svg.style.left = 0 + 'px'; + this.svg.style.right = ''; + } else { + this.dom.frame.style.right = '4px'; + this.dom.frame.style.textAlign = 'right'; + this.dom.textArea.style.textAlign = 'right'; + this.dom.textArea.style.right = this.options.iconSize + 15 + 'px'; + this.dom.textArea.style.left = ''; + this.svg.style.right = 0 + 'px'; + this.svg.style.left = ''; + } - // this event will trigger a rebuilding of the cache everything. Used when nodes or edges have been added or removed. - this.body.emitter.on('_dataChanged', function () { - // update shortcut lists - _this3._updateVisibleIndices(); - _this3.physics.updatePhysicsData(); + if (this.options[this.side].position == 'top-left' || this.options[this.side].position == 'top-right') { + this.dom.frame.style.top = 4 - Number(this.body.dom.center.style.top.replace('px', '')) + 'px'; + this.dom.frame.style.bottom = ''; + } else { + var scrollableHeight = this.body.domProps.center.height - this.body.domProps.centerContainer.height; + this.dom.frame.style.bottom = 4 + scrollableHeight + Number(this.body.dom.center.style.top.replace('px', '')) + 'px'; + this.dom.frame.style.top = ''; + } - // call the dataUpdated event because the only difference between the two is the updating of the indices - _this3.body.emitter.emit('_dataUpdated'); - }); + if (this.options.icons == false) { + this.dom.frame.style.width = this.dom.textArea.offsetWidth + 10 + 'px'; + this.dom.textArea.style.right = ''; + this.dom.textArea.style.left = ''; + this.svg.style.width = '0px'; + } else { + this.dom.frame.style.width = this.options.iconSize + 15 + this.dom.textArea.offsetWidth + 10 + 'px'; + this.drawLegendIcons(); + } - // this is called when options of EXISTING nodes or edges have changed. - this.body.emitter.on('_dataUpdated', function () { - // update values - _this3._updateValueRange(_this3.body.nodes); - _this3._updateValueRange(_this3.body.edges); - // start simulation (can be called safely, even if already running) - _this3.body.emitter.emit('startSimulation'); - }); + var content = ''; + for (var i = 0; i < groupArray.length; i++) { + var groupId = groupArray[i]; + if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) { + content += this.groups[groupId].content + '
'; + } + } + this.dom.textArea.innerHTML = content; + this.dom.textArea.style.lineHeight = 0.75 * this.options.iconSize + this.options.iconSpacing + 'px'; + } }; - /** - * Set nodes and edges, and optionally options as well. - * - * @param {Object} data Object containing parameters: - * {Array | DataSet | DataView} [nodes] Array with nodes - * {Array | DataSet | DataView} [edges] Array with edges - * {String} [dot] String containing data in DOT format - * {String} [gephi] String containing data in gephi JSON format - * {Options} [options] Object with options - */ - Network.prototype.setData = function (data) { - // reset the physics engine. - this.body.emitter.emit('resetPhysics'); - this.body.emitter.emit('_resetData'); + Legend.prototype.drawLegendIcons = function () { + if (this.dom.frame.parentNode) { + var groupArray = Object.keys(this.groups); + groupArray.sort(function (a, b) { + return a < b ? -1 : 1; + }); - // unselect all to ensure no selections from old data are carried over. - this.selectionHandler.unselectAll(); + DOMutil.prepareElements(this.svgElements); + var padding = window.getComputedStyle(this.dom.frame).paddingTop; + var iconOffset = Number(padding.replace('px', '')); + var x = iconOffset; + var iconWidth = this.options.iconSize; + var iconHeight = 0.75 * this.options.iconSize; + var y = iconOffset + 0.5 * iconHeight + 3; - if (data && data.dot && (data.nodes || data.edges)) { - throw new SyntaxError('Data must contain either parameter "dot" or ' + ' parameter pair "nodes" and "edges", but not both.'); - } + this.svg.style.width = iconWidth + 5 + iconOffset + 'px'; - // set options - this.setOptions(data && data.options); - // set all data - if (data && data.dot) { - console.log('The dot property has been depricated. Please use the static convertDot method to convert DOT into vis.network format and use the normal data format with nodes and edges. This converter is used like this: var data = vis.network.convertDot(dotString);'); - // parse DOT file - var dotData = dotparser.DOTToGraph(data.dot); - this.setData(dotData); - return; - } else if (data && data.gephi) { - // parse DOT file - console.log('The gephi property has been depricated. Please use the static convertGephi method to convert gephi into vis.network format and use the normal data format with nodes and edges. This converter is used like this: var data = vis.network.convertGephi(gephiJson);'); - var gephiData = gephiParser.parseGephi(data.gephi); - this.setData(gephiData); - return; - } else { - this.nodesHandler.setData(data && data.nodes, true); - this.edgesHandler.setData(data && data.edges, true); + for (var i = 0; i < groupArray.length; i++) { + var groupId = groupArray[i]; + if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) { + this.groups[groupId].drawIcon(x, y, this.svgElements, this.svg, iconWidth, iconHeight); + y += iconHeight + this.options.iconSpacing; + } + } + + DOMutil.cleanupElements(this.svgElements); } + }; - // emit change in data - this.body.emitter.emit('_dataChanged'); + module.exports = Legend; - // find a stable position or start animating to a stable position - this.body.emitter.emit('initPhysics'); - }; +/***/ }, +/* 66 */ +/***/ function(module, exports, __webpack_require__) { /** - * Cleans up all bindings of the network, removing it fully from the memory IF the variable is set to null after calling this function. - * var network = new vis.Network(..); - * network.destroy(); - * network = null; + * This object contains all possible options. It will check if the types are correct, if required if the option is one + * of the allowed values. + * + * __any__ means that the name of the property does not matter. + * __type__ is a required field for all objects and contains the allowed types of all objects */ - Network.prototype.destroy = function () { - this.body.emitter.emit('destroy'); - // clear events - this.body.emitter.off(); - this.off(); + 'use strict'; - // delete modules - delete this.groups; - delete this.canvas; - delete this.selectionHandler; - delete this.interactionHandler; - delete this.view; - delete this.renderer; - delete this.physics; - delete this.layoutEngine; - delete this.clustering; - delete this.manipulation; - delete this.nodesHandler; - delete this.edgesHandler; - delete this.configurator; - delete this.images; + Object.defineProperty(exports, '__esModule', { + value: true + }); + var string = 'string'; + var boolean = 'boolean'; + var number = 'number'; + var array = 'array'; + var date = 'date'; + var object = 'object'; // should only be in a __type__ property + var dom = 'dom'; + var moment = 'moment'; + var fn = 'function'; + var nada = 'null'; + var undef = 'undefined'; + var any = 'any'; - for (var nodeId in this.body.nodes) { - delete this.body.nodes[nodeId]; - } - for (var edgeId in this.body.edges) { - delete this.body.edges[edgeId]; - } + var allOptions = { + configure: { + enabled: { boolean: boolean }, + filter: { boolean: boolean, fn: fn }, + container: { dom: dom }, + __type__: { object: object, boolean: boolean, fn: fn } + }, - // remove the container and everything inside it recursively - util.recursiveDOMDelete(this.body.container); + //globals : + yAxisOrientation: { string: ['left', 'right'] }, + defaultGroup: { string: string }, + sort: { boolean: boolean }, + sampling: { boolean: boolean }, + stack: { boolean: boolean }, + graphHeight: { string: string, number: number }, + shaded: { + enabled: { boolean: boolean }, + orientation: { string: ['bottom', 'top'] }, // top, bottom + __type__: { boolean: boolean, object: object } + }, + style: { string: ['line', 'bar', 'points'] }, // line, bar + barChart: { + width: { number: number }, + sideBySide: { boolean: boolean }, + align: { string: ['left', 'center', 'right'] }, + __type__: { object: object } + }, + interpolation: { + enabled: { boolean: boolean }, + parametrization: { string: ['centripetal', 'chordal', 'uniform'] }, // uniform (alpha = 0.0), chordal (alpha = 1.0), centripetal (alpha = 0.5) + alpha: { number: number }, + __type__: { object: object, boolean: boolean } + }, + drawPoints: { + enabled: { boolean: boolean }, + size: { number: number }, + style: { string: ['square', 'circle'] }, // square, circle + __type__: { object: object, boolean: boolean } + }, + dataAxis: { + showMinorLabels: { boolean: boolean }, + showMajorLabels: { boolean: boolean }, + icons: { boolean: boolean }, + width: { string: string, number: number }, + visible: { boolean: boolean }, + alignZeros: { boolean: boolean }, + left: { + range: { min: { number: number }, max: { number: number }, __type__: { object: object } }, + format: { fn: fn }, + title: { text: { string: string, number: number }, style: { string: string }, __type__: { object: object } }, + __type__: { object: object } + }, + right: { + range: { min: { number: number }, max: { number: number }, __type__: { object: object } }, + format: { fn: fn }, + title: { text: { string: string, number: number }, style: { string: string }, __type__: { object: object } }, + __type__: { object: object } + }, + __type__: { object: object } + }, + legend: { + enabled: { boolean: boolean }, + icons: { boolean: boolean }, + left: { + visible: { boolean: boolean }, + position: { string: ['top-right', 'bottom-right', 'top-left', 'bottom-left'] }, + __type__: { object: object } + }, + right: { + visible: { boolean: boolean }, + position: { string: ['top-right', 'bottom-right', 'top-left', 'bottom-left'] }, + __type__: { object: object } + }, + __type__: { object: object, boolean: boolean } + }, + groups: { + visibility: { any: any }, + __type__: { object: object } + }, + + autoResize: { boolean: boolean }, + clickToUse: { boolean: boolean }, + end: { number: number, date: date, string: string, moment: moment }, + format: { + minorLabels: { + millisecond: { string: string, undef: undef }, + second: { string: string, undef: undef }, + minute: { string: string, undef: undef }, + hour: { string: string, undef: undef }, + weekday: { string: string, undef: undef }, + day: { string: string, undef: undef }, + month: { string: string, undef: undef }, + year: { string: string, undef: undef }, + __type__: { object: object } + }, + majorLabels: { + millisecond: { string: string, undef: undef }, + second: { string: string, undef: undef }, + minute: { string: string, undef: undef }, + hour: { string: string, undef: undef }, + weekday: { string: string, undef: undef }, + day: { string: string, undef: undef }, + month: { string: string, undef: undef }, + year: { string: string, undef: undef }, + __type__: { object: object } + }, + __type__: { object: object } + }, + height: { string: string, number: number }, + hiddenDates: { object: object, array: array }, + locale: { string: string }, + locales: { + __any__: { object: object }, + __type__: { object: object } + }, + max: { date: date, number: number, string: string, moment: moment }, + maxHeight: { number: number, string: string }, + min: { date: date, number: number, string: string, moment: moment }, + minHeight: { number: number, string: string }, + moveable: { boolean: boolean }, + multiselect: { boolean: boolean }, + orientation: { string: string }, + showCurrentTime: { boolean: boolean }, + showMajorLabels: { boolean: boolean }, + showMinorLabels: { boolean: boolean }, + start: { date: date, number: number, string: string, moment: moment }, + timeAxis: { + scale: { string: string, undef: undef }, + step: { number: number, undef: undef }, + __type__: { object: object } + }, + width: { string: string, number: number }, + zoomable: { boolean: boolean }, + zoomMax: { number: number }, + zoomMin: { number: number }, + __type__: { object: object } }; - /** - * Update the values of all object in the given array according to the current - * value range of the objects in the array. - * @param {Object} obj An object containing a set of Edges or Nodes - * The objects must have a method getValue() and - * setValueRange(min, max). - * @private - */ - Network.prototype._updateValueRange = function (obj) { - var id; - - // determine the range of the objects - var valueMin = undefined; - var valueMax = undefined; - var valueTotal = 0; - for (id in obj) { - if (obj.hasOwnProperty(id)) { - var value = obj[id].getValue(); - if (value !== undefined) { - valueMin = valueMin === undefined ? value : Math.min(value, valueMin); - valueMax = valueMax === undefined ? value : Math.max(value, valueMax); - valueTotal += value; + var configureOptions = { + global: { + //yAxisOrientation: ['left','right'], // TDOO: enable as soon as Grahp2d doesn't crash when changing this on the fly + sort: true, + sampling: true, + stack: false, + shaded: { + enabled: false, + orientation: ['top', 'bottom'] // top, bottom + }, + style: ['line', 'bar', 'points'], // line, bar + barChart: { + width: [50, 5, 100, 5], + sideBySide: false, + align: ['left', 'center', 'right'] // left, center, right + }, + interpolation: { + enabled: true, + parametrization: ['centripetal', 'chordal', 'uniform'] // uniform (alpha = 0.0), chordal (alpha = 1.0), centripetal (alpha = 0.5) + }, + drawPoints: { + enabled: true, + size: [6, 2, 30, 1], + style: ['square', 'circle'] // square, circle + }, + dataAxis: { + showMinorLabels: true, + showMajorLabels: true, + icons: false, + width: [40, 0, 200, 1], + visible: true, + alignZeros: true, + left: { + //range: {min:undefined,max:undefined}, + //format: function (value) {return value;}, + title: { text: '', style: '' } + }, + right: { + //range: {min:undefined,max:undefined}, + //format: function (value) {return value;}, + title: { text: '', style: '' } } - } - } - - // adjust the range of all objects - if (valueMin !== undefined && valueMax !== undefined) { - for (id in obj) { - if (obj.hasOwnProperty(id)) { - obj[id].setValueRange(valueMin, valueMax, valueTotal); + }, + legend: { + enabled: false, + icons: true, + left: { + visible: true, + position: ['top-right', 'bottom-right', 'top-left', 'bottom-left'] // top/bottom - left,right + }, + right: { + visible: true, + position: ['top-right', 'bottom-right', 'top-left', 'bottom-left'] // top/bottom - left,right } - } - } - }; + }, - /** - * Returns true when the Network is active. - * @returns {boolean} - */ - Network.prototype.isActive = function () { - return !this.activator || this.activator.active; - }; + autoResize: true, + clickToUse: false, + end: '', + format: { + minorLabels: { + millisecond: 'SSS', + second: 's', + minute: 'HH:mm', + hour: 'HH:mm', + weekday: 'ddd D', + day: 'D', + month: 'MMM', + year: 'YYYY' + }, + majorLabels: { + millisecond: 'HH:mm:ss', + second: 'D MMMM HH:mm', + minute: 'ddd D MMMM', + hour: 'ddd D MMMM', + weekday: 'MMMM YYYY', + day: 'MMMM YYYY', + month: 'YYYY', + year: '' + } + }, - Network.prototype.setSize = function () { - return this.canvas.setSize.apply(this.canvas, arguments); - }; - Network.prototype.canvasToDOM = function () { - return this.canvas.canvasToDOM.apply(this.canvas, arguments); - }; - Network.prototype.DOMtoCanvas = function () { - return this.canvas.DOMtoCanvas(this.canvas, arguments); - }; - Network.prototype.findNode = function () { - return this.clustering.findNode.apply(this.clustering, arguments); - }; - Network.prototype.isCluster = function () { - return this.clustering.isCluster.apply(this.clustering, arguments); - }; - Network.prototype.openCluster = function () { - return this.clustering.openCluster.apply(this.clustering, arguments); - }; - Network.prototype.cluster = function () { - return this.clustering.cluster.apply(this.clustering, arguments); - }; - Network.prototype.getNodesInCluster = function () { - return this.clustering.getNodesInCluster.apply(this.clustering, arguments); - }; - Network.prototype.clusterByConnection = function () { - return this.clustering.clusterByConnection.apply(this.clustering, arguments); - }; - Network.prototype.clusterByHubsize = function () { - return this.clustering.clusterByHubsize.apply(this.clustering, arguments); - }; - Network.prototype.clusterOutliers = function () { - return this.clustering.clusterOutliers.apply(this.clustering, arguments); - }; - Network.prototype.getSeed = function () { - return this.layoutEngine.getSeed.apply(this.layoutEngine, arguments); - }; - Network.prototype.enableEditMode = function () { - return this.manipulation.enableEditMode.apply(this.manipulation, arguments); - }; - Network.prototype.disableEditMode = function () { - return this.manipulation.disableEditMode.apply(this.manipulation, arguments); - }; - Network.prototype.addNodeMode = function () { - return this.manipulation.addNodeMode.apply(this.manipulation, arguments); - }; - Network.prototype.editNode = function () { - return this.manipulation.editNode.apply(this.manipulation, arguments); - }; - Network.prototype.editNodeMode = function () { - console.log('Depricated: Please use editNode instead of editNodeMode.');return this.manipulation.editNode.apply(this.manipulation, arguments); - }; - Network.prototype.addEdgeMode = function () { - return this.manipulation.addEdgeMode.apply(this.manipulation, arguments); - }; - Network.prototype.editEdgeMode = function () { - return this.manipulation.editEdgeMode.apply(this.manipulation, arguments); - }; - Network.prototype.deleteSelected = function () { - return this.manipulation.deleteSelected.apply(this.manipulation, arguments); - }; - Network.prototype.getPositions = function () { - return this.nodesHandler.getPositions.apply(this.nodesHandler, arguments); - }; - Network.prototype.storePositions = function () { - return this.nodesHandler.storePositions.apply(this.nodesHandler, arguments); - }; - Network.prototype.getBoundingBox = function () { - return this.nodesHandler.getBoundingBox.apply(this.nodesHandler, arguments); - }; - Network.prototype.getConnectedNodes = function (objectId) { - if (this.body.nodes[objectId] !== undefined) { - return this.nodesHandler.getConnectedNodes.apply(this.nodesHandler, arguments); - } else { - return this.edgesHandler.getConnectedNodes.apply(this.edgesHandler, arguments); - } - }; - Network.prototype.getConnectedEdges = function () { - return this.nodesHandler.getConnectedEdges.apply(this.nodesHandler, arguments); - }; - Network.prototype.startSimulation = function () { - return this.physics.startSimulation.apply(this.physics, arguments); - }; - Network.prototype.stopSimulation = function () { - return this.physics.stopSimulation.apply(this.physics, arguments); - }; - Network.prototype.stabilize = function () { - return this.physics.stabilize.apply(this.physics, arguments); - }; - Network.prototype.getSelection = function () { - return this.selectionHandler.getSelection.apply(this.selectionHandler, arguments); - }; - Network.prototype.getSelectedNodes = function () { - return this.selectionHandler.getSelectedNodes.apply(this.selectionHandler, arguments); - }; - Network.prototype.getSelectedEdges = function () { - return this.selectionHandler.getSelectedEdges.apply(this.selectionHandler, arguments); - }; - Network.prototype.getNodeAt = function () { - var node = this.selectionHandler.getNodeAt.apply(this.selectionHandler, arguments); - if (node !== undefined && node.id !== undefined) { - return node.id; - } - return node; - }; - Network.prototype.getEdgeAt = function () { - var edge = this.selectionHandler.getEdgeAt.apply(this.selectionHandler, arguments); - if (edge !== undefined && edge.id !== undefined) { - return edge.id; + height: '', + locale: '', + max: '', + maxHeight: '', + min: '', + minHeight: '', + moveable: true, + orientation: ['both', 'bottom', 'top'], + showCurrentTime: false, + showMajorLabels: true, + showMinorLabels: true, + start: '', + width: '100%', + zoomable: true, + zoomMax: [315360000000000, 10, 315360000000000, 1], + zoomMin: [10, 10, 315360000000000, 1] } - return edge; - }; - Network.prototype.selectNodes = function () { - return this.selectionHandler.selectNodes.apply(this.selectionHandler, arguments); - }; - Network.prototype.selectEdges = function () { - return this.selectionHandler.selectEdges.apply(this.selectionHandler, arguments); - }; - Network.prototype.unselectAll = function () { - return this.selectionHandler.unselectAll.apply(this.selectionHandler, arguments); - }; - Network.prototype.redraw = function () { - return this.renderer.redraw.apply(this.renderer, arguments); - }; - Network.prototype.getScale = function () { - return this.view.getScale.apply(this.view, arguments); - }; - Network.prototype.getViewPosition = function () { - return this.view.getViewPosition.apply(this.view, arguments); - }; - Network.prototype.fit = function () { - return this.view.fit.apply(this.view, arguments); - }; - Network.prototype.moveTo = function () { - return this.view.moveTo.apply(this.view, arguments); - }; - Network.prototype.focus = function () { - return this.view.focus.apply(this.view, arguments); - }; - Network.prototype.releaseNode = function () { - return this.view.releaseNode.apply(this.view, arguments); }; - module.exports = Network; + exports.allOptions = allOptions; + exports.configureOptions = configureOptions; /***/ }, -/* 64 */ +/* 67 */ /***/ function(module, exports, __webpack_require__) { "use strict"; @@ -28698,7 +30118,7 @@ return /******/ (function(modules) { // webpackBootstrap function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - var util = __webpack_require__(2); + var util = __webpack_require__(3); /** * @class Groups @@ -28827,7 +30247,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports["default"]; /***/ }, -/* 65 */ +/* 68 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -28842,17 +30262,17 @@ return /******/ (function(modules) { // webpackBootstrap function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - var _componentsNode = __webpack_require__(6); + var _componentsNode = __webpack_require__(9); var _componentsNode2 = _interopRequireDefault(_componentsNode); - var _componentsSharedLabel = __webpack_require__(66); + var _componentsSharedLabel = __webpack_require__(69); var _componentsSharedLabel2 = _interopRequireDefault(_componentsSharedLabel); - var util = __webpack_require__(2); - var DataSet = __webpack_require__(12); - var DataView = __webpack_require__(14); + var util = __webpack_require__(3); + var DataSet = __webpack_require__(16); + var DataView = __webpack_require__(18); var NodesHandler = (function () { function NodesHandler(body, images, groups, layoutEngine) { @@ -29305,7 +30725,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 66 */ +/* 69 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -29320,7 +30740,7 @@ return /******/ (function(modules) { // webpackBootstrap function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - var util = __webpack_require__(2); + var util = __webpack_require__(3); var Label = (function () { function Label(body, options) { @@ -29621,7 +31041,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 67 */ +/* 70 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -29640,7 +31060,7 @@ return /******/ (function(modules) { // webpackBootstrap function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } - var _utilNodeBase = __webpack_require__(68); + var _utilNodeBase = __webpack_require__(71); var _utilNodeBase2 = _interopRequireDefault(_utilNodeBase); @@ -29726,7 +31146,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 68 */ +/* 71 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -29794,7 +31214,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 69 */ +/* 72 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -29813,7 +31233,7 @@ return /******/ (function(modules) { // webpackBootstrap function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } - var _utilCircleImageBase = __webpack_require__(70); + var _utilCircleImageBase = __webpack_require__(73); var _utilCircleImageBase2 = _interopRequireDefault(_utilCircleImageBase); @@ -29884,7 +31304,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 70 */ +/* 73 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -29903,7 +31323,7 @@ return /******/ (function(modules) { // webpackBootstrap function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } - var _utilNodeBase = __webpack_require__(68); + var _utilNodeBase = __webpack_require__(71); var _utilNodeBase2 = _interopRequireDefault(_utilNodeBase); @@ -30033,7 +31453,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 71 */ +/* 74 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -30052,7 +31472,7 @@ return /******/ (function(modules) { // webpackBootstrap function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } - var _utilCircleImageBase = __webpack_require__(70); + var _utilCircleImageBase = __webpack_require__(73); var _utilCircleImageBase2 = _interopRequireDefault(_utilCircleImageBase); @@ -30138,7 +31558,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 72 */ +/* 75 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -30157,7 +31577,7 @@ return /******/ (function(modules) { // webpackBootstrap function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } - var _utilNodeBase = __webpack_require__(68); + var _utilNodeBase = __webpack_require__(71); var _utilNodeBase2 = _interopRequireDefault(_utilNodeBase); @@ -30243,7 +31663,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 73 */ +/* 76 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -30262,7 +31682,7 @@ return /******/ (function(modules) { // webpackBootstrap function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } - var _utilShapeBase = __webpack_require__(74); + var _utilShapeBase = __webpack_require__(77); var _utilShapeBase2 = _interopRequireDefault(_utilShapeBase); @@ -30299,7 +31719,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 74 */ +/* 77 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -30318,7 +31738,7 @@ return /******/ (function(modules) { // webpackBootstrap function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } - var _utilNodeBase = __webpack_require__(68); + var _utilNodeBase = __webpack_require__(71); var _utilNodeBase2 = _interopRequireDefault(_utilNodeBase); @@ -30398,7 +31818,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 75 */ +/* 78 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -30417,7 +31837,7 @@ return /******/ (function(modules) { // webpackBootstrap function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } - var _utilShapeBase = __webpack_require__(74); + var _utilShapeBase = __webpack_require__(77); var _utilShapeBase2 = _interopRequireDefault(_utilShapeBase); @@ -30454,7 +31874,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 76 */ +/* 79 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -30473,7 +31893,7 @@ return /******/ (function(modules) { // webpackBootstrap function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } - var _utilNodeBase = __webpack_require__(68); + var _utilNodeBase = __webpack_require__(71); var _utilNodeBase2 = _interopRequireDefault(_utilNodeBase); @@ -30570,7 +31990,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 77 */ +/* 80 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -30589,7 +32009,7 @@ return /******/ (function(modules) { // webpackBootstrap function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } - var _utilCircleImageBase = __webpack_require__(70); + var _utilCircleImageBase = __webpack_require__(73); var _utilCircleImageBase2 = _interopRequireDefault(_utilCircleImageBase); @@ -30657,7 +32077,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 78 */ +/* 81 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -30676,7 +32096,7 @@ return /******/ (function(modules) { // webpackBootstrap function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } - var _utilShapeBase = __webpack_require__(74); + var _utilShapeBase = __webpack_require__(77); var _utilShapeBase2 = _interopRequireDefault(_utilShapeBase); @@ -30714,7 +32134,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 79 */ +/* 82 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -30733,7 +32153,7 @@ return /******/ (function(modules) { // webpackBootstrap function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } - var _utilShapeBase = __webpack_require__(74); + var _utilShapeBase = __webpack_require__(77); var _utilShapeBase2 = _interopRequireDefault(_utilShapeBase); @@ -30770,7 +32190,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 80 */ +/* 83 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -30789,7 +32209,7 @@ return /******/ (function(modules) { // webpackBootstrap function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } - var _utilNodeBase = __webpack_require__(68); + var _utilNodeBase = __webpack_require__(71); var _utilNodeBase2 = _interopRequireDefault(_utilNodeBase); @@ -30855,7 +32275,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 81 */ +/* 84 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -30874,7 +32294,7 @@ return /******/ (function(modules) { // webpackBootstrap function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } - var _utilShapeBase = __webpack_require__(74); + var _utilShapeBase = __webpack_require__(77); var _utilShapeBase2 = _interopRequireDefault(_utilShapeBase); @@ -30911,7 +32331,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 82 */ +/* 85 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -30930,7 +32350,7 @@ return /******/ (function(modules) { // webpackBootstrap function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } - var _utilShapeBase = __webpack_require__(74); + var _utilShapeBase = __webpack_require__(77); var _utilShapeBase2 = _interopRequireDefault(_utilShapeBase); @@ -30967,7 +32387,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 83 */ +/* 86 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -30982,17 +32402,17 @@ return /******/ (function(modules) { // webpackBootstrap function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - var _componentsEdge = __webpack_require__(84); + var _componentsEdge = __webpack_require__(87); var _componentsEdge2 = _interopRequireDefault(_componentsEdge); - var _componentsSharedLabel = __webpack_require__(66); + var _componentsSharedLabel = __webpack_require__(69); var _componentsSharedLabel2 = _interopRequireDefault(_componentsSharedLabel); - var util = __webpack_require__(2); - var DataSet = __webpack_require__(12); - var DataView = __webpack_require__(14); + var util = __webpack_require__(3); + var DataSet = __webpack_require__(16); + var DataView = __webpack_require__(18); var EdgesHandler = (function () { function EdgesHandler(body, images, groups) { @@ -31404,7 +32824,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 84 */ +/* 87 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -31419,23 +32839,23 @@ return /******/ (function(modules) { // webpackBootstrap function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - var _sharedLabel = __webpack_require__(66); + var _sharedLabel = __webpack_require__(69); var _sharedLabel2 = _interopRequireDefault(_sharedLabel); - var _edgesBezierEdgeDynamic = __webpack_require__(85); + var _edgesBezierEdgeDynamic = __webpack_require__(2); var _edgesBezierEdgeDynamic2 = _interopRequireDefault(_edgesBezierEdgeDynamic); - var _edgesBezierEdgeStatic = __webpack_require__(88); + var _edgesBezierEdgeStatic = __webpack_require__(90); var _edgesBezierEdgeStatic2 = _interopRequireDefault(_edgesBezierEdgeStatic); - var _edgesStraightEdge = __webpack_require__(89); + var _edgesStraightEdge = __webpack_require__(91); var _edgesStraightEdge2 = _interopRequireDefault(_edgesStraightEdge); - var util = __webpack_require__(2); + var util = __webpack_require__(3); /** * @class Edge @@ -31968,171 +33388,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 85 */ -/***/ function(module, exports, __webpack_require__) { - - 'use strict'; - - Object.defineProperty(exports, '__esModule', { - value: true - }); - - var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); - - var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; - - function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - - function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } - - var _utilBezierEdgeBase = __webpack_require__(86); - - var _utilBezierEdgeBase2 = _interopRequireDefault(_utilBezierEdgeBase); - - var BezierEdgeDynamic = (function (_BezierEdgeBase) { - function BezierEdgeDynamic(options, body, labelModule) { - _classCallCheck(this, BezierEdgeDynamic); - - //this.via = undefined; // Here for completeness but not allowed to defined before super() is invoked. - _get(Object.getPrototypeOf(BezierEdgeDynamic.prototype), 'constructor', this).call(this, options, body, labelModule); // --> this calls the setOptions below - } - - _inherits(BezierEdgeDynamic, _BezierEdgeBase); - - _createClass(BezierEdgeDynamic, [{ - key: 'setOptions', - value: function setOptions(options) { - this.options = options; - this.id = this.options.id; - this.setupSupportNode(); - this.connect(); - } - }, { - key: 'connect', - value: function connect() { - this.from = this.body.nodes[this.options.from]; - this.to = this.body.nodes[this.options.to]; - if (this.from === undefined || this.to === undefined) { - this.via.setOptions({ physics: false }); - } else { - // fix weird behaviour where a selfreferencing node has physics enabled - if (this.from.id === this.to.id) { - this.via.setOptions({ physics: false }); - } else { - this.via.setOptions({ physics: true }); - } - } - } - }, { - key: 'cleanup', - value: function cleanup() { - if (this.via !== undefined) { - delete this.body.nodes[this.via.id]; - this.via = undefined; - return true; - } - return false; - } - }, { - key: 'togglePhysics', - value: function togglePhysics(status) { - this.via.setOptions({ physics: status }); - } - }, { - key: 'setupSupportNode', - - /** - * Bezier curves require an anchor point to calculate the smooth flow. These points are nodes. These nodes are invisible but - * are used for the force calculation. - * - * The changed data is not called, if needed, it is returned by the main edge constructor. - * @private - */ - value: function setupSupportNode() { - if (this.via === undefined) { - var nodeId = 'edgeId:' + this.id; - var node = this.body.functions.createNode({ - id: nodeId, - shape: 'circle', - physics: true, - hidden: true - }); - this.body.nodes[nodeId] = node; - this.via = node; - this.via.parentEdgeId = this.id; - this.positionBezierNode(); - } - } - }, { - key: 'positionBezierNode', - value: function positionBezierNode() { - if (this.via !== undefined && this.from !== undefined && this.to !== undefined) { - this.via.x = 0.5 * (this.from.x + this.to.x); - this.via.y = 0.5 * (this.from.y + this.to.y); - } else if (this.via !== undefined) { - this.via.x = 0; - this.via.y = 0; - } - } - }, { - key: '_line', - - /** - * Draw a line between two nodes - * @param {CanvasRenderingContext2D} ctx - * @private - */ - value: function _line(ctx) { - // draw a straight line - ctx.beginPath(); - ctx.moveTo(this.from.x, this.from.y); - ctx.quadraticCurveTo(this.via.x, this.via.y, this.to.x, this.to.y); - // draw shadow if enabled - this.enableShadow(ctx); - ctx.stroke(); - this.disableShadow(ctx); - return this.via; - } - }, { - key: 'getPoint', - - /** - * Combined function of pointOnLine and pointOnBezier. This gives the coordinates of a point on the line at a certain percentage of the way - * @param percentage - * @param via - * @returns {{x: number, y: number}} - * @private - */ - value: function getPoint(percentage) { - var t = percentage; - var x = Math.pow(1 - t, 2) * this.from.x + 2 * t * (1 - t) * this.via.x + Math.pow(t, 2) * this.to.x; - var y = Math.pow(1 - t, 2) * this.from.y + 2 * t * (1 - t) * this.via.y + Math.pow(t, 2) * this.to.y; - - return { x: x, y: y }; - } - }, { - key: '_findBorderPosition', - value: function _findBorderPosition(nearNode, ctx) { - return this._findBorderPositionBezier(nearNode, ctx, this.via); - } - }, { - key: '_getDistanceToEdge', - value: function _getDistanceToEdge(x1, y1, x2, y2, x3, y3) { - // x3,y3 is the point - return this._getDistanceToBezierEdge(x1, y1, x2, y2, x3, y3, this.via); - } - }]); - - return BezierEdgeDynamic; - })(_utilBezierEdgeBase2['default']); - - exports['default'] = BezierEdgeDynamic; - module.exports = exports['default']; - -/***/ }, -/* 86 */ +/* 88 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -32151,7 +33407,7 @@ return /******/ (function(modules) { // webpackBootstrap function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } - var _EdgeBase2 = __webpack_require__(87); + var _EdgeBase2 = __webpack_require__(89); var _EdgeBase3 = _interopRequireDefault(_EdgeBase2); @@ -32279,7 +33535,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 87 */ +/* 89 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -32294,7 +33550,7 @@ return /******/ (function(modules) { // webpackBootstrap function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - var util = __webpack_require__(2); + var util = __webpack_require__(3); var EdgeBase = (function () { function EdgeBase(options, body, labelModule) { @@ -32873,7 +34129,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 88 */ +/* 90 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -32892,7 +34148,7 @@ return /******/ (function(modules) { // webpackBootstrap function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } - var _utilBezierEdgeBase = __webpack_require__(86); + var _utilBezierEdgeBase = __webpack_require__(88); var _utilBezierEdgeBase2 = _interopRequireDefault(_utilBezierEdgeBase); @@ -33132,7 +34388,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 89 */ +/* 91 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -33151,7 +34407,7 @@ return /******/ (function(modules) { // webpackBootstrap function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } - var _utilEdgeBase = __webpack_require__(87); + var _utilEdgeBase = __webpack_require__(89); var _utilEdgeBase2 = _interopRequireDefault(_utilEdgeBase); @@ -33206,532 +34462,38 @@ return /******/ (function(modules) { // webpackBootstrap var node2 = this.from; if (nearNode.id === this.from.id) { node1 = this.from; - node2 = this.to; - } - - var angle = Math.atan2(node1.y - node2.y, node1.x - node2.x); - var dx = node1.x - node2.x; - var dy = node1.y - node2.y; - var edgeSegmentLength = Math.sqrt(dx * dx + dy * dy); - var toBorderDist = nearNode.distanceToBorder(ctx, angle); - var toBorderPoint = (edgeSegmentLength - toBorderDist) / edgeSegmentLength; - - var borderPos = {}; - borderPos.x = (1 - toBorderPoint) * node2.x + toBorderPoint * node1.x; - borderPos.y = (1 - toBorderPoint) * node2.y + toBorderPoint * node1.y; - - return borderPos; - } - }, { - key: '_getDistanceToEdge', - value: function _getDistanceToEdge(x1, y1, x2, y2, x3, y3) { - // x3,y3 is the point - return this._getDistanceToLine(x1, y1, x2, y2, x3, y3); - } - }]); - - return StraightEdge; - })(_utilEdgeBase2['default']); - - exports['default'] = StraightEdge; - module.exports = exports['default']; - -/***/ }, -/* 90 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - - Object.defineProperty(exports, "__esModule", { - value: true - }); - - var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); - - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - - var BarnesHutSolver = (function () { - function BarnesHutSolver(body, physicsBody, options) { - _classCallCheck(this, BarnesHutSolver); - - this.body = body; - this.physicsBody = physicsBody; - this.barnesHutTree; - this.setOptions(options); - } - - _createClass(BarnesHutSolver, [{ - key: "setOptions", - value: function setOptions(options) { - this.options = options; - this.thetaInversed = 1 / this.options.theta; - this.overlapAvoidanceFactor = 1 - Math.max(0, Math.min(1, this.options.avoidOverlap)); // if 1 then min distance = 0.5, if 0.5 then min distance = 0.5 + 0.5*node.shape.radius - } - }, { - key: "solve", - - /** - * This function calculates the forces the nodes apply on eachother based on a gravitational model. - * The Barnes Hut method is used to speed up this N-body simulation. - * - * @private - */ - value: function solve() { - if (this.options.gravitationalConstant !== 0 && this.physicsBody.physicsNodeIndices.length > 0) { - var node = undefined; - var nodes = this.body.nodes; - var nodeIndices = this.physicsBody.physicsNodeIndices; - var nodeCount = nodeIndices.length; - - // create the tree - var barnesHutTree = this._formBarnesHutTree(nodes, nodeIndices); - - // for debugging - this.barnesHutTree = barnesHutTree; - - // place the nodes one by one recursively - for (var i = 0; i < nodeCount; i++) { - node = nodes[nodeIndices[i]]; - if (node.options.mass > 0) { - // starting with root is irrelevant, it never passes the BarnesHutSolver condition - this._getForceContribution(barnesHutTree.root.children.NW, node); - this._getForceContribution(barnesHutTree.root.children.NE, node); - this._getForceContribution(barnesHutTree.root.children.SW, node); - this._getForceContribution(barnesHutTree.root.children.SE, node); - } - } - } - } - }, { - key: "_getForceContribution", - - /** - * This function traverses the barnesHutTree. It checks when it can approximate distant nodes with their center of mass. - * If a region contains a single node, we check if it is not itself, then we apply the force. - * - * @param parentBranch - * @param node - * @private - */ - value: function _getForceContribution(parentBranch, node) { - // we get no force contribution from an empty region - if (parentBranch.childrenCount > 0) { - var dx = undefined, - dy = undefined, - distance = undefined; - - // get the distance from the center of mass to the node. - dx = parentBranch.centerOfMass.x - node.x; - dy = parentBranch.centerOfMass.y - node.y; - distance = Math.sqrt(dx * dx + dy * dy); - - // BarnesHutSolver condition - // original condition : s/d < theta = passed === d/s > 1/theta = passed - // calcSize = 1/s --> d * 1/s > 1/theta = passed - if (distance * parentBranch.calcSize > this.thetaInversed) { - this._calculateForces(distance, dx, dy, node, parentBranch); - } else { - // Did not pass the condition, go into children if available - if (parentBranch.childrenCount === 4) { - this._getForceContribution(parentBranch.children.NW, node); - this._getForceContribution(parentBranch.children.NE, node); - this._getForceContribution(parentBranch.children.SW, node); - this._getForceContribution(parentBranch.children.SE, node); - } else { - // parentBranch must have only one node, if it was empty we wouldnt be here - if (parentBranch.children.data.id != node.id) { - // if it is not self - this._calculateForces(distance, dx, dy, node, parentBranch); - } - } - } - } - } - }, { - key: "_calculateForces", - - /** - * Calculate the forces based on the distance. - * - * @param distance - * @param dx - * @param dy - * @param node - * @param parentBranch - * @private - */ - value: function _calculateForces(distance, dx, dy, node, parentBranch) { - if (distance === 0) { - distance = 0.1; - dx = distance; - } - - if (this.overlapAvoidanceFactor < 1) { - distance = Math.max(0.1 + this.overlapAvoidanceFactor * node.shape.radius, distance - node.shape.radius); - } - - // the dividing by the distance cubed instead of squared allows us to get the fx and fy components without sines and cosines - // it is shorthand for gravityforce with distance squared and fx = dx/distance * gravityForce - var gravityForce = this.options.gravitationalConstant * parentBranch.mass * node.options.mass / Math.pow(distance, 3); - var fx = dx * gravityForce; - var fy = dy * gravityForce; - - this.physicsBody.forces[node.id].x += fx; - this.physicsBody.forces[node.id].y += fy; - } - }, { - key: "_formBarnesHutTree", - - /** - * This function constructs the barnesHut tree recursively. It creates the root, splits it and starts placing the nodes. - * - * @param nodes - * @param nodeIndices - * @private - */ - value: function _formBarnesHutTree(nodes, nodeIndices) { - var node = undefined; - var nodeCount = nodeIndices.length; - - var minX = nodes[nodeIndices[0]].x; - var minY = nodes[nodeIndices[0]].y; - var maxX = nodes[nodeIndices[0]].x; - var maxY = nodes[nodeIndices[0]].y; - - // get the range of the nodes - for (var i = 1; i < nodeCount; i++) { - var x = nodes[nodeIndices[i]].x; - var y = nodes[nodeIndices[i]].y; - if (nodes[nodeIndices[i]].options.mass > 0) { - if (x < minX) { - minX = x; - } - if (x > maxX) { - maxX = x; - } - if (y < minY) { - minY = y; - } - if (y > maxY) { - maxY = y; - } - } - } - // make the range a square - var sizeDiff = Math.abs(maxX - minX) - Math.abs(maxY - minY); // difference between X and Y - if (sizeDiff > 0) { - minY -= 0.5 * sizeDiff; - maxY += 0.5 * sizeDiff; - } // xSize > ySize - else { - minX += 0.5 * sizeDiff; - maxX -= 0.5 * sizeDiff; - } // xSize < ySize - - var minimumTreeSize = 0.00001; - var rootSize = Math.max(minimumTreeSize, Math.abs(maxX - minX)); - var halfRootSize = 0.5 * rootSize; - var centerX = 0.5 * (minX + maxX), - centerY = 0.5 * (minY + maxY); - - // construct the barnesHutTree - var barnesHutTree = { - root: { - centerOfMass: { x: 0, y: 0 }, - mass: 0, - range: { - minX: centerX - halfRootSize, maxX: centerX + halfRootSize, - minY: centerY - halfRootSize, maxY: centerY + halfRootSize - }, - size: rootSize, - calcSize: 1 / rootSize, - children: { data: null }, - maxWidth: 0, - level: 0, - childrenCount: 4 - } - }; - this._splitBranch(barnesHutTree.root); - - // place the nodes one by one recursively - for (var i = 0; i < nodeCount; i++) { - node = nodes[nodeIndices[i]]; - if (node.options.mass > 0) { - this._placeInTree(barnesHutTree.root, node); - } - } - - // make global - return barnesHutTree; - } - }, { - key: "_updateBranchMass", - - /** - * this updates the mass of a branch. this is increased by adding a node. - * - * @param parentBranch - * @param node - * @private - */ - value: function _updateBranchMass(parentBranch, node) { - var totalMass = parentBranch.mass + node.options.mass; - var totalMassInv = 1 / totalMass; - - parentBranch.centerOfMass.x = parentBranch.centerOfMass.x * parentBranch.mass + node.x * node.options.mass; - parentBranch.centerOfMass.x *= totalMassInv; - - parentBranch.centerOfMass.y = parentBranch.centerOfMass.y * parentBranch.mass + node.y * node.options.mass; - parentBranch.centerOfMass.y *= totalMassInv; - - parentBranch.mass = totalMass; - var biggestSize = Math.max(Math.max(node.height, node.radius), node.width); - parentBranch.maxWidth = parentBranch.maxWidth < biggestSize ? biggestSize : parentBranch.maxWidth; - } - }, { - key: "_placeInTree", - - /** - * determine in which branch the node will be placed. - * - * @param parentBranch - * @param node - * @param skipMassUpdate - * @private - */ - value: function _placeInTree(parentBranch, node, skipMassUpdate) { - if (skipMassUpdate != true || skipMassUpdate === undefined) { - // update the mass of the branch. - this._updateBranchMass(parentBranch, node); - } - - if (parentBranch.children.NW.range.maxX > node.x) { - // in NW or SW - if (parentBranch.children.NW.range.maxY > node.y) { - // in NW - this._placeInRegion(parentBranch, node, "NW"); - } else { - // in SW - this._placeInRegion(parentBranch, node, "SW"); - } - } else { - // in NE or SE - if (parentBranch.children.NW.range.maxY > node.y) { - // in NE - this._placeInRegion(parentBranch, node, "NE"); - } else { - // in SE - this._placeInRegion(parentBranch, node, "SE"); - } - } - } - }, { - key: "_placeInRegion", - - /** - * actually place the node in a region (or branch) - * - * @param parentBranch - * @param node - * @param region - * @private - */ - value: function _placeInRegion(parentBranch, node, region) { - switch (parentBranch.children[region].childrenCount) { - case 0: - // place node here - parentBranch.children[region].children.data = node; - parentBranch.children[region].childrenCount = 1; - this._updateBranchMass(parentBranch.children[region], node); - break; - case 1: - // convert into children - // if there are two nodes exactly overlapping (on init, on opening of cluster etc.) - // we move one node a pixel and we do not put it in the tree. - if (parentBranch.children[region].children.data.x === node.x && parentBranch.children[region].children.data.y === node.y) { - //node.x += Math.random(); - //node.y += Math.random(); - node.x += 0.1; - node.y += 0.1; - } else { - this._splitBranch(parentBranch.children[region]); - this._placeInTree(parentBranch.children[region], node); - } - break; - case 4: - // place in branch - this._placeInTree(parentBranch.children[region], node); - break; - } - } - }, { - key: "_splitBranch", - - /** - * this function splits a branch into 4 sub branches. If the branch contained a node, we place it in the subbranch - * after the split is complete. - * - * @param parentBranch - * @private - */ - value: function _splitBranch(parentBranch) { - // if the branch is shaded with a node, replace the node in the new subset. - var containedNode = null; - if (parentBranch.childrenCount === 1) { - containedNode = parentBranch.children.data; - parentBranch.mass = 0; - parentBranch.centerOfMass.x = 0; - parentBranch.centerOfMass.y = 0; - } - parentBranch.childrenCount = 4; - parentBranch.children.data = null; - this._insertRegion(parentBranch, "NW"); - this._insertRegion(parentBranch, "NE"); - this._insertRegion(parentBranch, "SW"); - this._insertRegion(parentBranch, "SE"); - - if (containedNode != null) { - this._placeInTree(parentBranch, containedNode); - } - } - }, { - key: "_insertRegion", - - /** - * This function subdivides the region into four new segments. - * Specifically, this inserts a single new segment. - * It fills the children section of the parentBranch - * - * @param parentBranch - * @param region - * @param parentRange - * @private - */ - value: function _insertRegion(parentBranch, region) { - var minX = undefined, - maxX = undefined, - minY = undefined, - maxY = undefined; - var childSize = 0.5 * parentBranch.size; - switch (region) { - case "NW": - minX = parentBranch.range.minX; - maxX = parentBranch.range.minX + childSize; - minY = parentBranch.range.minY; - maxY = parentBranch.range.minY + childSize; - break; - case "NE": - minX = parentBranch.range.minX + childSize; - maxX = parentBranch.range.maxX; - minY = parentBranch.range.minY; - maxY = parentBranch.range.minY + childSize; - break; - case "SW": - minX = parentBranch.range.minX; - maxX = parentBranch.range.minX + childSize; - minY = parentBranch.range.minY + childSize; - maxY = parentBranch.range.maxY; - break; - case "SE": - minX = parentBranch.range.minX + childSize; - maxX = parentBranch.range.maxX; - minY = parentBranch.range.minY + childSize; - maxY = parentBranch.range.maxY; - break; - } - - parentBranch.children[region] = { - centerOfMass: { x: 0, y: 0 }, - mass: 0, - range: { minX: minX, maxX: maxX, minY: minY, maxY: maxY }, - size: 0.5 * parentBranch.size, - calcSize: 2 * parentBranch.calcSize, - children: { data: null }, - maxWidth: 0, - level: parentBranch.level + 1, - childrenCount: 0 - }; - } - }, { - key: "_debug", - - //--------------------------- DEBUGGING BELOW ---------------------------// - - /** - * This function is for debugging purposed, it draws the tree. - * - * @param ctx - * @param color - * @private - */ - value: function _debug(ctx, color) { - if (this.barnesHutTree !== undefined) { - - ctx.lineWidth = 1; - - this._drawBranch(this.barnesHutTree.root, ctx, color); - } - } - }, { - key: "_drawBranch", - - /** - * This function is for debugging purposes. It draws the branches recursively. - * - * @param branch - * @param ctx - * @param color - * @private - */ - value: function _drawBranch(branch, ctx, color) { - if (color === undefined) { - color = "#FF0000"; - } - - if (branch.childrenCount === 4) { - this._drawBranch(branch.children.NW, ctx); - this._drawBranch(branch.children.NE, ctx); - this._drawBranch(branch.children.SE, ctx); - this._drawBranch(branch.children.SW, ctx); - } - ctx.strokeStyle = color; - ctx.beginPath(); - ctx.moveTo(branch.range.minX, branch.range.minY); - ctx.lineTo(branch.range.maxX, branch.range.minY); - ctx.stroke(); - - ctx.beginPath(); - ctx.moveTo(branch.range.maxX, branch.range.minY); - ctx.lineTo(branch.range.maxX, branch.range.maxY); - ctx.stroke(); - - ctx.beginPath(); - ctx.moveTo(branch.range.maxX, branch.range.maxY); - ctx.lineTo(branch.range.minX, branch.range.maxY); - ctx.stroke(); + node2 = this.to; + } - ctx.beginPath(); - ctx.moveTo(branch.range.minX, branch.range.maxY); - ctx.lineTo(branch.range.minX, branch.range.minY); - ctx.stroke(); + var angle = Math.atan2(node1.y - node2.y, node1.x - node2.x); + var dx = node1.x - node2.x; + var dy = node1.y - node2.y; + var edgeSegmentLength = Math.sqrt(dx * dx + dy * dy); + var toBorderDist = nearNode.distanceToBorder(ctx, angle); + var toBorderPoint = (edgeSegmentLength - toBorderDist) / edgeSegmentLength; - /* - if (branch.mass > 0) { - ctx.circle(branch.centerOfMass.x, branch.centerOfMass.y, 3*branch.mass); - ctx.stroke(); - } - */ + var borderPos = {}; + borderPos.x = (1 - toBorderPoint) * node2.x + toBorderPoint * node1.x; + borderPos.y = (1 - toBorderPoint) * node2.y + toBorderPoint * node1.y; + + return borderPos; + } + }, { + key: '_getDistanceToEdge', + value: function _getDistanceToEdge(x1, y1, x2, y2, x3, y3) { + // x3,y3 is the point + return this._getDistanceToLine(x1, y1, x2, y2, x3, y3); } }]); - return BarnesHutSolver; - })(); + return StraightEdge; + })(_utilEdgeBase2['default']); - exports["default"] = BarnesHutSolver; - module.exports = exports["default"]; + exports['default'] = StraightEdge; + module.exports = exports['default']; /***/ }, -/* 91 */ +/* 92 */ /***/ function(module, exports, __webpack_require__) { "use strict"; @@ -33820,486 +34582,13 @@ return /******/ (function(modules) { // webpackBootstrap }]); return RepulsionSolver; - })(); - - exports["default"] = RepulsionSolver; - module.exports = exports["default"]; - -/***/ }, -/* 92 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - - Object.defineProperty(exports, "__esModule", { - value: true - }); - - var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); - - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - - var HierarchicalRepulsionSolver = (function () { - function HierarchicalRepulsionSolver(body, physicsBody, options) { - _classCallCheck(this, HierarchicalRepulsionSolver); - - this.body = body; - this.physicsBody = physicsBody; - this.setOptions(options); - } - - _createClass(HierarchicalRepulsionSolver, [{ - key: "setOptions", - value: function setOptions(options) { - this.options = options; - } - }, { - key: "solve", - - /** - * Calculate the forces the nodes apply on each other based on a repulsion field. - * This field is linearly approximated. - * - * @private - */ - value: function solve() { - var dx, dy, distance, fx, fy, repulsingForce, node1, node2, i, j; - - var nodes = this.body.nodes; - var nodeIndices = this.physicsBody.physicsNodeIndices; - var forces = this.physicsBody.forces; - - // repulsing forces between nodes - var nodeDistance = this.options.nodeDistance; - - // we loop from i over all but the last entree in the array - // j loops from i+1 to the last. This way we do not double count any of the indices, nor i === j - for (i = 0; i < nodeIndices.length - 1; i++) { - node1 = nodes[nodeIndices[i]]; - for (j = i + 1; j < nodeIndices.length; j++) { - node2 = nodes[nodeIndices[j]]; - - // nodes only affect nodes on their level - if (node1.level === node2.level) { - dx = node2.x - node1.x; - dy = node2.y - node1.y; - distance = Math.sqrt(dx * dx + dy * dy); - - var steepness = 0.05; - if (distance < nodeDistance) { - repulsingForce = -Math.pow(steepness * distance, 2) + Math.pow(steepness * nodeDistance, 2); - } else { - repulsingForce = 0; - } - // normalize force with - if (distance === 0) { - distance = 0.01; - } else { - repulsingForce = repulsingForce / distance; - } - fx = dx * repulsingForce; - fy = dy * repulsingForce; - - forces[node1.id].x -= fx; - forces[node1.id].y -= fy; - forces[node2.id].x += fx; - forces[node2.id].y += fy; - } - } - } - } - }]); - - return HierarchicalRepulsionSolver; - })(); - - exports["default"] = HierarchicalRepulsionSolver; - module.exports = exports["default"]; - -/***/ }, -/* 93 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - - Object.defineProperty(exports, "__esModule", { - value: true - }); - - var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); - - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - - var SpringSolver = (function () { - function SpringSolver(body, physicsBody, options) { - _classCallCheck(this, SpringSolver); - - this.body = body; - this.physicsBody = physicsBody; - this.setOptions(options); - } - - _createClass(SpringSolver, [{ - key: "setOptions", - value: function setOptions(options) { - this.options = options; - } - }, { - key: "solve", - - /** - * This function calculates the springforces on the nodes, accounting for the support nodes. - * - * @private - */ - value: function solve() { - var edgeLength = undefined, - edge = undefined; - var edgeIndices = this.physicsBody.physicsEdgeIndices; - var edges = this.body.edges; - var node1 = undefined, - node2 = undefined, - node3 = undefined; - - // forces caused by the edges, modelled as springs - for (var i = 0; i < edgeIndices.length; i++) { - edge = edges[edgeIndices[i]]; - if (edge.connected === true && edge.toId !== edge.fromId) { - // only calculate forces if nodes are in the same sector - if (this.body.nodes[edge.toId] !== undefined && this.body.nodes[edge.fromId] !== undefined) { - if (edge.edgeType.via !== undefined) { - edgeLength = edge.options.length === undefined ? this.options.springLength : edge.options.length; - node1 = edge.to; - node2 = edge.edgeType.via; - node3 = edge.from; - - this._calculateSpringForce(node1, node2, 0.5 * edgeLength); - this._calculateSpringForce(node2, node3, 0.5 * edgeLength); - } else { - // the * 1.5 is here so the edge looks as large as a smooth edge. It does not initially because the smooth edges use - // the support nodes which exert a repulsive force on the to and from nodes, making the edge appear larger. - edgeLength = edge.options.length === undefined ? this.options.springLength * 1.5 : edge.options.length; - this._calculateSpringForce(edge.from, edge.to, edgeLength); - } - } - } - } - } - }, { - key: "_calculateSpringForce", - - /** - * This is the code actually performing the calculation for the function above. - * - * @param node1 - * @param node2 - * @param edgeLength - * @private - */ - value: function _calculateSpringForce(node1, node2, edgeLength) { - var dx = node1.x - node2.x; - var dy = node1.y - node2.y; - var distance = Math.max(Math.sqrt(dx * dx + dy * dy), 0.01); - - // the 1/distance is so the fx and fy can be calculated without sine or cosine. - var springForce = this.options.springConstant * (edgeLength - distance) / distance; - - var fx = dx * springForce; - var fy = dy * springForce; - - // handle the case where one node is not part of the physcis - if (this.physicsBody.forces[node1.id] !== undefined) { - this.physicsBody.forces[node1.id].x += fx; - this.physicsBody.forces[node1.id].y += fy; - } - - if (this.physicsBody.forces[node2.id] !== undefined) { - this.physicsBody.forces[node2.id].x -= fx; - this.physicsBody.forces[node2.id].y -= fy; - } - } - }]); - - return SpringSolver; - })(); - - exports["default"] = SpringSolver; - module.exports = exports["default"]; - -/***/ }, -/* 94 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - - Object.defineProperty(exports, "__esModule", { - value: true - }); - - var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); - - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - - var HierarchicalSpringSolver = (function () { - function HierarchicalSpringSolver(body, physicsBody, options) { - _classCallCheck(this, HierarchicalSpringSolver); - - this.body = body; - this.physicsBody = physicsBody; - this.setOptions(options); - } - - _createClass(HierarchicalSpringSolver, [{ - key: "setOptions", - value: function setOptions(options) { - this.options = options; - } - }, { - key: "solve", - - /** - * This function calculates the springforces on the nodes, accounting for the support nodes. - * - * @private - */ - value: function solve() { - var edgeLength, edge; - var dx, dy, fx, fy, springForce, distance; - var edges = this.body.edges; - var factor = 0.5; - - var edgeIndices = this.physicsBody.physicsEdgeIndices; - var nodeIndices = this.physicsBody.physicsNodeIndices; - var forces = this.physicsBody.forces; - - // initialize the spring force counters - for (var i = 0; i < nodeIndices.length; i++) { - var nodeId = nodeIndices[i]; - forces[nodeId].springFx = 0; - forces[nodeId].springFy = 0; - } - - // forces caused by the edges, modelled as springs - for (var i = 0; i < edgeIndices.length; i++) { - edge = edges[edgeIndices[i]]; - if (edge.connected === true) { - edgeLength = edge.options.length === undefined ? this.options.springLength : edge.options.length; - - dx = edge.from.x - edge.to.x; - dy = edge.from.y - edge.to.y; - distance = Math.sqrt(dx * dx + dy * dy); - distance = distance === 0 ? 0.01 : distance; - - // the 1/distance is so the fx and fy can be calculated without sine or cosine. - springForce = this.options.springConstant * (edgeLength - distance) / distance; - - fx = dx * springForce; - fy = dy * springForce; - - if (edge.to.level != edge.from.level) { - if (forces[edge.toId] !== undefined) { - forces[edge.toId].springFx -= fx; - forces[edge.toId].springFy -= fy; - } - if (forces[edge.fromId] !== undefined) { - forces[edge.fromId].springFx += fx; - forces[edge.fromId].springFy += fy; - } - } else { - if (forces[edge.toId] !== undefined) { - forces[edge.toId].x -= factor * fx; - forces[edge.toId].y -= factor * fy; - } - if (forces[edge.fromId] !== undefined) { - forces[edge.fromId].x += factor * fx; - forces[edge.fromId].y += factor * fy; - } - } - } - } - - // normalize spring forces - var springForce = 1; - var springFx, springFy; - for (var i = 0; i < nodeIndices.length; i++) { - var nodeId = nodeIndices[i]; - springFx = Math.min(springForce, Math.max(-springForce, forces[nodeId].springFx)); - springFy = Math.min(springForce, Math.max(-springForce, forces[nodeId].springFy)); - - forces[nodeId].x += springFx; - forces[nodeId].y += springFy; - } - - // retain energy balance - var totalFx = 0; - var totalFy = 0; - for (var i = 0; i < nodeIndices.length; i++) { - var nodeId = nodeIndices[i]; - totalFx += forces[nodeId].x; - totalFy += forces[nodeId].y; - } - var correctionFx = totalFx / nodeIndices.length; - var correctionFy = totalFy / nodeIndices.length; - - for (var i = 0; i < nodeIndices.length; i++) { - var nodeId = nodeIndices[i]; - forces[nodeId].x -= correctionFx; - forces[nodeId].y -= correctionFy; - } - } - }]); - - return HierarchicalSpringSolver; - })(); - - exports["default"] = HierarchicalSpringSolver; - module.exports = exports["default"]; - -/***/ }, -/* 95 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - - Object.defineProperty(exports, "__esModule", { - value: true - }); - - var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); - - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - - var CentralGravitySolver = (function () { - function CentralGravitySolver(body, physicsBody, options) { - _classCallCheck(this, CentralGravitySolver); - - this.body = body; - this.physicsBody = physicsBody; - this.setOptions(options); - } - - _createClass(CentralGravitySolver, [{ - key: "setOptions", - value: function setOptions(options) { - this.options = options; - } - }, { - key: "solve", - value: function solve() { - var dx = undefined, - dy = undefined, - distance = undefined, - node = undefined; - var nodes = this.body.nodes; - var nodeIndices = this.physicsBody.physicsNodeIndices; - var forces = this.physicsBody.forces; - - for (var i = 0; i < nodeIndices.length; i++) { - var nodeId = nodeIndices[i]; - node = nodes[nodeId]; - dx = -node.x; - dy = -node.y; - distance = Math.sqrt(dx * dx + dy * dy); - - this._calculateForces(distance, dx, dy, forces, node); - } - } - }, { - key: "_calculateForces", - - /** - * Calculate the forces based on the distance. - * @private - */ - value: function _calculateForces(distance, dx, dy, forces, node) { - var gravityForce = distance === 0 ? 0 : this.options.centralGravity / distance; - forces[node.id].x = dx * gravityForce; - forces[node.id].y = dy * gravityForce; - } - }]); - - return CentralGravitySolver; - })(); - - exports["default"] = CentralGravitySolver; - module.exports = exports["default"]; - -/***/ }, -/* 96 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - - Object.defineProperty(exports, "__esModule", { - value: true - }); - - var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); - - var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; - - function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } - - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - - function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } - - var _BarnesHutSolver2 = __webpack_require__(90); - - var _BarnesHutSolver3 = _interopRequireDefault(_BarnesHutSolver2); - - var ForceAtlas2BasedRepulsionSolver = (function (_BarnesHutSolver) { - function ForceAtlas2BasedRepulsionSolver(body, physicsBody, options) { - _classCallCheck(this, ForceAtlas2BasedRepulsionSolver); - - _get(Object.getPrototypeOf(ForceAtlas2BasedRepulsionSolver.prototype), "constructor", this).call(this, body, physicsBody, options); - } - - _inherits(ForceAtlas2BasedRepulsionSolver, _BarnesHutSolver); - - _createClass(ForceAtlas2BasedRepulsionSolver, [{ - key: "_calculateForces", - - /** - * Calculate the forces based on the distance. - * - * @param distance - * @param dx - * @param dy - * @param node - * @param parentBranch - * @private - */ - value: function _calculateForces(distance, dx, dy, node, parentBranch) { - if (distance === 0) { - distance = 0.1 * Math.random(); - dx = distance; - } - - if (this.overlapAvoidanceFactor < 1) { - distance = Math.max(0.1 + this.overlapAvoidanceFactor * node.shape.radius, distance - node.shape.radius); - } - - var degree = node.edges.length + 1; - // the dividing by the distance cubed instead of squared allows us to get the fx and fy components without sines and cosines - // it is shorthand for gravityforce with distance squared and fx = dx/distance * gravityForce - var gravityForce = this.options.gravitationalConstant * parentBranch.mass * node.options.mass * degree / Math.pow(distance, 2); - var fx = dx * gravityForce; - var fy = dy * gravityForce; - - this.physicsBody.forces[node.id].x += fx; - this.physicsBody.forces[node.id].y += fy; - } - }]); - - return ForceAtlas2BasedRepulsionSolver; - })(_BarnesHutSolver3["default"]); + })(); - exports["default"] = ForceAtlas2BasedRepulsionSolver; + exports["default"] = RepulsionSolver; module.exports = exports["default"]; /***/ }, -/* 97 */ +/* 93 */ /***/ function(module, exports, __webpack_require__) { "use strict"; @@ -34310,774 +34599,522 @@ return /******/ (function(modules) { // webpackBootstrap var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); - var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + var HierarchicalRepulsionSolver = (function () { + function HierarchicalRepulsionSolver(body, physicsBody, options) { + _classCallCheck(this, HierarchicalRepulsionSolver); - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + this.body = body; + this.physicsBody = physicsBody; + this.setOptions(options); + } - function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } + _createClass(HierarchicalRepulsionSolver, [{ + key: "setOptions", + value: function setOptions(options) { + this.options = options; + } + }, { + key: "solve", - var _CentralGravitySolver2 = __webpack_require__(95); + /** + * Calculate the forces the nodes apply on each other based on a repulsion field. + * This field is linearly approximated. + * + * @private + */ + value: function solve() { + var dx, dy, distance, fx, fy, repulsingForce, node1, node2, i, j; - var _CentralGravitySolver3 = _interopRequireDefault(_CentralGravitySolver2); + var nodes = this.body.nodes; + var nodeIndices = this.physicsBody.physicsNodeIndices; + var forces = this.physicsBody.forces; - var ForceAtlas2BasedCentralGravitySolver = (function (_CentralGravitySolver) { - function ForceAtlas2BasedCentralGravitySolver(body, physicsBody, options) { - _classCallCheck(this, ForceAtlas2BasedCentralGravitySolver); + // repulsing forces between nodes + var nodeDistance = this.options.nodeDistance; - _get(Object.getPrototypeOf(ForceAtlas2BasedCentralGravitySolver.prototype), "constructor", this).call(this, body, physicsBody, options); - } + // we loop from i over all but the last entree in the array + // j loops from i+1 to the last. This way we do not double count any of the indices, nor i === j + for (i = 0; i < nodeIndices.length - 1; i++) { + node1 = nodes[nodeIndices[i]]; + for (j = i + 1; j < nodeIndices.length; j++) { + node2 = nodes[nodeIndices[j]]; - _inherits(ForceAtlas2BasedCentralGravitySolver, _CentralGravitySolver); + // nodes only affect nodes on their level + if (node1.level === node2.level) { + dx = node2.x - node1.x; + dy = node2.y - node1.y; + distance = Math.sqrt(dx * dx + dy * dy); - _createClass(ForceAtlas2BasedCentralGravitySolver, [{ - key: "_calculateForces", + var steepness = 0.05; + if (distance < nodeDistance) { + repulsingForce = -Math.pow(steepness * distance, 2) + Math.pow(steepness * nodeDistance, 2); + } else { + repulsingForce = 0; + } + // normalize force with + if (distance === 0) { + distance = 0.01; + } else { + repulsingForce = repulsingForce / distance; + } + fx = dx * repulsingForce; + fy = dy * repulsingForce; - /** - * Calculate the forces based on the distance. - * @private - */ - value: function _calculateForces(distance, dx, dy, forces, node) { - if (distance > 0) { - var degree = node.edges.length + 1; - var gravityForce = this.options.centralGravity * degree * node.options.mass; - forces[node.id].x = dx * gravityForce; - forces[node.id].y = dy * gravityForce; + forces[node1.id].x -= fx; + forces[node1.id].y -= fy; + forces[node2.id].x += fx; + forces[node2.id].y += fy; + } + } } } }]); - return ForceAtlas2BasedCentralGravitySolver; - })(_CentralGravitySolver3["default"]); + return HierarchicalRepulsionSolver; + })(); - exports["default"] = ForceAtlas2BasedCentralGravitySolver; + exports["default"] = HierarchicalRepulsionSolver; module.exports = exports["default"]; /***/ }, -/* 98 */ +/* 94 */ /***/ function(module, exports, __webpack_require__) { - 'use strict'; + "use strict"; - Object.defineProperty(exports, '__esModule', { + Object.defineProperty(exports, "__esModule", { value: true }); - var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); - - function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - - var _componentsNodesCluster = __webpack_require__(99); - - var _componentsNodesCluster2 = _interopRequireDefault(_componentsNodesCluster); - - var util = __webpack_require__(2); + var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); - var ClusterEngine = (function () { - function ClusterEngine(body) { - var _this = this; + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - _classCallCheck(this, ClusterEngine); + var SpringSolver = (function () { + function SpringSolver(body, physicsBody, options) { + _classCallCheck(this, SpringSolver); this.body = body; - this.clusteredNodes = {}; - - this.options = {}; - this.defaultOptions = {}; - util.extend(this.options, this.defaultOptions); - - this.body.emitter.on('_resetData', function () { - _this.clusteredNodes = {}; - }); + this.physicsBody = physicsBody; + this.setOptions(options); } - _createClass(ClusterEngine, [{ - key: 'setOptions', + _createClass(SpringSolver, [{ + key: "setOptions", value: function setOptions(options) { - if (options !== undefined) {} - } - }, { - key: 'clusterByHubsize', - - /** - * - * @param hubsize - * @param options - */ - value: function clusterByHubsize(hubsize, options) { - if (hubsize === undefined) { - hubsize = this._getHubSize(); - } else if (typeof hubsize === 'object') { - options = this._checkOptions(hubsize); - hubsize = this._getHubSize(); - } - - var nodesToCluster = []; - for (var i = 0; i < this.body.nodeIndices.length; i++) { - var node = this.body.nodes[this.body.nodeIndices[i]]; - if (node.edges.length >= hubsize) { - nodesToCluster.push(node.id); - } - } - - for (var i = 0; i < nodesToCluster.length; i++) { - this.clusterByConnection(nodesToCluster[i], options, false); - } - this.body.emitter.emit('_dataChanged'); + this.options = options; } }, { - key: 'cluster', + key: "solve", /** - * loop over all nodes, check if they adhere to the condition and cluster if needed. - * @param options - * @param refreshData - */ - value: function cluster() { - var options = arguments[0] === undefined ? {} : arguments[0]; - var refreshData = arguments[1] === undefined ? true : arguments[1]; - - if (options.joinCondition === undefined) { - throw new Error('Cannot call clusterByNodeData without a joinCondition function in the options.'); - } - - // check if the options object is fine, append if needed - options = this._checkOptions(options); - - var childNodesObj = {}; - var childEdgesObj = {}; + * This function calculates the springforces on the nodes, accounting for the support nodes. + * + * @private + */ + value: function solve() { + var edgeLength = undefined, + edge = undefined; + var edgeIndices = this.physicsBody.physicsEdgeIndices; + var edges = this.body.edges; + var node1 = undefined, + node2 = undefined, + node3 = undefined; - // collect the nodes that will be in the cluster - for (var i = 0; i < this.body.nodeIndices.length; i++) { - var nodeId = this.body.nodeIndices[i]; - var node = this.body.nodes[nodeId]; - var clonedOptions = this._cloneOptions(node); - if (options.joinCondition(clonedOptions) === true) { - childNodesObj[nodeId] = this.body.nodes[nodeId]; + // forces caused by the edges, modelled as springs + for (var i = 0; i < edgeIndices.length; i++) { + edge = edges[edgeIndices[i]]; + if (edge.connected === true && edge.toId !== edge.fromId) { + // only calculate forces if nodes are in the same sector + if (this.body.nodes[edge.toId] !== undefined && this.body.nodes[edge.fromId] !== undefined) { + if (edge.edgeType.via !== undefined) { + edgeLength = edge.options.length === undefined ? this.options.springLength : edge.options.length; + node1 = edge.to; + node2 = edge.edgeType.via; + node3 = edge.from; - // collect the nodes that will be in the cluster - for (var _i = 0; _i < node.edges.length; _i++) { - var edge = node.edges[_i]; - childEdgesObj[edge.id] = edge; + this._calculateSpringForce(node1, node2, 0.5 * edgeLength); + this._calculateSpringForce(node2, node3, 0.5 * edgeLength); + } else { + // the * 1.5 is here so the edge looks as large as a smooth edge. It does not initially because the smooth edges use + // the support nodes which exert a repulsive force on the to and from nodes, making the edge appear larger. + edgeLength = edge.options.length === undefined ? this.options.springLength * 1.5 : edge.options.length; + this._calculateSpringForce(edge.from, edge.to, edgeLength); + } } } } - - this._cluster(childNodesObj, childEdgesObj, options, refreshData); } }, { - key: 'clusterOutliers', + key: "_calculateSpringForce", /** - * Cluster all nodes in the network that have only 1 edge - * @param options - * @param refreshData - */ - value: function clusterOutliers(options) { - var refreshData = arguments[1] === undefined ? true : arguments[1]; - - options = this._checkOptions(options); - var clusters = []; - - // collect the nodes that will be in the cluster - for (var i = 0; i < this.body.nodeIndices.length; i++) { - var childNodesObj = {}; - var childEdgesObj = {}; - var nodeId = this.body.nodeIndices[i]; - var visibleEdges = 0; - var edge = undefined; - for (var j = 0; j < this.body.nodes[nodeId].edges.length; j++) { - if (this.body.nodes[nodeId].edges[j].options.hidden === false) { - visibleEdges++; - edge = this.body.nodes[nodeId].edges[j]; - } - } + * This is the code actually performing the calculation for the function above. + * + * @param node1 + * @param node2 + * @param edgeLength + * @private + */ + value: function _calculateSpringForce(node1, node2, edgeLength) { + var dx = node1.x - node2.x; + var dy = node1.y - node2.y; + var distance = Math.max(Math.sqrt(dx * dx + dy * dy), 0.01); - if (visibleEdges === 1) { - // this is an outlier - var childNodeId = this._getConnectedId(edge, nodeId); - if (childNodeId !== nodeId) { - if (options.joinCondition === undefined) { - if (this._checkIfUsed(clusters, nodeId, edge.id) === false && this._checkIfUsed(clusters, childNodeId, edge.id) === false) { - childEdgesObj[edge.id] = edge; - childNodesObj[nodeId] = this.body.nodes[nodeId]; - childNodesObj[childNodeId] = this.body.nodes[childNodeId]; - } - } else { - var clonedOptions = this._cloneOptions(this.body.nodes[nodeId]); - if (options.joinCondition(clonedOptions) === true && this._checkIfUsed(clusters, nodeId, edge.id) === false) { - childEdgesObj[edge.id] = edge; - childNodesObj[nodeId] = this.body.nodes[nodeId]; - } - clonedOptions = this._cloneOptions(this.body.nodes[childNodeId]); - if (options.joinCondition(clonedOptions) === true && this._checkIfUsed(clusters, nodeId, edge.id) === false) { - childEdgesObj[edge.id] = edge; - childNodesObj[childNodeId] = this.body.nodes[childNodeId]; - } - } + // the 1/distance is so the fx and fy can be calculated without sine or cosine. + var springForce = this.options.springConstant * (edgeLength - distance) / distance; - if (Object.keys(childNodesObj).length > 0 && Object.keys(childEdgesObj).length > 0) { - clusters.push({ nodes: childNodesObj, edges: childEdgesObj }); - } - } - } - } + var fx = dx * springForce; + var fy = dy * springForce; - for (var i = 0; i < clusters.length; i++) { - this._cluster(clusters[i].nodes, clusters[i].edges, options, false); + // handle the case where one node is not part of the physcis + if (this.physicsBody.forces[node1.id] !== undefined) { + this.physicsBody.forces[node1.id].x += fx; + this.physicsBody.forces[node1.id].y += fy; } - if (refreshData === true) { - this.body.emitter.emit('_dataChanged'); - } - } - }, { - key: '_checkIfUsed', - value: function _checkIfUsed(clusters, nodeId, edgeId) { - for (var i = 0; i < clusters.length; i++) { - var cluster = clusters[i]; - if (cluster.nodes[nodeId] !== undefined || cluster.edges[edgeId] !== undefined) { - return true; - } + if (this.physicsBody.forces[node2.id] !== undefined) { + this.physicsBody.forces[node2.id].x -= fx; + this.physicsBody.forces[node2.id].y -= fy; } - return false; } - }, { - key: 'clusterByConnection', - - /** - * suck all connected nodes of a node into the node. - * @param nodeId - * @param options - * @param refreshData - */ - value: function clusterByConnection(nodeId, options) { - var refreshData = arguments[2] === undefined ? true : arguments[2]; - - // kill conditions - if (nodeId === undefined) { - throw new Error('No nodeId supplied to clusterByConnection!'); - } - if (this.body.nodes[nodeId] === undefined) { - throw new Error('The nodeId given to clusterByConnection does not exist!'); - } - - var node = this.body.nodes[nodeId]; - options = this._checkOptions(options, node); - if (options.clusterNodeProperties.x === undefined) { - options.clusterNodeProperties.x = node.x; - } - if (options.clusterNodeProperties.y === undefined) { - options.clusterNodeProperties.y = node.y; - } - if (options.clusterNodeProperties.fixed === undefined) { - options.clusterNodeProperties.fixed = {}; - options.clusterNodeProperties.fixed.x = node.options.fixed.x; - options.clusterNodeProperties.fixed.y = node.options.fixed.y; - } + }]); - var childNodesObj = {}; - var childEdgesObj = {}; - var parentNodeId = node.id; - var parentClonedOptions = this._cloneOptions(node); - childNodesObj[parentNodeId] = node; + return SpringSolver; + })(); - // collect the nodes that will be in the cluster - for (var i = 0; i < node.edges.length; i++) { - var edge = node.edges[i]; - var childNodeId = this._getConnectedId(edge, parentNodeId); + exports["default"] = SpringSolver; + module.exports = exports["default"]; - if (childNodeId !== parentNodeId) { - if (options.joinCondition === undefined) { - childEdgesObj[edge.id] = edge; - childNodesObj[childNodeId] = this.body.nodes[childNodeId]; - } else { - // clone the options and insert some additional parameters that could be interesting. - var childClonedOptions = this._cloneOptions(this.body.nodes[childNodeId]); - if (options.joinCondition(parentClonedOptions, childClonedOptions) === true) { - childEdgesObj[edge.id] = edge; - childNodesObj[childNodeId] = this.body.nodes[childNodeId]; - } - } - } else { - childEdgesObj[edge.id] = edge; - } - } +/***/ }, +/* 95 */ +/***/ function(module, exports, __webpack_require__) { - this._cluster(childNodesObj, childEdgesObj, options, refreshData); - } - }, { - key: '_cloneOptions', + "use strict"; - /** - * This returns a clone of the options or options of the edge or node to be used for construction of new edges or check functions for new nodes. - * @param objId - * @param type - * @returns {{}} - * @private - */ - value: function _cloneOptions(item, type) { - var clonedOptions = {}; - if (type === undefined || type === 'node') { - util.deepExtend(clonedOptions, item.options, true); - clonedOptions.x = item.x; - clonedOptions.y = item.y; - clonedOptions.amountOfConnections = item.edges.length; - } else { - util.deepExtend(clonedOptions, item.options, true); - } - return clonedOptions; - } - }, { - key: '_createClusterEdges', + Object.defineProperty(exports, "__esModule", { + value: true + }); - /** - * This function creates the edges that will be attached to the cluster. - * - * @param childNodesObj - * @param childEdgesObj - * @param newEdges - * @param options - * @private - */ - value: function _createClusterEdges(childNodesObj, childEdgesObj, newEdges, clusterNodeProperties, clusterEdgeProperties) { - var edge = undefined, - childNodeId = undefined, - childNode = undefined, - toId = undefined, - fromId = undefined, - otherNodeId = undefined; + var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); - var childKeys = Object.keys(childNodesObj); - for (var i = 0; i < childKeys.length; i++) { - childNodeId = childKeys[i]; - childNode = childNodesObj[childNodeId]; + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - // construct new edges from the cluster to others - for (var j = 0; j < childNode.edges.length; j++) { - edge = childNode.edges[j]; - childEdgesObj[edge.id] = edge; + var HierarchicalSpringSolver = (function () { + function HierarchicalSpringSolver(body, physicsBody, options) { + _classCallCheck(this, HierarchicalSpringSolver); - // childNodeId position will be replaced by the cluster. - if (edge.toId == childNodeId) { - // this is a double equals because ints and strings can be interchanged here. - toId = clusterNodeProperties.id; - fromId = edge.fromId; - otherNodeId = fromId; - } else { - toId = edge.toId; - fromId = clusterNodeProperties.id; - otherNodeId = toId; - } + this.body = body; + this.physicsBody = physicsBody; + this.setOptions(options); + } - // if the node connected to the cluster is also in the cluster we do not need a new edge. - if (childNodesObj[otherNodeId] === undefined) { - var clonedOptions = this._cloneOptions(edge, 'edge'); - util.deepExtend(clonedOptions, clusterEdgeProperties); - clonedOptions.from = fromId; - clonedOptions.to = toId; - clonedOptions.id = 'clusterEdge:' + util.randomUUID(); - newEdges.push(this.body.functions.createEdge(clonedOptions)); - } - } - } + _createClass(HierarchicalSpringSolver, [{ + key: "setOptions", + value: function setOptions(options) { + this.options = options; } }, { - key: '_checkOptions', + key: "solve", /** - * This function checks the options that can be supplied to the different cluster functions - * for certain fields and inserts defaults if needed - * @param options - * @returns {*} - * @private - */ - value: function _checkOptions() { - var options = arguments[0] === undefined ? {} : arguments[0]; - - if (options.clusterEdgeProperties === undefined) { - options.clusterEdgeProperties = {}; - } - if (options.clusterNodeProperties === undefined) { - options.clusterNodeProperties = {}; - } - - return options; - } - }, { - key: '_cluster', + * This function calculates the springforces on the nodes, accounting for the support nodes. + * + * @private + */ + value: function solve() { + var edgeLength, edge; + var dx, dy, fx, fy, springForce, distance; + var edges = this.body.edges; + var factor = 0.5; - /** - * - * @param {Object} childNodesObj | object with node objects, id as keys, same as childNodes except it also contains a source node - * @param {Object} childEdgesObj | object with edge objects, id as keys - * @param {Array} options | object with {clusterNodeProperties, clusterEdgeProperties, processProperties} - * @param {Boolean} refreshData | when true, do not wrap up - * @private - */ - value: function _cluster(childNodesObj, childEdgesObj, options) { - var refreshData = arguments[3] === undefined ? true : arguments[3]; + var edgeIndices = this.physicsBody.physicsEdgeIndices; + var nodeIndices = this.physicsBody.physicsNodeIndices; + var forces = this.physicsBody.forces; - // kill condition: no children so cant cluster - if (Object.keys(childNodesObj).length === 0) { - return; + // initialize the spring force counters + for (var i = 0; i < nodeIndices.length; i++) { + var nodeId = nodeIndices[i]; + forces[nodeId].springFx = 0; + forces[nodeId].springFy = 0; } - var clusterNodeProperties = util.deepExtend({}, options.clusterNodeProperties); + // forces caused by the edges, modelled as springs + for (var i = 0; i < edgeIndices.length; i++) { + edge = edges[edgeIndices[i]]; + if (edge.connected === true) { + edgeLength = edge.options.length === undefined ? this.options.springLength : edge.options.length; - // construct the clusterNodeProperties - if (options.processProperties !== undefined) { - // get the childNode options - var childNodesOptions = []; - for (var nodeId in childNodesObj) { - var clonedOptions = this._cloneOptions(childNodesObj[nodeId]); - childNodesOptions.push(clonedOptions); - } + dx = edge.from.x - edge.to.x; + dy = edge.from.y - edge.to.y; + distance = Math.sqrt(dx * dx + dy * dy); + distance = distance === 0 ? 0.01 : distance; - // get clusterproperties based on childNodes - var childEdgesOptions = []; - for (var edgeId in childEdgesObj) { - var clonedOptions = this._cloneOptions(childEdgesObj[edgeId], 'edge'); - childEdgesOptions.push(clonedOptions); - } + // the 1/distance is so the fx and fy can be calculated without sine or cosine. + springForce = this.options.springConstant * (edgeLength - distance) / distance; - clusterNodeProperties = options.processProperties(clusterNodeProperties, childNodesOptions, childEdgesOptions); - if (!clusterNodeProperties) { - throw new Error('The processProperties function does not return properties!'); + fx = dx * springForce; + fy = dy * springForce; + + if (edge.to.level != edge.from.level) { + if (forces[edge.toId] !== undefined) { + forces[edge.toId].springFx -= fx; + forces[edge.toId].springFy -= fy; + } + if (forces[edge.fromId] !== undefined) { + forces[edge.fromId].springFx += fx; + forces[edge.fromId].springFy += fy; + } + } else { + if (forces[edge.toId] !== undefined) { + forces[edge.toId].x -= factor * fx; + forces[edge.toId].y -= factor * fy; + } + if (forces[edge.fromId] !== undefined) { + forces[edge.fromId].x += factor * fx; + forces[edge.fromId].y += factor * fy; + } + } } } - // check if we have an unique id; - if (clusterNodeProperties.id === undefined) { - clusterNodeProperties.id = 'cluster:' + util.randomUUID(); - } - var clusterId = clusterNodeProperties.id; + // normalize spring forces + var springForce = 1; + var springFx, springFy; + for (var i = 0; i < nodeIndices.length; i++) { + var nodeId = nodeIndices[i]; + springFx = Math.min(springForce, Math.max(-springForce, forces[nodeId].springFx)); + springFy = Math.min(springForce, Math.max(-springForce, forces[nodeId].springFy)); - if (clusterNodeProperties.label === undefined) { - clusterNodeProperties.label = 'cluster'; + forces[nodeId].x += springFx; + forces[nodeId].y += springFy; } - // give the clusterNode a postion if it does not have one. - var pos = undefined; - if (clusterNodeProperties.x === undefined) { - pos = this._getClusterPosition(childNodesObj); - clusterNodeProperties.x = pos.x; + // retain energy balance + var totalFx = 0; + var totalFy = 0; + for (var i = 0; i < nodeIndices.length; i++) { + var nodeId = nodeIndices[i]; + totalFx += forces[nodeId].x; + totalFy += forces[nodeId].y; } - if (clusterNodeProperties.y === undefined) { - if (pos === undefined) { - pos = this._getClusterPosition(childNodesObj); - } - clusterNodeProperties.y = pos.y; + var correctionFx = totalFx / nodeIndices.length; + var correctionFy = totalFy / nodeIndices.length; + + for (var i = 0; i < nodeIndices.length; i++) { + var nodeId = nodeIndices[i]; + forces[nodeId].x -= correctionFx; + forces[nodeId].y -= correctionFy; } + } + }]); - // force the ID to remain the same - clusterNodeProperties.id = clusterId; + return HierarchicalSpringSolver; + })(); - // create the clusterNode - var clusterNode = this.body.functions.createNode(clusterNodeProperties, _componentsNodesCluster2['default']); - clusterNode.isCluster = true; - clusterNode.containedNodes = childNodesObj; - clusterNode.containedEdges = childEdgesObj; - // cache a copy from the cluster edge properties if we have to reconnect others later on - clusterNode.clusterEdgeProperties = options.clusterEdgeProperties; + exports["default"] = HierarchicalSpringSolver; + module.exports = exports["default"]; - // finally put the cluster node into global - this.body.nodes[clusterNodeProperties.id] = clusterNode; +/***/ }, +/* 96 */ +/***/ function(module, exports, __webpack_require__) { - // create the new edges that will connect to the cluster - var newEdges = []; - this._createClusterEdges(childNodesObj, childEdgesObj, newEdges, clusterNodeProperties, options.clusterEdgeProperties); + "use strict"; - // disable the childEdges - for (var edgeId in childEdgesObj) { - if (childEdgesObj.hasOwnProperty(edgeId)) { - if (this.body.edges[edgeId] !== undefined) { - var edge = this.body.edges[edgeId]; - edge.togglePhysics(false); - edge.options.hidden = true; - } - } - } + Object.defineProperty(exports, "__esModule", { + value: true + }); - // disable the childNodes - for (var nodeId in childNodesObj) { - if (childNodesObj.hasOwnProperty(nodeId)) { - this.clusteredNodes[nodeId] = { clusterId: clusterNodeProperties.id, node: this.body.nodes[nodeId] }; - this.body.nodes[nodeId].togglePhysics(false); - this.body.nodes[nodeId].options.hidden = true; - } - } + var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); - // push new edges to global - for (var i = 0; i < newEdges.length; i++) { - this.body.edges[newEdges[i].id] = newEdges[i]; - this.body.edges[newEdges[i].id].connect(); - } + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - // set ID to undefined so no duplicates arise - clusterNodeProperties.id = undefined; + var CentralGravitySolver = (function () { + function CentralGravitySolver(body, physicsBody, options) { + _classCallCheck(this, CentralGravitySolver); - // wrap up - if (refreshData === true) { - this.body.emitter.emit('_dataChanged'); - } + this.body = body; + this.physicsBody = physicsBody; + this.setOptions(options); + } + + _createClass(CentralGravitySolver, [{ + key: "setOptions", + value: function setOptions(options) { + this.options = options; } }, { - key: 'isCluster', + key: "solve", + value: function solve() { + var dx = undefined, + dy = undefined, + distance = undefined, + node = undefined; + var nodes = this.body.nodes; + var nodeIndices = this.physicsBody.physicsNodeIndices; + var forces = this.physicsBody.forces; - /** - * Check if a node is a cluster. - * @param nodeId - * @returns {*} - */ - value: function isCluster(nodeId) { - if (this.body.nodes[nodeId] !== undefined) { - return this.body.nodes[nodeId].isCluster === true; - } else { - console.log('Node does not exist.'); - return false; + for (var i = 0; i < nodeIndices.length; i++) { + var nodeId = nodeIndices[i]; + node = nodes[nodeId]; + dx = -node.x; + dy = -node.y; + distance = Math.sqrt(dx * dx + dy * dy); + + this._calculateForces(distance, dx, dy, forces, node); } } }, { - key: '_getClusterPosition', + key: "_calculateForces", /** - * get the position of the cluster node based on what's inside - * @param {object} childNodesObj | object with node objects, id as keys - * @returns {{x: number, y: number}} - * @private - */ - value: function _getClusterPosition(childNodesObj) { - var childKeys = Object.keys(childNodesObj); - var minX = childNodesObj[childKeys[0]].x; - var maxX = childNodesObj[childKeys[0]].x; - var minY = childNodesObj[childKeys[0]].y; - var maxY = childNodesObj[childKeys[0]].y; - var node = undefined; - for (var i = 1; i < childKeys.length; i++) { - node = childNodesObj[childKeys[i]]; - minX = node.x < minX ? node.x : minX; - maxX = node.x > maxX ? node.x : maxX; - minY = node.y < minY ? node.y : minY; - maxY = node.y > maxY ? node.y : maxY; - } - - return { x: 0.5 * (minX + maxX), y: 0.5 * (minY + maxY) }; + * Calculate the forces based on the distance. + * @private + */ + value: function _calculateForces(distance, dx, dy, forces, node) { + var gravityForce = distance === 0 ? 0 : this.options.centralGravity / distance; + forces[node.id].x = dx * gravityForce; + forces[node.id].y = dy * gravityForce; } - }, { - key: 'openCluster', + }]); - /** - * Open a cluster by calling this function. - * @param {String} clusterNodeId | the ID of the cluster node - * @param {Boolean} refreshData | wrap up afterwards if not true - */ - value: function openCluster(clusterNodeId) { - var refreshData = arguments[1] === undefined ? true : arguments[1]; + return CentralGravitySolver; + })(); - // kill conditions - if (clusterNodeId === undefined) { - throw new Error('No clusterNodeId supplied to openCluster.'); - } - if (this.body.nodes[clusterNodeId] === undefined) { - throw new Error('The clusterNodeId supplied to openCluster does not exist.'); - } - if (this.body.nodes[clusterNodeId].containedNodes === undefined) { - console.log('The node:' + clusterNodeId + ' is not a cluster.'); - return; - } - var clusterNode = this.body.nodes[clusterNodeId]; - var containedNodes = clusterNode.containedNodes; - var containedEdges = clusterNode.containedEdges; + exports["default"] = CentralGravitySolver; + module.exports = exports["default"]; - // release nodes - for (var nodeId in containedNodes) { - if (containedNodes.hasOwnProperty(nodeId)) { - var containedNode = this.body.nodes[nodeId]; - containedNode = containedNodes[nodeId]; - // inherit position - containedNode.x = clusterNode.x; - containedNode.y = clusterNode.y; +/***/ }, +/* 97 */ +/***/ function(module, exports, __webpack_require__) { - // inherit speed - containedNode.vx = clusterNode.vx; - containedNode.vy = clusterNode.vy; + "use strict"; - containedNode.options.hidden = false; - containedNode.togglePhysics(true); + Object.defineProperty(exports, "__esModule", { + value: true + }); - delete this.clusteredNodes[nodeId]; - } - } + var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); - // release edges - for (var edgeId in containedEdges) { - if (containedEdges.hasOwnProperty(edgeId)) { - var edge = containedEdges[edgeId]; - // if this edge was a temporary edge and it's connected nodes do not exist anymore, we remove it from the data - if (this.body.nodes[edge.fromId] === undefined || this.body.nodes[edge.toId] === undefined) { - edge.edgeType.cleanup(); - // this removes the edge from node.edges, which is why edgeIds is formed - edge.disconnect(); - delete this.body.edges[edgeId]; - } else { - // one of the nodes connected to this edge is in a cluster. We give the edge to that cluster so it will be released when that cluster is opened. - if (this.clusteredNodes[edge.fromId] !== undefined || this.clusteredNodes[edge.toId] !== undefined) { - var fromId = undefined, - toId = undefined; - var clusteredNode = this.clusteredNodes[edge.fromId] || this.clusteredNodes[edge.toId]; - var clusterId = clusteredNode.clusterId; - var _clusterNode = this.body.nodes[clusterId]; - _clusterNode.containedEdges[edgeId] = edge; + var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; - if (this.clusteredNodes[edge.fromId] !== undefined) { - fromId = clusterId; - toId = edge.toId; - } else { - fromId = edge.fromId; - toId = clusterId; - } + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } - // if both from and to nodes are visible, we create a new temporary edge - if (this.body.nodes[fromId].options.hidden !== true && this.body.nodes[toId].options.hidden !== true) { - var clonedOptions = this._cloneOptions(edge, 'edge'); - var id = 'clusterEdge:' + util.randomUUID(); - util.deepExtend(clonedOptions, _clusterNode.clusterEdgeProperties); - util.deepExtend(clonedOptions, { from: fromId, to: toId, hidden: false, physics: true, id: id }); - var newEdge = this.body.functions.createEdge(clonedOptions); + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - this.body.edges[id] = newEdge; - this.body.edges[id].connect(); - } - } else { - edge.options.hidden = false; - edge.togglePhysics(true); - } - } - } - } + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } - // remove all temporary edges - for (var i = 0; i < clusterNode.edges.length; i++) { - var edgeId = clusterNode.edges[i].id; - this.body.edges[edgeId].edgeType.cleanup(); - // this removes the edge from node.edges, which is why edgeIds is formed - this.body.edges[edgeId].disconnect(); - delete this.body.edges[edgeId]; - } + var _BarnesHutSolver2 = __webpack_require__(8); - // remove clusterNode - delete this.body.nodes[clusterNodeId]; + var _BarnesHutSolver3 = _interopRequireDefault(_BarnesHutSolver2); - if (refreshData === true) { - this.body.emitter.emit('_dataChanged'); - } - } - }, { - key: 'getNodesInCluster', - value: function getNodesInCluster(clusterId) { - var nodesArray = []; - if (this.isCluster(clusterId) === true) { - var containedNodes = this.body.nodes[clusterId].containedNodes; - for (var nodeId in containedNodes) { - if (containedNodes.hasOwnProperty(nodeId)) { - nodesArray.push(nodeId); - } - } - } + var ForceAtlas2BasedRepulsionSolver = (function (_BarnesHutSolver) { + function ForceAtlas2BasedRepulsionSolver(body, physicsBody, options) { + _classCallCheck(this, ForceAtlas2BasedRepulsionSolver); - return nodesArray; - } - }, { - key: 'findNode', + _get(Object.getPrototypeOf(ForceAtlas2BasedRepulsionSolver.prototype), "constructor", this).call(this, body, physicsBody, options); + } - /** - * Get the stack clusterId's that a certain node resides in. cluster A -> cluster B -> cluster C -> node - * @param nodeId - * @returns {Array} - * @private - */ - value: function findNode(nodeId) { - var stack = []; - var max = 100; - var counter = 0; + _inherits(ForceAtlas2BasedRepulsionSolver, _BarnesHutSolver); - while (this.clusteredNodes[nodeId] !== undefined && counter < max) { - stack.push(this.clusteredNodes[nodeId].node); - nodeId = this.clusteredNodes[nodeId].clusterId; - counter++; - } - stack.push(this.body.nodes[nodeId]); - return stack; - } - }, { - key: '_getConnectedId', + _createClass(ForceAtlas2BasedRepulsionSolver, [{ + key: "_calculateForces", /** - * Get the Id the node is connected to - * @param edge - * @param nodeId - * @returns {*} - * @private - */ - value: function _getConnectedId(edge, nodeId) { - if (edge.toId != nodeId) { - return edge.toId; - } else if (edge.fromId != nodeId) { - return edge.fromId; - } else { - return edge.fromId; + * Calculate the forces based on the distance. + * + * @param distance + * @param dx + * @param dy + * @param node + * @param parentBranch + * @private + */ + value: function _calculateForces(distance, dx, dy, node, parentBranch) { + if (distance === 0) { + distance = 0.1 * Math.random(); + dx = distance; + } + + if (this.overlapAvoidanceFactor < 1) { + distance = Math.max(0.1 + this.overlapAvoidanceFactor * node.shape.radius, distance - node.shape.radius); } + + var degree = node.edges.length + 1; + // the dividing by the distance cubed instead of squared allows us to get the fx and fy components without sines and cosines + // it is shorthand for gravityforce with distance squared and fx = dx/distance * gravityForce + var gravityForce = this.options.gravitationalConstant * parentBranch.mass * node.options.mass * degree / Math.pow(distance, 2); + var fx = dx * gravityForce; + var fy = dy * gravityForce; + + this.physicsBody.forces[node.id].x += fx; + this.physicsBody.forces[node.id].y += fy; } - }, { - key: '_getHubSize', + }]); - /** - * We determine how many connections denote an important hub. - * We take the mean + 2*std as the important hub size. (Assuming a normal distribution of data, ~2.2%) - * - * @private - */ - value: function _getHubSize() { - var average = 0; - var averageSquared = 0; - var hubCounter = 0; - var largestHub = 0; + return ForceAtlas2BasedRepulsionSolver; + })(_BarnesHutSolver3["default"]); - for (var i = 0; i < this.body.nodeIndices.length; i++) { - var node = this.body.nodes[this.body.nodeIndices[i]]; - if (node.edges.length > largestHub) { - largestHub = node.edges.length; - } - average += node.edges.length; - averageSquared += Math.pow(node.edges.length, 2); - hubCounter += 1; - } - average = average / hubCounter; - averageSquared = averageSquared / hubCounter; + exports["default"] = ForceAtlas2BasedRepulsionSolver; + module.exports = exports["default"]; - var letiance = averageSquared - Math.pow(average, 2); - var standardDeviation = Math.sqrt(letiance); +/***/ }, +/* 98 */ +/***/ function(module, exports, __webpack_require__) { - var hubThreshold = Math.floor(average + 2 * standardDeviation); + "use strict"; - // always have at least one to cluster - if (hubThreshold > largestHub) { - hubThreshold = largestHub; - } + Object.defineProperty(exports, "__esModule", { + value: true + }); - return hubThreshold; + var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + + var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } + + var _CentralGravitySolver2 = __webpack_require__(96); + + var _CentralGravitySolver3 = _interopRequireDefault(_CentralGravitySolver2); + + var ForceAtlas2BasedCentralGravitySolver = (function (_CentralGravitySolver) { + function ForceAtlas2BasedCentralGravitySolver(body, physicsBody, options) { + _classCallCheck(this, ForceAtlas2BasedCentralGravitySolver); + + _get(Object.getPrototypeOf(ForceAtlas2BasedCentralGravitySolver.prototype), "constructor", this).call(this, body, physicsBody, options); + } + + _inherits(ForceAtlas2BasedCentralGravitySolver, _CentralGravitySolver); + + _createClass(ForceAtlas2BasedCentralGravitySolver, [{ + key: "_calculateForces", + + /** + * Calculate the forces based on the distance. + * @private + */ + value: function _calculateForces(distance, dx, dy, forces, node) { + if (distance > 0) { + var degree = node.edges.length + 1; + var gravityForce = this.options.centralGravity * degree * node.options.mass; + forces[node.id].x = dx * gravityForce; + forces[node.id].y = dy * gravityForce; + } } }]); - return ClusterEngine; - })(); + return ForceAtlas2BasedCentralGravitySolver; + })(_CentralGravitySolver3["default"]); - exports['default'] = ClusterEngine; - module.exports = exports['default']; + exports["default"] = ForceAtlas2BasedCentralGravitySolver; + module.exports = exports["default"]; /***/ }, /* 99 */ @@ -35097,7 +35134,7 @@ return /******/ (function(modules) { // webpackBootstrap function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } - var _Node2 = __webpack_require__(6); + var _Node2 = __webpack_require__(9); var _Node3 = _interopRequireDefault(_Node2); @@ -35142,7 +35179,7 @@ return /******/ (function(modules) { // webpackBootstrap window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame; } - var util = __webpack_require__(2); + var util = __webpack_require__(3); var CanvasRenderer = (function () { function CanvasRenderer(body, canvas) { @@ -35524,10 +35561,10 @@ return /******/ (function(modules) { // webpackBootstrap function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - var Hammer = __webpack_require__(27); - var hammerUtil = __webpack_require__(32); + var Hammer = __webpack_require__(31); + var hammerUtil = __webpack_require__(36); - var util = __webpack_require__(2); + var util = __webpack_require__(3); /** * Create the main frame for the Network. @@ -35892,7 +35929,7 @@ return /******/ (function(modules) { // webpackBootstrap function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - var util = __webpack_require__(2); + var util = __webpack_require__(3); var View = (function () { function View(body, canvas) { @@ -36305,7 +36342,7 @@ return /******/ (function(modules) { // webpackBootstrap var _componentsPopup2 = _interopRequireDefault(_componentsPopup); - var util = __webpack_require__(2); + var util = __webpack_require__(3); var InteractionHandler = (function () { function InteractionHandler(body, canvas, selectionHandler) { @@ -37052,10 +37089,10 @@ return /******/ (function(modules) { // webpackBootstrap function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - var util = __webpack_require__(2); - var Hammer = __webpack_require__(27); - var hammerUtil = __webpack_require__(32); - var keycharm = __webpack_require__(47); + var util = __webpack_require__(3); + var Hammer = __webpack_require__(31); + var hammerUtil = __webpack_require__(36); + var keycharm = __webpack_require__(51); var NavigationHandler = (function () { function NavigationHandler(body, canvas) { @@ -37495,7 +37532,7 @@ return /******/ (function(modules) { // webpackBootstrap function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - var util = __webpack_require__(2); + var util = __webpack_require__(3); var LayoutEngine = (function () { function LayoutEngine(body) { @@ -38005,9 +38042,9 @@ return /******/ (function(modules) { // webpackBootstrap function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - var util = __webpack_require__(2); - var Hammer = __webpack_require__(27); - var hammerUtil = __webpack_require__(32); + var util = __webpack_require__(3); + var Hammer = __webpack_require__(31); + var hammerUtil = __webpack_require__(36); /** * clears the toolbar div element of children diff --git a/docs/network/index.html b/docs/network/index.html index 74c6ed86..959322e2 100644 --- a/docs/network/index.html +++ b/docs/network/index.html @@ -630,12 +630,25 @@ var locales = { openCluster( - String nodeId) + String nodeId, Object options) Returns: none Opens the cluster, releases the contained nodes and edges, removing the cluster node and cluster - edges. + edges. The options object is optional and currently supports one option, releaseFunction, which is a function that can be used to manually + position the nodes after the cluster is opened.
+
+function releaseFunction (clusterPosition, containedNodesPositions) {
+    var newPositions = {};
+    // clusterPosition = {x:clusterX, y:clusterY};
+    // containedNodesPositions = {nodeId:{x:nodeX,y:nodeY}, nodeId2....}
+    newPositions[nodeId] = {x:newPosX, y:newPosY};
+    return newPositions;
+}
+ This function is expected to return the newPositions, which can be the containedNodesPositions (altered) or a new object. This has to be an object with keys equal + to the nodeIds that exist in the containedNodesPositions and an {x:x,y:y} position object.

+ + For all nodeIds not listed in this returned object, we will position them at the location of the cluster. This is also the default behaviour when no releaseFunction is defined. diff --git a/examples/network/other/clustering.html b/examples/network/other/clustering.html index f4c31acb..30b745ef 100644 --- a/examples/network/other/clustering.html +++ b/examples/network/other/clustering.html @@ -77,7 +77,7 @@ Click any of the buttons below to cluster the network. On every push the network network.on("selectNode", function(params) { if (params.nodes.length == 1) { if (network.isCluster(params.nodes[0]) == true) { - network.openCluster(params.nodes[0]) + network.openCluster(params.nodes[0]); } } }) diff --git a/lib/network/modules/Clustering.js b/lib/network/modules/Clustering.js index ab5889df..062bb3dc 100644 --- a/lib/network/modules/Clustering.js +++ b/lib/network/modules/Clustering.js @@ -304,6 +304,7 @@ class ClusterEngine { * @private */ _cluster(childNodesObj, childEdgesObj, options, refreshData = true) { + console.log(childNodesObj) // kill condition: no children so cant cluster if (Object.keys(childNodesObj).length === 0) {return;} @@ -454,7 +455,7 @@ class ClusterEngine { * @param {String} clusterNodeId | the ID of the cluster node * @param {Boolean} refreshData | wrap up afterwards if not true */ - openCluster(clusterNodeId, refreshData = true) { + openCluster(clusterNodeId, options, refreshData = true) { // kill conditions if (clusterNodeId === undefined) {throw new Error("No clusterNodeId supplied to openCluster.");} if (this.body.nodes[clusterNodeId] === undefined) {throw new Error("The clusterNodeId supplied to openCluster does not exist.");} @@ -466,14 +467,45 @@ class ClusterEngine { let containedNodes = clusterNode.containedNodes; let containedEdges = clusterNode.containedEdges; + // allow the user to position the nodes after release. + if (options.releaseFunction !== undefined) { + let positions = {}; + let clusterPosition = {x:clusterNode.x, y:clusterNode.y}; + for (let nodeId in containedNodes) { + if (containedNodes.hasOwnProperty(nodeId)) { + let containedNode = this.body.nodes[nodeId]; + positions[nodeId] = {x: containedNode.x, y: containedNode.y}; + } + } + let newPositions = options.releaseFunction(clusterPosition, positions); + + for (let nodeId in containedNodes) { + if (containedNodes.hasOwnProperty(nodeId)) { + let containedNode = this.body.nodes[nodeId]; + if (newPositions[nodeId] !== undefined) { + containedNode.x = newPositions[nodeId].x || clusterNode.x; + containedNode.y = newPositions[nodeId].y || clusterNode.y; + } + } + } + } + else { + // copy the position from the cluster + for (let nodeId in containedNodes) { + if (containedNodes.hasOwnProperty(nodeId)) { + let containedNode = this.body.nodes[nodeId]; + containedNode = containedNodes[nodeId]; + // inherit position + containedNode.x = clusterNode.x; + containedNode.y = clusterNode.y; + } + } + } + // release nodes for (let nodeId in containedNodes) { if (containedNodes.hasOwnProperty(nodeId)) { let containedNode = this.body.nodes[nodeId]; - containedNode = containedNodes[nodeId]; - // inherit position - containedNode.x = clusterNode.x; - containedNode.y = clusterNode.y; // inherit speed containedNode.vx = clusterNode.vx; diff --git a/lib/network/modules/components/Node.js b/lib/network/modules/components/Node.js index cb2c80ba..5d86804f 100644 --- a/lib/network/modules/components/Node.js +++ b/lib/network/modules/components/Node.js @@ -123,7 +123,7 @@ class Node { if (options.x !== undefined) {this.x = parseInt(options.x); this.predefinedPosition = true;} if (options.y !== undefined) {this.y = parseInt(options.y); this.predefinedPosition = true;} if (options.size !== undefined) {this.baseSize = options.size;} - if (options.value !== undefined) {options.value = parseInt(options.value);} + if (options.value !== undefined) {options.value = parseFloat(options.value);} // copy group options diff --git a/lib/network/modules/components/edges/BezierEdgeDynamic.js b/lib/network/modules/components/edges/BezierEdgeDynamic.js index 8d43f79e..04008cc4 100644 --- a/lib/network/modules/components/edges/BezierEdgeDynamic.js +++ b/lib/network/modules/components/edges/BezierEdgeDynamic.js @@ -40,7 +40,8 @@ class BezierEdgeDynamic extends BezierEdgeBase { } togglePhysics(status) { - this.via.setOptions({physics:status}) + this.via.setOptions({physics:status}); + this.positionBezierNode(); } /** diff --git a/lib/network/modules/components/physics/BarnesHutSolver.js b/lib/network/modules/components/physics/BarnesHutSolver.js index a6912e09..b2da2d60 100644 --- a/lib/network/modules/components/physics/BarnesHutSolver.js +++ b/lib/network/modules/components/physics/BarnesHutSolver.js @@ -5,6 +5,7 @@ class BarnesHutSolver { this.physicsBody = physicsBody; this.barnesHutTree; this.setOptions(options); + this.randomSeed = 5; } setOptions(options) { @@ -13,6 +14,11 @@ class BarnesHutSolver { this.overlapAvoidanceFactor = 1 - Math.max(0, Math.min(1,this.options.avoidOverlap)); // if 1 then min distance = 0.5, if 0.5 then min distance = 0.5 + 0.5*node.shape.radius } + seededRandom() { + var x = Math.sin(this.randomSeed++) * 10000; + return x - Math.floor(x); + } + /** * This function calculates the forces the nodes apply on eachother based on a gravitational model. @@ -282,10 +288,8 @@ class BarnesHutSolver { // we move one node a pixel and we do not put it in the tree. if (parentBranch.children[region].children.data.x === node.x && parentBranch.children[region].children.data.y === node.y) { - //node.x += Math.random(); - //node.y += Math.random(); - node.x += 0.1; - node.y += 0.1; + node.x += this.seededRandom(); + node.y += this.seededRandom(); } else { this._splitBranch(parentBranch.children[region]); diff --git a/test/networkTest.html b/test/networkTest.html index 67c60f41..06562c81 100644 --- a/test/networkTest.html +++ b/test/networkTest.html @@ -26,46 +26,50 @@