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) {