From eec31b0d864e7146003bf93a25c0b6b6aafd2e84 Mon Sep 17 00:00:00 2001 From: Alex de Mulder Date: Fri, 27 Mar 2015 23:59:16 +0100 Subject: [PATCH] more bugfixes, setOptions now propagates shapes, smooth and font options when set globally. --- dist/vis.js | 14278 ++++++++++--------- examples/network/01_basic_usage.html | 3 +- lib/network/modules/ConfigurationSystem.js | 7 +- lib/network/modules/EdgesHandler.js | 24 +- lib/network/modules/NodesHandler.js | 15 + lib/network/modules/PhysicsEngine.js | 10 +- lib/network/modules/components/Edge.js | 7 +- lib/network/modules/components/Node.js | 7 +- 8 files changed, 7227 insertions(+), 7124 deletions(-) diff --git a/dist/vis.js b/dist/vis.js index fcea3387..a80f6cb4 100644 --- a/dist/vis.js +++ b/dist/vis.js @@ -23314,27 +23314,27 @@ return /******/ (function(modules) { // webpackBootstrap var NodesHandler = _interopRequire(__webpack_require__(59)); - var EdgesHandler = _interopRequire(__webpack_require__(60)); + var EdgesHandler = _interopRequire(__webpack_require__(79)); - var PhysicsEngine = _interopRequire(__webpack_require__(68)); + var PhysicsEngine = _interopRequire(__webpack_require__(86)); - var ClusterEngine = _interopRequire(__webpack_require__(75)); + var ClusterEngine = _interopRequire(__webpack_require__(87)); - var CanvasRenderer = _interopRequire(__webpack_require__(95)); + var CanvasRenderer = _interopRequire(__webpack_require__(89)); - var Canvas = _interopRequire(__webpack_require__(96)); + var Canvas = _interopRequire(__webpack_require__(90)); - var View = _interopRequire(__webpack_require__(97)); + var View = _interopRequire(__webpack_require__(91)); - var InteractionHandler = _interopRequire(__webpack_require__(98)); + var InteractionHandler = _interopRequire(__webpack_require__(92)); - var SelectionHandler = _interopRequire(__webpack_require__(101)); + var SelectionHandler = _interopRequire(__webpack_require__(95)); - var LayoutEngine = _interopRequire(__webpack_require__(102)); + var LayoutEngine = _interopRequire(__webpack_require__(96)); - var ManipulationSystem = _interopRequire(__webpack_require__(103)); + var ManipulationSystem = _interopRequire(__webpack_require__(97)); - var ConfigurationSystem = _interopRequire(__webpack_require__(105)); + var ConfigurationSystem = _interopRequire(__webpack_require__(99)); /** * @constructor Network @@ -25217,7 +25217,7 @@ return /******/ (function(modules) { // webpackBootstrap var DataSet = __webpack_require__(7); var DataView = __webpack_require__(9); - var Node = _interopRequire(__webpack_require__(77)); + var Node = _interopRequire(__webpack_require__(60)); var NodesHandler = (function () { function NodesHandler(body, images, groups, layoutEngine) { @@ -25342,6 +25342,7 @@ return /******/ (function(modules) { // webpackBootstrap } } + // update the shape if (options.shape !== undefined) { for (var nodeId in this.body.nodes) { if (this.body.nodes.hasOwnProperty(nodeId)) { @@ -25349,6 +25350,20 @@ return /******/ (function(modules) { // webpackBootstrap } } } + + // update fonts + if (options.font) { + for (var nodeId in this.body.nodes) { + if (this.body.nodes.hasOwnProperty(nodeId)) { + this.body.nodes[nodeId].updateLabelModule(); + } + } + } + + // update the state of the variables if needed + if (options.hidden !== undefined || options.physics !== undefined) { + this.body.emitter.emit("_dataChanged"); + } } }, writable: true, @@ -25515,679 +25530,355 @@ return /******/ (function(modules) { // webpackBootstrap var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; - /** - * Created by Alex on 3/4/2015. - */ + var util = __webpack_require__(1); + var Label = _interopRequire(__webpack_require__(61)); - var util = __webpack_require__(1); - var DataSet = __webpack_require__(7); - var DataView = __webpack_require__(9); + var Box = _interopRequire(__webpack_require__(62)); - var Edge = _interopRequire(__webpack_require__(61)); + var Circle = _interopRequire(__webpack_require__(64)); - var EdgesHandler = (function () { - function EdgesHandler(body, images, groups) { - var _this = this; - _classCallCheck(this, EdgesHandler); + var CircularImage = _interopRequire(__webpack_require__(66)); - this.body = body; - this.images = images; - this.groups = groups; + var Database = _interopRequire(__webpack_require__(67)); - // create the edge API in the body container - this.body.functions.createEdge = this.create.bind(this); + var Diamond = _interopRequire(__webpack_require__(68)); - this.edgesListeners = { - add: function (event, params) { - _this.add(params.items); - }, - update: function (event, params) { - _this.update(params.items); - }, - remove: function (event, params) { - _this.remove(params.items); - } - }; + var Dot = _interopRequire(__webpack_require__(70)); - this.options = {}; - this.defaultOptions = { - arrows: { - to: { enabled: false, scaleFactor: 1 }, // boolean / {arrowScaleFactor:1} / {enabled: false, arrowScaleFactor:1} - middle: { enabled: false, scaleFactor: 1 }, - from: { enabled: false, scaleFactor: 1 } - }, - color: { - color: "#848484", - highlight: "#848484", - hover: "#848484", - inherit: { - enabled: true, - source: "from", // from / true - useGradients: false // release in 4.0 - }, - opacity: 1 - }, - dashes: { - enabled: false, - preset: "dotted", - length: 10, - gap: 5, - altLength: undefined - }, - font: { - color: "#343434", - size: 14, // px - face: "arial", - background: "none", - stroke: 1, // px - strokeColor: "#ffffff", - align: "horizontal" - }, - hidden: false, - hoverWidth: 1.5, - label: undefined, - length: undefined, - physics: true, - scaling: { - min: 1, - max: 15, - label: { - enabled: true, - min: 14, - max: 30, - maxVisible: 30, - drawThreshold: 3 - }, - customScalingFunction: function (min, max, total, value) { - if (max == min) { - return 0.5; - } else { - var scale = 1 / (max - min); - return Math.max(0, (value - min) * scale); - } - } - }, - selfReferenceSize: 20, - smooth: { - enabled: true, - dynamic: true, - type: "continuous", - roundness: 0.5 - }, - title: undefined, - width: 1, - widthSelectionMultiplier: 2, - value: 1 - }; + var Ellipse = _interopRequire(__webpack_require__(71)); - util.extend(this.options, this.defaultOptions); + var Icon = _interopRequire(__webpack_require__(72)); + var Image = _interopRequire(__webpack_require__(73)); - // this allows external modules to force all dynamic curves to turn static. - this.body.emitter.on("_forceDisableDynamicCurves", function (type) { - var emitChange = false; - for (var edgeId in _this.body.edges) { - if (_this.body.edges.hasOwnProperty(edgeId)) { - var edgeOptions = _this.body.edges[edgeId].options.smooth; - if (edgeOptions.enabled === true && edgeOptions.dynamic === true) { - if (type === undefined) { - edge.setOptions({ smooth: false }); - } else { - edge.setOptions({ smooth: { dynamic: false, type: type } }); - } - emitChange = true; - } - } - } - if (emitChange === true) { - _this.body.emitter.emit("_dataChanged"); - } - }); + var Square = _interopRequire(__webpack_require__(74)); - // this is called when options of EXISTING nodes or edges have changed. - this.body.emitter.on("_dataUpdated", function () { - _this.reconnectEdges(); - _this.markAllEdgesAsDirty(); - }); - } + var Star = _interopRequire(__webpack_require__(75)); - _prototypeProperties(EdgesHandler, null, { - setOptions: { - value: function setOptions(options) { - if (options !== undefined) { - util.mergeOptions(this.options, options, "smooth"); - util.mergeOptions(this.options, options, "dashes"); + var Text = _interopRequire(__webpack_require__(76)); - // hanlde multiple input cases for arrows - if (options.arrows !== undefined) { - if (typeof options.arrows === "string") { - var arrows = options.arrows.toLowerCase(); - if (arrows.indexOf("to") != -1) { - this.options.arrows.to.enabled = true; - } - if (arrows.indexOf("middle") != -1) { - this.options.arrows.middle.enabled = true; - } - if (arrows.indexOf("from") != -1) { - this.options.arrows.from.enabled = true; - } - } else if (typeof options.arrows === "object") { - util.mergeOptions(this.options.arrows, options.arrows, "to"); - util.mergeOptions(this.options.arrows, options.arrows, "middle"); - util.mergeOptions(this.options.arrows, options.arrows, "from"); - } else { - throw new Error("The arrow options can only be an object or a string. Refer to the documentation. You used:" + JSON.stringify(options.arrows)); - } - } + var Triangle = _interopRequire(__webpack_require__(77)); + + var TriangleDown = _interopRequire(__webpack_require__(78)); + + /** + * @class Node + * A node. A node can be connected to other nodes via one or multiple edges. + * @param {object} options An object containing options for the node. All + * options are optional, except for the id. + * {number} id Id of the node. Required + * {string} label Text label for the node + * {number} x Horizontal position of the node + * {number} y Vertical position of the node + * {string} shape Node shape, available: + * "database", "circle", "ellipse", + * "box", "image", "text", "dot", + * "star", "triangle", "triangleDown", + * "square", "icon" + * {string} image An image url + * {string} title An title text, can be HTML + * {anytype} group A group name or number + * @param {Network.Images} imagelist A list with images. Only needed + * when the node has an image + * @param {Network.Groups} grouplist A list with groups. Needed for + * retrieving group options + * @param {Object} constants An object with default values for + * example for the color + * + */ + var Node = (function () { + function Node(options, body, imagelist, grouplist, globalOptions) { + _classCallCheck(this, Node); + + this.options = util.bridgeObject(globalOptions); + this.body = body; + + this.edges = []; // all edges connected to this node + + // set defaults for the options + this.id = undefined; + this.imagelist = imagelist; + this.grouplist = grouplist; + + // state options + this.x = undefined; + this.y = undefined; + this.predefinedPosition = false; // used to check if initial zoomExtent should just take the range or approximate + this.selected = false; + this.hover = false; + + this.labelModule = new Label(this.body, this.options); + this.setOptions(options); + } + + _prototypeProperties(Node, null, { + attachEdge: { - // hanlde multiple input cases for color - if (options.color !== undefined) { - if (util.isString(options.color)) { - util.assignAllKeys(this.options.color, options.color); - this.options.color.inherit.enabled = false; - } else { - util.extend(this.options.color, options.color); - if (options.color.inherit === undefined) { - this.options.color.inherit.enabled = false; - } - } - util.mergeOptions(this.options.color, options.color, "inherit"); - } - // font cases are handled by the Label class + /** + * Attach a edge to the node + * @param {Edge} edge + */ + value: function attachEdge(edge) { + if (this.edges.indexOf(edge) == -1) { + this.edges.push(edge); } }, writable: true, configurable: true }, - setData: { + detachEdge: { /** - * Load edges by reading the data table - * @param {Array | DataSet | DataView} edges The data containing the edges. - * @private - * @private + * Detach a edge from the node + * @param {Edge} edge */ - value: function setData(edges) { - var _this = this; - var doNotEmit = arguments[1] === undefined ? false : arguments[1]; - var oldEdgesData = this.body.data.edges; + value: function detachEdge(edge) { + var index = this.edges.indexOf(edge); + if (index != -1) { + this.edges.splice(index, 1); + } + }, + writable: true, + configurable: true + }, + togglePhysics: { - if (edges instanceof DataSet || edges instanceof DataView) { - this.body.data.edges = edges; - } else if (Array.isArray(edges)) { - this.body.data.edges = new DataSet(); - this.body.data.edges.add(edges); - } else if (!edges) { - this.body.data.edges = new DataSet(); - } else { - throw new TypeError("Array or DataSet expected"); - } - - // TODO: is this null or undefined or false? - if (oldEdgesData) { - // unsubscribe from old dataset - util.forEach(this.edgesListeners, function (callback, event) { - oldEdgesData.off(event, callback); - }); - } - - // remove drawn edges - this.body.edges = {}; - - // TODO: is this null or undefined or false? - if (this.body.data.edges) { - // subscribe to new dataset - util.forEach(this.edgesListeners, function (callback, event) { - _this.body.data.edges.on(event, callback); - }); - - // draw all new nodes - var ids = this.body.data.edges.getIds(); - this.add(ids, true); - } - - if (doNotEmit === false) { - this.body.emitter.emit("_dataChanged"); - } + /** + * Enable or disable the physics. + * @param status + */ + value: function togglePhysics(status) { + this.options.physics = status; }, writable: true, configurable: true }, - add: { + setOptions: { /** - * Add edges - * @param {Number[] | String[]} ids - * @private + * Set or overwrite options for the node + * @param {Object} options an object with options + * @param {Object} constants and object with default, global options */ - value: function add(ids) { - var doNotEmit = arguments[1] === undefined ? false : arguments[1]; - var edges = this.body.edges; - var edgesData = this.body.data.edges; - - for (var i = 0; i < ids.length; i++) { - var id = ids[i]; + value: function setOptions(options) { + if (!options) { + return; + } - var oldEdge = edges[id]; - if (oldEdge) { - oldEdge.disconnect(); - } + var fields = ["borderWidth", "borderWidthSelected", "brokenImage", "customScalingFunction", "font", "hidden", "icon", "id", "image", "label", "level", "physics", "shape", "size", "title", "value", "x", "y"]; + util.selectiveDeepExtend(fields, this.options, options); - var data = edgesData.get(id, { showInternalIds: true }); - edges[id] = this.create(data); + // basic options + if (options.id !== undefined) { + this.id = options.id; } - if (doNotEmit === false) { - this.body.emitter.emit("_dataChanged"); + if (this.id === undefined) { + throw "Node must have an id"; } - }, - writable: true, - configurable: true - }, - update: { + if (options.x !== undefined) { + this.x = options.x;this.predefinedPosition = true; + } + if (options.y !== undefined) { + this.y = options.y;this.predefinedPosition = true; + } + if (options.value !== undefined) { + this.value = options.value; + } + // copy group options + if (typeof options.group === "number" || typeof options.group === "string" && options.group != "") { + var groupObj = this.grouplist.get(options.group); + util.deepExtend(this.options, groupObj); + // the color object needs to be completely defined. Since groups can partially overwrite the colors, we parse it again, just in case. + this.options.color = util.parseColor(this.options.color); + } + // individual shape options + if (options.color !== undefined) { + this.options.color = util.parseColor(options.color); + } - /** - * Update existing edges, or create them when not yet existing - * @param {Number[] | String[]} ids - * @private - */ - value: function update(ids) { - var edges = this.body.edges; - var edgesData = this.body.data.edges; - var dataChanged = false; - for (var i = 0; i < ids.length; i++) { - var id = ids[i]; - var data = edgesData.get(id); - var edge = edges[id]; - if (edge === null) { - // update edge - edge.disconnect(); - dataChanged = edge.setOptions(data) || dataChanged; // if a support node is added, data can be changed. - edge.connect(); + if (this.options.image !== undefined && this.options.image != "") { + if (this.imagelist) { + this.imageObj = this.imagelist.load(this.options.image, this.options.brokenImage); } else { - // create edge - this.body.edges[id] = this.create(data); - dataChanged = true; + throw "No imagelist provided"; } } - if (dataChanged === true) { - this.body.emitter.emit("_dataChanged"); - } else { - this.body.emitter.emit("_dataUpdated"); - } - }, - writable: true, - configurable: true - }, - remove: { - - - - /** - * Remove existing edges. Non existing ids will be ignored - * @param {Number[] | String[]} ids - * @private - */ - value: function remove(ids) { - var edges = this.body.edges; - for (var i = 0; i < ids.length; i++) { - var id = ids[i]; - var edge = edges[id]; - if (edge !== undefined) { - if (edge.via != null) { - delete this.body.supportNodes[edge.via.id]; + if (options.fixed !== undefined) { + if (typeof options.fixed == "boolean") { + this.options.fixed.x = true; + this.options.fixed.y = true; + } else { + if (options.fixed.x !== undefined && typeof options.fixed.x == "boolean") { + this.options.fixed.x = options.fixed.x; + } + if (options.fixed.y !== undefined && typeof options.fixed.y == "boolean") { + this.options.fixed.y = options.fixed.y; } - edge.disconnect(); - delete edges[id]; } } - this.body.emitter.emit("_dataChanged"); + this.updateShape(); + this.updateLabelModule(); + + // reset the size of the node, this can be changed + this._reset(); }, writable: true, configurable: true }, - create: { - value: function create(properties) { - return new Edge(properties, this.body, this.options); + updateLabelModule: { + value: function updateLabelModule() { + this.labelModule.setOptions(this.options); }, writable: true, configurable: true }, - markAllEdgesAsDirty: { - value: function markAllEdgesAsDirty() { - for (var edgeId in this.body.edges) { - this.body.edges[edgeId].colorDirty = true; + updateShape: { + value: function updateShape() { + // choose draw method depending on the shape + switch (this.options.shape) { + case "box": + this.shape = new Box(this.options, this.body, this.labelModule); + break; + case "circle": + this.shape = new Circle(this.options, this.body, this.labelModule); + break; + case "circularImage": + this.shape = new CircularImage(this.options, this.body, this.labelModule, this.imageObj); + break; + case "database": + this.shape = new Database(this.options, this.body, this.labelModule); + break; + case "diamond": + this.shape = new Diamond(this.options, this.body, this.labelModule); + break; + case "dot": + this.shape = new Dot(this.options, this.body, this.labelModule); + break; + case "ellipse": + this.shape = new Ellipse(this.options, this.body, this.labelModule); + break; + case "icon": + this.shape = new Icon(this.options, this.body, this.labelModule); + break; + case "image": + this.shape = new Image(this.options, this.body, this.labelModule, this.imageObj); + break; + case "square": + this.shape = new Square(this.options, this.body, this.labelModule); + break; + case "star": + this.shape = new Star(this.options, this.body, this.labelModule); + break; + case "text": + this.shape = new Text(this.options, this.body, this.labelModule); + break; + case "triangle": + this.shape = new Triangle(this.options, this.body, this.labelModule); + break; + case "triangleDown": + this.shape = new TriangleDown(this.options, this.body, this.labelModule); + break; + default: + this.shape = new Ellipse(this.options, this.body, this.labelModule); + break; } }, writable: true, configurable: true }, - reconnectEdges: { - + select: { /** - * Reconnect all edges - * @private + * select this node */ - value: function reconnectEdges() { - var id; - var nodes = this.body.nodes; - var edges = this.body.edges; - - for (id in nodes) { - if (nodes.hasOwnProperty(id)) { - nodes[id].edges = []; - } - } - - for (id in edges) { - if (edges.hasOwnProperty(id)) { - var edge = edges[id]; - edge.from = null; - edge.to = null; - edge.connect(); - } - } + value: function select() { + this.selected = true; + this._reset(); }, writable: true, configurable: true - } - }); - - return EdgesHandler; - })(); - - module.exports = EdgesHandler; - -/***/ }, -/* 61 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - - var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; - - var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; - - var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; - - var util = __webpack_require__(1); - - - var Label = _interopRequire(__webpack_require__(62)); - - var BezierEdgeDynamic = _interopRequire(__webpack_require__(63)); - - var BezierEdgeStatic = _interopRequire(__webpack_require__(66)); - - var StraightEdge = _interopRequire(__webpack_require__(67)); - - /** - * @class Edge - * - * A edge connects two nodes - * @param {Object} properties Object with options. Must contain - * At least options from and to. - * Available options: from (number), - * to (number), label (string, color (string), - * width (number), style (string), - * length (number), title (string) - * @param {Network} network A Network object, used to find and edge to - * nodes. - * @param {Object} constants An object with default values for - * example for the color - */ - var Edge = (function () { - function Edge(options, body, globalOptions) { - _classCallCheck(this, Edge); - - if (body === undefined) { - throw "No body provided"; - } - this.options = util.bridgeObject(globalOptions); - this.body = body; - - // initialize variables - this.id = undefined; - this.fromId = undefined; - this.toId = undefined; - this.value = undefined; - this.selected = false; - this.hover = false; - this.labelDirty = true; - this.colorDirty = true; - - this.from = undefined; // a node - this.to = undefined; // a node - - this.edgeType = undefined; - - this.connected = false; - - this.labelModule = new Label(this.body, this.options); - - this.setOptions(options); - - this.controlNodesEnabled = false; - this.controlNodes = { from: undefined, to: undefined, positions: {} }; - this.connectedNode = undefined; - } - - _prototypeProperties(Edge, null, { - setOptions: { + }, + unselect: { /** - * Set or overwrite options for the edge - * @param {Object} options an object with options - * @param doNotEmit + * unselect this node */ - value: function setOptions(options) { - if (!options) { - return; - } - this.colorDirty = true; - - var fields = ["id", "font", "from", "hidden", "hoverWidth", "label", "length", "line", "opacity", "physics", "scaling", "selfReferenceSize", "to", "title", "value", "width", "widthMin", "widthMax", "widthSelectionMultiplier"]; - util.selectiveDeepExtend(fields, this.options, options); - - util.mergeOptions(this.options, options, "smooth"); - util.mergeOptions(this.options, options, "dashes"); - - if (options.id !== undefined) { - this.id = options.id; - } - if (options.from !== undefined) { - this.fromId = options.from; - } - if (options.to !== undefined) { - this.toId = options.to; - } - if (options.title !== undefined) { - this.title = options.title; - } - if (options.value !== undefined) { - this.value = options.value; - } - - // hanlde multiple input cases for arrows - if (options.arrows !== undefined) { - if (typeof options.arrows === "string") { - var arrows = options.arrows.toLowerCase(); - if (arrows.indexOf("to") != -1) { - this.options.arrows.to.enabled = true; - } - if (arrows.indexOf("middle") != -1) { - this.options.arrows.middle.enabled = true; - } - if (arrows.indexOf("from") != -1) { - this.options.arrows.from.enabled = true; - } - } else if (typeof options.arrows === "object") { - util.mergeOptions(this.options.arrows, options.arrows, "to"); - util.mergeOptions(this.options.arrows, options.arrows, "middle"); - util.mergeOptions(this.options.arrows, options.arrows, "from"); - } else { - throw new Error("The arrow options can only be an object or a string. Refer to the documentation. You used:" + JSON.stringify(options.arrows)); - } - } - - // hanlde multiple input cases for color - if (options.color !== undefined) { - if (util.isString(options.color)) { - util.assignAllKeys(this.options.color, options.color); - this.options.color.inherit.enabled = false; - } else { - util.extend(this.options.color, options.color); - if (options.color.inherit === undefined) { - this.options.color.inherit.enabled = false; - } - } - util.mergeOptions(this.options.color, options.color, "inherit"); - } - - // A node is connected when it has a from and to node that both exist in the network.body.nodes. - this.connect(); - - this.labelModule.setOptions(this.options); - - var dataChanged = this.updateEdgeType(); - return dataChanged; + value: function unselect() { + this.selected = false; + this._reset(); }, writable: true, configurable: true }, - updateEdgeType: { - value: function updateEdgeType() { - var dataChanged = false; - var changeInType = true; - if (this.edgeType !== undefined) { - if (this.edgeType instanceof BezierEdgeDynamic && this.options.smooth.enabled == true && this.options.smooth.dynamic == true) { - changeInType = false; - } - if (this.edgeType instanceof BezierEdgeStatic && this.options.smooth.enabled == true && this.options.smooth.dynamic == false) { - changeInType = false; - } - if (this.edgeType instanceof StraightEdge && this.options.smooth.enabled == false) { - changeInType = false; - } + _reset: { - if (changeInType == true) { - dataChanged = this.edgeType.cleanup(); - } - } - if (changeInType === true) { - if (this.options.smooth.enabled === true) { - if (this.options.smooth.dynamic === true) { - dataChanged = true; - this.edgeType = new BezierEdgeDynamic(this.options, this.body, this.labelModule); - } else { - this.edgeType = new BezierEdgeStatic(this.options, this.body, this.labelModule); - } - } else { - this.edgeType = new StraightEdge(this.options, this.body, this.labelModule); - } - } else { - // if nothing changes, we just set the options. - this.edgeType.setOptions(this.options); - } - return dataChanged; + /** + * Reset the calculated size of the node, forces it to recalculate its size + * @private + */ + value: function _reset() { + this.shape.width = undefined; + this.shape.height = undefined; }, writable: true, configurable: true }, - togglePhysics: { + getTitle: { /** - * Enable or disable the physics. - * @param status + * get the title of this node. + * @return {string} title The title of the node, or undefined when no title + * has been set. */ - value: function togglePhysics(status) { - if (this.options.smooth.enabled == true && this.options.smooth.dynamic == true) { - if (this.via === undefined) { - this.via.pptions.physics = status; - } - } - this.options.physics = status; + value: function getTitle() { + return typeof this.options.title === "function" ? this.options.title() : this.options.title; }, writable: true, configurable: true }, - connect: { + distanceToBorder: { + /** - * Connect an edge to its nodes + * Calculate the distance to the border of the Node + * @param {CanvasRenderingContext2D} ctx + * @param {Number} angle Angle in radians + * @returns {number} distance Distance to the border in pixels */ - value: function connect() { - this.disconnect(); + value: function distanceToBorder(ctx, angle) { + return this.shape.distanceToBorder(ctx, angle); + }, + writable: true, + configurable: true + }, + isFixed: { - this.from = this.body.nodes[this.fromId] || undefined; - this.to = this.body.nodes[this.toId] || undefined; - this.connected = this.from !== undefined && this.to !== undefined; - if (this.connected === true) { - this.from.attachEdge(this); - this.to.attachEdge(this); - } else { - if (this.from) { - this.from.detachEdge(this); - } - if (this.to) { - this.to.detachEdge(this); - } - } + /** + * Check if this node has a fixed x and y position + * @return {boolean} true if fixed, false if not + */ + value: function isFixed() { + return this.options.fixed.x && this.options.fixed.y; }, writable: true, configurable: true }, - disconnect: { - - - /** - * Disconnect an edge from its nodes - */ - value: function disconnect() { - if (this.from) { - this.from.detachEdge(this); - this.from = undefined; - } - if (this.to) { - this.to.detachEdge(this); - this.to = undefined; - } - - this.connected = false; - }, - writable: true, - configurable: true - }, - getTitle: { - - - /** - * get the title of this edge. - * @return {string} title The title of the edge, or undefined when no title - * has been set. - */ - value: function getTitle() { - return typeof this.title === "function" ? this.title() : this.title; - }, - writable: true, - configurable: true - }, - isSelected: { + isSelected: { /** @@ -26203,9 +25894,8 @@ return /******/ (function(modules) { // webpackBootstrap getValue: { - /** - * Retrieve the value of the edge. Can be undefined + * Retrieve the value of the node. Can be undefined * @return {Number} value */ value: function getValue() { @@ -26218,21 +25908,20 @@ return /******/ (function(modules) { // webpackBootstrap /** - * Adjust the value range of the edge. The edge will adjust it's width + * Adjust the value range of the node. The node will adjust it's size * based on its value. * @param {Number} min * @param {Number} max - * @param total */ value: function setValueRange(min, max, total) { if (this.value !== undefined) { var scale = this.options.scaling.customScalingFunction(min, max, total, this.value); - var widthDiff = this.options.scaling.max - this.options.scaling.min; + var sizeDiff = this.options.scaling.max - this.options.scaling.min; if (this.options.scaling.label.enabled == true) { var fontDiff = this.options.scaling.label.max - this.options.scaling.label.min; this.options.font.size = this.options.scaling.label.min + scale * fontDiff; } - this.options.width = this.options.scaling.min + scale * widthDiff; + this.options.size = this.options.scaling.min + scale * sizeDiff; } }, writable: true, @@ -26242,70 +25931,26 @@ return /******/ (function(modules) { // webpackBootstrap /** - * Redraw a edge - * Draw this edge in the given canvas + * Draw this node in the given canvas * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d"); * @param {CanvasRenderingContext2D} ctx */ value: function draw(ctx) { - var via = this.edgeType.drawLine(ctx, this.selected, this.hover); - this.drawArrows(ctx, via); - this.drawLabel(ctx, via); - }, - writable: true, - configurable: true - }, - drawArrows: { - value: function drawArrows(ctx, viaNode) { - if (this.options.arrows.from.enabled === true) { - this.edgeType.drawArrowHead(ctx, "from", viaNode, this.selected, this.hover); - } - if (this.options.arrows.middle.enabled === true) { - this.edgeType.drawArrowHead(ctx, "middle", viaNode, this.selected, this.hover); - } - if (this.options.arrows.to.enabled === true) { - this.edgeType.drawArrowHead(ctx, "to", viaNode, this.selected, this.hover); - } + this.shape.draw(ctx, this.x, this.y, this.selected, this.hover); }, writable: true, configurable: true }, - drawLabel: { - value: function drawLabel(ctx, viaNode) { - if (this.options.label !== undefined) { - // set style - var node1 = this.from; - var node2 = this.to; - var selected = this.from.selected || this.to.selected || this.selected; - if (node1.id != node2.id) { - var point = this.edgeType.getPoint(0.5, viaNode); - ctx.save(); - - // if the label has to be rotated: - if (this.options.font.align !== "horizontal") { - this.labelModule.calculateLabelSize(ctx, selected, point.x, point.y); - ctx.translate(point.x, this.labelModule.size.yLine); - this._rotateForLabelAlignment(ctx); - } + resize: { - // draw the label - this.labelModule.draw(ctx, point.x, point.y, selected); - ctx.restore(); - } else { - var x, y; - var radius = this.options.selfReferenceSize; - if (node1.width > node1.height) { - x = node1.x + node1.width * 0.5; - y = node1.y - radius; - } else { - x = node1.x + radius; - y = node1.y - node1.height * 0.5; - } - point = this._pointOnCircle(x, y, radius, 0.125); - this.labelModule.draw(ctx, point.x, point.y, selected); - } - } + /** + * Recalculate the size of this node in the given canvas + * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d"); + * @param {CanvasRenderingContext2D} ctx + */ + value: function resize(ctx) { + this.shape.resize(ctx); }, writable: true, configurable: true @@ -26315,97 +25960,24 @@ return /******/ (function(modules) { // webpackBootstrap /** * Check if this object is overlapping with the provided object - * @param {Object} obj an object with parameters left, top - * @return {boolean} True if location is located on the edge + * @param {Object} obj an object with parameters left, top, right, bottom + * @return {boolean} True if location is located on node */ value: function isOverlappingWith(obj) { - if (this.connected) { - var distMax = 10; - var xFrom = this.from.x; - var yFrom = this.from.y; - var xTo = this.to.x; - var yTo = this.to.y; - var xObj = obj.left; - var yObj = obj.top; - - var dist = this.edgeType.getDistanceToEdge(xFrom, yFrom, xTo, yTo, xObj, yObj); - - return dist < distMax; - } else { - return false; - } - }, - writable: true, - configurable: true - }, - _rotateForLabelAlignment: { - - - /** - * Rotates the canvas so the text is most readable - * @param {CanvasRenderingContext2D} ctx - * @private - */ - value: function _rotateForLabelAlignment(ctx) { - var dy = this.from.y - this.to.y; - var dx = this.from.x - this.to.x; - var angleInDegrees = Math.atan2(dy, dx); - - // rotate so label it is readable - if (angleInDegrees < -1 && dx < 0 || angleInDegrees > 0 && dx < 0) { - angleInDegrees = angleInDegrees + Math.PI; - } - - ctx.rotate(angleInDegrees); - }, - writable: true, - configurable: true - }, - _pointOnCircle: { - - - /** - * Get a point on a circle - * @param {Number} x - * @param {Number} y - * @param {Number} radius - * @param {Number} percentage. Value between 0 (line start) and 1 (line end) - * @return {Object} point - * @private - */ - value: function _pointOnCircle(x, y, radius, percentage) { - var angle = percentage * 2 * Math.PI; - return { - x: x + radius * Math.cos(angle), - y: y - radius * Math.sin(angle) - }; - }, - writable: true, - configurable: true - }, - select: { - value: function select() { - this.selected = true; - }, - writable: true, - configurable: true - }, - unselect: { - value: function unselect() { - this.selected = false; + return this.shape.left < obj.right && this.shape.left + this.shape.width > obj.left && this.shape.top < obj.bottom && this.shape.top + this.shape.height > obj.top; }, writable: true, configurable: true } }); - return Edge; + return Node; })(); - module.exports = Edge; + module.exports = Node; /***/ }, -/* 62 */ +/* 61 */ /***/ function(module, exports, __webpack_require__) { "use strict"; @@ -26730,9 +26302,12 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = Label; /***/ }, -/* 63 */ +/* 62 */ /***/ function(module, exports, __webpack_require__) { + /** + * Created by Alex on 3/18/2015. + */ "use strict"; var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; @@ -26745,152 +26320,136 @@ return /******/ (function(modules) { // webpackBootstrap var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; - /** - * Created by Alex on 3/20/2015. - */ - - var BezierEdgeBase = _interopRequire(__webpack_require__(64)); + var NodeBase = _interopRequire(__webpack_require__(63)); - var BezierEdgeDynamic = (function (BezierEdgeBase) { - function BezierEdgeDynamic(options, body, labelModule) { - _classCallCheck(this, BezierEdgeDynamic); + var Box = (function (NodeBase) { + function Box(options, body, labelModule) { + _classCallCheck(this, Box); - this.via = undefined; - _get(Object.getPrototypeOf(BezierEdgeDynamic.prototype), "constructor", this).call(this, options, body, labelModule); // --> this calls the setOptions below + _get(Object.getPrototypeOf(Box.prototype), "constructor", this).call(this, options, body, labelModule); } - _inherits(BezierEdgeDynamic, BezierEdgeBase); + _inherits(Box, NodeBase); - _prototypeProperties(BezierEdgeDynamic, null, { - setOptions: { - value: function setOptions(options) { - this.options = options; - this.from = this.body.nodes[this.options.from]; - this.to = this.body.nodes[this.options.to]; - this.id = this.options.id; - this.setupSupportNode(); - }, - writable: true, - configurable: true - }, - cleanup: { - value: function cleanup() { - if (this.via !== undefined) { - delete this.body.nodes[this.via.id]; - this.via = undefined; - return true; + _prototypeProperties(Box, null, { + resize: { + value: function resize(ctx) { + if (this.width === undefined) { + var margin = 5; + var textSize = this.labelModule.getTextSize(ctx, this.selected); + this.width = textSize.width + 2 * margin; + this.height = textSize.height + 2 * margin; } - return false; }, writable: true, configurable: true }, - setupSupportNode: { + draw: { + value: function draw(ctx, x, y, selected, hover) { + this.resize(ctx); + this.left = x - this.width / 2; + this.top = y - this.height / 2; - /** - * 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, - mass: 1, - shape: "circle", - image: "", - physics: true, - hidden: true - }); - this.body.nodes[nodeId] = node; - this.via = node; - this.via.parentEdgeId = this.id; - this.positionBezierNode(); - } + 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.roundRect(this.left, this.top, this.width, this.height, this.options.size); + ctx.fill(); + ctx.stroke(); + + this.boundingBox.top = this.top; + this.boundingBox.left = this.left; + this.boundingBox.right = this.left + this.width; + this.boundingBox.bottom = this.top + this.height; + + this.labelModule.draw(ctx, x, y, selected); }, writable: true, configurable: true }, - 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; - } + distanceToBorder: { + value: function distanceToBorder(ctx, angle) { + this.resize(ctx); + var a = this.width / 2; + var b = this.height / 2; + var w = Math.sin(angle) * a; + var h = Math.cos(angle) * b; + return a * b / Math.sqrt(w * w + h * h); }, writable: true, configurable: true - }, - _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); - ctx.stroke(); - return this.via; - }, - writable: true, - configurable: true - }, - getPoint: { + return Box; + })(NodeBase); + module.exports = Box; - /** - * 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; +/***/ }, +/* 63 */ +/***/ function(module, exports, __webpack_require__) { - return { x: x, y: y }; - }, - writable: true, - configurable: true - }, - _findBorderPosition: { - value: function _findBorderPosition(nearNode, ctx) { - return this._findBorderPositionBezier(nearNode, ctx, this.via); + "use strict"; + + var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; + + var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; + + /** + * Created by Alex on 3/19/2015. + */ + + var NodeBase = (function () { + function NodeBase(options, body, labelModule) { + _classCallCheck(this, NodeBase); + + this.body = body; + this.labelModule = labelModule; + this.setOptions(options); + this.top = undefined; + this.left = undefined; + this.height = undefined; + this.boundingBox = { top: 0, left: 0, right: 0, bottom: 0 }; + } + + _prototypeProperties(NodeBase, null, { + setOptions: { + value: function setOptions(options) { + this.options = options; }, writable: true, configurable: true }, - _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); + _distanceToBorder: { + value: function _distanceToBorder(angle) { + var borderWidth = 1; + return Math.min(Math.abs(this.width / 2 / Math.cos(angle)), Math.abs(this.height / 2 / Math.sin(angle))) + borderWidth; }, writable: true, configurable: true } }); - return BezierEdgeDynamic; - })(BezierEdgeBase); + return NodeBase; + })(); - module.exports = BezierEdgeDynamic; + module.exports = NodeBase; /***/ }, /* 64 */ /***/ function(module, exports, __webpack_require__) { + /** + * Created by Alex on 3/18/2015. + */ "use strict"; var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; @@ -26903,139 +26462,69 @@ return /******/ (function(modules) { // webpackBootstrap var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; - /** - * Created by Alex on 3/20/2015. - */ - - var EdgeBase = _interopRequire(__webpack_require__(65)); + var CircleImageBase = _interopRequire(__webpack_require__(65)); - var BezierEdgeBase = (function (EdgeBase) { - function BezierEdgeBase(options, body, labelModule) { - _classCallCheck(this, BezierEdgeBase); + var Circle = (function (CircleImageBase) { + function Circle(options, body, labelModule) { + _classCallCheck(this, Circle); - _get(Object.getPrototypeOf(BezierEdgeBase.prototype), "constructor", this).call(this, options, body, labelModule); + _get(Object.getPrototypeOf(Circle.prototype), "constructor", this).call(this, options, body, labelModule); } - _inherits(BezierEdgeBase, EdgeBase); + _inherits(Circle, CircleImageBase); - _prototypeProperties(BezierEdgeBase, null, { - _findBorderPositionBezier: { + _prototypeProperties(Circle, null, { + resize: { + value: function resize(ctx, selected) { + if (this.width === undefined) { + var margin = 5; + var textSize = this.labelModule.getTextSize(ctx, selected); + var diameter = Math.max(textSize.width, textSize.height) + 2 * margin; + this.options.size = diameter / 2; - /** - * This function uses binary search to look for the point where the bezier curve crosses the border of the node. - * - * @param nearNode - * @param ctx - * @param viaNode - * @param nearNode - * @param ctx - * @param viaNode - * @param nearNode - * @param ctx - * @param viaNode - */ - value: function _findBorderPositionBezier(nearNode, ctx) { - var viaNode = arguments[2] === undefined ? this._getViaCoordinates() : arguments[2]; - var maxIterations = 10; - var iteration = 0; - var low = 0; - var high = 1; - var pos, angle, distanceToBorder, distanceToPoint, difference; - var threshold = 0.2; - var node = this.to; - var from = false; - if (nearNode.id === this.from.id) { - node = this.from; - from = true; + this.width = diameter; + this.height = diameter; } + }, + writable: true, + configurable: true + }, + draw: { + value: function draw(ctx, x, y, selected, hover) { + this.resize(ctx, selected); + this.left = x - this.width / 2; + this.top = y - this.height / 2; - while (low <= high && iteration < maxIterations) { - var middle = (low + high) * 0.5; - - pos = this.getPoint(middle, viaNode); - angle = Math.atan2(node.y - pos.y, node.x - pos.x); - distanceToBorder = node.distanceToBorder(ctx, angle); - distanceToPoint = Math.sqrt(Math.pow(pos.x - node.x, 2) + Math.pow(pos.y - node.y, 2)); - difference = distanceToBorder - distanceToPoint; - if (Math.abs(difference) < threshold) { - break; // found - } else if (difference < 0) { - // distance to nodes is larger than distance to border --> t needs to be bigger if we're looking at the to node. - if (from == false) { - low = middle; - } else { - high = middle; - } - } else { - if (from == false) { - high = middle; - } else { - low = middle; - } - } + this._drawRawCircle(ctx, x, y, selected, hover, this.options.size); - iteration++; - } - pos.t = middle; + this.boundingBox.top = y - this.options.size; + this.boundingBox.left = x - this.options.size; + this.boundingBox.right = x + this.options.size; + this.boundingBox.bottom = y + this.options.size; - return pos; + this.labelModule.draw(ctx, x, y, selected); }, writable: true, configurable: true }, - _getDistanceToBezierEdge: { + distanceToBorder: { + value: function distanceToBorder(ctx, angle) { + this.resize(ctx); + var a = this.width / 2; + var b = this.height / 2; + var w = Math.sin(angle) * a; + var h = Math.cos(angle) * b; + return a * b / Math.sqrt(w * w + h * h); + }, + writable: true, + configurable: true + } + }); + return Circle; + })(CircleImageBase); - - /** - * Calculate the distance between a point (x3,y3) and a line segment from - * (x1,y1) to (x2,y2). - * http://stackoverflow.com/questions/849211/shortest-distancae-between-a-point-and-a-line-segment - * @param {number} x1 - * @param {number} y1 - * @param {number} x2 - * @param {number} y2 - * @param {number} x3 - * @param {number} y3 - * @private - */ - value: function _getDistanceToBezierEdge(x1, y1, x2, y2, x3, y3, via) { - // x3,y3 is the point - var xVia = undefined, - yVia = undefined; - xVia = via.x; - yVia = via.y; - var minDistance = 1000000000; - var distance = undefined; - var i = undefined, - t = undefined, - x = undefined, - y = undefined; - var lastX = x1; - var lastY = y1; - for (i = 1; i < 10; i++) { - t = 0.1 * i; - x = Math.pow(1 - t, 2) * x1 + 2 * t * (1 - t) * xVia + Math.pow(t, 2) * x2; - y = Math.pow(1 - t, 2) * y1 + 2 * t * (1 - t) * yVia + Math.pow(t, 2) * y2; - if (i > 0) { - distance = this._getDistanceToLine(lastX, lastY, x, y, x3, y3); - minDistance = distance < minDistance ? distance : minDistance; - } - lastX = x; - lastY = y; - } - - return minDistance; - }, - writable: true, - configurable: true - } - }); - - return BezierEdgeBase; - })(EdgeBase); - - module.exports = BezierEdgeBase; + module.exports = Circle; /***/ }, /* 65 */ @@ -27043,530 +26532,344 @@ return /******/ (function(modules) { // webpackBootstrap "use strict"; - var _slicedToArray = function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { var _arr = []; for (var _iterator = arr[Symbol.iterator](), _step; !(_step = _iterator.next()).done;) { _arr.push(_step.value); if (i && _arr.length === i) break; } return _arr; } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; + var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; + var _get = function get(object, property, receiver) { var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc && desc.writable) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + + var _inherits = function (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 _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; /** - * Created by Alex on 3/20/2015. + * Created by Alex on 3/19/2015. */ - var util = __webpack_require__(1); + var NodeBase = _interopRequire(__webpack_require__(63)); - var EdgeBase = (function () { - function EdgeBase(options, body, labelModule) { - _classCallCheck(this, EdgeBase); + var CircleImageBase = (function (NodeBase) { + function CircleImageBase(options, body, labelModule) { + _classCallCheck(this, CircleImageBase); - this.body = body; - this.labelModule = labelModule; - this.setOptions(options); - this.colorDirty = true; + _get(Object.getPrototypeOf(CircleImageBase.prototype), "constructor", this).call(this, options, body, labelModule); } - _prototypeProperties(EdgeBase, null, { - setOptions: { - value: function setOptions(options) { - this.options = options; - this.from = this.body.nodes[this.options.from]; - this.to = this.body.nodes[this.options.to]; - this.id = this.options.id; - }, - writable: true, - configurable: true - }, - drawLine: { + _inherits(CircleImageBase, NodeBase); - /** - * Redraw a edge as a line - * Draw this edge in the given canvas - * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d"); - * @param {CanvasRenderingContext2D} ctx - * @private - */ - value: function drawLine(ctx, selected, hover) { - // set style - ctx.strokeStyle = this.getColor(ctx); - ctx.lineWidth = this.getLineWidth(selected, hover); - var via = undefined; - if (this.from != this.to) { - // draw line - if (this.options.dashes.enabled == true) { - via = this._drawDashedLine(ctx); - } else { - via = this._line(ctx); - } - } else { - var _getCircleData = this._getCircleData(); + _prototypeProperties(CircleImageBase, null, { + _drawRawCircle: { + value: function _drawRawCircle(ctx, x, y, selected, hover, size) { + var borderWidth = this.options.borderWidth; + var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth; - var _getCircleData2 = _slicedToArray(_getCircleData, 3); + ctx.strokeStyle = selected ? this.options.color.highlight.border : hover ? this.options.color.hover.border : this.options.color.border; - var x = _getCircleData2[0]; - var y = _getCircleData2[1]; - var radius = _getCircleData2[2]; - this._circle(ctx, x, y, radius); - } + ctx.lineWidth = selected ? selectionLineWidth : borderWidth; + ctx.lineWidth *= this.networkScaleInv; + ctx.lineWidth = Math.min(this.width, ctx.lineWidth); - return via; + ctx.fillStyle = selected ? this.options.color.highlight.background : hover ? this.options.color.hover.background : this.options.color.background; + ctx.circle(x, y, size); + ctx.fill(); + ctx.stroke(); }, writable: true, configurable: true }, - _drawDashedLine: { - value: function _drawDashedLine(ctx) { - var via = undefined; - // only firefox and chrome support this method, else we use the legacy one. - if (ctx.setLineDash !== undefined && this.options.dashes.altLength === undefined) { - ctx.save(); - // configure the dash pattern - var pattern = [0]; - if (this.options.dashes.length !== undefined && this.options.dashes.gap !== undefined) { - pattern = [this.options.dashes.length, this.options.dashes.gap]; - } else { - pattern = [5, 5]; - } - - // set dash settings for chrome or firefox - ctx.setLineDash(pattern); - ctx.lineDashOffset = 0; - - // draw the line - via = this._line(ctx); - - // restore the dash settings. - ctx.setLineDash([0]); - ctx.lineDashOffset = 0; - ctx.restore(); - } else { - // unsupporting smooth lines - // draw dashes line - ctx.beginPath(); - ctx.lineCap = "round"; - if (this.options.dashes.altLength !== undefined) //If an alt dash value has been set add to the array this value - { - ctx.dashesLine(this.from.x, this.from.y, this.to.x, this.to.y, [this.options.dashes.length, this.options.dashes.gap, this.options.dashes.altLength, this.options.dashes.gap]); - } else if (this.options.dashes.length !== undefined && this.options.dashes.gap !== undefined) //If a dash and gap value has been set add to the array this value - { - ctx.dashesLine(this.from.x, this.from.y, this.to.x, this.to.y, [this.options.dashes.length, this.options.dashes.gap]); - } else //If all else fails draw a line - { - ctx.moveTo(this.from.x, this.from.y); - ctx.lineTo(this.to.x, this.to.y); - } - ctx.stroke(); + _drawImageAtPosition: { + value: function _drawImageAtPosition(ctx) { + if (this.imageObj.width != 0) { + // draw the image + ctx.globalAlpha = 1; + ctx.drawImage(this.imageObj, this.left, this.top, this.width, this.height); } - return via; }, writable: true, configurable: true }, - findBorderPosition: { - value: function findBorderPosition(nearNode, ctx, options) { - if (this.from != this.to) { - return this._findBorderPosition(nearNode, ctx, options); - } else { - return this._findBorderPositionCircle(nearNode, ctx, options); + _drawImageLabel: { + value: function _drawImageLabel(ctx, x, y, selected) { + var yLabel; + var offset = 0; + + if (this.height !== undefined) { + offset = this.height * 0.5; + var labelDimensions = this.labelModule.getTextSize(ctx); + + if (labelDimensions.lineCount >= 1) { + offset += labelDimensions.height / 2; + offset += 3; + } } + + yLabel = y + offset; + this.labelModule.draw(ctx, x, yLabel, selected, "hanging"); }, writable: true, configurable: true - }, - findBorderPositions: { - value: function findBorderPositions(ctx) { - var from = {}; - var to = {}; - if (this.from != this.to) { - from = this._findBorderPosition(this.from, ctx); - to = this._findBorderPosition(this.to, ctx); - } else { - var _getCircleData = this._getCircleData(); + } + }); - var _getCircleData2 = _slicedToArray(_getCircleData, 3); + return CircleImageBase; + })(NodeBase); - var x = _getCircleData2[0]; - var y = _getCircleData2[1]; - var radius = _getCircleData2[2]; + module.exports = CircleImageBase; +/***/ }, +/* 66 */ +/***/ function(module, exports, __webpack_require__) { - from = this._findBorderPositionCircle(this.from, ctx, { x: x, y: y, low: 0.25, high: 0.6, direction: -1 }); - to = this._findBorderPositionCircle(this.from, ctx, { x: x, y: y, low: 0.6, high: 0.8, direction: 1 }); - } - return { from: from, to: to }; - }, - writable: true, - configurable: true - }, - _getCircleData: { - value: function _getCircleData() { - var x = undefined, - y = undefined; - var node = this.from; - var radius = this.options.selfReferenceSize; + /** + * Created by Alex on 3/18/2015. + */ + "use strict"; - // get circle coordinates - if (node.shape.width > node.shape.height) { - x = node.x + node.shape.width * 0.5; - y = node.y - radius; - } else { - x = node.x + radius; - y = node.y - node.shape.height * 0.5; - } - return [x, y, radius]; - }, - writable: true, - configurable: true - }, - _pointOnCircle: { - /** - * Get a point on a circle - * @param {Number} x - * @param {Number} y - * @param {Number} radius - * @param {Number} percentage. Value between 0 (line start) and 1 (line end) - * @return {Object} point - * @private - */ - value: function _pointOnCircle(x, y, radius, percentage) { - var angle = percentage * 2 * Math.PI; - return { - x: x + radius * Math.cos(angle), - y: y - radius * Math.sin(angle) - }; - }, - writable: true, - configurable: true - }, - _findBorderPositionCircle: { + var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; - /** - * This function uses binary search to look for the point where the circle crosses the border of the node. - * @param node - * @param ctx - * @param options - * @returns {*} - * @private - */ - value: function _findBorderPositionCircle(node, ctx, options) { - var x = options.x; - var y = options.y; - var low = options.low; - var high = options.high; - var direction = options.direction; + var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; - var maxIterations = 10; - var iteration = 0; - var radius = this.options.selfReferenceSize; - var pos = undefined, - angle = undefined, - distanceToBorder = undefined, - distanceToPoint = undefined, - difference = undefined; - var threshold = 0.05; - var middle = (low + high) * 0.5; + var _get = function get(object, property, receiver) { var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc && desc.writable) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; - while (low <= high && iteration < maxIterations) { - middle = (low + high) * 0.5; + var _inherits = function (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; }; - pos = this._pointOnCircle(x, y, radius, middle); - angle = Math.atan2(node.y - pos.y, node.x - pos.x); - distanceToBorder = node.distanceToBorder(ctx, angle); - distanceToPoint = Math.sqrt(Math.pow(pos.x - node.x, 2) + Math.pow(pos.y - node.y, 2)); - difference = distanceToBorder - distanceToPoint; - if (Math.abs(difference) < threshold) { - break; // found - } else if (difference > 0) { - // distance to nodes is larger than distance to border --> t needs to be bigger if we're looking at the to node. - if (direction > 0) { - low = middle; - } else { - high = middle; - } - } else { - if (direction > 0) { - high = middle; - } else { - low = middle; - } - } - iteration++; - } - pos.t = middle; + var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; - return pos; - }, - writable: true, - configurable: true - }, - getLineWidth: { + var CircleImageBase = _interopRequire(__webpack_require__(65)); - /** - * Get the line width of the edge. Depends on width and whether one of the - * connected nodes is selected. - * @return {Number} width - * @private - */ - value: function getLineWidth(selected, hover) { - if (selected == true) { - return Math.max(Math.min(this.options.widthSelectionMultiplier * this.options.width, this.options.scaling.max), 0.3 / this.body.view.scale); + var CircularImage = (function (CircleImageBase) { + function CircularImage(options, body, labelModule, imageObj) { + _classCallCheck(this, CircularImage); + + _get(Object.getPrototypeOf(CircularImage.prototype), "constructor", this).call(this, options, body, labelModule); + this.imageObj = imageObj; + } + + _inherits(CircularImage, CircleImageBase); + + _prototypeProperties(CircularImage, null, { + resize: { + value: function resize(ctx) { + if (this.imageObj.src !== undefined || this.imageObj.width !== undefined || this.imageObj.height !== undefined) { + if (!this.width) { + var diameter = this.options.size * 2; + this.width = diameter; + this.height = diameter; + this._swapToImageResizeWhenImageLoaded = true; + } } else { - if (hover == true) { - return Math.max(Math.min(this.options.hoverWidth, this.options.scaling.max), 0.3 / this.body.view.scale); - } else { - return Math.max(this.options.width, 0.3 / this.body.view.scale); + if (this._swapToImageResizeWhenImageLoaded) { + this.width = 0; + this.height = 0; + delete this._swapToImageResizeWhenImageLoaded; } + this._resizeImage(ctx); } }, writable: true, configurable: true }, - getColor: { - value: function getColor(ctx) { - var colorObj = this.options.color; + draw: { + value: function draw(ctx, x, y, selected, hover) { + this.resize(ctx); - if (colorObj.inherit.enabled === true) { - if (colorObj.inherit.useGradients == true) { - var grd = ctx.createLinearGradient(this.from.x, this.from.y, this.to.x, this.to.y); - var fromColor, toColor; - fromColor = this.from.options.color.highlight.border; - toColor = this.to.options.color.highlight.border; + this.left = x - this.width / 2; + this.top = y - this.height / 2; - if (this.from.selected == false && this.to.selected == false) { - fromColor = util.overrideOpacity(this.from.options.color.border, this.options.color.opacity); - toColor = util.overrideOpacity(this.to.options.color.border, this.options.color.opacity); - } else if (this.from.selected == true && this.to.selected == false) { - toColor = this.to.options.color.border; - } else if (this.from.selected == false && this.to.selected == true) { - fromColor = this.from.options.color.border; - } - grd.addColorStop(0, fromColor); - grd.addColorStop(1, toColor); + var size = Math.abs(this.height / 2); + this._drawRawCircle(ctx, x, y, selected, hover, size); - // -------------------- this returns -------------------- // - return grd; - } + ctx.save(); + ctx.circle(x, y, size); + ctx.stroke(); + ctx.clip(); - if (this.colorDirty === true) { - if (colorObj.inherit.source == "to") { - colorObj.highlight = this.to.options.color.highlight.border; - colorObj.hover = this.to.options.color.hover.border; - colorObj.color = util.overrideOpacity(this.to.options.color.border, this.options.color.opacity); - } else { - // (this.options.color.inherit.source == "from") { - colorObj.highlight = this.from.options.color.highlight.border; - colorObj.hover = this.from.options.color.hover.border; - colorObj.color = util.overrideOpacity(this.from.options.color.border, this.options.color.opacity); - } - } - } + this._drawImageAtPosition(ctx); - // if color inherit is on and gradients are used, the function has already returned by now. - this.colorDirty = false; + ctx.restore(); - if (this.selected == true) { - return colorObj.highlight; - } else if (this.hover == true) { - return colorObj.hover; - } else { - return colorObj.color; - } + this.boundingBox.top = y - this.options.size; + this.boundingBox.left = x - this.options.size; + this.boundingBox.right = x + this.options.size; + this.boundingBox.bottom = y + this.options.size; + + this._drawImageLabel(ctx, x, y, selected); + + this.boundingBox.left = Math.min(this.boundingBox.left, this.labelModule.size.left); + this.boundingBox.right = Math.max(this.boundingBox.right, this.labelModule.size.left + this.labelModule.size.width); + this.boundingBox.bottom = Math.max(this.boundingBox.bottom, this.boundingBox.bottom + this.labelModule.size.height); }, writable: true, configurable: true }, - _circle: { - - /** - * Draw a line from a node to itself, a circle - * @param {CanvasRenderingContext2D} ctx - * @param {Number} x - * @param {Number} y - * @param {Number} radius - * @private - */ - value: function _circle(ctx, x, y, radius) { - // draw a circle - ctx.beginPath(); - ctx.arc(x, y, radius, 0, 2 * Math.PI, false); - ctx.stroke(); + distanceToBorder: { + value: function distanceToBorder(ctx, angle) { + this.resize(ctx); + return this._distanceToBorder(angle); }, writable: true, configurable: true - }, - getDistanceToEdge: { + } + }); + return CircularImage; + })(CircleImageBase); - /** - * Calculate the distance between a point (x3,y3) and a line segment from - * (x1,y1) to (x2,y2). - * http://stackoverflow.com/questions/849211/shortest-distancae-between-a-point-and-a-line-segment - * @param {number} x1 - * @param {number} y1 - * @param {number} x2 - * @param {number} y2 - * @param {number} x3 - * @param {number} y3 - * @private - */ - value: function getDistanceToEdge(x1, y1, x2, y2, x3, y3, via) { - // x3,y3 is the point - var returnValue = 0; - if (this.from != this.to) { - returnValue = this._getDistanceToEdge(x1, y1, x2, y2, x3, y3, via); - } else { - var _getCircleData = this._getCircleData(); + module.exports = CircularImage; - var _getCircleData2 = _slicedToArray(_getCircleData, 3); +/***/ }, +/* 67 */ +/***/ function(module, exports, __webpack_require__) { - var x = _getCircleData2[0]; - var y = _getCircleData2[1]; - var radius = _getCircleData2[2]; - var dx = x - x3; - var dy = y - y3; - returnValue = Math.abs(Math.sqrt(dx * dx + dy * dy) - radius); - } + /** + * Created by Alex on 3/18/2015. + */ + "use strict"; - if (this.labelModule.size.left < x3 && this.labelModule.size.left + this.labelModule.size.width > x3 && this.labelModule.size.top < y3 && this.labelModule.size.top + this.labelModule.size.height > y3) { - return 0; - } else { - return returnValue; - } - }, - writable: true, - configurable: true - }, - _getDistanceToLine: { - value: function _getDistanceToLine(x1, y1, x2, y2, x3, y3) { - var px = x2 - x1; - var py = y2 - y1; - var something = px * px + py * py; - var u = ((x3 - x1) * px + (y3 - y1) * py) / something; + var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; - if (u > 1) { - u = 1; - } else if (u < 0) { - u = 0; - } + var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; - var x = x1 + u * px; - var y = y1 + u * py; - var dx = x - x3; - var dy = y - y3; + var _get = function get(object, property, receiver) { var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc && desc.writable) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; - //# Note: If the actual distance does not matter, - //# if you only want to compare what this function - //# returns to other results of this function, you - //# can just return the squared distance instead - //# (i.e. remove the sqrt) to gain a little performance + var _inherits = function (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; }; - return Math.sqrt(dx * dx + dy * dy); + var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; + + var NodeBase = _interopRequire(__webpack_require__(63)); + + var Database = (function (NodeBase) { + function Database(options, body, labelModule) { + _classCallCheck(this, Database); + + _get(Object.getPrototypeOf(Database.prototype), "constructor", this).call(this, options, body, labelModule); + } + + _inherits(Database, NodeBase); + + _prototypeProperties(Database, null, { + resize: { + value: function resize(ctx, selected) { + if (this.width === undefined) { + var margin = 5; + var textSize = this.labelModule.getTextSize(ctx, selected); + var size = textSize.width + 2 * margin; + this.width = size; + this.height = size; + } }, writable: true, configurable: true }, - drawArrowHead: { + draw: { + value: function draw(ctx, x, y, selected, hover) { + this.resize(ctx, selected); + this.left = x - this.width / 2; + this.top = y - this.height / 2; - /** - * - * @param ctx - * @param position - * @param viaNode - */ - value: function drawArrowHead(ctx, position, viaNode, selected, hover) { - // set style - ctx.strokeStyle = this.getColor(ctx); - ctx.fillStyle = ctx.strokeStyle; - ctx.lineWidth = this.getLineWidth(selected, hover); + var borderWidth = this.options.borderWidth; + var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth; - // set lets - var angle = undefined; - var length = undefined; - var arrowPos = undefined; - var node1 = undefined; - var node2 = undefined; - var guideOffset = undefined; - var scaleFactor = undefined; + ctx.strokeStyle = selected ? this.options.color.highlight.border : hover ? this.options.color.hover.border : this.options.color.border; + ctx.lineWidth = this.selected ? selectionLineWidth : borderWidth; + ctx.lineWidth *= this.networkScaleInv; + ctx.lineWidth = Math.min(this.width, ctx.lineWidth); - if (position == "from") { - node1 = this.from; - node2 = this.to; - guideOffset = 0.1; - scaleFactor = this.options.arrows.from.scaleFactor; - } else if (position == "to") { - node1 = this.to; - node2 = this.from; - guideOffset = -0.1; - scaleFactor = this.options.arrows.to.scaleFactor; - } else { - node1 = this.to; - node2 = this.from; - scaleFactor = this.options.arrows.middle.scaleFactor; - } + ctx.fillStyle = selected ? this.options.color.highlight.background : hover ? this.options.color.hover.background : this.options.color.background; + ctx.database(x - this.width / 2, y - this.height * 0.5, this.width, this.height); + ctx.fill(); + ctx.stroke(); - // if not connected to itself - if (node1 != node2) { - if (position !== "middle") { - // draw arrow head - if (this.options.smooth.enabled == true) { - arrowPos = this.findBorderPosition(node1, ctx, { via: viaNode }); - var guidePos = this.getPoint(Math.max(0, Math.min(1, arrowPos.t + guideOffset)), viaNode); - angle = Math.atan2(arrowPos.y - guidePos.y, arrowPos.x - guidePos.x); - } else { - angle = Math.atan2(node1.y - node2.y, node1.x - node2.x); - arrowPos = this.findBorderPosition(node1, ctx); - } - } else { - angle = Math.atan2(node1.y - node2.y, node1.x - node2.x); - arrowPos = this.getPoint(0.6, viaNode); // this is 0.6 to account for the size of the arrow. - } - // draw arrow at the end of the line - length = (10 + 5 * this.options.width) * scaleFactor; - ctx.arrow(arrowPos.x, arrowPos.y, angle, length); - ctx.fill(); - ctx.stroke(); - } else { - // draw circle - var _angle = undefined, - point = undefined; - var _getCircleData = this._getCircleData(); + this.boundingBox.top = this.top; + this.boundingBox.left = this.left; + this.boundingBox.right = this.left + this.width; + this.boundingBox.bottom = this.top + this.height; - var _getCircleData2 = _slicedToArray(_getCircleData, 3); + this.labelModule.draw(ctx, x, y, selected); + }, + writable: true, + configurable: true + }, + distanceToBorder: { + value: function distanceToBorder(ctx, angle) { + this.resize(ctx); + var a = this.width / 2; + var b = this.height / 2; + var w = Math.sin(angle) * a; + var h = Math.cos(angle) * b; + return a * b / Math.sqrt(w * w + h * h); + }, + writable: true, + configurable: true + } + }); - var x = _getCircleData2[0]; - var y = _getCircleData2[1]; - var radius = _getCircleData2[2]; + return Database; + })(NodeBase); + module.exports = Database; - if (position == "from") { - point = this.findBorderPosition(this.from, ctx, { x: x, y: y, low: 0.25, high: 0.6, direction: -1 }); - _angle = point.t * -2 * Math.PI + 1.5 * Math.PI + 0.1 * Math.PI; - } else if (position == "to") { - point = this.findBorderPosition(this.from, ctx, { x: x, y: y, low: 0.6, high: 1, direction: 1 }); - _angle = point.t * -2 * Math.PI + 1.5 * Math.PI - 1.1 * Math.PI; - } else { - point = this._pointOnCircle(x, y, radius, 0.175); - _angle = 3.9269908169872414; // == 0.175 * -2 * Math.PI + 1.5 * Math.PI + 0.1 * Math.PI; - } +/***/ }, +/* 68 */ +/***/ function(module, exports, __webpack_require__) { - // draw the arrowhead - var _length = (10 + 5 * this.options.width) * scaleFactor; - ctx.arrow(point.x, point.y, _angle, _length); - ctx.fill(); - ctx.stroke(); - } + /** + * Created by Alex on 3/18/2015. + */ + "use strict"; + + var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; + + var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; + + var _get = function get(object, property, receiver) { var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc && desc.writable) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + + var _inherits = function (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 _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; + + var ShapeBase = _interopRequire(__webpack_require__(69)); + + var Diamond = (function (ShapeBase) { + function Diamond(options, body, labelModule) { + _classCallCheck(this, Diamond); + + _get(Object.getPrototypeOf(Diamond.prototype), "constructor", this).call(this, options, body, labelModule); + } + + _inherits(Diamond, ShapeBase); + + _prototypeProperties(Diamond, null, { + resize: { + value: function resize(ctx) { + this._resizeShape(); + }, + writable: true, + configurable: true + }, + draw: { + value: function draw(ctx, x, y, selected, hover) { + this._drawShape(ctx, "diamond", 4, x, y, selected, hover); + }, + writable: true, + configurable: true + }, + distanceToBorder: { + value: function distanceToBorder(ctx, angle) { + return this._distanceToBorder(angle); }, writable: true, configurable: true } }); - return EdgeBase; - })(); + return Diamond; + })(ShapeBase); - module.exports = EdgeBase; + module.exports = Diamond; /***/ }, -/* 66 */ +/* 69 */ /***/ function(module, exports, __webpack_require__) { "use strict"; @@ -27582,261 +26885,80 @@ return /******/ (function(modules) { // webpackBootstrap var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; /** - * Created by Alex on 3/20/2015. + * Created by Alex on 3/19/2015. */ + var NodeBase = _interopRequire(__webpack_require__(63)); - var BezierEdgeBase = _interopRequire(__webpack_require__(64)); - - var BezierEdgeStatic = (function (BezierEdgeBase) { - function BezierEdgeStatic(options, body, labelModule) { - _classCallCheck(this, BezierEdgeStatic); + var ShapeBase = (function (NodeBase) { + function ShapeBase(options, body, labelModule) { + _classCallCheck(this, ShapeBase); - _get(Object.getPrototypeOf(BezierEdgeStatic.prototype), "constructor", this).call(this, options, body, labelModule); + _get(Object.getPrototypeOf(ShapeBase.prototype), "constructor", this).call(this, options, body, labelModule); } - _inherits(BezierEdgeStatic, BezierEdgeBase); + _inherits(ShapeBase, NodeBase); - _prototypeProperties(BezierEdgeStatic, null, { - cleanup: { - value: function cleanup() { - return false; + _prototypeProperties(ShapeBase, null, { + _resizeShape: { + value: function _resizeShape() { + if (this.width === undefined) { + var size = 2 * this.options.size; + this.width = size; + this.height = size; + } }, writable: true, configurable: true }, - _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); - var via = this._getViaCoordinates(); + _drawShape: { + value: function _drawShape(ctx, shape, sizeMultiplier, x, y, selected, hover) { + this._resizeShape(); - // fallback to normal straight edges - if (via.x === undefined) { - ctx.lineTo(this.to.x, this.to.y); - ctx.stroke(); - return undefined; - } else { - ctx.quadraticCurveTo(via.x, via.y, this.to.x, this.to.y); - ctx.stroke(); - return via; - } - }, - writable: true, - configurable: true - }, - _getViaCoordinates: { - value: function _getViaCoordinates() { - var xVia = undefined; - var yVia = undefined; - var factor = this.options.smooth.roundness; - var type = this.options.smooth.type; - var dx = Math.abs(this.from.x - this.to.x); - var dy = Math.abs(this.from.y - this.to.y); - if (type == "discrete" || type == "diagonalCross") { - if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) { - if (this.from.y > this.to.y) { - if (this.from.x < this.to.x) { - xVia = this.from.x + factor * dy; - yVia = this.from.y - factor * dy; - } else if (this.from.x > this.to.x) { - xVia = this.from.x - factor * dy; - yVia = this.from.y - factor * dy; - } - } else if (this.from.y < this.to.y) { - if (this.from.x < this.to.x) { - xVia = this.from.x + factor * dy; - yVia = this.from.y + factor * dy; - } else if (this.from.x > this.to.x) { - xVia = this.from.x - factor * dy; - yVia = this.from.y + factor * dy; - } - } - if (type == "discrete") { - xVia = dx < factor * dy ? this.from.x : xVia; - } - } else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) { - if (this.from.y > this.to.y) { - if (this.from.x < this.to.x) { - xVia = this.from.x + factor * dx; - yVia = this.from.y - factor * dx; - } else if (this.from.x > this.to.x) { - xVia = this.from.x - factor * dx; - yVia = this.from.y - factor * dx; - } - } else if (this.from.y < this.to.y) { - if (this.from.x < this.to.x) { - xVia = this.from.x + factor * dx; - yVia = this.from.y + factor * dx; - } else if (this.from.x > this.to.x) { - xVia = this.from.x - factor * dx; - yVia = this.from.y + factor * dx; - } - } - if (type == "discrete") { - yVia = dy < factor * dx ? this.from.y : yVia; - } - } - } else if (type == "straightCross") { - if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) { - // up - down - xVia = this.from.x; - if (this.from.y < this.to.y) { - yVia = this.to.y - (1 - factor) * dy; - } else { - yVia = this.to.y + (1 - factor) * dy; - } - } else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) { - // left - right - if (this.from.x < this.to.x) { - xVia = this.to.x - (1 - factor) * dx; - } else { - xVia = this.to.x + (1 - factor) * dx; - } - yVia = this.from.y; - } - } else if (type == "horizontal") { - if (this.from.x < this.to.x) { - xVia = this.to.x - (1 - factor) * dx; - } else { - xVia = this.to.x + (1 - factor) * dx; - } - yVia = this.from.y; - } else if (type == "vertical") { - xVia = this.from.x; - if (this.from.y < this.to.y) { - yVia = this.to.y - (1 - factor) * dy; - } else { - yVia = this.to.y + (1 - factor) * dy; - } - } else if (type == "curvedCW") { - dx = this.to.x - this.from.x; - dy = this.from.y - this.to.y; - var radius = Math.sqrt(dx * dx + dy * dy); - var pi = Math.PI; + this.left = x - this.width / 2; + this.top = y - this.height / 2; - var originalAngle = Math.atan2(dy, dx); - var myAngle = (originalAngle + (factor * 0.5 + 0.5) * pi) % (2 * pi); + var borderWidth = this.options.borderWidth; + var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth; - xVia = this.from.x + (factor * 0.5 + 0.5) * radius * Math.sin(myAngle); - yVia = this.from.y + (factor * 0.5 + 0.5) * radius * Math.cos(myAngle); - } else if (type == "curvedCCW") { - dx = this.to.x - this.from.x; - dy = this.from.y - this.to.y; - var radius = Math.sqrt(dx * dx + dy * dy); - var pi = Math.PI; + 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[shape](x, y, this.options.size); + ctx.fill(); + ctx.stroke(); - var originalAngle = Math.atan2(dy, dx); - var myAngle = (originalAngle + (-factor * 0.5 + 0.5) * pi) % (2 * pi); + this.boundingBox.top = y - this.options.size; + this.boundingBox.left = x - this.options.size; + this.boundingBox.right = x + this.options.size; + this.boundingBox.bottom = y + this.options.size; - xVia = this.from.x + (factor * 0.5 + 0.5) * radius * Math.sin(myAngle); - yVia = this.from.y + (factor * 0.5 + 0.5) * radius * Math.cos(myAngle); - } else { - // continuous - if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) { - if (this.from.y > this.to.y) { - if (this.from.x < this.to.x) { - xVia = this.from.x + factor * dy; - yVia = this.from.y - factor * dy; - xVia = this.to.x < xVia ? this.to.x : xVia; - } else if (this.from.x > this.to.x) { - xVia = this.from.x - factor * dy; - yVia = this.from.y - factor * dy; - xVia = this.to.x > xVia ? this.to.x : xVia; - } - } else if (this.from.y < this.to.y) { - if (this.from.x < this.to.x) { - xVia = this.from.x + factor * dy; - yVia = this.from.y + factor * dy; - xVia = this.to.x < xVia ? this.to.x : xVia; - } else if (this.from.x > this.to.x) { - xVia = this.from.x - factor * dy; - yVia = this.from.y + factor * dy; - xVia = this.to.x > xVia ? this.to.x : xVia; - } - } - } else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) { - if (this.from.y > this.to.y) { - if (this.from.x < this.to.x) { - xVia = this.from.x + factor * dx; - yVia = this.from.y - factor * dx; - yVia = this.to.y > yVia ? this.to.y : yVia; - } else if (this.from.x > this.to.x) { - xVia = this.from.x - factor * dx; - yVia = this.from.y - factor * dx; - yVia = this.to.y > yVia ? this.to.y : yVia; - } - } else if (this.from.y < this.to.y) { - if (this.from.x < this.to.x) { - xVia = this.from.x + factor * dx; - yVia = this.from.y + factor * dx; - yVia = this.to.y < yVia ? this.to.y : yVia; - } else if (this.from.x > this.to.x) { - xVia = this.from.x - factor * dx; - yVia = this.from.y + factor * dx; - yVia = this.to.y < yVia ? this.to.y : yVia; - } - } - } + if (this.options.label !== undefined) { + var yLabel = y + 0.5 * this.height + 3; // the + 3 is to offset it a bit below the node. + this.labelModule.draw(ctx, x, yLabel, selected, "hanging"); + this.boundingBox.left = Math.min(this.boundingBox.left, this.labelModule.size.left); + this.boundingBox.right = Math.max(this.boundingBox.right, this.labelModule.size.left + this.labelModule.size.width); + this.boundingBox.bottom = Math.max(this.boundingBox.bottom, this.boundingBox.bottom + this.labelModule.size.height); } - return { x: xVia, y: yVia }; - }, - writable: true, - configurable: true - }, - _findBorderPosition: { - value: function _findBorderPosition(nearNode, ctx) { - var options = arguments[2] === undefined ? {} : arguments[2]; - return this._findBorderPositionBezier(nearNode, ctx, options.via); - }, - writable: true, - configurable: true - }, - _getDistanceToEdge: { - value: function _getDistanceToEdge(x1, y1, x2, y2, x3, y3) { - var via = arguments[6] === undefined ? this._getViaCoordinates() : arguments[6]; - // x3,y3 is the point - return this._getDistanceToBezierEdge(x1, y1, x2, y2, x3, y3, via); - }, - writable: true, - configurable: true - }, - 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 via = arguments[1] === undefined ? this._getViaCoordinates() : arguments[1]; - var t = percentage; - var x = Math.pow(1 - t, 2) * this.from.x + 2 * t * (1 - t) * via.x + Math.pow(t, 2) * this.to.x; - var y = Math.pow(1 - t, 2) * this.from.y + 2 * t * (1 - t) * via.y + Math.pow(t, 2) * this.to.y; - - return { x: x, y: y }; }, writable: true, configurable: true } }); - return BezierEdgeStatic; - })(BezierEdgeBase); + return ShapeBase; + })(NodeBase); - module.exports = BezierEdgeStatic; + module.exports = ShapeBase; /***/ }, -/* 67 */ +/* 70 */ /***/ function(module, exports, __webpack_require__) { + /** + * Created by Alex on 3/18/2015. + */ "use strict"; var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; @@ -27849,1648 +26971,1588 @@ return /******/ (function(modules) { // webpackBootstrap var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; - /** - * Created by Alex on 3/20/2015. - */ - - var EdgeBase = _interopRequire(__webpack_require__(65)); + var ShapeBase = _interopRequire(__webpack_require__(69)); - var StraightEdge = (function (EdgeBase) { - function StraightEdge(options, body, labelModule) { - _classCallCheck(this, StraightEdge); + var Dot = (function (ShapeBase) { + function Dot(options, body, labelModule) { + _classCallCheck(this, Dot); - _get(Object.getPrototypeOf(StraightEdge.prototype), "constructor", this).call(this, options, body, labelModule); + _get(Object.getPrototypeOf(Dot.prototype), "constructor", this).call(this, options, body, labelModule); } - _inherits(StraightEdge, EdgeBase); + _inherits(Dot, ShapeBase); - _prototypeProperties(StraightEdge, null, { - cleanup: { - value: function cleanup() { - return false; + _prototypeProperties(Dot, null, { + resize: { + value: function resize(ctx) { + this._resizeShape(); }, writable: true, configurable: true }, - _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.lineTo(this.to.x, this.to.y); - ctx.stroke(); - return undefined; + draw: { + value: function draw(ctx, x, y, selected, hover) { + this._drawShape(ctx, "circle", 2, x, y, selected, hover); }, writable: true, configurable: true }, - getPoint: { + distanceToBorder: { + value: function distanceToBorder(ctx, angle) { + return this.options.size + this.options.borderWidth; + }, + writable: true, + configurable: true + } + }); - /** - * 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) { - return { - x: (1 - percentage) * this.from.x + percentage * this.to.x, - y: (1 - percentage) * this.from.y + percentage * this.to.y - }; + return Dot; + })(ShapeBase); + + module.exports = Dot; + +/***/ }, +/* 71 */ +/***/ function(module, exports, __webpack_require__) { + + /** + * Created by Alex on 3/18/2015. + */ + "use strict"; + + var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; + + var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; + + var _get = function get(object, property, receiver) { var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc && desc.writable) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + + var _inherits = function (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 _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; + + var NodeBase = _interopRequire(__webpack_require__(63)); + + 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); + + _prototypeProperties(Ellipse, null, { + 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; + } + } }, writable: true, configurable: true }, - _findBorderPosition: { - value: function _findBorderPosition(nearNode, ctx) { - var node1 = this.to; - var node2 = this.from; - if (nearNode.id === this.from.id) { - node1 = this.from; - node2 = this.to; - } + draw: { + value: function draw(ctx, x, y, selected, hover) { + this.resize(ctx, selected); + this.left = x - this.width / 2; + this.top = y - this.height / 2; - 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 borderWidth = this.options.borderWidth; + var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth; - var borderPos = {}; - borderPos.x = (1 - toBorderPoint) * node2.x + toBorderPoint * node1.x; - borderPos.y = (1 - toBorderPoint) * node2.y + toBorderPoint * node1.y; + ctx.strokeStyle = selected ? this.options.color.highlight.border : hover ? this.options.color.hover.border : this.options.color.border; - return borderPos; + 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); + ctx.fill(); + 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; + + + this.labelModule.draw(ctx, x, y, selected); }, writable: true, configurable: true }, - _getDistanceToEdge: { - value: function _getDistanceToEdge(x1, y1, x2, y2, x3, y3) { - // x3,y3 is the point - return this._getDistanceToLine(x1, y1, x2, y2, x3, y3); + distanceToBorder: { + value: function distanceToBorder(ctx, angle) { + this.resize(ctx); + var a = this.width / 2; + var b = this.height / 2; + var w = Math.sin(angle) * a; + var h = Math.cos(angle) * b; + return a * b / Math.sqrt(w * w + h * h); }, writable: true, configurable: true } }); - return StraightEdge; - })(EdgeBase); + return Ellipse; + })(NodeBase); - module.exports = StraightEdge; + module.exports = Ellipse; /***/ }, -/* 68 */ +/* 72 */ /***/ function(module, exports, __webpack_require__) { + /** + * Created by Alex on 3/18/2015. + */ "use strict"; var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; - var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; - - /** - * Created by Alex on 2/23/2015. - */ + var _get = function get(object, property, receiver) { var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc && desc.writable) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; - var BarnesHutSolver = _interopRequire(__webpack_require__(69)); + var _inherits = function (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 Repulsion = _interopRequire(__webpack_require__(70)); + var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; - var HierarchicalRepulsion = _interopRequire(__webpack_require__(71)); + var NodeBase = _interopRequire(__webpack_require__(63)); - var SpringSolver = _interopRequire(__webpack_require__(72)); + var Icon = (function (NodeBase) { + function Icon(options, body, labelModule) { + _classCallCheck(this, Icon); - var HierarchicalSpringSolver = _interopRequire(__webpack_require__(73)); + _get(Object.getPrototypeOf(Icon.prototype), "constructor", this).call(this, options, body, labelModule); + } - var CentralGravitySolver = _interopRequire(__webpack_require__(74)); + _inherits(Icon, NodeBase); - var util = __webpack_require__(1); + _prototypeProperties(Icon, null, { + resize: { + value: function resize(ctx) { + if (this.width === undefined) { + var margin = 5; + var iconSize = { + width: Number(this.options.icon.size), + height: Number(this.options.icon.size) + }; + this.width = iconSize.width + 2 * margin; + this.height = iconSize.height + 2 * margin; + } + }, + writable: true, + configurable: true + }, + draw: { + value: function draw(ctx, x, y, selected, hover) { + this.resize(ctx); + this.options.icon.size = this.options.icon.size || 50; + this.left = x - this.width * 0.5; + this.top = y - this.height * 0.5; + this._icon(ctx, x, y, selected); - var PhysicsEngine = (function () { - function PhysicsEngine(body) { - var _this = this; - _classCallCheck(this, PhysicsEngine); - this.body = body; - this.physicsBody = { physicsNodeIndices: [], physicsEdgeIndices: [], forces: {}, velocities: {} }; + this.boundingBox.top = y - this.options.icon.size * 0.5; + this.boundingBox.left = x - this.options.icon.size * 0.5; + this.boundingBox.right = x + this.options.icon.size * 0.5; + this.boundingBox.bottom = y + this.options.icon.size * 0.5; - this.physicsEnabled = true; - this.simulationInterval = 1000 / 60; - this.requiresTimeout = true; - this.previousStates = {}; - this.freezeCache = {}; - this.renderTimer = undefined; + if (this.options.label !== undefined) { + var iconTextSpacing = 5; + this.labelModule.draw(ctx, x, y + this.height * 0.5 + iconTextSpacing, selected); + this.boundingBox.left = Math.min(this.boundingBox.left, this.labelModule.size.left); + this.boundingBox.right = Math.max(this.boundingBox.right, this.labelModule.size.left + this.labelModule.size.width); + this.boundingBox.bottom = Math.max(this.boundingBox.bottom, this.boundingBox.bottom + this.labelModule.size.height); + } + }, + writable: true, + configurable: true + }, + _icon: { + value: function _icon(ctx, x, y, selected) { + var iconSize = Number(this.options.icon.size); + var relativeIconSize = iconSize * this.body.view.scale; - this.stabilized = false; - this.stabilizationIterations = 0; - this.ready = false; // will be set to true if the stabilize + if (this.options.icon.code && relativeIconSize > this.options.scaling.label.drawThreshold - 1) { + ctx.font = (selected ? "bold " : "") + iconSize + "px " + this.options.icon.face; - // default options - this.options = {}; - this.defaultOptions = { - barnesHut: { - theta: 0.5, // inverted to save time during calculation - gravitationalConstant: -2000, - centralGravity: 0.3, - springLength: 95, - springConstant: 0.04, - damping: 0.09 - }, - repulsion: { - centralGravity: 0.2, - springLength: 200, - springConstant: 0.05, - nodeDistance: 100, - damping: 0.09 + // draw icon + ctx.fillStyle = this.options.icon.color || "black"; + ctx.textAlign = "center"; + ctx.textBaseline = "middle"; + ctx.fillText(this.options.icon.code, x, y); + } }, - hierarchicalRepulsion: { - centralGravity: 0, - springLength: 100, - springConstant: 0.01, - nodeDistance: 120, - damping: 0.09 + writable: true, + configurable: true + }, + distanceToBorder: { + value: function distanceToBorder(ctx, angle) { + this.resize(ctx); + this._distanceToBorder(angle); }, - maxVelocity: 50, - minVelocity: 0.1, // px/s - solver: "BarnesHut", - stabilization: { - enabled: true, - iterations: 1000, // maximum number of iteration to stabilize - updateInterval: 100, - onlyDynamicEdges: false, - zoomExtent: true - }, - timestep: 0.5 - }; - util.extend(this.options, this.defaultOptions); + writable: true, + configurable: true + } + }); - this.body.emitter.on("initPhysics", function () { - _this.initPhysics(); - }); - this.body.emitter.on("resetPhysics", function () { - _this.stopSimulation();_this.ready = false; - }); - this.body.emitter.on("disablePhysics", function () { - _this.physicsEnabled = false;_this.stopSimulation(); - }); - this.body.emitter.on("restorePhysics", function () { - _this.setOptions(_this.options); - if (_this.ready === true) { - _this.stabilized = false; - _this.runSimulation(); - } - }); - this.body.emitter.on("startSimulation", function () { - if (_this.ready === true) { - _this.stabilized = false; - _this.runSimulation(); - } - }); - this.body.emitter.on("stopSimulation", function () { - _this.stopSimulation(); - }); + return Icon; + })(NodeBase); + + module.exports = Icon; + +/***/ }, +/* 73 */ +/***/ function(module, exports, __webpack_require__) { + + /** + * Created by Alex on 3/18/2015. + */ + "use strict"; + + var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; + + var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; + + var _get = function get(object, property, receiver) { var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc && desc.writable) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + + var _inherits = function (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 _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; + + var CircleImageBase = _interopRequire(__webpack_require__(65)); + + var Image = (function (CircleImageBase) { + function Image(options, body, labelModule, imageObj) { + _classCallCheck(this, Image); + + _get(Object.getPrototypeOf(Image.prototype), "constructor", this).call(this, options, body, labelModule); + this.imageObj = imageObj; } - _prototypeProperties(PhysicsEngine, null, { - setOptions: { - value: function setOptions(options) { - if (options === false) { - this.physicsEnabled = false; - this.stopSimulation(); - } else { - this.physicsEnabled = true; - if (options !== undefined) { - util.selectiveNotDeepExtend(["stabilization"], this.options, options); - util.mergeOptions(this.options, options, "stabilization"); + _inherits(Image, CircleImageBase); + + _prototypeProperties(Image, null, { + resize: { + value: function resize() { + if (!this.width || !this.height) { + // undefined or 0 + var width, height; + if (this.value) { + var scale = this.imageObj.height / this.imageObj.width; + if (scale !== undefined) { + width = this.options.size || this.imageObj.width; + height = this.options.size * scale || this.imageObj.height; + } else { + width = 0; + height = 0; + } + } else { + width = this.imageObj.width; + height = this.imageObj.height; } - this.init(); + this.width = width; + this.height = height; } }, writable: true, configurable: true }, - init: { - value: function init() { - var options; - if (this.options.solver == "repulsion") { - options = this.options.repulsion; - this.nodesSolver = new Repulsion(this.body, this.physicsBody, options); - this.edgesSolver = new SpringSolver(this.body, this.physicsBody, options); - } else if (this.options.solver == "hierarchicalRepulsion") { - options = this.options.hierarchicalRepulsion; - this.nodesSolver = new HierarchicalRepulsion(this.body, this.physicsBody, options); - this.edgesSolver = new HierarchicalSpringSolver(this.body, this.physicsBody, options); - } else { - // barnesHut - options = this.options.barnesHut; - this.nodesSolver = new BarnesHutSolver(this.body, this.physicsBody, options); - this.edgesSolver = new SpringSolver(this.body, this.physicsBody, options); - } + draw: { + value: function draw(ctx, x, y, selected, hover) { + this.resize(ctx); + this.left = x - this.width / 2; + this.top = y - this.height / 2; - this.gravitySolver = new CentralGravitySolver(this.body, this.physicsBody, options); - this.modelOptions = options; + this._drawImageAtPosition(ctx); + + this.boundingBox.top = this.top; + this.boundingBox.left = this.left; + this.boundingBox.right = this.left + this.width; + this.boundingBox.bottom = this.top + this.height; + + this._drawImageLabel(ctx, x, y, selected || hover); + this.boundingBox.left = Math.min(this.boundingBox.left, this.labelModule.size.left); + this.boundingBox.right = Math.max(this.boundingBox.right, this.labelModule.size.left + this.labelModule.size.width); + this.boundingBox.bottom = Math.max(this.boundingBox.bottom, this.boundingBox.bottom + this.labelModule.size.height); }, writable: true, configurable: true }, - initPhysics: { - value: function initPhysics() { - if (this.physicsEnabled === true) { - this.stabilized = false; - if (this.options.stabilization.enabled === true) { - this.stabilize(); - } else { - this.ready = true; - this.body.emitter.emit("zoomExtent", { duration: 0 }, true); - this.runSimulation(); - } - } else { - this.ready = true; - this.body.emitter.emit("_redraw"); - } + distanceToBorder: { + value: function distanceToBorder(ctx, angle) { + this.resize(ctx); + var a = this.width / 2; + var b = this.height / 2; + var w = Math.sin(angle) * a; + var h = Math.cos(angle) * b; + return a * b / Math.sqrt(w * w + h * h); }, writable: true, configurable: true - }, - stopSimulation: { - value: function stopSimulation() { - this.stabilized = true; - if (this.viewFunction !== undefined) { - this.body.emitter.off("initRedraw", this.viewFunction); - this.viewFunction = undefined; - this.body.emitter.emit("_stopRendering"); - } + } + }); + + return Image; + })(CircleImageBase); + + module.exports = Image; + +/***/ }, +/* 74 */ +/***/ function(module, exports, __webpack_require__) { + + /** + * Created by Alex on 3/18/2015. + */ + "use strict"; + + var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; + + var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; + + var _get = function get(object, property, receiver) { var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc && desc.writable) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + + var _inherits = function (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 _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; + + var ShapeBase = _interopRequire(__webpack_require__(69)); + + var Square = (function (ShapeBase) { + function Square(options, body, labelModule) { + _classCallCheck(this, Square); + + _get(Object.getPrototypeOf(Square.prototype), "constructor", this).call(this, options, body, labelModule); + } + + _inherits(Square, ShapeBase); + + _prototypeProperties(Square, null, { + resize: { + value: function resize() { + this._resizeShape(); }, writable: true, configurable: true }, - runSimulation: { - value: function runSimulation() { - if (this.physicsEnabled === true) { - if (this.viewFunction === undefined) { - this.viewFunction = this.simulationStep.bind(this); - this.body.emitter.on("initRedraw", this.viewFunction); - this.body.emitter.emit("_startRendering"); - } - } else { - this.body.emitter.emit("_redraw"); - } + draw: { + value: function draw(ctx, x, y, selected, hover) { + this._drawShape(ctx, "square", 2, x, y, selected, hover); }, writable: true, configurable: true }, - simulationStep: { - value: function simulationStep() { - // check if the physics have settled - var startTime = Date.now(); - this.physicsTick(); - var physicsTime = Date.now() - startTime; + distanceToBorder: { + value: function distanceToBorder(ctx, angle) { + this.resize(ctx); + return this._distanceToBorder(angle); + }, + writable: true, + configurable: true + } + }); - // run double speed if it is a little graph - if ((physicsTime < 0.4 * this.simulationInterval || this.runDoubleSpeed == true) && this.stabilized === false) { - this.physicsTick(); + return Square; + })(ShapeBase); - // this makes sure there is no jitter. The decision is taken once to run it at double speed. - this.runDoubleSpeed = true; - } + module.exports = Square; - if (this.stabilized === true) { - if (this.stabilizationIterations > 1) { - // trigger the "stabilized" event. - // The event is triggered on the next tick, to prevent the case that - // it is fired while initializing the Network, in which case you would not - // be able to catch it - var me = this; - var params = { - iterations: this.stabilizationIterations - }; - this.stabilizationIterations = 0; - this.startedStabilization = false; - setTimeout(function () { - me.body.emitter.emit("stabilized", params); - }, 0); - } else { - this.stabilizationIterations = 0; - } - this.stopSimulation(); - } - }, - writable: true, - configurable: true - }, - physicsTick: { +/***/ }, +/* 75 */ +/***/ function(module, exports, __webpack_require__) { - /** - * A single simulation step (or "tick") in the physics simulation - * - * @private - */ - value: function physicsTick() { - if (this.stabilized === false) { - this.calculateForces(); - this.stabilized = this.moveNodes(); + /** + * Created by Alex on 3/18/2015. + */ + "use strict"; - // determine if the network has stabilzied - if (this.stabilized === true) { - this.revert(); - } else { - // this is here to ensure that there is no start event when the network is already stable. - if (this.startedStabilization == false) { - this.body.emitter.emit("startStabilizing"); - this.startedStabilization = true; - } - } + var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; - this.stabilizationIterations++; - } - }, - writable: true, - configurable: true - }, - updatePhysicsIndices: { + var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; - /** - * Smooth curves are created by adding invisible nodes in the center of the edges. These nodes are also - * handled in the calculateForces function. We then use a quadratic curve with the center node as control. - * This function joins the datanodes and invisible (called support) nodes into one object. - * We do this so we do not contaminate this.body.nodes with the support nodes. - * - * @private - */ - value: function updatePhysicsIndices() { - this.physicsBody.forces = {}; - this.physicsBody.physicsNodeIndices = []; - this.physicsBody.physicsEdgeIndices = []; - var nodes = this.body.nodes; - var edges = this.body.edges; + var _get = function get(object, property, receiver) { var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc && desc.writable) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; - // get node indices for physics - for (var nodeId in nodes) { - if (nodes.hasOwnProperty(nodeId)) { - if (nodes[nodeId].options.physics === true) { - this.physicsBody.physicsNodeIndices.push(nodeId); - } - } - } + var _inherits = function (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; }; - // get edge indices for physics - for (var edgeId in edges) { - if (edges.hasOwnProperty(edgeId)) { - if (edges[edgeId].options.physics === true) { - this.physicsBody.physicsEdgeIndices.push(edgeId); - } - } - } + var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; - // get the velocity and the forces vector - for (var i = 0; i < this.physicsBody.physicsNodeIndices.length; i++) { - var nodeId = this.physicsBody.physicsNodeIndices[i]; - this.physicsBody.forces[nodeId] = { x: 0, y: 0 }; + var ShapeBase = _interopRequire(__webpack_require__(69)); - // forces can be reset because they are recalculated. Velocities have to persist. - if (this.physicsBody.velocities[nodeId] === undefined) { - this.physicsBody.velocities[nodeId] = { x: 0, y: 0 }; - } - } + var Star = (function (ShapeBase) { + function Star(options, body, labelModule) { + _classCallCheck(this, Star); - // clean deleted nodes from the velocity vector - for (var nodeId in this.physicsBody.velocities) { - if (nodes[nodeId] === undefined) { - delete this.physicsBody.velocities[nodeId]; - } - } + _get(Object.getPrototypeOf(Star.prototype), "constructor", this).call(this, options, body, labelModule); + } + + _inherits(Star, ShapeBase); + + _prototypeProperties(Star, null, { + resize: { + value: function resize(ctx) { + this._resizeShape(); }, writable: true, configurable: true }, - revert: { - value: function revert() { - var nodeIds = Object.keys(this.previousStates); - var nodes = this.body.nodes; - var velocities = this.physicsBody.velocities; - - for (var i = 0; i < nodeIds.length; i++) { - var nodeId = nodeIds[i]; - if (nodes[nodeId] !== undefined) { - velocities[nodeId].x = this.previousStates[nodeId].vx; - velocities[nodeId].y = this.previousStates[nodeId].vy; - nodes[nodeId].x = this.previousStates[nodeId].x; - nodes[nodeId].y = this.previousStates[nodeId].y; - } else { - delete this.previousStates[nodeId]; - } - } + draw: { + value: function draw(ctx, x, y, selected, hover) { + this._drawShape(ctx, "star", 4, x, y, selected, hover); }, writable: true, configurable: true }, - moveNodes: { - value: function moveNodes() { - var nodesPresent = false; - var nodeIndices = this.physicsBody.physicsNodeIndices; - var maxVelocity = this.options.maxVelocity === 0 ? 1000000000 : this.options.maxVelocity; - var stabilized = true; - var vminCorrected = this.options.minVelocity / Math.max(this.body.view.scale, 0.05); + distanceToBorder: { + value: function distanceToBorder(ctx, angle) { + return this._distanceToBorder(angle); + }, + writable: true, + configurable: true + } + }); - for (var i = 0; i < nodeIndices.length; i++) { - var nodeId = nodeIndices[i]; - var nodeVelocity = this._performStep(nodeId, maxVelocity); - // stabilized is true if stabilized is true and velocity is smaller than vmin --> all nodes must be stabilized - stabilized = nodeVelocity < vminCorrected && stabilized === true; - nodesPresent = true; - } + return Star; + })(ShapeBase); + module.exports = Star; - if (nodesPresent == true) { - if (vminCorrected > 0.5 * this.options.maxVelocity) { - return false; - } else { - return stabilized; - } +/***/ }, +/* 76 */ +/***/ function(module, exports, __webpack_require__) { + + /** + * Created by Alex on 3/18/2015. + */ + "use strict"; + + var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; + + var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; + + var _get = function get(object, property, receiver) { var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc && desc.writable) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + + var _inherits = function (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 _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; + + var NodeBase = _interopRequire(__webpack_require__(63)); + + var Text = (function (NodeBase) { + function Text(options, body, labelModule) { + _classCallCheck(this, Text); + + _get(Object.getPrototypeOf(Text.prototype), "constructor", this).call(this, options, body, labelModule); + } + + _inherits(Text, NodeBase); + + _prototypeProperties(Text, null, { + resize: { + value: function resize(ctx, selected) { + if (this.width === undefined) { + var margin = 5; + var textSize = this.labelModule.getTextSize(ctx, selected); + this.width = textSize.width + 2 * margin; + this.height = textSize.height + 2 * margin; } - return true; }, writable: true, configurable: true }, - _performStep: { - value: function _performStep(nodeId, maxVelocity) { - var node = this.body.nodes[nodeId]; - var timestep = this.options.timestep; - var forces = this.physicsBody.forces; - var velocities = this.physicsBody.velocities; - - // store the state so we can revert - this.previousStates[nodeId] = { x: node.x, y: node.y, vx: velocities[nodeId].x, vy: velocities[nodeId].y }; - - if (node.options.fixed.x === false) { - var dx = this.modelOptions.damping * velocities[nodeId].x; // damping force - var ax = (forces[nodeId].x - dx) / node.options.mass; // acceleration - velocities[nodeId].x += ax * timestep; // velocity - velocities[nodeId].x = Math.abs(velocities[nodeId].x) > maxVelocity ? velocities[nodeId].x > 0 ? maxVelocity : -maxVelocity : velocities[nodeId].x; - node.x += velocities[nodeId].x * timestep; // position - } else { - forces[nodeId].x = 0; - velocities[nodeId].x = 0; - } + draw: { + value: function draw(ctx, x, y, selected, hover) { + this.resize(ctx, selected || hover); + this.left = x - this.width / 2; + this.top = y - this.height / 2; - if (node.options.fixed.y === false) { - var dy = this.modelOptions.damping * velocities[nodeId].y; // damping force - var ay = (forces[nodeId].y - dy) / node.options.mass; // acceleration - velocities[nodeId].y += ay * timestep; // velocity - velocities[nodeId].y = Math.abs(velocities[nodeId].y) > maxVelocity ? velocities[nodeId].y > 0 ? maxVelocity : -maxVelocity : velocities[nodeId].y; - node.y += velocities[nodeId].y * timestep; // position - } else { - forces[nodeId].y = 0; - velocities[nodeId].y = 0; - } + this.labelModule.draw(ctx, x, y, selected || hover); - var totalVelocity = Math.sqrt(Math.pow(velocities[nodeId].x, 2) + Math.pow(velocities[nodeId].y, 2)); - return totalVelocity; + this.boundingBox.top = this.top; + this.boundingBox.left = this.left; + this.boundingBox.right = this.left + this.width; + this.boundingBox.bottom = this.top + this.height; }, writable: true, configurable: true }, - calculateForces: { - value: function calculateForces() { - this.gravitySolver.solve(); - this.nodesSolver.solve(); - this.edgesSolver.solve(); + distanceToBorder: { + value: function distanceToBorder(ctx, angle) { + this.resize(ctx); + return this._distanceToBorder(angle); }, writable: true, configurable: true - }, - _freezeNodes: { + } + }); + return Text; + })(NodeBase); + module.exports = Text; - /** - * When initializing and stabilizing, we can freeze nodes with a predefined position. This greatly speeds up stabilization - * because only the supportnodes for the smoothCurves have to settle. - * - * @private - */ - value: function _freezeNodes() { - var nodes = this.body.nodes; - for (var id in nodes) { - if (nodes.hasOwnProperty(id)) { - if (nodes[id].x && nodes[id].y) { - this.freezeCache[id] = { x: nodes[id].options.fixed.x, y: nodes[id].options.fixed.y }; - nodes[id].options.fixed.x = true; - nodes[id].options.fixed.y = true; - } - } - } - }, - writable: true, - configurable: true - }, - _restoreFrozenNodes: { +/***/ }, +/* 77 */ +/***/ function(module, exports, __webpack_require__) { - /** - * Unfreezes the nodes that have been frozen by _freezeDefinedNodes. - * - * @private - */ - value: function _restoreFrozenNodes() { - var nodes = this.body.nodes; - for (var id in nodes) { - if (nodes.hasOwnProperty(id)) { - if (this.freezeCache[id] !== undefined) { - nodes[id].options.fixed.x = this.freezeCache[id].x; - nodes[id].options.fixed.y = this.freezeCache[id].y; - } - } - } - this.freezeCache = {}; - }, - writable: true, - configurable: true - }, - stabilize: { + /** + * Created by Alex on 3/18/2015. + */ + "use strict"; - /** - * Find a stable position for all nodes - * @private - */ - value: function stabilize() { - if (this.options.stabilization.onlyDynamicEdges == true) { - this._freezeNodes(); - } - this.stabilizationSteps = 0; + var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; - setTimeout(this._stabilizationBatch.bind(this), 0); + var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; + + var _get = function get(object, property, receiver) { var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc && desc.writable) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + + var _inherits = function (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 _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; + + var ShapeBase = _interopRequire(__webpack_require__(69)); + + var Triangle = (function (ShapeBase) { + function Triangle(options, body, labelModule) { + _classCallCheck(this, Triangle); + + _get(Object.getPrototypeOf(Triangle.prototype), "constructor", this).call(this, options, body, labelModule); + } + + _inherits(Triangle, ShapeBase); + + _prototypeProperties(Triangle, null, { + resize: { + value: function resize(ctx) { + this._resizeShape(); }, writable: true, configurable: true }, - _stabilizationBatch: { - value: function _stabilizationBatch() { - var count = 0; - while (this.stabilized == false && count < this.options.stabilization.updateInterval && this.stabilizationSteps < this.options.stabilization.iterations) { - this.physicsTick(); - this.stabilizationSteps++; - count++; - } - - if (this.stabilized == false && this.stabilizationSteps < this.options.stabilization.iterations) { - this.body.emitter.emit("stabilizationProgress", { steps: this.stabilizationSteps, total: this.options.stabilization.iterations }); - setTimeout(this._stabilizationBatch.bind(this), 0); - } else { - this._finalizeStabilization(); - } + draw: { + value: function draw(ctx, x, y, selected, hover) { + this._drawShape(ctx, "triangle", 3, x, y, selected, hover); }, writable: true, configurable: true }, - _finalizeStabilization: { - value: function _finalizeStabilization() { - if (this.options.stabilization.zoomExtent == true) { - this.body.emitter.emit("zoomExtent", { duration: 0 }); - } - - if (this.options.stabilization.onlyDynamicEdges == true) { - this._restoreFrozenNodes(); - } - - this.body.emitter.emit("stabilizationIterationsDone"); - this.body.emitter.emit("_requestRedraw"); - this.ready = true; + distanceToBorder: { + value: function distanceToBorder(ctx, angle) { + return this._distanceToBorder(angle); }, writable: true, configurable: true } }); - return PhysicsEngine; - })(); + return Triangle; + })(ShapeBase); - module.exports = PhysicsEngine; + module.exports = Triangle; /***/ }, -/* 69 */ +/* 78 */ /***/ function(module, exports, __webpack_require__) { + /** + * Created by Alex on 3/18/2015. + */ "use strict"; + var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; + var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; + var _get = function get(object, property, receiver) { var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc && desc.writable) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + + var _inherits = function (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 _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; - /** - * Created by Alex on 2/23/2015. - */ + var ShapeBase = _interopRequire(__webpack_require__(69)); - var BarnesHutSolver = (function () { - function BarnesHutSolver(body, physicsBody, options) { - _classCallCheck(this, BarnesHutSolver); + var TriangleDown = (function (ShapeBase) { + function TriangleDown(options, body, labelModule) { + _classCallCheck(this, TriangleDown); - this.body = body; - this.physicsBody = physicsBody; - this.barnesHutTree; - this.setOptions(options); + _get(Object.getPrototypeOf(TriangleDown.prototype), "constructor", this).call(this, options, body, labelModule); } - _prototypeProperties(BarnesHutSolver, null, { - setOptions: { - value: function setOptions(options) { - this.options = options; - this.thetaInversed = 1 / this.options.theta; + _inherits(TriangleDown, ShapeBase); + + _prototypeProperties(TriangleDown, null, { + resize: { + value: function resize(ctx) { + this._resizeShape(); }, writable: true, configurable: true }, - solve: { + draw: { + value: function draw(ctx, x, y, selected, hover) { + this._drawShape(ctx, "triangleDown", 3, x, y, selected, hover); + }, + writable: true, + configurable: true + }, + distanceToBorder: { + value: function distanceToBorder(ctx, angle) { + return this._distanceToBorder(angle); + }, + writable: true, + configurable: true + } + }); + return TriangleDown; + })(ShapeBase); - /** - * 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) { - var node; - var nodes = this.body.nodes; - var nodeIndices = this.physicsBody.physicsNodeIndices; - var nodeCount = nodeIndices.length; + module.exports = TriangleDown; - // create the tree - var barnesHutTree = this._formBarnesHutTree(nodes, nodeIndices); +/***/ }, +/* 79 */ +/***/ function(module, exports, __webpack_require__) { - // for debugging - this.barnesHutTree = barnesHutTree; + "use strict"; - // 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); - } - } - } - }, - writable: true, - configurable: true - }, - _getForceContribution: { + var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; + var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; - /** - * 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, dy, distance; + var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; - // 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); + /** + * Created by Alex on 3/4/2015. + */ - // 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) { - // duplicate code to reduce function calls to speed up program - if (distance === 0) { - distance = 0.1 * Math.random(); - dx = distance; - } - var gravityForce = this.options.gravitationalConstant * parentBranch.mass * node.options.mass / (distance * distance * distance); - var fx = dx * gravityForce; - var fy = dy * gravityForce; - this.physicsBody.forces[node.id].x += fx; - this.physicsBody.forces[node.id].y += fy; - } 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); + var util = __webpack_require__(1); + var DataSet = __webpack_require__(7); + var DataView = __webpack_require__(9); + + var Edge = _interopRequire(__webpack_require__(80)); + + var EdgesHandler = (function () { + function EdgesHandler(body, images, groups) { + var _this = this; + _classCallCheck(this, EdgesHandler); + + this.body = body; + this.images = images; + this.groups = groups; + + // create the edge API in the body container + this.body.functions.createEdge = this.create.bind(this); + + this.edgesListeners = { + add: function (event, params) { + _this.add(params.items); + }, + update: function (event, params) { + _this.update(params.items); + }, + remove: function (event, params) { + _this.remove(params.items); + } + }; + + this.options = {}; + this.defaultOptions = { + arrows: { + to: { enabled: false, scaleFactor: 1 }, // boolean / {arrowScaleFactor:1} / {enabled: false, arrowScaleFactor:1} + middle: { enabled: false, scaleFactor: 1 }, + from: { enabled: false, scaleFactor: 1 } + }, + color: { + color: "#848484", + highlight: "#848484", + hover: "#848484", + inherit: { + enabled: true, + source: "from", // from / true + useGradients: false // release in 4.0 + }, + opacity: 1 + }, + dashes: { + enabled: false, + preset: "dotted", + length: 10, + gap: 5, + altLength: undefined + }, + font: { + color: "#343434", + size: 14, // px + face: "arial", + background: "none", + stroke: 1, // px + strokeColor: "#ffffff", + align: "horizontal" + }, + hidden: false, + hoverWidth: 1.5, + label: undefined, + length: undefined, + physics: true, + scaling: { + min: 1, + max: 15, + label: { + enabled: true, + min: 14, + max: 30, + maxVisible: 30, + drawThreshold: 3 + }, + customScalingFunction: function (min, max, total, value) { + if (max == min) { + return 0.5; + } else { + var scale = 1 / (max - min); + return Math.max(0, (value - min) * scale); + } + } + }, + selfReferenceSize: 20, + smooth: { + enabled: true, + dynamic: true, + type: "continuous", + roundness: 0.5 + }, + title: undefined, + width: 1, + widthSelectionMultiplier: 2, + value: 1 + }; + + util.extend(this.options, this.defaultOptions); + + + // this allows external modules to force all dynamic curves to turn static. + this.body.emitter.on("_forceDisableDynamicCurves", function (type) { + var emitChange = false; + for (var edgeId in _this.body.edges) { + if (_this.body.edges.hasOwnProperty(edgeId)) { + var edgeOptions = _this.body.edges[edgeId].options.smooth; + if (edgeOptions.enabled === true && edgeOptions.dynamic === true) { + if (type === undefined) { + edge.setOptions({ smooth: false }); } 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 - // duplicate code to reduce function calls to speed up program - if (distance === 0) { - distance = 0.5 * Math.random(); - dx = distance; - } - var gravityForce = this.options.gravitationalConstant * parentBranch.mass * node.options.mass / (distance * distance * distance); - var fx = dx * gravityForce; - var fy = dy * gravityForce; + edge.setOptions({ smooth: { dynamic: false, type: type } }); + } + emitChange = true; + } + } + } + if (emitChange === true) { + _this.body.emitter.emit("_dataChanged"); + } + }); - this.physicsBody.forces[node.id].x += fx; - this.physicsBody.forces[node.id].y += fy; + // this is called when options of EXISTING nodes or edges have changed. + this.body.emitter.on("_dataUpdated", function () { + _this.reconnectEdges(); + _this.markAllEdgesAsDirty(); + }); + } + + _prototypeProperties(EdgesHandler, null, { + setOptions: { + value: function setOptions(options) { + if (options !== undefined) { + util.mergeOptions(this.options, options, "smooth"); + util.mergeOptions(this.options, options, "dashes"); + + // hanlde multiple input cases for arrows + if (options.arrows !== undefined) { + if (typeof options.arrows === "string") { + var arrows = options.arrows.toLowerCase(); + if (arrows.indexOf("to") != -1) { + this.options.arrows.to.enabled = true; + } + if (arrows.indexOf("middle") != -1) { + this.options.arrows.middle.enabled = true; + } + if (arrows.indexOf("from") != -1) { + this.options.arrows.from.enabled = true; + } + } else if (typeof options.arrows === "object") { + util.mergeOptions(this.options.arrows, options.arrows, "to"); + util.mergeOptions(this.options.arrows, options.arrows, "middle"); + util.mergeOptions(this.options.arrows, options.arrows, "from"); + } else { + throw new Error("The arrow options can only be an object or a string. Refer to the documentation. You used:" + JSON.stringify(options.arrows)); + } + } + + // hanlde multiple input cases for color + if (options.color !== undefined) { + if (util.isString(options.color)) { + util.assignAllKeys(this.options.color, options.color); + this.options.color.inherit.enabled = false; + } else { + util.extend(this.options.color, options.color); + if (options.color.inherit === undefined) { + this.options.color.inherit.enabled = false; + } + } + util.mergeOptions(this.options.color, options.color, "inherit"); + } + + // update smooth settings + var dataChanged = false; + if (options.smooth !== undefined) { + for (var nodeId in this.body.edges) { + if (this.body.edges.hasOwnProperty(nodeId)) { + dataChanged = this.body.edges[nodeId].updateEdgeType() || dataChanged; + } + } + } + + // update fonts + if (options.font) { + for (var nodeId in this.body.edges) { + if (this.body.edges.hasOwnProperty(nodeId)) { + this.body.edges[nodeId].updateLabelModule(); } } } + + // update the state of the variables if needed + if (options.hidden !== undefined || options.physics !== undefined || dataChanged === true) { + this.body.emitter.emit("_dataChanged"); + } } }, writable: true, configurable: true }, - _formBarnesHutTree: { + setData: { /** - * This function constructs the barnesHut tree recursively. It creates the root, splits it and starts placing the nodes. - * - * @param nodes - * @param nodeIndices + * Load edges by reading the data table + * @param {Array | DataSet | DataView} edges The data containing the edges. + * @private * @private */ - value: function _formBarnesHutTree(nodes, nodeIndices) { - var node; - var nodeCount = nodeIndices.length; + value: function setData(edges) { + var _this = this; + var doNotEmit = arguments[1] === undefined ? false : arguments[1]; + var oldEdgesData = this.body.data.edges; - var minX = Number.MAX_VALUE, - minY = Number.MAX_VALUE, - maxX = -Number.MAX_VALUE, - maxY = -Number.MAX_VALUE; + if (edges instanceof DataSet || edges instanceof DataView) { + this.body.data.edges = edges; + } else if (Array.isArray(edges)) { + this.body.data.edges = new DataSet(); + this.body.data.edges.add(edges); + } else if (!edges) { + this.body.data.edges = new DataSet(); + } else { + throw new TypeError("Array or DataSet expected"); + } - // get the range of the nodes - for (var i = 0; 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; - } - } + // TODO: is this null or undefined or false? + if (oldEdgesData) { + // unsubscribe from old dataset + util.forEach(this.edgesListeners, function (callback, event) { + oldEdgesData.off(event, callback); + }); } - // 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 + // remove drawn edges + this.body.edges = {}; - 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); + // TODO: is this null or undefined or false? + if (this.body.data.edges) { + // subscribe to new dataset + util.forEach(this.edgesListeners, function (callback, event) { + _this.body.data.edges.on(event, callback); + }); - // place the nodes one by one recursively - for (i = 0; i < nodeCount; i++) { - node = nodes[nodeIndices[i]]; - if (node.options.mass > 0) { - this._placeInTree(barnesHutTree.root, node); - } + // draw all new nodes + var ids = this.body.data.edges.getIds(); + this.add(ids, true); } - // make global - return barnesHutTree; + if (doNotEmit === false) { + this.body.emitter.emit("_dataChanged"); + } }, writable: true, configurable: true }, - _updateBranchMass: { + add: { /** - * this updates the mass of a branch. this is increased by adding a node. - * - * @param parentBranch - * @param node + * Add edges + * @param {Number[] | String[]} ids * @private */ - value: function _updateBranchMass(parentBranch, node) { - var totalMass = parentBranch.mass + node.options.mass; - var totalMassInv = 1 / totalMass; + value: function add(ids) { + var doNotEmit = arguments[1] === undefined ? false : arguments[1]; + var edges = this.body.edges; + var edgesData = this.body.data.edges; - parentBranch.centerOfMass.x = parentBranch.centerOfMass.x * parentBranch.mass + node.x * node.options.mass; - parentBranch.centerOfMass.x *= totalMassInv; + for (var i = 0; i < ids.length; i++) { + var id = ids[i]; - parentBranch.centerOfMass.y = parentBranch.centerOfMass.y * parentBranch.mass + node.y * node.options.mass; - parentBranch.centerOfMass.y *= totalMassInv; + var oldEdge = edges[id]; + if (oldEdge) { + oldEdge.disconnect(); + } - parentBranch.mass = totalMass; - var biggestSize = Math.max(Math.max(node.height, node.radius), node.width); - parentBranch.maxWidth = parentBranch.maxWidth < biggestSize ? biggestSize : parentBranch.maxWidth; + var data = edgesData.get(id, { showInternalIds: true }); + edges[id] = this.create(data); + } + + if (doNotEmit === false) { + this.body.emitter.emit("_dataChanged"); + } }, writable: true, configurable: true }, - _placeInTree: { + update: { + /** - * determine in which branch the node will be placed. - * - * @param parentBranch - * @param node - * @param skipMassUpdate + * Update existing edges, or create them when not yet existing + * @param {Number[] | String[]} ids * @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"); + value: function update(ids) { + var edges = this.body.edges; + var edgesData = this.body.data.edges; + var dataChanged = false; + for (var i = 0; i < ids.length; i++) { + var id = ids[i]; + var data = edgesData.get(id); + var edge = edges[id]; + if (edge === null) { + // update edge + edge.disconnect(); + dataChanged = edge.setOptions(data) || dataChanged; // if a support node is added, data can be changed. + edge.connect(); } else { - // in SW - this._placeInRegion(parentBranch, node, "SW"); + // create edge + this.body.edges[id] = this.create(data); + dataChanged = true; } + } + + if (dataChanged === true) { + this.body.emitter.emit("_dataChanged"); } 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"); - } + this.body.emitter.emit("_dataUpdated"); } }, writable: true, configurable: true }, - _placeInRegion: { + remove: { + /** - * actually place the node in a region (or branch) - * - * @param parentBranch - * @param node - * @param region + * Remove existing edges. Non existing ids will be ignored + * @param {Number[] | String[]} ids * @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(); - } else { - this._splitBranch(parentBranch.children[region]); - this._placeInTree(parentBranch.children[region], node); + value: function remove(ids) { + var edges = this.body.edges; + for (var i = 0; i < ids.length; i++) { + var id = ids[i]; + var edge = edges[id]; + if (edge !== undefined) { + if (edge.via != null) { + delete this.body.supportNodes[edge.via.id]; } - break; - case 4: - // place in branch - this._placeInTree(parentBranch.children[region], node); - break; + edge.disconnect(); + delete edges[id]; + } } + + this.body.emitter.emit("_dataChanged"); }, writable: true, configurable: true }, - _splitBranch: { + create: { + value: function create(properties) { + return new Edge(properties, this.body, this.options); + }, + writable: true, + configurable: true + }, + markAllEdgesAsDirty: { + value: function markAllEdgesAsDirty() { + for (var edgeId in this.body.edges) { + this.body.edges[edgeId].colorDirty = true; + } + }, + writable: true, + configurable: true + }, + reconnectEdges: { + /** - * 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 + * Reconnect all edges * @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; + value: function reconnectEdges() { + var id; + var nodes = this.body.nodes; + var edges = this.body.edges; + + for (id in nodes) { + if (nodes.hasOwnProperty(id)) { + nodes[id].edges = []; + } } - 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); + for (id in edges) { + if (edges.hasOwnProperty(id)) { + var edge = edges[id]; + edge.from = null; + edge.to = null; + edge.connect(); + } } }, writable: true, configurable: true - }, - _insertRegion: { + } + }); + return EdgesHandler; + })(); - /** - * 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, maxX, minY, maxY; - 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 - }; - }, - writable: true, - configurable: true - }, - _debug: { - + module.exports = EdgesHandler; +/***/ }, +/* 80 */ +/***/ function(module, exports, __webpack_require__) { + "use strict"; - //--------------------------- DEBUGGING BELOW ---------------------------// + var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; + var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; - /** - * 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; + var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; - this._drawBranch(this.barnesHutTree.root, ctx, color); - } - }, - writable: true, - configurable: true - }, - _drawBranch: { + var util = __webpack_require__(1); - /** - * 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"; - } + var Label = _interopRequire(__webpack_require__(61)); - 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(); + var BezierEdgeDynamic = _interopRequire(__webpack_require__(81)); - ctx.beginPath(); - ctx.moveTo(branch.range.maxX, branch.range.minY); - ctx.lineTo(branch.range.maxX, branch.range.maxY); - ctx.stroke(); + var BezierEdgeStatic = _interopRequire(__webpack_require__(84)); - ctx.beginPath(); - ctx.moveTo(branch.range.maxX, branch.range.maxY); - ctx.lineTo(branch.range.minX, branch.range.maxY); - ctx.stroke(); + var StraightEdge = _interopRequire(__webpack_require__(85)); - ctx.beginPath(); - ctx.moveTo(branch.range.minX, branch.range.maxY); - ctx.lineTo(branch.range.minX, branch.range.minY); - ctx.stroke(); + /** + * @class Edge + * + * A edge connects two nodes + * @param {Object} properties Object with options. Must contain + * At least options from and to. + * Available options: from (number), + * to (number), label (string, color (string), + * width (number), style (string), + * length (number), title (string) + * @param {Network} network A Network object, used to find and edge to + * nodes. + * @param {Object} constants An object with default values for + * example for the color + */ + var Edge = (function () { + function Edge(options, body, globalOptions) { + _classCallCheck(this, Edge); - /* - if (branch.mass > 0) { - ctx.circle(branch.centerOfMass.x, branch.centerOfMass.y, 3*branch.mass); - ctx.stroke(); - } - */ - }, - writable: true, - configurable: true + if (body === undefined) { + throw "No body provided"; } - }); - - return BarnesHutSolver; - })(); - - module.exports = BarnesHutSolver; - -/***/ }, -/* 70 */ -/***/ function(module, exports, __webpack_require__) { + this.options = util.bridgeObject(globalOptions); + this.body = body; - "use strict"; + // initialize variables + this.id = undefined; + this.fromId = undefined; + this.toId = undefined; + this.value = undefined; + this.selected = false; + this.hover = false; + this.labelDirty = true; + this.colorDirty = true; - var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; + this.from = undefined; // a node + this.to = undefined; // a node - var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; + this.edgeType = undefined; - /** - * Created by Alex on 2/23/2015. - */ + this.connected = false; - var RepulsionSolver = (function () { - function RepulsionSolver(body, physicsBody, options) { - _classCallCheck(this, RepulsionSolver); + this.labelModule = new Label(this.body, this.options); - this.body = body; - this.physicsBody = physicsBody; this.setOptions(options); + + this.controlNodesEnabled = false; + this.controlNodes = { from: undefined, to: undefined, positions: {} }; + this.connectedNode = undefined; } - _prototypeProperties(RepulsionSolver, null, { + _prototypeProperties(Edge, null, { setOptions: { - value: function setOptions(options) { - this.options = options; - }, - writable: true, - configurable: true - }, - 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; - var nodes = this.body.nodes; - var nodeIndices = this.physicsBody.physicsNodeIndices; - var forces = this.physicsBody.forces; - // repulsing forces between nodes - var nodeDistance = this.options.nodeDistance; + /** + * Set or overwrite options for the edge + * @param {Object} options an object with options + * @param doNotEmit + */ + value: function setOptions(options) { + if (!options) { + return; + } + this.colorDirty = true; - // approximation constants - var a = -2 / 3 / nodeDistance; - var b = 4 / 3; + var fields = ["id", "font", "from", "hidden", "hoverWidth", "label", "length", "line", "opacity", "physics", "scaling", "selfReferenceSize", "to", "title", "value", "width", "widthMin", "widthMax", "widthSelectionMultiplier"]; + util.selectiveDeepExtend(fields, this.options, 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 (var i = 0; i < nodeIndices.length - 1; i++) { - node1 = nodes[nodeIndices[i]]; - for (var j = i + 1; j < nodeIndices.length; j++) { - node2 = nodes[nodeIndices[j]]; + util.mergeOptions(this.options, options, "smooth"); + util.mergeOptions(this.options, options, "dashes"); - dx = node2.x - node1.x; - dy = node2.y - node1.y; - distance = Math.sqrt(dx * dx + dy * dy); + if (options.id !== undefined) { + this.id = options.id; + } + if (options.from !== undefined) { + this.fromId = options.from; + } + if (options.to !== undefined) { + this.toId = options.to; + } + if (options.title !== undefined) { + this.title = options.title; + } + if (options.value !== undefined) { + this.value = options.value; + } - // same condition as BarnesHutSolver, making sure nodes are never 100% overlapping. - if (distance == 0) { - distance = 0.1 * Math.random(); - dx = distance; + // hanlde multiple input cases for arrows + if (options.arrows !== undefined) { + if (typeof options.arrows === "string") { + var arrows = options.arrows.toLowerCase(); + if (arrows.indexOf("to") != -1) { + this.options.arrows.to.enabled = true; } - - if (distance < 2 * nodeDistance) { - if (distance < 0.5 * nodeDistance) { - repulsingForce = 1; - } else { - repulsingForce = a * distance + b; // linear approx of 1 / (1 + Math.exp((distance / nodeDistance - 1) * steepness)) - } - 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; + if (arrows.indexOf("middle") != -1) { + this.options.arrows.middle.enabled = true; + } + if (arrows.indexOf("from") != -1) { + this.options.arrows.from.enabled = true; } + } else if (typeof options.arrows === "object") { + util.mergeOptions(this.options.arrows, options.arrows, "to"); + util.mergeOptions(this.options.arrows, options.arrows, "middle"); + util.mergeOptions(this.options.arrows, options.arrows, "from"); + } else { + throw new Error("The arrow options can only be an object or a string. Refer to the documentation. You used:" + JSON.stringify(options.arrows)); } } - }, - writable: true, - configurable: true - } - }); - - return RepulsionSolver; - })(); - - module.exports = RepulsionSolver; - -/***/ }, -/* 71 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - - var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; - - var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; - /** - * Created by Alex on 2/23/2015. - */ + // hanlde multiple input cases for color + if (options.color !== undefined) { + if (util.isString(options.color)) { + util.assignAllKeys(this.options.color, options.color); + this.options.color.inherit.enabled = false; + } else { + util.extend(this.options.color, options.color); + if (options.color.inherit === undefined) { + this.options.color.inherit.enabled = false; + } + } + util.mergeOptions(this.options.color, options.color, "inherit"); + } - var HierarchicalRepulsionSolver = (function () { - function HierarchicalRepulsionSolver(body, physicsBody, options) { - _classCallCheck(this, HierarchicalRepulsionSolver); + // A node is connected when it has a from and to node that both exist in the network.body.nodes. + this.connect(); - this.body = body; - this.physicsBody = physicsBody; - this.setOptions(options); - } + // update label Module + this.updateLabelModule(); - _prototypeProperties(HierarchicalRepulsionSolver, null, { - setOptions: { - value: function setOptions(options) { - this.options = options; + var dataChanged = this.updateEdgeType(); + return dataChanged; }, writable: true, configurable: true }, - 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); + updateLabelModule: { + value: function updateLabelModule() { + this.labelModule.setOptions(this.options); + }, + writable: true, + configurable: true + }, + updateEdgeType: { + value: function updateEdgeType() { + var dataChanged = false; + var changeInType = true; + if (this.edgeType !== undefined) { + if (this.edgeType instanceof BezierEdgeDynamic && this.options.smooth.enabled == true && this.options.smooth.dynamic == true) { + changeInType = false; + } + if (this.edgeType instanceof BezierEdgeStatic && this.options.smooth.enabled == true && this.options.smooth.dynamic == false) { + changeInType = false; + } + if (this.edgeType instanceof StraightEdge && this.options.smooth.enabled == false) { + changeInType = false; + } - 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; + if (changeInType == true) { + dataChanged = this.edgeType.cleanup(); + } + } - forces[node1.id].x -= fx; - forces[node1.id].y -= fy; - forces[node2.id].x += fx; - forces[node2.id].y += fy; + if (changeInType === true) { + if (this.options.smooth.enabled === true) { + if (this.options.smooth.dynamic === true) { + dataChanged = true; + this.edgeType = new BezierEdgeDynamic(this.options, this.body, this.labelModule); + } else { + this.edgeType = new BezierEdgeStatic(this.options, this.body, this.labelModule); } + } else { + this.edgeType = new StraightEdge(this.options, this.body, this.labelModule); } + } else { + // if nothing changes, we just set the options. + this.edgeType.setOptions(this.options); } + + return dataChanged; }, writable: true, configurable: true - } - }); - - return HierarchicalRepulsionSolver; - })(); - - module.exports = HierarchicalRepulsionSolver; - -/***/ }, -/* 72 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - - var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; - - var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; - - /** - * Created by Alex on 2/23/2015. - */ - - var SpringSolver = (function () { - function SpringSolver(body, physicsBody, options) { - _classCallCheck(this, SpringSolver); + }, + togglePhysics: { - this.body = body; - this.physicsBody = physicsBody; - this.setOptions(options); - } - _prototypeProperties(SpringSolver, null, { - setOptions: { - value: function setOptions(options) { - this.options = options; + /** + * Enable or disable the physics. + * @param status + */ + value: function togglePhysics(status) { + if (this.options.smooth.enabled == true && this.options.smooth.dynamic == true) { + if (this.via === undefined) { + this.via.pptions.physics = status; + } + } + this.options.physics = status; }, writable: true, configurable: true }, - solve: { + connect: { /** - * This function calculates the springforces on the nodes, accounting for the support nodes. - * - * @private + * Connect an edge to its nodes */ - value: function solve() { - var edgeLength, edge; - var edgeIndices = this.physicsBody.physicsEdgeIndices; - var edges = this.body.edges; - - // forces caused by the edges, modelled as springs - for (var i = 0; i < edgeIndices.length; i++) { - edge = edges[edgeIndices[i]]; - if (edge.connected === true) { - // 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; - var node1 = edge.to; - var node2 = edge.edgeType.via; - var node3 = edge.from; + value: function connect() { + this.disconnect(); + this.from = this.body.nodes[this.fromId] || undefined; + this.to = this.body.nodes[this.toId] || undefined; + this.connected = this.from !== undefined && this.to !== undefined; - 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); - } - } + if (this.connected === true) { + this.from.attachEdge(this); + this.to.attachEdge(this); + } else { + if (this.from) { + this.from.detachEdge(this); + } + if (this.to) { + this.to.detachEdge(this); } } }, writable: true, configurable: true }, - _calculateSpringForce: { + disconnect: { /** - * This is the code actually performing the calculation for the function above. - * - * @param node1 - * @param node2 - * @param edgeLength - * @private + * Disconnect an edge from its nodes */ - value: function _calculateSpringForce(node1, node2, edgeLength) { - var dx, dy, fx, fy, springForce, distance; - - dx = node1.x - node2.x; - dy = node1.y - node2.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; - - // 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; + value: function disconnect() { + if (this.from) { + this.from.detachEdge(this); + this.from = undefined; } - - if (this.physicsBody.forces[node2.id] !== undefined) { - this.physicsBody.forces[node2.id].x -= fx; - this.physicsBody.forces[node2.id].y -= fy; + if (this.to) { + this.to.detachEdge(this); + this.to = undefined; } + + this.connected = false; }, writable: true, configurable: true - } - }); - - return SpringSolver; - })(); - - module.exports = SpringSolver; - -/***/ }, -/* 73 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - - var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; - - var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; - - /** - * Created by Alex on 2/25/2015. - */ - - var HierarchicalSpringSolver = (function () { - function HierarchicalSpringSolver(body, physicsBody, options) { - _classCallCheck(this, HierarchicalSpringSolver); + }, + getTitle: { - this.body = body; - this.physicsBody = physicsBody; - this.setOptions(options); - } - _prototypeProperties(HierarchicalSpringSolver, null, { - setOptions: { - value: function setOptions(options) { - this.options = options; + /** + * get the title of this edge. + * @return {string} title The title of the edge, or undefined when no title + * has been set. + */ + value: function getTitle() { + return typeof this.title === "function" ? this.title() : this.title; }, writable: true, configurable: true }, - solve: { + isSelected: { + /** - * This function calculates the springforces on the nodes, accounting for the support nodes. - * - * @private + * check if this node is selecte + * @return {boolean} selected True if node is selected, else false */ - 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; - } - + value: function isSelected() { + return this.selected; + }, + writable: true, + configurable: true + }, + getValue: { - // 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; + /** + * Retrieve the value of the edge. Can be undefined + * @return {Number} value + */ + value: function getValue() { + return this.value; + }, + writable: true, + configurable: true + }, + setValueRange: { - fx = dx * springForce; - fy = dy * springForce; - if (edge.to.level != edge.from.level) { - forces[edge.toId].springFx -= fx; - forces[edge.toId].springFy -= fy; - forces[edge.fromId].springFx += fx; - forces[edge.fromId].springFy += fy; - } else { - forces[edge.toId].x -= factor * fx; - forces[edge.toId].y -= factor * fy; - forces[edge.fromId].x += factor * fx; - forces[edge.fromId].y += factor * fy; - } + /** + * Adjust the value range of the edge. The edge will adjust it's width + * based on its value. + * @param {Number} min + * @param {Number} max + * @param total + */ + value: function setValueRange(min, max, total) { + if (this.value !== undefined) { + var scale = this.options.scaling.customScalingFunction(min, max, total, this.value); + var widthDiff = this.options.scaling.max - this.options.scaling.min; + if (this.options.scaling.label.enabled == true) { + var fontDiff = this.options.scaling.label.max - this.options.scaling.label.min; + this.options.font.size = this.options.scaling.label.min + scale * fontDiff; } + this.options.width = this.options.scaling.min + scale * widthDiff; } + }, + writable: true, + configurable: true + }, + draw: { - // 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; + /** + * Redraw a edge + * Draw this edge in the given canvas + * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d"); + * @param {CanvasRenderingContext2D} ctx + */ + value: function draw(ctx) { + var via = this.edgeType.drawLine(ctx, this.selected, this.hover); + this.drawArrows(ctx, via); + this.drawLabel(ctx, via); + }, + writable: true, + configurable: true + }, + drawArrows: { + value: function drawArrows(ctx, viaNode) { + if (this.options.arrows.from.enabled === true) { + this.edgeType.drawArrowHead(ctx, "from", viaNode, this.selected, this.hover); } - - // 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 (this.options.arrows.middle.enabled === true) { + this.edgeType.drawArrowHead(ctx, "middle", viaNode, this.selected, this.hover); } - 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; + if (this.options.arrows.to.enabled === true) { + this.edgeType.drawArrowHead(ctx, "to", viaNode, this.selected, this.hover); } }, writable: true, configurable: true - } - }); - - return HierarchicalSpringSolver; - })(); + }, + drawLabel: { + value: function drawLabel(ctx, viaNode) { + if (this.options.label !== undefined) { + // set style + var node1 = this.from; + var node2 = this.to; + var selected = this.from.selected || this.to.selected || this.selected; + if (node1.id != node2.id) { + var point = this.edgeType.getPoint(0.5, viaNode); + ctx.save(); - module.exports = HierarchicalSpringSolver; + // if the label has to be rotated: + if (this.options.font.align !== "horizontal") { + this.labelModule.calculateLabelSize(ctx, selected, point.x, point.y); + ctx.translate(point.x, this.labelModule.size.yLine); + this._rotateForLabelAlignment(ctx); + } -/***/ }, -/* 74 */ -/***/ function(module, exports, __webpack_require__) { + // draw the label + this.labelModule.draw(ctx, point.x, point.y, selected); + ctx.restore(); + } else { + var x, y; + var radius = this.options.selfReferenceSize; + if (node1.width > node1.height) { + x = node1.x + node1.width * 0.5; + y = node1.y - radius; + } else { + x = node1.x + radius; + y = node1.y - node1.height * 0.5; + } + point = this._pointOnCircle(x, y, radius, 0.125); - "use strict"; + this.labelModule.draw(ctx, point.x, point.y, selected); + } + } + }, + writable: true, + configurable: true + }, + isOverlappingWith: { - var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; - var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; - - /** - * Created by Alex on 2/23/2015. - */ - - var CentralGravitySolver = (function () { - function CentralGravitySolver(body, physicsBody, options) { - _classCallCheck(this, CentralGravitySolver); + /** + * Check if this object is overlapping with the provided object + * @param {Object} obj an object with parameters left, top + * @return {boolean} True if location is located on the edge + */ + value: function isOverlappingWith(obj) { + if (this.connected) { + var distMax = 10; + var xFrom = this.from.x; + var yFrom = this.from.y; + var xTo = this.to.x; + var yTo = this.to.y; + var xObj = obj.left; + var yObj = obj.top; - this.body = body; - this.physicsBody = physicsBody; - this.setOptions(options); - } + var dist = this.edgeType.getDistanceToEdge(xFrom, yFrom, xTo, yTo, xObj, yObj); - _prototypeProperties(CentralGravitySolver, null, { - setOptions: { - value: function setOptions(options) { - this.options = options; + return dist < distMax; + } else { + return false; + } }, writable: true, configurable: true }, - solve: { - value: function solve() { - var dx, dy, distance, node, i; - var nodes = this.body.nodes; - var nodeIndices = this.physicsBody.physicsNodeIndices; - var forces = this.physicsBody.forces; - + _rotateForLabelAlignment: { - var gravity = this.options.centralGravity; - var gravityForce = 0; - for (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); + /** + * Rotates the canvas so the text is most readable + * @param {CanvasRenderingContext2D} ctx + * @private + */ + value: function _rotateForLabelAlignment(ctx) { + var dy = this.from.y - this.to.y; + var dx = this.from.x - this.to.x; + var angleInDegrees = Math.atan2(dy, dx); - gravityForce = distance == 0 ? 0 : gravity / distance; - forces[nodeId].x = dx * gravityForce; - forces[nodeId].y = dy * gravityForce; + // rotate so label it is readable + if (angleInDegrees < -1 && dx < 0 || angleInDegrees > 0 && dx < 0) { + angleInDegrees = angleInDegrees + Math.PI; } + + ctx.rotate(angleInDegrees); + }, + writable: true, + configurable: true + }, + _pointOnCircle: { + + + /** + * Get a point on a circle + * @param {Number} x + * @param {Number} y + * @param {Number} radius + * @param {Number} percentage. Value between 0 (line start) and 1 (line end) + * @return {Object} point + * @private + */ + value: function _pointOnCircle(x, y, radius, percentage) { + var angle = percentage * 2 * Math.PI; + return { + x: x + radius * Math.cos(angle), + y: y - radius * Math.sin(angle) + }; + }, + writable: true, + configurable: true + }, + select: { + value: function select() { + this.selected = true; + }, + writable: true, + configurable: true + }, + unselect: { + value: function unselect() { + this.selected = false; }, writable: true, configurable: true } }); - return CentralGravitySolver; + return Edge; })(); - module.exports = CentralGravitySolver; + module.exports = Edge; /***/ }, -/* 75 */ +/* 81 */ /***/ function(module, exports, __webpack_require__) { "use strict"; @@ -29499,2503 +28561,2813 @@ return /******/ (function(modules) { // webpackBootstrap var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; + var _get = function get(object, property, receiver) { var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc && desc.writable) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + + var _inherits = function (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 _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; /** - * Created by Alex on 24-Feb-15. + * Created by Alex on 3/20/2015. */ - var util = __webpack_require__(1); - var Cluster = _interopRequire(__webpack_require__(76)); - - var ClusterEngine = (function () { - function ClusterEngine(body) { - _classCallCheck(this, ClusterEngine); + var BezierEdgeBase = _interopRequire(__webpack_require__(82)); - this.body = body; - this.clusteredNodes = {}; + var BezierEdgeDynamic = (function (BezierEdgeBase) { + function BezierEdgeDynamic(options, body, labelModule) { + _classCallCheck(this, BezierEdgeDynamic); - this.options = {}; - this.defaultOptions = {}; - util.extend(this.options, this.defaultOptions); + this.via = undefined; + _get(Object.getPrototypeOf(BezierEdgeDynamic.prototype), "constructor", this).call(this, options, body, labelModule); // --> this calls the setOptions below } - _prototypeProperties(ClusterEngine, null, { + _inherits(BezierEdgeDynamic, BezierEdgeBase); + + _prototypeProperties(BezierEdgeDynamic, null, { setOptions: { value: function setOptions(options) { - if (options !== undefined) {} + this.options = options; + this.from = this.body.nodes[this.options.from]; + this.to = this.body.nodes[this.options.to]; + this.id = this.options.id; + this.setupSupportNode(); }, writable: true, configurable: true }, - clusterByConnectionCount: { - - /** - * - * @param hubsize - * @param options - */ - value: function clusterByConnectionCount(hubsize, options) { - if (hubsize === undefined) { - hubsize = this._getHubSize(); - } else if (tyepof(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++) { - var node = this.body.nodes[nodesToCluster[i]]; - this.clusterByConnection(node, options, {}, {}, false); + cleanup: { + value: function cleanup() { + if (this.via !== undefined) { + delete this.body.nodes[this.via.id]; + this.via = undefined; + return true; } - this.body.emitter.emit("_dataChanged"); + return false; }, writable: true, configurable: true }, - clusterByNodeData: { - + setupSupportNode: { /** - * loop over all nodes, check if they adhere to the condition and cluster if needed. - * @param options - * @param refreshData - */ - value: function clusterByNodeData() { - 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."); + * 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, + mass: 1, + shape: "circle", + image: "", + physics: true, + hidden: true + }); + this.body.nodes[nodeId] = node; + this.via = node; + this.via.parentEdgeId = this.id; + this.positionBezierNode(); } - - // 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 clonedOptions = this._cloneOptions(nodeId); - if (options.joinCondition(clonedOptions) == true) { - childNodesObj[nodeId] = this.body.nodes[nodeId]; - } + }, + writable: true, + configurable: true + }, + 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; } + }, + writable: true, + configurable: true + }, + _line: { - this._cluster(childNodesObj, childEdgesObj, options, refreshData); + /** + * 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); + ctx.stroke(); + return this.via; }, writable: true, configurable: true }, - clusterOutliers: { + getPoint: { /** - * 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]; - if (this.body.nodes[nodeId].edges.length == 1) { - var edge = this.body.nodes[nodeId].edges[0]; - var childNodeId = this._getConnectedId(edge, nodeId); - if (childNodeId != nodeId) { - if (options.joinCondition === undefined) { - childNodesObj[nodeId] = this.body.nodes[nodeId]; - childNodesObj[childNodeId] = this.body.nodes[childNodeId]; - } else { - var clonedOptions = this._cloneOptions(nodeId); - if (options.joinCondition(clonedOptions) == true) { - childNodesObj[nodeId] = this.body.nodes[nodeId]; - } - clonedOptions = this._cloneOptions(childNodeId); - if (options.joinCondition(clonedOptions) == true) { - childNodesObj[childNodeId] = this.body.nodes[childNodeId]; - } - } - clusters.push({ nodes: childNodesObj, edges: childEdgesObj }); - } - } - } - - for (var i = 0; i < clusters.length; i++) { - this._cluster(clusters[i].nodes, clusters[i].edges, options, false); - } + * 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; - if (refreshData === true) { - this.body.emitter.emit("_dataChanged"); - } + return { x: x, y: y }; }, writable: true, configurable: true }, - clusterByConnection: { - - /** - * - * @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(parentNodeId); - 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(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); + _findBorderPosition: { + value: function _findBorderPosition(nearNode, ctx) { + return this._findBorderPositionBezier(nearNode, ctx, this.via); }, writable: true, configurable: true }, - _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(objId, type) { - var clonedOptions = {}; - if (type === undefined || type == "node") { - util.deepExtend(clonedOptions, this.body.nodes[objId].options, true); - util.deepExtend(clonedOptions, this.body.nodes[objId].properties, true); - clonedOptions.amountOfConnections = this.body.nodes[objId].edges.length; - } else { - util.deepExtend(clonedOptions, this.body.edges[objId].properties, true); - } - return clonedOptions; + _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); }, writable: true, configurable: true - }, - _createClusterEdges: { + } + }); + return BezierEdgeDynamic; + })(BezierEdgeBase); - /** - * 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, options) { - var edge, childNodeId, childNode; + module.exports = BezierEdgeDynamic; - var childKeys = Object.keys(childNodesObj); - for (var i = 0; i < childKeys.length; i++) { - childNodeId = childKeys[i]; - childNode = childNodesObj[childNodeId]; +/***/ }, +/* 82 */ +/***/ function(module, exports, __webpack_require__) { - // mark all edges for removal from global and 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; + "use strict"; - var otherNodeId = edge.toId; - var otherOnTo = true; - if (edge.toId != childNodeId) { - otherNodeId = edge.toId; - otherOnTo = true; - } else if (edge.fromId != childNodeId) { - otherNodeId = edge.fromId; - otherOnTo = false; - } + var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; - if (childNodesObj[otherNodeId] === undefined) { - var clonedOptions = this._cloneOptions(edge.id, "edge"); - util.deepExtend(clonedOptions, options.clusterEdgeProperties); - if (otherOnTo === true) { - clonedOptions.from = options.clusterNodeProperties.id; - clonedOptions.to = otherNodeId; - } else { - clonedOptions.from = otherNodeId; - clonedOptions.to = options.clusterNodeProperties.id; - } - clonedOptions.id = "clusterEdge:" + util.randomUUID(); - newEdges.push(this.body.functions.createEdge(clonedOptions)); - } - } - } - }, - writable: true, - configurable: true - }, - _checkOptions: { + var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; + var _get = function get(object, property, receiver) { var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc && desc.writable) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; - /** - * 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 = {}; - } + var _inherits = function (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; }; - return options; - }, - writable: true, - configurable: true - }, - _cluster: { + var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; - /** - * - * @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]; - // kill condition: no children so cant cluster - if (Object.keys(childNodesObj).length == 0) { - return; - } + /** + * Created by Alex on 3/20/2015. + */ - // check if we have an unique id; - if (options.clusterNodeProperties.id === undefined) { - options.clusterNodeProperties.id = "cluster:" + util.randomUUID(); - } - var clusterId = options.clusterNodeProperties.id; + var EdgeBase = _interopRequire(__webpack_require__(83)); - // create the new edges that will connect to the cluster - var newEdges = []; - this._createClusterEdges(childNodesObj, childEdgesObj, newEdges, options); + var BezierEdgeBase = (function (EdgeBase) { + function BezierEdgeBase(options, body, labelModule) { + _classCallCheck(this, BezierEdgeBase); - // construct the clusterNodeProperties - var clusterNodeProperties = options.clusterNodeProperties; - if (options.processProperties !== undefined) { - // get the childNode options - var childNodesOptions = []; - for (var nodeId in childNodesObj) { - var clonedOptions = this._cloneOptions(nodeId); - childNodesOptions.push(clonedOptions); - } + _get(Object.getPrototypeOf(BezierEdgeBase.prototype), "constructor", this).call(this, options, body, labelModule); + } - // get clusterproperties based on childNodes - var childEdgesOptions = []; - for (var edgeId in childEdgesObj) { - var clonedOptions = this._cloneOptions(edgeId, "edge"); - childEdgesOptions.push(clonedOptions); - } + _inherits(BezierEdgeBase, EdgeBase); - clusterNodeProperties = options.processProperties(clusterNodeProperties, childNodesOptions, childEdgesOptions); - if (!clusterNodeProperties) { - throw new Error("The processClusterProperties function does not return properties!"); - } - } - if (clusterNodeProperties.label === undefined) { - clusterNodeProperties.label = "cluster"; + _prototypeProperties(BezierEdgeBase, null, { + _findBorderPositionBezier: { + + /** + * This function uses binary search to look for the point where the bezier curve crosses the border of the node. + * + * @param nearNode + * @param ctx + * @param viaNode + * @param nearNode + * @param ctx + * @param viaNode + * @param nearNode + * @param ctx + * @param viaNode + */ + value: function _findBorderPositionBezier(nearNode, ctx) { + var viaNode = arguments[2] === undefined ? this._getViaCoordinates() : arguments[2]; + var maxIterations = 10; + var iteration = 0; + var low = 0; + var high = 1; + var pos, angle, distanceToBorder, distanceToPoint, difference; + var threshold = 0.2; + var node = this.to; + var from = false; + if (nearNode.id === this.from.id) { + node = this.from; + from = true; } + while (low <= high && iteration < maxIterations) { + var middle = (low + high) * 0.5; - // 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; - clusterNodeProperties.allowedToMoveX = true; - } - if (clusterNodeProperties.x === undefined) { - if (pos === undefined) { - pos = this._getClusterPosition(childNodesObj); + pos = this.getPoint(middle, viaNode); + angle = Math.atan2(node.y - pos.y, node.x - pos.x); + distanceToBorder = node.distanceToBorder(ctx, angle); + distanceToPoint = Math.sqrt(Math.pow(pos.x - node.x, 2) + Math.pow(pos.y - node.y, 2)); + difference = distanceToBorder - distanceToPoint; + if (Math.abs(difference) < threshold) { + break; // found + } else if (difference < 0) { + // distance to nodes is larger than distance to border --> t needs to be bigger if we're looking at the to node. + if (from == false) { + low = middle; + } else { + high = middle; + } + } else { + if (from == false) { + high = middle; + } else { + low = middle; + } } - clusterNodeProperties.y = pos.y; - clusterNodeProperties.allowedToMoveY = true; - } + iteration++; + } + pos.t = middle; - // force the ID to remain the same - clusterNodeProperties.id = clusterId; - + return pos; + }, + writable: true, + configurable: true + }, + _getDistanceToBezierEdge: { - // create the clusterNode - var clusterNode = this.body.functions.createNode(clusterNodeProperties, Cluster); - clusterNode.isCluster = true; - clusterNode.containedNodes = childNodesObj; - clusterNode.containedEdges = childEdgesObj; - // 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; - } + /** + * Calculate the distance between a point (x3,y3) and a line segment from + * (x1,y1) to (x2,y2). + * http://stackoverflow.com/questions/849211/shortest-distancae-between-a-point-and-a-line-segment + * @param {number} x1 + * @param {number} y1 + * @param {number} x2 + * @param {number} y2 + * @param {number} x3 + * @param {number} y3 + * @private + */ + value: function _getDistanceToBezierEdge(x1, y1, x2, y2, x3, y3, via) { + // x3,y3 is the point + var xVia = undefined, + yVia = undefined; + xVia = via.x; + yVia = via.y; + var minDistance = 1000000000; + var distance = undefined; + var i = undefined, + t = undefined, + x = undefined, + y = undefined; + var lastX = x1; + var lastY = y1; + for (i = 1; i < 10; i++) { + t = 0.1 * i; + x = Math.pow(1 - t, 2) * x1 + 2 * t * (1 - t) * xVia + Math.pow(t, 2) * x2; + y = Math.pow(1 - t, 2) * y1 + 2 * t * (1 - t) * yVia + Math.pow(t, 2) * y2; + if (i > 0) { + distance = this._getDistanceToLine(lastX, lastY, x, y, x3, y3); + minDistance = distance < minDistance ? distance : minDistance; } + lastX = x; + lastY = y; } + return minDistance; + }, + writable: true, + configurable: 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; - } - } + return BezierEdgeBase; + })(EdgeBase); + module.exports = BezierEdgeBase; - // finally put the cluster node into global - this.body.nodes[clusterNodeProperties.id] = clusterNode; +/***/ }, +/* 83 */ +/***/ function(module, exports, __webpack_require__) { + "use strict"; - // 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(); - } + var _slicedToArray = function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { var _arr = []; for (var _iterator = arr[Symbol.iterator](), _step; !(_step = _iterator.next()).done;) { _arr.push(_step.value); if (i && _arr.length === i) break; } return _arr; } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; - // set ID to undefined so no duplicates arise - clusterNodeProperties.id = undefined; + var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; + var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; - // wrap up - if (refreshData === true) { - this.body.emitter.emit("_dataChanged"); - } + /** + * Created by Alex on 3/20/2015. + */ + var util = __webpack_require__(1); + + var EdgeBase = (function () { + function EdgeBase(options, body, labelModule) { + _classCallCheck(this, EdgeBase); + + this.body = body; + this.labelModule = labelModule; + this.setOptions(options); + this.colorDirty = true; + } + + _prototypeProperties(EdgeBase, null, { + setOptions: { + value: function setOptions(options) { + this.options = options; + this.from = this.body.nodes[this.options.from]; + this.to = this.body.nodes[this.options.to]; + this.id = this.options.id; }, writable: true, configurable: true }, - isCluster: { - + drawLine: { /** - * 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; + * Redraw a edge as a line + * Draw this edge in the given canvas + * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d"); + * @param {CanvasRenderingContext2D} ctx + * @private + */ + value: function drawLine(ctx, selected, hover) { + // set style + ctx.strokeStyle = this.getColor(ctx); + ctx.lineWidth = this.getLineWidth(selected, hover); + var via = undefined; + if (this.from != this.to) { + // draw line + if (this.options.dashes.enabled == true) { + via = this._drawDashedLine(ctx); + } else { + via = this._line(ctx); + } } else { - console.log("Node does not exist."); - return false; + var _getCircleData = this._getCircleData(); + + var _getCircleData2 = _slicedToArray(_getCircleData, 3); + + var x = _getCircleData2[0]; + var y = _getCircleData2[1]; + var radius = _getCircleData2[2]; + this._circle(ctx, x, y, radius); } + + return via; }, writable: true, configurable: true }, - _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; - for (var i = 0; i < childKeys.lenght; i++) { - node = childNodesObj[childKeys[0]]; - 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) }; - }, - writable: true, - configurable: true - }, - 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]; - // 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; - - // 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; - - // 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 = this.body.edges[edgeId]; - edge.options.hidden = false; - edge.togglePhysics(true); + _drawDashedLine: { + value: function _drawDashedLine(ctx) { + var via = undefined; + // only firefox and chrome support this method, else we use the legacy one. + if (ctx.setLineDash !== undefined && this.options.dashes.altLength === undefined) { + ctx.save(); + // configure the dash pattern + var pattern = [0]; + if (this.options.dashes.length !== undefined && this.options.dashes.gap !== undefined) { + pattern = [this.options.dashes.length, this.options.dashes.gap]; + } else { + pattern = [5, 5]; } - } - // remove all temporary edges - for (var i = 0; i < clusterNode.edges.length; i++) { - var edgeId = clusterNode.edges[i].id; - var viaId = this.body.edges[edgeId].via.id; - if (viaId) { - this.body.edges[edgeId].via = undefined; - delete this.body.nodes[viaId]; - } - // this removes the edge from node.edges, which is why edgeIds is formed - this.body.edges[edgeId].disconnect(); - delete this.body.edges[edgeId]; - } + // set dash settings for chrome or firefox + ctx.setLineDash(pattern); + ctx.lineDashOffset = 0; - // remove clusterNode - delete this.body.nodes[clusterNodeId]; + // draw the line + via = this._line(ctx); - if (refreshData === true) { - this.body.emitter.emit("_dataChanged"); + // restore the dash settings. + ctx.setLineDash([0]); + ctx.lineDashOffset = 0; + ctx.restore(); + } else { + // unsupporting smooth lines + // draw dashes line + ctx.beginPath(); + ctx.lineCap = "round"; + if (this.options.dashes.altLength !== undefined) //If an alt dash value has been set add to the array this value + { + ctx.dashesLine(this.from.x, this.from.y, this.to.x, this.to.y, [this.options.dashes.length, this.options.dashes.gap, this.options.dashes.altLength, this.options.dashes.gap]); + } else if (this.options.dashes.length !== undefined && this.options.dashes.gap !== undefined) //If a dash and gap value has been set add to the array this value + { + ctx.dashesLine(this.from.x, this.from.y, this.to.x, this.to.y, [this.options.dashes.length, this.options.dashes.gap]); + } else //If all else fails draw a line + { + ctx.moveTo(this.from.x, this.from.y); + ctx.lineTo(this.to.x, this.to.y); + } + ctx.stroke(); } + return via; }, writable: true, configurable: true }, - _connectEdge: { - - - - /** - * Connect an edge that was previously contained from cluster A to cluster B if the node that it was originally connected to - * is currently residing in cluster B - * @param edge - * @param nodeId - * @param from - * @private - */ - value: function _connectEdge(edge, nodeId, from) { - var clusterStack = this._getClusterStack(nodeId); - if (from == true) { - edge.from = clusterStack[clusterStack.length - 1]; - edge.fromId = clusterStack[clusterStack.length - 1].id; - clusterStack.pop(); - edge.fromArray = clusterStack; + findBorderPosition: { + value: function findBorderPosition(nearNode, ctx, options) { + if (this.from != this.to) { + return this._findBorderPosition(nearNode, ctx, options); } else { - edge.to = clusterStack[clusterStack.length - 1]; - edge.toId = clusterStack[clusterStack.length - 1].id; - clusterStack.pop(); - edge.toArray = clusterStack; + return this._findBorderPositionCircle(nearNode, ctx, options); } - edge.connect(); }, writable: true, configurable: true }, - _getClusterStack: { + findBorderPositions: { + value: function findBorderPositions(ctx) { + var from = {}; + var to = {}; + if (this.from != this.to) { + from = this._findBorderPosition(this.from, ctx); + to = this._findBorderPosition(this.to, ctx); + } else { + var _getCircleData = this._getCircleData(); - /** - * 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 _getClusterStack(nodeId) { - var stack = []; - var max = 100; - var counter = 0; + var _getCircleData2 = _slicedToArray(_getCircleData, 3); - while (this.clusteredNodes[nodeId] !== undefined && counter < max) { - stack.push(this.clusteredNodes[nodeId].node); - nodeId = this.clusteredNodes[nodeId].clusterId; - counter++; + var x = _getCircleData2[0]; + var y = _getCircleData2[1]; + var radius = _getCircleData2[2]; + + + from = this._findBorderPositionCircle(this.from, ctx, { x: x, y: y, low: 0.25, high: 0.6, direction: -1 }); + to = this._findBorderPositionCircle(this.from, ctx, { x: x, y: y, low: 0.6, high: 0.8, direction: 1 }); } - stack.push(this.body.nodes[nodeId]); - return stack; + return { from: from, to: to }; }, writable: true, configurable: true }, - _getConnectedId: { - + _getCircleData: { + value: function _getCircleData() { + var x = undefined, + y = undefined; + var node = this.from; + var radius = this.options.selfReferenceSize; - /** - * 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; + // get circle coordinates + if (node.shape.width > node.shape.height) { + x = node.x + node.shape.width * 0.5; + y = node.y - radius; } else { - return edge.fromId; + x = node.x + radius; + y = node.y - node.shape.height * 0.5; } + return [x, y, radius]; }, writable: true, configurable: true }, - _getHubSize: { + _pointOnCircle: { /** - * 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; + * Get a point on a circle + * @param {Number} x + * @param {Number} y + * @param {Number} radius + * @param {Number} percentage. Value between 0 (line start) and 1 (line end) + * @return {Object} point + * @private + */ + value: function _pointOnCircle(x, y, radius, percentage) { + var angle = percentage * 2 * Math.PI; + return { + x: x + radius * Math.cos(angle), + y: y - radius * Math.sin(angle) + }; + }, + writable: true, + configurable: true + }, + _findBorderPositionCircle: { - 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; + /** + * This function uses binary search to look for the point where the circle crosses the border of the node. + * @param node + * @param ctx + * @param options + * @returns {*} + * @private + */ + value: function _findBorderPositionCircle(node, ctx, options) { + var x = options.x; + var y = options.y; + var low = options.low; + var high = options.high; + var direction = options.direction; - var variance = averageSquared - Math.pow(average, 2); - var standardDeviation = Math.sqrt(variance); + var maxIterations = 10; + var iteration = 0; + var radius = this.options.selfReferenceSize; + var pos = undefined, + angle = undefined, + distanceToBorder = undefined, + distanceToPoint = undefined, + difference = undefined; + var threshold = 0.05; + var middle = (low + high) * 0.5; - var hubThreshold = Math.floor(average + 2 * standardDeviation); + while (low <= high && iteration < maxIterations) { + middle = (low + high) * 0.5; - // always have at least one to cluster - if (hubThreshold > largestHub) { - hubThreshold = largestHub; + pos = this._pointOnCircle(x, y, radius, middle); + angle = Math.atan2(node.y - pos.y, node.x - pos.x); + distanceToBorder = node.distanceToBorder(ctx, angle); + distanceToPoint = Math.sqrt(Math.pow(pos.x - node.x, 2) + Math.pow(pos.y - node.y, 2)); + difference = distanceToBorder - distanceToPoint; + if (Math.abs(difference) < threshold) { + break; // found + } else if (difference > 0) { + // distance to nodes is larger than distance to border --> t needs to be bigger if we're looking at the to node. + if (direction > 0) { + low = middle; + } else { + high = middle; + } + } else { + if (direction > 0) { + high = middle; + } else { + low = middle; + } + } + iteration++; } + pos.t = middle; - return hubThreshold; + return pos; }, writable: true, configurable: true - } - }); - - return ClusterEngine; - })(); + }, + getLineWidth: { - module.exports = ClusterEngine; + /** + * Get the line width of the edge. Depends on width and whether one of the + * connected nodes is selected. + * @return {Number} width + * @private + */ + value: function getLineWidth(selected, hover) { + if (selected == true) { + return Math.max(Math.min(this.options.widthSelectionMultiplier * this.options.width, this.options.scaling.max), 0.3 / this.body.view.scale); + } else { + if (hover == true) { + return Math.max(Math.min(this.options.hoverWidth, this.options.scaling.max), 0.3 / this.body.view.scale); + } else { + return Math.max(this.options.width, 0.3 / this.body.view.scale); + } + } + }, + writable: true, + configurable: true + }, + getColor: { + value: function getColor(ctx) { + var colorObj = this.options.color; -/***/ }, -/* 76 */ -/***/ function(module, exports, __webpack_require__) { + if (colorObj.inherit.enabled === true) { + if (colorObj.inherit.useGradients == true) { + var grd = ctx.createLinearGradient(this.from.x, this.from.y, this.to.x, this.to.y); + var fromColor, toColor; + fromColor = this.from.options.color.highlight.border; + toColor = this.to.options.color.highlight.border; - "use strict"; + if (this.from.selected == false && this.to.selected == false) { + fromColor = util.overrideOpacity(this.from.options.color.border, this.options.color.opacity); + toColor = util.overrideOpacity(this.to.options.color.border, this.options.color.opacity); + } else if (this.from.selected == true && this.to.selected == false) { + toColor = this.to.options.color.border; + } else if (this.from.selected == false && this.to.selected == true) { + fromColor = this.from.options.color.border; + } + grd.addColorStop(0, fromColor); + grd.addColorStop(1, toColor); - var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; + // -------------------- this returns -------------------- // + return grd; + } - var _get = function get(object, property, receiver) { var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc && desc.writable) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + if (this.colorDirty === true) { + if (colorObj.inherit.source == "to") { + colorObj.highlight = this.to.options.color.highlight.border; + colorObj.hover = this.to.options.color.hover.border; + colorObj.color = util.overrideOpacity(this.to.options.color.border, this.options.color.opacity); + } else { + // (this.options.color.inherit.source == "from") { + colorObj.highlight = this.from.options.color.highlight.border; + colorObj.hover = this.from.options.color.hover.border; + colorObj.color = util.overrideOpacity(this.from.options.color.border, this.options.color.opacity); + } + } + } - var _inherits = function (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; }; + // if color inherit is on and gradients are used, the function has already returned by now. + this.colorDirty = false; - var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; + if (this.selected == true) { + return colorObj.highlight; + } else if (this.hover == true) { + return colorObj.hover; + } else { + return colorObj.color; + } + }, + writable: true, + configurable: true + }, + _circle: { - var Node = _interopRequire(__webpack_require__(77)); + /** + * Draw a line from a node to itself, a circle + * @param {CanvasRenderingContext2D} ctx + * @param {Number} x + * @param {Number} y + * @param {Number} radius + * @private + */ + value: function _circle(ctx, x, y, radius) { + // draw a circle + ctx.beginPath(); + ctx.arc(x, y, radius, 0, 2 * Math.PI, false); + ctx.stroke(); + }, + writable: true, + configurable: true + }, + getDistanceToEdge: { - /** - * - */ - var Cluster = (function (Node) { - function Cluster(options, body, imagelist, grouplist, globalOptions) { - _classCallCheck(this, Cluster); - _get(Object.getPrototypeOf(Cluster.prototype), "constructor", this).call(this, options, body, imagelist, grouplist, globalOptions); + /** + * Calculate the distance between a point (x3,y3) and a line segment from + * (x1,y1) to (x2,y2). + * http://stackoverflow.com/questions/849211/shortest-distancae-between-a-point-and-a-line-segment + * @param {number} x1 + * @param {number} y1 + * @param {number} x2 + * @param {number} y2 + * @param {number} x3 + * @param {number} y3 + * @private + */ + value: function getDistanceToEdge(x1, y1, x2, y2, x3, y3, via) { + // x3,y3 is the point + var returnValue = 0; + if (this.from != this.to) { + returnValue = this._getDistanceToEdge(x1, y1, x2, y2, x3, y3, via); + } else { + var _getCircleData = this._getCircleData(); - this.isCluster = true; - this.containedNodes = {}; - this.containedEdges = {}; - } + var _getCircleData2 = _slicedToArray(_getCircleData, 3); - _inherits(Cluster, Node); + var x = _getCircleData2[0]; + var y = _getCircleData2[1]; + var radius = _getCircleData2[2]; + var dx = x - x3; + var dy = y - y3; + returnValue = Math.abs(Math.sqrt(dx * dx + dy * dy) - radius); + } - return Cluster; - })(Node); + if (this.labelModule.size.left < x3 && this.labelModule.size.left + this.labelModule.size.width > x3 && this.labelModule.size.top < y3 && this.labelModule.size.top + this.labelModule.size.height > y3) { + return 0; + } else { + return returnValue; + } + }, + writable: true, + configurable: true + }, + _getDistanceToLine: { + value: function _getDistanceToLine(x1, y1, x2, y2, x3, y3) { + var px = x2 - x1; + var py = y2 - y1; + var something = px * px + py * py; + var u = ((x3 - x1) * px + (y3 - y1) * py) / something; - module.exports = Cluster; + if (u > 1) { + u = 1; + } else if (u < 0) { + u = 0; + } -/***/ }, -/* 77 */ -/***/ function(module, exports, __webpack_require__) { + var x = x1 + u * px; + var y = y1 + u * py; + var dx = x - x3; + var dy = y - y3; - "use strict"; + //# Note: If the actual distance does not matter, + //# if you only want to compare what this function + //# returns to other results of this function, you + //# can just return the squared distance instead + //# (i.e. remove the sqrt) to gain a little performance - var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; + return Math.sqrt(dx * dx + dy * dy); + }, + writable: true, + configurable: true + }, + drawArrowHead: { - var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; + /** + * + * @param ctx + * @param position + * @param viaNode + */ + value: function drawArrowHead(ctx, position, viaNode, selected, hover) { + // set style + ctx.strokeStyle = this.getColor(ctx); + ctx.fillStyle = ctx.strokeStyle; + ctx.lineWidth = this.getLineWidth(selected, hover); - var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; + // set lets + var angle = undefined; + var length = undefined; + var arrowPos = undefined; + var node1 = undefined; + var node2 = undefined; + var guideOffset = undefined; + var scaleFactor = undefined; - var util = __webpack_require__(1); + if (position == "from") { + node1 = this.from; + node2 = this.to; + guideOffset = 0.1; + scaleFactor = this.options.arrows.from.scaleFactor; + } else if (position == "to") { + node1 = this.to; + node2 = this.from; + guideOffset = -0.1; + scaleFactor = this.options.arrows.to.scaleFactor; + } else { + node1 = this.to; + node2 = this.from; + scaleFactor = this.options.arrows.middle.scaleFactor; + } - var Label = _interopRequire(__webpack_require__(62)); + // if not connected to itself + if (node1 != node2) { + if (position !== "middle") { + // draw arrow head + if (this.options.smooth.enabled == true) { + arrowPos = this.findBorderPosition(node1, ctx, { via: viaNode }); + var guidePos = this.getPoint(Math.max(0, Math.min(1, arrowPos.t + guideOffset)), viaNode); + angle = Math.atan2(arrowPos.y - guidePos.y, arrowPos.x - guidePos.x); + } else { + angle = Math.atan2(node1.y - node2.y, node1.x - node2.x); + arrowPos = this.findBorderPosition(node1, ctx); + } + } else { + angle = Math.atan2(node1.y - node2.y, node1.x - node2.x); + arrowPos = this.getPoint(0.6, viaNode); // this is 0.6 to account for the size of the arrow. + } + // draw arrow at the end of the line + length = (10 + 5 * this.options.width) * scaleFactor; + ctx.arrow(arrowPos.x, arrowPos.y, angle, length); + ctx.fill(); + ctx.stroke(); + } else { + // draw circle + var _angle = undefined, + point = undefined; + var _getCircleData = this._getCircleData(); - var Box = _interopRequire(__webpack_require__(78)); + var _getCircleData2 = _slicedToArray(_getCircleData, 3); - var Circle = _interopRequire(__webpack_require__(80)); + var x = _getCircleData2[0]; + var y = _getCircleData2[1]; + var radius = _getCircleData2[2]; - var CircularImage = _interopRequire(__webpack_require__(82)); - var Database = _interopRequire(__webpack_require__(83)); + if (position == "from") { + point = this.findBorderPosition(this.from, ctx, { x: x, y: y, low: 0.25, high: 0.6, direction: -1 }); + _angle = point.t * -2 * Math.PI + 1.5 * Math.PI + 0.1 * Math.PI; + } else if (position == "to") { + point = this.findBorderPosition(this.from, ctx, { x: x, y: y, low: 0.6, high: 1, direction: 1 }); + _angle = point.t * -2 * Math.PI + 1.5 * Math.PI - 1.1 * Math.PI; + } else { + point = this._pointOnCircle(x, y, radius, 0.175); + _angle = 3.9269908169872414; // == 0.175 * -2 * Math.PI + 1.5 * Math.PI + 0.1 * Math.PI; + } - var Diamond = _interopRequire(__webpack_require__(84)); + // draw the arrowhead + var _length = (10 + 5 * this.options.width) * scaleFactor; + ctx.arrow(point.x, point.y, _angle, _length); + ctx.fill(); + ctx.stroke(); + } + }, + writable: true, + configurable: true + } + }); - var Dot = _interopRequire(__webpack_require__(86)); + return EdgeBase; + })(); - var Ellipse = _interopRequire(__webpack_require__(87)); + module.exports = EdgeBase; - var Icon = _interopRequire(__webpack_require__(88)); +/***/ }, +/* 84 */ +/***/ function(module, exports, __webpack_require__) { - var Image = _interopRequire(__webpack_require__(89)); + "use strict"; - var Square = _interopRequire(__webpack_require__(90)); + var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; - var Star = _interopRequire(__webpack_require__(91)); + var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; - var Text = _interopRequire(__webpack_require__(92)); + var _get = function get(object, property, receiver) { var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc && desc.writable) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; - var Triangle = _interopRequire(__webpack_require__(93)); + var _inherits = function (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 TriangleDown = _interopRequire(__webpack_require__(94)); + var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; /** - * @class Node - * A node. A node can be connected to other nodes via one or multiple edges. - * @param {object} options An object containing options for the node. All - * options are optional, except for the id. - * {number} id Id of the node. Required - * {string} label Text label for the node - * {number} x Horizontal position of the node - * {number} y Vertical position of the node - * {string} shape Node shape, available: - * "database", "circle", "ellipse", - * "box", "image", "text", "dot", - * "star", "triangle", "triangleDown", - * "square", "icon" - * {string} image An image url - * {string} title An title text, can be HTML - * {anytype} group A group name or number - * @param {Network.Images} imagelist A list with images. Only needed - * when the node has an image - * @param {Network.Groups} grouplist A list with groups. Needed for - * retrieving group options - * @param {Object} constants An object with default values for - * example for the color - * + * Created by Alex on 3/20/2015. */ - var Node = (function () { - function Node(options, body, imagelist, grouplist, globalOptions) { - _classCallCheck(this, Node); - - this.options = util.bridgeObject(globalOptions); - this.body = body; - - this.edges = []; // all edges connected to this node - // set defaults for the options - this.id = undefined; - this.imagelist = imagelist; - this.grouplist = grouplist; + var BezierEdgeBase = _interopRequire(__webpack_require__(82)); - // state options - this.x = undefined; - this.y = undefined; - this.predefinedPosition = false; // used to check if initial zoomExtent should just take the range or approximate - this.selected = false; - this.hover = false; + var BezierEdgeStatic = (function (BezierEdgeBase) { + function BezierEdgeStatic(options, body, labelModule) { + _classCallCheck(this, BezierEdgeStatic); - this.labelModule = new Label(this.body, this.options); - this.setOptions(options); + _get(Object.getPrototypeOf(BezierEdgeStatic.prototype), "constructor", this).call(this, options, body, labelModule); } - _prototypeProperties(Node, null, { - attachEdge: { - + _inherits(BezierEdgeStatic, BezierEdgeBase); - /** - * Attach a edge to the node - * @param {Edge} edge - */ - value: function attachEdge(edge) { - if (this.edges.indexOf(edge) == -1) { - this.edges.push(edge); - } + _prototypeProperties(BezierEdgeStatic, null, { + cleanup: { + value: function cleanup() { + return false; }, writable: true, configurable: true }, - detachEdge: { - - + _line: { /** - * Detach a edge from the node - * @param {Edge} edge + * Draw a line between two nodes + * @param {CanvasRenderingContext2D} ctx + * @private */ - value: function detachEdge(edge) { - var index = this.edges.indexOf(edge); - if (index != -1) { - this.edges.splice(index, 1); - } - }, - writable: true, - configurable: true - }, - togglePhysics: { + value: function _line(ctx) { + // draw a straight line + ctx.beginPath(); + ctx.moveTo(this.from.x, this.from.y); + var via = this._getViaCoordinates(); - /** - * Enable or disable the physics. - * @param status - */ - value: function togglePhysics(status) { - this.options.physics = status; + // fallback to normal straight edges + if (via.x === undefined) { + ctx.lineTo(this.to.x, this.to.y); + ctx.stroke(); + return undefined; + } else { + ctx.quadraticCurveTo(via.x, via.y, this.to.x, this.to.y); + ctx.stroke(); + return via; + } }, writable: true, configurable: true }, - setOptions: { - - - /** - * Set or overwrite options for the node - * @param {Object} options an object with options - * @param {Object} constants and object with default, global options - */ - value: function setOptions(options) { - if (!options) { - return; - } - - var fields = ["borderWidth", "borderWidthSelected", "brokenImage", "customScalingFunction", "font", "hidden", "icon", "id", "image", "label", "level", "physics", "shape", "size", "title", "value", "x", "y"]; - util.selectiveDeepExtend(fields, this.options, options); - - // basic options - if (options.id !== undefined) { - this.id = options.id; - } - - if (this.id === undefined) { - throw "Node must have an id"; - } - - if (options.x !== undefined) { - this.x = options.x;this.predefinedPosition = true; - } - if (options.y !== undefined) { - this.y = options.y;this.predefinedPosition = true; - } - if (options.value !== undefined) { - this.value = options.value; - } - - // copy group options - if (typeof options.group === "number" || typeof options.group === "string" && options.group != "") { - var groupObj = this.grouplist.get(options.group); - util.deepExtend(this.options, groupObj); - // the color object needs to be completely defined. Since groups can partially overwrite the colors, we parse it again, just in case. - this.options.color = util.parseColor(this.options.color); - } - // individual shape options - if (options.color !== undefined) { - this.options.color = util.parseColor(options.color); - } - - if (this.options.image !== undefined && this.options.image != "") { - if (this.imagelist) { - this.imageObj = this.imagelist.load(this.options.image, this.options.brokenImage); - } else { - throw "No imagelist provided"; - } - } - - if (options.fixed !== undefined) { - if (typeof options.fixed == "boolean") { - this.options.fixed.x = true; - this.options.fixed.y = true; - } else { - if (options.fixed.x !== undefined && typeof options.fixed.x == "boolean") { - this.options.fixed.x = options.fixed.x; - } - if (options.fixed.y !== undefined && typeof options.fixed.y == "boolean") { - this.options.fixed.y = options.fixed.y; + _getViaCoordinates: { + value: function _getViaCoordinates() { + var xVia = undefined; + var yVia = undefined; + var factor = this.options.smooth.roundness; + var type = this.options.smooth.type; + var dx = Math.abs(this.from.x - this.to.x); + var dy = Math.abs(this.from.y - this.to.y); + if (type == "discrete" || type == "diagonalCross") { + if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) { + if (this.from.y > this.to.y) { + if (this.from.x < this.to.x) { + xVia = this.from.x + factor * dy; + yVia = this.from.y - factor * dy; + } else if (this.from.x > this.to.x) { + xVia = this.from.x - factor * dy; + yVia = this.from.y - factor * dy; + } + } else if (this.from.y < this.to.y) { + if (this.from.x < this.to.x) { + xVia = this.from.x + factor * dy; + yVia = this.from.y + factor * dy; + } else if (this.from.x > this.to.x) { + xVia = this.from.x - factor * dy; + yVia = this.from.y + factor * dy; + } + } + if (type == "discrete") { + xVia = dx < factor * dy ? this.from.x : xVia; + } + } else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) { + if (this.from.y > this.to.y) { + if (this.from.x < this.to.x) { + xVia = this.from.x + factor * dx; + yVia = this.from.y - factor * dx; + } else if (this.from.x > this.to.x) { + xVia = this.from.x - factor * dx; + yVia = this.from.y - factor * dx; + } + } else if (this.from.y < this.to.y) { + if (this.from.x < this.to.x) { + xVia = this.from.x + factor * dx; + yVia = this.from.y + factor * dx; + } else if (this.from.x > this.to.x) { + xVia = this.from.x - factor * dx; + yVia = this.from.y + factor * dx; + } + } + if (type == "discrete") { + yVia = dy < factor * dx ? this.from.y : yVia; } } - } + } else if (type == "straightCross") { + if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) { + // up - down + xVia = this.from.x; + if (this.from.y < this.to.y) { + yVia = this.to.y - (1 - factor) * dy; + } else { + yVia = this.to.y + (1 - factor) * dy; + } + } else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) { + // left - right + if (this.from.x < this.to.x) { + xVia = this.to.x - (1 - factor) * dx; + } else { + xVia = this.to.x + (1 - factor) * dx; + } + yVia = this.from.y; + } + } else if (type == "horizontal") { + if (this.from.x < this.to.x) { + xVia = this.to.x - (1 - factor) * dx; + } else { + xVia = this.to.x + (1 - factor) * dx; + } + yVia = this.from.y; + } else if (type == "vertical") { + xVia = this.from.x; + if (this.from.y < this.to.y) { + yVia = this.to.y - (1 - factor) * dy; + } else { + yVia = this.to.y + (1 - factor) * dy; + } + } else if (type == "curvedCW") { + dx = this.to.x - this.from.x; + dy = this.from.y - this.to.y; + var radius = Math.sqrt(dx * dx + dy * dy); + var pi = Math.PI; - this.updateShape(); + var originalAngle = Math.atan2(dy, dx); + var myAngle = (originalAngle + (factor * 0.5 + 0.5) * pi) % (2 * pi); - this.labelModule.setOptions(this.options, options); + xVia = this.from.x + (factor * 0.5 + 0.5) * radius * Math.sin(myAngle); + yVia = this.from.y + (factor * 0.5 + 0.5) * radius * Math.cos(myAngle); + } else if (type == "curvedCCW") { + dx = this.to.x - this.from.x; + dy = this.from.y - this.to.y; + var radius = Math.sqrt(dx * dx + dy * dy); + var pi = Math.PI; - // reset the size of the node, this can be changed - this._reset(); - }, - writable: true, - configurable: true - }, - updateShape: { - value: function updateShape() { - // choose draw method depending on the shape - switch (this.options.shape) { - case "box": - this.shape = new Box(this.options, this.body, this.labelModule); - break; - case "circle": - this.shape = new Circle(this.options, this.body, this.labelModule); - break; - case "circularImage": - this.shape = new CircularImage(this.options, this.body, this.labelModule, this.imageObj); - break; - case "database": - this.shape = new Database(this.options, this.body, this.labelModule); - break; - case "diamond": - this.shape = new Diamond(this.options, this.body, this.labelModule); - break; - case "dot": - this.shape = new Dot(this.options, this.body, this.labelModule); - break; - case "ellipse": - this.shape = new Ellipse(this.options, this.body, this.labelModule); - break; - case "icon": - this.shape = new Icon(this.options, this.body, this.labelModule); - break; - case "image": - this.shape = new Image(this.options, this.body, this.labelModule, this.imageObj); - break; - case "square": - this.shape = new Square(this.options, this.body, this.labelModule); - break; - case "star": - this.shape = new Star(this.options, this.body, this.labelModule); - break; - case "text": - this.shape = new Text(this.options, this.body, this.labelModule); - break; - case "triangle": - this.shape = new Triangle(this.options, this.body, this.labelModule); - break; - case "triangleDown": - this.shape = new TriangleDown(this.options, this.body, this.labelModule); - break; - default: - this.shape = new Ellipse(this.options, this.body, this.labelModule); - break; + var originalAngle = Math.atan2(dy, dx); + var myAngle = (originalAngle + (-factor * 0.5 + 0.5) * pi) % (2 * pi); + + xVia = this.from.x + (factor * 0.5 + 0.5) * radius * Math.sin(myAngle); + yVia = this.from.y + (factor * 0.5 + 0.5) * radius * Math.cos(myAngle); + } else { + // continuous + if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) { + if (this.from.y > this.to.y) { + if (this.from.x < this.to.x) { + xVia = this.from.x + factor * dy; + yVia = this.from.y - factor * dy; + xVia = this.to.x < xVia ? this.to.x : xVia; + } else if (this.from.x > this.to.x) { + xVia = this.from.x - factor * dy; + yVia = this.from.y - factor * dy; + xVia = this.to.x > xVia ? this.to.x : xVia; + } + } else if (this.from.y < this.to.y) { + if (this.from.x < this.to.x) { + xVia = this.from.x + factor * dy; + yVia = this.from.y + factor * dy; + xVia = this.to.x < xVia ? this.to.x : xVia; + } else if (this.from.x > this.to.x) { + xVia = this.from.x - factor * dy; + yVia = this.from.y + factor * dy; + xVia = this.to.x > xVia ? this.to.x : xVia; + } + } + } else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) { + if (this.from.y > this.to.y) { + if (this.from.x < this.to.x) { + xVia = this.from.x + factor * dx; + yVia = this.from.y - factor * dx; + yVia = this.to.y > yVia ? this.to.y : yVia; + } else if (this.from.x > this.to.x) { + xVia = this.from.x - factor * dx; + yVia = this.from.y - factor * dx; + yVia = this.to.y > yVia ? this.to.y : yVia; + } + } else if (this.from.y < this.to.y) { + if (this.from.x < this.to.x) { + xVia = this.from.x + factor * dx; + yVia = this.from.y + factor * dx; + yVia = this.to.y < yVia ? this.to.y : yVia; + } else if (this.from.x > this.to.x) { + xVia = this.from.x - factor * dx; + yVia = this.from.y + factor * dx; + yVia = this.to.y < yVia ? this.to.y : yVia; + } + } + } } + return { x: xVia, y: yVia }; }, writable: true, configurable: true }, - select: { - - - /** - * select this node - */ - value: function select() { - this.selected = true; - this._reset(); + _findBorderPosition: { + value: function _findBorderPosition(nearNode, ctx) { + var options = arguments[2] === undefined ? {} : arguments[2]; + return this._findBorderPositionBezier(nearNode, ctx, options.via); }, writable: true, configurable: true }, - unselect: { - - - /** - * unselect this node - */ - value: function unselect() { - this.selected = false; - this._reset(); + _getDistanceToEdge: { + value: function _getDistanceToEdge(x1, y1, x2, y2, x3, y3) { + var via = arguments[6] === undefined ? this._getViaCoordinates() : arguments[6]; + // x3,y3 is the point + return this._getDistanceToBezierEdge(x1, y1, x2, y2, x3, y3, via); }, writable: true, configurable: true }, - _reset: { - - + getPoint: { /** - * Reset the calculated size of the node, forces it to recalculate its size + * 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 _reset() { - this.shape.width = undefined; - this.shape.height = undefined; + value: function getPoint(percentage) { + var via = arguments[1] === undefined ? this._getViaCoordinates() : arguments[1]; + var t = percentage; + var x = Math.pow(1 - t, 2) * this.from.x + 2 * t * (1 - t) * via.x + Math.pow(t, 2) * this.to.x; + var y = Math.pow(1 - t, 2) * this.from.y + 2 * t * (1 - t) * via.y + Math.pow(t, 2) * this.to.y; + + return { x: x, y: y }; }, writable: true, configurable: true - }, - getTitle: { + } + }); + return BezierEdgeStatic; + })(BezierEdgeBase); - /** - * get the title of this node. - * @return {string} title The title of the node, or undefined when no title - * has been set. - */ - value: function getTitle() { - return typeof this.options.title === "function" ? this.options.title() : this.options.title; - }, - writable: true, - configurable: true - }, - distanceToBorder: { + module.exports = BezierEdgeStatic; +/***/ }, +/* 85 */ +/***/ function(module, exports, __webpack_require__) { - /** - * Calculate the distance to the border of the Node - * @param {CanvasRenderingContext2D} ctx - * @param {Number} angle Angle in radians - * @returns {number} distance Distance to the border in pixels - */ - value: function distanceToBorder(ctx, angle) { - return this.shape.distanceToBorder(ctx, angle); - }, - writable: true, - configurable: true - }, - isFixed: { + "use strict"; + var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; - /** - * Check if this node has a fixed x and y position - * @return {boolean} true if fixed, false if not - */ - value: function isFixed() { - return this.options.fixed.x && this.options.fixed.y; - }, - writable: true, - configurable: true - }, - isSelected: { + var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; + var _get = function get(object, property, receiver) { var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc && desc.writable) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; - /** - * check if this node is selecte - * @return {boolean} selected True if node is selected, else false - */ - value: function isSelected() { - return this.selected; - }, - writable: true, - configurable: true - }, - getValue: { + var _inherits = function (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 _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; - /** - * Retrieve the value of the node. Can be undefined - * @return {Number} value - */ - value: function getValue() { - return this.value; + /** + * Created by Alex on 3/20/2015. + */ + + var EdgeBase = _interopRequire(__webpack_require__(83)); + + var StraightEdge = (function (EdgeBase) { + function StraightEdge(options, body, labelModule) { + _classCallCheck(this, StraightEdge); + + _get(Object.getPrototypeOf(StraightEdge.prototype), "constructor", this).call(this, options, body, labelModule); + } + + _inherits(StraightEdge, EdgeBase); + + _prototypeProperties(StraightEdge, null, { + cleanup: { + value: function cleanup() { + return false; }, writable: true, configurable: true }, - setValueRange: { - - + _line: { /** - * Adjust the value range of the node. The node will adjust it's size - * based on its value. - * @param {Number} min - * @param {Number} max + * Draw a line between two nodes + * @param {CanvasRenderingContext2D} ctx + * @private */ - value: function setValueRange(min, max, total) { - if (this.value !== undefined) { - var scale = this.options.scaling.customScalingFunction(min, max, total, this.value); - var sizeDiff = this.options.scaling.max - this.options.scaling.min; - if (this.options.scaling.label.enabled == true) { - var fontDiff = this.options.scaling.label.max - this.options.scaling.label.min; - this.options.font.size = this.options.scaling.label.min + scale * fontDiff; - } - this.options.size = this.options.scaling.min + scale * sizeDiff; - } + value: function _line(ctx) { + // draw a straight line + ctx.beginPath(); + ctx.moveTo(this.from.x, this.from.y); + ctx.lineTo(this.to.x, this.to.y); + ctx.stroke(); + return undefined; }, writable: true, configurable: true }, - draw: { - + getPoint: { /** - * Draw this node in the given canvas - * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d"); - * @param {CanvasRenderingContext2D} ctx + * 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 draw(ctx) { - this.shape.draw(ctx, this.x, this.y, this.selected, this.hover); + value: function getPoint(percentage) { + return { + x: (1 - percentage) * this.from.x + percentage * this.to.x, + y: (1 - percentage) * this.from.y + percentage * this.to.y + }; }, writable: true, configurable: true }, - resize: { + _findBorderPosition: { + value: function _findBorderPosition(nearNode, ctx) { + var node1 = this.to; + 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; - /** - * Recalculate the size of this node in the given canvas - * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d"); - * @param {CanvasRenderingContext2D} ctx - */ - value: function resize(ctx) { - this.shape.resize(ctx); + return borderPos; }, writable: true, configurable: true }, - isOverlappingWith: { - - - /** - * Check if this object is overlapping with the provided object - * @param {Object} obj an object with parameters left, top, right, bottom - * @return {boolean} True if location is located on node - */ - value: function isOverlappingWith(obj) { - return this.shape.left < obj.right && this.shape.left + this.shape.width > obj.left && this.shape.top < obj.bottom && this.shape.top + this.shape.height > obj.top; + _getDistanceToEdge: { + value: function _getDistanceToEdge(x1, y1, x2, y2, x3, y3) { + // x3,y3 is the point + return this._getDistanceToLine(x1, y1, x2, y2, x3, y3); }, writable: true, configurable: true } }); - return Node; - })(); + return StraightEdge; + })(EdgeBase); - module.exports = Node; + module.exports = StraightEdge; /***/ }, -/* 78 */ +/* 86 */ /***/ function(module, exports, __webpack_require__) { - /** - * Created by Alex on 3/18/2015. - */ "use strict"; var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; - var _get = function get(object, property, receiver) { var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc && desc.writable) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; - var _inherits = function (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; }; + /** + * Created by Alex on 2/23/2015. + */ - var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; + var BarnesHutSolver = _interopRequire(__webpack_require__(101)); - var NodeBase = _interopRequire(__webpack_require__(79)); + var Repulsion = _interopRequire(__webpack_require__(102)); - var Box = (function (NodeBase) { - function Box(options, body, labelModule) { - _classCallCheck(this, Box); + var HierarchicalRepulsion = _interopRequire(__webpack_require__(103)); - _get(Object.getPrototypeOf(Box.prototype), "constructor", this).call(this, options, body, labelModule); - } + var SpringSolver = _interopRequire(__webpack_require__(104)); - _inherits(Box, NodeBase); + var HierarchicalSpringSolver = _interopRequire(__webpack_require__(105)); - _prototypeProperties(Box, null, { - resize: { - value: function resize(ctx) { - if (this.width === undefined) { - var margin = 5; - var textSize = this.labelModule.getTextSize(ctx, this.selected); - this.width = textSize.width + 2 * margin; - this.height = textSize.height + 2 * margin; - } - }, - writable: true, - configurable: true - }, - draw: { - value: function draw(ctx, x, y, selected, hover) { - this.resize(ctx); - this.left = x - this.width / 2; - this.top = y - this.height / 2; + var CentralGravitySolver = _interopRequire(__webpack_require__(106)); - var borderWidth = this.options.borderWidth; - var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth; + var util = __webpack_require__(1); - 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; + var PhysicsEngine = (function () { + function PhysicsEngine(body) { + var _this = this; + _classCallCheck(this, PhysicsEngine); - ctx.roundRect(this.left, this.top, this.width, this.height, this.options.size); - ctx.fill(); - ctx.stroke(); + this.body = body; + this.physicsBody = { physicsNodeIndices: [], physicsEdgeIndices: [], forces: {}, velocities: {} }; - this.boundingBox.top = this.top; - this.boundingBox.left = this.left; - this.boundingBox.right = this.left + this.width; - this.boundingBox.bottom = this.top + this.height; + this.physicsEnabled = true; + this.simulationInterval = 1000 / 60; + this.requiresTimeout = true; + this.previousStates = {}; + this.freezeCache = {}; + this.renderTimer = undefined; - this.labelModule.draw(ctx, x, y, selected); + this.stabilized = false; + this.stabilizationIterations = 0; + this.ready = false; // will be set to true if the stabilize + + // default options + this.options = {}; + this.defaultOptions = { + barnesHut: { + theta: 0.5, // inverted to save time during calculation + gravitationalConstant: -2000, + centralGravity: 0.3, + springLength: 95, + springConstant: 0.04, + damping: 0.09 }, - writable: true, - configurable: true - }, - distanceToBorder: { - value: function distanceToBorder(ctx, angle) { - this.resize(ctx); - var a = this.width / 2; - var b = this.height / 2; - var w = Math.sin(angle) * a; - var h = Math.cos(angle) * b; - return a * b / Math.sqrt(w * w + h * h); + repulsion: { + centralGravity: 0.2, + springLength: 200, + springConstant: 0.05, + nodeDistance: 100, + damping: 0.09 }, - writable: true, - configurable: true - } - }); - - return Box; - })(NodeBase); - - module.exports = Box; - -/***/ }, -/* 79 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - - var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; - - var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; - - /** - * Created by Alex on 3/19/2015. - */ - - var NodeBase = (function () { - function NodeBase(options, body, labelModule) { - _classCallCheck(this, NodeBase); + hierarchicalRepulsion: { + centralGravity: 0, + springLength: 100, + springConstant: 0.01, + nodeDistance: 120, + damping: 0.09 + }, + maxVelocity: 50, + minVelocity: 0.1, // px/s + solver: "BarnesHut", + stabilization: { + enabled: true, + iterations: 1000, // maximum number of iteration to stabilize + updateInterval: 100, + onlyDynamicEdges: false, + zoomExtent: true + }, + timestep: 0.5 + }; + util.extend(this.options, this.defaultOptions); - this.body = body; - this.labelModule = labelModule; - this.setOptions(options); - this.top = undefined; - this.left = undefined; - this.height = undefined; - this.boundingBox = { top: 0, left: 0, right: 0, bottom: 0 }; + this.body.emitter.on("initPhysics", function () { + _this.initPhysics(); + }); + this.body.emitter.on("resetPhysics", function () { + _this.stopSimulation();_this.ready = false; + }); + this.body.emitter.on("disablePhysics", function () { + _this.physicsEnabled = false;_this.stopSimulation(); + }); + this.body.emitter.on("restorePhysics", function () { + _this.setOptions(_this.options); + if (_this.ready === true) { + _this.stabilized = false; + _this.runSimulation(); + } + }); + this.body.emitter.on("startSimulation", function () { + if (_this.ready === true) { + _this.stabilized = false; + _this.runSimulation(); + } + }); + this.body.emitter.on("stopSimulation", function () { + _this.stopSimulation(); + }); } - _prototypeProperties(NodeBase, null, { + _prototypeProperties(PhysicsEngine, null, { setOptions: { value: function setOptions(options) { - this.options = options; + if (options === false) { + this.physicsEnabled = false; + this.stopSimulation(); + } else { + this.physicsEnabled = true; + if (options !== undefined) { + util.selectiveNotDeepExtend(["stabilization"], this.options, options); + util.mergeOptions(this.options, options, "stabilization"); + } + this.init(); + } }, writable: true, configurable: true }, - _distanceToBorder: { - value: function _distanceToBorder(angle) { - var borderWidth = 1; - return Math.min(Math.abs(this.width / 2 / Math.cos(angle)), Math.abs(this.height / 2 / Math.sin(angle))) + borderWidth; + init: { + value: function init() { + var options; + if (this.options.solver == "repulsion") { + options = this.options.repulsion; + this.nodesSolver = new Repulsion(this.body, this.physicsBody, options); + this.edgesSolver = new SpringSolver(this.body, this.physicsBody, options); + } else if (this.options.solver == "hierarchicalRepulsion") { + options = this.options.hierarchicalRepulsion; + this.nodesSolver = new HierarchicalRepulsion(this.body, this.physicsBody, options); + this.edgesSolver = new HierarchicalSpringSolver(this.body, this.physicsBody, options); + } else { + // barnesHut + options = this.options.barnesHut; + this.nodesSolver = new BarnesHutSolver(this.body, this.physicsBody, options); + this.edgesSolver = new SpringSolver(this.body, this.physicsBody, options); + } + + this.gravitySolver = new CentralGravitySolver(this.body, this.physicsBody, options); + this.modelOptions = options; }, writable: true, configurable: true - } - }); - - return NodeBase; - })(); - - module.exports = NodeBase; - -/***/ }, -/* 80 */ -/***/ function(module, exports, __webpack_require__) { - - /** - * Created by Alex on 3/18/2015. - */ - "use strict"; - - var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; - - var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; - - var _get = function get(object, property, receiver) { var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc && desc.writable) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; - - var _inherits = function (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 _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; - - var CircleImageBase = _interopRequire(__webpack_require__(81)); - - var Circle = (function (CircleImageBase) { - function Circle(options, body, labelModule) { - _classCallCheck(this, Circle); - - _get(Object.getPrototypeOf(Circle.prototype), "constructor", this).call(this, options, body, labelModule); - } - - _inherits(Circle, CircleImageBase); - - _prototypeProperties(Circle, null, { - resize: { - value: function resize(ctx, selected) { - if (this.width === undefined) { - var margin = 5; - var textSize = this.labelModule.getTextSize(ctx, selected); - var diameter = Math.max(textSize.width, textSize.height) + 2 * margin; - this.options.size = diameter / 2; - - this.width = diameter; - this.height = diameter; + }, + initPhysics: { + value: function initPhysics() { + if (this.physicsEnabled === true) { + this.stabilized = false; + if (this.options.stabilization.enabled === true) { + this.stabilize(); + } else { + this.ready = true; + this.body.emitter.emit("zoomExtent", { duration: 0 }, true); + this.runSimulation(); + } + } else { + this.ready = true; + this.body.emitter.emit("_redraw"); } }, writable: true, configurable: true }, - draw: { - value: function draw(ctx, x, y, selected, hover) { - this.resize(ctx, selected); - this.left = x - this.width / 2; - this.top = y - this.height / 2; - - this._drawRawCircle(ctx, x, y, selected, hover, this.options.size); - - this.boundingBox.top = y - this.options.size; - this.boundingBox.left = x - this.options.size; - this.boundingBox.right = x + this.options.size; - this.boundingBox.bottom = y + this.options.size; - - this.labelModule.draw(ctx, x, y, selected); + stopSimulation: { + value: function stopSimulation() { + this.stabilized = true; + if (this.viewFunction !== undefined) { + this.body.emitter.off("initRedraw", this.viewFunction); + this.viewFunction = undefined; + this.body.emitter.emit("_stopRendering"); + } }, writable: true, configurable: true }, - distanceToBorder: { - value: function distanceToBorder(ctx, angle) { - this.resize(ctx); - var a = this.width / 2; - var b = this.height / 2; - var w = Math.sin(angle) * a; - var h = Math.cos(angle) * b; - return a * b / Math.sqrt(w * w + h * h); + runSimulation: { + value: function runSimulation() { + if (this.physicsEnabled === true) { + if (this.viewFunction === undefined) { + this.viewFunction = this.simulationStep.bind(this); + this.body.emitter.on("initRedraw", this.viewFunction); + this.body.emitter.emit("_startRendering"); + } + } else { + this.body.emitter.emit("_redraw"); + } }, writable: true, configurable: true - } - }); + }, + simulationStep: { + value: function simulationStep() { + // check if the physics have settled + var startTime = Date.now(); + this.physicsTick(); + var physicsTime = Date.now() - startTime; - return Circle; - })(CircleImageBase); + // run double speed if it is a little graph + if ((physicsTime < 0.4 * this.simulationInterval || this.runDoubleSpeed == true) && this.stabilized === false) { + this.physicsTick(); - module.exports = Circle; + // this makes sure there is no jitter. The decision is taken once to run it at double speed. + this.runDoubleSpeed = true; + } -/***/ }, -/* 81 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - - var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; - - var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; - - var _get = function get(object, property, receiver) { var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc && desc.writable) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; - - var _inherits = function (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; }; + if (this.stabilized === true) { + if (this.stabilizationIterations > 1) { + // trigger the "stabilized" event. + // The event is triggered on the next tick, to prevent the case that + // it is fired while initializing the Network, in which case you would not + // be able to catch it + var me = this; + var params = { + iterations: this.stabilizationIterations + }; + this.stabilizationIterations = 0; + this.startedStabilization = false; + setTimeout(function () { + me.body.emitter.emit("stabilized", params); + }, 0); + } else { + this.stabilizationIterations = 0; + } + this.stopSimulation(); + } + }, + writable: true, + configurable: true + }, + physicsTick: { - var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; + /** + * A single simulation step (or "tick") in the physics simulation + * + * @private + */ + value: function physicsTick() { + if (this.stabilized === false) { + this.calculateForces(); + this.stabilized = this.moveNodes(); - /** - * Created by Alex on 3/19/2015. - */ - var NodeBase = _interopRequire(__webpack_require__(79)); + // determine if the network has stabilzied + if (this.stabilized === true) { + this.revert(); + } else { + // this is here to ensure that there is no start event when the network is already stable. + if (this.startedStabilization == false) { + this.body.emitter.emit("startStabilizing"); + this.startedStabilization = true; + } + } - var CircleImageBase = (function (NodeBase) { - function CircleImageBase(options, body, labelModule) { - _classCallCheck(this, CircleImageBase); + this.stabilizationIterations++; + } + }, + writable: true, + configurable: true + }, + updatePhysicsIndices: { - _get(Object.getPrototypeOf(CircleImageBase.prototype), "constructor", this).call(this, options, body, labelModule); - } + /** + * Smooth curves are created by adding invisible nodes in the center of the edges. These nodes are also + * handled in the calculateForces function. We then use a quadratic curve with the center node as control. + * This function joins the datanodes and invisible (called support) nodes into one object. + * We do this so we do not contaminate this.body.nodes with the support nodes. + * + * @private + */ + value: function updatePhysicsIndices() { + this.physicsBody.forces = {}; + this.physicsBody.physicsNodeIndices = []; + this.physicsBody.physicsEdgeIndices = []; + var nodes = this.body.nodes; + var edges = this.body.edges; - _inherits(CircleImageBase, NodeBase); + // get node indices for physics + for (var nodeId in nodes) { + if (nodes.hasOwnProperty(nodeId)) { + if (nodes[nodeId].options.physics === true) { + this.physicsBody.physicsNodeIndices.push(nodeId); + } + } + } - _prototypeProperties(CircleImageBase, null, { - _drawRawCircle: { - value: function _drawRawCircle(ctx, x, y, selected, hover, size) { - var borderWidth = this.options.borderWidth; - var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth; + // get edge indices for physics + for (var edgeId in edges) { + if (edges.hasOwnProperty(edgeId)) { + if (edges[edgeId].options.physics === true) { + this.physicsBody.physicsEdgeIndices.push(edgeId); + } + } + } - ctx.strokeStyle = selected ? this.options.color.highlight.border : hover ? this.options.color.hover.border : this.options.color.border; + // get the velocity and the forces vector + for (var i = 0; i < this.physicsBody.physicsNodeIndices.length; i++) { + var nodeId = this.physicsBody.physicsNodeIndices[i]; + this.physicsBody.forces[nodeId] = { x: 0, y: 0 }; - ctx.lineWidth = selected ? selectionLineWidth : borderWidth; - ctx.lineWidth *= this.networkScaleInv; - ctx.lineWidth = Math.min(this.width, ctx.lineWidth); + // forces can be reset because they are recalculated. Velocities have to persist. + if (this.physicsBody.velocities[nodeId] === undefined) { + this.physicsBody.velocities[nodeId] = { x: 0, y: 0 }; + } + } - ctx.fillStyle = selected ? this.options.color.highlight.background : hover ? this.options.color.hover.background : this.options.color.background; - ctx.circle(x, y, size); - ctx.fill(); - ctx.stroke(); + // clean deleted nodes from the velocity vector + for (var nodeId in this.physicsBody.velocities) { + if (nodes[nodeId] === undefined) { + delete this.physicsBody.velocities[nodeId]; + } + } }, writable: true, configurable: true }, - _drawImageAtPosition: { - value: function _drawImageAtPosition(ctx) { - if (this.imageObj.width != 0) { - // draw the image - ctx.globalAlpha = 1; - ctx.drawImage(this.imageObj, this.left, this.top, this.width, this.height); + revert: { + value: function revert() { + var nodeIds = Object.keys(this.previousStates); + var nodes = this.body.nodes; + var velocities = this.physicsBody.velocities; + + for (var i = 0; i < nodeIds.length; i++) { + var nodeId = nodeIds[i]; + if (nodes[nodeId] !== undefined) { + if (nodes[nodeId].options.physics === true) { + velocities[nodeId].x = this.previousStates[nodeId].vx; + velocities[nodeId].y = this.previousStates[nodeId].vy; + nodes[nodeId].x = this.previousStates[nodeId].x; + nodes[nodeId].y = this.previousStates[nodeId].y; + } + } else { + delete this.previousStates[nodeId]; + } } }, writable: true, configurable: true }, - _drawImageLabel: { - value: function _drawImageLabel(ctx, x, y, selected) { - var yLabel; - var offset = 0; + moveNodes: { + value: function moveNodes() { + var nodesPresent = false; + var nodeIndices = this.physicsBody.physicsNodeIndices; + var maxVelocity = this.options.maxVelocity === 0 ? 1000000000 : this.options.maxVelocity; + var stabilized = true; + var vminCorrected = this.options.minVelocity / Math.max(this.body.view.scale, 0.05); - if (this.height !== undefined) { - offset = this.height * 0.5; - var labelDimensions = this.labelModule.getTextSize(ctx); + for (var i = 0; i < nodeIndices.length; i++) { + var nodeId = nodeIndices[i]; + var nodeVelocity = this._performStep(nodeId, maxVelocity); + // stabilized is true if stabilized is true and velocity is smaller than vmin --> all nodes must be stabilized + stabilized = nodeVelocity < vminCorrected && stabilized === true; + nodesPresent = true; + } - if (labelDimensions.lineCount >= 1) { - offset += labelDimensions.height / 2; - offset += 3; + + if (nodesPresent == true) { + if (vminCorrected > 0.5 * this.options.maxVelocity) { + return false; + } else { + return stabilized; } } - - yLabel = y + offset; - this.labelModule.draw(ctx, x, yLabel, selected, "hanging"); + return true; }, writable: true, configurable: true - } - }); - - return CircleImageBase; - })(NodeBase); - - module.exports = CircleImageBase; - -/***/ }, -/* 82 */ -/***/ function(module, exports, __webpack_require__) { - - /** - * Created by Alex on 3/18/2015. - */ - "use strict"; - - - var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; + }, + _performStep: { + value: function _performStep(nodeId, maxVelocity) { + var node = this.body.nodes[nodeId]; + var timestep = this.options.timestep; + var forces = this.physicsBody.forces; + var velocities = this.physicsBody.velocities; - var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; + // store the state so we can revert + this.previousStates[nodeId] = { x: node.x, y: node.y, vx: velocities[nodeId].x, vy: velocities[nodeId].y }; - var _get = function get(object, property, receiver) { var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc && desc.writable) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + if (node.options.fixed.x === false) { + var dx = this.modelOptions.damping * velocities[nodeId].x; // damping force + var ax = (forces[nodeId].x - dx) / node.options.mass; // acceleration + velocities[nodeId].x += ax * timestep; // velocity + velocities[nodeId].x = Math.abs(velocities[nodeId].x) > maxVelocity ? velocities[nodeId].x > 0 ? maxVelocity : -maxVelocity : velocities[nodeId].x; + node.x += velocities[nodeId].x * timestep; // position + } else { + forces[nodeId].x = 0; + velocities[nodeId].x = 0; + } - var _inherits = function (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; }; + if (node.options.fixed.y === false) { + var dy = this.modelOptions.damping * velocities[nodeId].y; // damping force + var ay = (forces[nodeId].y - dy) / node.options.mass; // acceleration + velocities[nodeId].y += ay * timestep; // velocity + velocities[nodeId].y = Math.abs(velocities[nodeId].y) > maxVelocity ? velocities[nodeId].y > 0 ? maxVelocity : -maxVelocity : velocities[nodeId].y; + node.y += velocities[nodeId].y * timestep; // position + } else { + forces[nodeId].y = 0; + velocities[nodeId].y = 0; + } - var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; + var totalVelocity = Math.sqrt(Math.pow(velocities[nodeId].x, 2) + Math.pow(velocities[nodeId].y, 2)); + return totalVelocity; + }, + writable: true, + configurable: true + }, + calculateForces: { + value: function calculateForces() { + this.gravitySolver.solve(); + this.nodesSolver.solve(); + this.edgesSolver.solve(); + }, + writable: true, + configurable: true + }, + _freezeNodes: { - var CircleImageBase = _interopRequire(__webpack_require__(81)); - var CircularImage = (function (CircleImageBase) { - function CircularImage(options, body, labelModule, imageObj) { - _classCallCheck(this, CircularImage); - _get(Object.getPrototypeOf(CircularImage.prototype), "constructor", this).call(this, options, body, labelModule); - this.imageObj = imageObj; - } - - _inherits(CircularImage, CircleImageBase); - - _prototypeProperties(CircularImage, null, { - resize: { - value: function resize(ctx) { - if (this.imageObj.src !== undefined || this.imageObj.width !== undefined || this.imageObj.height !== undefined) { - if (!this.width) { - var diameter = this.options.size * 2; - this.width = diameter; - this.height = diameter; - this._swapToImageResizeWhenImageLoaded = true; - } - } else { - if (this._swapToImageResizeWhenImageLoaded) { - this.width = 0; - this.height = 0; - delete this._swapToImageResizeWhenImageLoaded; + /** + * When initializing and stabilizing, we can freeze nodes with a predefined position. This greatly speeds up stabilization + * because only the supportnodes for the smoothCurves have to settle. + * + * @private + */ + value: function _freezeNodes() { + var nodes = this.body.nodes; + for (var id in nodes) { + if (nodes.hasOwnProperty(id)) { + if (nodes[id].x && nodes[id].y) { + this.freezeCache[id] = { x: nodes[id].options.fixed.x, y: nodes[id].options.fixed.y }; + nodes[id].options.fixed.x = true; + nodes[id].options.fixed.y = true; + } } - this._resizeImage(ctx); } }, writable: true, configurable: true }, - draw: { - value: function draw(ctx, x, y, selected, hover) { - this.resize(ctx); - - this.left = x - this.width / 2; - this.top = y - this.height / 2; - - var size = Math.abs(this.height / 2); - this._drawRawCircle(ctx, x, y, selected, hover, size); - - ctx.save(); - ctx.circle(x, y, size); - ctx.stroke(); - ctx.clip(); - - this._drawImageAtPosition(ctx); + _restoreFrozenNodes: { - ctx.restore(); + /** + * Unfreezes the nodes that have been frozen by _freezeDefinedNodes. + * + * @private + */ + value: function _restoreFrozenNodes() { + var nodes = this.body.nodes; + for (var id in nodes) { + if (nodes.hasOwnProperty(id)) { + if (this.freezeCache[id] !== undefined) { + nodes[id].options.fixed.x = this.freezeCache[id].x; + nodes[id].options.fixed.y = this.freezeCache[id].y; + } + } + } + this.freezeCache = {}; + }, + writable: true, + configurable: true + }, + stabilize: { - this.boundingBox.top = y - this.options.size; - this.boundingBox.left = x - this.options.size; - this.boundingBox.right = x + this.options.size; - this.boundingBox.bottom = y + this.options.size; + /** + * Find a stable position for all nodes + * @private + */ + value: function stabilize() { + if (this.options.stabilization.onlyDynamicEdges == true) { + this._freezeNodes(); + } + this.stabilizationSteps = 0; - this._drawImageLabel(ctx, x, y, selected); + setTimeout(this._stabilizationBatch.bind(this), 0); + }, + writable: true, + configurable: true + }, + _stabilizationBatch: { + value: function _stabilizationBatch() { + var count = 0; + while (this.stabilized == false && count < this.options.stabilization.updateInterval && this.stabilizationSteps < this.options.stabilization.iterations) { + this.physicsTick(); + this.stabilizationSteps++; + count++; + } - this.boundingBox.left = Math.min(this.boundingBox.left, this.labelModule.size.left); - this.boundingBox.right = Math.max(this.boundingBox.right, this.labelModule.size.left + this.labelModule.size.width); - this.boundingBox.bottom = Math.max(this.boundingBox.bottom, this.boundingBox.bottom + this.labelModule.size.height); + if (this.stabilized == false && this.stabilizationSteps < this.options.stabilization.iterations) { + this.body.emitter.emit("stabilizationProgress", { steps: this.stabilizationSteps, total: this.options.stabilization.iterations }); + setTimeout(this._stabilizationBatch.bind(this), 0); + } else { + this._finalizeStabilization(); + } }, writable: true, configurable: true }, - distanceToBorder: { - value: function distanceToBorder(ctx, angle) { - this.resize(ctx); - return this._distanceToBorder(angle); + _finalizeStabilization: { + value: function _finalizeStabilization() { + if (this.options.stabilization.zoomExtent == true) { + this.body.emitter.emit("zoomExtent", { duration: 0 }); + } + + if (this.options.stabilization.onlyDynamicEdges == true) { + this._restoreFrozenNodes(); + } + + this.body.emitter.emit("stabilizationIterationsDone"); + this.body.emitter.emit("_requestRedraw"); + this.ready = true; }, writable: true, configurable: true } }); - return CircularImage; - })(CircleImageBase); + return PhysicsEngine; + })(); - module.exports = CircularImage; + module.exports = PhysicsEngine; /***/ }, -/* 83 */ +/* 87 */ /***/ function(module, exports, __webpack_require__) { - /** - * Created by Alex on 3/18/2015. - */ "use strict"; var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; - var _get = function get(object, property, receiver) { var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc && desc.writable) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; - var _inherits = function (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; }; + /** + * Created by Alex on 24-Feb-15. + */ - var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; + var util = __webpack_require__(1); + var Cluster = _interopRequire(__webpack_require__(88)); - var NodeBase = _interopRequire(__webpack_require__(79)); + var ClusterEngine = (function () { + function ClusterEngine(body) { + _classCallCheck(this, ClusterEngine); - var Database = (function (NodeBase) { - function Database(options, body, labelModule) { - _classCallCheck(this, Database); + this.body = body; + this.clusteredNodes = {}; - _get(Object.getPrototypeOf(Database.prototype), "constructor", this).call(this, options, body, labelModule); + this.options = {}; + this.defaultOptions = {}; + util.extend(this.options, this.defaultOptions); } - _inherits(Database, NodeBase); - - _prototypeProperties(Database, null, { - resize: { - value: function resize(ctx, selected) { - if (this.width === undefined) { - var margin = 5; - var textSize = this.labelModule.getTextSize(ctx, selected); - var size = textSize.width + 2 * margin; - this.width = size; - this.height = size; - } + _prototypeProperties(ClusterEngine, null, { + setOptions: { + value: function setOptions(options) { + if (options !== undefined) {} }, writable: true, configurable: true }, - draw: { - value: function draw(ctx, x, y, selected, hover) { - this.resize(ctx, selected); - this.left = x - this.width / 2; - this.top = y - this.height / 2; - - 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 = this.selected ? selectionLineWidth : borderWidth; - ctx.lineWidth *= this.networkScaleInv; - ctx.lineWidth = Math.min(this.width, ctx.lineWidth); + clusterByConnectionCount: { - ctx.fillStyle = selected ? this.options.color.highlight.background : hover ? this.options.color.hover.background : this.options.color.background; - ctx.database(x - this.width / 2, y - this.height * 0.5, this.width, this.height); - ctx.fill(); - ctx.stroke(); + /** + * + * @param hubsize + * @param options + */ + value: function clusterByConnectionCount(hubsize, options) { + if (hubsize === undefined) { + hubsize = this._getHubSize(); + } else if (tyepof(hubsize) == "object") { + options = this._checkOptions(hubsize); + hubsize = this._getHubSize(); + } - this.boundingBox.top = this.top; - this.boundingBox.left = this.left; - this.boundingBox.right = this.left + this.width; - this.boundingBox.bottom = this.top + this.height; + 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); + } + } - this.labelModule.draw(ctx, x, y, selected); + for (var i = 0; i < nodesToCluster.length; i++) { + var node = this.body.nodes[nodesToCluster[i]]; + this.clusterByConnection(node, options, {}, {}, false); + } + this.body.emitter.emit("_dataChanged"); }, writable: true, configurable: true }, - distanceToBorder: { - value: function distanceToBorder(ctx, angle) { - this.resize(ctx); - var a = this.width / 2; - var b = this.height / 2; - var w = Math.sin(angle) * a; - var h = Math.cos(angle) * b; - return a * b / Math.sqrt(w * w + h * h); - }, - writable: true, - configurable: true - } - }); + clusterByNodeData: { - return Database; - })(NodeBase); - module.exports = Database; + /** + * loop over all nodes, check if they adhere to the condition and cluster if needed. + * @param options + * @param refreshData + */ + value: function clusterByNodeData() { + 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."); + } -/***/ }, -/* 84 */ -/***/ function(module, exports, __webpack_require__) { + // check if the options object is fine, append if needed + options = this._checkOptions(options); - /** - * Created by Alex on 3/18/2015. - */ - "use strict"; - - var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; - - var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; - - var _get = function get(object, property, receiver) { var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc && desc.writable) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; - - var _inherits = function (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 _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; - - var ShapeBase = _interopRequire(__webpack_require__(85)); - - var Diamond = (function (ShapeBase) { - function Diamond(options, body, labelModule) { - _classCallCheck(this, Diamond); - - _get(Object.getPrototypeOf(Diamond.prototype), "constructor", this).call(this, options, body, labelModule); - } + var childNodesObj = {}; + var childEdgesObj = {}; - _inherits(Diamond, ShapeBase); + // 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 clonedOptions = this._cloneOptions(nodeId); + if (options.joinCondition(clonedOptions) == true) { + childNodesObj[nodeId] = this.body.nodes[nodeId]; + } + } - _prototypeProperties(Diamond, null, { - resize: { - value: function resize(ctx) { - this._resizeShape(); - }, - writable: true, - configurable: true - }, - draw: { - value: function draw(ctx, x, y, selected, hover) { - this._drawShape(ctx, "diamond", 4, x, y, selected, hover); + this._cluster(childNodesObj, childEdgesObj, options, refreshData); }, writable: true, configurable: true }, - distanceToBorder: { - value: function distanceToBorder(ctx, angle) { - return this._distanceToBorder(angle); - }, - writable: true, - configurable: true - } - }); - - return Diamond; - })(ShapeBase); - - module.exports = Diamond; - -/***/ }, -/* 85 */ -/***/ function(module, exports, __webpack_require__) { + clusterOutliers: { - "use strict"; - var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; + /** + * 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 = []; - var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; + // 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]; + if (this.body.nodes[nodeId].edges.length == 1) { + var edge = this.body.nodes[nodeId].edges[0]; + var childNodeId = this._getConnectedId(edge, nodeId); + if (childNodeId != nodeId) { + if (options.joinCondition === undefined) { + childNodesObj[nodeId] = this.body.nodes[nodeId]; + childNodesObj[childNodeId] = this.body.nodes[childNodeId]; + } else { + var clonedOptions = this._cloneOptions(nodeId); + if (options.joinCondition(clonedOptions) == true) { + childNodesObj[nodeId] = this.body.nodes[nodeId]; + } + clonedOptions = this._cloneOptions(childNodeId); + if (options.joinCondition(clonedOptions) == true) { + childNodesObj[childNodeId] = this.body.nodes[childNodeId]; + } + } + clusters.push({ nodes: childNodesObj, edges: childEdgesObj }); + } + } + } - var _get = function get(object, property, receiver) { var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc && desc.writable) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + for (var i = 0; i < clusters.length; i++) { + this._cluster(clusters[i].nodes, clusters[i].edges, options, false); + } - var _inherits = function (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; }; + if (refreshData === true) { + this.body.emitter.emit("_dataChanged"); + } + }, + writable: true, + configurable: true + }, + clusterByConnection: { - var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; + /** + * + * @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!"); + } - /** - * Created by Alex on 3/19/2015. - */ - var NodeBase = _interopRequire(__webpack_require__(79)); + 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 ShapeBase = (function (NodeBase) { - function ShapeBase(options, body, labelModule) { - _classCallCheck(this, ShapeBase); - _get(Object.getPrototypeOf(ShapeBase.prototype), "constructor", this).call(this, options, body, labelModule); - } + var childNodesObj = {}; + var childEdgesObj = {}; + var parentNodeId = node.id; + var parentClonedOptions = this._cloneOptions(parentNodeId); + childNodesObj[parentNodeId] = node; - _inherits(ShapeBase, NodeBase); + // 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); - _prototypeProperties(ShapeBase, null, { - _resizeShape: { - value: function _resizeShape() { - if (this.width === undefined) { - var size = 2 * this.options.size; - this.width = size; - this.height = size; + 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(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); }, writable: true, configurable: true }, - _drawShape: { - value: function _drawShape(ctx, shape, sizeMultiplier, x, y, selected, hover) { - this._resizeShape(); - - this.left = x - this.width / 2; - this.top = y - this.height / 2; - - 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[shape](x, y, this.options.size); - ctx.fill(); - ctx.stroke(); + _cloneOptions: { - this.boundingBox.top = y - this.options.size; - this.boundingBox.left = x - this.options.size; - this.boundingBox.right = x + this.options.size; - this.boundingBox.bottom = y + this.options.size; - if (this.options.label !== undefined) { - var yLabel = y + 0.5 * this.height + 3; // the + 3 is to offset it a bit below the node. - this.labelModule.draw(ctx, x, yLabel, selected, "hanging"); - this.boundingBox.left = Math.min(this.boundingBox.left, this.labelModule.size.left); - this.boundingBox.right = Math.max(this.boundingBox.right, this.labelModule.size.left + this.labelModule.size.width); - this.boundingBox.bottom = Math.max(this.boundingBox.bottom, this.boundingBox.bottom + this.labelModule.size.height); + /** + * 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(objId, type) { + var clonedOptions = {}; + if (type === undefined || type == "node") { + util.deepExtend(clonedOptions, this.body.nodes[objId].options, true); + util.deepExtend(clonedOptions, this.body.nodes[objId].properties, true); + clonedOptions.amountOfConnections = this.body.nodes[objId].edges.length; + } else { + util.deepExtend(clonedOptions, this.body.edges[objId].properties, true); } + return clonedOptions; }, writable: true, configurable: true - } - }); + }, + _createClusterEdges: { - return ShapeBase; - })(NodeBase); - module.exports = ShapeBase; + /** + * 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, options) { + var edge, childNodeId, childNode; -/***/ }, -/* 86 */ -/***/ function(module, exports, __webpack_require__) { + var childKeys = Object.keys(childNodesObj); + for (var i = 0; i < childKeys.length; i++) { + childNodeId = childKeys[i]; + childNode = childNodesObj[childNodeId]; - /** - * Created by Alex on 3/18/2015. - */ - "use strict"; - - var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; - - var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; - - var _get = function get(object, property, receiver) { var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc && desc.writable) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; - - var _inherits = function (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 _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; - - var ShapeBase = _interopRequire(__webpack_require__(85)); - - var Dot = (function (ShapeBase) { - function Dot(options, body, labelModule) { - _classCallCheck(this, Dot); - - _get(Object.getPrototypeOf(Dot.prototype), "constructor", this).call(this, options, body, labelModule); - } + // mark all edges for removal from global and 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; - _inherits(Dot, ShapeBase); + var otherNodeId = edge.toId; + var otherOnTo = true; + if (edge.toId != childNodeId) { + otherNodeId = edge.toId; + otherOnTo = true; + } else if (edge.fromId != childNodeId) { + otherNodeId = edge.fromId; + otherOnTo = false; + } - _prototypeProperties(Dot, null, { - resize: { - value: function resize(ctx) { - this._resizeShape(); + if (childNodesObj[otherNodeId] === undefined) { + var clonedOptions = this._cloneOptions(edge.id, "edge"); + util.deepExtend(clonedOptions, options.clusterEdgeProperties); + if (otherOnTo === true) { + clonedOptions.from = options.clusterNodeProperties.id; + clonedOptions.to = otherNodeId; + } else { + clonedOptions.from = otherNodeId; + clonedOptions.to = options.clusterNodeProperties.id; + } + clonedOptions.id = "clusterEdge:" + util.randomUUID(); + newEdges.push(this.body.functions.createEdge(clonedOptions)); + } + } + } }, writable: true, configurable: true }, - draw: { - value: function draw(ctx, x, y, selected, hover) { - this._drawShape(ctx, "circle", 2, x, y, selected, hover); + _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; }, writable: true, configurable: true }, - distanceToBorder: { - value: function distanceToBorder(ctx, angle) { - return this.options.size + this.options.borderWidth; - }, - writable: true, - configurable: true - } - }); + _cluster: { - return Dot; - })(ShapeBase); + /** + * + * @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]; + // kill condition: no children so cant cluster + if (Object.keys(childNodesObj).length == 0) { + return; + } - module.exports = Dot; + // check if we have an unique id; + if (options.clusterNodeProperties.id === undefined) { + options.clusterNodeProperties.id = "cluster:" + util.randomUUID(); + } + var clusterId = options.clusterNodeProperties.id; -/***/ }, -/* 87 */ -/***/ function(module, exports, __webpack_require__) { + // create the new edges that will connect to the cluster + var newEdges = []; + this._createClusterEdges(childNodesObj, childEdgesObj, newEdges, options); - /** - * Created by Alex on 3/18/2015. - */ - "use strict"; + // construct the clusterNodeProperties + var clusterNodeProperties = options.clusterNodeProperties; + if (options.processProperties !== undefined) { + // get the childNode options + var childNodesOptions = []; + for (var nodeId in childNodesObj) { + var clonedOptions = this._cloneOptions(nodeId); + childNodesOptions.push(clonedOptions); + } - var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; + // get clusterproperties based on childNodes + var childEdgesOptions = []; + for (var edgeId in childEdgesObj) { + var clonedOptions = this._cloneOptions(edgeId, "edge"); + childEdgesOptions.push(clonedOptions); + } - var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; + clusterNodeProperties = options.processProperties(clusterNodeProperties, childNodesOptions, childEdgesOptions); + if (!clusterNodeProperties) { + throw new Error("The processClusterProperties function does not return properties!"); + } + } + if (clusterNodeProperties.label === undefined) { + clusterNodeProperties.label = "cluster"; + } - var _get = function get(object, property, receiver) { var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc && desc.writable) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; - var _inherits = function (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; }; + // 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; + clusterNodeProperties.allowedToMoveX = true; + } + if (clusterNodeProperties.x === undefined) { + if (pos === undefined) { + pos = this._getClusterPosition(childNodesObj); + } + clusterNodeProperties.y = pos.y; + clusterNodeProperties.allowedToMoveY = true; + } - var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; - var NodeBase = _interopRequire(__webpack_require__(79)); + // force the ID to remain the same + clusterNodeProperties.id = clusterId; + - var Ellipse = (function (NodeBase) { - function Ellipse(options, body, labelModule) { - _classCallCheck(this, Ellipse); + // create the clusterNode + var clusterNode = this.body.functions.createNode(clusterNodeProperties, Cluster); + clusterNode.isCluster = true; + clusterNode.containedNodes = childNodesObj; + clusterNode.containedEdges = childEdgesObj; - _get(Object.getPrototypeOf(Ellipse.prototype), "constructor", this).call(this, options, body, labelModule); - } - _inherits(Ellipse, NodeBase); + // 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; + } + } + } - _prototypeProperties(Ellipse, null, { - 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; + // 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; } } - }, - writable: true, - configurable: true - }, - draw: { - value: function draw(ctx, x, y, selected, hover) { - this.resize(ctx, selected); - this.left = x - this.width / 2; - this.top = y - this.height / 2; - 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; + // finally put the cluster node into global + this.body.nodes[clusterNodeProperties.id] = clusterNode; - 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); - ctx.fill(); - ctx.stroke(); + // 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(); + } - this.boundingBox.left = this.left; - this.boundingBox.top = this.top; - this.boundingBox.bottom = this.top + this.height; - this.boundingBox.right = this.left + this.width; + // set ID to undefined so no duplicates arise + clusterNodeProperties.id = undefined; - this.labelModule.draw(ctx, x, y, selected); + // wrap up + if (refreshData === true) { + this.body.emitter.emit("_dataChanged"); + } }, writable: true, configurable: true }, - distanceToBorder: { - value: function distanceToBorder(ctx, angle) { - this.resize(ctx); - var a = this.width / 2; - var b = this.height / 2; - var w = Math.sin(angle) * a; - var h = Math.cos(angle) * b; - return a * b / Math.sqrt(w * w + h * h); - }, - writable: true, - configurable: true - } - }); - - return Ellipse; - })(NodeBase); + isCluster: { - module.exports = Ellipse; -/***/ }, -/* 88 */ -/***/ function(module, exports, __webpack_require__) { + /** + * 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; + } + }, + writable: true, + configurable: true + }, + _getClusterPosition: { - /** - * Created by Alex on 3/18/2015. - */ - "use strict"; + /** + * 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; + for (var i = 0; i < childKeys.lenght; i++) { + node = childNodesObj[childKeys[0]]; + 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) }; + }, + writable: true, + configurable: true + }, + openCluster: { - var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; - var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; + /** + * 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]; + // 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 _get = function get(object, property, receiver) { var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc && desc.writable) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + var clusterNode = this.body.nodes[clusterNodeId]; + var containedNodes = clusterNode.containedNodes; + var containedEdges = clusterNode.containedEdges; - var _inherits = function (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; }; + // 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; - var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; + // inherit speed + containedNode.vx = clusterNode.vx; + containedNode.vy = clusterNode.vy; - var NodeBase = _interopRequire(__webpack_require__(79)); + containedNode.options.hidden = false; + containedNode.togglePhysics(true); - var Icon = (function (NodeBase) { - function Icon(options, body, labelModule) { - _classCallCheck(this, Icon); + delete this.clusteredNodes[nodeId]; + } + } - _get(Object.getPrototypeOf(Icon.prototype), "constructor", this).call(this, options, body, labelModule); - } + // release edges + for (var edgeId in containedEdges) { + if (containedEdges.hasOwnProperty(edgeId)) { + var edge = this.body.edges[edgeId]; + edge.options.hidden = false; + edge.togglePhysics(true); + } + } - _inherits(Icon, NodeBase); + // remove all temporary edges + for (var i = 0; i < clusterNode.edges.length; i++) { + var edgeId = clusterNode.edges[i].id; + var viaId = this.body.edges[edgeId].via.id; + if (viaId) { + this.body.edges[edgeId].via = undefined; + delete this.body.nodes[viaId]; + } + // this removes the edge from node.edges, which is why edgeIds is formed + this.body.edges[edgeId].disconnect(); + delete this.body.edges[edgeId]; + } - _prototypeProperties(Icon, null, { - resize: { - value: function resize(ctx) { - if (this.width === undefined) { - var margin = 5; - var iconSize = { - width: Number(this.options.icon.size), - height: Number(this.options.icon.size) - }; - this.width = iconSize.width + 2 * margin; - this.height = iconSize.height + 2 * margin; + // remove clusterNode + delete this.body.nodes[clusterNodeId]; + + if (refreshData === true) { + this.body.emitter.emit("_dataChanged"); } }, writable: true, configurable: true }, - draw: { - value: function draw(ctx, x, y, selected, hover) { - this.resize(ctx); - this.options.icon.size = this.options.icon.size || 50; - - this.left = x - this.width * 0.5; - this.top = y - this.height * 0.5; - this._icon(ctx, x, y, selected); + _connectEdge: { - this.boundingBox.top = y - this.options.icon.size * 0.5; - this.boundingBox.left = x - this.options.icon.size * 0.5; - this.boundingBox.right = x + this.options.icon.size * 0.5; - this.boundingBox.bottom = y + this.options.icon.size * 0.5; - if (this.options.label !== undefined) { - var iconTextSpacing = 5; - this.labelModule.draw(ctx, x, y + this.height * 0.5 + iconTextSpacing, selected); - this.boundingBox.left = Math.min(this.boundingBox.left, this.labelModule.size.left); - this.boundingBox.right = Math.max(this.boundingBox.right, this.labelModule.size.left + this.labelModule.size.width); - this.boundingBox.bottom = Math.max(this.boundingBox.bottom, this.boundingBox.bottom + this.labelModule.size.height); + /** + * Connect an edge that was previously contained from cluster A to cluster B if the node that it was originally connected to + * is currently residing in cluster B + * @param edge + * @param nodeId + * @param from + * @private + */ + value: function _connectEdge(edge, nodeId, from) { + var clusterStack = this._getClusterStack(nodeId); + if (from == true) { + edge.from = clusterStack[clusterStack.length - 1]; + edge.fromId = clusterStack[clusterStack.length - 1].id; + clusterStack.pop(); + edge.fromArray = clusterStack; + } else { + edge.to = clusterStack[clusterStack.length - 1]; + edge.toId = clusterStack[clusterStack.length - 1].id; + clusterStack.pop(); + edge.toArray = clusterStack; } + edge.connect(); }, writable: true, configurable: true }, - _icon: { - value: function _icon(ctx, x, y, selected) { - var iconSize = Number(this.options.icon.size); - var relativeIconSize = iconSize * this.body.view.scale; + _getClusterStack: { - if (this.options.icon.code && relativeIconSize > this.options.scaling.label.drawThreshold - 1) { - ctx.font = (selected ? "bold " : "") + iconSize + "px " + this.options.icon.face; + /** + * 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 _getClusterStack(nodeId) { + var stack = []; + var max = 100; + var counter = 0; - // draw icon - ctx.fillStyle = this.options.icon.color || "black"; - ctx.textAlign = "center"; - ctx.textBaseline = "middle"; - ctx.fillText(this.options.icon.code, x, y); + 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; }, writable: true, configurable: true }, - distanceToBorder: { - value: function distanceToBorder(ctx, angle) { - this.resize(ctx); - this._distanceToBorder(angle); + _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; + } }, writable: true, configurable: true - } - }); + }, + _getHubSize: { - return Icon; - })(NodeBase); + /** + * 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; - module.exports = Icon; + 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; -/***/ }, -/* 89 */ -/***/ function(module, exports, __webpack_require__) { + var variance = averageSquared - Math.pow(average, 2); + var standardDeviation = Math.sqrt(variance); - /** - * Created by Alex on 3/18/2015. - */ - "use strict"; - - var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; - - var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; - - var _get = function get(object, property, receiver) { var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc && desc.writable) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; - - var _inherits = function (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 _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; - - var CircleImageBase = _interopRequire(__webpack_require__(81)); - - var Image = (function (CircleImageBase) { - function Image(options, body, labelModule, imageObj) { - _classCallCheck(this, Image); - - _get(Object.getPrototypeOf(Image.prototype), "constructor", this).call(this, options, body, labelModule); - this.imageObj = imageObj; - } - - _inherits(Image, CircleImageBase); + var hubThreshold = Math.floor(average + 2 * standardDeviation); - _prototypeProperties(Image, null, { - resize: { - value: function resize() { - if (!this.width || !this.height) { - // undefined or 0 - var width, height; - if (this.value) { - var scale = this.imageObj.height / this.imageObj.width; - if (scale !== undefined) { - width = this.options.size || this.imageObj.width; - height = this.options.size * scale || this.imageObj.height; - } else { - width = 0; - height = 0; - } - } else { - width = this.imageObj.width; - height = this.imageObj.height; - } - this.width = width; - this.height = height; + // always have at least one to cluster + if (hubThreshold > largestHub) { + hubThreshold = largestHub; } - }, - writable: true, - configurable: true - }, - draw: { - value: function draw(ctx, x, y, selected, hover) { - this.resize(ctx); - this.left = x - this.width / 2; - this.top = y - this.height / 2; - - this._drawImageAtPosition(ctx); - - this.boundingBox.top = this.top; - this.boundingBox.left = this.left; - this.boundingBox.right = this.left + this.width; - this.boundingBox.bottom = this.top + this.height; - this._drawImageLabel(ctx, x, y, selected || hover); - this.boundingBox.left = Math.min(this.boundingBox.left, this.labelModule.size.left); - this.boundingBox.right = Math.max(this.boundingBox.right, this.labelModule.size.left + this.labelModule.size.width); - this.boundingBox.bottom = Math.max(this.boundingBox.bottom, this.boundingBox.bottom + this.labelModule.size.height); - }, - writable: true, - configurable: true - }, - distanceToBorder: { - value: function distanceToBorder(ctx, angle) { - this.resize(ctx); - var a = this.width / 2; - var b = this.height / 2; - var w = Math.sin(angle) * a; - var h = Math.cos(angle) * b; - return a * b / Math.sqrt(w * w + h * h); + return hubThreshold; }, writable: true, configurable: true } }); - return Image; - })(CircleImageBase); + return ClusterEngine; + })(); - module.exports = Image; + module.exports = ClusterEngine; /***/ }, -/* 90 */ +/* 88 */ /***/ function(module, exports, __webpack_require__) { - /** - * Created by Alex on 3/18/2015. - */ "use strict"; var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; - var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; - var _get = function get(object, property, receiver) { var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc && desc.writable) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; var _inherits = function (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 _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; - var ShapeBase = _interopRequire(__webpack_require__(85)); + var Node = _interopRequire(__webpack_require__(60)); - var Square = (function (ShapeBase) { - function Square(options, body, labelModule) { - _classCallCheck(this, Square); + /** + * + */ + var Cluster = (function (Node) { + function Cluster(options, body, imagelist, grouplist, globalOptions) { + _classCallCheck(this, Cluster); - _get(Object.getPrototypeOf(Square.prototype), "constructor", this).call(this, options, body, labelModule); - } + _get(Object.getPrototypeOf(Cluster.prototype), "constructor", this).call(this, options, body, imagelist, grouplist, globalOptions); - _inherits(Square, ShapeBase); + this.isCluster = true; + this.containedNodes = {}; + this.containedEdges = {}; + } - _prototypeProperties(Square, null, { - resize: { - value: function resize() { - this._resizeShape(); - }, - writable: true, - configurable: true - }, - draw: { - value: function draw(ctx, x, y, selected, hover) { - this._drawShape(ctx, "square", 2, x, y, selected, hover); - }, - writable: true, - configurable: true - }, - distanceToBorder: { - value: function distanceToBorder(ctx, angle) { - this.resize(ctx); - return this._distanceToBorder(angle); - }, - writable: true, - configurable: true - } - }); + _inherits(Cluster, Node); - return Square; - })(ShapeBase); + return Cluster; + })(Node); - module.exports = Square; + module.exports = Cluster; /***/ }, -/* 91 */ +/* 89 */ /***/ function(module, exports, __webpack_require__) { + "use strict"; + + var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; + + var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; + /** - * Created by Alex on 3/18/2015. + * Created by Alex on 26-Feb-15. */ - "use strict"; - var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; + if (typeof window !== "undefined") { + window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame; + } - var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; + var util = __webpack_require__(1); - var _get = function get(object, property, receiver) { var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc && desc.writable) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; - var _inherits = function (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 CanvasRenderer = (function () { + function CanvasRenderer(body, canvas) { + var _this = this; + _classCallCheck(this, CanvasRenderer); - var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; + this.body = body; + this.canvas = canvas; - var ShapeBase = _interopRequire(__webpack_require__(85)); + this.redrawRequested = false; + this.renderTimer = false; + this.requiresTimeout = true; + this.renderingActive = false; + this.renderRequests = 0; + this.pixelRatio = undefined; - var Star = (function (ShapeBase) { - function Star(options, body, labelModule) { - _classCallCheck(this, Star); + // redefined in this._redraw + this.canvasTopLeft = { x: 0, y: 0 }; + this.canvasBottomRight = { x: 0, y: 0 }; - _get(Object.getPrototypeOf(Star.prototype), "constructor", this).call(this, options, body, labelModule); - } + this.dragging = false; - _inherits(Star, ShapeBase); + this.body.emitter.on("dragStart", function () { + _this.dragging = true; + }); + this.body.emitter.on("dragEnd", function () { + return _this.dragging = false; + }); + this.body.emitter.on("_redraw", function () { + if (_this.renderingActive === false) { + _this._redraw(); + } + }); + this.body.emitter.on("_requestRedraw", this._requestRedraw.bind(this)); + this.body.emitter.on("_startRendering", function () { + _this.renderRequests += 1;_this.renderingActive = true;_this.startRendering(); + }); + this.body.emitter.on("_stopRendering", function () { + _this.renderRequests -= 1;_this.renderingActive = _this.renderRequests > 0; + }); - _prototypeProperties(Star, null, { - resize: { - value: function resize(ctx) { - this._resizeShape(); + this.options = {}; + this.defaultOptions = { + hideEdgesOnDrag: false, + hideNodesOnDrag: false + }; + util.extend(this.options, this.defaultOptions); + + this._determineBrowserMethod(); + } + + _prototypeProperties(CanvasRenderer, null, { + setOptions: { + value: function setOptions(options) { + if (options !== undefined) { + util.deepExtend(this.options, options); + } }, writable: true, configurable: true }, - draw: { - value: function draw(ctx, x, y, selected, hover) { - this._drawShape(ctx, "star", 4, x, y, selected, hover); + startRendering: { + value: function startRendering() { + if (this.renderingActive === true) { + if (!this.renderTimer) { + if (this.requiresTimeout == true) { + this.renderTimer = window.setTimeout(this.renderStep.bind(this), this.simulationInterval); // wait this.renderTimeStep milliseconds and perform the animation step function + } else { + this.renderTimer = window.requestAnimationFrame(this.renderStep.bind(this)); // wait this.renderTimeStep milliseconds and perform the animation step function + } + } + } else {} }, writable: true, configurable: true }, - distanceToBorder: { - value: function distanceToBorder(ctx, angle) { - return this._distanceToBorder(angle); - }, - writable: true, - configurable: true - } - }); + renderStep: { + value: function renderStep() { + // reset the renderTimer so a new scheduled animation step can be set + this.renderTimer = undefined; - return Star; - })(ShapeBase); + if (this.requiresTimeout == true) { + // this schedules a new simulation step + this.startRendering(); + } - module.exports = Star; + this._redraw(); -/***/ }, -/* 92 */ -/***/ function(module, exports, __webpack_require__) { - - /** - * Created by Alex on 3/18/2015. - */ - "use strict"; - - var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; - - var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; - - var _get = function get(object, property, receiver) { var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc && desc.writable) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; - - var _inherits = function (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 _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; - - var NodeBase = _interopRequire(__webpack_require__(79)); - - var Text = (function (NodeBase) { - function Text(options, body, labelModule) { - _classCallCheck(this, Text); - - _get(Object.getPrototypeOf(Text.prototype), "constructor", this).call(this, options, body, labelModule); - } - - _inherits(Text, NodeBase); - - _prototypeProperties(Text, null, { - resize: { - value: function resize(ctx, selected) { - if (this.width === undefined) { - var margin = 5; - var textSize = this.labelModule.getTextSize(ctx, selected); - this.width = textSize.width + 2 * margin; - this.height = textSize.height + 2 * margin; + if (this.requiresTimeout == false) { + // this schedules a new simulation step + this.startRendering(); } }, writable: true, configurable: true }, - draw: { - value: function draw(ctx, x, y, selected, hover) { - this.resize(ctx, selected || hover); - this.left = x - this.width / 2; - this.top = y - this.height / 2; - - this.labelModule.draw(ctx, x, y, selected || hover); + redraw: { - this.boundingBox.top = this.top; - this.boundingBox.left = this.left; - this.boundingBox.right = this.left + this.width; - this.boundingBox.bottom = this.top + this.height; + /** + * Redraw the network with the current data + * chart will be resized too. + */ + value: function redraw() { + this._setSize(this.constants.width, this.constants.height); + this._redraw(); }, writable: true, configurable: true }, - distanceToBorder: { - value: function distanceToBorder(ctx, angle) { - this.resize(ctx); - return this._distanceToBorder(angle); + _requestRedraw: { + + /** + * Redraw the network with the current data + * @param hidden | used to get the first estimate of the node sizes. only the nodes are drawn after which they are quickly drawn over. + * @private + */ + value: function _requestRedraw() { + if (this.redrawRequested !== true && this.renderingActive === false) { + this.redrawRequested = true; + if (this.requiresTimeout === true) { + window.setTimeout(this._redraw.bind(this, false), 0); + } else { + window.requestAnimationFrame(this._redraw.bind(this, false)); + } + } }, writable: true, configurable: true - } - }); + }, + _redraw: { + value: function _redraw() { + var hidden = arguments[0] === undefined ? false : arguments[0]; + this.body.emitter.emit("initRedraw"); - return Text; - })(NodeBase); + this.redrawRequested = false; + var ctx = this.canvas.frame.canvas.getContext("2d"); - module.exports = Text; + if (this.pixelRation === undefined) { + this.pixelRatio = (window.devicePixelRatio || 1) / (ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1); + } -/***/ }, -/* 93 */ -/***/ function(module, exports, __webpack_require__) { + ctx.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0); - /** - * Created by Alex on 3/18/2015. - */ - "use strict"; + // clear the canvas + var w = this.canvas.frame.canvas.clientWidth; + var h = this.canvas.frame.canvas.clientHeight; + ctx.clearRect(0, 0, w, h); - var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; + this.body.emitter.emit("beforeDrawing", ctx); - var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; + // set scaling and translation + ctx.save(); + ctx.translate(this.body.view.translation.x, this.body.view.translation.y); + ctx.scale(this.body.view.scale, this.body.view.scale); - var _get = function get(object, property, receiver) { var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc && desc.writable) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + this.canvasTopLeft = this.canvas.DOMtoCanvas({ x: 0, y: 0 }); + this.canvasBottomRight = this.canvas.DOMtoCanvas({ x: this.canvas.frame.canvas.clientWidth, y: this.canvas.frame.canvas.clientHeight }); - var _inherits = function (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; }; + if (hidden === false) { + if (this.dragging === false || this.dragging === true && this.options.hideEdgesOnDrag === false) { + this._drawEdges(ctx); + } + } - var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; + if (this.dragging === false || this.dragging === true && this.options.hideNodesOnDrag === false) { + this._drawNodes(ctx, hidden); + } + + if (this.controlNodesActive === true) { + this._drawControlNodes(ctx); + } - var ShapeBase = _interopRequire(__webpack_require__(85)); + //this.physics.nodesSolver._debug(ctx,"#F00F0F"); - var Triangle = (function (ShapeBase) { - function Triangle(options, body, labelModule) { - _classCallCheck(this, Triangle); + this.body.emitter.emit("afterDrawing", ctx); - _get(Object.getPrototypeOf(Triangle.prototype), "constructor", this).call(this, options, body, labelModule); - } + // restore original scaling and translation + ctx.restore(); - _inherits(Triangle, ShapeBase); + if (hidden === true) { + ctx.clearRect(0, 0, w, h); + } - _prototypeProperties(Triangle, null, { - resize: { - value: function resize(ctx) { - this._resizeShape(); - }, - writable: true, - configurable: true - }, - draw: { - value: function draw(ctx, x, y, selected, hover) { - this._drawShape(ctx, "triangle", 3, x, y, selected, hover); }, writable: true, configurable: true }, - distanceToBorder: { - value: function distanceToBorder(ctx, angle) { - return this._distanceToBorder(angle); - }, - writable: true, - configurable: true - } - }); - - return Triangle; - })(ShapeBase); - - module.exports = Triangle; - -/***/ }, -/* 94 */ -/***/ function(module, exports, __webpack_require__) { - - /** - * Created by Alex on 3/18/2015. - */ - "use strict"; - - var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; - - var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; - - var _get = function get(object, property, receiver) { var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc && desc.writable) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; - - var _inherits = function (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 _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; - - var ShapeBase = _interopRequire(__webpack_require__(85)); + _drawNodes: { - var TriangleDown = (function (ShapeBase) { - function TriangleDown(options, body, labelModule) { - _classCallCheck(this, TriangleDown); - _get(Object.getPrototypeOf(TriangleDown.prototype), "constructor", this).call(this, options, body, labelModule); - } + /** + * Redraw all nodes + * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d'); + * @param {CanvasRenderingContext2D} ctx + * @param {Boolean} [alwaysShow] + * @private + */ + value: function _drawNodes(ctx) { + var alwaysShow = arguments[1] === undefined ? false : arguments[1]; + var nodes = this.body.nodes; + var nodeIndices = this.body.nodeIndices; + var node; + var selected = []; - _inherits(TriangleDown, ShapeBase); + // draw unselected nodes; + for (var i = 0; i < nodeIndices.length; i++) { + node = nodes[nodeIndices[i]]; + // set selected nodes aside + if (node.isSelected()) { + selected.push(nodeIndices[i]); + } else { + if (alwaysShow === true) { + node.draw(ctx); + } + // todo: replace check + //else if (node.inArea() === true) { + node.draw(ctx); + //} + } + } - _prototypeProperties(TriangleDown, null, { - resize: { - value: function resize(ctx) { - this._resizeShape(); + // draw the selected nodes on top + for (var i = 0; i < selected.length; i++) { + node = nodes[selected[i]]; + node.draw(ctx); + } }, writable: true, configurable: true }, - draw: { - value: function draw(ctx, x, y, selected, hover) { - this._drawShape(ctx, "triangleDown", 3, x, y, selected, hover); + _drawEdges: { + + + /** + * Redraw all edges + * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d'); + * @param {CanvasRenderingContext2D} ctx + * @private + */ + value: function _drawEdges(ctx) { + var edges = this.body.edges; + var edgeIndices = this.body.edgeIndices; + var edge; + + for (var i = 0; i < edgeIndices.length; i++) { + edge = edges[edgeIndices[i]]; + if (edge.connected === true) { + edge.draw(ctx); + } + } }, writable: true, configurable: true }, - distanceToBorder: { - value: function distanceToBorder(ctx, angle) { - return this._distanceToBorder(angle); + _drawControlNodes: { + + /** + * Redraw all edges + * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d'); + * @param {CanvasRenderingContext2D} ctx + * @private + */ + value: function _drawControlNodes(ctx) { + var edges = this.body.edges; + var edgeIndices = this.body.edgeIndices; + var edge; + + for (var i = 0; i < edgeIndices.length; i++) { + edge = edges[edgeIndices[i]]; + edge._drawControlNodes(ctx); + } + }, + writable: true, + configurable: true + }, + _determineBrowserMethod: { + + /** + * Determine if the browser requires a setTimeout or a requestAnimationFrame. This was required because + * some implementations (safari and IE9) did not support requestAnimationFrame + * @private + */ + value: function _determineBrowserMethod() { + if (typeof window !== "undefined") { + var browserType = navigator.userAgent.toLowerCase(); + this.requiresTimeout = false; + if (browserType.indexOf("msie 9.0") != -1) { + // IE 9 + this.requiresTimeout = true; + } else if (browserType.indexOf("safari") != -1) { + // safari + if (browserType.indexOf("chrome") <= -1) { + this.requiresTimeout = true; + } + } + } else { + this.requiresTimeout = true; + } }, writable: true, configurable: true } }); - return TriangleDown; - })(ShapeBase); + return CanvasRenderer; + })(); - module.exports = TriangleDown; + module.exports = CanvasRenderer; /***/ }, -/* 95 */ +/* 90 */ /***/ function(module, exports, __webpack_require__) { "use strict"; @@ -32004,68 +31376,43 @@ return /******/ (function(modules) { // webpackBootstrap var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; - /** - * Created by Alex on 26-Feb-15. - */ - - if (typeof window !== "undefined") { - window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame; - } + var Hammer = __webpack_require__(19); + var hammerUtil = __webpack_require__(24); var util = __webpack_require__(1); - - var CanvasRenderer = (function () { - function CanvasRenderer(body, canvas) { + /** + * Create the main frame for the Network. + * This function is executed once when a Network object is created. The frame + * contains a canvas, and this canvas contains all objects like the axis and + * nodes. + * @private + */ + var Canvas = (function () { + function Canvas(body) { var _this = this; - _classCallCheck(this, CanvasRenderer); + _classCallCheck(this, Canvas); this.body = body; - this.canvas = canvas; - - this.redrawRequested = false; - this.renderTimer = false; - this.requiresTimeout = true; - this.renderingActive = false; - this.renderRequests = 0; - this.pixelRatio = undefined; - - // redefined in this._redraw - this.canvasTopLeft = { x: 0, y: 0 }; - this.canvasBottomRight = { x: 0, y: 0 }; - - this.dragging = false; - - this.body.emitter.on("dragStart", function () { - _this.dragging = true; - }); - this.body.emitter.on("dragEnd", function () { - return _this.dragging = false; - }); - this.body.emitter.on("_redraw", function () { - if (_this.renderingActive === false) { - _this._redraw(); - } - }); - this.body.emitter.on("_requestRedraw", this._requestRedraw.bind(this)); - this.body.emitter.on("_startRendering", function () { - _this.renderRequests += 1;_this.renderingActive = true;_this.startRendering(); - }); - this.body.emitter.on("_stopRendering", function () { - _this.renderRequests -= 1;_this.renderingActive = _this.renderRequests > 0; - }); this.options = {}; this.defaultOptions = { - hideEdgesOnDrag: false, - hideNodesOnDrag: false + width: "100%", + height: "100%" }; util.extend(this.options, this.defaultOptions); - this._determineBrowserMethod(); + this.body.emitter.once("resize", function (obj) { + _this.body.view.translation.x = obj.width * 0.5;_this.body.view.translation.y = obj.height * 0.5; + }); + this.body.emitter.on("destroy", function () { + return _this.hammer.destroy(); + }); + + this.pixelRatio = 1; } - _prototypeProperties(CanvasRenderer, null, { + _prototypeProperties(Canvas, null, { setOptions: { value: function setOptions(options) { if (options !== undefined) { @@ -32075,256 +31422,264 @@ return /******/ (function(modules) { // webpackBootstrap writable: true, configurable: true }, - startRendering: { - value: function startRendering() { - if (this.renderingActive === true) { - if (!this.renderTimer) { - if (this.requiresTimeout == true) { - this.renderTimer = window.setTimeout(this.renderStep.bind(this), this.simulationInterval); // wait this.renderTimeStep milliseconds and perform the animation step function - } else { - this.renderTimer = window.requestAnimationFrame(this.renderStep.bind(this)); // wait this.renderTimeStep milliseconds and perform the animation step function - } - } - } else {} - }, - writable: true, - configurable: true - }, - renderStep: { - value: function renderStep() { - // reset the renderTimer so a new scheduled animation step can be set - this.renderTimer = undefined; - - if (this.requiresTimeout == true) { - // this schedules a new simulation step - this.startRendering(); + create: { + value: function create() { + // remove all elements from the container element. + while (this.body.container.hasChildNodes()) { + this.body.container.removeChild(this.body.container.firstChild); } - this._redraw(); + this.frame = document.createElement("div"); + this.frame.className = "vis network-frame"; + this.frame.style.position = "relative"; + this.frame.style.overflow = "hidden"; + this.frame.tabIndex = 900; - if (this.requiresTimeout == false) { - // this schedules a new simulation step - this.startRendering(); + ////////////////////////////////////////////////////////////////// + + this.frame.canvas = document.createElement("canvas"); + this.frame.canvas.style.position = "relative"; + this.frame.appendChild(this.frame.canvas); + + if (!this.frame.canvas.getContext) { + var noCanvas = document.createElement("DIV"); + noCanvas.style.color = "red"; + noCanvas.style.fontWeight = "bold"; + noCanvas.style.padding = "10px"; + noCanvas.innerHTML = "Error: your browser does not support HTML canvas"; + this.frame.canvas.appendChild(noCanvas); + } else { + var ctx = this.frame.canvas.getContext("2d"); + this.pixelRatio = (window.devicePixelRatio || 1) / (ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1); + + this.frame.canvas.getContext("2d").setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0); } + + // add the frame to the container element + this.body.container.appendChild(this.frame); + + this.body.view.scale = 1; + this.body.view.translation = { x: 0.5 * this.frame.canvas.clientWidth, y: 0.5 * this.frame.canvas.clientHeight }; + + this._bindHammer(); }, writable: true, configurable: true }, - redraw: { + _bindHammer: { + /** - * Redraw the network with the current data - * chart will be resized too. + * This function binds hammer, it can be repeated over and over due to the uniqueness check. + * @private */ - value: function redraw() { - this._setSize(this.constants.width, this.constants.height); - this._redraw(); + value: function _bindHammer() { + var _this = this; + if (this.hammer !== undefined) { + this.hammer.destroy(); + } + this.drag = {}; + this.pinch = {}; + + // init hammer + this.hammer = new Hammer(this.frame.canvas); + this.hammer.get("pinch").set({ enable: true }); + + hammerUtil.onTouch(this.hammer, function (event) { + _this.body.eventListeners.onTouch(event); + }); + this.hammer.on("tap", function (event) { + _this.body.eventListeners.onTap(event); + }); + this.hammer.on("doubletap", function (event) { + _this.body.eventListeners.onDoubleTap(event); + }); + this.hammer.on("press", function (event) { + _this.body.eventListeners.onHold(event); + }); + this.hammer.on("panstart", function (event) { + _this.body.eventListeners.onDragStart(event); + }); + this.hammer.on("panmove", function (event) { + _this.body.eventListeners.onDrag(event); + }); + this.hammer.on("panend", function (event) { + _this.body.eventListeners.onDragEnd(event); + }); + this.hammer.on("pinch", function (event) { + _this.body.eventListeners.onPinch(event); + }); + + // TODO: neatly cleanup these handlers when re-creating the Canvas, IF these are done with hammer, event.stopPropagation will not work? + this.frame.canvas.addEventListener("mousewheel", function (event) { + _this.body.eventListeners.onMouseWheel(event); + }); + this.frame.canvas.addEventListener("DOMMouseScroll", function (event) { + _this.body.eventListeners.onMouseWheel(event); + }); + + this.frame.canvas.addEventListener("mousemove", function (event) { + _this.body.eventListeners.onMouseMove(event); + }); + + this.hammerFrame = new Hammer(this.frame); + hammerUtil.onRelease(this.hammerFrame, function (event) { + _this.body.eventListeners.onRelease(event); + }); }, writable: true, configurable: true }, - _requestRedraw: { + setSize: { + /** - * Redraw the network with the current data - * @param hidden | used to get the first estimate of the node sizes. only the nodes are drawn after which they are quickly drawn over. - * @private + * Set a new size for the network + * @param {string} width Width in pixels or percentage (for example '800px' + * or '50%') + * @param {string} height Height in pixels or percentage (for example '400px' + * or '30%') */ - value: function _requestRedraw() { - if (this.redrawRequested !== true && this.renderingActive === false) { - this.redrawRequested = true; - if (this.requiresTimeout === true) { - window.setTimeout(this._redraw.bind(this, false), 0); - } else { - window.requestAnimationFrame(this._redraw.bind(this, false)); - } - } - }, - writable: true, - configurable: true - }, - _redraw: { - value: function _redraw() { - var hidden = arguments[0] === undefined ? false : arguments[0]; - this.body.emitter.emit("initRedraw"); - - this.redrawRequested = false; - var ctx = this.canvas.frame.canvas.getContext("2d"); - - if (this.pixelRation === undefined) { - this.pixelRatio = (window.devicePixelRatio || 1) / (ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1); - } - - ctx.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0); + value: function setSize() { + var width = arguments[0] === undefined ? this.options.width : arguments[0]; + var height = arguments[1] === undefined ? this.options.height : arguments[1]; + var emitEvent = false; + var oldWidth = this.frame.canvas.width; + var oldHeight = this.frame.canvas.height; + if (width != this.options.width || height != this.options.height || this.frame.style.width != width || this.frame.style.height != height) { + this.frame.style.width = width; + this.frame.style.height = height; - // clear the canvas - var w = this.canvas.frame.canvas.clientWidth; - var h = this.canvas.frame.canvas.clientHeight; - ctx.clearRect(0, 0, w, h); + this.frame.canvas.style.width = "100%"; + this.frame.canvas.style.height = "100%"; - this.body.emitter.emit("beforeDrawing", ctx); + this.frame.canvas.width = this.frame.canvas.clientWidth * this.pixelRatio; + this.frame.canvas.height = this.frame.canvas.clientHeight * this.pixelRatio; - // set scaling and translation - ctx.save(); - ctx.translate(this.body.view.translation.x, this.body.view.translation.y); - ctx.scale(this.body.view.scale, this.body.view.scale); + this.options.width = width; + this.options.height = height; - this.canvasTopLeft = this.canvas.DOMtoCanvas({ x: 0, y: 0 }); - this.canvasBottomRight = this.canvas.DOMtoCanvas({ x: this.canvas.frame.canvas.clientWidth, y: this.canvas.frame.canvas.clientHeight }); + emitEvent = true; + } else { + // this would adapt the width of the canvas to the width from 100% if and only if + // there is a change. - if (hidden === false) { - if (this.dragging === false || this.dragging === true && this.options.hideEdgesOnDrag === false) { - this._drawEdges(ctx); + if (this.frame.canvas.width != this.frame.canvas.clientWidth * this.pixelRatio) { + this.frame.canvas.width = this.frame.canvas.clientWidth * this.pixelRatio; + emitEvent = true; + } + if (this.frame.canvas.height != this.frame.canvas.clientHeight * this.pixelRatio) { + this.frame.canvas.height = this.frame.canvas.clientHeight * this.pixelRatio; + emitEvent = true; } } - if (this.dragging === false || this.dragging === true && this.options.hideNodesOnDrag === false) { - this._drawNodes(ctx, hidden); - } - - if (this.controlNodesActive === true) { - this._drawControlNodes(ctx); - } - - //this.physics.nodesSolver._debug(ctx,"#F00F0F"); - - this.body.emitter.emit("afterDrawing", ctx); - - // restore original scaling and translation - ctx.restore(); - - if (hidden === true) { - ctx.clearRect(0, 0, w, h); + if (emitEvent === true) { + this.body.emitter.emit("resize", { width: this.frame.canvas.width / this.pixelRatio, height: this.frame.canvas.height / this.pixelRatio, oldWidth: oldWidth / this.pixelRatio, oldHeight: oldHeight / this.pixelRatio }); } - }, writable: true, configurable: true }, - _drawNodes: { + _XconvertDOMtoCanvas: { /** - * Redraw all nodes - * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d'); - * @param {CanvasRenderingContext2D} ctx - * @param {Boolean} [alwaysShow] + * Convert the X coordinate in DOM-space (coordinate point in browser relative to the container div) to + * the X coordinate in canvas-space (the simulation sandbox, which the camera looks upon) + * @param {number} x + * @returns {number} * @private */ - value: function _drawNodes(ctx) { - var alwaysShow = arguments[1] === undefined ? false : arguments[1]; - var nodes = this.body.nodes; - var nodeIndices = this.body.nodeIndices; - var node; - var selected = []; - - // draw unselected nodes; - for (var i = 0; i < nodeIndices.length; i++) { - node = nodes[nodeIndices[i]]; - // set selected nodes aside - if (node.isSelected()) { - selected.push(nodeIndices[i]); - } else { - if (alwaysShow === true) { - node.draw(ctx); - } - // todo: replace check - //else if (node.inArea() === true) { - node.draw(ctx); - //} - } - } - - // draw the selected nodes on top - for (var i = 0; i < selected.length; i++) { - node = nodes[selected[i]]; - node.draw(ctx); - } + value: function _XconvertDOMtoCanvas(x) { + return (x - this.body.view.translation.x) / this.body.view.scale; }, writable: true, configurable: true }, - _drawEdges: { - + _XconvertCanvasToDOM: { /** - * Redraw all edges - * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d'); - * @param {CanvasRenderingContext2D} ctx + * Convert the X coordinate in canvas-space (the simulation sandbox, which the camera looks upon) to + * the X coordinate in DOM-space (coordinate point in browser relative to the container div) + * @param {number} x + * @returns {number} * @private */ - value: function _drawEdges(ctx) { - var edges = this.body.edges; - var edgeIndices = this.body.edgeIndices; - var edge; + value: function _XconvertCanvasToDOM(x) { + return x * this.body.view.scale + this.body.view.translation.x; + }, + writable: true, + configurable: true + }, + _YconvertDOMtoCanvas: { - for (var i = 0; i < edgeIndices.length; i++) { - edge = edges[edgeIndices[i]]; - if (edge.connected === true) { - edge.draw(ctx); - } - } + /** + * Convert the Y coordinate in DOM-space (coordinate point in browser relative to the container div) to + * the Y coordinate in canvas-space (the simulation sandbox, which the camera looks upon) + * @param {number} y + * @returns {number} + * @private + */ + value: function _YconvertDOMtoCanvas(y) { + return (y - this.body.view.translation.y) / this.body.view.scale; }, writable: true, configurable: true }, - _drawControlNodes: { + _YconvertCanvasToDOM: { /** - * Redraw all edges - * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d'); - * @param {CanvasRenderingContext2D} ctx + * Convert the Y coordinate in canvas-space (the simulation sandbox, which the camera looks upon) to + * the Y coordinate in DOM-space (coordinate point in browser relative to the container div) + * @param {number} y + * @returns {number} * @private */ - value: function _drawControlNodes(ctx) { - var edges = this.body.edges; - var edgeIndices = this.body.edgeIndices; - var edge; + value: function _YconvertCanvasToDOM(y) { + return y * this.body.view.scale + this.body.view.translation.y; + }, + writable: true, + configurable: true + }, + canvasToDOM: { - for (var i = 0; i < edgeIndices.length; i++) { - edge = edges[edgeIndices[i]]; - edge._drawControlNodes(ctx); - } + + /** + * + * @param {object} pos = {x: number, y: number} + * @returns {{x: number, y: number}} + * @constructor + */ + value: function canvasToDOM(pos) { + return { x: this._XconvertCanvasToDOM(pos.x), y: this._YconvertCanvasToDOM(pos.y) }; }, writable: true, configurable: true }, - _determineBrowserMethod: { + DOMtoCanvas: { /** - * Determine if the browser requires a setTimeout or a requestAnimationFrame. This was required because - * some implementations (safari and IE9) did not support requestAnimationFrame - * @private + * + * @param {object} pos = {x: number, y: number} + * @returns {{x: number, y: number}} + * @constructor */ - value: function _determineBrowserMethod() { - if (typeof window !== "undefined") { - var browserType = navigator.userAgent.toLowerCase(); - this.requiresTimeout = false; - if (browserType.indexOf("msie 9.0") != -1) { - // IE 9 - this.requiresTimeout = true; - } else if (browserType.indexOf("safari") != -1) { - // safari - if (browserType.indexOf("chrome") <= -1) { - this.requiresTimeout = true; - } - } - } else { - this.requiresTimeout = true; - } + value: function DOMtoCanvas(pos) { + return { x: this._XconvertDOMtoCanvas(pos.x), y: this._YconvertDOMtoCanvas(pos.y) }; }, writable: true, configurable: true } }); - return CanvasRenderer; + return Canvas; })(); - module.exports = CanvasRenderer; + module.exports = Canvas; /***/ }, -/* 96 */ +/* 91 */ /***/ function(module, exports, __webpack_require__) { "use strict"; @@ -32333,1681 +31688,1369 @@ return /******/ (function(modules) { // webpackBootstrap var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; - var Hammer = __webpack_require__(19); - var hammerUtil = __webpack_require__(24); + /** + * Created by Alex on 26-Feb-15. + */ var util = __webpack_require__(1); - /** - * Create the main frame for the Network. - * This function is executed once when a Network object is created. The frame - * contains a canvas, and this canvas contains all objects like the axis and - * nodes. - * @private - */ - var Canvas = (function () { - function Canvas(body) { + var View = (function () { + function View(body, canvas) { var _this = this; - _classCallCheck(this, Canvas); + _classCallCheck(this, View); this.body = body; + this.canvas = canvas; - this.options = {}; - this.defaultOptions = { - width: "100%", - height: "100%" - }; - util.extend(this.options, this.defaultOptions); + this.animationSpeed = 1 / this.renderRefreshRate; + this.animationEasingFunction = "easeInOutQuint"; + this.easingTime = 0; + this.sourceScale = 0; + this.targetScale = 0; + this.sourceTranslation = 0; + this.targetTranslation = 0; + this.lockedOnNodeId = undefined; + this.lockedOnNodeOffset = undefined; + this.touchTime = 0; - this.body.emitter.once("resize", function (obj) { - _this.body.view.translation.x = obj.width * 0.5;_this.body.view.translation.y = obj.height * 0.5; - }); - this.body.emitter.on("destroy", function () { - return _this.hammer.destroy(); - }); + this.viewFunction = undefined; - this.pixelRatio = 1; + this.body.emitter.on("zoomExtent", this.zoomExtent.bind(this)); + this.body.emitter.on("animationFinished", function () { + _this.body.emitter.emit("_stopRendering"); + }); + this.body.emitter.on("unlockNode", this.releaseNode.bind(this)); } - _prototypeProperties(Canvas, null, { + _prototypeProperties(View, null, { setOptions: { - value: function setOptions(options) { - if (options !== undefined) { - util.deepExtend(this.options, options); - } + value: function setOptions() { + var options = arguments[0] === undefined ? {} : arguments[0]; + this.options = options; }, writable: true, configurable: true }, - create: { - value: function create() { - // remove all elements from the container element. - while (this.body.container.hasChildNodes()) { - this.body.container.removeChild(this.body.container.firstChild); - } - - this.frame = document.createElement("div"); - this.frame.className = "vis network-frame"; - this.frame.style.position = "relative"; - this.frame.style.overflow = "hidden"; - this.frame.tabIndex = 900; - - ////////////////////////////////////////////////////////////////// + _getRange: { - this.frame.canvas = document.createElement("canvas"); - this.frame.canvas.style.position = "relative"; - this.frame.appendChild(this.frame.canvas); - if (!this.frame.canvas.getContext) { - var noCanvas = document.createElement("DIV"); - noCanvas.style.color = "red"; - noCanvas.style.fontWeight = "bold"; - noCanvas.style.padding = "10px"; - noCanvas.innerHTML = "Error: your browser does not support HTML canvas"; - this.frame.canvas.appendChild(noCanvas); + // zoomExtent + /** + * Find the center position of the network + * @private + */ + value: function _getRange() { + var specificNodes = arguments[0] === undefined ? [] : arguments[0]; + var minY = 1000000000, + maxY = -1000000000, + minX = 1000000000, + maxX = -1000000000, + node; + if (specificNodes.length > 0) { + for (var i = 0; i < specificNodes.length; i++) { + node = this.body.nodes[specificNodes[i]]; + if (minX > node.shape.boundingBox.left) { + minX = node.shape.boundingBox.left; + } + if (maxX < node.shape.boundingBox.right) { + maxX = node.shape.boundingBox.right; + } + if (minY > node.shape.boundingBox.bottom) { + minY = node.shape.boundingBox.top; + } // top is negative, bottom is positive + if (maxY < node.shape.boundingBox.top) { + maxY = node.shape.boundingBox.bottom; + } // top is negative, bottom is positive + } } else { - var ctx = this.frame.canvas.getContext("2d"); - this.pixelRatio = (window.devicePixelRatio || 1) / (ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1); - - this.frame.canvas.getContext("2d").setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0); + for (var nodeId in this.body.nodes) { + if (this.body.nodes.hasOwnProperty(nodeId)) { + node = this.body.nodes[nodeId]; + if (minX > node.shape.boundingBox.left) { + minX = node.shape.boundingBox.left; + } + if (maxX < node.shape.boundingBox.right) { + maxX = node.shape.boundingBox.right; + } + if (minY > node.shape.boundingBox.bottom) { + minY = node.shape.boundingBox.top; + } // top is negative, bottom is positive + if (maxY < node.shape.boundingBox.top) { + maxY = node.shape.boundingBox.bottom; + } // top is negative, bottom is positive + } + } } - // add the frame to the container element - this.body.container.appendChild(this.frame); - - this.body.view.scale = 1; - this.body.view.translation = { x: 0.5 * this.frame.canvas.clientWidth, y: 0.5 * this.frame.canvas.clientHeight }; - - this._bindHammer(); + if (minX == 1000000000 && maxX == -1000000000 && minY == 1000000000 && maxY == -1000000000) { + minY = 0, maxY = 0, minX = 0, maxX = 0; + } + return { minX: minX, maxX: maxX, minY: minY, maxY: maxY }; }, writable: true, configurable: true }, - _bindHammer: { + _findCenter: { /** - * This function binds hammer, it can be repeated over and over due to the uniqueness check. + * @param {object} range = {minX: minX, maxX: maxX, minY: minY, maxY: maxY}; + * @returns {{x: number, y: number}} * @private */ - value: function _bindHammer() { - var _this = this; - if (this.hammer !== undefined) { - this.hammer.destroy(); - } - this.drag = {}; - this.pinch = {}; - - // init hammer - this.hammer = new Hammer(this.frame.canvas); - this.hammer.get("pinch").set({ enable: true }); - - hammerUtil.onTouch(this.hammer, function (event) { - _this.body.eventListeners.onTouch(event); - }); - this.hammer.on("tap", function (event) { - _this.body.eventListeners.onTap(event); - }); - this.hammer.on("doubletap", function (event) { - _this.body.eventListeners.onDoubleTap(event); - }); - this.hammer.on("press", function (event) { - _this.body.eventListeners.onHold(event); - }); - this.hammer.on("panstart", function (event) { - _this.body.eventListeners.onDragStart(event); - }); - this.hammer.on("panmove", function (event) { - _this.body.eventListeners.onDrag(event); - }); - this.hammer.on("panend", function (event) { - _this.body.eventListeners.onDragEnd(event); - }); - this.hammer.on("pinch", function (event) { - _this.body.eventListeners.onPinch(event); - }); - - // TODO: neatly cleanup these handlers when re-creating the Canvas, IF these are done with hammer, event.stopPropagation will not work? - this.frame.canvas.addEventListener("mousewheel", function (event) { - _this.body.eventListeners.onMouseWheel(event); - }); - this.frame.canvas.addEventListener("DOMMouseScroll", function (event) { - _this.body.eventListeners.onMouseWheel(event); - }); - - this.frame.canvas.addEventListener("mousemove", function (event) { - _this.body.eventListeners.onMouseMove(event); - }); - - this.hammerFrame = new Hammer(this.frame); - hammerUtil.onRelease(this.hammerFrame, function (event) { - _this.body.eventListeners.onRelease(event); - }); + value: function _findCenter(range) { + return { x: 0.5 * (range.maxX + range.minX), + y: 0.5 * (range.maxY + range.minY) }; }, writable: true, configurable: true }, - setSize: { + zoomExtent: { /** - * Set a new size for the network - * @param {string} width Width in pixels or percentage (for example '800px' - * or '50%') - * @param {string} height Height in pixels or percentage (for example '400px' - * or '30%') + * This function zooms out to fit all data on screen based on amount of nodes + * @param {Object} + * @param {Boolean} [initialZoom] | zoom based on fitted formula or range, true = fitted, default = false; + * @param {Boolean} [disableStart] | If true, start is not called. */ - value: function setSize() { - var width = arguments[0] === undefined ? this.options.width : arguments[0]; - var height = arguments[1] === undefined ? this.options.height : arguments[1]; - var emitEvent = false; - var oldWidth = this.frame.canvas.width; - var oldHeight = this.frame.canvas.height; - if (width != this.options.width || height != this.options.height || this.frame.style.width != width || this.frame.style.height != height) { - this.frame.style.width = width; - this.frame.style.height = height; + value: function zoomExtent() { + var options = arguments[0] === undefined ? { nodes: [] } : arguments[0]; + var initialZoom = arguments[1] === undefined ? false : arguments[1]; + var range; + var zoomLevel; - this.frame.canvas.style.width = "100%"; - this.frame.canvas.style.height = "100%"; + if (initialZoom === true) { + // check if more than half of the nodes have a predefined position. If so, we use the range, not the approximation. + var positionDefined = 0; + for (var nodeId in this.body.nodes) { + if (this.body.nodes.hasOwnProperty(nodeId)) { + var node = this.body.nodes[nodeId]; + if (node.predefinedPosition == true) { + positionDefined += 1; + } + } + } + if (positionDefined > 0.5 * this.body.nodeIndices.length) { + this.zoomExtent(options, false); + return; + } - this.frame.canvas.width = this.frame.canvas.clientWidth * this.pixelRatio; - this.frame.canvas.height = this.frame.canvas.clientHeight * this.pixelRatio; + range = this._getRange(options.nodes); - this.options.width = width; - this.options.height = height; + var numberOfNodes = this.body.nodeIndices.length; + zoomLevel = 12.662 / (numberOfNodes + 7.4147) + 0.0964822; // this is obtained from fitting a dataset from 5 points with scale levels that looked good. - emitEvent = true; + // correct for larger canvasses. + var factor = Math.min(this.canvas.frame.canvas.clientWidth / 600, this.canvas.frame.canvas.clientHeight / 600); + zoomLevel *= factor; } else { - // this would adapt the width of the canvas to the width from 100% if and only if - // there is a change. + this.body.emitter.emit("_redraw", true); + range = this._getRange(options.nodes); + var xDistance = Math.abs(range.maxX - range.minX) * 1.1; + var yDistance = Math.abs(range.maxY - range.minY) * 1.1; - if (this.frame.canvas.width != this.frame.canvas.clientWidth * this.pixelRatio) { - this.frame.canvas.width = this.frame.canvas.clientWidth * this.pixelRatio; - emitEvent = true; - } - if (this.frame.canvas.height != this.frame.canvas.clientHeight * this.pixelRatio) { - this.frame.canvas.height = this.frame.canvas.clientHeight * this.pixelRatio; - emitEvent = true; - } + var xZoomLevel = this.canvas.frame.canvas.clientWidth / xDistance; + var yZoomLevel = this.canvas.frame.canvas.clientHeight / yDistance; + zoomLevel = xZoomLevel <= yZoomLevel ? xZoomLevel : yZoomLevel; } - if (emitEvent === true) { - this.body.emitter.emit("resize", { width: this.frame.canvas.width / this.pixelRatio, height: this.frame.canvas.height / this.pixelRatio, oldWidth: oldWidth / this.pixelRatio, oldHeight: oldHeight / this.pixelRatio }); + if (zoomLevel > 1) { + zoomLevel = 1; } + + var center = this._findCenter(range); + var animationOptions = { position: center, scale: zoomLevel, animation: options }; + this.moveTo(animationOptions); }, writable: true, configurable: true }, - _XconvertDOMtoCanvas: { + focusOnNode: { + // animation /** - * Convert the X coordinate in DOM-space (coordinate point in browser relative to the container div) to - * the X coordinate in canvas-space (the simulation sandbox, which the camera looks upon) - * @param {number} x - * @returns {number} - * @private + * Center a node in view. + * + * @param {Number} nodeId + * @param {Number} [options] */ - value: function _XconvertDOMtoCanvas(x) { - return (x - this.body.view.translation.x) / this.body.view.scale; + value: function focusOnNode(nodeId) { + var options = arguments[1] === undefined ? {} : arguments[1]; + if (this.body.nodes[nodeId] !== undefined) { + var nodePosition = { x: this.body.nodes[nodeId].x, y: this.body.nodes[nodeId].y }; + options.position = nodePosition; + options.lockedOnNode = nodeId; + + this.moveTo(options); + } else { + console.log("Node: " + nodeId + " cannot be found."); + } }, writable: true, configurable: true }, - _XconvertCanvasToDOM: { + moveTo: { /** - * Convert the X coordinate in canvas-space (the simulation sandbox, which the camera looks upon) to - * the X coordinate in DOM-space (coordinate point in browser relative to the container div) - * @param {number} x - * @returns {number} - * @private + * + * @param {Object} options | options.offset = {x:Number, y:Number} // offset from the center in DOM pixels + * | options.scale = Number // scale to move to + * | options.position = {x:Number, y:Number} // position to move to + * | options.animation = {duration:Number, easingFunction:String} || Boolean // position to move to */ - value: function _XconvertCanvasToDOM(x) { - return x * this.body.view.scale + this.body.view.translation.x; + value: function moveTo(options) { + if (options === undefined) { + options = {}; + return; + } + if (options.offset === undefined) { + options.offset = { x: 0, y: 0 }; + } + if (options.offset.x === undefined) { + options.offset.x = 0; + } + if (options.offset.y === undefined) { + options.offset.y = 0; + } + if (options.scale === undefined) { + options.scale = this.body.view.scale; + } + if (options.position === undefined) { + options.position = this.body.view.translation; + } + if (options.animation === undefined) { + options.animation = { duration: 0 }; + } + if (options.animation === false) { + options.animation = { duration: 0 }; + } + if (options.animation === true) { + options.animation = {}; + } + if (options.animation.duration === undefined) { + options.animation.duration = 1000; + } // default duration + if (options.animation.easingFunction === undefined) { + options.animation.easingFunction = "easeInOutQuad"; + } // default easing function + + this.animateView(options); }, writable: true, configurable: true }, - _YconvertDOMtoCanvas: { + animateView: { /** - * Convert the Y coordinate in DOM-space (coordinate point in browser relative to the container div) to - * the Y coordinate in canvas-space (the simulation sandbox, which the camera looks upon) - * @param {number} y - * @returns {number} - * @private + * + * @param {Object} options | options.offset = {x:Number, y:Number} // offset from the center in DOM pixels + * | options.time = Number // animation time in milliseconds + * | options.scale = Number // scale to animate to + * | options.position = {x:Number, y:Number} // position to animate to + * | options.easingFunction = String // linear, easeInQuad, easeOutQuad, easeInOutQuad, + * // easeInCubic, easeOutCubic, easeInOutCubic, + * // easeInQuart, easeOutQuart, easeInOutQuart, + * // easeInQuint, easeOutQuint, easeInOutQuint */ - value: function _YconvertDOMtoCanvas(y) { - return (y - this.body.view.translation.y) / this.body.view.scale; + value: function animateView(options) { + if (options === undefined) { + return; + } + this.animationEasingFunction = options.animation.easingFunction; + // release if something focussed on the node + this.releaseNode(); + if (options.locked == true) { + this.lockedOnNodeId = options.lockedOnNode; + this.lockedOnNodeOffset = options.offset; + } + + // forcefully complete the old animation if it was still running + if (this.easingTime != 0) { + this._transitionRedraw(true); // by setting easingtime to 1, we finish the animation. + } + + this.sourceScale = this.body.view.scale; + this.sourceTranslation = this.body.view.translation; + this.targetScale = options.scale; + + // set the scale so the viewCenter is based on the correct zoom level. This is overridden in the transitionRedraw + // but at least then we'll have the target transition + this.body.view.scale = this.targetScale; + var viewCenter = this.canvas.DOMtoCanvas({ x: 0.5 * this.canvas.frame.canvas.clientWidth, y: 0.5 * this.canvas.frame.canvas.clientHeight }); + var distanceFromCenter = { // offset from view, distance view has to change by these x and y to center the node + x: viewCenter.x - options.position.x, + y: viewCenter.y - options.position.y + }; + this.targetTranslation = { + x: this.sourceTranslation.x + distanceFromCenter.x * this.targetScale + options.offset.x, + y: this.sourceTranslation.y + distanceFromCenter.y * this.targetScale + options.offset.y + }; + + // if the time is set to 0, don't do an animation + if (options.animation.duration == 0) { + if (this.lockedOnNodeId != undefined) { + this.viewFunction = this._lockedRedraw.bind(this); + this.body.emitter.on("initRedraw", this.viewFunction); + } else { + this.body.view.scale = this.targetScale; + this.body.view.translation = this.targetTranslation; + this.body.emitter.emit("_requestRedraw"); + } + } else { + this.animationSpeed = 1 / (60 * options.animation.duration * 0.001) || 1 / 60; // 60 for 60 seconds, 0.001 for milli's + this.animationEasingFunction = options.animation.easingFunction; + + + this.viewFunction = this._transitionRedraw.bind(this); + this.body.emitter.on("initRedraw", this.viewFunction); + this.body.emitter.emit("_startRendering"); + } }, writable: true, configurable: true }, - _YconvertCanvasToDOM: { + _lockedRedraw: { /** - * Convert the Y coordinate in canvas-space (the simulation sandbox, which the camera looks upon) to - * the Y coordinate in DOM-space (coordinate point in browser relative to the container div) - * @param {number} y - * @returns {number} + * used to animate smoothly by hijacking the redraw function. * @private */ - value: function _YconvertCanvasToDOM(y) { - return y * this.body.view.scale + this.body.view.translation.y; + value: function _lockedRedraw() { + var nodePosition = { x: this.body.nodes[this.lockedOnNodeId].x, y: this.body.nodes[this.lockedOnNodeId].y }; + var viewCenter = this.DOMtoCanvas({ x: 0.5 * this.frame.canvas.clientWidth, y: 0.5 * this.frame.canvas.clientHeight }); + var distanceFromCenter = { // offset from view, distance view has to change by these x and y to center the node + x: viewCenter.x - nodePosition.x, + y: viewCenter.y - nodePosition.y + }; + var sourceTranslation = this.body.view.translation; + var targetTranslation = { + x: sourceTranslation.x + distanceFromCenter.x * this.body.view.scale + this.lockedOnNodeOffset.x, + y: sourceTranslation.y + distanceFromCenter.y * this.body.view.scale + this.lockedOnNodeOffset.y + }; + + this.body.view.translation = targetTranslation; }, writable: true, configurable: true }, - canvasToDOM: { - - - /** - * - * @param {object} pos = {x: number, y: number} - * @returns {{x: number, y: number}} - * @constructor - */ - value: function canvasToDOM(pos) { - return { x: this._XconvertCanvasToDOM(pos.x), y: this._YconvertCanvasToDOM(pos.y) }; + releaseNode: { + value: function releaseNode() { + if (this.lockedOnNodeId !== undefined && this.viewFunction !== undefined) { + this.body.emitter.off("initRedraw", this.viewFunction); + this.lockedOnNodeId = undefined; + this.lockedOnNodeOffset = undefined; + } }, writable: true, configurable: true }, - DOMtoCanvas: { + _transitionRedraw: { /** * - * @param {object} pos = {x: number, y: number} - * @returns {{x: number, y: number}} - * @constructor + * @param easingTime + * @private */ - value: function DOMtoCanvas(pos) { - return { x: this._XconvertDOMtoCanvas(pos.x), y: this._YconvertDOMtoCanvas(pos.y) }; + value: function _transitionRedraw() { + var finished = arguments[0] === undefined ? false : arguments[0]; + this.easingTime += this.animationSpeed; + this.easingTime = finished === true ? 1 : this.easingTime; + + var progress = util.easingFunctions[this.animationEasingFunction](this.easingTime); + + this.body.view.scale = this.sourceScale + (this.targetScale - this.sourceScale) * progress; + this.body.view.translation = { + x: this.sourceTranslation.x + (this.targetTranslation.x - this.sourceTranslation.x) * progress, + y: this.sourceTranslation.y + (this.targetTranslation.y - this.sourceTranslation.y) * progress + }; + + // cleanup + if (this.easingTime >= 1) { + this.body.emitter.off("initRedraw", this.viewFunction); + this.easingTime = 0; + if (this.lockedOnNodeId != undefined) { + this.viewFunction = this._lockedRedraw.bind(this); + this.body.emitter.on("initRedraw", this.viewFunction); + } + this.body.emitter.emit("animationFinished"); + } }, writable: true, configurable: true } }); - return Canvas; + return View; })(); - module.exports = Canvas; + module.exports = View; /***/ }, -/* 97 */ +/* 92 */ /***/ function(module, exports, __webpack_require__) { "use strict"; + var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; + var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; /** - * Created by Alex on 26-Feb-15. + * Created by Alex on 2/27/2015. + * */ var util = __webpack_require__(1); - var View = (function () { - function View(body, canvas) { - var _this = this; - _classCallCheck(this, View); + var NavigationHandler = _interopRequire(__webpack_require__(93)); + + var Popup = _interopRequire(__webpack_require__(94)); + + var InteractionHandler = (function () { + function InteractionHandler(body, canvas, selectionHandler) { + _classCallCheck(this, InteractionHandler); this.body = body; this.canvas = canvas; + this.selectionHandler = selectionHandler; + this.navigationHandler = new NavigationHandler(body, canvas); - this.animationSpeed = 1 / this.renderRefreshRate; - this.animationEasingFunction = "easeInOutQuint"; - this.easingTime = 0; - this.sourceScale = 0; - this.targetScale = 0; - this.sourceTranslation = 0; - this.targetTranslation = 0; - this.lockedOnNodeId = undefined; - this.lockedOnNodeOffset = undefined; - this.touchTime = 0; + // bind the events from hammer to functions in this object + this.body.eventListeners.onTap = this.onTap.bind(this); + this.body.eventListeners.onTouch = this.onTouch.bind(this); + this.body.eventListeners.onDoubleTap = this.onDoubleTap.bind(this); + this.body.eventListeners.onHold = this.onHold.bind(this); + this.body.eventListeners.onDragStart = this.onDragStart.bind(this); + this.body.eventListeners.onDrag = this.onDrag.bind(this); + this.body.eventListeners.onDragEnd = this.onDragEnd.bind(this); + this.body.eventListeners.onMouseWheel = this.onMouseWheel.bind(this); + this.body.eventListeners.onPinch = this.onPinch.bind(this); + this.body.eventListeners.onMouseMove = this.onMouseMove.bind(this); + this.body.eventListeners.onRelease = this.onRelease.bind(this); - this.viewFunction = undefined; + this.touchTime = 0; + this.drag = {}; + this.pinch = {}; + this.hoverObj = { nodes: {}, edges: {} }; + this.popup = undefined; + this.popupObj = undefined; + this.popupTimer = undefined; - this.body.emitter.on("zoomExtent", this.zoomExtent.bind(this)); - this.body.emitter.on("animationFinished", function () { - _this.body.emitter.emit("_stopRendering"); - }); - this.body.emitter.on("unlockNode", this.releaseNode.bind(this)); + this.body.functions.getPointer = this.getPointer.bind(this); + + this.options = {}; + this.defaultOptions = { + dragNodes: true, + dragView: true, + zoomView: true, + hoverEnabled: false, + showNavigationIcons: false, + tooltip: { + delay: 300, + fontColor: "#000000", + fontSize: 14, // px + fontFace: "verdana", + color: { + border: "#666666", + background: "#FFFFC6" + } + }, + keyboard: { + enabled: false, + speed: { x: 10, y: 10, zoom: 0.02 }, + bindToWindow: true + } + }; + util.extend(this.options, this.defaultOptions); } - _prototypeProperties(View, null, { + _prototypeProperties(InteractionHandler, null, { setOptions: { - value: function setOptions() { - var options = arguments[0] === undefined ? {} : arguments[0]; - this.options = options; + value: function setOptions(options) { + if (options !== undefined) { + // extend all but the values in fields + var fields = ["keyboard", "tooltip"]; + util.selectiveNotDeepExtend(fields, this.options, options); + + // merge the keyboard options in. + util.mergeOptions(this.options, options, "keyboard"); + + if (options.tooltip) { + util.extend(this.options.tooltip, options.tooltip); + if (options.tooltip.color) { + this.options.tooltip.color = util.parseColor(options.tooltip.color); + } + } + } + + this.navigationHandler.setOptions(this.options); }, writable: true, configurable: true }, - _getRange: { + getPointer: { - // zoomExtent /** - * Find the center position of the network + * Get the pointer location from a touch location + * @param {{x: Number, y: Number}} touch + * @return {{x: Number, y: Number}} pointer * @private */ - value: function _getRange() { - var specificNodes = arguments[0] === undefined ? [] : arguments[0]; - var minY = 1000000000, - maxY = -1000000000, - minX = 1000000000, - maxX = -1000000000, - node; - if (specificNodes.length > 0) { - for (var i = 0; i < specificNodes.length; i++) { - node = this.body.nodes[specificNodes[i]]; - if (minX > node.shape.boundingBox.left) { - minX = node.shape.boundingBox.left; - } - if (maxX < node.shape.boundingBox.right) { - maxX = node.shape.boundingBox.right; - } - if (minY > node.shape.boundingBox.bottom) { - minY = node.shape.boundingBox.top; - } // top is negative, bottom is positive - if (maxY < node.shape.boundingBox.top) { - maxY = node.shape.boundingBox.bottom; - } // top is negative, bottom is positive - } - } else { - for (var nodeId in this.body.nodes) { - if (this.body.nodes.hasOwnProperty(nodeId)) { - node = this.body.nodes[nodeId]; - if (minX > node.shape.boundingBox.left) { - minX = node.shape.boundingBox.left; - } - if (maxX < node.shape.boundingBox.right) { - maxX = node.shape.boundingBox.right; - } - if (minY > node.shape.boundingBox.bottom) { - minY = node.shape.boundingBox.top; - } // top is negative, bottom is positive - if (maxY < node.shape.boundingBox.top) { - maxY = node.shape.boundingBox.bottom; - } // top is negative, bottom is positive - } - } - } - - if (minX == 1000000000 && maxX == -1000000000 && minY == 1000000000 && maxY == -1000000000) { - minY = 0, maxY = 0, minX = 0, maxX = 0; - } - return { minX: minX, maxX: maxX, minY: minY, maxY: maxY }; + value: function getPointer(touch) { + return { + x: touch.x - util.getAbsoluteLeft(this.canvas.frame.canvas), + y: touch.y - util.getAbsoluteTop(this.canvas.frame.canvas) + }; }, writable: true, configurable: true }, - _findCenter: { + onTouch: { /** - * @param {object} range = {minX: minX, maxX: maxX, minY: minY, maxY: maxY}; - * @returns {{x: number, y: number}} + * On start of a touch gesture, store the pointer + * @param event * @private */ - value: function _findCenter(range) { - return { x: 0.5 * (range.maxX + range.minX), - y: 0.5 * (range.maxY + range.minY) }; + value: function onTouch(event) { + if (new Date().valueOf() - this.touchTime > 100) { + this.drag.pointer = this.getPointer(event.center); + this.drag.pinched = false; + this.pinch.scale = this.body.view.scale; + // to avoid double fireing of this event because we have two hammer instances. (on canvas and on frame) + this.touchTime = new Date().valueOf(); + } }, writable: true, configurable: true }, - zoomExtent: { - + onTap: { /** - * This function zooms out to fit all data on screen based on amount of nodes - * @param {Object} - * @param {Boolean} [initialZoom] | zoom based on fitted formula or range, true = fitted, default = false; - * @param {Boolean} [disableStart] | If true, start is not called. + * handle tap/click event: select/unselect a node + * @private */ - value: function zoomExtent() { - var options = arguments[0] === undefined ? { nodes: [] } : arguments[0]; - var initialZoom = arguments[1] === undefined ? false : arguments[1]; - var range; - var zoomLevel; - - if (initialZoom === true) { - // check if more than half of the nodes have a predefined position. If so, we use the range, not the approximation. - var positionDefined = 0; - for (var nodeId in this.body.nodes) { - if (this.body.nodes.hasOwnProperty(nodeId)) { - var node = this.body.nodes[nodeId]; - if (node.predefinedPosition == true) { - positionDefined += 1; - } - } - } - if (positionDefined > 0.5 * this.body.nodeIndices.length) { - this.zoomExtent(options, false); - return; - } - - range = this._getRange(options.nodes); - - var numberOfNodes = this.body.nodeIndices.length; - zoomLevel = 12.662 / (numberOfNodes + 7.4147) + 0.0964822; // this is obtained from fitting a dataset from 5 points with scale levels that looked good. + value: function onTap(event) { + var pointer = this.getPointer(event.center); - // correct for larger canvasses. - var factor = Math.min(this.canvas.frame.canvas.clientWidth / 600, this.canvas.frame.canvas.clientHeight / 600); - zoomLevel *= factor; - } else { - this.body.emitter.emit("_redraw", true); - range = this._getRange(options.nodes); - var xDistance = Math.abs(range.maxX - range.minX) * 1.1; - var yDistance = Math.abs(range.maxY - range.minY) * 1.1; + var previouslySelected = this.selectionHandler._getSelectedObjectCount() > 0; + var selected = this.selectionHandler.selectOnPoint(pointer); - var xZoomLevel = this.canvas.frame.canvas.clientWidth / xDistance; - var yZoomLevel = this.canvas.frame.canvas.clientHeight / yDistance; - zoomLevel = xZoomLevel <= yZoomLevel ? xZoomLevel : yZoomLevel; + if (selected === true || previouslySelected == true && selected === false) { + // select or unselect + this.body.emitter.emit("select", this.selectionHandler.getSelection()); } - if (zoomLevel > 1) { - zoomLevel = 1; - } + this.selectionHandler._generateClickEvent("click", pointer); + }, + writable: true, + configurable: true + }, + onDoubleTap: { - var center = this._findCenter(range); - var animationOptions = { position: center, scale: zoomLevel, animation: options }; - this.moveTo(animationOptions); + + /** + * handle doubletap event + * @private + */ + value: function onDoubleTap(event) { + var pointer = this.getPointer(event.center); + this.selectionHandler._generateClickEvent("doubleClick", pointer); }, writable: true, configurable: true }, - focusOnNode: { + onHold: { + - // animation /** - * Center a node in view. - * - * @param {Number} nodeId - * @param {Number} [options] + * handle long tap event: multi select nodes + * @private */ - value: function focusOnNode(nodeId) { - var options = arguments[1] === undefined ? {} : arguments[1]; - if (this.body.nodes[nodeId] !== undefined) { - var nodePosition = { x: this.body.nodes[nodeId].x, y: this.body.nodes[nodeId].y }; - options.position = nodePosition; - options.lockedOnNode = nodeId; + value: function onHold(event) { + var pointer = this.getPointer(event.center); - this.moveTo(options); - } else { - console.log("Node: " + nodeId + " cannot be found."); + var selectionChanged = this.selectionHandler.selectAdditionalOnPoint(pointer); + + if (selectionChanged === true) { + // select or longpress + this.body.emitter.emit("select", this.selectionHandler.getSelection()); } + + this.selectionHandler._generateClickEvent("click", pointer); }, writable: true, configurable: true }, - moveTo: { + onRelease: { + /** + * handle the release of the screen * - * @param {Object} options | options.offset = {x:Number, y:Number} // offset from the center in DOM pixels - * | options.scale = Number // scale to move to - * | options.position = {x:Number, y:Number} // position to move to - * | options.animation = {duration:Number, easingFunction:String} || Boolean // position to move to + * @private */ - value: function moveTo(options) { - if (options === undefined) { - options = {}; - return; - } - if (options.offset === undefined) { - options.offset = { x: 0, y: 0 }; - } - if (options.offset.x === undefined) { - options.offset.x = 0; - } - if (options.offset.y === undefined) { - options.offset.y = 0; - } - if (options.scale === undefined) { - options.scale = this.body.view.scale; - } - if (options.position === undefined) { - options.position = this.body.view.translation; - } - if (options.animation === undefined) { - options.animation = { duration: 0 }; - } - if (options.animation === false) { - options.animation = { duration: 0 }; - } - if (options.animation === true) { - options.animation = {}; - } - if (options.animation.duration === undefined) { - options.animation.duration = 1000; - } // default duration - if (options.animation.easingFunction === undefined) { - options.animation.easingFunction = "easeInOutQuad"; - } // default easing function - - this.animateView(options); + value: function onRelease(event) { + this.body.emitter.emit("release", event); }, writable: true, configurable: true }, - animateView: { + onDragStart: { + /** + * This function is called by onDragStart. + * It is separated out because we can then overload it for the datamanipulation system. * - * @param {Object} options | options.offset = {x:Number, y:Number} // offset from the center in DOM pixels - * | options.time = Number // animation time in milliseconds - * | options.scale = Number // scale to animate to - * | options.position = {x:Number, y:Number} // position to animate to - * | options.easingFunction = String // linear, easeInQuad, easeOutQuad, easeInOutQuad, - * // easeInCubic, easeOutCubic, easeInOutCubic, - * // easeInQuart, easeOutQuart, easeInOutQuart, - * // easeInQuint, easeOutQuint, easeInOutQuint + * @private */ - value: function animateView(options) { - if (options === undefined) { - return; - } - this.animationEasingFunction = options.animation.easingFunction; - // release if something focussed on the node - this.releaseNode(); - if (options.locked == true) { - this.lockedOnNodeId = options.lockedOnNode; - this.lockedOnNodeOffset = options.offset; + value: function onDragStart(event) { + //in case the touch event was triggered on an external div, do the initial touch now. + if (this.drag.pointer === undefined) { + this.onTouch(event); } - // forcefully complete the old animation if it was still running - if (this.easingTime != 0) { - this._transitionRedraw(true); // by setting easingtime to 1, we finish the animation. - } + // note: drag.pointer is set in onTouch to get the initial touch location + var node = this.selectionHandler.getNodeAt(this.drag.pointer); - this.sourceScale = this.body.view.scale; - this.sourceTranslation = this.body.view.translation; - this.targetScale = options.scale; + this.drag.dragging = true; + this.drag.selection = []; + this.drag.translation = util.extend({}, this.body.view.translation); // copy the object + this.drag.nodeId = undefined; - // set the scale so the viewCenter is based on the correct zoom level. This is overridden in the transitionRedraw - // but at least then we'll have the target transition - this.body.view.scale = this.targetScale; - var viewCenter = this.canvas.DOMtoCanvas({ x: 0.5 * this.canvas.frame.canvas.clientWidth, y: 0.5 * this.canvas.frame.canvas.clientHeight }); - var distanceFromCenter = { // offset from view, distance view has to change by these x and y to center the node - x: viewCenter.x - options.position.x, - y: viewCenter.y - options.position.y - }; - this.targetTranslation = { - x: this.sourceTranslation.x + distanceFromCenter.x * this.targetScale + options.offset.x, - y: this.sourceTranslation.y + distanceFromCenter.y * this.targetScale + options.offset.y - }; + this.body.emitter.emit("dragStart", { nodeIds: this.selectionHandler.getSelection().nodes }); - // if the time is set to 0, don't do an animation - if (options.animation.duration == 0) { - if (this.lockedOnNodeId != undefined) { - this.viewFunction = this._lockedRedraw.bind(this); - this.body.emitter.on("initRedraw", this.viewFunction); - } else { - this.body.view.scale = this.targetScale; - this.body.view.translation = this.targetTranslation; - this.body.emitter.emit("_requestRedraw"); + if (node !== undefined && this.options.dragNodes === true) { + this.drag.nodeId = node.id; + // select the clicked node if not yet selected + if (node.isSelected() === false) { + this.selectionHandler.unselectAll(); + this.selectionHandler.selectObject(node); } - } else { - this.animationSpeed = 1 / (60 * options.animation.duration * 0.001) || 1 / 60; // 60 for 60 seconds, 0.001 for milli's - this.animationEasingFunction = options.animation.easingFunction; + var selection = this.selectionHandler.selectionObj.nodes; + // create an array with the selected nodes and their original location and status + for (var nodeId in selection) { + if (selection.hasOwnProperty(nodeId)) { + var object = selection[nodeId]; + var s = { + id: object.id, + node: object, - this.viewFunction = this._transitionRedraw.bind(this); - this.body.emitter.on("initRedraw", this.viewFunction); - this.body.emitter.emit("_startRendering"); + // store original x, y, xFixed and yFixed, make the node temporarily Fixed + x: object.x, + y: object.y, + xFixed: object.options.fixed.x, + yFixed: object.options.fixed.y + }; + + object.options.fixed.x = true; + object.options.fixed.y = true; + + this.drag.selection.push(s); + } + } } }, writable: true, configurable: true }, - _lockedRedraw: { + onDrag: { + /** - * used to animate smoothly by hijacking the redraw function. + * handle drag event * @private */ - value: function _lockedRedraw() { - var nodePosition = { x: this.body.nodes[this.lockedOnNodeId].x, y: this.body.nodes[this.lockedOnNodeId].y }; - var viewCenter = this.DOMtoCanvas({ x: 0.5 * this.frame.canvas.clientWidth, y: 0.5 * this.frame.canvas.clientHeight }); - var distanceFromCenter = { // offset from view, distance view has to change by these x and y to center the node - x: viewCenter.x - nodePosition.x, - y: viewCenter.y - nodePosition.y - }; - var sourceTranslation = this.body.view.translation; - var targetTranslation = { - x: sourceTranslation.x + distanceFromCenter.x * this.body.view.scale + this.lockedOnNodeOffset.x, - y: sourceTranslation.y + distanceFromCenter.y * this.body.view.scale + this.lockedOnNodeOffset.y - }; + value: function onDrag(event) { + var _this = this; + if (this.drag.pinched === true) { + return; + } - this.body.view.translation = targetTranslation; + // remove the focus on node if it is focussed on by the focusOnNode + this.body.emitter.emit("unlockNode"); + + var pointer = this.getPointer(event.center); + var selection = this.drag.selection; + if (selection && selection.length && this.options.dragNodes === true) { + (function () { + // calculate delta's and new location + var deltaX = pointer.x - _this.drag.pointer.x; + var deltaY = pointer.y - _this.drag.pointer.y; + + // update position of all selected nodes + selection.forEach(function (selection) { + var node = selection.node; + // only move the node if it was not fixed initially + if (selection.xFixed === false) { + node.x = _this.canvas._XconvertDOMtoCanvas(_this.canvas._XconvertCanvasToDOM(selection.x) + deltaX); + } + // only move the node if it was not fixed initially + if (selection.yFixed === false) { + node.y = _this.canvas._YconvertDOMtoCanvas(_this.canvas._YconvertCanvasToDOM(selection.y) + deltaY); + } + }); + + // start the simulation of the physics + _this.body.emitter.emit("startSimulation"); + })(); + } else { + // move the network + if (this.options.dragView === true) { + // if the drag was not started properly because the click started outside the network div, start it now. + if (this.drag.pointer === undefined) { + this._handleDragStart(event); + return; + } + var diffX = pointer.x - this.drag.pointer.x; + var diffY = pointer.y - this.drag.pointer.y; + + this.body.view.translation = { x: this.drag.translation.x + diffX, y: this.drag.translation.y + diffY }; + this.body.emitter.emit("_redraw"); + } + } }, writable: true, configurable: true }, - releaseNode: { - value: function releaseNode() { - if (this.lockedOnNodeId !== undefined && this.viewFunction !== undefined) { - this.body.emitter.off("initRedraw", this.viewFunction); - this.lockedOnNodeId = undefined; - this.lockedOnNodeOffset = undefined; + onDragEnd: { + + + /** + * handle drag start event + * @private + */ + value: function onDragEnd(event) { + this.drag.dragging = false; + var selection = this.drag.selection; + if (selection && selection.length) { + selection.forEach(function (s) { + // restore original xFixed and yFixed + s.node.options.fixed.x = s.xFixed; + s.node.options.fixed.y = s.yFixed; + }); + this.body.emitter.emit("startSimulation"); + } else { + this.body.emitter.emit("_requestRedraw"); } + + this.body.emitter.emit("dragEnd", { nodeIds: this.selectionHandler.getSelection().nodes }); }, writable: true, configurable: true }, - _transitionRedraw: { + onPinch: { + + /** - * - * @param easingTime + * Handle pinch event + * @param event * @private */ - value: function _transitionRedraw() { - var finished = arguments[0] === undefined ? false : arguments[0]; - this.easingTime += this.animationSpeed; - this.easingTime = finished === true ? 1 : this.easingTime; - - var progress = util.easingFunctions[this.animationEasingFunction](this.easingTime); + value: function onPinch(event) { + var pointer = this.getPointer(event.center); - this.body.view.scale = this.sourceScale + (this.targetScale - this.sourceScale) * progress; - this.body.view.translation = { - x: this.sourceTranslation.x + (this.targetTranslation.x - this.sourceTranslation.x) * progress, - y: this.sourceTranslation.y + (this.targetTranslation.y - this.sourceTranslation.y) * progress - }; + this.drag.pinched = true; + if (this.pinch.scale === undefined) { + this.pinch.scale = 1; + } - // cleanup - if (this.easingTime >= 1) { - this.body.emitter.off("initRedraw", this.viewFunction); - this.easingTime = 0; - if (this.lockedOnNodeId != undefined) { - this.viewFunction = this._lockedRedraw.bind(this); - this.body.emitter.on("initRedraw", this.viewFunction); - } - this.body.emitter.emit("animationFinished"); - } + // TODO: enabled moving while pinching? + var scale = this.pinch.scale * event.scale; + this.zoom(scale, pointer); }, writable: true, configurable: true - } - }); - - return View; - })(); - - module.exports = View; - -/***/ }, -/* 98 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - - var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; - - var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; - - var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; - - /** - * Created by Alex on 2/27/2015. - * - */ - - var util = __webpack_require__(1); - - var NavigationHandler = _interopRequire(__webpack_require__(99)); - - var Popup = _interopRequire(__webpack_require__(100)); - - var InteractionHandler = (function () { - function InteractionHandler(body, canvas, selectionHandler) { - _classCallCheck(this, InteractionHandler); + }, + zoom: { - this.body = body; - this.canvas = canvas; - this.selectionHandler = selectionHandler; - this.navigationHandler = new NavigationHandler(body, canvas); - // bind the events from hammer to functions in this object - this.body.eventListeners.onTap = this.onTap.bind(this); - this.body.eventListeners.onTouch = this.onTouch.bind(this); - this.body.eventListeners.onDoubleTap = this.onDoubleTap.bind(this); - this.body.eventListeners.onHold = this.onHold.bind(this); - this.body.eventListeners.onDragStart = this.onDragStart.bind(this); - this.body.eventListeners.onDrag = this.onDrag.bind(this); - this.body.eventListeners.onDragEnd = this.onDragEnd.bind(this); - this.body.eventListeners.onMouseWheel = this.onMouseWheel.bind(this); - this.body.eventListeners.onPinch = this.onPinch.bind(this); - this.body.eventListeners.onMouseMove = this.onMouseMove.bind(this); - this.body.eventListeners.onRelease = this.onRelease.bind(this); + /** + * Zoom the network in or out + * @param {Number} scale a number around 1, and between 0.01 and 10 + * @param {{x: Number, y: Number}} pointer Position on screen + * @return {Number} appliedScale scale is limited within the boundaries + * @private + */ + value: function zoom(scale, pointer) { + if (this.options.zoomView === true) { + var scaleOld = this.body.view.scale; + if (scale < 0.00001) { + scale = 0.00001; + } + if (scale > 10) { + scale = 10; + } - this.touchTime = 0; - this.drag = {}; - this.pinch = {}; - this.hoverObj = { nodes: {}, edges: {} }; - this.popup = undefined; - this.popupObj = undefined; - this.popupTimer = undefined; + var preScaleDragPointer = undefined; + if (this.drag !== undefined) { + if (this.drag.dragging === true) { + preScaleDragPointer = this.canvas.DOMtoCanvas(this.drag.pointer); + } + } + // + this.canvas.frame.canvas.clientHeight / 2 + var translation = this.body.view.translation; - this.body.functions.getPointer = this.getPointer.bind(this); + var scaleFrac = scale / scaleOld; + var tx = (1 - scaleFrac) * pointer.x + translation.x * scaleFrac; + var ty = (1 - scaleFrac) * pointer.y + translation.y * scaleFrac; - this.options = {}; - this.defaultOptions = { - dragNodes: true, - dragView: true, - zoomView: true, - hoverEnabled: false, - showNavigationIcons: false, - tooltip: { - delay: 300, - fontColor: "#000000", - fontSize: 14, // px - fontFace: "verdana", - color: { - border: "#666666", - background: "#FFFFC6" - } - }, - keyboard: { - enabled: false, - speed: { x: 10, y: 10, zoom: 0.02 }, - bindToWindow: true - } - }; - util.extend(this.options, this.defaultOptions); - } + this.body.view.scale = scale; + this.body.view.translation = { x: tx, y: ty }; - _prototypeProperties(InteractionHandler, null, { - setOptions: { - value: function setOptions(options) { - if (options !== undefined) { - // extend all but the values in fields - var fields = ["keyboard", "tooltip"]; - util.selectiveNotDeepExtend(fields, this.options, options); + if (preScaleDragPointer != undefined) { + var postScaleDragPointer = this.canvas.canvasToDOM(preScaleDragPointer); + this.drag.pointer.x = postScaleDragPointer.x; + this.drag.pointer.y = postScaleDragPointer.y; + } - // merge the keyboard options in. - util.mergeOptions(this.options, options, "keyboard"); + this.body.emitter.emit("_requestRedraw"); - if (options.tooltip) { - util.extend(this.options.tooltip, options.tooltip); - if (options.tooltip.color) { - this.options.tooltip.color = util.parseColor(options.tooltip.color); - } + if (scaleOld < scale) { + this.body.emitter.emit("zoom", { direction: "+" }); + } else { + this.body.emitter.emit("zoom", { direction: "-" }); } } - - this.navigationHandler.setOptions(this.options); - }, - writable: true, - configurable: true - }, - getPointer: { - - - /** - * Get the pointer location from a touch location - * @param {{x: Number, y: Number}} touch - * @return {{x: Number, y: Number}} pointer - * @private - */ - value: function getPointer(touch) { - return { - x: touch.x - util.getAbsoluteLeft(this.canvas.frame.canvas), - y: touch.y - util.getAbsoluteTop(this.canvas.frame.canvas) - }; }, writable: true, configurable: true }, - onTouch: { + onMouseWheel: { /** - * On start of a touch gesture, store the pointer - * @param event + * Event handler for mouse wheel event, used to zoom the timeline + * See http://adomas.org/javascript-mouse-wheel/ + * https://github.com/EightMedia/hammer.js/issues/256 + * @param {MouseEvent} event * @private */ - value: function onTouch(event) { - if (new Date().valueOf() - this.touchTime > 100) { - this.drag.pointer = this.getPointer(event.center); - this.drag.pinched = false; - this.pinch.scale = this.body.view.scale; - // to avoid double fireing of this event because we have two hammer instances. (on canvas and on frame) - this.touchTime = new Date().valueOf(); + value: function onMouseWheel(event) { + // retrieve delta + var delta = 0; + if (event.wheelDelta) { + /* IE/Opera. */ + delta = event.wheelDelta / 120; + } else if (event.detail) { + /* Mozilla case. */ + // In Mozilla, sign of delta is different than in IE. + // Also, delta is multiple of 3. + delta = -event.detail / 3; } - }, - writable: true, - configurable: true - }, - onTap: { - /** - * handle tap/click event: select/unselect a node - * @private - */ - value: function onTap(event) { - var pointer = this.getPointer(event.center); + // If delta is nonzero, handle it. + // Basically, delta is now positive if wheel was scrolled up, + // and negative, if wheel was scrolled down. + if (delta !== 0) { + // calculate the new scale + var scale = this.body.view.scale; + var zoom = delta / 10; + if (delta < 0) { + zoom = zoom / (1 - zoom); + } + scale *= 1 + zoom; - var previouslySelected = this.selectionHandler._getSelectedObjectCount() > 0; - var selected = this.selectionHandler.selectOnPoint(pointer); + // calculate the pointer location + var pointer = this.getPointer({ x: event.pageX, y: event.pageY }); - if (selected === true || previouslySelected == true && selected === false) { - // select or unselect - this.body.emitter.emit("select", this.selectionHandler.getSelection()); + // apply the new scale + this.zoom(scale, pointer); } - this.selectionHandler._generateClickEvent("click", pointer); + // Prevent default actions caused by mouse wheel. + event.preventDefault(); }, writable: true, configurable: true }, - onDoubleTap: { + onMouseMove: { /** - * handle doubletap event + * Mouse move handler for checking whether the title moves over a node with a title. + * @param {Event} event * @private */ - value: function onDoubleTap(event) { - var pointer = this.getPointer(event.center); - this.selectionHandler._generateClickEvent("doubleClick", pointer); - }, - writable: true, - configurable: true - }, - onHold: { - + value: function onMouseMove(event) { + var _this = this; + var pointer = this.getPointer({ x: event.pageX, y: event.pageY }); + var popupVisible = false; + // check if the previously selected node is still selected + if (this.popup !== undefined) { + if (this.popup.hidden === false) { + this._checkHidePopup(pointer); + } - /** - * handle long tap event: multi select nodes - * @private - */ - value: function onHold(event) { - var pointer = this.getPointer(event.center); + // if the popup was not hidden above + if (this.popup.hidden === false) { + popupVisible = true; + this.popup.setPosition(pointer.x + 3, pointer.y - 5); + this.popup.show(); + } + } - var selectionChanged = this.selectionHandler.selectAdditionalOnPoint(pointer); + // if we bind the keyboard to the div, we have to highlight it to use it. This highlights it on mouse over. + if (this.options.keyboard.bindToWindow == false && this.options.keyboard.enabled === true) { + this.canvas.frame.focus(); + } - if (selectionChanged === true) { - // select or longpress - this.body.emitter.emit("select", this.selectionHandler.getSelection()); + // start a timeout that will check if the mouse is positioned above an element + if (popupVisible === false) { + if (this.popupTimer !== undefined) { + clearInterval(this.popupTimer); // stop any running calculationTimer + this.popupTimer = undefined; + } + if (!this.drag.dragging) { + this.popupTimer = setTimeout(function () { + return _this._checkShowPopup(pointer); + }, this.options.tooltip.delay); + } } - this.selectionHandler._generateClickEvent("click", pointer); - }, - writable: true, - configurable: true - }, - onRelease: { + /** + * Adding hover highlights + */ + if (this.options.hoverEnabled === true) { + // removing all hover highlights + for (var edgeId in this.hoverObj.edges) { + if (this.hoverObj.edges.hasOwnProperty(edgeId)) { + this.hoverObj.edges[edgeId].hover = false; + delete this.hoverObj.edges[edgeId]; + } + } + // adding hover highlights + var obj = this.selectionHandler.getNodeAt(pointer); + if (obj == undefined) { + obj = this.selectionHandler.getEdgeAt(pointer); + } + if (obj != undefined) { + this.selectionHandler.hoverObject(obj); + } - /** - * handle the release of the screen - * - * @private - */ - value: function onRelease(event) { - this.body.emitter.emit("release", event); + // removing all node hover highlights except for the selected one. + for (var nodeId in this.hoverObj.nodes) { + if (this.hoverObj.nodes.hasOwnProperty(nodeId)) { + if (obj instanceof Node && obj.id != nodeId || obj instanceof Edge || obj == undefined) { + this.selectionHandler.blurObject(this.hoverObj.nodes[nodeId]); + delete this.hoverObj.nodes[nodeId]; + } + } + } + this.body.emitter.emit("_requestRedraw"); + } }, writable: true, configurable: true }, - onDragStart: { + _checkShowPopup: { + /** - * This function is called by onDragStart. - * It is separated out because we can then overload it for the datamanipulation system. + * Check if there is an element on the given position in the network + * (a node or edge). If so, and if this element has a title, + * show a popup window with its title. * + * @param {{x:Number, y:Number}} pointer * @private */ - value: function onDragStart(event) { - //in case the touch event was triggered on an external div, do the initial touch now. - if (this.drag.pointer === undefined) { - this.onTouch(event); - } + value: function _checkShowPopup(pointer) { + var x = this.canvas._XconvertDOMtoCanvas(pointer.x); + var y = this.canvas._YconvertDOMtoCanvas(pointer.y); + var pointerObj = { + left: x, + top: y, + right: x, + bottom: y + }; - // note: drag.pointer is set in onTouch to get the initial touch location - var node = this.selectionHandler.getNodeAt(this.drag.pointer); + var previousPopupObjId = this.popupObj === undefined ? "" : this.popupObj.id; + var nodeUnderCursor = false; + var popupType = "node"; - this.drag.dragging = true; - this.drag.selection = []; - this.drag.translation = util.extend({}, this.body.view.translation); // copy the object - this.drag.nodeId = undefined; + // check if a node is under the cursor. + if (this.popupObj === undefined) { + // search the nodes for overlap, select the top one in case of multiple nodes + var nodeIndices = this.body.nodeIndices; + var nodes = this.body.nodes; + var node = undefined; + var overlappingNodes = []; + for (var i = 0; i < nodeIndices.length; i++) { + node = nodes[nodeIndices[i]]; + if (node.isOverlappingWith(pointerObj) === true) { + if (node.getTitle() !== undefined) { + overlappingNodes.push(nodeIndices[i]); + } + } + } - this.body.emitter.emit("dragStart", { nodeIds: this.selectionHandler.getSelection().nodes }); + if (overlappingNodes.length > 0) { + // if there are overlapping nodes, select the last one, this is the one which is drawn on top of the others + this.popupObj = nodes[overlappingNodes[overlappingNodes.length - 1]]; + // if you hover over a node, the title of the edge is not supposed to be shown. + nodeUnderCursor = true; + } + } - if (node !== undefined && this.options.dragNodes === true) { - this.drag.nodeId = node.id; - // select the clicked node if not yet selected - if (node.isSelected() === false) { - this.selectionHandler.unselectAll(); - this.selectionHandler.selectObject(node); + if (this.popupObj === undefined && nodeUnderCursor == false) { + // search the edges for overlap + var edgeIndices = this.body.edgeIndices; + var edges = this.body.edges; + var edge = undefined; + var overlappingEdges = []; + for (var i = 0; i < edgeIndices.length; i++) { + edge = edges[edgeIndices[i]]; + if (edge.isOverlappingWith(pointerObj) === true) { + if (edge.connected === true && edge.getTitle() !== undefined) { + overlappingEdges.push(edgeIndices[i]); + } + } } - var selection = this.selectionHandler.selectionObj.nodes; - // create an array with the selected nodes and their original location and status - for (var nodeId in selection) { - if (selection.hasOwnProperty(nodeId)) { - var object = selection[nodeId]; - var s = { - id: object.id, - node: object, + if (overlappingEdges.length > 0) { + this.popupObj = edges[overlappingEdges[overlappingEdges.length - 1]]; + popupType = "edge"; + } + } - // store original x, y, xFixed and yFixed, make the node temporarily Fixed - x: object.x, - y: object.y, - xFixed: object.options.fixed.x, - yFixed: object.options.fixed.y - }; + if (this.popupObj !== undefined) { + // show popup message window + if (this.popupObj.id != previousPopupObjId) { + if (this.popup === undefined) { + this.popup = new Popup(this.frame, this.options.tooltip); + } - object.options.fixed.x = true; - object.options.fixed.y = true; + this.popup.popupTargetType = popupType; + this.popup.popupTargetId = this.popupObj.id; - this.drag.selection.push(s); - } + // adjust a small offset such that the mouse cursor is located in the + // bottom left location of the popup, and you can easily move over the + // popup area + this.popup.setPosition(pointer.x + 3, pointer.y - 5); + this.popup.setText(this.popupObj.getTitle()); + this.popup.show(); + } + } else { + if (this.popup) { + this.popup.hide(); } } }, writable: true, configurable: true }, - onDrag: { + _checkHidePopup: { /** - * handle drag event + * Check if the popup must be hidden, which is the case when the mouse is no + * longer hovering on the object + * @param {{x:Number, y:Number}} pointer * @private */ - value: function onDrag(event) { - var _this = this; - if (this.drag.pinched === true) { - return; - } - - // remove the focus on node if it is focussed on by the focusOnNode - this.body.emitter.emit("unlockNode"); - - var pointer = this.getPointer(event.center); - var selection = this.drag.selection; - if (selection && selection.length && this.options.dragNodes === true) { - (function () { - // calculate delta's and new location - var deltaX = pointer.x - _this.drag.pointer.x; - var deltaY = pointer.y - _this.drag.pointer.y; + value: function _checkHidePopup(pointer) { + var x = this.canvas._XconvertDOMtoCanvas(pointer.x); + var y = this.canvas._YconvertDOMtoCanvas(pointer.y); + var pointerObj = { + left: x, + top: y, + right: x, + bottom: y + }; - // update position of all selected nodes - selection.forEach(function (selection) { - var node = selection.node; - // only move the node if it was not fixed initially - if (selection.xFixed === false) { - node.x = _this.canvas._XconvertDOMtoCanvas(_this.canvas._XconvertCanvasToDOM(selection.x) + deltaX); - } - // only move the node if it was not fixed initially - if (selection.yFixed === false) { - node.y = _this.canvas._YconvertDOMtoCanvas(_this.canvas._YconvertCanvasToDOM(selection.y) + deltaY); - } - }); + var stillOnObj = false; + if (this.popup.popupTargetType == "node") { + if (this.body.nodes[this.popup.popupTargetId] !== undefined) { + stillOnObj = this.body.nodes[this.popup.popupTargetId].isOverlappingWith(pointerObj); - // start the simulation of the physics - _this.body.emitter.emit("startSimulation"); - })(); + // if the mouse is still one the node, we have to check if it is not also on one that is drawn on top of it. + // we initially only check stillOnObj because this is much faster. + if (stillOnObj === true) { + var overNode = this.selectionHandler.getNodeAt(pointer); + stillOnObj = overNode.id == this.popup.popupTargetId; + } + } } else { - // move the network - if (this.options.dragView === true) { - // if the drag was not started properly because the click started outside the network div, start it now. - if (this.drag.pointer === undefined) { - this._handleDragStart(event); - return; + if (this.selectionHandler.getNodeAt(pointer) === undefined) { + if (this.body.edges[this.popup.popupTargetId] !== undefined) { + stillOnObj = this.body.edges[this.popup.popupTargetId].isOverlappingWith(pointerObj); } - var diffX = pointer.x - this.drag.pointer.x; - var diffY = pointer.y - this.drag.pointer.y; - - this.body.view.translation = { x: this.drag.translation.x + diffX, y: this.drag.translation.y + diffY }; - this.body.emitter.emit("_redraw"); - } - } - }, - writable: true, - configurable: true - }, - onDragEnd: { + } + } - /** - * handle drag start event - * @private - */ - value: function onDragEnd(event) { - this.drag.dragging = false; - var selection = this.drag.selection; - if (selection && selection.length) { - selection.forEach(function (s) { - // restore original xFixed and yFixed - s.node.options.fixed.x = s.xFixed; - s.node.options.fixed.y = s.yFixed; - }); - this.body.emitter.emit("startSimulation"); - } else { - this.body.emitter.emit("_requestRedraw"); + if (stillOnObj === false) { + this.popupObj = undefined; + this.popup.hide(); } - - this.body.emitter.emit("dragEnd", { nodeIds: this.selectionHandler.getSelection().nodes }); }, writable: true, configurable: true - }, - onPinch: { + } + }); + return InteractionHandler; + })(); + module.exports = InteractionHandler; - /** - * Handle pinch event - * @param event - * @private - */ - value: function onPinch(event) { - var pointer = this.getPointer(event.center); +/***/ }, +/* 93 */ +/***/ function(module, exports, __webpack_require__) { - this.drag.pinched = true; - if (this.pinch.scale === undefined) { - this.pinch.scale = 1; - } + "use strict"; - // TODO: enabled moving while pinching? - var scale = this.pinch.scale * event.scale; - this.zoom(scale, pointer); - }, - writable: true, - configurable: true - }, - zoom: { + var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; + var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; - /** - * Zoom the network in or out - * @param {Number} scale a number around 1, and between 0.01 and 10 - * @param {{x: Number, y: Number}} pointer Position on screen - * @return {Number} appliedScale scale is limited within the boundaries - * @private - */ - value: function zoom(scale, pointer) { - if (this.options.zoomView === true) { - var scaleOld = this.body.view.scale; - if (scale < 0.00001) { - scale = 0.00001; - } - if (scale > 10) { - scale = 10; - } + var util = __webpack_require__(1); + var Hammer = __webpack_require__(19); + var hammerUtil = __webpack_require__(24); + var keycharm = __webpack_require__(39); - var preScaleDragPointer = undefined; - if (this.drag !== undefined) { - if (this.drag.dragging === true) { - preScaleDragPointer = this.canvas.DOMtoCanvas(this.drag.pointer); - } - } - // + this.canvas.frame.canvas.clientHeight / 2 - var translation = this.body.view.translation; + var NavigationHandler = (function () { + function NavigationHandler(body, canvas) { + var _this = this; + _classCallCheck(this, NavigationHandler); - var scaleFrac = scale / scaleOld; - var tx = (1 - scaleFrac) * pointer.x + translation.x * scaleFrac; - var ty = (1 - scaleFrac) * pointer.y + translation.y * scaleFrac; + this.body = body; + this.canvas = canvas; - this.body.view.scale = scale; - this.body.view.translation = { x: tx, y: ty }; + this.iconsCreated = false; + this.navigationHammers = []; + this.boundFunctions = {}; + this.touchTime = 0; + this.activated = false; - if (preScaleDragPointer != undefined) { - var postScaleDragPointer = this.canvas.canvasToDOM(preScaleDragPointer); - this.drag.pointer.x = postScaleDragPointer.x; - this.drag.pointer.y = postScaleDragPointer.y; - } - this.body.emitter.emit("_requestRedraw"); + this.body.emitter.on("release", this._stopMovement.bind(this)); + this.body.emitter.on("activate", function () { + _this.activated = true;_this.configureKeyboardBindings(); + }); + this.body.emitter.on("deactivate", function () { + _this.activated = false;_this.configureKeyboardBindings(); + }); + this.body.emitter.on("destroy", function () { + if (_this.keycharm !== undefined) { + _this.keycharm.destroy(); + } + }); - if (scaleOld < scale) { - this.body.emitter.emit("zoom", { direction: "+" }); - } else { - this.body.emitter.emit("zoom", { direction: "-" }); - } + this.options = {}; + } + + _prototypeProperties(NavigationHandler, null, { + setOptions: { + value: function setOptions(options) { + if (options !== undefined) { + this.options = options; + this.create(); } }, writable: true, configurable: true }, - onMouseWheel: { - - - /** - * Event handler for mouse wheel event, used to zoom the timeline - * See http://adomas.org/javascript-mouse-wheel/ - * https://github.com/EightMedia/hammer.js/issues/256 - * @param {MouseEvent} event - * @private - */ - value: function onMouseWheel(event) { - // retrieve delta - var delta = 0; - if (event.wheelDelta) { - /* IE/Opera. */ - delta = event.wheelDelta / 120; - } else if (event.detail) { - /* Mozilla case. */ - // In Mozilla, sign of delta is different than in IE. - // Also, delta is multiple of 3. - delta = -event.detail / 3; + create: { + value: function create() { + if (this.options.showNavigationIcons === true) { + if (this.iconsCreated === false) { + this.loadNavigationElements(); + } + } else if (this.iconsCreated === true) { + this.cleanNavigation(); } - // If delta is nonzero, handle it. - // Basically, delta is now positive if wheel was scrolled up, - // and negative, if wheel was scrolled down. - if (delta !== 0) { - // calculate the new scale - var scale = this.body.view.scale; - var zoom = delta / 10; - if (delta < 0) { - zoom = zoom / (1 - zoom); + this.configureKeyboardBindings(); + }, + writable: true, + configurable: true + }, + cleanNavigation: { + value: function cleanNavigation() { + // clean hammer bindings + if (this.navigationHammers.length != 0) { + for (var i = 0; i < this.navigationHammers.length; i++) { + this.navigationHammers[i].destroy(); } - scale *= 1 + zoom; + this.navigationHammers = []; + } - // calculate the pointer location - var pointer = this.getPointer({ x: event.pageX, y: event.pageY }); + this._navigationReleaseOverload = function () {}; - // apply the new scale - this.zoom(scale, pointer); + // clean up previous navigation items + if (this.navigationDOM && this.navigationDOM.wrapper && this.navigationDOM.wrapper.parentNode) { + this.navigationDOM.wrapper.parentNode.removeChild(this.navigationDOM.wrapper); } - // Prevent default actions caused by mouse wheel. - event.preventDefault(); + this.iconsCreated = false; }, writable: true, configurable: true }, - onMouseMove: { - + loadNavigationElements: { /** - * Mouse move handler for checking whether the title moves over a node with a title. - * @param {Event} event + * Creation of the navigation controls nodes. They are drawn over the rest of the nodes and are not affected by scale and translation + * they have a triggerFunction which is called on click. If the position of the navigation controls is dependent + * on this.frame.canvas.clientWidth or this.frame.canvas.clientHeight, we flag horizontalAlignLeft and verticalAlignTop false. + * This means that the location will be corrected by the _relocateNavigation function on a size change of the canvas. + * * @private */ - value: function onMouseMove(event) { - var _this = this; - var pointer = this.getPointer({ x: event.pageX, y: event.pageY }); - var popupVisible = false; + value: function loadNavigationElements() { + this.cleanNavigation(); - // check if the previously selected node is still selected - if (this.popup !== undefined) { - if (this.popup.hidden === false) { - this._checkHidePopup(pointer); - } + this.navigationDOM = {}; + var navigationDivs = ["up", "down", "left", "right", "zoomIn", "zoomOut", "zoomExtends"]; + var navigationDivActions = ["_moveUp", "_moveDown", "_moveLeft", "_moveRight", "_zoomIn", "_zoomOut", "_zoomExtent"]; - // if the popup was not hidden above - if (this.popup.hidden === false) { - popupVisible = true; - this.popup.setPosition(pointer.x + 3, pointer.y - 5); - this.popup.show(); + this.navigationDOM.wrapper = document.createElement("div"); + this.canvas.frame.appendChild(this.navigationDOM.wrapper); + + for (var i = 0; i < navigationDivs.length; i++) { + this.navigationDOM[navigationDivs[i]] = document.createElement("div"); + this.navigationDOM[navigationDivs[i]].className = "network-navigation " + navigationDivs[i]; + this.navigationDOM.wrapper.appendChild(this.navigationDOM[navigationDivs[i]]); + + var hammer = new Hammer(this.navigationDOM[navigationDivs[i]]); + if (navigationDivActions[i] == "_zoomExtent") { + hammerUtil.onTouch(hammer, this._zoomExtent.bind(this)); + } else { + hammerUtil.onTouch(hammer, this.bindToRedraw.bind(this, navigationDivActions[i])); } - } - // if we bind the keyboard to the div, we have to highlight it to use it. This highlights it on mouse over. - if (this.options.keyboard.bindToWindow == false && this.options.keyboard.enabled === true) { - this.canvas.frame.focus(); + this.navigationHammers.push(hammer); } - // start a timeout that will check if the mouse is positioned above an element - if (popupVisible === false) { - if (this.popupTimer !== undefined) { - clearInterval(this.popupTimer); // stop any running calculationTimer - this.popupTimer = undefined; - } - if (!this.drag.dragging) { - this.popupTimer = setTimeout(function () { - return _this._checkShowPopup(pointer); - }, this.options.tooltip.delay); - } + this.iconsCreated = true; + }, + writable: true, + configurable: true + }, + bindToRedraw: { + value: function bindToRedraw(action) { + if (this.boundFunctions[action] === undefined) { + this.boundFunctions[action] = this[action].bind(this); + this.body.emitter.on("initRedraw", this.boundFunctions[action]); + this.body.emitter.emit("_startRendering"); } - - /** - * Adding hover highlights - */ - if (this.options.hoverEnabled === true) { - // removing all hover highlights - for (var edgeId in this.hoverObj.edges) { - if (this.hoverObj.edges.hasOwnProperty(edgeId)) { - this.hoverObj.edges[edgeId].hover = false; - delete this.hoverObj.edges[edgeId]; - } - } - - // adding hover highlights - var obj = this.selectionHandler.getNodeAt(pointer); - if (obj == undefined) { - obj = this.selectionHandler.getEdgeAt(pointer); - } - if (obj != undefined) { - this.selectionHandler.hoverObject(obj); - } - - // removing all node hover highlights except for the selected one. - for (var nodeId in this.hoverObj.nodes) { - if (this.hoverObj.nodes.hasOwnProperty(nodeId)) { - if (obj instanceof Node && obj.id != nodeId || obj instanceof Edge || obj == undefined) { - this.selectionHandler.blurObject(this.hoverObj.nodes[nodeId]); - delete this.hoverObj.nodes[nodeId]; - } - } - } - this.body.emitter.emit("_requestRedraw"); + }, + writable: true, + configurable: true + }, + unbindFromRedraw: { + value: function unbindFromRedraw(action) { + if (this.boundFunctions[action] !== undefined) { + this.body.emitter.off("initRedraw", this.boundFunctions[action]); + this.body.emitter.emit("_stopRendering"); + delete this.boundFunctions[action]; } }, writable: true, configurable: true }, - _checkShowPopup: { - - + _zoomExtent: { /** - * Check if there is an element on the given position in the network - * (a node or edge). If so, and if this element has a title, - * show a popup window with its title. + * this stops all movement induced by the navigation buttons * - * @param {{x:Number, y:Number}} pointer * @private */ - value: function _checkShowPopup(pointer) { - var x = this.canvas._XconvertDOMtoCanvas(pointer.x); - var y = this.canvas._YconvertDOMtoCanvas(pointer.y); - var pointerObj = { - left: x, - top: y, - right: x, - bottom: y - }; - - var previousPopupObjId = this.popupObj === undefined ? "" : this.popupObj.id; - var nodeUnderCursor = false; - var popupType = "node"; - - // check if a node is under the cursor. - if (this.popupObj === undefined) { - // search the nodes for overlap, select the top one in case of multiple nodes - var nodeIndices = this.body.nodeIndices; - var nodes = this.body.nodes; - var node = undefined; - var overlappingNodes = []; - for (var i = 0; i < nodeIndices.length; i++) { - node = nodes[nodeIndices[i]]; - if (node.isOverlappingWith(pointerObj) === true) { - if (node.getTitle() !== undefined) { - overlappingNodes.push(nodeIndices[i]); - } - } - } - - if (overlappingNodes.length > 0) { - // if there are overlapping nodes, select the last one, this is the one which is drawn on top of the others - this.popupObj = nodes[overlappingNodes[overlappingNodes.length - 1]]; - // if you hover over a node, the title of the edge is not supposed to be shown. - nodeUnderCursor = true; - } - } - - if (this.popupObj === undefined && nodeUnderCursor == false) { - // search the edges for overlap - var edgeIndices = this.body.edgeIndices; - var edges = this.body.edges; - var edge = undefined; - var overlappingEdges = []; - for (var i = 0; i < edgeIndices.length; i++) { - edge = edges[edgeIndices[i]]; - if (edge.isOverlappingWith(pointerObj) === true) { - if (edge.connected === true && edge.getTitle() !== undefined) { - overlappingEdges.push(edgeIndices[i]); - } - } - } - - if (overlappingEdges.length > 0) { - this.popupObj = edges[overlappingEdges[overlappingEdges.length - 1]]; - popupType = "edge"; - } - } - - if (this.popupObj !== undefined) { - // show popup message window - if (this.popupObj.id != previousPopupObjId) { - if (this.popup === undefined) { - this.popup = new Popup(this.frame, this.options.tooltip); - } - - this.popup.popupTargetType = popupType; - this.popup.popupTargetId = this.popupObj.id; - - // adjust a small offset such that the mouse cursor is located in the - // bottom left location of the popup, and you can easily move over the - // popup area - this.popup.setPosition(pointer.x + 3, pointer.y - 5); - this.popup.setText(this.popupObj.getTitle()); - this.popup.show(); - } - } else { - if (this.popup) { - this.popup.hide(); - } + value: function _zoomExtent() { + if (new Date().valueOf() - this.touchTime > 700) { + // TODO: fix ugly hack to avoid hammer's double fireing of event (because we use release?) + this.body.emitter.emit("zoomExtent", { duration: 700 }); + this.touchTime = new Date().valueOf(); } }, writable: true, configurable: true }, - _checkHidePopup: { - + _stopMovement: { /** - * Check if the popup must be hidden, which is the case when the mouse is no - * longer hovering on the object - * @param {{x:Number, y:Number}} pointer + * this stops all movement induced by the navigation buttons + * * @private */ - value: function _checkHidePopup(pointer) { - var x = this.canvas._XconvertDOMtoCanvas(pointer.x); - var y = this.canvas._YconvertDOMtoCanvas(pointer.y); - var pointerObj = { - left: x, - top: y, - right: x, - bottom: y - }; - - var stillOnObj = false; - if (this.popup.popupTargetType == "node") { - if (this.body.nodes[this.popup.popupTargetId] !== undefined) { - stillOnObj = this.body.nodes[this.popup.popupTargetId].isOverlappingWith(pointerObj); - - // if the mouse is still one the node, we have to check if it is not also on one that is drawn on top of it. - // we initially only check stillOnObj because this is much faster. - if (stillOnObj === true) { - var overNode = this.selectionHandler.getNodeAt(pointer); - stillOnObj = overNode.id == this.popup.popupTargetId; - } - } - } else { - if (this.selectionHandler.getNodeAt(pointer) === undefined) { - if (this.body.edges[this.popup.popupTargetId] !== undefined) { - stillOnObj = this.body.edges[this.popup.popupTargetId].isOverlappingWith(pointerObj); - } + value: function _stopMovement() { + for (var boundAction in this.boundFunctions) { + if (this.boundFunctions.hasOwnProperty(boundAction)) { + this.body.emitter.off("initRedraw", this.boundFunctions[boundAction]); + this.body.emitter.emit("_stopRendering"); } } - - - if (stillOnObj === false) { - this.popupObj = undefined; - this.popup.hide(); - } + this.boundFunctions = {}; }, writable: true, configurable: true - } - }); + }, + _moveUp: { + value: function _moveUp() { + this.body.view.translation.y += this.options.keyboard.speed.y; + }, + writable: true, + configurable: true + }, + _moveDown: { + value: function _moveDown() { + this.body.view.translation.y -= this.options.keyboard.speed.y; + }, + writable: true, + configurable: true + }, + _moveLeft: { + value: function _moveLeft() { + this.body.view.translation.x += this.options.keyboard.speed.x; + }, + writable: true, + configurable: true + }, + _moveRight: { + value: function _moveRight() { + this.body.view.translation.x -= this.options.keyboard.speed.x; + }, + writable: true, + configurable: true + }, + _zoomIn: { + value: function _zoomIn() { + this.body.view.scale += this.options.keyboard.speed.zoom; + }, + writable: true, + configurable: true + }, + _zoomOut: { + value: function _zoomOut() { + this.body.view.scale -= this.options.keyboard.speed.zoom; + }, + writable: true, + configurable: true + }, + configureKeyboardBindings: { - return InteractionHandler; - })(); - module.exports = InteractionHandler; - -/***/ }, -/* 99 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - - var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; - - var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; - - var util = __webpack_require__(1); - var Hammer = __webpack_require__(19); - var hammerUtil = __webpack_require__(24); - var keycharm = __webpack_require__(39); - - var NavigationHandler = (function () { - function NavigationHandler(body, canvas) { - var _this = this; - _classCallCheck(this, NavigationHandler); - - this.body = body; - this.canvas = canvas; - - this.iconsCreated = false; - this.navigationHammers = []; - this.boundFunctions = {}; - this.touchTime = 0; - this.activated = false; - - - this.body.emitter.on("release", this._stopMovement.bind(this)); - this.body.emitter.on("activate", function () { - _this.activated = true;_this.configureKeyboardBindings(); - }); - this.body.emitter.on("deactivate", function () { - _this.activated = false;_this.configureKeyboardBindings(); - }); - this.body.emitter.on("destroy", function () { - if (_this.keycharm !== undefined) { - _this.keycharm.destroy(); - } - }); - - this.options = {}; - } - - _prototypeProperties(NavigationHandler, null, { - setOptions: { - value: function setOptions(options) { - if (options !== undefined) { - this.options = options; - this.create(); - } - }, - writable: true, - configurable: true - }, - create: { - value: function create() { - if (this.options.showNavigationIcons === true) { - if (this.iconsCreated === false) { - this.loadNavigationElements(); - } - } else if (this.iconsCreated === true) { - this.cleanNavigation(); - } - - this.configureKeyboardBindings(); - }, - writable: true, - configurable: true - }, - cleanNavigation: { - value: function cleanNavigation() { - // clean hammer bindings - if (this.navigationHammers.length != 0) { - for (var i = 0; i < this.navigationHammers.length; i++) { - this.navigationHammers[i].destroy(); - } - this.navigationHammers = []; - } - - this._navigationReleaseOverload = function () {}; - - // clean up previous navigation items - if (this.navigationDOM && this.navigationDOM.wrapper && this.navigationDOM.wrapper.parentNode) { - this.navigationDOM.wrapper.parentNode.removeChild(this.navigationDOM.wrapper); - } - - this.iconsCreated = false; - }, - writable: true, - configurable: true - }, - loadNavigationElements: { - - /** - * Creation of the navigation controls nodes. They are drawn over the rest of the nodes and are not affected by scale and translation - * they have a triggerFunction which is called on click. If the position of the navigation controls is dependent - * on this.frame.canvas.clientWidth or this.frame.canvas.clientHeight, we flag horizontalAlignLeft and verticalAlignTop false. - * This means that the location will be corrected by the _relocateNavigation function on a size change of the canvas. - * - * @private - */ - value: function loadNavigationElements() { - this.cleanNavigation(); - - this.navigationDOM = {}; - var navigationDivs = ["up", "down", "left", "right", "zoomIn", "zoomOut", "zoomExtends"]; - var navigationDivActions = ["_moveUp", "_moveDown", "_moveLeft", "_moveRight", "_zoomIn", "_zoomOut", "_zoomExtent"]; - - this.navigationDOM.wrapper = document.createElement("div"); - this.canvas.frame.appendChild(this.navigationDOM.wrapper); - - for (var i = 0; i < navigationDivs.length; i++) { - this.navigationDOM[navigationDivs[i]] = document.createElement("div"); - this.navigationDOM[navigationDivs[i]].className = "network-navigation " + navigationDivs[i]; - this.navigationDOM.wrapper.appendChild(this.navigationDOM[navigationDivs[i]]); - - var hammer = new Hammer(this.navigationDOM[navigationDivs[i]]); - if (navigationDivActions[i] == "_zoomExtent") { - hammerUtil.onTouch(hammer, this._zoomExtent.bind(this)); - } else { - hammerUtil.onTouch(hammer, this.bindToRedraw.bind(this, navigationDivActions[i])); - } - - this.navigationHammers.push(hammer); - } - - this.iconsCreated = true; - }, - writable: true, - configurable: true - }, - bindToRedraw: { - value: function bindToRedraw(action) { - if (this.boundFunctions[action] === undefined) { - this.boundFunctions[action] = this[action].bind(this); - this.body.emitter.on("initRedraw", this.boundFunctions[action]); - this.body.emitter.emit("_startRendering"); - } - }, - writable: true, - configurable: true - }, - unbindFromRedraw: { - value: function unbindFromRedraw(action) { - if (this.boundFunctions[action] !== undefined) { - this.body.emitter.off("initRedraw", this.boundFunctions[action]); - this.body.emitter.emit("_stopRendering"); - delete this.boundFunctions[action]; - } - }, - writable: true, - configurable: true - }, - _zoomExtent: { - - /** - * this stops all movement induced by the navigation buttons - * - * @private - */ - value: function _zoomExtent() { - if (new Date().valueOf() - this.touchTime > 700) { - // TODO: fix ugly hack to avoid hammer's double fireing of event (because we use release?) - this.body.emitter.emit("zoomExtent", { duration: 700 }); - this.touchTime = new Date().valueOf(); - } - }, - writable: true, - configurable: true - }, - _stopMovement: { - - /** - * this stops all movement induced by the navigation buttons - * - * @private - */ - value: function _stopMovement() { - for (var boundAction in this.boundFunctions) { - if (this.boundFunctions.hasOwnProperty(boundAction)) { - this.body.emitter.off("initRedraw", this.boundFunctions[boundAction]); - this.body.emitter.emit("_stopRendering"); - } - } - this.boundFunctions = {}; - }, - writable: true, - configurable: true - }, - _moveUp: { - value: function _moveUp() { - this.body.view.translation.y += this.options.keyboard.speed.y; - }, - writable: true, - configurable: true - }, - _moveDown: { - value: function _moveDown() { - this.body.view.translation.y -= this.options.keyboard.speed.y; - }, - writable: true, - configurable: true - }, - _moveLeft: { - value: function _moveLeft() { - this.body.view.translation.x += this.options.keyboard.speed.x; - }, - writable: true, - configurable: true - }, - _moveRight: { - value: function _moveRight() { - this.body.view.translation.x -= this.options.keyboard.speed.x; - }, - writable: true, - configurable: true - }, - _zoomIn: { - value: function _zoomIn() { - this.body.view.scale += this.options.keyboard.speed.zoom; - }, - writable: true, - configurable: true - }, - _zoomOut: { - value: function _zoomOut() { - this.body.view.scale -= this.options.keyboard.speed.zoom; - }, - writable: true, - configurable: true - }, - configureKeyboardBindings: { - - - /** - * bind all keys using keycharm. - */ - value: function configureKeyboardBindings() { - if (this.keycharm !== undefined) { - this.keycharm.destroy(); - } + /** + * bind all keys using keycharm. + */ + value: function configureKeyboardBindings() { + if (this.keycharm !== undefined) { + this.keycharm.destroy(); + } if (this.options.keyboard.enabled === true) { if (this.options.keyboard.bindToWindow === true) { @@ -34058,7 +33101,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = NavigationHandler; /***/ }, -/* 100 */ +/* 94 */ /***/ function(module, exports, __webpack_require__) { "use strict"; @@ -34226,7 +33269,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = Popup; /***/ }, -/* 101 */ +/* 95 */ /***/ function(module, exports, __webpack_require__) { "use strict"; @@ -34239,7 +33282,7 @@ return /******/ (function(modules) { // webpackBootstrap * Created by Alex on 2/27/2015. */ - var Node = __webpack_require__(77); + var Node = __webpack_require__(60); var util = __webpack_require__(1); var SelectionHandler = (function () { @@ -34983,7 +34026,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = SelectionHandler; /***/ }, -/* 102 */ +/* 96 */ /***/ function(module, exports, __webpack_require__) { "use strict"; @@ -35477,7 +34520,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = LayoutEngine; /***/ }, -/* 103 */ +/* 97 */ /***/ function(module, exports, __webpack_require__) { "use strict"; @@ -35489,7 +34532,7 @@ return /******/ (function(modules) { // webpackBootstrap var util = __webpack_require__(1); var Hammer = __webpack_require__(19); var hammerUtil = __webpack_require__(24); - var locales = __webpack_require__(104); + var locales = __webpack_require__(98); /** * clears the toolbar div element of children @@ -36660,7 +35703,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = ManipulationSystem; /***/ }, -/* 104 */ +/* 98 */ /***/ function(module, exports, __webpack_require__) { "use strict"; @@ -36704,7 +35747,7 @@ return /******/ (function(modules) { // webpackBootstrap exports.nl_BE = exports.nl; /***/ }, -/* 105 */ +/* 99 */ /***/ function(module, exports, __webpack_require__) { "use strict"; @@ -36722,7 +35765,7 @@ return /******/ (function(modules) { // webpackBootstrap var util = __webpack_require__(1); - var ColorPicker = _interopRequire(__webpack_require__(106)); + var ColorPicker = _interopRequire(__webpack_require__(100)); var ConfigurationSystem = (function () { function ConfigurationSystem(network) { @@ -36964,497 +36007,1617 @@ return /******/ (function(modules) { // webpackBootstrap util.deepExtend(this.actualOptions.selection, this.network.selectionHandler.selection, true); util.deepExtend(this.actualOptions.renderer, this.network.renderer.selection, true); - if (this.actualOptions.configurationContainer !== undefined) { - this.container = this.actualOptions.configurationContainer; - } else { - this.container = this.network.body.container; - } + if (this.actualOptions.configurationContainer !== undefined) { + this.container = this.actualOptions.configurationContainer; + } else { + this.container = this.network.body.container; + } + + + var config = undefined; + if (this.actualOptions.configure instanceof Array) { + config = this.actualOptions.configure.join(); + } else if (typeof this.actualOptions.configure === "string") { + config = this.actualOptions.configure; + } else if (typeof this.actualOptions.configure === "boolean") { + config = this.actualOptions.configure; + } else { + this._clean(); + throw new Error("the option for configure has to be either a string, boolean or an array. Supplied:" + this.options.configure); + return; + } + this._create(config); + } + }, + writable: true, + configurable: true + }, + _create: { + + /** + * Create all DOM elements + * @param {Boolean | String} config + * @private + */ + value: function _create(config) { + this._clean(); + var counter = 0; + for (var option in this.possibleOptions) { + if (this.possibleOptions.hasOwnProperty(option)) { + if (config === true || config.indexOf(option) !== -1) { + var optionObj = this.possibleOptions[option]; + + // linebreak between categories + if (counter > 0) { + this._makeEntree([]); + } + // a header for the category + this._makeHeader(option); + + // get the suboptions + var path = [option]; + this._handleObject(optionObj, path); + } + counter++; + } + } + this._push(); + + this.colorPicker.insertTo(this.container); + }, + writable: true, + configurable: true + }, + _push: { + + + /** + * draw all DOM elements on the screen + * @private + */ + value: function _push() { + for (var i = 0; i < this.domElements.length; i++) { + this.container.appendChild(this.domElements[i]); + } + }, + writable: true, + configurable: true + }, + _clean: { + + /** + * delete all DOM elements + * @private + */ + value: function _clean() { + for (var i = 0; i < this.domElements.length; i++) { + this.container.removeChild(this.domElements[i]); + } + this.domElements = []; + }, + writable: true, + configurable: true + }, + _getValue: { + + /** + * get the value from the actualOptions if it exists + * @param {array} path | where to look for the actual option + * @returns {*} + * @private + */ + value: function _getValue(path) { + var base = this.actualOptions; + for (var i = 0; i < path.length; i++) { + if (base[path[i]] !== undefined) { + base = base[path[i]]; + } else { + base = undefined; + break; + } + } + return base; + }, + writable: true, + configurable: true + }, + _addToPath: { + + + /** + * Copy the path and add a step. It needs to copy because the path will keep stacking otherwise. + * @param path + * @param newValue + * @returns {Array} + * @private + */ + value: function _addToPath(path, newValue) { + var newPath = []; + for (var i = 0; i < path.length; i++) { + newPath.push(path[i]); + } + newPath.push(newValue); + return newPath; + }, + writable: true, + configurable: true + }, + _makeEntree: { + + /** + * all option elements are wrapped in an entree + * @param path + * @param domElements + * @private + */ + value: function _makeEntree(path) { + for (var _len = arguments.length, domElements = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + domElements[_key - 1] = arguments[_key]; + } + + var entree = document.createElement("div"); + entree.className = "vis-network-configuration entree s" + path.length; + domElements.forEach(function (element) { + entree.appendChild(element); + }); + this.domElements.push(entree); + }, + writable: true, + configurable: true + }, + _makeHeader: { + + /** + * header for major subjects + * @param name + * @private + */ + value: function _makeHeader(name) { + var div = document.createElement("div"); + div.className = "vis-network-configuration header"; + div.innerHTML = name; + this._makeEntree([], div); + }, + writable: true, + configurable: true + }, + _makeLabel: { + + /** + * make a label, if it is an object label, it gets different styling. + * @param name + * @param path + * @param objectLabel + * @returns {HTMLElement} + * @private + */ + value: function _makeLabel(name, path) { + var objectLabel = arguments[2] === undefined ? false : arguments[2]; + var div = document.createElement("div"); + div.className = "vis-network-configuration label s" + path.length; + if (objectLabel === true) { + div.innerHTML = "" + name + ":"; + } else { + div.innerHTML = name + ":"; + } + return div; + }, + writable: true, + configurable: true + }, + _makeDropdown: { + + + /** + * make a dropdown list for multiple possible string optoins + * @param arr + * @param value + * @param path + * @private + */ + value: function _makeDropdown(arr, value, path) { + var select = document.createElement("select"); + select.className = "vis-network-configuration select"; + var selectedValue = 0; + if (value !== undefined) { + if (arr.indexOf(value) !== -1) { + selectedValue = arr.indexOf(value); + } + } + + for (var i = 0; i < arr.length; i++) { + var option = document.createElement("option"); + option.value = arr[i]; + if (i == selectedValue) { + option.selected = "selected"; + } + option.innerHTML = arr[i]; + select.appendChild(option); + } + + var me = this; + select.onchange = function () { + me._update(this.value, path); + }; + + var label = this._makeLabel(path[path.length - 1], path); + this._makeEntree(path, label, select); + }, + writable: true, + configurable: true + }, + _makeRange: { + + + /** + * make a range object for numeric options + * @param arr + * @param value + * @param path + * @private + */ + value: function _makeRange(arr, value, path) { + var defaultValue = arr[0]; + var min = arr[1]; + var max = arr[2]; + var step = arr[3]; + var range = document.createElement("input"); + range.type = "range"; + range.className = "vis-network-configuration range"; + range.min = min; + range.max = max; + range.step = step; + + if (value !== undefined) { + if (value * 0.1 < min) { + range.min = value / 10; + } + if (value * 2 > max && max !== 1) { + range.max = value * 2; + } + range.value = value; + } else { + range.value = defaultValue; + } + var input = document.createElement("input"); + input.className = "vis-network-configuration rangeinput"; + input.value = range.value; + + var me = this; + range.onchange = function () { + input.value = this.value;me._update(this.value, path); + }; + range.oninput = function () { + input.value = this.value; + }; + + var label = this._makeLabel(path[path.length - 1], path); + this._makeEntree(path, label, range, input); + }, + writable: true, + configurable: true + }, + _makeCheckbox: { + + + /** + * make a checkbox for boolean options. + * @param defaultValue + * @param value + * @param path + * @private + */ + value: function _makeCheckbox(defaultValue, value, path) { + var checkbox = document.createElement("input"); + checkbox.type = "checkbox"; + checkbox.className = "vis-network-configuration checkbox"; + checkbox.checked = defaultValue; + if (value !== undefined) { + checkbox.checked = value; + } + + var me = this; + checkbox.onchange = function () { + me._update(this.checked, path); + }; + + var label = this._makeLabel(path[path.length - 1], path); + this._makeEntree(path, label, checkbox); + }, + writable: true, + configurable: true + }, + _makeColorField: { + + + /** + * make a color field with a color picker for color fields + * @param arr + * @param value + * @param path + * @private + */ + value: function _makeColorField(arr, value, path) { + var _this = this; + var defaultColor = arr[1]; + var div = document.createElement("div"); + value = value === undefined ? defaultColor : value; + + if (value !== "none") { + div.className = "vis-network-configuration colorBlock"; + div.style.backgroundColor = value; + } else { + div.className = "vis-network-configuration colorBlock none"; + } + + value = value === undefined ? defaultColor : value; + div.onclick = function (event) { + _this._showColorPicker(event, value, div, path); + }; + + var label = this._makeLabel(path[path.length - 1], path); + this._makeEntree(path, label, div); + }, + writable: true, + configurable: true + }, + _showColorPicker: { + + + /** + * used by the color buttons to call the color picker. + * @param event + * @param value + * @param div + * @param path + * @private + */ + value: function _showColorPicker(event, value, div, path) { + var _this = this; + this.colorPicker.show(event.pageX, event.pageY); + this.colorPicker.setColor(value); + this.colorPicker.setCallback(function (color) { + var colorString = "rgba(" + color.r + "," + color.g + "," + color.b + "," + color.a + ")"; + div.style.backgroundColor = colorString; + _this._update(colorString, path); + }); + }, + writable: true, + configurable: true + }, + _handleObject: { + + + /** + * parse an object and draw the correct entrees + * @param obj + * @param path + * @private + */ + value: function _handleObject(obj) { + var path = arguments[1] === undefined ? [] : arguments[1]; + for (var subObj in obj) { + if (obj.hasOwnProperty(subObj)) { + var item = obj[subObj]; + var newPath = this._addToPath(path, subObj); + var value = this._getValue(newPath); + + if (item instanceof Array) { + this._handleArray(subObj, item, value, newPath); + } else if (typeof item === "string") { + this._handleString(subObj, item, value, newPath); + } else if (typeof item === "boolean") { + this._makeCheckbox(item, value, newPath); + } else if (item instanceof Object) { + var label = this._makeLabel(subObj, newPath, true); + this._makeEntree(newPath, label); + this._handleObject(item, newPath); + } else { + console.error("dont know how to handle", item, subObj, newPath); + } + } + } + }, + writable: true, + configurable: true + }, + _handleArray: { + + + /** + * handle the array type of option + * @param optionName + * @param arr + * @param value + * @param path + * @private + */ + value: function _handleArray(optionName, arr, value, path) { + if (typeof arr[0] === "string" && arr[0] === "color") { + this._makeColorField(arr, value, path); + } else if (typeof arr[0] === "string") { + this._makeDropdown(arr, value, path); + } else if (typeof arr[0] === "number") { + this._makeRange(arr, value, path); + } + }, + writable: true, + configurable: true + }, + _handleString: { + + + /** + * handle the string type of option. + * TODO: Not sure what to do with this + * @param optionName + * @param string + * @param value + * @param path + * @private + */ + value: function _handleString(optionName, string, value, path) { + if (string === "string") {} else {} + }, + writable: true, + configurable: true + }, + _update: { + + + /** + * called to update the network with the new settings. + * @param value + * @param path + * @private + */ + value: function _update(value, path) { + var options = {}; + var pointer = options; + for (var i = 0; i < path.length; i++) { + pointer[path[i]] = {}; + if (i !== path.length - 1) { + pointer = pointer[path[i]]; + } else { + pointer[path[i]] = value; + } + } + console.log(JSON.stringify(options)); + this.network.setOptions(options); + }, + writable: true, + configurable: true + } + }); + + return ConfigurationSystem; + })(); + + module.exports = ConfigurationSystem; + //this._makeLabel(optionName, path); + //console.log('string', string, value, path); + +/***/ }, +/* 100 */ +/***/ function(module, exports, __webpack_require__) { + + "use strict"; + + var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; + + var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; + + /** + * Created by Alex on 3/27/2015. + */ + + var Hammer = __webpack_require__(19); + var hammerUtil = __webpack_require__(24); + var util = __webpack_require__(1); + + var ColorPicker = (function () { + function ColorPicker() { + var pixelRatio = arguments[0] === undefined ? 1 : arguments[0]; + _classCallCheck(this, ColorPicker); + + this.pixelRatio = pixelRatio; + this.generated = false; + this.centerCoordinates = { x: 289 / 2, y: 289 / 2 }; + this.r = 289 * 0.49; + this.color = { r: 255, g: 255, b: 255, a: 1 }; + this.hueCircle = undefined; + this.initialColor = { r: 255, g: 255, b: 255, a: 1 }; + this.previousColor = undefined; + this.applied = false; + + // bound by + this.updateCallback = function () {}; + + // create all DOM elements + this._create(); + } + + _prototypeProperties(ColorPicker, null, { + insertTo: { + + + /** + * this inserts the colorPicker into a div from the DOM + * @param container + */ + value: function insertTo(container) { + if (this.hammer !== undefined) { + this.hammer.destroy(); + this.hammer = undefined; + } + this.container = container; + this.container.appendChild(this.frame); + this._bindHammer(); + + this._setSize(); + }, + writable: true, + configurable: true + }, + setCallback: { + + /** + * the callback is executed on apply and save. Bind it to the application + * @param callback + */ + value: function setCallback(callback) { + if (typeof callback === "function") { + this.updateCallback = callback; + } else { + throw new Error("Function attempted to set as colorPicker callback is not a function."); + } + }, + writable: true, + configurable: true + }, + setColor: { + + + /** + * Set the color of the colorPicker + * Supported formats: + * '#ffffff' --> hex string + * 'rbg(255,255,255)' --> rgb string + * 'rgba(255,255,255,1.0)' --> rgba string + * {r:255,g:255,b:255} --> rgb object + * {r:255,g:255,b:255,a:1.0} --> rgba object + * @param color + * @param setInitial + */ + value: function setColor(color) { + var setInitial = arguments[1] === undefined ? true : arguments[1]; + if (color === "none") { + return; + } + + var rgba = undefined; + + // check format + if (util.isString(color) === true) { + if (util.isValidRGB(color) === true) { + var rgbaArray = color.substr(4).substr(0, color.length - 5).split(","); + rgba = { r: rgbaArray[0], g: rgbaArray[1], b: rgbaArray[2], a: 1 }; + } else if (util.isValidRGBA(color) === true) { + var rgbaArray = color.substr(5).substr(0, color.length - 6).split(","); + rgba = { r: rgbaArray[0], g: rgbaArray[1], b: rgbaArray[2], a: rgbaArray[3] }; + } else if (util.isValidHex(color) === true) { + var rgbObj = util.hexToRGB(color); + rgba = { r: rgbObj.r, g: rgbObj.g, b: rgbObj.b, a: 1 }; + } + } else { + if (color instanceof Object) { + if (color.r !== undefined && color.g !== undefined && color.b !== undefined) { + var alpha = color.a !== undefined ? color.a : "1.0"; + rgba = { r: color.r, g: color.g, b: color.b, a: alpha }; + } + } + } + + // set color + if (rgba === undefined) { + throw new Error("Unknown color passed to the colorPicker. Supported are strings: rgb, hex, rgba. Object: rgb ({r:r,g:g,b:b,[a:a]}). Supplied: " + JSON.stringify(color)); + } else { + this._setColor(rgba, setInitial); + } + }, + writable: true, + configurable: true + }, + show: { + + + /** + * this shows the color picker at a location. The hue circle is constructed once and stored. + * @param x + * @param y + */ + value: function show(x, y) { + this.applied = false; + this.frame.style.display = "block"; + this.frame.style.top = y + "px"; + this.frame.style.left = x + "px"; + this._generateHueCircle(); + }, + writable: true, + configurable: true + }, + _hide: { + + + // ------------------------------------------ PRIVATE ----------------------------- // + + /** + * Hide the picker. Is called by the cancel button. + * Optional boolean to store the previous color for easy access later on. + * @param storePrevious + * @private + */ + value: function _hide() { + var storePrevious = arguments[0] === undefined ? true : arguments[0]; + // store the previous color for next time; + if (storePrevious === true) { + this.previousColor = util.extend({}, this.color); + } + + if (this.applied === true) { + this.updateCallback(this.initialColor); + } + + this.frame.style.display = "none"; + }, + writable: true, + configurable: true + }, + _save: { + + + /** + * bound to the save button. Saves and hides. + * @private + */ + value: function _save() { + this.updateCallback(this.color); + this.applied = false; + this.hide(); + }, + writable: true, + configurable: true + }, + _apply: { + + + /** + * Bound to apply button. Saves but does not close. Is undone by the cancel button. + * @private + */ + value: function _apply() { + this.applied = true; + this.updateCallback(this.color); + this._updatePicker(this.color); + }, + writable: true, + configurable: true + }, + _loadLast: { + + + /** + * load the color from the previous session. + * @private + */ + value: function _loadLast() { + if (this.previousColor !== undefined) { + this.setColor(this.previousColor, false); + } else { + alert("There is no last color to load..."); + } + }, + writable: true, + configurable: true + }, + _setColor: { + + + /** + * set the color, place the picker + * @param rgba + * @param setInitial + * @private + */ + value: function _setColor(rgba) { + var setInitial = arguments[1] === undefined ? true : arguments[1]; + // store the initial color + if (setInitial === true) { + console.log("here"); + this.initialColor = util.extend({}, rgba); + } + + this.color = rgba; + var hsv = util.RGBToHSV(rgba.r, rgba.g, rgba.b); + + var angleConvert = 2 * Math.PI; + var radius = this.r * hsv.s; + var x = this.centerCoordinates.x + radius * Math.sin(angleConvert * hsv.h); + var y = this.centerCoordinates.y + radius * Math.cos(angleConvert * hsv.h); + + this.colorPickerSelector.style.left = x - 0.5 * this.colorPickerSelector.clientWidth + "px"; + this.colorPickerSelector.style.top = y - 0.5 * this.colorPickerSelector.clientHeight + "px"; + + this._updatePicker(rgba); + }, + writable: true, + configurable: true + }, + _setOpacity: { + + + /** + * bound to opacity control + * @param value + * @private + */ + value: function _setOpacity(value) { + this.color.a = value / 100; + this._updatePicker(this.color); + }, + writable: true, + configurable: true + }, + _setBrightness: { + + + /** + * bound to brightness control + * @param value + * @private + */ + value: function _setBrightness(value) { + var hsv = util.RGBToHSV(this.color.r, this.color.g, this.color.b); + hsv.v = value / 100; + var rgba = util.HSVToRGB(hsv.h, hsv.s, hsv.v); + rgba.a = this.color.a; + this.color = rgba; + this._updatePicker(); + }, + writable: true, + configurable: true + }, + _updatePicker: { + + + /** + * update the colorpicker. A black circle overlays the hue circle to mimic the brightness decreasing. + * @param rgba + * @private + */ + value: function _updatePicker() { + var rgba = arguments[0] === undefined ? this.color : arguments[0]; + var hsv = util.RGBToHSV(rgba.r, rgba.g, rgba.b); + var ctx = this.colorPickerCanvas.getContext("2d"); + if (this.pixelRation === undefined) { + this.pixelRatio = (window.devicePixelRatio || 1) / (ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1); + } + ctx.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0); + + // clear the canvas + var w = this.colorPickerCanvas.clientWidth; + var h = this.colorPickerCanvas.clientHeight; + ctx.clearRect(0, 0, w, h); + ctx.putImageData(this.hueCircle, 0, 0); + ctx.fillStyle = "rgba(0,0,0," + (1 - hsv.v) + ")"; + ctx.circle(this.centerCoordinates.x, this.centerCoordinates.y, this.r); + ctx.fill(); - var config = undefined; - if (this.actualOptions.configure instanceof Array) { - config = this.actualOptions.configure.join(); - } else if (typeof this.actualOptions.configure === "string") { - config = this.actualOptions.configure; - } else if (typeof this.actualOptions.configure === "boolean") { - config = this.actualOptions.configure; - } else { - this._clean(); - throw new Error("the option for configure has to be either a string, boolean or an array. Supplied:" + this.options.configure); - return; - } - this._create(config); - } + this.brightnessRange.value = 100 * hsv.v; + this.opacityRange.value = 100 * rgba.a; + + this.initialColorDiv.style.backgroundColor = "rgba(" + this.initialColor.r + "," + this.initialColor.g + "," + this.initialColor.b + "," + this.initialColor.a + ")"; + this.newColorDiv.style.backgroundColor = "rgba(" + this.color.r + "," + this.color.g + "," + this.color.b + "," + this.color.a + ")"; }, writable: true, configurable: true }, - _create: { + _setSize: { + /** - * Create all DOM elements - * @param {Boolean | String} config + * used by create to set the size of the canvas. * @private */ - value: function _create(config) { - this._clean(); - var counter = 0; - for (var option in this.possibleOptions) { - if (this.possibleOptions.hasOwnProperty(option)) { - if (config === true || config.indexOf(option) !== -1) { - var optionObj = this.possibleOptions[option]; - - // linebreak between categories - if (counter > 0) { - this._makeEntree([]); - } - // a header for the category - this._makeHeader(option); - - // get the suboptions - var path = [option]; - this._handleObject(optionObj, path); - } - counter++; - } - } - this._push(); + value: function _setSize() { + this.colorPickerCanvas.style.width = "100%"; + this.colorPickerCanvas.style.height = "100%"; - this.colorPicker.insertTo(this.container); + this.colorPickerCanvas.width = 289 * this.pixelRatio; + this.colorPickerCanvas.height = 289 * this.pixelRatio; }, writable: true, configurable: true }, - _push: { + _create: { /** - * draw all DOM elements on the screen + * create all dom elements + * TODO: cleanup, lots of similar dom elements * @private */ - value: function _push() { - for (var i = 0; i < this.domElements.length; i++) { - this.container.appendChild(this.domElements[i]); + value: function _create() { + var visPrefix = "vis-network-"; + + this.frame = document.createElement("div"); + this.frame.className = visPrefix + "colorPicker-frame"; + + this.colorPickerDiv = document.createElement("div"); + this.colorPickerSelector = document.createElement("div"); + this.colorPickerSelector.className = visPrefix + "colorPicker-selector"; + this.colorPickerDiv.appendChild(this.colorPickerSelector); + + this.colorPickerCanvas = document.createElement("canvas"); + this.colorPickerDiv.appendChild(this.colorPickerCanvas); + + if (!this.colorPickerCanvas.getContext) { + var noCanvas = document.createElement("DIV"); + noCanvas.style.color = "red"; + noCanvas.style.fontWeight = "bold"; + noCanvas.style.padding = "10px"; + noCanvas.innerHTML = "Error: your browser does not support HTML canvas"; + this.colorPickerCanvas.appendChild(noCanvas); + } else { + var ctx = this.colorPickerCanvas.getContext("2d"); + this.pixelRatio = (window.devicePixelRatio || 1) / (ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1); + + this.colorPickerCanvas.getContext("2d").setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0); } + + this.colorPickerDiv.className = visPrefix + "colorPicker-color"; + + this.opacityDiv = document.createElement("div"); + this.opacityDiv.className = visPrefix + "colorPicker-opacity"; + + this.brightnessDiv = document.createElement("div"); + this.brightnessDiv.className = visPrefix + "colorPicker-brightness"; + + this.opacityRange = document.createElement("input"); + this.opacityRange.type = "range"; + this.opacityRange.min = "0"; + this.opacityRange.max = "100"; + this.opacityRange.value = "100"; + this.opacityRange.className = visPrefix + "configuration range colorPicker"; + + this.brightnessRange = document.createElement("input"); + this.brightnessRange.type = "range"; + this.brightnessRange.min = "0"; + this.brightnessRange.max = "100"; + this.brightnessRange.value = "100"; + this.brightnessRange.className = visPrefix + "configuration range colorPicker"; + + this.opacityDiv.appendChild(this.opacityRange); + this.brightnessDiv.appendChild(this.brightnessRange); + + var me = this; + this.opacityRange.onchange = function () { + me._setOpacity(this.value); + }; + this.opacityRange.oninput = function () { + me._setOpacity(this.value); + }; + this.brightnessRange.onchange = function () { + me._setBrightness(this.value); + }; + this.brightnessRange.oninput = function () { + me._setBrightness(this.value); + }; + + this.brightnessLabel = document.createElement("div"); + this.brightnessLabel.className = visPrefix + "colorPicker-label brightness"; + this.brightnessLabel.innerHTML = "brightness:"; + + this.opacityLabel = document.createElement("div"); + this.opacityLabel.className = visPrefix + "colorPicker-label opacity"; + this.opacityLabel.innerHTML = "opacity:"; + + this.newColorDiv = document.createElement("div"); + this.newColorDiv.className = visPrefix + "colorPicker-newColor"; + this.newColorDiv.innerHTML = "new"; + + this.initialColorDiv = document.createElement("div"); + this.initialColorDiv.className = visPrefix + "colorPicker-initialColor"; + this.initialColorDiv.innerHTML = "initial"; + + this.cancelButton = document.createElement("div"); + this.cancelButton.className = visPrefix + "colorPicker-button cancel"; + this.cancelButton.innerHTML = "cancel"; + this.cancelButton.onclick = this._hide.bind(this, false); + + this.applyButton = document.createElement("div"); + this.applyButton.className = visPrefix + "colorPicker-button apply"; + this.applyButton.innerHTML = "apply"; + this.applyButton.onclick = this._apply.bind(this); + + this.saveButton = document.createElement("div"); + this.saveButton.className = visPrefix + "colorPicker-button save"; + this.saveButton.innerHTML = "save"; + this.saveButton.onclick = this._save.bind(this); + + this.loadButton = document.createElement("div"); + this.loadButton.className = visPrefix + "colorPicker-button load"; + this.loadButton.innerHTML = "load last"; + this.loadButton.onclick = this._loadLast.bind(this); + + this.frame.appendChild(this.colorPickerDiv); + this.frame.appendChild(this.brightnessLabel); + this.frame.appendChild(this.brightnessDiv); + this.frame.appendChild(this.opacityLabel); + this.frame.appendChild(this.opacityDiv); + this.frame.appendChild(this.newColorDiv); + this.frame.appendChild(this.initialColorDiv); + + this.frame.appendChild(this.cancelButton); + this.frame.appendChild(this.applyButton); + this.frame.appendChild(this.saveButton); + this.frame.appendChild(this.loadButton); }, writable: true, configurable: true }, - _clean: { + _bindHammer: { + /** - * delete all DOM elements + * bind hammer to the color picker * @private */ - value: function _clean() { - for (var i = 0; i < this.domElements.length; i++) { - this.container.removeChild(this.domElements[i]); - } - this.domElements = []; + value: function _bindHammer() { + var _this = this; + this.drag = {}; + this.pinch = {}; + this.hammer = new Hammer(this.colorPickerCanvas); + this.hammer.get("pinch").set({ enable: true }); + + hammerUtil.onTouch(this.hammer, function (event) { + _this._moveSelector(event); + }); + this.hammer.on("tap", function (event) { + _this._moveSelector(event); + }); + this.hammer.on("panstart", function (event) { + _this._moveSelector(event); + }); + this.hammer.on("panmove", function (event) { + _this._moveSelector(event); + }); + this.hammer.on("panend", function (event) { + _this._moveSelector(event); + }); }, writable: true, configurable: true }, - _getValue: { + _generateHueCircle: { + /** - * get the value from the actualOptions if it exists - * @param {array} path | where to look for the actual option - * @returns {*} + * generate the hue circle. This is relatively heavy (200ms) and is done only once on the first time it is shown. * @private */ - value: function _getValue(path) { - var base = this.actualOptions; - for (var i = 0; i < path.length; i++) { - if (base[path[i]] !== undefined) { - base = base[path[i]]; - } else { - base = undefined; - break; + value: function _generateHueCircle() { + if (this.generated === false) { + var ctx = this.colorPickerCanvas.getContext("2d"); + if (this.pixelRation === undefined) { + this.pixelRatio = (window.devicePixelRatio || 1) / (ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1); + } + ctx.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0); + + // clear the canvas + var w = this.colorPickerCanvas.clientWidth; + var h = this.colorPickerCanvas.clientHeight; + ctx.clearRect(0, 0, w, h); + + + // draw hue circle + var x = undefined, + y = undefined, + hue = undefined, + sat = undefined; + this.centerCoordinates = { x: w * 0.5, y: h * 0.5 }; + this.r = 0.49 * w; + var angleConvert = 2 * Math.PI / 360; + var hfac = 1 / 360; + var sfac = 1 / this.r; + var rgb = undefined; + for (hue = 0; hue < 360; hue++) { + for (sat = 0; sat < this.r; sat++) { + x = this.centerCoordinates.x + sat * Math.sin(angleConvert * hue); + y = this.centerCoordinates.y + sat * Math.cos(angleConvert * hue); + rgb = util.HSVToRGB(hue * hfac, sat * sfac, 1); + ctx.fillStyle = "rgb(" + rgb.r + "," + rgb.g + "," + rgb.b + ")"; + ctx.fillRect(x - 0.5, y - 0.5, 2, 2); + } } + ctx.strokeStyle = "rgba(0,0,0,1)"; + ctx.circle(this.centerCoordinates.x, this.centerCoordinates.y, this.r); + ctx.stroke(); + + this.hueCircle = ctx.getImageData(0, 0, w, h); } - return base; + this.generated = true; }, writable: true, configurable: true }, - _addToPath: { + _moveSelector: { /** - * Copy the path and add a step. It needs to copy because the path will keep stacking otherwise. - * @param path - * @param newValue - * @returns {Array} + * move the selector. This is called by hammer functions. + * + * @param event * @private */ - value: function _addToPath(path, newValue) { - var newPath = []; - for (var i = 0; i < path.length; i++) { - newPath.push(path[i]); - } - newPath.push(newValue); - return newPath; - }, - writable: true, - configurable: true - }, - _makeEntree: { + value: function _moveSelector(event) { + var rect = this.colorPickerDiv.getBoundingClientRect(); + var left = event.center.x - rect.left; + var top = event.center.y - rect.top; - /** - * all option elements are wrapped in an entree - * @param path - * @param domElements - * @private - */ - value: function _makeEntree(path) { - for (var _len = arguments.length, domElements = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { - domElements[_key - 1] = arguments[_key]; - } + var centerY = 0.5 * this.colorPickerDiv.clientHeight; + var centerX = 0.5 * this.colorPickerDiv.clientWidth; - var entree = document.createElement("div"); - entree.className = "vis-network-configuration entree s" + path.length; - domElements.forEach(function (element) { - entree.appendChild(element); - }); - this.domElements.push(entree); + var x = left - centerX; + var y = top - centerY; + + var angle = Math.atan2(x, y); + var radius = 0.98 * Math.min(Math.sqrt(x * x + y * y), centerX); + + var newTop = Math.cos(angle) * radius + centerY; + var newLeft = Math.sin(angle) * radius + centerX; + + this.colorPickerSelector.style.top = newTop - 0.5 * this.colorPickerSelector.clientHeight + "px"; + this.colorPickerSelector.style.left = newLeft - 0.5 * this.colorPickerSelector.clientWidth + "px"; + + // set color + var h = angle / (2 * Math.PI); + h = h < 0 ? h + 1 : h; + var s = radius / this.r; + var hsv = util.RGBToHSV(this.color.r, this.color.g, this.color.b); + hsv.h = h; + hsv.s = s; + var rgba = util.HSVToRGB(hsv.h, hsv.s, hsv.v); + rgba.a = this.color.a; + this.color = rgba; + + // update previews + this.initialColorDiv.style.backgroundColor = "rgba(" + this.initialColor.r + "," + this.initialColor.g + "," + this.initialColor.b + "," + this.initialColor.a + ")"; + this.newColorDiv.style.backgroundColor = "rgba(" + this.color.r + "," + this.color.g + "," + this.color.b + "," + this.color.a + ")"; }, writable: true, configurable: true - }, - _makeHeader: { + } + }); - /** - * header for major subjects - * @param name - * @private - */ - value: function _makeHeader(name) { - var div = document.createElement("div"); - div.className = "vis-network-configuration header"; - div.innerHTML = name; - this._makeEntree([], div); + return ColorPicker; + })(); + + module.exports = ColorPicker; + +/***/ }, +/* 101 */ +/***/ function(module, exports, __webpack_require__) { + + "use strict"; + + var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; + + var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; + + /** + * Created by Alex on 2/23/2015. + */ + + var BarnesHutSolver = (function () { + function BarnesHutSolver(body, physicsBody, options) { + _classCallCheck(this, BarnesHutSolver); + + this.body = body; + this.physicsBody = physicsBody; + this.barnesHutTree; + this.setOptions(options); + } + + _prototypeProperties(BarnesHutSolver, null, { + setOptions: { + value: function setOptions(options) { + this.options = options; + this.thetaInversed = 1 / this.options.theta; }, writable: true, configurable: true }, - _makeLabel: { + solve: { + /** - * make a label, if it is an object label, it gets different styling. - * @param name - * @param path - * @param objectLabel - * @returns {HTMLElement} + * 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 _makeLabel(name, path) { - var objectLabel = arguments[2] === undefined ? false : arguments[2]; - var div = document.createElement("div"); - div.className = "vis-network-configuration label s" + path.length; - if (objectLabel === true) { - div.innerHTML = "" + name + ":"; - } else { - div.innerHTML = name + ":"; + value: function solve() { + if (this.options.gravitationalConstant != 0) { + var node; + 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); + } + } } - return div; }, writable: true, configurable: true }, - _makeDropdown: { + _getForceContribution: { /** - * make a dropdown list for multiple possible string optoins - * @param arr - * @param value - * @param path + * 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 _makeDropdown(arr, value, path) { - var select = document.createElement("select"); - select.className = "vis-network-configuration select"; - var selectedValue = 0; - if (value !== undefined) { - if (arr.indexOf(value) !== -1) { - selectedValue = arr.indexOf(value); - } - } + value: function _getForceContribution(parentBranch, node) { + // we get no force contribution from an empty region + if (parentBranch.childrenCount > 0) { + var dx, dy, distance; - for (var i = 0; i < arr.length; i++) { - var option = document.createElement("option"); - option.value = arr[i]; - if (i == selectedValue) { - option.selected = "selected"; - } - option.innerHTML = arr[i]; - select.appendChild(option); - } + // 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); - var me = this; - select.onchange = function () { - me._update(this.value, path); - }; + // 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) { + // duplicate code to reduce function calls to speed up program + if (distance === 0) { + distance = 0.1 * Math.random(); + dx = distance; + } + var gravityForce = this.options.gravitationalConstant * parentBranch.mass * node.options.mass / (distance * distance * distance); + var fx = dx * gravityForce; + var fy = dy * gravityForce; + + this.physicsBody.forces[node.id].x += fx; + this.physicsBody.forces[node.id].y += fy; + } 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 + // duplicate code to reduce function calls to speed up program + if (distance === 0) { + distance = 0.5 * Math.random(); + dx = distance; + } + var gravityForce = this.options.gravitationalConstant * parentBranch.mass * node.options.mass / (distance * distance * distance); + var fx = dx * gravityForce; + var fy = dy * gravityForce; - var label = this._makeLabel(path[path.length - 1], path); - this._makeEntree(path, label, select); + this.physicsBody.forces[node.id].x += fx; + this.physicsBody.forces[node.id].y += fy; + } + } + } + } }, writable: true, configurable: true }, - _makeRange: { + _formBarnesHutTree: { /** - * make a range object for numeric options - * @param arr - * @param value - * @param path + * 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 _makeRange(arr, value, path) { - var defaultValue = arr[0]; - var min = arr[1]; - var max = arr[2]; - var step = arr[3]; - var range = document.createElement("input"); - range.type = "range"; - range.className = "vis-network-configuration range"; - range.min = min; - range.max = max; - range.step = step; + value: function _formBarnesHutTree(nodes, nodeIndices) { + var node; + var nodeCount = nodeIndices.length; - if (value !== undefined) { - if (value * 0.1 < min) { - range.min = value / 10; - } - if (value * 10 > max && max !== 1) { - range.max = value * 10; + var minX = Number.MAX_VALUE, + minY = Number.MAX_VALUE, + maxX = -Number.MAX_VALUE, + maxY = -Number.MAX_VALUE; + + // get the range of the nodes + for (var i = 0; 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; + } } - range.value = value; - } else { - range.value = defaultValue; } - var input = document.createElement("input"); - input.className = "vis-network-configuration rangeinput"; - input.value = range.value; + // 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 me = this; - range.onchange = function () { - input.value = this.value;me._update(this.value, path); - }; - range.oninput = function () { - input.value = this.value; + + 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); - var label = this._makeLabel(path[path.length - 1], path); - this._makeEntree(path, label, range, input); + // place the nodes one by one recursively + for (i = 0; i < nodeCount; i++) { + node = nodes[nodeIndices[i]]; + if (node.options.mass > 0) { + this._placeInTree(barnesHutTree.root, node); + } + } + + // make global + return barnesHutTree; }, writable: true, configurable: true }, - _makeCheckbox: { + _updateBranchMass: { /** - * make a checkbox for boolean options. - * @param defaultValue - * @param value - * @param path + * this updates the mass of a branch. this is increased by adding a node. + * + * @param parentBranch + * @param node * @private */ - value: function _makeCheckbox(defaultValue, value, path) { - var checkbox = document.createElement("input"); - checkbox.type = "checkbox"; - checkbox.className = "vis-network-configuration checkbox"; - checkbox.checked = defaultValue; - if (value !== undefined) { - checkbox.checked = value; - } + value: function _updateBranchMass(parentBranch, node) { + var totalMass = parentBranch.mass + node.options.mass; + var totalMassInv = 1 / totalMass; - var me = this; - checkbox.onchange = function () { - me._update(this.value, path); - }; + parentBranch.centerOfMass.x = parentBranch.centerOfMass.x * parentBranch.mass + node.x * node.options.mass; + parentBranch.centerOfMass.x *= totalMassInv; - var label = this._makeLabel(path[path.length - 1], path); - this._makeEntree(path, label, checkbox); + 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; }, writable: true, configurable: true }, - _makeColorField: { + _placeInTree: { /** - * make a color field with a color picker for color fields - * @param arr - * @param value - * @param path + * determine in which branch the node will be placed. + * + * @param parentBranch + * @param node + * @param skipMassUpdate * @private */ - value: function _makeColorField(arr, value, path) { - var _this = this; - var defaultColor = arr[1]; - var div = document.createElement("div"); - value = value === undefined ? defaultColor : value; + value: function _placeInTree(parentBranch, node, skipMassUpdate) { + if (skipMassUpdate != true || skipMassUpdate === undefined) { + // update the mass of the branch. + this._updateBranchMass(parentBranch, node); + } - if (value !== "none") { - div.className = "vis-network-configuration colorBlock"; - div.style.backgroundColor = value; + 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 { - div.className = "vis-network-configuration colorBlock none"; + // 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"); + } } - - value = value === undefined ? defaultColor : value; - div.onclick = function (event) { - _this._showColorPicker(event, value, div, path); - }; - - var label = this._makeLabel(path[path.length - 1], path); - this._makeEntree(path, label, div); }, writable: true, configurable: true }, - _showColorPicker: { + _placeInRegion: { /** - * used by the color buttons to call the color picker. - * @param event - * @param value - * @param div - * @param path + * actually place the node in a region (or branch) + * + * @param parentBranch + * @param node + * @param region * @private */ - value: function _showColorPicker(event, value, div, path) { - var _this = this; - this.colorPicker.show(event.pageX, event.pageY); - this.colorPicker.setColor(value); - this.colorPicker.setCallback(function (color) { - var colorString = "rgba(" + color.r + "," + color.g + "," + color.b + "," + color.a + ")"; - div.style.backgroundColor = colorString; - _this._update(colorString, path); - }); + 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(); + } 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; + } }, writable: true, configurable: true }, - _handleObject: { + _splitBranch: { /** - * parse an object and draw the correct entrees - * @param obj - * @param path + * 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 _handleObject(obj) { - var path = arguments[1] === undefined ? [] : arguments[1]; - for (var subObj in obj) { - if (obj.hasOwnProperty(subObj)) { - var item = obj[subObj]; - var newPath = this._addToPath(path, subObj); - var value = this._getValue(newPath); + 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 (item instanceof Array) { - this._handleArray(subObj, item, value, newPath); - } else if (typeof item === "string") { - this._handleString(subObj, item, value, newPath); - } else if (typeof item === "boolean") { - this._makeCheckbox(item, value, newPath); - } else if (item instanceof Object) { - var label = this._makeLabel(subObj, newPath, true); - this._makeEntree(newPath, label); - this._handleObject(item, newPath); - } else { - console.error("dont know how to handle", item, subObj, newPath); - } - } + if (containedNode != null) { + this._placeInTree(parentBranch, containedNode); } }, writable: true, configurable: true }, - _handleArray: { + _insertRegion: { /** - * handle the array type of option - * @param optionName - * @param arr - * @param value - * @param path + * 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 _handleArray(optionName, arr, value, path) { - if (typeof arr[0] === "string" && arr[0] === "color") { - this._makeColorField(arr, value, path); - } else if (typeof arr[0] === "string") { - this._makeDropdown(arr, value, path); - } else if (typeof arr[0] === "number") { - this._makeRange(arr, value, path); + value: function _insertRegion(parentBranch, region) { + var minX, maxX, minY, maxY; + 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 + }; }, writable: true, configurable: true }, - _handleString: { + _debug: { + + + + + //--------------------------- DEBUGGING BELOW ---------------------------// /** - * handle the string type of option. - * TODO: Not sure what to do with this - * @param optionName - * @param string - * @param value - * @param path + * This function is for debugging purposed, it draws the tree. + * + * @param ctx + * @param color * @private */ - value: function _handleString(optionName, string, value, path) { - if (string === "string") {} else {} + value: function _debug(ctx, color) { + if (this.barnesHutTree !== undefined) { + ctx.lineWidth = 1; + + this._drawBranch(this.barnesHutTree.root, ctx, color); + } }, writable: true, configurable: true }, - _update: { + _drawBranch: { /** - * called to update the network with the new settings. - * @param value - * @param path + * This function is for debugging purposes. It draws the branches recursively. + * + * @param branch + * @param ctx + * @param color * @private */ - value: function _update(value, path) { - var options = {}; - var pointer = options; - for (var i = 0; i < path.length; i++) { - pointer[path[i]] = {}; - if (i !== path.length - 1) { - pointer = pointer[path[i]]; - } else { - pointer[path[i]] = value; - } + value: function _drawBranch(branch, ctx, color) { + if (color === undefined) { + color = "#FF0000"; } - this.network.setOptions(options); + + 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(); + + ctx.beginPath(); + ctx.moveTo(branch.range.minX, branch.range.maxY); + ctx.lineTo(branch.range.minX, branch.range.minY); + ctx.stroke(); + + /* + if (branch.mass > 0) { + ctx.circle(branch.centerOfMass.x, branch.centerOfMass.y, 3*branch.mass); + ctx.stroke(); + } + */ }, writable: true, configurable: true } }); - return ConfigurationSystem; + return BarnesHutSolver; })(); - module.exports = ConfigurationSystem; - //this._makeLabel(optionName, path); - //console.log('string', string, value, path); + module.exports = BarnesHutSolver; /***/ }, -/* 106 */ +/* 102 */ /***/ function(module, exports, __webpack_require__) { "use strict"; @@ -37464,603 +37627,494 @@ return /******/ (function(modules) { // webpackBootstrap var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; /** - * Created by Alex on 3/27/2015. + * Created by Alex on 2/23/2015. */ - var Hammer = __webpack_require__(19); - var hammerUtil = __webpack_require__(24); - var util = __webpack_require__(1); - - var ColorPicker = (function () { - function ColorPicker() { - var pixelRatio = arguments[0] === undefined ? 1 : arguments[0]; - _classCallCheck(this, ColorPicker); - - this.pixelRatio = pixelRatio; - this.generated = false; - this.centerCoordinates = { x: 289 / 2, y: 289 / 2 }; - this.r = 289 * 0.49; - this.color = { r: 255, g: 255, b: 255, a: 1 }; - this.hueCircle = undefined; - this.initialColor = { r: 255, g: 255, b: 255, a: 1 }; - this.previousColor = undefined; - this.applied = false; - - // bound by - this.updateCallback = function () {}; - - // create all DOM elements - this._create(); - } - - _prototypeProperties(ColorPicker, null, { - insertTo: { - - - /** - * this inserts the colorPicker into a div from the DOM - * @param container - */ - value: function insertTo(container) { - if (this.hammer !== undefined) { - this.hammer.destroy(); - this.hammer = undefined; - } - this.container = container; - this.container.appendChild(this.frame); - this._bindHammer(); - - this._setSize(); - }, - writable: true, - configurable: true - }, - setCallback: { + var RepulsionSolver = (function () { + function RepulsionSolver(body, physicsBody, options) { + _classCallCheck(this, RepulsionSolver); - /** - * the callback is executed on apply and save. Bind it to the application - * @param callback - */ - value: function setCallback(callback) { - if (typeof callback === "function") { - this.updateCallback = callback; - } else { - throw new Error("Function attempted to set as colorPicker callback is not a function."); - } + this.body = body; + this.physicsBody = physicsBody; + this.setOptions(options); + } + + _prototypeProperties(RepulsionSolver, null, { + setOptions: { + value: function setOptions(options) { + this.options = options; }, writable: true, configurable: true }, - setColor: { - - + solve: { /** - * Set the color of the colorPicker - * Supported formats: - * '#ffffff' --> hex string - * 'rbg(255,255,255)' --> rgb string - * 'rgba(255,255,255,1.0)' --> rgba string - * {r:255,g:255,b:255} --> rgb object - * {r:255,g:255,b:255,a:1.0} --> rgba object - * @param color - * @param setInitial + * Calculate the forces the nodes apply on each other based on a repulsion field. + * This field is linearly approximated. + * + * @private */ - value: function setColor(color) { - var setInitial = arguments[1] === undefined ? true : arguments[1]; - if (color === "none") { - return; - } + value: function solve() { + var dx, dy, distance, fx, fy, repulsingForce, node1, node2; - var rgba = undefined; + var nodes = this.body.nodes; + var nodeIndices = this.physicsBody.physicsNodeIndices; + var forces = this.physicsBody.forces; - // check format - if (util.isString(color) === true) { - if (util.isValidRGB(color) === true) { - var rgbaArray = color.substr(4).substr(0, color.length - 5).split(","); - rgba = { r: rgbaArray[0], g: rgbaArray[1], b: rgbaArray[2], a: 1 }; - } else if (util.isValidRGBA(color) === true) { - var rgbaArray = color.substr(5).substr(0, color.length - 6).split(","); - rgba = { r: rgbaArray[0], g: rgbaArray[1], b: rgbaArray[2], a: rgbaArray[3] }; - } else if (util.isValidHex(color) === true) { - var rgbObj = util.hexToRGB(color); - rgba = { r: rgbObj.r, g: rgbObj.g, b: rgbObj.b, a: 1 }; - } - } else { - if (color instanceof Object) { - if (color.r !== undefined && color.g !== undefined && color.b !== undefined) { - var alpha = color.a !== undefined ? color.a : "1.0"; - rgba = { r: color.r, g: color.g, b: color.b, a: alpha }; + // repulsing forces between nodes + var nodeDistance = this.options.nodeDistance; + + // approximation constants + var a = -2 / 3 / nodeDistance; + var b = 4 / 3; + + // 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 (var i = 0; i < nodeIndices.length - 1; i++) { + node1 = nodes[nodeIndices[i]]; + for (var j = i + 1; j < nodeIndices.length; j++) { + node2 = nodes[nodeIndices[j]]; + + dx = node2.x - node1.x; + dy = node2.y - node1.y; + distance = Math.sqrt(dx * dx + dy * dy); + + // same condition as BarnesHutSolver, making sure nodes are never 100% overlapping. + if (distance == 0) { + distance = 0.1 * Math.random(); + dx = distance; } - } - } - // set color - if (rgba === undefined) { - throw new Error("Unknown color passed to the colorPicker. Supported are strings: rgb, hex, rgba. Object: rgb ({r:r,g:g,b:b,[a:a]}). Supplied: " + JSON.stringify(color)); - } else { - this._setColor(rgba, setInitial); - } - }, - writable: true, - configurable: true - }, - show: { + if (distance < 2 * nodeDistance) { + if (distance < 0.5 * nodeDistance) { + repulsingForce = 1; + } else { + repulsingForce = a * distance + b; // linear approx of 1 / (1 + Math.exp((distance / nodeDistance - 1) * steepness)) + } + repulsingForce = repulsingForce / distance; + fx = dx * repulsingForce; + fy = dy * repulsingForce; - /** - * this shows the color picker at a location. The hue circle is constructed once and stored. - * @param x - * @param y - */ - value: function show(x, y) { - this.applied = false; - this.frame.style.display = "block"; - this.frame.style.top = y + "px"; - this.frame.style.left = x + "px"; - this._generateHueCircle(); + forces[node1.id].x -= fx; + forces[node1.id].y -= fy; + forces[node2.id].x += fx; + forces[node2.id].y += fy; + } + } + } }, writable: true, configurable: true - }, - _hide: { + } + }); + return RepulsionSolver; + })(); - // ------------------------------------------ PRIVATE ----------------------------- // + module.exports = RepulsionSolver; - /** - * Hide the picker. Is called by the cancel button. - * Optional boolean to store the previous color for easy access later on. - * @param storePrevious - * @private - */ - value: function _hide() { - var storePrevious = arguments[0] === undefined ? true : arguments[0]; - // store the previous color for next time; - if (storePrevious === true) { - this.previousColor = util.extend({}, this.color); - } +/***/ }, +/* 103 */ +/***/ function(module, exports, __webpack_require__) { - if (this.applied === true) { - this.updateCallback(this.initialColor); - } + "use strict"; - this.frame.style.display = "none"; - }, - writable: true, - configurable: true - }, - _save: { + var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; + var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; - /** - * bound to the save button. Saves and hides. - * @private - */ - value: function _save() { - this.updateCallback(this.color); - this.applied = false; - this.hide(); - }, - writable: true, - configurable: true - }, - _apply: { + /** + * Created by Alex on 2/23/2015. + */ + var HierarchicalRepulsionSolver = (function () { + function HierarchicalRepulsionSolver(body, physicsBody, options) { + _classCallCheck(this, HierarchicalRepulsionSolver); - /** - * Bound to apply button. Saves but does not close. Is undone by the cancel button. - * @private - */ - value: function _apply() { - this.applied = true; - this.updateCallback(this.color); - this._updatePicker(this.color); + this.body = body; + this.physicsBody = physicsBody; + this.setOptions(options); + } + + _prototypeProperties(HierarchicalRepulsionSolver, null, { + setOptions: { + value: function setOptions(options) { + this.options = options; }, writable: true, configurable: true }, - _loadLast: { - + solve: { /** - * load the color from the previous session. + * Calculate the forces the nodes apply on each other based on a repulsion field. + * This field is linearly approximated. + * * @private */ - value: function _loadLast() { - if (this.previousColor !== undefined) { - this.setColor(this.previousColor, false); - } else { - alert("There is no last color to load..."); - } - }, - writable: true, - configurable: true - }, - _setColor: { + 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; - /** - * set the color, place the picker - * @param rgba - * @param setInitial - * @private - */ - value: function _setColor(rgba) { - var setInitial = arguments[1] === undefined ? true : arguments[1]; - // store the initial color - if (setInitial === true) { - console.log("here"); - this.initialColor = util.extend({}, rgba); - } + // repulsing forces between nodes + var nodeDistance = this.options.nodeDistance; - this.color = rgba; - var hsv = util.RGBToHSV(rgba.r, rgba.g, rgba.b); + // 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]]; - var angleConvert = 2 * Math.PI; - var radius = this.r * hsv.s; - var x = this.centerCoordinates.x + radius * Math.sin(angleConvert * hsv.h); - var y = this.centerCoordinates.y + radius * Math.cos(angleConvert * hsv.h); + // 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); - this.colorPickerSelector.style.left = x - 0.5 * this.colorPickerSelector.clientWidth + "px"; - this.colorPickerSelector.style.top = y - 0.5 * this.colorPickerSelector.clientHeight + "px"; + 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; - this._updatePicker(rgba); + forces[node1.id].x -= fx; + forces[node1.id].y -= fy; + forces[node2.id].x += fx; + forces[node2.id].y += fy; + } + } + } }, writable: true, configurable: true - }, - _setOpacity: { + } + }); + return HierarchicalRepulsionSolver; + })(); - /** - * bound to opacity control - * @param value - * @private - */ - value: function _setOpacity(value) { - this.color.a = value / 100; - this._updatePicker(this.color); - }, - writable: true, - configurable: true - }, - _setBrightness: { + module.exports = HierarchicalRepulsionSolver; +/***/ }, +/* 104 */ +/***/ function(module, exports, __webpack_require__) { - /** - * bound to brightness control - * @param value - * @private - */ - value: function _setBrightness(value) { - var hsv = util.RGBToHSV(this.color.r, this.color.g, this.color.b); - hsv.v = value / 100; - var rgba = util.HSVToRGB(hsv.h, hsv.s, hsv.v); - rgba.a = this.color.a; - this.color = rgba; - this._updatePicker(); - }, - writable: true, - configurable: true - }, - _updatePicker: { + "use strict"; + var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; - /** - * update the colorpicker. A black circle overlays the hue circle to mimic the brightness decreasing. - * @param rgba - * @private - */ - value: function _updatePicker() { - var rgba = arguments[0] === undefined ? this.color : arguments[0]; - var hsv = util.RGBToHSV(rgba.r, rgba.g, rgba.b); - var ctx = this.colorPickerCanvas.getContext("2d"); - if (this.pixelRation === undefined) { - this.pixelRatio = (window.devicePixelRatio || 1) / (ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1); - } - ctx.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0); + var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; - // clear the canvas - var w = this.colorPickerCanvas.clientWidth; - var h = this.colorPickerCanvas.clientHeight; - ctx.clearRect(0, 0, w, h); + /** + * Created by Alex on 2/23/2015. + */ - ctx.putImageData(this.hueCircle, 0, 0); - ctx.fillStyle = "rgba(0,0,0," + (1 - hsv.v) + ")"; - ctx.circle(this.centerCoordinates.x, this.centerCoordinates.y, this.r); - ctx.fill(); + var SpringSolver = (function () { + function SpringSolver(body, physicsBody, options) { + _classCallCheck(this, SpringSolver); - this.brightnessRange.value = 100 * hsv.v; - this.opacityRange.value = 100 * rgba.a; + this.body = body; + this.physicsBody = physicsBody; + this.setOptions(options); + } - this.initialColorDiv.style.backgroundColor = "rgba(" + this.initialColor.r + "," + this.initialColor.g + "," + this.initialColor.b + "," + this.initialColor.a + ")"; - this.newColorDiv.style.backgroundColor = "rgba(" + this.color.r + "," + this.color.g + "," + this.color.b + "," + this.color.a + ")"; + _prototypeProperties(SpringSolver, null, { + setOptions: { + value: function setOptions(options) { + this.options = options; }, writable: true, configurable: true }, - _setSize: { - + solve: { /** - * used by create to set the size of the canvas. + * This function calculates the springforces on the nodes, accounting for the support nodes. + * * @private */ - value: function _setSize() { - this.colorPickerCanvas.style.width = "100%"; - this.colorPickerCanvas.style.height = "100%"; + value: function solve() { + var edgeLength, edge; + var edgeIndices = this.physicsBody.physicsEdgeIndices; + var edges = this.body.edges; - this.colorPickerCanvas.width = 289 * this.pixelRatio; - this.colorPickerCanvas.height = 289 * this.pixelRatio; + // forces caused by the edges, modelled as springs + for (var i = 0; i < edgeIndices.length; i++) { + edge = edges[edgeIndices[i]]; + if (edge.connected === true) { + // 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; + var node1 = edge.to; + var node2 = edge.edgeType.via; + var 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); + } + } + } + } }, writable: true, configurable: true }, - _create: { + _calculateSpringForce: { /** - * create all dom elements - * TODO: cleanup, lots of similar dom elements + * This is the code actually performing the calculation for the function above. + * + * @param node1 + * @param node2 + * @param edgeLength * @private */ - value: function _create() { - var visPrefix = "vis-network-"; - - this.frame = document.createElement("div"); - this.frame.className = visPrefix + "colorPicker-frame"; + value: function _calculateSpringForce(node1, node2, edgeLength) { + var dx, dy, fx, fy, springForce, distance; - this.colorPickerDiv = document.createElement("div"); - this.colorPickerSelector = document.createElement("div"); - this.colorPickerSelector.className = visPrefix + "colorPicker-selector"; - this.colorPickerDiv.appendChild(this.colorPickerSelector); + dx = node1.x - node2.x; + dy = node1.y - node2.y; + distance = Math.sqrt(dx * dx + dy * dy); + distance = distance == 0 ? 0.01 : distance; - this.colorPickerCanvas = document.createElement("canvas"); - this.colorPickerDiv.appendChild(this.colorPickerCanvas); + // the 1/distance is so the fx and fy can be calculated without sine or cosine. + springForce = this.options.springConstant * (edgeLength - distance) / distance; - if (!this.colorPickerCanvas.getContext) { - var noCanvas = document.createElement("DIV"); - noCanvas.style.color = "red"; - noCanvas.style.fontWeight = "bold"; - noCanvas.style.padding = "10px"; - noCanvas.innerHTML = "Error: your browser does not support HTML canvas"; - this.colorPickerCanvas.appendChild(noCanvas); - } else { - var ctx = this.colorPickerCanvas.getContext("2d"); - this.pixelRatio = (window.devicePixelRatio || 1) / (ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1); + fx = dx * springForce; + fy = dy * springForce; - this.colorPickerCanvas.getContext("2d").setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0); + // 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; } - this.colorPickerDiv.className = visPrefix + "colorPicker-color"; - - this.opacityDiv = document.createElement("div"); - this.opacityDiv.className = visPrefix + "colorPicker-opacity"; - - this.brightnessDiv = document.createElement("div"); - this.brightnessDiv.className = visPrefix + "colorPicker-brightness"; - - this.opacityRange = document.createElement("input"); - this.opacityRange.type = "range"; - this.opacityRange.min = "0"; - this.opacityRange.max = "100"; - this.opacityRange.value = "100"; - this.opacityRange.className = visPrefix + "configuration range colorPicker"; - - this.brightnessRange = document.createElement("input"); - this.brightnessRange.type = "range"; - this.brightnessRange.min = "0"; - this.brightnessRange.max = "100"; - this.brightnessRange.value = "100"; - this.brightnessRange.className = visPrefix + "configuration range colorPicker"; - - this.opacityDiv.appendChild(this.opacityRange); - this.brightnessDiv.appendChild(this.brightnessRange); - - var me = this; - this.opacityRange.onchange = function () { - me._setOpacity(this.value); - }; - this.opacityRange.oninput = function () { - me._setOpacity(this.value); - }; - this.brightnessRange.onchange = function () { - me._setBrightness(this.value); - }; - this.brightnessRange.oninput = function () { - me._setBrightness(this.value); - }; + if (this.physicsBody.forces[node2.id] !== undefined) { + this.physicsBody.forces[node2.id].x -= fx; + this.physicsBody.forces[node2.id].y -= fy; + } + }, + writable: true, + configurable: true + } + }); - this.brightnessLabel = document.createElement("div"); - this.brightnessLabel.className = visPrefix + "colorPicker-label brightness"; - this.brightnessLabel.innerHTML = "brightness:"; + return SpringSolver; + })(); - this.opacityLabel = document.createElement("div"); - this.opacityLabel.className = visPrefix + "colorPicker-label opacity"; - this.opacityLabel.innerHTML = "opacity:"; + module.exports = SpringSolver; - this.newColorDiv = document.createElement("div"); - this.newColorDiv.className = visPrefix + "colorPicker-newColor"; - this.newColorDiv.innerHTML = "new"; +/***/ }, +/* 105 */ +/***/ function(module, exports, __webpack_require__) { - this.initialColorDiv = document.createElement("div"); - this.initialColorDiv.className = visPrefix + "colorPicker-initialColor"; - this.initialColorDiv.innerHTML = "initial"; + "use strict"; - this.cancelButton = document.createElement("div"); - this.cancelButton.className = visPrefix + "colorPicker-button cancel"; - this.cancelButton.innerHTML = "cancel"; - this.cancelButton.onclick = this._hide.bind(this, false); + var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; - this.applyButton = document.createElement("div"); - this.applyButton.className = visPrefix + "colorPicker-button apply"; - this.applyButton.innerHTML = "apply"; - this.applyButton.onclick = this._apply.bind(this); + var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; - this.saveButton = document.createElement("div"); - this.saveButton.className = visPrefix + "colorPicker-button save"; - this.saveButton.innerHTML = "save"; - this.saveButton.onclick = this._save.bind(this); + /** + * Created by Alex on 2/25/2015. + */ - this.loadButton = document.createElement("div"); - this.loadButton.className = visPrefix + "colorPicker-button load"; - this.loadButton.innerHTML = "load last"; - this.loadButton.onclick = this._loadLast.bind(this); + var HierarchicalSpringSolver = (function () { + function HierarchicalSpringSolver(body, physicsBody, options) { + _classCallCheck(this, HierarchicalSpringSolver); - this.frame.appendChild(this.colorPickerDiv); - this.frame.appendChild(this.brightnessLabel); - this.frame.appendChild(this.brightnessDiv); - this.frame.appendChild(this.opacityLabel); - this.frame.appendChild(this.opacityDiv); - this.frame.appendChild(this.newColorDiv); - this.frame.appendChild(this.initialColorDiv); + this.body = body; + this.physicsBody = physicsBody; + this.setOptions(options); + } - this.frame.appendChild(this.cancelButton); - this.frame.appendChild(this.applyButton); - this.frame.appendChild(this.saveButton); - this.frame.appendChild(this.loadButton); + _prototypeProperties(HierarchicalSpringSolver, null, { + setOptions: { + value: function setOptions(options) { + this.options = options; }, writable: true, configurable: true }, - _bindHammer: { - + solve: { /** - * bind hammer to the color picker + * This function calculates the springforces on the nodes, accounting for the support nodes. + * * @private */ - value: function _bindHammer() { - var _this = this; - this.drag = {}; - this.pinch = {}; - this.hammer = new Hammer(this.colorPickerCanvas); - this.hammer.get("pinch").set({ enable: true }); + value: function solve() { + var edgeLength, edge; + var dx, dy, fx, fy, springForce, distance; + var edges = this.body.edges; + var factor = 0.5; - hammerUtil.onTouch(this.hammer, function (event) { - _this._moveSelector(event); - }); - this.hammer.on("tap", function (event) { - _this._moveSelector(event); - }); - this.hammer.on("panstart", function (event) { - _this._moveSelector(event); - }); - this.hammer.on("panmove", function (event) { - _this._moveSelector(event); - }); - this.hammer.on("panend", function (event) { - _this._moveSelector(event); - }); - }, - writable: true, - configurable: true - }, - _generateHueCircle: { + 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; + } - /** - * generate the hue circle. This is relatively heavy (200ms) and is done only once on the first time it is shown. - * @private - */ - value: function _generateHueCircle() { - if (this.generated === false) { - var ctx = this.colorPickerCanvas.getContext("2d"); - if (this.pixelRation === undefined) { - this.pixelRatio = (window.devicePixelRatio || 1) / (ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1); - } - ctx.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0); - // clear the canvas - var w = this.colorPickerCanvas.clientWidth; - var h = this.colorPickerCanvas.clientHeight; - ctx.clearRect(0, 0, w, h); + // 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; - // draw hue circle - var x = undefined, - y = undefined, - hue = undefined, - sat = undefined; - this.centerCoordinates = { x: w * 0.5, y: h * 0.5 }; - this.r = 0.49 * w; - var angleConvert = 2 * Math.PI / 360; - var hfac = 1 / 360; - var sfac = 1 / this.r; - var rgb = undefined; - for (hue = 0; hue < 360; hue++) { - for (sat = 0; sat < this.r; sat++) { - x = this.centerCoordinates.x + sat * Math.sin(angleConvert * hue); - y = this.centerCoordinates.y + sat * Math.cos(angleConvert * hue); - rgb = util.HSVToRGB(hue * hfac, sat * sfac, 1); - ctx.fillStyle = "rgb(" + rgb.r + "," + rgb.g + "," + rgb.b + ")"; - ctx.fillRect(x - 0.5, y - 0.5, 2, 2); + fx = dx * springForce; + fy = dy * springForce; + + if (edge.to.level != edge.from.level) { + forces[edge.toId].springFx -= fx; + forces[edge.toId].springFy -= fy; + forces[edge.fromId].springFx += fx; + forces[edge.fromId].springFy += fy; + } else { + forces[edge.toId].x -= factor * fx; + forces[edge.toId].y -= factor * fy; + forces[edge.fromId].x += factor * fx; + forces[edge.fromId].y += factor * fy; } } - ctx.strokeStyle = "rgba(0,0,0,1)"; - ctx.circle(this.centerCoordinates.x, this.centerCoordinates.y, this.r); - ctx.stroke(); + } - this.hueCircle = ctx.getImageData(0, 0, w, h); + // 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; } - this.generated = true; }, writable: true, configurable: true - }, - _moveSelector: { + } + }); + return HierarchicalSpringSolver; + })(); - /** - * move the selector. This is called by hammer functions. - * - * @param event - * @private - */ - value: function _moveSelector(event) { - var rect = this.colorPickerDiv.getBoundingClientRect(); - var left = event.center.x - rect.left; - var top = event.center.y - rect.top; + module.exports = HierarchicalSpringSolver; - var centerY = 0.5 * this.colorPickerDiv.clientHeight; - var centerX = 0.5 * this.colorPickerDiv.clientWidth; +/***/ }, +/* 106 */ +/***/ function(module, exports, __webpack_require__) { - var x = left - centerX; - var y = top - centerY; + "use strict"; - var angle = Math.atan2(x, y); - var radius = 0.98 * Math.min(Math.sqrt(x * x + y * y), centerX); + var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; - var newTop = Math.cos(angle) * radius + centerY; - var newLeft = Math.sin(angle) * radius + centerX; + var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; - this.colorPickerSelector.style.top = newTop - 0.5 * this.colorPickerSelector.clientHeight + "px"; - this.colorPickerSelector.style.left = newLeft - 0.5 * this.colorPickerSelector.clientWidth + "px"; + /** + * Created by Alex on 2/23/2015. + */ - // set color - var h = angle / (2 * Math.PI); - h = h < 0 ? h + 1 : h; - var s = radius / this.r; - var hsv = util.RGBToHSV(this.color.r, this.color.g, this.color.b); - hsv.h = h; - hsv.s = s; - var rgba = util.HSVToRGB(hsv.h, hsv.s, hsv.v); - rgba.a = this.color.a; - this.color = rgba; + var CentralGravitySolver = (function () { + function CentralGravitySolver(body, physicsBody, options) { + _classCallCheck(this, CentralGravitySolver); - // update previews - this.initialColorDiv.style.backgroundColor = "rgba(" + this.initialColor.r + "," + this.initialColor.g + "," + this.initialColor.b + "," + this.initialColor.a + ")"; - this.newColorDiv.style.backgroundColor = "rgba(" + this.color.r + "," + this.color.g + "," + this.color.b + "," + this.color.a + ")"; + this.body = body; + this.physicsBody = physicsBody; + this.setOptions(options); + } + + _prototypeProperties(CentralGravitySolver, null, { + setOptions: { + value: function setOptions(options) { + this.options = options; + }, + writable: true, + configurable: true + }, + solve: { + value: function solve() { + var dx, dy, distance, node, i; + var nodes = this.body.nodes; + var nodeIndices = this.physicsBody.physicsNodeIndices; + var forces = this.physicsBody.forces; + + + var gravity = this.options.centralGravity; + var gravityForce = 0; + + for (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); + + gravityForce = distance == 0 ? 0 : gravity / distance; + forces[nodeId].x = dx * gravityForce; + forces[nodeId].y = dy * gravityForce; + } }, writable: true, configurable: true } }); - return ColorPicker; + return CentralGravitySolver; })(); - module.exports = ColorPicker; + module.exports = CentralGravitySolver; /***/ } /******/ ]) diff --git a/examples/network/01_basic_usage.html b/examples/network/01_basic_usage.html index 4d8537bf..c2a42712 100644 --- a/examples/network/01_basic_usage.html +++ b/examples/network/01_basic_usage.html @@ -46,7 +46,8 @@ }; var options = { configure: true, - physics:{solver:'BarnesHut'} + physics:{solver:'BarnesHut'}, +// nodes:{physics:false} } var network = new vis.Network(container, data, options); // network.setOptions({nodes:{color:'red'}}) diff --git a/lib/network/modules/ConfigurationSystem.js b/lib/network/modules/ConfigurationSystem.js index cb15cf5a..56634f2b 100644 --- a/lib/network/modules/ConfigurationSystem.js +++ b/lib/network/modules/ConfigurationSystem.js @@ -467,8 +467,8 @@ class ConfigurationSystem { if (value * 0.1 < min) { range.min = value / 10; } - if (value * 10 > max && max !== 1) { - range.max = value * 10; + if (value * 2 > max && max !== 1) { + range.max = value * 2; } range.value = value; } @@ -505,7 +505,7 @@ class ConfigurationSystem { } let me = this; - checkbox.onchange = function() {me._update(this.value, path)}; + checkbox.onchange = function() {me._update(this.checked, path)}; let label = this._makeLabel(path[path.length-1], path); this._makeEntree(path, label, checkbox); @@ -653,6 +653,7 @@ class ConfigurationSystem { pointer[path[i]] = value; } } + console.log(JSON.stringify(options)) this.network.setOptions(options); } } diff --git a/lib/network/modules/EdgesHandler.js b/lib/network/modules/EdgesHandler.js index ce53169c..f9c16c98 100644 --- a/lib/network/modules/EdgesHandler.js +++ b/lib/network/modules/EdgesHandler.js @@ -167,7 +167,29 @@ class EdgesHandler { util.mergeOptions(this.options.color, options.color, 'inherit'); } - // font cases are handled by the Label class + // update smooth settings + let dataChanged = false; + if (options.smooth !== undefined) { + for (let nodeId in this.body.edges) { + if (this.body.edges.hasOwnProperty(nodeId)) { + dataChanged = this.body.edges[nodeId].updateEdgeType() || dataChanged; + } + } + } + + // update fonts + if (options.font) { + for (let nodeId in this.body.edges) { + if (this.body.edges.hasOwnProperty(nodeId)) { + this.body.edges[nodeId].updateLabelModule(); + } + } + } + + // update the state of the variables if needed + if (options.hidden !== undefined || options.physics !== undefined || dataChanged === true) { + this.body.emitter.emit('_dataChanged'); + } } } diff --git a/lib/network/modules/NodesHandler.js b/lib/network/modules/NodesHandler.js index 5698b1ff..1749557a 100644 --- a/lib/network/modules/NodesHandler.js +++ b/lib/network/modules/NodesHandler.js @@ -110,6 +110,7 @@ class NodesHandler { if (parsedColor.hover.background !== undefined) {this.options.color.hover.background = parsedColor.hover.background;} } + // update the shape if (options.shape !== undefined) { for (let nodeId in this.body.nodes) { if (this.body.nodes.hasOwnProperty(nodeId)) { @@ -117,6 +118,20 @@ class NodesHandler { } } } + + // update fonts + if (options.font) { + for (let nodeId in this.body.nodes) { + if (this.body.nodes.hasOwnProperty(nodeId)) { + this.body.nodes[nodeId].updateLabelModule(); + } + } + } + + // update the state of the variables if needed + if (options.hidden !== undefined || options.physics !== undefined) { + this.body.emitter.emit('_dataChanged'); + } } } diff --git a/lib/network/modules/PhysicsEngine.js b/lib/network/modules/PhysicsEngine.js index 6725e73a..6f49eae8 100644 --- a/lib/network/modules/PhysicsEngine.js +++ b/lib/network/modules/PhysicsEngine.js @@ -288,10 +288,12 @@ class PhysicsEngine { for (let i = 0; i < nodeIds.length; i++) { let nodeId = nodeIds[i]; if (nodes[nodeId] !== undefined) { - velocities[nodeId].x = this.previousStates[nodeId].vx; - velocities[nodeId].y = this.previousStates[nodeId].vy; - nodes[nodeId].x = this.previousStates[nodeId].x; - nodes[nodeId].y = this.previousStates[nodeId].y; + if (nodes[nodeId].options.physics === true) { + velocities[nodeId].x = this.previousStates[nodeId].vx; + velocities[nodeId].y = this.previousStates[nodeId].vy; + nodes[nodeId].x = this.previousStates[nodeId].x; + nodes[nodeId].y = this.previousStates[nodeId].y; + } } else { delete this.previousStates[nodeId]; diff --git a/lib/network/modules/components/Edge.js b/lib/network/modules/components/Edge.js index bb50ef00..90ec8122 100644 --- a/lib/network/modules/components/Edge.js +++ b/lib/network/modules/components/Edge.js @@ -134,12 +134,17 @@ class Edge { // A node is connected when it has a from and to node that both exist in the network.body.nodes. this.connect(); - this.labelModule.setOptions(this.options); + // update label Module + this.updateLabelModule(); let dataChanged = this.updateEdgeType(); return dataChanged; } + updateLabelModule() { + this.labelModule.setOptions(this.options); + } + updateEdgeType() { let dataChanged = false; let changeInType = true; diff --git a/lib/network/modules/components/Node.js b/lib/network/modules/components/Node.js index 3901d8d1..65b2ae3e 100644 --- a/lib/network/modules/components/Node.js +++ b/lib/network/modules/components/Node.js @@ -177,14 +177,17 @@ class Node { } this.updateShape(); - - this.labelModule.setOptions(this.options, options); + this.updateLabelModule(); // reset the size of the node, this can be changed this._reset(); } + updateLabelModule() { + this.labelModule.setOptions(this.options); + } + updateShape() { // choose draw method depending on the shape switch (this.options.shape) {