diff --git a/dist/img/graph/acceptDeleteIcon.png b/dist/img/graph/acceptDeleteIcon.png
new file mode 100644
index 00000000..02a06285
Binary files /dev/null and b/dist/img/graph/acceptDeleteIcon.png differ
diff --git a/dist/img/graph/addNodeIcon.png b/dist/img/graph/addNodeIcon.png
new file mode 100644
index 00000000..6fa30613
Binary files /dev/null and b/dist/img/graph/addNodeIcon.png differ
diff --git a/dist/img/graph/backIcon.png b/dist/img/graph/backIcon.png
new file mode 100644
index 00000000..e2f99126
Binary files /dev/null and b/dist/img/graph/backIcon.png differ
diff --git a/dist/img/graph/connectIcon.png b/dist/img/graph/connectIcon.png
new file mode 100644
index 00000000..4164da1f
Binary files /dev/null and b/dist/img/graph/connectIcon.png differ
diff --git a/dist/img/graph/cross.png b/dist/img/graph/cross.png
new file mode 100644
index 00000000..9cbd189a
Binary files /dev/null and b/dist/img/graph/cross.png differ
diff --git a/dist/img/graph/cross2.png b/dist/img/graph/cross2.png
new file mode 100644
index 00000000..9fc4b95c
Binary files /dev/null and b/dist/img/graph/cross2.png differ
diff --git a/dist/img/graph/deleteIcon.png b/dist/img/graph/deleteIcon.png
new file mode 100644
index 00000000..54025647
Binary files /dev/null and b/dist/img/graph/deleteIcon.png differ
diff --git a/dist/img/downarrow.png b/dist/img/graph/downArrow.png
similarity index 100%
rename from dist/img/downarrow.png
rename to dist/img/graph/downArrow.png
diff --git a/dist/img/graph/editIcon.png b/dist/img/graph/editIcon.png
new file mode 100644
index 00000000..494d0f00
Binary files /dev/null and b/dist/img/graph/editIcon.png differ
diff --git a/dist/img/leftarrow.png b/dist/img/graph/leftArrow.png
similarity index 100%
rename from dist/img/leftarrow.png
rename to dist/img/graph/leftArrow.png
diff --git a/dist/img/minus.png b/dist/img/graph/minus.png
similarity index 100%
rename from dist/img/minus.png
rename to dist/img/graph/minus.png
diff --git a/dist/img/plus.png b/dist/img/graph/plus.png
similarity index 100%
rename from dist/img/plus.png
rename to dist/img/graph/plus.png
diff --git a/dist/img/rightarrow.png b/dist/img/graph/rightArrow.png
similarity index 100%
rename from dist/img/rightarrow.png
rename to dist/img/graph/rightArrow.png
diff --git a/dist/img/uparrow.png b/dist/img/graph/upArrow.png
similarity index 100%
rename from dist/img/uparrow.png
rename to dist/img/graph/upArrow.png
diff --git a/dist/img/zoomExtends.png b/dist/img/graph/zoomExtends.png
similarity index 100%
rename from dist/img/zoomExtends.png
rename to dist/img/graph/zoomExtends.png
diff --git a/dist/img/timeline/delete.png b/dist/img/timeline/delete.png
new file mode 100644
index 00000000..d54d0e06
Binary files /dev/null and b/dist/img/timeline/delete.png differ
diff --git a/dist/vis.css b/dist/vis.css
index 32bfe22c..6c5a0e44 100644
--- a/dist/vis.css
+++ b/dist/vis.css
@@ -11,7 +11,7 @@
box-sizing: border-box;
}
-.vis.timeline .panel {
+.vis.timeline .vpanel {
position: absolute;
overflow: hidden;
}
@@ -51,7 +51,7 @@
border-bottom: 1px solid #bfbfbf;
}
-.vis.timeline .labels .label-set .label {
+.vis.timeline .labels .label-set .vlabel {
position: absolute;
left: 0;
top: 0;
@@ -59,19 +59,19 @@
color: #4d4d4d;
}
-.vis.timeline.top .labels .label-set .label,
+.vis.timeline.top .labels .label-set .vlabel,
.vis.timeline.top .groupset .itemset-axis {
border-top: 1px solid #bfbfbf;
border-bottom: none;
}
-.vis.timeline.bottom .labels .label-set .label,
+.vis.timeline.bottom .labels .label-set .vlabel,
.vis.timeline.bottom .groupset .itemset-axis {
border-top: none;
border-bottom: 1px solid #bfbfbf;
}
-.vis.timeline .labels .label-set .label .inner {
+.vis.timeline .labels .label-set .vlabel .inner {
display: inline-block;
padding: 5px;
}
@@ -101,6 +101,7 @@
border-color: #97B0F8;
background-color: #D5DDF6;
display: inline-block;
+ padding: 5px;
}
.vis.timeline .item.selected {
@@ -109,6 +110,10 @@
z-index: 999;
}
+.vis.timeline.editable .item.selected {
+ cursor: move;
+}
+
.vis.timeline .item.point.selected {
background-color: #FFF785;
z-index: 999;
@@ -139,43 +144,35 @@
}
.vis.timeline .dot {
+ padding: 0;
border: 5px solid #97B0F8;
position: absolute;
border-radius: 5px;
-moz-border-radius: 5px; /* For Firefox 3.6 and older */
}
-.vis.timeline .item.range {
- overflow: hidden;
+.vis.timeline .item.range,
+.vis.timeline .item.rangeoverflow{
border-style: solid;
border-width: 1px;
border-radius: 2px;
-moz-border-radius: 2px; /* For Firefox 3.6 and older */
+ box-sizing: border-box;
}
-.vis.timeline .item.rangeoverflow {
- border-style: solid;
- border-width: 1px;
- border-radius: 2px;
- -moz-border-radius: 2px; /* For Firefox 3.6 and older */
-}
-
-.vis.timeline .item.range .drag-left, .vis.timeline .item.rangeoverflow .drag-left {
- cursor: w-resize;
- z-index: 1000;
-}
-
-.vis.timeline .item.range .drag-right, .vis.timeline .item.rangeoverflow .drag-right {
- cursor: e-resize;
- z-index: 1000;
-}
-
-.vis.timeline .item.range .content, .vis.timeline .item.rangeoverflow .content {
+.vis.timeline .item.range .content,
+.vis.timeline .item.rangeoverflow .content {
position: relative;
display: inline-block;
}
+.vis.timeline .item.range .content {
+ overflow: hidden;
+ max-width: 100%;
+}
+
.vis.timeline .item.line {
+ padding: 0;
position: absolute;
width: 0;
border-left-width: 1px;
@@ -183,11 +180,44 @@
}
.vis.timeline .item .content {
- margin: 5px;
white-space: nowrap;
overflow: hidden;
}
+.vis.timeline .item .delete {
+ background: url('img/timeline/delete.png') no-repeat top center;
+ position: absolute;
+ width: 24px;
+ height: 24px;
+ top: 0;
+ right: -24px;
+ cursor: pointer;
+}
+
+.vis.timeline .item.range .drag-left,
+.vis.timeline .item.rangeoverflow .drag-left {
+ position: absolute;
+ width: 24px;
+ height: 100%;
+ top: 0;
+ left: -4px;
+
+ cursor: w-resize;
+ z-index: 10000;
+}
+
+.vis.timeline .item.range .drag-right,
+.vis.timeline .item.rangeoverflow .drag-right {
+ position: absolute;
+ width: 24px;
+ height: 100%;
+ top: 0;
+ right: -4px;
+
+ cursor: e-resize;
+ z-index: 10001; /* a little higher z-index than .drag-left */
+}
+
.vis.timeline .axis {
position: relative;
}
@@ -241,3 +271,193 @@
cursor: move;
z-index: 9;
}
+div.graph-manipulationDiv {
+ border-width:0px;
+ border-bottom: 1px;
+ border-style:solid;
+ border-color: #d6d9d8;
+ background: #ffffff; /* Old browsers */
+ background: -moz-linear-gradient(top, #ffffff 0%, #fcfcfc 48%, #fafafa 50%, #fcfcfc 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffffff), color-stop(48%,#fcfcfc), color-stop(50%,#fafafa), color-stop(100%,#fcfcfc)); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, #ffffff 0%,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, #ffffff 0%,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%); /* Opera 11.10+ */
+ background: -ms-linear-gradient(top, #ffffff 0%,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%); /* IE10+ */
+ background: linear-gradient(to bottom, #ffffff 0%,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%); /* W3C */
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#fcfcfc',GradientType=0 ); /* IE6-9 */
+
+ width: 600px;
+ height:30px;
+ z-index:10;
+ position:absolute;
+}
+
+div.graph-manipulation-editMode {
+ height:30px;
+ z-index:10;
+ position:absolute;
+ margin-top:20px;
+}
+
+div.graph-manipulation-closeDiv {
+ height:30px;
+ width:30px;
+ z-index:11;
+ position:absolute;
+ margin-top:3px;
+ margin-left:590px;
+ background-position: 0px 0px;
+ background-repeat:no-repeat;
+ background-image: url("img/graph/cross.png");
+ cursor: pointer;
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+
+span.graph-manipulationUI {
+ font-family: verdana;
+ font-size: 12px;
+ -moz-border-radius: 15px;
+ border-radius: 15px;
+ display:inline-block;
+ background-position: 0px 0px;
+ background-repeat:no-repeat;
+ height:24px;
+ margin: -14px 0px 0px 10px;
+ vertical-align:middle;
+ cursor: pointer;
+ padding: 0px 8px 0px 8px;
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+
+span.graph-manipulationUI:hover {
+ box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.20);
+}
+
+span.graph-manipulationUI:active {
+ box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.50);
+}
+
+span.graph-manipulationUI.back {
+ background-image: url("img/graph/backIcon.png");
+}
+
+span.graph-manipulationUI.none:hover {
+ box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.0);
+ cursor: default;
+}
+span.graph-manipulationUI.none:active {
+ box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.0);
+}
+span.graph-manipulationUI.none {
+ padding: 0px 0px 0px 0px;
+}
+span.graph-manipulationUI.notification{
+ margin: 2px;
+ font-weight: bold;
+}
+
+span.graph-manipulationUI.add {
+ background-image: url("img/graph/addNodeIcon.png");
+}
+
+span.graph-manipulationUI.edit {
+ background-image: url("img/graph/editIcon.png");
+}
+
+span.graph-manipulationUI.edit.editmode {
+ background-color: #fcfcfc;
+ border-style:solid;
+ border-width:1px;
+ border-color: #cccccc;
+}
+
+span.graph-manipulationUI.connect {
+ background-image: url("img/graph/connectIcon.png");
+}
+
+span.graph-manipulationUI.delete {
+ background-image: url("img/graph/deleteIcon.png");
+}
+/* top right bottom left */
+span.graph-manipulationLabel {
+ margin: 0px 0px 0px 23px;
+ line-height: 25px;
+}
+div.graph-seperatorLine {
+ display:inline-block;
+ width:1px;
+ height:20px;
+ background-color: #bdbdbd;
+ margin: 5px 7px 0px 15px;
+}
+div.graph-navigation {
+ width:34px;
+ height:34px;
+ z-index:10;
+ -moz-border-radius: 17px;
+ border-radius: 17px;
+ position:absolute;
+ display:inline-block;
+ background-position: 2px 2px;
+ background-repeat:no-repeat;
+ cursor: pointer;
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+
+div.graph-navigation:hover {
+ box-shadow: 0px 0px 3px 3px rgba(56, 207, 21, 0.30);
+}
+
+div.graph-navigation:active {
+ box-shadow: 0px 0px 1px 3px rgba(56, 207, 21, 0.95);
+}
+
+div.graph-navigation.up {
+ background-image: url("img/graph/upArrow.png");
+ margin-top:520px;
+ margin-left:55px;
+}
+div.graph-navigation.down {
+ background-image: url("img/graph/downArrow.png");
+ margin-top:560px;
+ margin-left:55px;
+}
+div.graph-navigation.left {
+ background-image: url("img/graph/leftArrow.png");
+ margin-top:560px;
+ margin-left:15px;
+}
+div.graph-navigation.right {
+ background-image: url("img/graph/rightArrow.png");
+ margin-top:560px;
+ margin-left:95px;
+}
+div.graph-navigation.zoomIn {
+ background-image: url("img/graph/plus.png");
+ margin-top:560px;
+ margin-left:555px;
+}
+div.graph-navigation.zoomOut {
+ background-image: url("img/graph/minus.png");
+ margin-top:560px;
+ margin-left:515px;
+}
+div.graph-navigation.zoomExtends {
+ background-image: url("img/graph/zoomExtends.png");
+ margin-top:520px;
+ margin-left:555px;
+}
diff --git a/dist/vis.js b/dist/vis.js
index 794f3f67..1c362a0b 100644
--- a/dist/vis.js
+++ b/dist/vis.js
@@ -4,8 +4,8 @@
*
* A dynamic, browser-based visualization library.
*
- * @version 0.4.0
- * @date 2014-01-31
+ * @version 0.5.1
+ * @date 2014-02-20
*
* @license
* Copyright (C) 2011-2014 Almende B.V, http://almende.com
@@ -31,6 +31,7 @@
// If not available there, load via require.
var moment = (typeof window !== 'undefined') && window['moment'] || require('moment');
+var Emitter = require('emitter-component');
var Hammer;
if (typeof window !== 'undefined') {
@@ -55,8 +56,6 @@ else {
}
-
-
// Internet Explorer 8 and older does not support Array.indexOf, so we define
// it here in that case.
// http://soledadpenades.com/2007/05/17/arrayindexof-in-internet-explorer/
@@ -844,21 +843,6 @@ util.getTarget = function getTarget(event) {
return target;
};
-/**
- * Stop event propagation
- */
-util.stopPropagation = function stopPropagation(event) {
- if (!event)
- event = window.event;
-
- if (event.stopPropagation) {
- event.stopPropagation(); // non-IE browsers
- }
- else {
- event.cancelBubble = true; // IE browsers
- }
-};
-
/**
* Fake a hammer.js gesture. Event can be a ScrollEvent or MouseMoveEvent
* @param {Element} element
@@ -868,28 +852,23 @@ util.fakeGesture = function fakeGesture (element, event) {
var eventType = null;
// for hammer.js 1.0.5
- return Hammer.event.collectEventData(this, eventType, event);
+ var gesture = Hammer.event.collectEventData(this, eventType, event);
// for hammer.js 1.0.6
//var touches = Hammer.event.getTouchList(event, eventType);
- //return Hammer.event.collectEventData(this, eventType, touches, event);
-};
-
-/**
- * Cancels the event if it is cancelable, without stopping further propagation of the event.
- */
-util.preventDefault = function preventDefault (event) {
- if (!event)
- event = window.event;
+ // var gesture = Hammer.event.collectEventData(this, eventType, touches, event);
- if (event.preventDefault) {
- event.preventDefault(); // non-IE browsers
+ // on IE in standards mode, no touches are recognized by hammer.js,
+ // resulting in NaN values for center.pageX and center.pageY
+ if (isNaN(gesture.center.pageX)) {
+ gesture.center.pageX = event.pageX;
}
- else {
- event.returnValue = false; // IE browsers
+ if (isNaN(gesture.center.pageY)) {
+ gesture.center.pageY = event.pageY;
}
-};
+ return gesture;
+};
util.option = {};
@@ -983,213 +962,165 @@ util.option.asElement = function (value, defaultValue) {
return value || defaultValue || null;
};
-/**
- * Event listener (singleton)
- */
-// TODO: replace usage of the event listener for the EventBus
-var events = {
- 'listeners': [],
- /**
- * Find a single listener by its object
- * @param {Object} object
- * @return {Number} index -1 when not found
- */
- 'indexOf': function (object) {
- var listeners = this.listeners;
- for (var i = 0, iMax = this.listeners.length; i < iMax; i++) {
- var listener = listeners[i];
- if (listener && listener.object == object) {
- return i;
- }
- }
- return -1;
- },
- /**
- * Add an event listener
- * @param {Object} object
- * @param {String} event The name of an event, for example 'select'
- * @param {function} callback The callback method, called when the
- * event takes place
- */
- 'addListener': function (object, event, callback) {
- var index = this.indexOf(object);
- var listener = this.listeners[index];
- if (!listener) {
- listener = {
- 'object': object,
- 'events': {}
- };
- this.listeners.push(listener);
- }
+util.GiveDec = function GiveDec(Hex)
+{
+ if(Hex == "A")
+ Value = 10;
+ else
+ if(Hex == "B")
+ Value = 11;
+ else
+ if(Hex == "C")
+ Value = 12;
+ else
+ if(Hex == "D")
+ Value = 13;
+ else
+ if(Hex == "E")
+ Value = 14;
+ else
+ if(Hex == "F")
+ Value = 15;
+ else
+ Value = eval(Hex)
+ return Value;
+}
- var callbacks = listener.events[event];
- if (!callbacks) {
- callbacks = [];
- listener.events[event] = callbacks;
- }
+util.GiveHex = function GiveHex(Dec)
+{
+ if(Dec == 10)
+ Value = "A";
+ else
+ if(Dec == 11)
+ Value = "B";
+ else
+ if(Dec == 12)
+ Value = "C";
+ else
+ if(Dec == 13)
+ Value = "D";
+ else
+ if(Dec == 14)
+ Value = "E";
+ else
+ if(Dec == 15)
+ Value = "F";
+ else
+ Value = "" + Dec;
+ return Value;
+}
- // add the callback if it does not yet exist
- if (callbacks.indexOf(callback) == -1) {
- callbacks.push(callback);
- }
- },
+/**
+ * http://www.yellowpipe.com/yis/tools/hex-to-rgb/color-converter.php
+ *
+ * @param {String} hex
+ * @returns {{r: *, g: *, b: *}}
+ */
+util.hexToRGB = function hexToRGB(hex) {
+ hex = hex.replace("#","").toUpperCase();
- /**
- * Remove an event listener
- * @param {Object} object
- * @param {String} event The name of an event, for example 'select'
- * @param {function} callback The registered callback method
- */
- 'removeListener': function (object, event, callback) {
- var index = this.indexOf(object);
- var listener = this.listeners[index];
- if (listener) {
- var callbacks = listener.events[event];
- if (callbacks) {
- index = callbacks.indexOf(callback);
- if (index != -1) {
- callbacks.splice(index, 1);
- }
+ var a = util.GiveDec(hex.substring(0, 1));
+ var b = util.GiveDec(hex.substring(1, 2));
+ var c = util.GiveDec(hex.substring(2, 3));
+ var d = util.GiveDec(hex.substring(3, 4));
+ var e = util.GiveDec(hex.substring(4, 5));
+ var f = util.GiveDec(hex.substring(5, 6));
- // remove the array when empty
- if (callbacks.length == 0) {
- delete listener.events[event];
- }
- }
+ var r = (a * 16) + b;
+ var g = (c * 16) + d;
+ var b = (e * 16) + f;
- // count the number of registered events. remove listener when empty
- var count = 0;
- var events = listener.events;
- for (var e in events) {
- if (events.hasOwnProperty(e)) {
- count++;
- }
- }
- if (count == 0) {
- delete this.listeners[index];
- }
- }
- },
+ return {r:r,g:g,b:b};
+};
- /**
- * Remove all registered event listeners
- */
- 'removeAllListeners': function () {
- this.listeners = [];
- },
+util.RGBToHex = function RGBToHex(red,green,blue) {
+ var a = util.GiveHex(Math.floor(red / 16));
+ var b = util.GiveHex(red % 16);
+ var c = util.GiveHex(Math.floor(green / 16));
+ var d = util.GiveHex(green % 16);
+ var e = util.GiveHex(Math.floor(blue / 16));
+ var f = util.GiveHex(blue % 16);
- /**
- * Trigger an event. All registered event handlers will be called
- * @param {Object} object
- * @param {String} event
- * @param {Object} properties (optional)
- */
- 'trigger': function (object, event, properties) {
- var index = this.indexOf(object);
- var listener = this.listeners[index];
- if (listener) {
- var callbacks = listener.events[event];
- if (callbacks) {
- for (var i = 0, iMax = callbacks.length; i < iMax; i++) {
- callbacks[i](properties);
- }
- }
- }
- }
+ var hex = a + b + c + d + e + f;
+ return "#" + hex;
};
-/**
- * An event bus can be used to emit events, and to subscribe to events
- * @constructor EventBus
- */
-function EventBus() {
- this.subscriptions = [];
-}
/**
- * Subscribe to an event
- * @param {String | RegExp} event The event can be a regular expression, or
- * a string with wildcards, like 'server.*'.
- * @param {function} callback. Callback are called with three parameters:
- * {String} event, {*} [data], {*} [source]
- * @param {*} [target]
- * @returns {String} id A subscription id
- */
-EventBus.prototype.on = function (event, callback, target) {
- var regexp = (event instanceof RegExp) ?
- event :
- new RegExp(event.replace('*', '\\w+'));
-
- var subscription = {
- id: util.randomUUID(),
- event: event,
- regexp: regexp,
- callback: (typeof callback === 'function') ? callback : null,
- target: target
- };
+ * http://www.javascripter.net/faq/rgb2hsv.htm
+ *
+ * @param red
+ * @param green
+ * @param blue
+ * @returns {*}
+ * @constructor
+ */
+util.RGBToHSV = function RGBToHSV (red,green,blue) {
+ red=red/255; green=green/255; blue=blue/255;
+ var minRGB = Math.min(red,Math.min(green,blue));
+ var maxRGB = Math.max(red,Math.max(green,blue));
- this.subscriptions.push(subscription);
+ // Black-gray-white
+ if (minRGB == maxRGB) {
+ return {h:0,s:0,v:minRGB};
+ }
- return subscription.id;
+ // Colors other than black-gray-white:
+ var d = (red==minRGB) ? green-blue : ((blue==minRGB) ? red-green : blue-red);
+ var h = (red==minRGB) ? 3 : ((blue==minRGB) ? 1 : 5);
+ var hue = 60*(h - d/(maxRGB - minRGB))/360;
+ var saturation = (maxRGB - minRGB)/maxRGB;
+ var value = maxRGB;
+ return {h:hue,s:saturation,v:value};
};
+
/**
- * Unsubscribe from an event
- * @param {String | Object} filter Filter for subscriptions to be removed
- * Filter can be a string containing a
- * subscription id, or an object containing
- * one or more of the fields id, event,
- * callback, and target.
+ * https://gist.github.com/mjijackson/5311256
+ * @param hue
+ * @param saturation
+ * @param value
+ * @returns {{r: number, g: number, b: number}}
+ * @constructor
*/
-EventBus.prototype.off = function (filter) {
- var i = 0;
- while (i < this.subscriptions.length) {
- var subscription = this.subscriptions[i];
+util.HSVToRGB = function HSVToRGB(h, s, v) {
+ var r, g, b;
- var match = true;
- if (filter instanceof Object) {
- // filter is an object. All fields must match
- for (var prop in filter) {
- if (filter.hasOwnProperty(prop)) {
- if (filter[prop] !== subscription[prop]) {
- match = false;
- }
- }
- }
- }
- else {
- // filter is a string, filter on id
- match = (subscription.id == filter);
- }
+ var i = Math.floor(h * 6);
+ var f = h * 6 - i;
+ var p = v * (1 - s);
+ var q = v * (1 - f * s);
+ var t = v * (1 - (1 - f) * s);
- if (match) {
- this.subscriptions.splice(i, 1);
- }
- else {
- i++;
- }
+ switch (i % 6) {
+ case 0: r = v, g = t, b = p; break;
+ case 1: r = q, g = v, b = p; break;
+ case 2: r = p, g = v, b = t; break;
+ case 3: r = p, g = q, b = v; break;
+ case 4: r = t, g = p, b = v; break;
+ case 5: r = v, g = p, b = q; break;
}
-};
-/**
- * Emit an event
- * @param {String} event
- * @param {*} [data]
- * @param {*} [source]
- */
-EventBus.prototype.emit = function (event, data, source) {
- for (var i =0; i < this.subscriptions.length; i++) {
- var subscription = this.subscriptions[i];
- if (subscription.regexp.test(event)) {
- if (subscription.callback) {
- subscription.callback(event, data, source);
- }
- }
- }
+ return {r:Math.floor(r * 255), g:Math.floor(g * 255), b:Math.floor(b * 255) };
};
+util.HSVToHex = function HSVToHex(h,s,v) {
+ var rgb = util.HSVToRGB(h,s,v);
+ return util.RGBToHex(rgb.r,rgb.g,rgb.b);
+}
+
+util.hexToHSV = function hexToHSV(hex) {
+ var rgb = util.hexToRGB(hex);
+ return util.RGBToHSV(rgb.r,rgb.g,rgb.b);
+}
+
+util.isValidHex = function isValidHex(hex) {
+ var isOk = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(hex);
+ return isOk;
+}
+
/**
* DataSet
*
@@ -1265,7 +1196,7 @@ function DataSet (options) {
* {Object | null} params
* {String | Number} senderId
*/
-DataSet.prototype.subscribe = function (event, callback) {
+DataSet.prototype.on = function on (event, callback) {
var subscribers = this.subscribers[event];
if (!subscribers) {
subscribers = [];
@@ -1277,12 +1208,15 @@ DataSet.prototype.subscribe = function (event, callback) {
});
};
+// TODO: make this function deprecated (replaced with `on` since version 0.5)
+DataSet.prototype.subscribe = DataSet.prototype.on;
+
/**
* Unsubscribe from an event, remove an event listener
* @param {String} event
* @param {function} callback
*/
-DataSet.prototype.unsubscribe = function (event, callback) {
+DataSet.prototype.off = function off(event, callback) {
var subscribers = this.subscribers[event];
if (subscribers) {
this.subscribers[event] = subscribers.filter(function (listener) {
@@ -1291,6 +1225,9 @@ DataSet.prototype.unsubscribe = function (event, callback) {
}
};
+// TODO: make this function deprecated (replaced with `on` since version 0.5)
+DataSet.prototype.unsubscribe = DataSet.prototype.off;
+
/**
* Trigger an event
* @param {String} event
@@ -2198,8 +2135,8 @@ DataView.prototype.setData = function (data) {
this._trigger('add', {items: ids});
// subscribe to new dataset
- if (this.data.subscribe) {
- this.data.subscribe('*', this.listener);
+ if (this.data.on) {
+ this.data.on('*', this.listener);
}
}
};
@@ -2405,10 +2342,14 @@ DataView.prototype._onEvent = function (event, params, senderId) {
};
// copy subscription functionality from DataSet
-DataView.prototype.subscribe = DataSet.prototype.subscribe;
-DataView.prototype.unsubscribe = DataSet.prototype.unsubscribe;
+DataView.prototype.on = DataSet.prototype.on;
+DataView.prototype.off = DataSet.prototype.off;
DataView.prototype._trigger = DataSet.prototype._trigger;
+// TODO: make these functions deprecated (replaced with `on` and `off` since version 0.5)
+DataView.prototype.subscribe = DataView.prototype.on;
+DataView.prototype.unsubscribe = DataView.prototype.off;
+
/**
* @constructor TimeStep
* The class TimeStep is an iterator for dates. You provide a start date and an
@@ -2692,35 +2633,38 @@ TimeStep.prototype.setMinimumStep = function(minimumStep) {
};
/**
- * Snap a date to a rounded value. The snap intervals are dependent on the
- * current scale and step.
- * @param {Date} date the date to be snapped
+ * Snap a date to a rounded value.
+ * The snap intervals are dependent on the current scale and step.
+ * @param {Date} date the date to be snapped.
+ * @return {Date} snappedDate
*/
TimeStep.prototype.snap = function(date) {
+ var clone = new Date(date.valueOf());
+
if (this.scale == TimeStep.SCALE.YEAR) {
- var year = date.getFullYear() + Math.round(date.getMonth() / 12);
- date.setFullYear(Math.round(year / this.step) * this.step);
- date.setMonth(0);
- date.setDate(0);
- date.setHours(0);
- date.setMinutes(0);
- date.setSeconds(0);
- date.setMilliseconds(0);
+ var year = clone.getFullYear() + Math.round(clone.getMonth() / 12);
+ clone.setFullYear(Math.round(year / this.step) * this.step);
+ clone.setMonth(0);
+ clone.setDate(0);
+ clone.setHours(0);
+ clone.setMinutes(0);
+ clone.setSeconds(0);
+ clone.setMilliseconds(0);
}
else if (this.scale == TimeStep.SCALE.MONTH) {
- if (date.getDate() > 15) {
- date.setDate(1);
- date.setMonth(date.getMonth() + 1);
+ if (clone.getDate() > 15) {
+ clone.setDate(1);
+ clone.setMonth(clone.getMonth() + 1);
// important: first set Date to 1, after that change the month.
}
else {
- date.setDate(1);
+ clone.setDate(1);
}
- date.setHours(0);
- date.setMinutes(0);
- date.setSeconds(0);
- date.setMilliseconds(0);
+ clone.setHours(0);
+ clone.setMinutes(0);
+ clone.setSeconds(0);
+ clone.setMilliseconds(0);
}
else if (this.scale == TimeStep.SCALE.DAY ||
this.scale == TimeStep.SCALE.WEEKDAY) {
@@ -2728,56 +2672,58 @@ TimeStep.prototype.snap = function(date) {
switch (this.step) {
case 5:
case 2:
- date.setHours(Math.round(date.getHours() / 24) * 24); break;
+ clone.setHours(Math.round(clone.getHours() / 24) * 24); break;
default:
- date.setHours(Math.round(date.getHours() / 12) * 12); break;
+ clone.setHours(Math.round(clone.getHours() / 12) * 12); break;
}
- date.setMinutes(0);
- date.setSeconds(0);
- date.setMilliseconds(0);
+ clone.setMinutes(0);
+ clone.setSeconds(0);
+ clone.setMilliseconds(0);
}
else if (this.scale == TimeStep.SCALE.HOUR) {
switch (this.step) {
case 4:
- date.setMinutes(Math.round(date.getMinutes() / 60) * 60); break;
+ clone.setMinutes(Math.round(clone.getMinutes() / 60) * 60); break;
default:
- date.setMinutes(Math.round(date.getMinutes() / 30) * 30); break;
+ clone.setMinutes(Math.round(clone.getMinutes() / 30) * 30); break;
}
- date.setSeconds(0);
- date.setMilliseconds(0);
+ clone.setSeconds(0);
+ clone.setMilliseconds(0);
} else if (this.scale == TimeStep.SCALE.MINUTE) {
//noinspection FallthroughInSwitchStatementJS
switch (this.step) {
case 15:
case 10:
- date.setMinutes(Math.round(date.getMinutes() / 5) * 5);
- date.setSeconds(0);
+ clone.setMinutes(Math.round(clone.getMinutes() / 5) * 5);
+ clone.setSeconds(0);
break;
case 5:
- date.setSeconds(Math.round(date.getSeconds() / 60) * 60); break;
+ clone.setSeconds(Math.round(clone.getSeconds() / 60) * 60); break;
default:
- date.setSeconds(Math.round(date.getSeconds() / 30) * 30); break;
+ clone.setSeconds(Math.round(clone.getSeconds() / 30) * 30); break;
}
- date.setMilliseconds(0);
+ clone.setMilliseconds(0);
}
else if (this.scale == TimeStep.SCALE.SECOND) {
//noinspection FallthroughInSwitchStatementJS
switch (this.step) {
case 15:
case 10:
- date.setSeconds(Math.round(date.getSeconds() / 5) * 5);
- date.setMilliseconds(0);
+ clone.setSeconds(Math.round(clone.getSeconds() / 5) * 5);
+ clone.setMilliseconds(0);
break;
case 5:
- date.setMilliseconds(Math.round(date.getMilliseconds() / 1000) * 1000); break;
+ clone.setMilliseconds(Math.round(clone.getMilliseconds() / 1000) * 1000); break;
default:
- date.setMilliseconds(Math.round(date.getMilliseconds() / 500) * 500); break;
+ clone.setMilliseconds(Math.round(clone.getMilliseconds() / 500) * 500); break;
}
}
else if (this.scale == TimeStep.SCALE.MILLISECOND) {
var step = this.step > 5 ? this.step / 2 : 1;
- date.setMilliseconds(Math.round(date.getMilliseconds() / step) * step);
+ clone.setMilliseconds(Math.round(clone.getMilliseconds() / step) * step);
}
+
+ return clone;
};
/**
@@ -2862,11 +2808,11 @@ TimeStep.prototype.getLabelMajor = function(date) {
/**
* @constructor Stack
* Stacks items on top of each other.
- * @param {ItemSet} parent
+ * @param {ItemSet} itemset
* @param {Object} [options]
*/
-function Stack (parent, options) {
- this.parent = parent;
+function Stack (itemset, options) {
+ this.itemset = itemset;
this.options = options || {};
this.defaultOptions = {
@@ -2904,14 +2850,14 @@ function Stack (parent, options) {
/**
* Set options for the stack
* @param {Object} options Available options:
- * {ItemSet} parent
+ * {ItemSet} itemset
* {Number} margin
* {function} order Stacking order
*/
Stack.prototype.setOptions = function setOptions (options) {
util.extend(this.options, options);
- // TODO: register on data changes at the connected parent itemset, and update the changed part only and immediately
+ // TODO: register on data changes at the connected itemset, and update the changed part only and immediately
};
/**
@@ -2924,16 +2870,14 @@ Stack.prototype.update = function update() {
};
/**
- * Order the items. The items are ordered by width first, and by left position
- * second.
- * If a custom order function has been provided via the options, then this will
- * be used.
+ * Order the items. If a custom order function has been provided via the options,
+ * then this will be used.
* @private
*/
Stack.prototype._order = function _order () {
- var items = this.parent.items;
+ var items = this.itemset.items;
if (!items) {
- throw new Error('Cannot stack items: parent does not contain items');
+ throw new Error('Cannot stack items: ItemSet does not contain items');
}
// TODO: store the sorted items, to have less work later on
@@ -3046,8 +2990,8 @@ Stack.prototype.checkOverlap = function checkOverlap (items, itemIndex,
* @return {boolean} true if a and b collide, else false
*/
Stack.prototype.collision = function collision (a, b, margin) {
- return ((a.left - margin) < (b.left + b.getWidth()) &&
- (a.left + a.getWidth() + margin) > b.left &&
+ return ((a.left - margin) < (b.left + b.width) &&
+ (a.left + a.width + margin) > b.left &&
(a.top - margin) < (b.top + b.height) &&
(a.top + a.height + margin) > b.top);
};
@@ -3070,6 +3014,9 @@ function Range(options) {
this.setOptions(options);
}
+// extend the Range prototype with an event emitter mixin
+Emitter(Range.prototype);
+
/**
* Set options for the range controller
* @param {Object} options Available options:
@@ -3102,42 +3049,48 @@ function validateDirection (direction) {
/**
* Add listeners for mouse and touch events to the component
- * @param {Component} component
+ * @param {Controller} controller
+ * @param {Component} component Should be a rootpanel
* @param {String} event Available events: 'move', 'zoom'
* @param {String} direction Available directions: 'horizontal', 'vertical'
*/
-Range.prototype.subscribe = function (component, event, direction) {
+Range.prototype.subscribe = function (controller, component, event, direction) {
var me = this;
if (event == 'move') {
// drag start listener
- component.on('dragstart', function (event) {
+ controller.on('dragstart', function (event) {
me._onDragStart(event, component);
});
// drag listener
- component.on('drag', function (event) {
+ controller.on('drag', function (event) {
me._onDrag(event, component, direction);
});
// drag end listener
- component.on('dragend', function (event) {
+ controller.on('dragend', function (event) {
me._onDragEnd(event, component);
});
+
+ // ignore dragging when holding
+ controller.on('hold', function (event) {
+ me._onHold();
+ });
}
else if (event == 'zoom') {
// mouse wheel
function mousewheel (event) {
me._onMouseWheel(event, component, direction);
}
- component.on('mousewheel', mousewheel);
- component.on('DOMMouseScroll', mousewheel); // For FF
+ controller.on('mousewheel', mousewheel);
+ controller.on('DOMMouseScroll', mousewheel); // For FF
// pinch
- component.on('touch', function (event) {
- me._onTouch();
+ controller.on('touch', function (event) {
+ me._onTouch(event);
});
- component.on('pinch', function (event) {
+ controller.on('pinch', function (event) {
me._onPinch(event, component, direction);
});
}
@@ -3147,44 +3100,6 @@ Range.prototype.subscribe = function (component, event, direction) {
}
};
-/**
- * Add event listener
- * @param {String} event Name of the event.
- * Available events: 'rangechange', 'rangechanged'
- * @param {function} callback Callback function, invoked as callback({start: Date, end: Date})
- */
-Range.prototype.on = function on (event, callback) {
- var available = ['rangechange', 'rangechanged'];
-
- if (available.indexOf(event) == -1) {
- throw new Error('Unknown event "' + event + '". Choose from ' + available.join());
- }
-
- events.addListener(this, event, callback);
-};
-
-/**
- * Remove an event listener
- * @param {String} event name of the event
- * @param {function} callback callback handler
- */
-Range.prototype.off = function off (event, callback) {
- events.removeListener(this, event, callback);
-};
-
-/**
- * Trigger an event
- * @param {String} event name of the event, available events: 'rangechange',
- * 'rangechanged'
- * @private
- */
-Range.prototype._trigger = function (event) {
- events.trigger(this, event, {
- start: this.start,
- end: this.end
- });
-};
-
/**
* Set a new start and end range
* @param {Number} [start]
@@ -3193,8 +3108,12 @@ Range.prototype._trigger = function (event) {
Range.prototype.setRange = function(start, end) {
var changed = this._applyRange(start, end);
if (changed) {
- this._trigger('rangechange');
- this._trigger('rangechanged');
+ var params = {
+ start: this.start,
+ end: this.end
+ };
+ this.emit('rangechange', params);
+ this.emit('rangechanged', params);
}
};
@@ -3365,7 +3284,9 @@ var touchParams = {};
Range.prototype._onDragStart = function(event, component) {
// refuse to drag when we where pinching to prevent the timeline make a jump
// when releasing the fingers in opposite order from the touch screen
- if (touchParams.pinching) return;
+ if (touchParams.ignore) return;
+
+ // TODO: reckon with option movable
touchParams.start = this.start;
touchParams.end = this.end;
@@ -3386,9 +3307,12 @@ Range.prototype._onDragStart = function(event, component) {
Range.prototype._onDrag = function (event, component, direction) {
validateDirection(direction);
+ // TODO: reckon with option movable
+
+
// refuse to drag when we where pinching to prevent the timeline make a jump
// when releasing the fingers in opposite order from the touch screen
- if (touchParams.pinching) return;
+ if (touchParams.ignore) return;
var delta = (direction == 'horizontal') ? event.gesture.deltaX : event.gesture.deltaY,
interval = (touchParams.end - touchParams.start),
@@ -3397,8 +3321,10 @@ Range.prototype._onDrag = function (event, component, direction) {
this._applyRange(touchParams.start + diffRange, touchParams.end + diffRange);
- // fire a rangechange event
- this._trigger('rangechange');
+ this.emit('rangechange', {
+ start: this.start,
+ end: this.end
+ });
};
/**
@@ -3410,14 +3336,19 @@ Range.prototype._onDrag = function (event, component, direction) {
Range.prototype._onDragEnd = function (event, component) {
// refuse to drag when we where pinching to prevent the timeline make a jump
// when releasing the fingers in opposite order from the touch screen
- if (touchParams.pinching) return;
+ if (touchParams.ignore) return;
+
+ // TODO: reckon with option movable
if (component.frame) {
component.frame.style.cursor = 'auto';
}
// fire a rangechanged event
- this._trigger('rangechanged');
+ this.emit('rangechanged', {
+ start: this.start,
+ end: this.end
+ });
};
/**
@@ -3431,6 +3362,8 @@ Range.prototype._onDragEnd = function (event, component) {
Range.prototype._onMouseWheel = function(event, component, direction) {
validateDirection(direction);
+ // TODO: reckon with option zoomable
+
// retrieve delta
var delta = 0;
if (event.wheelDelta) { /* IE/Opera. */
@@ -3459,7 +3392,7 @@ Range.prototype._onMouseWheel = function(event, component, direction) {
// calculate center, the date to zoom around
var gesture = util.fakeGesture(this, event),
- pointer = getPointer(gesture.touches[0], component.frame),
+ pointer = getPointer(gesture.center, component.frame),
pointerDate = this._pointerToDate(component, direction, pointer);
this.zoom(scale, pointerDate);
@@ -3467,18 +3400,33 @@ Range.prototype._onMouseWheel = function(event, component, direction) {
// Prevent default actions caused by mouse wheel
// (else the page and timeline both zoom and scroll)
- util.preventDefault(event);
+ event.preventDefault();
};
/**
- * On start of a touch gesture, initialize scale to 1
+ * Start of a touch gesture
* @private
*/
-Range.prototype._onTouch = function () {
+Range.prototype._onTouch = function (event) {
touchParams.start = this.start;
touchParams.end = this.end;
- touchParams.pinching = false;
+ touchParams.ignore = false;
touchParams.center = null;
+
+ // don't move the range when dragging a selected event
+ // TODO: it's not so neat to have to know about the state of the ItemSet
+ var item = ItemSet.itemFromTarget(event);
+ if (item && item.selected && this.options.editable) {
+ touchParams.ignore = true;
+ }
+};
+
+/**
+ * On start of a hold gesture
+ * @private
+ */
+Range.prototype._onHold = function () {
+ touchParams.ignore = true;
};
/**
@@ -3489,7 +3437,9 @@ Range.prototype._onTouch = function () {
* @private
*/
Range.prototype._onPinch = function (event, component, direction) {
- touchParams.pinching = true;
+ touchParams.ignore = true;
+
+ // TODO: reckon with option zoomable
if (event.gesture.touches.length > 1) {
if (!touchParams.center) {
@@ -3609,16 +3559,59 @@ Range.prototype.moveTo = function(moveTo) {
/**
* @constructor Controller
*
- * A Controller controls the reflows and repaints of all visual components
+ * A Controller controls the reflows and repaints of all components,
+ * and is used as an event bus for all components.
*/
function Controller () {
+ var me = this;
+
this.id = util.randomUUID();
this.components = {};
- this.repaintTimer = undefined;
- this.reflowTimer = undefined;
+ /**
+ * Listen for a 'request-reflow' event. The controller will schedule a reflow
+ * @param {Boolean} [force] If true, an immediate reflow is forced. Default
+ * is false.
+ */
+ var reflowTimer = null;
+ this.on('request-reflow', function requestReflow(force) {
+ if (force) {
+ me.reflow();
+ }
+ else {
+ if (!reflowTimer) {
+ reflowTimer = setTimeout(function () {
+ reflowTimer = null;
+ me.reflow();
+ }, 0);
+ }
+ }
+ });
+
+ /**
+ * Request a repaint. The controller will schedule a repaint
+ * @param {Boolean} [force] If true, an immediate repaint is forced. Default
+ * is false.
+ */
+ var repaintTimer = null;
+ this.on('request-repaint', function requestRepaint(force) {
+ if (force) {
+ me.repaint();
+ }
+ else {
+ if (!repaintTimer) {
+ repaintTimer = setTimeout(function () {
+ repaintTimer = null;
+ me.repaint();
+ }, 0);
+ }
+ }
+ });
}
+// Extend controller with Emitter mixin
+Emitter(Controller.prototype);
+
/**
* Add a component to the controller
* @param {Component} component
@@ -3634,7 +3627,7 @@ Controller.prototype.add = function add(component) {
}
// add the component
- component.controller = this;
+ component.setController(this);
this.components[component.id] = component;
};
@@ -3646,54 +3639,18 @@ Controller.prototype.remove = function remove(component) {
var id;
for (id in this.components) {
if (this.components.hasOwnProperty(id)) {
- if (id == component || this.components[id] == component) {
+ if (id == component || this.components[id] === component) {
break;
}
}
}
if (id) {
- delete this.components[id];
- }
-};
-
-/**
- * Request a reflow. The controller will schedule a reflow
- * @param {Boolean} [force] If true, an immediate reflow is forced. Default
- * is false.
- */
-Controller.prototype.requestReflow = function requestReflow(force) {
- if (force) {
- this.reflow();
- }
- else {
- if (!this.reflowTimer) {
- var me = this;
- this.reflowTimer = setTimeout(function () {
- me.reflowTimer = undefined;
- me.reflow();
- }, 0);
- }
- }
-};
+ // unregister the controller (gives the component the ability to unregister
+ // event listeners and clean up other stuff)
+ this.components[id].setController(null);
-/**
- * Request a repaint. The controller will schedule a repaint
- * @param {Boolean} [force] If true, an immediate repaint is forced. Default
- * is false.
- */
-Controller.prototype.requestRepaint = function requestRepaint(force) {
- if (force) {
- this.repaint();
- }
- else {
- if (!this.repaintTimer) {
- var me = this;
- this.repaintTimer = setTimeout(function () {
- me.repaintTimer = undefined;
- me.repaint();
- }, 0);
- }
+ delete this.components[id];
}
};
@@ -3731,6 +3688,8 @@ Controller.prototype.repaint = function repaint() {
util.forEach(this.components, repaint);
+ this.emit('repaint');
+
// immediately reflow when needed
if (changed) {
this.reflow();
@@ -3772,6 +3731,8 @@ Controller.prototype.reflow = function reflow() {
util.forEach(this.components, reflow);
+ this.emit('reflow');
+
// immediately repaint when needed
if (resized) {
this.repaint();
@@ -3801,7 +3762,6 @@ function Component () {
* set.
* @param {Object} options Available parameters:
* {String | function} [className]
- * {EventBus} [eventBus]
* {String | Number | function} [left]
* {String | Number | function} [top]
* {String | Number | function} [width]
@@ -3836,6 +3796,23 @@ Component.prototype.getOption = function getOption(name) {
return value;
};
+/**
+ * Set controller for this component, or remove current controller by passing
+ * null as parameter value.
+ * @param {Controller | null} controller
+ */
+Component.prototype.setController = function setController (controller) {
+ this.controller = controller || null;
+};
+
+/**
+ * Get controller of this component
+ * @return {Controller} controller
+ */
+Component.prototype.getController = function getController () {
+ return this.controller;
+};
+
/**
* Get the container element of the component, which can be used by a child to
* add its own widgets. Not all components do have a container for childs, in
@@ -3907,7 +3884,7 @@ Component.prototype.show = function show() {
*/
Component.prototype.requestRepaint = function requestRepaint() {
if (this.controller) {
- this.controller.requestRepaint();
+ this.controller.emit('request-repaint');
}
else {
throw new Error('Cannot request a repaint: no controller configured');
@@ -3920,7 +3897,7 @@ Component.prototype.requestRepaint = function requestRepaint() {
*/
Component.prototype.requestReflow = function requestReflow() {
if (this.controller) {
- this.controller.requestReflow();
+ this.controller.emit('request-reflow');
}
else {
throw new Error('Cannot request a reflow: no controller configured');
@@ -3984,7 +3961,7 @@ Panel.prototype.repaint = function () {
frame = this.frame;
if (!frame) {
frame = document.createElement('div');
- frame.className = 'panel';
+ frame.className = 'vpanel';
var className = options.className;
if (className) {
@@ -4053,12 +4030,29 @@ function RootPanel(container, options) {
this.id = util.randomUUID();
this.container = container;
+ // create functions to be used as DOM event listeners
+ var me = this;
+ this.hammer = null;
+
+ // create listeners for all interesting events, these events will be emitted
+ // via the controller
+ var events = [
+ 'touch', 'pinch', 'tap', 'doubletap', 'hold',
+ 'dragstart', 'drag', 'dragend',
+ 'mousewheel', 'DOMMouseScroll' // DOMMouseScroll is for Firefox
+ ];
+ this.listeners = {};
+ events.forEach(function (event) {
+ me.listeners[event] = function () {
+ var args = [event].concat(Array.prototype.slice.call(arguments, 0));
+ me.controller.emit.apply(me.controller, args);
+ };
+ });
+
this.options = options || {};
this.defaultOptions = {
autoResize: true
};
-
- this.listeners = {}; // event listeners
}
RootPanel.prototype = new Panel();
@@ -4091,6 +4085,8 @@ RootPanel.prototype.repaint = function () {
this.frame = frame;
+ this._registerListeners();
+
changed += 1;
}
if (!frame.parentNode) {
@@ -4101,7 +4097,8 @@ RootPanel.prototype.repaint = function () {
changed += 1;
}
- frame.className = 'vis timeline rootpanel ' + options.orientation;
+ frame.className = 'vis timeline rootpanel ' + options.orientation +
+ (options.editable ? ' editable' : '');
var className = options.className;
if (className) {
util.addClassName(frame, util.option.asString(className));
@@ -4112,7 +4109,6 @@ RootPanel.prototype.repaint = function () {
changed += update(frame.style, 'width', asSize(options.width, '100%'));
changed += update(frame.style, 'height', asSize(options.height, '100%'));
- this._updateEventEmitters();
this._updateWatch();
return (changed > 0);
@@ -4201,59 +4197,52 @@ RootPanel.prototype._unwatch = function () {
};
/**
- * Event handler
- * @param {String} event name of the event, for example 'click', 'mousemove'
- * @param {function} callback callback handler, invoked with the raw HTML Event
- * as parameter.
+ * Set controller for this component, or remove current controller by passing
+ * null as parameter value.
+ * @param {Controller | null} controller
*/
-RootPanel.prototype.on = function (event, callback) {
- // register the listener at this component
- var arr = this.listeners[event];
- if (!arr) {
- arr = [];
- this.listeners[event] = arr;
- }
- arr.push(callback);
+RootPanel.prototype.setController = function setController (controller) {
+ this.controller = controller || null;
- this._updateEventEmitters();
+ if (this.controller) {
+ this._registerListeners();
+ }
+ else {
+ this._unregisterListeners();
+ }
};
/**
- * Update the event listeners for all event emitters
+ * Register event emitters emitted by the rootpanel
* @private
*/
-RootPanel.prototype._updateEventEmitters = function () {
- if (this.listeners) {
- var me = this;
- util.forEach(this.listeners, function (listeners, event) {
- if (!me.emitters) {
- me.emitters = {};
- }
- if (!(event in me.emitters)) {
- // create event
- var frame = me.frame;
- if (frame) {
- //console.log('Created a listener for event ' + event + ' on component ' + me.id); // TODO: cleanup logging
- var callback = function(event) {
- listeners.forEach(function (listener) {
- // TODO: filter on event target!
- listener(event);
- });
- };
- me.emitters[event] = callback;
+RootPanel.prototype._registerListeners = function () {
+ if (this.frame && this.controller && !this.hammer) {
+ this.hammer = Hammer(this.frame, {
+ prevent_default: true
+ });
- if (!me.hammer) {
- me.hammer = Hammer(frame, {
- prevent_default: true
- });
- }
- me.hammer.on(event, callback);
- }
+ for (var event in this.listeners) {
+ if (this.listeners.hasOwnProperty(event)) {
+ this.hammer.on(event, this.listeners[event]);
}
- });
+ }
+ }
+};
+
+/**
+ * Unregister event emitters from the rootpanel
+ * @private
+ */
+RootPanel.prototype._unregisterListeners = function () {
+ if (this.hammer) {
+ for (var event in this.listeners) {
+ if (this.listeners.hasOwnProperty(event)) {
+ this.hammer.off(event, this.listeners[event]);
+ }
+ }
- // TODO: be able to delete event listeners
- // TODO: be able to move event listeners to a parent when available
+ this.hammer = null;
}
};
@@ -4780,6 +4769,16 @@ TimeAxis.prototype._updateConversion = function() {
}
};
+/**
+ * Snap a date to a rounded value.
+ * The snap intervals are dependent on the current scale and step.
+ * @param {Date} date the date to be snapped.
+ * @return {Date} snappedDate
+ */
+TimeAxis.prototype.snap = function snap (date) {
+ return this.step.snap(date);
+};
+
/**
* A current time bar
* @param {Component} parent
@@ -4838,7 +4837,7 @@ CurrentTime.prototype.repaint = function () {
delete this.frame;
}
- return;
+ return false;
}
if (!bar) {
@@ -4903,12 +4902,14 @@ function CustomTime (parent, depends, options) {
showCustomTime: false
};
- this.listeners = [];
this.customTime = new Date();
+ this.eventParams = {}; // stores state parameters while dragging the bar
}
CustomTime.prototype = new Component();
+Emitter(CustomTime.prototype);
+
CustomTime.prototype.setOptions = Component.prototype.setOptions;
/**
@@ -4926,13 +4927,13 @@ CustomTime.prototype.getContainer = function () {
*/
CustomTime.prototype.repaint = function () {
var bar = this.frame,
- parent = this.parent,
- parentContainer = parent.parent.getContainer();
+ parent = this.parent;
if (!parent) {
throw new Error('Cannot repaint bar: no parent attached');
}
+ var parentContainer = parent.parent.getContainer();
if (!parentContainer) {
throw new Error('Cannot repaint bar: parent has no container element');
}
@@ -4943,7 +4944,7 @@ CustomTime.prototype.repaint = function () {
delete this.frame;
}
- return;
+ return false;
}
if (!bar) {
@@ -4965,7 +4966,13 @@ CustomTime.prototype.repaint = function () {
this.frame = bar;
- this.subscribe(this, 'movetime');
+ // attach event listeners
+ this.hammer = Hammer(bar, {
+ prevent_default: true
+ });
+ this.hammer.on('dragstart', this._onDragStart.bind(this));
+ this.hammer.on('drag', this._onDrag.bind(this));
+ this.hammer.on('dragend', this._onDragEnd.bind(this));
}
if (!parent.conversion) {
@@ -4984,7 +4991,7 @@ CustomTime.prototype.repaint = function () {
* Set custom time.
* @param {Date} time
*/
-CustomTime.prototype._setCustomTime = function(time) {
+CustomTime.prototype.setCustomTime = function(time) {
this.customTime = new Date(time.valueOf());
this.repaint();
};
@@ -4993,149 +5000,60 @@ CustomTime.prototype._setCustomTime = function(time) {
* Retrieve the current custom time.
* @return {Date} customTime
*/
-CustomTime.prototype._getCustomTime = function() {
+CustomTime.prototype.getCustomTime = function() {
return new Date(this.customTime.valueOf());
};
/**
- * Add listeners for mouse and touch events to the component
- * @param {Component} component
+ * Start moving horizontally
+ * @param {Event} event
+ * @private
*/
-CustomTime.prototype.subscribe = function (component, event) {
- var me = this;
- var listener = {
- component: component,
- event: event,
- callback: function (event) {
- me._onMouseDown(event, listener);
- },
- params: {}
- };
+CustomTime.prototype._onDragStart = function(event) {
+ this.eventParams.customTime = this.customTime;
- component.on('mousedown', listener.callback);
- me.listeners.push(listener);
-
-};
-
-/**
- * Event handler
- * @param {String} event name of the event, for example 'click', 'mousemove'
- * @param {function} callback callback handler, invoked with the raw HTML Event
- * as parameter.
- */
-CustomTime.prototype.on = function (event, callback) {
- var bar = this.frame;
- if (!bar) {
- throw new Error('Cannot add event listener: no parent attached');
- }
-
- events.addListener(this, event, callback);
- util.addEventListener(bar, event, callback);
-};
-
-/**
- * Start moving horizontally
- * @param {Event} event
- * @param {Object} listener Listener containing the component and params
- * @private
- */
-CustomTime.prototype._onMouseDown = function(event, listener) {
- event = event || window.event;
- var params = listener.params;
-
- // only react on left mouse button down
- var leftButtonDown = event.which ? (event.which == 1) : (event.button == 1);
- if (!leftButtonDown) {
- return;
- }
-
- // get mouse position
- params.mouseX = util.getPageX(event);
- params.moved = false;
-
- params.customTime = this.customTime;
-
- // add event listeners to handle moving the custom time bar
- var me = this;
- if (!params.onMouseMove) {
- params.onMouseMove = function (event) {
- me._onMouseMove(event, listener);
- };
- util.addEventListener(document, 'mousemove', params.onMouseMove);
- }
- if (!params.onMouseUp) {
- params.onMouseUp = function (event) {
- me._onMouseUp(event, listener);
- };
- util.addEventListener(document, 'mouseup', params.onMouseUp);
- }
-
- util.stopPropagation(event);
- util.preventDefault(event);
+ event.stopPropagation();
+ event.preventDefault();
};
/**
* Perform moving operating.
- * This function activated from within the funcion CustomTime._onMouseDown().
* @param {Event} event
- * @param {Object} listener
* @private
*/
-CustomTime.prototype._onMouseMove = function (event, listener) {
- event = event || window.event;
- var params = listener.params;
- var parent = this.parent;
-
- // calculate change in mouse position
- var mouseX = util.getPageX(event);
-
- if (params.mouseX === undefined) {
- params.mouseX = mouseX;
- }
+CustomTime.prototype._onDrag = function (event) {
+ var deltaX = event.gesture.deltaX,
+ x = this.parent.toScreen(this.eventParams.customTime) + deltaX,
+ time = this.parent.toTime(x);
- var diff = mouseX - params.mouseX;
-
- // if mouse movement is big enough, register it as a "moved" event
- if (Math.abs(diff) >= 1) {
- params.moved = true;
- }
-
- var x = parent.toScreen(params.customTime);
- var xnew = x + diff;
- var time = parent.toTime(xnew);
- this._setCustomTime(time);
+ this.setCustomTime(time);
// fire a timechange event
- events.trigger(this, 'timechange', {customTime: this.customTime});
+ if (this.controller) {
+ this.controller.emit('timechange', {
+ time: this.customTime
+ })
+ }
- util.preventDefault(event);
+ event.stopPropagation();
+ event.preventDefault();
};
/**
* Stop moving operating.
- * This function activated from within the function CustomTime._onMouseDown().
* @param {event} event
- * @param {Object} listener
* @private
*/
-CustomTime.prototype._onMouseUp = function (event, listener) {
- event = event || window.event;
- var params = listener.params;
-
- // remove event listeners here, important for Safari
- if (params.onMouseMove) {
- util.removeEventListener(document, 'mousemove', params.onMouseMove);
- params.onMouseMove = null;
- }
- if (params.onMouseUp) {
- util.removeEventListener(document, 'mouseup', params.onMouseUp);
- params.onMouseUp = null;
+CustomTime.prototype._onDragEnd = function (event) {
+ // fire a timechanged event
+ if (this.controller) {
+ this.controller.emit('timechanged', {
+ time: this.customTime
+ })
}
- if (params.moved) {
- // fire a timechanged event
- events.trigger(this, 'timechanged', {customTime: this.customTime});
- }
+ event.stopPropagation();
+ event.preventDefault();
};
/**
@@ -5156,6 +5074,13 @@ function ItemSet(parent, depends, options) {
this.parent = parent;
this.depends = depends;
+ // event listeners
+ this.eventListeners = {
+ dragstart: this._onDragStart.bind(this),
+ drag: this._onDrag.bind(this),
+ dragend: this._onDragEnd.bind(this)
+ };
+
// one options object is shared by this itemset and all its items
this.options = options || {};
this.defaultOptions = {
@@ -5175,6 +5100,7 @@ function ItemSet(parent, depends, options) {
this.itemsData = null; // DataSet
this.range = null; // Range or Object {start: number, end: number}
+ // data change listeners
this.listeners = {
'add': function (event, params, senderId) {
if (senderId != me.id) {
@@ -5199,6 +5125,8 @@ function ItemSet(parent, depends, options) {
this.stack = new Stack(this, Object.create(this.options));
this.conversion = null;
+ this.touchParams = {}; // stores properties while dragging
+
// TODO: ItemSet should also attach event listeners for rangechange and rangechanged, like timeaxis
}
@@ -5236,9 +5164,61 @@ ItemSet.types = {
* {Number} padding
* Padding of the contents of an item in pixels.
* Must correspond with the items css. Default is 5.
+ * {Function} snap
+ * Function to let items snap to nice dates when
+ * dragging items.
*/
ItemSet.prototype.setOptions = Component.prototype.setOptions;
+
+
+/**
+ * Set controller for this component
+ * @param {Controller | null} controller
+ */
+ItemSet.prototype.setController = function setController (controller) {
+ var event;
+
+ // unregister old event listeners
+ if (this.controller) {
+ for (event in this.eventListeners) {
+ if (this.eventListeners.hasOwnProperty(event)) {
+ this.controller.off(event, this.eventListeners[event]);
+ }
+ }
+ }
+
+ this.controller = controller || null;
+
+ // register new event listeners
+ if (this.controller) {
+ for (event in this.eventListeners) {
+ if (this.eventListeners.hasOwnProperty(event)) {
+ this.controller.on(event, this.eventListeners[event]);
+ }
+ }
+ }
+};
+
+// attach event listeners for dragging items to the controller
+(function (me) {
+ var _controller = null;
+ var _onDragStart = null;
+ var _onDrag = null;
+ var _onDragEnd = null;
+
+ Object.defineProperty(me, 'controller', {
+ get: function () {
+ return _controller;
+ },
+
+ set: function (controller) {
+
+ }
+ });
+}) (this);
+
+
/**
* Set range (start and end).
* @param {Range | Object} range A Range or an object containing start and end.
@@ -5284,12 +5264,6 @@ ItemSet.prototype.setSelection = function setSelection(ids) {
}
}
- // trigger a select event
- selection = this.selection.concat([]);
- events.trigger(this, 'select', {
- ids: selection
- });
-
if (this.controller) {
this.requestRepaint();
}
@@ -5335,6 +5309,7 @@ ItemSet.prototype.repaint = function repaint() {
if (!frame) {
frame = document.createElement('div');
frame.className = 'itemset';
+ frame['timeline-itemset'] = this;
var className = options.className;
if (className) {
@@ -5641,7 +5616,7 @@ ItemSet.prototype.setItems = function setItems(items) {
// subscribe to new dataset
var id = this.id;
util.forEach(this.listeners, function (callback, event) {
- me.itemsData.subscribe(event, callback, id);
+ me.itemsData.on(event, callback, id);
});
// draw all new items
@@ -5658,6 +5633,24 @@ ItemSet.prototype.getItems = function getItems() {
return this.itemsData;
};
+/**
+ * Remove an item by its id
+ * @param {String | Number} id
+ */
+ItemSet.prototype.removeItem = function removeItem (id) {
+ var item = this.itemsData.get(id),
+ dataset = this._myDataSet();
+
+ if (item) {
+ // confirm deletion
+ this.options.onRemove(item, function (item) {
+ if (item) {
+ dataset.remove(item);
+ }
+ });
+ }
+};
+
/**
* Handle updated items
* @param {Number[]} ids
@@ -5751,6 +5744,193 @@ ItemSet.prototype.toScreen = function toScreen(time) {
return (time.valueOf() - conversion.offset) * conversion.scale;
};
+/**
+ * Start dragging the selected events
+ * @param {Event} event
+ * @private
+ */
+ItemSet.prototype._onDragStart = function (event) {
+ if (!this.options.editable) {
+ return;
+ }
+
+ var item = ItemSet.itemFromTarget(event),
+ me = this;
+
+ if (item && item.selected) {
+ var dragLeftItem = event.target.dragLeftItem;
+ var dragRightItem = event.target.dragRightItem;
+
+ if (dragLeftItem) {
+ this.touchParams.itemProps = [{
+ item: dragLeftItem,
+ start: item.data.start.valueOf()
+ }];
+ }
+ else if (dragRightItem) {
+ this.touchParams.itemProps = [{
+ item: dragRightItem,
+ end: item.data.end.valueOf()
+ }];
+ }
+ else {
+ this.touchParams.itemProps = this.getSelection().map(function (id) {
+ var item = me.items[id];
+ var props = {
+ item: item
+ };
+
+ if ('start' in item.data) {
+ props.start = item.data.start.valueOf()
+ }
+ if ('end' in item.data) {
+ props.end = item.data.end.valueOf()
+ }
+
+ return props;
+ });
+ }
+
+ event.stopPropagation();
+ }
+};
+
+/**
+ * Drag selected items
+ * @param {Event} event
+ * @private
+ */
+ItemSet.prototype._onDrag = function (event) {
+ if (this.touchParams.itemProps) {
+ var snap = this.options.snap || null,
+ deltaX = event.gesture.deltaX,
+ offset = deltaX / this.conversion.scale;
+
+ // move
+ this.touchParams.itemProps.forEach(function (props) {
+ if ('start' in props) {
+ var start = new Date(props.start + offset);
+ props.item.data.start = snap ? snap(start) : start;
+ }
+ if ('end' in props) {
+ var end = new Date(props.end + offset);
+ props.item.data.end = snap ? snap(end) : end;
+ }
+ });
+
+ // TODO: implement onMoving handler
+
+ // TODO: implement dragging from one group to another
+
+ this.requestReflow();
+
+ event.stopPropagation();
+ }
+};
+
+/**
+ * End of dragging selected items
+ * @param {Event} event
+ * @private
+ */
+ItemSet.prototype._onDragEnd = function (event) {
+ if (this.touchParams.itemProps) {
+ // prepare a change set for the changed items
+ var changes = [],
+ me = this,
+ dataset = this._myDataSet(),
+ type;
+
+ this.touchParams.itemProps.forEach(function (props) {
+ var id = props.item.id,
+ item = me.itemsData.get(id);
+
+ var changed = false;
+ if ('start' in props.item.data) {
+ changed = (props.start != props.item.data.start.valueOf());
+ item.start = util.convert(props.item.data.start, dataset.convert['start']);
+ }
+ if ('end' in props.item.data) {
+ changed = changed || (props.end != props.item.data.end.valueOf());
+ item.end = util.convert(props.item.data.end, dataset.convert['end']);
+ }
+
+ // only apply changes when start or end is actually changed
+ if (changed) {
+ me.options.onMove(item, function (item) {
+ if (item) {
+ // apply changes
+ changes.push(item);
+ }
+ else {
+ // restore original values
+ if ('start' in props) props.item.data.start = props.start;
+ if ('end' in props) props.item.data.end = props.end;
+ me.requestReflow();
+ }
+ });
+ }
+ });
+ this.touchParams.itemProps = null;
+
+ // apply the changes to the data (if there are changes)
+ if (changes.length) {
+ dataset.update(changes);
+ }
+
+ event.stopPropagation();
+ }
+};
+
+/**
+ * Find an item from an event target:
+ * searches for the attribute 'timeline-item' in the event target's element tree
+ * @param {Event} event
+ * @return {Item | null} item
+ */
+ItemSet.itemFromTarget = function itemFromTarget (event) {
+ var target = event.target;
+ while (target) {
+ if (target.hasOwnProperty('timeline-item')) {
+ return target['timeline-item'];
+ }
+ target = target.parentNode;
+ }
+
+ return null;
+};
+
+/**
+ * Find the ItemSet from an event target:
+ * searches for the attribute 'timeline-itemset' in the event target's element tree
+ * @param {Event} event
+ * @return {ItemSet | null} item
+ */
+ItemSet.itemSetFromTarget = function itemSetFromTarget (event) {
+ var target = event.target;
+ while (target) {
+ if (target.hasOwnProperty('timeline-itemset')) {
+ return target['timeline-itemset'];
+ }
+ target = target.parentNode;
+ }
+
+ return null;
+};
+
+/**
+ * Find the DataSet to which this ItemSet is connected
+ * @returns {null | DataSet} dataset
+ * @private
+ */
+ItemSet.prototype._myDataSet = function _myDataSet() {
+ // find the root DataSet
+ var dataset = this.itemsData;
+ while (dataset instanceof DataView) {
+ dataset = dataset.data;
+ }
+ return dataset;
+};
/**
* @constructor Item
* @param {ItemSet} parent
@@ -5773,6 +5953,7 @@ function Item (parent, data, options, defaultOptions) {
this.left = 0;
this.width = 0;
this.height = 0;
+ this.offset = 0;
}
/**
@@ -5826,12 +6007,46 @@ Item.prototype.reflow = function reflow() {
};
/**
- * Return the items width
- * @return {Integer} width
+ * Give the item a display offset in pixels
+ * @param {Number} offset Offset on screen in pixels
*/
-Item.prototype.getWidth = function getWidth() {
- return this.width;
-}
+Item.prototype.setOffset = function setOffset(offset) {
+ this.offset = offset;
+};
+
+/**
+ * Repaint a delete button on the top right of the item when the item is selected
+ * @param {HTMLElement} anchor
+ * @private
+ */
+Item.prototype._repaintDeleteButton = function (anchor) {
+ if (this.selected && this.options.editable && !this.dom.deleteButton) {
+ // create and show button
+ var parent = this.parent;
+ var id = this.id;
+
+ var deleteButton = document.createElement('div');
+ deleteButton.className = 'delete';
+ deleteButton.title = 'Delete this item';
+
+ Hammer(deleteButton, {
+ preventDefault: true
+ }).on('tap', function (event) {
+ parent.removeItem(id);
+ event.stopPropagation();
+ });
+
+ anchor.appendChild(deleteButton);
+ this.dom.deleteButton = deleteButton;
+ }
+ else if (!this.selected && this.dom.deleteButton) {
+ // remove button
+ if (this.dom.deleteButton.parentNode) {
+ this.dom.deleteButton.parentNode.removeChild(this.dom.deleteButton);
+ }
+ this.dom.deleteButton = null;
+ }
+};
/**
* @constructor ItemBox
@@ -5914,6 +6129,8 @@ ItemBox.prototype.repaint = function repaint() {
changed = true;
}
+ this._repaintDeleteButton(dom.box);
+
// update contents
if (this.data.content != this.content) {
this.content = this.data.content;
@@ -6022,7 +6239,7 @@ ItemBox.prototype.reflow = function reflow() {
update = util.updateProperty;
props = this.props;
options = this.options;
- start = this.parent.toScreen(this.data.start);
+ start = this.parent.toScreen(this.data.start) + this.offset;
align = options.align || this.defaultOptions.align;
margin = options.margin && options.margin.axis || this.defaultOptions.margin.axis;
orientation = options.orientation || this.defaultOptions.orientation;
@@ -6211,6 +6428,8 @@ ItemPoint.prototype.repaint = function repaint() {
changed = true;
}
+ this._repaintDeleteButton(dom.point);
+
// update class
var className = (this.data.className? ' ' + this.data.className : '') +
(this.selected ? ' selected' : '');
@@ -6295,7 +6514,7 @@ ItemPoint.prototype.reflow = function reflow() {
options = this.options;
orientation = options.orientation || this.defaultOptions.orientation;
margin = options.margin && options.margin.axis || this.defaultOptions.margin.axis;
- start = this.parent.toScreen(this.data.start);
+ start = this.parent.toScreen(this.data.start) + this.offset;
changed += update(this, 'width', dom.point.offsetWidth);
changed += update(this, 'height', dom.point.offsetHeight);
@@ -6443,8 +6662,12 @@ ItemRange.prototype.repaint = function repaint() {
changed = true;
}
+ this._repaintDeleteButton(dom.box);
+ this._repaintDragLeft();
+ this._repaintDragRight();
+
// update class
- var className = (this.data.className? ' ' + this.data.className : '') +
+ var className = (this.data.className ? (' ' + this.data.className) : '') +
(this.selected ? ' selected' : '');
if (this.className != className) {
this.className = className;
@@ -6533,8 +6756,8 @@ ItemRange.prototype.reflow = function reflow() {
props = this.props;
options = this.options;
parent = this.parent;
- start = parent.toScreen(this.data.start);
- end = parent.toScreen(this.data.end);
+ start = parent.toScreen(this.data.start) + this.offset;
+ end = parent.toScreen(this.data.end) + this.offset;
update = util.updateProperty;
box = dom.box;
parentWidth = parent.width;
@@ -6626,6 +6849,66 @@ ItemRange.prototype.reposition = function reposition() {
}
};
+/**
+ * Repaint a drag area on the left side of the range when the range is selected
+ * @private
+ */
+ItemRange.prototype._repaintDragLeft = function () {
+ if (this.selected && this.options.editable && !this.dom.dragLeft) {
+ // create and show drag area
+ var dragLeft = document.createElement('div');
+ dragLeft.className = 'drag-left';
+ dragLeft.dragLeftItem = this;
+
+ // TODO: this should be redundant?
+ Hammer(dragLeft, {
+ preventDefault: true
+ }).on('drag', function () {
+ //console.log('drag left')
+ });
+
+ this.dom.box.appendChild(dragLeft);
+ this.dom.dragLeft = dragLeft;
+ }
+ else if (!this.selected && this.dom.dragLeft) {
+ // delete drag area
+ if (this.dom.dragLeft.parentNode) {
+ this.dom.dragLeft.parentNode.removeChild(this.dom.dragLeft);
+ }
+ this.dom.dragLeft = null;
+ }
+};
+
+/**
+ * Repaint a drag area on the right side of the range when the range is selected
+ * @private
+ */
+ItemRange.prototype._repaintDragRight = function () {
+ if (this.selected && this.options.editable && !this.dom.dragRight) {
+ // create and show drag area
+ var dragRight = document.createElement('div');
+ dragRight.className = 'drag-right';
+ dragRight.dragRightItem = this;
+
+ // TODO: this should be redundant?
+ Hammer(dragRight, {
+ preventDefault: true
+ }).on('drag', function () {
+ //console.log('drag right')
+ });
+
+ this.dom.box.appendChild(dragRight);
+ this.dom.dragRight = dragRight;
+ }
+ else if (!this.selected && this.dom.dragRight) {
+ // delete drag area
+ if (this.dom.dragRight.parentNode) {
+ this.dom.dragRight.parentNode.removeChild(this.dom.dragRight);
+ }
+ this.dom.dragRight = null;
+ }
+};
+
/**
* @constructor ItemRangeOverflow
* @extends ItemRange
@@ -6644,6 +6927,22 @@ function ItemRangeOverflow (parent, data, options, defaultOptions) {
}
};
+ // define a private property _width, which is the with of the range box
+ // adhering to the ranges start and end date. The property width has a
+ // getter which returns the max of border width and content width
+ this._width = 0;
+ Object.defineProperty(this, 'width', {
+ get: function () {
+ return (this.props.content && this._width < this.props.content.width) ?
+ this.props.content.width :
+ this._width;
+ },
+
+ set: function (width) {
+ this._width = width;
+ }
+ });
+
ItemRange.call(this, parent, data, options, defaultOptions);
}
@@ -6690,13 +6989,18 @@ ItemRangeOverflow.prototype.repaint = function repaint() {
dom.content.innerHTML = this.content;
}
else {
- throw new Error('Property "content" missing in item ' + this.data.id);
+ throw new Error('Property "content" missing in item ' + this.id);
}
changed = true;
}
+ this._repaintDeleteButton(dom.box);
+ this._repaintDragLeft();
+ this._repaintDragRight();
+
// update class
- var className = this.data.className ? (' ' + this.data.className) : '';
+ var className = (this.data.className? ' ' + this.data.className : '') +
+ (this.selected ? ' selected' : '');
if (this.className != className) {
this.className = className;
dom.box.className = 'item rangeoverflow' + className;
@@ -6708,14 +7012,21 @@ ItemRangeOverflow.prototype.repaint = function repaint() {
};
/**
- * Return the items width
- * @return {Number} width
+ * Reposition the item, recalculate its left, top, and width, using the current
+ * range and size of the items itemset
+ * @override
*/
-ItemRangeOverflow.prototype.getWidth = function getWidth() {
- if (this.props.content !== undefined && this.width < this.props.content.width)
- return this.props.content.width;
- else
- return this.width;
+ItemRangeOverflow.prototype.reposition = function reposition() {
+ var dom = this.dom,
+ props = this.props;
+
+ if (dom) {
+ dom.box.style.top = this.top + 'px';
+ dom.box.style.left = this.left + 'px';
+ dom.box.style.width = this._width + 'px';
+
+ dom.content.style.left = props.content.left + 'px';
+ }
};
/**
@@ -6982,7 +7293,7 @@ GroupSet.prototype.setGroups = function setGroups(groups) {
// subscribe to new dataset
var id = this.id;
util.forEach(this.listeners, function (callback, event) {
- me.groupsData.subscribe(event, callback, id);
+ me.groupsData.on(event, callback, id);
});
// draw all new groups
@@ -7066,6 +7377,7 @@ GroupSet.prototype.repaint = function repaint() {
if (!frame) {
frame = document.createElement('div');
frame.className = 'groupset';
+ frame['timeline-groupset'] = this;
this.dom.frame = frame;
var className = options.className;
@@ -7229,7 +7541,7 @@ GroupSet.prototype.repaint = function repaint() {
GroupSet.prototype._createLabel = function(id) {
var group = this.groups[id];
var label = document.createElement('div');
- label.className = 'label';
+ label.className = 'vlabel';
var inner = document.createElement('div');
inner.className = 'inner';
label.appendChild(inner);
@@ -7396,28 +7708,79 @@ GroupSet.prototype._toQueue = function _toQueue(ids, action) {
};
/**
- * Create a timeline visualization
- * @param {HTMLElement} container
- * @param {vis.DataSet | Array | DataTable} [items]
- * @param {Object} [options] See Timeline.setOptions for the available options.
- * @constructor
+ * Find the Group from an event target:
+ * searches for the attribute 'timeline-groupset' in the event target's element
+ * tree, then finds the right group in this groupset
+ * @param {Event} event
+ * @return {Group | null} group
*/
-function Timeline (container, items, options) {
- var me = this;
- var now = moment().hours(0).minutes(0).seconds(0).milliseconds(0);
- this.options = {
- orientation: 'bottom',
- min: null,
- max: null,
- zoomMin: 10, // milliseconds
- zoomMax: 1000 * 60 * 60 * 24 * 365 * 10000, // milliseconds
+GroupSet.groupFromTarget = function groupFromTarget (event) {
+ var groupset,
+ target = event.target;
+
+ while (target) {
+ if (target.hasOwnProperty('timeline-groupset')) {
+ groupset = target['timeline-groupset'];
+ break;
+ }
+ target = target.parentNode;
+ }
+
+ if (groupset) {
+ for (var groupId in groupset.groups) {
+ if (groupset.groups.hasOwnProperty(groupId)) {
+ var group = groupset.groups[groupId];
+ if (group.itemset && ItemSet.itemSetFromTarget(event) == group.itemset) {
+ return group;
+ }
+ }
+ }
+ }
+
+ return null;
+};
+
+/**
+ * Create a timeline visualization
+ * @param {HTMLElement} container
+ * @param {vis.DataSet | Array | google.visualization.DataTable} [items]
+ * @param {Object} [options] See Timeline.setOptions for the available options.
+ * @constructor
+ */
+function Timeline (container, items, options) {
+ var me = this;
+ var now = moment().hours(0).minutes(0).seconds(0).milliseconds(0);
+ this.options = {
+ orientation: 'bottom',
+ autoResize: true,
+ editable: false,
+ selectable: true,
+ snap: null, // will be specified after timeaxis is created
+
+ min: null,
+ max: null,
+ zoomMin: 10, // milliseconds
+ zoomMax: 1000 * 60 * 60 * 24 * 365 * 10000, // milliseconds
// moveable: true, // TODO: option moveable
// zoomable: true, // TODO: option zoomable
+
showMinorLabels: true,
showMajorLabels: true,
showCurrentTime: false,
showCustomTime: false,
- autoResize: false
+
+ onAdd: function (item, callback) {
+ callback(item);
+ },
+ onUpdate: function (item, callback) {
+ callback(item);
+ },
+ onMove: function (item, callback) {
+ callback(item);
+ },
+ onRemove: function (item, callback) {
+ callback(item);
+ }
};
// controller
@@ -7442,6 +7805,15 @@ function Timeline (container, items, options) {
this.rootPanel = new RootPanel(container, rootOptions);
this.controller.add(this.rootPanel);
+ // single select (or unselect) when tapping an item
+ this.controller.on('tap', this._onSelectItem.bind(this));
+
+ // multi select when holding mouse/touch, or on ctrl+click
+ this.controller.on('hold', this._onMultiSelectItem.bind(this));
+
+ // add item on doubletap
+ this.controller.on('doubletap', this._onAddItem.bind(this));
+
// item panel
var itemOptions = Object.create(this.options);
itemOptions.left = function () {
@@ -7479,28 +7851,19 @@ function Timeline (container, items, options) {
now.clone().add('days', 4).valueOf()
);
- // TODO: reckon with options moveable and zoomable
- // TODO: put the listeners in setOptions, be able to dynamically change with options moveable and zoomable
- this.range.subscribe(this.rootPanel, 'move', 'horizontal');
- this.range.subscribe(this.rootPanel, 'zoom', 'horizontal');
+ this.range.subscribe(this.controller, this.rootPanel, 'move', 'horizontal');
+ this.range.subscribe(this.controller, this.rootPanel, 'zoom', 'horizontal');
this.range.on('rangechange', function (properties) {
var force = true;
- me.controller.requestReflow(force);
- me._trigger('rangechange', properties);
+ me.controller.emit('rangechange', properties);
+ me.controller.emit('request-reflow', force);
});
this.range.on('rangechanged', function (properties) {
var force = true;
- me.controller.requestReflow(force);
- me._trigger('rangechanged', properties);
+ me.controller.emit('rangechanged', properties);
+ me.controller.emit('request-reflow', force);
});
- // single select (or unselect) when tapping an item
- // TODO: implement ctrl+click
- this.rootPanel.on('tap', this._onSelectItem.bind(this));
-
- // multi select when holding mouse/touch, or on ctrl+click
- this.rootPanel.on('hold', this._onMultiSelectItem.bind(this));
-
// time axis
var timeaxisOptions = Object.create(rootOptions);
timeaxisOptions.range = this.range;
@@ -7511,6 +7874,7 @@ function Timeline (container, items, options) {
this.timeaxis = new TimeAxis(this.itemPanel, [], timeaxisOptions);
this.timeaxis.setRange(this.range);
this.controller.add(this.timeaxis);
+ this.options.snap = this.timeaxis.snap.bind(this.timeaxis);
// current time bar
this.currenttime = new CurrentTime(this.timeaxis, [], rootOptions);
@@ -7537,6 +7901,25 @@ function Timeline (container, items, options) {
}
}
+/**
+ * Add an event listener to the timeline
+ * @param {String} event Available events: select, rangechange, rangechanged,
+ * timechange, timechanged
+ * @param {function} callback
+ */
+Timeline.prototype.on = function on (event, callback) {
+ this.controller.on(event, callback);
+};
+
+/**
+ * Add an event listener from the timeline
+ * @param {String} event
+ * @param {function} callback
+ */
+Timeline.prototype.off = function off (event, callback) {
+ this.controller.off(event, callback);
+};
+
/**
* Set options
* @param {Object} options TODO: describe the available options
@@ -7548,6 +7931,25 @@ Timeline.prototype.setOptions = function (options) {
// both start and end are optional
this.range.setRange(options.start, options.end);
+ if ('editable' in options || 'selectable' in options) {
+ if (this.options.selectable) {
+ // force update of selection
+ this.setSelection(this.getSelection());
+ }
+ else {
+ // remove selection
+ this.setSelection([]);
+ }
+ }
+
+ // validate the callback functions
+ var validateCallback = (function (fn) {
+ if (!(this.options[fn] instanceof Function) || this.options[fn].length != 2) {
+ throw new Error('option ' + fn + ' must be a function ' + fn + '(item, callback)');
+ }
+ }).bind(this);
+ ['onAdd', 'onUpdate', 'onRemove', 'onMove'].forEach(validateCallback);
+
this.controller.reflow();
this.controller.repaint();
};
@@ -7557,7 +7959,11 @@ Timeline.prototype.setOptions = function (options) {
* @param {Date} time
*/
Timeline.prototype.setCustomTime = function (time) {
- this.customtime._setCustomTime(time);
+ if (!this.customtime) {
+ throw new Error('Cannot get custom time: Custom time bar is not enabled');
+ }
+
+ this.customtime.setCustomTime(time);
};
/**
@@ -7565,37 +7971,41 @@ Timeline.prototype.setCustomTime = function (time) {
* @return {Date} customTime
*/
Timeline.prototype.getCustomTime = function() {
- return new Date(this.customtime.customTime.valueOf());
+ if (!this.customtime) {
+ throw new Error('Cannot get custom time: Custom time bar is not enabled');
+ }
+
+ return this.customtime.getCustomTime();
};
/**
* Set items
- * @param {vis.DataSet | Array | DataTable | null} items
+ * @param {vis.DataSet | Array | google.visualization.DataTable | null} items
*/
Timeline.prototype.setItems = function(items) {
var initialLoad = (this.itemsData == null);
// convert to type DataSet when needed
- var newItemSet;
+ var newDataSet;
if (!items) {
- newItemSet = null;
+ newDataSet = null;
}
else if (items instanceof DataSet) {
- newItemSet = items;
+ newDataSet = items;
}
if (!(items instanceof DataSet)) {
- newItemSet = new DataSet({
+ newDataSet = new DataSet({
convert: {
start: 'Date',
end: 'Date'
}
});
- newItemSet.add(items);
+ newDataSet.add(items);
}
// set items
- this.itemsData = newItemSet;
- this.content.setItems(newItemSet);
+ this.itemsData = newDataSet;
+ this.content.setItems(newDataSet);
if (initialLoad && (this.options.start == undefined || this.options.end == undefined)) {
// apply the data range as range
@@ -7631,7 +8041,7 @@ Timeline.prototype.setItems = function(items) {
/**
* Set groups
- * @param {vis.DataSet | Array | DataTable} groups
+ * @param {vis.DataSet | Array | google.visualization.DataTable} groups
*/
Timeline.prototype.setGroups = function(groups) {
var me = this;
@@ -7765,41 +8175,25 @@ Timeline.prototype.getSelection = function getSelection() {
};
/**
- * Add event listener
- * @param {String} event Event name. Available events:
- * 'rangechange', 'rangechanged', 'select'
- * @param {function} callback Callback function, invoked as callback(properties)
- * where properties is an optional object containing
- * event specific properties.
- */
-Timeline.prototype.on = function on (event, callback) {
- var available = ['rangechange', 'rangechanged', 'select'];
-
- if (available.indexOf(event) == -1) {
- throw new Error('Unknown event "' + event + '". Choose from ' + available.join());
- }
-
- events.addListener(this, event, callback);
-};
-
-/**
- * Remove an event listener
- * @param {String} event Event name
- * @param {function} callback Callback function
+ * Set the visible window. Both parameters are optional, you can change only
+ * start or only end.
+ * @param {Date | Number | String} [start] Start date of visible window
+ * @param {Date | Number | String} [end] End date of visible window
*/
-Timeline.prototype.off = function off (event, callback) {
- events.removeListener(this, event, callback);
+Timeline.prototype.setWindow = function setWindow(start, end) {
+ this.range.setRange(start, end);
};
/**
- * Trigger an event
- * @param {String} event Event name, available events: 'rangechange',
- * 'rangechanged', 'select'
- * @param {Object} [properties] Event specific properties
- * @private
+ * Get the visible window
+ * @return {{start: Date, end: Date}} Visible range
*/
-Timeline.prototype._trigger = function _trigger(event, properties) {
- events.trigger(this, event, properties || {});
+Timeline.prototype.getWindow = function setWindow() {
+ var range = this.range.getRange();
+ return {
+ start: new Date(range.start),
+ end: new Date(range.end)
+ };
};
/**
@@ -7807,13 +8201,23 @@ Timeline.prototype._trigger = function _trigger(event, properties) {
* @param {Event} event
* @private
*/
+// TODO: move this function to ItemSet
Timeline.prototype._onSelectItem = function (event) {
- var item = this._itemFromTarget(event);
+ if (!this.options.selectable) return;
+
+ var ctrlKey = event.gesture.srcEvent && event.gesture.srcEvent.ctrlKey;
+ var shiftKey = event.gesture.srcEvent && event.gesture.srcEvent.shiftKey;
+ if (ctrlKey || shiftKey) {
+ this._onMultiSelectItem(event);
+ return;
+ }
+
+ var item = ItemSet.itemFromTarget(event);
var selection = item ? [item.id] : [];
this.setSelection(selection);
- this._trigger('select', {
+ this.controller.emit('select', {
items: this.getSelection()
});
@@ -7821,56 +8225,120 @@ Timeline.prototype._onSelectItem = function (event) {
};
/**
- * Handle selecting/deselecting multiple items when holding an item
- * @param {Event} event
+ * Handle creation and updates of an item on double tap
+ * @param event
* @private
*/
-Timeline.prototype._onMultiSelectItem = function (event) {
- var selection,
- item = this._itemFromTarget(event);
+Timeline.prototype._onAddItem = function (event) {
+ if (!this.options.selectable) return;
+ if (!this.options.editable) return;
- if (!item) {
- // do nothing...
- return;
- }
+ var me = this,
+ item = ItemSet.itemFromTarget(event);
+
+ if (item) {
+ // update item
- selection = this.getSelection(); // current selection
- var index = selection.indexOf(item.id);
- if (index == -1) {
- // item is not yet selected -> select it
- selection.push(item.id);
+ // execute async handler to update the item (or cancel it)
+ var itemData = me.itemsData.get(item.id); // get a clone of the data from the dataset
+ this.options.onUpdate(itemData, function (itemData) {
+ if (itemData) {
+ me.itemsData.update(itemData);
+ }
+ });
}
else {
- // item is already selected -> deselect it
- selection.splice(index, 1);
- }
- this.setSelection(selection);
+ // add item
+ var xAbs = vis.util.getAbsoluteLeft(this.rootPanel.frame);
+ var x = event.gesture.center.pageX - xAbs;
+ var newItem = {
+ start: this.timeaxis.snap(this._toTime(x)),
+ content: 'new item'
+ };
- this._trigger('select', {
- items: this.getSelection()
- });
+ var id = util.randomUUID();
+ newItem[this.itemsData.fieldId] = id;
- event.stopPropagation();
+ var group = GroupSet.groupFromTarget(event);
+ if (group) {
+ newItem.group = group.groupId;
+ }
+
+ // execute async handler to customize (or cancel) adding an item
+ this.options.onAdd(newItem, function (item) {
+ if (item) {
+ me.itemsData.add(newItem);
+
+ // select the created item after it is repainted
+ me.controller.once('repaint', function () {
+ me.setSelection([id]);
+
+ me.controller.emit('select', {
+ items: me.getSelection()
+ });
+ }.bind(me));
+ }
+ });
+ }
};
/**
- * Find an item from an event target:
- * searches for the attribute 'timeline-item' in the event target's element tree
+ * Handle selecting/deselecting multiple items when holding an item
* @param {Event} event
- * @return {Item | null| item
* @private
*/
-Timeline.prototype._itemFromTarget = function _itemFromTarget (event) {
- var target = event.target;
- while (target) {
- if (target.hasOwnProperty('timeline-item')) {
- return target['timeline-item'];
+// TODO: move this function to ItemSet
+Timeline.prototype._onMultiSelectItem = function (event) {
+ if (!this.options.selectable) return;
+
+ var selection,
+ item = ItemSet.itemFromTarget(event);
+
+ if (item) {
+ // multi select items
+ selection = this.getSelection(); // current selection
+ var index = selection.indexOf(item.id);
+ if (index == -1) {
+ // item is not yet selected -> select it
+ selection.push(item.id);
}
- target = target.parentNode;
+ else {
+ // item is already selected -> deselect it
+ selection.splice(index, 1);
+ }
+ this.setSelection(selection);
+
+ this.controller.emit('select', {
+ items: this.getSelection()
+ });
+
+ event.stopPropagation();
}
+};
- return null;
+/**
+ * Convert a position on screen (pixels) to a datetime
+ * @param {int} x Position on the screen in pixels
+ * @return {Date} time The datetime the corresponds with given position x
+ * @private
+ */
+Timeline.prototype._toTime = function _toTime(x) {
+ var conversion = this.range.conversion(this.content.width);
+ return new Date(x / conversion.scale + conversion.offset);
+};
+
+/**
+ * Convert a datetime (Date object) into a position on the screen
+ * @param {Date} time A date
+ * @return {int} x The position on the screen in pixels which corresponds
+ * with the given date.
+ * @private
+ */
+Timeline.prototype._toScreen = function _toScreen(time) {
+ var conversion = this.range.conversion(this.content.width);
+ return (time.valueOf() - conversion.offset) * conversion.scale;
};
+
(function(exports) {
/**
* Parse a text source containing data in DOT language into a JSON object.
@@ -8950,6 +9418,7 @@ if (typeof CanvasRenderingContext2D !== 'undefined') {
* retrieving group properties
* @param {Object} constants An object with default values for
* example for the color
+ *
*/
function Node(properties, imagelist, grouplist, constants) {
this.selected = false;
@@ -8962,6 +9431,7 @@ function Node(properties, imagelist, grouplist, constants) {
this.fontSize = constants.nodes.fontSize;
this.fontFace = constants.nodes.fontFace;
this.fontColor = constants.nodes.fontColor;
+ this.fontDrawThreshold = 3;
this.color = constants.nodes.color;
@@ -8980,11 +9450,20 @@ function Node(properties, imagelist, grouplist, constants) {
this.radiusFixed = false;
this.radiusMin = constants.nodes.radiusMin;
this.radiusMax = constants.nodes.radiusMax;
+ this.level = -1;
this.imagelist = imagelist;
-
this.grouplist = grouplist;
+ // physics properties
+ this.fx = 0.0; // external force x
+ this.fy = 0.0; // external force y
+ this.vx = 0.0; // velocity x
+ this.vy = 0.0; // velocity y
+ this.minForce = constants.minForce;
+ this.damping = constants.physics.damping;
+ this.mass = 1; // kg
+
this.setProperties(properties, constants);
// creating the variables for clustering
@@ -8994,20 +9473,15 @@ function Node(properties, imagelist, grouplist, constants) {
this.clusterSizeWidthFactor = constants.clustering.nodeScaling.width;
this.clusterSizeHeightFactor = constants.clustering.nodeScaling.height;
this.clusterSizeRadiusFactor = constants.clustering.nodeScaling.radius;
+ this.maxNodeSizeIncrements = constants.clustering.maxNodeSizeIncrements;
+ this.growthIndicator = 0;
- // mass, force, velocity
- this.mass = 1; // kg (mass is adjusted for the number of connected edges)
- this.fx = 0.0; // external force x
- this.fy = 0.0; // external force y
- this.vx = 0.0; // velocity x
- this.vy = 0.0; // velocity y
- this.minForce = constants.minForce;
- this.damping = 0.9;
- this.dampingFactor = 75;
-
+ // variables to tell the node about the graph.
this.graphScaleInv = 1;
+ this.graphScale = 1;
this.canvasTopLeft = {"x": -300, "y": -300};
this.canvasBottomRight = {"x": 300, "y": 300};
+ this.parentEdgeId = null;
}
/**
@@ -9034,7 +9508,6 @@ Node.prototype.attachEdge = function(edge) {
this.dynamicEdges.push(edge);
}
this.dynamicEdgesLength = this.dynamicEdges.length;
- this._updateMass();
};
/**
@@ -9048,17 +9521,8 @@ Node.prototype.detachEdge = function(edge) {
this.dynamicEdges.splice(index, 1);
}
this.dynamicEdgesLength = this.dynamicEdges.length;
- this._updateMass();
};
-/**
- * Update the nodes mass, which is determined by the number of edges connecting
- * to it (more edges -> heavier node).
- * @private
- */
-Node.prototype._updateMass = function() {
- this.mass = 1 + 0.6 * this.edges.length; // kg
-};
/**
* Set or overwrite properties for the node
@@ -9078,6 +9542,13 @@ Node.prototype.setProperties = function(properties, constants) {
if (properties.x !== undefined) {this.x = properties.x;}
if (properties.y !== undefined) {this.y = properties.y;}
if (properties.value !== undefined) {this.value = properties.value;}
+ if (properties.level !== undefined) {this.level = properties.level;}
+
+
+ // physics
+ if (properties.internalMultiplier !== undefined) {this.internalMultiplier = properties.internalMultiplier;}
+ if (properties.damping !== undefined) {this.dampingBase = properties.damping;}
+ if (properties.mass !== undefined) {this.mass = properties.mass;}
// navigation controls properties
if (properties.horizontalAlignLeft !== undefined) {this.horizontalAlignLeft = properties.horizontalAlignLeft;}
@@ -9117,8 +9588,8 @@ Node.prototype.setProperties = function(properties, constants) {
}
}
- this.xFixed = this.xFixed || (properties.x !== undefined);
- this.yFixed = this.yFixed || (properties.y !== undefined);
+ this.xFixed = this.xFixed || (properties.x !== undefined && !properties.allowedToMove);
+ this.yFixed = this.yFixed || (properties.y !== undefined && !properties.allowedToMove);
this.radiusFixed = this.radiusFixed || (properties.radius !== undefined);
if (this.shape == 'image') {
@@ -9155,15 +9626,32 @@ Node.prototype.setProperties = function(properties, constants) {
Node.parseColor = function(color) {
var c;
if (util.isString(color)) {
- c = {
- border: color,
- background: color,
- highlight: {
- border: color,
- background: color
- }
- };
- // TODO: automatically generate a nice highlight color
+ if (util.isValidHex(color)) {
+ var hsv = util.hexToHSV(color);
+ var lighterColorHSV = {h:hsv.h,s:hsv.s * 0.45,v:Math.min(1,hsv.v * 1.05)};
+ var darkerColorHSV = {h:hsv.h,s:Math.min(1,hsv.v * 1.25),v:hsv.v*0.6};
+ var darkerColorHex = util.HSVToHex(darkerColorHSV.h ,darkerColorHSV.h ,darkerColorHSV.v);
+ var lighterColorHex = util.HSVToHex(lighterColorHSV.h,lighterColorHSV.s,lighterColorHSV.v);
+
+ c = {
+ background: color,
+ border:darkerColorHex,
+ highlight: {
+ background:lighterColorHex,
+ border:darkerColorHex
+ }
+ };
+ }
+ else {
+ c = {
+ background:color,
+ border:color,
+ highlight: {
+ background:color,
+ border:color
+ }
+ };
+ }
}
else {
c = {};
@@ -9241,7 +9729,6 @@ Node.prototype.distanceToBorder = function (ctx, angle) {
this.resize(ctx);
}
- //noinspection FallthroughInSwitchStatementJS
switch (this.shape) {
case 'circle':
case 'dot':
@@ -9273,7 +9760,6 @@ Node.prototype.distanceToBorder = function (ctx, angle) {
}
}
-
// TODO: implement calculation of distance to border for all shapes
};
@@ -9304,21 +9790,44 @@ Node.prototype._addForce = function(fx, fy) {
*/
Node.prototype.discreteStep = function(interval) {
if (!this.xFixed) {
- var dx = -this.damping * this.vx; // damping force
- var ax = (this.fx + dx) / this.mass; // acceleration
+ var dx = this.damping * this.vx; // damping force
+ var ax = (this.fx - dx) / this.mass; // acceleration
this.vx += ax * interval; // velocity
this.x += this.vx * interval; // position
}
if (!this.yFixed) {
- var dy = -this.damping * this.vy; // damping force
- var ay = (this.fy + dy) / this.mass; // acceleration
+ var dy = this.damping * this.vy; // damping force
+ var ay = (this.fy - dy) / this.mass; // acceleration
this.vy += ay * interval; // velocity
this.y += this.vy * interval; // position
}
};
+
+/**
+ * Perform one discrete step for the node
+ * @param {number} interval Time interval in seconds
+ */
+Node.prototype.discreteStepLimited = function(interval, maxVelocity) {
+ if (!this.xFixed) {
+ var dx = this.damping * this.vx; // damping force
+ var ax = (this.fx - dx) / this.mass; // acceleration
+ this.vx += ax * interval; // velocity
+ this.vx = (Math.abs(this.vx) > maxVelocity) ? ((this.vx > 0) ? maxVelocity : -maxVelocity) : this.vx;
+ this.x += this.vx * interval; // position
+ }
+
+ if (!this.yFixed) {
+ var dy = this.damping * this.vy; // damping force
+ var ay = (this.fy - dy) / this.mass; // acceleration
+ this.vy += ay * interval; // velocity
+ this.vy = (Math.abs(this.vy) > maxVelocity) ? ((this.vy > 0) ? maxVelocity : -maxVelocity) : this.vy;
+ this.y += this.vy * interval; // position
+ }
+};
+
/**
* Check if this node has a fixed x and y position
* @return {boolean} true if fixed, false if not
@@ -9334,16 +9843,7 @@ Node.prototype.isFixed = function() {
*/
// TODO: replace this method with calculating the kinetic energy
Node.prototype.isMoving = function(vmin) {
-
- if (Math.abs(this.vx) > vmin || Math.abs(this.vy) > vmin) {
-// console.log(vmin,this.vx,this.vy);
- return true;
- }
- else {
- this.vx = 0; this.vy = 0;
- return false;
- }
- //return (Math.abs(this.vx) > vmin || Math.abs(this.vy) > vmin);
+ return (Math.abs(this.vx) > vmin || Math.abs(this.vy) > vmin);
};
/**
@@ -9448,10 +9948,12 @@ Node.prototype._resizeImage = function (ctx) {
this.width = width;
this.height = height;
+ this.growthIndicator = 0;
if (this.width > 0 && this.height > 0) {
- this.width += (this.clusterSize - 1) * this.clusterSizeWidthFactor;
- this.height += (this.clusterSize - 1) * this.clusterSizeHeightFactor;
- this.radius += (this.clusterSize - 1) * this.clusterSizeRadiusFactor;
+ this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
+ this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
+ this.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor;
+ this.growthIndicator = this.width - width;
}
}
@@ -9496,9 +9998,11 @@ Node.prototype._resizeBox = function (ctx) {
this.width = textSize.width + 2 * margin;
this.height = textSize.height + 2 * margin;
- this.width += (this.clusterSize - 1) * 0.5 * this.clusterSizeWidthFactor;
- this.height += (this.clusterSize - 1) * 0.5 * this.clusterSizeHeightFactor;
-// this.radius += (this.clusterSize - 1) * 0.5 * this.clusterSizeRadiusFactor;
+ this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeWidthFactor;
+ this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeHeightFactor;
+ this.growthIndicator = this.width - (textSize.width + 2 * margin);
+// this.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeRadiusFactor;
+
}
};
@@ -9545,9 +10049,10 @@ Node.prototype._resizeDatabase = function (ctx) {
this.height = size;
// scaling used for clustering
- this.width += (this.clusterSize - 1) * this.clusterSizeWidthFactor;
- this.height += (this.clusterSize - 1) * this.clusterSizeHeightFactor;
- this.radius += (this.clusterSize - 1) * this.clusterSizeRadiusFactor;
+ this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
+ this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
+ this.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor;
+ this.growthIndicator = this.width - size;
}
};
@@ -9594,9 +10099,10 @@ Node.prototype._resizeCircle = function (ctx) {
this.height = diameter;
// scaling used for clustering
-// this.width += (this.clusterSize - 1) * 0.5 * this.clusterSizeWidthFactor;
-// this.height += (this.clusterSize - 1) * 0.5 * this.clusterSizeHeightFactor;
- this.radius += (this.clusterSize - 1) * 0.5 * this.clusterSizeRadiusFactor;
+// this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeWidthFactor;
+// this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeHeightFactor;
+ this.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeRadiusFactor;
+ this.growthIndicator = this.radius - 0.5*diameter;
}
};
@@ -9640,11 +10146,13 @@ Node.prototype._resizeEllipse = function (ctx) {
if (this.width < this.height) {
this.width = this.height;
}
+ var defaultSize = this.width;
- // scaling used for clustering
- this.width += (this.clusterSize - 1) * this.clusterSizeWidthFactor;
- this.height += (this.clusterSize - 1) * this.clusterSizeHeightFactor;
- this.radius += (this.clusterSize - 1) * this.clusterSizeRadiusFactor;
+ // scaling used for clustering
+ this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
+ this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
+ this.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor;
+ this.growthIndicator = this.width - defaultSize;
}
};
@@ -9707,9 +10215,10 @@ Node.prototype._resizeShape = function (ctx) {
this.height = size;
// scaling used for clustering
- this.width += (this.clusterSize - 1) * this.clusterSizeWidthFactor;
- this.height += (this.clusterSize - 1) * this.clusterSizeHeightFactor;
- this.radius += (this.clusterSize - 1) * 0.5 * this.clusterSizeRadiusFactor;
+ this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
+ this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
+ this.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeRadiusFactor;
+ this.growthIndicator = this.width - size;
}
};
@@ -9766,9 +10275,10 @@ Node.prototype._resizeText = function (ctx) {
this.height = textSize.height + 2 * margin;
// scaling used for clustering
- this.width += (this.clusterSize - 1) * this.clusterSizeWidthFactor;
- this.height += (this.clusterSize - 1) * this.clusterSizeHeightFactor;
- this.radius += (this.clusterSize - 1) * this.clusterSizeRadiusFactor;
+ this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
+ this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
+ this.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor;
+ this.growthIndicator = this.width - (textSize.width + 2 * margin);
}
};
@@ -9782,7 +10292,7 @@ Node.prototype._drawText = function (ctx) {
Node.prototype._label = function (ctx, text, x, y, align, baseline) {
- if (text) {
+ if (text && this.fontSize * this.graphScale > this.fontDrawThreshold) {
ctx.font = (this.selected ? "bold " : "") + this.fontSize + "px " + this.fontFace;
ctx.fillStyle = this.fontColor || "black";
ctx.textAlign = align || "center";
@@ -9836,7 +10346,7 @@ Node.prototype.inArea = function() {
else {
return true;
}
-}
+};
/**
* checks if the core of the node is in the display area, this is used for opening clusters around zoom
@@ -9847,7 +10357,7 @@ Node.prototype.inView = function() {
this.x < this.canvasBottomRight.x &&
this.y >= this.canvasTopLeft.y &&
this.y < this.canvasBottomRight.y);
-}
+};
/**
* This allows the zoom level of the graph to influence the rendering
@@ -9859,6 +10369,7 @@ Node.prototype.inView = function() {
*/
Node.prototype.setScaleAndPos = function(scale,canvasTopLeft,canvasBottomRight) {
this.graphScaleInv = 1.0/scale;
+ this.graphScale = scale;
this.canvasTopLeft = canvasTopLeft;
this.canvasBottomRight = canvasBottomRight;
};
@@ -9871,17 +10382,9 @@ Node.prototype.setScaleAndPos = function(scale,canvasTopLeft,canvasBottomRight)
*/
Node.prototype.setScale = function(scale) {
this.graphScaleInv = 1.0/scale;
+ this.graphScale = scale;
};
-/**
- * This function updates the damping parameter for clusters, based ont he
- *
- * @param {Number} numberOfNodes
- */
-Node.prototype.updateDamping = function(numberOfNodes) {
- this.damping = (0.8 + 0.1*this.clusterSize * (1 + Math.pow(numberOfNodes,-2)));
- this.damping *= this.dampingFactor;
-};
/**
@@ -9900,8 +10403,10 @@ Node.prototype.clearVelocity = function() {
*/
Node.prototype.updateVelocity = function(massBeforeClustering) {
var energyBefore = this.vx * this.vx * massBeforeClustering;
+ //this.vx = (this.vx < 0) ? -Math.sqrt(energyBefore/this.mass) : Math.sqrt(energyBefore/this.mass);
this.vx = Math.sqrt(energyBefore/this.mass);
energyBefore = this.vy * this.vy * massBeforeClustering;
+ //this.vy = (this.vy < 0) ? -Math.sqrt(energyBefore/this.mass) : Math.sqrt(energyBefore/this.mass);
this.vy = Math.sqrt(energyBefore/this.mass);
};
@@ -9939,10 +10444,13 @@ function Edge (properties, graph, constants) {
this.title = undefined;
this.width = constants.edges.width;
this.value = undefined;
- this.length = constants.edges.length;
+ this.length = constants.physics.springLength;
+ this.selected = false;
+ this.smooth = constants.smoothCurves;
this.from = null; // a node
this.to = null; // a node
+ this.via = null; // a temp node
// we use this to be able to reconnect the edge to a cluster if its node is put into a cluster
// by storing the original information we can revert to the original connection when the cluser is opened.
@@ -9956,13 +10464,11 @@ function Edge (properties, graph, constants) {
// 2012-08-08
this.dash = util.extend({}, constants.edges.dash); // contains properties length, gap, altLength
- this.stiffness = undefined; // depends on the length of the edge
this.color = constants.edges.color;
this.widthFixed = false;
this.lengthFixed = false;
this.setProperties(properties, constants);
-
}
/**
@@ -9981,6 +10487,7 @@ Edge.prototype.setProperties = function(properties, constants) {
if (properties.id !== undefined) {this.id = properties.id;}
if (properties.style !== undefined) {this.style = properties.style;}
if (properties.label !== undefined) {this.label = properties.label;}
+
if (this.label) {
this.fontSize = constants.edges.fontSize;
this.fontFace = constants.edges.fontFace;
@@ -9989,10 +10496,11 @@ Edge.prototype.setProperties = function(properties, constants) {
if (properties.fontSize !== undefined) {this.fontSize = properties.fontSize;}
if (properties.fontFace !== undefined) {this.fontFace = properties.fontFace;}
}
- if (properties.title !== undefined) {this.title = properties.title;}
- if (properties.width !== undefined) {this.width = properties.width;}
- if (properties.value !== undefined) {this.value = properties.value;}
- if (properties.length !== undefined) {this.length = properties.length;}
+
+ if (properties.title !== undefined) {this.title = properties.title;}
+ if (properties.width !== undefined) {this.width = properties.width;}
+ if (properties.value !== undefined) {this.value = properties.value;}
+ if (properties.length !== undefined) {this.length = properties.length;}
// Added to support dashed lines
// David Jordan
@@ -10010,7 +10518,6 @@ Edge.prototype.setProperties = function(properties, constants) {
this.widthFixed = this.widthFixed || (properties.width !== undefined);
this.lengthFixed = this.lengthFixed || (properties.length !== undefined);
- this.stiffness = 1 / this.length;
// set draw method based on style
switch (this.style) {
@@ -10118,8 +10625,7 @@ Edge.prototype.isOverlappingWith = function(obj) {
var xObj = obj.left;
var yObj = obj.top;
-
- var dist = Edge._dist(xFrom, yFrom, xTo, yTo, xObj, yObj);
+ var dist = this._getDistanceToEdge(xFrom, yFrom, xTo, yTo, xObj, yObj);
return (dist < distMax);
};
@@ -10138,7 +10644,7 @@ Edge.prototype._drawLine = function(ctx) {
ctx.lineWidth = this._getLineWidth();
var point;
- if (this.from != this.to) {
+ if (this.from != this.to+9) {
// draw line
this._line(ctx);
@@ -10176,7 +10682,7 @@ Edge.prototype._drawLine = function(ctx) {
* @private
*/
Edge.prototype._getLineWidth = function() {
- if (this.from.selected || this.to.selected) {
+ if (this.selected == true) {
return Math.min(this.width * 2, this.widthMax)*this.graphScaleInv;
}
else {
@@ -10193,7 +10699,12 @@ Edge.prototype._line = function (ctx) {
// draw a straight line
ctx.beginPath();
ctx.moveTo(this.from.x, this.from.y);
- ctx.lineTo(this.to.x, this.to.y);
+ if (this.smooth == true) {
+ ctx.quadraticCurveTo(this.via.x,this.via.y,this.to.x, this.to.y);
+ }
+ else {
+ ctx.lineTo(this.to.x, this.to.y);
+ }
ctx.stroke();
};
@@ -10255,25 +10766,70 @@ Edge.prototype._drawDashLine = function(ctx) {
ctx.strokeStyle = this.color;
ctx.lineWidth = this._getLineWidth();
- // draw dashed line
- ctx.beginPath();
- ctx.lineCap = 'round';
- if (this.dash.altLength !== undefined) //If an alt dash value has been set add to the array this value
- {
- ctx.dashedLine(this.from.x,this.from.y,this.to.x,this.to.y,
- [this.dash.length,this.dash.gap,this.dash.altLength,this.dash.gap]);
- }
- else if (this.dash.length !== undefined && this.dash.gap !== undefined) //If a dash and gap value has been set add to the array this value
- {
- ctx.dashedLine(this.from.x,this.from.y,this.to.x,this.to.y,
- [this.dash.length,this.dash.gap]);
- }
- else //If all else fails draw a line
- {
+ // only firefox and chrome support this method, else we use the legacy one.
+ if (ctx.mozDash !== undefined || ctx.setLineDash !== undefined) {
+ ctx.beginPath();
ctx.moveTo(this.from.x, this.from.y);
- ctx.lineTo(this.to.x, this.to.y);
+
+ // configure the dash pattern
+ var pattern = [0];
+ if (this.dash.length !== undefined && this.dash.gap !== undefined) {
+ pattern = [this.dash.length,this.dash.gap];
+ }
+ else {
+ pattern = [5,5];
+ }
+
+ // set dash settings for chrome or firefox
+ if (typeof ctx.setLineDash !== 'undefined') { //Chrome
+ ctx.setLineDash(pattern);
+ ctx.lineDashOffset = 0;
+
+ } else { //Firefox
+ ctx.mozDash = pattern;
+ ctx.mozDashOffset = 0;
+ }
+
+ // draw the line
+ if (this.smooth == true) {
+ ctx.quadraticCurveTo(this.via.x,this.via.y,this.to.x, this.to.y);
+ }
+ else {
+ ctx.lineTo(this.to.x, this.to.y);
+ }
+ ctx.stroke();
+
+ // restore the dash settings.
+ if (typeof ctx.setLineDash !== 'undefined') { //Chrome
+ ctx.setLineDash([0]);
+ ctx.lineDashOffset = 0;
+
+ } else { //Firefox
+ ctx.mozDash = [0];
+ ctx.mozDashOffset = 0;
+ }
+ }
+ else { // unsupporting smooth lines
+ // draw dashed line
+ ctx.beginPath();
+ ctx.lineCap = 'round';
+ if (this.dash.altLength !== undefined) //If an alt dash value has been set add to the array this value
+ {
+ ctx.dashedLine(this.from.x,this.from.y,this.to.x,this.to.y,
+ [this.dash.length,this.dash.gap,this.dash.altLength,this.dash.gap]);
+ }
+ else if (this.dash.length !== undefined && this.dash.gap !== undefined) //If a dash and gap value has been set add to the array this value
+ {
+ ctx.dashedLine(this.from.x,this.from.y,this.to.x,this.to.y,
+ [this.dash.length,this.dash.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();
}
- ctx.stroke();
// draw label
if (this.label) {
@@ -10330,11 +10886,19 @@ Edge.prototype._drawArrowCenter = function(ctx) {
// draw line
this._line(ctx);
- // draw an arrow halfway the line
var angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
var length = 10 + 5 * this.width; // TODO: make customizable?
- point = this._pointOnLine(0.5);
- ctx.arrow(point.x, point.y, angle, length);
+ // draw an arrow halfway the line
+ if (this.smooth == true) {
+ var midpointX = 0.5*(0.5*(this.from.x + this.via.x) + 0.5*(this.to.x + this.via.x));
+ var midpointY = 0.5*(0.5*(this.from.y + this.via.y) + 0.5*(this.to.y + this.via.y));
+ point = {x:midpointX, y:midpointY};
+ }
+ else {
+ point = this._pointOnLine(0.5);
+ }
+
+ ctx.arrow(point.x, point.y, angle, length);
ctx.fill();
ctx.stroke();
@@ -10347,18 +10911,18 @@ Edge.prototype._drawArrowCenter = function(ctx) {
else {
// draw circle
var x, y;
- var radius = this.length / 4;
+ var radius = 0.25 * Math.max(100,this.length);
var node = this.from;
if (!node.width) {
node.resize(ctx);
}
if (node.width > node.height) {
- x = node.x + node.width / 2;
+ x = node.x + node.width * 0.5;
y = node.y - radius;
}
else {
x = node.x + radius;
- y = node.y - node.height / 2;
+ y = node.y - node.height * 0.5;
}
this._circle(ctx, x, y, radius);
@@ -10393,32 +10957,51 @@ Edge.prototype._drawArrow = function(ctx) {
ctx.fillStyle = this.color;
ctx.lineWidth = this._getLineWidth();
- // draw line
var angle, length;
+ //draw a line
if (this.from != this.to) {
- // calculate length and angle of the line
angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
var dx = (this.to.x - this.from.x);
var dy = (this.to.y - this.from.y);
- var lEdge = Math.sqrt(dx * dx + dy * dy);
+ var edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
+
+ var fromBorderDist = this.from.distanceToBorder(ctx, angle + Math.PI);
+ var fromBorderPoint = (edgeSegmentLength - fromBorderDist) / edgeSegmentLength;
+ var xFrom = (fromBorderPoint) * this.from.x + (1 - fromBorderPoint) * this.to.x;
+ var yFrom = (fromBorderPoint) * this.from.y + (1 - fromBorderPoint) * this.to.y;
+
- var lFrom = this.from.distanceToBorder(ctx, angle + Math.PI);
- var pFrom = (lEdge - lFrom) / lEdge;
- var xFrom = (pFrom) * this.from.x + (1 - pFrom) * this.to.x;
- var yFrom = (pFrom) * this.from.y + (1 - pFrom) * this.to.y;
+ if (this.smooth == true) {
+ angle = Math.atan2((this.to.y - this.via.y), (this.to.x - this.via.x));
+ dx = (this.to.x - this.via.x);
+ dy = (this.to.y - this.via.y);
+ edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
+ }
+ var toBorderDist = this.to.distanceToBorder(ctx, angle);
+ var toBorderPoint = (edgeSegmentLength - toBorderDist) / edgeSegmentLength;
- var lTo = this.to.distanceToBorder(ctx, angle);
- var pTo = (lEdge - lTo) / lEdge;
- var xTo = (1 - pTo) * this.from.x + pTo * this.to.x;
- var yTo = (1 - pTo) * this.from.y + pTo * this.to.y;
+ var xTo,yTo;
+ if (this.smooth == true) {
+ xTo = (1 - toBorderPoint) * this.via.x + toBorderPoint * this.to.x;
+ yTo = (1 - toBorderPoint) * this.via.y + toBorderPoint * this.to.y;
+ }
+ else {
+ xTo = (1 - toBorderPoint) * this.from.x + toBorderPoint * this.to.x;
+ yTo = (1 - toBorderPoint) * this.from.y + toBorderPoint * this.to.y;
+ }
ctx.beginPath();
- ctx.moveTo(xFrom, yFrom);
- ctx.lineTo(xTo, yTo);
+ ctx.moveTo(xFrom,yFrom);
+ if (this.smooth == true) {
+ ctx.quadraticCurveTo(this.via.x,this.via.y,xTo, yTo);
+ }
+ else {
+ ctx.lineTo(xTo, yTo);
+ }
ctx.stroke();
// draw arrow at the end of the line
- length = 10 + 5 * this.width; // TODO: make customizable?
+ length = 10 + 5 * this.width;
ctx.arrow(xTo, yTo, angle, length);
ctx.fill();
ctx.stroke();
@@ -10433,12 +11016,12 @@ Edge.prototype._drawArrow = function(ctx) {
// draw circle
var node = this.from;
var x, y, arrow;
- var radius = this.length / 4;
+ var radius = 0.25 * Math.max(100,this.length);
if (!node.width) {
node.resize(ctx);
}
if (node.width > node.height) {
- x = node.x + node.width / 2;
+ x = node.x + node.width * 0.5;
y = node.y - radius;
arrow = {
x: x,
@@ -10448,7 +11031,7 @@ Edge.prototype._drawArrow = function(ctx) {
}
else {
x = node.x + radius;
- y = node.y - node.height / 2;
+ y = node.y - node.height * 0.5;
arrow = {
x: node.x,
y: y,
@@ -10456,7 +11039,6 @@ Edge.prototype._drawArrow = function(ctx) {
};
}
ctx.beginPath();
- // TODO: do not draw a circle, but an arc
// TODO: similarly, for a line without arrows, draw to the border of the nodes instead of the center
ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
ctx.stroke();
@@ -10489,31 +11071,46 @@ Edge.prototype._drawArrow = function(ctx) {
* @param {number} y3
* @private
*/
-Edge._dist = function (x1,y1, x2,y2, x3,y3) { // x3,y3 is the point
- var px = x2-x1,
- py = y2-y1,
- something = px*px + py*py,
- u = ((x3 - x1) * px + (y3 - y1) * py) / something;
-
- if (u > 1) {
- u = 1;
- }
- else if (u < 0) {
- u = 0;
+Edge.prototype._getDistanceToEdge = function (x1,y1, x2,y2, x3,y3) { // x3,y3 is the point
+ if (this.smooth == true) {
+ var minDistance = 1e9;
+ var i,t,x,y,dx,dy;
+ for (i = 0; i < 10; i++) {
+ t = 0.1*i;
+ x = Math.pow(1-t,2)*x1 + (2*t*(1 - t))*this.via.x + Math.pow(t,2)*x2;
+ y = Math.pow(1-t,2)*y1 + (2*t*(1 - t))*this.via.y + Math.pow(t,2)*y2;
+ dx = Math.abs(x3-x);
+ dy = Math.abs(y3-y);
+ minDistance = Math.min(minDistance,Math.sqrt(dx*dx + dy*dy));
+ }
+ return minDistance
}
+ else {
+ var px = x2-x1,
+ py = y2-y1,
+ something = px*px + py*py,
+ u = ((x3 - x1) * px + (y3 - y1) * py) / something;
+
+ if (u > 1) {
+ u = 1;
+ }
+ else if (u < 0) {
+ u = 0;
+ }
- var x = x1 + u * px,
- y = y1 + u * py,
- dx = x - x3,
- dy = y - y3;
+ var x = x1 + u * px,
+ y = y1 + u * py,
+ dx = x - x3,
+ dy = y - y3;
- //# 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
+ //# 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
- return Math.sqrt(dx*dx + dy*dy);
+ return Math.sqrt(dx*dx + dy*dy);
+ }
};
@@ -10526,6 +11123,22 @@ Edge._dist = function (x1,y1, x2,y2, x3,y3) { // x3,y3 is the point
Edge.prototype.setScale = function(scale) {
this.graphScaleInv = 1.0/scale;
};
+
+
+Edge.prototype.select = function() {
+ this.selected = true;
+};
+
+Edge.prototype.unselect = function() {
+ this.selected = false;
+};
+
+Edge.prototype.positionBezierNode = function() {
+ if (this.via !== null) {
+ this.via.x = 0.5 * (this.from.x + this.to.x);
+ this.via.y = 0.5 * (this.from.y + this.to.y);
+ }
+};
/**
* Popup is a class to create a popup window with some text
* @param {Element} container The container object.
@@ -10756,221 +11369,1670 @@ Images.prototype.load = function(url) {
};
/**
- * Creation of the SectorMixin var.
- *
- * This contains all the functions the Graph object can use to employ the sector system.
- * The sector system is always used by Graph, though the benefits only apply to the use of clustering.
- * If clustering is not used, there is no overhead except for a duplicate object with references to nodes and edges.
- *
- * Alex de Mulder
- * 21-01-2013
+ * Created by Alex on 2/6/14.
*/
-var SectorMixin = {
+
+
+var physicsMixin = {
/**
- * This function is only called by the setData function of the Graph object.
- * This loads the global references into the active sector. This initializes the sector.
+ * Toggling barnes Hut calculation on and off.
*
* @private
*/
- _putDataInSector : function() {
- this.sectors["active"][this._sector()].nodes = this.nodes;
- this.sectors["active"][this._sector()].edges = this.edges;
- this.sectors["active"][this._sector()].nodeIndices = this.nodeIndices;
+ _toggleBarnesHut : function() {
+ this.constants.physics.barnesHut.enabled = !this.constants.physics.barnesHut.enabled;
+ this._loadSelectedForceSolver();
+ this.moving = true;
+ this.start();
},
/**
- * /**
- * This function sets the global references to nodes, edges and nodeIndices back to
- * those of the supplied (active) sector. If a type is defined, do the specific type
+ * Before calculating the forces, we check if we need to cluster to keep up performance and we check
+ * if there is more than one node. If it is just one node, we dont calculate anything.
*
- * @param {String} sectorId
- * @param {String} [sectorType] | "active" or "frozen"
* @private
*/
- _switchToSector : function(sectorId, sectorType) {
- if (sectorType === undefined || sectorType == "active") {
- this._switchToActiveSector(sectorId);
+ _initializeForceCalculation : function() {
+ // stop calculation if there is only one node
+ if (this.nodeIndices.length == 1) {
+ this.nodes[this.nodeIndices[0]]._setForce(0,0);
}
else {
- this._switchToFrozenSector(sectorId);
+ // if there are too many nodes on screen, we cluster without repositioning
+ if (this.nodeIndices.length > this.constants.clustering.clusterThreshold && this.constants.clustering.enabled == true) {
+ this.clusterToFit(this.constants.clustering.reduceToNodes, false);
+ }
+
+ // we now start the force calculation
+ this._calculateForces();
}
},
/**
- * This function sets the global references to nodes, edges and nodeIndices back to
- * those of the supplied active sector.
- *
- * @param sectorId
+ * Calculate the external forces acting on the nodes
+ * Forces are caused by: edges, repulsing forces between nodes, gravity
* @private
*/
- _switchToActiveSector : function(sectorId) {
- this.nodeIndices = this.sectors["active"][sectorId]["nodeIndices"];
- this.nodes = this.sectors["active"][sectorId]["nodes"];
- this.edges = this.sectors["active"][sectorId]["edges"];
- },
+ _calculateForces : function() {
+ // Gravity is required to keep separated groups from floating off
+ // the forces are reset to zero in this loop by using _setForce instead
+ // of _addForce
+ this._calculateGravitationalForces();
+ this._calculateNodeForces();
- /**
- * This function sets the global references to nodes, edges and nodeIndices back to
- * those of the supplied frozen sector.
- *
- * @param sectorId
- * @private
- */
- _switchToFrozenSector : function(sectorId) {
- this.nodeIndices = this.sectors["frozen"][sectorId]["nodeIndices"];
- this.nodes = this.sectors["frozen"][sectorId]["nodes"];
- this.edges = this.sectors["frozen"][sectorId]["edges"];
+
+ if (this.constants.smoothCurves == true) {
+ this._calculateSpringForcesWithSupport();
+ }
+ else {
+ this._calculateSpringForces();
+ }
},
/**
- * This function sets the global references to nodes, edges and nodeIndices to
- * those of the navigation controls sector.
+ * 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.nodes with the support nodes.
*
* @private
*/
- _switchToNavigationSector : function() {
- this.nodeIndices = this.sectors["navigation"]["nodeIndices"];
- this.nodes = this.sectors["navigation"]["nodes"];
- this.edges = this.sectors["navigation"]["edges"];
+ _updateCalculationNodes : function() {
+ if (this.constants.smoothCurves == true) {
+ this.calculationNodes = {};
+ this.calculationNodeIndices = [];
+
+ for (var nodeId in this.nodes) {
+ if (this.nodes.hasOwnProperty(nodeId)) {
+ this.calculationNodes[nodeId] = this.nodes[nodeId];
+ }
+ }
+ var supportNodes = this.sectors['support']['nodes'];
+ for (var supportNodeId in supportNodes) {
+ if (supportNodes.hasOwnProperty(supportNodeId)) {
+ if (this.edges.hasOwnProperty(supportNodes[supportNodeId].parentEdgeId)) {
+ this.calculationNodes[supportNodeId] = supportNodes[supportNodeId];
+ }
+ else {
+ supportNodes[supportNodeId]._setForce(0,0);
+ }
+ }
+ }
+
+ for (var idx in this.calculationNodes) {
+ if (this.calculationNodes.hasOwnProperty(idx)) {
+ this.calculationNodeIndices.push(idx);
+ }
+ }
+ }
+ else {
+ this.calculationNodes = this.nodes;
+ this.calculationNodeIndices = this.nodeIndices;
+ }
},
/**
- * This function sets the global references to nodes, edges and nodeIndices back to
- * those of the currently active sector.
+ * this function applies the central gravity effect to keep groups from floating off
*
* @private
*/
- _loadLatestSector : function() {
- this._switchToSector(this._sector());
+ _calculateGravitationalForces : function() {
+ var dx, dy, distance, node, i;
+ var nodes = this.calculationNodes;
+ var gravity = this.constants.physics.centralGravity;
+ var gravityForce = 0;
+
+ for (i = 0; i < this.calculationNodeIndices.length; i++) {
+ node = nodes[this.calculationNodeIndices[i]];
+ node.damping = this.constants.physics.damping; // possibly add function to alter damping properties of clusters.
+ // gravity does not apply when we are in a pocket sector
+ if (this._sector() == "default" && gravity != 0) {
+ dx = -node.x;
+ dy = -node.y;
+ distance = Math.sqrt(dx*dx + dy*dy);
+ gravityForce = gravity / distance;
+
+ node.fx = dx * gravityForce;
+ node.fy = dy * gravityForce;
+ }
+ else {
+ node.fx = 0;
+ node.fy = 0;
+ }
+ }
},
/**
- * This function returns the currently active sector Id
+ * this function calculates the effects of the springs in the case of unsmooth curves.
*
- * @returns {String}
* @private
*/
- _sector : function() {
- return this.activeSector[this.activeSector.length-1];
- },
+ _calculateSpringForces : function() {
+ var edgeLength, edge, edgeId;
+ var dx, dy, fx, fy, springForce, length;
+ var edges = this.edges;
+ // forces caused by the edges, modelled as springs
+ for (edgeId in edges) {
+ if (edges.hasOwnProperty(edgeId)) {
+ edge = edges[edgeId];
+ if (edge.connected) {
+ // only calculate forces if nodes are in the same sector
+ if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) {
+ edgeLength = edge.length;
+ // this implies that the edges between big clusters are longer
+ edgeLength += (edge.to.clusterSize + edge.from.clusterSize - 2) * this.constants.clustering.edgeGrowth;
+
+ dx = (edge.from.x - edge.to.x);
+ dy = (edge.from.y - edge.to.y);
+ length = Math.sqrt(dx * dx + dy * dy);
+
+ if (length == 0) {
+ length = 0.01;
+ }
- /**
- * This function returns the previously active sector Id
- *
- * @returns {String}
- * @private
- */
- _previousSector : function() {
- if (this.activeSector.length > 1) {
- return this.activeSector[this.activeSector.length-2];
- }
- else {
- throw new TypeError('there are not enough sectors in the this.activeSector array.');
+ springForce = this.constants.physics.springConstant * (edgeLength - length) / length;
+
+ fx = dx * springForce;
+ fy = dy * springForce;
+
+ edge.from.fx += fx;
+ edge.from.fy += fy;
+ edge.to.fx -= fx;
+ edge.to.fy -= fy;
+ }
+ }
+ }
}
},
/**
- * We add the active sector at the end of the this.activeSector array
- * This ensures it is the currently active sector returned by _sector() and it reaches the top
- * of the activeSector stack. When we reverse our steps we move from the end to the beginning of this stack.
+ * This function calculates the springforces on the nodes, accounting for the support nodes.
*
- * @param newId
* @private
*/
- _setActiveSector : function(newId) {
- this.activeSector.push(newId);
+ _calculateSpringForcesWithSupport : function() {
+ var edgeLength, edge, edgeId, combinedClusterSize;
+ var edges = this.edges;
+
+ // forces caused by the edges, modelled as springs
+ for (edgeId in edges) {
+ if (edges.hasOwnProperty(edgeId)) {
+ edge = edges[edgeId];
+ if (edge.connected) {
+ // only calculate forces if nodes are in the same sector
+ if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) {
+ if (edge.via != null) {
+ var node1 = edge.to;
+ var node2 = edge.via;
+ var node3 = edge.from;
+
+ edgeLength = edge.length;
+
+ combinedClusterSize = node1.clusterSize + node3.clusterSize - 2;
+
+ // this implies that the edges between big clusters are longer
+ edgeLength += combinedClusterSize * this.constants.clustering.edgeGrowth;
+ this._calculateSpringForce(node1,node2,0.5*edgeLength);
+ this._calculateSpringForce(node2,node3,0.5*edgeLength);
+ }
+ }
+ }
+ }
+ }
},
/**
- * We remove the currently active sector id from the active sector stack. This happens when
- * we reactivate the previously active sector
+ * This is the code actually performing the calculation for the function above. It is split out to avoid repetition.
*
+ * @param node1
+ * @param node2
+ * @param edgeLength
* @private
*/
- _forgetLastSector : function() {
- this.activeSector.pop();
- },
+ _calculateSpringForce : function(node1,node2,edgeLength) {
+ var dx, dy, fx, fy, springForce, length;
+
+ dx = (node1.x - node2.x);
+ dy = (node1.y - node2.y);
+ length = Math.sqrt(dx * dx + dy * dy);
+
+ springForce = this.constants.physics.springConstant * (edgeLength - length) / length;
+
+ fx = dx * springForce;
+ fy = dy * springForce;
+
+ node1.fx += fx;
+ node1.fy += fy;
+ node2.fx -= fx;
+ node2.fy -= fy;
+ }
+}
+/**
+ * Created by Alex on 2/10/14.
+ */
+
+var hierarchalRepulsionMixin = {
/**
- * This function creates a new active sector with the supplied newId. This newId
- * is the expanding node id.
+ * Calculate the forces the nodes apply on eachother based on a repulsion field.
+ * This field is linearly approximated.
*
- * @param {String} newId | Id of the new active sector
* @private
- */
- _createNewSector : function(newId) {
- // create the new sector
- this.sectors["active"][newId] = {"nodes":{},
- "edges":{},
- "nodeIndices":[],
- "formationScale": this.scale,
- "drawingNode": undefined};
+ */
+ _calculateNodeForces : function() {
+ var dx, dy, distance, fx, fy, combinedClusterSize,
+ repulsingForce, node1, node2, i, j;
- // create the new sector render node. This gives visual feedback that you are in a new sector.
- this.sectors["active"][newId]['drawingNode'] = new Node(
- {id:newId,
- color: {
- background: "#eaefef",
- border: "495c5e"
+ var nodes = this.calculationNodes;
+ var nodeIndices = this.calculationNodeIndices;
+
+ // approximation constants
+ var b = 5;
+ var a_base = 0.5*-b;
+
+
+ // repulsing forces between nodes
+ var nodeDistance = this.constants.physics.repulsion.nodeDistance;
+ var minimumDistance = 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]];
+
+ dx = node2.x - node1.x;
+ dy = node2.y - node1.y;
+ distance = Math.sqrt(dx * dx + dy * dy);
+
+ var a = a_base / minimumDistance;
+ if (distance < 2*minimumDistance) {
+ repulsingForce = a * distance + b; // linear approx of 1 / (1 + Math.exp((distance / minimumDistance - 1) * steepness))
+
+ // normalize force with
+ if (distance == 0) {
+ distance = 0.01;
}
- },{},{},this.constants);
- this.sectors["active"][newId]['drawingNode'].clusterSize = 2;
- },
+ else {
+ repulsingForce = repulsingForce/distance;
+ }
+ fx = dx * repulsingForce;
+ fy = dy * repulsingForce;
+
+ node1.fx -= fx;
+ node1.fy -= fy;
+ node2.fx += fx;
+ node2.fy += fy;
+ }
+ }
+ }
+ }
+}
+/**
+ * Created by Alex on 2/10/14.
+ */
+var barnesHutMixin = {
/**
- * This function removes the currently active sector. This is called when we create a new
- * active sector.
+ * 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.
*
- * @param {String} sectorId | Id of the active sector that will be removed
* @private
*/
- _deleteActiveSector : function(sectorId) {
- delete this.sectors["active"][sectorId];
+ _calculateNodeForces : function() {
+ var node;
+ var nodes = this.calculationNodes;
+ var nodeIndices = this.calculationNodeIndices;
+ var nodeCount = nodeIndices.length;
+
+ this._formBarnesHutTree(nodes,nodeIndices);
+
+ var barnesHutTree = this.barnesHutTree;
+
+ // place the nodes one by one recursively
+ for (var i = 0; i < nodeCount; i++) {
+ node = nodes[nodeIndices[i]];
+ // starting with root is irrelevant, it never passes the BarnesHut 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);
+ }
},
/**
- * This function removes the currently active sector. This is called when we reactivate
- * the previously active sector.
+ * 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 {String} sectorId | Id of the active sector that will be removed
+ * @param parentBranch
+ * @param node
* @private
*/
- _deleteFrozenSector : function(sectorId) {
- delete this.sectors["frozen"][sectorId];
- },
+ _getForceContribution : function(parentBranch,node) {
+ // we get no force contribution from an empty region
+ if (parentBranch.childrenCount > 0) {
+ var dx,dy,distance;
+
+ // 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);
+ // BarnesHut 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.constants.physics.barnesHut.theta) {
+ // duplicate code to reduce function calls to speed up program
+ if (distance == 0) {
+ distance = 0.1*Math.random();
+ dx = distance;
+ }
+ var gravityForce = this.constants.physics.barnesHut.gravitationalConstant * parentBranch.mass * node.mass / (distance * distance * distance);
+ var fx = dx * gravityForce;
+ var fy = dy * gravityForce;
+ node.fx += fx;
+ node.fy += 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.constants.physics.barnesHut.gravitationalConstant * parentBranch.mass * node.mass / (distance * distance * distance);
+ var fx = dx * gravityForce;
+ var fy = dy * gravityForce;
+ node.fx += fx;
+ node.fy += fy;
+ }
+ }
+ }
+ }
+ },
/**
- * Freezing an active sector means moving it from the "active" object to the "frozen" object.
- * We copy the references, then delete the active entree.
+ * This function constructs the barnesHut tree recursively. It creates the root, splits it and starts placing the nodes.
*
- * @param sectorId
+ * @param nodes
+ * @param nodeIndices
* @private
*/
- _freezeSector : function(sectorId) {
- // we move the set references from the active to the frozen stack.
- this.sectors["frozen"][sectorId] = this.sectors["active"][sectorId];
+ _formBarnesHutTree : function(nodes,nodeIndices) {
+ var node;
+ var nodeCount = nodeIndices.length;
+
+ 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 (x < minX) { minX = x; }
+ if (x > maxX) { maxX = x; }
+ if (y < minY) { minY = y; }
+ if (y > maxY) { maxY = y; }
+ }
+ // make the range a square
+ var sizeDiff = Math.abs(maxX - minX) - Math.abs(maxY - minY); // difference between X and Y
+ if (sizeDiff > 0) {minY -= 0.5 * sizeDiff; maxY += 0.5 * sizeDiff;} // xSize > ySize
+ else {minX += 0.5 * sizeDiff; maxX -= 0.5 * sizeDiff;} // xSize < ySize
+
+
+ var minimumTreeSize = 1e-5;
+ 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}, // Center of Mass
+ mass:0,
+ range: {minX:centerX-halfRootSize,maxX:centerX+halfRootSize,
+ minY:centerY-halfRootSize,maxY:centerY+halfRootSize},
+ size: rootSize,
+ calcSize: 1 / rootSize,
+ children: {data:null},
+ maxWidth: 0,
+ level: 0,
+ childrenCount: 4
+ }};
+ this._splitBranch(barnesHutTree.root);
+
+ // place the nodes one by one recursively
+ for (i = 0; i < nodeCount; i++) {
+ node = nodes[nodeIndices[i]];
+ this._placeInTree(barnesHutTree.root,node);
+ }
+
+ // make global
+ this.barnesHutTree = barnesHutTree
+ },
+
+
+ _updateBranchMass : function(parentBranch, node) {
+ var totalMass = parentBranch.mass + node.mass;
+ var totalMassInv = 1/totalMass;
+
+ parentBranch.centerOfMass.x = parentBranch.centerOfMass.x * parentBranch.mass + node.x * node.mass;
+ parentBranch.centerOfMass.x *= totalMassInv;
+
+ parentBranch.centerOfMass.y = parentBranch.centerOfMass.y * parentBranch.mass + node.y * node.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;
- // we have moved the sector data into the frozen set, we now remove it from the active set
- this._deleteActiveSector(sectorId);
},
- /**
- * This is the reverse operation of _freezeSector. Activating means moving the sector from the "frozen"
+ _placeInTree : function(parentBranch,node,skipMassUpdate) {
+ if (skipMassUpdate != true || skipMassUpdate === undefined) {
+ // update the mass of the branch.
+ this._updateBranchMass(parentBranch,node);
+ }
+
+ if (parentBranch.children.NW.range.maxX > node.x) { // in NW or SW
+ if (parentBranch.children.NW.range.maxY > node.y) { // in NW
+ this._placeInRegion(parentBranch,node,"NW");
+ }
+ else { // in SW
+ this._placeInRegion(parentBranch,node,"SW");
+ }
+ }
+ else { // in NE or SE
+ if (parentBranch.children.NW.range.maxY > node.y) { // in NE
+ this._placeInRegion(parentBranch,node,"NE");
+ }
+ else { // in SE
+ this._placeInRegion(parentBranch,node,"SE");
+ }
+ }
+ },
+
+
+ _placeInRegion : function(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();
+ this._placeInTree(parentBranch,node, true);
+ }
+ 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;
+ }
+ },
+
+
+ /**
+ * 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
+ */
+ _splitBranch : function(parentBranch) {
+ // if the branch is filled with a node, replace the node in the new subset.
+ var containedNode = null;
+ if (parentBranch.childrenCount == 1) {
+ containedNode = parentBranch.children.data;
+ parentBranch.mass = 0; parentBranch.centerOfMass.x = 0; parentBranch.centerOfMass.y = 0;
+ }
+ parentBranch.childrenCount = 4;
+ parentBranch.children.data = null;
+ this._insertRegion(parentBranch,"NW");
+ this._insertRegion(parentBranch,"NE");
+ this._insertRegion(parentBranch,"SW");
+ this._insertRegion(parentBranch,"SE");
+
+ if (containedNode != null) {
+ this._placeInTree(parentBranch,containedNode);
+ }
+ },
+
+
+ /**
+ * 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
+ */
+ _insertRegion : function(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
+ };
+ },
+
+
+ /**
+ * This function is for debugging purposed, it draws the tree.
+ *
+ * @param ctx
+ * @param color
+ * @private
+ */
+ _drawTree : function(ctx,color) {
+ if (this.barnesHutTree !== undefined) {
+
+ ctx.lineWidth = 1;
+
+ this._drawBranch(this.barnesHutTree.root,ctx,color);
+ }
+ },
+
+
+ /**
+ * This function is for debugging purposes. It draws the branches recursively.
+ *
+ * @param branch
+ * @param ctx
+ * @param color
+ * @private
+ */
+ _drawBranch : function(branch,ctx,color) {
+ if (color === undefined) {
+ color = "#FF0000";
+ }
+
+ if (branch.childrenCount == 4) {
+ this._drawBranch(branch.children.NW,ctx);
+ this._drawBranch(branch.children.NE,ctx);
+ this._drawBranch(branch.children.SE,ctx);
+ this._drawBranch(branch.children.SW,ctx);
+ }
+ ctx.strokeStyle = color;
+ ctx.beginPath();
+ ctx.moveTo(branch.range.minX,branch.range.minY);
+ ctx.lineTo(branch.range.maxX,branch.range.minY);
+ ctx.stroke();
+
+ ctx.beginPath();
+ ctx.moveTo(branch.range.maxX,branch.range.minY);
+ ctx.lineTo(branch.range.maxX,branch.range.maxY);
+ ctx.stroke();
+
+ ctx.beginPath();
+ ctx.moveTo(branch.range.maxX,branch.range.maxY);
+ ctx.lineTo(branch.range.minX,branch.range.maxY);
+ ctx.stroke();
+
+ 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();
+ }
+ */
+ }
+
+};
+/**
+ * Created by Alex on 2/10/14.
+ */
+
+var repulsionMixin = {
+
+
+ /**
+ * Calculate the forces the nodes apply on eachother based on a repulsion field.
+ * This field is linearly approximated.
+ *
+ * @private
+ */
+ _calculateNodeForces : function() {
+ var dx, dy, angle, distance, fx, fy, combinedClusterSize,
+ repulsingForce, node1, node2, i, j;
+
+ var nodes = this.calculationNodes;
+ var nodeIndices = this.calculationNodeIndices;
+
+ // approximation constants
+ var a_base = -2/3;
+ var b = 4/3;
+
+ // repulsing forces between nodes
+ var nodeDistance = this.constants.physics.repulsion.nodeDistance;
+ var minimumDistance = 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]];
+ combinedClusterSize = node1.clusterSize + node2.clusterSize - 2;
+
+ dx = node2.x - node1.x;
+ dy = node2.y - node1.y;
+ distance = Math.sqrt(dx * dx + dy * dy);
+
+ minimumDistance = (combinedClusterSize == 0) ? nodeDistance : (nodeDistance * (1 + combinedClusterSize * this.constants.clustering.distanceAmplification));
+ var a = a_base / minimumDistance;
+ if (distance < 2*minimumDistance) {
+ if (distance < 0.5*minimumDistance) {
+ repulsingForce = 1.0;
+ }
+ else {
+ repulsingForce = a * distance + b; // linear approx of 1 / (1 + Math.exp((distance / minimumDistance - 1) * steepness))
+ }
+
+ // amplify the repulsion for clusters.
+ repulsingForce *= (combinedClusterSize == 0) ? 1 : 1 + combinedClusterSize * this.constants.clustering.forceAmplification;
+ repulsingForce = repulsingForce/distance;
+
+ fx = dx * repulsingForce;
+ fy = dy * repulsingForce;
+
+ node1.fx -= fx;
+ node1.fy -= fy;
+ node2.fx += fx;
+ node2.fy += fy;
+ }
+ }
+ }
+ }
+}
+var HierarchicalLayoutMixin = {
+
+
+ /**
+ * This is the main function to layout the nodes in a hierarchical way.
+ * It checks if the node details are supplied correctly
+ *
+ * @private
+ */
+ _setupHierarchicalLayout : function() {
+ if (this.constants.hierarchicalLayout.enabled == true) {
+
+ // get the size of the largest hubs and check if the user has defined a level for a node.
+ var hubsize = 0;
+ var node, nodeId;
+ var definedLevel = false;
+ var undefinedLevel = false;
+
+ for (nodeId in this.nodes) {
+ if (this.nodes.hasOwnProperty(nodeId)) {
+ node = this.nodes[nodeId];
+ if (node.level != -1) {
+ definedLevel = true;
+ }
+ else {
+ undefinedLevel = true;
+ }
+ if (hubsize < node.edges.length) {
+ hubsize = node.edges.length;
+ }
+ }
+ }
+
+ // if the user defined some levels but not all, alert and run without hierarchical layout
+ if (undefinedLevel == true && definedLevel == true) {
+ alert("To use the hierarchical layout, nodes require either no predefined levels or levels have to be defined for all nodes.")
+ this.zoomToFit(true,this.constants.clustering.enabled);
+ if (!this.constants.clustering.enabled) {
+ this.start();
+ }
+ }
+ else {
+ // setup the system to use hierarchical method.
+ this._changeConstants();
+
+ // define levels if undefined by the users. Based on hubsize
+ if (undefinedLevel == true) {
+ this._determineLevels(hubsize);
+ }
+ // check the distribution of the nodes per level.
+ var distribution = this._getDistribution();
+
+ // place the nodes on the canvas. This also stablilizes the system.
+ this._placeNodesByHierarchy(distribution);
+
+ // start the simulation.
+ this.start();
+ }
+ }
+ },
+
+
+ /**
+ * This function places the nodes on the canvas based on the hierarchial distribution.
+ *
+ * @param {Object} distribution | obtained by the function this._getDistribution()
+ * @private
+ */
+ _placeNodesByHierarchy : function(distribution) {
+ var nodeId, node;
+
+ // start placing all the level 0 nodes first. Then recursively position their branches.
+ for (nodeId in distribution[0].nodes) {
+ if (distribution[0].nodes.hasOwnProperty(nodeId)) {
+ node = distribution[0].nodes[nodeId];
+ if (node.xFixed) {
+ node.x = distribution[0].minPos;
+ distribution[0].minPos += distribution[0].nodeSpacing;
+ node.xFixed = false;
+ }
+ this._placeBranchNodes(node.edges,node.id,distribution,node.level);
+ }
+ }
+
+ // give the nodes a defined width so the zoomToFit can be used. This size is arbitrary.
+ for (nodeId in this.nodes) {
+ if (this.nodes.hasOwnProperty(nodeId)) {
+ node = this.nodes[nodeId];
+ node.width = 100;
+ node.height = 100;
+ }
+ }
+
+ // stabilize the system after positioning. This function calls zoomToFit.
+ this._doStabilize();
+
+ // reset the arbitrary width and height we gave the nodes.
+ for (nodeId in this.nodes) {
+ if (this.nodes.hasOwnProperty(nodeId)) {
+ this.nodes[nodeId]._reset();
+ }
+ }
+ },
+
+
+ /**
+ * This function get the distribution of levels based on hubsize
+ *
+ * @returns {Object}
+ * @private
+ */
+ _getDistribution : function() {
+ var distribution = {};
+ var nodeId, node;
+
+ // we fix Y because the hierarchy is vertical, we fix X so we do not give a node an x position for a second time.
+ // the fix of X is removed after the x value has been set.
+ for (nodeId in this.nodes) {
+ if (this.nodes.hasOwnProperty(nodeId)) {
+ node = this.nodes[nodeId];
+ node.xFixed = true;
+ node.yFixed = true;
+ node.y = this.constants.hierarchicalLayout.levelSeparation*node.level;
+ if (!distribution.hasOwnProperty(node.level)) {
+ distribution[node.level] = {amount: 0, nodes: {}, minPos:0, nodeSpacing:0};
+ }
+ distribution[node.level].amount += 1;
+ distribution[node.level].nodes[node.id] = node;
+ }
+ }
+
+ // determine the largest amount of nodes of all levels
+ var maxCount = 0;
+ for (var level in distribution) {
+ if (distribution.hasOwnProperty(level)) {
+ if (maxCount < distribution[level].amount) {
+ maxCount = distribution[level].amount;
+ }
+ }
+ }
+
+ // set the initial position and spacing of each nodes accordingly
+ for (var level in distribution) {
+ if (distribution.hasOwnProperty(level)) {
+ distribution[level].nodeSpacing = (maxCount + 1) * this.constants.hierarchicalLayout.nodeSpacing;
+ distribution[level].nodeSpacing /= (distribution[level].amount + 1);
+ distribution[level].minPos = distribution[level].nodeSpacing - (0.5 * (distribution[level].amount + 1) * distribution[level].nodeSpacing);
+ }
+ }
+
+ return distribution;
+ },
+
+
+ /**
+ * this function allocates nodes in levels based on the recursive branching from the largest hubs.
+ *
+ * @param hubsize
+ * @private
+ */
+ _determineLevels : function(hubsize) {
+ var nodeId, node;
+
+ // determine hubs
+ for (nodeId in this.nodes) {
+ if (this.nodes.hasOwnProperty(nodeId)) {
+ node = this.nodes[nodeId];
+ if (node.edges.length == hubsize) {
+ node.level = 0;
+ }
+ }
+ }
+
+ // branch from hubs
+ for (nodeId in this.nodes) {
+ if (this.nodes.hasOwnProperty(nodeId)) {
+ node = this.nodes[nodeId];
+ if (node.level == 0) {
+ this._setLevel(1,node.edges,node.id);
+ }
+ }
+ }
+ },
+
+
+ /**
+ * Since hierarchical layout does not support:
+ * - smooth curves (based on the physics),
+ * - clustering (based on dynamic node counts)
+ *
+ * We disable both features so there will be no problems.
+ *
+ * @private
+ */
+ _changeConstants : function() {
+ this.constants.clustering.enabled = false;
+ this.constants.physics.barnesHut.enabled = false;
+ this.constants.physics.hierarchicalRepulsion.enabled = true;
+ this._loadSelectedForceSolver();
+ this.constants.smoothCurves = false;
+ this._configureSmoothCurves();
+ },
+
+
+ /**
+ * This is a recursively called function to enumerate the branches from the largest hubs and place the nodes
+ * on a X position that ensures there will be no overlap.
+ *
+ * @param edges
+ * @param parentId
+ * @param distribution
+ * @param parentLevel
+ * @private
+ */
+ _placeBranchNodes : function(edges, parentId, distribution, parentLevel) {
+ for (var i = 0; i < edges.length; i++) {
+ var childNode = null;
+ if (edges[i].toId == parentId) {
+ childNode = edges[i].from;
+ }
+ else {
+ childNode = edges[i].to;
+ }
+
+ // if a node is conneceted to another node on the same level (or higher (means lower level))!, this is not handled here.
+ if (childNode.xFixed && childNode.level > parentLevel) {
+ childNode.xFixed = false;
+ childNode.x = distribution[childNode.level].minPos;
+ distribution[childNode.level].minPos += distribution[childNode.level].nodeSpacing;
+ if (childNode.edges.length > 1) {
+ this._placeBranchNodes(childNode.edges,childNode.id,distribution,childNode.level);
+ }
+ }
+ }
+ },
+
+
+ /**
+ * this function is called recursively to enumerate the barnches of the largest hubs and give each node a level.
+ *
+ * @param level
+ * @param edges
+ * @param parentId
+ * @private
+ */
+ _setLevel : function(level, edges, parentId) {
+ for (var i = 0; i < edges.length; i++) {
+ var childNode = null;
+ if (edges[i].toId == parentId) {
+ childNode = edges[i].from;
+ }
+ else {
+ childNode = edges[i].to;
+ }
+ if (childNode.level == -1 || childNode.level > level) {
+ childNode.level = level;
+ if (edges.length > 1) {
+ this._setLevel(level+1, childNode.edges, childNode.id);
+ }
+ }
+ }
+ }
+};
+/**
+ * Created by Alex on 2/4/14.
+ */
+
+var manipulationMixin = {
+
+ /**
+ * clears the toolbar div element of children
+ *
+ * @private
+ */
+ _clearManipulatorBar : function() {
+ while (this.manipulationDiv.hasChildNodes()) {
+ this.manipulationDiv.removeChild(this.manipulationDiv.firstChild);
+ }
+ },
+
+ /**
+ * Manipulation UI temporarily overloads certain functions to extend or replace them. To be able to restore
+ * these functions to their original functionality, we saved them in this.cachedFunctions.
+ * This function restores these functions to their original function.
+ *
+ * @private
+ */
+ _restoreOverloadedFunctions : function() {
+ for (var functionName in this.cachedFunctions) {
+ if (this.cachedFunctions.hasOwnProperty(functionName)) {
+ this[functionName] = this.cachedFunctions[functionName];
+ }
+ }
+ },
+
+ /**
+ * Enable or disable edit-mode.
+ *
+ * @private
+ */
+ _toggleEditMode : function() {
+ this.editMode = !this.editMode;
+ var toolbar = document.getElementById("graph-manipulationDiv");
+ var closeDiv = document.getElementById("graph-manipulation-closeDiv");
+ var editModeDiv = document.getElementById("graph-manipulation-editMode");
+ if (this.editMode == true) {
+ toolbar.style.display="block";
+ closeDiv.style.display="block";
+ editModeDiv.style.display="none";
+ closeDiv.onclick = this._toggleEditMode.bind(this);
+ }
+ else {
+ toolbar.style.display="none";
+ closeDiv.style.display="none";
+ editModeDiv.style.display="block";
+ closeDiv.onclick = null;
+ }
+ this._createManipulatorBar()
+ },
+
+ /**
+ * main function, creates the main toolbar. Removes functions bound to the select event. Binds all the buttons of the toolbar.
+ *
+ * @private
+ */
+ _createManipulatorBar : function() {
+ // remove bound functions
+ this.off('select', this.boundFunction);
+
+ // restore overloaded functions
+ this._restoreOverloadedFunctions();
+
+ // resume calculation
+ this.freezeSimulation = false;
+
+ // reset global variables
+ this.blockConnectingEdgeSelection = false;
+ this.forceAppendSelection = false
+
+ if (this.editMode == true) {
+ while (this.manipulationDiv.hasChildNodes()) {
+ this.manipulationDiv.removeChild(this.manipulationDiv.firstChild);
+ }
+ // add the icons to the manipulator div
+ this.manipulationDiv.innerHTML = "" +
+ "" +
+ "Add Node" +
+ "
" +
+ "" +
+ "Add Link";
+ if (this._getSelectedNodeCount() == 1 && this.triggerFunctions.edit) {
+ this.manipulationDiv.innerHTML += "" +
+ "" +
+ "" +
+ "Edit Node";
+ }
+ if (this._selectionIsEmpty() == false) {
+ this.manipulationDiv.innerHTML += "" +
+ "" +
+ "" +
+ "Delete selected";
+ }
+
+
+ // bind the icons
+ var addNodeButton = document.getElementById("graph-manipulate-addNode");
+ addNodeButton.onclick = this._createAddNodeToolbar.bind(this);
+ var addEdgeButton = document.getElementById("graph-manipulate-connectNode");
+ addEdgeButton.onclick = this._createAddEdgeToolbar.bind(this);
+ if (this._getSelectedNodeCount() == 1 && this.triggerFunctions.edit) {
+ var editButton = document.getElementById("graph-manipulate-editNode");
+ editButton.onclick = this._editNode.bind(this);
+ }
+ if (this._selectionIsEmpty() == false) {
+ var deleteButton = document.getElementById("graph-manipulate-delete");
+ deleteButton.onclick = this._deleteSelected.bind(this);
+ }
+ var closeDiv = document.getElementById("graph-manipulation-closeDiv");
+ closeDiv.onclick = this._toggleEditMode.bind(this);
+
+ this.boundFunction = this._createManipulatorBar.bind(this);
+ this.on('select', this.boundFunction);
+ }
+ else {
+ this.editModeDiv.innerHTML = "" +
+ "" +
+ "Edit"
+ var editModeButton = document.getElementById("graph-manipulate-editModeButton");
+ editModeButton.onclick = this._toggleEditMode.bind(this);
+ }
+ },
+
+
+
+ /**
+ * Create the toolbar for adding Nodes
+ *
+ * @private
+ */
+ _createAddNodeToolbar : function() {
+ // clear the toolbar
+ this._clearManipulatorBar();
+ this.off('select', this.boundFunction);
+
+ // create the toolbar contents
+ this.manipulationDiv.innerHTML = "" +
+ "" +
+ "Back" +
+ "" +
+ "" +
+ "Click in an empty space to place a new node";
+
+ // bind the icon
+ var backButton = document.getElementById("graph-manipulate-back");
+ backButton.onclick = this._createManipulatorBar.bind(this);
+
+ // we use the boundFunction so we can reference it when we unbind it from the "select" event.
+ this.boundFunction = this._addNode.bind(this);
+ this.on('select', this.boundFunction);
+ },
+
+
+ /**
+ * create the toolbar to connect nodes
+ *
+ * @private
+ */
+ _createAddEdgeToolbar : function() {
+ // clear the toolbar
+ this._clearManipulatorBar();
+ this._unselectAll(true);
+ this.freezeSimulation = true;
+
+ this.off('select', this.boundFunction);
+
+ this._unselectAll();
+ this.forceAppendSelection = false;
+ this.blockConnectingEdgeSelection = true;
+
+ this.manipulationDiv.innerHTML = "" +
+ "" +
+ "Back" +
+ "" +
+ "" +
+ "Click on a node and drag the edge to another node to connect them.";
+
+ // bind the icon
+ var backButton = document.getElementById("graph-manipulate-back");
+ backButton.onclick = this._createManipulatorBar.bind(this);
+
+ // we use the boundFunction so we can reference it when we unbind it from the "select" event.
+ this.boundFunction = this._handleConnect.bind(this);
+ this.on('select', this.boundFunction);
+
+ // temporarily overload functions
+ this.cachedFunctions["_handleTouch"] = this._handleTouch;
+ this.cachedFunctions["_handleOnRelease"] = this._handleOnRelease;
+ this._handleTouch = this._handleConnect;
+ this._handleOnRelease = this._finishConnect;
+
+ // redraw to show the unselect
+ this._redraw();
+
+ },
+
+
+ /**
+ * the function bound to the selection event. It checks if you want to connect a cluster and changes the description
+ * to walk the user through the process.
+ *
+ * @private
+ */
+ _handleConnect : function(pointer) {
+ if (this._getSelectedNodeCount() == 0) {
+ var node = this._getNodeAt(pointer);
+ if (node != null) {
+ if (node.clusterSize > 1) {
+ alert("Cannot create edges to a cluster.")
+ }
+ else {
+ this._selectObject(node,false);
+ // create a node the temporary line can look at
+ this.sectors['support']['nodes']['targetNode'] = new Node({id:'targetNode'},{},{},this.constants);
+ this.sectors['support']['nodes']['targetNode'].x = node.x;
+ this.sectors['support']['nodes']['targetNode'].y = node.y;
+ this.sectors['support']['nodes']['targetViaNode'] = new Node({id:'targetViaNode'},{},{},this.constants);
+ this.sectors['support']['nodes']['targetViaNode'].x = node.x;
+ this.sectors['support']['nodes']['targetViaNode'].y = node.y;
+ this.sectors['support']['nodes']['targetViaNode'].parentEdgeId = "connectionEdge";
+
+ // create a temporary edge
+ this.edges['connectionEdge'] = new Edge({id:"connectionEdge",from:node.id,to:this.sectors['support']['nodes']['targetNode'].id}, this, this.constants);
+ this.edges['connectionEdge'].from = node;
+ this.edges['connectionEdge'].connected = true;
+ this.edges['connectionEdge'].smooth = true;
+ this.edges['connectionEdge'].selected = true;
+ this.edges['connectionEdge'].to = this.sectors['support']['nodes']['targetNode'];
+ this.edges['connectionEdge'].via = this.sectors['support']['nodes']['targetViaNode'];
+
+ this.cachedFunctions["_handleOnDrag"] = this._handleOnDrag;
+ this._handleOnDrag = function(event) {
+ var pointer = this._getPointer(event.gesture.center);
+ this.sectors['support']['nodes']['targetNode'].x = this._canvasToX(pointer.x);
+ this.sectors['support']['nodes']['targetNode'].y = this._canvasToY(pointer.y);
+ this.sectors['support']['nodes']['targetViaNode'].x = 0.5 * (this._canvasToX(pointer.x) + this.edges['connectionEdge'].from.x);
+ this.sectors['support']['nodes']['targetViaNode'].y = this._canvasToY(pointer.y);
+ };
+
+ this.moving = true;
+ this.start();
+ }
+ }
+ }
+ },
+
+ _finishConnect : function(pointer) {
+ if (this._getSelectedNodeCount() == 1) {
+
+ // restore the drag function
+ this._handleOnDrag = this.cachedFunctions["_handleOnDrag"];
+ delete this.cachedFunctions["_handleOnDrag"];
+
+ // remember the edge id
+ var connectFromId = this.edges['connectionEdge'].fromId;
+
+ // remove the temporary nodes and edge
+ delete this.edges['connectionEdge']
+ delete this.sectors['support']['nodes']['targetNode'];
+ delete this.sectors['support']['nodes']['targetViaNode'];
+
+ var node = this._getNodeAt(pointer);
+ if (node != null) {
+ if (node.clusterSize > 1) {
+ alert("Cannot create edges to a cluster.")
+ }
+ else {
+ this._createEdge(connectFromId,node.id);
+ this._createManipulatorBar();
+ }
+ }
+ this._unselectAll();
+ }
+ },
+
+
+ /**
+ * Adds a node on the specified location
+ *
+ * @param {Object} pointer
+ */
+ _addNode : function() {
+ if (this._selectionIsEmpty() && this.editMode == true) {
+ var positionObject = this._pointerToPositionObject(this.pointerPosition);
+ var defaultData = {id:util.randomUUID(),x:positionObject.left,y:positionObject.top,label:"new",allowedToMove:true};
+ if (this.triggerFunctions.add) {
+ if (this.triggerFunctions.add.length == 2) {
+ var me = this;
+ this.triggerFunctions.add(defaultData, function(finalizedData) {
+ me.createNodeOnClick = true;
+ me.nodesData.add(finalizedData);
+ me.createNodeOnClick = false;
+ me._createManipulatorBar();
+ me.moving = true;
+ me.start();
+ });
+ }
+ else {
+ alert("The function for add does not support two arguments (data,callback).");
+ this._createManipulatorBar();
+ this.moving = true;
+ this.start();
+ }
+ }
+ else {
+ this.createNodeOnClick = true;
+ this.nodesData.add(defaultData);
+ this.createNodeOnClick = false;
+ this._createManipulatorBar();
+ this.moving = true;
+ this.start();
+ }
+ }
+ },
+
+
+ /**
+ * connect two nodes with a new edge.
+ *
+ * @private
+ */
+ _createEdge : function(sourceNodeId,targetNodeId) {
+ if (this.editMode == true) {
+ var defaultData = {from:sourceNodeId, to:targetNodeId};
+ if (this.triggerFunctions.connect) {
+ if (this.triggerFunctions.connect.length == 2) {
+ var me = this;
+ this.triggerFunctions.connect(defaultData, function(finalizedData) {
+ me.edgesData.add(finalizedData)
+ me.moving = true;
+ me.start();
+ });
+ }
+ else {
+ alert("The function for connect does not support two arguments (data,callback).");
+ this.moving = true;
+ this.start();
+ }
+ }
+ else {
+ this.edgesData.add(defaultData)
+ this.moving = true;
+ this.start();
+ }
+ }
+ },
+
+
+ /**
+ * Create the toolbar to edit the selected node. The label and the color can be changed. Other colors are derived from the chosen color.
+ *
+ * @private
+ */
+ _editNode : function() {
+ if (this.triggerFunctions.edit && this.editMode == true) {
+ var node = this._getSelectedNode();
+ var data = {id:node.id,
+ label: node.label,
+ group: node.group,
+ shape: node.shape,
+ color: {
+ background:node.color.background,
+ border:node.color.border,
+ highlight: {
+ background:node.color.highlight.background,
+ border:node.color.highlight.border
+ }
+ }};
+ if (this.triggerFunctions.edit.length == 2) {
+ var me = this;
+ this.triggerFunctions.edit(data, function (finalizedData) {
+ me.nodesData.update(finalizedData);
+ me._createManipulatorBar();
+ me.moving = true;
+ me.start();
+ });
+ }
+ else {
+ alert("The function for edit does not support two arguments (data, callback).")
+ }
+ }
+ else {
+ alert("No edit function has been bound to this button.")
+ }
+ },
+
+
+ /**
+ * delete everything in the selection
+ *
+ * @private
+ */
+ _deleteSelected : function() {
+ if (!this._selectionIsEmpty() && this.editMode == true) {
+ if (!this._clusterInSelection()) {
+ var selectedNodes = this.getSelectedNodes();
+ var selectedEdges = this.getSelectedEdges();
+ if (this.triggerFunctions.delete) {
+ var me = this;
+ var data = {nodes: selectedNodes, edges: selectedEdges};
+ if (this.triggerFunctions.delete.length = 2) {
+ this.triggerFunctions.delete(data, function (finalizedData) {
+ me.edgesData.remove(finalizedData.edges);
+ me.nodesData.remove(finalizedData.nodes);
+ this._unselectAll();
+ me.moving = true;
+ me.start();
+ });
+ }
+ else {
+ alert("The function for edit does not support two arguments (data, callback).")
+ }
+ }
+ else {
+ this.edgesData.remove(selectedEdges);
+ this.nodesData.remove(selectedNodes);
+ this._unselectAll();
+ this.moving = true;
+ this.start();
+ }
+ }
+ else {
+ alert("Clusters cannot be deleted.");
+ }
+ }
+ }
+};
+/**
+ * Creation of the SectorMixin var.
+ *
+ * This contains all the functions the Graph object can use to employ the sector system.
+ * The sector system is always used by Graph, though the benefits only apply to the use of clustering.
+ * If clustering is not used, there is no overhead except for a duplicate object with references to nodes and edges.
+ *
+ * Alex de Mulder
+ * 21-01-2013
+ */
+var SectorMixin = {
+
+ /**
+ * This function is only called by the setData function of the Graph object.
+ * This loads the global references into the active sector. This initializes the sector.
+ *
+ * @private
+ */
+ _putDataInSector : function() {
+ this.sectors["active"][this._sector()].nodes = this.nodes;
+ this.sectors["active"][this._sector()].edges = this.edges;
+ this.sectors["active"][this._sector()].nodeIndices = this.nodeIndices;
+ },
+
+
+ /**
+ * /**
+ * This function sets the global references to nodes, edges and nodeIndices back to
+ * those of the supplied (active) sector. If a type is defined, do the specific type
+ *
+ * @param {String} sectorId
+ * @param {String} [sectorType] | "active" or "frozen"
+ * @private
+ */
+ _switchToSector : function(sectorId, sectorType) {
+ if (sectorType === undefined || sectorType == "active") {
+ this._switchToActiveSector(sectorId);
+ }
+ else {
+ this._switchToFrozenSector(sectorId);
+ }
+ },
+
+
+ /**
+ * This function sets the global references to nodes, edges and nodeIndices back to
+ * those of the supplied active sector.
+ *
+ * @param sectorId
+ * @private
+ */
+ _switchToActiveSector : function(sectorId) {
+ this.nodeIndices = this.sectors["active"][sectorId]["nodeIndices"];
+ this.nodes = this.sectors["active"][sectorId]["nodes"];
+ this.edges = this.sectors["active"][sectorId]["edges"];
+ },
+
+
+ /**
+ * This function sets the global references to nodes, edges and nodeIndices back to
+ * those of the supplied active sector.
+ *
+ * @param sectorId
+ * @private
+ */
+ _switchToSupportSector : function() {
+ this.nodeIndices = this.sectors["support"]["nodeIndices"];
+ this.nodes = this.sectors["support"]["nodes"];
+ this.edges = this.sectors["support"]["edges"];
+ },
+
+
+ /**
+ * This function sets the global references to nodes, edges and nodeIndices back to
+ * those of the supplied frozen sector.
+ *
+ * @param sectorId
+ * @private
+ */
+ _switchToFrozenSector : function(sectorId) {
+ this.nodeIndices = this.sectors["frozen"][sectorId]["nodeIndices"];
+ this.nodes = this.sectors["frozen"][sectorId]["nodes"];
+ this.edges = this.sectors["frozen"][sectorId]["edges"];
+ },
+
+
+ /**
+ * This function sets the global references to nodes, edges and nodeIndices back to
+ * those of the currently active sector.
+ *
+ * @private
+ */
+ _loadLatestSector : function() {
+ this._switchToSector(this._sector());
+ },
+
+
+ /**
+ * This function returns the currently active sector Id
+ *
+ * @returns {String}
+ * @private
+ */
+ _sector : function() {
+ return this.activeSector[this.activeSector.length-1];
+ },
+
+
+ /**
+ * This function returns the previously active sector Id
+ *
+ * @returns {String}
+ * @private
+ */
+ _previousSector : function() {
+ if (this.activeSector.length > 1) {
+ return this.activeSector[this.activeSector.length-2];
+ }
+ else {
+ throw new TypeError('there are not enough sectors in the this.activeSector array.');
+ }
+ },
+
+
+ /**
+ * We add the active sector at the end of the this.activeSector array
+ * This ensures it is the currently active sector returned by _sector() and it reaches the top
+ * of the activeSector stack. When we reverse our steps we move from the end to the beginning of this stack.
+ *
+ * @param newId
+ * @private
+ */
+ _setActiveSector : function(newId) {
+ this.activeSector.push(newId);
+ },
+
+
+ /**
+ * We remove the currently active sector id from the active sector stack. This happens when
+ * we reactivate the previously active sector
+ *
+ * @private
+ */
+ _forgetLastSector : function() {
+ this.activeSector.pop();
+ },
+
+
+ /**
+ * This function creates a new active sector with the supplied newId. This newId
+ * is the expanding node id.
+ *
+ * @param {String} newId | Id of the new active sector
+ * @private
+ */
+ _createNewSector : function(newId) {
+ // create the new sector
+ this.sectors["active"][newId] = {"nodes":{},
+ "edges":{},
+ "nodeIndices":[],
+ "formationScale": this.scale,
+ "drawingNode": undefined};
+
+ // create the new sector render node. This gives visual feedback that you are in a new sector.
+ this.sectors["active"][newId]['drawingNode'] = new Node(
+ {id:newId,
+ color: {
+ background: "#eaefef",
+ border: "495c5e"
+ }
+ },{},{},this.constants);
+ this.sectors["active"][newId]['drawingNode'].clusterSize = 2;
+ },
+
+
+ /**
+ * This function removes the currently active sector. This is called when we create a new
+ * active sector.
+ *
+ * @param {String} sectorId | Id of the active sector that will be removed
+ * @private
+ */
+ _deleteActiveSector : function(sectorId) {
+ delete this.sectors["active"][sectorId];
+ },
+
+
+ /**
+ * This function removes the currently active sector. This is called when we reactivate
+ * the previously active sector.
+ *
+ * @param {String} sectorId | Id of the active sector that will be removed
+ * @private
+ */
+ _deleteFrozenSector : function(sectorId) {
+ delete this.sectors["frozen"][sectorId];
+ },
+
+
+ /**
+ * Freezing an active sector means moving it from the "active" object to the "frozen" object.
+ * We copy the references, then delete the active entree.
+ *
+ * @param sectorId
+ * @private
+ */
+ _freezeSector : function(sectorId) {
+ // we move the set references from the active to the frozen stack.
+ this.sectors["frozen"][sectorId] = this.sectors["active"][sectorId];
+
+ // we have moved the sector data into the frozen set, we now remove it from the active set
+ this._deleteActiveSector(sectorId);
+ },
+
+
+ /**
+ * This is the reverse operation of _freezeSector. Activating means moving the sector from the "frozen"
* object to the "active" object.
*
* @param sectorId
@@ -11106,6 +13168,9 @@ var SectorMixin = {
// finally, we update the node index list.
this._updateNodeIndexList();
+
+ // we refresh the list with calulation nodes and calculation node indices.
+ this._updateCalculationNodes();
}
}
},
@@ -11150,6 +13215,35 @@ var SectorMixin = {
},
+ /**
+ * This runs a function in all active sectors. This is used in _redraw() and the _initializeForceCalculation().
+ *
+ * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors
+ * | we dont pass the function itself because then the "this" is the window object
+ * | instead of the Graph object
+ * @param {*} [argument] | Optional: arguments to pass to the runFunction
+ * @private
+ */
+ _doInSupportSector : function(runFunction,argument) {
+ if (argument === undefined) {
+ this._switchToSupportSector();
+ this[runFunction]();
+ }
+ else {
+ this._switchToSupportSector();
+ var args = Array.prototype.splice.call(arguments, 1);
+ if (args.length > 1) {
+ this[runFunction](args[0],args[1]);
+ }
+ else {
+ this[runFunction](argument);
+ }
+ }
+ // we revert the global references back to our active sector
+ this._loadLatestSector();
+ },
+
+
/**
* This runs a function in all frozen sectors. This is used in the _redraw().
*
@@ -11188,33 +13282,6 @@ var SectorMixin = {
},
- /**
- * This runs a function in the navigation controls sector.
- *
- * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors
- * | we don't pass the function itself because then the "this" is the window object
- * | instead of the Graph object
- * @param {*} [argument] | Optional: arguments to pass to the runFunction
- * @private
- */
- _doInNavigationSector : function(runFunction,argument) {
- this._switchToNavigationSector();
- if (argument === undefined) {
- this[runFunction]();
- }
- else {
- var args = Array.prototype.splice.call(arguments, 1);
- if (args.length > 1) {
- this[runFunction](args[0],args[1]);
- }
- else {
- this[runFunction](argument);
- }
- }
- this._loadLatestSector();
- },
-
-
/**
* This runs a function in all sectors. This is used in the _redraw().
*
@@ -11240,7 +13307,6 @@ var SectorMixin = {
this._doInAllFrozenSectors(runFunction,argument);
}
}
-
},
@@ -11312,23 +13378,24 @@ var SectorMixin = {
*/
var ClusterMixin = {
-/**
- * This is only called in the constructor of the graph object
- * */
+ /**
+ * This is only called in the constructor of the graph object
+ *
+ */
startWithClustering : function() {
- // cluster if the data set is big
- this.clusterToFit(this.constants.clustering.initialMaxNodes, true);
+ // cluster if the data set is big
+ this.clusterToFit(this.constants.clustering.initialMaxNodes, true);
- // updates the lables after clustering
- this.updateLabels();
+ // updates the lables after clustering
+ this.updateLabels();
- // this is called here because if clusterin is disabled, the start and stabilize are called in
- // the setData function.
- if (this.stabilize) {
- this._doStabilize();
- }
- this.start();
- },
+ // this is called here because if clusterin is disabled, the start and stabilize are called in
+ // the setData function.
+ if (this.stabilize) {
+ this._doStabilize();
+ }
+ this.start();
+ },
/**
* This function clusters until the initialMaxNodes has been reached
@@ -11345,20 +13412,23 @@ var ClusterMixin = {
// we first cluster the hubs, then we pull in the outliers, repeat
while (numberOfNodes > maxNumberOfNodes && level < maxLevels) {
if (level % 3 == 0) {
- this.forceAggregateHubs();
+ this.forceAggregateHubs(true);
+ this.normalizeClusterLevels();
}
else {
- this.increaseClusterLevel();
+ this.increaseClusterLevel(); // this also includes a cluster normalization
}
+
numberOfNodes = this.nodeIndices.length;
level += 1;
}
// after the clustering we reposition the nodes to reduce the initial chaos
- if (level > 1 && reposition == true) {
+ if (level > 0 && reposition == true) {
this.repositionNodes();
}
- },
+ this._updateCalculationNodes();
+ },
/**
* This function can be called to open up a specific cluster. It is only called by
@@ -11370,12 +13440,16 @@ var ClusterMixin = {
var isMovingBeforeClustering = this.moving;
if (node.clusterSize > this.constants.clustering.sectorThreshold && this._nodeInActiveArea(node) &&
!(this._sector() == "default" && this.nodeIndices.length == 1)) {
+ // this loads a new sector, loads the nodes and edges and nodeIndices of it.
this._addSector(node);
var level = 0;
+
+ // we decluster until we reach a decent number of nodes
while ((this.nodeIndices.length < this.constants.clustering.initialMaxNodes) && (level < 10)) {
this.decreaseClusterLevel();
level += 1;
}
+
}
else {
this._expandClusterNode(node,false,true);
@@ -11383,6 +13457,7 @@ var ClusterMixin = {
// update the index list, dynamic edges and labels
this._updateNodeIndexList();
this._updateDynamicEdges();
+ this._updateCalculationNodes();
this.updateLabels();
}
@@ -11390,7 +13465,8 @@ var ClusterMixin = {
if (this.moving != isMovingBeforeClustering) {
this.start();
}
- },
+ },
+
/**
* This calls the updateClustes with default arguments
@@ -11401,6 +13477,7 @@ var ClusterMixin = {
}
},
+
/**
* This function can be called to increase the cluster level. This means that the nodes with only one edge connection will
* be clustered with their connected node. This can be repeated as many times as needed.
@@ -11408,8 +13485,7 @@ var ClusterMixin = {
*/
increaseClusterLevel : function() {
this.updateClusters(-1,false,true);
- },
-
+ },
/**
@@ -11419,7 +13495,7 @@ var ClusterMixin = {
*/
decreaseClusterLevel : function() {
this.updateClusters(1,false,true);
- },
+ },
/**
@@ -11433,7 +13509,7 @@ var ClusterMixin = {
* @param {Boolean} force | enabled or disable forcing
*
*/
- updateClusters : function(zoomDirection,recursive,force) {
+ updateClusters : function(zoomDirection,recursive,force,doNotStart) {
var isMovingBeforeClustering = this.moving;
var amountOfNodes = this.nodeIndices.length;
@@ -11482,13 +13558,19 @@ var ClusterMixin = {
// if a cluster was formed, we increase the clusterSession
if (this.nodeIndices.length < amountOfNodes) { // this means a clustering operation has taken place
this.clusterSession += 1;
+ // if clusters have been made, we normalize the cluster level
+ this.normalizeClusterLevels();
}
- // if the simulation was settled, we restart the simulation if a cluster has been formed or expanded
- if (this.moving != isMovingBeforeClustering) {
- this.start();
+ if (doNotStart == false || doNotStart === undefined) {
+ // if the simulation was settled, we restart the simulation if a cluster has been formed or expanded
+ if (this.moving != isMovingBeforeClustering) {
+ this.start();
+ }
}
- },
+
+ this._updateCalculationNodes();
+ },
/**
* This function handles the chains. It is called on every updateClusters().
@@ -11500,7 +13582,7 @@ var ClusterMixin = {
this._reduceAmountOfChains(1 - this.constants.clustering.chainThreshold / chainPercentage)
}
- },
+ },
/**
* this functions starts clustering by hubs
@@ -11511,14 +13593,14 @@ var ClusterMixin = {
_aggregateHubs : function(force) {
this._getHubSize();
this._formClustersByHub(force,false);
- },
+ },
/**
* This function is fired by keypress. It forces hubs to form.
*
*/
- forceAggregateHubs : function() {
+ forceAggregateHubs : function(doNotStart) {
var isMovingBeforeClustering = this.moving;
var amountOfNodes = this.nodeIndices.length;
@@ -11534,11 +13616,13 @@ var ClusterMixin = {
this.clusterSession += 1;
}
- // if the simulation was settled, we restart the simulation if a cluster has been formed or expanded
- if (this.moving != isMovingBeforeClustering) {
- this.start();
+ if (doNotStart == false || doNotStart === undefined) {
+ // if the simulation was settled, we restart the simulation if a cluster has been formed or expanded
+ if (this.moving != isMovingBeforeClustering) {
+ this.start();
+ }
}
- },
+ },
/**
* If a cluster takes up more than a set percentage of the screen, open the cluster
@@ -11557,7 +13641,7 @@ var ClusterMixin = {
}
}
}
- },
+ },
/**
@@ -11570,8 +13654,9 @@ var ClusterMixin = {
for (var i = 0; i < this.nodeIndices.length; i++) {
var node = this.nodes[this.nodeIndices[i]];
this._expandClusterNode(node,recursive,force);
+ this._updateCalculationNodes();
}
- },
+ },
/**
* This function checks if a node has to be opened. This is done by checking the zoom level.
@@ -11617,7 +13702,7 @@ var ClusterMixin = {
}
}
}
- },
+ },
/**
* ONLY CALLED FROM _expandClusterNode
@@ -11634,11 +13719,14 @@ var ClusterMixin = {
* @param {Boolean} openAll | This will recursively force all nodes in the parent to be released
* @private
*/
- _expelChildFromParent : function(parentNode, containedNodeId, recursive, force, openAll) {
+ _expelChildFromParent : function(parentNode, containedNodeId, recursive, force, openAll) {
var childNode = parentNode.containedNodes[containedNodeId];
// if child node has been added on smaller scale than current, kick out
if (childNode.formationScale < this.scale || force == true) {
+ // unselect all selected items
+ this._unselectAll();
+
// put the child node back in the global nodes object
this.nodes[containedNodeId] = childNode;
@@ -11652,14 +13740,14 @@ var ClusterMixin = {
this._validateEdges(parentNode);
// undo the changes from the clustering operation on the parent node
- parentNode.mass -= this.constants.clustering.massTransferCoefficient * childNode.mass;
- parentNode.fontSize -= this.constants.clustering.fontSizeMultiplier * childNode.clusterSize;
+ parentNode.mass -= childNode.mass;
parentNode.clusterSize -= childNode.clusterSize;
+ parentNode.fontSize = Math.min(this.constants.clustering.maxFontSize, this.constants.nodes.fontSize + this.constants.clustering.fontSizeMultiplier*parentNode.clusterSize);
parentNode.dynamicEdgesLength = parentNode.dynamicEdges.length;
// place the child node near the parent, not at the exact same location to avoid chaos in the system
- childNode.x = parentNode.x + this.constants.edges.length * 0.3 * (0.5 - Math.random()) * parentNode.clusterSize;
- childNode.y = parentNode.y + this.constants.edges.length * 0.3 * (0.5 - Math.random()) * parentNode.clusterSize;
+ childNode.x = parentNode.x + parentNode.growthIndicator * (0.5 - Math.random());
+ childNode.y = parentNode.y + parentNode.growthIndicator * (0.5 - Math.random());
// remove node from the list
delete parentNode.containedNodes[containedNodeId];
@@ -11679,21 +13767,37 @@ var ClusterMixin = {
parentNode.clusterSessions.pop();
}
+ this._repositionBezierNodes(childNode);
+// this._repositionBezierNodes(parentNode);
+
// remove the clusterSession from the child node
childNode.clusterSession = 0;
- // restart the simulation to reorganise all nodes
- this.moving = true;
-
// recalculate the size of the node on the next time the node is rendered
parentNode.clearSizeCache();
+
+ // restart the simulation to reorganise all nodes
+ this.moving = true;
}
// check if a further expansion step is possible if recursivity is enabled
if (recursive == true) {
this._expandClusterNode(childNode,recursive,force,openAll);
}
- },
+ },
+
+
+ /**
+ * position the bezier nodes at the center of the edges
+ *
+ * @param node
+ * @private
+ */
+ _repositionBezierNodes : function(node) {
+ for (var i = 0; i < node.dynamicEdges.length; i++) {
+ node.dynamicEdges[i].positionBezierNode();
+ }
+ },
/**
@@ -11712,7 +13816,8 @@ var ClusterMixin = {
else {
this._forceClustersByZoom();
}
- },
+ },
+
/**
* This function handles the clustering by zooming out, this is based on a minimum edge distance
@@ -11755,7 +13860,7 @@ var ClusterMixin = {
}
}
}
- },
+ },
/**
* This function forces the graph to cluster all nodes with only one connecting edge to their
@@ -11786,8 +13891,41 @@ var ClusterMixin = {
}
}
}
- },
+ },
+
+
+ /**
+ * To keep the nodes of roughly equal size we normalize the cluster levels.
+ * This function clusters a node to its smallest connected neighbour.
+ *
+ * @param node
+ * @private
+ */
+ _clusterToSmallestNeighbour : function(node) {
+ var smallestNeighbour = -1;
+ var smallestNeighbourNode = null;
+ for (var i = 0; i < node.dynamicEdges.length; i++) {
+ if (node.dynamicEdges[i] !== undefined) {
+ var neighbour = null;
+ if (node.dynamicEdges[i].fromId != node.id) {
+ neighbour = node.dynamicEdges[i].from;
+ }
+ else if (node.dynamicEdges[i].toId != node.id) {
+ neighbour = node.dynamicEdges[i].to;
+ }
+
+
+ if (neighbour != null && smallestNeighbour > neighbour.clusterSessions.length) {
+ smallestNeighbour = neighbour.clusterSessions.length;
+ smallestNeighbourNode = neighbour;
+ }
+ }
+ }
+ if (neighbour != null && this.nodes[neighbour.id] !== undefined) {
+ this._addToCluster(neighbour, node, true);
+ }
+ },
/**
@@ -11805,7 +13943,7 @@ var ClusterMixin = {
this._formClusterFromHub(this.nodes[nodeId],force,onlyEqual);
}
}
- },
+ },
/**
* This function forms a cluster from a specific preselected hub node
@@ -11875,7 +14013,7 @@ var ClusterMixin = {
}
}
}
- },
+ },
@@ -11914,9 +14052,9 @@ var ClusterMixin = {
// update the properties of the child and parent
var massBefore = parentNode.mass;
childNode.clusterSession = this.clusterSession;
- parentNode.mass += this.constants.clustering.massTransferCoefficient * childNode.mass;
+ parentNode.mass += childNode.mass;
parentNode.clusterSize += childNode.clusterSize;
- parentNode.fontSize += this.constants.clustering.fontSizeMultiplier * childNode.clusterSize;
+ parentNode.fontSize = Math.min(this.constants.clustering.maxFontSize, this.constants.nodes.fontSize + this.constants.clustering.fontSizeMultiplier*parentNode.clusterSize);
// keep track of the clustersessions so we can open the cluster up as it has been formed.
if (parentNode.clusterSessions[parentNode.clusterSessions.length - 1] != this.clusterSession) {
@@ -11946,12 +14084,12 @@ var ClusterMixin = {
// restart the simulation to reorganise all nodes
this.moving = true;
- },
+ },
/**
* This function will apply the changes made to the remainingEdges during the formation of the clusters.
- * This is a seperate function to allow for level-wise collapsing of the node tree.
+ * This is a seperate function to allow for level-wise collapsing of the node barnesHutTree.
* It has to be called if a level is collapsed. It is called by _formClusters().
* @private
*/
@@ -11976,7 +14114,7 @@ var ClusterMixin = {
}
node.dynamicEdgesLength -= correction;
}
- },
+ },
/**
@@ -12005,7 +14143,7 @@ var ClusterMixin = {
break;
}
}
- },
+ },
/**
* This function connects an edge that was connected to a child node to the parent node.
@@ -12036,9 +14174,17 @@ var ClusterMixin = {
this._addToReroutedEdges(parentNode,childNode,edge);
}
- },
+ },
+ /**
+ * If a node is connected to itself, a circular edge is drawn. When clustering we want to contain
+ * these edges inside of the cluster.
+ *
+ * @param parentNode
+ * @param childNode
+ * @private
+ */
_containCircularEdgesFromNode : function(parentNode, childNode) {
// manage all the edges connected to the child and parent nodes
for (var i = 0; i < parentNode.dynamicEdges.length; i++) {
@@ -12109,7 +14255,7 @@ var ClusterMixin = {
// remove the entry from the rerouted edges
delete parentNode.reroutedEdges[childNode.id];
}
- },
+ },
/**
@@ -12127,7 +14273,7 @@ var ClusterMixin = {
parentNode.dynamicEdges.splice(i,1);
}
}
- },
+ },
/**
@@ -12152,7 +14298,7 @@ var ClusterMixin = {
// remove the entry from the contained edges
delete parentNode.containedEdges[childNode.id];
- },
+ },
@@ -12190,15 +14336,57 @@ var ClusterMixin = {
}
}
- /* Debug Override */
- // for (nodeId in this.nodes) {
- // if (this.nodes.hasOwnProperty(nodeId)) {
- // node = this.nodes[nodeId];
- // node.label = String(Math.round(node.width)).concat(":",Math.round(node.width*this.scale));
- // }
- // }
+// /* Debug Override */
+// for (nodeId in this.nodes) {
+// if (this.nodes.hasOwnProperty(nodeId)) {
+// node = this.nodes[nodeId];
+// node.label = String(node.level);
+// }
+// }
+
+ },
+
+
+ /**
+ * We want to keep the cluster level distribution rather small. This means we do not want unclustered nodes
+ * if the rest of the nodes are already a few cluster levels in.
+ * To fix this we use this function. It determines the min and max cluster level and sends nodes that have not
+ * clustered enough to the clusterToSmallestNeighbours function.
+ */
+ normalizeClusterLevels : function() {
+ var maxLevel = 0;
+ var minLevel = 1e9;
+ var clusterLevel = 0;
+
+ // we loop over all nodes in the list
+ for (var nodeId in this.nodes) {
+ if (this.nodes.hasOwnProperty(nodeId)) {
+ clusterLevel = this.nodes[nodeId].clusterSessions.length;
+ if (maxLevel < clusterLevel) {maxLevel = clusterLevel;}
+ if (minLevel > clusterLevel) {minLevel = clusterLevel;}
+ }
+ }
+
+ if (maxLevel - minLevel > this.constants.clustering.clusterLevelDifference) {
+ var amountOfNodes = this.nodeIndices.length;
+ var targetLevel = maxLevel - this.constants.clustering.clusterLevelDifference;
+ // we loop over all nodes in the list
+ for (var nodeId in this.nodes) {
+ if (this.nodes.hasOwnProperty(nodeId)) {
+ if (this.nodes[nodeId].clusterSessions.length < targetLevel) {
+ this._clusterToSmallestNeighbour(this.nodes[nodeId]);
+ }
+ }
+ }
+ this._updateNodeIndexList();
+ this._updateDynamicEdges();
+ // if a cluster was formed, we increase the clusterSession
+ if (this.nodeIndices.length != amountOfNodes) {
+ this.clusterSession += 1;
+ }
+ }
+ },
- },
/**
@@ -12215,7 +14403,7 @@ var ClusterMixin = {
&&
Math.abs(node.y - this.areaCenter.y) <= this.constants.clustering.activeAreaBoxSize/this.scale
)
- },
+ },
/**
@@ -12226,17 +14414,15 @@ var ClusterMixin = {
repositionNodes : function() {
for (var i = 0; i < this.nodeIndices.length; i++) {
var node = this.nodes[this.nodeIndices[i]];
- if (!node.isFixed()) {
- var radius = this.constants.edges.length * (1 + 0.6*node.clusterSize);
+ if ((node.xFixed == false || node.yFixed == false) && this.createNodeOnClick != true) {
+ var radius = this.constants.physics.springLength * Math.min(100,node.mass);
var angle = 2 * Math.PI * Math.random();
- node.x = radius * Math.cos(angle);
- node.y = radius * Math.sin(angle);
+ if (node.xFixed == false) {node.x = radius * Math.cos(angle);}
+ if (node.yFixed == false) {node.y = radius * Math.sin(angle);}
+ this._repositionBezierNodes(node);
}
}
- },
-
-
-
+ },
/**
@@ -12252,6 +14438,7 @@ var ClusterMixin = {
var largestHub = 0;
for (var i = 0; i < this.nodeIndices.length; i++) {
+
var node = this.nodes[this.nodeIndices[i]];
if (node.dynamicEdgesLength > largestHub) {
largestHub = node.dynamicEdgesLength;
@@ -12276,7 +14463,7 @@ var ClusterMixin = {
// console.log("average",average,"averageSQ",averageSquared,"var",variance,"std",standardDeviation);
// console.log("hubThreshold:",this.hubThreshold);
- },
+ },
/**
@@ -12299,7 +14486,7 @@ var ClusterMixin = {
}
}
}
- },
+ },
/**
* We get the amount of "extension nodes" or chains. These are not quickly clustered with the outliers and hubs methods
@@ -12319,7 +14506,8 @@ var ClusterMixin = {
}
}
return chains/total;
- }
+ }
+
};
@@ -12356,18 +14544,6 @@ var SelectionMixin = {
},
- /**
- * retrieve all nodes in the navigation controls overlapping with given object
- * @param {Object} object An object with parameters left, top, right, bottom
- * @return {Number[]} An array with id's of the overlapping nodes
- * @private
- */
- _getAllNavigationNodesOverlappingWith : function (object) {
- var overlappingNodes = [];
- this._doInNavigationSector("_getNodesOverlappingWith",object,overlappingNodes);
- return overlappingNodes;
- },
-
/**
* Return a position object in canvasspace from a single point in screenspace
*
@@ -12385,36 +14561,74 @@ var SelectionMixin = {
bottom: y};
},
+
/**
- * Return a position object in canvasspace from a single point in screenspace
+ * Get the top node at the a specific point (like a click)
*
- * @param pointer
- * @returns {{left: number, top: number, right: number, bottom: number}}
+ * @param {{x: Number, y: Number}} pointer
+ * @return {Node | null} node
* @private
*/
- _pointerToScreenPositionObject : function(pointer) {
- var x = pointer.x;
- var y = pointer.y;
+ _getNodeAt : function (pointer) {
+ // we first check if this is an navigation controls element
+ var positionObject = this._pointerToPositionObject(pointer);
+ var overlappingNodes = this._getAllNodesOverlappingWith(positionObject);
- return {left: x,
- top: y,
- right: x,
- bottom: y};
+ // if there are overlapping nodes, select the last one, this is the
+ // one which is drawn on top of the others
+ if (overlappingNodes.length > 0) {
+ return this.nodes[overlappingNodes[overlappingNodes.length - 1]];
+ }
+ else {
+ return null;
+ }
+ },
+
+
+ /**
+ * retrieve all edges overlapping with given object, selector is around center
+ * @param {Object} object An object with parameters left, top, right, bottom
+ * @return {Number[]} An array with id's of the overlapping nodes
+ * @private
+ */
+ _getEdgesOverlappingWith : function (object, overlappingEdges) {
+ var edges = this.edges;
+ for (var edgeId in edges) {
+ if (edges.hasOwnProperty(edgeId)) {
+ if (edges[edgeId].isOverlappingWith(object)) {
+ overlappingEdges.push(edgeId);
+ }
+ }
+ }
},
/**
- * Get the top navigation controls node at the a specific point (like a click)
+ * retrieve all nodes overlapping with given object
+ * @param {Object} object An object with parameters left, top, right, bottom
+ * @return {Number[]} An array with id's of the overlapping nodes
+ * @private
+ */
+ _getAllEdgesOverlappingWith : function (object) {
+ var overlappingEdges = [];
+ this._doInAllActiveSectors("_getEdgesOverlappingWith",object,overlappingEdges);
+ return overlappingEdges;
+ },
+
+ /**
+ * Place holder. To implement change the _getNodeAt to a _getObjectAt. Have the _getObjectAt call
+ * _getNodeAt and _getEdgesAt, then priortize the selection to user preferences.
*
- * @param {{x: Number, y: Number}} pointer
- * @return {Node | null} node
+ * @param pointer
+ * @returns {null}
* @private
*/
- _getNavigationNodeAt : function (pointer) {
- var screenPositionObject = this._pointerToScreenPositionObject(pointer);
- var overlappingNodes = this._getAllNavigationNodesOverlappingWith(screenPositionObject);
- if (overlappingNodes.length > 0) {
- return this.sectors["navigation"]["nodes"][overlappingNodes[overlappingNodes.length - 1]];
+ _getEdgeAt : function(pointer) {
+ var positionObject = this._pointerToPositionObject(pointer);
+ var overlappingEdges = this._getAllEdgesOverlappingWith(positionObject);
+
+ if (overlappingEdges.length > 0) {
+ return this.edges[overlappingEdges[overlappingEdges.length - 1]];
}
else {
return null;
@@ -12423,144 +14637,246 @@ var SelectionMixin = {
/**
- * Get the top node at the a specific point (like a click)
+ * Add object to the selection array.
*
- * @param {{x: Number, y: Number}} pointer
- * @return {Node | null} node
+ * @param obj
* @private
*/
- _getNodeAt : function (pointer) {
- // we first check if this is an navigation controls element
- var positionObject = this._pointerToPositionObject(pointer);
- overlappingNodes = this._getAllNodesOverlappingWith(positionObject);
+ _addToSelection : function(obj) {
+ this.selectionObj[obj.id] = obj;
+ },
- // if there are overlapping nodes, select the last one, this is the
- // one which is drawn on top of the others
- if (overlappingNodes.length > 0) {
- return this.nodes[overlappingNodes[overlappingNodes.length - 1]];
+
+ /**
+ * Remove a single option from selection.
+ *
+ * @param {Object} obj
+ * @private
+ */
+ _removeFromSelection : function(obj) {
+ delete this.selectionObj[obj.id];
+ },
+
+
+ /**
+ * Unselect all. The selectionObj is useful for this.
+ *
+ * @param {Boolean} [doNotTrigger] | ignore trigger
+ * @private
+ */
+ _unselectAll : function(doNotTrigger) {
+ if (doNotTrigger === undefined) {
+ doNotTrigger = false;
}
- else {
- return null;
+
+ for (var objectId in this.selectionObj) {
+ if (this.selectionObj.hasOwnProperty(objectId)) {
+ this.selectionObj[objectId].unselect();
+ }
+ }
+ this.selectionObj = {};
+
+ if (doNotTrigger == false) {
+ this.emit('select', this.getSelection());
+ }
+ },
+
+ /**
+ * Unselect all clusters. The selectionObj is useful for this.
+ *
+ * @param {Boolean} [doNotTrigger] | ignore trigger
+ * @private
+ */
+ _unselectClusters : function(doNotTrigger) {
+ if (doNotTrigger === undefined) {
+ doNotTrigger = false;
+ }
+
+ for (var objectId in this.selectionObj) {
+ if (this.selectionObj.hasOwnProperty(objectId)) {
+ if (this.selectionObj[objectId] instanceof Node) {
+ if (this.selectionObj[objectId].clusterSize > 1) {
+ this.selectionObj[objectId].unselect();
+ this._removeFromSelection(this.selectionObj[objectId]);
+ }
+ }
+ }
+ }
+
+ if (doNotTrigger == false) {
+ this.emit('select', this.getSelection());
}
},
/**
- * Place holder. To implement change the _getNodeAt to a _getObjectAt. Have the _getObjectAt call
- * _getNodeAt and _getEdgesAt, then priortize the selection to user preferences.
+ * return the number of selected nodes
*
- * @param pointer
- * @returns {null}
+ * @returns {number}
* @private
*/
- _getEdgeAt : function(pointer) {
+ _getSelectedNodeCount : function() {
+ var count = 0;
+ for (var objectId in this.selectionObj) {
+ if (this.selectionObj.hasOwnProperty(objectId)) {
+ if (this.selectionObj[objectId] instanceof Node) {
+ count += 1;
+ }
+ }
+ }
+ return count;
+ },
+
+ /**
+ * return the number of selected nodes
+ *
+ * @returns {number}
+ * @private
+ */
+ _getSelectedNode : function() {
+ for (var objectId in this.selectionObj) {
+ if (this.selectionObj.hasOwnProperty(objectId)) {
+ if (this.selectionObj[objectId] instanceof Node) {
+ return this.selectionObj[objectId];
+ }
+ }
+ }
return null;
},
/**
- * Add object to the selection array. The this.selection id array may not be needed.
+ * return the number of selected edges
+ *
+ * @returns {number}
+ * @private
+ */
+ _getSelectedEdgeCount : function() {
+ var count = 0;
+ for (var objectId in this.selectionObj) {
+ if (this.selectionObj.hasOwnProperty(objectId)) {
+ if (this.selectionObj[objectId] instanceof Edge) {
+ count += 1;
+ }
+ }
+ }
+ return count;
+ },
+
+
+ /**
+ * return the number of selected objects.
*
- * @param obj
+ * @returns {number}
* @private
*/
- _addToSelection : function(obj) {
- this.selection.push(obj.id);
- this.selectionObj[obj.id] = obj;
+ _getSelectedObjectCount : function() {
+ var count = 0;
+ for (var objectId in this.selectionObj) {
+ if (this.selectionObj.hasOwnProperty(objectId)) {
+ count += 1;
+ }
+ }
+ return count;
},
-
/**
- * Remove a single option from selection.
+ * Check if anything is selected
*
- * @param obj
+ * @returns {boolean}
* @private
*/
- _removeFromSelection : function(obj) {
- for (var i = 0; i < this.selection.length; i++) {
- if (obj.id == this.selection[i]) {
- this.selection.splice(i,1);
- break;
+ _selectionIsEmpty : function() {
+ for(var objectId in this.selectionObj) {
+ if(this.selectionObj.hasOwnProperty(objectId)) {
+ return false;
}
}
- delete this.selectionObj[obj.id];
+ return true;
},
/**
- * Unselect all. The selectionObj is useful for this.
+ * check if one of the selected nodes is a cluster.
*
- * @param {Boolean} [doNotTrigger] | ignore trigger
+ * @returns {boolean}
* @private
*/
- _unselectAll : function(doNotTrigger) {
- if (doNotTrigger === undefined) {
- doNotTrigger = false;
- }
-
- this.selection = [];
- for (var objId in this.selectionObj) {
- if (this.selectionObj.hasOwnProperty(objId)) {
- this.selectionObj[objId].unselect();
+ _clusterInSelection : function() {
+ for(var objectId in this.selectionObj) {
+ if(this.selectionObj.hasOwnProperty(objectId)) {
+ if (this.selectionObj[objectId] instanceof Node) {
+ if (this.selectionObj[objectId].clusterSize > 1) {
+ return true;
+ }
+ }
}
}
- this.selectionObj = {};
+ return false;
+ },
- if (doNotTrigger == false) {
- this._trigger('select', {
- nodes: this.getSelection()
- });
+ /**
+ * select the edges connected to the node that is being selected
+ *
+ * @param {Node} node
+ * @private
+ */
+ _selectConnectedEdges : function(node) {
+ for (var i = 0; i < node.dynamicEdges.length; i++) {
+ var edge = node.dynamicEdges[i];
+ edge.select();
+ this._addToSelection(edge);
}
},
/**
- * Check if anything is selected
+ * unselect the edges connected to the node that is being selected
*
- * @returns {boolean}
+ * @param {Node} node
* @private
*/
- _selectionIsEmpty : function() {
- if (this.selection.length == 0) {
- return true;
- }
- else {
- return false;
+ _unselectConnectedEdges : function(node) {
+ for (var i = 0; i < node.dynamicEdges.length; i++) {
+ var edge = node.dynamicEdges[i];
+ edge.unselect();
+ this._removeFromSelection(edge);
}
},
+
/**
* This is called when someone clicks on a node. either select or deselect it.
* If there is an existing selection and we don't want to append to it, clear the existing selection
*
- * @param {Node} node
+ * @param {Node || Edge} object
* @param {Boolean} append
* @param {Boolean} [doNotTrigger] | ignore trigger
* @private
*/
- _selectNode : function(node, append, doNotTrigger) {
+ _selectObject : function(object, append, doNotTrigger) {
if (doNotTrigger === undefined) {
doNotTrigger = false;
}
- if (this._selectionIsEmpty() == false && append == false) {
+ if (this._selectionIsEmpty() == false && append == false && this.forceAppendSelection == false) {
this._unselectAll(true);
}
-
- if (node.selected == false) {
- node.select();
- this._addToSelection(node);
+ if (object.selected == false) {
+ object.select();
+ this._addToSelection(object);
+ if (object instanceof Node && this.blockConnectingEdgeSelection == false) {
+ this._selectConnectedEdges(object);
+ }
}
else {
- node.unselect();
- this._removeFromSelection(node);
+ object.unselect();
+ this._removeFromSelection(object);
}
if (doNotTrigger == false) {
- this._trigger('select', {
- nodes: this.getSelection()
- });
+ this.emit('select', this.getSelection());
}
},
@@ -12574,14 +14890,7 @@ var SelectionMixin = {
* @private
*/
_handleTouch : function(pointer) {
- if (this.constants.navigation.enabled == true) {
- var node = this._getNavigationNodeAt(pointer);
- if (node != null) {
- if (this[node.triggerFunction] !== undefined) {
- this[node.triggerFunction]();
- }
- }
- }
+
},
@@ -12594,10 +14903,16 @@ var SelectionMixin = {
_handleTap : function(pointer) {
var node = this._getNodeAt(pointer);
if (node != null) {
- this._selectNode(node,false);
+ this._selectObject(node,false);
}
else {
- this._unselectAll();
+ var edge = this._getEdgeAt(pointer);
+ if (edge != null) {
+ this._selectObject(edge,false);
+ }
+ else {
+ this._unselectAll();
+ }
}
this._redraw();
},
@@ -12629,7 +14944,13 @@ var SelectionMixin = {
_handleOnHold : function(pointer) {
var node = this._getNodeAt(pointer);
if (node != null) {
- this._selectNode(node,true);
+ this._selectObject(node,true);
+ }
+ else {
+ var edge = this._getEdgeAt(pointer);
+ if (edge != null) {
+ this._selectObject(edge,true);
+ }
}
this._redraw();
},
@@ -12640,38 +14961,62 @@ var SelectionMixin = {
*
* @private
*/
- _handleOnRelease : function() {
- this.xIncrement = 0;
- this.yIncrement = 0;
- this.zoomIncrement = 0;
- this._unHighlightAll();
+ _handleOnRelease : function(pointer) {
+
},
/**
*
- * retrieve the currently selected nodes
+ * retrieve the currently selected objects
* @return {Number[] | String[]} selection An array with the ids of the
* selected nodes.
*/
getSelection : function() {
- return this.selection.concat([]);
+ var nodeIds = this.getSelectedNodes();
+ var edgeIds = this.getSelectedEdges();
+ return {nodes:nodeIds, edges:edgeIds};
},
/**
*
- * retrieve the currently selected nodes as objects
- * @return {Objects} selection An array with the ids of the
+ * retrieve the currently selected nodes
+ * @return {String} selection An array with the ids of the
* selected nodes.
*/
- getSelectionObjects : function() {
- return this.selectionObj;
+ getSelectedNodes : function() {
+ var idArray = [];
+ for(var objectId in this.selectionObj) {
+ if(this.selectionObj.hasOwnProperty(objectId)) {
+ if (this.selectionObj[objectId] instanceof Node) {
+ idArray.push(objectId);
+ }
+ }
+ }
+ return idArray
},
/**
- * // TODO: rework this function, it is from the old system
*
+ * retrieve the currently selected edges
+ * @return {Array} selection An array with the ids of the
+ * selected nodes.
+ */
+ getSelectedEdges : function() {
+ var idArray = [];
+ for(var objectId in this.selectionObj) {
+ if(this.selectionObj.hasOwnProperty(objectId)) {
+ if (this.selectionObj[objectId] instanceof Edge) {
+ idArray.push(objectId);
+ }
+ }
+ }
+ return idArray
+ },
+
+
+ /**
* select zero or more nodes
* @param {Number[] | String[]} selection An array with the ids of the
* selected nodes.
@@ -12692,187 +15037,51 @@ var SelectionMixin = {
if (!node) {
throw new RangeError('Node with id "' + id + '" not found');
}
- this._selectNode(node,true,true);
+ this._selectObject(node,true,true);
}
-
this.redraw();
},
/**
- * TODO: rework this function, it is from the old system
- *
* Validate the selection: remove ids of nodes which no longer exist
* @private
*/
_updateSelection : function () {
- var i = 0;
- while (i < this.selection.length) {
- var nodeId = this.selection[i];
- if (!this.nodes.hasOwnProperty(nodeId)) {
- this.selection.splice(i, 1);
- delete this.selectionObj[nodeId];
- }
- else {
- i++;
- }
- }
- }
-
-
- /**
- * Unselect selected nodes. If no selection array is provided, all nodes
- * are unselected
- * @param {Object[]} selection Array with selection objects, each selection
- * object has a parameter row. Optional
- * @param {Boolean} triggerSelect If true (default), the select event
- * is triggered when nodes are unselected
- * @return {Boolean} changed True if the selection is changed
- * @private
- */
- /* _unselectNodes : function(selection, triggerSelect) {
- var changed = false;
- var i, iMax, id;
-
- if (selection) {
- // remove provided selections
- for (i = 0, iMax = selection.length; i < iMax; i++) {
- id = selection[i];
- if (this.nodes.hasOwnProperty(id)) {
- this.nodes[id].unselect();
- }
- var j = 0;
- while (j < this.selection.length) {
- if (this.selection[j] == id) {
- this.selection.splice(j, 1);
- changed = true;
- }
- else {
- j++;
+ for(var objectId in this.selectionObj) {
+ if(this.selectionObj.hasOwnProperty(objectId)) {
+ if (this.selectionObj[objectId] instanceof Node) {
+ if (!this.nodes.hasOwnProperty(objectId)) {
+ delete this.selectionObj[objectId];
}
}
- }
- }
- else if (this.selection && this.selection.length) {
- // remove all selections
- for (i = 0, iMax = this.selection.length; i < iMax; i++) {
- id = this.selection[i];
- if (this.nodes.hasOwnProperty(id)) {
- this.nodes[id].unselect();
- }
- changed = true;
- }
- this.selection = [];
- }
-
- if (changed && (triggerSelect == true || triggerSelect == undefined)) {
- // fire the select event
- this._trigger('select', {
- nodes: this.getSelection()
- });
- }
-
- return changed;
- },
-*/
-/**
- * select all nodes on given location x, y
- * @param {Array} selection an array with node ids
- * @param {boolean} append If true, the new selection will be appended to the
- * current selection (except for duplicate entries)
- * @return {Boolean} changed True if the selection is changed
- * @private
- */
-/* _selectNodes : function(selection, append) {
- var changed = false;
- var i, iMax;
-
- // TODO: the selectNodes method is a little messy, rework this
-
- // check if the current selection equals the desired selection
- var selectionAlreadyThere = true;
- if (selection.length != this.selection.length) {
- selectionAlreadyThere = false;
- }
- else {
- for (i = 0, iMax = Math.min(selection.length, this.selection.length); i < iMax; i++) {
- if (selection[i] != this.selection[i]) {
- selectionAlreadyThere = false;
- break;
+ else { // assuming only edges and nodes are selected
+ if (!this.edges.hasOwnProperty(objectId)) {
+ delete this.selectionObj[objectId];
+ }
}
}
}
- if (selectionAlreadyThere) {
- return changed;
- }
-
- if (append == undefined || append == false) {
- // first deselect any selected node
- var triggerSelect = false;
- changed = this._unselectNodes(undefined, triggerSelect);
- }
-
- for (i = 0, iMax = selection.length; i < iMax; i++) {
- // add each of the new selections, but only when they are not duplicate
- var id = selection[i];
- var isDuplicate = (this.selection.indexOf(id) != -1);
- if (!isDuplicate) {
- this.nodes[id].select();
- this.selection.push(id);
- changed = true;
- }
- }
-
- if (changed) {
- // fire the select event
- this._trigger('select', {
- nodes: this.getSelection()
- });
- }
-
- return changed;
- },
- */
+ }
};
-
/**
* Created by Alex on 1/22/14.
*/
var NavigationMixin = {
- /**
- * This function moves the navigation controls if the canvas size has been changed. If the arugments
- * verticaAlignTop and horizontalAlignLeft are false, the correction will be made
- *
- * @private
- */
- _relocateNavigation : function() {
- if (this.sectors !== undefined) {
- var xOffset = this.navigationClientWidth - this.frame.canvas.clientWidth;
- var yOffset = this.navigationClientHeight - this.frame.canvas.clientHeight;
- this.navigationClientWidth = this.frame.canvas.clientWidth;
- this.navigationClientHeight = this.frame.canvas.clientHeight;
- var node = null;
-
- for (var nodeId in this.sectors["navigation"]["nodes"]) {
- if (this.sectors["navigation"]["nodes"].hasOwnProperty(nodeId)) {
- node = this.sectors["navigation"]["nodes"][nodeId];
- if (!node.horizontalAlignLeft) {
- node.x -= xOffset;
- }
- if (!node.verticalAlignTop) {
- node.y -= yOffset;
- }
- }
- }
+ _cleanNavigation : function() {
+ // clean up previosu navigation items
+ var wrapper = document.getElementById('graph-navigation_wrapper');
+ if (wrapper != null) {
+ this.containerElement.removeChild(wrapper);
}
+ document.onmouseup = null;
},
-
/**
* 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
@@ -12882,83 +15091,45 @@ var NavigationMixin = {
* @private
*/
_loadNavigationElements : function() {
- var DIR = this.constants.navigation.iconPath;
- this.navigationClientWidth = this.frame.canvas.clientWidth;
- this.navigationClientHeight = this.frame.canvas.clientHeight;
- if (this.navigationClientWidth === undefined) {
- this.navigationClientWidth = 0;
- this.navigationClientHeight = 0;
- }
- var offset = 15;
- var intermediateOffset = 7;
- var navigationNodes = [
- {id: 'navigation_up', shape: 'image', image: DIR + '/uparrow.png', triggerFunction: "_moveUp",
- verticalAlignTop: false, x: 45 + offset + intermediateOffset, y: this.navigationClientHeight - 45 - offset - intermediateOffset},
- {id: 'navigation_down', shape: 'image', image: DIR + '/downarrow.png', triggerFunction: "_moveDown",
- verticalAlignTop: false, x: 45 + offset + intermediateOffset, y: this.navigationClientHeight - 15 - offset},
- {id: 'navigation_left', shape: 'image', image: DIR + '/leftarrow.png', triggerFunction: "_moveLeft",
- verticalAlignTop: false, x: 15 + offset, y: this.navigationClientHeight - 15 - offset},
- {id: 'navigation_right', shape: 'image', image: DIR + '/rightarrow.png',triggerFunction: "_moveRight",
- verticalAlignTop: false, x: 75 + offset + 2 * intermediateOffset, y: this.navigationClientHeight - 15 - offset},
-
- {id: 'navigation_plus', shape: 'image', image: DIR + '/plus.png', triggerFunction: "_zoomIn",
- verticalAlignTop: false, horizontalAlignLeft: false,
- x: this.navigationClientWidth - 45 - offset - intermediateOffset, y: this.navigationClientHeight - 15 - offset},
- {id: 'navigation_min', shape: 'image', image: DIR + '/minus.png', triggerFunction: "_zoomOut",
- verticalAlignTop: false, horizontalAlignLeft: false,
- x: this.navigationClientWidth - 15 - offset, y: this.navigationClientHeight - 15 - offset},
- {id: 'navigation_zoomExtends', shape: 'image', image: DIR + '/zoomExtends.png', triggerFunction: "zoomToFit",
- verticalAlignTop: false, horizontalAlignLeft: false,
- x: this.navigationClientWidth - 15 - offset, y: this.navigationClientHeight - 45 - offset - intermediateOffset}
- ];
-
- var nodeObj = null;
- for (var i = 0; i < navigationNodes.length; i++) {
- nodeObj = this.sectors["navigation"]['nodes'];
- nodeObj[navigationNodes[i]['id']] = new Node(navigationNodes[i], this.images, this.groups, this.constants);
- }
- },
+ this._cleanNavigation();
+ this.navigationDivs = {};
+ var navigationDivs = ['up','down','left','right','zoomIn','zoomOut','zoomExtends'];
+ var navigationDivActions = ['_moveUp','_moveDown','_moveLeft','_moveRight','_zoomIn','_zoomOut','zoomToFit'];
- /**
- * By setting the clustersize to be larger than 1, we use the clustering drawing method
- * to illustrate the buttons are presed. We call this highlighting.
- *
- * @param {String} elementId
- * @private
- */
- _highlightNavigationElement : function(elementId) {
- if (this.sectors["navigation"]["nodes"].hasOwnProperty(elementId)) {
- this.sectors["navigation"]["nodes"][elementId].clusterSize = 2;
+ this.navigationDivs['wrapper'] = document.createElement('div');
+ this.navigationDivs['wrapper'].id = "graph-navigation_wrapper";
+ this.containerElement.insertBefore(this.navigationDivs['wrapper'],this.frame);
+
+ for (var i = 0; i < navigationDivs.length; i++) {
+ this.navigationDivs[navigationDivs[i]] = document.createElement('div');
+ this.navigationDivs[navigationDivs[i]].id = "graph-navigation_" + navigationDivs[i];
+ this.navigationDivs[navigationDivs[i]].className = "graph-navigation " + navigationDivs[i];
+ this.navigationDivs['wrapper'].appendChild(this.navigationDivs[navigationDivs[i]]);
+ this.navigationDivs[navigationDivs[i]].onmousedown = this[navigationDivActions[i]].bind(this);
}
- },
+ document.onmouseup = this._stopMovement.bind(this);
+ },
/**
- * Reverting back to a normal button
+ * this stops all movement induced by the navigation buttons
*
- * @param {String} elementId
* @private
*/
- _unHighlightNavigationElement : function(elementId) {
- if (this.sectors["navigation"]["nodes"].hasOwnProperty(elementId)) {
- this.sectors["navigation"]["nodes"][elementId].clusterSize = 1;
- }
+ _stopMovement : function() {
+ this._xStopMoving();
+ this._yStopMoving();
+ this._stopZoom();
},
+
/**
- * un-highlight (for lack of a better term) all navigation controls elements
+ * stops the actions performed by page up and down etc.
+ *
+ * @param event
* @private
*/
- _unHighlightAll : function() {
- for (var nodeId in this.sectors['navigation']['nodes']) {
- if (this.sectors['navigation']['nodes'].hasOwnProperty(nodeId)) {
- this._unHighlightNavigationElement(nodeId);
- }
- }
- },
-
-
_preventDefault : function(event) {
if (event !== undefined) {
if (event.preventDefault) {
@@ -12979,7 +15150,7 @@ var NavigationMixin = {
* @private
*/
_moveUp : function(event) {
- this._highlightNavigationElement("navigation_up");
+ console.log("here")
this.yIncrement = this.constants.keyboard.speed.y;
this.start(); // if there is no node movement, the calculation wont be done
this._preventDefault(event);
@@ -12991,7 +15162,6 @@ var NavigationMixin = {
* @private
*/
_moveDown : function(event) {
- this._highlightNavigationElement("navigation_down");
this.yIncrement = -this.constants.keyboard.speed.y;
this.start(); // if there is no node movement, the calculation wont be done
this._preventDefault(event);
@@ -13003,7 +15173,6 @@ var NavigationMixin = {
* @private
*/
_moveLeft : function(event) {
- this._highlightNavigationElement("navigation_left");
this.xIncrement = this.constants.keyboard.speed.x;
this.start(); // if there is no node movement, the calculation wont be done
this._preventDefault(event);
@@ -13015,7 +15184,6 @@ var NavigationMixin = {
* @private
*/
_moveRight : function(event) {
- this._highlightNavigationElement("navigation_right");
this.xIncrement = -this.constants.keyboard.speed.y;
this.start(); // if there is no node movement, the calculation wont be done
this._preventDefault(event);
@@ -13027,7 +15195,6 @@ var NavigationMixin = {
* @private
*/
_zoomIn : function(event) {
- this._highlightNavigationElement("navigation_plus");
this.zoomIncrement = this.constants.keyboard.speed.zoom;
this.start(); // if there is no node movement, the calculation wont be done
this._preventDefault(event);
@@ -13039,7 +15206,6 @@ var NavigationMixin = {
* @private
*/
_zoomOut : function() {
- this._highlightNavigationElement("navigation_min");
this.zoomIncrement = -this.constants.keyboard.speed.zoom;
this.start(); // if there is no node movement, the calculation wont be done
this._preventDefault(event);
@@ -13047,42 +15213,274 @@ var NavigationMixin = {
/**
- * Stop zooming and unhighlight the zoom controls
+ * Stop zooming and unhighlight the zoom controls
+ * @private
+ */
+ _stopZoom : function() {
+ this.zoomIncrement = 0;
+ },
+
+
+ /**
+ * Stop moving in the Y direction and unHighlight the up and down
+ * @private
+ */
+ _yStopMoving : function() {
+ this.yIncrement = 0;
+ },
+
+
+ /**
+ * Stop moving in the X direction and unHighlight left and right.
+ * @private
+ */
+ _xStopMoving : function() {
+ this.xIncrement = 0;
+ }
+
+
+};
+
+/**
+ * Created by Alex on 2/10/14.
+ */
+
+
+var graphMixinLoaders = {
+
+ /**
+ * Load a mixin into the graph object
+ *
+ * @param {Object} sourceVariable | this object has to contain functions.
+ * @private
+ */
+ _loadMixin : function(sourceVariable) {
+ for (var mixinFunction in sourceVariable) {
+ if (sourceVariable.hasOwnProperty(mixinFunction)) {
+ Graph.prototype[mixinFunction] = sourceVariable[mixinFunction];
+ }
+ }
+ },
+
+
+ /**
+ * removes a mixin from the graph object.
+ *
+ * @param {Object} sourceVariable | this object has to contain functions.
+ * @private
+ */
+ _clearMixin : function(sourceVariable) {
+ for (var mixinFunction in sourceVariable) {
+ if (sourceVariable.hasOwnProperty(mixinFunction)) {
+ Graph.prototype[mixinFunction] = undefined;
+ }
+ }
+ },
+
+
+ /**
+ * Mixin the physics system and initialize the parameters required.
+ *
+ * @private
+ */
+ _loadPhysicsSystem : function() {
+ this._loadMixin(physicsMixin);
+ this._loadSelectedForceSolver();
+ },
+
+
+ /**
+ * This loads the node force solver based on the barnes hut or repulsion algorithm
+ *
+ * @private
+ */
+ _loadSelectedForceSolver : function() {
+ // this overloads the this._calculateNodeForces
+ if (this.constants.physics.barnesHut.enabled == true) {
+ this._clearMixin(repulsionMixin);
+ this._clearMixin(hierarchalRepulsionMixin);
+
+ this.constants.physics.centralGravity = this.constants.physics.barnesHut.centralGravity;
+ this.constants.physics.springLength = this.constants.physics.barnesHut.springLength;
+ this.constants.physics.springConstant = this.constants.physics.barnesHut.springConstant;
+ this.constants.physics.damping = this.constants.physics.barnesHut.damping;
+
+ this._loadMixin(barnesHutMixin);
+ }
+ else if (this.constants.physics.hierarchicalRepulsion.enabled == true) {
+ this._clearMixin(barnesHutMixin);
+ this._clearMixin(repulsionMixin);
+
+ this.constants.physics.centralGravity = this.constants.physics.hierarchicalRepulsion.centralGravity;
+ this.constants.physics.springLength = this.constants.physics.hierarchicalRepulsion.springLength;
+ this.constants.physics.springConstant = this.constants.physics.hierarchicalRepulsion.springConstant;
+ this.constants.physics.damping = this.constants.physics.hierarchicalRepulsion.damping;
+
+ this._loadMixin(hierarchalRepulsionMixin);
+ }
+ else {
+ this._clearMixin(barnesHutMixin);
+ this._clearMixin(hierarchalRepulsionMixin);
+ this.barnesHutTree = undefined;
+
+ this.constants.physics.centralGravity = this.constants.physics.repulsion.centralGravity;
+ this.constants.physics.springLength = this.constants.physics.repulsion.springLength;
+ this.constants.physics.springConstant = this.constants.physics.repulsion.springConstant;
+ this.constants.physics.damping = this.constants.physics.repulsion.damping;
+
+ this._loadMixin(repulsionMixin);
+ }
+ },
+
+
+ /**
+ * Mixin the cluster system and initialize the parameters required.
+ *
+ * @private
+ */
+ _loadClusterSystem : function() {
+ this.clusterSession = 0;
+ this.hubThreshold = 5;
+ this._loadMixin(ClusterMixin);
+ },
+
+
+ /**
+ * Mixin the sector system and initialize the parameters required
+ *
+ * @private
+ */
+ _loadSectorSystem : function() {
+ this.sectors = { },
+ this.activeSector = ["default"];
+ this.sectors["active"] = { },
+ this.sectors["active"]["default"] = {"nodes":{},
+ "edges":{},
+ "nodeIndices":[],
+ "formationScale": 1.0,
+ "drawingNode": undefined };
+ this.sectors["frozen"] = {},
+ this.sectors["support"] = {"nodes":{},
+ "edges":{},
+ "nodeIndices":[],
+ "formationScale": 1.0,
+ "drawingNode": undefined };
+
+ this.nodeIndices = this.sectors["active"]["default"]["nodeIndices"]; // the node indices list is used to speed up the computation of the repulsion fields
+
+ this._loadMixin(SectorMixin);
+ },
+
+
+ /**
+ * Mixin the selection system and initialize the parameters required
+ *
+ * @private
+ */
+ _loadSelectionSystem : function() {
+ this.selectionObj = { };
+
+ this._loadMixin(SelectionMixin);
+ },
+
+
+ /**
+ * Mixin the navigationUI (User Interface) system and initialize the parameters required
+ *
* @private
*/
- _stopZoom : function() {
- this._unHighlightNavigationElement("navigation_plus");
- this._unHighlightNavigationElement("navigation_min");
+ _loadManipulationSystem : function() {
+ // reset global variables -- these are used by the selection of nodes and edges.
+ this.blockConnectingEdgeSelection = false;
+ this.forceAppendSelection = false
+
+ if (this.constants.dataManipulation.enabled == true) {
+ // load the manipulator HTML elements. All styling done in css.
+ if (this.manipulationDiv === undefined) {
+ this.manipulationDiv = document.createElement('div');
+ this.manipulationDiv.className = 'graph-manipulationDiv';
+ this.manipulationDiv.id = 'graph-manipulationDiv';
+ if (this.editMode == true) {
+ this.manipulationDiv.style.display = "block";
+ }
+ else {
+ this.manipulationDiv.style.display = "none";
+ }
+ this.containerElement.insertBefore(this.manipulationDiv, this.frame);
+ }
- this.zoomIncrement = 0;
- },
+ if (this.editModeDiv === undefined) {
+ this.editModeDiv = document.createElement('div');
+ this.editModeDiv.className = 'graph-manipulation-editMode';
+ this.editModeDiv.id = 'graph-manipulation-editMode';
+ if (this.editMode == true) {
+ this.editModeDiv.style.display = "none";
+ }
+ else {
+ this.editModeDiv.style.display = "block";
+ }
+ this.containerElement.insertBefore(this.editModeDiv, this.frame);
+ }
+
+ if (this.closeDiv === undefined) {
+ this.closeDiv = document.createElement('div');
+ this.closeDiv.className = 'graph-manipulation-closeDiv';
+ this.closeDiv.id = 'graph-manipulation-closeDiv';
+ this.closeDiv.style.display = this.manipulationDiv.style.display;
+ this.containerElement.insertBefore(this.closeDiv, this.frame);
+ }
+
+ // load the manipulation functions
+ this._loadMixin(manipulationMixin);
+
+ // create the manipulator toolbar
+ this._createManipulatorBar();
+ }
+ else {
+ if (this.manipulationDiv !== undefined) {
+ // removes all the bindings and overloads
+ this._createManipulatorBar();
+ // remove the manipulation divs
+ this.containerElement.removeChild(this.manipulationDiv);
+ this.containerElement.removeChild(this.editModeDiv);
+ this.containerElement.removeChild(this.closeDiv);
+
+ this.manipulationDiv = undefined;
+ this.editModeDiv = undefined;
+ this.closeDiv = undefined;
+ // remove the mixin functions
+ this._clearMixin(manipulationMixin);
+ }
+ }
+ },
/**
- * Stop moving in the Y direction and unHighlight the up and down
+ * Mixin the navigation (User Interface) system and initialize the parameters required
+ *
* @private
*/
- _yStopMoving : function() {
- this._unHighlightNavigationElement("navigation_up");
- this._unHighlightNavigationElement("navigation_down");
+ _loadNavigationControls : function() {
+ this._loadMixin(NavigationMixin);
- this.yIncrement = 0;
- },
+ // the clean function removes the button divs, this is done to remove the bindings.
+ this._cleanNavigation();
+ if (this.constants.navigation.enabled == true) {
+ this._loadNavigationElements();
+ }
+ },
/**
- * Stop moving in the X direction and unHighlight left and right.
+ * Mixin the hierarchical layout system.
+ *
* @private
*/
- _xStopMoving : function() {
- this._unHighlightNavigationElement("navigation_left");
- this._unHighlightNavigationElement("navigation_right");
-
- this.xIncrement = 0;
+ _loadHierarchySystem : function() {
+ this._loadMixin(HierarchicalLayoutMixin);
}
-
-};
+}
/**
* @constructor Graph
@@ -13096,17 +15494,25 @@ var NavigationMixin = {
* @param {Object} options Options
*/
function Graph (container, data, options) {
+
+ this._initializeMixinLoaders();
+
// create variables and set default values
this.containerElement = container;
this.width = '100%';
this.height = '100%';
- // to give everything a nice fluidity, we seperate the rendering and calculating of the forces
- this.renderRefreshRate = 60; // hz (fps)
+
+ // render and calculation settings
+ this.renderRefreshRate = 60; // hz (fps)
this.renderTimestep = 1000 / this.renderRefreshRate; // ms -- saves calculation later on
- this.stabilize = true; // stabilize before displaying the graph
+ this.renderTime = 0.5 * this.renderTimestep; // measured time it takes to render a frame
+ this.maxRenderSteps = 3; // max amount of physics ticks per render step.
+
+ this.stabilize = true; // stabilize before displaying the graph
this.selectable = true;
- this.forceFactor = 50000;
+ // these functions are triggered when the dataset is edited
+ this.triggerFunctions = {add:null,edit:null,connect:null,delete:null};
// set constant values
this.constants = {
@@ -13114,15 +15520,15 @@ function Graph (container, data, options) {
radiusMin: 5,
radiusMax: 20,
radius: 5,
- distance: 100, // px
shape: 'ellipse',
image: undefined,
widthMin: 16, // px
widthMax: 64, // px
+ fixed: false,
fontColor: 'black',
fontSize: 14, // px
- //fontFace: verdana,
- fontFace: 'arial',
+ fontFace: 'verdana',
+ level: -1,
color: {
border: '#2B7CE9',
background: '#97C2FC',
@@ -13141,18 +15547,46 @@ function Graph (container, data, options) {
widthMax: 15,
width: 1,
style: 'line',
- color: '#343434',
+ color: '#848484',
fontColor: '#343434',
fontSize: 14, // px
fontFace: 'arial',
- //distance: 100, //px
- length: 100, // px
dash: {
length: 10,
gap: 5,
altLength: undefined
}
},
+ physics: {
+ barnesHut: {
+ enabled: true,
+ theta: 1 / 0.6, // inverted to save time during calculation
+ gravitationalConstant: -2000,
+ centralGravity: 0.3,
+ springLength: 100,
+ springConstant: 0.05,
+ damping: 0.09
+ },
+ repulsion: {
+ centralGravity: 0.1,
+ springLength: 200,
+ springConstant: 0.05,
+ nodeDistance: 100,
+ damping: 0.09
+ },
+ hierarchicalRepulsion: {
+ enabled: false,
+ centralGravity: 0.0,
+ springLength: 100,
+ springConstant: 0.01,
+ nodeDistance: 60,
+ damping: 0.09
+ },
+ damping: null,
+ centralGravity: null,
+ springLength: null,
+ springConstant: null
+ },
clustering: { // Per Node in Cluster = PNiC
enabled: false, // (Boolean) | global on/off switch for clustering.
initialMaxNodes: 100, // (# nodes) | if the initial amount of nodes is larger than this, we cluster until the total number is less than this threshold.
@@ -13160,118 +15594,142 @@ function Graph (container, data, options) {
reduceToNodes:300, // (# nodes) | during calculate forces, we check if the total number of nodes is larger than clusterThreshold. If it is, cluster until reduced to this
chainThreshold: 0.4, // (% of all drawn nodes)| maximum percentage of allowed chainnodes (long strings of connected nodes) within all nodes. (lower means less chains).
clusterEdgeThreshold: 20, // (px) | edge length threshold. if smaller, this node is clustered.
- sectorThreshold: 50, // (# nodes in cluster) | cluster size threshold. If larger, expanding in own sector.
+ sectorThreshold: 100, // (# nodes in cluster) | cluster size threshold. If larger, expanding in own sector.
screenSizeThreshold: 0.2, // (% of canvas) | relative size threshold. If the width or height of a clusternode takes up this much of the screen, decluster node.
fontSizeMultiplier: 4.0, // (px PNiC) | how much the cluster font size grows per node in cluster (in px).
- forceAmplification: 0.6, // (multiplier PNiC) | factor of increase fo the repulsion force of a cluster (per node in cluster).
- distanceAmplification: 0.2, // (multiplier PNiC) | factor how much the repulsion distance of a cluster increases (per node in cluster).
- edgeGrowth: 11, // (px PNiC) | amount of clusterSize connected to the edge is multiplied with this and added to edgeLength.
- nodeScaling: {width: 10, // (px PNiC) | growth of the width per node in cluster.
- height: 10, // (px PNiC) | growth of the height per node in cluster.
- radius: 10}, // (px PNiC) | growth of the radius per node in cluster.
- activeAreaBoxSize: 100, // (px) | box area around the curser where clusters are popped open.
- massTransferCoefficient: 1 // (multiplier) | parent.mass += massTransferCoefficient * child.mass
+ maxFontSize: 1000,
+ forceAmplification: 0.1, // (multiplier PNiC) | factor of increase fo the repulsion force of a cluster (per node in cluster).
+ distanceAmplification: 0.1, // (multiplier PNiC) | factor how much the repulsion distance of a cluster increases (per node in cluster).
+ edgeGrowth: 20, // (px PNiC) | amount of clusterSize connected to the edge is multiplied with this and added to edgeLength.
+ nodeScaling: {width: 1, // (px PNiC) | growth of the width per node in cluster.
+ height: 1, // (px PNiC) | growth of the height per node in cluster.
+ radius: 1}, // (px PNiC) | growth of the radius per node in cluster.
+ maxNodeSizeIncrements: 600, // (# increments) | max growth of the width per node in cluster.
+ activeAreaBoxSize: 80, // (px) | box area around the curser where clusters are popped open.
+ clusterLevelDifference: 2
},
navigation: {
- enabled: false,
- iconPath: this._getScriptPath() + '/img'
+ enabled: false
},
keyboard: {
enabled: false,
speed: {x: 10, y: 10, zoom: 0.02}
},
- minVelocity: 2, // px/s
+ dataManipulation: {
+ enabled: false,
+ initiallyVisible: false
+ },
+ hierarchicalLayout: {
+ enabled:false,
+ levelSeparation: 150,
+ nodeSpacing: 100
+ },
+ smoothCurves: true,
+ maxVelocity: 10,
+ minVelocity: 0.1, // px/s
maxIterations: 1000 // maximum number of iteration to stabilize
};
+ this.editMode = this.constants.dataManipulation.initiallyVisible;
// Node variables
+ var graph = this;
this.groups = new Groups(); // object with groups
this.images = new Images(); // object with images
this.images.setOnloadCallback(function () {
graph._redraw();
});
- // navigation variables
+ // keyboard navigation variables
this.xIncrement = 0;
this.yIncrement = 0;
this.zoomIncrement = 0;
+ // loading all the mixins:
+ // load the force calculation functions, grouped under the physics system.
+ this._loadPhysicsSystem();
// create a frame and canvas
this._create();
-
// load the sector system. (mandatory, fully integrated with Graph)
this._loadSectorSystem();
-
- // apply options
- this.setOptions(options);
-
// load the cluster system. (mandatory, even when not using the cluster system, there are function calls to it)
this._loadClusterSystem();
-
// load the selection system. (mandatory, required by Graph)
this._loadSelectionSystem();
+ // load the selection system. (mandatory, required by Graph)
+ this._loadHierarchySystem();
+
+ // apply options
+ this.setOptions(options);
// other vars
- var graph = this;
this.freezeSimulation = false;// freeze the simulation
+ this.cachedFunctions = {};
+ // containers for nodes and edges
+ this.calculationNodes = {};
+ this.calculationNodeIndices = [];
this.nodeIndices = []; // array with all the indices of the nodes. Used to speed up forces calculation
this.nodes = {}; // object with Node objects
this.edges = {}; // object with Edge objects
+ // position and scale variables and objects
this.canvasTopLeft = {"x": 0,"y": 0}; // coordinates of the top left of the canvas. they will be set during _redraw.
this.canvasBottomRight = {"x": 0,"y": 0}; // coordinates of the bottom right of the canvas. they will be set during _redraw
-
+ this.pointerPosition = {"x": 0,"y": 0}; // coordinates of the bottom right of the canvas. they will be set during _redraw
this.areaCenter = {}; // object with x and y elements used for determining the center of the zoom action
this.scale = 1; // defining the global scale variable in the constructor
this.previousScale = this.scale; // this is used to check if the zoom operation is zooming in or out
- // TODO: create a counter to keep track on the number of nodes having values
- // TODO: create a counter to keep track on the number of nodes currently moving
- // TODO: create a counter to keep track on the number of edges having values
+ // datasets or dataviews
this.nodesData = null; // A DataSet or DataView
this.edgesData = null; // A DataSet or DataView
// create event listeners used to subscribe on the DataSets of the nodes and edges
- var me = this;
this.nodesListeners = {
'add': function (event, params) {
- me._addNodes(params.items);
- me.start();
+ graph._addNodes(params.items);
+ graph.start();
},
'update': function (event, params) {
- me._updateNodes(params.items);
- me.start();
+ graph._updateNodes(params.items);
+ graph.start();
},
'remove': function (event, params) {
- me._removeNodes(params.items);
- me.start();
+ graph._removeNodes(params.items);
+ graph.start();
}
};
this.edgesListeners = {
'add': function (event, params) {
- me._addEdges(params.items);
- me.start();
+ graph._addEdges(params.items);
+ graph.start();
},
'update': function (event, params) {
- me._updateEdges(params.items);
- me.start();
+ graph._updateEdges(params.items);
+ graph.start();
},
'remove': function (event, params) {
- me._removeEdges(params.items);
- me.start();
+ graph._removeEdges(params.items);
+ graph.start();
}
};
- // properties of the data
- this.moving = false; // True if any of the nodes have an undefined position
- this.timer = undefined;
+ // properties for the animation
+ this.moving = true;
+ this.timer = undefined; // Scheduling function. Is definded in this.start();
// load data (the disable start variable will be the same as the enabled clustering)
- this.setData(data,this.constants.clustering.enabled);
+ this.setData(data,this.constants.clustering.enabled || this.constants.hierarchicalLayout.enabled);
+
+ // hierarchical layout
+ if (this.constants.hierarchicalLayout.enabled == true) {
+ this._setupHierarchicalLayout();
+ }
+ else {
+ // zoom so all data will fit on the screen, if clustering is enabled, we do not want start to be called here.
+ this.zoomToFit(true,this.constants.clustering.enabled);
+ }
- // zoom so all data will fit on the screen
- this.zoomToFit(true);
// if clustering is disabled, the simulation will have started in the setData function
if (this.constants.clustering.enabled) {
@@ -13279,6 +15737,9 @@ function Graph (container, data, options) {
}
}
+// Extend Graph with an Emitter mixin
+Emitter(Graph.prototype);
+
/**
* Get the script path where the vis.js library is located
*
@@ -13309,12 +15770,14 @@ Graph.prototype._getScriptPath = function() {
*/
Graph.prototype._getRange = function() {
var minY = 1e9, maxY = -1e9, minX = 1e9, maxX = -1e9, node;
- for (var i = 0; i < this.nodeIndices.length; i++) {
- node = this.nodes[this.nodeIndices[i]];
- if (minX > (node.x - node.width)) {minX = node.x - node.width;}
- if (maxX < (node.x + node.width)) {maxX = node.x + node.width;}
- if (minY > (node.y - node.height)) {minY = node.y - node.height;}
- if (maxY < (node.y + node.height)) {maxY = node.y + node.height;}
+ for (var nodeId in this.nodes) {
+ if (this.nodes.hasOwnProperty(nodeId)) {
+ node = this.nodes[nodeId];
+ if (minX > (node.x - node.width)) {minX = node.x - node.width;}
+ if (maxX < (node.x + node.width)) {maxX = node.x + node.width;}
+ if (minY > (node.y - node.height)) {minY = node.y - node.height;}
+ if (maxY < (node.y + node.height)) {maxY = node.y + node.height;}
+ }
}
return {minX: minX, maxX: maxX, minY: minY, maxY: maxY};
};
@@ -13326,9 +15789,8 @@ Graph.prototype._getRange = function() {
* @private
*/
Graph.prototype._findCenter = function(range) {
- var center = {x: (0.5 * (range.maxX + range.minX)),
- y: (0.5 * (range.maxY + range.minY))};
- return center;
+ return {x: (0.5 * (range.maxX + range.minX)),
+ y: (0.5 * (range.maxY + range.minY))};
};
@@ -13354,21 +15816,33 @@ Graph.prototype._centerGraph = function(range) {
*
* @param {Boolean} [initialZoom] | zoom based on fitted formula or range, true = fitted, default = false;
*/
-Graph.prototype.zoomToFit = function(initialZoom) {
+Graph.prototype.zoomToFit = function(initialZoom, disableStart) {
if (initialZoom === undefined) {
initialZoom = false;
}
- var numberOfNodes = this.nodeIndices.length;
var range = this._getRange();
+ var zoomLevel;
if (initialZoom == true) {
- if (this.constants.clustering.enabled == true &&
+ var numberOfNodes = this.nodeIndices.length;
+ if (this.constants.smoothCurves == true) {
+ if (this.constants.clustering.enabled == true &&
numberOfNodes >= this.constants.clustering.initialMaxNodes) {
- var zoomLevel = 38.8467 / (numberOfNodes - 14.50184) + 0.0116; // this is obtained from fitting a dataset from 5 points with scale levels that looked good.
+ zoomLevel = 49.07548 / (numberOfNodes + 142.05338) + 9.1444e-04; // this is obtained from fitting a dataset from 5 points with scale levels that looked good.
+ }
+ else {
+ zoomLevel = 12.662 / (numberOfNodes + 7.4147) + 0.0964822; // this is obtained from fitting a dataset from 5 points with scale levels that looked good.
+ }
}
else {
- var zoomLevel = 42.54117319 / (numberOfNodes + 39.31966387) + 0.1944405; // this is obtained from fitting a dataset from 5 points with scale levels that looked good.
+ if (this.constants.clustering.enabled == true &&
+ numberOfNodes >= this.constants.clustering.initialMaxNodes) {
+ zoomLevel = 77.5271985 / (numberOfNodes + 187.266146) + 4.76710517e-05; // this is obtained from fitting a dataset from 5 points with scale levels that looked good.
+ }
+ else {
+ zoomLevel = 30.5062972 / (numberOfNodes + 19.93597763) + 0.08413486; // this is obtained from fitting a dataset from 5 points with scale levels that looked good.
+ }
}
}
else {
@@ -13388,7 +15862,10 @@ Graph.prototype.zoomToFit = function(initialZoom) {
this.pinch.mousewheelScale = zoomLevel;
this._setScale(zoomLevel);
this._centerGraph(range);
- this.start();
+ if (disableStart == false || disableStart === undefined) {
+ this.moving = true;
+ this.start();
+ }
};
@@ -13450,7 +15927,6 @@ Graph.prototype.setData = function(data, disableStart) {
if (this.stabilize) {
this._doStabilize();
}
- this.moving = true;
this.start();
}
};
@@ -13461,15 +15937,65 @@ Graph.prototype.setData = function(data, disableStart) {
*/
Graph.prototype.setOptions = function (options) {
if (options) {
+ var prop;
// retrieve parameter values
if (options.width !== undefined) {this.width = options.width;}
if (options.height !== undefined) {this.height = options.height;}
if (options.stabilize !== undefined) {this.stabilize = options.stabilize;}
if (options.selectable !== undefined) {this.selectable = options.selectable;}
+ if (options.smoothCurves !== undefined) {this.constants.smoothCurves = options.smoothCurves;}
+
+ if (options.onAdd) {
+ this.triggerFunctions.add = options.onAdd;
+ }
+
+ if (options.onEdit) {
+ this.triggerFunctions.edit = options.onEdit;
+ }
+
+ if (options.onConnect) {
+ this.triggerFunctions.connect = options.onConnect;
+ }
+
+ if (options.onDelete) {
+ this.triggerFunctions.delete = options.onDelete;
+ }
+
+ if (options.physics) {
+ if (options.physics.barnesHut) {
+ this.constants.physics.barnesHut.enabled = true;
+ for (prop in options.physics.barnesHut) {
+ if (options.physics.barnesHut.hasOwnProperty(prop)) {
+ this.constants.physics.barnesHut[prop] = options.physics.barnesHut[prop];
+ }
+ }
+ }
+
+ if (options.physics.repulsion) {
+ this.constants.physics.barnesHut.enabled = false;
+ for (prop in options.physics.repulsion) {
+ if (options.physics.repulsion.hasOwnProperty(prop)) {
+ this.constants.physics.repulsion[prop] = options.physics.repulsion[prop];
+ }
+ }
+ }
+ }
+
+ if (options.hierarchicalLayout) {
+ this.constants.hierarchicalLayout.enabled = true;
+ for (prop in options.hierarchicalLayout) {
+ if (options.hierarchicalLayout.hasOwnProperty(prop)) {
+ this.constants.hierarchicalLayout[prop] = options.hierarchicalLayout[prop];
+ }
+ }
+ }
+ else if (options.hierarchicalLayout !== undefined) {
+ this.constants.hierarchicalLayout.enabled = false;
+ }
if (options.clustering) {
this.constants.clustering.enabled = true;
- for (var prop in options.clustering) {
+ for (prop in options.clustering) {
if (options.clustering.hasOwnProperty(prop)) {
this.constants.clustering[prop] = options.clustering[prop];
}
@@ -13481,7 +16007,7 @@ Graph.prototype.setOptions = function (options) {
if (options.navigation) {
this.constants.navigation.enabled = true;
- for (var prop in options.navigation) {
+ for (prop in options.navigation) {
if (options.navigation.hasOwnProperty(prop)) {
this.constants.navigation[prop] = options.navigation[prop];
}
@@ -13493,7 +16019,7 @@ Graph.prototype.setOptions = function (options) {
if (options.keyboard) {
this.constants.keyboard.enabled = true;
- for (var prop in options.keyboard) {
+ for (prop in options.keyboard) {
if (options.keyboard.hasOwnProperty(prop)) {
this.constants.keyboard[prop] = options.keyboard[prop];
}
@@ -13503,6 +16029,17 @@ Graph.prototype.setOptions = function (options) {
this.constants.keyboard.enabled = false;
}
+ if (options.dataManipulation) {
+ this.constants.dataManipulation.enabled = true;
+ for (prop in options.dataManipulation) {
+ if (options.dataManipulation.hasOwnProperty(prop)) {
+ this.constants.dataManipulation[prop] = options.dataManipulation[prop];
+ }
+ }
+ }
+ else if (options.dataManipulation !== undefined) {
+ this.constants.dataManipulation.enabled = false;
+ }
// TODO: work out these options and document them
if (options.edges) {
@@ -13512,12 +16049,6 @@ Graph.prototype.setOptions = function (options) {
}
}
- if (options.edges.length !== undefined &&
- options.nodes && options.nodes.distance === undefined) {
- this.constants.edges.length = options.edges.length;
- this.constants.nodes.distance = options.edges.length * 1.25;
- }
-
if (!options.edges.fontColor) {
this.constants.edges.fontColor = options.edges.color;
}
@@ -13564,57 +16095,27 @@ Graph.prototype.setOptions = function (options) {
}
}
- this.setSize(this.width, this.height);
- this._setTranslation(this.frame.clientWidth / 2, this.frame.clientHeight / 2);
- this._setScale(1);
+ // (Re)loading the mixins that can be enabled or disabled in the options.
+ // load the force calculation functions, grouped under the physics system.
+ this._loadPhysicsSystem();
// load the navigation system.
this._loadNavigationControls();
+ // load the data manipulation system
+ this._loadManipulationSystem();
+ // configure the smooth curves
+ this._configureSmoothCurves();
+
// bind keys. If disabled, this will not do anything;
this._createKeyBinds();
+ this.setSize(this.width, this.height);
+ this._setTranslation(this.frame.clientWidth / 2, this.frame.clientHeight / 2);
+ this._setScale(1);
this._redraw();
};
-/**
- * Add event listener
- * @param {String} event Event name. Available events:
- * 'select'
- * @param {function} callback Callback function, invoked as callback(properties)
- * where properties is an optional object containing
- * event specific properties.
- */
-Graph.prototype.on = function on (event, callback) {
- var available = ['select'];
-
- if (available.indexOf(event) == -1) {
- throw new Error('Unknown event "' + event + '". Choose from ' + available.join());
- }
-
- events.addListener(this, event, callback);
-};
-
-/**
- * Remove an event listener
- * @param {String} event Event name
- * @param {function} callback Callback function
- */
-Graph.prototype.off = function off (event, callback) {
- events.removeListener(this, event, callback);
-};
-
-/**
- * fire an event
- * @param {String} event The name of an event, for example 'select'
- * @param {Object} params Optional object with event parameters
- * @private
- */
-Graph.prototype._trigger = function (event, params) {
- events.trigger(this, event, params);
-};
-
-
/**
* Create the main frame for the Graph.
* This function is executed once when a Graph object is created. The frame
@@ -13632,6 +16133,7 @@ Graph.prototype._create = function () {
this.frame.className = 'graph-frame';
this.frame.style.position = 'relative';
this.frame.style.overflow = 'hidden';
+ this.frame.style.zIndex = "1";
// create the graph canvas (HTML canvas element)
this.frame.canvas = document.createElement( 'canvas' );
@@ -13667,6 +16169,7 @@ Graph.prototype._create = function () {
// add the frame to the container element
this.containerElement.appendChild(this.frame);
+
};
@@ -13702,15 +16205,12 @@ Graph.prototype._createKeyBinds = function() {
this.mousetrap.bind("pagedown",this._zoomOut.bind(me),"keydown");
this.mousetrap.bind("pagedown",this._stopZoom.bind(me), "keyup");
}
- /*
- this.mousetrap.bind("=",this.decreaseClusterLevel.bind(me));
- this.mousetrap.bind("-",this.increaseClusterLevel.bind(me));
- this.mousetrap.bind("s",this.singleStep.bind(me));
- this.mousetrap.bind("h",this.updateClustersDefault.bind(me));
- this.mousetrap.bind("c",this._collapseSector.bind(me));
- this.mousetrap.bind("f",this.toggleFreeze.bind(me));
- */
-}
+
+ if (this.constants.dataManipulation.enabled == true) {
+ this.mousetrap.bind("escape",this._createManipulatorBar.bind(me));
+ this.mousetrap.bind("del",this._deleteSelected.bind(me));
+ }
+};
/**
* Get the pointer location from a touch location
@@ -13731,7 +16231,7 @@ Graph.prototype._getPointer = function (touch) {
* @private
*/
Graph.prototype._onTouch = function (event) {
- this.drag.pointer = this._getPointer(event.gesture.touches[0]);
+ this.drag.pointer = this._getPointer(event.gesture.center);
this.drag.pinched = false;
this.pinch.scale = this._getScale();
@@ -13743,6 +16243,17 @@ Graph.prototype._onTouch = function (event) {
* @private
*/
Graph.prototype._onDragStart = function () {
+ this._handleDragStart();
+};
+
+
+/**
+ * This function is called by _onDragStart.
+ * It is separated out because we can then overload it for the datamanipulation system.
+ *
+ * @private
+ */
+Graph.prototype._handleDragStart = function() {
var drag = this.drag;
var node = this._getNodeAt(drag.pointer);
// note: drag.pointer is set in _onTouch to get the initial touch location
@@ -13756,52 +16267,65 @@ Graph.prototype._onDragStart = function () {
drag.nodeId = node.id;
// select the clicked node if not yet selected
if (!node.isSelected()) {
- this._selectNode(node,false);
+ this._selectObject(node,false);
}
// create an array with the selected nodes and their original location and status
- var me = this;
- this.selection.forEach(function (id) {
- var node = me.nodes[id];
- if (node) {
- var s = {
- id: id,
- node: node,
-
- // store original x, y, xFixed and yFixed, make the node temporarily Fixed
- x: node.x,
- y: node.y,
- xFixed: node.xFixed,
- yFixed: node.yFixed
- };
+ for (var objectId in this.selectionObj) {
+ if (this.selectionObj.hasOwnProperty(objectId)) {
+ var object = this.selectionObj[objectId];
+ if (object instanceof Node) {
+ var s = {
+ id: object.id,
+ node: object,
+
+ // store original x, y, xFixed and yFixed, make the node temporarily Fixed
+ x: object.x,
+ y: object.y,
+ xFixed: object.xFixed,
+ yFixed: object.yFixed
+ };
- node.xFixed = true;
- node.yFixed = true;
+ object.xFixed = true;
+ object.yFixed = true;
- drag.selection.push(s);
+ drag.selection.push(s);
+ }
}
- });
+ }
}
};
+
/**
* handle drag event
* @private
*/
Graph.prototype._onDrag = function (event) {
+ this._handleOnDrag(event)
+};
+
+
+/**
+ * This function is called by _onDrag.
+ * It is separated out because we can then overload it for the datamanipulation system.
+ *
+ * @private
+ */
+Graph.prototype._handleOnDrag = function(event) {
if (this.drag.pinched) {
return;
}
- var pointer = this._getPointer(event.gesture.touches[0]);
+ var pointer = this._getPointer(event.gesture.center);
var me = this,
- drag = this.drag,
- selection = drag.selection;
+ drag = this.drag,
+ selection = drag.selection;
if (selection && selection.length) {
// calculate delta's and new location
var deltaX = pointer.x - drag.pointer.x,
- deltaY = pointer.y - drag.pointer.y;
+ deltaY = pointer.y - drag.pointer.y;
// update position of all selected nodes
selection.forEach(function (s) {
@@ -13816,7 +16340,7 @@ Graph.prototype._onDrag = function (event) {
}
});
- // start animation if not yet running
+ // start _animationStep if not yet running
if (!this.moving) {
this.moving = true;
this.start();
@@ -13828,8 +16352,8 @@ Graph.prototype._onDrag = function (event) {
var diffY = pointer.y - this.drag.pointer.y;
this._setTranslation(
- this.drag.translation.x + diffX,
- this.drag.translation.y + diffY);
+ this.drag.translation.x + diffX,
+ this.drag.translation.y + diffY);
this._redraw();
this.moved = true;
}
@@ -13856,8 +16380,10 @@ Graph.prototype._onDragEnd = function () {
* @private
*/
Graph.prototype._onTap = function (event) {
- var pointer = this._getPointer(event.gesture.touches[0]);
+ var pointer = this._getPointer(event.gesture.center);
+ this.pointerPosition = pointer;
this._handleTap(pointer);
+
};
@@ -13866,7 +16392,7 @@ Graph.prototype._onTap = function (event) {
* @private
*/
Graph.prototype._onDoubleTap = function (event) {
- var pointer = this._getPointer(event.gesture.touches[0]);
+ var pointer = this._getPointer(event.gesture.center);
this._handleDoubleTap(pointer);
};
@@ -13877,18 +16403,19 @@ Graph.prototype._onDoubleTap = function (event) {
* @private
*/
Graph.prototype._onHold = function (event) {
- var pointer = this._getPointer(event.gesture.touches[0]);
+ var pointer = this._getPointer(event.gesture.center);
+ this.pointerPosition = pointer;
this._handleOnHold(pointer);
};
/**
* handle the release of the screen
*
- * @param event
* @private
*/
Graph.prototype._onRelease = function (event) {
- this._handleOnRelease();
+ var pointer = this._getPointer(event.gesture.center);
+ this._handleOnRelease(pointer);
};
/**
@@ -13934,8 +16461,6 @@ Graph.prototype._zoom = function(scale, pointer) {
this.areaCenter = {"x" : this._canvasToX(pointer.x),
"y" : this._canvasToY(pointer.y)};
- // this.areaCenter = {"x" : pointer.x,"y" : pointer.y };
-// console.log(translation.x,translation.y,pointer.x,pointer.y,scale);
this.pinch.mousewheelScale = scale;
this._setScale(scale);
this._setTranslation(tx, ty);
@@ -13945,6 +16470,7 @@ Graph.prototype._zoom = function(scale, pointer) {
return scale;
};
+
/**
* Event handler for mouse wheel event, used to zoom the timeline
* See http://adomas.org/javascript-mouse-wheel/
@@ -13984,7 +16510,7 @@ Graph.prototype._onMouseWheel = function(event) {
var pointer = this._getPointer(gesture.center);
// apply the new scale
- scale = this._zoom(scale, pointer);
+ this._zoom(scale, pointer);
// store the new, applied scale -- this is now done in _zoom
// this.pinch.mousewheelScale = scale;
@@ -14094,6 +16620,7 @@ Graph.prototype._checkShowPopup = function (pointer) {
}
};
+
/**
* Check if the popup must be hided, which is the case when the mouse is no
* longer hovering on the object
@@ -14110,85 +16637,6 @@ Graph.prototype._checkHidePopup = function (pointer) {
};
-/**
- * Temporary method to test calculating a hub value for the nodes
- * @param {number} level Maximum number edges between two nodes in order
- * to call them connected. Optional, 1 by default
- * @return {Number[]} connectioncount array with the connection count
- * for each node
- * @private
- */
-Graph.prototype._getConnectionCount = function(level) {
- if (level == undefined) {
- level = 1;
- }
-
- // get the nodes connected to given nodes
- function getConnectedNodes(nodes) {
- var connectedNodes = [];
-
- for (var j = 0, jMax = nodes.length; j < jMax; j++) {
- var node = nodes[j];
-
- // find all nodes connected to this node
- var edges = node.edges;
- for (var i = 0, iMax = edges.length; i < iMax; i++) {
- var edge = edges[i];
- var other = null;
-
- // check if connected
- if (edge.from == node)
- other = edge.to;
- else if (edge.to == node)
- other = edge.from;
-
- // check if the other node is not already in the list with nodes
- var k, kMax;
- if (other) {
- for (k = 0, kMax = nodes.length; k < kMax; k++) {
- if (nodes[k] == other) {
- other = null;
- break;
- }
- }
- }
- if (other) {
- for (k = 0, kMax = connectedNodes.length; k < kMax; k++) {
- if (connectedNodes[k] == other) {
- other = null;
- break;
- }
- }
- }
-
- if (other)
- connectedNodes.push(other);
- }
- }
-
- return connectedNodes;
- }
-
- var connections = [];
- var nodes = this.nodes;
- for (var id in nodes) {
- if (nodes.hasOwnProperty(id)) {
- var c = [nodes[id]];
- for (var l = 0; l < level; l++) {
- c = c.concat(getConnectedNodes(c));
- }
- connections.push(c);
- }
- }
-
- var hubs = [];
- for (var i = 0, len = connections.length; i < len; i++) {
- hubs.push(connections[i].length);
- }
-
- return hubs;
-};
-
/**
* Set a new size for the graph
* @param {string} width Width in pixels or percentage (for example '800px'
@@ -14206,9 +16654,11 @@ Graph.prototype.setSize = function(width, height) {
this.frame.canvas.width = this.frame.canvas.clientWidth;
this.frame.canvas.height = this.frame.canvas.clientHeight;
- if (this.constants.navigation.enabled == true) {
- this._relocateNavigation();
+ if (this.manipulationDiv !== undefined) {
+ this.manipulationDiv.style.width = this.frame.canvas.clientWidth;
}
+
+ this.emit('frameResize', {width:this.frame.canvas.width,height:this.frame.canvas.height});
};
/**
@@ -14236,7 +16686,7 @@ Graph.prototype._setNodes = function(nodes) {
if (oldNodesData) {
// unsubscribe from old dataset
util.forEach(this.nodesListeners, function (callback, event) {
- oldNodesData.unsubscribe(event, callback);
+ oldNodesData.off(event, callback);
});
}
@@ -14247,7 +16697,7 @@ Graph.prototype._setNodes = function(nodes) {
// subscribe to new dataset
var me = this;
util.forEach(this.nodesListeners, function (callback, event) {
- me.nodesData.subscribe(event, callback);
+ me.nodesData.on(event, callback);
});
// draw all new nodes
@@ -14270,13 +16720,11 @@ Graph.prototype._addNodes = function(ids) {
var node = new Node(data, this.images, this.groups, this.constants);
this.nodes[id] = node; // note: this may replace an existing node
- if (!node.isFixed()) {
- // TODO: position new nodes in a smarter way!
- var radius = this.constants.edges.length * 2;
- var count = ids.length;
- var angle = 2 * Math.PI * (i / count);
- node.x = radius * Math.cos(angle);
- node.y = radius * Math.sin(angle);
+ if ((node.xFixed == false || node.yFixed == false) && this.createNodeOnClick != true) {
+ var radius = 10 * 0.1*ids.length;
+ var angle = 2 * Math.PI * Math.random();
+ if (node.xFixed == false) {node.x = radius * Math.cos(angle);}
+ if (node.yFixed == false) {node.y = radius * Math.sin(angle);}
// note: no not use node.isMoving() here, as that gives the current
// velocity of the node, which is zero after creation of the node.
@@ -14284,8 +16732,10 @@ Graph.prototype._addNodes = function(ids) {
}
}
this._updateNodeIndexList();
+ this._updateCalculationNodes();
this._reconnectEdges();
this._updateValueRange(this.nodes);
+ this.updateLabels();
};
/**
@@ -14362,7 +16812,7 @@ Graph.prototype._setEdges = function(edges) {
if (oldEdgesData) {
// unsubscribe from old dataset
util.forEach(this.edgesListeners, function (callback, event) {
- oldEdgesData.unsubscribe(event, callback);
+ oldEdgesData.off(event, callback);
});
}
@@ -14373,7 +16823,7 @@ Graph.prototype._setEdges = function(edges) {
// subscribe to new dataset
var me = this;
util.forEach(this.edgesListeners, function (callback, event) {
- me.edgesData.subscribe(event, callback);
+ me.edgesData.on(event, callback);
});
// draw all new nodes
@@ -14407,6 +16857,8 @@ Graph.prototype._addEdges = function (ids) {
this.moving = true;
this._updateValueRange(edges);
+ this._createBezierNodes();
+ this._updateCalculationNodes();
};
/**
@@ -14435,6 +16887,7 @@ Graph.prototype._updateEdges = function (ids) {
}
}
+ this._createBezierNodes();
this.moving = true;
this._updateValueRange(edges);
};
@@ -14450,6 +16903,9 @@ Graph.prototype._removeEdges = function (ids) {
var id = ids[i];
var edge = edges[id];
if (edge) {
+ if (edge.via != null) {
+ delete this.sectors['support']['nodes'][edge.via.id];
+ }
edge.disconnect();
delete edges[id];
}
@@ -14457,6 +16913,7 @@ Graph.prototype._removeEdges = function (ids) {
this.moving = true;
this._updateValueRange(edges);
+ this._updateCalculationNodes();
};
/**
@@ -14554,14 +17011,13 @@ Graph.prototype._redraw = function() {
this._doInAllSectors("_drawAllSectorNodes",ctx);
this._doInAllSectors("_drawEdges",ctx);
- this._doInAllSectors("_drawNodes",ctx);
+ this._doInAllSectors("_drawNodes",ctx,false);
+
+// this._doInSupportSector("_drawNodes",ctx,true);
+// this._drawTree(ctx,"#F00F0F");
// restore original scaling and translation
ctx.restore();
-
- if (this.constants.navigation.enabled == true) {
- this._doInNavigationSector("_drawNodes",ctx,true);
- }
};
/**
@@ -14695,269 +17151,42 @@ Graph.prototype._drawNodes = function(ctx,alwaysShow) {
};
/**
- * Redraw all edges
- * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d');
- * @param {CanvasRenderingContext2D} ctx
- * @private
- */
-Graph.prototype._drawEdges = function(ctx) {
- var edges = this.edges;
- for (var id in edges) {
- if (edges.hasOwnProperty(id)) {
- var edge = edges[id];
- edge.setScale(this.scale);
- if (edge.connected) {
- edges[id].draw(ctx);
- }
- }
- }
-};
-
-/**
- * Find a stable position for all nodes
- * @private
- */
-Graph.prototype._doStabilize = function() {
- //var start = new Date();
-
- // find stable position
- var count = 0;
- var vmin = this.constants.minVelocity;
- var stable = false;
- while (!stable && count < this.constants.maxIterations) {
- this._initializeForceCalculation();
- this._discreteStepNodes();
- stable = !this._isMoving(vmin);
- count++;
- }
- this.zoomToFit();
-
- // var end = new Date();
-
- // console.log('Stabilized in ' + (end-start) + ' ms, ' + count + ' iterations' ); // TODO: cleanup
-};
-
-
-/**
- * Before calculating the forces, we check if we need to cluster to keep up performance and we check
- * if there is more than one node. If it is just one node, we dont calculate anything.
- *
- * @private
- */
-Graph.prototype._initializeForceCalculation = function() {
- // stop calculation if there is only one node
- if (this.nodeIndices.length == 1) {
- this.nodes[this.nodeIndices[0]]._setForce(0,0);
- }
- else {
- // if there are too many nodes on screen, we cluster without repositioning
- if (this.nodeIndices.length > this.constants.clustering.clusterThreshold && this.constants.clustering.enabled == true) {
- this.clusterToFit(this.constants.clustering.reduceToNodes, false);
- }
-
- // we now start the force calculation
- this._calculateForces();
- }
-};
-
-
-/**
- * Calculate the external forces acting on the nodes
- * Forces are caused by: edges, repulsing forces between nodes, gravity
+ * Redraw all edges
+ * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d');
+ * @param {CanvasRenderingContext2D} ctx
* @private
*/
-Graph.prototype._calculateForces = function() {
-// var screenCenterPos = {"x":(0.5*(this.canvasTopLeft.x + this.canvasBottomRight.x)),
-// "y":(0.5*(this.canvasTopLeft.y + this.canvasBottomRight.y))}
- // create a local edge to the nodes and edges, that is faster
- var dx, dy, angle, distance, fx, fy,
- repulsingForce, springForce, length, edgeLength,
- node, node1, node2, edge, edgeId, i, j, nodeId, xCenter, yCenter;
- var clusterSize;
- var nodes = this.nodes;
+Graph.prototype._drawEdges = function(ctx) {
var edges = this.edges;
-
- // Gravity is required to keep separated groups from floating off
- // the forces are reset to zero in this loop by using _setForce instead
- // of _addForce
- var gravity = 0.08 * this.forceFactor;
- for (i = 0; i < this.nodeIndices.length; i++) {
- node = nodes[this.nodeIndices[i]];
- // gravity does not apply when we are in a pocket sector
- if (this._sector() == "default") {
- dx = -node.x;// + screenCenterPos.x;
- dy = -node.y;// + screenCenterPos.y;
-
- angle = Math.atan2(dy, dx);
- fx = Math.cos(angle) * gravity;
- fy = Math.sin(angle) * gravity;
- }
- else {
- fx = 0;
- fy = 0;
- }
- node._setForce(fx, fy);
-
- node.updateDamping(this.nodeIndices.length);
- }
-
- // repulsing forces between nodes
- var minimumDistance = this.constants.nodes.distance,
- steepness = 10; // higher value gives steeper slope of the force around the given minimumDistance
-
-
- // 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 < this.nodeIndices.length-1; i++) {
- node1 = nodes[this.nodeIndices[i]];
- for (j = i+1; j < this.nodeIndices.length; j++) {
- node2 = nodes[this.nodeIndices[j]];
- clusterSize = (node1.clusterSize + node2.clusterSize - 2);
- dx = node2.x - node1.x;
- dy = node2.y - node1.y;
- distance = Math.sqrt(dx * dx + dy * dy);
-
-
- // clusters have a larger region of influence
- minimumDistance = (clusterSize == 0) ? this.constants.nodes.distance : (this.constants.nodes.distance * (1 + clusterSize * this.constants.clustering.distanceAmplification));
- if (distance < 2*minimumDistance) { // at 2.0 * the minimum distance, the force is 0.000045
- angle = Math.atan2(dy, dx);
-
- if (distance < 0.5*minimumDistance) { // at 0.5 * the minimum distance, the force is 0.993307
- repulsingForce = 1.0;
- }
- else {
- // TODO: correct factor for repulsing force
- //repulsingForce = 2 * Math.exp(-5 * (distance * distance) / (dmin * dmin) ); // TODO: customize the repulsing force
- //repulsingForce = Math.exp(-1 * (distance * distance) / (dmin * dmin) ); // TODO: customize the repulsing force
- repulsingForce = 1 / (1 + Math.exp((distance / minimumDistance - 1) * steepness)); // TODO: customize the repulsing force
- }
- // amplify the repulsion for clusters.
- repulsingForce *= (clusterSize == 0) ? 1 : 1 + clusterSize * this.constants.clustering.forceAmplification;
- repulsingForce *= this.forceFactor;
-
-
- fx = Math.cos(angle) * repulsingForce;
- fy = Math.sin(angle) * repulsingForce ;
-
- node1._addForce(-fx, -fy);
- node2._addForce(fx, fy);
+ for (var id in edges) {
+ if (edges.hasOwnProperty(id)) {
+ var edge = edges[id];
+ edge.setScale(this.scale);
+ if (edge.connected) {
+ edges[id].draw(ctx);
}
}
}
+};
-/*
- // repulsion of the edges on the nodes and
- for (var nodeId in nodes) {
- if (nodes.hasOwnProperty(nodeId)) {
- node = nodes[nodeId];
- for(var edgeId in edges) {
- if (edges.hasOwnProperty(edgeId)) {
- edge = edges[edgeId];
-
- // get the center of the edge
- xCenter = edge.from.x+(edge.to.x - edge.from.x)/2;
- yCenter = edge.from.y+(edge.to.y - edge.from.y)/2;
-
- // calculate normally distributed force
- dx = node.x - xCenter;
- dy = node.y - yCenter;
- distance = Math.sqrt(dx * dx + dy * dy);
- if (distance < 2*minimumDistance) { // at 2.0 * the minimum distance, the force is 0.000045
- angle = Math.atan2(dy, dx);
-
- if (distance < 0.5*minimumDistance) { // at 0.5 * the minimum distance, the force is 0.993307
- repulsingForce = 1.0;
- }
- else {
- // TODO: correct factor for repulsing force
- //var repulsingforce = 2 * Math.exp(-5 * (distance * distance) / (dmin * dmin) ); // TODO: customize the repulsing force
- //repulsingforce = Math.exp(-1 * (distance * distance) / (dmin * dmin) ), // TODO: customize the repulsing force
- repulsingForce = 1 / (1 + Math.exp((distance / (minimumDistance / 2) - 1) * steepness)); // TODO: customize the repulsing force
- }
- fx = Math.cos(angle) * repulsingForce;
- fy = Math.sin(angle) * repulsingForce;
- node._addForce(fx, fy);
- edge.from._addForce(-fx/2,-fy/2);
- edge.to._addForce(-fx/2,-fy/2);
- }
- }
- }
- }
+/**
+ * Find a stable position for all nodes
+ * @private
+ */
+Graph.prototype._doStabilize = function() {
+ // find stable position
+ var count = 0;
+ while (this.moving && count < this.constants.maxIterations) {
+ this._physicsTick();
+ count++;
}
-*/
- // forces caused by the edges, modelled as springs
- for (edgeId in edges) {
- if (edges.hasOwnProperty(edgeId)) {
- edge = edges[edgeId];
- if (edge.connected) {
- // only calculate forces if nodes are in the same sector
- if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) {
- clusterSize = (edge.to.clusterSize + edge.from.clusterSize - 2);
- dx = (edge.to.x - edge.from.x);
- dy = (edge.to.y - edge.from.y);
- //edgeLength = (edge.from.width + edge.from.height + edge.to.width + edge.to.height)/2 || edge.length; // TODO: dmin
- //edgeLength = (edge.from.width + edge.to.width)/2 || edge.length; // TODO: dmin
- //edgeLength = 20 + ((edge.from.width + edge.to.width) || 0) / 2;
- edgeLength = edge.length;
- // this implies that the edges between big clusters are longer
- edgeLength += clusterSize * this.constants.clustering.edgeGrowth;
- length = Math.sqrt(dx * dx + dy * dy);
- angle = Math.atan2(dy, dx);
-
- springForce = edge.stiffness * (edgeLength - length) * this.forceFactor;
-
- fx = Math.cos(angle) * springForce;
- fy = Math.sin(angle) * springForce;
-
- edge.from._addForce(-fx, -fy);
- edge.to._addForce(fx, fy);
- }
- }
- }
- }
-/*
- // TODO: re-implement repulsion of edges
-
- // repulsing forces between edges
- var minimumDistance = this.constants.edges.distance,
- steepness = 10; // higher value gives steeper slope of the force around the given minimumDistance
- for (var l = 0; l < edges.length; l++) {
- //Keep distance from other edge centers
- for (var l2 = l + 1; l2 < this.edges.length; l2++) {
- //var dmin = (nodes[n].width + nodes[n].height + nodes[n2].width + nodes[n2].height) / 1 || minimumDistance, // TODO: dmin
- //var dmin = (nodes[n].width + nodes[n2].width)/2 || minimumDistance, // TODO: dmin
- //dmin = 40 + ((nodes[n].width/2 + nodes[n2].width/2) || 0),
- var lx = edges[l].from.x+(edges[l].to.x - edges[l].from.x)/2,
- ly = edges[l].from.y+(edges[l].to.y - edges[l].from.y)/2,
- l2x = edges[l2].from.x+(edges[l2].to.x - edges[l2].from.x)/2,
- l2y = edges[l2].from.y+(edges[l2].to.y - edges[l2].from.y)/2,
-
- // calculate normally distributed force
- dx = l2x - lx,
- dy = l2y - ly,
- distance = Math.sqrt(dx * dx + dy * dy),
- angle = Math.atan2(dy, dx),
-
-
- // TODO: correct factor for repulsing force
- //var repulsingforce = 2 * Math.exp(-5 * (distance * distance) / (dmin * dmin) ); // TODO: customize the repulsing force
- //repulsingforce = Math.exp(-1 * (distance * distance) / (dmin * dmin) ), // TODO: customize the repulsing force
- repulsingforce = 1 / (1 + Math.exp((distance / minimumDistance - 1) * steepness)), // TODO: customize the repulsing force
- fx = Math.cos(angle) * repulsingforce,
- fy = Math.sin(angle) * repulsingforce;
-
- edges[l].from._addForce(-fx, -fy);
- edges[l].to._addForce(-fx, -fy);
- edges[l2].from._addForce(fx, fy);
- edges[l2].to._addForce(fx, fy);
- }
- }
-*/
+ this.zoomToFit(false,true);
};
+
+
/**
* Check if any of the nodes is still moving
* @param {number} vmin the minimum velocity considered as 'moving'
@@ -14965,10 +17194,9 @@ Graph.prototype._calculateForces = function() {
* @private
*/
Graph.prototype._isMoving = function(vmin) {
- var vminCorrected = vmin / this.scale;
var nodes = this.nodes;
for (var id in nodes) {
- if (nodes.hasOwnProperty(id) && nodes[id].isMoving(vminCorrected)) {
+ if (nodes.hasOwnProperty(id) && nodes[id].isMoving(vmin)) {
return true;
}
}
@@ -14980,93 +17208,124 @@ Graph.prototype._isMoving = function(vmin) {
* /**
* Perform one discrete step for all nodes
*
- * @param interval
* @private
*/
Graph.prototype._discreteStepNodes = function() {
- var interval = 0.01;
+ var interval = 0.65;
var nodes = this.nodes;
- for (var id in nodes) {
- if (nodes.hasOwnProperty(id)) {
- nodes[id].discreteStep(interval);
+ var nodeId;
+
+ if (this.constants.maxVelocity > 0) {
+ for (nodeId in nodes) {
+ if (nodes.hasOwnProperty(nodeId)) {
+ nodes[nodeId].discreteStepLimited(interval, this.constants.maxVelocity);
+ }
}
}
-
- var vmin = this.constants.minVelocity;
- this.moving = this._isMoving(vmin);
+ else {
+ for (nodeId in nodes) {
+ if (nodes.hasOwnProperty(nodeId)) {
+ nodes[nodeId].discreteStep(interval);
+ }
+ }
+ }
+ var vminCorrected = this.constants.minVelocity / Math.max(this.scale,0.05);
+ if (vminCorrected > 0.5*this.constants.maxVelocity) {
+ this.moving = true;
+ }
+ else {
+ this.moving = this._isMoving(vminCorrected);
+ }
};
-
-/**
- * Start animating nodes and edges
- *
- * @poram {Boolean} runCalculationStep
- */
-Graph.prototype.start = function() {
+Graph.prototype._physicsTick = function() {
if (!this.freezeSimulation) {
-
if (this.moving) {
this._doInAllActiveSectors("_initializeForceCalculation");
+ if (this.constants.smoothCurves) {
+ this._doInSupportSector("_discreteStepNodes");
+ }
this._doInAllActiveSectors("_discreteStepNodes");
this._findCenter(this._getRange())
}
+ }
+};
- if (this.moving || this.xIncrement != 0 || this.yIncrement != 0 || this.zoomIncrement != 0) {
- // start animation. only start calculationTimer if it is not already running
- if (!this.timer) {
- var graph = this;
- this.timer = window.setTimeout(function () {
- graph.timer = undefined;
- // keyboad movement
- if (graph.xIncrement != 0 || graph.yIncrement != 0) {
- var translation = graph._getTranslation();
- graph._setTranslation(translation.x+graph.xIncrement, translation.y+graph.yIncrement);
- }
- if (graph.zoomIncrement != 0) {
- var center = {
- x: graph.frame.canvas.clientWidth / 2,
- y: graph.frame.canvas.clientHeight / 2
- };
- graph._zoom(graph.scale*(1 + graph.zoomIncrement), center);
- }
+/**
+ * This function runs one step of the animation. It calls an x amount of physics ticks and one render tick.
+ * It reschedules itself at the beginning of the function
+ *
+ * @private
+ */
+Graph.prototype._animationStep = function() {
+ // reset the timer so a new scheduled animation step can be set
+ this.timer = undefined;
+ // handle the keyboad movement
+ this._handleNavigation();
- graph.start();
- graph._redraw();
+ // this schedules a new animation step
+ this.start();
- //this.end = window.performance.now();
- //this.time = this.end - this.startTime;
- //console.log('refresh time: ' + this.time);
- //this.startTime = window.performance.now();
+ // start the physics simulation
+ var calculationTime = Date.now();
+ var maxSteps = 1;
+ this._physicsTick();
+ var timeRequired = Date.now() - calculationTime;
+ while (timeRequired < (this.renderTimestep - this.renderTime) && maxSteps < this.maxRenderSteps) {
+ this._physicsTick();
+ timeRequired = Date.now() - calculationTime;
+ maxSteps++;
- }, this.renderTimestep);
- }
- }
- else {
- this._redraw();
- }
}
-};
-
-
+ // start the rendering process
+ var renderTime = Date.now();
+ this._redraw();
+ this.renderTime = Date.now() - renderTime;
+};
-Graph.prototype.singleStep = function() {
- if (this.moving) {
- this._initializeForceCalculation();
- this._discreteStepNodes();
- var vmin = this.constants.minVelocity;
- this.moving = this._isMoving(vmin);
+/**
+ * Schedule a animation step with the refreshrate interval.
+ *
+ * @poram {Boolean} runCalculationStep
+ */
+Graph.prototype.start = function() {
+ if (this.moving || this.xIncrement != 0 || this.yIncrement != 0 || this.zoomIncrement != 0) {
+ if (!this.timer) {
+ this.timer = window.setTimeout(this._animationStep.bind(this), this.renderTimestep); // wait this.renderTimeStep milliseconds and perform the animation step function
+ }
+ }
+ else {
this._redraw();
}
};
+/**
+ * Move the graph according to the keyboard presses.
+ *
+ * @private
+ */
+Graph.prototype._handleNavigation = function() {
+ if (this.xIncrement != 0 || this.yIncrement != 0) {
+ var translation = this._getTranslation();
+ this._setTranslation(translation.x+this.xIncrement, translation.y+this.yIncrement);
+ }
+ if (this.zoomIncrement != 0) {
+ var center = {
+ x: this.frame.canvas.clientWidth / 2,
+ y: this.frame.canvas.clientHeight / 2
+ };
+ this._zoom(this.scale*(1 + this.zoomIncrement), center);
+ }
+};
+
/**
- * Freeze the animation
+ * Freeze the _animationStep
*/
Graph.prototype.toggleFreeze = function() {
if (this.freezeSimulation == false) {
@@ -15078,104 +17337,64 @@ Graph.prototype.toggleFreeze = function() {
}
};
-/**
- * Mixin the cluster system and initialize the parameters required.
- *
- * @private
- */
-Graph.prototype._loadClusterSystem = function() {
- this.clusterSession = 0;
- this.hubThreshold = 5;
- for (var mixinFunction in ClusterMixin) {
- if (ClusterMixin.hasOwnProperty(mixinFunction)) {
- Graph.prototype[mixinFunction] = ClusterMixin[mixinFunction];
- }
- }
-}
-/**
- * Mixin the sector system and initialize the parameters required
- *
- * @private
- */
-Graph.prototype._loadSectorSystem = function() {
- this.sectors = {};
- this.activeSector = ["default"];
- this.sectors["active"] = {};
- this.sectors["active"]["default"] = {"nodes":{},
- "edges":{},
- "nodeIndices":[],
- "formationScale": 1.0,
- "drawingNode": undefined};
- this.sectors["frozen"] = {};
- this.sectors["navigation"] = {"nodes":{},
- "edges":{},
- "nodeIndices":[],
- "formationScale": 1.0,
- "drawingNode": undefined};
+Graph.prototype._configureSmoothCurves = function(disableStart) {
+ if (disableStart === undefined) {
+ disableStart = true;
+ }
- this.nodeIndices = this.sectors["active"]["default"]["nodeIndices"]; // the node indices list is used to speed up the computation of the repulsion fields
- for (var mixinFunction in SectorMixin) {
- if (SectorMixin.hasOwnProperty(mixinFunction)) {
- Graph.prototype[mixinFunction] = SectorMixin[mixinFunction];
+ if (this.constants.smoothCurves == true) {
+ this._createBezierNodes();
+ }
+ else {
+ // delete the support nodes
+ this.sectors['support']['nodes'] = {};
+ for (var edgeId in this.edges) {
+ if (this.edges.hasOwnProperty(edgeId)) {
+ this.edges[edgeId].smooth = false;
+ this.edges[edgeId].via = null;
+ }
}
}
+ this._updateCalculationNodes();
+ if (!disableStart) {
+ this.moving = true;
+ this.start();
+ }
};
-
-/**
- * Mixin the selection system and initialize the parameters required
- *
- * @private
- */
-Graph.prototype._loadSelectionSystem = function() {
- this.selection = [];
- this.selectionObj = {};
-
- for (var mixinFunction in SelectionMixin) {
- if (SelectionMixin.hasOwnProperty(mixinFunction)) {
- Graph.prototype[mixinFunction] = SelectionMixin[mixinFunction];
+Graph.prototype._createBezierNodes = function() {
+ if (this.constants.smoothCurves == true) {
+ for (var edgeId in this.edges) {
+ if (this.edges.hasOwnProperty(edgeId)) {
+ var edge = this.edges[edgeId];
+ if (edge.via == null) {
+ edge.smooth = true;
+ var nodeId = "edgeId:".concat(edge.id);
+ this.sectors['support']['nodes'][nodeId] = new Node(
+ {id:nodeId,
+ mass:1,
+ shape:'circle',
+ internalMultiplier:1
+ },{},{},this.constants);
+ edge.via = this.sectors['support']['nodes'][nodeId];
+ edge.via.parentEdgeId = edge.id;
+ edge.positionBezierNode();
+ }
+ }
}
}
-}
+};
-/**
- * Mixin the navigation (User Interface) system and initialize the parameters required
- *
- * @private
- */
-Graph.prototype._loadNavigationControls = function() {
- for (var mixinFunction in NavigationMixin) {
- if (NavigationMixin.hasOwnProperty(mixinFunction)) {
- Graph.prototype[mixinFunction] = NavigationMixin[mixinFunction];
+Graph.prototype._initializeMixinLoaders = function () {
+ for (var mixinFunction in graphMixinLoaders) {
+ if (graphMixinLoaders.hasOwnProperty(mixinFunction)) {
+ Graph.prototype[mixinFunction] = graphMixinLoaders[mixinFunction];
}
}
-
- if (this.constants.navigation.enabled == true) {
- this._loadNavigationElements();
- }
-}
-
-/**
- * this function exists to avoid errors when not loading the navigation system
- */
-Graph.prototype._relocateNavigation = function() {
- // empty, is overloaded by navigation system
-}
-
-/**
- * * this function exists to avoid errors when not loading the navigation system
- */
-Graph.prototype._unHighlightAll = function() {
- // empty, is overloaded by the navigation system
-}
-
-
-
-
-
+};
@@ -15201,7 +17420,6 @@ Graph.prototype._unHighlightAll = function() {
*/
var vis = {
util: util,
- events: events,
Controller: Controller,
DataSet: DataSet,
@@ -15209,7 +17427,6 @@ var vis = {
Range: Range,
Stack: Stack,
TimeStep: TimeStep,
- EventBus: EventBus,
components: {
items: {
@@ -15266,7 +17483,173 @@ if (typeof window !== 'undefined') {
}
-},{"hammerjs":2,"moment":3,"mousetrap":4}],2:[function(require,module,exports){
+},{"emitter-component":2,"hammerjs":3,"moment":4,"mousetrap":5}],2:[function(require,module,exports){
+
+/**
+ * Expose `Emitter`.
+ */
+
+module.exports = Emitter;
+
+/**
+ * Initialize a new `Emitter`.
+ *
+ * @api public
+ */
+
+function Emitter(obj) {
+ if (obj) return mixin(obj);
+};
+
+/**
+ * Mixin the emitter properties.
+ *
+ * @param {Object} obj
+ * @return {Object}
+ * @api private
+ */
+
+function mixin(obj) {
+ for (var key in Emitter.prototype) {
+ obj[key] = Emitter.prototype[key];
+ }
+ return obj;
+}
+
+/**
+ * Listen on the given `event` with `fn`.
+ *
+ * @param {String} event
+ * @param {Function} fn
+ * @return {Emitter}
+ * @api public
+ */
+
+Emitter.prototype.on =
+Emitter.prototype.addEventListener = function(event, fn){
+ this._callbacks = this._callbacks || {};
+ (this._callbacks[event] = this._callbacks[event] || [])
+ .push(fn);
+ return this;
+};
+
+/**
+ * Adds an `event` listener that will be invoked a single
+ * time then automatically removed.
+ *
+ * @param {String} event
+ * @param {Function} fn
+ * @return {Emitter}
+ * @api public
+ */
+
+Emitter.prototype.once = function(event, fn){
+ var self = this;
+ this._callbacks = this._callbacks || {};
+
+ function on() {
+ self.off(event, on);
+ fn.apply(this, arguments);
+ }
+
+ on.fn = fn;
+ this.on(event, on);
+ return this;
+};
+
+/**
+ * Remove the given callback for `event` or all
+ * registered callbacks.
+ *
+ * @param {String} event
+ * @param {Function} fn
+ * @return {Emitter}
+ * @api public
+ */
+
+Emitter.prototype.off =
+Emitter.prototype.removeListener =
+Emitter.prototype.removeAllListeners =
+Emitter.prototype.removeEventListener = function(event, fn){
+ this._callbacks = this._callbacks || {};
+
+ // all
+ if (0 == arguments.length) {
+ this._callbacks = {};
+ return this;
+ }
+
+ // specific event
+ var callbacks = this._callbacks[event];
+ if (!callbacks) return this;
+
+ // remove all handlers
+ if (1 == arguments.length) {
+ delete this._callbacks[event];
+ return this;
+ }
+
+ // remove specific handler
+ var cb;
+ for (var i = 0; i < callbacks.length; i++) {
+ cb = callbacks[i];
+ if (cb === fn || cb.fn === fn) {
+ callbacks.splice(i, 1);
+ break;
+ }
+ }
+ return this;
+};
+
+/**
+ * Emit `event` with the given args.
+ *
+ * @param {String} event
+ * @param {Mixed} ...
+ * @return {Emitter}
+ */
+
+Emitter.prototype.emit = function(event){
+ this._callbacks = this._callbacks || {};
+ var args = [].slice.call(arguments, 1)
+ , callbacks = this._callbacks[event];
+
+ if (callbacks) {
+ callbacks = callbacks.slice(0);
+ for (var i = 0, len = callbacks.length; i < len; ++i) {
+ callbacks[i].apply(this, args);
+ }
+ }
+
+ return this;
+};
+
+/**
+ * Return array of callbacks for `event`.
+ *
+ * @param {String} event
+ * @return {Array}
+ * @api public
+ */
+
+Emitter.prototype.listeners = function(event){
+ this._callbacks = this._callbacks || {};
+ return this._callbacks[event] || [];
+};
+
+/**
+ * Check if this emitter has `event` handlers.
+ *
+ * @param {String} event
+ * @return {Boolean}
+ * @api public
+ */
+
+Emitter.prototype.hasListeners = function(event){
+ return !! this.listeners(event).length;
+};
+
+},{}],3:[function(require,module,exports){
/*! Hammer.JS - v1.0.5 - 2013-04-07
* http://eightmedia.github.com/hammer.js
*
@@ -16688,7 +19071,7 @@ else {
}
}
})(this);
-},{}],3:[function(require,module,exports){
+},{}],4:[function(require,module,exports){
//! moment.js
//! version : 2.5.1
//! authors : Tim Wood, Iskren Chernev, Moment.js contributors
@@ -19090,7 +21473,7 @@ else {
}
}).call(this);
-},{}],4:[function(require,module,exports){
+},{}],5:[function(require,module,exports){
/**
* Copyright 2012 Craig Campbell
*
diff --git a/dist/vis.min.js b/dist/vis.min.js
index 083f0f1f..f6b673d1 100644
--- a/dist/vis.min.js
+++ b/dist/vis.min.js
@@ -4,8 +4,8 @@
*
* A dynamic, browser-based visualization library.
*
- * @version 0.4.0
- * @date 2014-01-31
+ * @version 0.5.1
+ * @date 2014-02-20
*
* @license
* Copyright (C) 2011-2014 Almende B.V, http://almende.com
@@ -22,10 +22,11 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
-!function(t){if("object"==typeof exports)module.exports=t();else if("function"==typeof define&&define.amd)define(t);else{var e;"undefined"!=typeof window?e=window:"undefined"!=typeof global?e=global:"undefined"!=typeof self&&(e=self),e.vis=t()}}(function(){var t;return function e(t,i,n){function s(r,a){if(!i[r]){if(!t[r]){var h="function"==typeof require&&require;if(!a&&h)return h(r,!0);if(o)return o(r,!0);throw new Error("Cannot find module '"+r+"'")}var d=i[r]={exports:{}};t[r][0].call(d.exports,function(e){var i=t[r][1][e];return s(i?i:e)},d,d.exports,e,t,i,n)}return i[r].exports}for(var o="function"==typeof require&&require,r=0;ri;++i)t.call(e||this,this[i],i,this)}),Array.prototype.map||(Array.prototype.map=function(t,e){var i,n,s;if(null==this)throw new TypeError(" this is null or not defined");var o=Object(this),r=o.length>>>0;if("function"!=typeof t)throw new TypeError(t+" is not a function");for(e&&(i=e),n=new Array(r),s=0;r>s;){var a,h;s in o&&(a=o[s],h=t.call(i,a,s,o),n[s]=h),s++}return n}),Array.prototype.filter||(Array.prototype.filter=function(t){"use strict";if(null==this)throw new TypeError;var e=Object(this),i=e.length>>>0;if("function"!=typeof t)throw new TypeError;for(var n=[],s=arguments[1],o=0;i>o;o++)if(o in e){var r=e[o];t.call(s,r,o,e)&&n.push(r)}return n}),Object.keys||(Object.keys=function(){var t=Object.prototype.hasOwnProperty,e=!{toString:null}.propertyIsEnumerable("toString"),i=["toString","toLocaleString","valueOf","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","constructor"],n=i.length;return function(s){if("object"!=typeof s&&"function"!=typeof s||null===s)throw new TypeError("Object.keys called on non-object");var o=[];for(var r in s)t.call(s,r)&&o.push(r);if(e)for(var a=0;n>a;a++)t.call(s,i[a])&&o.push(i[a]);return o}}()),Array.isArray||(Array.isArray=function(t){return"[object Array]"===Object.prototype.toString.call(t)}),Function.prototype.bind||(Function.prototype.bind=function(t){if("function"!=typeof this)throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");var e=Array.prototype.slice.call(arguments,1),i=this,n=function(){},s=function(){return i.apply(this instanceof n&&t?this:t,e.concat(Array.prototype.slice.call(arguments)))};return n.prototype=this.prototype,s.prototype=new n,s}),Object.create||(Object.create=function(t){function e(){}if(arguments.length>1)throw new Error("Object.create implementation only accepts the first parameter.");return e.prototype=t,new e}),Function.prototype.bind||(Function.prototype.bind=function(t){if("function"!=typeof this)throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");var e=Array.prototype.slice.call(arguments,1),i=this,n=function(){},s=function(){return i.apply(this instanceof n&&t?this:t,e.concat(Array.prototype.slice.call(arguments)))};return n.prototype=this.prototype,s.prototype=new n,s});var z={};z.isNumber=function(t){return t instanceof Number||"number"==typeof t},z.isString=function(t){return t instanceof String||"string"==typeof t},z.isDate=function(t){if(t instanceof Date)return!0;if(z.isString(t)){var e=P.exec(t);if(e)return!0;if(!isNaN(Date.parse(t)))return!0}return!1},z.isDataTable=function(t){return"undefined"!=typeof google&&google.visualization&&google.visualization.DataTable&&t instanceof google.visualization.DataTable},z.randomUUID=function(){var t=function(){return Math.floor(65536*Math.random()).toString(16)};return t()+t()+"-"+t()+"-"+t()+"-"+t()+"-"+t()+t()+t()},z.extend=function(t){for(var e=1,i=arguments.length;i>e;e++){var n=arguments[e];for(var s in n)n.hasOwnProperty(s)&&void 0!==n[s]&&(t[s]=n[s])}return t},z.convert=function(t,e){var i;if(void 0===t)return void 0;if(null===t)return null;if(!e)return t;if("string"!=typeof e&&!(e instanceof String))throw new Error("Type must be a string");switch(e){case"boolean":case"Boolean":return Boolean(t);case"number":case"Number":return Number(t.valueOf());case"string":case"String":return String(t);case"Date":if(z.isNumber(t))return new Date(t);if(t instanceof Date)return new Date(t.valueOf());if(L.isMoment(t))return new Date(t.valueOf());if(z.isString(t))return i=P.exec(t),i?new Date(Number(i[1])):L(t).toDate();throw new Error("Cannot convert object of type "+z.getType(t)+" to type Date");case"Moment":if(z.isNumber(t))return L(t);if(t instanceof Date)return L(t.valueOf());if(L.isMoment(t))return L(t);if(z.isString(t))return i=P.exec(t),L(i?Number(i[1]):t);throw new Error("Cannot convert object of type "+z.getType(t)+" to type Date");case"ISODate":if(z.isNumber(t))return new Date(t);if(t instanceof Date)return t.toISOString();if(L.isMoment(t))return t.toDate().toISOString();if(z.isString(t))return i=P.exec(t),i?new Date(Number(i[1])).toISOString():new Date(t).toISOString();throw new Error("Cannot convert object of type "+z.getType(t)+" to type ISODate");case"ASPDate":if(z.isNumber(t))return"/Date("+t+")/";if(t instanceof Date)return"/Date("+t.valueOf()+")/";if(z.isString(t)){i=P.exec(t);var n;return n=i?new Date(Number(i[1])).valueOf():new Date(t).valueOf(),"/Date("+n+")/"}throw new Error("Cannot convert object of type "+z.getType(t)+" to type ASPDate");default:throw new Error("Cannot convert object of type "+z.getType(t)+' to type "'+e+'"')}};var P=/^\/?Date\((\-?\d+)/i;z.getType=function(t){var e=typeof t;return"object"==e?null==t?"null":t instanceof Boolean?"Boolean":t instanceof Number?"Number":t instanceof String?"String":t instanceof Array?"Array":t instanceof Date?"Date":"Object":"number"==e?"Number":"boolean"==e?"Boolean":"string"==e?"String":e},z.getAbsoluteLeft=function(t){for(var e=document.documentElement,i=document.body,n=t.offsetLeft,s=t.offsetParent;null!=s&&s!=i&&s!=e;)n+=s.offsetLeft,n-=s.scrollLeft,s=s.offsetParent;return n},z.getAbsoluteTop=function(t){for(var e=document.documentElement,i=document.body,n=t.offsetTop,s=t.offsetParent;null!=s&&s!=i&&s!=e;)n+=s.offsetTop,n-=s.scrollTop,s=s.offsetParent;return n},z.getPageY=function(t){if("pageY"in t)return t.pageY;var e;e="targetTouches"in t&&t.targetTouches.length?t.targetTouches[0].clientY:t.clientY;var i=document.documentElement,n=document.body;return e+(i&&i.scrollTop||n&&n.scrollTop||0)-(i&&i.clientTop||n&&n.clientTop||0)},z.getPageX=function(t){if("pageY"in t)return t.pageX;var e;e="targetTouches"in t&&t.targetTouches.length?t.targetTouches[0].clientX:t.clientX;var i=document.documentElement,n=document.body;return e+(i&&i.scrollLeft||n&&n.scrollLeft||0)-(i&&i.clientLeft||n&&n.clientLeft||0)},z.addClassName=function(t,e){var i=t.className.split(" ");-1==i.indexOf(e)&&(i.push(e),t.className=i.join(" "))},z.removeClassName=function(t,e){var i=t.className.split(" "),n=i.indexOf(e);-1!=n&&(i.splice(n,1),t.className=i.join(" "))},z.forEach=function(t,e){var i,n;if(t instanceof Array)for(i=0,n=t.length;n>i;i++)e(t[i],i,t);else for(i in t)t.hasOwnProperty(i)&&e(t[i],i,t)},z.updateProperty=function(t,e,i){return t[e]!==i?(t[e]=i,!0):!1},z.addEventListener=function(t,e,i,n){t.addEventListener?(void 0===n&&(n=!1),"mousewheel"===e&&navigator.userAgent.indexOf("Firefox")>=0&&(e="DOMMouseScroll"),t.addEventListener(e,i,n)):t.attachEvent("on"+e,i)},z.removeEventListener=function(t,e,i,n){t.removeEventListener?(void 0===n&&(n=!1),"mousewheel"===e&&navigator.userAgent.indexOf("Firefox")>=0&&(e="DOMMouseScroll"),t.removeEventListener(e,i,n)):t.detachEvent("on"+e,i)},z.getTarget=function(t){t||(t=window.event);var e;return t.target?e=t.target:t.srcElement&&(e=t.srcElement),void 0!=e.nodeType&&3==e.nodeType&&(e=e.parentNode),e},z.stopPropagation=function(t){t||(t=window.event),t.stopPropagation?t.stopPropagation():t.cancelBubble=!0},z.fakeGesture=function(t,e){var i=null;return N.event.collectEventData(this,i,e)},z.preventDefault=function(t){t||(t=window.event),t.preventDefault?t.preventDefault():t.returnValue=!1},z.option={},z.option.asBoolean=function(t,e){return"function"==typeof t&&(t=t()),null!=t?0!=t:e||null},z.option.asNumber=function(t,e){return"function"==typeof t&&(t=t()),null!=t?Number(t)||e||null:e||null},z.option.asString=function(t,e){return"function"==typeof t&&(t=t()),null!=t?String(t):e||null},z.option.asSize=function(t,e){return"function"==typeof t&&(t=t()),z.isString(t)?t:z.isNumber(t)?t+"px":e||null},z.option.asElement=function(t,e){return"function"==typeof t&&(t=t()),t||e||null};var F={listeners:[],indexOf:function(t){for(var e=this.listeners,i=0,n=this.listeners.length;n>i;i++){var s=e[i];if(s&&s.object==t)return i}return-1},addListener:function(t,e,i){var n=this.indexOf(t),s=this.listeners[n];s||(s={object:t,events:{}},this.listeners.push(s));var o=s.events[e];o||(o=[],s.events[e]=o),-1==o.indexOf(i)&&o.push(i)},removeListener:function(t,e,i){var n=this.indexOf(t),s=this.listeners[n];if(s){var o=s.events[e];o&&(n=o.indexOf(i),-1!=n&&o.splice(n,1),0==o.length&&delete s.events[e]);var r=0,a=s.events;for(var h in a)a.hasOwnProperty(h)&&r++;0==r&&delete this.listeners[n]}},removeAllListeners:function(){this.listeners=[]},trigger:function(t,e,i){var n=this.indexOf(t),s=this.listeners[n];if(s){var o=s.events[e];if(o)for(var r=0,a=o.length;a>r;r++)o[r](i)}}};s.prototype.on=function(t,e,i){var n=t instanceof RegExp?t:new RegExp(t.replace("*","\\w+")),s={id:z.randomUUID(),event:t,regexp:n,callback:"function"==typeof e?e:null,target:i};return this.subscriptions.push(s),s.id},s.prototype.off=function(t){for(var e=0;eo;o++)i=s._addItem(t[o]),n.push(i);else if(z.isDataTable(t))for(var a=this._getColumnNames(t),h=0,d=t.getNumberOfRows();d>h;h++){for(var c={},l=0,u=a.length;u>l;l++){var p=a[l];c[p]=t.getValue(h,l)}i=s._addItem(c),n.push(i)}else{if(!(t instanceof Object))throw new Error("Unknown dataType");i=s._addItem(t),n.push(i)}return n.length&&this._trigger("add",{items:n},e),n},o.prototype.update=function(t,e){var i=[],n=[],s=this,o=s.fieldId,r=function(t){var e=t[o];s.data[e]?(e=s._updateItem(t),n.push(e)):(e=s._addItem(t),i.push(e))};if(t instanceof Array)for(var a=0,h=t.length;h>a;a++)r(t[a]);else if(z.isDataTable(t))for(var d=this._getColumnNames(t),c=0,l=t.getNumberOfRows();l>c;c++){for(var u={},p=0,f=d.length;f>p;p++){var g=d[p];u[g]=t.getValue(c,p)}r(u)}else{if(!(t instanceof Object))throw new Error("Unknown dataType");r(t)}return i.length&&this._trigger("add",{items:i},e),n.length&&this._trigger("update",{items:n},e),i.concat(n)},o.prototype.get=function(){var t,e,i,n,s=this,o=this.showInternalIds,r=z.getType(arguments[0]);"String"==r||"Number"==r?(t=arguments[0],i=arguments[1],n=arguments[2]):"Array"==r?(e=arguments[0],i=arguments[1],n=arguments[2]):(i=arguments[0],n=arguments[1]);var a;if(i&&i.type){if(a="DataTable"==i.type?"DataTable":"Array",n&&a!=z.getType(n))throw new Error('Type of parameter "data" ('+z.getType(n)+") does not correspond with specified options.type ("+i.type+")");if("DataTable"==a&&!z.isDataTable(n))throw new Error('Parameter "data" must be a DataTable when options.type is "DataTable"')}else a=n?"DataTable"==z.getType(n)?"DataTable":"Array":"Array";void 0!=i&&void 0!=i.showInternalIds&&(this.showInternalIds=i.showInternalIds);var h,d,c,l,u=i&&i.convert||this.options.convert,p=i&&i.filter,f=[];if(void 0!=t)h=s._getItem(t,u),p&&!p(h)&&(h=null);else if(void 0!=e)for(c=0,l=e.length;l>c;c++)h=s._getItem(e[c],u),(!p||p(h))&&f.push(h);else for(d in this.data)this.data.hasOwnProperty(d)&&(h=s._getItem(d,u),(!p||p(h))&&f.push(h));if(this.showInternalIds=o,i&&i.order&&void 0==t&&this._sort(f,i.order),i&&i.fields){var g=i.fields;if(void 0!=t)h=this._filterFields(h,g);else for(c=0,l=f.length;l>c;c++)f[c]=this._filterFields(f[c],g)}if("DataTable"==a){var m=this._getColumnNames(n);if(void 0!=t)s._appendRow(n,m,h);else for(c=0,l=f.length;l>c;c++)s._appendRow(n,m,f[c]);return n}if(void 0!=t)return h;if(n){for(c=0,l=f.length;l>c;c++)n.push(f[c]);return n}return f},o.prototype.getIds=function(t){var e,i,n,s,o,r=this.data,a=t&&t.filter,h=t&&t.order,d=t&&t.convert||this.options.convert,c=[];if(a)if(h){o=[];for(n in r)r.hasOwnProperty(n)&&(s=this._getItem(n,d),a(s)&&o.push(s));for(this._sort(o,h),e=0,i=o.length;i>e;e++)c[e]=o[e][this.fieldId]}else for(n in r)r.hasOwnProperty(n)&&(s=this._getItem(n,d),a(s)&&c.push(s[this.fieldId]));else if(h){o=[];for(n in r)r.hasOwnProperty(n)&&o.push(r[n]);for(this._sort(o,h),e=0,i=o.length;i>e;e++)c[e]=o[e][this.fieldId]}else for(n in r)r.hasOwnProperty(n)&&(s=r[n],c.push(s[this.fieldId]));return c},o.prototype.forEach=function(t,e){var i,n,s=e&&e.filter,o=e&&e.convert||this.options.convert,r=this.data;if(e&&e.order)for(var a=this.get(e),h=0,d=a.length;d>h;h++)i=a[h],n=i[this.fieldId],t(i,n);else for(n in r)r.hasOwnProperty(n)&&(i=this._getItem(n,o),(!s||s(i))&&t(i,n))},o.prototype.map=function(t,e){var i,n=e&&e.filter,s=e&&e.convert||this.options.convert,o=[],r=this.data;for(var a in r)r.hasOwnProperty(a)&&(i=this._getItem(a,s),(!n||n(i))&&o.push(t(i,a)));return e&&e.order&&this._sort(o,e.order),o},o.prototype._filterFields=function(t,e){var i={};for(var n in t)t.hasOwnProperty(n)&&-1!=e.indexOf(n)&&(i[n]=t[n]);return i},o.prototype._sort=function(t,e){if(z.isString(e)){var i=e;t.sort(function(t,e){var n=t[i],s=e[i];return n>s?1:s>n?-1:0})}else{if("function"!=typeof e)throw new TypeError("Order must be a function or a string");t.sort(e)}},o.prototype.remove=function(t,e){var i,n,s,o=[];if(t instanceof Array)for(i=0,n=t.length;n>i;i++)s=this._remove(t[i]),null!=s&&o.push(s);else s=this._remove(t),null!=s&&o.push(s);return o.length&&this._trigger("remove",{items:o},e),o},o.prototype._remove=function(t){if(z.isNumber(t)||z.isString(t)){if(this.data[t])return delete this.data[t],delete this.internalIds[t],t}else if(t instanceof Object){var e=t[this.fieldId];if(e&&this.data[e])return delete this.data[e],delete this.internalIds[e],e}return null},o.prototype.clear=function(t){var e=Object.keys(this.data);return this.data={},this.internalIds={},this._trigger("remove",{items:e},t),e},o.prototype.max=function(t){var e=this.data,i=null,n=null;for(var s in e)if(e.hasOwnProperty(s)){var o=e[s],r=o[t];null!=r&&(!i||r>n)&&(i=o,n=r)}return i},o.prototype.min=function(t){var e=this.data,i=null,n=null;for(var s in e)if(e.hasOwnProperty(s)){var o=e[s],r=o[t];null!=r&&(!i||n>r)&&(i=o,n=r)}return i},o.prototype.distinct=function(t){var e=this.data,i=[],n=this.options.convert[t],s=0;for(var o in e)if(e.hasOwnProperty(o)){for(var r=e[o],a=z.convert(r[t],n),h=!1,d=0;s>d;d++)if(i[d]==a){h=!0;break}h||(i[s]=a,s++)}return i},o.prototype._addItem=function(t){var e=t[this.fieldId];if(void 0!=e){if(this.data[e])throw new Error("Cannot add item: item with id "+e+" already exists")}else e=z.randomUUID(),t[this.fieldId]=e,this.internalIds[e]=t;var i={};for(var n in t)if(t.hasOwnProperty(n)){var s=this.convert[n];i[n]=z.convert(t[n],s)}return this.data[e]=i,e},o.prototype._getItem=function(t,e){var i,n,s=this.data[t];if(!s)return null;var o={},r=this.fieldId,a=this.internalIds;if(e)for(i in s)s.hasOwnProperty(i)&&(n=s[i],i==r&&n in a&&!this.showInternalIds||(o[i]=z.convert(n,e[i])));else for(i in s)s.hasOwnProperty(i)&&(n=s[i],i==r&&n in a&&!this.showInternalIds||(o[i]=n));return o},o.prototype._updateItem=function(t){var e=t[this.fieldId];if(void 0==e)throw new Error("Cannot update item: item has no id (item: "+JSON.stringify(t)+")");var i=this.data[e];if(!i)throw new Error("Cannot update item: no item with id "+e+" found");for(var n in t)if(t.hasOwnProperty(n)){var s=this.convert[n];i[n]=z.convert(t[n],s)}return e},o.prototype.isInternalId=function(t){return t in this.internalIds},o.prototype._getColumnNames=function(t){for(var e=[],i=0,n=t.getNumberOfColumns();n>i;i++)e[i]=t.getColumnId(i)||t.getColumnLabel(i);return e},o.prototype._appendRow=function(t,e,i){for(var n=t.addRow(),s=0,o=e.length;o>s;s++){var r=e[s];t.setValue(n,s,i[r])}},r.prototype.setData=function(t){var e,i,n;if(this.data){this.data.unsubscribe&&this.data.unsubscribe("*",this.listener),e=[];for(var s in this.ids)this.ids.hasOwnProperty(s)&&e.push(s);this.ids={},this._trigger("remove",{items:e})}if(this.data=t,this.data){for(this.fieldId=this.options.fieldId||this.data&&this.data.options&&this.data.options.fieldId||"id",e=this.data.getIds({filter:this.options&&this.options.filter}),i=0,n=e.length;n>i;i++)s=e[i],this.ids[s]=!0;this._trigger("add",{items:e}),this.data.subscribe&&this.data.subscribe("*",this.listener)}},r.prototype.get=function(){var t,e,i,n=this,s=z.getType(arguments[0]);"String"==s||"Number"==s||"Array"==s?(t=arguments[0],e=arguments[1],i=arguments[2]):(e=arguments[0],i=arguments[1]);var o=z.extend({},this.options,e);this.options.filter&&e&&e.filter&&(o.filter=function(t){return n.options.filter(t)&&e.filter(t)});var r=[];return void 0!=t&&r.push(t),r.push(o),r.push(i),this.data&&this.data.get.apply(this.data,r)},r.prototype.getIds=function(t){var e;if(this.data){var i,n=this.options.filter;i=t&&t.filter?n?function(e){return n(e)&&t.filter(e)}:t.filter:n,e=this.data.getIds({filter:i,order:t&&t.order})}else e=[];return e},r.prototype._onEvent=function(t,e,i){var n,s,o,r,a=e&&e.items,h=this.data,d=[],c=[],l=[];if(a&&h){switch(t){case"add":for(n=0,s=a.length;s>n;n++)o=a[n],r=this.get(o),r&&(this.ids[o]=!0,d.push(o));break;case"update":for(n=0,s=a.length;s>n;n++)o=a[n],r=this.get(o),r?this.ids[o]?c.push(o):(this.ids[o]=!0,d.push(o)):this.ids[o]&&(delete this.ids[o],l.push(o));break;case"remove":for(n=0,s=a.length;s>n;n++)o=a[n],this.ids[o]&&(delete this.ids[o],l.push(o))}d.length&&this._trigger("add",{items:d},i),c.length&&this._trigger("update",{items:c},i),l.length&&this._trigger("remove",{items:l},i)}},r.prototype.subscribe=o.prototype.subscribe,r.prototype.unsubscribe=o.prototype.unsubscribe,r.prototype._trigger=o.prototype._trigger,TimeStep=function(t,e,i){this.current=new Date,this._start=new Date,this._end=new Date,this.autoScale=!0,this.scale=TimeStep.SCALE.DAY,this.step=1,this.setRange(t,e,i)},TimeStep.SCALE={MILLISECOND:1,SECOND:2,MINUTE:3,HOUR:4,DAY:5,WEEKDAY:6,MONTH:7,YEAR:8},TimeStep.prototype.setRange=function(t,e,i){if(!(t instanceof Date&&e instanceof Date))throw"No legal start or end date in method setRange";this._start=void 0!=t?new Date(t.valueOf()):new Date,this._end=void 0!=e?new Date(e.valueOf()):new Date,this.autoScale&&this.setMinimumStep(i)},TimeStep.prototype.first=function(){this.current=new Date(this._start.valueOf()),this.roundToMinor()},TimeStep.prototype.roundToMinor=function(){switch(this.scale){case TimeStep.SCALE.YEAR:this.current.setFullYear(this.step*Math.floor(this.current.getFullYear()/this.step)),this.current.setMonth(0);case TimeStep.SCALE.MONTH:this.current.setDate(1);case TimeStep.SCALE.DAY:case TimeStep.SCALE.WEEKDAY:this.current.setHours(0);case TimeStep.SCALE.HOUR:this.current.setMinutes(0);case TimeStep.SCALE.MINUTE:this.current.setSeconds(0);case TimeStep.SCALE.SECOND:this.current.setMilliseconds(0)}if(1!=this.step)switch(this.scale){case TimeStep.SCALE.MILLISECOND:this.current.setMilliseconds(this.current.getMilliseconds()-this.current.getMilliseconds()%this.step);break;case TimeStep.SCALE.SECOND:this.current.setSeconds(this.current.getSeconds()-this.current.getSeconds()%this.step);break;case TimeStep.SCALE.MINUTE:this.current.setMinutes(this.current.getMinutes()-this.current.getMinutes()%this.step);break;case TimeStep.SCALE.HOUR:this.current.setHours(this.current.getHours()-this.current.getHours()%this.step);break;case TimeStep.SCALE.WEEKDAY:case TimeStep.SCALE.DAY:this.current.setDate(this.current.getDate()-1-(this.current.getDate()-1)%this.step+1);break;case TimeStep.SCALE.MONTH:this.current.setMonth(this.current.getMonth()-this.current.getMonth()%this.step);
-break;case TimeStep.SCALE.YEAR:this.current.setFullYear(this.current.getFullYear()-this.current.getFullYear()%this.step)}},TimeStep.prototype.hasNext=function(){return this.current.valueOf()<=this._end.valueOf()},TimeStep.prototype.next=function(){var t=this.current.valueOf();if(this.current.getMonth()<6)switch(this.scale){case TimeStep.SCALE.MILLISECOND:this.current=new Date(this.current.valueOf()+this.step);break;case TimeStep.SCALE.SECOND:this.current=new Date(this.current.valueOf()+1e3*this.step);break;case TimeStep.SCALE.MINUTE:this.current=new Date(this.current.valueOf()+1e3*this.step*60);break;case TimeStep.SCALE.HOUR:this.current=new Date(this.current.valueOf()+1e3*this.step*60*60);var e=this.current.getHours();this.current.setHours(e-e%this.step);break;case TimeStep.SCALE.WEEKDAY:case TimeStep.SCALE.DAY:this.current.setDate(this.current.getDate()+this.step);break;case TimeStep.SCALE.MONTH:this.current.setMonth(this.current.getMonth()+this.step);break;case TimeStep.SCALE.YEAR:this.current.setFullYear(this.current.getFullYear()+this.step)}else switch(this.scale){case TimeStep.SCALE.MILLISECOND:this.current=new Date(this.current.valueOf()+this.step);break;case TimeStep.SCALE.SECOND:this.current.setSeconds(this.current.getSeconds()+this.step);break;case TimeStep.SCALE.MINUTE:this.current.setMinutes(this.current.getMinutes()+this.step);break;case TimeStep.SCALE.HOUR:this.current.setHours(this.current.getHours()+this.step);break;case TimeStep.SCALE.WEEKDAY:case TimeStep.SCALE.DAY:this.current.setDate(this.current.getDate()+this.step);break;case TimeStep.SCALE.MONTH:this.current.setMonth(this.current.getMonth()+this.step);break;case TimeStep.SCALE.YEAR:this.current.setFullYear(this.current.getFullYear()+this.step)}if(1!=this.step)switch(this.scale){case TimeStep.SCALE.MILLISECOND:this.current.getMilliseconds()0&&(this.step=e),this.autoScale=!1},TimeStep.prototype.setAutoScale=function(t){this.autoScale=t},TimeStep.prototype.setMinimumStep=function(t){if(void 0!=t){var e=31104e6,i=2592e6,n=864e5,s=36e5,o=6e4,r=1e3,a=1;1e3*e>t&&(this.scale=TimeStep.SCALE.YEAR,this.step=1e3),500*e>t&&(this.scale=TimeStep.SCALE.YEAR,this.step=500),100*e>t&&(this.scale=TimeStep.SCALE.YEAR,this.step=100),50*e>t&&(this.scale=TimeStep.SCALE.YEAR,this.step=50),10*e>t&&(this.scale=TimeStep.SCALE.YEAR,this.step=10),5*e>t&&(this.scale=TimeStep.SCALE.YEAR,this.step=5),e>t&&(this.scale=TimeStep.SCALE.YEAR,this.step=1),3*i>t&&(this.scale=TimeStep.SCALE.MONTH,this.step=3),i>t&&(this.scale=TimeStep.SCALE.MONTH,this.step=1),5*n>t&&(this.scale=TimeStep.SCALE.DAY,this.step=5),2*n>t&&(this.scale=TimeStep.SCALE.DAY,this.step=2),n>t&&(this.scale=TimeStep.SCALE.DAY,this.step=1),n/2>t&&(this.scale=TimeStep.SCALE.WEEKDAY,this.step=1),4*s>t&&(this.scale=TimeStep.SCALE.HOUR,this.step=4),s>t&&(this.scale=TimeStep.SCALE.HOUR,this.step=1),15*o>t&&(this.scale=TimeStep.SCALE.MINUTE,this.step=15),10*o>t&&(this.scale=TimeStep.SCALE.MINUTE,this.step=10),5*o>t&&(this.scale=TimeStep.SCALE.MINUTE,this.step=5),o>t&&(this.scale=TimeStep.SCALE.MINUTE,this.step=1),15*r>t&&(this.scale=TimeStep.SCALE.SECOND,this.step=15),10*r>t&&(this.scale=TimeStep.SCALE.SECOND,this.step=10),5*r>t&&(this.scale=TimeStep.SCALE.SECOND,this.step=5),r>t&&(this.scale=TimeStep.SCALE.SECOND,this.step=1),200*a>t&&(this.scale=TimeStep.SCALE.MILLISECOND,this.step=200),100*a>t&&(this.scale=TimeStep.SCALE.MILLISECOND,this.step=100),50*a>t&&(this.scale=TimeStep.SCALE.MILLISECOND,this.step=50),10*a>t&&(this.scale=TimeStep.SCALE.MILLISECOND,this.step=10),5*a>t&&(this.scale=TimeStep.SCALE.MILLISECOND,this.step=5),a>t&&(this.scale=TimeStep.SCALE.MILLISECOND,this.step=1)}},TimeStep.prototype.snap=function(t){if(this.scale==TimeStep.SCALE.YEAR){var e=t.getFullYear()+Math.round(t.getMonth()/12);t.setFullYear(Math.round(e/this.step)*this.step),t.setMonth(0),t.setDate(0),t.setHours(0),t.setMinutes(0),t.setSeconds(0),t.setMilliseconds(0)}else if(this.scale==TimeStep.SCALE.MONTH)t.getDate()>15?(t.setDate(1),t.setMonth(t.getMonth()+1)):t.setDate(1),t.setHours(0),t.setMinutes(0),t.setSeconds(0),t.setMilliseconds(0);else if(this.scale==TimeStep.SCALE.DAY||this.scale==TimeStep.SCALE.WEEKDAY){switch(this.step){case 5:case 2:t.setHours(24*Math.round(t.getHours()/24));break;default:t.setHours(12*Math.round(t.getHours()/12))}t.setMinutes(0),t.setSeconds(0),t.setMilliseconds(0)}else if(this.scale==TimeStep.SCALE.HOUR){switch(this.step){case 4:t.setMinutes(60*Math.round(t.getMinutes()/60));break;default:t.setMinutes(30*Math.round(t.getMinutes()/30))}t.setSeconds(0),t.setMilliseconds(0)}else if(this.scale==TimeStep.SCALE.MINUTE){switch(this.step){case 15:case 10:t.setMinutes(5*Math.round(t.getMinutes()/5)),t.setSeconds(0);break;case 5:t.setSeconds(60*Math.round(t.getSeconds()/60));break;default:t.setSeconds(30*Math.round(t.getSeconds()/30))}t.setMilliseconds(0)}else if(this.scale==TimeStep.SCALE.SECOND)switch(this.step){case 15:case 10:t.setSeconds(5*Math.round(t.getSeconds()/5)),t.setMilliseconds(0);break;case 5:t.setMilliseconds(1e3*Math.round(t.getMilliseconds()/1e3));break;default:t.setMilliseconds(500*Math.round(t.getMilliseconds()/500))}else if(this.scale==TimeStep.SCALE.MILLISECOND){var i=this.step>5?this.step/2:1;t.setMilliseconds(Math.round(t.getMilliseconds()/i)*i)}},TimeStep.prototype.isMajor=function(){switch(this.scale){case TimeStep.SCALE.MILLISECOND:return 0==this.current.getMilliseconds();case TimeStep.SCALE.SECOND:return 0==this.current.getSeconds();case TimeStep.SCALE.MINUTE:return 0==this.current.getHours()&&0==this.current.getMinutes();case TimeStep.SCALE.HOUR:return 0==this.current.getHours();case TimeStep.SCALE.WEEKDAY:case TimeStep.SCALE.DAY:return 1==this.current.getDate();case TimeStep.SCALE.MONTH:return 0==this.current.getMonth();case TimeStep.SCALE.YEAR:return!1;default:return!1}},TimeStep.prototype.getLabelMinor=function(t){switch(void 0==t&&(t=this.current),this.scale){case TimeStep.SCALE.MILLISECOND:return L(t).format("SSS");case TimeStep.SCALE.SECOND:return L(t).format("s");case TimeStep.SCALE.MINUTE:return L(t).format("HH:mm");case TimeStep.SCALE.HOUR:return L(t).format("HH:mm");case TimeStep.SCALE.WEEKDAY:return L(t).format("ddd D");case TimeStep.SCALE.DAY:return L(t).format("D");case TimeStep.SCALE.MONTH:return L(t).format("MMM");case TimeStep.SCALE.YEAR:return L(t).format("YYYY");default:return""}},TimeStep.prototype.getLabelMajor=function(t){switch(void 0==t&&(t=this.current),this.scale){case TimeStep.SCALE.MILLISECOND:return L(t).format("HH:mm:ss");case TimeStep.SCALE.SECOND:return L(t).format("D MMMM HH:mm");case TimeStep.SCALE.MINUTE:case TimeStep.SCALE.HOUR:return L(t).format("ddd D MMMM");case TimeStep.SCALE.WEEKDAY:case TimeStep.SCALE.DAY:return L(t).format("MMMM YYYY");case TimeStep.SCALE.MONTH:return L(t).format("YYYY");case TimeStep.SCALE.YEAR:return"";default:return""}},a.prototype.setOptions=function(t){z.extend(this.options,t)},a.prototype.update=function(){this._order(),this._stack()},a.prototype._order=function(){var t=this.parent.items;if(!t)throw new Error("Cannot stack items: parent does not contain items");var e=[],i=0;z.forEach(t,function(t){t.visible&&(e[i]=t,i++)});var n=this.options.order||this.defaultOptions.order;if("function"!=typeof n)throw new Error("Option order must be a function");e.sort(n),this.ordered=e},a.prototype._stack=function(){var t,e,i,n=this.ordered,s=this.options,o=s.orientation||this.defaultOptions.orientation,r="top"==o;for(i=s.margin&&void 0!==s.margin.item?s.margin.item:this.defaultOptions.margin.item,t=0,e=n.length;e>t;t++){var a=n[t],h=null;do h=this.checkOverlap(n,t,0,t-1,i),null!=h&&(a.top=r?h.top+h.height+i:h.top-a.height-i);while(h)}},a.prototype.checkOverlap=function(t,e,i,n,s){for(var o=this.collision,r=t[e],a=n;a>=i;a--){var h=t[a];if(o(r,h,s)&&a!=e)return h}return null},a.prototype.collision=function(t,e,i){return t.left-ie.left&&t.top-ie.top},h.prototype.setOptions=function(t){z.extend(this.options,t),null!==this.start&&null!==this.end&&this.setRange(this.start,this.end)},h.prototype.subscribe=function(t,e,i){function n(e){s._onMouseWheel(e,t,i)}var s=this;if("move"==e)t.on("dragstart",function(e){s._onDragStart(e,t)}),t.on("drag",function(e){s._onDrag(e,t,i)}),t.on("dragend",function(e){s._onDragEnd(e,t)});else{if("zoom"!=e)throw new TypeError('Unknown event "'+e+'". Choose "move" or "zoom".');t.on("mousewheel",n),t.on("DOMMouseScroll",n),t.on("touch",function(){s._onTouch()}),t.on("pinch",function(e){s._onPinch(e,t,i)})}},h.prototype.on=function(t,e){var i=["rangechange","rangechanged"];if(-1==i.indexOf(t))throw new Error('Unknown event "'+t+'". Choose from '+i.join());F.addListener(this,t,e)},h.prototype.off=function(t,e){F.removeListener(this,t,e)},h.prototype._trigger=function(t){F.trigger(this,t,{start:this.start,end:this.end})},h.prototype.setRange=function(t,e){var i=this._applyRange(t,e);i&&(this._trigger("rangechange"),this._trigger("rangechanged"))},h.prototype._applyRange=function(t,e){var i,n=null!=t?z.convert(t,"Date").valueOf():this.start,s=null!=e?z.convert(e,"Date").valueOf():this.end,o=null!=this.options.max?z.convert(this.options.max,"Date").valueOf():null,r=null!=this.options.min?z.convert(this.options.min,"Date").valueOf():null;if(isNaN(n)||null===n)throw new Error('Invalid start "'+t+'"');if(isNaN(s)||null===s)throw new Error('Invalid end "'+e+'"');if(n>s&&(s=n),null!==r&&r>n&&(i=r-n,n+=i,s+=i,null!=o&&s>o&&(s=o)),null!==o&&s>o&&(i=s-o,n-=i,s-=i,null!=r&&r>n&&(n=r)),null!==this.options.zoomMin){var a=parseFloat(this.options.zoomMin);0>a&&(a=0),a>s-n&&(this.end-this.start===a?(n=this.start,s=this.end):(i=a-(s-n),n-=i/2,s+=i/2))}if(null!==this.options.zoomMax){var h=parseFloat(this.options.zoomMax);0>h&&(h=0),s-n>h&&(this.end-this.start===h?(n=this.start,s=this.end):(i=s-n-h,n+=i/2,s-=i/2))}var d=this.start!=n||this.end!=s;return this.start=n,this.end=s,d},h.prototype.getRange=function(){return{start:this.start,end:this.end}},h.prototype.conversion=function(t){return h.conversion(this.start,this.end,t)},h.conversion=function(t,e,i){return 0!=i&&e-t!=0?{offset:t,scale:i/(e-t)}:{offset:0,scale:1}};var Y={};h.prototype._onDragStart=function(t,e){if(!Y.pinching){Y.start=this.start,Y.end=this.end;var i=e.frame;i&&(i.style.cursor="move")}},h.prototype._onDrag=function(t,e,i){if(d(i),!Y.pinching){var n="horizontal"==i?t.gesture.deltaX:t.gesture.deltaY,s=Y.end-Y.start,o="horizontal"==i?e.width:e.height,r=-n/o*s;this._applyRange(Y.start+r,Y.end+r),this._trigger("rangechange")}},h.prototype._onDragEnd=function(t,e){Y.pinching||(e.frame&&(e.frame.style.cursor="auto"),this._trigger("rangechanged"))},h.prototype._onMouseWheel=function(t,e,i){d(i);var n=0;if(t.wheelDelta?n=t.wheelDelta/120:t.detail&&(n=-t.detail/3),n){var s;s=0>n?1-n/5:1/(1+n/5);var o=z.fakeGesture(this,t),r=c(o.touches[0],e.frame),a=this._pointerToDate(e,i,r);this.zoom(s,a)}z.preventDefault(t)},h.prototype._onTouch=function(){Y.start=this.start,Y.end=this.end,Y.pinching=!1,Y.center=null},h.prototype._onPinch=function(t,e,i){if(Y.pinching=!0,t.gesture.touches.length>1){Y.center||(Y.center=c(t.gesture.center,e.frame));var n=1/t.gesture.scale,s=this._pointerToDate(e,i,Y.center),o=c(t.gesture.center,e.frame),r=(this._pointerToDate(e,i,o),parseInt(s+(Y.start-s)*n)),a=parseInt(s+(Y.end-s)*n);this.setRange(r,a)}},h.prototype._pointerToDate=function(t,e,i){var n;if("horizontal"==e){var s=t.width;return n=this.conversion(s),i.x/n.scale+n.offset}var o=t.height;return n=this.conversion(o),i.y/n.scale+n.offset},h.prototype.zoom=function(t,e){null==e&&(e=(this.start+this.end)/2);var i=e+(this.start-e)*t,n=e+(this.end-e)*t;this.setRange(i,n)},h.prototype.move=function(t){var e=this.end-this.start,i=this.start+e*t,n=this.end+e*t;this.start=i,this.end=n},h.prototype.moveTo=function(t){var e=(this.start+this.end)/2,i=e-t,n=this.start-i,s=this.end-i;this.setRange(n,s)},l.prototype.add=function(t){if(void 0==t.id)throw new Error("Component has no field id");if(!(t instanceof u||t instanceof l))throw new TypeError("Component must be an instance of prototype Component or Controller");t.controller=this,this.components[t.id]=t},l.prototype.remove=function(t){var e;for(e in this.components)if(this.components.hasOwnProperty(e)&&(e==t||this.components[e]==t))break;e&&delete this.components[e]},l.prototype.requestReflow=function(t){if(t)this.reflow();else if(!this.reflowTimer){var e=this;this.reflowTimer=setTimeout(function(){e.reflowTimer=void 0,e.reflow()},0)}},l.prototype.requestRepaint=function(t){if(t)this.repaint();else if(!this.repaintTimer){var e=this;this.repaintTimer=setTimeout(function(){e.repaintTimer=void 0,e.repaint()},0)}},l.prototype.repaint=function V(){function V(i,n){n in e||(i.depends&&i.depends.forEach(function(t){V(t,t.id)}),i.parent&&V(i.parent,i.parent.id),t=i.repaint()||t,e[n]=!0)}var t=!1;this.repaintTimer&&(clearTimeout(this.repaintTimer),this.repaintTimer=void 0);var e={};z.forEach(this.components,V),t&&this.reflow()},l.prototype.reflow=function G(){function G(i,n){n in e||(i.depends&&i.depends.forEach(function(t){G(t,t.id)}),i.parent&&G(i.parent,i.parent.id),t=i.reflow()||t,e[n]=!0)}var t=!1;this.reflowTimer&&(clearTimeout(this.reflowTimer),this.reflowTimer=void 0);var e={};z.forEach(this.components,G),t&&this.repaint()},u.prototype.setOptions=function(t){t&&(z.extend(this.options,t),this.controller&&(this.requestRepaint(),this.requestReflow()))},u.prototype.getOption=function(t){var e;return this.options&&(e=this.options[t]),void 0===e&&this.defaultOptions&&(e=this.defaultOptions[t]),e},u.prototype.getContainer=function(){return null},u.prototype.getFrame=function(){return this.frame},u.prototype.repaint=function(){return!1},u.prototype.reflow=function(){return!1},u.prototype.hide=function(){return this.frame&&this.frame.parentNode?(this.frame.parentNode.removeChild(this.frame),!0):!1},u.prototype.show=function(){return this.frame&&this.frame.parentNode?!1:this.repaint()},u.prototype.requestRepaint=function(){if(!this.controller)throw new Error("Cannot request a repaint: no controller configured");this.controller.requestRepaint()},u.prototype.requestReflow=function(){if(!this.controller)throw new Error("Cannot request a reflow: no controller configured");this.controller.requestReflow()},p.prototype=new u,p.prototype.setOptions=u.prototype.setOptions,p.prototype.getContainer=function(){return this.frame},p.prototype.repaint=function(){var t=0,e=z.updateProperty,i=z.option.asSize,n=this.options,s=this.frame;if(!s){s=document.createElement("div"),s.className="panel";var o=n.className;o&&("function"==typeof o?z.addClassName(s,String(o())):z.addClassName(s,String(o))),this.frame=s,t+=1}if(!s.parentNode){if(!this.parent)throw new Error("Cannot repaint panel: no parent attached");var r=this.parent.getContainer();if(!r)throw new Error("Cannot repaint panel: parent has no container element");r.appendChild(s),t+=1}return t+=e(s.style,"top",i(n.top,"0px")),t+=e(s.style,"left",i(n.left,"0px")),t+=e(s.style,"width",i(n.width,"100%")),t+=e(s.style,"height",i(n.height,"100%")),t>0},p.prototype.reflow=function(){var t=0,e=z.updateProperty,i=this.frame;return i?(t+=e(this,"top",i.offsetTop),t+=e(this,"left",i.offsetLeft),t+=e(this,"width",i.offsetWidth),t+=e(this,"height",i.offsetHeight)):t+=1,t>0},f.prototype=new p,f.prototype.setOptions=u.prototype.setOptions,f.prototype.repaint=function(){var t=0,e=z.updateProperty,i=z.option.asSize,n=this.options,s=this.frame;if(s||(s=document.createElement("div"),this.frame=s,t+=1),!s.parentNode){if(!this.container)throw new Error("Cannot repaint root panel: no container attached");this.container.appendChild(s),t+=1}s.className="vis timeline rootpanel "+n.orientation;var o=n.className;return o&&z.addClassName(s,z.option.asString(o)),t+=e(s.style,"top",i(n.top,"0px")),t+=e(s.style,"left",i(n.left,"0px")),t+=e(s.style,"width",i(n.width,"100%")),t+=e(s.style,"height",i(n.height,"100%")),this._updateEventEmitters(),this._updateWatch(),t>0},f.prototype.reflow=function(){var t=0,e=z.updateProperty,i=this.frame;return i?(t+=e(this,"top",i.offsetTop),t+=e(this,"left",i.offsetLeft),t+=e(this,"width",i.offsetWidth),t+=e(this,"height",i.offsetHeight)):t+=1,t>0},f.prototype._updateWatch=function(){var t=this.getOption("autoResize");t?this._watch():this._unwatch()},f.prototype._watch=function(){var t=this;this._unwatch();var e=function(){var e=t.getOption("autoResize");return e?void(t.frame&&(t.frame.clientWidth!=t.width||t.frame.clientHeight!=t.height)&&t.requestReflow()):void t._unwatch()};z.addEventListener(window,"resize",e),this.watchTimer=setInterval(e,1e3)},f.prototype._unwatch=function(){this.watchTimer&&(clearInterval(this.watchTimer),this.watchTimer=void 0)},f.prototype.on=function(t,e){var i=this.listeners[t];i||(i=[],this.listeners[t]=i),i.push(e),this._updateEventEmitters()},f.prototype._updateEventEmitters=function(){if(this.listeners){var t=this;z.forEach(this.listeners,function(e,i){if(t.emitters||(t.emitters={}),!(i in t.emitters)){var n=t.frame;if(n){var s=function(t){e.forEach(function(e){e(t)})};t.emitters[i]=s,t.hammer||(t.hammer=N(n,{prevent_default:!0})),t.hammer.on(i,s)}}})}},g.prototype=new u,g.prototype.setOptions=u.prototype.setOptions,g.prototype.setRange=function(t){if(!(t instanceof h||t&&t.start&&t.end))throw new TypeError("Range must be an instance of Range, or an object containing start and end.");this.range=t},g.prototype.toTime=function(t){var e=this.conversion;return new Date(t/e.scale+e.offset)},g.prototype.toScreen=function(t){var e=this.conversion;return(t.valueOf()-e.offset)*e.scale},g.prototype.repaint=function(){var t=0,e=z.updateProperty,i=z.option.asSize,n=this.options,s=this.getOption("orientation"),o=this.props,r=this.step,a=this.frame;if(a||(a=document.createElement("div"),this.frame=a,t+=1),a.className="axis",!a.parentNode){if(!this.parent)throw new Error("Cannot repaint time axis: no parent attached");var h=this.parent.getContainer();if(!h)throw new Error("Cannot repaint time axis: parent has no container element");h.appendChild(a),t+=1}var d=a.parentNode;if(d){var c=a.nextSibling;d.removeChild(a);var l="bottom"==s&&this.props.parentHeight&&this.height?this.props.parentHeight-this.height+"px":"0px";if(t+=e(a.style,"top",i(n.top,l)),t+=e(a.style,"left",i(n.left,"0px")),t+=e(a.style,"width",i(n.width,"100%")),t+=e(a.style,"height",i(n.height,this.height+"px")),this._repaintMeasureChars(),this.step){this._repaintStart(),r.first();for(var u=void 0,p=0;r.hasNext()&&1e3>p;){p++;var f=r.getCurrent(),g=this.toScreen(f),m=r.isMajor();this.getOption("showMinorLabels")&&this._repaintMinorText(g,r.getLabelMinor()),m&&this.getOption("showMajorLabels")?(g>0&&(void 0==u&&(u=g),this._repaintMajorText(g,r.getLabelMajor())),this._repaintMajorLine(g)):this._repaintMinorLine(g),r.next()}if(this.getOption("showMajorLabels")){var v=this.toTime(0),y=r.getLabelMajor(v),_=y.length*(o.majorCharWidth||10)+10;(void 0==u||u>_)&&this._repaintMajorText(0,y)}this._repaintEnd()}this._repaintLine(),c?d.insertBefore(a,c):d.appendChild(a)}return t>0},g.prototype._repaintStart=function(){var t=this.dom,e=t.redundant;e.majorLines=t.majorLines,e.majorTexts=t.majorTexts,e.minorLines=t.minorLines,e.minorTexts=t.minorTexts,t.majorLines=[],t.majorTexts=[],t.minorLines=[],t.minorTexts=[]},g.prototype._repaintEnd=function(){z.forEach(this.dom.redundant,function(t){for(;t.length;){var e=t.pop();e&&e.parentNode&&e.parentNode.removeChild(e)}})},g.prototype._repaintMinorText=function(t,e){var i=this.dom.redundant.minorTexts.shift();if(!i){var n=document.createTextNode("");i=document.createElement("div"),i.appendChild(n),i.className="text minor",this.frame.appendChild(i)}this.dom.minorTexts.push(i),i.childNodes[0].nodeValue=e,i.style.left=t+"px",i.style.top=this.props.minorLabelTop+"px"},g.prototype._repaintMajorText=function(t,e){var i=this.dom.redundant.majorTexts.shift();if(!i){var n=document.createTextNode(e);i=document.createElement("div"),i.className="text major",i.appendChild(n),this.frame.appendChild(i)}this.dom.majorTexts.push(i),i.childNodes[0].nodeValue=e,i.style.top=this.props.majorLabelTop+"px",i.style.left=t+"px"},g.prototype._repaintMinorLine=function(t){var e=this.dom.redundant.minorLines.shift();e||(e=document.createElement("div"),e.className="grid vertical minor",this.frame.appendChild(e)),this.dom.minorLines.push(e);var i=this.props;e.style.top=i.minorLineTop+"px",e.style.height=i.minorLineHeight+"px",e.style.left=t-i.minorLineWidth/2+"px"},g.prototype._repaintMajorLine=function(t){var e=this.dom.redundant.majorLines.shift();e||(e=document.createElement("DIV"),e.className="grid vertical major",this.frame.appendChild(e)),this.dom.majorLines.push(e);var i=this.props;e.style.top=i.majorLineTop+"px",e.style.left=t-i.majorLineWidth/2+"px",e.style.height=i.majorLineHeight+"px"},g.prototype._repaintLine=function(){{var t=this.dom.line,e=this.frame;this.options}this.getOption("showMinorLabels")||this.getOption("showMajorLabels")?(t?(e.removeChild(t),e.appendChild(t)):(t=document.createElement("div"),t.className="grid horizontal major",e.appendChild(t),this.dom.line=t),t.style.top=this.props.lineTop+"px"):t&&t.parentElement&&(e.removeChild(t.line),delete this.dom.line)},g.prototype._repaintMeasureChars=function(){var t,e=this.dom;if(!e.measureCharMinor){t=document.createTextNode("0");var i=document.createElement("DIV");i.className="text minor measure",i.appendChild(t),this.frame.appendChild(i),e.measureCharMinor=i}if(!e.measureCharMajor){t=document.createTextNode("0");var n=document.createElement("DIV");n.className="text major measure",n.appendChild(t),this.frame.appendChild(n),e.measureCharMajor=n}},g.prototype.reflow=function(){var t=0,e=z.updateProperty,i=this.frame,n=this.range;if(!n)throw new Error("Cannot repaint time axis: no range configured");if(i){t+=e(this,"top",i.offsetTop),t+=e(this,"left",i.offsetLeft);var s=this.props,o=this.getOption("showMinorLabels"),r=this.getOption("showMajorLabels"),a=this.dom.measureCharMinor,h=this.dom.measureCharMajor;a&&(s.minorCharHeight=a.clientHeight,s.minorCharWidth=a.clientWidth),h&&(s.majorCharHeight=h.clientHeight,s.majorCharWidth=h.clientWidth);var d=i.parentNode?i.parentNode.offsetHeight:0;switch(d!=s.parentHeight&&(s.parentHeight=d,t+=1),this.getOption("orientation")){case"bottom":s.minorLabelHeight=o?s.minorCharHeight:0,s.majorLabelHeight=r?s.majorCharHeight:0,s.minorLabelTop=0,s.majorLabelTop=s.minorLabelTop+s.minorLabelHeight,s.minorLineTop=-this.top,s.minorLineHeight=Math.max(this.top+s.majorLabelHeight,0),s.minorLineWidth=1,s.majorLineTop=-this.top,s.majorLineHeight=Math.max(this.top+s.minorLabelHeight+s.majorLabelHeight,0),s.majorLineWidth=1,s.lineTop=0;break;case"top":s.minorLabelHeight=o?s.minorCharHeight:0,s.majorLabelHeight=r?s.majorCharHeight:0,s.majorLabelTop=0,s.minorLabelTop=s.majorLabelTop+s.majorLabelHeight,s.minorLineTop=s.minorLabelTop,s.minorLineHeight=Math.max(d-s.majorLabelHeight-this.top),s.minorLineWidth=1,s.majorLineTop=0,s.majorLineHeight=Math.max(d-this.top),s.majorLineWidth=1,s.lineTop=s.majorLabelHeight+s.minorLabelHeight;break;default:throw new Error('Unkown orientation "'+this.getOption("orientation")+'"')}var c=s.minorLabelHeight+s.majorLabelHeight;t+=e(this,"width",i.offsetWidth),t+=e(this,"height",c),this._updateConversion();var l=z.convert(n.start,"Number"),u=z.convert(n.end,"Number"),p=this.toTime(5*(s.minorCharWidth||10)).valueOf()-this.toTime(0).valueOf();this.step=new TimeStep(new Date(l),new Date(u),p),t+=e(s.range,"start",l),t+=e(s.range,"end",u),t+=e(s.range,"minimumStep",p.valueOf())}return t>0},g.prototype._updateConversion=function(){var t=this.range;if(!t)throw new Error("No range configured");this.conversion=t.conversion?t.conversion(this.width):h.conversion(t.start,t.end,this.width)},m.prototype=new u,m.prototype.setOptions=u.prototype.setOptions,m.prototype.getContainer=function(){return this.frame},m.prototype.repaint=function(){var t=this.frame,e=this.parent,i=e.parent.getContainer();if(!e)throw new Error("Cannot repaint bar: no parent attached");if(!i)throw new Error("Cannot repaint bar: parent has no container element");if(!this.getOption("showCurrentTime"))return void(t&&(i.removeChild(t),delete this.frame));t||(t=document.createElement("div"),t.className="currenttime",t.style.position="absolute",t.style.top="0px",t.style.height="100%",i.appendChild(t),this.frame=t),e.conversion||e._updateConversion();var n=new Date,s=e.toScreen(n);t.style.left=s+"px",t.title="Current time: "+n,void 0!==this.currentTimeTimer&&(clearTimeout(this.currentTimeTimer),delete this.currentTimeTimer);var o=this,r=1/e.conversion.scale/2;return 30>r&&(r=30),this.currentTimeTimer=setTimeout(function(){o.repaint()},r),!1},v.prototype=new u,v.prototype.setOptions=u.prototype.setOptions,v.prototype.getContainer=function(){return this.frame},v.prototype.repaint=function(){var t=this.frame,e=this.parent,i=e.parent.getContainer();if(!e)throw new Error("Cannot repaint bar: no parent attached");if(!i)throw new Error("Cannot repaint bar: parent has no container element");if(!this.getOption("showCustomTime"))return void(t&&(i.removeChild(t),delete this.frame));if(!t){t=document.createElement("div"),t.className="customtime",t.style.position="absolute",t.style.top="0px",t.style.height="100%",i.appendChild(t);var n=document.createElement("div");n.style.position="relative",n.style.top="0px",n.style.left="-10px",n.style.height="100%",n.style.width="20px",t.appendChild(n),this.frame=t,this.subscribe(this,"movetime")}e.conversion||e._updateConversion();var s=e.toScreen(this.customTime);return t.style.left=s+"px",t.title="Time: "+this.customTime,!1},v.prototype._setCustomTime=function(t){this.customTime=new Date(t.valueOf()),this.repaint()},v.prototype._getCustomTime=function(){return new Date(this.customTime.valueOf())},v.prototype.subscribe=function(t,e){var i=this,n={component:t,event:e,callback:function(t){i._onMouseDown(t,n)},params:{}};t.on("mousedown",n.callback),i.listeners.push(n)},v.prototype.on=function(t,e){var i=this.frame;if(!i)throw new Error("Cannot add event listener: no parent attached");F.addListener(this,t,e),z.addEventListener(i,t,e)},v.prototype._onMouseDown=function(t,e){t=t||window.event;var i=e.params,n=t.which?1==t.which:1==t.button;if(n){i.mouseX=z.getPageX(t),i.moved=!1,i.customTime=this.customTime;var s=this;i.onMouseMove||(i.onMouseMove=function(t){s._onMouseMove(t,e)},z.addEventListener(document,"mousemove",i.onMouseMove)),i.onMouseUp||(i.onMouseUp=function(t){s._onMouseUp(t,e)},z.addEventListener(document,"mouseup",i.onMouseUp)),z.stopPropagation(t),z.preventDefault(t)}},v.prototype._onMouseMove=function(t,e){t=t||window.event;var i=e.params,n=this.parent,s=z.getPageX(t);void 0===i.mouseX&&(i.mouseX=s);var o=s-i.mouseX;Math.abs(o)>=1&&(i.moved=!0);var r=n.toScreen(i.customTime),a=r+o,h=n.toTime(a);this._setCustomTime(h),F.trigger(this,"timechange",{customTime:this.customTime}),z.preventDefault(t)},v.prototype._onMouseUp=function(t,e){t=t||window.event;var i=e.params;i.onMouseMove&&(z.removeEventListener(document,"mousemove",i.onMouseMove),i.onMouseMove=null),i.onMouseUp&&(z.removeEventListener(document,"mouseup",i.onMouseUp),i.onMouseUp=null),i.moved&&F.trigger(this,"timechanged",{customTime:this.customTime})},y.prototype=new p,y.types={box:w,range:S,rangeoverflow:E,point:b},y.prototype.setOptions=u.prototype.setOptions,y.prototype.setRange=function(t){if(!(t instanceof h||t&&t.start&&t.end))throw new TypeError("Range must be an instance of Range, or an object containing start and end.");this.range=t},y.prototype.setSelection=function(t){var e,i,n,s,o;if(t){if(!Array.isArray(t))throw new TypeError("Array expected");for(e=0,i=this.selection.length;i>e;e++)n=this.selection[e],s=this.items[n],s&&s.unselect();for(this.selection=[],e=0,i=t.length;i>e;e++)n=t[e],s=this.items[n],s&&(this.selection.push(n),s.select());o=this.selection.concat([]),F.trigger(this,"select",{ids:o}),this.controller&&this.requestRepaint()}},y.prototype.getSelection=function(){return this.selection.concat([])},y.prototype._deselect=function(t){for(var e=this.selection,i=0,n=e.length;n>i;i++)if(e[i]==t){e.splice(i,1);break}},y.prototype.repaint=function(){var t=0,e=z.updateProperty,i=z.option.asSize,n=this.options,s=this.getOption("orientation"),o=this.defaultOptions,r=this.frame;if(!r){r=document.createElement("div"),r.className="itemset";var a=n.className;a&&z.addClassName(r,z.option.asString(a));var h=document.createElement("div");h.className="background",r.appendChild(h),this.dom.background=h;var d=document.createElement("div");d.className="foreground",r.appendChild(d),this.dom.foreground=d;var c=document.createElement("div");c.className="itemset-axis",this.dom.axis=c,this.frame=r,t+=1}if(!this.parent)throw new Error("Cannot repaint itemset: no parent attached");var l=this.parent.getContainer();if(!l)throw new Error("Cannot repaint itemset: parent has no container element");r.parentNode||(l.appendChild(r),t+=1),this.dom.axis.parentNode||(l.appendChild(this.dom.axis),t+=1),t+=e(r.style,"left",i(n.left,"0px")),t+=e(r.style,"top",i(n.top,"0px")),t+=e(r.style,"width",i(n.width,"100%")),t+=e(r.style,"height",i(n.height,this.height+"px")),t+=e(this.dom.axis.style,"left",i(n.left,"0px")),t+=e(this.dom.axis.style,"width",i(n.width,"100%")),t+="bottom"==s?e(this.dom.axis.style,"top",this.height+this.top+"px"):e(this.dom.axis.style,"top",this.top+"px"),this._updateConversion();var u=this,p=this.queue,f=this.itemsData,g=this.items,m={};for(var v in p)if(p.hasOwnProperty(v)){var _=p[v],w=g[v],b=_.action;switch(b){case"add":case"update":var S=f&&f.get(v,m);if(S){var E=S.type||S.start&&S.end&&"range"||n.type||"box",T=y.types[E];if(w&&(T&&w instanceof T?(w.data=S,t++):(t+=w.hide(),w=null)),!w){if(!T)throw new TypeError('Unknown item type "'+E+'"');w=new T(u,S,n,o),w.id=_.id,t++}w.repaint(),g[v]=w}delete p[v];break;case"remove":w&&(w.selected&&u._deselect(v),t+=w.hide()),delete g[v],delete p[v];break;default:console.log('Error: unknown action "'+b+'"')}}return z.forEach(this.items,function(e){e.visible?(t+=e.show(),e.reposition()):t+=e.hide()}),t>0},y.prototype.getForeground=function(){return this.dom.foreground},y.prototype.getBackground=function(){return this.dom.background},y.prototype.getAxis=function(){return this.dom.axis},y.prototype.reflow=function(){var t=0,e=this.options,i=e.margin&&e.margin.axis||this.defaultOptions.margin.axis,n=e.margin&&e.margin.item||this.defaultOptions.margin.item,s=z.updateProperty,o=z.option.asNumber,r=z.option.asSize,a=this.frame;if(a){this._updateConversion(),z.forEach(this.items,function(e){t+=e.reflow()}),this.stack.update();var h,d=o(e.maxHeight),c=null!=r(e.height);if(c)h=a.offsetHeight;else{var l=this.stack.ordered;if(l.length){var u=l[0].top,p=l[0].top+l[0].height;z.forEach(l,function(t){u=Math.min(u,t.top),p=Math.max(p,t.top+t.height)}),h=p-u+i+n}else h=i+n}null!=d&&(h=Math.min(h,d)),t+=s(this,"height",h),t+=s(this,"top",a.offsetTop),t+=s(this,"left",a.offsetLeft),t+=s(this,"width",a.offsetWidth)}else t+=1;return t>0},y.prototype.hide=function(){var t=!1;return this.frame&&this.frame.parentNode&&(this.frame.parentNode.removeChild(this.frame),t=!0),this.dom.axis&&this.dom.axis.parentNode&&(this.dom.axis.parentNode.removeChild(this.dom.axis),t=!0),t
-},y.prototype.setItems=function(t){var e,i=this,n=this.itemsData;if(t){if(!(t instanceof o||t instanceof r))throw new TypeError("Data must be an instance of DataSet");this.itemsData=t}else this.itemsData=null;if(n&&(z.forEach(this.listeners,function(t,e){n.unsubscribe(e,t)}),e=n.getIds(),this._onRemove(e)),this.itemsData){var s=this.id;z.forEach(this.listeners,function(t,e){i.itemsData.subscribe(e,t,s)}),e=this.itemsData.getIds(),this._onAdd(e)}},y.prototype.getItems=function(){return this.itemsData},y.prototype._onUpdate=function(t){this._toQueue("update",t)},y.prototype._onAdd=function(t){this._toQueue("add",t)},y.prototype._onRemove=function(t){this._toQueue("remove",t)},y.prototype._toQueue=function(t,e){var i=this.queue;e.forEach(function(e){i[e]={id:e,action:t}}),this.controller&&this.requestRepaint()},y.prototype._updateConversion=function(){var t=this.range;if(!t)throw new Error("No range configured");this.conversion=t.conversion?t.conversion(this.width):h.conversion(t.start,t.end,this.width)},y.prototype.toTime=function(t){var e=this.conversion;return new Date(t/e.scale+e.offset)},y.prototype.toScreen=function(t){var e=this.conversion;return(t.valueOf()-e.offset)*e.scale},_.prototype.select=function(){this.selected=!0,this.visible&&this.repaint()},_.prototype.unselect=function(){this.selected=!1,this.visible&&this.repaint()},_.prototype.show=function(){return!1},_.prototype.hide=function(){return!1},_.prototype.repaint=function(){return!1},_.prototype.reflow=function(){return!1},_.prototype.getWidth=function(){return this.width},w.prototype=new _(null,null),w.prototype.repaint=function(){var t=!1,e=this.dom;if(e||(this._create(),e=this.dom,t=!0),e){if(!this.parent)throw new Error("Cannot repaint item: no parent attached");if(!e.box.parentNode){var i=this.parent.getForeground();if(!i)throw new Error("Cannot repaint time axis: parent has no foreground container element");i.appendChild(e.box),t=!0}if(!e.line.parentNode){var n=this.parent.getBackground();if(!n)throw new Error("Cannot repaint time axis: parent has no background container element");n.appendChild(e.line),t=!0}if(!e.dot.parentNode){var s=this.parent.getAxis();if(!n)throw new Error("Cannot repaint time axis: parent has no axis container element");s.appendChild(e.dot),t=!0}if(this.data.content!=this.content){if(this.content=this.data.content,this.content instanceof Element)e.content.innerHTML="",e.content.appendChild(this.content);else{if(void 0==this.data.content)throw new Error('Property "content" missing in item '+this.data.id);e.content.innerHTML=this.content}t=!0}var o=(this.data.className?" "+this.data.className:"")+(this.selected?" selected":"");this.className!=o&&(this.className=o,e.box.className="item box"+o,e.line.className="item line"+o,e.dot.className="item dot"+o,t=!0)}return t},w.prototype.show=function(){return this.dom&&this.dom.box.parentNode?!1:this.repaint()},w.prototype.hide=function(){var t=!1,e=this.dom;return e&&(e.box.parentNode&&(e.box.parentNode.removeChild(e.box),t=!0),e.line.parentNode&&e.line.parentNode.removeChild(e.line),e.dot.parentNode&&e.dot.parentNode.removeChild(e.dot)),t},w.prototype.reflow=function(){var t,e,i,n,s,o,r,a,h,d,c,l,u=0;if(void 0==this.data.start)throw new Error('Property "start" missing in item '+this.data.id);if(c=this.data,l=this.parent&&this.parent.range,c&&l){var p=l.end-l.start;this.visible=c.start>l.start-p&&c.start0},w.prototype._create=function(){var t=this.dom;t||(this.dom=t={},t.box=document.createElement("DIV"),t.content=document.createElement("DIV"),t.content.className="content",t.box.appendChild(t.content),t.line=document.createElement("DIV"),t.line.className="line",t.dot=document.createElement("DIV"),t.dot.className="dot",t.box["timeline-item"]=this)},w.prototype.reposition=function(){var t=this.dom,e=this.props,i=this.options.orientation||this.defaultOptions.orientation;if(t){var n=t.box,s=t.line,o=t.dot;n.style.left=this.left+"px",n.style.top=this.top+"px",s.style.left=e.line.left+"px","top"==i?(s.style.top="0px",s.style.height=this.top+"px"):(s.style.top=this.top+this.height+"px",s.style.height=Math.max(this.parent.height-this.top-this.height+this.props.dot.height/2,0)+"px"),o.style.left=e.dot.left+"px",o.style.top=e.dot.top+"px"}},b.prototype=new _(null,null),b.prototype.repaint=function(){var t=!1,e=this.dom;if(e||(this._create(),e=this.dom,t=!0),e){if(!this.parent)throw new Error("Cannot repaint item: no parent attached");var i=this.parent.getForeground();if(!i)throw new Error("Cannot repaint time axis: parent has no foreground container element");if(e.point.parentNode||(i.appendChild(e.point),i.appendChild(e.point),t=!0),this.data.content!=this.content){if(this.content=this.data.content,this.content instanceof Element)e.content.innerHTML="",e.content.appendChild(this.content);else{if(void 0==this.data.content)throw new Error('Property "content" missing in item '+this.data.id);e.content.innerHTML=this.content}t=!0}var n=(this.data.className?" "+this.data.className:"")+(this.selected?" selected":"");this.className!=n&&(this.className=n,e.point.className="item point"+n,t=!0)}return t},b.prototype.show=function(){return this.dom&&this.dom.point.parentNode?!1:this.repaint()},b.prototype.hide=function(){var t=!1,e=this.dom;return e&&e.point.parentNode&&(e.point.parentNode.removeChild(e.point),t=!0),t},b.prototype.reflow=function(){var t,e,i,n,s,o,r,a,h,d,c=0;if(void 0==this.data.start)throw new Error('Property "start" missing in item '+this.data.id);if(h=this.data,d=this.parent&&this.parent.range,h&&d){var l=d.end-d.start;this.visible=h.start>d.start-l&&h.start0},b.prototype._create=function(){var t=this.dom;t||(this.dom=t={},t.point=document.createElement("div"),t.content=document.createElement("div"),t.content.className="content",t.point.appendChild(t.content),t.dot=document.createElement("div"),t.dot.className="dot",t.point.appendChild(t.dot),t.point["timeline-item"]=this)},b.prototype.reposition=function(){var t=this.dom,e=this.props;t&&(t.point.style.top=this.top+"px",t.point.style.left=this.left+"px",t.content.style.marginLeft=e.content.marginLeft+"px",t.dot.style.top=e.dot.top+"px")},S.prototype=new _(null,null),S.prototype.repaint=function(){var t=!1,e=this.dom;if(e||(this._create(),e=this.dom,t=!0),e){if(!this.parent)throw new Error("Cannot repaint item: no parent attached");var i=this.parent.getForeground();if(!i)throw new Error("Cannot repaint time axis: parent has no foreground container element");if(e.box.parentNode||(i.appendChild(e.box),t=!0),this.data.content!=this.content){if(this.content=this.data.content,this.content instanceof Element)e.content.innerHTML="",e.content.appendChild(this.content);else{if(void 0==this.data.content)throw new Error('Property "content" missing in item '+this.data.id);e.content.innerHTML=this.content}t=!0}var n=(this.data.className?" "+this.data.className:"")+(this.selected?" selected":"");this.className!=n&&(this.className=n,e.box.className="item range"+n,t=!0)}return t},S.prototype.show=function(){return this.dom&&this.dom.box.parentNode?!1:this.repaint()},S.prototype.hide=function(){var t=!1,e=this.dom;return e&&e.box.parentNode&&(e.box.parentNode.removeChild(e.box),t=!0),t},S.prototype.reflow=function(){var t,e,i,n,s,o,r,a,h,d,c,l,u,p,f,g,m=0;if(void 0==this.data.start)throw new Error('Property "start" missing in item '+this.data.id);if(void 0==this.data.end)throw new Error('Property "end" missing in item '+this.data.id);return h=this.data,d=this.parent&&this.parent.range,this.visible=h&&d?h.startd.start:!1,this.visible&&(t=this.dom,t?(e=this.props,i=this.options,o=this.parent,r=o.toScreen(this.data.start),a=o.toScreen(this.data.end),c=z.updateProperty,l=t.box,u=o.width,f=i.orientation||this.defaultOptions.orientation,n=i.margin&&i.margin.axis||this.defaultOptions.margin.axis,s=i.padding||this.defaultOptions.padding,m+=c(e.content,"width",t.content.offsetWidth),m+=c(this,"height",l.offsetHeight),-u>r&&(r=-u),a>2*u&&(a=2*u),p=0>r?Math.min(-r,a-r-e.content.width-2*s):0,m+=c(e.content,"left",p),"top"==f?(g=n,m+=c(this,"top",g)):(g=o.height-this.height-n,m+=c(this,"top",g)),m+=c(this,"left",r),m+=c(this,"width",Math.max(a-r,1))):m+=1),m>0},S.prototype._create=function(){var t=this.dom;t||(this.dom=t={},t.box=document.createElement("div"),t.content=document.createElement("div"),t.content.className="content",t.box.appendChild(t.content),t.box["timeline-item"]=this)},S.prototype.reposition=function(){var t=this.dom,e=this.props;t&&(t.box.style.top=this.top+"px",t.box.style.left=this.left+"px",t.box.style.width=this.width+"px",t.content.style.left=e.content.left+"px")},E.prototype=new S(null,null),E.prototype.repaint=function(){var t=!1,e=this.dom;if(e||(this._create(),e=this.dom,t=!0),e){if(!this.parent)throw new Error("Cannot repaint item: no parent attached");var i=this.parent.getForeground();if(!i)throw new Error("Cannot repaint time axis: parent has no foreground container element");if(e.box.parentNode||(i.appendChild(e.box),t=!0),this.data.content!=this.content){if(this.content=this.data.content,this.content instanceof Element)e.content.innerHTML="",e.content.appendChild(this.content);else{if(void 0==this.data.content)throw new Error('Property "content" missing in item '+this.data.id);e.content.innerHTML=this.content}t=!0}var n=this.data.className?" "+this.data.className:"";this.className!=n&&(this.className=n,e.box.className="item rangeoverflow"+n,t=!0)}return t},E.prototype.getWidth=function(){return void 0!==this.props.content&&this.width0},x.prototype=new p,x.prototype.setOptions=u.prototype.setOptions,x.prototype.setRange=function(){},x.prototype.setItems=function(t){this.itemsData=t;for(var e in this.groups)if(this.groups.hasOwnProperty(e)){var i=this.groups[e];i.setItems(t)}},x.prototype.getItems=function(){return this.itemsData},x.prototype.setRange=function(t){this.range=t},x.prototype.setGroups=function(t){var e,i=this;if(this.groupsData&&(z.forEach(this.listeners,function(t,e){i.groupsData.unsubscribe(e,t)}),e=this.groupsData.getIds(),this._onRemove(e)),t?t instanceof o?this.groupsData=t:(this.groupsData=new o({convert:{start:"Date",end:"Date"}}),this.groupsData.add(t)):this.groupsData=null,this.groupsData){var n=this.id;z.forEach(this.listeners,function(t,e){i.groupsData.subscribe(e,t,n)}),e=this.groupsData.getIds(),this._onAdd(e)}},x.prototype.getGroups=function(){return this.groupsData},x.prototype.setSelection=function(t){var e=[],i=this.groups;for(var n in i)if(i.hasOwnProperty(n)){var s=i[n];s.setSelection(t)}return e},x.prototype.getSelection=function(){var t=[],e=this.groups;for(var i in e)if(e.hasOwnProperty(i)){var n=e[i];t=t.concat(n.getSelection())}return t},x.prototype.repaint=function(){var t,e,i,n,s=0,o=z.updateProperty,r=z.option.asSize,a=z.option.asElement,h=this.options,d=this.dom.frame,c=this.dom.labels,l=this.dom.labelSet;if(!this.parent)throw new Error("Cannot repaint groupset: no parent attached");var u=this.parent.getContainer();if(!u)throw new Error("Cannot repaint groupset: parent has no container element");if(!d){d=document.createElement("div"),d.className="groupset",this.dom.frame=d;var p=h.className;p&&z.addClassName(d,z.option.asString(p)),s+=1}d.parentNode||(u.appendChild(d),s+=1);var f=a(h.labelContainer);if(!f)throw new Error('Cannot repaint groupset: option "labelContainer" not defined');c||(c=document.createElement("div"),c.className="labels",this.dom.labels=c),l||(l=document.createElement("div"),l.className="label-set",c.appendChild(l),this.dom.labelSet=l),c.parentNode&&c.parentNode==f||(c.parentNode&&c.parentNode.removeChild(c.parentNode),f.appendChild(c)),s+=o(d.style,"height",r(h.height,this.height+"px")),s+=o(d.style,"top",r(h.top,"0px")),s+=o(d.style,"left",r(h.left,"0px")),s+=o(d.style,"width",r(h.width,"100%")),s+=o(l.style,"top",r(h.top,"0px")),s+=o(l.style,"height",r(h.height,this.height+"px"));var g=this,m=this.queue,v=this.groups,y=this.groupsData,_=Object.keys(m);if(_.length){_.forEach(function(t){var e=m[t],i=v[t];switch(e){case"add":case"update":if(!i){var n=Object.create(g.options);z.extend(n,{height:null,maxHeight:null}),i=new T(g,t,n),i.setItems(g.itemsData),v[t]=i,g.controller.add(i)}i.data=y.get(t),delete m[t];break;case"remove":i&&(i.setItems(),delete v[t],g.controller.remove(i)),delete m[t];break;default:console.log('Error: unknown action "'+e+'"')}});var w=this.groupsData.getIds({order:this.options.groupOrder});for(t=0;t0},x.prototype._createLabel=function(t){var e=this.groups[t],i=document.createElement("div");i.className="label";var n=document.createElement("div");n.className="inner",i.appendChild(n);var s=e.data&&e.data.content;s instanceof Element?n.appendChild(s):void 0!=s&&(n.innerHTML=s);var o=e.data&&e.data.className;return o&&z.addClassName(i,o),e.label=i,i},x.prototype.getContainer=function(){return this.dom.frame},x.prototype.getLabelsWidth=function(){return this.props.labels.width},x.prototype.reflow=function(){var t,e,i=0,n=this.options,s=z.updateProperty,o=z.option.asNumber,r=z.option.asSize,a=this.dom.frame;if(a){var h,d=o(n.maxHeight),c=null!=r(n.height);if(c)h=a.offsetHeight;else{h=0;for(t in this.groups)this.groups.hasOwnProperty(t)&&(e=this.groups[t],h+=e.height)}null!=d&&(h=Math.min(h,d)),i+=s(this,"height",h),i+=s(this,"top",a.offsetTop),i+=s(this,"left",a.offsetLeft),i+=s(this,"width",a.offsetWidth)}var l=0;for(t in this.groups)if(this.groups.hasOwnProperty(t)){e=this.groups[t];var u=e.props&&e.props.label&&e.props.label.width||0;l=Math.max(l,u)}return i+=s(this.props.labels,"width",l),i>0},x.prototype.hide=function(){return this.dom.frame&&this.dom.frame.parentNode?(this.dom.frame.parentNode.removeChild(this.dom.frame),!0):!1},x.prototype.show=function(){return this.dom.frame&&this.dom.frame.parentNode?!1:this.repaint()},x.prototype._onUpdate=function(t){this._toQueue(t,"update")},x.prototype._onAdd=function(t){this._toQueue(t,"add")},x.prototype._onRemove=function(t){this._toQueue(t,"remove")},x.prototype._toQueue=function(t,e){var i=this.queue;t.forEach(function(t){i[t]=e}),this.controller&&this.requestRepaint()},C.prototype.setOptions=function(t){z.extend(this.options,t),this.range.setRange(t.start,t.end),this.controller.reflow(),this.controller.repaint()},C.prototype.setCustomTime=function(t){this.customtime._setCustomTime(t)},C.prototype.getCustomTime=function(){return new Date(this.customtime.customTime.valueOf())},C.prototype.setItems=function(t){var e,i=null==this.itemsData;if(t?t instanceof o&&(e=t):e=null,t instanceof o||(e=new o({convert:{start:"Date",end:"Date"}}),e.add(t)),this.itemsData=e,this.content.setItems(e),i&&(void 0==this.options.start||void 0==this.options.end)){var n=this.getItemRange(),s=n.min,r=n.max;if(null!=s&&null!=r){var a=r.valueOf()-s.valueOf();0>=a&&(a=864e5),s=new Date(s.valueOf()-.05*a),r=new Date(r.valueOf()+.05*a)}void 0!=this.options.start&&(s=z.convert(this.options.start,"Date")),void 0!=this.options.end&&(r=z.convert(this.options.end,"Date")),(null!=s||null!=r)&&this.range.setRange(s,r)}},C.prototype.setGroups=function(t){var e=this;this.groupsData=t;var i=this.groupsData?x:y;if(!(this.content instanceof i)){this.content&&(this.content.hide(),this.content.setItems&&this.content.setItems(),this.content.setGroups&&this.content.setGroups(),this.controller.remove(this.content));var n=Object.create(this.options);z.extend(n,{top:function(){return"top"==e.options.orientation?e.timeaxis.height:e.itemPanel.height-e.timeaxis.height-e.content.height},left:null,width:"100%",height:function(){return e.options.height?e.itemPanel.height-e.timeaxis.height:null},maxHeight:function(){if(e.options.maxHeight){if(!z.isNumber(e.options.maxHeight))throw new TypeError("Number expected for property maxHeight");return e.options.maxHeight-e.timeaxis.height}return null},labelContainer:function(){return e.labelPanel.getContainer()}}),this.content=new i(this.itemPanel,[this.timeaxis],n),this.content.setRange&&this.content.setRange(this.range),this.content.setItems&&this.content.setItems(this.itemsData),this.content.setGroups&&this.content.setGroups(this.groupsData),this.controller.add(this.content)}},C.prototype.getItemRange=function(){var t=this.itemsData,e=null,i=null;if(t){var n=t.min("start");e=n?n.start.valueOf():null;var s=t.max("start");s&&(i=s.start.valueOf());var o=t.max("end");o&&(i=null==i?o.end.valueOf():Math.max(i,o.end.valueOf()))}return{min:null!=e?new Date(e):null,max:null!=i?new Date(i):null}},C.prototype.setSelection=function(t){this.content&&this.content.setSelection(t)},C.prototype.getSelection=function(){return this.content?this.content.getSelection():[]},C.prototype.on=function(t,e){var i=["rangechange","rangechanged","select"];if(-1==i.indexOf(t))throw new Error('Unknown event "'+t+'". Choose from '+i.join());F.addListener(this,t,e)},C.prototype.off=function(t,e){F.removeListener(this,t,e)},C.prototype._trigger=function(t,e){F.trigger(this,t,e||{})},C.prototype._onSelectItem=function(t){var e=this._itemFromTarget(t),i=e?[e.id]:[];this.setSelection(i),this._trigger("select",{items:this.getSelection()}),t.stopPropagation()},C.prototype._onMultiSelectItem=function(t){var e,i=this._itemFromTarget(t);if(i){e=this.getSelection();var n=e.indexOf(i.id);-1==n?e.push(i.id):e.splice(n,1),this.setSelection(e),this._trigger("select",{items:this.getSelection()}),t.stopPropagation()}},C.prototype._itemFromTarget=function(t){for(var e=t.target;e;){if(e.hasOwnProperty("timeline-item"))return e["timeline-item"];e=e.parentNode}return null},function(t){function e(t){return C=t,u()}function i(){M=0,D=C.charAt(0)}function n(){M++,D=C.charAt(M)}function s(){return C.charAt(M+1)}function o(t){return N.test(t)}function r(t,e){if(t||(t={}),e)for(var i in e)e.hasOwnProperty(i)&&(t[i]=e[i]);return t}function a(t,e,i){for(var n=e.split("."),s=t;n.length;){var o=n.shift();n.length?(s[o]||(s[o]={}),s=s[o]):s[o]=i}}function h(t,e){for(var i,n,s=null,o=[t],a=t;a.parent;)o.push(a.parent),a=a.parent;if(a.nodes)for(i=0,n=a.nodes.length;n>i;i++)if(e.id===a.nodes[i].id){s=a.nodes[i];break}for(s||(s={id:e.id},t.node&&(s.attr=r(s.attr,t.node))),i=o.length-1;i>=0;i--){var h=o[i];h.nodes||(h.nodes=[]),-1==h.nodes.indexOf(s)&&h.nodes.push(s)}e.attr&&(s.attr=r(s.attr,e.attr))}function d(t,e){if(t.edges||(t.edges=[]),t.edges.push(e),t.edge){var i=r({},t.edge);e.attr=r(i,e.attr)}}function c(t,e,i,n,s){var o={from:e,to:i,type:n};return t.edge&&(o.attr=r({},t.edge)),o.attr=r(o.attr||{},s),o}function l(){for(O=T.NULL,I="";" "==D||" "==D||"\n"==D||"\r"==D;)n();do{var t=!1;if("#"==D){for(var e=M-1;" "==C.charAt(e)||" "==C.charAt(e);)e--;if("\n"==C.charAt(e)||""==C.charAt(e)){for(;""!=D&&"\n"!=D;)n();t=!0}}if("/"==D&&"/"==s()){for(;""!=D&&"\n"!=D;)n();t=!0}if("/"==D&&"*"==s()){for(;""!=D;){if("*"==D&&"/"==s()){n(),n();break}n()}t=!0}for(;" "==D||" "==D||"\n"==D||"\r"==D;)n()}while(t);if(""==D)return void(O=T.DELIMITER);var i=D+s();if(x[i])return O=T.DELIMITER,I=i,n(),void n();if(x[D])return O=T.DELIMITER,I=D,void n();if(o(D)||"-"==D){for(I+=D,n();o(D);)I+=D,n();return"false"==I?I=!1:"true"==I?I=!0:isNaN(Number(I))||(I=Number(I)),void(O=T.IDENTIFIER)}if('"'==D){for(n();""!=D&&('"'!=D||'"'==D&&'"'==s());)I+=D,'"'==D&&n(),n();if('"'!=D)throw w('End of string " expected');return n(),void(O=T.IDENTIFIER)}for(O=T.UNKNOWN;""!=D;)I+=D,n();throw new SyntaxError('Syntax error in part "'+b(I,30)+'"')}function u(){var t={};if(i(),l(),"strict"==I&&(t.strict=!0,l()),("graph"==I||"digraph"==I)&&(t.type=I,l()),O==T.IDENTIFIER&&(t.id=I,l()),"{"!=I)throw w("Angle bracket { expected");if(l(),p(t),"}"!=I)throw w("Angle bracket } expected");if(l(),""!==I)throw w("End of file expected");return l(),delete t.node,delete t.edge,delete t.graph,t}function p(t){for(;""!==I&&"}"!=I;)f(t),";"==I&&l()}function f(t){var e=g(t);if(e)return void y(t,e);var i=m(t);if(!i){if(O!=T.IDENTIFIER)throw w("Identifier expected");var n=I;if(l(),"="==I){if(l(),O!=T.IDENTIFIER)throw w("Identifier expected");t[n]=I,l()}else v(t,n)}}function g(t){var e=null;if("subgraph"==I&&(e={},e.type="subgraph",l(),O==T.IDENTIFIER&&(e.id=I,l())),"{"==I){if(l(),e||(e={}),e.parent=t,e.node=t.node,e.edge=t.edge,e.graph=t.graph,p(e),"}"!=I)throw w("Angle bracket } expected");l(),delete e.node,delete e.edge,delete e.graph,delete e.parent,t.subgraphs||(t.subgraphs=[]),t.subgraphs.push(e)}return e}function m(t){return"node"==I?(l(),t.node=_(),"node"):"edge"==I?(l(),t.edge=_(),"edge"):"graph"==I?(l(),t.graph=_(),"graph"):null}function v(t,e){var i={id:e},n=_();n&&(i.attr=n),h(t,i),y(t,e)}function y(t,e){for(;"->"==I||"--"==I;){var i,n=I;l();var s=g(t);if(s)i=s;else{if(O!=T.IDENTIFIER)throw w("Identifier or subgraph expected");i=I,h(t,{id:i}),l()}var o=_(),r=c(t,e,i,n,o);d(t,r),e=i}}function _(){for(var t=null;"["==I;){for(l(),t={};""!==I&&"]"!=I;){if(O!=T.IDENTIFIER)throw w("Attribute name expected");var e=I;if(l(),"="!=I)throw w("Equal sign = expected");if(l(),O!=T.IDENTIFIER)throw w("Attribute value expected");var i=I;a(t,e,i),l(),","==I&&l()}if("]"!=I)throw w("Bracket ] expected");l()}return t}function w(t){return new SyntaxError(t+', got "'+b(I,30)+'" (char '+M+")")}function b(t,e){return t.length<=e?t:t.substr(0,27)+"..."}function S(t,e,i){t instanceof Array?t.forEach(function(t){e instanceof Array?e.forEach(function(e){i(t,e)}):i(t,e)}):e instanceof Array?e.forEach(function(e){i(t,e)}):i(t,e)}function E(t){function i(t){var e={from:t.from,to:t.to};return r(e,t.attr),e.style="->"==t.type?"arrow":"line",e}var n=e(t),s={nodes:[],edges:[],options:{}};return n.nodes&&n.nodes.forEach(function(t){var e={id:t.id,label:String(t.label||t.id)};r(e,t.attr),e.image&&(e.shape="image"),s.nodes.push(e)}),n.edges&&n.edges.forEach(function(t){var e,n;e=t.from instanceof Object?t.from.nodes:{id:t.from},n=t.to instanceof Object?t.to.nodes:{id:t.to},t.from instanceof Object&&t.from.edges&&t.from.edges.forEach(function(t){var e=i(t);s.edges.push(e)}),S(e,n,function(e,n){var o=c(s,e.id,n.id,t.type,t.attr),r=i(o);s.edges.push(r)}),t.to instanceof Object&&t.to.edges&&t.to.edges.forEach(function(t){var e=i(t);s.edges.push(e)})}),n.attr&&(s.options=n.attr),s}var T={NULL:0,DELIMITER:1,IDENTIFIER:2,UNKNOWN:3},x={"{":!0,"}":!0,"[":!0,"]":!0,";":!0,"=":!0,",":!0,"->":!0,"--":!0},C="",M=0,D="",I="",O=T.NULL,N=/[a-zA-Z_0-9.:#]/;t.parseDOT=e,t.DOTToGraph=E}("undefined"!=typeof z?z:n),"undefined"!=typeof CanvasRenderingContext2D&&(CanvasRenderingContext2D.prototype.circle=function(t,e,i){this.beginPath(),this.arc(t,e,i,0,2*Math.PI,!1)},CanvasRenderingContext2D.prototype.square=function(t,e,i){this.beginPath(),this.rect(t-i,e-i,2*i,2*i)},CanvasRenderingContext2D.prototype.triangle=function(t,e,i){this.beginPath();var n=2*i,s=n/2,o=Math.sqrt(3)/6*n,r=Math.sqrt(n*n-s*s);this.moveTo(t,e-(r-o)),this.lineTo(t+s,e+o),this.lineTo(t-s,e+o),this.lineTo(t,e-(r-o)),this.closePath()},CanvasRenderingContext2D.prototype.triangleDown=function(t,e,i){this.beginPath();var n=2*i,s=n/2,o=Math.sqrt(3)/6*n,r=Math.sqrt(n*n-s*s);this.moveTo(t,e+(r-o)),this.lineTo(t+s,e-o),this.lineTo(t-s,e-o),this.lineTo(t,e+(r-o)),this.closePath()},CanvasRenderingContext2D.prototype.star=function(t,e,i){this.beginPath();for(var n=0;10>n;n++){var s=n%2===0?1.3*i:.5*i;this.lineTo(t+s*Math.sin(2*n*Math.PI/10),e-s*Math.cos(2*n*Math.PI/10))}this.closePath()},CanvasRenderingContext2D.prototype.roundRect=function(t,e,i,n,s){var o=Math.PI/180;0>i-2*s&&(s=i/2),0>n-2*s&&(s=n/2),this.beginPath(),this.moveTo(t+s,e),this.lineTo(t+i-s,e),this.arc(t+i-s,e+s,s,270*o,360*o,!1),this.lineTo(t+i,e+n-s),this.arc(t+i-s,e+n-s,s,0,90*o,!1),this.lineTo(t+s,e+n),this.arc(t+s,e+n-s,s,90*o,180*o,!1),this.lineTo(t,e+s),this.arc(t+s,e+s,s,180*o,270*o,!1)},CanvasRenderingContext2D.prototype.ellipse=function(t,e,i,n){var s=.5522848,o=i/2*s,r=n/2*s,a=t+i,h=e+n,d=t+i/2,c=e+n/2;this.beginPath(),this.moveTo(t,c),this.bezierCurveTo(t,c-r,d-o,e,d,e),this.bezierCurveTo(d+o,e,a,c-r,a,c),this.bezierCurveTo(a,c+r,d+o,h,d,h),this.bezierCurveTo(d-o,h,t,c+r,t,c)},CanvasRenderingContext2D.prototype.database=function(t,e,i,n){var s=1/3,o=i,r=n*s,a=.5522848,h=o/2*a,d=r/2*a,c=t+o,l=e+r,u=t+o/2,p=e+r/2,f=e+(n-r/2),g=e+n;this.beginPath(),this.moveTo(c,p),this.bezierCurveTo(c,p+d,u+h,l,u,l),this.bezierCurveTo(u-h,l,t,p+d,t,p),this.bezierCurveTo(t,p-d,u-h,e,u,e),this.bezierCurveTo(u+h,e,c,p-d,c,p),this.lineTo(c,f),this.bezierCurveTo(c,f+d,u+h,g,u,g),this.bezierCurveTo(u-h,g,t,f+d,t,f),this.lineTo(t,p)},CanvasRenderingContext2D.prototype.arrow=function(t,e,i,n){var s=t-n*Math.cos(i),o=e-n*Math.sin(i),r=t-.9*n*Math.cos(i),a=e-.9*n*Math.sin(i),h=s+n/3*Math.cos(i+.5*Math.PI),d=o+n/3*Math.sin(i+.5*Math.PI),c=s+n/3*Math.cos(i-.5*Math.PI),l=o+n/3*Math.sin(i-.5*Math.PI);this.beginPath(),this.moveTo(t,e),this.lineTo(h,d),this.lineTo(r,a),this.lineTo(c,l),this.closePath()},CanvasRenderingContext2D.prototype.dashedLine=function(t,e,i,n,s){s||(s=[10,5]),0==u&&(u=.001);var o=s.length;this.moveTo(t,e);for(var r=i-t,a=n-e,h=a/r,d=Math.sqrt(r*r+a*a),c=0,l=!0;d>=.1;){var u=s[c++%o];u>d&&(u=d);var p=Math.sqrt(u*u/(1+h*h));0>r&&(p=-p),t+=p,e+=h*p,this[l?"lineTo":"moveTo"](t,e),d-=u,l=!l}}),M.prototype.resetCluster=function(){this.formationScale=void 0,this.clusterSize=1,this.containedNodes={},this.containedEdges={},this.clusterSessions=[]},M.prototype.attachEdge=function(t){-1==this.edges.indexOf(t)&&this.edges.push(t),-1==this.dynamicEdges.indexOf(t)&&this.dynamicEdges.push(t),this.dynamicEdgesLength=this.dynamicEdges.length,this._updateMass()},M.prototype.detachEdge=function(t){var e=this.edges.indexOf(t);-1!=e&&(this.edges.splice(e,1),this.dynamicEdges.splice(e,1)),this.dynamicEdgesLength=this.dynamicEdges.length,this._updateMass()},M.prototype._updateMass=function(){this.mass=1+.6*this.edges.length},M.prototype.setProperties=function(t,e){if(t){if(this.originalLabel=void 0,void 0!==t.id&&(this.id=t.id),void 0!==t.label&&(this.label=t.label,this.originalLabel=t.label),void 0!==t.title&&(this.title=t.title),void 0!==t.group&&(this.group=t.group),void 0!==t.x&&(this.x=t.x),void 0!==t.y&&(this.y=t.y),void 0!==t.value&&(this.value=t.value),void 0!==t.horizontalAlignLeft&&(this.horizontalAlignLeft=t.horizontalAlignLeft),void 0!==t.verticalAlignTop&&(this.verticalAlignTop=t.verticalAlignTop),void 0!==t.triggerFunction&&(this.triggerFunction=t.triggerFunction),void 0===this.id)throw"Node must have an id";if(this.group){var i=this.grouplist.get(this.group);for(var n in i)i.hasOwnProperty(n)&&(this[n]=i[n])}if(void 0!==t.shape&&(this.shape=t.shape),void 0!==t.image&&(this.image=t.image),void 0!==t.radius&&(this.radius=t.radius),void 0!==t.color&&(this.color=M.parseColor(t.color)),void 0!==t.fontColor&&(this.fontColor=t.fontColor),void 0!==t.fontSize&&(this.fontSize=t.fontSize),void 0!==t.fontFace&&(this.fontFace=t.fontFace),void 0!==this.image){if(!this.imagelist)throw"No imagelist provided";this.imageObj=this.imagelist.load(this.image)}switch(this.xFixed=this.xFixed||void 0!==t.x,this.yFixed=this.yFixed||void 0!==t.y,this.radiusFixed=this.radiusFixed||void 0!==t.radius,"image"==this.shape&&(this.radiusMin=e.nodes.widthMin,this.radiusMax=e.nodes.widthMax),this.shape){case"database":this.draw=this._drawDatabase,this.resize=this._resizeDatabase;break;case"box":this.draw=this._drawBox,this.resize=this._resizeBox;break;case"circle":this.draw=this._drawCircle,this.resize=this._resizeCircle;break;case"ellipse":this.draw=this._drawEllipse,this.resize=this._resizeEllipse;break;case"image":this.draw=this._drawImage,this.resize=this._resizeImage;break;case"text":this.draw=this._drawText,this.resize=this._resizeText;break;case"dot":this.draw=this._drawDot,this.resize=this._resizeShape;break;case"square":this.draw=this._drawSquare,this.resize=this._resizeShape;break;case"triangle":this.draw=this._drawTriangle,this.resize=this._resizeShape;break;case"triangleDown":this.draw=this._drawTriangleDown,this.resize=this._resizeShape;break;case"star":this.draw=this._drawStar,this.resize=this._resizeShape;break;default:this.draw=this._drawEllipse,this.resize=this._resizeEllipse}this._reset()}},M.parseColor=function(t){var e;return z.isString(t)?e={border:t,background:t,highlight:{border:t,background:t}}:(e={},e.background=t.background||"white",e.border=t.border||e.background,z.isString(t.highlight)?e.highlight={border:t.highlight,background:t.highlight}:(e.highlight={},e.highlight.background=t.highlight&&t.highlight.background||e.background,e.highlight.border=t.highlight&&t.highlight.border||e.border)),e},M.prototype.select=function(){this.selected=!0,this._reset()},M.prototype.unselect=function(){this.selected=!1,this._reset()},M.prototype.clearSizeCache=function(){this._reset()},M.prototype._reset=function(){this.width=void 0,this.height=void 0},M.prototype.getTitle=function(){return this.title},M.prototype.distanceToBorder=function(t,e){var i=1;switch(this.width||this.resize(t),this.shape){case"circle":case"dot":return this.radius+i;case"ellipse":var n=this.width/2,s=this.height/2,o=Math.sin(e)*n,r=Math.cos(e)*s;
-return n*s/Math.sqrt(o*o+r*r);case"box":case"image":case"text":default:return this.width?Math.min(Math.abs(this.width/2/Math.cos(e)),Math.abs(this.height/2/Math.sin(e)))+i:0}},M.prototype._setForce=function(t,e){this.fx=t,this.fy=e},M.prototype._addForce=function(t,e){this.fx+=t,this.fy+=e},M.prototype.discreteStep=function(t){if(!this.xFixed){var e=-this.damping*this.vx,i=(this.fx+e)/this.mass;this.vx+=i*t,this.x+=this.vx*t}if(!this.yFixed){var n=-this.damping*this.vy,s=(this.fy+n)/this.mass;this.vy+=s*t,this.y+=this.vy*t}},M.prototype.isFixed=function(){return this.xFixed&&this.yFixed},M.prototype.isMoving=function(t){return Math.abs(this.vx)>t||Math.abs(this.vy)>t?!0:(this.vx=0,this.vy=0,!1)},M.prototype.isSelected=function(){return this.selected},M.prototype.getValue=function(){return this.value},M.prototype.getDistance=function(t,e){var i=this.x-t,n=this.y-e;return Math.sqrt(i*i+n*n)},M.prototype.setValueRange=function(t,e){if(!this.radiusFixed&&void 0!==this.value)if(e==t)this.radius=(this.radiusMin+this.radiusMax)/2;else{var i=(this.radiusMax-this.radiusMin)/(e-t);this.radius=(this.value-t)*i+this.radiusMin}this.baseRadiusValue=this.radius},M.prototype.draw=function(){throw"Draw method not initialized for node"},M.prototype.resize=function(){throw"Resize method not initialized for node"},M.prototype.isOverlappingWith=function(t){return this.leftt.left&&this.topt.top},M.prototype._resizeImage=function(){if(!this.width||!this.height){var t,e;if(this.value){this.radius=this.baseRadiusValue;var i=this.imageObj.height/this.imageObj.width;void 0!==i?(t=this.radius||this.imageObj.width,e=this.radius*i||this.imageObj.height):(t=0,e=0)}else t=this.imageObj.width,e=this.imageObj.height;this.width=t,this.height=e,this.width>0&&this.height>0&&(this.width+=(this.clusterSize-1)*this.clusterSizeWidthFactor,this.height+=(this.clusterSize-1)*this.clusterSizeHeightFactor,this.radius+=(this.clusterSize-1)*this.clusterSizeRadiusFactor)}},M.prototype._drawImage=function(t){this._resizeImage(t),this.left=this.x-this.width/2,this.top=this.y-this.height/2;var e;if(0!=this.imageObj.width){if(this.clusterSize>1){var i=this.clusterSize>1?10:0;i*=this.graphScaleInv,i=Math.min(.2*this.width,i),t.globalAlpha=.5,t.drawImage(this.imageObj,this.left-i,this.top-i,this.width+2*i,this.height+2*i)}t.globalAlpha=1,t.drawImage(this.imageObj,this.left,this.top,this.width,this.height),e=this.y+this.height/2}else e=this.y;this._label(t,this.label,this.x,e,void 0,"top")},M.prototype._resizeBox=function(t){if(!this.width){var e=5,i=this.getTextSize(t);this.width=i.width+2*e,this.height=i.height+2*e,this.width+=.5*(this.clusterSize-1)*this.clusterSizeWidthFactor,this.height+=.5*(this.clusterSize-1)*this.clusterSizeHeightFactor}},M.prototype._drawBox=function(t){this._resizeBox(t),this.left=this.x-this.width/2,this.top=this.y-this.height/2;var e=2.5,i=2;t.strokeStyle=this.selected?this.color.highlight.border:this.color.border,this.clusterSize>1&&(t.lineWidth=(this.selected?i:1)+(this.clusterSize>1?e:0),t.lineWidth*=this.graphScaleInv,t.lineWidth=Math.min(.1*this.width,t.lineWidth),t.roundRect(this.left-2*t.lineWidth,this.top-2*t.lineWidth,this.width+4*t.lineWidth,this.height+4*t.lineWidth,this.radius),t.stroke()),t.lineWidth=(this.selected?i:1)+(this.clusterSize>1?e:0),t.lineWidth*=this.graphScaleInv,t.lineWidth=Math.min(.1*this.width,t.lineWidth),t.fillStyle=this.selected?this.color.highlight.background:this.color.background,t.roundRect(this.left,this.top,this.width,this.height,this.radius),t.fill(),t.stroke(),this._label(t,this.label,this.x,this.y)},M.prototype._resizeDatabase=function(t){if(!this.width){var e=5,i=this.getTextSize(t),n=i.width+2*e;this.width=n,this.height=n,this.width+=(this.clusterSize-1)*this.clusterSizeWidthFactor,this.height+=(this.clusterSize-1)*this.clusterSizeHeightFactor,this.radius+=(this.clusterSize-1)*this.clusterSizeRadiusFactor}},M.prototype._drawDatabase=function(t){this._resizeDatabase(t),this.left=this.x-this.width/2,this.top=this.y-this.height/2;var e=2.5,i=2;t.strokeStyle=this.selected?this.color.highlight.border:this.color.border,this.clusterSize>1&&(t.lineWidth=(this.selected?i:1)+(this.clusterSize>1?e:0),t.lineWidth*=this.graphScaleInv,t.lineWidth=Math.min(.1*this.width,t.lineWidth),t.database(this.x-this.width/2-2*t.lineWidth,this.y-.5*this.height-2*t.lineWidth,this.width+4*t.lineWidth,this.height+4*t.lineWidth),t.stroke()),t.lineWidth=(this.selected?i:1)+(this.clusterSize>1?e:0),t.lineWidth*=this.graphScaleInv,t.lineWidth=Math.min(.1*this.width,t.lineWidth),t.fillStyle=this.selected?this.color.highlight.background:this.color.background,t.database(this.x-this.width/2,this.y-.5*this.height,this.width,this.height),t.fill(),t.stroke(),this._label(t,this.label,this.x,this.y)},M.prototype._resizeCircle=function(t){if(!this.width){var e=5,i=this.getTextSize(t),n=Math.max(i.width,i.height)+2*e;this.radius=n/2,this.width=n,this.height=n,this.radius+=.5*(this.clusterSize-1)*this.clusterSizeRadiusFactor}},M.prototype._drawCircle=function(t){this._resizeCircle(t),this.left=this.x-this.width/2,this.top=this.y-this.height/2;var e=2.5,i=2;t.strokeStyle=this.selected?this.color.highlight.border:this.color.border,this.clusterSize>1&&(t.lineWidth=(this.selected?i:1)+(this.clusterSize>1?e:0),t.lineWidth*=this.graphScaleInv,t.lineWidth=Math.min(.1*this.width,t.lineWidth),t.circle(this.x,this.y,this.radius+2*t.lineWidth),t.stroke()),t.lineWidth=(this.selected?i:1)+(this.clusterSize>1?e:0),t.lineWidth*=this.graphScaleInv,t.lineWidth=Math.min(.1*this.width,t.lineWidth),t.fillStyle=this.selected?this.color.highlight.background:this.color.background,t.circle(this.x,this.y,this.radius),t.fill(),t.stroke(),this._label(t,this.label,this.x,this.y)},M.prototype._resizeEllipse=function(t){if(!this.width){var e=this.getTextSize(t);this.width=1.5*e.width,this.height=2*e.height,this.width1&&(t.lineWidth=(this.selected?i:1)+(this.clusterSize>1?e:0),t.lineWidth*=this.graphScaleInv,t.lineWidth=Math.min(.1*this.width,t.lineWidth),t.ellipse(this.left-2*t.lineWidth,this.top-2*t.lineWidth,this.width+4*t.lineWidth,this.height+4*t.lineWidth),t.stroke()),t.lineWidth=(this.selected?i:1)+(this.clusterSize>1?e:0),t.lineWidth*=this.graphScaleInv,t.lineWidth=Math.min(.1*this.width,t.lineWidth),t.fillStyle=this.selected?this.color.highlight.background:this.color.background,t.ellipse(this.left,this.top,this.width,this.height),t.fill(),t.stroke(),this._label(t,this.label,this.x,this.y)},M.prototype._drawDot=function(t){this._drawShape(t,"circle")},M.prototype._drawTriangle=function(t){this._drawShape(t,"triangle")},M.prototype._drawTriangleDown=function(t){this._drawShape(t,"triangleDown")},M.prototype._drawSquare=function(t){this._drawShape(t,"square")},M.prototype._drawStar=function(t){this._drawShape(t,"star")},M.prototype._resizeShape=function(){if(!this.width){this.radius=this.baseRadiusValue;var t=2*this.radius;this.width=t,this.height=t,this.width+=(this.clusterSize-1)*this.clusterSizeWidthFactor,this.height+=(this.clusterSize-1)*this.clusterSizeHeightFactor,this.radius+=.5*(this.clusterSize-1)*this.clusterSizeRadiusFactor}},M.prototype._drawShape=function(t,e){this._resizeShape(t),this.left=this.x-this.width/2,this.top=this.y-this.height/2;var i=2.5,n=2,s=2;switch(e){case"dot":s=2;break;case"square":s=2;break;case"triangle":s=3;break;case"triangleDown":s=3;break;case"star":s=4}t.strokeStyle=this.selected?this.color.highlight.border:this.color.border,this.clusterSize>1&&(t.lineWidth=(this.selected?n:1)+(this.clusterSize>1?i:0),t.lineWidth*=this.graphScaleInv,t.lineWidth=Math.min(.1*this.width,t.lineWidth),t[e](this.x,this.y,this.radius+s*t.lineWidth),t.stroke()),t.lineWidth=(this.selected?n:1)+(this.clusterSize>1?i:0),t.lineWidth*=this.graphScaleInv,t.lineWidth=Math.min(.1*this.width,t.lineWidth),t.fillStyle=this.selected?this.color.highlight.background:this.color.background,t[e](this.x,this.y,this.radius),t.fill(),t.stroke(),this.label&&this._label(t,this.label,this.x,this.y+this.height/2,void 0,"top")},M.prototype._resizeText=function(t){if(!this.width){var e=5,i=this.getTextSize(t);this.width=i.width+2*e,this.height=i.height+2*e,this.width+=(this.clusterSize-1)*this.clusterSizeWidthFactor,this.height+=(this.clusterSize-1)*this.clusterSizeHeightFactor,this.radius+=(this.clusterSize-1)*this.clusterSizeRadiusFactor}},M.prototype._drawText=function(t){this._resizeText(t),this.left=this.x-this.width/2,this.top=this.y-this.height/2,this._label(t,this.label,this.x,this.y)},M.prototype._label=function(t,e,i,n,s,o){if(e){t.font=(this.selected?"bold ":"")+this.fontSize+"px "+this.fontFace,t.fillStyle=this.fontColor||"black",t.textAlign=s||"center",t.textBaseline=o||"middle";for(var r=e.split("\n"),a=r.length,h=this.fontSize+4,d=n+(1-a)/2*h,c=0;a>c;c++)t.fillText(r[c],i,d),d+=h}},M.prototype.getTextSize=function(t){if(void 0!==this.label){t.font=(this.selected?"bold ":"")+this.fontSize+"px "+this.fontFace;for(var e=this.label.split("\n"),i=(this.fontSize+4)*e.length,n=0,s=0,o=e.length;o>s;s++)n=Math.max(n,t.measureText(e[s]).width);return{width:n,height:i}}return{width:0,height:0}},M.prototype.inArea=function(){return void 0!==this.width?this.x+this.width*this.graphScaleInv>=this.canvasTopLeft.x&&this.x-this.width*this.graphScaleInv=this.canvasTopLeft.y&&this.y-this.height*this.graphScaleInv=this.canvasTopLeft.x&&this.x=this.canvasTopLeft.y&&this.yh},D.prototype._drawLine=function(t){t.strokeStyle=this.color,t.lineWidth=this._getLineWidth();var e;if(this.from!=this.to)this._line(t),this.label&&(e=this._pointOnLine(.5),this._label(t,this.label,e.x,e.y));else{var i,n,s=this.length/4,o=this.from;o.width||o.resize(t),o.width>o.height?(i=o.x+o.width/2,n=o.y-s):(i=o.x+s,n=o.y-o.height/2),this._circle(t,i,n,s),e=this._pointOnCircle(i,n,s,.5),this._label(t,this.label,e.x,e.y)}},D.prototype._getLineWidth=function(){return this.from.selected||this.to.selected?Math.min(2*this.width,this.widthMax)*this.graphScaleInv:this.width*this.graphScaleInv},D.prototype._line=function(t){t.beginPath(),t.moveTo(this.from.x,this.from.y),t.lineTo(this.to.x,this.to.y),t.stroke()},D.prototype._circle=function(t,e,i,n){t.beginPath(),t.arc(e,i,n,0,2*Math.PI,!1),t.stroke()},D.prototype._label=function(t,e,i,n){if(e){t.font=(this.from.selected||this.to.selected?"bold ":"")+this.fontSize+"px "+this.fontFace,t.fillStyle="white";var s=t.measureText(e).width,o=this.fontSize,r=i-s/2,a=n-o/2;t.fillRect(r,a,s,o),t.fillStyle=this.fontColor||"black",t.textAlign="left",t.textBaseline="top",t.fillText(e,r,a)}},D.prototype._drawDashLine=function(t){if(t.strokeStyle=this.color,t.lineWidth=this._getLineWidth(),t.beginPath(),t.lineCap="round",void 0!==this.dash.altLength?t.dashedLine(this.from.x,this.from.y,this.to.x,this.to.y,[this.dash.length,this.dash.gap,this.dash.altLength,this.dash.gap]):void 0!==this.dash.length&&void 0!==this.dash.gap?t.dashedLine(this.from.x,this.from.y,this.to.x,this.to.y,[this.dash.length,this.dash.gap]):(t.moveTo(this.from.x,this.from.y),t.lineTo(this.to.x,this.to.y)),t.stroke(),this.label){var e=this._pointOnLine(.5);this._label(t,this.label,e.x,e.y)}},D.prototype._pointOnLine=function(t){return{x:(1-t)*this.from.x+t*this.to.x,y:(1-t)*this.from.y+t*this.to.y}},D.prototype._pointOnCircle=function(t,e,i,n){var s=2*(n-3/8)*Math.PI;return{x:t+i*Math.cos(s),y:e-i*Math.sin(s)}},D.prototype._drawArrowCenter=function(t){var e;if(t.strokeStyle=this.color,t.fillStyle=this.color,t.lineWidth=this._getLineWidth(),this.from!=this.to){this._line(t);var i=Math.atan2(this.to.y-this.from.y,this.to.x-this.from.x),n=10+5*this.width;e=this._pointOnLine(.5),t.arrow(e.x,e.y,i,n),t.fill(),t.stroke(),this.label&&(e=this._pointOnLine(.5),this._label(t,this.label,e.x,e.y))}else{var s,o,r=this.length/4,a=this.from;a.width||a.resize(t),a.width>a.height?(s=a.x+a.width/2,o=a.y-r):(s=a.x+r,o=a.y-a.height/2),this._circle(t,s,o,r);var i=.2*Math.PI,n=10+5*this.width;e=this._pointOnCircle(s,o,r,.5),t.arrow(e.x,e.y,i,n),t.fill(),t.stroke(),this.label&&(e=this._pointOnCircle(s,o,r,.5),this._label(t,this.label,e.x,e.y))}},D.prototype._drawArrow=function(t){t.strokeStyle=this.color,t.fillStyle=this.color,t.lineWidth=this._getLineWidth();var e,i;if(this.from!=this.to){e=Math.atan2(this.to.y-this.from.y,this.to.x-this.from.x);var n=this.to.x-this.from.x,s=this.to.y-this.from.y,o=Math.sqrt(n*n+s*s),r=this.from.distanceToBorder(t,e+Math.PI),a=(o-r)/o,h=a*this.from.x+(1-a)*this.to.x,d=a*this.from.y+(1-a)*this.to.y,c=this.to.distanceToBorder(t,e),l=(o-c)/o,u=(1-l)*this.from.x+l*this.to.x,p=(1-l)*this.from.y+l*this.to.y;if(t.beginPath(),t.moveTo(h,d),t.lineTo(u,p),t.stroke(),i=10+5*this.width,t.arrow(u,p,e,i),t.fill(),t.stroke(),this.label){var f=this._pointOnLine(.5);this._label(t,this.label,f.x,f.y)}}else{var g,m,v,y=this.from,_=this.length/4;y.width||y.resize(t),y.width>y.height?(g=y.x+y.width/2,m=y.y-_,v={x:g,y:y.y,angle:.9*Math.PI}):(g=y.x+_,m=y.y-y.height/2,v={x:y.x,y:m,angle:.6*Math.PI}),t.beginPath(),t.arc(g,m,_,0,2*Math.PI,!1),t.stroke(),i=10+5*this.width,t.arrow(v.x,v.y,v.angle,i),t.fill(),t.stroke(),this.label&&(f=this._pointOnCircle(g,m,_,.5),this._label(t,this.label,f.x,f.y))}},D._dist=function(t,e,i,n,s,o){var r=i-t,a=n-e,h=r*r+a*a,d=((s-t)*r+(o-e)*a)/h;d>1?d=1:0>d&&(d=0);var c=t+d*r,l=e+d*a,u=c-s,p=l-o;return Math.sqrt(u*u+p*p)},D.prototype.setScale=function(t){this.graphScaleInv=1/t},I.prototype.setPosition=function(t,e){this.x=parseInt(t),this.y=parseInt(e)},I.prototype.setText=function(t){this.frame.innerHTML=t},I.prototype.show=function(t){if(void 0===t&&(t=!0),t){var e=this.frame.clientHeight,i=this.frame.clientWidth,n=this.frame.parentNode.clientHeight,s=this.frame.parentNode.clientWidth,o=this.y-e;o+e+this.padding>n&&(o=n-e-this.padding),os&&(r=s-i-this.padding),r1)return this.activeSector[this.activeSector.length-2];throw new TypeError("there are not enough sectors in the this.activeSector array.")},_setActiveSector:function(t){this.activeSector.push(t)},_forgetLastSector:function(){this.activeSector.pop()},_createNewSector:function(t){this.sectors.active[t]={nodes:{},edges:{},nodeIndices:[],formationScale:this.scale,drawingNode:void 0},this.sectors.active[t].drawingNode=new M({id:t,color:{background:"#eaefef",border:"495c5e"}},{},{},this.constants),this.sectors.active[t].drawingNode.clusterSize=2},_deleteActiveSector:function(t){delete this.sectors.active[t]},_deleteFrozenSector:function(t){delete this.sectors.frozen[t]},_freezeSector:function(t){this.sectors.frozen[t]=this.sectors.active[t],this._deleteActiveSector(t)},_activateSector:function(t){this.sectors.active[t]=this.sectors.frozen[t],this._deleteFrozenSector(t)},_mergeThisWithFrozen:function(t){for(var e in this.nodes)this.nodes.hasOwnProperty(e)&&(this.sectors.frozen[t].nodes[e]=this.nodes[e]);for(var i in this.edges)this.edges.hasOwnProperty(i)&&(this.sectors.frozen[t].edges[i]=this.edges[i]);for(var n=0;n1?this[t](n[0],n[1]):this[t](e)}this._loadLatestSector()},_doInAllFrozenSectors:function(t,e){if(void 0===e)for(var i in this.sectors.frozen)this.sectors.frozen.hasOwnProperty(i)&&(this._switchToFrozenSector(i),this[t]());else for(var i in this.sectors.frozen)if(this.sectors.frozen.hasOwnProperty(i)){this._switchToFrozenSector(i);var n=Array.prototype.splice.call(arguments,1);n.length>1?this[t](n[0],n[1]):this[t](e)}this._loadLatestSector()},_doInNavigationSector:function(t,e){if(this._switchToNavigationSector(),void 0===e)this[t]();else{var i=Array.prototype.splice.call(arguments,1);i.length>1?this[t](i[0],i[1]):this[t](e)}this._loadLatestSector()},_doInAllSectors:function(t,e){var i=Array.prototype.splice.call(arguments,1);void 0===e?(this._doInAllActiveSectors(t),this._doInAllFrozenSectors(t)):i.length>1?(this._doInAllActiveSectors(t,i[0],i[1]),this._doInAllFrozenSectors(t,i[0],i[1])):(this._doInAllActiveSectors(t,e),this._doInAllFrozenSectors(t,e))},_clearNodeIndexList:function(){var t=this._sector();this.sectors.active[t].nodeIndices=[],this.nodeIndices=this.sectors.active[t].nodeIndices},_drawSectorNodes:function(t,e){var i,n=1e9,s=-1e9,o=1e9,r=-1e9;for(var a in this.sectors[e])if(this.sectors[e].hasOwnProperty(a)&&void 0!==this.sectors[e][a].drawingNode){this._switchToSector(a,e),n=1e9,s=-1e9,o=1e9,r=-1e9;for(var h in this.nodes)this.nodes.hasOwnProperty(h)&&(i=this.nodes[h],i.resize(t),o>i.x-.5*i.width&&(o=i.x-.5*i.width),ri.y-.5*i.height&&(n=i.y-.5*i.height),st&&n>s;)s%3==0?this.forceAggregateHubs():this.increaseClusterLevel(),i=this.nodeIndices.length,s+=1;s>1&&1==e&&this.repositionNodes()},openCluster:function(t){var e=this.moving;if(t.clusterSize>this.constants.clustering.sectorThreshold&&this._nodeInActiveArea(t)&&("default"!=this._sector()||1!=this.nodeIndices.length)){this._addSector(t);for(var i=0;this.nodeIndices.lengthi;)this.decreaseClusterLevel(),i+=1}else this._expandClusterNode(t,!1,!0),this._updateNodeIndexList(),this._updateDynamicEdges(),this.updateLabels();this.moving!=e&&this.start()},updateClustersDefault:function(){1==this.constants.clustering.enabled&&this.updateClusters(0,!1,!1)},increaseClusterLevel:function(){this.updateClusters(-1,!1,!0)},decreaseClusterLevel:function(){this.updateClusters(1,!1,!0)},updateClusters:function(t,e,i){var n=this.moving,s=this.nodeIndices.length;this.previousScale>this.scale&&0==t&&this._collapseSector(),this.previousScale>this.scale||-1==t?this._formClusters(i):(this.previousScalethis.scale||-1==t)&&(this._aggregateHubs(i),this._updateNodeIndexList()),(this.previousScale>this.scale||-1==t)&&(this.handleChains(),this._updateNodeIndexList()),this.previousScale=this.scale,this._updateDynamicEdges(),this.updateLabels(),this.nodeIndices.lengththis.constants.clustering.chainThreshold&&this._reduceAmountOfChains(1-this.constants.clustering.chainThreshold/t)},_aggregateHubs:function(t){this._getHubSize(),this._formClustersByHub(t,!1)},forceAggregateHubs:function(){var t=this.moving,e=this.nodeIndices.length;this._aggregateHubs(!0),this._updateNodeIndexList(),this._updateDynamicEdges(),this.updateLabels(),this.nodeIndices.length!=e&&(this.clusterSession+=1),this.moving!=t&&this.start()},_openClustersBySize:function(){for(var t in this.nodes)if(this.nodes.hasOwnProperty(t)){var e=this.nodes[t];1==e.inView()&&(e.width*this.scale>this.constants.clustering.screenSizeThreshold*this.frame.canvas.clientWidth||e.height*this.scale>this.constants.clustering.screenSizeThreshold*this.frame.canvas.clientHeight)&&this.openCluster(e)}},_openClusters:function(t,e){for(var i=0;i1&&(t.clusterSizei)){var r=o.from,a=o.to;o.to.mass>o.from.mass&&(r=o.to,a=o.from),1==a.dynamicEdgesLength?this._addToCluster(r,a,!1):1==r.dynamicEdgesLength&&this._addToCluster(a,r,!1)}}},_forceClustersByZoom:function(){for(var t in this.nodes)if(this.nodes.hasOwnProperty(t)){var e=this.nodes[t];if(1==e.dynamicEdgesLength&&0!=e.dynamicEdges.length){var i=e.dynamicEdges[0],n=i.toId==e.id?this.nodes[i.fromId]:this.nodes[i.toId];e.id!=n.id&&(n.mass>e.mass?this._addToCluster(n,e,!0):this._addToCluster(e,n,!0))}}},_formClustersByHub:function(t,e){for(var i in this.nodes)this.nodes.hasOwnProperty(i)&&this._formClusterFromHub(this.nodes[i],t,e)},_formClusterFromHub:function(t,e,i,n){if(void 0===n&&(n=0),t.dynamicEdgesLength>=this.hubThreshold&&0==i||t.dynamicEdgesLength==this.hubThreshold&&1==i){for(var s,o,r,a=this.constants.clustering.clusterEdgeThreshold/this.scale,h=!1,d=[],c=t.dynamicEdges.length,l=0;c>l;l++)d.push(t.dynamicEdges[l].id);if(0==e)for(h=!1,l=0;c>l;l++){var u=this.edges[d[l]];if(void 0!==u&&u.connected&&u.toId!=u.fromId&&(s=u.to.x-u.from.x,o=u.to.y-u.from.y,r=Math.sqrt(s*s+o*o),a>r)){h=!0;break}}if(!e&&h||e)for(l=0;c>l;l++)if(u=this.edges[d[l]],void 0!==u){var p=this.nodes[u.fromId==t.id?u.toId:u.fromId];p.dynamicEdges.length<=this.hubThreshold+n&&p.id!=t.id&&this._addToCluster(t,p,e)}}},_addToCluster:function(t,e,i){t.containedNodes[e.id]=e;for(var n=0;n1)for(var n=0;n1&&(e.label="[".concat(String(e.clusterSize),"]"))}for(t in this.nodes)this.nodes.hasOwnProperty(t)&&(e=this.nodes[t],1==e.clusterSize&&(e.label=void 0!==e.originalLabel?e.originalLabel:String(e.id)))},_nodeInActiveArea:function(t){return Math.abs(t.x-this.areaCenter.x)<=this.constants.clustering.activeAreaBoxSize/this.scale&&Math.abs(t.y-this.areaCenter.y)<=this.constants.clustering.activeAreaBoxSize/this.scale},repositionNodes:function(){for(var t=0;tn&&(n=o.dynamicEdgesLength),t+=o.dynamicEdgesLength,e+=Math.pow(o.dynamicEdgesLength,2),i+=1}t/=i,e/=i;var r=e-Math.pow(t,2),a=Math.sqrt(r);this.hubThreshold=Math.floor(t+2*a),this.hubThreshold>n&&(this.hubThreshold=n)},_reduceAmountOfChains:function(t){this.hubThreshold=2;var e=Math.floor(this.nodeIndices.length*t);for(var i in this.nodes)this.nodes.hasOwnProperty(i)&&2==this.nodes[i].dynamicEdgesLength&&this.nodes[i].dynamicEdges.length>=2&&e>0&&(this._formClusterFromHub(this.nodes[i],!0,!0,1),e-=1)},_getChainFraction:function(){var t=0,e=0;for(var i in this.nodes)this.nodes.hasOwnProperty(i)&&(2==this.nodes[i].dynamicEdgesLength&&this.nodes[i].dynamicEdges.length>=2&&(t+=1),e+=1);return t/e}},W={_getNodesOverlappingWith:function(t,e){var i=this.nodes;for(var n in i)i.hasOwnProperty(n)&&i[n].isOverlappingWith(t)&&e.push(n)},_getAllNodesOverlappingWith:function(t){var e=[];return this._doInAllActiveSectors("_getNodesOverlappingWith",t,e),e},_getAllNavigationNodesOverlappingWith:function(t){var e=[];return this._doInNavigationSector("_getNodesOverlappingWith",t,e),e},_pointerToPositionObject:function(t){var e=this._canvasToX(t.x),i=this._canvasToY(t.y);return{left:e,top:i,right:e,bottom:i}},_pointerToScreenPositionObject:function(t){var e=t.x,i=t.y;return{left:e,top:i,right:e,bottom:i}},_getNavigationNodeAt:function(t){var e=this._pointerToScreenPositionObject(t),i=this._getAllNavigationNodesOverlappingWith(e);return i.length>0?this.sectors.navigation.nodes[i[i.length-1]]:null},_getNodeAt:function(t){var e=this._pointerToPositionObject(t);return overlappingNodes=this._getAllNodesOverlappingWith(e),overlappingNodes.length>0?this.nodes[overlappingNodes[overlappingNodes.length-1]]:null},_getEdgeAt:function(){return null},_addToSelection:function(t){this.selection.push(t.id),this.selectionObj[t.id]=t},_removeFromSelection:function(t){for(var e=0;ee;e++){n=t[e];var s=this.nodes[n];if(!s)throw new RangeError('Node with id "'+n+'" not found');this._selectNode(s,!0,!0)}this.redraw()},_updateSelection:function(){for(var t=0;tt.x-t.width&&(n=t.x-t.width),st.y-t.height&&(e=t.y-t.height),i=this.constants.clustering.initialMaxNodes)var n=38.8467/(e-14.50184)+.0116;else var n=42.54117319/(e+39.31966387)+.1944405;else{var s=1.1*(Math.abs(i.minX)+Math.abs(i.maxX)),o=1.1*(Math.abs(i.minY)+Math.abs(i.maxY)),r=this.frame.canvas.clientWidth/s,a=this.frame.canvas.clientHeight/o;n=a>=r?r:a}n>1&&(n=1),this.pinch.mousewheelScale=n,this._setScale(n),this._centerGraph(i),this.start()},O.prototype._updateNodeIndexList=function(){this._clearNodeIndexList();for(var t in this.nodes)this.nodes.hasOwnProperty(t)&&this.nodeIndices.push(t)},O.prototype.setData=function(t,e){if(void 0===e&&(e=!1),t&&t.dot&&(t.nodes||t.edges))throw new SyntaxError('Data must contain either parameter "dot" or parameter pair "nodes" and "edges", but not both.');if(this.setOptions(t&&t.options),t&&t.dot){if(t&&t.dot){var i=U.util.DOTToGraph(t.dot);return void this.setData(i)}}else this._setNodes(t&&t.nodes),this._setEdges(t&&t.edges);this._putDataInSector(),e||(this.stabilize&&this._doStabilize(),this.moving=!0,this.start())},O.prototype.setOptions=function(t){if(t){if(void 0!==t.width&&(this.width=t.width),void 0!==t.height&&(this.height=t.height),void 0!==t.stabilize&&(this.stabilize=t.stabilize),void 0!==t.selectable&&(this.selectable=t.selectable),t.clustering){this.constants.clustering.enabled=!0;for(var e in t.clustering)t.clustering.hasOwnProperty(e)&&(this.constants.clustering[e]=t.clustering[e])}else void 0!==t.clustering&&(this.constants.clustering.enabled=!1);if(t.navigation){this.constants.navigation.enabled=!0;for(var e in t.navigation)t.navigation.hasOwnProperty(e)&&(this.constants.navigation[e]=t.navigation[e])}else void 0!==t.navigation&&(this.constants.navigation.enabled=!1);if(t.keyboard){this.constants.keyboard.enabled=!0;for(var e in t.keyboard)t.keyboard.hasOwnProperty(e)&&(this.constants.keyboard[e]=t.keyboard[e])}else void 0!==t.keyboard&&(this.constants.keyboard.enabled=!1);if(t.edges){for(e in t.edges)t.edges.hasOwnProperty(e)&&(this.constants.edges[e]=t.edges[e]);void 0!==t.edges.length&&t.nodes&&void 0===t.nodes.distance&&(this.constants.edges.length=t.edges.length,this.constants.nodes.distance=1.25*t.edges.length),t.edges.fontColor||(this.constants.edges.fontColor=t.edges.color),t.edges.dash&&(void 0!==t.edges.dash.length&&(this.constants.edges.dash.length=t.edges.dash.length),void 0!==t.edges.dash.gap&&(this.constants.edges.dash.gap=t.edges.dash.gap),void 0!==t.edges.dash.altLength&&(this.constants.edges.dash.altLength=t.edges.dash.altLength))}if(t.nodes){for(e in t.nodes)t.nodes.hasOwnProperty(e)&&(this.constants.nodes[e]=t.nodes[e]);t.nodes.color&&(this.constants.nodes.color=M.parseColor(t.nodes.color))}if(t.groups)for(var i in t.groups)if(t.groups.hasOwnProperty(i)){var n=t.groups[i];this.groups.add(i,n)}}this.setSize(this.width,this.height),this._setTranslation(this.frame.clientWidth/2,this.frame.clientHeight/2),this._setScale(1),this._loadNavigationControls(),this._createKeyBinds(),this._redraw()},O.prototype.on=function(t,e){var i=["select"];if(-1==i.indexOf(t))throw new Error('Unknown event "'+t+'". Choose from '+i.join());F.addListener(this,t,e)},O.prototype.off=function(t,e){F.removeListener(this,t,e)},O.prototype._trigger=function(t,e){F.trigger(this,t,e)},O.prototype._create=function(){for(;this.containerElement.hasChildNodes();)this.containerElement.removeChild(this.containerElement.firstChild);if(this.frame=document.createElement("div"),this.frame.className="graph-frame",this.frame.style.position="relative",this.frame.style.overflow="hidden",this.frame.canvas=document.createElement("canvas"),this.frame.canvas.style.position="relative",this.frame.appendChild(this.frame.canvas),!this.frame.canvas.getContext){var t=document.createElement("DIV");t.style.color="red",t.style.fontWeight="bold",t.style.padding="10px",t.innerHTML="Error: your browser does not support HTML canvas",this.frame.canvas.appendChild(t)}var e=this;this.drag={},this.pinch={},this.hammer=N(this.frame.canvas,{prevent_default:!0}),this.hammer.on("tap",e._onTap.bind(e)),this.hammer.on("doubletap",e._onDoubleTap.bind(e)),this.hammer.on("hold",e._onHold.bind(e)),this.hammer.on("pinch",e._onPinch.bind(e)),this.hammer.on("touch",e._onTouch.bind(e)),this.hammer.on("dragstart",e._onDragStart.bind(e)),this.hammer.on("drag",e._onDrag.bind(e)),this.hammer.on("dragend",e._onDragEnd.bind(e)),this.hammer.on("release",e._onRelease.bind(e)),this.hammer.on("mousewheel",e._onMouseWheel.bind(e)),this.hammer.on("DOMMouseScroll",e._onMouseWheel.bind(e)),this.hammer.on("mousemove",e._onMouseMoveTitle.bind(e)),this.containerElement.appendChild(this.frame)},O.prototype._createKeyBinds=function(){var t=this;this.mousetrap=A,this.mousetrap.reset(),1==this.constants.keyboard.enabled&&(this.mousetrap.bind("up",this._moveUp.bind(t),"keydown"),this.mousetrap.bind("up",this._yStopMoving.bind(t),"keyup"),this.mousetrap.bind("down",this._moveDown.bind(t),"keydown"),this.mousetrap.bind("down",this._yStopMoving.bind(t),"keyup"),this.mousetrap.bind("left",this._moveLeft.bind(t),"keydown"),this.mousetrap.bind("left",this._xStopMoving.bind(t),"keyup"),this.mousetrap.bind("right",this._moveRight.bind(t),"keydown"),this.mousetrap.bind("right",this._xStopMoving.bind(t),"keyup"),this.mousetrap.bind("=",this._zoomIn.bind(t),"keydown"),this.mousetrap.bind("=",this._stopZoom.bind(t),"keyup"),this.mousetrap.bind("-",this._zoomOut.bind(t),"keydown"),this.mousetrap.bind("-",this._stopZoom.bind(t),"keyup"),this.mousetrap.bind("[",this._zoomIn.bind(t),"keydown"),this.mousetrap.bind("[",this._stopZoom.bind(t),"keyup"),this.mousetrap.bind("]",this._zoomOut.bind(t),"keydown"),this.mousetrap.bind("]",this._stopZoom.bind(t),"keyup"),this.mousetrap.bind("pageup",this._zoomIn.bind(t),"keydown"),this.mousetrap.bind("pageup",this._stopZoom.bind(t),"keyup"),this.mousetrap.bind("pagedown",this._zoomOut.bind(t),"keydown"),this.mousetrap.bind("pagedown",this._stopZoom.bind(t),"keyup"))},O.prototype._getPointer=function(t){return{x:t.pageX-U.util.getAbsoluteLeft(this.frame.canvas),y:t.pageY-U.util.getAbsoluteTop(this.frame.canvas)}},O.prototype._onTouch=function(t){this.drag.pointer=this._getPointer(t.gesture.touches[0]),this.drag.pinched=!1,this.pinch.scale=this._getScale(),this._handleTouch(this.drag.pointer)},O.prototype._onDragStart=function(){var t=this.drag,e=this._getNodeAt(t.pointer);if(t.dragging=!0,t.selection=[],t.translation=this._getTranslation(),t.nodeId=null,null!=e){t.nodeId=e.id,e.isSelected()||this._selectNode(e,!1);var i=this;this.selection.forEach(function(e){var n=i.nodes[e];if(n){var s={id:e,node:n,x:n.x,y:n.y,xFixed:n.xFixed,yFixed:n.yFixed};n.xFixed=!0,n.yFixed=!0,t.selection.push(s)}})}},O.prototype._onDrag=function(t){if(!this.drag.pinched){var e=this._getPointer(t.gesture.touches[0]),i=this,n=this.drag,s=n.selection;if(s&&s.length){var o=e.x-n.pointer.x,r=e.y-n.pointer.y;s.forEach(function(t){var e=t.node;t.xFixed||(e.x=i._canvasToX(i._xToCanvas(t.x)+o)),t.yFixed||(e.y=i._canvasToY(i._yToCanvas(t.y)+r))}),this.moving||(this.moving=!0,this.start())}else{var a=e.x-this.drag.pointer.x,h=e.y-this.drag.pointer.y;this._setTranslation(this.drag.translation.x+a,this.drag.translation.y+h),this._redraw(),this.moved=!0}}},O.prototype._onDragEnd=function(){this.drag.dragging=!1;var t=this.drag.selection;t&&t.forEach(function(t){t.node.xFixed=t.xFixed,t.node.yFixed=t.yFixed})},O.prototype._onTap=function(t){var e=this._getPointer(t.gesture.touches[0]);this._handleTap(e)},O.prototype._onDoubleTap=function(t){var e=this._getPointer(t.gesture.touches[0]);this._handleDoubleTap(e)},O.prototype._onHold=function(t){var e=this._getPointer(t.gesture.touches[0]);this._handleOnHold(e)},O.prototype._onRelease=function(){this._handleOnRelease()},O.prototype._onPinch=function(t){var e=this._getPointer(t.gesture.center);this.drag.pinched=!0,"scale"in this.pinch||(this.pinch.scale=1);var i=this.pinch.scale*t.gesture.scale;this._zoom(i,e)},O.prototype._zoom=function(t,e){var i=this._getScale();1e-5>t&&(t=1e-5),t>10&&(t=10);var n=this._getTranslation(),s=t/i,o=(1-s)*e.x+n.x*s,r=(1-s)*e.y+n.y*s;return this.areaCenter={x:this._canvasToX(e.x),y:this._canvasToY(e.y)},this.pinch.mousewheelScale=t,this._setScale(t),this._setTranslation(o,r),this.updateClustersDefault(),this._redraw(),t},O.prototype._onMouseWheel=function(t){var e=0;if(t.wheelDelta?e=t.wheelDelta/120:t.detail&&(e=-t.detail/3),e){"mousewheelScale"in this.pinch||(this.pinch.mousewheelScale=1);var i=this.pinch.mousewheelScale,n=e/10;0>e&&(n/=1-n),i*=1+n;var s=z.fakeGesture(this,t),o=this._getPointer(s.center);i=this._zoom(i,o)}t.preventDefault()},O.prototype._onMouseMoveTitle=function(t){var e=z.fakeGesture(this,t),i=this._getPointer(e.center);this.popupNode&&this._checkHidePopup(i);var n=this,s=function(){n._checkShowPopup(i)};this.popupTimer&&clearInterval(this.popupTimer),this.drag.dragging||(this.popupTimer=setTimeout(s,300))},O.prototype._checkShowPopup=function(t){var e,i={left:this._canvasToX(t.x),top:this._canvasToY(t.y),right:this._canvasToX(t.x),bottom:this._canvasToY(t.y)},n=this.popupNode;if(void 0==this.popupNode){var s=this.nodes;for(e in s)if(s.hasOwnProperty(e)){var o=s[e];if(void 0!==o.getTitle()&&o.isOverlappingWith(i)){this.popupNode=o;break}}}if(void 0===this.popupNode){var r=this.edges;for(e in r)if(r.hasOwnProperty(e)){var a=r[e];if(a.connected&&void 0!==a.getTitle()&&a.isOverlappingWith(i)){this.popupNode=a;break}}}if(this.popupNode){if(this.popupNode!=n){var h=this;h.popup||(h.popup=new I(h.frame)),h.popup.setPosition(t.x-3,t.y-3),h.popup.setText(h.popupNode.getTitle()),h.popup.show()}}else this.popup&&this.popup.hide()},O.prototype._checkHidePopup=function(t){this.popupNode&&this._getNodeAt(t)||(this.popupNode=void 0,this.popup&&this.popup.hide())},O.prototype._getConnectionCount=function(t){function e(t){for(var e=[],i=0,n=t.length;n>i;i++)for(var s=t[i],o=s.edges,r=0,a=o.length;a>r;r++){var h=o[r],d=null;h.from==s?d=h.to:h.to==s&&(d=h.from);var c,l;if(d)for(c=0,l=t.length;l>c;c++)if(t[c]==d){d=null;break}if(d)for(c=0,l=e.length;l>c;c++)if(e[c]==d){d=null;break}d&&e.push(d)}return e}void 0==t&&(t=1);var i=[],n=this.nodes;for(var s in n)if(n.hasOwnProperty(s)){for(var o=[n[s]],r=0;t>r;r++)o=o.concat(e(o));i.push(o)}for(var a=[],h=0,d=i.length;d>h;h++)a.push(i[h].length);return a},O.prototype.setSize=function(t,e){this.frame.style.width=t,this.frame.style.height=e,this.frame.canvas.style.width="100%",this.frame.canvas.style.height="100%",this.frame.canvas.width=this.frame.canvas.clientWidth,this.frame.canvas.height=this.frame.canvas.clientHeight,1==this.constants.navigation.enabled&&this._relocateNavigation()},O.prototype._setNodes=function(t){var e=this.nodesData;if(t instanceof o||t instanceof r)this.nodesData=t;else if(t instanceof Array)this.nodesData=new o,this.nodesData.add(t);else{if(t)throw new TypeError("Array or DataSet expected");this.nodesData=new o}if(e&&z.forEach(this.nodesListeners,function(t,i){e.unsubscribe(i,t)}),this.nodes={},this.nodesData){var i=this;z.forEach(this.nodesListeners,function(t,e){i.nodesData.subscribe(e,t)});var n=this.nodesData.getIds();this._addNodes(n)}this._updateSelection()},O.prototype._addNodes=function(t){for(var e,i=0,n=t.length;n>i;i++){e=t[i];var s=this.nodesData.get(e),o=new M(s,this.images,this.groups,this.constants);if(this.nodes[e]=o,!o.isFixed()){var r=2*this.constants.edges.length,a=t.length,h=2*Math.PI*(i/a);o.x=r*Math.cos(h),o.y=r*Math.sin(h),this.moving=!0}}this._updateNodeIndexList(),this._reconnectEdges(),this._updateValueRange(this.nodes)},O.prototype._updateNodes=function(t){for(var e=this.nodes,i=this.nodesData,n=0,s=t.length;s>n;n++){var o=t[n],r=e[o],a=i.get(o);r?r.setProperties(a,this.constants):(r=new M(properties,this.images,this.groups,this.constants),e[o]=r,r.isFixed()||(this.moving=!0))}this._updateNodeIndexList(),this._reconnectEdges(),this._updateValueRange(e)},O.prototype._removeNodes=function(t){for(var e=this.nodes,i=0,n=t.length;n>i;i++){var s=t[i];delete e[s]}this._updateNodeIndexList(),this._reconnectEdges(),this._updateSelection(),this._updateValueRange(e)},O.prototype._setEdges=function(t){var e=this.edgesData;if(t instanceof o||t instanceof r)this.edgesData=t;else if(t instanceof Array)this.edgesData=new o,this.edgesData.add(t);else{if(t)throw new TypeError("Array or DataSet expected");this.edgesData=new o}if(e&&z.forEach(this.edgesListeners,function(t,i){e.unsubscribe(i,t)}),this.edges={},this.edgesData){var i=this;z.forEach(this.edgesListeners,function(t,e){i.edgesData.subscribe(e,t)});var n=this.edgesData.getIds();this._addEdges(n)}this._reconnectEdges()},O.prototype._addEdges=function(t){for(var e=this.edges,i=this.edgesData,n=0,s=t.length;s>n;n++){var o=t[n],r=e[o];r&&r.disconnect();var a=i.get(o,{showInternalIds:!0});e[o]=new D(a,this,this.constants)}this.moving=!0,this._updateValueRange(e)},O.prototype._updateEdges=function(t){for(var e=this.edges,i=this.edgesData,n=0,s=t.length;s>n;n++){var o=t[n],r=i.get(o),a=e[o];a?(a.disconnect(),a.setProperties(r,this.constants),a.connect()):(a=new D(r,this,this.constants),this.edges[o]=a)}this.moving=!0,this._updateValueRange(e)},O.prototype._removeEdges=function(t){for(var e=this.edges,i=0,n=t.length;n>i;i++){var s=t[i],o=e[s];o&&(o.disconnect(),delete e[s])}this.moving=!0,this._updateValueRange(e)},O.prototype._reconnectEdges=function(){var t,e=this.nodes,i=this.edges;for(t in e)e.hasOwnProperty(t)&&(e[t].edges=[]);for(t in i)if(i.hasOwnProperty(t)){var n=i[t];n.from=null,n.to=null,n.connect()}},O.prototype._updateValueRange=function(t){var e,i=void 0,n=void 0;for(e in t)if(t.hasOwnProperty(e)){var s=t[e].getValue();void 0!==s&&(i=void 0===i?s:Math.min(s,i),n=void 0===n?s:Math.max(s,n))}if(void 0!==i&&void 0!==n)for(e in t)t.hasOwnProperty(e)&&t[e].setValueRange(i,n)},O.prototype.redraw=function(){this.setSize(this.width,this.height),this._redraw()},O.prototype._redraw=function(){var t=this.frame.canvas.getContext("2d"),e=this.frame.canvas.width,i=this.frame.canvas.height;t.clearRect(0,0,e,i),t.save(),t.translate(this.translation.x,this.translation.y),t.scale(this.scale,this.scale),this.canvasTopLeft={x:this._canvasToX(0),y:this._canvasToY(0)},this.canvasBottomRight={x:this._canvasToX(this.frame.canvas.clientWidth),y:this._canvasToY(this.frame.canvas.clientHeight)},this._doInAllSectors("_drawAllSectorNodes",t),this._doInAllSectors("_drawEdges",t),this._doInAllSectors("_drawNodes",t),t.restore(),1==this.constants.navigation.enabled&&this._doInNavigationSector("_drawNodes",t,!0)},O.prototype._setTranslation=function(t,e){void 0===this.translation&&(this.translation={x:0,y:0}),void 0!==t&&(this.translation.x=t),void 0!==e&&(this.translation.y=e)},O.prototype._getTranslation=function(){return{x:this.translation.x,y:this.translation.y}},O.prototype._setScale=function(t){this.scale=t},O.prototype._getScale=function(){return this.scale},O.prototype._canvasToX=function(t){return(t-this.translation.x)/this.scale},O.prototype._xToCanvas=function(t){return t*this.scale+this.translation.x},O.prototype._canvasToY=function(t){return(t-this.translation.y)/this.scale},O.prototype._yToCanvas=function(t){return t*this.scale+this.translation.y},O.prototype._drawNodes=function(t,e){void 0===e&&(e=!1);var i=this.nodes,n=[];for(var s in i)i.hasOwnProperty(s)&&(i[s].setScaleAndPos(this.scale,this.canvasTopLeft,this.canvasBottomRight),i[s].isSelected()?n.push(s):(i[s].inArea()||e)&&i[s].draw(t));for(var o=0,r=n.length;r>o;o++)(i[n[o]].inArea()||e)&&i[n[o]].draw(t)},O.prototype._drawEdges=function(t){var e=this.edges;for(var i in e)if(e.hasOwnProperty(i)){var n=e[i];n.setScale(this.scale),n.connected&&e[i].draw(t)}},O.prototype._doStabilize=function(){for(var t=0,e=this.constants.minVelocity,i=!1;!i&&tthis.constants.clustering.clusterThreshold&&1==this.constants.clustering.enabled&&this.clusterToFit(this.constants.clustering.reduceToNodes,!1),this._calculateForces())},O.prototype._calculateForces=function(){var t,e,i,n,s,o,r,a,h,d,c,l,u,p,f,g,m,v,y=this.nodes,_=this.edges,w=.08*this.forceFactor;for(g=0;gn&&(i=Math.atan2(e,t),r=.5*b>n?1:1/(1+Math.exp((n/b-1)*S)),r*=0==v?1:1+v*this.constants.clustering.forceAmplification,r*=this.forceFactor,s=Math.cos(i)*r,o=Math.sin(i)*r,l._addForce(-s,-o),u._addForce(s,o));for(f in _)_.hasOwnProperty(f)&&(p=_[f],p.connected&&this.nodes.hasOwnProperty(p.toId)&&this.nodes.hasOwnProperty(p.fromId)&&(v=p.to.clusterSize+p.from.clusterSize-2,t=p.to.x-p.from.x,e=p.to.y-p.from.y,d=p.length,d+=v*this.constants.clustering.edgeGrowth,h=Math.sqrt(t*t+e*e),i=Math.atan2(e,t),a=p.stiffness*(d-h)*this.forceFactor,s=Math.cos(i)*a,o=Math.sin(i)*a,p.from._addForce(-s,-o),p.to._addForce(s,o)))},O.prototype._isMoving=function(t){var e=t/this.scale,i=this.nodes;for(var n in i)if(i.hasOwnProperty(n)&&i[n].isMoving(e))return!0;return!1},O.prototype._discreteStepNodes=function(){var t=.01,e=this.nodes;for(var i in e)e.hasOwnProperty(i)&&e[i].discreteStep(t);var n=this.constants.minVelocity;this.moving=this._isMoving(n)},O.prototype.start=function(){if(!this.freezeSimulation)if(this.moving&&(this._doInAllActiveSectors("_initializeForceCalculation"),this._doInAllActiveSectors("_discreteStepNodes"),this._findCenter(this._getRange())),this.moving||0!=this.xIncrement||0!=this.yIncrement||0!=this.zoomIncrement){if(!this.timer){var t=this;this.timer=window.setTimeout(function(){if(t.timer=void 0,0!=t.xIncrement||0!=t.yIncrement){var e=t._getTranslation();t._setTranslation(e.x+t.xIncrement,e.y+t.yIncrement)}if(0!=t.zoomIncrement){var i={x:t.frame.canvas.clientWidth/2,y:t.frame.canvas.clientHeight/2};t._zoom(t.scale*(1+t.zoomIncrement),i)}t.start(),t._redraw()},this.renderTimestep)}}else this._redraw()},O.prototype.singleStep=function(){if(this.moving){this._initializeForceCalculation(),this._discreteStepNodes();var t=this.constants.minVelocity;this.moving=this._isMoving(t),this._redraw()}},O.prototype.toggleFreeze=function(){0==this.freezeSimulation?this.freezeSimulation=!0:(this.freezeSimulation=!1,this.start())},O.prototype._loadClusterSystem=function(){this.clusterSession=0,this.hubThreshold=5;for(var t in H)H.hasOwnProperty(t)&&(O.prototype[t]=H[t])},O.prototype._loadSectorSystem=function(){this.sectors={},this.activeSector=["default"],this.sectors.active={},this.sectors.active["default"]={nodes:{},edges:{},nodeIndices:[],formationScale:1,drawingNode:void 0},this.sectors.frozen={},this.sectors.navigation={nodes:{},edges:{},nodeIndices:[],formationScale:1,drawingNode:void 0},this.nodeIndices=this.sectors.active["default"].nodeIndices;for(var t in R)R.hasOwnProperty(t)&&(O.prototype[t]=R[t])},O.prototype._loadSelectionSystem=function(){this.selection=[],this.selectionObj={};for(var t in W)W.hasOwnProperty(t)&&(O.prototype[t]=W[t])},O.prototype._loadNavigationControls=function(){for(var t in j)j.hasOwnProperty(t)&&(O.prototype[t]=j[t]);1==this.constants.navigation.enabled&&this._loadNavigationElements()},O.prototype._relocateNavigation=function(){},O.prototype._unHighlightAll=function(){};var U={util:z,events:F,Controller:l,DataSet:o,DataView:r,Range:h,Stack:a,TimeStep:TimeStep,EventBus:s,components:{items:{Item:_,ItemBox:w,ItemPoint:b,ItemRange:S},Component:u,Panel:p,RootPanel:f,ItemSet:y,TimeAxis:g},graph:{Node:M,Edge:D,Popup:I,Groups:Groups,Images:Images},Timeline:C,Graph:O};"undefined"!=typeof n&&(n=U),"undefined"!=typeof i&&"undefined"!=typeof i.exports&&(i.exports=U),"function"==typeof t&&t(function(){return U}),"undefined"!=typeof window&&(window.vis=U)},{hammerjs:2,moment:3,mousetrap:4}],2:[function(t,e){!function(t,i){"use strict";function n(){if(!s.READY){s.event.determineEventTypes();for(var t in s.gestures)s.gestures.hasOwnProperty(t)&&s.detection.register(s.gestures[t]);s.event.onTouch(s.DOCUMENT,s.EVENT_MOVE,s.detection.detect),s.event.onTouch(s.DOCUMENT,s.EVENT_END,s.detection.detect),s.READY=!0}}var s=function(t,e){return new s.Instance(t,e||{})};s.defaults={stop_browser_behavior:{userSelect:"none",touchAction:"none",touchCallout:"none",contentZooming:"none",userDrag:"none",tapHighlightColor:"rgba(0,0,0,0)"}},s.HAS_POINTEREVENTS=navigator.pointerEnabled||navigator.msPointerEnabled,s.HAS_TOUCHEVENTS="ontouchstart"in t,s.MOBILE_REGEX=/mobile|tablet|ip(ad|hone|od)|android/i,s.NO_MOUSEEVENTS=s.HAS_TOUCHEVENTS&&navigator.userAgent.match(s.MOBILE_REGEX),s.EVENT_TYPES={},s.DIRECTION_DOWN="down",s.DIRECTION_LEFT="left",s.DIRECTION_UP="up",s.DIRECTION_RIGHT="right",s.POINTER_MOUSE="mouse",s.POINTER_TOUCH="touch",s.POINTER_PEN="pen",s.EVENT_START="start",s.EVENT_MOVE="move",s.EVENT_END="end",s.DOCUMENT=document,s.plugins={},s.READY=!1,s.Instance=function(t,e){var i=this;
-return n(),this.element=t,this.enabled=!0,this.options=s.utils.extend(s.utils.extend({},s.defaults),e||{}),this.options.stop_browser_behavior&&s.utils.stopDefaultBrowserBehavior(this.element,this.options.stop_browser_behavior),s.event.onTouch(t,s.EVENT_START,function(t){i.enabled&&s.detection.startDetect(i,t)}),this},s.Instance.prototype={on:function(t,e){for(var i=t.split(" "),n=0;n0&&e==s.EVENT_END?e=s.EVENT_MOVE:c||(e=s.EVENT_END),c||null===o?o=h:h=o,i.call(s.detection,n.collectEventData(t,e,h)),s.HAS_POINTEREVENTS&&e==s.EVENT_END&&(c=s.PointerEvent.updatePointer(e,h))),c||(o=null,r=!1,a=!1,s.PointerEvent.reset())}})},determineEventTypes:function(){var t;t=s.HAS_POINTEREVENTS?s.PointerEvent.getEvents():s.NO_MOUSEEVENTS?["touchstart","touchmove","touchend touchcancel"]:["touchstart mousedown","touchmove mousemove","touchend touchcancel mouseup"],s.EVENT_TYPES[s.EVENT_START]=t[0],s.EVENT_TYPES[s.EVENT_MOVE]=t[1],s.EVENT_TYPES[s.EVENT_END]=t[2]},getTouchList:function(t){return s.HAS_POINTEREVENTS?s.PointerEvent.getTouchList():t.touches?t.touches:[{identifier:1,pageX:t.pageX,pageY:t.pageY,target:t.target}]},collectEventData:function(t,e,i){var n=this.getTouchList(i,e),o=s.POINTER_TOUCH;return(i.type.match(/mouse/)||s.PointerEvent.matchType(s.POINTER_MOUSE,i))&&(o=s.POINTER_MOUSE),{center:s.utils.getCenter(n),timeStamp:(new Date).getTime(),target:i.target,touches:n,eventType:e,pointerType:o,srcEvent:i,preventDefault:function(){this.srcEvent.preventManipulation&&this.srcEvent.preventManipulation(),this.srcEvent.preventDefault&&this.srcEvent.preventDefault()},stopPropagation:function(){this.srcEvent.stopPropagation()},stopDetect:function(){return s.detection.stopDetect()}}}},s.PointerEvent={pointers:{},getTouchList:function(){var t=this,e=[];return Object.keys(t.pointers).sort().forEach(function(i){e.push(t.pointers[i])}),e},updatePointer:function(t,e){return t==s.EVENT_END?this.pointers={}:(e.identifier=e.pointerId,this.pointers[e.pointerId]=e),Object.keys(this.pointers).length},matchType:function(t,e){if(!e.pointerType)return!1;var i={};return i[s.POINTER_MOUSE]=e.pointerType==e.MSPOINTER_TYPE_MOUSE||e.pointerType==s.POINTER_MOUSE,i[s.POINTER_TOUCH]=e.pointerType==e.MSPOINTER_TYPE_TOUCH||e.pointerType==s.POINTER_TOUCH,i[s.POINTER_PEN]=e.pointerType==e.MSPOINTER_TYPE_PEN||e.pointerType==s.POINTER_PEN,i[t]},getEvents:function(){return["pointerdown MSPointerDown","pointermove MSPointerMove","pointerup pointercancel MSPointerUp MSPointerCancel"]},reset:function(){this.pointers={}}},s.utils={extend:function(t,e,n){for(var s in e)t[s]!==i&&n||(t[s]=e[s]);return t},hasParent:function(t,e){for(;t;){if(t==e)return!0;t=t.parentNode}return!1},getCenter:function(t){for(var e=[],i=[],n=0,s=t.length;s>n;n++)e.push(t[n].pageX),i.push(t[n].pageY);return{pageX:(Math.min.apply(Math,e)+Math.max.apply(Math,e))/2,pageY:(Math.min.apply(Math,i)+Math.max.apply(Math,i))/2}},getVelocity:function(t,e,i){return{x:Math.abs(e/t)||0,y:Math.abs(i/t)||0}},getAngle:function(t,e){var i=e.pageY-t.pageY,n=e.pageX-t.pageX;return 180*Math.atan2(i,n)/Math.PI},getDirection:function(t,e){var i=Math.abs(t.pageX-e.pageX),n=Math.abs(t.pageY-e.pageY);return i>=n?t.pageX-e.pageX>0?s.DIRECTION_LEFT:s.DIRECTION_RIGHT:t.pageY-e.pageY>0?s.DIRECTION_UP:s.DIRECTION_DOWN},getDistance:function(t,e){var i=e.pageX-t.pageX,n=e.pageY-t.pageY;return Math.sqrt(i*i+n*n)},getScale:function(t,e){return t.length>=2&&e.length>=2?this.getDistance(e[0],e[1])/this.getDistance(t[0],t[1]):1},getRotation:function(t,e){return t.length>=2&&e.length>=2?this.getAngle(e[1],e[0])-this.getAngle(t[1],t[0]):0},isVertical:function(t){return t==s.DIRECTION_UP||t==s.DIRECTION_DOWN},stopDefaultBrowserBehavior:function(t,e){var i,n=["webkit","khtml","moz","ms","o",""];if(e&&t.style){for(var s=0;si;i++){var o=this.gestures[i];if(!this.stopped&&e[o.name]!==!1&&o.handler.call(o,t,this.current.inst)===!1){this.stopDetect();break}}return this.current&&(this.current.lastEvent=t),t.eventType==s.EVENT_END&&!t.touches.length-1&&this.stopDetect(),t}},stopDetect:function(){this.previous=s.utils.extend({},this.current),this.current=null,this.stopped=!0},extendEventData:function(t){var e=this.current.startEvent;if(e&&(t.touches.length!=e.touches.length||t.touches===e.touches)){e.touches=[];for(var i=0,n=t.touches.length;n>i;i++)e.touches.push(s.utils.extend({},t.touches[i]))}var o=t.timeStamp-e.timeStamp,r=t.center.pageX-e.center.pageX,a=t.center.pageY-e.center.pageY,h=s.utils.getVelocity(o,r,a);return s.utils.extend(t,{deltaTime:o,deltaX:r,deltaY:a,velocityX:h.x,velocityY:h.y,distance:s.utils.getDistance(e.center,t.center),angle:s.utils.getAngle(e.center,t.center),direction:s.utils.getDirection(e.center,t.center),scale:s.utils.getScale(e.touches,t.touches),rotation:s.utils.getRotation(e.touches,t.touches),startEvent:e}),t},register:function(t){var e=t.defaults||{};return e[t.name]===i&&(e[t.name]=!0),s.utils.extend(s.defaults,e,!0),t.index=t.index||1e3,this.gestures.push(t),this.gestures.sort(function(t,e){return t.indexe.index?1:0}),this.gestures}},s.gestures=s.gestures||{},s.gestures.Hold={name:"hold",index:10,defaults:{hold_timeout:500,hold_threshold:1},timer:null,handler:function(t,e){switch(t.eventType){case s.EVENT_START:clearTimeout(this.timer),s.detection.current.name=this.name,this.timer=setTimeout(function(){"hold"==s.detection.current.name&&e.trigger("hold",t)},e.options.hold_timeout);break;case s.EVENT_MOVE:t.distance>e.options.hold_threshold&&clearTimeout(this.timer);break;case s.EVENT_END:clearTimeout(this.timer)}}},s.gestures.Tap={name:"tap",index:100,defaults:{tap_max_touchtime:250,tap_max_distance:10,tap_always:!0,doubletap_distance:20,doubletap_interval:300},handler:function(t,e){if(t.eventType==s.EVENT_END){var i=s.detection.previous,n=!1;if(t.deltaTime>e.options.tap_max_touchtime||t.distance>e.options.tap_max_distance)return;i&&"tap"==i.name&&t.timeStamp-i.lastEvent.timeStamp0&&t.touches.length>e.options.swipe_max_touches)return;(t.velocityX>e.options.swipe_velocity||t.velocityY>e.options.swipe_velocity)&&(e.trigger(this.name,t),e.trigger(this.name+t.direction,t))}}},s.gestures.Drag={name:"drag",index:50,defaults:{drag_min_distance:10,drag_max_touches:1,drag_block_horizontal:!1,drag_block_vertical:!1,drag_lock_to_axis:!1,drag_lock_min_distance:25},triggered:!1,handler:function(t,e){if(s.detection.current.name!=this.name&&this.triggered)return e.trigger(this.name+"end",t),void(this.triggered=!1);if(!(e.options.drag_max_touches>0&&t.touches.length>e.options.drag_max_touches))switch(t.eventType){case s.EVENT_START:this.triggered=!1;break;case s.EVENT_MOVE:if(t.distancee.options.transform_min_rotation&&e.trigger("rotate",t),i>e.options.transform_min_scale&&(e.trigger("pinch",t),e.trigger("pinch"+(t.scale<1?"in":"out"),t));break;case s.EVENT_END:this.triggered&&e.trigger(this.name+"end",t),this.triggered=!1}}},s.gestures.Touch={name:"touch",index:-1/0,defaults:{prevent_default:!1,prevent_mouseevents:!1},handler:function(t,e){return e.options.prevent_mouseevents&&t.pointerType==s.POINTER_MOUSE?void t.stopDetect():(e.options.prevent_default&&t.preventDefault(),void(t.eventType==s.EVENT_START&&e.trigger(this.name,t)))}},s.gestures.Release={name:"release",index:1/0,handler:function(t,e){t.eventType==s.EVENT_END&&e.trigger(this.name,t)}},"object"==typeof e&&"object"==typeof e.exports?e.exports=s:(t.Hammer=s,"function"==typeof t.define&&t.define.amd&&t.define("hammer",[],function(){return s}))}(this)},{}],3:[function(e,i){(function(n){function s(){return{empty:!1,unusedTokens:[],unusedInput:[],overflow:-2,charsLeftOver:0,nullInput:!1,invalidMonth:null,invalidFormat:!1,userInvalidated:!1,iso:!1}}function o(t,e){return function(i){return p(t.call(this,i),e)}}function r(t,e){return function(i){return this.lang().ordinal(t.call(this,i),e)}}function a(){}function h(t){x(t),c(this,t)}function d(t){var e=_(t),i=e.year||0,n=e.month||0,s=e.week||0,o=e.day||0,r=e.hour||0,a=e.minute||0,h=e.second||0,d=e.millisecond||0;this._milliseconds=+d+1e3*h+6e4*a+36e5*r,this._days=+o+7*s,this._months=+n+12*i,this._data={},this._bubble()}function c(t,e){for(var i in e)e.hasOwnProperty(i)&&(t[i]=e[i]);return e.hasOwnProperty("toString")&&(t.toString=e.toString),e.hasOwnProperty("valueOf")&&(t.valueOf=e.valueOf),t}function l(t){var e,i={};for(e in t)t.hasOwnProperty(e)&&_e.hasOwnProperty(e)&&(i[e]=t[e]);return i}function u(t){return 0>t?Math.ceil(t):Math.floor(t)}function p(t,e,i){for(var n=""+Math.abs(t),s=t>=0;n.lengthn;n++)(i&&t[n]!==e[n]||!i&&b(t[n])!==b(e[n]))&&r++;return r+o}function y(t){if(t){var e=t.toLowerCase().replace(/(.)s$/,"$1");t=Xe[t]||Ze[e]||e}return t}function _(t){var e,i,n={};for(i in t)t.hasOwnProperty(i)&&(e=y(i),e&&(n[e]=t[i]));return n}function w(t){var e,i;if(0===t.indexOf("week"))e=7,i="day";else{if(0!==t.indexOf("month"))return;e=12,i="month"}re[t]=function(s,o){var r,a,h=re.fn._lang[t],d=[];if("number"==typeof s&&(o=s,s=n),a=function(t){var e=re().utc().set(i,t);return h.call(re.fn._lang,e,s||"")},null!=o)return a(o);for(r=0;e>r;r++)d.push(a(r));return d}}function b(t){var e=+t,i=0;return 0!==e&&isFinite(e)&&(i=e>=0?Math.floor(e):Math.ceil(e)),i}function S(t,e){return new Date(Date.UTC(t,e+1,0)).getUTCDate()}function E(t){return T(t)?366:365}function T(t){return t%4===0&&t%100!==0||t%400===0}function x(t){var e;t._a&&-2===t._pf.overflow&&(e=t._a[ue]<0||t._a[ue]>11?ue:t._a[pe]<1||t._a[pe]>S(t._a[le],t._a[ue])?pe:t._a[fe]<0||t._a[fe]>23?fe:t._a[ge]<0||t._a[ge]>59?ge:t._a[me]<0||t._a[me]>59?me:t._a[ve]<0||t._a[ve]>999?ve:-1,t._pf._overflowDayOfYear&&(le>e||e>pe)&&(e=pe),t._pf.overflow=e)}function C(t){return null==t._isValid&&(t._isValid=!isNaN(t._d.getTime())&&t._pf.overflow<0&&!t._pf.empty&&!t._pf.invalidMonth&&!t._pf.nullInput&&!t._pf.invalidFormat&&!t._pf.userInvalidated,t._strict&&(t._isValid=t._isValid&&0===t._pf.charsLeftOver&&0===t._pf.unusedTokens.length)),t._isValid}function M(t){return t?t.toLowerCase().replace("_","-"):t}function D(t,e){return e._isUTC?re(t).zone(e._offset||0):re(t).local()}function I(t,e){return e.abbr=t,ye[t]||(ye[t]=new a),ye[t].set(e),ye[t]}function O(t){delete ye[t]}function N(t){var i,n,s,o,r=0,a=function(t){if(!ye[t]&&we)try{e("./lang/"+t)}catch(i){}return ye[t]};if(!t)return re.fn._lang;if(!g(t)){if(n=a(t))return n;t=[t]}for(;r0;){if(n=a(o.slice(0,i).join("-")))return n;if(s&&s.length>=i&&v(o,s,!0)>=i-1)break;i--}r++}return re.fn._lang}function L(t){return t.match(/\[[\s\S]/)?t.replace(/^\[|\]$/g,""):t.replace(/\\/g,"")}function A(t){var e,i,n=t.match(Te);for(e=0,i=n.length;i>e;e++)n[e]=Je[n[e]]?Je[n[e]]:L(n[e]);return function(s){var o="";for(e=0;i>e;e++)o+=n[e]instanceof Function?n[e].call(s,t):n[e];return o}}function k(t,e){return t.isValid()?(e=z(e,t.lang()),Ke[e]||(Ke[e]=A(e)),Ke[e](t)):t.lang().invalidDate()}function z(t,e){function i(t){return e.longDateFormat(t)||t}var n=5;for(xe.lastIndex=0;n>=0&&xe.test(t);)t=t.replace(xe,i),xe.lastIndex=0,n-=1;return t}function P(t,e){var i,n=e._strict;switch(t){case"DDDD":return Fe;case"YYYY":case"GGGG":case"gggg":return n?Ye:De;case"Y":case"G":case"g":return He;case"YYYYYY":case"YYYYY":case"GGGGG":case"ggggg":return n?Re:Ie;case"S":if(n)return ze;case"SS":if(n)return Pe;case"SSS":if(n)return Fe;case"DDD":return Me;case"MMM":case"MMMM":case"dd":case"ddd":case"dddd":return Ne;case"a":case"A":return N(e._l)._meridiemParse;case"X":return ke;case"Z":case"ZZ":return Le;case"T":return Ae;case"SSSS":return Oe;case"MM":case"DD":case"YY":case"GG":case"gg":case"HH":case"hh":case"mm":case"ss":case"ww":case"WW":return n?Pe:Ce;case"M":case"D":case"d":case"H":case"h":case"m":case"s":case"w":case"W":case"e":case"E":return Ce;default:return i=new RegExp(V(U(t.replace("\\","")),"i"))}}function F(t){t=t||"";var e=t.match(Le)||[],i=e[e.length-1]||[],n=(i+"").match(Ge)||["-",0,0],s=+(60*n[1])+b(n[2]);return"+"===n[0]?-s:s}function Y(t,e,i){var n,s=i._a;switch(t){case"M":case"MM":null!=e&&(s[ue]=b(e)-1);break;case"MMM":case"MMMM":n=N(i._l).monthsParse(e),null!=n?s[ue]=n:i._pf.invalidMonth=e;break;case"D":case"DD":null!=e&&(s[pe]=b(e));break;case"DDD":case"DDDD":null!=e&&(i._dayOfYear=b(e));break;case"YY":s[le]=b(e)+(b(e)>68?1900:2e3);break;case"YYYY":case"YYYYY":case"YYYYYY":s[le]=b(e);break;case"a":case"A":i._isPm=N(i._l).isPM(e);break;case"H":case"HH":case"h":case"hh":s[fe]=b(e);break;case"m":case"mm":s[ge]=b(e);break;case"s":case"ss":s[me]=b(e);break;case"S":case"SS":case"SSS":case"SSSS":s[ve]=b(1e3*("0."+e));break;case"X":i._d=new Date(1e3*parseFloat(e));break;case"Z":case"ZZ":i._useUTC=!0,i._tzm=F(e);break;case"w":case"ww":case"W":case"WW":case"d":case"dd":case"ddd":case"dddd":case"e":case"E":t=t.substr(0,1);case"gg":case"gggg":case"GG":case"GGGG":case"GGGGG":t=t.substr(0,2),e&&(i._w=i._w||{},i._w[t]=e)}}function R(t){var e,i,n,s,o,r,a,h,d,c,l=[];if(!t._d){for(n=W(t),t._w&&null==t._a[pe]&&null==t._a[ue]&&(o=function(e){var i=parseInt(e,10);return e?e.length<3?i>68?1900+i:2e3+i:i:null==t._a[le]?re().weekYear():t._a[le]},r=t._w,null!=r.GG||null!=r.W||null!=r.E?a=te(o(r.GG),r.W||1,r.E,4,1):(h=N(t._l),d=null!=r.d?K(r.d,h):null!=r.e?parseInt(r.e,10)+h._week.dow:0,c=parseInt(r.w,10)||1,null!=r.d&&dE(s)&&(t._pf._overflowDayOfYear=!0),i=Z(s,0,t._dayOfYear),t._a[ue]=i.getUTCMonth(),t._a[pe]=i.getUTCDate()),e=0;3>e&&null==t._a[e];++e)t._a[e]=l[e]=n[e];for(;7>e;e++)t._a[e]=l[e]=null==t._a[e]?2===e?1:0:t._a[e];l[fe]+=b((t._tzm||0)/60),l[ge]+=b((t._tzm||0)%60),t._d=(t._useUTC?Z:X).apply(null,l)}}function H(t){var e;t._d||(e=_(t._i),t._a=[e.year,e.month,e.day,e.hour,e.minute,e.second,e.millisecond],R(t))}function W(t){var e=new Date;return t._useUTC?[e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate()]:[e.getFullYear(),e.getMonth(),e.getDate()]}function j(t){t._a=[],t._pf.empty=!0;var e,i,n,s,o,r=N(t._l),a=""+t._i,h=a.length,d=0;for(n=z(t._f,r).match(Te)||[],e=0;e0&&t._pf.unusedInput.push(o),a=a.slice(a.indexOf(i)+i.length),d+=i.length),Je[s]?(i?t._pf.empty=!1:t._pf.unusedTokens.push(s),Y(s,i,t)):t._strict&&!i&&t._pf.unusedTokens.push(s);t._pf.charsLeftOver=h-d,a.length>0&&t._pf.unusedInput.push(a),t._isPm&&t._a[fe]<12&&(t._a[fe]+=12),t._isPm===!1&&12===t._a[fe]&&(t._a[fe]=0),R(t),x(t)}function U(t){return t.replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(t,e,i,n,s){return e||i||n||s})}function V(t){return t.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function G(t){var e,i,n,o,r;if(0===t._f.length)return t._pf.invalidFormat=!0,void(t._d=new Date(0/0));for(o=0;or)&&(n=r,i=e));c(t,i||e)}function B(t){var e,i,n=t._i,s=We.exec(n);if(s){for(t._pf.iso=!0,e=0,i=Ue.length;i>e;e++)if(Ue[e][1].exec(n)){t._f=Ue[e][0]+(s[6]||" ");break}for(e=0,i=Ve.length;i>e;e++)if(Ve[e][1].exec(n)){t._f+=Ve[e][0];break}n.match(Le)&&(t._f+="Z"),j(t)}else t._d=new Date(n)}function q(t){var e=t._i,i=be.exec(e);e===n?t._d=new Date:i?t._d=new Date(+i[1]):"string"==typeof e?B(t):g(e)?(t._a=e.slice(0),R(t)):m(e)?t._d=new Date(+e):"object"==typeof e?H(t):t._d=new Date(e)}function X(t,e,i,n,s,o,r){var a=new Date(t,e,i,n,s,o,r);return 1970>t&&a.setFullYear(t),a}function Z(t){var e=new Date(Date.UTC.apply(null,arguments));return 1970>t&&e.setUTCFullYear(t),e}function K(t,e){if("string"==typeof t)if(isNaN(t)){if(t=e.weekdaysParse(t),"number"!=typeof t)return null}else t=parseInt(t,10);return t}function $(t,e,i,n,s){return s.relativeTime(e||1,!!i,t,n)}function Q(t,e,i){var n=ce(Math.abs(t)/1e3),s=ce(n/60),o=ce(s/60),r=ce(o/24),a=ce(r/365),h=45>n&&["s",n]||1===s&&["m"]||45>s&&["mm",s]||1===o&&["h"]||22>o&&["hh",o]||1===r&&["d"]||25>=r&&["dd",r]||45>=r&&["M"]||345>r&&["MM",ce(r/30)]||1===a&&["y"]||["yy",a];return h[2]=e,h[3]=t>0,h[4]=i,$.apply({},h)}function J(t,e,i){var n,s=i-e,o=i-t.day();return o>s&&(o-=7),s-7>o&&(o+=7),n=re(t).add("d",o),{week:Math.ceil(n.dayOfYear()/7),year:n.year()}}function te(t,e,i,n,s){var o,r,a=Z(t,0,1).getUTCDay();return i=null!=i?i:s,o=s-a+(a>n?7:0)-(s>a?7:0),r=7*(e-1)+(i-s)+o+1,{year:r>0?t:t-1,dayOfYear:r>0?r:E(t-1)+r}}function ee(t){var e=t._i,i=t._f;return null===e?re.invalid({nullInput:!0}):("string"==typeof e&&(t._i=e=N().preparse(e)),re.isMoment(e)?(t=l(e),t._d=new Date(+e._d)):i?g(i)?G(t):j(t):q(t),new h(t))}function ie(t,e){re.fn[t]=re.fn[t+"s"]=function(t){var i=this._isUTC?"UTC":"";return null!=t?(this._d["set"+i+e](t),re.updateOffset(this),this):this._d["get"+i+e]()}}function ne(t){re.duration.fn[t]=function(){return this._data[t]}}function se(t,e){re.duration.fn["as"+t]=function(){return+this/e}}function oe(t){var e=!1,i=re;"undefined"==typeof ender&&(t?(de.moment=function(){return!e&&console&&console.warn&&(e=!0,console.warn("Accessing Moment through the global scope is deprecated, and will be removed in an upcoming release.")),i.apply(null,arguments)},c(de.moment,i)):de.moment=re)}for(var re,ae,he="2.5.1",de=this,ce=Math.round,le=0,ue=1,pe=2,fe=3,ge=4,me=5,ve=6,ye={},_e={_isAMomentObject:null,_i:null,_f:null,_l:null,_strict:null,_isUTC:null,_offset:null,_pf:null,_lang:null},we="undefined"!=typeof i&&i.exports&&"undefined"!=typeof e,be=/^\/?Date\((\-?\d+)/i,Se=/(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,Ee=/^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/,Te=/(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|X|zz?|ZZ?|.)/g,xe=/(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g,Ce=/\d\d?/,Me=/\d{1,3}/,De=/\d{1,4}/,Ie=/[+\-]?\d{1,6}/,Oe=/\d+/,Ne=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,Le=/Z|[\+\-]\d\d:?\d\d/gi,Ae=/T/i,ke=/[\+\-]?\d+(\.\d{1,3})?/,ze=/\d/,Pe=/\d\d/,Fe=/\d{3}/,Ye=/\d{4}/,Re=/[+-]?\d{6}/,He=/[+-]?\d+/,We=/^\s*(?:[+-]\d{6}|\d{4})-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,je="YYYY-MM-DDTHH:mm:ssZ",Ue=[["YYYYYY-MM-DD",/[+-]\d{6}-\d{2}-\d{2}/],["YYYY-MM-DD",/\d{4}-\d{2}-\d{2}/],["GGGG-[W]WW-E",/\d{4}-W\d{2}-\d/],["GGGG-[W]WW",/\d{4}-W\d{2}/],["YYYY-DDD",/\d{4}-\d{3}/]],Ve=[["HH:mm:ss.SSSS",/(T| )\d\d:\d\d:\d\d\.\d{1,3}/],["HH:mm:ss",/(T| )\d\d:\d\d:\d\d/],["HH:mm",/(T| )\d\d:\d\d/],["HH",/(T| )\d\d/]],Ge=/([\+\-]|\d\d)/gi,Be="Date|Hours|Minutes|Seconds|Milliseconds".split("|"),qe={Milliseconds:1,Seconds:1e3,Minutes:6e4,Hours:36e5,Days:864e5,Months:2592e6,Years:31536e6},Xe={ms:"millisecond",s:"second",m:"minute",h:"hour",d:"day",D:"date",w:"week",W:"isoWeek",M:"month",y:"year",DDD:"dayOfYear",e:"weekday",E:"isoWeekday",gg:"weekYear",GG:"isoWeekYear"},Ze={dayofyear:"dayOfYear",isoweekday:"isoWeekday",isoweek:"isoWeek",weekyear:"weekYear",isoweekyear:"isoWeekYear"},Ke={},$e="DDD w W M D d".split(" "),Qe="M D H h m s w W".split(" "),Je={M:function(){return this.month()+1},MMM:function(t){return this.lang().monthsShort(this,t)},MMMM:function(t){return this.lang().months(this,t)},D:function(){return this.date()},DDD:function(){return this.dayOfYear()},d:function(){return this.day()},dd:function(t){return this.lang().weekdaysMin(this,t)},ddd:function(t){return this.lang().weekdaysShort(this,t)},dddd:function(t){return this.lang().weekdays(this,t)},w:function(){return this.week()},W:function(){return this.isoWeek()},YY:function(){return p(this.year()%100,2)},YYYY:function(){return p(this.year(),4)},YYYYY:function(){return p(this.year(),5)},YYYYYY:function(){var t=this.year(),e=t>=0?"+":"-";return e+p(Math.abs(t),6)},gg:function(){return p(this.weekYear()%100,2)},gggg:function(){return p(this.weekYear(),4)},ggggg:function(){return p(this.weekYear(),5)},GG:function(){return p(this.isoWeekYear()%100,2)},GGGG:function(){return p(this.isoWeekYear(),4)},GGGGG:function(){return p(this.isoWeekYear(),5)},e:function(){return this.weekday()},E:function(){return this.isoWeekday()},a:function(){return this.lang().meridiem(this.hours(),this.minutes(),!0)},A:function(){return this.lang().meridiem(this.hours(),this.minutes(),!1)},H:function(){return this.hours()},h:function(){return this.hours()%12||12},m:function(){return this.minutes()},s:function(){return this.seconds()},S:function(){return b(this.milliseconds()/100)},SS:function(){return p(b(this.milliseconds()/10),2)},SSS:function(){return p(this.milliseconds(),3)},SSSS:function(){return p(this.milliseconds(),3)},Z:function(){var t=-this.zone(),e="+";return 0>t&&(t=-t,e="-"),e+p(b(t/60),2)+":"+p(b(t)%60,2)},ZZ:function(){var t=-this.zone(),e="+";return 0>t&&(t=-t,e="-"),e+p(b(t/60),2)+p(b(t)%60,2)},z:function(){return this.zoneAbbr()},zz:function(){return this.zoneName()},X:function(){return this.unix()},Q:function(){return this.quarter()}},ti=["months","monthsShort","weekdays","weekdaysShort","weekdaysMin"];$e.length;)ae=$e.pop(),Je[ae+"o"]=r(Je[ae],ae);for(;Qe.length;)ae=Qe.pop(),Je[ae+ae]=o(Je[ae],2);for(Je.DDDD=o(Je.DDD,3),c(a.prototype,{set:function(t){var e,i;for(i in t)e=t[i],"function"==typeof e?this[i]=e:this["_"+i]=e},_months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),months:function(t){return this._months[t.month()]},_monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),monthsShort:function(t){return this._monthsShort[t.month()]},monthsParse:function(t){var e,i,n;for(this._monthsParse||(this._monthsParse=[]),e=0;12>e;e++)if(this._monthsParse[e]||(i=re.utc([2e3,e]),n="^"+this.months(i,"")+"|^"+this.monthsShort(i,""),this._monthsParse[e]=new RegExp(n.replace(".",""),"i")),this._monthsParse[e].test(t))return e},_weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdays:function(t){return this._weekdays[t.day()]},_weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysShort:function(t){return this._weekdaysShort[t.day()]},_weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),weekdaysMin:function(t){return this._weekdaysMin[t.day()]},weekdaysParse:function(t){var e,i,n;for(this._weekdaysParse||(this._weekdaysParse=[]),e=0;7>e;e++)if(this._weekdaysParse[e]||(i=re([2e3,1]).day(e),n="^"+this.weekdays(i,"")+"|^"+this.weekdaysShort(i,"")+"|^"+this.weekdaysMin(i,""),this._weekdaysParse[e]=new RegExp(n.replace(".",""),"i")),this._weekdaysParse[e].test(t))return e},_longDateFormat:{LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D YYYY",LLL:"MMMM D YYYY LT",LLLL:"dddd, MMMM D YYYY LT"},longDateFormat:function(t){var e=this._longDateFormat[t];return!e&&this._longDateFormat[t.toUpperCase()]&&(e=this._longDateFormat[t.toUpperCase()].replace(/MMMM|MM|DD|dddd/g,function(t){return t.slice(1)}),this._longDateFormat[t]=e),e},isPM:function(t){return"p"===(t+"").toLowerCase().charAt(0)},_meridiemParse:/[ap]\.?m?\.?/i,meridiem:function(t,e,i){return t>11?i?"pm":"PM":i?"am":"AM"},_calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},calendar:function(t,e){var i=this._calendar[t];return"function"==typeof i?i.apply(e):i},_relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},relativeTime:function(t,e,i,n){var s=this._relativeTime[i];return"function"==typeof s?s(t,e,i,n):s.replace(/%d/i,t)},pastFuture:function(t,e){var i=this._relativeTime[t>0?"future":"past"];return"function"==typeof i?i(e):i.replace(/%s/i,e)},ordinal:function(t){return this._ordinal.replace("%d",t)},_ordinal:"%d",preparse:function(t){return t},postformat:function(t){return t},week:function(t){return J(t,this._week.dow,this._week.doy).week},_week:{dow:0,doy:6},_invalidDate:"Invalid date",invalidDate:function(){return this._invalidDate}}),re=function(t,e,i,o){var r;return"boolean"==typeof i&&(o=i,i=n),r={},r._isAMomentObject=!0,r._i=t,r._f=e,r._l=i,r._strict=o,r._isUTC=!1,r._pf=s(),ee(r)},re.utc=function(t,e,i,o){var r;return"boolean"==typeof i&&(o=i,i=n),r={},r._isAMomentObject=!0,r._useUTC=!0,r._isUTC=!0,r._l=i,r._i=t,r._f=e,r._strict=o,r._pf=s(),ee(r).utc()},re.unix=function(t){return re(1e3*t)},re.duration=function(t,e){var i,n,s,o=t,r=null;return re.isDuration(t)?o={ms:t._milliseconds,d:t._days,M:t._months}:"number"==typeof t?(o={},e?o[e]=t:o.milliseconds=t):(r=Se.exec(t))?(i="-"===r[1]?-1:1,o={y:0,d:b(r[pe])*i,h:b(r[fe])*i,m:b(r[ge])*i,s:b(r[me])*i,ms:b(r[ve])*i}):(r=Ee.exec(t))&&(i="-"===r[1]?-1:1,s=function(t){var e=t&&parseFloat(t.replace(",","."));return(isNaN(e)?0:e)*i},o={y:s(r[2]),M:s(r[3]),d:s(r[4]),h:s(r[5]),m:s(r[6]),s:s(r[7]),w:s(r[8])}),n=new d(o),re.isDuration(t)&&t.hasOwnProperty("_lang")&&(n._lang=t._lang),n},re.version=he,re.defaultFormat=je,re.updateOffset=function(){},re.lang=function(t,e){var i;return t?(e?I(M(t),e):null===e?(O(t),t="en"):ye[t]||N(t),i=re.duration.fn._lang=re.fn._lang=N(t),i._abbr):re.fn._lang._abbr},re.langData=function(t){return t&&t._lang&&t._lang._abbr&&(t=t._lang._abbr),N(t)},re.isMoment=function(t){return t instanceof h||null!=t&&t.hasOwnProperty("_isAMomentObject")},re.isDuration=function(t){return t instanceof d},ae=ti.length-1;ae>=0;--ae)w(ti[ae]);for(re.normalizeUnits=function(t){return y(t)},re.invalid=function(t){var e=re.utc(0/0);return null!=t?c(e._pf,t):e._pf.userInvalidated=!0,e},re.parseZone=function(t){return re(t).parseZone()},c(re.fn=h.prototype,{clone:function(){return re(this)},valueOf:function(){return+this._d+6e4*(this._offset||0)},unix:function(){return Math.floor(+this/1e3)},toString:function(){return this.clone().lang("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},toDate:function(){return this._offset?new Date(+this):this._d},toISOString:function(){var t=re(this).utc();return 00:!1},parsingFlags:function(){return c({},this._pf)},invalidAt:function(){return this._pf.overflow},utc:function(){return this.zone(0)},local:function(){return this.zone(0),this._isUTC=!1,this},format:function(t){var e=k(this,t||re.defaultFormat);return this.lang().postformat(e)},add:function(t,e){var i;return i="string"==typeof t?re.duration(+e,t):re.duration(t,e),f(this,i,1),this},subtract:function(t,e){var i;return i="string"==typeof t?re.duration(+e,t):re.duration(t,e),f(this,i,-1),this},diff:function(t,e,i){var n,s,o=D(t,this),r=6e4*(this.zone()-o.zone());return e=y(e),"year"===e||"month"===e?(n=432e5*(this.daysInMonth()+o.daysInMonth()),s=12*(this.year()-o.year())+(this.month()-o.month()),s+=(this-re(this).startOf("month")-(o-re(o).startOf("month")))/n,s-=6e4*(this.zone()-re(this).startOf("month").zone()-(o.zone()-re(o).startOf("month").zone()))/n,"year"===e&&(s/=12)):(n=this-o,s="second"===e?n/1e3:"minute"===e?n/6e4:"hour"===e?n/36e5:"day"===e?(n-r)/864e5:"week"===e?(n-r)/6048e5:n),i?s:u(s)},from:function(t,e){return re.duration(this.diff(t)).lang(this.lang()._abbr).humanize(!e)},fromNow:function(t){return this.from(re(),t)},calendar:function(){var t=D(re(),this).startOf("day"),e=this.diff(t,"days",!0),i=-6>e?"sameElse":-1>e?"lastWeek":0>e?"lastDay":1>e?"sameDay":2>e?"nextDay":7>e?"nextWeek":"sameElse";return this.format(this.lang().calendar(i,this))},isLeapYear:function(){return T(this.year())},isDST:function(){return this.zone()+re(t).startOf(e)},isBefore:function(t,e){return e="undefined"!=typeof e?e:"millisecond",+this.clone().startOf(e)<+re(t).startOf(e)},isSame:function(t,e){return e=e||"ms",+this.clone().startOf(e)===+D(t,this).startOf(e)},min:function(t){return t=re.apply(null,arguments),this>t?this:t},max:function(t){return t=re.apply(null,arguments),t>this?this:t},zone:function(t){var e=this._offset||0;return null==t?this._isUTC?e:this._d.getTimezoneOffset():("string"==typeof t&&(t=F(t)),Math.abs(t)<16&&(t=60*t),this._offset=t,this._isUTC=!0,e!==t&&f(this,re.duration(e-t,"m"),1,!0),this)},zoneAbbr:function(){return this._isUTC?"UTC":""},zoneName:function(){return this._isUTC?"Coordinated Universal Time":""},parseZone:function(){return this._tzm?this.zone(this._tzm):"string"==typeof this._i&&this.zone(this._i),this},hasAlignedHourOffset:function(t){return t=t?re(t).zone():0,(this.zone()-t)%60===0},daysInMonth:function(){return S(this.year(),this.month())},dayOfYear:function(t){var e=ce((re(this).startOf("day")-re(this).startOf("year"))/864e5)+1;return null==t?e:this.add("d",t-e)},quarter:function(){return Math.ceil((this.month()+1)/3)},weekYear:function(t){var e=J(this,this.lang()._week.dow,this.lang()._week.doy).year;return null==t?e:this.add("y",t-e)},isoWeekYear:function(t){var e=J(this,1,4).year;return null==t?e:this.add("y",t-e)},week:function(t){var e=this.lang().week(this);return null==t?e:this.add("d",7*(t-e))},isoWeek:function(t){var e=J(this,1,4).week;return null==t?e:this.add("d",7*(t-e))},weekday:function(t){var e=(this.day()+7-this.lang()._week.dow)%7;return null==t?e:this.add("d",t-e)},isoWeekday:function(t){return null==t?this.day()||7:this.day(this.day()%7?t:t-7)},get:function(t){return t=y(t),this[t]()},set:function(t,e){return t=y(t),"function"==typeof this[t]&&this[t](e),this},lang:function(t){return t===n?this._lang:(this._lang=N(t),this)}}),ae=0;ae-1?!1:"INPUT"==i||"SELECT"==i||"TEXTAREA"==i||e.contentEditable&&"true"==e.contentEditable}function o(t,e){return t.sort().join(",")===e.sort().join(",")}function r(t){t=t||{};var e,i=!1;for(e in M)t[e]?i=!0:M[e]=0;i||(I=!1)}function a(t,e,i,n,s){var r,a,h=[];if(!x[t])return[];for("keyup"==i&&u(t)&&(e=[t]),r=0;r95&&112>t||b.hasOwnProperty(t)&&(_[b[t]]=t)}return _}function g(t,e,i){return i||(i=f()[t]?"keydown":"keypress"),"keypress"==i&&e.length&&(i="keydown"),i}function m(t,e,i,s){M[t]=0,s||(s=g(e[0],[]));var o,a=function(){I=s,++M[t],p()},h=function(t){d(i,t),"keyup"!==s&&(D=n(t)),setTimeout(r,10)};for(o=0;o1)return m(t,d,e,i);for(h="+"===t?["+"]:t.split("+"),o=0;o":".","?":"/","|":"\\"},T={option:"alt",command:"meta","return":"enter",escape:"esc"},x={},C={},M={},D=!1,I=!1,O=1;20>O;++O)b[111+O]="f"+O;for(O=0;9>=O;++O)b[O+96]=O;i(document,"keypress",l),i(document,"keydown",l),i(document,"keyup",l);var N={bind:function(t,e,i){return y(t instanceof Array?t:[t],e,i),C[t+":"+i]=e,this},unbind:function(t,e){return C[t+":"+e]&&(delete C[t+":"+e],this.bind(t,function(){},e)),this},trigger:function(t,e){return C[t+":"+e](),this},reset:function(){return x={},C={},this}};e.exports=N},{}]},{},[1])(1)});
\ No newline at end of file
+!function(t){if("object"==typeof exports)module.exports=t();else if("function"==typeof define&&define.amd)define(t);else{var e;"undefined"!=typeof window?e=window:"undefined"!=typeof global?e=global:"undefined"!=typeof self&&(e=self),e.vis=t()}}(function(){var define,module,exports;return function t(e,i,n){function s(r,a){if(!i[r]){if(!e[r]){var h="function"==typeof require&&require;if(!a&&h)return h(r,!0);if(o)return o(r,!0);throw new Error("Cannot find module '"+r+"'")}var d=i[r]={exports:{}};e[r][0].call(d.exports,function(t){var i=e[r][1][t];return s(i?i:t)},d,d.exports,t,e,i,n)}return i[r].exports}for(var o="function"==typeof require&&require,r=0;ri;++i)t.call(e||this,this[i],i,this)}),Array.prototype.map||(Array.prototype.map=function(t,e){var i,n,s;if(null==this)throw new TypeError(" this is null or not defined");var o=Object(this),r=o.length>>>0;if("function"!=typeof t)throw new TypeError(t+" is not a function");for(e&&(i=e),n=new Array(r),s=0;r>s;){var a,h;s in o&&(a=o[s],h=t.call(i,a,s,o),n[s]=h),s++}return n}),Array.prototype.filter||(Array.prototype.filter=function(t){"use strict";if(null==this)throw new TypeError;var e=Object(this),i=e.length>>>0;if("function"!=typeof t)throw new TypeError;for(var n=[],s=arguments[1],o=0;i>o;o++)if(o in e){var r=e[o];t.call(s,r,o,e)&&n.push(r)}return n}),Object.keys||(Object.keys=function(){var t=Object.prototype.hasOwnProperty,e=!{toString:null}.propertyIsEnumerable("toString"),i=["toString","toLocaleString","valueOf","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","constructor"],n=i.length;return function(s){if("object"!=typeof s&&"function"!=typeof s||null===s)throw new TypeError("Object.keys called on non-object");var o=[];for(var r in s)t.call(s,r)&&o.push(r);if(e)for(var a=0;n>a;a++)t.call(s,i[a])&&o.push(i[a]);return o}}()),Array.isArray||(Array.isArray=function(t){return"[object Array]"===Object.prototype.toString.call(t)}),Function.prototype.bind||(Function.prototype.bind=function(t){if("function"!=typeof this)throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");var e=Array.prototype.slice.call(arguments,1),i=this,n=function(){},s=function(){return i.apply(this instanceof n&&t?this:t,e.concat(Array.prototype.slice.call(arguments)))};return n.prototype=this.prototype,s.prototype=new n,s}),Object.create||(Object.create=function(t){function e(){}if(arguments.length>1)throw new Error("Object.create implementation only accepts the first parameter.");return e.prototype=t,new e}),Function.prototype.bind||(Function.prototype.bind=function(t){if("function"!=typeof this)throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");var e=Array.prototype.slice.call(arguments,1),i=this,n=function(){},s=function(){return i.apply(this instanceof n&&t?this:t,e.concat(Array.prototype.slice.call(arguments)))};return n.prototype=this.prototype,s.prototype=new n,s});var util={};util.isNumber=function(t){return t instanceof Number||"number"==typeof t},util.isString=function(t){return t instanceof String||"string"==typeof t},util.isDate=function(t){if(t instanceof Date)return!0;if(util.isString(t)){var e=ASPDateRegex.exec(t);if(e)return!0;if(!isNaN(Date.parse(t)))return!0}return!1},util.isDataTable=function(t){return"undefined"!=typeof google&&google.visualization&&google.visualization.DataTable&&t instanceof google.visualization.DataTable},util.randomUUID=function(){var t=function(){return Math.floor(65536*Math.random()).toString(16)};return t()+t()+"-"+t()+"-"+t()+"-"+t()+"-"+t()+t()+t()},util.extend=function(t){for(var e=1,i=arguments.length;i>e;e++){var n=arguments[e];for(var s in n)n.hasOwnProperty(s)&&void 0!==n[s]&&(t[s]=n[s])}return t},util.convert=function(t,e){var i;if(void 0===t)return void 0;if(null===t)return null;if(!e)return t;if("string"!=typeof e&&!(e instanceof String))throw new Error("Type must be a string");switch(e){case"boolean":case"Boolean":return Boolean(t);case"number":case"Number":return Number(t.valueOf());case"string":case"String":return String(t);case"Date":if(util.isNumber(t))return new Date(t);if(t instanceof Date)return new Date(t.valueOf());if(moment.isMoment(t))return new Date(t.valueOf());if(util.isString(t))return i=ASPDateRegex.exec(t),i?new Date(Number(i[1])):moment(t).toDate();throw new Error("Cannot convert object of type "+util.getType(t)+" to type Date");case"Moment":if(util.isNumber(t))return moment(t);if(t instanceof Date)return moment(t.valueOf());if(moment.isMoment(t))return moment(t);if(util.isString(t))return i=ASPDateRegex.exec(t),moment(i?Number(i[1]):t);throw new Error("Cannot convert object of type "+util.getType(t)+" to type Date");case"ISODate":if(util.isNumber(t))return new Date(t);if(t instanceof Date)return t.toISOString();if(moment.isMoment(t))return t.toDate().toISOString();if(util.isString(t))return i=ASPDateRegex.exec(t),i?new Date(Number(i[1])).toISOString():new Date(t).toISOString();throw new Error("Cannot convert object of type "+util.getType(t)+" to type ISODate");case"ASPDate":if(util.isNumber(t))return"/Date("+t+")/";if(t instanceof Date)return"/Date("+t.valueOf()+")/";if(util.isString(t)){i=ASPDateRegex.exec(t);var n;return n=i?new Date(Number(i[1])).valueOf():new Date(t).valueOf(),"/Date("+n+")/"}throw new Error("Cannot convert object of type "+util.getType(t)+" to type ASPDate");default:throw new Error("Cannot convert object of type "+util.getType(t)+' to type "'+e+'"')}};var ASPDateRegex=/^\/?Date\((\-?\d+)/i;util.getType=function(t){var e=typeof t;return"object"==e?null==t?"null":t instanceof Boolean?"Boolean":t instanceof Number?"Number":t instanceof String?"String":t instanceof Array?"Array":t instanceof Date?"Date":"Object":"number"==e?"Number":"boolean"==e?"Boolean":"string"==e?"String":e},util.getAbsoluteLeft=function(t){for(var e=document.documentElement,i=document.body,n=t.offsetLeft,s=t.offsetParent;null!=s&&s!=i&&s!=e;)n+=s.offsetLeft,n-=s.scrollLeft,s=s.offsetParent;return n},util.getAbsoluteTop=function(t){for(var e=document.documentElement,i=document.body,n=t.offsetTop,s=t.offsetParent;null!=s&&s!=i&&s!=e;)n+=s.offsetTop,n-=s.scrollTop,s=s.offsetParent;return n},util.getPageY=function(t){if("pageY"in t)return t.pageY;var e;e="targetTouches"in t&&t.targetTouches.length?t.targetTouches[0].clientY:t.clientY;var i=document.documentElement,n=document.body;return e+(i&&i.scrollTop||n&&n.scrollTop||0)-(i&&i.clientTop||n&&n.clientTop||0)},util.getPageX=function(t){if("pageY"in t)return t.pageX;var e;e="targetTouches"in t&&t.targetTouches.length?t.targetTouches[0].clientX:t.clientX;var i=document.documentElement,n=document.body;return e+(i&&i.scrollLeft||n&&n.scrollLeft||0)-(i&&i.clientLeft||n&&n.clientLeft||0)},util.addClassName=function(t,e){var i=t.className.split(" ");-1==i.indexOf(e)&&(i.push(e),t.className=i.join(" "))},util.removeClassName=function(t,e){var i=t.className.split(" "),n=i.indexOf(e);-1!=n&&(i.splice(n,1),t.className=i.join(" "))},util.forEach=function(t,e){var i,n;if(t instanceof Array)for(i=0,n=t.length;n>i;i++)e(t[i],i,t);else for(i in t)t.hasOwnProperty(i)&&e(t[i],i,t)},util.updateProperty=function(t,e,i){return t[e]!==i?(t[e]=i,!0):!1},util.addEventListener=function(t,e,i,n){t.addEventListener?(void 0===n&&(n=!1),"mousewheel"===e&&navigator.userAgent.indexOf("Firefox")>=0&&(e="DOMMouseScroll"),t.addEventListener(e,i,n)):t.attachEvent("on"+e,i)},util.removeEventListener=function(t,e,i,n){t.removeEventListener?(void 0===n&&(n=!1),"mousewheel"===e&&navigator.userAgent.indexOf("Firefox")>=0&&(e="DOMMouseScroll"),t.removeEventListener(e,i,n)):t.detachEvent("on"+e,i)},util.getTarget=function(t){t||(t=window.event);var e;return t.target?e=t.target:t.srcElement&&(e=t.srcElement),void 0!=e.nodeType&&3==e.nodeType&&(e=e.parentNode),e},util.fakeGesture=function(t,e){var i=null,n=Hammer.event.collectEventData(this,i,e);return isNaN(n.center.pageX)&&(n.center.pageX=e.pageX),isNaN(n.center.pageY)&&(n.center.pageY=e.pageY),n},util.option={},util.option.asBoolean=function(t,e){return"function"==typeof t&&(t=t()),null!=t?0!=t:e||null},util.option.asNumber=function(t,e){return"function"==typeof t&&(t=t()),null!=t?Number(t)||e||null:e||null},util.option.asString=function(t,e){return"function"==typeof t&&(t=t()),null!=t?String(t):e||null},util.option.asSize=function(t,e){return"function"==typeof t&&(t=t()),util.isString(t)?t:util.isNumber(t)?t+"px":e||null},util.option.asElement=function(t,e){return"function"==typeof t&&(t=t()),t||e||null},util.GiveDec=function GiveDec(Hex){return Value="A"==Hex?10:"B"==Hex?11:"C"==Hex?12:"D"==Hex?13:"E"==Hex?14:"F"==Hex?15:eval(Hex)},util.GiveHex=function(t){return Value=10==t?"A":11==t?"B":12==t?"C":13==t?"D":14==t?"E":15==t?"F":""+t},util.hexToRGB=function(t){t=t.replace("#","").toUpperCase();var e=util.GiveDec(t.substring(0,1)),i=util.GiveDec(t.substring(1,2)),n=util.GiveDec(t.substring(2,3)),s=util.GiveDec(t.substring(3,4)),o=util.GiveDec(t.substring(4,5)),r=util.GiveDec(t.substring(5,6)),a=16*e+i,h=16*n+s,i=16*o+r;return{r:a,g:h,b:i}},util.RGBToHex=function(t,e,i){var n=util.GiveHex(Math.floor(t/16)),s=util.GiveHex(t%16),o=util.GiveHex(Math.floor(e/16)),r=util.GiveHex(e%16),a=util.GiveHex(Math.floor(i/16)),h=util.GiveHex(i%16),d=n+s+o+r+a+h;return"#"+d},util.RGBToHSV=function(t,e,i){t/=255,e/=255,i/=255;var n=Math.min(t,Math.min(e,i)),s=Math.max(t,Math.max(e,i));if(n==s)return{h:0,s:0,v:n};var o=t==n?e-i:i==n?t-e:i-t,r=t==n?3:i==n?1:5,a=60*(r-o/(s-n))/360,h=(s-n)/s,d=s;return{h:a,s:h,v:d}},util.HSVToRGB=function(t,e,i){var n,s,o,r=Math.floor(6*t),a=6*t-r,h=i*(1-e),d=i*(1-a*e),l=i*(1-(1-a)*e);switch(r%6){case 0:n=i,s=l,o=h;break;case 1:n=d,s=i,o=h;break;case 2:n=h,s=i,o=l;break;case 3:n=h,s=d,o=i;break;case 4:n=l,s=h,o=i;break;case 5:n=i,s=h,o=d}return{r:Math.floor(255*n),g:Math.floor(255*s),b:Math.floor(255*o)}},util.HSVToHex=function(t,e,i){var n=util.HSVToRGB(t,e,i);return util.RGBToHex(n.r,n.g,n.b)},util.hexToHSV=function(t){var e=util.hexToRGB(t);return util.RGBToHSV(e.r,e.g,e.b)},util.isValidHex=function(t){var e=/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(t);return e},DataSet.prototype.on=function(t,e){var i=this.subscribers[t];i||(i=[],this.subscribers[t]=i),i.push({callback:e})},DataSet.prototype.subscribe=DataSet.prototype.on,DataSet.prototype.off=function(t,e){var i=this.subscribers[t];i&&(this.subscribers[t]=i.filter(function(t){return t.callback!=e}))},DataSet.prototype.unsubscribe=DataSet.prototype.off,DataSet.prototype._trigger=function(t,e,i){if("*"==t)throw new Error("Cannot trigger event *");var n=[];t in this.subscribers&&(n=n.concat(this.subscribers[t])),"*"in this.subscribers&&(n=n.concat(this.subscribers["*"]));for(var s=0;so;o++)i=s._addItem(t[o]),n.push(i);else if(util.isDataTable(t))for(var a=this._getColumnNames(t),h=0,d=t.getNumberOfRows();d>h;h++){for(var l={},c=0,u=a.length;u>c;c++){var p=a[c];l[p]=t.getValue(h,c)}i=s._addItem(l),n.push(i)}else{if(!(t instanceof Object))throw new Error("Unknown dataType");i=s._addItem(t),n.push(i)}return n.length&&this._trigger("add",{items:n},e),n},DataSet.prototype.update=function(t,e){var i=[],n=[],s=this,o=s.fieldId,r=function(t){var e=t[o];s.data[e]?(e=s._updateItem(t),n.push(e)):(e=s._addItem(t),i.push(e))};if(t instanceof Array)for(var a=0,h=t.length;h>a;a++)r(t[a]);else if(util.isDataTable(t))for(var d=this._getColumnNames(t),l=0,c=t.getNumberOfRows();c>l;l++){for(var u={},p=0,f=d.length;f>p;p++){var m=d[p];u[m]=t.getValue(l,p)}r(u)}else{if(!(t instanceof Object))throw new Error("Unknown dataType");r(t)}return i.length&&this._trigger("add",{items:i},e),n.length&&this._trigger("update",{items:n},e),i.concat(n)},DataSet.prototype.get=function(){var t,e,i,n,s=this,o=this.showInternalIds,r=util.getType(arguments[0]);"String"==r||"Number"==r?(t=arguments[0],i=arguments[1],n=arguments[2]):"Array"==r?(e=arguments[0],i=arguments[1],n=arguments[2]):(i=arguments[0],n=arguments[1]);var a;if(i&&i.type){if(a="DataTable"==i.type?"DataTable":"Array",n&&a!=util.getType(n))throw new Error('Type of parameter "data" ('+util.getType(n)+") does not correspond with specified options.type ("+i.type+")");if("DataTable"==a&&!util.isDataTable(n))throw new Error('Parameter "data" must be a DataTable when options.type is "DataTable"')}else a=n?"DataTable"==util.getType(n)?"DataTable":"Array":"Array";void 0!=i&&void 0!=i.showInternalIds&&(this.showInternalIds=i.showInternalIds);var h,d,l,c,u=i&&i.convert||this.options.convert,p=i&&i.filter,f=[];if(void 0!=t)h=s._getItem(t,u),p&&!p(h)&&(h=null);else if(void 0!=e)for(l=0,c=e.length;c>l;l++)h=s._getItem(e[l],u),(!p||p(h))&&f.push(h);else for(d in this.data)this.data.hasOwnProperty(d)&&(h=s._getItem(d,u),(!p||p(h))&&f.push(h));if(this.showInternalIds=o,i&&i.order&&void 0==t&&this._sort(f,i.order),i&&i.fields){var m=i.fields;if(void 0!=t)h=this._filterFields(h,m);else for(l=0,c=f.length;c>l;l++)f[l]=this._filterFields(f[l],m)}if("DataTable"==a){var g=this._getColumnNames(n);if(void 0!=t)s._appendRow(n,g,h);else for(l=0,c=f.length;c>l;l++)s._appendRow(n,g,f[l]);return n}if(void 0!=t)return h;if(n){for(l=0,c=f.length;c>l;l++)n.push(f[l]);return n}return f},DataSet.prototype.getIds=function(t){var e,i,n,s,o,r=this.data,a=t&&t.filter,h=t&&t.order,d=t&&t.convert||this.options.convert,l=[];if(a)if(h){o=[];for(n in r)r.hasOwnProperty(n)&&(s=this._getItem(n,d),a(s)&&o.push(s));for(this._sort(o,h),e=0,i=o.length;i>e;e++)l[e]=o[e][this.fieldId]}else for(n in r)r.hasOwnProperty(n)&&(s=this._getItem(n,d),a(s)&&l.push(s[this.fieldId]));else if(h){o=[];for(n in r)r.hasOwnProperty(n)&&o.push(r[n]);for(this._sort(o,h),e=0,i=o.length;i>e;e++)l[e]=o[e][this.fieldId]}else for(n in r)r.hasOwnProperty(n)&&(s=r[n],l.push(s[this.fieldId]));return l},DataSet.prototype.forEach=function(t,e){var i,n,s=e&&e.filter,o=e&&e.convert||this.options.convert,r=this.data;if(e&&e.order)for(var a=this.get(e),h=0,d=a.length;d>h;h++)i=a[h],n=i[this.fieldId],t(i,n);else for(n in r)r.hasOwnProperty(n)&&(i=this._getItem(n,o),(!s||s(i))&&t(i,n))},DataSet.prototype.map=function(t,e){var i,n=e&&e.filter,s=e&&e.convert||this.options.convert,o=[],r=this.data;for(var a in r)r.hasOwnProperty(a)&&(i=this._getItem(a,s),(!n||n(i))&&o.push(t(i,a)));return e&&e.order&&this._sort(o,e.order),o},DataSet.prototype._filterFields=function(t,e){var i={};for(var n in t)t.hasOwnProperty(n)&&-1!=e.indexOf(n)&&(i[n]=t[n]);return i},DataSet.prototype._sort=function(t,e){if(util.isString(e)){var i=e;t.sort(function(t,e){var n=t[i],s=e[i];return n>s?1:s>n?-1:0})}else{if("function"!=typeof e)throw new TypeError("Order must be a function or a string");t.sort(e)}},DataSet.prototype.remove=function(t,e){var i,n,s,o=[];if(t instanceof Array)for(i=0,n=t.length;n>i;i++)s=this._remove(t[i]),null!=s&&o.push(s);else s=this._remove(t),null!=s&&o.push(s);return o.length&&this._trigger("remove",{items:o},e),o},DataSet.prototype._remove=function(t){if(util.isNumber(t)||util.isString(t)){if(this.data[t])return delete this.data[t],delete this.internalIds[t],t}else if(t instanceof Object){var e=t[this.fieldId];if(e&&this.data[e])return delete this.data[e],delete this.internalIds[e],e}return null},DataSet.prototype.clear=function(t){var e=Object.keys(this.data);return this.data={},this.internalIds={},this._trigger("remove",{items:e},t),e},DataSet.prototype.max=function(t){var e=this.data,i=null,n=null;for(var s in e)if(e.hasOwnProperty(s)){var o=e[s],r=o[t];null!=r&&(!i||r>n)&&(i=o,n=r)}return i},DataSet.prototype.min=function(t){var e=this.data,i=null,n=null;for(var s in e)if(e.hasOwnProperty(s)){var o=e[s],r=o[t];null!=r&&(!i||n>r)&&(i=o,n=r)}return i},DataSet.prototype.distinct=function(t){var e=this.data,i=[],n=this.options.convert[t],s=0;for(var o in e)if(e.hasOwnProperty(o)){for(var r=e[o],a=util.convert(r[t],n),h=!1,d=0;s>d;d++)if(i[d]==a){h=!0;break}h||(i[s]=a,s++)}return i},DataSet.prototype._addItem=function(t){var e=t[this.fieldId];if(void 0!=e){if(this.data[e])throw new Error("Cannot add item: item with id "+e+" already exists")}else e=util.randomUUID(),t[this.fieldId]=e,this.internalIds[e]=t;var i={};for(var n in t)if(t.hasOwnProperty(n)){var s=this.convert[n];i[n]=util.convert(t[n],s)}return this.data[e]=i,e},DataSet.prototype._getItem=function(t,e){var i,n,s=this.data[t];if(!s)return null;var o={},r=this.fieldId,a=this.internalIds;if(e)for(i in s)s.hasOwnProperty(i)&&(n=s[i],i==r&&n in a&&!this.showInternalIds||(o[i]=util.convert(n,e[i])));else for(i in s)s.hasOwnProperty(i)&&(n=s[i],i==r&&n in a&&!this.showInternalIds||(o[i]=n));return o},DataSet.prototype._updateItem=function(t){var e=t[this.fieldId];if(void 0==e)throw new Error("Cannot update item: item has no id (item: "+JSON.stringify(t)+")");var i=this.data[e];if(!i)throw new Error("Cannot update item: no item with id "+e+" found");for(var n in t)if(t.hasOwnProperty(n)){var s=this.convert[n];i[n]=util.convert(t[n],s)}return e},DataSet.prototype.isInternalId=function(t){return t in this.internalIds},DataSet.prototype._getColumnNames=function(t){for(var e=[],i=0,n=t.getNumberOfColumns();n>i;i++)e[i]=t.getColumnId(i)||t.getColumnLabel(i);return e},DataSet.prototype._appendRow=function(t,e,i){for(var n=t.addRow(),s=0,o=e.length;o>s;s++){var r=e[s];t.setValue(n,s,i[r])}},DataView.prototype.setData=function(t){var e,i,n;if(this.data){this.data.unsubscribe&&this.data.unsubscribe("*",this.listener),e=[];for(var s in this.ids)this.ids.hasOwnProperty(s)&&e.push(s);this.ids={},this._trigger("remove",{items:e})}if(this.data=t,this.data){for(this.fieldId=this.options.fieldId||this.data&&this.data.options&&this.data.options.fieldId||"id",e=this.data.getIds({filter:this.options&&this.options.filter}),i=0,n=e.length;n>i;i++)s=e[i],this.ids[s]=!0;
+this._trigger("add",{items:e}),this.data.on&&this.data.on("*",this.listener)}},DataView.prototype.get=function(){var t,e,i,n=this,s=util.getType(arguments[0]);"String"==s||"Number"==s||"Array"==s?(t=arguments[0],e=arguments[1],i=arguments[2]):(e=arguments[0],i=arguments[1]);var o=util.extend({},this.options,e);this.options.filter&&e&&e.filter&&(o.filter=function(t){return n.options.filter(t)&&e.filter(t)});var r=[];return void 0!=t&&r.push(t),r.push(o),r.push(i),this.data&&this.data.get.apply(this.data,r)},DataView.prototype.getIds=function(t){var e;if(this.data){var i,n=this.options.filter;i=t&&t.filter?n?function(e){return n(e)&&t.filter(e)}:t.filter:n,e=this.data.getIds({filter:i,order:t&&t.order})}else e=[];return e},DataView.prototype._onEvent=function(t,e,i){var n,s,o,r,a=e&&e.items,h=this.data,d=[],l=[],c=[];if(a&&h){switch(t){case"add":for(n=0,s=a.length;s>n;n++)o=a[n],r=this.get(o),r&&(this.ids[o]=!0,d.push(o));break;case"update":for(n=0,s=a.length;s>n;n++)o=a[n],r=this.get(o),r?this.ids[o]?l.push(o):(this.ids[o]=!0,d.push(o)):this.ids[o]&&(delete this.ids[o],c.push(o));break;case"remove":for(n=0,s=a.length;s>n;n++)o=a[n],this.ids[o]&&(delete this.ids[o],c.push(o))}d.length&&this._trigger("add",{items:d},i),l.length&&this._trigger("update",{items:l},i),c.length&&this._trigger("remove",{items:c},i)}},DataView.prototype.on=DataSet.prototype.on,DataView.prototype.off=DataSet.prototype.off,DataView.prototype._trigger=DataSet.prototype._trigger,DataView.prototype.subscribe=DataView.prototype.on,DataView.prototype.unsubscribe=DataView.prototype.off,TimeStep=function(t,e,i){this.current=new Date,this._start=new Date,this._end=new Date,this.autoScale=!0,this.scale=TimeStep.SCALE.DAY,this.step=1,this.setRange(t,e,i)},TimeStep.SCALE={MILLISECOND:1,SECOND:2,MINUTE:3,HOUR:4,DAY:5,WEEKDAY:6,MONTH:7,YEAR:8},TimeStep.prototype.setRange=function(t,e,i){if(!(t instanceof Date&&e instanceof Date))throw"No legal start or end date in method setRange";this._start=void 0!=t?new Date(t.valueOf()):new Date,this._end=void 0!=e?new Date(e.valueOf()):new Date,this.autoScale&&this.setMinimumStep(i)},TimeStep.prototype.first=function(){this.current=new Date(this._start.valueOf()),this.roundToMinor()},TimeStep.prototype.roundToMinor=function(){switch(this.scale){case TimeStep.SCALE.YEAR:this.current.setFullYear(this.step*Math.floor(this.current.getFullYear()/this.step)),this.current.setMonth(0);case TimeStep.SCALE.MONTH:this.current.setDate(1);case TimeStep.SCALE.DAY:case TimeStep.SCALE.WEEKDAY:this.current.setHours(0);case TimeStep.SCALE.HOUR:this.current.setMinutes(0);case TimeStep.SCALE.MINUTE:this.current.setSeconds(0);case TimeStep.SCALE.SECOND:this.current.setMilliseconds(0)}if(1!=this.step)switch(this.scale){case TimeStep.SCALE.MILLISECOND:this.current.setMilliseconds(this.current.getMilliseconds()-this.current.getMilliseconds()%this.step);break;case TimeStep.SCALE.SECOND:this.current.setSeconds(this.current.getSeconds()-this.current.getSeconds()%this.step);break;case TimeStep.SCALE.MINUTE:this.current.setMinutes(this.current.getMinutes()-this.current.getMinutes()%this.step);break;case TimeStep.SCALE.HOUR:this.current.setHours(this.current.getHours()-this.current.getHours()%this.step);break;case TimeStep.SCALE.WEEKDAY:case TimeStep.SCALE.DAY:this.current.setDate(this.current.getDate()-1-(this.current.getDate()-1)%this.step+1);break;case TimeStep.SCALE.MONTH:this.current.setMonth(this.current.getMonth()-this.current.getMonth()%this.step);break;case TimeStep.SCALE.YEAR:this.current.setFullYear(this.current.getFullYear()-this.current.getFullYear()%this.step)}},TimeStep.prototype.hasNext=function(){return this.current.valueOf()<=this._end.valueOf()},TimeStep.prototype.next=function(){var t=this.current.valueOf();if(this.current.getMonth()<6)switch(this.scale){case TimeStep.SCALE.MILLISECOND:this.current=new Date(this.current.valueOf()+this.step);break;case TimeStep.SCALE.SECOND:this.current=new Date(this.current.valueOf()+1e3*this.step);break;case TimeStep.SCALE.MINUTE:this.current=new Date(this.current.valueOf()+1e3*this.step*60);break;case TimeStep.SCALE.HOUR:this.current=new Date(this.current.valueOf()+1e3*this.step*60*60);var e=this.current.getHours();this.current.setHours(e-e%this.step);break;case TimeStep.SCALE.WEEKDAY:case TimeStep.SCALE.DAY:this.current.setDate(this.current.getDate()+this.step);break;case TimeStep.SCALE.MONTH:this.current.setMonth(this.current.getMonth()+this.step);break;case TimeStep.SCALE.YEAR:this.current.setFullYear(this.current.getFullYear()+this.step)}else switch(this.scale){case TimeStep.SCALE.MILLISECOND:this.current=new Date(this.current.valueOf()+this.step);break;case TimeStep.SCALE.SECOND:this.current.setSeconds(this.current.getSeconds()+this.step);break;case TimeStep.SCALE.MINUTE:this.current.setMinutes(this.current.getMinutes()+this.step);break;case TimeStep.SCALE.HOUR:this.current.setHours(this.current.getHours()+this.step);break;case TimeStep.SCALE.WEEKDAY:case TimeStep.SCALE.DAY:this.current.setDate(this.current.getDate()+this.step);break;case TimeStep.SCALE.MONTH:this.current.setMonth(this.current.getMonth()+this.step);break;case TimeStep.SCALE.YEAR:this.current.setFullYear(this.current.getFullYear()+this.step)}if(1!=this.step)switch(this.scale){case TimeStep.SCALE.MILLISECOND:this.current.getMilliseconds()0&&(this.step=e),this.autoScale=!1},TimeStep.prototype.setAutoScale=function(t){this.autoScale=t},TimeStep.prototype.setMinimumStep=function(t){if(void 0!=t){var e=31104e6,i=2592e6,n=864e5,s=36e5,o=6e4,r=1e3,a=1;1e3*e>t&&(this.scale=TimeStep.SCALE.YEAR,this.step=1e3),500*e>t&&(this.scale=TimeStep.SCALE.YEAR,this.step=500),100*e>t&&(this.scale=TimeStep.SCALE.YEAR,this.step=100),50*e>t&&(this.scale=TimeStep.SCALE.YEAR,this.step=50),10*e>t&&(this.scale=TimeStep.SCALE.YEAR,this.step=10),5*e>t&&(this.scale=TimeStep.SCALE.YEAR,this.step=5),e>t&&(this.scale=TimeStep.SCALE.YEAR,this.step=1),3*i>t&&(this.scale=TimeStep.SCALE.MONTH,this.step=3),i>t&&(this.scale=TimeStep.SCALE.MONTH,this.step=1),5*n>t&&(this.scale=TimeStep.SCALE.DAY,this.step=5),2*n>t&&(this.scale=TimeStep.SCALE.DAY,this.step=2),n>t&&(this.scale=TimeStep.SCALE.DAY,this.step=1),n/2>t&&(this.scale=TimeStep.SCALE.WEEKDAY,this.step=1),4*s>t&&(this.scale=TimeStep.SCALE.HOUR,this.step=4),s>t&&(this.scale=TimeStep.SCALE.HOUR,this.step=1),15*o>t&&(this.scale=TimeStep.SCALE.MINUTE,this.step=15),10*o>t&&(this.scale=TimeStep.SCALE.MINUTE,this.step=10),5*o>t&&(this.scale=TimeStep.SCALE.MINUTE,this.step=5),o>t&&(this.scale=TimeStep.SCALE.MINUTE,this.step=1),15*r>t&&(this.scale=TimeStep.SCALE.SECOND,this.step=15),10*r>t&&(this.scale=TimeStep.SCALE.SECOND,this.step=10),5*r>t&&(this.scale=TimeStep.SCALE.SECOND,this.step=5),r>t&&(this.scale=TimeStep.SCALE.SECOND,this.step=1),200*a>t&&(this.scale=TimeStep.SCALE.MILLISECOND,this.step=200),100*a>t&&(this.scale=TimeStep.SCALE.MILLISECOND,this.step=100),50*a>t&&(this.scale=TimeStep.SCALE.MILLISECOND,this.step=50),10*a>t&&(this.scale=TimeStep.SCALE.MILLISECOND,this.step=10),5*a>t&&(this.scale=TimeStep.SCALE.MILLISECOND,this.step=5),a>t&&(this.scale=TimeStep.SCALE.MILLISECOND,this.step=1)}},TimeStep.prototype.snap=function(t){var e=new Date(t.valueOf());if(this.scale==TimeStep.SCALE.YEAR){var i=e.getFullYear()+Math.round(e.getMonth()/12);e.setFullYear(Math.round(i/this.step)*this.step),e.setMonth(0),e.setDate(0),e.setHours(0),e.setMinutes(0),e.setSeconds(0),e.setMilliseconds(0)}else if(this.scale==TimeStep.SCALE.MONTH)e.getDate()>15?(e.setDate(1),e.setMonth(e.getMonth()+1)):e.setDate(1),e.setHours(0),e.setMinutes(0),e.setSeconds(0),e.setMilliseconds(0);else if(this.scale==TimeStep.SCALE.DAY||this.scale==TimeStep.SCALE.WEEKDAY){switch(this.step){case 5:case 2:e.setHours(24*Math.round(e.getHours()/24));break;default:e.setHours(12*Math.round(e.getHours()/12))}e.setMinutes(0),e.setSeconds(0),e.setMilliseconds(0)}else if(this.scale==TimeStep.SCALE.HOUR){switch(this.step){case 4:e.setMinutes(60*Math.round(e.getMinutes()/60));break;default:e.setMinutes(30*Math.round(e.getMinutes()/30))}e.setSeconds(0),e.setMilliseconds(0)}else if(this.scale==TimeStep.SCALE.MINUTE){switch(this.step){case 15:case 10:e.setMinutes(5*Math.round(e.getMinutes()/5)),e.setSeconds(0);break;case 5:e.setSeconds(60*Math.round(e.getSeconds()/60));break;default:e.setSeconds(30*Math.round(e.getSeconds()/30))}e.setMilliseconds(0)}else if(this.scale==TimeStep.SCALE.SECOND)switch(this.step){case 15:case 10:e.setSeconds(5*Math.round(e.getSeconds()/5)),e.setMilliseconds(0);break;case 5:e.setMilliseconds(1e3*Math.round(e.getMilliseconds()/1e3));break;default:e.setMilliseconds(500*Math.round(e.getMilliseconds()/500))}else if(this.scale==TimeStep.SCALE.MILLISECOND){var n=this.step>5?this.step/2:1;e.setMilliseconds(Math.round(e.getMilliseconds()/n)*n)}return e},TimeStep.prototype.isMajor=function(){switch(this.scale){case TimeStep.SCALE.MILLISECOND:return 0==this.current.getMilliseconds();case TimeStep.SCALE.SECOND:return 0==this.current.getSeconds();case TimeStep.SCALE.MINUTE:return 0==this.current.getHours()&&0==this.current.getMinutes();case TimeStep.SCALE.HOUR:return 0==this.current.getHours();case TimeStep.SCALE.WEEKDAY:case TimeStep.SCALE.DAY:return 1==this.current.getDate();case TimeStep.SCALE.MONTH:return 0==this.current.getMonth();case TimeStep.SCALE.YEAR:return!1;default:return!1}},TimeStep.prototype.getLabelMinor=function(t){switch(void 0==t&&(t=this.current),this.scale){case TimeStep.SCALE.MILLISECOND:return moment(t).format("SSS");case TimeStep.SCALE.SECOND:return moment(t).format("s");case TimeStep.SCALE.MINUTE:return moment(t).format("HH:mm");case TimeStep.SCALE.HOUR:return moment(t).format("HH:mm");case TimeStep.SCALE.WEEKDAY:return moment(t).format("ddd D");case TimeStep.SCALE.DAY:return moment(t).format("D");case TimeStep.SCALE.MONTH:return moment(t).format("MMM");case TimeStep.SCALE.YEAR:return moment(t).format("YYYY");default:return""}},TimeStep.prototype.getLabelMajor=function(t){switch(void 0==t&&(t=this.current),this.scale){case TimeStep.SCALE.MILLISECOND:return moment(t).format("HH:mm:ss");case TimeStep.SCALE.SECOND:return moment(t).format("D MMMM HH:mm");case TimeStep.SCALE.MINUTE:case TimeStep.SCALE.HOUR:return moment(t).format("ddd D MMMM");case TimeStep.SCALE.WEEKDAY:case TimeStep.SCALE.DAY:return moment(t).format("MMMM YYYY");case TimeStep.SCALE.MONTH:return moment(t).format("YYYY");case TimeStep.SCALE.YEAR:return"";default:return""}},Stack.prototype.setOptions=function(t){util.extend(this.options,t)},Stack.prototype.update=function(){this._order(),this._stack()},Stack.prototype._order=function(){var t=this.itemset.items;if(!t)throw new Error("Cannot stack items: ItemSet does not contain items");var e=[],i=0;util.forEach(t,function(t){t.visible&&(e[i]=t,i++)});var n=this.options.order||this.defaultOptions.order;if("function"!=typeof n)throw new Error("Option order must be a function");e.sort(n),this.ordered=e},Stack.prototype._stack=function(){var t,e,i,n=this.ordered,s=this.options,o=s.orientation||this.defaultOptions.orientation,r="top"==o;for(i=s.margin&&void 0!==s.margin.item?s.margin.item:this.defaultOptions.margin.item,t=0,e=n.length;e>t;t++){var a=n[t],h=null;do h=this.checkOverlap(n,t,0,t-1,i),null!=h&&(a.top=r?h.top+h.height+i:h.top-a.height-i);while(h)}},Stack.prototype.checkOverlap=function(t,e,i,n,s){for(var o=this.collision,r=t[e],a=n;a>=i;a--){var h=t[a];if(o(r,h,s)&&a!=e)return h}return null},Stack.prototype.collision=function(t,e,i){return t.left-ie.left&&t.top-ie.top},Emitter(Range.prototype),Range.prototype.setOptions=function(t){util.extend(this.options,t),null!==this.start&&null!==this.end&&this.setRange(this.start,this.end)},Range.prototype.subscribe=function(t,e,i,n){function s(t){o._onMouseWheel(t,e,n)}var o=this;if("move"==i)t.on("dragstart",function(t){o._onDragStart(t,e)}),t.on("drag",function(t){o._onDrag(t,e,n)}),t.on("dragend",function(t){o._onDragEnd(t,e)}),t.on("hold",function(){o._onHold()});else{if("zoom"!=i)throw new TypeError('Unknown event "'+i+'". Choose "move" or "zoom".');t.on("mousewheel",s),t.on("DOMMouseScroll",s),t.on("touch",function(t){o._onTouch(t)}),t.on("pinch",function(t){o._onPinch(t,e,n)})}},Range.prototype.setRange=function(t,e){var i=this._applyRange(t,e);if(i){var n={start:this.start,end:this.end};this.emit("rangechange",n),this.emit("rangechanged",n)}},Range.prototype._applyRange=function(t,e){var i,n=null!=t?util.convert(t,"Date").valueOf():this.start,s=null!=e?util.convert(e,"Date").valueOf():this.end,o=null!=this.options.max?util.convert(this.options.max,"Date").valueOf():null,r=null!=this.options.min?util.convert(this.options.min,"Date").valueOf():null;if(isNaN(n)||null===n)throw new Error('Invalid start "'+t+'"');if(isNaN(s)||null===s)throw new Error('Invalid end "'+e+'"');if(n>s&&(s=n),null!==r&&r>n&&(i=r-n,n+=i,s+=i,null!=o&&s>o&&(s=o)),null!==o&&s>o&&(i=s-o,n-=i,s-=i,null!=r&&r>n&&(n=r)),null!==this.options.zoomMin){var a=parseFloat(this.options.zoomMin);0>a&&(a=0),a>s-n&&(this.end-this.start===a?(n=this.start,s=this.end):(i=a-(s-n),n-=i/2,s+=i/2))}if(null!==this.options.zoomMax){var h=parseFloat(this.options.zoomMax);0>h&&(h=0),s-n>h&&(this.end-this.start===h?(n=this.start,s=this.end):(i=s-n-h,n+=i/2,s-=i/2))}var d=this.start!=n||this.end!=s;return this.start=n,this.end=s,d},Range.prototype.getRange=function(){return{start:this.start,end:this.end}},Range.prototype.conversion=function(t){return Range.conversion(this.start,this.end,t)},Range.conversion=function(t,e,i){return 0!=i&&e-t!=0?{offset:t,scale:i/(e-t)}:{offset:0,scale:1}};var touchParams={};Range.prototype._onDragStart=function(t,e){if(!touchParams.ignore){touchParams.start=this.start,touchParams.end=this.end;var i=e.frame;i&&(i.style.cursor="move")}},Range.prototype._onDrag=function(t,e,i){if(validateDirection(i),!touchParams.ignore){var n="horizontal"==i?t.gesture.deltaX:t.gesture.deltaY,s=touchParams.end-touchParams.start,o="horizontal"==i?e.width:e.height,r=-n/o*s;this._applyRange(touchParams.start+r,touchParams.end+r),this.emit("rangechange",{start:this.start,end:this.end})}},Range.prototype._onDragEnd=function(t,e){touchParams.ignore||(e.frame&&(e.frame.style.cursor="auto"),this.emit("rangechanged",{start:this.start,end:this.end}))},Range.prototype._onMouseWheel=function(t,e,i){validateDirection(i);var n=0;if(t.wheelDelta?n=t.wheelDelta/120:t.detail&&(n=-t.detail/3),n){var s;s=0>n?1-n/5:1/(1+n/5);var o=util.fakeGesture(this,t),r=getPointer(o.center,e.frame),a=this._pointerToDate(e,i,r);this.zoom(s,a)}t.preventDefault()},Range.prototype._onTouch=function(t){touchParams.start=this.start,touchParams.end=this.end,touchParams.ignore=!1,touchParams.center=null;var e=ItemSet.itemFromTarget(t);e&&e.selected&&this.options.editable&&(touchParams.ignore=!0)},Range.prototype._onHold=function(){touchParams.ignore=!0},Range.prototype._onPinch=function(t,e,i){if(touchParams.ignore=!0,t.gesture.touches.length>1){touchParams.center||(touchParams.center=getPointer(t.gesture.center,e.frame));var n=1/t.gesture.scale,s=this._pointerToDate(e,i,touchParams.center),o=getPointer(t.gesture.center,e.frame),r=(this._pointerToDate(e,i,o),parseInt(s+(touchParams.start-s)*n)),a=parseInt(s+(touchParams.end-s)*n);this.setRange(r,a)}},Range.prototype._pointerToDate=function(t,e,i){var n;if("horizontal"==e){var s=t.width;return n=this.conversion(s),i.x/n.scale+n.offset}var o=t.height;return n=this.conversion(o),i.y/n.scale+n.offset},Range.prototype.zoom=function(t,e){null==e&&(e=(this.start+this.end)/2);var i=e+(this.start-e)*t,n=e+(this.end-e)*t;this.setRange(i,n)},Range.prototype.move=function(t){var e=this.end-this.start,i=this.start+e*t,n=this.end+e*t;this.start=i,this.end=n},Range.prototype.moveTo=function(t){var e=(this.start+this.end)/2,i=e-t,n=this.start-i,s=this.end-i;this.setRange(n,s)},Emitter(Controller.prototype),Controller.prototype.add=function(t){if(void 0==t.id)throw new Error("Component has no field id");if(!(t instanceof Component||t instanceof Controller))throw new TypeError("Component must be an instance of prototype Component or Controller");t.setController(this),this.components[t.id]=t},Controller.prototype.remove=function(t){var e;for(e in this.components)if(this.components.hasOwnProperty(e)&&(e==t||this.components[e]===t))break;e&&(this.components[e].setController(null),delete this.components[e])},Controller.prototype.repaint=function t(){function t(n,s){s in i||(n.depends&&n.depends.forEach(function(e){t(e,e.id)}),n.parent&&t(n.parent,n.parent.id),e=n.repaint()||e,i[s]=!0)}var e=!1;this.repaintTimer&&(clearTimeout(this.repaintTimer),this.repaintTimer=void 0);var i={};util.forEach(this.components,t),this.emit("repaint"),e&&this.reflow()},Controller.prototype.reflow=function e(){function e(n,s){s in i||(n.depends&&n.depends.forEach(function(t){e(t,t.id)}),n.parent&&e(n.parent,n.parent.id),t=n.reflow()||t,i[s]=!0)}var t=!1;this.reflowTimer&&(clearTimeout(this.reflowTimer),this.reflowTimer=void 0);var i={};util.forEach(this.components,e),this.emit("reflow"),t&&this.repaint()},Component.prototype.setOptions=function(t){t&&(util.extend(this.options,t),this.controller&&(this.requestRepaint(),this.requestReflow()))},Component.prototype.getOption=function(t){var e;return this.options&&(e=this.options[t]),void 0===e&&this.defaultOptions&&(e=this.defaultOptions[t]),e},Component.prototype.setController=function(t){this.controller=t||null},Component.prototype.getController=function(){return this.controller},Component.prototype.getContainer=function(){return null},Component.prototype.getFrame=function(){return this.frame},Component.prototype.repaint=function(){return!1},Component.prototype.reflow=function(){return!1},Component.prototype.hide=function(){return this.frame&&this.frame.parentNode?(this.frame.parentNode.removeChild(this.frame),!0):!1},Component.prototype.show=function(){return this.frame&&this.frame.parentNode?!1:this.repaint()},Component.prototype.requestRepaint=function(){if(!this.controller)throw new Error("Cannot request a repaint: no controller configured");this.controller.emit("request-repaint")},Component.prototype.requestReflow=function(){if(!this.controller)throw new Error("Cannot request a reflow: no controller configured");this.controller.emit("request-reflow")},Panel.prototype=new Component,Panel.prototype.setOptions=Component.prototype.setOptions,Panel.prototype.getContainer=function(){return this.frame},Panel.prototype.repaint=function(){var t=0,e=util.updateProperty,i=util.option.asSize,n=this.options,s=this.frame;if(!s){s=document.createElement("div"),s.className="vpanel";var o=n.className;o&&("function"==typeof o?util.addClassName(s,String(o())):util.addClassName(s,String(o))),this.frame=s,t+=1}if(!s.parentNode){if(!this.parent)throw new Error("Cannot repaint panel: no parent attached");var r=this.parent.getContainer();if(!r)throw new Error("Cannot repaint panel: parent has no container element");r.appendChild(s),t+=1}return t+=e(s.style,"top",i(n.top,"0px")),t+=e(s.style,"left",i(n.left,"0px")),t+=e(s.style,"width",i(n.width,"100%")),t+=e(s.style,"height",i(n.height,"100%")),t>0},Panel.prototype.reflow=function(){var t=0,e=util.updateProperty,i=this.frame;return i?(t+=e(this,"top",i.offsetTop),t+=e(this,"left",i.offsetLeft),t+=e(this,"width",i.offsetWidth),t+=e(this,"height",i.offsetHeight)):t+=1,t>0},RootPanel.prototype=new Panel,RootPanel.prototype.setOptions=Component.prototype.setOptions,RootPanel.prototype.repaint=function(){var t=0,e=util.updateProperty,i=util.option.asSize,n=this.options,s=this.frame;if(s||(s=document.createElement("div"),this.frame=s,this._registerListeners(),t+=1),!s.parentNode){if(!this.container)throw new Error("Cannot repaint root panel: no container attached");this.container.appendChild(s),t+=1}s.className="vis timeline rootpanel "+n.orientation+(n.editable?" editable":"");var o=n.className;return o&&util.addClassName(s,util.option.asString(o)),t+=e(s.style,"top",i(n.top,"0px")),t+=e(s.style,"left",i(n.left,"0px")),t+=e(s.style,"width",i(n.width,"100%")),t+=e(s.style,"height",i(n.height,"100%")),this._updateWatch(),t>0},RootPanel.prototype.reflow=function(){var t=0,e=util.updateProperty,i=this.frame;return i?(t+=e(this,"top",i.offsetTop),t+=e(this,"left",i.offsetLeft),t+=e(this,"width",i.offsetWidth),t+=e(this,"height",i.offsetHeight)):t+=1,t>0},RootPanel.prototype._updateWatch=function(){var t=this.getOption("autoResize");t?this._watch():this._unwatch()},RootPanel.prototype._watch=function(){var t=this;this._unwatch();var e=function(){var e=t.getOption("autoResize");return e?void(t.frame&&(t.frame.clientWidth!=t.width||t.frame.clientHeight!=t.height)&&t.requestReflow()):void t._unwatch()};util.addEventListener(window,"resize",e),this.watchTimer=setInterval(e,1e3)},RootPanel.prototype._unwatch=function(){this.watchTimer&&(clearInterval(this.watchTimer),this.watchTimer=void 0)},RootPanel.prototype.setController=function(t){this.controller=t||null,this.controller?this._registerListeners():this._unregisterListeners()},RootPanel.prototype._registerListeners=function(){if(this.frame&&this.controller&&!this.hammer){this.hammer=Hammer(this.frame,{prevent_default:!0});for(var t in this.listeners)this.listeners.hasOwnProperty(t)&&this.hammer.on(t,this.listeners[t])}},RootPanel.prototype._unregisterListeners=function(){if(this.hammer){for(var t in this.listeners)this.listeners.hasOwnProperty(t)&&this.hammer.off(t,this.listeners[t]);this.hammer=null}},TimeAxis.prototype=new Component,TimeAxis.prototype.setOptions=Component.prototype.setOptions,TimeAxis.prototype.setRange=function(t){if(!(t instanceof Range||t&&t.start&&t.end))throw new TypeError("Range must be an instance of Range, or an object containing start and end.");this.range=t},TimeAxis.prototype.toTime=function(t){var e=this.conversion;return new Date(t/e.scale+e.offset)},TimeAxis.prototype.toScreen=function(t){var e=this.conversion;return(t.valueOf()-e.offset)*e.scale},TimeAxis.prototype.repaint=function(){var t=0,e=util.updateProperty,i=util.option.asSize,n=this.options,s=this.getOption("orientation"),o=this.props,r=this.step,a=this.frame;if(a||(a=document.createElement("div"),this.frame=a,t+=1),a.className="axis",!a.parentNode){if(!this.parent)throw new Error("Cannot repaint time axis: no parent attached");var h=this.parent.getContainer();if(!h)throw new Error("Cannot repaint time axis: parent has no container element");h.appendChild(a),t+=1}var d=a.parentNode;if(d){var l=a.nextSibling;d.removeChild(a);var c="bottom"==s&&this.props.parentHeight&&this.height?this.props.parentHeight-this.height+"px":"0px";if(t+=e(a.style,"top",i(n.top,c)),t+=e(a.style,"left",i(n.left,"0px")),t+=e(a.style,"width",i(n.width,"100%")),t+=e(a.style,"height",i(n.height,this.height+"px")),this._repaintMeasureChars(),this.step){this._repaintStart(),r.first();for(var u=void 0,p=0;r.hasNext()&&1e3>p;){p++;var f=r.getCurrent(),m=this.toScreen(f),g=r.isMajor();this.getOption("showMinorLabels")&&this._repaintMinorText(m,r.getLabelMinor()),g&&this.getOption("showMajorLabels")?(m>0&&(void 0==u&&(u=m),this._repaintMajorText(m,r.getLabelMajor())),this._repaintMajorLine(m)):this._repaintMinorLine(m),r.next()}if(this.getOption("showMajorLabels")){var v=this.toTime(0),y=r.getLabelMajor(v),_=y.length*(o.majorCharWidth||10)+10;(void 0==u||u>_)&&this._repaintMajorText(0,y)}this._repaintEnd()}this._repaintLine(),l?d.insertBefore(a,l):d.appendChild(a)}return t>0},TimeAxis.prototype._repaintStart=function(){var t=this.dom,e=t.redundant;e.majorLines=t.majorLines,e.majorTexts=t.majorTexts,e.minorLines=t.minorLines,e.minorTexts=t.minorTexts,t.majorLines=[],t.majorTexts=[],t.minorLines=[],t.minorTexts=[]},TimeAxis.prototype._repaintEnd=function(){util.forEach(this.dom.redundant,function(t){for(;t.length;){var e=t.pop();e&&e.parentNode&&e.parentNode.removeChild(e)}})},TimeAxis.prototype._repaintMinorText=function(t,e){var i=this.dom.redundant.minorTexts.shift();if(!i){var n=document.createTextNode("");i=document.createElement("div"),i.appendChild(n),i.className="text minor",this.frame.appendChild(i)}this.dom.minorTexts.push(i),i.childNodes[0].nodeValue=e,i.style.left=t+"px",i.style.top=this.props.minorLabelTop+"px"},TimeAxis.prototype._repaintMajorText=function(t,e){var i=this.dom.redundant.majorTexts.shift();if(!i){var n=document.createTextNode(e);i=document.createElement("div"),i.className="text major",i.appendChild(n),this.frame.appendChild(i)}this.dom.majorTexts.push(i),i.childNodes[0].nodeValue=e,i.style.top=this.props.majorLabelTop+"px",i.style.left=t+"px"},TimeAxis.prototype._repaintMinorLine=function(t){var e=this.dom.redundant.minorLines.shift();e||(e=document.createElement("div"),e.className="grid vertical minor",this.frame.appendChild(e)),this.dom.minorLines.push(e);var i=this.props;e.style.top=i.minorLineTop+"px",e.style.height=i.minorLineHeight+"px",e.style.left=t-i.minorLineWidth/2+"px"},TimeAxis.prototype._repaintMajorLine=function(t){var e=this.dom.redundant.majorLines.shift();e||(e=document.createElement("DIV"),e.className="grid vertical major",this.frame.appendChild(e)),this.dom.majorLines.push(e);var i=this.props;e.style.top=i.majorLineTop+"px",e.style.left=t-i.majorLineWidth/2+"px",e.style.height=i.majorLineHeight+"px"},TimeAxis.prototype._repaintLine=function(){{var t=this.dom.line,e=this.frame;this.options}this.getOption("showMinorLabels")||this.getOption("showMajorLabels")?(t?(e.removeChild(t),e.appendChild(t)):(t=document.createElement("div"),t.className="grid horizontal major",e.appendChild(t),this.dom.line=t),t.style.top=this.props.lineTop+"px"):t&&t.parentElement&&(e.removeChild(t.line),delete this.dom.line)},TimeAxis.prototype._repaintMeasureChars=function(){var t,e=this.dom;if(!e.measureCharMinor){t=document.createTextNode("0");var i=document.createElement("DIV");i.className="text minor measure",i.appendChild(t),this.frame.appendChild(i),e.measureCharMinor=i}if(!e.measureCharMajor){t=document.createTextNode("0");var n=document.createElement("DIV");n.className="text major measure",n.appendChild(t),this.frame.appendChild(n),e.measureCharMajor=n}},TimeAxis.prototype.reflow=function(){var t=0,e=util.updateProperty,i=this.frame,n=this.range;if(!n)throw new Error("Cannot repaint time axis: no range configured");if(i){t+=e(this,"top",i.offsetTop),t+=e(this,"left",i.offsetLeft);var s=this.props,o=this.getOption("showMinorLabels"),r=this.getOption("showMajorLabels"),a=this.dom.measureCharMinor,h=this.dom.measureCharMajor;a&&(s.minorCharHeight=a.clientHeight,s.minorCharWidth=a.clientWidth),h&&(s.majorCharHeight=h.clientHeight,s.majorCharWidth=h.clientWidth);var d=i.parentNode?i.parentNode.offsetHeight:0;switch(d!=s.parentHeight&&(s.parentHeight=d,t+=1),this.getOption("orientation")){case"bottom":s.minorLabelHeight=o?s.minorCharHeight:0,s.majorLabelHeight=r?s.majorCharHeight:0,s.minorLabelTop=0,s.majorLabelTop=s.minorLabelTop+s.minorLabelHeight,s.minorLineTop=-this.top,s.minorLineHeight=Math.max(this.top+s.majorLabelHeight,0),s.minorLineWidth=1,s.majorLineTop=-this.top,s.majorLineHeight=Math.max(this.top+s.minorLabelHeight+s.majorLabelHeight,0),s.majorLineWidth=1,s.lineTop=0;break;case"top":s.minorLabelHeight=o?s.minorCharHeight:0,s.majorLabelHeight=r?s.majorCharHeight:0,s.majorLabelTop=0,s.minorLabelTop=s.majorLabelTop+s.majorLabelHeight,s.minorLineTop=s.minorLabelTop,s.minorLineHeight=Math.max(d-s.majorLabelHeight-this.top),s.minorLineWidth=1,s.majorLineTop=0,s.majorLineHeight=Math.max(d-this.top),s.majorLineWidth=1,s.lineTop=s.majorLabelHeight+s.minorLabelHeight;break;default:throw new Error('Unkown orientation "'+this.getOption("orientation")+'"')}var l=s.minorLabelHeight+s.majorLabelHeight;t+=e(this,"width",i.offsetWidth),t+=e(this,"height",l),this._updateConversion();var c=util.convert(n.start,"Number"),u=util.convert(n.end,"Number"),p=this.toTime(5*(s.minorCharWidth||10)).valueOf()-this.toTime(0).valueOf();this.step=new TimeStep(new Date(c),new Date(u),p),t+=e(s.range,"start",c),t+=e(s.range,"end",u),t+=e(s.range,"minimumStep",p.valueOf())}return t>0},TimeAxis.prototype._updateConversion=function(){var t=this.range;if(!t)throw new Error("No range configured");this.conversion=t.conversion?t.conversion(this.width):Range.conversion(t.start,t.end,this.width)},TimeAxis.prototype.snap=function(t){return this.step.snap(t)},CurrentTime.prototype=new Component,CurrentTime.prototype.setOptions=Component.prototype.setOptions,CurrentTime.prototype.getContainer=function(){return this.frame},CurrentTime.prototype.repaint=function(){var t=this.frame,e=this.parent,i=e.parent.getContainer();if(!e)throw new Error("Cannot repaint bar: no parent attached");if(!i)throw new Error("Cannot repaint bar: parent has no container element");if(!this.getOption("showCurrentTime"))return t&&(i.removeChild(t),delete this.frame),!1;t||(t=document.createElement("div"),t.className="currenttime",t.style.position="absolute",t.style.top="0px",t.style.height="100%",i.appendChild(t),this.frame=t),e.conversion||e._updateConversion();var n=new Date,s=e.toScreen(n);t.style.left=s+"px",t.title="Current time: "+n,void 0!==this.currentTimeTimer&&(clearTimeout(this.currentTimeTimer),delete this.currentTimeTimer);var o=this,r=1/e.conversion.scale/2;return 30>r&&(r=30),this.currentTimeTimer=setTimeout(function(){o.repaint()},r),!1},CustomTime.prototype=new Component,Emitter(CustomTime.prototype),CustomTime.prototype.setOptions=Component.prototype.setOptions,CustomTime.prototype.getContainer=function(){return this.frame},CustomTime.prototype.repaint=function(){var t=this.frame,e=this.parent;if(!e)throw new Error("Cannot repaint bar: no parent attached");var i=e.parent.getContainer();if(!i)throw new Error("Cannot repaint bar: parent has no container element");if(!this.getOption("showCustomTime"))return t&&(i.removeChild(t),delete this.frame),!1;if(!t){t=document.createElement("div"),t.className="customtime",t.style.position="absolute",t.style.top="0px",t.style.height="100%",i.appendChild(t);var n=document.createElement("div");n.style.position="relative",n.style.top="0px",n.style.left="-10px",n.style.height="100%",n.style.width="20px",t.appendChild(n),this.frame=t,this.hammer=Hammer(t,{prevent_default:!0}),this.hammer.on("dragstart",this._onDragStart.bind(this)),this.hammer.on("drag",this._onDrag.bind(this)),this.hammer.on("dragend",this._onDragEnd.bind(this))}e.conversion||e._updateConversion();var s=e.toScreen(this.customTime);return t.style.left=s+"px",t.title="Time: "+this.customTime,!1},CustomTime.prototype.setCustomTime=function(t){this.customTime=new Date(t.valueOf()),this.repaint()},CustomTime.prototype.getCustomTime=function(){return new Date(this.customTime.valueOf())},CustomTime.prototype._onDragStart=function(t){this.eventParams.customTime=this.customTime,t.stopPropagation(),t.preventDefault()},CustomTime.prototype._onDrag=function(t){var e=t.gesture.deltaX,i=this.parent.toScreen(this.eventParams.customTime)+e,n=this.parent.toTime(i);this.setCustomTime(n),this.controller&&this.controller.emit("timechange",{time:this.customTime}),t.stopPropagation(),t.preventDefault()},CustomTime.prototype._onDragEnd=function(t){this.controller&&this.controller.emit("timechanged",{time:this.customTime}),t.stopPropagation(),t.preventDefault()},ItemSet.prototype=new Panel,ItemSet.types={box:ItemBox,range:ItemRange,rangeoverflow:ItemRangeOverflow,point:ItemPoint},ItemSet.prototype.setOptions=Component.prototype.setOptions,ItemSet.prototype.setController=function(t){var e;
+if(this.controller)for(e in this.eventListeners)this.eventListeners.hasOwnProperty(e)&&this.controller.off(e,this.eventListeners[e]);if(this.controller=t||null,this.controller)for(e in this.eventListeners)this.eventListeners.hasOwnProperty(e)&&this.controller.on(e,this.eventListeners[e])},function(t){var e=null;Object.defineProperty(t,"controller",{get:function(){return e},set:function(){}})}(this),ItemSet.prototype.setRange=function(t){if(!(t instanceof Range||t&&t.start&&t.end))throw new TypeError("Range must be an instance of Range, or an object containing start and end.");this.range=t},ItemSet.prototype.setSelection=function(t){var e,i,n,s;if(t){if(!Array.isArray(t))throw new TypeError("Array expected");for(e=0,i=this.selection.length;i>e;e++)n=this.selection[e],s=this.items[n],s&&s.unselect();for(this.selection=[],e=0,i=t.length;i>e;e++)n=t[e],s=this.items[n],s&&(this.selection.push(n),s.select());this.controller&&this.requestRepaint()}},ItemSet.prototype.getSelection=function(){return this.selection.concat([])},ItemSet.prototype._deselect=function(t){for(var e=this.selection,i=0,n=e.length;n>i;i++)if(e[i]==t){e.splice(i,1);break}},ItemSet.prototype.repaint=function(){var t=0,e=util.updateProperty,i=util.option.asSize,n=this.options,s=this.getOption("orientation"),o=this.defaultOptions,r=this.frame;if(!r){r=document.createElement("div"),r.className="itemset",r["timeline-itemset"]=this;var a=n.className;a&&util.addClassName(r,util.option.asString(a));var h=document.createElement("div");h.className="background",r.appendChild(h),this.dom.background=h;var d=document.createElement("div");d.className="foreground",r.appendChild(d),this.dom.foreground=d;var l=document.createElement("div");l.className="itemset-axis",this.dom.axis=l,this.frame=r,t+=1}if(!this.parent)throw new Error("Cannot repaint itemset: no parent attached");var c=this.parent.getContainer();if(!c)throw new Error("Cannot repaint itemset: parent has no container element");r.parentNode||(c.appendChild(r),t+=1),this.dom.axis.parentNode||(c.appendChild(this.dom.axis),t+=1),t+=e(r.style,"left",i(n.left,"0px")),t+=e(r.style,"top",i(n.top,"0px")),t+=e(r.style,"width",i(n.width,"100%")),t+=e(r.style,"height",i(n.height,this.height+"px")),t+=e(this.dom.axis.style,"left",i(n.left,"0px")),t+=e(this.dom.axis.style,"width",i(n.width,"100%")),t+="bottom"==s?e(this.dom.axis.style,"top",this.height+this.top+"px"):e(this.dom.axis.style,"top",this.top+"px"),this._updateConversion();var u=this,p=this.queue,f=this.itemsData,m=this.items,g={};for(var v in p)if(p.hasOwnProperty(v)){var y=p[v],_=m[v],w=y.action;switch(w){case"add":case"update":var b=f&&f.get(v,g);if(b){var S=b.type||b.start&&b.end&&"range"||n.type||"box",E=ItemSet.types[S];if(_&&(E&&_ instanceof E?(_.data=b,t++):(t+=_.hide(),_=null)),!_){if(!E)throw new TypeError('Unknown item type "'+S+'"');_=new E(u,b,n,o),_.id=y.id,t++}_.repaint(),m[v]=_}delete p[v];break;case"remove":_&&(_.selected&&u._deselect(v),t+=_.hide()),delete m[v],delete p[v];break;default:console.log('Error: unknown action "'+w+'"')}}return util.forEach(this.items,function(e){e.visible?(t+=e.show(),e.reposition()):t+=e.hide()}),t>0},ItemSet.prototype.getForeground=function(){return this.dom.foreground},ItemSet.prototype.getBackground=function(){return this.dom.background},ItemSet.prototype.getAxis=function(){return this.dom.axis},ItemSet.prototype.reflow=function(){var t=0,e=this.options,i=e.margin&&e.margin.axis||this.defaultOptions.margin.axis,n=e.margin&&e.margin.item||this.defaultOptions.margin.item,s=util.updateProperty,o=util.option.asNumber,r=util.option.asSize,a=this.frame;if(a){this._updateConversion(),util.forEach(this.items,function(e){t+=e.reflow()}),this.stack.update();var h,d=o(e.maxHeight),l=null!=r(e.height);if(l)h=a.offsetHeight;else{var c=this.stack.ordered;if(c.length){var u=c[0].top,p=c[0].top+c[0].height;util.forEach(c,function(t){u=Math.min(u,t.top),p=Math.max(p,t.top+t.height)}),h=p-u+i+n}else h=i+n}null!=d&&(h=Math.min(h,d)),t+=s(this,"height",h),t+=s(this,"top",a.offsetTop),t+=s(this,"left",a.offsetLeft),t+=s(this,"width",a.offsetWidth)}else t+=1;return t>0},ItemSet.prototype.hide=function(){var t=!1;return this.frame&&this.frame.parentNode&&(this.frame.parentNode.removeChild(this.frame),t=!0),this.dom.axis&&this.dom.axis.parentNode&&(this.dom.axis.parentNode.removeChild(this.dom.axis),t=!0),t},ItemSet.prototype.setItems=function(t){var e,i=this,n=this.itemsData;if(t){if(!(t instanceof DataSet||t instanceof DataView))throw new TypeError("Data must be an instance of DataSet");this.itemsData=t}else this.itemsData=null;if(n&&(util.forEach(this.listeners,function(t,e){n.unsubscribe(e,t)}),e=n.getIds(),this._onRemove(e)),this.itemsData){var s=this.id;util.forEach(this.listeners,function(t,e){i.itemsData.on(e,t,s)}),e=this.itemsData.getIds(),this._onAdd(e)}},ItemSet.prototype.getItems=function(){return this.itemsData},ItemSet.prototype.removeItem=function(t){var e=this.itemsData.get(t),i=this._myDataSet();e&&this.options.onRemove(e,function(t){t&&i.remove(t)})},ItemSet.prototype._onUpdate=function(t){this._toQueue("update",t)},ItemSet.prototype._onAdd=function(t){this._toQueue("add",t)},ItemSet.prototype._onRemove=function(t){this._toQueue("remove",t)},ItemSet.prototype._toQueue=function(t,e){var i=this.queue;e.forEach(function(e){i[e]={id:e,action:t}}),this.controller&&this.requestRepaint()},ItemSet.prototype._updateConversion=function(){var t=this.range;if(!t)throw new Error("No range configured");this.conversion=t.conversion?t.conversion(this.width):Range.conversion(t.start,t.end,this.width)},ItemSet.prototype.toTime=function(t){var e=this.conversion;return new Date(t/e.scale+e.offset)},ItemSet.prototype.toScreen=function(t){var e=this.conversion;return(t.valueOf()-e.offset)*e.scale},ItemSet.prototype._onDragStart=function(t){if(this.options.editable){var e=ItemSet.itemFromTarget(t),i=this;if(e&&e.selected){var n=t.target.dragLeftItem,s=t.target.dragRightItem;this.touchParams.itemProps=n?[{item:n,start:e.data.start.valueOf()}]:s?[{item:s,end:e.data.end.valueOf()}]:this.getSelection().map(function(t){var e=i.items[t],n={item:e};return"start"in e.data&&(n.start=e.data.start.valueOf()),"end"in e.data&&(n.end=e.data.end.valueOf()),n}),t.stopPropagation()}}},ItemSet.prototype._onDrag=function(t){if(this.touchParams.itemProps){var e=this.options.snap||null,i=t.gesture.deltaX,n=i/this.conversion.scale;this.touchParams.itemProps.forEach(function(t){if("start"in t){var i=new Date(t.start+n);t.item.data.start=e?e(i):i}if("end"in t){var s=new Date(t.end+n);t.item.data.end=e?e(s):s}}),this.requestReflow(),t.stopPropagation()}},ItemSet.prototype._onDragEnd=function(t){if(this.touchParams.itemProps){var e=[],i=this,n=this._myDataSet();this.touchParams.itemProps.forEach(function(t){var s=t.item.id,o=i.itemsData.get(s),r=!1;"start"in t.item.data&&(r=t.start!=t.item.data.start.valueOf(),o.start=util.convert(t.item.data.start,n.convert.start)),"end"in t.item.data&&(r=r||t.end!=t.item.data.end.valueOf(),o.end=util.convert(t.item.data.end,n.convert.end)),r&&i.options.onMove(o,function(n){n?e.push(n):("start"in t&&(t.item.data.start=t.start),"end"in t&&(t.item.data.end=t.end),i.requestReflow())})}),this.touchParams.itemProps=null,e.length&&n.update(e),t.stopPropagation()}},ItemSet.itemFromTarget=function(t){for(var e=t.target;e;){if(e.hasOwnProperty("timeline-item"))return e["timeline-item"];e=e.parentNode}return null},ItemSet.itemSetFromTarget=function(t){for(var e=t.target;e;){if(e.hasOwnProperty("timeline-itemset"))return e["timeline-itemset"];e=e.parentNode}return null},ItemSet.prototype._myDataSet=function(){for(var t=this.itemsData;t instanceof DataView;)t=t.data;return t},Item.prototype.select=function(){this.selected=!0,this.visible&&this.repaint()},Item.prototype.unselect=function(){this.selected=!1,this.visible&&this.repaint()},Item.prototype.show=function(){return!1},Item.prototype.hide=function(){return!1},Item.prototype.repaint=function(){return!1},Item.prototype.reflow=function(){return!1},Item.prototype.setOffset=function(t){this.offset=t},Item.prototype._repaintDeleteButton=function(t){if(this.selected&&this.options.editable&&!this.dom.deleteButton){var e=this.parent,i=this.id,n=document.createElement("div");n.className="delete",n.title="Delete this item",Hammer(n,{preventDefault:!0}).on("tap",function(t){e.removeItem(i),t.stopPropagation()}),t.appendChild(n),this.dom.deleteButton=n}else!this.selected&&this.dom.deleteButton&&(this.dom.deleteButton.parentNode&&this.dom.deleteButton.parentNode.removeChild(this.dom.deleteButton),this.dom.deleteButton=null)},ItemBox.prototype=new Item(null,null),ItemBox.prototype.repaint=function(){var t=!1,e=this.dom;if(e||(this._create(),e=this.dom,t=!0),e){if(!this.parent)throw new Error("Cannot repaint item: no parent attached");if(!e.box.parentNode){var i=this.parent.getForeground();if(!i)throw new Error("Cannot repaint time axis: parent has no foreground container element");i.appendChild(e.box),t=!0}if(!e.line.parentNode){var n=this.parent.getBackground();if(!n)throw new Error("Cannot repaint time axis: parent has no background container element");n.appendChild(e.line),t=!0}if(!e.dot.parentNode){var s=this.parent.getAxis();if(!n)throw new Error("Cannot repaint time axis: parent has no axis container element");s.appendChild(e.dot),t=!0}if(this._repaintDeleteButton(e.box),this.data.content!=this.content){if(this.content=this.data.content,this.content instanceof Element)e.content.innerHTML="",e.content.appendChild(this.content);else{if(void 0==this.data.content)throw new Error('Property "content" missing in item '+this.data.id);e.content.innerHTML=this.content}t=!0}var o=(this.data.className?" "+this.data.className:"")+(this.selected?" selected":"");this.className!=o&&(this.className=o,e.box.className="item box"+o,e.line.className="item line"+o,e.dot.className="item dot"+o,t=!0)}return t},ItemBox.prototype.show=function(){return this.dom&&this.dom.box.parentNode?!1:this.repaint()},ItemBox.prototype.hide=function(){var t=!1,e=this.dom;return e&&(e.box.parentNode&&(e.box.parentNode.removeChild(e.box),t=!0),e.line.parentNode&&e.line.parentNode.removeChild(e.line),e.dot.parentNode&&e.dot.parentNode.removeChild(e.dot)),t},ItemBox.prototype.reflow=function(){var t,e,i,n,s,o,r,a,h,d,l,c,u=0;if(void 0==this.data.start)throw new Error('Property "start" missing in item '+this.data.id);if(l=this.data,c=this.parent&&this.parent.range,l&&c){var p=c.end-c.start;this.visible=l.start>c.start-p&&l.start0},ItemBox.prototype._create=function(){var t=this.dom;t||(this.dom=t={},t.box=document.createElement("DIV"),t.content=document.createElement("DIV"),t.content.className="content",t.box.appendChild(t.content),t.line=document.createElement("DIV"),t.line.className="line",t.dot=document.createElement("DIV"),t.dot.className="dot",t.box["timeline-item"]=this)},ItemBox.prototype.reposition=function(){var t=this.dom,e=this.props,i=this.options.orientation||this.defaultOptions.orientation;if(t){var n=t.box,s=t.line,o=t.dot;n.style.left=this.left+"px",n.style.top=this.top+"px",s.style.left=e.line.left+"px","top"==i?(s.style.top="0px",s.style.height=this.top+"px"):(s.style.top=this.top+this.height+"px",s.style.height=Math.max(this.parent.height-this.top-this.height+this.props.dot.height/2,0)+"px"),o.style.left=e.dot.left+"px",o.style.top=e.dot.top+"px"}},ItemPoint.prototype=new Item(null,null),ItemPoint.prototype.repaint=function(){var t=!1,e=this.dom;if(e||(this._create(),e=this.dom,t=!0),e){if(!this.parent)throw new Error("Cannot repaint item: no parent attached");var i=this.parent.getForeground();if(!i)throw new Error("Cannot repaint time axis: parent has no foreground container element");if(e.point.parentNode||(i.appendChild(e.point),i.appendChild(e.point),t=!0),this.data.content!=this.content){if(this.content=this.data.content,this.content instanceof Element)e.content.innerHTML="",e.content.appendChild(this.content);else{if(void 0==this.data.content)throw new Error('Property "content" missing in item '+this.data.id);e.content.innerHTML=this.content}t=!0}this._repaintDeleteButton(e.point);var n=(this.data.className?" "+this.data.className:"")+(this.selected?" selected":"");this.className!=n&&(this.className=n,e.point.className="item point"+n,t=!0)}return t},ItemPoint.prototype.show=function(){return this.dom&&this.dom.point.parentNode?!1:this.repaint()},ItemPoint.prototype.hide=function(){var t=!1,e=this.dom;return e&&e.point.parentNode&&(e.point.parentNode.removeChild(e.point),t=!0),t},ItemPoint.prototype.reflow=function(){var t,e,i,n,s,o,r,a,h,d,l=0;if(void 0==this.data.start)throw new Error('Property "start" missing in item '+this.data.id);if(h=this.data,d=this.parent&&this.parent.range,h&&d){var c=d.end-d.start;this.visible=h.start>d.start-c&&h.start0},ItemPoint.prototype._create=function(){var t=this.dom;t||(this.dom=t={},t.point=document.createElement("div"),t.content=document.createElement("div"),t.content.className="content",t.point.appendChild(t.content),t.dot=document.createElement("div"),t.dot.className="dot",t.point.appendChild(t.dot),t.point["timeline-item"]=this)},ItemPoint.prototype.reposition=function(){var t=this.dom,e=this.props;t&&(t.point.style.top=this.top+"px",t.point.style.left=this.left+"px",t.content.style.marginLeft=e.content.marginLeft+"px",t.dot.style.top=e.dot.top+"px")},ItemRange.prototype=new Item(null,null),ItemRange.prototype.repaint=function(){var t=!1,e=this.dom;if(e||(this._create(),e=this.dom,t=!0),e){if(!this.parent)throw new Error("Cannot repaint item: no parent attached");var i=this.parent.getForeground();if(!i)throw new Error("Cannot repaint time axis: parent has no foreground container element");if(e.box.parentNode||(i.appendChild(e.box),t=!0),this.data.content!=this.content){if(this.content=this.data.content,this.content instanceof Element)e.content.innerHTML="",e.content.appendChild(this.content);else{if(void 0==this.data.content)throw new Error('Property "content" missing in item '+this.data.id);e.content.innerHTML=this.content}t=!0}this._repaintDeleteButton(e.box),this._repaintDragLeft(),this._repaintDragRight();var n=(this.data.className?" "+this.data.className:"")+(this.selected?" selected":"");this.className!=n&&(this.className=n,e.box.className="item range"+n,t=!0)}return t},ItemRange.prototype.show=function(){return this.dom&&this.dom.box.parentNode?!1:this.repaint()},ItemRange.prototype.hide=function(){var t=!1,e=this.dom;return e&&e.box.parentNode&&(e.box.parentNode.removeChild(e.box),t=!0),t},ItemRange.prototype.reflow=function(){var t,e,i,n,s,o,r,a,h,d,l,c,u,p,f,m,g=0;if(void 0==this.data.start)throw new Error('Property "start" missing in item '+this.data.id);if(void 0==this.data.end)throw new Error('Property "end" missing in item '+this.data.id);return h=this.data,d=this.parent&&this.parent.range,this.visible=h&&d?h.startd.start:!1,this.visible&&(t=this.dom,t?(e=this.props,i=this.options,o=this.parent,r=o.toScreen(this.data.start)+this.offset,a=o.toScreen(this.data.end)+this.offset,l=util.updateProperty,c=t.box,u=o.width,f=i.orientation||this.defaultOptions.orientation,n=i.margin&&i.margin.axis||this.defaultOptions.margin.axis,s=i.padding||this.defaultOptions.padding,g+=l(e.content,"width",t.content.offsetWidth),g+=l(this,"height",c.offsetHeight),-u>r&&(r=-u),a>2*u&&(a=2*u),p=0>r?Math.min(-r,a-r-e.content.width-2*s):0,g+=l(e.content,"left",p),"top"==f?(m=n,g+=l(this,"top",m)):(m=o.height-this.height-n,g+=l(this,"top",m)),g+=l(this,"left",r),g+=l(this,"width",Math.max(a-r,1))):g+=1),g>0},ItemRange.prototype._create=function(){var t=this.dom;t||(this.dom=t={},t.box=document.createElement("div"),t.content=document.createElement("div"),t.content.className="content",t.box.appendChild(t.content),t.box["timeline-item"]=this)},ItemRange.prototype.reposition=function(){var t=this.dom,e=this.props;t&&(t.box.style.top=this.top+"px",t.box.style.left=this.left+"px",t.box.style.width=this.width+"px",t.content.style.left=e.content.left+"px")},ItemRange.prototype._repaintDragLeft=function(){if(this.selected&&this.options.editable&&!this.dom.dragLeft){var t=document.createElement("div");t.className="drag-left",t.dragLeftItem=this,Hammer(t,{preventDefault:!0}).on("drag",function(){}),this.dom.box.appendChild(t),this.dom.dragLeft=t}else!this.selected&&this.dom.dragLeft&&(this.dom.dragLeft.parentNode&&this.dom.dragLeft.parentNode.removeChild(this.dom.dragLeft),this.dom.dragLeft=null)},ItemRange.prototype._repaintDragRight=function(){if(this.selected&&this.options.editable&&!this.dom.dragRight){var t=document.createElement("div");t.className="drag-right",t.dragRightItem=this,Hammer(t,{preventDefault:!0}).on("drag",function(){}),this.dom.box.appendChild(t),this.dom.dragRight=t}else!this.selected&&this.dom.dragRight&&(this.dom.dragRight.parentNode&&this.dom.dragRight.parentNode.removeChild(this.dom.dragRight),this.dom.dragRight=null)},ItemRangeOverflow.prototype=new ItemRange(null,null),ItemRangeOverflow.prototype.repaint=function(){var t=!1,e=this.dom;if(e||(this._create(),e=this.dom,t=!0),e){if(!this.parent)throw new Error("Cannot repaint item: no parent attached");var i=this.parent.getForeground();if(!i)throw new Error("Cannot repaint time axis: parent has no foreground container element");if(e.box.parentNode||(i.appendChild(e.box),t=!0),this.data.content!=this.content){if(this.content=this.data.content,this.content instanceof Element)e.content.innerHTML="",e.content.appendChild(this.content);else{if(void 0==this.data.content)throw new Error('Property "content" missing in item '+this.id);e.content.innerHTML=this.content}t=!0}this._repaintDeleteButton(e.box),this._repaintDragLeft(),this._repaintDragRight();var n=(this.data.className?" "+this.data.className:"")+(this.selected?" selected":"");this.className!=n&&(this.className=n,e.box.className="item rangeoverflow"+n,t=!0)}return t},ItemRangeOverflow.prototype.reposition=function(){var t=this.dom,e=this.props;t&&(t.box.style.top=this.top+"px",t.box.style.left=this.left+"px",t.box.style.width=this._width+"px",t.content.style.left=e.content.left+"px")},Group.prototype=new Component,Group.prototype.setOptions=Component.prototype.setOptions,Group.prototype.getContainer=function(){return this.parent.getContainer()},Group.prototype.setItems=function(t){if(this.itemset&&(this.itemset.hide(),this.itemset.setItems(),this.parent.controller.remove(this.itemset),this.itemset=null),t){var e=this.groupId,i=Object.create(this.options);this.itemset=new ItemSet(this,null,i),this.itemset.setRange(this.parent.range),this.view=new DataView(t,{filter:function(t){return t.group==e}}),this.itemset.setItems(this.view),this.parent.controller.add(this.itemset)}},Group.prototype.setSelection=function(t){this.itemset&&this.itemset.setSelection(t)},Group.prototype.getSelection=function(){return this.itemset?this.itemset.getSelection():[]},Group.prototype.repaint=function(){return!1},Group.prototype.reflow=function(){var t=0,e=util.updateProperty;if(t+=e(this,"top",this.itemset?this.itemset.top:0),t+=e(this,"height",this.itemset?this.itemset.height:0),this.label){var i=this.label.firstChild;t+=e(this.props.label,"width",i.clientWidth),t+=e(this.props.label,"height",i.clientHeight)}else t+=e(this.props.label,"width",0),t+=e(this.props.label,"height",0);return t>0},GroupSet.prototype=new Panel,GroupSet.prototype.setOptions=Component.prototype.setOptions,GroupSet.prototype.setRange=function(){},GroupSet.prototype.setItems=function(t){this.itemsData=t;for(var e in this.groups)if(this.groups.hasOwnProperty(e)){var i=this.groups[e];i.setItems(t)}},GroupSet.prototype.getItems=function(){return this.itemsData},GroupSet.prototype.setRange=function(t){this.range=t},GroupSet.prototype.setGroups=function(t){var e,i=this;if(this.groupsData&&(util.forEach(this.listeners,function(t,e){i.groupsData.unsubscribe(e,t)}),e=this.groupsData.getIds(),this._onRemove(e)),t?t instanceof DataSet?this.groupsData=t:(this.groupsData=new DataSet({convert:{start:"Date",end:"Date"}}),this.groupsData.add(t)):this.groupsData=null,this.groupsData){var n=this.id;util.forEach(this.listeners,function(t,e){i.groupsData.on(e,t,n)}),e=this.groupsData.getIds(),this._onAdd(e)}},GroupSet.prototype.getGroups=function(){return this.groupsData},GroupSet.prototype.setSelection=function(t){var e=[],i=this.groups;for(var n in i)if(i.hasOwnProperty(n)){var s=i[n];s.setSelection(t)}return e},GroupSet.prototype.getSelection=function(){var t=[],e=this.groups;for(var i in e)if(e.hasOwnProperty(i)){var n=e[i];t=t.concat(n.getSelection())}return t},GroupSet.prototype.repaint=function(){var t,e,i,n,s=0,o=util.updateProperty,r=util.option.asSize,a=util.option.asElement,h=this.options,d=this.dom.frame,l=this.dom.labels,c=this.dom.labelSet;if(!this.parent)throw new Error("Cannot repaint groupset: no parent attached");var u=this.parent.getContainer();if(!u)throw new Error("Cannot repaint groupset: parent has no container element");if(!d){d=document.createElement("div"),d.className="groupset",d["timeline-groupset"]=this,this.dom.frame=d;var p=h.className;p&&util.addClassName(d,util.option.asString(p)),s+=1}d.parentNode||(u.appendChild(d),s+=1);var f=a(h.labelContainer);if(!f)throw new Error('Cannot repaint groupset: option "labelContainer" not defined');l||(l=document.createElement("div"),l.className="labels",this.dom.labels=l),c||(c=document.createElement("div"),c.className="label-set",l.appendChild(c),this.dom.labelSet=c),l.parentNode&&l.parentNode==f||(l.parentNode&&l.parentNode.removeChild(l.parentNode),f.appendChild(l)),s+=o(d.style,"height",r(h.height,this.height+"px")),s+=o(d.style,"top",r(h.top,"0px")),s+=o(d.style,"left",r(h.left,"0px")),s+=o(d.style,"width",r(h.width,"100%")),s+=o(c.style,"top",r(h.top,"0px")),s+=o(c.style,"height",r(h.height,this.height+"px"));var m=this,g=this.queue,v=this.groups,y=this.groupsData,_=Object.keys(g);if(_.length){_.forEach(function(t){var e=g[t],i=v[t];switch(e){case"add":case"update":if(!i){var n=Object.create(m.options);util.extend(n,{height:null,maxHeight:null}),i=new Group(m,t,n),i.setItems(m.itemsData),v[t]=i,m.controller.add(i)}i.data=y.get(t),delete g[t];break;case"remove":i&&(i.setItems(),delete v[t],m.controller.remove(i)),delete g[t];break;default:console.log('Error: unknown action "'+e+'"')}});var w=this.groupsData.getIds({order:this.options.groupOrder});for(t=0;t0},GroupSet.prototype._createLabel=function(t){var e=this.groups[t],i=document.createElement("div");i.className="vlabel";var n=document.createElement("div");n.className="inner",i.appendChild(n);var s=e.data&&e.data.content;s instanceof Element?n.appendChild(s):void 0!=s&&(n.innerHTML=s);var o=e.data&&e.data.className;return o&&util.addClassName(i,o),e.label=i,i},GroupSet.prototype.getContainer=function(){return this.dom.frame},GroupSet.prototype.getLabelsWidth=function(){return this.props.labels.width},GroupSet.prototype.reflow=function(){var t,e,i=0,n=this.options,s=util.updateProperty,o=util.option.asNumber,r=util.option.asSize,a=this.dom.frame;if(a){var h,d=o(n.maxHeight),l=null!=r(n.height);if(l)h=a.offsetHeight;else{h=0;for(t in this.groups)this.groups.hasOwnProperty(t)&&(e=this.groups[t],h+=e.height)}null!=d&&(h=Math.min(h,d)),i+=s(this,"height",h),i+=s(this,"top",a.offsetTop),i+=s(this,"left",a.offsetLeft),i+=s(this,"width",a.offsetWidth)}var c=0;for(t in this.groups)if(this.groups.hasOwnProperty(t)){e=this.groups[t];var u=e.props&&e.props.label&&e.props.label.width||0;c=Math.max(c,u)}return i+=s(this.props.labels,"width",c),i>0},GroupSet.prototype.hide=function(){return this.dom.frame&&this.dom.frame.parentNode?(this.dom.frame.parentNode.removeChild(this.dom.frame),!0):!1},GroupSet.prototype.show=function(){return this.dom.frame&&this.dom.frame.parentNode?!1:this.repaint()},GroupSet.prototype._onUpdate=function(t){this._toQueue(t,"update")},GroupSet.prototype._onAdd=function(t){this._toQueue(t,"add")},GroupSet.prototype._onRemove=function(t){this._toQueue(t,"remove")},GroupSet.prototype._toQueue=function(t,e){var i=this.queue;t.forEach(function(t){i[t]=e}),this.controller&&this.requestRepaint()},GroupSet.groupFromTarget=function(t){for(var e,i=t.target;i;){if(i.hasOwnProperty("timeline-groupset")){e=i["timeline-groupset"];break}i=i.parentNode}if(e)for(var n in e.groups)if(e.groups.hasOwnProperty(n)){var s=e.groups[n];if(s.itemset&&ItemSet.itemSetFromTarget(t)==s.itemset)return s}return null},Timeline.prototype.on=function(t,e){this.controller.on(t,e)},Timeline.prototype.off=function(t,e){this.controller.off(t,e)},Timeline.prototype.setOptions=function(t){util.extend(this.options,t),this.range.setRange(t.start,t.end),("editable"in t||"selectable"in t)&&this.setSelection(this.options.selectable?this.getSelection():[]);var e=function(t){if(!(this.options[t]instanceof Function)||2!=this.options[t].length)throw new Error("option "+t+" must be a function "+t+"(item, callback)")}.bind(this);["onAdd","onUpdate","onRemove","onMove"].forEach(e),this.controller.reflow(),this.controller.repaint()},Timeline.prototype.setCustomTime=function(t){if(!this.customtime)throw new Error("Cannot get custom time: Custom time bar is not enabled");this.customtime.setCustomTime(t)},Timeline.prototype.getCustomTime=function(){if(!this.customtime)throw new Error("Cannot get custom time: Custom time bar is not enabled");return this.customtime.getCustomTime()},Timeline.prototype.setItems=function(t){var e,i=null==this.itemsData;if(t?t instanceof DataSet&&(e=t):e=null,t instanceof DataSet||(e=new DataSet({convert:{start:"Date",end:"Date"}}),e.add(t)),this.itemsData=e,this.content.setItems(e),i&&(void 0==this.options.start||void 0==this.options.end)){var n=this.getItemRange(),s=n.min,o=n.max;if(null!=s&&null!=o){var r=o.valueOf()-s.valueOf();0>=r&&(r=864e5),s=new Date(s.valueOf()-.05*r),o=new Date(o.valueOf()+.05*r)}void 0!=this.options.start&&(s=util.convert(this.options.start,"Date")),void 0!=this.options.end&&(o=util.convert(this.options.end,"Date")),(null!=s||null!=o)&&this.range.setRange(s,o)}},Timeline.prototype.setGroups=function(t){var e=this;this.groupsData=t;var i=this.groupsData?GroupSet:ItemSet;if(!(this.content instanceof i)){this.content&&(this.content.hide(),this.content.setItems&&this.content.setItems(),this.content.setGroups&&this.content.setGroups(),this.controller.remove(this.content));var n=Object.create(this.options);util.extend(n,{top:function(){return"top"==e.options.orientation?e.timeaxis.height:e.itemPanel.height-e.timeaxis.height-e.content.height},left:null,width:"100%",height:function(){return e.options.height?e.itemPanel.height-e.timeaxis.height:null},maxHeight:function(){if(e.options.maxHeight){if(!util.isNumber(e.options.maxHeight))throw new TypeError("Number expected for property maxHeight");return e.options.maxHeight-e.timeaxis.height}return null},labelContainer:function(){return e.labelPanel.getContainer()}}),this.content=new i(this.itemPanel,[this.timeaxis],n),this.content.setRange&&this.content.setRange(this.range),this.content.setItems&&this.content.setItems(this.itemsData),this.content.setGroups&&this.content.setGroups(this.groupsData),this.controller.add(this.content)}},Timeline.prototype.getItemRange=function(){var t=this.itemsData,e=null,i=null;if(t){var n=t.min("start");e=n?n.start.valueOf():null;var s=t.max("start");s&&(i=s.start.valueOf());var o=t.max("end");o&&(i=null==i?o.end.valueOf():Math.max(i,o.end.valueOf()))}return{min:null!=e?new Date(e):null,max:null!=i?new Date(i):null}},Timeline.prototype.setSelection=function(t){this.content&&this.content.setSelection(t)},Timeline.prototype.getSelection=function(){return this.content?this.content.getSelection():[]},Timeline.prototype.setWindow=function(t,e){this.range.setRange(t,e)},Timeline.prototype.getWindow=function(){var t=this.range.getRange();return{start:new Date(t.start),end:new Date(t.end)}},Timeline.prototype._onSelectItem=function(t){if(this.options.selectable){var e=t.gesture.srcEvent&&t.gesture.srcEvent.ctrlKey,i=t.gesture.srcEvent&&t.gesture.srcEvent.shiftKey;if(e||i)return void this._onMultiSelectItem(t);var n=ItemSet.itemFromTarget(t),s=n?[n.id]:[];this.setSelection(s),this.controller.emit("select",{items:this.getSelection()}),t.stopPropagation()}},Timeline.prototype._onAddItem=function(t){if(this.options.selectable&&this.options.editable){var e=this,i=ItemSet.itemFromTarget(t);if(i){var n=e.itemsData.get(i.id);this.options.onUpdate(n,function(t){t&&e.itemsData.update(t)})}else{var s=vis.util.getAbsoluteLeft(this.rootPanel.frame),o=t.gesture.center.pageX-s,r={start:this.timeaxis.snap(this._toTime(o)),content:"new item"},a=util.randomUUID();r[this.itemsData.fieldId]=a;var h=GroupSet.groupFromTarget(t);h&&(r.group=h.groupId),this.options.onAdd(r,function(t){t&&(e.itemsData.add(r),e.controller.once("repaint",function(){e.setSelection([a]),e.controller.emit("select",{items:e.getSelection()})}.bind(e)))})}}},Timeline.prototype._onMultiSelectItem=function(t){if(this.options.selectable){var e,i=ItemSet.itemFromTarget(t);if(i){e=this.getSelection();var n=e.indexOf(i.id);-1==n?e.push(i.id):e.splice(n,1),this.setSelection(e),this.controller.emit("select",{items:this.getSelection()}),t.stopPropagation()}}},Timeline.prototype._toTime=function(t){var e=this.range.conversion(this.content.width);return new Date(t/e.scale+e.offset)},Timeline.prototype._toScreen=function(t){var e=this.range.conversion(this.content.width);return(t.valueOf()-e.offset)*e.scale},function(t){function e(t){return D=t,u()}function i(){M=0,C=D.charAt(0)}function n(){M++,C=D.charAt(M)}function s(){return D.charAt(M+1)}function o(t){return O.test(t)}function r(t,e){if(t||(t={}),e)for(var i in e)e.hasOwnProperty(i)&&(t[i]=e[i]);return t}function a(t,e,i){for(var n=e.split("."),s=t;n.length;){var o=n.shift();n.length?(s[o]||(s[o]={}),s=s[o]):s[o]=i}}function h(t,e){for(var i,n,s=null,o=[t],a=t;a.parent;)o.push(a.parent),a=a.parent;if(a.nodes)for(i=0,n=a.nodes.length;n>i;i++)if(e.id===a.nodes[i].id){s=a.nodes[i];break}for(s||(s={id:e.id},t.node&&(s.attr=r(s.attr,t.node))),i=o.length-1;i>=0;i--){var h=o[i];h.nodes||(h.nodes=[]),-1==h.nodes.indexOf(s)&&h.nodes.push(s)
+}e.attr&&(s.attr=r(s.attr,e.attr))}function d(t,e){if(t.edges||(t.edges=[]),t.edges.push(e),t.edge){var i=r({},t.edge);e.attr=r(i,e.attr)}}function l(t,e,i,n,s){var o={from:e,to:i,type:n};return t.edge&&(o.attr=r({},t.edge)),o.attr=r(o.attr||{},s),o}function c(){for(N=T.NULL,I="";" "==C||" "==C||"\n"==C||"\r"==C;)n();do{var t=!1;if("#"==C){for(var e=M-1;" "==D.charAt(e)||" "==D.charAt(e);)e--;if("\n"==D.charAt(e)||""==D.charAt(e)){for(;""!=C&&"\n"!=C;)n();t=!0}}if("/"==C&&"/"==s()){for(;""!=C&&"\n"!=C;)n();t=!0}if("/"==C&&"*"==s()){for(;""!=C;){if("*"==C&&"/"==s()){n(),n();break}n()}t=!0}for(;" "==C||" "==C||"\n"==C||"\r"==C;)n()}while(t);if(""==C)return void(N=T.DELIMITER);var i=C+s();if(x[i])return N=T.DELIMITER,I=i,n(),void n();if(x[C])return N=T.DELIMITER,I=C,void n();if(o(C)||"-"==C){for(I+=C,n();o(C);)I+=C,n();return"false"==I?I=!1:"true"==I?I=!0:isNaN(Number(I))||(I=Number(I)),void(N=T.IDENTIFIER)}if('"'==C){for(n();""!=C&&('"'!=C||'"'==C&&'"'==s());)I+=C,'"'==C&&n(),n();if('"'!=C)throw w('End of string " expected');return n(),void(N=T.IDENTIFIER)}for(N=T.UNKNOWN;""!=C;)I+=C,n();throw new SyntaxError('Syntax error in part "'+b(I,30)+'"')}function u(){var t={};if(i(),c(),"strict"==I&&(t.strict=!0,c()),("graph"==I||"digraph"==I)&&(t.type=I,c()),N==T.IDENTIFIER&&(t.id=I,c()),"{"!=I)throw w("Angle bracket { expected");if(c(),p(t),"}"!=I)throw w("Angle bracket } expected");if(c(),""!==I)throw w("End of file expected");return c(),delete t.node,delete t.edge,delete t.graph,t}function p(t){for(;""!==I&&"}"!=I;)f(t),";"==I&&c()}function f(t){var e=m(t);if(e)return void y(t,e);var i=g(t);if(!i){if(N!=T.IDENTIFIER)throw w("Identifier expected");var n=I;if(c(),"="==I){if(c(),N!=T.IDENTIFIER)throw w("Identifier expected");t[n]=I,c()}else v(t,n)}}function m(t){var e=null;if("subgraph"==I&&(e={},e.type="subgraph",c(),N==T.IDENTIFIER&&(e.id=I,c())),"{"==I){if(c(),e||(e={}),e.parent=t,e.node=t.node,e.edge=t.edge,e.graph=t.graph,p(e),"}"!=I)throw w("Angle bracket } expected");c(),delete e.node,delete e.edge,delete e.graph,delete e.parent,t.subgraphs||(t.subgraphs=[]),t.subgraphs.push(e)}return e}function g(t){return"node"==I?(c(),t.node=_(),"node"):"edge"==I?(c(),t.edge=_(),"edge"):"graph"==I?(c(),t.graph=_(),"graph"):null}function v(t,e){var i={id:e},n=_();n&&(i.attr=n),h(t,i),y(t,e)}function y(t,e){for(;"->"==I||"--"==I;){var i,n=I;c();var s=m(t);if(s)i=s;else{if(N!=T.IDENTIFIER)throw w("Identifier or subgraph expected");i=I,h(t,{id:i}),c()}var o=_(),r=l(t,e,i,n,o);d(t,r),e=i}}function _(){for(var t=null;"["==I;){for(c(),t={};""!==I&&"]"!=I;){if(N!=T.IDENTIFIER)throw w("Attribute name expected");var e=I;if(c(),"="!=I)throw w("Equal sign = expected");if(c(),N!=T.IDENTIFIER)throw w("Attribute value expected");var i=I;a(t,e,i),c(),","==I&&c()}if("]"!=I)throw w("Bracket ] expected");c()}return t}function w(t){return new SyntaxError(t+', got "'+b(I,30)+'" (char '+M+")")}function b(t,e){return t.length<=e?t:t.substr(0,27)+"..."}function S(t,e,i){t instanceof Array?t.forEach(function(t){e instanceof Array?e.forEach(function(e){i(t,e)}):i(t,e)}):e instanceof Array?e.forEach(function(e){i(t,e)}):i(t,e)}function E(t){function i(t){var e={from:t.from,to:t.to};return r(e,t.attr),e.style="->"==t.type?"arrow":"line",e}var n=e(t),s={nodes:[],edges:[],options:{}};return n.nodes&&n.nodes.forEach(function(t){var e={id:t.id,label:String(t.label||t.id)};r(e,t.attr),e.image&&(e.shape="image"),s.nodes.push(e)}),n.edges&&n.edges.forEach(function(t){var e,n;e=t.from instanceof Object?t.from.nodes:{id:t.from},n=t.to instanceof Object?t.to.nodes:{id:t.to},t.from instanceof Object&&t.from.edges&&t.from.edges.forEach(function(t){var e=i(t);s.edges.push(e)}),S(e,n,function(e,n){var o=l(s,e.id,n.id,t.type,t.attr),r=i(o);s.edges.push(r)}),t.to instanceof Object&&t.to.edges&&t.to.edges.forEach(function(t){var e=i(t);s.edges.push(e)})}),n.attr&&(s.options=n.attr),s}var T={NULL:0,DELIMITER:1,IDENTIFIER:2,UNKNOWN:3},x={"{":!0,"}":!0,"[":!0,"]":!0,";":!0,"=":!0,",":!0,"->":!0,"--":!0},D="",M=0,C="",I="",N=T.NULL,O=/[a-zA-Z_0-9.:#]/;t.parseDOT=e,t.DOTToGraph=E}("undefined"!=typeof util?util:exports),"undefined"!=typeof CanvasRenderingContext2D&&(CanvasRenderingContext2D.prototype.circle=function(t,e,i){this.beginPath(),this.arc(t,e,i,0,2*Math.PI,!1)},CanvasRenderingContext2D.prototype.square=function(t,e,i){this.beginPath(),this.rect(t-i,e-i,2*i,2*i)},CanvasRenderingContext2D.prototype.triangle=function(t,e,i){this.beginPath();var n=2*i,s=n/2,o=Math.sqrt(3)/6*n,r=Math.sqrt(n*n-s*s);this.moveTo(t,e-(r-o)),this.lineTo(t+s,e+o),this.lineTo(t-s,e+o),this.lineTo(t,e-(r-o)),this.closePath()},CanvasRenderingContext2D.prototype.triangleDown=function(t,e,i){this.beginPath();var n=2*i,s=n/2,o=Math.sqrt(3)/6*n,r=Math.sqrt(n*n-s*s);this.moveTo(t,e+(r-o)),this.lineTo(t+s,e-o),this.lineTo(t-s,e-o),this.lineTo(t,e+(r-o)),this.closePath()},CanvasRenderingContext2D.prototype.star=function(t,e,i){this.beginPath();for(var n=0;10>n;n++){var s=n%2===0?1.3*i:.5*i;this.lineTo(t+s*Math.sin(2*n*Math.PI/10),e-s*Math.cos(2*n*Math.PI/10))}this.closePath()},CanvasRenderingContext2D.prototype.roundRect=function(t,e,i,n,s){var o=Math.PI/180;0>i-2*s&&(s=i/2),0>n-2*s&&(s=n/2),this.beginPath(),this.moveTo(t+s,e),this.lineTo(t+i-s,e),this.arc(t+i-s,e+s,s,270*o,360*o,!1),this.lineTo(t+i,e+n-s),this.arc(t+i-s,e+n-s,s,0,90*o,!1),this.lineTo(t+s,e+n),this.arc(t+s,e+n-s,s,90*o,180*o,!1),this.lineTo(t,e+s),this.arc(t+s,e+s,s,180*o,270*o,!1)},CanvasRenderingContext2D.prototype.ellipse=function(t,e,i,n){var s=.5522848,o=i/2*s,r=n/2*s,a=t+i,h=e+n,d=t+i/2,l=e+n/2;this.beginPath(),this.moveTo(t,l),this.bezierCurveTo(t,l-r,d-o,e,d,e),this.bezierCurveTo(d+o,e,a,l-r,a,l),this.bezierCurveTo(a,l+r,d+o,h,d,h),this.bezierCurveTo(d-o,h,t,l+r,t,l)},CanvasRenderingContext2D.prototype.database=function(t,e,i,n){var s=1/3,o=i,r=n*s,a=.5522848,h=o/2*a,d=r/2*a,l=t+o,c=e+r,u=t+o/2,p=e+r/2,f=e+(n-r/2),m=e+n;this.beginPath(),this.moveTo(l,p),this.bezierCurveTo(l,p+d,u+h,c,u,c),this.bezierCurveTo(u-h,c,t,p+d,t,p),this.bezierCurveTo(t,p-d,u-h,e,u,e),this.bezierCurveTo(u+h,e,l,p-d,l,p),this.lineTo(l,f),this.bezierCurveTo(l,f+d,u+h,m,u,m),this.bezierCurveTo(u-h,m,t,f+d,t,f),this.lineTo(t,p)},CanvasRenderingContext2D.prototype.arrow=function(t,e,i,n){var s=t-n*Math.cos(i),o=e-n*Math.sin(i),r=t-.9*n*Math.cos(i),a=e-.9*n*Math.sin(i),h=s+n/3*Math.cos(i+.5*Math.PI),d=o+n/3*Math.sin(i+.5*Math.PI),l=s+n/3*Math.cos(i-.5*Math.PI),c=o+n/3*Math.sin(i-.5*Math.PI);this.beginPath(),this.moveTo(t,e),this.lineTo(h,d),this.lineTo(r,a),this.lineTo(l,c),this.closePath()},CanvasRenderingContext2D.prototype.dashedLine=function(t,e,i,n,s){s||(s=[10,5]),0==u&&(u=.001);var o=s.length;this.moveTo(t,e);for(var r=i-t,a=n-e,h=a/r,d=Math.sqrt(r*r+a*a),l=0,c=!0;d>=.1;){var u=s[l++%o];u>d&&(u=d);var p=Math.sqrt(u*u/(1+h*h));0>r&&(p=-p),t+=p,e+=h*p,this[c?"lineTo":"moveTo"](t,e),d-=u,c=!c}}),Node.prototype.resetCluster=function(){this.formationScale=void 0,this.clusterSize=1,this.containedNodes={},this.containedEdges={},this.clusterSessions=[]},Node.prototype.attachEdge=function(t){-1==this.edges.indexOf(t)&&this.edges.push(t),-1==this.dynamicEdges.indexOf(t)&&this.dynamicEdges.push(t),this.dynamicEdgesLength=this.dynamicEdges.length},Node.prototype.detachEdge=function(t){var e=this.edges.indexOf(t);-1!=e&&(this.edges.splice(e,1),this.dynamicEdges.splice(e,1)),this.dynamicEdgesLength=this.dynamicEdges.length},Node.prototype.setProperties=function(t,e){if(t){if(this.originalLabel=void 0,void 0!==t.id&&(this.id=t.id),void 0!==t.label&&(this.label=t.label,this.originalLabel=t.label),void 0!==t.title&&(this.title=t.title),void 0!==t.group&&(this.group=t.group),void 0!==t.x&&(this.x=t.x),void 0!==t.y&&(this.y=t.y),void 0!==t.value&&(this.value=t.value),void 0!==t.level&&(this.level=t.level),void 0!==t.internalMultiplier&&(this.internalMultiplier=t.internalMultiplier),void 0!==t.damping&&(this.dampingBase=t.damping),void 0!==t.mass&&(this.mass=t.mass),void 0!==t.horizontalAlignLeft&&(this.horizontalAlignLeft=t.horizontalAlignLeft),void 0!==t.verticalAlignTop&&(this.verticalAlignTop=t.verticalAlignTop),void 0!==t.triggerFunction&&(this.triggerFunction=t.triggerFunction),void 0===this.id)throw"Node must have an id";if(this.group){var i=this.grouplist.get(this.group);for(var n in i)i.hasOwnProperty(n)&&(this[n]=i[n])}if(void 0!==t.shape&&(this.shape=t.shape),void 0!==t.image&&(this.image=t.image),void 0!==t.radius&&(this.radius=t.radius),void 0!==t.color&&(this.color=Node.parseColor(t.color)),void 0!==t.fontColor&&(this.fontColor=t.fontColor),void 0!==t.fontSize&&(this.fontSize=t.fontSize),void 0!==t.fontFace&&(this.fontFace=t.fontFace),void 0!==this.image){if(!this.imagelist)throw"No imagelist provided";this.imageObj=this.imagelist.load(this.image)}switch(this.xFixed=this.xFixed||void 0!==t.x&&!t.allowedToMove,this.yFixed=this.yFixed||void 0!==t.y&&!t.allowedToMove,this.radiusFixed=this.radiusFixed||void 0!==t.radius,"image"==this.shape&&(this.radiusMin=e.nodes.widthMin,this.radiusMax=e.nodes.widthMax),this.shape){case"database":this.draw=this._drawDatabase,this.resize=this._resizeDatabase;break;case"box":this.draw=this._drawBox,this.resize=this._resizeBox;break;case"circle":this.draw=this._drawCircle,this.resize=this._resizeCircle;break;case"ellipse":this.draw=this._drawEllipse,this.resize=this._resizeEllipse;break;case"image":this.draw=this._drawImage,this.resize=this._resizeImage;break;case"text":this.draw=this._drawText,this.resize=this._resizeText;break;case"dot":this.draw=this._drawDot,this.resize=this._resizeShape;break;case"square":this.draw=this._drawSquare,this.resize=this._resizeShape;break;case"triangle":this.draw=this._drawTriangle,this.resize=this._resizeShape;break;case"triangleDown":this.draw=this._drawTriangleDown,this.resize=this._resizeShape;break;case"star":this.draw=this._drawStar,this.resize=this._resizeShape;break;default:this.draw=this._drawEllipse,this.resize=this._resizeEllipse}this._reset()}},Node.parseColor=function(t){var e;if(util.isString(t))if(util.isValidHex(t)){var i=util.hexToHSV(t),n={h:i.h,s:.45*i.s,v:Math.min(1,1.05*i.v)},s={h:i.h,s:Math.min(1,1.25*i.v),v:.6*i.v},o=util.HSVToHex(s.h,s.h,s.v),r=util.HSVToHex(n.h,n.s,n.v);e={background:t,border:o,highlight:{background:r,border:o}}}else e={background:t,border:t,highlight:{background:t,border:t}};else e={},e.background=t.background||"white",e.border=t.border||e.background,util.isString(t.highlight)?e.highlight={border:t.highlight,background:t.highlight}:(e.highlight={},e.highlight.background=t.highlight&&t.highlight.background||e.background,e.highlight.border=t.highlight&&t.highlight.border||e.border);return e},Node.prototype.select=function(){this.selected=!0,this._reset()},Node.prototype.unselect=function(){this.selected=!1,this._reset()},Node.prototype.clearSizeCache=function(){this._reset()},Node.prototype._reset=function(){this.width=void 0,this.height=void 0},Node.prototype.getTitle=function(){return this.title},Node.prototype.distanceToBorder=function(t,e){var i=1;switch(this.width||this.resize(t),this.shape){case"circle":case"dot":return this.radius+i;case"ellipse":var n=this.width/2,s=this.height/2,o=Math.sin(e)*n,r=Math.cos(e)*s;return n*s/Math.sqrt(o*o+r*r);case"box":case"image":case"text":default:return this.width?Math.min(Math.abs(this.width/2/Math.cos(e)),Math.abs(this.height/2/Math.sin(e)))+i:0}},Node.prototype._setForce=function(t,e){this.fx=t,this.fy=e},Node.prototype._addForce=function(t,e){this.fx+=t,this.fy+=e},Node.prototype.discreteStep=function(t){if(!this.xFixed){var e=this.damping*this.vx,i=(this.fx-e)/this.mass;this.vx+=i*t,this.x+=this.vx*t}if(!this.yFixed){var n=this.damping*this.vy,s=(this.fy-n)/this.mass;this.vy+=s*t,this.y+=this.vy*t}},Node.prototype.discreteStepLimited=function(t,e){if(!this.xFixed){var i=this.damping*this.vx,n=(this.fx-i)/this.mass;this.vx+=n*t,this.vx=Math.abs(this.vx)>e?this.vx>0?e:-e:this.vx,this.x+=this.vx*t}if(!this.yFixed){var s=this.damping*this.vy,o=(this.fy-s)/this.mass;this.vy+=o*t,this.vy=Math.abs(this.vy)>e?this.vy>0?e:-e:this.vy,this.y+=this.vy*t}},Node.prototype.isFixed=function(){return this.xFixed&&this.yFixed},Node.prototype.isMoving=function(t){return Math.abs(this.vx)>t||Math.abs(this.vy)>t},Node.prototype.isSelected=function(){return this.selected},Node.prototype.getValue=function(){return this.value},Node.prototype.getDistance=function(t,e){var i=this.x-t,n=this.y-e;return Math.sqrt(i*i+n*n)},Node.prototype.setValueRange=function(t,e){if(!this.radiusFixed&&void 0!==this.value)if(e==t)this.radius=(this.radiusMin+this.radiusMax)/2;else{var i=(this.radiusMax-this.radiusMin)/(e-t);this.radius=(this.value-t)*i+this.radiusMin}this.baseRadiusValue=this.radius},Node.prototype.draw=function(){throw"Draw method not initialized for node"},Node.prototype.resize=function(){throw"Resize method not initialized for node"},Node.prototype.isOverlappingWith=function(t){return this.leftt.left&&this.topt.top},Node.prototype._resizeImage=function(){if(!this.width||!this.height){var t,e;if(this.value){this.radius=this.baseRadiusValue;var i=this.imageObj.height/this.imageObj.width;void 0!==i?(t=this.radius||this.imageObj.width,e=this.radius*i||this.imageObj.height):(t=0,e=0)}else t=this.imageObj.width,e=this.imageObj.height;this.width=t,this.height=e,this.growthIndicator=0,this.width>0&&this.height>0&&(this.width+=Math.min(this.clusterSize-1,this.maxNodeSizeIncrements)*this.clusterSizeWidthFactor,this.height+=Math.min(this.clusterSize-1,this.maxNodeSizeIncrements)*this.clusterSizeHeightFactor,this.radius+=Math.min(this.clusterSize-1,this.maxNodeSizeIncrements)*this.clusterSizeRadiusFactor,this.growthIndicator=this.width-t)}},Node.prototype._drawImage=function(t){this._resizeImage(t),this.left=this.x-this.width/2,this.top=this.y-this.height/2;var e;if(0!=this.imageObj.width){if(this.clusterSize>1){var i=this.clusterSize>1?10:0;i*=this.graphScaleInv,i=Math.min(.2*this.width,i),t.globalAlpha=.5,t.drawImage(this.imageObj,this.left-i,this.top-i,this.width+2*i,this.height+2*i)}t.globalAlpha=1,t.drawImage(this.imageObj,this.left,this.top,this.width,this.height),e=this.y+this.height/2}else e=this.y;this._label(t,this.label,this.x,e,void 0,"top")},Node.prototype._resizeBox=function(t){if(!this.width){var e=5,i=this.getTextSize(t);this.width=i.width+2*e,this.height=i.height+2*e,this.width+=.5*Math.min(this.clusterSize-1,this.maxNodeSizeIncrements)*this.clusterSizeWidthFactor,this.height+=.5*Math.min(this.clusterSize-1,this.maxNodeSizeIncrements)*this.clusterSizeHeightFactor,this.growthIndicator=this.width-(i.width+2*e)}},Node.prototype._drawBox=function(t){this._resizeBox(t),this.left=this.x-this.width/2,this.top=this.y-this.height/2;var e=2.5,i=2;t.strokeStyle=this.selected?this.color.highlight.border:this.color.border,this.clusterSize>1&&(t.lineWidth=(this.selected?i:1)+(this.clusterSize>1?e:0),t.lineWidth*=this.graphScaleInv,t.lineWidth=Math.min(.1*this.width,t.lineWidth),t.roundRect(this.left-2*t.lineWidth,this.top-2*t.lineWidth,this.width+4*t.lineWidth,this.height+4*t.lineWidth,this.radius),t.stroke()),t.lineWidth=(this.selected?i:1)+(this.clusterSize>1?e:0),t.lineWidth*=this.graphScaleInv,t.lineWidth=Math.min(.1*this.width,t.lineWidth),t.fillStyle=this.selected?this.color.highlight.background:this.color.background,t.roundRect(this.left,this.top,this.width,this.height,this.radius),t.fill(),t.stroke(),this._label(t,this.label,this.x,this.y)},Node.prototype._resizeDatabase=function(t){if(!this.width){var e=5,i=this.getTextSize(t),n=i.width+2*e;this.width=n,this.height=n,this.width+=Math.min(this.clusterSize-1,this.maxNodeSizeIncrements)*this.clusterSizeWidthFactor,this.height+=Math.min(this.clusterSize-1,this.maxNodeSizeIncrements)*this.clusterSizeHeightFactor,this.radius+=Math.min(this.clusterSize-1,this.maxNodeSizeIncrements)*this.clusterSizeRadiusFactor,this.growthIndicator=this.width-n}},Node.prototype._drawDatabase=function(t){this._resizeDatabase(t),this.left=this.x-this.width/2,this.top=this.y-this.height/2;var e=2.5,i=2;t.strokeStyle=this.selected?this.color.highlight.border:this.color.border,this.clusterSize>1&&(t.lineWidth=(this.selected?i:1)+(this.clusterSize>1?e:0),t.lineWidth*=this.graphScaleInv,t.lineWidth=Math.min(.1*this.width,t.lineWidth),t.database(this.x-this.width/2-2*t.lineWidth,this.y-.5*this.height-2*t.lineWidth,this.width+4*t.lineWidth,this.height+4*t.lineWidth),t.stroke()),t.lineWidth=(this.selected?i:1)+(this.clusterSize>1?e:0),t.lineWidth*=this.graphScaleInv,t.lineWidth=Math.min(.1*this.width,t.lineWidth),t.fillStyle=this.selected?this.color.highlight.background:this.color.background,t.database(this.x-this.width/2,this.y-.5*this.height,this.width,this.height),t.fill(),t.stroke(),this._label(t,this.label,this.x,this.y)},Node.prototype._resizeCircle=function(t){if(!this.width){var e=5,i=this.getTextSize(t),n=Math.max(i.width,i.height)+2*e;this.radius=n/2,this.width=n,this.height=n,this.radius+=.5*Math.min(this.clusterSize-1,this.maxNodeSizeIncrements)*this.clusterSizeRadiusFactor,this.growthIndicator=this.radius-.5*n}},Node.prototype._drawCircle=function(t){this._resizeCircle(t),this.left=this.x-this.width/2,this.top=this.y-this.height/2;var e=2.5,i=2;t.strokeStyle=this.selected?this.color.highlight.border:this.color.border,this.clusterSize>1&&(t.lineWidth=(this.selected?i:1)+(this.clusterSize>1?e:0),t.lineWidth*=this.graphScaleInv,t.lineWidth=Math.min(.1*this.width,t.lineWidth),t.circle(this.x,this.y,this.radius+2*t.lineWidth),t.stroke()),t.lineWidth=(this.selected?i:1)+(this.clusterSize>1?e:0),t.lineWidth*=this.graphScaleInv,t.lineWidth=Math.min(.1*this.width,t.lineWidth),t.fillStyle=this.selected?this.color.highlight.background:this.color.background,t.circle(this.x,this.y,this.radius),t.fill(),t.stroke(),this._label(t,this.label,this.x,this.y)},Node.prototype._resizeEllipse=function(t){if(!this.width){var e=this.getTextSize(t);this.width=1.5*e.width,this.height=2*e.height,this.width1&&(t.lineWidth=(this.selected?i:1)+(this.clusterSize>1?e:0),t.lineWidth*=this.graphScaleInv,t.lineWidth=Math.min(.1*this.width,t.lineWidth),t.ellipse(this.left-2*t.lineWidth,this.top-2*t.lineWidth,this.width+4*t.lineWidth,this.height+4*t.lineWidth),t.stroke()),t.lineWidth=(this.selected?i:1)+(this.clusterSize>1?e:0),t.lineWidth*=this.graphScaleInv,t.lineWidth=Math.min(.1*this.width,t.lineWidth),t.fillStyle=this.selected?this.color.highlight.background:this.color.background,t.ellipse(this.left,this.top,this.width,this.height),t.fill(),t.stroke(),this._label(t,this.label,this.x,this.y)},Node.prototype._drawDot=function(t){this._drawShape(t,"circle")},Node.prototype._drawTriangle=function(t){this._drawShape(t,"triangle")},Node.prototype._drawTriangleDown=function(t){this._drawShape(t,"triangleDown")},Node.prototype._drawSquare=function(t){this._drawShape(t,"square")},Node.prototype._drawStar=function(t){this._drawShape(t,"star")},Node.prototype._resizeShape=function(){if(!this.width){this.radius=this.baseRadiusValue;var t=2*this.radius;this.width=t,this.height=t,this.width+=Math.min(this.clusterSize-1,this.maxNodeSizeIncrements)*this.clusterSizeWidthFactor,this.height+=Math.min(this.clusterSize-1,this.maxNodeSizeIncrements)*this.clusterSizeHeightFactor,this.radius+=.5*Math.min(this.clusterSize-1,this.maxNodeSizeIncrements)*this.clusterSizeRadiusFactor,this.growthIndicator=this.width-t}},Node.prototype._drawShape=function(t,e){this._resizeShape(t),this.left=this.x-this.width/2,this.top=this.y-this.height/2;var i=2.5,n=2,s=2;switch(e){case"dot":s=2;break;case"square":s=2;break;case"triangle":s=3;break;case"triangleDown":s=3;break;case"star":s=4}t.strokeStyle=this.selected?this.color.highlight.border:this.color.border,this.clusterSize>1&&(t.lineWidth=(this.selected?n:1)+(this.clusterSize>1?i:0),t.lineWidth*=this.graphScaleInv,t.lineWidth=Math.min(.1*this.width,t.lineWidth),t[e](this.x,this.y,this.radius+s*t.lineWidth),t.stroke()),t.lineWidth=(this.selected?n:1)+(this.clusterSize>1?i:0),t.lineWidth*=this.graphScaleInv,t.lineWidth=Math.min(.1*this.width,t.lineWidth),t.fillStyle=this.selected?this.color.highlight.background:this.color.background,t[e](this.x,this.y,this.radius),t.fill(),t.stroke(),this.label&&this._label(t,this.label,this.x,this.y+this.height/2,void 0,"top")},Node.prototype._resizeText=function(t){if(!this.width){var e=5,i=this.getTextSize(t);this.width=i.width+2*e,this.height=i.height+2*e,this.width+=Math.min(this.clusterSize-1,this.maxNodeSizeIncrements)*this.clusterSizeWidthFactor,this.height+=Math.min(this.clusterSize-1,this.maxNodeSizeIncrements)*this.clusterSizeHeightFactor,this.radius+=Math.min(this.clusterSize-1,this.maxNodeSizeIncrements)*this.clusterSizeRadiusFactor,this.growthIndicator=this.width-(i.width+2*e)}},Node.prototype._drawText=function(t){this._resizeText(t),this.left=this.x-this.width/2,this.top=this.y-this.height/2,this._label(t,this.label,this.x,this.y)},Node.prototype._label=function(t,e,i,n,s,o){if(e&&this.fontSize*this.graphScale>this.fontDrawThreshold){t.font=(this.selected?"bold ":"")+this.fontSize+"px "+this.fontFace,t.fillStyle=this.fontColor||"black",t.textAlign=s||"center",t.textBaseline=o||"middle";for(var r=e.split("\n"),a=r.length,h=this.fontSize+4,d=n+(1-a)/2*h,l=0;a>l;l++)t.fillText(r[l],i,d),d+=h}},Node.prototype.getTextSize=function(t){if(void 0!==this.label){t.font=(this.selected?"bold ":"")+this.fontSize+"px "+this.fontFace;for(var e=this.label.split("\n"),i=(this.fontSize+4)*e.length,n=0,s=0,o=e.length;o>s;s++)n=Math.max(n,t.measureText(e[s]).width);return{width:n,height:i}}return{width:0,height:0}},Node.prototype.inArea=function(){return void 0!==this.width?this.x+this.width*this.graphScaleInv>=this.canvasTopLeft.x&&this.x-this.width*this.graphScaleInv=this.canvasTopLeft.y&&this.y-this.height*this.graphScaleInv=this.canvasTopLeft.x&&this.x=this.canvasTopLeft.y&&this.yh},Edge.prototype._drawLine=function(t){t.strokeStyle=this.color,t.lineWidth=this._getLineWidth();var e;if(this.from!=this.to+9)this._line(t),this.label&&(e=this._pointOnLine(.5),this._label(t,this.label,e.x,e.y));else{var i,n,s=this.length/4,o=this.from;o.width||o.resize(t),o.width>o.height?(i=o.x+o.width/2,n=o.y-s):(i=o.x+s,n=o.y-o.height/2),this._circle(t,i,n,s),e=this._pointOnCircle(i,n,s,.5),this._label(t,this.label,e.x,e.y)}},Edge.prototype._getLineWidth=function(){return 1==this.selected?Math.min(2*this.width,this.widthMax)*this.graphScaleInv:this.width*this.graphScaleInv},Edge.prototype._line=function(t){t.beginPath(),t.moveTo(this.from.x,this.from.y),1==this.smooth?t.quadraticCurveTo(this.via.x,this.via.y,this.to.x,this.to.y):t.lineTo(this.to.x,this.to.y),t.stroke()},Edge.prototype._circle=function(t,e,i,n){t.beginPath(),t.arc(e,i,n,0,2*Math.PI,!1),t.stroke()},Edge.prototype._label=function(t,e,i,n){if(e){t.font=(this.from.selected||this.to.selected?"bold ":"")+this.fontSize+"px "+this.fontFace,t.fillStyle="white";var s=t.measureText(e).width,o=this.fontSize,r=i-s/2,a=n-o/2;t.fillRect(r,a,s,o),t.fillStyle=this.fontColor||"black",t.textAlign="left",t.textBaseline="top",t.fillText(e,r,a)}},Edge.prototype._drawDashLine=function(t){if(t.strokeStyle=this.color,t.lineWidth=this._getLineWidth(),void 0!==t.mozDash||void 0!==t.setLineDash){t.beginPath(),t.moveTo(this.from.x,this.from.y);var e=[0];e=void 0!==this.dash.length&&void 0!==this.dash.gap?[this.dash.length,this.dash.gap]:[5,5],"undefined"!=typeof t.setLineDash?(t.setLineDash(e),t.lineDashOffset=0):(t.mozDash=e,t.mozDashOffset=0),1==this.smooth?t.quadraticCurveTo(this.via.x,this.via.y,this.to.x,this.to.y):t.lineTo(this.to.x,this.to.y),t.stroke(),"undefined"!=typeof t.setLineDash?(t.setLineDash([0]),t.lineDashOffset=0):(t.mozDash=[0],t.mozDashOffset=0)}else t.beginPath(),t.lineCap="round",void 0!==this.dash.altLength?t.dashedLine(this.from.x,this.from.y,this.to.x,this.to.y,[this.dash.length,this.dash.gap,this.dash.altLength,this.dash.gap]):void 0!==this.dash.length&&void 0!==this.dash.gap?t.dashedLine(this.from.x,this.from.y,this.to.x,this.to.y,[this.dash.length,this.dash.gap]):(t.moveTo(this.from.x,this.from.y),t.lineTo(this.to.x,this.to.y)),t.stroke();if(this.label){var i=this._pointOnLine(.5);this._label(t,this.label,i.x,i.y)}},Edge.prototype._pointOnLine=function(t){return{x:(1-t)*this.from.x+t*this.to.x,y:(1-t)*this.from.y+t*this.to.y}},Edge.prototype._pointOnCircle=function(t,e,i,n){var s=2*(n-3/8)*Math.PI;return{x:t+i*Math.cos(s),y:e-i*Math.sin(s)}},Edge.prototype._drawArrowCenter=function(t){var e;if(t.strokeStyle=this.color,t.fillStyle=this.color,t.lineWidth=this._getLineWidth(),this.from!=this.to){this._line(t);var i=Math.atan2(this.to.y-this.from.y,this.to.x-this.from.x),n=10+5*this.width;if(1==this.smooth){var s=.5*(.5*(this.from.x+this.via.x)+.5*(this.to.x+this.via.x)),o=.5*(.5*(this.from.y+this.via.y)+.5*(this.to.y+this.via.y));e={x:s,y:o}}else e=this._pointOnLine(.5);t.arrow(e.x,e.y,i,n),t.fill(),t.stroke(),this.label&&(e=this._pointOnLine(.5),this._label(t,this.label,e.x,e.y))}else{var r,a,h=.25*Math.max(100,this.length),d=this.from;d.width||d.resize(t),d.width>d.height?(r=d.x+.5*d.width,a=d.y-h):(r=d.x+h,a=d.y-.5*d.height),this._circle(t,r,a,h);var i=.2*Math.PI,n=10+5*this.width;e=this._pointOnCircle(r,a,h,.5),t.arrow(e.x,e.y,i,n),t.fill(),t.stroke(),this.label&&(e=this._pointOnCircle(r,a,h,.5),this._label(t,this.label,e.x,e.y))}},Edge.prototype._drawArrow=function(t){t.strokeStyle=this.color,t.fillStyle=this.color,t.lineWidth=this._getLineWidth();var e,i;if(this.from!=this.to){e=Math.atan2(this.to.y-this.from.y,this.to.x-this.from.x);var n=this.to.x-this.from.x,s=this.to.y-this.from.y,o=Math.sqrt(n*n+s*s),r=this.from.distanceToBorder(t,e+Math.PI),a=(o-r)/o,h=a*this.from.x+(1-a)*this.to.x,d=a*this.from.y+(1-a)*this.to.y;1==this.smooth&&(e=Math.atan2(this.to.y-this.via.y,this.to.x-this.via.x),n=this.to.x-this.via.x,s=this.to.y-this.via.y,o=Math.sqrt(n*n+s*s));var l,c,u=this.to.distanceToBorder(t,e),p=(o-u)/o;if(1==this.smooth?(l=(1-p)*this.via.x+p*this.to.x,c=(1-p)*this.via.y+p*this.to.y):(l=(1-p)*this.from.x+p*this.to.x,c=(1-p)*this.from.y+p*this.to.y),t.beginPath(),t.moveTo(h,d),1==this.smooth?t.quadraticCurveTo(this.via.x,this.via.y,l,c):t.lineTo(l,c),t.stroke(),i=10+5*this.width,t.arrow(l,c,e,i),t.fill(),t.stroke(),this.label){var f=this._pointOnLine(.5);this._label(t,this.label,f.x,f.y)}}else{var m,g,v,y=this.from,_=.25*Math.max(100,this.length);y.width||y.resize(t),y.width>y.height?(m=y.x+.5*y.width,g=y.y-_,v={x:m,y:y.y,angle:.9*Math.PI}):(m=y.x+_,g=y.y-.5*y.height,v={x:y.x,y:g,angle:.6*Math.PI}),t.beginPath(),t.arc(m,g,_,0,2*Math.PI,!1),t.stroke(),i=10+5*this.width,t.arrow(v.x,v.y,v.angle,i),t.fill(),t.stroke(),this.label&&(f=this._pointOnCircle(m,g,_,.5),this._label(t,this.label,f.x,f.y))}},Edge.prototype._getDistanceToEdge=function(t,e,i,n,s,o){if(1==this.smooth){var r,a,h,d,l,c,u=1e9;for(r=0;10>r;r++)a=.1*r,h=Math.pow(1-a,2)*t+2*a*(1-a)*this.via.x+Math.pow(a,2)*i,d=Math.pow(1-a,2)*e+2*a*(1-a)*this.via.y+Math.pow(a,2)*n,l=Math.abs(s-h),c=Math.abs(o-d),u=Math.min(u,Math.sqrt(l*l+c*c));return u}var p=i-t,f=n-e,m=p*p+f*f,g=((s-t)*p+(o-e)*f)/m;g>1?g=1:0>g&&(g=0);var h=t+g*p,d=e+g*f,l=h-s,c=d-o;return Math.sqrt(l*l+c*c)},Edge.prototype.setScale=function(t){this.graphScaleInv=1/t},Edge.prototype.select=function(){this.selected=!0},Edge.prototype.unselect=function(){this.selected=!1},Edge.prototype.positionBezierNode=function(){null!==this.via&&(this.via.x=.5*(this.from.x+this.to.x),this.via.y=.5*(this.from.y+this.to.y))},Popup.prototype.setPosition=function(t,e){this.x=parseInt(t),this.y=parseInt(e)},Popup.prototype.setText=function(t){this.frame.innerHTML=t},Popup.prototype.show=function(t){if(void 0===t&&(t=!0),t){var e=this.frame.clientHeight,i=this.frame.clientWidth,n=this.frame.parentNode.clientHeight,s=this.frame.parentNode.clientWidth,o=this.y-e;o+e+this.padding>n&&(o=n-e-this.padding),os&&(r=s-i-this.padding),rthis.constants.clustering.clusterThreshold&&1==this.constants.clustering.enabled&&this.clusterToFit(this.constants.clustering.reduceToNodes,!1),this._calculateForces())},_calculateForces:function(){this._calculateGravitationalForces(),this._calculateNodeForces(),1==this.constants.smoothCurves?this._calculateSpringForcesWithSupport():this._calculateSpringForces()},_updateCalculationNodes:function(){if(1==this.constants.smoothCurves){this.calculationNodes={},this.calculationNodeIndices=[];for(var t in this.nodes)this.nodes.hasOwnProperty(t)&&(this.calculationNodes[t]=this.nodes[t]);var e=this.sectors.support.nodes;for(var i in e)e.hasOwnProperty(i)&&(this.edges.hasOwnProperty(e[i].parentEdgeId)?this.calculationNodes[i]=e[i]:e[i]._setForce(0,0));for(var n in this.calculationNodes)this.calculationNodes.hasOwnProperty(n)&&this.calculationNodeIndices.push(n)}else this.calculationNodes=this.nodes,this.calculationNodeIndices=this.nodeIndices},_calculateGravitationalForces:function(){var t,e,i,n,s,o=this.calculationNodes,r=this.constants.physics.centralGravity,a=0;for(s=0;si&&(o=g*i+u,0==i?i=.01:o/=i,n=t*o,s=e*o,r.fx-=n,r.fy-=s,a.fx+=n,a.fy+=s)}}},barnesHutMixin={_calculateNodeForces:function(){var t,e=this.calculationNodes,i=this.calculationNodeIndices,n=i.length;this._formBarnesHutTree(e,i);for(var s=this.barnesHutTree,o=0;n>o;o++)t=e[i[o]],this._getForceContribution(s.root.children.NW,t),this._getForceContribution(s.root.children.NE,t),this._getForceContribution(s.root.children.SW,t),this._getForceContribution(s.root.children.SE,t)},_getForceContribution:function(t,e){if(t.childrenCount>0){var i,n,s;if(i=t.centerOfMass.x-e.x,n=t.centerOfMass.y-e.y,s=Math.sqrt(i*i+n*n),s*t.calcSize>this.constants.physics.barnesHut.theta){0==s&&(s=.1*Math.random(),i=s);var o=this.constants.physics.barnesHut.gravitationalConstant*t.mass*e.mass/(s*s*s),r=i*o,a=n*o;e.fx+=r,e.fy+=a}else if(4==t.childrenCount)this._getForceContribution(t.children.NW,e),this._getForceContribution(t.children.NE,e),this._getForceContribution(t.children.SW,e),this._getForceContribution(t.children.SE,e);else if(t.children.data.id!=e.id){0==s&&(s=.5*Math.random(),i=s);var o=this.constants.physics.barnesHut.gravitationalConstant*t.mass*e.mass/(s*s*s),r=i*o,a=n*o;e.fx+=r,e.fy+=a}}},_formBarnesHutTree:function(t,e){for(var i,n=e.length,s=Number.MAX_VALUE,o=Number.MAX_VALUE,r=-Number.MAX_VALUE,a=-Number.MAX_VALUE,h=0;n>h;h++){var d=t[e[h]].x,l=t[e[h]].y;s>d&&(s=d),d>r&&(r=d),o>l&&(o=l),l>a&&(a=l)}var c=Math.abs(r-s)-Math.abs(a-o);c>0?(o-=.5*c,a+=.5*c):(s+=.5*c,r-=.5*c);var u=1e-5,p=Math.max(u,Math.abs(r-s)),f=.5*p,m=.5*(s+r),g=.5*(o+a),v={root:{centerOfMass:{x:0,y:0},mass:0,range:{minX:m-f,maxX:m+f,minY:g-f,maxY:g+f},size:p,calcSize:1/p,children:{data:null},maxWidth:0,level:0,childrenCount:4}};for(this._splitBranch(v.root),h=0;n>h;h++)i=t[e[h]],this._placeInTree(v.root,i);this.barnesHutTree=v},_updateBranchMass:function(t,e){var i=t.mass+e.mass,n=1/i;t.centerOfMass.x=t.centerOfMass.x*t.mass+e.x*e.mass,t.centerOfMass.x*=n,t.centerOfMass.y=t.centerOfMass.y*t.mass+e.y*e.mass,t.centerOfMass.y*=n,t.mass=i;var s=Math.max(Math.max(e.height,e.radius),e.width);t.maxWidth=t.maxWidthe.x?t.children.NW.range.maxY>e.y?this._placeInRegion(t,e,"NW"):this._placeInRegion(t,e,"SW"):t.children.NW.range.maxY>e.y?this._placeInRegion(t,e,"NE"):this._placeInRegion(t,e,"SE")},_placeInRegion:function(t,e,i){switch(t.children[i].childrenCount){case 0:t.children[i].children.data=e,t.children[i].childrenCount=1,this._updateBranchMass(t.children[i],e);break;case 1:t.children[i].children.data.x==e.x&&t.children[i].children.data.y==e.y?(e.x+=Math.random(),e.y+=Math.random(),this._placeInTree(t,e,!0)):(this._splitBranch(t.children[i]),this._placeInTree(t.children[i],e));break;case 4:this._placeInTree(t.children[i],e)}},_splitBranch:function(t){var e=null;1==t.childrenCount&&(e=t.children.data,t.mass=0,t.centerOfMass.x=0,t.centerOfMass.y=0),t.childrenCount=4,t.children.data=null,this._insertRegion(t,"NW"),this._insertRegion(t,"NE"),this._insertRegion(t,"SW"),this._insertRegion(t,"SE"),null!=e&&this._placeInTree(t,e)},_insertRegion:function(t,e){var i,n,s,o,r=.5*t.size;switch(e){case"NW":i=t.range.minX,n=t.range.minX+r,s=t.range.minY,o=t.range.minY+r;break;case"NE":i=t.range.minX+r,n=t.range.maxX,s=t.range.minY,o=t.range.minY+r;break;case"SW":i=t.range.minX,n=t.range.minX+r,s=t.range.minY+r,o=t.range.maxY;break;case"SE":i=t.range.minX+r,n=t.range.maxX,s=t.range.minY+r,o=t.range.maxY}t.children[e]={centerOfMass:{x:0,y:0},mass:0,range:{minX:i,maxX:n,minY:s,maxY:o},size:.5*t.size,calcSize:2*t.calcSize,children:{data:null},maxWidth:0,level:t.level+1,childrenCount:0}},_drawTree:function(t,e){void 0!==this.barnesHutTree&&(t.lineWidth=1,this._drawBranch(this.barnesHutTree.root,t,e))},_drawBranch:function(t,e,i){void 0===i&&(i="#FF0000"),4==t.childrenCount&&(this._drawBranch(t.children.NW,e),this._drawBranch(t.children.NE,e),this._drawBranch(t.children.SE,e),this._drawBranch(t.children.SW,e)),e.strokeStyle=i,e.beginPath(),e.moveTo(t.range.minX,t.range.minY),e.lineTo(t.range.maxX,t.range.minY),e.stroke(),e.beginPath(),e.moveTo(t.range.maxX,t.range.minY),e.lineTo(t.range.maxX,t.range.maxY),e.stroke(),e.beginPath(),e.moveTo(t.range.maxX,t.range.maxY),e.lineTo(t.range.minX,t.range.maxY),e.stroke(),e.beginPath(),e.moveTo(t.range.minX,t.range.maxY),e.lineTo(t.range.minX,t.range.minY),e.stroke()}},repulsionMixin={_calculateNodeForces:function(){var t,e,i,n,s,o,r,a,h,d,l,c=this.calculationNodes,u=this.calculationNodeIndices,p=-2/3,f=4/3,m=this.constants.physics.repulsion.nodeDistance,g=m;for(d=0;di&&(r=.5*g>i?1:v*i+f,r*=0==o?1:1+o*this.constants.clustering.forceAmplification,r/=i,n=t*r,s=e*r,a.fx-=n,a.fy-=s,h.fx+=n,h.fy+=s)}}},HierarchicalLayoutMixin={_setupHierarchicalLayout:function(){if(1==this.constants.hierarchicalLayout.enabled){var t,e,i=0,n=!1,s=!1;for(e in this.nodes)this.nodes.hasOwnProperty(e)&&(t=this.nodes[e],-1!=t.level?n=!0:s=!0,in&&(o.xFixed=!1,o.x=i[o.level].minPos,i[o.level].minPos+=i[o.level].nodeSpacing,o.edges.length>1&&this._placeBranchNodes(o.edges,o.id,i,o.level))}},_setLevel:function(t,e,i){for(var n=0;nt)&&(s.level=t,e.length>1&&this._setLevel(t+1,s.edges,s.id))}}},manipulationMixin={_clearManipulatorBar:function(){for(;this.manipulationDiv.hasChildNodes();)this.manipulationDiv.removeChild(this.manipulationDiv.firstChild)},_restoreOverloadedFunctions:function(){for(var t in this.cachedFunctions)this.cachedFunctions.hasOwnProperty(t)&&(this[t]=this.cachedFunctions[t])},_toggleEditMode:function(){this.editMode=!this.editMode;var t=document.getElementById("graph-manipulationDiv"),e=document.getElementById("graph-manipulation-closeDiv"),i=document.getElementById("graph-manipulation-editMode");1==this.editMode?(t.style.display="block",e.style.display="block",i.style.display="none",e.onclick=this._toggleEditMode.bind(this)):(t.style.display="none",e.style.display="none",i.style.display="block",e.onclick=null),this._createManipulatorBar()},_createManipulatorBar:function(){if(this.off("select",this.boundFunction),this._restoreOverloadedFunctions(),this.freezeSimulation=!1,this.blockConnectingEdgeSelection=!1,this.forceAppendSelection=!1,1==this.editMode){for(;this.manipulationDiv.hasChildNodes();)this.manipulationDiv.removeChild(this.manipulationDiv.firstChild);this.manipulationDiv.innerHTML="Add NodeAdd Link",1==this._getSelectedNodeCount()&&this.triggerFunctions.edit&&(this.manipulationDiv.innerHTML+="Edit Node"),0==this._selectionIsEmpty()&&(this.manipulationDiv.innerHTML+="Delete selected");var t=document.getElementById("graph-manipulate-addNode");t.onclick=this._createAddNodeToolbar.bind(this);var e=document.getElementById("graph-manipulate-connectNode");if(e.onclick=this._createAddEdgeToolbar.bind(this),1==this._getSelectedNodeCount()&&this.triggerFunctions.edit){var i=document.getElementById("graph-manipulate-editNode");i.onclick=this._editNode.bind(this)}if(0==this._selectionIsEmpty()){var n=document.getElementById("graph-manipulate-delete");n.onclick=this._deleteSelected.bind(this)}var s=document.getElementById("graph-manipulation-closeDiv");s.onclick=this._toggleEditMode.bind(this),this.boundFunction=this._createManipulatorBar.bind(this),this.on("select",this.boundFunction)}else{this.editModeDiv.innerHTML="Edit";var o=document.getElementById("graph-manipulate-editModeButton");o.onclick=this._toggleEditMode.bind(this)}},_createAddNodeToolbar:function(){this._clearManipulatorBar(),this.off("select",this.boundFunction),this.manipulationDiv.innerHTML="BackClick in an empty space to place a new node";var t=document.getElementById("graph-manipulate-back");t.onclick=this._createManipulatorBar.bind(this),this.boundFunction=this._addNode.bind(this),this.on("select",this.boundFunction)},_createAddEdgeToolbar:function(){this._clearManipulatorBar(),this._unselectAll(!0),this.freezeSimulation=!0,this.off("select",this.boundFunction),this._unselectAll(),this.forceAppendSelection=!1,this.blockConnectingEdgeSelection=!0,this.manipulationDiv.innerHTML="BackClick on a node and drag the edge to another node to connect them.";var t=document.getElementById("graph-manipulate-back");t.onclick=this._createManipulatorBar.bind(this),this.boundFunction=this._handleConnect.bind(this),this.on("select",this.boundFunction),this.cachedFunctions._handleTouch=this._handleTouch,this.cachedFunctions._handleOnRelease=this._handleOnRelease,this._handleTouch=this._handleConnect,this._handleOnRelease=this._finishConnect,this._redraw()},_handleConnect:function(t){if(0==this._getSelectedNodeCount()){var e=this._getNodeAt(t);null!=e&&(e.clusterSize>1?alert("Cannot create edges to a cluster."):(this._selectObject(e,!1),this.sectors.support.nodes.targetNode=new Node({id:"targetNode"},{},{},this.constants),this.sectors.support.nodes.targetNode.x=e.x,this.sectors.support.nodes.targetNode.y=e.y,this.sectors.support.nodes.targetViaNode=new Node({id:"targetViaNode"},{},{},this.constants),this.sectors.support.nodes.targetViaNode.x=e.x,this.sectors.support.nodes.targetViaNode.y=e.y,this.sectors.support.nodes.targetViaNode.parentEdgeId="connectionEdge",this.edges.connectionEdge=new Edge({id:"connectionEdge",from:e.id,to:this.sectors.support.nodes.targetNode.id},this,this.constants),this.edges.connectionEdge.from=e,this.edges.connectionEdge.connected=!0,this.edges.connectionEdge.smooth=!0,this.edges.connectionEdge.selected=!0,this.edges.connectionEdge.to=this.sectors.support.nodes.targetNode,this.edges.connectionEdge.via=this.sectors.support.nodes.targetViaNode,this.cachedFunctions._handleOnDrag=this._handleOnDrag,this._handleOnDrag=function(t){var e=this._getPointer(t.gesture.center);this.sectors.support.nodes.targetNode.x=this._canvasToX(e.x),this.sectors.support.nodes.targetNode.y=this._canvasToY(e.y),this.sectors.support.nodes.targetViaNode.x=.5*(this._canvasToX(e.x)+this.edges.connectionEdge.from.x),this.sectors.support.nodes.targetViaNode.y=this._canvasToY(e.y)},this.moving=!0,this.start()))}},_finishConnect:function(t){if(1==this._getSelectedNodeCount()){this._handleOnDrag=this.cachedFunctions._handleOnDrag,delete this.cachedFunctions._handleOnDrag;var e=this.edges.connectionEdge.fromId;delete this.edges.connectionEdge,delete this.sectors.support.nodes.targetNode,delete this.sectors.support.nodes.targetViaNode;var i=this._getNodeAt(t);null!=i&&(i.clusterSize>1?alert("Cannot create edges to a cluster."):(this._createEdge(e,i.id),this._createManipulatorBar())),this._unselectAll()}},_addNode:function(){if(this._selectionIsEmpty()&&1==this.editMode){var t=this._pointerToPositionObject(this.pointerPosition),e={id:util.randomUUID(),x:t.left,y:t.top,label:"new",allowedToMove:!0};if(this.triggerFunctions.add)if(2==this.triggerFunctions.add.length){var i=this;this.triggerFunctions.add(e,function(t){i.createNodeOnClick=!0,i.nodesData.add(t),i.createNodeOnClick=!1,i._createManipulatorBar(),i.moving=!0,i.start()})}else alert("The function for add does not support two arguments (data,callback)."),this._createManipulatorBar(),this.moving=!0,this.start();else this.createNodeOnClick=!0,this.nodesData.add(e),this.createNodeOnClick=!1,this._createManipulatorBar(),this.moving=!0,this.start()}},_createEdge:function(t,e){if(1==this.editMode){var i={from:t,to:e};if(this.triggerFunctions.connect)if(2==this.triggerFunctions.connect.length){var n=this;this.triggerFunctions.connect(i,function(t){n.edgesData.add(t),n.moving=!0,n.start()})}else alert("The function for connect does not support two arguments (data,callback)."),this.moving=!0,this.start();else this.edgesData.add(i),this.moving=!0,this.start()}},_editNode:function(){if(this.triggerFunctions.edit&&1==this.editMode){var t=this._getSelectedNode(),e={id:t.id,label:t.label,group:t.group,shape:t.shape,color:{background:t.color.background,border:t.color.border,highlight:{background:t.color.highlight.background,border:t.color.highlight.border}}};if(2==this.triggerFunctions.edit.length){var i=this;this.triggerFunctions.edit(e,function(t){i.nodesData.update(t),i._createManipulatorBar(),i.moving=!0,i.start()})}else alert("The function for edit does not support two arguments (data, callback).")}else alert("No edit function has been bound to this button.")},_deleteSelected:function(){if(!this._selectionIsEmpty()&&1==this.editMode)if(this._clusterInSelection())alert("Clusters cannot be deleted.");else{var t=this.getSelectedNodes(),e=this.getSelectedEdges();if(this.triggerFunctions.delete){var i=this,n={nodes:t,edges:e};(this.triggerFunctions.delete.length=2)?this.triggerFunctions.delete(n,function(t){i.edgesData.remove(t.edges),i.nodesData.remove(t.nodes),this._unselectAll(),i.moving=!0,i.start()}):alert("The function for edit does not support two arguments (data, callback).")}else this.edgesData.remove(e),this.nodesData.remove(t),this._unselectAll(),this.moving=!0,this.start()}}},SectorMixin={_putDataInSector:function(){this.sectors.active[this._sector()].nodes=this.nodes,this.sectors.active[this._sector()].edges=this.edges,this.sectors.active[this._sector()].nodeIndices=this.nodeIndices},_switchToSector:function(t,e){void 0===e||"active"==e?this._switchToActiveSector(t):this._switchToFrozenSector(t)},_switchToActiveSector:function(t){this.nodeIndices=this.sectors.active[t].nodeIndices,this.nodes=this.sectors.active[t].nodes,this.edges=this.sectors.active[t].edges},_switchToSupportSector:function(){this.nodeIndices=this.sectors.support.nodeIndices,this.nodes=this.sectors.support.nodes,this.edges=this.sectors.support.edges},_switchToFrozenSector:function(t){this.nodeIndices=this.sectors.frozen[t].nodeIndices,this.nodes=this.sectors.frozen[t].nodes,this.edges=this.sectors.frozen[t].edges},_loadLatestSector:function(){this._switchToSector(this._sector())},_sector:function(){return this.activeSector[this.activeSector.length-1]},_previousSector:function(){if(this.activeSector.length>1)return this.activeSector[this.activeSector.length-2];throw new TypeError("there are not enough sectors in the this.activeSector array.")},_setActiveSector:function(t){this.activeSector.push(t)},_forgetLastSector:function(){this.activeSector.pop()},_createNewSector:function(t){this.sectors.active[t]={nodes:{},edges:{},nodeIndices:[],formationScale:this.scale,drawingNode:void 0},this.sectors.active[t].drawingNode=new Node({id:t,color:{background:"#eaefef",border:"495c5e"}},{},{},this.constants),this.sectors.active[t].drawingNode.clusterSize=2},_deleteActiveSector:function(t){delete this.sectors.active[t]},_deleteFrozenSector:function(t){delete this.sectors.frozen[t]},_freezeSector:function(t){this.sectors.frozen[t]=this.sectors.active[t],this._deleteActiveSector(t)},_activateSector:function(t){this.sectors.active[t]=this.sectors.frozen[t],this._deleteFrozenSector(t)},_mergeThisWithFrozen:function(t){for(var e in this.nodes)this.nodes.hasOwnProperty(e)&&(this.sectors.frozen[t].nodes[e]=this.nodes[e]);for(var i in this.edges)this.edges.hasOwnProperty(i)&&(this.sectors.frozen[t].edges[i]=this.edges[i]);for(var n=0;n1?this[t](n[0],n[1]):this[t](e)}this._loadLatestSector()},_doInSupportSector:function(t,e){if(void 0===e)this._switchToSupportSector(),this[t]();else{this._switchToSupportSector();var i=Array.prototype.splice.call(arguments,1);i.length>1?this[t](i[0],i[1]):this[t](e)}this._loadLatestSector()},_doInAllFrozenSectors:function(t,e){if(void 0===e)for(var i in this.sectors.frozen)this.sectors.frozen.hasOwnProperty(i)&&(this._switchToFrozenSector(i),this[t]());else for(var i in this.sectors.frozen)if(this.sectors.frozen.hasOwnProperty(i)){this._switchToFrozenSector(i);var n=Array.prototype.splice.call(arguments,1);n.length>1?this[t](n[0],n[1]):this[t](e)}this._loadLatestSector()},_doInAllSectors:function(t,e){var i=Array.prototype.splice.call(arguments,1);void 0===e?(this._doInAllActiveSectors(t),this._doInAllFrozenSectors(t)):i.length>1?(this._doInAllActiveSectors(t,i[0],i[1]),this._doInAllFrozenSectors(t,i[0],i[1])):(this._doInAllActiveSectors(t,e),this._doInAllFrozenSectors(t,e))},_clearNodeIndexList:function(){var t=this._sector();this.sectors.active[t].nodeIndices=[],this.nodeIndices=this.sectors.active[t].nodeIndices},_drawSectorNodes:function(t,e){var i,n=1e9,s=-1e9,o=1e9,r=-1e9;for(var a in this.sectors[e])if(this.sectors[e].hasOwnProperty(a)&&void 0!==this.sectors[e][a].drawingNode){this._switchToSector(a,e),n=1e9,s=-1e9,o=1e9,r=-1e9;for(var h in this.nodes)this.nodes.hasOwnProperty(h)&&(i=this.nodes[h],i.resize(t),o>i.x-.5*i.width&&(o=i.x-.5*i.width),ri.y-.5*i.height&&(n=i.y-.5*i.height),st&&n>s;)s%3==0?(this.forceAggregateHubs(!0),this.normalizeClusterLevels()):this.increaseClusterLevel(),i=this.nodeIndices.length,s+=1;s>0&&1==e&&this.repositionNodes(),this._updateCalculationNodes()},openCluster:function(t){var e=this.moving;if(t.clusterSize>this.constants.clustering.sectorThreshold&&this._nodeInActiveArea(t)&&("default"!=this._sector()||1!=this.nodeIndices.length)){this._addSector(t);for(var i=0;this.nodeIndices.lengthi;)this.decreaseClusterLevel(),i+=1}else this._expandClusterNode(t,!1,!0),this._updateNodeIndexList(),this._updateDynamicEdges(),this._updateCalculationNodes(),this.updateLabels();this.moving!=e&&this.start()},updateClustersDefault:function(){1==this.constants.clustering.enabled&&this.updateClusters(0,!1,!1)},increaseClusterLevel:function(){this.updateClusters(-1,!1,!0)},decreaseClusterLevel:function(){this.updateClusters(1,!1,!0)},updateClusters:function(t,e,i,n){var s=this.moving,o=this.nodeIndices.length;this.previousScale>this.scale&&0==t&&this._collapseSector(),this.previousScale>this.scale||-1==t?this._formClusters(i):(this.previousScalethis.scale||-1==t)&&(this._aggregateHubs(i),this._updateNodeIndexList()),(this.previousScale>this.scale||-1==t)&&(this.handleChains(),this._updateNodeIndexList()),this.previousScale=this.scale,this._updateDynamicEdges(),this.updateLabels(),this.nodeIndices.lengththis.constants.clustering.chainThreshold&&this._reduceAmountOfChains(1-this.constants.clustering.chainThreshold/t)},_aggregateHubs:function(t){this._getHubSize(),this._formClustersByHub(t,!1)},forceAggregateHubs:function(t){var e=this.moving,i=this.nodeIndices.length;this._aggregateHubs(!0),this._updateNodeIndexList(),this._updateDynamicEdges(),this.updateLabels(),this.nodeIndices.length!=i&&(this.clusterSession+=1),(0==t||void 0===t)&&this.moving!=e&&this.start()},_openClustersBySize:function(){for(var t in this.nodes)if(this.nodes.hasOwnProperty(t)){var e=this.nodes[t];1==e.inView()&&(e.width*this.scale>this.constants.clustering.screenSizeThreshold*this.frame.canvas.clientWidth||e.height*this.scale>this.constants.clustering.screenSizeThreshold*this.frame.canvas.clientHeight)&&this.openCluster(e)}},_openClusters:function(t,e){for(var i=0;i1&&(t.clusterSizei)){var r=o.from,a=o.to;o.to.mass>o.from.mass&&(r=o.to,a=o.from),1==a.dynamicEdgesLength?this._addToCluster(r,a,!1):1==r.dynamicEdgesLength&&this._addToCluster(a,r,!1)}}},_forceClustersByZoom:function(){for(var t in this.nodes)if(this.nodes.hasOwnProperty(t)){var e=this.nodes[t];if(1==e.dynamicEdgesLength&&0!=e.dynamicEdges.length){var i=e.dynamicEdges[0],n=i.toId==e.id?this.nodes[i.fromId]:this.nodes[i.toId];e.id!=n.id&&(n.mass>e.mass?this._addToCluster(n,e,!0):this._addToCluster(e,n,!0))}}},_clusterToSmallestNeighbour:function(t){for(var e=-1,i=null,n=0;ns.clusterSessions.length&&(e=s.clusterSessions.length,i=s)}null!=s&&void 0!==this.nodes[s.id]&&this._addToCluster(s,t,!0)},_formClustersByHub:function(t,e){for(var i in this.nodes)this.nodes.hasOwnProperty(i)&&this._formClusterFromHub(this.nodes[i],t,e)},_formClusterFromHub:function(t,e,i,n){if(void 0===n&&(n=0),t.dynamicEdgesLength>=this.hubThreshold&&0==i||t.dynamicEdgesLength==this.hubThreshold&&1==i){for(var s,o,r,a=this.constants.clustering.clusterEdgeThreshold/this.scale,h=!1,d=[],l=t.dynamicEdges.length,c=0;l>c;c++)d.push(t.dynamicEdges[c].id);if(0==e)for(h=!1,c=0;l>c;c++){var u=this.edges[d[c]];if(void 0!==u&&u.connected&&u.toId!=u.fromId&&(s=u.to.x-u.from.x,o=u.to.y-u.from.y,r=Math.sqrt(s*s+o*o),a>r)){h=!0;break}}if(!e&&h||e)for(c=0;l>c;c++)if(u=this.edges[d[c]],void 0!==u){var p=this.nodes[u.fromId==t.id?u.toId:u.fromId];
+p.dynamicEdges.length<=this.hubThreshold+n&&p.id!=t.id&&this._addToCluster(t,p,e)}}},_addToCluster:function(t,e,i){t.containedNodes[e.id]=e;for(var n=0;n1)for(var n=0;n1&&(e.label="[".concat(String(e.clusterSize),"]"))}for(t in this.nodes)this.nodes.hasOwnProperty(t)&&(e=this.nodes[t],1==e.clusterSize&&(e.label=void 0!==e.originalLabel?e.originalLabel:String(e.id)))},normalizeClusterLevels:function(){var t=0,e=1e9,i=0;for(var n in this.nodes)this.nodes.hasOwnProperty(n)&&(i=this.nodes[n].clusterSessions.length,i>t&&(t=i),e>i&&(e=i));if(t-e>this.constants.clustering.clusterLevelDifference){var s=this.nodeIndices.length,o=t-this.constants.clustering.clusterLevelDifference;for(var n in this.nodes)this.nodes.hasOwnProperty(n)&&this.nodes[n].clusterSessions.lengthn&&(n=o.dynamicEdgesLength),t+=o.dynamicEdgesLength,e+=Math.pow(o.dynamicEdgesLength,2),i+=1}t/=i,e/=i;var r=e-Math.pow(t,2),a=Math.sqrt(r);this.hubThreshold=Math.floor(t+2*a),this.hubThreshold>n&&(this.hubThreshold=n)},_reduceAmountOfChains:function(t){this.hubThreshold=2;var e=Math.floor(this.nodeIndices.length*t);for(var i in this.nodes)this.nodes.hasOwnProperty(i)&&2==this.nodes[i].dynamicEdgesLength&&this.nodes[i].dynamicEdges.length>=2&&e>0&&(this._formClusterFromHub(this.nodes[i],!0,!0,1),e-=1)},_getChainFraction:function(){var t=0,e=0;for(var i in this.nodes)this.nodes.hasOwnProperty(i)&&(2==this.nodes[i].dynamicEdgesLength&&this.nodes[i].dynamicEdges.length>=2&&(t+=1),e+=1);return t/e}},SelectionMixin={_getNodesOverlappingWith:function(t,e){var i=this.nodes;for(var n in i)i.hasOwnProperty(n)&&i[n].isOverlappingWith(t)&&e.push(n)},_getAllNodesOverlappingWith:function(t){var e=[];return this._doInAllActiveSectors("_getNodesOverlappingWith",t,e),e},_pointerToPositionObject:function(t){var e=this._canvasToX(t.x),i=this._canvasToY(t.y);return{left:e,top:i,right:e,bottom:i}},_getNodeAt:function(t){var e=this._pointerToPositionObject(t),i=this._getAllNodesOverlappingWith(e);return i.length>0?this.nodes[i[i.length-1]]:null},_getEdgesOverlappingWith:function(t,e){var i=this.edges;for(var n in i)i.hasOwnProperty(n)&&i[n].isOverlappingWith(t)&&e.push(n)},_getAllEdgesOverlappingWith:function(t){var e=[];return this._doInAllActiveSectors("_getEdgesOverlappingWith",t,e),e},_getEdgeAt:function(t){var e=this._pointerToPositionObject(t),i=this._getAllEdgesOverlappingWith(e);return i.length>0?this.edges[i[i.length-1]]:null},_addToSelection:function(t){this.selectionObj[t.id]=t},_removeFromSelection:function(t){delete this.selectionObj[t.id]},_unselectAll:function(t){void 0===t&&(t=!1);for(var e in this.selectionObj)this.selectionObj.hasOwnProperty(e)&&this.selectionObj[e].unselect();this.selectionObj={},0==t&&this.emit("select",this.getSelection())},_unselectClusters:function(t){void 0===t&&(t=!1);for(var e in this.selectionObj)this.selectionObj.hasOwnProperty(e)&&this.selectionObj[e]instanceof Node&&this.selectionObj[e].clusterSize>1&&(this.selectionObj[e].unselect(),this._removeFromSelection(this.selectionObj[e]));0==t&&this.emit("select",this.getSelection())},_getSelectedNodeCount:function(){var t=0;for(var e in this.selectionObj)this.selectionObj.hasOwnProperty(e)&&this.selectionObj[e]instanceof Node&&(t+=1);return t},_getSelectedNode:function(){for(var t in this.selectionObj)if(this.selectionObj.hasOwnProperty(t)&&this.selectionObj[t]instanceof Node)return this.selectionObj[t];return null},_getSelectedEdgeCount:function(){var t=0;for(var e in this.selectionObj)this.selectionObj.hasOwnProperty(e)&&this.selectionObj[e]instanceof Edge&&(t+=1);return t},_getSelectedObjectCount:function(){var t=0;for(var e in this.selectionObj)this.selectionObj.hasOwnProperty(e)&&(t+=1);return t},_selectionIsEmpty:function(){for(var t in this.selectionObj)if(this.selectionObj.hasOwnProperty(t))return!1;return!0},_clusterInSelection:function(){for(var t in this.selectionObj)if(this.selectionObj.hasOwnProperty(t)&&this.selectionObj[t]instanceof Node&&this.selectionObj[t].clusterSize>1)return!0;return!1},_selectConnectedEdges:function(t){for(var e=0;ee;e++){n=t[e];var s=this.nodes[n];if(!s)throw new RangeError('Node with id "'+n+'" not found');this._selectObject(s,!0,!0)}this.redraw()},_updateSelection:function(){for(var t in this.selectionObj)this.selectionObj.hasOwnProperty(t)&&(this.selectionObj[t]instanceof Node?this.nodes.hasOwnProperty(t)||delete this.selectionObj[t]:this.edges.hasOwnProperty(t)||delete this.selectionObj[t])}},NavigationMixin={_cleanNavigation:function(){var t=document.getElementById("graph-navigation_wrapper");null!=t&&this.containerElement.removeChild(t),document.onmouseup=null},_loadNavigationElements:function(){this._cleanNavigation(),this.navigationDivs={};var t=["up","down","left","right","zoomIn","zoomOut","zoomExtends"],e=["_moveUp","_moveDown","_moveLeft","_moveRight","_zoomIn","_zoomOut","zoomToFit"];this.navigationDivs.wrapper=document.createElement("div"),this.navigationDivs.wrapper.id="graph-navigation_wrapper",this.containerElement.insertBefore(this.navigationDivs.wrapper,this.frame);for(var i=0;it.x-t.width&&(n=t.x-t.width),st.y-t.height&&(e=t.y-t.height),i=this.constants.clustering.initialMaxNodes?49.07548/(s+142.05338)+91444e-8:12.662/(s+7.4147)+.0964822:1==this.constants.clustering.enabled&&s>=this.constants.clustering.initialMaxNodes?77.5271985/(s+187.266146)+476710517e-13:30.5062972/(s+19.93597763)+.08413486}else{var o=1.1*(Math.abs(n.minX)+Math.abs(n.maxX)),r=1.1*(Math.abs(n.minY)+Math.abs(n.maxY)),a=this.frame.canvas.clientWidth/o,h=this.frame.canvas.clientHeight/r;i=h>=a?a:h}i>1&&(i=1),this.pinch.mousewheelScale=i,this._setScale(i),this._centerGraph(n),(0==e||void 0===e)&&(this.moving=!0,this.start())},Graph.prototype._updateNodeIndexList=function(){this._clearNodeIndexList();for(var t in this.nodes)this.nodes.hasOwnProperty(t)&&this.nodeIndices.push(t)},Graph.prototype.setData=function(t,e){if(void 0===e&&(e=!1),t&&t.dot&&(t.nodes||t.edges))throw new SyntaxError('Data must contain either parameter "dot" or parameter pair "nodes" and "edges", but not both.');if(this.setOptions(t&&t.options),t&&t.dot){if(t&&t.dot){var i=vis.util.DOTToGraph(t.dot);return void this.setData(i)}}else this._setNodes(t&&t.nodes),this._setEdges(t&&t.edges);this._putDataInSector(),e||(this.stabilize&&this._doStabilize(),this.start())},Graph.prototype.setOptions=function(t){if(t){var e;if(void 0!==t.width&&(this.width=t.width),void 0!==t.height&&(this.height=t.height),void 0!==t.stabilize&&(this.stabilize=t.stabilize),void 0!==t.selectable&&(this.selectable=t.selectable),void 0!==t.smoothCurves&&(this.constants.smoothCurves=t.smoothCurves),t.onAdd&&(this.triggerFunctions.add=t.onAdd),t.onEdit&&(this.triggerFunctions.edit=t.onEdit),t.onConnect&&(this.triggerFunctions.connect=t.onConnect),t.onDelete&&(this.triggerFunctions.delete=t.onDelete),t.physics){if(t.physics.barnesHut){this.constants.physics.barnesHut.enabled=!0;for(e in t.physics.barnesHut)t.physics.barnesHut.hasOwnProperty(e)&&(this.constants.physics.barnesHut[e]=t.physics.barnesHut[e])}if(t.physics.repulsion){this.constants.physics.barnesHut.enabled=!1;for(e in t.physics.repulsion)t.physics.repulsion.hasOwnProperty(e)&&(this.constants.physics.repulsion[e]=t.physics.repulsion[e])}}if(t.hierarchicalLayout){this.constants.hierarchicalLayout.enabled=!0;for(e in t.hierarchicalLayout)t.hierarchicalLayout.hasOwnProperty(e)&&(this.constants.hierarchicalLayout[e]=t.hierarchicalLayout[e])}else void 0!==t.hierarchicalLayout&&(this.constants.hierarchicalLayout.enabled=!1);if(t.clustering){this.constants.clustering.enabled=!0;for(e in t.clustering)t.clustering.hasOwnProperty(e)&&(this.constants.clustering[e]=t.clustering[e])}else void 0!==t.clustering&&(this.constants.clustering.enabled=!1);if(t.navigation){this.constants.navigation.enabled=!0;for(e in t.navigation)t.navigation.hasOwnProperty(e)&&(this.constants.navigation[e]=t.navigation[e])}else void 0!==t.navigation&&(this.constants.navigation.enabled=!1);if(t.keyboard){this.constants.keyboard.enabled=!0;for(e in t.keyboard)t.keyboard.hasOwnProperty(e)&&(this.constants.keyboard[e]=t.keyboard[e])}else void 0!==t.keyboard&&(this.constants.keyboard.enabled=!1);if(t.dataManipulation){this.constants.dataManipulation.enabled=!0;for(e in t.dataManipulation)t.dataManipulation.hasOwnProperty(e)&&(this.constants.dataManipulation[e]=t.dataManipulation[e])}else void 0!==t.dataManipulation&&(this.constants.dataManipulation.enabled=!1);if(t.edges){for(e in t.edges)t.edges.hasOwnProperty(e)&&(this.constants.edges[e]=t.edges[e]);t.edges.fontColor||(this.constants.edges.fontColor=t.edges.color),t.edges.dash&&(void 0!==t.edges.dash.length&&(this.constants.edges.dash.length=t.edges.dash.length),void 0!==t.edges.dash.gap&&(this.constants.edges.dash.gap=t.edges.dash.gap),void 0!==t.edges.dash.altLength&&(this.constants.edges.dash.altLength=t.edges.dash.altLength))}if(t.nodes){for(e in t.nodes)t.nodes.hasOwnProperty(e)&&(this.constants.nodes[e]=t.nodes[e]);t.nodes.color&&(this.constants.nodes.color=Node.parseColor(t.nodes.color))}if(t.groups)for(var i in t.groups)if(t.groups.hasOwnProperty(i)){var n=t.groups[i];this.groups.add(i,n)}}this._loadPhysicsSystem(),this._loadNavigationControls(),this._loadManipulationSystem(),this._configureSmoothCurves(),this._createKeyBinds(),this.setSize(this.width,this.height),this._setTranslation(this.frame.clientWidth/2,this.frame.clientHeight/2),this._setScale(1),this._redraw()},Graph.prototype._create=function(){for(;this.containerElement.hasChildNodes();)this.containerElement.removeChild(this.containerElement.firstChild);if(this.frame=document.createElement("div"),this.frame.className="graph-frame",this.frame.style.position="relative",this.frame.style.overflow="hidden",this.frame.style.zIndex="1",this.frame.canvas=document.createElement("canvas"),this.frame.canvas.style.position="relative",this.frame.appendChild(this.frame.canvas),!this.frame.canvas.getContext){var t=document.createElement("DIV");t.style.color="red",t.style.fontWeight="bold",t.style.padding="10px",t.innerHTML="Error: your browser does not support HTML canvas",this.frame.canvas.appendChild(t)}var e=this;this.drag={},this.pinch={},this.hammer=Hammer(this.frame.canvas,{prevent_default:!0}),this.hammer.on("tap",e._onTap.bind(e)),this.hammer.on("doubletap",e._onDoubleTap.bind(e)),this.hammer.on("hold",e._onHold.bind(e)),this.hammer.on("pinch",e._onPinch.bind(e)),this.hammer.on("touch",e._onTouch.bind(e)),this.hammer.on("dragstart",e._onDragStart.bind(e)),this.hammer.on("drag",e._onDrag.bind(e)),this.hammer.on("dragend",e._onDragEnd.bind(e)),this.hammer.on("release",e._onRelease.bind(e)),this.hammer.on("mousewheel",e._onMouseWheel.bind(e)),this.hammer.on("DOMMouseScroll",e._onMouseWheel.bind(e)),this.hammer.on("mousemove",e._onMouseMoveTitle.bind(e)),this.containerElement.appendChild(this.frame)},Graph.prototype._createKeyBinds=function(){var t=this;this.mousetrap=mousetrap,this.mousetrap.reset(),1==this.constants.keyboard.enabled&&(this.mousetrap.bind("up",this._moveUp.bind(t),"keydown"),this.mousetrap.bind("up",this._yStopMoving.bind(t),"keyup"),this.mousetrap.bind("down",this._moveDown.bind(t),"keydown"),this.mousetrap.bind("down",this._yStopMoving.bind(t),"keyup"),this.mousetrap.bind("left",this._moveLeft.bind(t),"keydown"),this.mousetrap.bind("left",this._xStopMoving.bind(t),"keyup"),this.mousetrap.bind("right",this._moveRight.bind(t),"keydown"),this.mousetrap.bind("right",this._xStopMoving.bind(t),"keyup"),this.mousetrap.bind("=",this._zoomIn.bind(t),"keydown"),this.mousetrap.bind("=",this._stopZoom.bind(t),"keyup"),this.mousetrap.bind("-",this._zoomOut.bind(t),"keydown"),this.mousetrap.bind("-",this._stopZoom.bind(t),"keyup"),this.mousetrap.bind("[",this._zoomIn.bind(t),"keydown"),this.mousetrap.bind("[",this._stopZoom.bind(t),"keyup"),this.mousetrap.bind("]",this._zoomOut.bind(t),"keydown"),this.mousetrap.bind("]",this._stopZoom.bind(t),"keyup"),this.mousetrap.bind("pageup",this._zoomIn.bind(t),"keydown"),this.mousetrap.bind("pageup",this._stopZoom.bind(t),"keyup"),this.mousetrap.bind("pagedown",this._zoomOut.bind(t),"keydown"),this.mousetrap.bind("pagedown",this._stopZoom.bind(t),"keyup")),1==this.constants.dataManipulation.enabled&&(this.mousetrap.bind("escape",this._createManipulatorBar.bind(t)),this.mousetrap.bind("del",this._deleteSelected.bind(t)))},Graph.prototype._getPointer=function(t){return{x:t.pageX-vis.util.getAbsoluteLeft(this.frame.canvas),y:t.pageY-vis.util.getAbsoluteTop(this.frame.canvas)}},Graph.prototype._onTouch=function(t){this.drag.pointer=this._getPointer(t.gesture.center),this.drag.pinched=!1,this.pinch.scale=this._getScale(),this._handleTouch(this.drag.pointer)},Graph.prototype._onDragStart=function(){this._handleDragStart()},Graph.prototype._handleDragStart=function(){var t=this.drag,e=this._getNodeAt(t.pointer);if(t.dragging=!0,t.selection=[],t.translation=this._getTranslation(),t.nodeId=null,null!=e){t.nodeId=e.id,e.isSelected()||this._selectObject(e,!1);for(var i in this.selectionObj)if(this.selectionObj.hasOwnProperty(i)){var n=this.selectionObj[i];if(n instanceof Node){var s={id:n.id,node:n,x:n.x,y:n.y,xFixed:n.xFixed,yFixed:n.yFixed};n.xFixed=!0,n.yFixed=!0,t.selection.push(s)}}}},Graph.prototype._onDrag=function(t){this._handleOnDrag(t)},Graph.prototype._handleOnDrag=function(t){if(!this.drag.pinched){var e=this._getPointer(t.gesture.center),i=this,n=this.drag,s=n.selection;if(s&&s.length){var o=e.x-n.pointer.x,r=e.y-n.pointer.y;s.forEach(function(t){var e=t.node;t.xFixed||(e.x=i._canvasToX(i._xToCanvas(t.x)+o)),t.yFixed||(e.y=i._canvasToY(i._yToCanvas(t.y)+r))}),this.moving||(this.moving=!0,this.start())}else{var a=e.x-this.drag.pointer.x,h=e.y-this.drag.pointer.y;this._setTranslation(this.drag.translation.x+a,this.drag.translation.y+h),this._redraw(),this.moved=!0}}},Graph.prototype._onDragEnd=function(){this.drag.dragging=!1;var t=this.drag.selection;t&&t.forEach(function(t){t.node.xFixed=t.xFixed,t.node.yFixed=t.yFixed})},Graph.prototype._onTap=function(t){var e=this._getPointer(t.gesture.center);this.pointerPosition=e,this._handleTap(e)},Graph.prototype._onDoubleTap=function(t){var e=this._getPointer(t.gesture.center);this._handleDoubleTap(e)},Graph.prototype._onHold=function(t){var e=this._getPointer(t.gesture.center);this.pointerPosition=e,this._handleOnHold(e)},Graph.prototype._onRelease=function(t){var e=this._getPointer(t.gesture.center);this._handleOnRelease(e)},Graph.prototype._onPinch=function(t){var e=this._getPointer(t.gesture.center);this.drag.pinched=!0,"scale"in this.pinch||(this.pinch.scale=1);var i=this.pinch.scale*t.gesture.scale;this._zoom(i,e)},Graph.prototype._zoom=function(t,e){var i=this._getScale();1e-5>t&&(t=1e-5),t>10&&(t=10);var n=this._getTranslation(),s=t/i,o=(1-s)*e.x+n.x*s,r=(1-s)*e.y+n.y*s;return this.areaCenter={x:this._canvasToX(e.x),y:this._canvasToY(e.y)},this.pinch.mousewheelScale=t,this._setScale(t),this._setTranslation(o,r),this.updateClustersDefault(),this._redraw(),t},Graph.prototype._onMouseWheel=function(t){var e=0;if(t.wheelDelta?e=t.wheelDelta/120:t.detail&&(e=-t.detail/3),e){"mousewheelScale"in this.pinch||(this.pinch.mousewheelScale=1);var i=this.pinch.mousewheelScale,n=e/10;0>e&&(n/=1-n),i*=1+n;var s=util.fakeGesture(this,t),o=this._getPointer(s.center);this._zoom(i,o)}t.preventDefault()},Graph.prototype._onMouseMoveTitle=function(t){var e=util.fakeGesture(this,t),i=this._getPointer(e.center);this.popupNode&&this._checkHidePopup(i);var n=this,s=function(){n._checkShowPopup(i)};this.popupTimer&&clearInterval(this.popupTimer),this.drag.dragging||(this.popupTimer=setTimeout(s,300))},Graph.prototype._checkShowPopup=function(t){var e,i={left:this._canvasToX(t.x),top:this._canvasToY(t.y),right:this._canvasToX(t.x),bottom:this._canvasToY(t.y)},n=this.popupNode;if(void 0==this.popupNode){var s=this.nodes;for(e in s)if(s.hasOwnProperty(e)){var o=s[e];if(void 0!==o.getTitle()&&o.isOverlappingWith(i)){this.popupNode=o;break}}}if(void 0===this.popupNode){var r=this.edges;for(e in r)if(r.hasOwnProperty(e)){var a=r[e];if(a.connected&&void 0!==a.getTitle()&&a.isOverlappingWith(i)){this.popupNode=a;break}}}if(this.popupNode){if(this.popupNode!=n){var h=this;h.popup||(h.popup=new Popup(h.frame)),h.popup.setPosition(t.x-3,t.y-3),h.popup.setText(h.popupNode.getTitle()),h.popup.show()}}else this.popup&&this.popup.hide()},Graph.prototype._checkHidePopup=function(t){this.popupNode&&this._getNodeAt(t)||(this.popupNode=void 0,this.popup&&this.popup.hide())},Graph.prototype.setSize=function(t,e){this.frame.style.width=t,this.frame.style.height=e,this.frame.canvas.style.width="100%",this.frame.canvas.style.height="100%",this.frame.canvas.width=this.frame.canvas.clientWidth,this.frame.canvas.height=this.frame.canvas.clientHeight,void 0!==this.manipulationDiv&&(this.manipulationDiv.style.width=this.frame.canvas.clientWidth),this.emit("frameResize",{width:this.frame.canvas.width,height:this.frame.canvas.height})},Graph.prototype._setNodes=function(t){var e=this.nodesData;if(t instanceof DataSet||t instanceof DataView)this.nodesData=t;else if(t instanceof Array)this.nodesData=new DataSet,this.nodesData.add(t);else{if(t)throw new TypeError("Array or DataSet expected");this.nodesData=new DataSet}if(e&&util.forEach(this.nodesListeners,function(t,i){e.off(i,t)}),this.nodes={},this.nodesData){var i=this;util.forEach(this.nodesListeners,function(t,e){i.nodesData.on(e,t)});var n=this.nodesData.getIds();this._addNodes(n)}this._updateSelection()},Graph.prototype._addNodes=function(t){for(var e,i=0,n=t.length;n>i;i++){e=t[i];var s=this.nodesData.get(e),o=new Node(s,this.images,this.groups,this.constants);if(this.nodes[e]=o,(0==o.xFixed||0==o.yFixed)&&1!=this.createNodeOnClick){var r=1*t.length,a=2*Math.PI*Math.random();0==o.xFixed&&(o.x=r*Math.cos(a)),0==o.yFixed&&(o.y=r*Math.sin(a)),this.moving=!0}}this._updateNodeIndexList(),this._updateCalculationNodes(),this._reconnectEdges(),this._updateValueRange(this.nodes),this.updateLabels()},Graph.prototype._updateNodes=function(t){for(var e=this.nodes,i=this.nodesData,n=0,s=t.length;s>n;n++){var o=t[n],r=e[o],a=i.get(o);r?r.setProperties(a,this.constants):(r=new Node(properties,this.images,this.groups,this.constants),e[o]=r,r.isFixed()||(this.moving=!0))}this._updateNodeIndexList(),this._reconnectEdges(),this._updateValueRange(e)},Graph.prototype._removeNodes=function(t){for(var e=this.nodes,i=0,n=t.length;n>i;i++){var s=t[i];delete e[s]}this._updateNodeIndexList(),this._reconnectEdges(),this._updateSelection(),this._updateValueRange(e)},Graph.prototype._setEdges=function(t){var e=this.edgesData;if(t instanceof DataSet||t instanceof DataView)this.edgesData=t;else if(t instanceof Array)this.edgesData=new DataSet,this.edgesData.add(t);else{if(t)throw new TypeError("Array or DataSet expected");this.edgesData=new DataSet}if(e&&util.forEach(this.edgesListeners,function(t,i){e.off(i,t)}),this.edges={},this.edgesData){var i=this;util.forEach(this.edgesListeners,function(t,e){i.edgesData.on(e,t)});var n=this.edgesData.getIds();this._addEdges(n)}this._reconnectEdges()},Graph.prototype._addEdges=function(t){for(var e=this.edges,i=this.edgesData,n=0,s=t.length;s>n;n++){var o=t[n],r=e[o];r&&r.disconnect();var a=i.get(o,{showInternalIds:!0});e[o]=new Edge(a,this,this.constants)}this.moving=!0,this._updateValueRange(e),this._createBezierNodes(),this._updateCalculationNodes()},Graph.prototype._updateEdges=function(t){for(var e=this.edges,i=this.edgesData,n=0,s=t.length;s>n;n++){var o=t[n],r=i.get(o),a=e[o];a?(a.disconnect(),a.setProperties(r,this.constants),a.connect()):(a=new Edge(r,this,this.constants),this.edges[o]=a)}this._createBezierNodes(),this.moving=!0,this._updateValueRange(e)},Graph.prototype._removeEdges=function(t){for(var e=this.edges,i=0,n=t.length;n>i;i++){var s=t[i],o=e[s];o&&(null!=o.via&&delete this.sectors.support.nodes[o.via.id],o.disconnect(),delete e[s])}this.moving=!0,this._updateValueRange(e),this._updateCalculationNodes()},Graph.prototype._reconnectEdges=function(){var t,e=this.nodes,i=this.edges;
+for(t in e)e.hasOwnProperty(t)&&(e[t].edges=[]);for(t in i)if(i.hasOwnProperty(t)){var n=i[t];n.from=null,n.to=null,n.connect()}},Graph.prototype._updateValueRange=function(t){var e,i=void 0,n=void 0;for(e in t)if(t.hasOwnProperty(e)){var s=t[e].getValue();void 0!==s&&(i=void 0===i?s:Math.min(s,i),n=void 0===n?s:Math.max(s,n))}if(void 0!==i&&void 0!==n)for(e in t)t.hasOwnProperty(e)&&t[e].setValueRange(i,n)},Graph.prototype.redraw=function(){this.setSize(this.width,this.height),this._redraw()},Graph.prototype._redraw=function(){var t=this.frame.canvas.getContext("2d"),e=this.frame.canvas.width,i=this.frame.canvas.height;t.clearRect(0,0,e,i),t.save(),t.translate(this.translation.x,this.translation.y),t.scale(this.scale,this.scale),this.canvasTopLeft={x:this._canvasToX(0),y:this._canvasToY(0)},this.canvasBottomRight={x:this._canvasToX(this.frame.canvas.clientWidth),y:this._canvasToY(this.frame.canvas.clientHeight)},this._doInAllSectors("_drawAllSectorNodes",t),this._doInAllSectors("_drawEdges",t),this._doInAllSectors("_drawNodes",t,!1),t.restore()},Graph.prototype._setTranslation=function(t,e){void 0===this.translation&&(this.translation={x:0,y:0}),void 0!==t&&(this.translation.x=t),void 0!==e&&(this.translation.y=e)},Graph.prototype._getTranslation=function(){return{x:this.translation.x,y:this.translation.y}},Graph.prototype._setScale=function(t){this.scale=t},Graph.prototype._getScale=function(){return this.scale},Graph.prototype._canvasToX=function(t){return(t-this.translation.x)/this.scale},Graph.prototype._xToCanvas=function(t){return t*this.scale+this.translation.x},Graph.prototype._canvasToY=function(t){return(t-this.translation.y)/this.scale},Graph.prototype._yToCanvas=function(t){return t*this.scale+this.translation.y},Graph.prototype._drawNodes=function(t,e){void 0===e&&(e=!1);var i=this.nodes,n=[];for(var s in i)i.hasOwnProperty(s)&&(i[s].setScaleAndPos(this.scale,this.canvasTopLeft,this.canvasBottomRight),i[s].isSelected()?n.push(s):(i[s].inArea()||e)&&i[s].draw(t));for(var o=0,r=n.length;r>o;o++)(i[n[o]].inArea()||e)&&i[n[o]].draw(t)},Graph.prototype._drawEdges=function(t){var e=this.edges;for(var i in e)if(e.hasOwnProperty(i)){var n=e[i];n.setScale(this.scale),n.connected&&e[i].draw(t)}},Graph.prototype._doStabilize=function(){for(var t=0;this.moving&&t0)for(t in i)i.hasOwnProperty(t)&&i[t].discreteStepLimited(e,this.constants.maxVelocity);else for(t in i)i.hasOwnProperty(t)&&i[t].discreteStep(e);var n=this.constants.minVelocity/Math.max(this.scale,.05);this.moving=n>.5*this.constants.maxVelocity?!0:this._isMoving(n)},Graph.prototype._physicsTick=function(){this.freezeSimulation||this.moving&&(this._doInAllActiveSectors("_initializeForceCalculation"),this.constants.smoothCurves&&this._doInSupportSector("_discreteStepNodes"),this._doInAllActiveSectors("_discreteStepNodes"),this._findCenter(this._getRange()))},Graph.prototype._animationStep=function(){this.timer=void 0,this._handleNavigation(),this.start();var t=Date.now(),e=1;this._physicsTick();for(var i=Date.now()-t;in;++n)i[n].apply(this,e)}return this},i.prototype.listeners=function(t){return this._callbacks=this._callbacks||{},this._callbacks[t]||[]},i.prototype.hasListeners=function(t){return!!this.listeners(t).length}},{}],3:[function(t,e){!function(t,i){"use strict";function n(){if(!s.READY){s.event.determineEventTypes();for(var t in s.gestures)s.gestures.hasOwnProperty(t)&&s.detection.register(s.gestures[t]);s.event.onTouch(s.DOCUMENT,s.EVENT_MOVE,s.detection.detect),s.event.onTouch(s.DOCUMENT,s.EVENT_END,s.detection.detect),s.READY=!0}}var s=function(t,e){return new s.Instance(t,e||{})};s.defaults={stop_browser_behavior:{userSelect:"none",touchAction:"none",touchCallout:"none",contentZooming:"none",userDrag:"none",tapHighlightColor:"rgba(0,0,0,0)"}},s.HAS_POINTEREVENTS=navigator.pointerEnabled||navigator.msPointerEnabled,s.HAS_TOUCHEVENTS="ontouchstart"in t,s.MOBILE_REGEX=/mobile|tablet|ip(ad|hone|od)|android/i,s.NO_MOUSEEVENTS=s.HAS_TOUCHEVENTS&&navigator.userAgent.match(s.MOBILE_REGEX),s.EVENT_TYPES={},s.DIRECTION_DOWN="down",s.DIRECTION_LEFT="left",s.DIRECTION_UP="up",s.DIRECTION_RIGHT="right",s.POINTER_MOUSE="mouse",s.POINTER_TOUCH="touch",s.POINTER_PEN="pen",s.EVENT_START="start",s.EVENT_MOVE="move",s.EVENT_END="end",s.DOCUMENT=document,s.plugins={},s.READY=!1,s.Instance=function(t,e){var i=this;return n(),this.element=t,this.enabled=!0,this.options=s.utils.extend(s.utils.extend({},s.defaults),e||{}),this.options.stop_browser_behavior&&s.utils.stopDefaultBrowserBehavior(this.element,this.options.stop_browser_behavior),s.event.onTouch(t,s.EVENT_START,function(t){i.enabled&&s.detection.startDetect(i,t)}),this},s.Instance.prototype={on:function(t,e){for(var i=t.split(" "),n=0;n0&&e==s.EVENT_END?e=s.EVENT_MOVE:l||(e=s.EVENT_END),l||null===o?o=h:h=o,i.call(s.detection,n.collectEventData(t,e,h)),s.HAS_POINTEREVENTS&&e==s.EVENT_END&&(l=s.PointerEvent.updatePointer(e,h))),l||(o=null,r=!1,a=!1,s.PointerEvent.reset())}})},determineEventTypes:function(){var t;t=s.HAS_POINTEREVENTS?s.PointerEvent.getEvents():s.NO_MOUSEEVENTS?["touchstart","touchmove","touchend touchcancel"]:["touchstart mousedown","touchmove mousemove","touchend touchcancel mouseup"],s.EVENT_TYPES[s.EVENT_START]=t[0],s.EVENT_TYPES[s.EVENT_MOVE]=t[1],s.EVENT_TYPES[s.EVENT_END]=t[2]},getTouchList:function(t){return s.HAS_POINTEREVENTS?s.PointerEvent.getTouchList():t.touches?t.touches:[{identifier:1,pageX:t.pageX,pageY:t.pageY,target:t.target}]},collectEventData:function(t,e,i){var n=this.getTouchList(i,e),o=s.POINTER_TOUCH;return(i.type.match(/mouse/)||s.PointerEvent.matchType(s.POINTER_MOUSE,i))&&(o=s.POINTER_MOUSE),{center:s.utils.getCenter(n),timeStamp:(new Date).getTime(),target:i.target,touches:n,eventType:e,pointerType:o,srcEvent:i,preventDefault:function(){this.srcEvent.preventManipulation&&this.srcEvent.preventManipulation(),this.srcEvent.preventDefault&&this.srcEvent.preventDefault()},stopPropagation:function(){this.srcEvent.stopPropagation()},stopDetect:function(){return s.detection.stopDetect()}}}},s.PointerEvent={pointers:{},getTouchList:function(){var t=this,e=[];return Object.keys(t.pointers).sort().forEach(function(i){e.push(t.pointers[i])}),e},updatePointer:function(t,e){return t==s.EVENT_END?this.pointers={}:(e.identifier=e.pointerId,this.pointers[e.pointerId]=e),Object.keys(this.pointers).length},matchType:function(t,e){if(!e.pointerType)return!1;var i={};return i[s.POINTER_MOUSE]=e.pointerType==e.MSPOINTER_TYPE_MOUSE||e.pointerType==s.POINTER_MOUSE,i[s.POINTER_TOUCH]=e.pointerType==e.MSPOINTER_TYPE_TOUCH||e.pointerType==s.POINTER_TOUCH,i[s.POINTER_PEN]=e.pointerType==e.MSPOINTER_TYPE_PEN||e.pointerType==s.POINTER_PEN,i[t]},getEvents:function(){return["pointerdown MSPointerDown","pointermove MSPointerMove","pointerup pointercancel MSPointerUp MSPointerCancel"]},reset:function(){this.pointers={}}},s.utils={extend:function(t,e,n){for(var s in e)t[s]!==i&&n||(t[s]=e[s]);return t},hasParent:function(t,e){for(;t;){if(t==e)return!0;t=t.parentNode}return!1},getCenter:function(t){for(var e=[],i=[],n=0,s=t.length;s>n;n++)e.push(t[n].pageX),i.push(t[n].pageY);return{pageX:(Math.min.apply(Math,e)+Math.max.apply(Math,e))/2,pageY:(Math.min.apply(Math,i)+Math.max.apply(Math,i))/2}},getVelocity:function(t,e,i){return{x:Math.abs(e/t)||0,y:Math.abs(i/t)||0}},getAngle:function(t,e){var i=e.pageY-t.pageY,n=e.pageX-t.pageX;return 180*Math.atan2(i,n)/Math.PI},getDirection:function(t,e){var i=Math.abs(t.pageX-e.pageX),n=Math.abs(t.pageY-e.pageY);return i>=n?t.pageX-e.pageX>0?s.DIRECTION_LEFT:s.DIRECTION_RIGHT:t.pageY-e.pageY>0?s.DIRECTION_UP:s.DIRECTION_DOWN},getDistance:function(t,e){var i=e.pageX-t.pageX,n=e.pageY-t.pageY;return Math.sqrt(i*i+n*n)},getScale:function(t,e){return t.length>=2&&e.length>=2?this.getDistance(e[0],e[1])/this.getDistance(t[0],t[1]):1},getRotation:function(t,e){return t.length>=2&&e.length>=2?this.getAngle(e[1],e[0])-this.getAngle(t[1],t[0]):0},isVertical:function(t){return t==s.DIRECTION_UP||t==s.DIRECTION_DOWN},stopDefaultBrowserBehavior:function(t,e){var i,n=["webkit","khtml","moz","ms","o",""];if(e&&t.style){for(var s=0;si;i++){var o=this.gestures[i];if(!this.stopped&&e[o.name]!==!1&&o.handler.call(o,t,this.current.inst)===!1){this.stopDetect();break}}return this.current&&(this.current.lastEvent=t),t.eventType==s.EVENT_END&&!t.touches.length-1&&this.stopDetect(),t}},stopDetect:function(){this.previous=s.utils.extend({},this.current),this.current=null,this.stopped=!0},extendEventData:function(t){var e=this.current.startEvent;if(e&&(t.touches.length!=e.touches.length||t.touches===e.touches)){e.touches=[];for(var i=0,n=t.touches.length;n>i;i++)e.touches.push(s.utils.extend({},t.touches[i]))}var o=t.timeStamp-e.timeStamp,r=t.center.pageX-e.center.pageX,a=t.center.pageY-e.center.pageY,h=s.utils.getVelocity(o,r,a);return s.utils.extend(t,{deltaTime:o,deltaX:r,deltaY:a,velocityX:h.x,velocityY:h.y,distance:s.utils.getDistance(e.center,t.center),angle:s.utils.getAngle(e.center,t.center),direction:s.utils.getDirection(e.center,t.center),scale:s.utils.getScale(e.touches,t.touches),rotation:s.utils.getRotation(e.touches,t.touches),startEvent:e}),t},register:function(t){var e=t.defaults||{};return e[t.name]===i&&(e[t.name]=!0),s.utils.extend(s.defaults,e,!0),t.index=t.index||1e3,this.gestures.push(t),this.gestures.sort(function(t,e){return t.indexe.index?1:0}),this.gestures}},s.gestures=s.gestures||{},s.gestures.Hold={name:"hold",index:10,defaults:{hold_timeout:500,hold_threshold:1},timer:null,handler:function(t,e){switch(t.eventType){case s.EVENT_START:clearTimeout(this.timer),s.detection.current.name=this.name,this.timer=setTimeout(function(){"hold"==s.detection.current.name&&e.trigger("hold",t)},e.options.hold_timeout);break;case s.EVENT_MOVE:t.distance>e.options.hold_threshold&&clearTimeout(this.timer);break;case s.EVENT_END:clearTimeout(this.timer)}}},s.gestures.Tap={name:"tap",index:100,defaults:{tap_max_touchtime:250,tap_max_distance:10,tap_always:!0,doubletap_distance:20,doubletap_interval:300},handler:function(t,e){if(t.eventType==s.EVENT_END){var i=s.detection.previous,n=!1;if(t.deltaTime>e.options.tap_max_touchtime||t.distance>e.options.tap_max_distance)return;i&&"tap"==i.name&&t.timeStamp-i.lastEvent.timeStamp0&&t.touches.length>e.options.swipe_max_touches)return;(t.velocityX>e.options.swipe_velocity||t.velocityY>e.options.swipe_velocity)&&(e.trigger(this.name,t),e.trigger(this.name+t.direction,t))}}},s.gestures.Drag={name:"drag",index:50,defaults:{drag_min_distance:10,drag_max_touches:1,drag_block_horizontal:!1,drag_block_vertical:!1,drag_lock_to_axis:!1,drag_lock_min_distance:25},triggered:!1,handler:function(t,e){if(s.detection.current.name!=this.name&&this.triggered)return e.trigger(this.name+"end",t),void(this.triggered=!1);if(!(e.options.drag_max_touches>0&&t.touches.length>e.options.drag_max_touches))switch(t.eventType){case s.EVENT_START:this.triggered=!1;break;case s.EVENT_MOVE:if(t.distancee.options.transform_min_rotation&&e.trigger("rotate",t),i>e.options.transform_min_scale&&(e.trigger("pinch",t),e.trigger("pinch"+(t.scale<1?"in":"out"),t));break;case s.EVENT_END:this.triggered&&e.trigger(this.name+"end",t),this.triggered=!1}}},s.gestures.Touch={name:"touch",index:-1/0,defaults:{prevent_default:!1,prevent_mouseevents:!1},handler:function(t,e){return e.options.prevent_mouseevents&&t.pointerType==s.POINTER_MOUSE?void t.stopDetect():(e.options.prevent_default&&t.preventDefault(),void(t.eventType==s.EVENT_START&&e.trigger(this.name,t)))}},s.gestures.Release={name:"release",index:1/0,handler:function(t,e){t.eventType==s.EVENT_END&&e.trigger(this.name,t)}},"object"==typeof e&&"object"==typeof e.exports?e.exports=s:(t.Hammer=s,"function"==typeof t.define&&t.define.amd&&t.define("hammer",[],function(){return s}))}(this)},{}],4:[function(t,e){(function(i){function n(){return{empty:!1,unusedTokens:[],unusedInput:[],overflow:-2,charsLeftOver:0,nullInput:!1,invalidMonth:null,invalidFormat:!1,userInvalidated:!1,iso:!1}}function s(t,e){return function(i){return u(t.call(this,i),e)}}function o(t,e){return function(i){return this.lang().ordinal(t.call(this,i),e)}}function r(){}function a(t){T(t),d(this,t)}function h(t){var e=y(t),i=e.year||0,n=e.month||0,s=e.week||0,o=e.day||0,r=e.hour||0,a=e.minute||0,h=e.second||0,d=e.millisecond||0;this._milliseconds=+d+1e3*h+6e4*a+36e5*r,this._days=+o+7*s,this._months=+n+12*i,this._data={},this._bubble()}function d(t,e){for(var i in e)e.hasOwnProperty(i)&&(t[i]=e[i]);return e.hasOwnProperty("toString")&&(t.toString=e.toString),e.hasOwnProperty("valueOf")&&(t.valueOf=e.valueOf),t}function l(t){var e,i={};for(e in t)t.hasOwnProperty(e)&&ye.hasOwnProperty(e)&&(i[e]=t[e]);return i}function c(t){return 0>t?Math.ceil(t):Math.floor(t)}function u(t,e,i){for(var n=""+Math.abs(t),s=t>=0;n.lengthn;n++)(i&&t[n]!==e[n]||!i&&w(t[n])!==w(e[n]))&&r++;return r+o}function v(t){if(t){var e=t.toLowerCase().replace(/(.)s$/,"$1");t=Xe[t]||qe[e]||e}return t}function y(t){var e,i,n={};for(i in t)t.hasOwnProperty(i)&&(e=v(i),e&&(n[e]=t[i]));return n}function _(t){var e,n;if(0===t.indexOf("week"))e=7,n="day";else{if(0!==t.indexOf("month"))return;e=12,n="month"}oe[t]=function(s,o){var r,a,h=oe.fn._lang[t],d=[];if("number"==typeof s&&(o=s,s=i),a=function(t){var e=oe().utc().set(n,t);return h.call(oe.fn._lang,e,s||"")},null!=o)return a(o);for(r=0;e>r;r++)d.push(a(r));return d}}function w(t){var e=+t,i=0;return 0!==e&&isFinite(e)&&(i=e>=0?Math.floor(e):Math.ceil(e)),i}function b(t,e){return new Date(Date.UTC(t,e+1,0)).getUTCDate()}function S(t){return E(t)?366:365}function E(t){return t%4===0&&t%100!==0||t%400===0}function T(t){var e;t._a&&-2===t._pf.overflow&&(e=t._a[ce]<0||t._a[ce]>11?ce:t._a[ue]<1||t._a[ue]>b(t._a[le],t._a[ce])?ue:t._a[pe]<0||t._a[pe]>23?pe:t._a[fe]<0||t._a[fe]>59?fe:t._a[me]<0||t._a[me]>59?me:t._a[ge]<0||t._a[ge]>999?ge:-1,t._pf._overflowDayOfYear&&(le>e||e>ue)&&(e=ue),t._pf.overflow=e)}function x(t){return null==t._isValid&&(t._isValid=!isNaN(t._d.getTime())&&t._pf.overflow<0&&!t._pf.empty&&!t._pf.invalidMonth&&!t._pf.nullInput&&!t._pf.invalidFormat&&!t._pf.userInvalidated,t._strict&&(t._isValid=t._isValid&&0===t._pf.charsLeftOver&&0===t._pf.unusedTokens.length)),t._isValid}function D(t){return t?t.toLowerCase().replace("_","-"):t}function M(t,e){return e._isUTC?oe(t).zone(e._offset||0):oe(t).local()}function C(t,e){return e.abbr=t,ve[t]||(ve[t]=new r),ve[t].set(e),ve[t]}function I(t){delete ve[t]}function N(e){var i,n,s,o,r=0,a=function(e){if(!ve[e]&&_e)try{t("./lang/"+e)}catch(i){}return ve[e]};if(!e)return oe.fn._lang;if(!f(e)){if(n=a(e))return n;e=[e]}for(;r0;){if(n=a(o.slice(0,i).join("-")))return n;if(s&&s.length>=i&&g(o,s,!0)>=i-1)break;i--}r++}return oe.fn._lang}function O(t){return t.match(/\[[\s\S]/)?t.replace(/^\[|\]$/g,""):t.replace(/\\/g,"")}function L(t){var e,i,n=t.match(Ee);for(e=0,i=n.length;i>e;e++)n[e]=Qe[n[e]]?Qe[n[e]]:O(n[e]);return function(s){var o="";for(e=0;i>e;e++)o+=n[e]instanceof Function?n[e].call(s,t):n[e];return o}}function k(t,e){return t.isValid()?(e=P(e,t.lang()),Ze[e]||(Ze[e]=L(e)),Ze[e](t)):t.lang().invalidDate()}function P(t,e){function i(t){return e.longDateFormat(t)||t}var n=5;for(Te.lastIndex=0;n>=0&&Te.test(t);)t=t.replace(Te,i),Te.lastIndex=0,n-=1;return t}function A(t,e){var i,n=e._strict;switch(t){case"DDDD":return ze;case"YYYY":case"GGGG":case"gggg":return n?Fe:Me;case"Y":case"G":case"g":return Ye;case"YYYYYY":case"YYYYY":case"GGGGG":case"ggggg":return n?Re:Ce;case"S":if(n)return Pe;case"SS":if(n)return Ae;case"SSS":if(n)return ze;case"DDD":return De;case"MMM":case"MMMM":case"dd":case"ddd":case"dddd":return Ne;case"a":case"A":return N(e._l)._meridiemParse;case"X":return ke;case"Z":case"ZZ":return Oe;case"T":return Le;case"SSSS":return Ie;case"MM":case"DD":case"YY":case"GG":case"gg":case"HH":case"hh":case"mm":case"ss":case"ww":case"WW":return n?Ae:xe;case"M":case"D":case"d":case"H":case"h":case"m":case"s":case"w":case"W":case"e":case"E":return xe;default:return i=new RegExp(j(G(t.replace("\\","")),"i"))}}function z(t){t=t||"";var e=t.match(Oe)||[],i=e[e.length-1]||[],n=(i+"").match(Be)||["-",0,0],s=+(60*n[1])+w(n[2]);return"+"===n[0]?-s:s}function F(t,e,i){var n,s=i._a;switch(t){case"M":case"MM":null!=e&&(s[ce]=w(e)-1);break;case"MMM":case"MMMM":n=N(i._l).monthsParse(e),null!=n?s[ce]=n:i._pf.invalidMonth=e;break;case"D":case"DD":null!=e&&(s[ue]=w(e));break;case"DDD":case"DDDD":null!=e&&(i._dayOfYear=w(e));break;case"YY":s[le]=w(e)+(w(e)>68?1900:2e3);break;case"YYYY":case"YYYYY":case"YYYYYY":s[le]=w(e);break;case"a":case"A":i._isPm=N(i._l).isPM(e);break;case"H":case"HH":case"h":case"hh":s[pe]=w(e);break;case"m":case"mm":s[fe]=w(e);break;case"s":case"ss":s[me]=w(e);break;case"S":case"SS":case"SSS":case"SSSS":s[ge]=w(1e3*("0."+e));break;case"X":i._d=new Date(1e3*parseFloat(e));break;case"Z":case"ZZ":i._useUTC=!0,i._tzm=z(e);break;case"w":case"ww":case"W":case"WW":case"d":case"dd":case"ddd":case"dddd":case"e":case"E":t=t.substr(0,1);case"gg":case"gggg":case"GG":case"GGGG":case"GGGGG":t=t.substr(0,2),e&&(i._w=i._w||{},i._w[t]=e)}}function R(t){var e,i,n,s,o,r,a,h,d,l,c=[];if(!t._d){for(n=H(t),t._w&&null==t._a[ue]&&null==t._a[ce]&&(o=function(e){var i=parseInt(e,10);return e?e.length<3?i>68?1900+i:2e3+i:i:null==t._a[le]?oe().weekYear():t._a[le]},r=t._w,null!=r.GG||null!=r.W||null!=r.E?a=J(o(r.GG),r.W||1,r.E,4,1):(h=N(t._l),d=null!=r.d?Z(r.d,h):null!=r.e?parseInt(r.e,10)+h._week.dow:0,l=parseInt(r.w,10)||1,null!=r.d&&dS(s)&&(t._pf._overflowDayOfYear=!0),i=q(s,0,t._dayOfYear),t._a[ce]=i.getUTCMonth(),t._a[ue]=i.getUTCDate()),e=0;3>e&&null==t._a[e];++e)t._a[e]=c[e]=n[e];for(;7>e;e++)t._a[e]=c[e]=null==t._a[e]?2===e?1:0:t._a[e];c[pe]+=w((t._tzm||0)/60),c[fe]+=w((t._tzm||0)%60),t._d=(t._useUTC?q:X).apply(null,c)}}function Y(t){var e;t._d||(e=y(t._i),t._a=[e.year,e.month,e.day,e.hour,e.minute,e.second,e.millisecond],R(t))}function H(t){var e=new Date;return t._useUTC?[e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate()]:[e.getFullYear(),e.getMonth(),e.getDate()]}function W(t){t._a=[],t._pf.empty=!0;var e,i,n,s,o,r=N(t._l),a=""+t._i,h=a.length,d=0;for(n=P(t._f,r).match(Ee)||[],e=0;e0&&t._pf.unusedInput.push(o),a=a.slice(a.indexOf(i)+i.length),d+=i.length),Qe[s]?(i?t._pf.empty=!1:t._pf.unusedTokens.push(s),F(s,i,t)):t._strict&&!i&&t._pf.unusedTokens.push(s);t._pf.charsLeftOver=h-d,a.length>0&&t._pf.unusedInput.push(a),t._isPm&&t._a[pe]<12&&(t._a[pe]+=12),t._isPm===!1&&12===t._a[pe]&&(t._a[pe]=0),R(t),T(t)}function G(t){return t.replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(t,e,i,n,s){return e||i||n||s})}function j(t){return t.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function B(t){var e,i,s,o,r;if(0===t._f.length)return t._pf.invalidFormat=!0,void(t._d=new Date(0/0));for(o=0;or)&&(s=r,i=e));d(t,i||e)}function U(t){var e,i,n=t._i,s=He.exec(n);if(s){for(t._pf.iso=!0,e=0,i=Ge.length;i>e;e++)if(Ge[e][1].exec(n)){t._f=Ge[e][0]+(s[6]||" ");break}for(e=0,i=je.length;i>e;e++)if(je[e][1].exec(n)){t._f+=je[e][0];break}n.match(Oe)&&(t._f+="Z"),W(t)}else t._d=new Date(n)}function V(t){var e=t._i,n=we.exec(e);e===i?t._d=new Date:n?t._d=new Date(+n[1]):"string"==typeof e?U(t):f(e)?(t._a=e.slice(0),R(t)):m(e)?t._d=new Date(+e):"object"==typeof e?Y(t):t._d=new Date(e)}function X(t,e,i,n,s,o,r){var a=new Date(t,e,i,n,s,o,r);return 1970>t&&a.setFullYear(t),a}function q(t){var e=new Date(Date.UTC.apply(null,arguments));return 1970>t&&e.setUTCFullYear(t),e}function Z(t,e){if("string"==typeof t)if(isNaN(t)){if(t=e.weekdaysParse(t),"number"!=typeof t)return null}else t=parseInt(t,10);return t}function K(t,e,i,n,s){return s.relativeTime(e||1,!!i,t,n)}function $(t,e,i){var n=de(Math.abs(t)/1e3),s=de(n/60),o=de(s/60),r=de(o/24),a=de(r/365),h=45>n&&["s",n]||1===s&&["m"]||45>s&&["mm",s]||1===o&&["h"]||22>o&&["hh",o]||1===r&&["d"]||25>=r&&["dd",r]||45>=r&&["M"]||345>r&&["MM",de(r/30)]||1===a&&["y"]||["yy",a];return h[2]=e,h[3]=t>0,h[4]=i,K.apply({},h)}function Q(t,e,i){var n,s=i-e,o=i-t.day();return o>s&&(o-=7),s-7>o&&(o+=7),n=oe(t).add("d",o),{week:Math.ceil(n.dayOfYear()/7),year:n.year()}}function J(t,e,i,n,s){var o,r,a=q(t,0,1).getUTCDay();return i=null!=i?i:s,o=s-a+(a>n?7:0)-(s>a?7:0),r=7*(e-1)+(i-s)+o+1,{year:r>0?t:t-1,dayOfYear:r>0?r:S(t-1)+r}}function te(t){var e=t._i,i=t._f;return null===e?oe.invalid({nullInput:!0}):("string"==typeof e&&(t._i=e=N().preparse(e)),oe.isMoment(e)?(t=l(e),t._d=new Date(+e._d)):i?f(i)?B(t):W(t):V(t),new a(t))}function ee(t,e){oe.fn[t]=oe.fn[t+"s"]=function(t){var i=this._isUTC?"UTC":"";return null!=t?(this._d["set"+i+e](t),oe.updateOffset(this),this):this._d["get"+i+e]()}}function ie(t){oe.duration.fn[t]=function(){return this._data[t]}}function ne(t,e){oe.duration.fn["as"+t]=function(){return+this/e}}function se(t){var e=!1,i=oe;"undefined"==typeof ender&&(t?(he.moment=function(){return!e&&console&&console.warn&&(e=!0,console.warn("Accessing Moment through the global scope is deprecated, and will be removed in an upcoming release.")),i.apply(null,arguments)},d(he.moment,i)):he.moment=oe)}for(var oe,re,ae="2.5.1",he=this,de=Math.round,le=0,ce=1,ue=2,pe=3,fe=4,me=5,ge=6,ve={},ye={_isAMomentObject:null,_i:null,_f:null,_l:null,_strict:null,_isUTC:null,_offset:null,_pf:null,_lang:null},_e="undefined"!=typeof e&&e.exports&&"undefined"!=typeof t,we=/^\/?Date\((\-?\d+)/i,be=/(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,Se=/^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/,Ee=/(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|X|zz?|ZZ?|.)/g,Te=/(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g,xe=/\d\d?/,De=/\d{1,3}/,Me=/\d{1,4}/,Ce=/[+\-]?\d{1,6}/,Ie=/\d+/,Ne=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,Oe=/Z|[\+\-]\d\d:?\d\d/gi,Le=/T/i,ke=/[\+\-]?\d+(\.\d{1,3})?/,Pe=/\d/,Ae=/\d\d/,ze=/\d{3}/,Fe=/\d{4}/,Re=/[+-]?\d{6}/,Ye=/[+-]?\d+/,He=/^\s*(?:[+-]\d{6}|\d{4})-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,We="YYYY-MM-DDTHH:mm:ssZ",Ge=[["YYYYYY-MM-DD",/[+-]\d{6}-\d{2}-\d{2}/],["YYYY-MM-DD",/\d{4}-\d{2}-\d{2}/],["GGGG-[W]WW-E",/\d{4}-W\d{2}-\d/],["GGGG-[W]WW",/\d{4}-W\d{2}/],["YYYY-DDD",/\d{4}-\d{3}/]],je=[["HH:mm:ss.SSSS",/(T| )\d\d:\d\d:\d\d\.\d{1,3}/],["HH:mm:ss",/(T| )\d\d:\d\d:\d\d/],["HH:mm",/(T| )\d\d:\d\d/],["HH",/(T| )\d\d/]],Be=/([\+\-]|\d\d)/gi,Ue="Date|Hours|Minutes|Seconds|Milliseconds".split("|"),Ve={Milliseconds:1,Seconds:1e3,Minutes:6e4,Hours:36e5,Days:864e5,Months:2592e6,Years:31536e6},Xe={ms:"millisecond",s:"second",m:"minute",h:"hour",d:"day",D:"date",w:"week",W:"isoWeek",M:"month",y:"year",DDD:"dayOfYear",e:"weekday",E:"isoWeekday",gg:"weekYear",GG:"isoWeekYear"},qe={dayofyear:"dayOfYear",isoweekday:"isoWeekday",isoweek:"isoWeek",weekyear:"weekYear",isoweekyear:"isoWeekYear"},Ze={},Ke="DDD w W M D d".split(" "),$e="M D H h m s w W".split(" "),Qe={M:function(){return this.month()+1},MMM:function(t){return this.lang().monthsShort(this,t)},MMMM:function(t){return this.lang().months(this,t)},D:function(){return this.date()},DDD:function(){return this.dayOfYear()},d:function(){return this.day()},dd:function(t){return this.lang().weekdaysMin(this,t)},ddd:function(t){return this.lang().weekdaysShort(this,t)},dddd:function(t){return this.lang().weekdays(this,t)},w:function(){return this.week()},W:function(){return this.isoWeek()},YY:function(){return u(this.year()%100,2)},YYYY:function(){return u(this.year(),4)},YYYYY:function(){return u(this.year(),5)},YYYYYY:function(){var t=this.year(),e=t>=0?"+":"-";return e+u(Math.abs(t),6)},gg:function(){return u(this.weekYear()%100,2)},gggg:function(){return u(this.weekYear(),4)
+},ggggg:function(){return u(this.weekYear(),5)},GG:function(){return u(this.isoWeekYear()%100,2)},GGGG:function(){return u(this.isoWeekYear(),4)},GGGGG:function(){return u(this.isoWeekYear(),5)},e:function(){return this.weekday()},E:function(){return this.isoWeekday()},a:function(){return this.lang().meridiem(this.hours(),this.minutes(),!0)},A:function(){return this.lang().meridiem(this.hours(),this.minutes(),!1)},H:function(){return this.hours()},h:function(){return this.hours()%12||12},m:function(){return this.minutes()},s:function(){return this.seconds()},S:function(){return w(this.milliseconds()/100)},SS:function(){return u(w(this.milliseconds()/10),2)},SSS:function(){return u(this.milliseconds(),3)},SSSS:function(){return u(this.milliseconds(),3)},Z:function(){var t=-this.zone(),e="+";return 0>t&&(t=-t,e="-"),e+u(w(t/60),2)+":"+u(w(t)%60,2)},ZZ:function(){var t=-this.zone(),e="+";return 0>t&&(t=-t,e="-"),e+u(w(t/60),2)+u(w(t)%60,2)},z:function(){return this.zoneAbbr()},zz:function(){return this.zoneName()},X:function(){return this.unix()},Q:function(){return this.quarter()}},Je=["months","monthsShort","weekdays","weekdaysShort","weekdaysMin"];Ke.length;)re=Ke.pop(),Qe[re+"o"]=o(Qe[re],re);for(;$e.length;)re=$e.pop(),Qe[re+re]=s(Qe[re],2);for(Qe.DDDD=s(Qe.DDD,3),d(r.prototype,{set:function(t){var e,i;for(i in t)e=t[i],"function"==typeof e?this[i]=e:this["_"+i]=e},_months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),months:function(t){return this._months[t.month()]},_monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),monthsShort:function(t){return this._monthsShort[t.month()]},monthsParse:function(t){var e,i,n;for(this._monthsParse||(this._monthsParse=[]),e=0;12>e;e++)if(this._monthsParse[e]||(i=oe.utc([2e3,e]),n="^"+this.months(i,"")+"|^"+this.monthsShort(i,""),this._monthsParse[e]=new RegExp(n.replace(".",""),"i")),this._monthsParse[e].test(t))return e},_weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdays:function(t){return this._weekdays[t.day()]},_weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysShort:function(t){return this._weekdaysShort[t.day()]},_weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),weekdaysMin:function(t){return this._weekdaysMin[t.day()]},weekdaysParse:function(t){var e,i,n;for(this._weekdaysParse||(this._weekdaysParse=[]),e=0;7>e;e++)if(this._weekdaysParse[e]||(i=oe([2e3,1]).day(e),n="^"+this.weekdays(i,"")+"|^"+this.weekdaysShort(i,"")+"|^"+this.weekdaysMin(i,""),this._weekdaysParse[e]=new RegExp(n.replace(".",""),"i")),this._weekdaysParse[e].test(t))return e},_longDateFormat:{LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D YYYY",LLL:"MMMM D YYYY LT",LLLL:"dddd, MMMM D YYYY LT"},longDateFormat:function(t){var e=this._longDateFormat[t];return!e&&this._longDateFormat[t.toUpperCase()]&&(e=this._longDateFormat[t.toUpperCase()].replace(/MMMM|MM|DD|dddd/g,function(t){return t.slice(1)}),this._longDateFormat[t]=e),e},isPM:function(t){return"p"===(t+"").toLowerCase().charAt(0)},_meridiemParse:/[ap]\.?m?\.?/i,meridiem:function(t,e,i){return t>11?i?"pm":"PM":i?"am":"AM"},_calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},calendar:function(t,e){var i=this._calendar[t];return"function"==typeof i?i.apply(e):i},_relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},relativeTime:function(t,e,i,n){var s=this._relativeTime[i];return"function"==typeof s?s(t,e,i,n):s.replace(/%d/i,t)},pastFuture:function(t,e){var i=this._relativeTime[t>0?"future":"past"];return"function"==typeof i?i(e):i.replace(/%s/i,e)},ordinal:function(t){return this._ordinal.replace("%d",t)},_ordinal:"%d",preparse:function(t){return t},postformat:function(t){return t},week:function(t){return Q(t,this._week.dow,this._week.doy).week},_week:{dow:0,doy:6},_invalidDate:"Invalid date",invalidDate:function(){return this._invalidDate}}),oe=function(t,e,s,o){var r;return"boolean"==typeof s&&(o=s,s=i),r={},r._isAMomentObject=!0,r._i=t,r._f=e,r._l=s,r._strict=o,r._isUTC=!1,r._pf=n(),te(r)},oe.utc=function(t,e,s,o){var r;return"boolean"==typeof s&&(o=s,s=i),r={},r._isAMomentObject=!0,r._useUTC=!0,r._isUTC=!0,r._l=s,r._i=t,r._f=e,r._strict=o,r._pf=n(),te(r).utc()},oe.unix=function(t){return oe(1e3*t)},oe.duration=function(t,e){var i,n,s,o=t,r=null;return oe.isDuration(t)?o={ms:t._milliseconds,d:t._days,M:t._months}:"number"==typeof t?(o={},e?o[e]=t:o.milliseconds=t):(r=be.exec(t))?(i="-"===r[1]?-1:1,o={y:0,d:w(r[ue])*i,h:w(r[pe])*i,m:w(r[fe])*i,s:w(r[me])*i,ms:w(r[ge])*i}):(r=Se.exec(t))&&(i="-"===r[1]?-1:1,s=function(t){var e=t&&parseFloat(t.replace(",","."));return(isNaN(e)?0:e)*i},o={y:s(r[2]),M:s(r[3]),d:s(r[4]),h:s(r[5]),m:s(r[6]),s:s(r[7]),w:s(r[8])}),n=new h(o),oe.isDuration(t)&&t.hasOwnProperty("_lang")&&(n._lang=t._lang),n},oe.version=ae,oe.defaultFormat=We,oe.updateOffset=function(){},oe.lang=function(t,e){var i;return t?(e?C(D(t),e):null===e?(I(t),t="en"):ve[t]||N(t),i=oe.duration.fn._lang=oe.fn._lang=N(t),i._abbr):oe.fn._lang._abbr},oe.langData=function(t){return t&&t._lang&&t._lang._abbr&&(t=t._lang._abbr),N(t)},oe.isMoment=function(t){return t instanceof a||null!=t&&t.hasOwnProperty("_isAMomentObject")},oe.isDuration=function(t){return t instanceof h},re=Je.length-1;re>=0;--re)_(Je[re]);for(oe.normalizeUnits=function(t){return v(t)},oe.invalid=function(t){var e=oe.utc(0/0);return null!=t?d(e._pf,t):e._pf.userInvalidated=!0,e},oe.parseZone=function(t){return oe(t).parseZone()},d(oe.fn=a.prototype,{clone:function(){return oe(this)},valueOf:function(){return+this._d+6e4*(this._offset||0)},unix:function(){return Math.floor(+this/1e3)},toString:function(){return this.clone().lang("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},toDate:function(){return this._offset?new Date(+this):this._d},toISOString:function(){var t=oe(this).utc();return 00:!1},parsingFlags:function(){return d({},this._pf)},invalidAt:function(){return this._pf.overflow},utc:function(){return this.zone(0)},local:function(){return this.zone(0),this._isUTC=!1,this},format:function(t){var e=k(this,t||oe.defaultFormat);return this.lang().postformat(e)},add:function(t,e){var i;return i="string"==typeof t?oe.duration(+e,t):oe.duration(t,e),p(this,i,1),this},subtract:function(t,e){var i;return i="string"==typeof t?oe.duration(+e,t):oe.duration(t,e),p(this,i,-1),this},diff:function(t,e,i){var n,s,o=M(t,this),r=6e4*(this.zone()-o.zone());return e=v(e),"year"===e||"month"===e?(n=432e5*(this.daysInMonth()+o.daysInMonth()),s=12*(this.year()-o.year())+(this.month()-o.month()),s+=(this-oe(this).startOf("month")-(o-oe(o).startOf("month")))/n,s-=6e4*(this.zone()-oe(this).startOf("month").zone()-(o.zone()-oe(o).startOf("month").zone()))/n,"year"===e&&(s/=12)):(n=this-o,s="second"===e?n/1e3:"minute"===e?n/6e4:"hour"===e?n/36e5:"day"===e?(n-r)/864e5:"week"===e?(n-r)/6048e5:n),i?s:c(s)},from:function(t,e){return oe.duration(this.diff(t)).lang(this.lang()._abbr).humanize(!e)},fromNow:function(t){return this.from(oe(),t)},calendar:function(){var t=M(oe(),this).startOf("day"),e=this.diff(t,"days",!0),i=-6>e?"sameElse":-1>e?"lastWeek":0>e?"lastDay":1>e?"sameDay":2>e?"nextDay":7>e?"nextWeek":"sameElse";return this.format(this.lang().calendar(i,this))},isLeapYear:function(){return E(this.year())},isDST:function(){return this.zone()+oe(t).startOf(e)},isBefore:function(t,e){return e="undefined"!=typeof e?e:"millisecond",+this.clone().startOf(e)<+oe(t).startOf(e)},isSame:function(t,e){return e=e||"ms",+this.clone().startOf(e)===+M(t,this).startOf(e)},min:function(t){return t=oe.apply(null,arguments),this>t?this:t},max:function(t){return t=oe.apply(null,arguments),t>this?this:t},zone:function(t){var e=this._offset||0;return null==t?this._isUTC?e:this._d.getTimezoneOffset():("string"==typeof t&&(t=z(t)),Math.abs(t)<16&&(t=60*t),this._offset=t,this._isUTC=!0,e!==t&&p(this,oe.duration(e-t,"m"),1,!0),this)},zoneAbbr:function(){return this._isUTC?"UTC":""},zoneName:function(){return this._isUTC?"Coordinated Universal Time":""},parseZone:function(){return this._tzm?this.zone(this._tzm):"string"==typeof this._i&&this.zone(this._i),this},hasAlignedHourOffset:function(t){return t=t?oe(t).zone():0,(this.zone()-t)%60===0},daysInMonth:function(){return b(this.year(),this.month())},dayOfYear:function(t){var e=de((oe(this).startOf("day")-oe(this).startOf("year"))/864e5)+1;return null==t?e:this.add("d",t-e)},quarter:function(){return Math.ceil((this.month()+1)/3)},weekYear:function(t){var e=Q(this,this.lang()._week.dow,this.lang()._week.doy).year;return null==t?e:this.add("y",t-e)},isoWeekYear:function(t){var e=Q(this,1,4).year;return null==t?e:this.add("y",t-e)},week:function(t){var e=this.lang().week(this);return null==t?e:this.add("d",7*(t-e))},isoWeek:function(t){var e=Q(this,1,4).week;return null==t?e:this.add("d",7*(t-e))},weekday:function(t){var e=(this.day()+7-this.lang()._week.dow)%7;return null==t?e:this.add("d",t-e)},isoWeekday:function(t){return null==t?this.day()||7:this.day(this.day()%7?t:t-7)},get:function(t){return t=v(t),this[t]()},set:function(t,e){return t=v(t),"function"==typeof this[t]&&this[t](e),this},lang:function(t){return t===i?this._lang:(this._lang=N(t),this)}}),re=0;re-1?!1:"INPUT"==i||"SELECT"==i||"TEXTAREA"==i||e.contentEditable&&"true"==e.contentEditable}function o(t,e){return t.sort().join(",")===e.sort().join(",")}function r(t){t=t||{};var e,i=!1;for(e in M)t[e]?i=!0:M[e]=0;i||(I=!1)}function a(t,e,i,n,s){var r,a,h=[];if(!x[t])return[];for("keyup"==i&&u(t)&&(e=[t]),r=0;r95&&112>t||b.hasOwnProperty(t)&&(_[b[t]]=t)}return _}function m(t,e,i){return i||(i=f()[t]?"keydown":"keypress"),"keypress"==i&&e.length&&(i="keydown"),i}function g(t,e,i,s){M[t]=0,s||(s=m(e[0],[]));var o,a=function(){I=s,++M[t],p()},h=function(t){d(i,t),"keyup"!==s&&(C=n(t)),setTimeout(r,10)};for(o=0;o1)return g(t,d,e,i);for(h="+"===t?["+"]:t.split("+"),o=0;o":".","?":"/","|":"\\"},T={option:"alt",command:"meta","return":"enter",escape:"esc"},x={},D={},M={},C=!1,I=!1,N=1;20>N;++N)b[111+N]="f"+N;for(N=0;9>=N;++N)b[N+96]=N;i(document,"keypress",c),i(document,"keydown",c),i(document,"keyup",c);var O={bind:function(t,e,i){return y(t instanceof Array?t:[t],e,i),D[t+":"+i]=e,this},unbind:function(t,e){return D[t+":"+e]&&(delete D[t+":"+e],this.bind(t,function(){},e)),this},trigger:function(t,e){return D[t+":"+e](),this},reset:function(){return x={},D={},this}};e.exports=O},{}]},{},[1])(1)});
\ No newline at end of file
diff --git a/docs/dataset.html b/docs/dataset.html
index e6e8310b..cbb2ec8f 100644
--- a/docs/dataset.html
+++ b/docs/dataset.html
@@ -62,8 +62,8 @@ data.add([
]);
// subscribe to any change in the DataSet
-data.subscribe('*', function (event, params, senderId) {
- console.log('event', event, params);
+data.on('*', function (event, properties, senderId) {
+ console.log('event', event, properties);
});
// update an existing item
@@ -545,8 +545,8 @@ var items = data.get({
One can subscribe on changes in a DataSet.
- A subscription can be created using the method subscribe
,
- and removed with unsubscribe
.
+ A subscription can be created using the method on
,
+ and removed with off
.
@@ -554,8 +554,8 @@ var items = data.get({
var data = new vis.DataSet();
// subscribe to any change in the DataSet
-data.subscribe('*', function (event, params, senderId) {
- console.log('event:', event, 'params:', params, 'senderId:', senderId);
+data.on('*', function (event, properties, senderId) {
+ console.log('event:', event, 'properties:', properties, 'senderId:', senderId);
});
// add an item
@@ -565,14 +565,14 @@ data.remove(1); // triggers an 'remove' event
-Subscribe
+On
Subscribe to an event.
Syntax:
-DataSet.subscribe(event, callback)
+DataSet.on(event, callback)
Where:
@@ -587,17 +587,17 @@ Where:
-Unsubscribe
+Off
Unsubscribe from an event.
Syntax:
-DataSet.unsubscribe(event, callback)
+DataSet.off(event, callback)
Where event
and callback
correspond with the
-parameters used to subscribe to the event.
+parameters used to subscribe to the event.
Events
@@ -650,7 +650,7 @@ parameters used to subscribe to the event.
-function (event, params, senderId) {
+function (event, properties, senderId) {
// handle the event
});
@@ -674,13 +674,13 @@ function (event, params, senderId) {
- params |
+ properties |
Object | null |
- Optional parameters providing more information on the event.
+ Optional properties providing more information on the event.
In case of the events add ,
update , and remove ,
- params is always an object containing a property
+ properties is always an object containing a property
items, which contains an array with the ids of the affected
items.
|
diff --git a/docs/dataview.html b/docs/dataview.html
index 1698ffb1..a1fd350f 100644
--- a/docs/dataview.html
+++ b/docs/dataview.html
@@ -63,8 +63,8 @@ var view = new vis.DataView(data, {
});
// subscribe to any change in the DataView
-view.subscribe('*', function (event, params, senderId) {
- console.log('event', event, params);
+view.on('*', function (event, properties, senderId) {
+ console.log('event', event, properties);
});
// update an item in the data set
@@ -201,8 +201,8 @@ var view = new vis.DataView({
});
// subscribe to any change in the DataView
-view.subscribe('*', function (event, params, senderId) {
- console.log('event:', event, 'params:', params, 'senderId:', senderId);
+view.on('*', function (event, properties, senderId) {
+ console.log('event:', event, 'properties:', properties, 'senderId:', senderId);
});
// add, update, and remove data in the DataSet...
diff --git a/docs/graph.html b/docs/graph.html
index 8dfe2594..8545a958 100644
--- a/docs/graph.html
+++ b/docs/graph.html
@@ -24,7 +24,8 @@
The graph visualization works smooth on any modern browser for up to a
- few hundred nodes and edges.
+ few thousand nodes and edges. To handle a larger amount of nodes, Graph
+ has clustering support.
@@ -53,9 +54,12 @@
Nodes
Edges
Groups
- Clustering
+ Physics
+ Data_manipulation
+ Clustering
Navigation controls
Keyboard navigation
+ Hierarchical layout
Methods
@@ -288,6 +292,14 @@ var nodes = [
the same color schema.
+
+ allowedToMove |
+ Boolean |
+ true |
+ If allowedToMove is false, then the node will not move from its supplied position.
+ If only an x position has been supplied, it is only fixed in the x-direction.
+ The same holds for y. If both x and y have been defined, the node will not move. |
+
fontColor |
String |
@@ -324,8 +336,15 @@ var nodes = [
string |
no |
Url of an image. Only applicable when the shape of the node is
- image . |
-
+ image
.
+
+
+
+ level |
+ number |
+ -1 |
+ This level is used in the hierarchical layout. If this is not selected, the level does not do anything. |
+
radius |
@@ -529,13 +548,6 @@ var edges = [
type.
-
- length |
- number |
- no |
- The length of the edge in pixels. |
-
-
style |
string |
@@ -647,6 +659,25 @@ var options = {
Description |
+
+ physics |
+ Object |
+ none |
+
+ Configuration of the physics system governing the simulation of the nodes and edges.
+ Barnes-Hut nBody simulation is used by default. See section Physics for an overview of the available options.
+ |
+
+
+
+ dataManipulation |
+ Object |
+ none |
+
+ Settings for manipulating the Dataset. See section Data manipulation for an overview of the available options.
+ |
+
+
clustering |
Object |
@@ -710,6 +741,13 @@ var options = {
+
+ smoothCurves |
+ Boolean |
+ true |
+ If true, edges are drawn as smooth curves. This is more computationally intensive since the edge now is a quadratic Bezier curve with control points on both nodes and an invisible node in the center of the edge. This support node is also handed by the physics simulation. |
+
+
selectable |
Boolean |
@@ -806,6 +844,14 @@ var options = {
"#2B7CE9" |
Default border color of the node when selected. |
+
+ allowedToMove |
+ Boolean |
+ false |
+ If allowedToMove is false, then the node will not move from its supplied position.
+ If only an x position has been supplied, it is only fixed in the x-direction.
+ The same holds for y. If both x and y have been defined, the node will not move. |
+
fontColor |
@@ -838,6 +884,12 @@ var options = {
Default image url for the nodes. only applicable to shape image . |
+
+ level |
+ number |
+ -1 |
+ This level is used in the hierarchical layout. If this is not selected, the level does not do anything. |
+
widthMin |
Number |
@@ -964,12 +1016,6 @@ var options = {
Only applicable when the line style is dash-line
.
-
- length |
- Number |
- 100 |
- The default length of a edge. |
-
style |
String |
@@ -1122,6 +1168,234 @@ var nodes = [
+Physics
+
+ The original simulation method was based on particel physics with a repulsion field (potential) around each node,
+ and the edges were modelled as springs. The new system employed the Barnes-Hut gravitational simulation model. The edges are still modelled as springs.
+ To unify the physics system, the damping, repulsion distance and edge length have been combined in an physics option. To retain good behaviour, both the old repulsion model and the Barnes-Hut model have their own parameters.
+ If no options for the physics system are supplied, the Barnes-Hut method will be used with the default parameters.
+
+
+// These variables must be defined in an options object named physics.
+// If a variable is not supplied, the default value is used.
+var options = {
+ physics: {
+ barnesHut: {
+ enabled: true,
+ gravitationalConstant: -2000,
+ centralGravity: 0.1,
+ springLength: 100,
+ springConstant: 0.05,
+ damping: 0.09
+ },
+ repulsion: {
+ centralGravity: 0.1,
+ springLength: 50,
+ springConstant: 0.05,
+ nodeDistance: 100,
+ damping: 0.09
+ },
+ }
+
+barnesHut:
+
+
+ Name |
+ Type |
+ Default |
+ Description |
+
+
+ enabled |
+ Boolean |
+ true |
+ This switches the Barnes-Hut simulation on or off. If it is turned off, the old repulsion model is used. Barnes-Hut is generally faster and yields better results. |
+
+
+ gravitationalConstant |
+ Number |
+ -2000 |
+ This is the gravitational constand used to calculate the gravity forces. More information is available here. |
+
+
+ centralGravity |
+ Number |
+ 0.1 |
+ The central gravity is a force that pulls all nodes to the center. This ensures independent groups do not float apart. |
+
+
+ springLength |
+ Number |
+ 100 |
+ In the previous versions this was a property of the edges, called length. This is the length of the springs when they are at rest. During the simulation they will be streched by the gravitational fields.
+ To greatly reduce the edge length, the gravitationalConstant has to be reduced as well. |
+
+
+ springConstant |
+ Number |
+ 0.05 |
+ This is the spring constant used to calculate the spring forces based on Hooke′s Law. More information is available here. |
+
+
+ damping |
+ Number |
+ 0.09 |
+ This is the damping constant. It is used to dissipate energy from the system to have it settle in an equilibrium. More information is available here. |
+
+
+repulsion:
+
+
+ Name |
+ Type |
+ Default |
+ Description |
+
+
+
+ centralGravity |
+ Number |
+ 0.1 |
+ The central gravity is a force that pulls all nodes to the center. This ensures independent groups do not float apart. |
+
+
+ springLength |
+ Number |
+ 50 |
+ In the previous versions this was a property of the edges, called length. This is the length of the springs when they are at rest. During the simulation they will be streched by the gravitational fields.
+ To greatly reduce the edge length, the gravitationalConstant has to be reduced as well. |
+
+
+ nodeDistance |
+ Number |
+ 100 |
+ This parameter is used to define the distance of influence of the repulsion field of the nodes. Below half this distance, the repulsion is maximal and beyond twice this distance the repulsion is zero. |
+
+
+
+ springConstant |
+ Number |
+ 0.05 |
+ This is the spring constant used to calculate the spring forces based on Hooke′s Law. More information is available here. |
+
+
+ damping |
+ Number |
+ 0.09 |
+ This is the damping constant. It is used to dissipate energy from the system to have it settle in an equilibrium. More information is available here. |
+
+
+
+Data manipulation
+
+ By using the data manipulation feature of the graph you can dynamically create nodes, connect nodes with edges, edit nodes or delete nodes and edges.
+ The toolbar is fully HTML and CSS so the user can style this to their preference. To control the behaviour of the data manipulation, users can insert custom functions
+ into the data manipulation process. For example, an injected function can show an detailed pop-up when a user wants to add a node. In example 21,
+ two functions have been injected into the add and edit functionality. This is described in more detail in the next subsection.
+
+
+// These variables must be defined in an options object named dataManipulation.
+// If a variable is not supplied, the default value is used.
+var options = {
+ dataManipulation: {
+ enabled: false,
+ initiallyVisible: false
+ }
+}
+// OR to just load the module with default values:
+var options: {
+ dataManipulation: true
+}
+
+
+
+ Name |
+ Type |
+ Default |
+ Description |
+
+
+
+ enabled |
+ Boolean |
+ false |
+ Enabling or disabling of the data manipulation toolbar. If it is initially hidden, an edit button appears in the top left corner. |
+
+
+ initiallyVisible |
+ Boolean |
+ false |
+ Initially hide or show the data manipulation toolbar. |
+
+
+
+Data manipulation: custom functionality
+
+ Users can insert custom functions into the add node, edit node, connect nodes, and delete selected operations. This is done by supplying them in the options.
+ If the callback is NOT called, nothing happens. Example 21 has two working examples
+ for the add and edit functions. The data the user is supplied with in these functions has been described in the code below.
+ For the add data, you can add any and all options that are accepted for node creation as described above. The same goes for edit, however only the fields described
+ in the code below contain information on the selected node. The callback for connect accepts any options that are used for edge creation. Only the callback for delete selected
+ requires the same data structure that is supplied to the user.
+ If there is no injected function supplied for the edit operation, the button will not be shown in the toolbar.
+
+
+// If a variable is not supplied, the default value is used.
+var options: {
+ dataManipulation: true,
+ onAdd: function(data,callback) {
+ /** data = {id: random unique id,
+ * label: new,
+ * x: x position of click (canvas space),
+ * y: y position of click (canvas space),
+ * allowedToMove: true
+ * };
+ */
+ var newData = {..}; // alter the data as you want.
+ // all fields normally accepted by a node can be used.
+ callback(newData); // call the callback to add a node.
+ },
+ onEdit: function(data,callback) {
+ /** data = {id:...,
+ * label: ...,
+ * group: ...,
+ * shape: ...,
+ * color: {
+ * background:...,
+ * border:...,
+ * highlight: {
+ * background:...,
+ * border:...
+ * }
+ * }
+ * };
+ */
+ var newData = {..}; // alter the data as you want.
+ // all fields normally accepted by a node can be used.
+ callback(newData); // call the callback with the new data to edit the node.
+ }
+ onConnect: function(data,callback) {
+ // data = {from: nodeId1, to: nodeId2};
+ var newData = {..}; // check or alter data as you see fit.
+ callback(newData); // call the callback to connect the nodes.
+ },
+ onDelete: function(data,callback) {
+ // data = {nodes: [selectedNodeIds], edges: [selectedEdgeIds]};
+ var newData = {..}; // alter the data as you want.
+ // the same data structure is required.
+ callback(newData); // call the callback to delete the objects.
+ }
+};
+
+
+ Because the interface elements are CSS and HTML, the user will have to correct for size changes of the canvas. To facilitate this, a new event has been added called frameResize.
+ A function can be bound to this event. This function is supplied with the new widht and height of the canvas. The CSS can then be updated accordingly.
+ An code snippet from example 21 is shown below.
+
+
+graph.on("frameResize", function(params) {console.log(params.width,params.height)});
+
+
Clustering
The graph now supports dynamic clustering of nodes. This allows a user to view a very large dataset (> 50.000 nodes) without
@@ -1150,16 +1424,19 @@ var options = {
reduceToNodes:300,
chainThreshold: 0.4,
clusterEdgeThreshold: 20,
- sectorThreshold: 50,
+ sectorThreshold: 100,
screenSizeThreshold: 0.2,
fontSizeMultiplier: 4.0,
- forceAmplification: 0.6,
- distanceAmplification: 0.2,
- edgeGrowth: 11,
- nodeScaling: {width: 10,
- height: 10,
- radius: 10},
- activeAreaBoxSize: 100
+ maxFontSize: 1000,
+ forceAmplification: 0.1,
+ distanceAmplification: 0.1,
+ edgeGrowth: 20,
+ nodeScaling: {width: 1,
+ height: 1,
+ radius: 1},
+ maxNodeSizeIncrements: 600,
+ activeAreaBoxSize: 100,
+ clusterLevelDifference: 2
}
}
// OR to just load the module with default values:
@@ -1233,6 +1510,12 @@ var options: {
4.0 |
This parameter denotes the increase in fontSize of the cluster when a single node is added to it. |
+
+ maxFontSize |
+ Number |
+ 1000 |
+ This parameter denotes the largest allowed font size. If the font becomes too large, some browsers experience problems displaying this. |
+
forceAmplification |
Number |
@@ -1251,7 +1534,7 @@ var options: {
edgeGrowth |
Number |
- 11 |
+ 20 |
This factor determines the elongation of edges connected to a cluster. |
@@ -1272,13 +1555,29 @@ var options: {
10 |
This factor determines how much the radius of a cluster increases in pixels per added node. |
-
- activeAreaBoxSize |
+
+ maxNodeSizeIncrements |
+ Number |
+ 600 |
+ This limits the size clusters can grow to. The default value, 600, implies that if a cluster contains more than 600 nodes, it will no longer grow. |
+
+
+ activeAreaBoxSize |
+ Number |
+ 100 |
+ Imagine a square with an edge length of activeAreaBoxSize pixels around your cursor.
+ If a cluster is in this box as you zoom in, the cluster can be opened in a seperate sector.
+ This is regardless of the zoom level. |
+
+
+ clusterLevelDifference |
Number |
- 100 |
- Imagine a square with an edge length of activeAreaBoxSize pixels around your cursor.
- If a cluster is in this box as you zoom in, the cluster can be opened in a seperate sector.
- This is regardless of the zoom level. |
+ 2 |
+ At every clustering session, Graph will check if the difference between cluster levels is
+ acceptable. When a cluster is formed when zooming out, that is one cluster level.
+ If you zoom out further and it encompasses more nodes, that is another level. For example:
+ If the highest level of your graph at any given time is 3, nodes that have not clustered or
+ have clustered only once will join their neighbour with the lowest cluster level. |
@@ -1289,39 +1588,16 @@ var options: {
-// simple use of navigation controls
+// use of navigation controls
var options: {
navigation: true
}
-// advanced use of navigation controls
-var options: {
- navigation: {
- iconPath: '/path/to/navigation/icons/'
- }
-}
-
-
- Name |
- Type |
- Default |
- Description |
-
-
-
- iconPath |
- string |
- "/img" |
- The path to the icon images can be defined here. If custom icons are used, they have to have the same filename as the ones originally packaged with vis.js. |
-
-
-
Keyboard navigation
The graph can be navigated using shortcut keys.
- It can be configured with the following user-configurable settings.
The default state for the keyboard navigation is off. The predefined keys can be found in the example 20_navigation.html.
@@ -1372,6 +1648,59 @@ var options: {
+
+Hierarchical layout
+
+ The graph can be used to display nodes in a hierarchical way. This can be determined automatically, based on the amount of edges connected to each node, or defined by the user.
+ If the user wants to manually determine the hierarchy, each node has to be supplied with a level (from 0 being heighest to n). The automatic method
+ is shown in example 23 and the user-defined method is shown in example 24.
+ This layout method does not support smooth curves or clustering. It automatically turns these features off.
+
+
+
+// simple use of the hierarchical layout
+var options: {
+ hierarchicalLayout: true
+}
+
+// advanced configuration for keyboard controls
+var options: {
+ hierarchicalLayout: {
+ enabled:false,
+ levelSeparation: 150,
+ nodeSpacing: 100
+ }
+}
+
+
+
+
+ Name |
+ Type |
+ Default |
+ Description |
+
+
+
+ enabled |
+ Boolean |
+ false |
+ Enable or disable the hierarchical layout.
+ |
+
+
+ levelSeparation |
+ Number |
+ 150 |
+ This defines the space between levels (in the Y-direction). |
+
+
+ nodeSpacing |
+ Number |
+ 100 |
+ This defines the space between nodes in the same level (in the X-direction). |
+
+
Methods
Graph supports the following methods.
diff --git a/docs/timeline.html b/docs/timeline.html
index eaaf2339..6cfe8bf3 100644
--- a/docs/timeline.html
+++ b/docs/timeline.html
@@ -39,6 +39,7 @@
Configuration Options
Methods
Events
+ Editing Items
Styles
Data Policy
@@ -336,11 +337,19 @@ var options = {
autoResize |
boolean |
- false |
+ true |
If true, the Timeline will automatically detect when its
container is resized, and redraw itself accordingly. |
+
+ editable |
+ Boolean |
+ false |
+ If true, the items on the timeline can be dragged. Only applicable when option selectable is true . See also the callbacks onAdd , onUpdate , onMove , and onRemove , described in detail in section Editing Items.
+ |
+
+
end |
Date | Number | String |
@@ -412,6 +421,38 @@ var options = {
+
+ onAdd |
+ Function |
+ none |
+ Callback function triggered when an item is about to be added: when the user double taps an empty space in the Timeline. See section Editing Items for more information. Only applicable when both options selectable and editable are set true .
+ |
+
+
+
+ onUpdate |
+ Function |
+ none |
+ Callback function triggered when an item is about to be updated, when the user double taps an item in the Timeline. See section Editing Items for more information. Only applicable when both options selectable and editable are set true .
+ |
+
+
+
+ onMove |
+ Function |
+ none |
+ Callback function triggered when an item has been moved: after the user has dragged the item to an other position. See section Editing Items for more information. Only applicable when both options selectable and editable are set true .
+ |
+
+
+
+ onRemove |
+ Function |
+ none |
+ Callback function triggered when an item is about to be removed: when the user tapped the delete button on the top right of a selected item. See section Editing Items for more information. Only applicable when both options selectable and editable are set true .
+ |
+
+
order |
Function |
@@ -427,9 +468,7 @@ var options = {
orientation |
String |
'bottom' |
- Orientation of the timeline: 'top' or 'bottom' (default).
- If orientation is 'bottom', the time axis is drawn at the bottom,
- and if 'top', the axis is drawn on top. |
+ Orientation of the timeline: 'top' or 'bottom' (default). If orientation is 'bottom', the time axis is drawn at the bottom, and if 'top', the axis is drawn on top. |
@@ -440,6 +479,13 @@ var options = {
of item ranges. Must correspond with the css of item ranges.
+
+ selectable |
+ Boolean |
+ true |
+ If true, the items on the timeline can be selected. Multiple items can be selected by long pressing them, or by using ctrl+click or shift+click. The event select is fired each time the selection has changed (see section Events). |
+
+
showCurrentTime |
boolean |
@@ -451,9 +497,7 @@ var options = {
showCustomTime |
boolean |
false |
- Show a vertical bar displaying a custom time. This line can be dragged by the user. The custom time can be utilized to show a state in the past or in the future.
- |
+ Show a vertical bar displaying a custom time. This line can be dragged by the user. The custom time can be utilized to show a state in the past or in the future. When the custom time bar is dragged by the user, the event timechange is fired repeatedly. After the bar is dragged, the event timechanged is fired once. |
@@ -555,10 +599,16 @@ var options = {
getSelection() |
- ids |
+ Number[] |
Get an array with the ids of the currently selected items. |
+
+ getWindow() |
+ Object |
+ Get the current visible window. Returns an object with properties start: Date and end: Date . |
+
+
on(event, callback) |
none |
@@ -605,13 +655,19 @@ var options = {
+
+ setWindow(start, end) |
+ none |
+ Set the current visible window. The parameters start and end can be a Date , Number , or String . If the parameter value of start or end is null, the parameter will be left unchanged. |
+
+
Events
- Timeline fires events when changing the visible window by dragging, or when
- selecting items.
+ Timeline fires events when changing the visible window by dragging, when
+ selecting items, and when dragging the custom time bar.
@@ -674,7 +730,7 @@ timeline.off('select', onSelect);
rangechanged |
- Fired once after the user has dragging the timeline window.
+ | Fired once after the user has dragged the timeline window.
|
@@ -696,8 +752,75 @@ timeline.off('select', onSelect);
|
+
+ timechange |
+ Fired repeatedly when the user is dragging the custom time bar.
+ Only available when the custom time bar is enabled.
+ |
+
+
+ time (Date): the current time.
+
+ |
+
+
+
+ timechanged |
+ Fired once after the user has dragged the custom time bar.
+ Only available when the custom time bar is enabled.
+ |
+
+
+ time (Date): the current time.
+
+ |
+
+
+Editing Items
+
+ When the Timeline is configured to be editable (both options selectable
and editable
are true
), the user can move items by dragging them, can create a new item by double tapping on an empty space, can update an item by double tapping it, and can delete a selected item by clicking the delete button on the top right.
+
+
+
+ One can specify callback functions to validate changes made by the user. There are a number of callback functions for this purpose:
+
+
+
+ onAdd(item, callback)
Fired when a new item is about to be added. If not implemented, the item will be added with default text contents.
+ onUpdate(item, callback)
Fired when an item is about to be updated. This function typically has to show a dialog where the user change the item. If not implemented, nothing happens.
+ onMove(item, callback)
Fired when an item has been moved. If not implemented, the move action will be accepted.
+ onRemove(item, callback)
Fired when an item is about to be deleted. If not implemented, the item will be always removed.
+
+
+
+ Each of the callbacks is invoked with two arguments:
+
+
+ item
: the item being manipulated
+ callback
: a callback function which must be invoked to report back. The callback must be invoked as callback(item | null)
. Here, item
can contain changes to the passed item. When invoked as callback(null)
, the action will be cancelled.
+
+
+
+ Example code:
+
+
+var options = {
+ onUpdate: function (item, callback) {
+ item.content = prompt('Edit items text:', item.content);
+ if (item.content != null) {
+ callback(item); // send back adjusted item
+ }
+ else {
+ callback(null); // cancel updating the item
+ }
+ }
+}
+
+
+A full example is available here: 08_edit_items.html.
+
Styles
@@ -709,7 +832,7 @@ timeline.off('select', onSelect);
For example, to change the border and background color of all items, include the
following code inside the head of your html code or in a separate stylesheet.
<style>
- .graph .item {
+ .vis.timeline .item {
border-color: orange;
background-color: yellow;
}
diff --git a/download/vis.zip b/download/vis.zip
index be2fa4b4..536c8a42 100644
Binary files a/download/vis.zip and b/download/vis.zip differ
diff --git a/examples/graph/02_random_nodes.html b/examples/graph/02_random_nodes.html
index f5a05364..ea1202aa 100755
--- a/examples/graph/02_random_nodes.html
+++ b/examples/graph/02_random_nodes.html
@@ -57,6 +57,7 @@
j++;
}
+
var from = i;
var to = j;
edges.push({
@@ -87,7 +88,6 @@
*/
var options = {
edges: {
- length: 50
},
stabilize: false
};
@@ -102,7 +102,6 @@
-