update VIS to ver. 4.7.0

closes issue #1707
This commit is contained in:
vitalisator
2015-08-18 13:12:58 +02:00
parent 9dc7a89f99
commit e603881113
57 changed files with 3337 additions and 1917 deletions
+42
View File
@@ -1,6 +1,48 @@
# vis.js history
http://visjs.org
## 2015-07-27, version 4.7.0
### Timeline
- Fixed #192: Items keep their group offset while dragging items located in
multiple groups. Thanks @Fice.
- Fixed #1118: since v4.6.0, grid of time axis was wrongly positioned on some
scales.
### Network
- Added moveNode method.
- Added cubic Bezier curves.
## 2015-07-22, version 4.6.0
### Timeline
- Implemented #24: support for custom timezones, see configuration option `moment`.
### Graph2d
- Implemented #24: support for custom timezones, see configuration option `moment`.
### Network
- Fixed #1111, check if edges exist was not correct on update.
- Fixed #1112, network now works in firefox on unix again.
- Added #931, borderRadius in shapeProperties for the box shape.
- Added #936, useImageSize for images and circularImages
## 2015-07-20, version 4.5.1
### Network
- Fixed another clustering bug, phantom edges should be gone now.
- Fixed disabling hierarchical layout.
- Fixed delete button when using multiple selected items in manipulation system.
## 2015-07-17, version 4.5.0
### General
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "vis",
"version": "4.5.0",
"version": "4.7.0",
"main": ["dist/vis.min.js", "dist/vis.min.css"],
"description": "A dynamic, browser-based visualization library.",
"homepage": "http://visjs.org/",
+1726 -1210
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
File diff suppressed because one or more lines are too long
+20 -20
View File
File diff suppressed because one or more lines are too long
+37 -4
View File
@@ -145,7 +145,6 @@
<h2 id="Contents">Contents</h2>
<ul>
<li><a href="#Overview">Overview</a></li>
<li><a href="#Data_Format">Data Format</a>
<ul>
<li><a href="#items">Items</a></li>
@@ -161,8 +160,8 @@
<li><a href="#Methods">Methods</a></li>
<li><a href="#Events">Events</a></li>
<li><a href="#Localization">Localization</a></li>
<li><a href="#Time_zone">Time zone</a></li>
<li><a href="#Styles">Styles</a></li>
<li><a href="#Data_Policy">Data Policy</a></li>
</ul>
@@ -227,7 +226,7 @@
Options is a name-value map in the JSON format. The available options
are described in section <a href="#Configuration_Options">Configuration Options</a>.
Groups is a vis <code>DataSet</code> containing groups. The available options and the method of construction
are described in section <a href="#Group_Options">Data Format</a>.
are described in section <a href="#Data_Format">Data Format</a>.
</p>
<pre class="prettyprint lang-js options">var graph = new vis.Graph2d(container [, data] [, groups] [, options]);</pre>
For backwards compatibility, groups and options can be interchanged.
@@ -245,7 +244,7 @@
<h2 id="Data_Format">Data Format</h2>
<p>
Graph2d can load data from an <code>Array</code>, a <code>DataSet</code> or a <code>DataView</code>.
Graph2d can load data from an <code>Array</code>, a <code>DataSet</code> (offering 2 way data binding), or a <code>DataView</code> (offering one way data binding).
Objects are added to this DataSet by using the <code>add()</code> function.
Data points must have properties <code>x</code>, <code>y</code>, and <code>z</code>,
and can optionally have a property <code>style</code> and <code>filter</code>.
@@ -718,6 +717,14 @@ onRender: function(item, group, graph2d) {
<td>'top-right'</td>
<td>Determine the position of the legend coupled to the right axis. Options are 'top-left', 'top-right', 'bottom-left' or 'bottom-right'.</td>
</tr>
<tr>
<td>moment</td>
<td>function</td>
<td>vis.moment</td>
<td>A constructor for creating a moment.js Date. Allows for applying a custom time zone. See section <a href="#Time_zone">Time zone</a> for more information.</td>
</tr>
<tr>
<td class="greenField">sampling</td>
<td>Boolean</td>
@@ -1425,6 +1432,32 @@ Graph2d.off('rangechanged', onChange);
</tr>
</table>
<h2 id="Time_zone">Time zone</h2>
<p>
By default, the Timeline displays time in local time. To display a Timeline in an other time zone or in UTC, the date constructor can be overloaded via the configuration option <code>moment</code>, which by default is the constructor function of moment.js. More information about UTC with moment.js can be found in the docs: <a href="http://momentjs.com/docs/#/parsing/utc/">http://momentjs.com/docs/#/parsing/utc/</a>.
</p>
<p>
Examples:
</p>
<pre class="prettyprint lang-js">// display in UTC
var options = {
moment: function(date) {
return vis.moment(date).utc();
}
};
// display in UTC +08:00
var options = {
moment: function(date) {
return vis.moment(date).utcOffset('+08:00');
}
};
</pre>
<h2 id="Styles">Styles</h2>
<p>
All parts of the Graph2d have a class name and a default css style just like the Graph2d.
+1 -1
View File
@@ -199,7 +199,7 @@
<h2 id="Data_Format">Data Format</h2>
<p>
Graph3d can load data from an <code>Array</code>, a <code>DataSet</code> or a <code>DataView</code>.
Graph3d can load data from an <code>Array</code>, a <code>DataSet</code> (offering 2 way data binding), or a <code>DataView</code> (offering 1 way data binding).
JSON objects are added to this DataSet by using the <code>add()</code> function.
Data points must have properties <code>x</code>, <code>y</code>, and <code>z</code>,
and can optionally have a property <code>style</code> and <code>filter</code>.
+9 -1
View File
@@ -623,13 +623,21 @@ var options: {
<td>String</td>
<td><code>'dynamic'</code></td>
<td>Possible options: <code>'dynamic', 'continuous', 'discrete', 'diagonalCross', 'straightCross', 'horizontal',
'vertical', 'curvedCW', 'curvedCCW'</code>. Take a look at our example 26 to see what these look like
'vertical', 'curvedCW', 'curvedCCW', 'cubicBezier'</code>. Take a look at our example 26 to see what these look like
and pick the one that you like best!
<br><br>
When using dynamic, the edges will have an invisible support node guiding the shape. This node is part of the
physics simulation.
</td>
</tr>
<tr parent="smooth" class="hidden">
<td class="indent">smooth.forceDirection</td>
<td>String or Boolean</td>
<td><code>false</code></td>
<td>Accepted options: <code>['horizontal', 'vertical', 'none']</code>. This options is only used with the cubicBezier curves. When true, horizontal is chosen, when false,
the direction that is larger (x distance between nodes vs y distance between nodes) is used. If the x distance is larger, horizontal. This is ment to be used with hierarchical layouts.
</td>
</tr>
<tr parent="smooth" class="hidden">
<td class="indent">smooth.roundness</td>
<td>Number</td>
+15 -9
View File
@@ -227,33 +227,33 @@
<td>Generates an interactive option editor with filtering.</td>
</tr>
<tr>
<td href="./edges.html">edges</a></td>
<td><a href="./edges.html">edges</a></td>
<td>Handles the creation and deletion of edges and contains the global edge options and styles.</td>
</tr>
<tr>
<td href="./groups.html">groups</a></td>
<td><a href="./groups.html">groups</a></td>
<td>Contains the groups and some options on how to handle nodes with non-existing groups.</td>
</tr>
<tr>
<td href="./interaction.html">interaction</a></td>
<td><a href="./interaction.html">interaction</a></td>
<td>Used for all user interaction with the network. Handles mouse and touch events and selection as well as
the navigation buttons and the popups.
</td>
</tr>
<tr>
<td href="./layout.html">layout</a></td>
<td><a href="./layout.html">layout</a></td>
<td>Governs the initial and hierarchical positioning.</td>
</tr>
<tr>
<td href="./manipulation.html">manipulation</a></td>
<td><a href="./manipulation.html">manipulation</a></td>
<td>Supplies an API and optional GUI to alter the data in the network.</td>
</tr>
<tr>
<td href="./nodes.html">nodes</a></td>
<td><a href="./nodes.html">nodes</a></td>
<td>Handles the creation and deletion of nodes and contains the global node options and styles.</td>
</tr>
<tr>
<td href="./physics.html">physics</a></td>
<td><a href="./physics.html">physics</a></td>
<td>Does all the simulation moving the nodes and edges to their final positions, also governs
stabilization.
</td>
@@ -780,8 +780,14 @@ function releaseFunction (clusterPosition, containedNodesPositions) {
positions when using clusters since they cannot be correctly initialized from just the
positions.</b>
</td>
</tr>
<tr class="collapsible toggle" onclick="toggleTable('methodTable','moveNode', this);">
<td colspan="2"><span parent="moveNode" class="right-caret" id="method_moveNode"></span> moveNode(<code><i>nodeId, Number x, Number y</i></code>)</td>
</tr>
<tr class="hidden" parent="moveNode">
<td class="midMethods">Returns: none</td>
<td>You can use this to programatically move a node. <i>The supplied x and y positions have to be in canvas space!</i>
</td>
</tr>
<tr class="collapsible toggle" onclick="toggleTable('methodTable','getBoundingBox', this);">
<td colspan="2"><span parent="getBoundingBox" class="right-caret" id="method_getBoundingBox"></span> getBoundingBox(<code><i>String
+21 -1
View File
@@ -176,6 +176,10 @@ var options = {
y:5
},
shape: 'ellipse',
shapeProperties: {
borderDashes: false, // only for shapes with a border
borderRadius: 6 // only for box shape
}
size: 25,
title: undefined,
value: undefined,
@@ -619,7 +623,23 @@ mySize = minSize + diff * scale;
<td>Array or Boolean</td>
<td><code>false</code></td>
<td>This property applies to all shapes that have borders.
You set the dashes by supplying an Array. Array formart: [dash length, gap length].
You set the dashes by supplying an Array. Array formart: [dash length, gap length].
You can also use a Boolean, false is disable and true is default [5,15].
</td>
</tr>
<tr parent="shapeProperties" class="hidden">
<td class="indent">shapeProperties.borderRadius</td>
<td>Number</td>
<td><code>6</code></td>
<td>This property is used only for the <code>box</code> shape. It allows you to determine the roundness of the corners of the shape.
</td>
</tr>
<tr parent="shapeProperties" class="hidden">
<td class="indent">shapeProperties.useImageSize</td>
<td>Boolean</td>
<td><code>false</code></td>
<td>This property only applies to the <code>image</code> and <code>circularImage</code> shapes. When false, the size option is used, when true, the size of the image is used. <br><i><b>Important</b>:
if this is set to true, the image cannot be scaled with the value option!</i>
</td>
</tr>
<tr>
+49 -20
View File
@@ -108,8 +108,8 @@
<li><a href="#Editing_Items">Editing Items</a></li>
<li><a href="#Templates">Templates</a></li>
<li><a href="#Localization">Localization</a></li>
<li><a href="#Time_zone">Time zone</a></li>
<li><a href="#Styles">Styles</a></li>
<li><a href="#Data_Policy">Data Policy</a></li>
</ul>
<h2 id="Example">Example</h2>
@@ -209,19 +209,20 @@
<h3 id="items">Items</h3>
<p>
The Timeline uses regular Arrays and Objects as data format.
Data items can contain the properties <code>start</code>,
For items, the Timeline accepts an Array, a DataSet (offering 2 way data binding),
or a DataView (offering 1 way data binding).
Items are regular objects and can contain the properties <code>start</code>,
<code>end</code> (optional), <code>content</code>,
<code>group</code> (optional), <code>className</code> (optional),
<code>editable</code> (optional), and <code>style</code> (optional).
</p>
<p>
A table is constructed as:
A DataSet is constructed as:
</p>
<pre class="prettyprint lang-js">
var items = [
var items = new vis.DataSet([
{
start: new Date(2010, 7, 15),
end: new Date(2010, 8, 2), // end is optional
@@ -343,7 +344,9 @@ var items = [
<h3 id="groups">Groups</h3>
<p>
Like the items, groups are regular JavaScript Arrays and Objects.
For the items, groups can be an Array, a DataSet (offering 2 way data binding),
or a DataView (offering 1 way data binding).
Using groups, items can be grouped together.
Items are filtered per group, and displayed as
@@ -638,6 +641,13 @@ function (option, path) {
<td>A map with i18n locales. See section <a href="#Localization">Localization</a> for more information.</td>
</tr>
<tr>
<td>moment</td>
<td>function</td>
<td>vis.moment</td>
<td>A constructor for creating a moment.js Date. Allows for applying a custom time zone. See section <a href="#Time_zone">Time zone</a> for more information.</td>
</tr>
<tr class='toggle collapsible' onclick="toggleTable('optionTable','margin', this);">
<td><span parent="margin" class="right-caret"></span> margin</td>
<td>number or Object</td>
@@ -1120,9 +1130,9 @@ document.getElementById('myTimeline').onclick = function (event) {
<td>none</td>
<td>Set both groups and items at once. Both properties are optional. This is a convenience method for individually calling both <code>setItems(items)</code> and <code>setGroups(groups)</code>.
Both <code>items</code> and <code>groups</code> can be an Array with Objects,
a DataSet, or a DataView. For each of the groups, the items of the
timeline are filtered on the property <code>group</code>, which
must correspond with the id of the group.
a DataSet (offering 2 way data binding), or a DataView (offering 1 way data binding).
For each of the groups, the items of the timeline are filtered on the
property <code>group</code>, which must correspond with the id of the group.
</td>
</tr>
@@ -1131,9 +1141,9 @@ document.getElementById('myTimeline').onclick = function (event) {
<td>none</td>
<td>Set a data set with groups for the Timeline.
<code>groups</code> can be an Array with Objects,
a DataSet, or a DataView. For each of the groups, the items of the
timeline are filtered on the property <code>group</code>, which
must correspond with the id of the group.
a DataSet (offering 2 way data binding), or a DataView (offering 1 way data binding).
For each of the groups, the items of the timeline are filtered on the
property <code>group</code>, which must correspond with the id of the group.
</td>
</tr>
@@ -1142,7 +1152,7 @@ document.getElementById('myTimeline').onclick = function (event) {
<td>none</td>
<td>Set a data set with items for the Timeline.
<code>items</code> can be an Array with Objects,
a DataSet, or a DataView.
a DataSet (offering 2 way data binding), or a DataView (offering 1 way data binding).
</td>
</tr>
@@ -1540,6 +1550,32 @@ var options = {
</table>
<h2 id="Time_zone">Time zone</h2>
<p>
By default, the Timeline displays time in local time. To display a Timeline in an other time zone or in UTC, the date constructor can be overloaded via the configuration option <code>moment</code>, which by default is the constructor function of moment.js. More information about UTC with moment.js can be found in the docs: <a href="http://momentjs.com/docs/#/parsing/utc/">http://momentjs.com/docs/#/parsing/utc/</a>.
</p>
<p>
Examples:
</p>
<pre class="prettyprint lang-js">// display in UTC
var options = {
moment: function(date) {
return vis.moment(date).utc();
}
};
// display in UTC +08:00
var options = {
moment: function(date) {
return vis.moment(date).utcOffset('+08:00');
}
};
</pre>
<h2 id="Styles">Styles</h2>
<p>
All parts of the Timeline have a class name and a default css style.
@@ -1617,13 +1653,6 @@ var options = {
&lt;/style&gt;
</pre>
<h2 id="Data_Policy">Data Policy</h2>
<p>
All code and data is processed and rendered in the browser.
No data is sent to any server.
</p>
</div>
<!-- Bootstrap core JavaScript
@@ -33,37 +33,15 @@
<br /> <br />
</div>
<p>
Smooth curve type:
<select id="dropdownID" onchange="update();">
<option value="continuous" selected="selected">continuous</option>
<option value="discrete">discrete</option>
<option value="diagonalCross">diagonalCross</option>
<option value="straightCross">straightCross</option>
<option value="horizontal">horizontal</option>
<option value="vertical">vertical</option>
<option value="curvedCW">curvedCW</option>
<option value="curvedCCW">curvedCCW</option>
<option value="dynamic">dynamic</option>
<option value="none">none</option>
</select>
</p>
<p>
Roundness (0..1): <input type="range" min="0" max="1" value="0.5" step="0.05" style="width:200px" id="roundnessSlider" onchange="update();"> <input id="roundnessScreen" style="width:30px;" value="0.5"> <br>(0.5 is max roundness for continuous, 1.0 for the others)
</p>
<div id="mynetwork"></div>
<script type="text/javascript">
var dropdown = document.getElementById("dropdownID");
var roundnessSlider = document.getElementById("roundnessSlider");
var roundnessScreen = document.getElementById("roundnessScreen");
// create an array with nodes
var nodes = [
{id: 1, label: 'Fixed node', x:0, y:0, fixed:true},
{id: 2, label: 'Drag me', x:150, y:130, physics:false},
{id: 3, label: 'Obstacle', x:80, y:-80, fixed:true, mass:5}
{id: 3, label: 'Obstacle', x:80, y:-80, fixed:true, mass:10}
];
// create an array with edges
@@ -79,6 +57,12 @@
};
var options = {
physics:true,
configure:function (option, path) {
if (path.indexOf('smooth') !== -1 || option === 'smooth') {
return true;
}
return false;
},
edges: {
smooth: {
type: 'continuous'
@@ -87,33 +71,7 @@
};
var network = new vis.Network(container, data, options);
function update() {
var type = dropdown.value;
var options;
var roundness = parseFloat(roundnessSlider.value);
roundnessScreen.value = roundness;
if (type === 'none') {
options = {
edges: {
smooth: false
}
};
}
else {
options = {
edges: {
smooth: {
type: type,
roundness: roundness
}
}
};
}
network.setOptions(options);
}
update();
</script>
</body>
@@ -79,7 +79,11 @@
var options = {
edges: {
smooth: true
smooth: {
type:'cubicBezier',
forceDirection: (directionInput.value == "UD" || directionInput.value == "DU") ? 'vertical' : 'horizontal',
roundness: 0.4
}
},
layout: {
hierarchical:{
@@ -45,7 +45,8 @@
{id: 12, shape: 'circularImage', image: DIR + '12.png'},
{id: 13, shape: 'circularImage', image: DIR + '13.png'},
{id: 14, shape: 'circularImage', image: DIR + '14.png'},
{id: 15, shape: 'circularImage', image: DIR + 'missing.png', brokenImage: DIR + 'missingBrokenImage.png', label:"when images\nfail\nto load"}
{id: 15, shape: 'circularImage', image: DIR + 'missing.png', brokenImage: DIR + 'missingBrokenImage.png', label:"when images\nfail\nto load"},
{id: 16, shape: 'circularImage', image: DIR + 'anotherMissing.png', brokenImage: DIR + '9.png', label:"fallback image in action"}
];
// create connections between people
@@ -64,7 +65,8 @@
{from: 10, to: 11},
{from: 11, to: 12},
{from: 12, to: 13},
{from: 13, to: 14}
{from: 13, to: 14},
{from: 9, to: 16}
];
// create a network
@@ -30,7 +30,7 @@
{id: 7, font:{size:30}, size:40, label: 'square', shape: 'square', shapeProperties:{borderDashes:[5,5]}},
{id: 8, font:{size:30}, size:40, label: 'triangle',shape: 'triangle', shapeProperties:{borderDashes:[5,5]}},
{id: 9, font:{size:30}, size:40, label: 'triangleDown', shape: 'triangleDown', shapeProperties:{borderDashes:[5,5]}},
{id: 10, font:{size:30}, size:40, label: 'star', shape: 'star', shapeProperties:{borderDashes:[5,5]}},
{id: 10, font:{size:30}, size:40, label: 'star', shape: 'star', shapeProperties:{borderDashes:true}},
{id: 11, font:{size:30}, size:40, label: 'circularImage', shape: 'circularImage', image: '../img/indonesia/4.png', shapeProperties: {borderDashes:[15,5]}},
];
@@ -0,0 +1,80 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Timeline | Time zone</title>
<style type="text/css">
body, html {
font-family: sans-serif;
max-width: 800px;
}
</style>
<script src="../../../dist/vis.js"></script>
<link href="../../../dist/vis.css" rel="stylesheet" type="text/css" />
<script src="../../googleAnalytics.js"></script>
</head>
<body>
<h1>Time zone</h1>
<p>
The following demo shows how to display items in local time (default), in UTC, or for a specific time zone offset. By configuring your own <code>moment</code> constructor, you can display items in the time zone that you want. All timelines have the same start and end date.
</p>
<h2>Local time</h2>
<div id="local"></div>
<h2>UTC</h2>
<div id="utc"></div>
<h2>UTC +08:00</h2>
<div id="plus8"></div>
<script type="text/javascript">
// Create a DataSet (allows two way data-binding)
var today = vis.moment(vis.moment.utc().format('YYYY-MM-DDT00:00:00.000Z'));
var start = today.clone();
var end = today.clone().add(2, 'day');
var customTime = today.clone().add(28, 'hour');
var items = new vis.DataSet([
{id: 1, content: 'item 1', start: today.clone().add(8, 'hour')},
{id: 2, content: 'item 2', start: today.clone().add(16, 'hour')},
{id: 3, content: 'item 3', start: today.clone().add(32, 'hour')}
]);
// Create a timeline displaying in local time (default)
var timelineLocal = new vis.Timeline(document.getElementById('local'), items, {
editable: true,
start: start,
end: end
});
timelineLocal.addCustomTime(customTime);
// Create a timeline displaying in UTC
var timelineUTC = new vis.Timeline(document.getElementById('utc'), items, {
editable: true,
start: start,
end: end,
moment: function (date) {
return vis.moment(date).utc();
}
});
timelineUTC.addCustomTime(customTime);
// Create a timeline displaying in UTC +08:00
var timelinePlus8 = new vis.Timeline(document.getElementById('plus8'), items, {
editable: true,
start: start,
end: end,
moment: function (date) {
return vis.moment(date).utcOffset('+08:00');
}
});
timelinePlus8.addCustomTime(customTime);
</script>
</body>
</html>
+86 -66
View File
@@ -2,71 +2,91 @@
* @class Images
* This class loads images and keeps them stored.
*/
function Images(callback) {
this.images = {};
this.imageBroken = {};
this.callback = callback;
class Images{
constructor(callback){
this.images = {};
this.imageBroken = {};
this.callback = callback;
}
/**
* @param {string} url The Url to cache the image as
* @return {Image} imageToLoadBrokenUrlOn The image object
*/
_addImageToCache (url, imageToCache) {
// IE11 fix -- thanks dponch!
if (imageToCache.width === 0) {
document.body.appendChild(imageToCache);
imageToCache.width = imageToCache.offsetWidth;
imageToCache.height = imageToCache.offsetHeight;
document.body.removeChild(imageToCache);
}
this.images[url] = imageToCache;
}
/**
* @param {string} url The original Url that failed to load, if the broken image is successfully loaded it will be added to the cache using this Url as the key so that subsequent requests for this Url will return the broken image
* @param {string} brokenUrl Url the broken image to try and load
* @return {Image} imageToLoadBrokenUrlOn The image object
*/
_tryloadBrokenUrl (url, brokenUrl, imageToLoadBrokenUrlOn) {
//If any of the parameters aren't specified then exit the function because nothing constructive can be done
if (url === undefined || brokenUrl === undefined || imageToLoadBrokenUrlOn === undefined) return;
//Clear the old subscription to the error event and put a new in place that only handle errors in loading the brokenImageUrl
imageToLoadBrokenUrlOn.onerror = () => {
console.error("Could not load brokenImage:", brokenUrl);
//Add an empty image to the cache so that when subsequent load calls are made for the url we don't try load the image and broken image again
this._addImageToCache(url, new Image());
};
//Set the source of the image to the brokenUrl, this is actually what kicks off the loading of the broken image
imageToLoadBrokenUrlOn.src = brokenUrl;
}
/**
* @return {Image} imageToRedrawWith The images that will be passed to the callback when it is invoked
*/
_redrawWithImage (imageToRedrawWith) {
if (this.callback) {
this.callback(imageToRedrawWith);
}
}
/**
* @param {string} url Url of the image
* @param {string} brokenUrl Url of an image to use if the url image is not found
* @return {Image} img The image object
*/
load (url, brokenUrl, id) {
//Try and get the image from the cache, if successful then return the cached image
var cachedImage = this.images[url];
if (cachedImage) return cachedImage;
//Create a new image
var img = new Image();
//Subscribe to the event that is raised if the image loads successfully
img.onload = () => {
//Add the image to the cache and then request a redraw
this._addImageToCache(url, img);
this._redrawWithImage(img);
};
//Subscribe to the event that is raised if the image fails to load
img.onerror = () => {
console.error("Could not load image:", url);
//Try and load the image specified by the brokenUrl using
this._tryloadBrokenUrl(url, brokenUrl, img);
}
//Set the source of the image to the url, this is actuall what kicks off the loading of the image
img.src = url;
//Return the new image
return img;
}
}
/**
*
* @param {string} url Url of the image
* @param {string} url Url of an image to use if the url image is not found
* @return {Image} img The image object
*/
Images.prototype.load = function(url, brokenUrl, id) {
var img = this.images[url]; // make a pointer
if (img === undefined) {
// create the image
var me = this;
img = new Image();
img.onload = function () {
// IE11 fix -- thanks dponch!
if (this.width === 0) {
document.body.appendChild(this);
this.width = this.offsetWidth;
this.height = this.offsetHeight;
document.body.removeChild(this);
}
if (me.callback) {
me.images[url] = img;
me.callback(this);
}
};
img.onerror = function () {
if (brokenUrl === undefined) {
console.error("Could not load image:", url);
delete this.src;
if (me.callback) {
me.callback(this);
}
}
else {
if (me.imageBroken[id] && me.imageBroken[id][url] === true) {
console.error("Could not load brokenImage:", brokenUrl);
delete this.src;
if (me.callback) {
me.callback(this);
}
}
else {
console.error("Could not load image:", url);
this.src = brokenUrl;
if (me.imageBroken[id] === undefined) {
me.imageBroken[id] = {};
}
me.imageBroken[id][url] = true;
}
}
};
img.src = url;
}
return img;
};
module.exports = Images;
export default Images;
+3
View File
@@ -445,6 +445,7 @@ Network.prototype.editEdgeMode = function() {return this.manipulation.edi
Network.prototype.deleteSelected = function() {return this.manipulation.deleteSelected.apply(this.manipulation,arguments);};
Network.prototype.getPositions = function() {return this.nodesHandler.getPositions.apply(this.nodesHandler,arguments);};
Network.prototype.storePositions = function() {return this.nodesHandler.storePositions.apply(this.nodesHandler,arguments);};
Network.prototype.moveNode = function() {return this.nodesHandler.moveNode.apply(this.nodesHandler,arguments);};
Network.prototype.getBoundingBox = function() {return this.nodesHandler.getBoundingBox.apply(this.nodesHandler,arguments);};
Network.prototype.getConnectedNodes = function(objectId) {
if (this.body.nodes[objectId] !== undefined) {
@@ -493,4 +494,6 @@ Network.prototype.getOptionsFromConfigurator = function() {
return options;
};
module.exports = Network;
@@ -57,6 +57,7 @@ class CanvasRenderer {
});
this.body.emitter.on('destroy', () => {
this.renderRequests = 0;
this.allowRedraw = false;
this.renderingActive = false;
if (this.requiresTimeout === true) {
clearTimeout(this.renderTimer);
+23 -39
View File
@@ -380,37 +380,18 @@ class ClusterEngine {
if (this.body.edges[edgeId] !== undefined) {
let edge = this.body.edges[edgeId];
// if the edge is a clusterEdge, we delete it. The opening of the clusters will restore these edges when required.
if (edgeId.substr(0,12) === "clusterEdge:") {
// we only delete the cluster edge if there is another edge to the node that is not a cluster.
let target = edge.from.isCluster === true ? edge.toId : edge.fromId;
let deleteEdge = false;
// search the contained edges for an edge that has a link to the targetNode
for (let edgeId2 in childEdgesObj) {
if (childEdgesObj.hasOwnProperty(edgeId2)) {
if (this.body.edges[edgeId2] !== undefined && edgeId2 !== edgeId) {
let edge2 = this.body.edges[edgeId2];
if (edge2.fromId == target || edge2.toId == target) {
deleteEdge = true;
break;
}
}
}
}
// if we found the edge that will trigger the recreation of a new cluster edge on opening, we can delete this edge.
if (deleteEdge === true) {
edge.edgeType.cleanup();
// this removes the edge from node.edges, which is why edgeIds is formed
edge.disconnect();
delete childEdgesObj[edgeId];
delete this.body.edges[edgeId];
}
// if this is a cluster edge that is fully encompassed in the cluster, we want to delete it
// this check verifies that both of the connected nodes are in this cluster
if (edgeId.substr(0,12) === "clusterEdge:" && childNodesObj[edge.fromId] !== undefined && childNodesObj[edge.toId] !== undefined) {
edge.cleanup();
// this removes the edge from node.edges, which is why edgeIds is formed
edge.disconnect();
delete childEdgesObj[edgeId];
delete this.body.edges[edgeId];
}
else {
edge.togglePhysics(false);
edge.options.hidden = true;
edge.setOptions({physics:false, hidden:true});
//edge.options.hidden = true;
}
}
}
@@ -420,8 +401,7 @@ class ClusterEngine {
for (let nodeId in childNodesObj) {
if (childNodesObj.hasOwnProperty(nodeId)) {
this.clusteredNodes[nodeId] = {clusterId:clusterNodeProperties.id, node: this.body.nodes[nodeId]};
this.body.nodes[nodeId].togglePhysics(false);
this.body.nodes[nodeId].options.hidden = true;
this.body.nodes[nodeId].setOptions({hidden:true, physics:false});
}
}
@@ -544,8 +524,10 @@ class ClusterEngine {
containedNode.vx = clusterNode.vx;
containedNode.vy = clusterNode.vy;
containedNode.options.hidden = false;
containedNode.togglePhysics(true);
// we use these methods to avoid reinstantiating the shape, which happens with setOptions.
//containedNode.toggleHidden(false);
//containedNode.togglePhysics(true);
containedNode.setOptions({hidden:false, physics:true});
delete this.clusteredNodes[nodeId];
}
@@ -557,7 +539,7 @@ class ClusterEngine {
let edge = containedEdges[edgeId];
// if this edge was a temporary edge and it's connected nodes do not exist anymore, we remove it from the data
if (this.body.nodes[edge.fromId] === undefined || this.body.nodes[edge.toId] === undefined || edge.toId == clusterNodeId || edge.fromId == clusterNodeId) {
edge.edgeType.cleanup();
edge.cleanup();
// this removes the edge from node.edges, which is why edgeIds is formed
edge.disconnect();
delete this.body.edges[edgeId];
@@ -593,8 +575,9 @@ class ClusterEngine {
}
}
else {
edge.options.hidden = false;
edge.togglePhysics(true);
edge.setOptions({physics:true, hidden:false});
//edge.options.hidden = false;
//edge.togglePhysics(true);
}
}
}
@@ -610,13 +593,14 @@ class ClusterEngine {
// actually removing the edges
for (let i = 0; i < removeIds.length; i++) {
let edgeId = removeIds[i];
this.body.edges[edgeId].edgeType.cleanup();
this.body.edges[edgeId].cleanup();
// this removes the edge from node.edges, which is why edgeIds is formed
this.body.edges[edgeId].disconnect();
delete this.body.edges[edgeId];
}
// remove clusterNode
this.body.nodes[clusterNodeId].cleanup();
delete this.body.nodes[clusterNodeId];
if (refreshData === true) {
@@ -702,8 +686,8 @@ class ClusterEngine {
average = average / hubCounter;
averageSquared = averageSquared / hubCounter;
let letiance = averageSquared - Math.pow(average,2);
let standardDeviation = Math.sqrt(letiance);
let variance = averageSquared - Math.pow(average,2);
let standardDeviation = Math.sqrt(variance);
let hubThreshold = Math.floor(average + 2*standardDeviation);
+3 -2
View File
@@ -81,6 +81,7 @@ class EdgesHandler {
smooth: {
enabled: true,
type: "dynamic",
forceDirection:'none',
roundness: 0.5
},
title:undefined,
@@ -275,7 +276,7 @@ class EdgesHandler {
var id = ids[i];
var data = edgesData.get(id);
var edge = edges[id];
if (edge === null) {
if (edge !== undefined) {
// update edge
edge.disconnect();
dataChanged = edge.setOptions(data) || dataChanged; // if a support node is added, data can be changed.
@@ -309,7 +310,7 @@ class EdgesHandler {
var id = ids[i];
var edge = edges[id];
if (edge !== undefined) {
edge.edgeType.cleanup();
edge.cleanup();
edge.disconnect();
delete edges[id];
}
+5 -3
View File
@@ -48,7 +48,7 @@ class LayoutEngine {
if (this.options.hierarchical.enabled === true) {
if (prevHierarchicalState === true) {
// refresh the overridden options for nodes and edges.
this.body.emitter.emit('refresh');
this.body.emitter.emit('refresh', true);
}
// make sure the level seperation is the right way up
@@ -126,12 +126,14 @@ class LayoutEngine {
this.optionsBackup.edges = {
smooth: allOptions.edges.smooth.enabled === undefined ? true : allOptions.edges.smooth.enabled,
type:allOptions.edges.smooth.type === undefined ? 'dynamic' : allOptions.edges.smooth.type,
roundness: allOptions.edges.smooth.roundness === undefined ? 0.5 : allOptions.edges.smooth.roundness
roundness: allOptions.edges.smooth.roundness === undefined ? 0.5 : allOptions.edges.smooth.roundness,
forceDirection: allOptions.edges.smooth.forceDirection === undefined ? false : allOptions.edges.smooth.forceDirection
};
allOptions.edges.smooth = {
enabled: allOptions.edges.smooth.enabled === undefined ? true : allOptions.edges.smooth.enabled,
type:type,
roundness: allOptions.edges.smooth.roundness === undefined ? 0.5 : allOptions.edges.smooth.roundness
roundness: allOptions.edges.smooth.roundness === undefined ? 0.5 : allOptions.edges.smooth.roundness,
forceDirection: allOptions.edges.smooth.forceDirection === undefined ? false : allOptions.edges.smooth.forceDirection
}
}
}
@@ -192,7 +192,7 @@ class ManipulationSystem {
// remove buttons
if (selectedTotalCount !== 0) {
if (selectedNodeCount === 1 && this.options.deleteNode !== false) {
if (selectedNodeCount > 0 && this.options.deleteNode !== false) {
if (needSeperator === true) {
this._createSeperator(4);
}
+27 -4
View File
@@ -93,7 +93,9 @@ class NodesHandler {
},
shape: 'ellipse',
shapeProperties: {
borderDashes: false
borderDashes: false, // only for borders
borderRadius: 6, // only for box shape
useImageSize: false // only for image and circularImage shapes
},
size: 25,
title: undefined,
@@ -273,6 +275,7 @@ class NodesHandler {
for (let i = 0; i < ids.length; i++) {
let id = ids[i];
nodes[id].cleanup();
delete nodes[id];
}
@@ -290,7 +293,7 @@ class NodesHandler {
}
refresh() {
refresh(clearPositions = false) {
let nodes = this.body.nodes;
for (let nodeId in nodes) {
let node = undefined;
@@ -299,7 +302,10 @@ class NodesHandler {
}
let data = this.body.data.nodes._data[nodeId];
if (node !== undefined && data !== undefined) {
node.setOptions({ fixed: false, x:null, y:null });
if (clearPositions === true) {
node.setOptions({x:null, y:null});
}
node.setOptions({ fixed: false });
node.setOptions(data);
}
}
@@ -414,11 +420,28 @@ class NodesHandler {
}
}
else {
console.log("NodeId provided for getConnectedEdges does not exist. Provided: ", nodeId)
console.log("NodeId provided for getConnectedEdges does not exist. Provided: ", nodeId);
}
return edgeList;
}
/**
* Move a node.
* @param String nodeId
* @param Number x
* @param Number y
*/
moveNode(nodeId, x, y) {
if (this.body.nodes[nodeId] !== undefined) {
this.body.nodes[nodeId].x = Number(x);
this.body.nodes[nodeId].y = Number(y);
setTimeout(() => {this.body.emitter.emit("startSimulation")},0);
}
else {
console.log("Node id supplied to moveNode does not exist. Provided: ", nodeId);
}
}
}
export default NodesHandler;
+7 -7
View File
@@ -399,10 +399,10 @@ class PhysicsEngine {
* @private
*/
_performStep(nodeId,maxVelocity) {
var node = this.body.nodes[nodeId];
var timestep = this.options.timestep;
var forces = this.physicsBody.forces;
var velocities = this.physicsBody.velocities;
let node = this.body.nodes[nodeId];
let timestep = this.options.timestep;
let forces = this.physicsBody.forces;
let velocities = this.physicsBody.velocities;
// store the state so we can revert
this.previousStates[nodeId] = {x:node.x, y:node.y, vx:velocities[nodeId].x, vy:velocities[nodeId].y};
@@ -412,7 +412,7 @@ class PhysicsEngine {
let ax = (forces[nodeId].x - dx) / node.options.mass; // acceleration
velocities[nodeId].x += ax * timestep; // velocity
velocities[nodeId].x = (Math.abs(velocities[nodeId].x) > maxVelocity) ? ((velocities[nodeId].x > 0) ? maxVelocity : -maxVelocity) : velocities[nodeId].x;
node.x += velocities[nodeId].x * timestep; // position
node.x += velocities[nodeId].x * timestep; // position
}
else {
forces[nodeId].x = 0;
@@ -424,14 +424,14 @@ class PhysicsEngine {
let ay = (forces[nodeId].y - dy) / node.options.mass; // acceleration
velocities[nodeId].y += ay * timestep; // velocity
velocities[nodeId].y = (Math.abs(velocities[nodeId].y) > maxVelocity) ? ((velocities[nodeId].y > 0) ? maxVelocity : -maxVelocity) : velocities[nodeId].y;
node.y += velocities[nodeId].y * timestep; // position
node.y += velocities[nodeId].y * timestep; // position
}
else {
forces[nodeId].y = 0;
velocities[nodeId].y = 0;
}
var totalVelocity = Math.sqrt(Math.pow(velocities[nodeId].x,2) + Math.pow(velocities[nodeId].y,2));
let totalVelocity = Math.sqrt(Math.pow(velocities[nodeId].x,2) + Math.pow(velocities[nodeId].y,2));
return totalVelocity;
}
+19 -13
View File
@@ -1,6 +1,7 @@
var util = require('../../../util');
import Label from './shared/Label'
import CubicBezierEdge from './edges/CubicBezierEdge'
import BezierEdgeDynamic from './edges/BezierEdgeDynamic'
import BezierEdgeStatic from './edges/BezierEdgeStatic'
import StraightEdge from './edges/StraightEdge'
@@ -208,13 +209,15 @@ class Edge {
updateEdgeType() {
let dataChanged = false;
let changeInType = true;
let smooth = this.options.smooth;
if (this.edgeType !== undefined) {
if (this.edgeType instanceof BezierEdgeDynamic && this.options.smooth.enabled === true && this.options.smooth.type === 'dynamic') {changeInType = false;}
if (this.edgeType instanceof BezierEdgeStatic && this.options.smooth.enabled === true && this.options.smooth.type !== 'dynamic') {changeInType = false;}
if (this.edgeType instanceof StraightEdge && this.options.smooth.enabled === false) {changeInType = false;}
if (this.edgeType instanceof BezierEdgeDynamic && smooth.enabled === true && smooth.type === 'dynamic') {changeInType = false;}
if (this.edgeType instanceof CubicBezierEdge && smooth.enabled === true && smooth.type === 'cubicBezier') {changeInType = false;}
if (this.edgeType instanceof BezierEdgeStatic && smooth.enabled === true && smooth.type !== 'dynamic' && smooth.type !== 'cubicBezier') {changeInType = false;}
if (this.edgeType instanceof StraightEdge && smooth.enabled === false) {changeInType = false;}
if (changeInType === true) {
dataChanged = this.edgeType.cleanup();
dataChanged = this.cleanup();
}
}
@@ -224,6 +227,9 @@ class Edge {
dataChanged = true;
this.edgeType = new BezierEdgeDynamic(this.options, this.body, this.labelModule);
}
else if (this.options.smooth.type === 'cubicBezier') {
this.edgeType = new CubicBezierEdge(this.options, this.body, this.labelModule);
}
else {
this.edgeType = new BezierEdgeStatic(this.options, this.body, this.labelModule);
}
@@ -241,15 +247,6 @@ class Edge {
}
/**
* Enable or disable the physics.
* @param status
*/
togglePhysics(status) {
this.options.physics = status;
this.edgeType.togglePhysics(status);
}
/**
* Connect an edge to its nodes
*/
@@ -495,6 +492,15 @@ class Edge {
unselect() {
this.selected = false;
}
/**
* cleans all required things on delete
* @returns {*}
*/
cleanup() {
return this.edgeType.cleanup();
}
}
export default Edge;
+66 -61
View File
@@ -93,14 +93,6 @@ class Node {
}
}
/**
* Enable or disable the physics.
* @param status
*/
togglePhysics(status) {
this.options.physics = status;
}
/**
* Set or overwrite options for the node
@@ -108,6 +100,7 @@ class Node {
* @param {Object} constants and object with default, global options
*/
setOptions(options) {
let currentShape = this.options.shape;
if (!options) {
return;
}
@@ -154,12 +147,9 @@ class Node {
}
}
this.updateShape();
this.updateShape(currentShape);
this.updateLabelModule();
// reset the size of the node, this can be changed
this._reset();
if (options.hidden !== undefined || options.physics !== undefined) {
return true;
}
@@ -232,54 +222,63 @@ class Node {
}
}
updateShape() {
// choose draw method depending on the shape
switch (this.options.shape) {
case 'box':
this.shape = new Box(this.options, this.body, this.labelModule);
break;
case 'circle':
this.shape = new Circle(this.options, this.body, this.labelModule);
break;
case 'circularImage':
this.shape = new CircularImage(this.options, this.body, this.labelModule, this.imageObj);
break;
case 'database':
this.shape = new Database(this.options, this.body, this.labelModule);
break;
case 'diamond':
this.shape = new Diamond(this.options, this.body, this.labelModule);
break;
case 'dot':
this.shape = new Dot(this.options, this.body, this.labelModule);
break;
case 'ellipse':
this.shape = new Ellipse(this.options, this.body, this.labelModule);
break;
case 'icon':
this.shape = new Icon(this.options, this.body, this.labelModule);
break;
case 'image':
this.shape = new Image(this.options, this.body, this.labelModule, this.imageObj);
break;
case 'square':
this.shape = new Square(this.options, this.body, this.labelModule);
break;
case 'star':
this.shape = new Star(this.options, this.body, this.labelModule);
break;
case 'text':
this.shape = new Text(this.options, this.body, this.labelModule);
break;
case 'triangle':
this.shape = new Triangle(this.options, this.body, this.labelModule);
break;
case 'triangleDown':
this.shape = new TriangleDown(this.options, this.body, this.labelModule);
break;
default:
this.shape = new Ellipse(this.options, this.body, this.labelModule);
break;
updateShape(currentShape) {
if (currentShape === this.options.shape && this.shape) {
this.shape.setOptions(this.options);
}
else {
// clean up the shape if it is already made so the new shape can start clean.
if (this.shape) {
this.shape.cleanup();
}
// choose draw method depending on the shape
switch (this.options.shape) {
case 'box':
this.shape = new Box(this.options, this.body, this.labelModule);
break;
case 'circle':
this.shape = new Circle(this.options, this.body, this.labelModule);
break;
case 'circularImage':
this.shape = new CircularImage(this.options, this.body, this.labelModule, this.imageObj);
break;
case 'database':
this.shape = new Database(this.options, this.body, this.labelModule);
break;
case 'diamond':
this.shape = new Diamond(this.options, this.body, this.labelModule);
break;
case 'dot':
this.shape = new Dot(this.options, this.body, this.labelModule);
break;
case 'ellipse':
this.shape = new Ellipse(this.options, this.body, this.labelModule);
break;
case 'icon':
this.shape = new Icon(this.options, this.body, this.labelModule);
break;
case 'image':
this.shape = new Image(this.options, this.body, this.labelModule, this.imageObj);
break;
case 'square':
this.shape = new Square(this.options, this.body, this.labelModule);
break;
case 'star':
this.shape = new Star(this.options, this.body, this.labelModule);
break;
case 'text':
this.shape = new Text(this.options, this.body, this.labelModule);
break;
case 'triangle':
this.shape = new Triangle(this.options, this.body, this.labelModule);
break;
case 'triangleDown':
this.shape = new TriangleDown(this.options, this.body, this.labelModule);
break;
default:
this.shape = new Ellipse(this.options, this.body, this.labelModule);
break;
}
}
this._reset();
}
@@ -440,7 +439,13 @@ class Node {
);
}
/**
* clean all required things on delete.
* @returns {*}
*/
cleanup() {
return this.shape.cleanup();
}
}
export default Node;
@@ -10,6 +10,12 @@ class BezierEdgeDynamic extends BezierEdgeBase {
this.options = options;
this.id = this.options.id;
this.setupSupportNode();
// when we change the physics state of the edge, we reposition the support node.
if (this.options.physics !== options.physics) {
this.via.setOptions({physics: this.options.physics})
this.positionBezierNode();
}
this.connect();
}
@@ -30,6 +36,10 @@ class BezierEdgeDynamic extends BezierEdgeBase {
}
}
/**
* remove the support nodes
* @returns {boolean}
*/
cleanup() {
if (this.via !== undefined) {
delete this.body.nodes[this.via.id];
@@ -39,11 +49,6 @@ class BezierEdgeDynamic extends BezierEdgeBase {
return false;
}
togglePhysics(status) {
this.via.setOptions({physics:status});
this.positionBezierNode();
}
/**
* Bezier curves require an anchor point to calculate the smooth flow. These points are nodes. These nodes are invisible but
* are used for the force calculation.
@@ -0,0 +1,91 @@
import CubicBezierEdgeBase from './util/CubicBezierEdgeBase'
class CubicBezierEdge extends CubicBezierEdgeBase {
constructor(options, body, labelModule) {
super(options, body, labelModule);
}
/**
* Draw a line between two nodes
* @param {CanvasRenderingContext2D} ctx
* @private
*/
_line(ctx) {
// get the coordinates of the support points.
let [via1,via2] = this._getViaCoordinates();
let returnValue = [via1,via2];
// start drawing the line.
ctx.beginPath();
ctx.moveTo(this.from.x, this.from.y);
// fallback to normal straight edges
if (via1.x === undefined) {
ctx.lineTo(this.to.x, this.to.y);
returnValue = undefined;
}
else {
ctx.bezierCurveTo(via1.x, via1.y, via2.x, via2.y, this.to.x, this.to.y);
}
// draw shadow if enabled
this.enableShadow(ctx);
ctx.stroke();
this.disableShadow(ctx);
return returnValue;
}
_getViaCoordinates() {
let dx = this.from.x - this.to.x;
let dy = this.from.y - this.to.y;
let x1, y1, x2, y2;
let roundness = this.options.smooth.roundness;;
// horizontal if x > y or if direction is forced or if direction is horizontal
if ((Math.abs(dx) > Math.abs(dy) || this.options.smooth.forceDirection === true || this.options.smooth.forceDirection === 'horizontal') && this.options.smooth.forceDirection !== 'vertical') {
y1 = this.from.y;
y2 = this.to.y;
x1 = this.from.x - roundness * dx;
x2 = this.to.x + roundness * dx;
}
else {
y1 = this.from.y - roundness * dy;
y2 = this.to.y + roundness * dy;
x1 = this.from.x;
x2 = this.to.x;
}
return [{x: x1, y: y1},{x: x2, y: y2}];
}
_findBorderPosition(nearNode, ctx) {
return this._findBorderPositionBezier(nearNode, ctx);
}
_getDistanceToEdge(x1, y1, x2, y2, x3, y3, [via1, via2] = this._getViaCoordinates()) { // x3,y3 is the point
return this._getDistanceToBezierEdge(x1, y1, x2, y2, x3, y3, via1, via2);
}
/**
* Combined function of pointOnLine and pointOnBezier. This gives the coordinates of a point on the line at a certain percentage of the way
* @param percentage
* @param via
* @returns {{x: number, y: number}}
* @private
*/
getPoint(percentage, [via1, via2] = this._getViaCoordinates()) {
let t = percentage;
let vec = [];
vec[0] = Math.pow(1 - t, 3);
vec[1] = 3 * t * Math.pow(1 - t, 2);
vec[2] = 3 * Math.pow(t,2) * (1 - t);
vec[3] = Math.pow(t, 3);
let x = vec[0] * this.from.x + vec[1] * via1.x + vec[2] * via2.x + vec[3] * this.to.x;
let y = vec[0] * this.from.y + vec[1] * via1.y + vec[2] * via2.y + vec[3] * this.to.y;
return {x: x, y: y};
}
}
export default CubicBezierEdge;
@@ -73,18 +73,15 @@ class BezierEdgeBase extends EdgeBase {
* Calculate the distance between a point (x3,y3) and a line segment from
* (x1,y1) to (x2,y2).
* http://stackoverflow.com/questions/849211/shortest-distancae-between-a-point-and-a-line-segment
* @param {number} x1
* @param {number} y1
* @param {number} x2
* @param {number} y2
* @param {number} x3
* @param {number} y3
* @param {number} x1 from x
* @param {number} y1 from y
* @param {number} x2 to x
* @param {number} y2 to y
* @param {number} x3 point to check x
* @param {number} y3 point to check y
* @private
*/
_getDistanceToBezierEdge(x1, y1, x2, y2, x3, y3, via) { // x3,y3 is the point
let xVia, yVia;
xVia = via.x;
yVia = via.y;
let minDistance = 1e9;
let distance;
let i, t, x, y;
@@ -92,8 +89,8 @@ class BezierEdgeBase extends EdgeBase {
let lastY = y1;
for (i = 1; i < 10; i++) {
t = 0.1 * i;
x = Math.pow(1 - t, 2) * x1 + (2 * t * (1 - t)) * xVia + Math.pow(t, 2) * x2;
y = Math.pow(1 - t, 2) * y1 + (2 * t * (1 - t)) * yVia + Math.pow(t, 2) * y2;
x = Math.pow(1 - t, 2) * x1 + (2 * t * (1 - t)) * via.x + Math.pow(t, 2) * x2;
y = Math.pow(1 - t, 2) * y1 + (2 * t * (1 - t)) * via.y + Math.pow(t, 2) * y2;
if (i > 0) {
distance = this._getDistanceToLine(lastX, lastY, x, y, x3, y3);
minDistance = distance < minDistance ? distance : minDistance;
@@ -0,0 +1,48 @@
import BezierEdgeBase from './BezierEdgeBase'
class CubicBezierEdgeBase extends BezierEdgeBase {
constructor(options, body, labelModule) {
super(options, body, labelModule);
}
/**
* Calculate the distance between a point (x3,y3) and a line segment from
* (x1,y1) to (x2,y2).
* http://stackoverflow.com/questions/849211/shortest-distancae-between-a-point-and-a-line-segment
* https://en.wikipedia.org/wiki/B%C3%A9zier_curve
* @param {number} x1 from x
* @param {number} y1 from y
* @param {number} x2 to x
* @param {number} y2 to y
* @param {number} x3 point to check x
* @param {number} y3 point to check y
* @private
*/
_getDistanceToBezierEdge(x1, y1, x2, y2, x3, y3, via1, via2) { // x3,y3 is the point
let minDistance = 1e9;
let distance;
let i, t, x, y;
let lastX = x1;
let lastY = y1;
let vec = [0,0,0,0]
for (i = 1; i < 10; i++) {
t = 0.1 * i;
vec[0] = Math.pow(1 - t, 3);
vec[1] = 3 * t * Math.pow(1 - t, 2);
vec[2] = 3 * Math.pow(t,2) * (1 - t);
vec[3] = Math.pow(t, 3);
x = vec[0] * x1 + vec[1] * via1.x + vec[2] * via2.x + vec[3] * x2;
y = vec[0] * y1 + vec[1] * via1.y + vec[2] * via2.y + vec[3] * y2;
if (i > 0) {
distance = this._getDistanceToLine(lastX, lastY, x, y, x3, y3);
minDistance = distance < minDistance ? distance : minDistance;
}
lastX = x;
lastY = y;
}
return minDistance;
}
}
export default CubicBezierEdgeBase;
@@ -24,12 +24,6 @@ class EdgeBase {
this.id = this.options.id;
}
/**
* overloadable if the shape has to toggle the via node to disabled
* @param status
*/
togglePhysics(status) {}
/**
* Redraw a edge as a line
* Draw this edge in the given canvas
@@ -97,8 +91,6 @@ class EdgeBase {
ctx.restore();
}
else { // unsupporting smooth lines
if (this.from != this.to) {
// draw line
ctx.dashedLine(this.from.x, this.from.y, this.to.x, this.to.y, pattern);
@@ -32,21 +32,24 @@ class Box extends NodeBase {
ctx.fillStyle = selected ? this.options.color.highlight.background : hover ? this.options.color.hover.background : this.options.color.background;
let borderRadius = 6;
let borderRadius = this.options.shapeProperties.borderRadius; // only effective for box
ctx.roundRect(this.left, this.top, this.width, this.height, borderRadius);
//draw dashed border if enabled
this.enableBorderDashes(ctx);
// draw shadow if enabled
this.enableShadow(ctx);
// draw the background
ctx.fill();
//disable dashed border for other elements
this.disableBorderDashes(ctx);
// disable shadows for other elements.
this.disableShadow(ctx);
//draw dashed border if enabled, save and restore is required for firefox not to crash on unix.
ctx.save();
this.enableBorderDashes(ctx);
//draw the border
ctx.stroke();
//disable dashed border for other elements
this.disableBorderDashes(ctx);
ctx.restore();
this.updateBoundingBox(x,y);
this.labelModule.draw(ctx, x, y, selected);
@@ -38,15 +38,16 @@ class CircularImage extends CircleImageBase {
let size = Math.min(0.5*this.height, 0.5*this.width);
// draw the backgroun circle. IMPORTANT: the stroke in this method is used by the clip method below.
this._drawRawCircle(ctx, x, y, selected, hover, size);
// now we draw in the cicle, we save so we can revert the clip operation after drawing.
ctx.save();
ctx.circle(x, y, size);
ctx.stroke();
// clip is used to use the stroke in drawRawCircle as an area that we can draw in.
ctx.clip();
// draw the image
this._drawImageAtPosition(ctx);
// restore so we can again draw on the full canvas
ctx.restore();
this._drawImageLabel(ctx, x, y, selected);
@@ -34,21 +34,23 @@ class Database extends NodeBase {
ctx.fillStyle = selected ? this.options.color.highlight.background : hover ? this.options.color.hover.background : this.options.color.background;
ctx.database(x - this.width / 2, y - this.height * 0.5, this.width, this.height);
//draw dashed border if enabled
this.enableBorderDashes(ctx);
// draw shadow if enabled
this.enableShadow(ctx);
// draw the background
ctx.fill();
//disable dashed border for other elements
this.disableBorderDashes(ctx);
// disable shadows for other elements.
this.disableShadow(ctx);
//draw dashed border if enabled, save and restore is required for firefox not to crash on unix.
ctx.save();
this.enableBorderDashes(ctx);
//draw the border
ctx.stroke();
//disable dashed border for other elements
this.disableBorderDashes(ctx);
ctx.restore();
this.updateBoundingBox(x,y,ctx,selected);
this.labelModule.draw(ctx, x, y, selected);
}
@@ -37,18 +37,21 @@ class Ellipse extends NodeBase {
ctx.fillStyle = selected ? this.options.color.highlight.background : hover ? this.options.color.hover.background : this.options.color.background;
ctx.ellipse(this.left, this.top, this.width, this.height);
//draw dashed border if enabled
this.enableBorderDashes(ctx);
// draw shadow if enabled
this.enableShadow(ctx);
// draw the background
ctx.fill();
//disable dashed border for other elements
this.disableBorderDashes(ctx);
// disable shadows for other elements.
this.disableShadow(ctx);
//draw dashed border if enabled, save and restore is required for firefox not to crash on unix.
ctx.save();
this.enableBorderDashes(ctx);
//draw the border
ctx.stroke();
//disable dashed border for other elements
this.disableBorderDashes(ctx);
ctx.restore();
this.updateBoundingBox(x, y, ctx, selected);
this.labelModule.draw(ctx, x, y, selected);
@@ -61,7 +61,6 @@ class Icon extends NodeBase {
ctx.textAlign = "center";
ctx.textBaseline = "middle";
// draw shadow if enabled
this.enableShadow(ctx);
ctx.fillText(this.options.icon.code, x, y);
@@ -29,20 +29,27 @@ class CircleImageBase extends NodeBase {
width = 0;
height = 0;
}
if (this.imageObj.width > this.imageObj.height) {
ratio = this.imageObj.width / this.imageObj.height;
width = this.options.size * 2 * ratio || this.imageObj.width;
height = this.options.size * 2 || this.imageObj.height;
}
else {
if (this.imageObj.width && this.imageObj.height) { // not undefined or 0
ratio = this.imageObj.height / this.imageObj.width;
if (this.options.shapeProperties.useImageSize === false) {
if (this.imageObj.width > this.imageObj.height) {
ratio = this.imageObj.width / this.imageObj.height;
width = this.options.size * 2 * ratio || this.imageObj.width;
height = this.options.size * 2 || this.imageObj.height;
}
else {
ratio = 1;
if (this.imageObj.width && this.imageObj.height) { // not undefined or 0
ratio = this.imageObj.height / this.imageObj.width;
}
else {
ratio = 1;
}
width = this.options.size * 2;
height = this.options.size * 2 * ratio;
}
width = this.options.size * 2 || this.imageObj.width;
height = this.options.size * 2 * ratio || this.imageObj.height;
}
else {
// when not using the size property, we use the image size
width = this.imageObj.width;
height = this.imageObj.height;
}
this.width = width;
this.height = height;
@@ -63,18 +70,21 @@ class CircleImageBase extends NodeBase {
ctx.fillStyle = selected ? this.options.color.highlight.background : hover ? this.options.color.hover.background : this.options.color.background;
ctx.circle(x, y, size);
//draw dashed border if enabled
this.enableBorderDashes(ctx);
// draw shadow if enabled
this.enableShadow(ctx);
// draw the background
ctx.fill();
//disable dashed border for other elements
this.disableBorderDashes(ctx);
// disable shadows for other elements.
this.disableShadow(ctx);
//draw dashed border if enabled, save and restore is required for firefox not to crash on unix.
ctx.save();
this.enableBorderDashes(ctx);
//draw the border
ctx.stroke();
//disable dashed border for other elements
this.disableBorderDashes(ctx);
ctx.restore();
}
_drawImageAtPosition(ctx) {
@@ -84,6 +94,8 @@ class CircleImageBase extends NodeBase {
// draw shadow if enabled
this.enableShadow(ctx);
// draw image
ctx.drawImage(this.imageObj, this.left, this.top, this.width, this.height);
// disable shadows for other elements.
@@ -42,15 +42,34 @@ class NodeBase {
enableBorderDashes(ctx) {
if (this.options.shapeProperties.borderDashes !== false) {
ctx.setLineDash(this.options.shapeProperties.borderDashes);
if (ctx.setLineDash !== undefined) {
let dashes = this.options.shapeProperties.borderDashes;
if (dashes === true) {
dashes = [5,15]
}
ctx.setLineDash(dashes);
}
else {
console.warn("setLineDash is not supported in this browser. The dashed borders cannot be used.");
this.options.shapeProperties.borderDashes = false;
}
}
}
disableBorderDashes(ctx) {
if (this.options.shapeProperties.borderDashes == false) {
ctx.setLineDash([0]);
if (this.options.shapeProperties.borderDashes !== false) {
if (ctx.setLineDash !== undefined) {
ctx.setLineDash([0]);
}
else {
console.warn("setLineDash is not supported in this browser. The dashed borders cannot be used.");
this.options.shapeProperties.borderDashes = false;
}
}
}
// possible cleanup for use in shapes
cleanup() {}
}
export default NodeBase;
@@ -30,19 +30,21 @@ class ShapeBase extends NodeBase {
ctx.fillStyle = selected ? this.options.color.highlight.background : hover ? this.options.color.hover.background : this.options.color.background;
ctx[shape](x, y, this.options.size);
//draw dashed border if enabled
this.enableBorderDashes(ctx);
// draw shadow if enabled
this.enableShadow(ctx);
// draw the background
ctx.fill();
//disable dashed border for other elements
this.disableBorderDashes(ctx);
// disable shadows for other elements.
this.disableShadow(ctx);
//draw dashed border if enabled, save and restore is required for firefox not to crash on unix.
ctx.save();
this.enableBorderDashes(ctx);
//draw the border
ctx.stroke();
//disable dashed border for other elements
this.disableBorderDashes(ctx);
ctx.restore();
if (this.options.label !== undefined) {
let yLabel = y + 0.5 * this.height + 3; // the + 3 is to offset it a bit below the node.
+9 -3
View File
@@ -79,8 +79,9 @@ let allOptions = {
},
smooth: {
enabled: { boolean },
type: { string: ['dynamic', 'continuous', 'discrete', 'diagonalCross', 'straightCross', 'horizontal', 'vertical', 'curvedCW', 'curvedCCW'] },
type: { string: ['dynamic', 'continuous', 'discrete', 'diagonalCross', 'straightCross', 'horizontal', 'vertical', 'curvedCW', 'curvedCCW', 'cubicBezier'] },
roundness: { number },
forceDirection: { string: ['horizontal', 'vertical', 'none'], boolean },
__type__: { object, boolean }
},
title: { string, 'undefined': 'undefined' },
@@ -210,6 +211,8 @@ let allOptions = {
shape: { string: ['ellipse', 'circle', 'database', 'box', 'text', 'image', 'circularImage', 'diamond', 'dot', 'star', 'triangle', 'triangleDown', 'square', 'icon'] },
shapeProperties: {
borderDashes: { boolean, array },
borderRadius: { number },
useImageSize: { boolean },
__type__: { object }
},
size: { number },
@@ -345,7 +348,9 @@ let configureOptions = {
},
shape: ['ellipse', 'box', 'circle', 'database', 'diamond', 'dot', 'square', 'star', 'text', 'triangle', 'triangleDown'],
shapeProperties: {
borderDashes: false
borderDashes: false,
borderRadius: [6, 0, 20, 1],
useImageSize: false
},
size: [25, 0, 200, 1]
},
@@ -397,7 +402,8 @@ let configureOptions = {
},
smooth: {
enabled: true,
type: ['dynamic', 'continuous', 'discrete', 'diagonalCross', 'straightCross', 'horizontal', 'vertical', 'curvedCW', 'curvedCCW'],
type: ['dynamic', 'continuous', 'discrete', 'diagonalCross', 'straightCross', 'horizontal', 'vertical', 'curvedCW', 'curvedCCW', 'cubicBezier'],
forceDirection: ['horizontal', 'vertical', 'none'],
roundness: [0.5, 0, 1, 0.05]
},
width: [1, 0, 30, 1]
+9 -5
View File
@@ -213,7 +213,11 @@ Core.prototype._create = function (container) {
Core.prototype.setOptions = function (options) {
if (options) {
// copy the known options
var fields = ['width', 'height', 'minHeight', 'maxHeight', 'autoResize', 'start', 'end', 'clickToUse', 'dataAttributes', 'hiddenDates'];
var fields = [
'width', 'height', 'minHeight', 'maxHeight', 'autoResize',
'start', 'end', 'clickToUse', 'dataAttributes', 'hiddenDates',
'locale', 'locales', 'moment'
];
util.selectiveExtend(fields, this.options, options);
if ('orientation' in options) {
@@ -263,7 +267,7 @@ Core.prototype.setOptions = function (options) {
}
if ('hiddenDates' in this.options) {
DateUtil.convertHiddenOptions(this.body, this.options.hiddenDates);
DateUtil.convertHiddenOptions(this.options.moment, this.body, this.options.hiddenDates);
}
if ('clickToUse' in options) {
@@ -428,10 +432,10 @@ Core.prototype.addCustomTime = function (time, id) {
throw new Error('A custom time with id ' + JSON.stringify(id) + ' already exists');
}
var customTime = new CustomTime(this.body, {
var customTime = new CustomTime(this.body, util.extend({}, this.options, {
time : timestamp,
id : id
});
}));
this.customTimes.push(customTime);
this.components.push(customTime);
@@ -595,7 +599,7 @@ Core.prototype._redraw = function() {
if (!dom) return; // when destroyed
DateUtil.updateHiddenDates(this.body, this.options.hiddenDates);
DateUtil.updateHiddenDates(this.options.moment, this.body, this.options.hiddenDates);
// update class names
if (options.orientation == 'top') {
+21 -18
View File
@@ -1,12 +1,12 @@
var moment = require('../module/moment');
/**
* used in Core to convert the options into a volatile variable
*
* @param Core
* @param {function} moment
* @param {Object} body
* @param {Array} hiddenDates
*/
exports.convertHiddenOptions = function(body, hiddenDates) {
exports.convertHiddenOptions = function(moment, body, hiddenDates) {
body.hiddenDates = [];
if (hiddenDates) {
if (Array.isArray(hiddenDates) == true) {
@@ -28,12 +28,13 @@ exports.convertHiddenOptions = function(body, hiddenDates) {
/**
* create new entrees for the repeating hidden dates
* @param body
* @param hiddenDates
* @param {function} moment
* @param {Object} body
* @param {Array} hiddenDates
*/
exports.updateHiddenDates = function (body, hiddenDates) {
exports.updateHiddenDates = function (moment, body, hiddenDates) {
if (hiddenDates && body.domProps.centerContainer.width !== undefined) {
exports.convertHiddenOptions(body, hiddenDates);
exports.convertHiddenOptions(moment, body, hiddenDates);
var start = moment(body.range.start);
var end = moment(body.range.end);
@@ -208,20 +209,21 @@ exports.removeDuplicates = function(body) {
body.hiddenDates.sort(function (a, b) {
return a.start - b.start;
}); // sort by start time
}
};
exports.printDates = function(dates) {
for (var i =0; i < dates.length; i++) {
console.log(i, new Date(dates[i].start),new Date(dates[i].end), dates[i].start, dates[i].end, dates[i].remove);
}
}
};
/**
* Used in TimeStep to avoid the hidden times.
* @param timeStep
* @param {function} moment
* @param {TimeStep} timeStep
* @param previousTime
*/
exports.stepOverHiddenDates = function(timeStep, previousTime) {
exports.stepOverHiddenDates = function(moment, timeStep, previousTime) {
var stepInHidden = false;
var currentValue = timeStep.current.valueOf();
for (var i = 0; i < timeStep.hiddenDates.length; i++) {
@@ -241,7 +243,7 @@ exports.stepOverHiddenDates = function(timeStep, previousTime) {
else if (prevValue.month() != newValue.month()) {timeStep.switchedMonth = true;}
else if (prevValue.dayOfYear() != newValue.dayOfYear()) {timeStep.switchedDay = true;}
timeStep.current = newValue.toDate();
timeStep.current = newValue;
}
};
@@ -282,13 +284,13 @@ exports.toScreen = function(Core, time, width) {
return (time.valueOf() - conversion.offset) * conversion.scale;
}
else {
var hidden = exports.isHidden(time, Core.body.hiddenDates)
var hidden = exports.isHidden(time, Core.body.hiddenDates);
if (hidden.hidden == true) {
time = hidden.startDate;
}
var duration = exports.getHiddenDurationBetween(Core.body.hiddenDates, Core.range.start, Core.range.end);
time = exports.correctTimeForHidden(Core.body.hiddenDates, Core.range, time);
time = exports.correctTimeForHidden(Core.options.moment, Core.body.hiddenDates, Core.range, time);
var conversion = Core.range.conversion(width, duration);
return (time.valueOf() - conversion.offset) * conversion.scale;
@@ -344,18 +346,19 @@ exports.getHiddenDurationBetween = function(hiddenDates, start, end) {
/**
* Support function
* @param moment
* @param hiddenDates
* @param range
* @param time
* @returns {{duration: number, time: *, offset: number}}
*/
exports.correctTimeForHidden = function(hiddenDates, range, time) {
exports.correctTimeForHidden = function(moment, hiddenDates, range, time) {
time = moment(time).toDate().valueOf();
time -= exports.getHiddenDurationBefore(hiddenDates,range,time);
time -= exports.getHiddenDurationBefore(moment, hiddenDates,range,time);
return time;
};
exports.getHiddenDurationBefore = function(hiddenDates, range, time) {
exports.getHiddenDurationBefore = function(moment, hiddenDates, range, time) {
var timeOffset = 0;
time = moment(time).toDate().valueOf();
+3
View File
@@ -1,5 +1,6 @@
var Emitter = require('emitter-component');
var Hammer = require('../module/hammer');
var moment = require('../module/moment');
var util = require('../util');
var DataSet = require('../DataSet');
var DataView = require('../DataView');
@@ -44,6 +45,8 @@ function Graph2d (container, items, groups, options) {
item: 'bottom' // not relevant for Graph2d
},
moment: moment,
width: null,
height: null,
maxHeight: null,
+10 -6
View File
@@ -27,6 +27,7 @@ function Range(body, options) {
this.defaultOptions = {
start: null,
end: null,
moment: moment,
direction: 'horizontal', // 'horizontal' or 'vertical'
moveable: true,
zoomable: true,
@@ -78,7 +79,10 @@ Range.prototype = new Component();
Range.prototype.setOptions = function (options) {
if (options) {
// copy the options that we know
var fields = ['direction', 'min', 'max', 'zoomMin', 'zoomMax', 'moveable', 'zoomable', 'activate', 'hiddenDates', 'zoomKey'];
var fields = [
'direction', 'min', 'max', 'zoomMin', 'zoomMax', 'moveable', 'zoomable',
'moment', 'activate', 'hiddenDates', 'zoomKey'
];
util.selectiveExtend(fields, this.options, options);
if ('start' in options || 'end' in options) {
@@ -145,7 +149,7 @@ Range.prototype.setRange = function(start, end, animation, byUser) {
var e = (done || finalEnd === null) ? finalEnd : initEnd + (finalEnd - initEnd) * ease;
changed = me._applyRange(s, e);
DateUtil.updateHiddenDates(me.body, me.options.hiddenDates);
DateUtil.updateHiddenDates(me.options.moment, me.body, me.options.hiddenDates);
anyChanged = anyChanged || changed;
if (changed) {
me.body.emitter.emit('rangechange', {start: new Date(me.start), end: new Date(me.end), byUser:byUser});
@@ -168,7 +172,7 @@ Range.prototype.setRange = function(start, end, animation, byUser) {
}
else {
var changed = this._applyRange(finalStart, finalEnd);
DateUtil.updateHiddenDates(this.body, this.options.hiddenDates);
DateUtil.updateHiddenDates(this.options.moment, this.body, this.options.hiddenDates);
if (changed) {
var params = {start: new Date(this.start), end: new Date(this.end), byUser:byUser};
this.body.emitter.emit('rangechange', params);
@@ -547,8 +551,8 @@ Range.prototype._onPinch = function (event) {
var scale = 1 / (event.scale + this.scaleOffset);
var centerDate = this._pointerToDate(this.props.touch.center);
var hiddenDuration = DateUtil.getHiddenDurationBetween(this.body.hiddenDates, this.start, this.end);
var hiddenDurationBefore = DateUtil.getHiddenDurationBefore(this.body.hiddenDates, this, centerDate);
var hiddenDuration = DateUtil.getHiddenDurationBetween(this.options.moment, this.body.hiddenDates, this.start, this.end);
var hiddenDurationBefore = DateUtil.getHiddenDurationBefore(this.options.moment, this.body.hiddenDates, this, centerDate);
var hiddenDurationAfter = hiddenDuration - hiddenDurationBefore;
// calculate new start and end
@@ -645,7 +649,7 @@ Range.prototype.zoom = function(scale, center, delta) {
}
var hiddenDuration = DateUtil.getHiddenDurationBetween(this.body.hiddenDates, this.start, this.end);
var hiddenDurationBefore = DateUtil.getHiddenDurationBefore(this.body.hiddenDates, this, center);
var hiddenDurationBefore = DateUtil.getHiddenDurationBefore(this.options.moment, this.body.hiddenDates, this, center);
var hiddenDurationAfter = hiddenDuration - hiddenDurationBefore;
// calculate new start and end
+135 -119
View File
@@ -29,10 +29,12 @@ var util = require('../util');
* @param {Number} [minimumStep] Optional. Minimum step size in milliseconds
*/
function TimeStep(start, end, minimumStep, hiddenDates) {
this.moment = moment;
// variables
this.current = new Date();
this._start = new Date();
this._end = new Date();
this.current = this.moment();
this._start = this.moment();
this._end = this.moment();
this.autoScale = true;
this.scale = 'day';
@@ -77,6 +79,20 @@ TimeStep.FORMAT = {
}
};
/**
* Set custom constructor function for moment. Can be used to set dates
* to UTC or to set a utcOffset.
* @param {function} moment
*/
TimeStep.prototype.setMoment = function (moment) {
this.moment = moment;
// update the date properties, can have a new utcOffset
this.current = this.moment(this.current);
this._start = this.moment(this._start);
this._end = this.moment(this._end);
};
/**
* Set custom formatting for the minor an major labels of the TimeStep.
* Both `minorLabels` and `majorLabels` are an Object with properties:
@@ -103,8 +119,8 @@ TimeStep.prototype.setRange = function(start, end, minimumStep) {
throw "No legal start or end date in method setRange";
}
this._start = (start != undefined) ? new Date(start.valueOf()) : new Date();
this._end = (end != undefined) ? new Date(end.valueOf()) : new Date();
this._start = (start != undefined) ? this.moment(start.valueOf()) : new Date();
this._end = (end != undefined) ? this.moment(end.valueOf()) : new Date();
if (this.autoScale) {
this.setMinimumStep(minimumStep);
@@ -114,8 +130,8 @@ TimeStep.prototype.setRange = function(start, end, minimumStep) {
/**
* Set the range iterator to the start date.
*/
TimeStep.prototype.first = function() {
this.current = new Date(this._start.valueOf());
TimeStep.prototype.start = function() {
this.current = this._start.clone();
this.roundToMinor();
};
@@ -129,28 +145,28 @@ TimeStep.prototype.roundToMinor = function() {
// noinspection FallThroughInSwitchStatementJS
switch (this.scale) {
case 'year':
this.current.setFullYear(this.step * Math.floor(this.current.getFullYear() / this.step));
this.current.setMonth(0);
case 'month': this.current.setDate(1);
this.current.year(this.step * Math.floor(this.current.year() / this.step));
this.current.month(0);
case 'month': this.current.date(1);
case 'day': // intentional fall through
case 'weekday': this.current.setHours(0);
case 'hour': this.current.setMinutes(0);
case 'minute': this.current.setSeconds(0);
case 'second': this.current.setMilliseconds(0);
case 'weekday': this.current.hours(0);
case 'hour': this.current.minutes(0);
case 'minute': this.current.seconds(0);
case 'second': this.current.milliseconds(0);
//case 'millisecond': // nothing to do for milliseconds
}
if (this.step != 1) {
// round down to the first minor value that is a multiple of the current step size
switch (this.scale) {
case 'millisecond': this.current.setMilliseconds(this.current.getMilliseconds() - this.current.getMilliseconds() % this.step); break;
case 'second': this.current.setSeconds(this.current.getSeconds() - this.current.getSeconds() % this.step); break;
case 'minute': this.current.setMinutes(this.current.getMinutes() - this.current.getMinutes() % this.step); break;
case 'hour': this.current.setHours(this.current.getHours() - this.current.getHours() % this.step); break;
case 'millisecond': this.current.subtract(this.current.milliseconds() % this.step, 'milliseconds'); break;
case 'second': this.current.subtract(this.current.seconds() % this.step, 'seconds'); break;
case 'minute': this.current.subtract(this.current.minutes() % this.step, 'minutes'); break;
case 'hour': this.current.subtract(this.current.hours() % this.step, 'hours'); break;
case 'weekday': // intentional fall through
case 'day': this.current.setDate((this.current.getDate()-1) - (this.current.getDate()-1) % this.step + 1); break;
case 'month': this.current.setMonth(this.current.getMonth() - this.current.getMonth() % this.step); break;
case 'year': this.current.setFullYear(this.current.getFullYear() - this.current.getFullYear() % this.step); break;
case 'day': this.current.subtract((this.current.date() - 1) % this.step, 'day'); break;
case 'month': this.current.subtract(this.current.month() % this.step, 'month'); break;
case 'year': this.current.subtract(this.current.year() % this.step, 'year'); break;
default: break;
}
}
@@ -172,67 +188,65 @@ TimeStep.prototype.next = function() {
// Two cases, needed to prevent issues with switching daylight savings
// (end of March and end of October)
if (this.current.getMonth() < 6) {
if (this.current.month() < 6) {
switch (this.scale) {
case 'millisecond':
this.current = new Date(this.current.valueOf() + this.step); break;
case 'second': this.current = new Date(this.current.valueOf() + this.step * 1000); break;
case 'minute': this.current = new Date(this.current.valueOf() + this.step * 1000 * 60); break;
case 'millisecond': this.current.add(this.step, 'millisecond'); break;
case 'second': this.current.add(this.step, 'second'); break;
case 'minute': this.current.add(this.step, 'minute'); break;
case 'hour':
this.current = new Date(this.current.valueOf() + this.step * 1000 * 60 * 60);
this.current.add(this.step, 'hour');
// in case of skipping an hour for daylight savings, adjust the hour again (else you get: 0h 5h 9h ... instead of 0h 4h 8h ...)
var h = this.current.getHours();
this.current.setHours(h - (h % this.step));
// TODO: is this still needed now we use the function of moment.js?
this.current.subtract(this.current.hours() % this.step, 'hour');
break;
case 'weekday': // intentional fall through
case 'day': this.current.setDate(this.current.getDate() + this.step); break;
case 'month': this.current.setMonth(this.current.getMonth() + this.step); break;
case 'year': this.current.setFullYear(this.current.getFullYear() + this.step); break;
default: break;
case 'day': this.current.add(this.step, 'day'); break;
case 'month': this.current.add(this.step, 'month'); break;
case 'year': this.current.add(this.step, 'year'); break;
default: break;
}
}
else {
switch (this.scale) {
case 'millisecond': this.current = new Date(this.current.valueOf() + this.step); break;
case 'second': this.current.setSeconds(this.current.getSeconds() + this.step); break;
case 'minute': this.current.setMinutes(this.current.getMinutes() + this.step); break;
case 'hour': this.current.setHours(this.current.getHours() + this.step); break;
case 'millisecond': this.current.add(this.step, 'millisecond'); break;
case 'second': this.current.add(this.step, 'second'); break;
case 'minute': this.current.add(this.step, 'minute'); break;
case 'hour': this.current.add(this.step, 'hour'); break;
case 'weekday': // intentional fall through
case 'day': this.current.setDate(this.current.getDate() + this.step); break;
case 'month': this.current.setMonth(this.current.getMonth() + this.step); break;
case 'year': this.current.setFullYear(this.current.getFullYear() + this.step); break;
default: break;
case 'day': this.current.add(this.step, 'day'); break;
case 'month': this.current.add(this.step, 'month'); break;
case 'year': this.current.add(this.step, 'year'); break;
default: break;
}
}
if (this.step != 1) {
// round down to the correct major value
switch (this.scale) {
case 'millisecond': if(this.current.getMilliseconds() < this.step) this.current.setMilliseconds(0); break;
case 'second': if(this.current.getSeconds() < this.step) this.current.setSeconds(0); break;
case 'minute': if(this.current.getMinutes() < this.step) this.current.setMinutes(0); break;
case 'hour': if(this.current.getHours() < this.step) this.current.setHours(0); break;
case 'millisecond': if(this.current.milliseconds() < this.step) this.current.milliseconds(0); break;
case 'second': if(this.current.seconds() < this.step) this.current.seconds(0); break;
case 'minute': if(this.current.minutes() < this.step) this.current.minutes(0); break;
case 'hour': if(this.current.hours() < this.step) this.current.hours(0); break;
case 'weekday': // intentional fall through
case 'day': if(this.current.getDate() < this.step+1) this.current.setDate(1); break;
case 'month': if(this.current.getMonth() < this.step) this.current.setMonth(0); break;
case 'day': if(this.current.date() < this.step+1) this.current.date(1); break;
case 'month': if(this.current.month() < this.step) this.current.month(0); break;
case 'year': break; // nothing to do for year
default: break;
default: break;
}
}
// safety mechanism: if current time is still unchanged, move to the end
if (this.current.valueOf() == prev) {
this.current = new Date(this._end.valueOf());
this.current = this._end.clone();
}
DateUtil.stepOverHiddenDates(this, prev);
DateUtil.stepOverHiddenDates(this.moment, this, prev);
};
/**
* Get the current datetime
* @return {Date} current The current date
* @return {Moment} current The current date
*/
TimeStep.prototype.getCurrent = function() {
return this.current;
@@ -329,100 +343,100 @@ TimeStep.prototype.setMinimumStep = function(minimumStep) {
* @return {Date} snappedDate
*/
TimeStep.snap = function(date, scale, step) {
var clone = new Date(date.valueOf());
var clone = moment(date);
if (scale == 'year') {
var year = clone.getFullYear() + Math.round(clone.getMonth() / 12);
clone.setFullYear(Math.round(year / step) * step);
clone.setMonth(0);
clone.setDate(0);
clone.setHours(0);
clone.setMinutes(0);
clone.setSeconds(0);
clone.setMilliseconds(0);
var year = clone.year() + Math.round(clone.month() / 12);
clone.year(Math.round(year / step) * step);
clone.month(0);
clone.date(0);
clone.hours(0);
clone.minutes(0);
clone.seconds(0);
clone.mlliseconds(0);
}
else if (scale == 'month') {
if (clone.getDate() > 15) {
clone.setDate(1);
clone.setMonth(clone.getMonth() + 1);
if (clone.date() > 15) {
clone.date(1);
clone.add(1, 'month');
// important: first set Date to 1, after that change the month.
}
else {
clone.setDate(1);
clone.date(1);
}
clone.setHours(0);
clone.setMinutes(0);
clone.setSeconds(0);
clone.setMilliseconds(0);
clone.hours(0);
clone.minutes(0);
clone.seconds(0);
clone.milliseconds(0);
}
else if (scale == 'day') {
//noinspection FallthroughInSwitchStatementJS
switch (step) {
case 5:
case 2:
clone.setHours(Math.round(clone.getHours() / 24) * 24); break;
clone.hours(Math.round(clone.hours() / 24) * 24); break;
default:
clone.setHours(Math.round(clone.getHours() / 12) * 12); break;
clone.hours(Math.round(clone.hours() / 12) * 12); break;
}
clone.setMinutes(0);
clone.setSeconds(0);
clone.setMilliseconds(0);
clone.minutes(0);
clone.seconds(0);
clone.milliseconds(0);
}
else if (scale == 'weekday') {
//noinspection FallthroughInSwitchStatementJS
switch (step) {
case 5:
case 2:
clone.setHours(Math.round(clone.getHours() / 12) * 12); break;
clone.hours(Math.round(clone.hours() / 12) * 12); break;
default:
clone.setHours(Math.round(clone.getHours() / 6) * 6); break;
clone.hours(Math.round(clone.hours() / 6) * 6); break;
}
clone.setMinutes(0);
clone.setSeconds(0);
clone.setMilliseconds(0);
clone.minutes(0);
clone.seconds(0);
clone.milliseconds(0);
}
else if (scale == 'hour') {
switch (step) {
case 4:
clone.setMinutes(Math.round(clone.getMinutes() / 60) * 60); break;
clone.minutes(Math.round(clone.minutes() / 60) * 60); break;
default:
clone.setMinutes(Math.round(clone.getMinutes() / 30) * 30); break;
clone.minutes(Math.round(clone.minutes() / 30) * 30); break;
}
clone.setSeconds(0);
clone.setMilliseconds(0);
clone.seconds(0);
clone.milliseconds(0);
} else if (scale == 'minute') {
//noinspection FallthroughInSwitchStatementJS
switch (step) {
case 15:
case 10:
clone.setMinutes(Math.round(clone.getMinutes() / 5) * 5);
clone.setSeconds(0);
clone.minutes(Math.round(clone.minutes() / 5) * 5);
clone.seconds(0);
break;
case 5:
clone.setSeconds(Math.round(clone.getSeconds() / 60) * 60); break;
clone.seconds(Math.round(clone.seconds() / 60) * 60); break;
default:
clone.setSeconds(Math.round(clone.getSeconds() / 30) * 30); break;
clone.seconds(Math.round(clone.seconds() / 30) * 30); break;
}
clone.setMilliseconds(0);
clone.milliseconds(0);
}
else if (scale == 'second') {
//noinspection FallthroughInSwitchStatementJS
switch (step) {
case 15:
case 10:
clone.setSeconds(Math.round(clone.getSeconds() / 5) * 5);
clone.setMilliseconds(0);
clone.seconds(Math.round(clone.seconds() / 5) * 5);
clone.milliseconds(0);
break;
case 5:
clone.setMilliseconds(Math.round(clone.getMilliseconds() / 1000) * 1000); break;
clone.milliseconds(Math.round(clone.milliseconds() / 1000) * 1000); break;
default:
clone.setMilliseconds(Math.round(clone.getMilliseconds() / 500) * 500); break;
clone.milliseconds(Math.round(clone.milliseconds() / 500) * 500); break;
}
}
else if (scale == 'millisecond') {
var _step = step > 5 ? step / 2 : 1;
clone.setMilliseconds(Math.round(clone.getMilliseconds() / _step) * _step);
clone.milliseconds(Math.round(clone.milliseconds() / _step) * _step);
}
return clone;
@@ -477,20 +491,21 @@ TimeStep.prototype.isMajor = function() {
}
}
var date = this.moment(this.current);
switch (this.scale) {
case 'millisecond':
return (this.current.getMilliseconds() == 0);
return (date.milliseconds() == 0);
case 'second':
return (this.current.getSeconds() == 0);
return (date.seconds() == 0);
case 'minute':
return (this.current.getHours() == 0) && (this.current.getMinutes() == 0);
return (date.hours() == 0) && (date.minutes() == 0);
case 'hour':
return (this.current.getHours() == 0);
return (date.hours() == 0);
case 'weekday': // intentional fall through
case 'day':
return (this.current.getDate() == 1);
return (date.date() == 1);
case 'month':
return (this.current.getMonth() == 0);
return (date.month() == 0);
case 'year':
return false;
default:
@@ -511,7 +526,7 @@ TimeStep.prototype.getLabelMinor = function(date) {
}
var format = this.format.minorLabels[this.scale];
return (format && format.length > 0) ? moment(date).format(format) : '';
return (format && format.length > 0) ? this.moment(date).format(format) : '';
};
/**
@@ -526,12 +541,13 @@ TimeStep.prototype.getLabelMajor = function(date) {
}
var format = this.format.majorLabels[this.scale];
return (format && format.length > 0) ? moment(date).format(format) : '';
return (format && format.length > 0) ? this.moment(date).format(format) : '';
};
TimeStep.prototype.getClassName = function() {
var m = moment(this.current);
var date = m.locale ? m.locale('en') : m.lang('en'); // old versions of moment have .lang() function
var _moment = this.moment;
var m = this.moment(this.current);
var current = m.locale ? m.locale('en') : m.lang('en'); // old versions of moment have .lang() function
var step = this.step;
function even(value) {
@@ -542,10 +558,10 @@ TimeStep.prototype.getClassName = function() {
if (date.isSame(new Date(), 'day')) {
return ' vis-today';
}
if (date.isSame(moment().add(1, 'day'), 'day')) {
if (date.isSame(_moment().add(1, 'day'), 'day')) {
return ' vis-tomorrow';
}
if (date.isSame(moment().add(-1, 'day'), 'day')) {
if (date.isSame(_moment().add(-1, 'day'), 'day')) {
return ' vis-yesterday';
}
return '';
@@ -565,37 +581,37 @@ TimeStep.prototype.getClassName = function() {
switch (this.scale) {
case 'millisecond':
return even(date.milliseconds()).trim();
return even(current.milliseconds()).trim();
case 'second':
return even(date.seconds()).trim();
return even(current.seconds()).trim();
case 'minute':
return even(date.minutes()).trim();
return even(current.minutes()).trim();
case 'hour':
var hours = date.hours();
var hours = current.hours();
if (this.step == 4) {
hours = hours + '-h' + (hours + 4);
}
return 'vis-h' + hours + today(date) + even(date.hours());
return 'vis-h' + hours + today(current) + even(current.hours());
case 'weekday':
return 'vis-' + date.format('dddd').toLowerCase() +
today(date) + currentWeek(date) + even(date.date());
return 'vis-' + current.format('dddd').toLowerCase() +
today(current) + currentWeek(current) + even(current.date());
case 'day':
var day = date.date();
var month = date.format('MMMM').toLowerCase();
return 'vis-day' + day + ' vis-' + month + currentMonth(date) + even(day - 1);
var day = current.date();
var month = current.format('MMMM').toLowerCase();
return 'vis-day' + day + ' vis-' + month + currentMonth(current) + even(day - 1);
case 'month':
return 'vis-' + date.format('MMMM').toLowerCase() +
currentMonth(date) + even(date.month());
return 'vis-' + current.format('MMMM').toLowerCase() +
currentMonth(current) + even(current.month());
case 'year':
var year = date.year();
return 'vis-year' + year + currentYear(date)+ even(year);
var year = current.year();
return 'vis-year' + year + currentYear(current)+ even(year);
default:
return '';
+3
View File
@@ -1,5 +1,6 @@
var Emitter = require('emitter-component');
var Hammer = require('../module/hammer');
var moment = require('../module/moment');
var util = require('../util');
var DataSet = require('../DataSet');
var DataView = require('../DataView');
@@ -49,6 +50,8 @@ function Timeline (container, items, groups, options) {
item: 'bottom' // not relevant
},
moment: moment,
width: null,
height: null,
maxHeight: null,
@@ -18,6 +18,7 @@ function CurrentTime (body, options) {
this.defaultOptions = {
showCurrentTime: true,
moment: moment,
locales: locales,
locale: 'en'
};
@@ -63,7 +64,7 @@ CurrentTime.prototype.destroy = function () {
CurrentTime.prototype.setOptions = function(options) {
if (options) {
// copy all options that we know
util.selectiveExtend(['showCurrentTime', 'locale', 'locales'], this.options, options);
util.selectiveExtend(['showCurrentTime', 'moment', 'locale', 'locales'], this.options, options);
}
};
@@ -84,7 +85,7 @@ CurrentTime.prototype.redraw = function() {
this.start();
}
var now = new Date(new Date().valueOf() + this.offset);
var now = this.options.moment(new Date().valueOf() + this.offset);
var x = this.body.util.toScreen(now);
var locale = this.options.locales[this.options.locale];
@@ -95,7 +96,7 @@ CurrentTime.prototype.redraw = function() {
}
locale = this.options.locales['en']; // fall back on english when not available
}
var title = locale.current + ' ' + locale.time + ': ' + moment(now).format('dddd, MMMM Do YYYY, H:mm:ss');
var title = locale.current + ' ' + locale.time + ': ' + now.format('dddd, MMMM Do YYYY, H:mm:ss');
title = title.charAt(0).toUpperCase() + title.substring(1);
this.bar.style.left = x + 'px';
+4 -6
View File
@@ -20,6 +20,7 @@ function CustomTime (body, options) {
// default options
this.defaultOptions = {
moment: moment,
locales: locales,
locale: 'en',
id: undefined
@@ -52,7 +53,7 @@ CustomTime.prototype = new Component();
CustomTime.prototype.setOptions = function(options) {
if (options) {
// copy all options that we know
util.selectiveExtend(['locale', 'locales', 'id'], this.options, options);
util.selectiveExtend(['moment', 'locale', 'locales', 'id'], this.options, options);
}
};
@@ -83,10 +84,6 @@ CustomTime.prototype._create = function() {
this.hammer.on('panmove', this._onDrag.bind(this));
this.hammer.on('panend', this._onDragEnd.bind(this));
this.hammer.get('pan').set({threshold:5, direction:30}); // 30 is ALL_DIRECTIONS in hammer.
// TODO: cleanup
//this.hammer.on('pan', function (event) {
// event.preventDefault();
//});
};
/**
@@ -125,7 +122,8 @@ CustomTime.prototype.redraw = function () {
}
locale = this.options.locales['en']; // fall back on english when not available
}
var title = locale.time + ': ' + moment(this.customTime).format('dddd, MMMM Do YYYY, H:mm:ss');
var title = locale.time + ': ' + this.options.moment(this.customTime).format('dddd, MMMM Do YYYY, H:mm:ss');
title = title.charAt(0).toUpperCase() + title.substring(1);
this.bar.style.left = x + 'px';
+44 -6
View File
@@ -1110,6 +1110,20 @@ ItemSet.prototype._onTouch = function (event) {
this.touchParams.itemProps = null;
};
/**
* Given an group id, returns the index it has.
*
* @param {Number} groupID
* @private
*/
ItemSet.prototype._getGroupIndex = function(groupId) {
for (var i = 0; i < this.groupIds.length; i++) {
if (groupId == this.groupIds[i])
return i;
}
}
/**
* Start dragging the selected events
* @param {Event} event
@@ -1157,11 +1171,17 @@ ItemSet.prototype._onDragStart = function (event) {
this.touchParams.itemProps = [props];
}
else {
this.touchParams.selectedItem = item;
var baseGroupIndex = this._getGroupIndex(item.data.group);
this.touchParams.itemProps = this.getSelection().map(function (id) {
var item = me.items[id];
var groupIndex = me._getGroupIndex(item.data.group);
var props = {
item: item,
initialX: event.center.x,
groupOffset: baseGroupIndex-groupIndex,
data: util.extend({}, item.data) // clone the items data
};
@@ -1238,6 +1258,22 @@ ItemSet.prototype._onDrag = function (event) {
var scale = this.body.util.getScale();
var step = this.body.util.getStep();
//only calculate the new group for the item that's actually dragged
var selectedItem = this.touchParams.selectedItem;
var updateGroupAllowed = me.options.editable.updateGroup;
var newGroupBase = null;
if (updateGroupAllowed && selectedItem) {
if (selectedItem.data.group != undefined) {
// drag from one group to another
var group = me.groupFromTarget(event);
if (group) {
//we know the offset for all items, so the new group for all items
//will be relative to this one.
newGroupBase = this._getGroupIndex(group.groupId);
}
}
}
// move
this.touchParams.itemProps.forEach(function (props) {
var newProps = {};
@@ -1294,13 +1330,15 @@ ItemSet.prototype._onDrag = function (event) {
var updateGroupAllowed = me.options.editable.updateGroup ||
props.item.editable === true;
if (updateGroupAllowed && (!props.dragLeft && !props.dragRight)) {
if (updateGroupAllowed && (!props.dragLeft && !props.dragRight) && newGroupBase!=null) {
if (itemData.group != undefined) {
// drag from one group to another
var group = me.groupFromTarget(event);
if (group) {
itemData.group = group.groupId;
}
var newOffset = newGroupBase - props.groupOffset;
//make sure we stay in bounds
newOffset = Math.max(0, newOffset);
newOffset = Math.min(me.groupIds.length-1, newOffset);
itemData.group = me.groupIds[newOffset];
}
}
+7 -4
View File
@@ -40,6 +40,7 @@ function TimeAxis (body, options) {
showMinorLabels: true,
showMajorLabels: true,
format: TimeStep.FORMAT,
moment: moment,
timeAxis: null
};
this.options = util.extend({}, this.defaultOptions);
@@ -69,7 +70,8 @@ TimeAxis.prototype.setOptions = function(options) {
'showMinorLabels',
'showMajorLabels',
'hiddenDates',
'timeAxis'
'timeAxis',
'moment'
], this.options, options);
// deep copy the format options
@@ -194,10 +196,11 @@ TimeAxis.prototype._repaintLabels = function () {
var start = util.convert(this.body.range.start, 'Number');
var end = util.convert(this.body.range.end, 'Number');
var timeLabelsize = this.body.util.toTime((this.props.minorCharWidth || 10) * 7).valueOf();
var minimumStep = timeLabelsize - DateUtil.getHiddenDurationBefore(this.body.hiddenDates, this.body.range, timeLabelsize);
var minimumStep = timeLabelsize - DateUtil.getHiddenDurationBefore(this.options.moment, this.body.hiddenDates, this.body.range, timeLabelsize);
minimumStep -= this.body.util.toTime(0).valueOf();
var step = new TimeStep(new Date(start), new Date(end), minimumStep, this.body.hiddenDates);
step.setMoment(this.options.moment);
if (this.options.format) {
step.setFormat(this.options.format);
}
@@ -229,7 +232,7 @@ TimeAxis.prototype._repaintLabels = function () {
var max = 0;
var className;
step.first();
step.start();
next = step.getCurrent();
xNext = this.body.util.toScreen(next);
while (step.hasNext() && max < 1000) {
@@ -247,7 +250,7 @@ TimeAxis.prototype._repaintLabels = function () {
xNext = this.body.util.toScreen(next);
width = xNext - x;
var labelFits = labelMinor.length * this.props.minorCharWidth < width;
var labelFits = (labelMinor.length + 1) * this.props.minorCharWidth < width;
if (this.options.showMinorLabels && labelFits) {
this._repaintMinorText(x, labelMinor, orientation, className);
+1
View File
@@ -125,6 +125,7 @@ let allOptions = {
},
__type__: {object}
},
moment: {'function': 'function'},
height: {string, number},
hiddenDates: {object, array},
locale:{string},
+1
View File
@@ -62,6 +62,7 @@ let allOptions = {
},
__type__: {object}
},
moment: {'function': 'function'},
groupOrder: {string, 'function': 'function'},
height: {string, number},
hiddenDates: {object, array},
+2
View File
@@ -76,6 +76,8 @@ This generates the vis.js library in the folder `./dist`.
- Update the library version number in the index.html page.
- Update the CDN links at the download section of index.html AND the CDN link at the top.
- Commit the changes in the `gh-pages` branch.
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "vis",
"version": "4.5.0",
"version": "4.7.0",
"description": "A dynamic, browser-based visualization library.",
"homepage": "http://visjs.org/",
"license": "(Apache-2.0 OR MIT)",
+538 -129
View File
@@ -1,148 +1,557 @@
<!doctype html>
<html>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Network | Hierarchical layout</title>
<title>Test</title>
<meta http-equiv="x-ua-compatible" content="IE=Edge" />
<style type="text/css">
body {
font: 10pt sans;
}
#mynetwork {
width: 600px;
height: 600px;
border: 1px solid lightgray;
}
</style>
<script type="text/javascript" src="../dist/vis.js"></script>
<link type="text/css" rel="stylesheet" href="../dist/vis.css">
<script type="text/javascript" src="http://code.jquery.com/jquery-1.7.2.js"></script>
<script type="text/javascript">
var nodes = null;
var edges = null;
var network = null;
function destroy() {
if (network !== null) {
network.destroy();
network = null;
</head>
<body>
<input type="button" id="changeVis" value="ChangeVis"/>
<div id="kl" style="position:absolute;height:700px; width:1200px;float:left;border: 2px solid #0094ff;overflow:auto">
</div>
<script>
var graph = null;
var chartVisual = null;
window.onload = function () {
chartVisual = new weve.cg.view.ChartVisual();
chartVisual.LoadChart();
}
//
// Copyright © 2011-2015 Cambridge Intelligence Limited.
// All rights reserved.
//
// Sample Code
// Click here to see the code in action: Combos 1
var weve = weve || {};
weve.cg = weve.cg || {};
weve.cg.view = weve.cg.view || {};
network = {};
nodes = new vis.DataSet();
edges = new vis.DataSet();
// Javascript Constructor - The Main Graph object
weve.cg.view.ChartVisual = function () {
nodes2 = new vis.DataSet([
{ label: "0", id: "d0" },
{ label: "1", id: "d1" },
{ label: "2", id: "d2" },
{ label: "3", id: "d3" },
{ label: "4", id: "d4" },
{ label: "5", id: "d5" },
{ label: "6", id: "d6" },
{ label: "7", id: "d7" },
{ label: "8", id: "d8" },
{ label: "9", id: "d9" },
{ label: "10", id: "d10" },
{ label: "11", id: "d11" },
{ label: "12", id: "d12" },
{ label: "13", id: "d13" },
{ label: "14", id: "d14" },
{ label: "15", id: "d15" },
{ label: "16", id: "d16" },
{ label: "17", id: "d17" },
{ label: "18", id: "d18" },
{ label: "19", id: "d19" },
{ label: "20", id: "d20" },
{ label: "21", id: "d21" },
{ label: "22", id: "d22" },
{ label: "23", id: "d23" },
{ label: "24", id: "d24" },
{ label: "25", id: "d25" },
{ label: "26", id: "d26" },
{ label: "27", id: "d27" },
{ label: "28", id: "d28" },
{ label: "29", id: "d29" },
{ label: "30", id: "d30" },
{ label: "31", id: "d31" },
{ label: "32", id: "d32" },
{ label: "33", id: "d33" },
{ label: "34", id: "d34" },
{ label: "35", id: "d35" },
{ label: "36", id: "d36" },
{ label: "37", id: "d37" },
{ label: "38", id: "d38" },
{ label: "39", id: "d39" },
{ label: "40", id: "d40" },
{ label: "41", id: "d41" },
{ label: "42", id: "d42" },
{ label: "43", id: "d43" },
{ label: "44", id: "d44" },
{ label: "45", id: "d45" },
{ label: "46", id: "d46" },
{ label: "47", id: "d47" },
{ label: "48", id: "d48" },
{ label: "49", id: "d49" },
{ label: "50", id: "d50" },
{ label: "51", id: "d51" },
{ label: "52", id: "d52" },
{ label: "53", id: "d53" },
{ label: "54", id: "d54" },
{ label: "55", id: "d55" },
{ label: "56", id: "d56" },
{ label: "57", id: "d57" },
{ label: "58", id: "d58" },
{ label: "59", id: "d59" },
{ label: "60", id: "d60" },
{ label: "61", id: "d61" },
{ label: "62", id: "d62" },
{ label: "63", id: "d63" },
{ label: "64", id: "d64" },
{ label: "65", id: "d65" },
{ label: "66", id: "d66" },
{ label: "67", id: "d67" },
{ label: "68", id: "d68" },
{ label: "69", id: "d69" },
{ label: "70", id: "d70" },
{ label: "71", id: "d71" },
{ label: "72", id: "d72" },
{ label: "73", id: "d73" },
{ label: "74", id: "d74" },
{ label: "75", id: "d75" },
{ label: "76", id: "d76" },
{ label: "77", id: "d77" },
{ label: "78", id: "d78" },
{ label: "79", id: "d79" },
{ label: "80", id: "d80" },
{ label: "81", id: "d81" },
{ label: "82", id: "d82" },
{ label: "83", id: "d83" },
{ label: "84", id: "d84" },
{ label: "85", id: "d85" },
{ label: "86", id: "d86" },
{ label: "87", id: "d87" },
{ label: "88", id: "d88" },
{ label: "89", id: "d89" },
{ label: "90", id: "d90" },
{ label: "91", id: "d91" },
{ label: "92", id: "d92" },
{ label: "93", id: "d93" },
{ label: "94", id: "d94" },
{ label: "95", id: "d95" },
{ label: "96", id: "d96" },
{ label: "97", id: "d97" },
{ label: "98", id: "d98" },
{ label: "99", id: "d99" },
{ label: "100", id: "d100" },
{ label: "101", id: "d101" },
{ label: "102", id: "d102" },
{ label: "103", id: "d103" },
{ label: "104", id: "d104" },
{ label: "105", id: "d105" },
{ label: "106", id: "d106" },
{ label: "107", id: "d107" },
{ label: "108", id: "d108" },
{ label: "109", id: "d109" },
{ label: "110", id: "d110" },
{ label: "111", id: "d111" },
{ label: "112", id: "d112" },
{ label: "113", id: "d113" },
{ label: "114", id: "d114" },
{ label: "115", id: "d115" },
{ label: "116", id: "d116" },
{ label: "117", id: "d117" },
{ label: "118", id: "d118" },
{ label: "119", id: "d119" },
{ label: "120", id: "d120" },
{ label: "121", id: "d121" },
{ label: "122", id: "d122" },
{ label: "123", id: "d123" },
{ label: "124", id: "d124" },
{ label: "125", id: "d125" },
{ label: "126", id: "d126" },
{ label: "127", id: "d127" },
{ label: "128", id: "d128" },
{ label: "129", id: "d129" },
{ label: "130", id: "d130" },
{ label: "131", id: "d131" },
{ label: "132", id: "d132" },
{ label: "133", id: "d133" },
{ label: "134", id: "d134" },
{ label: "135", id: "d135" },
{ label: "136", id: "d136" },
{ label: "137", id: "d137" },
{ label: "138", id: "d138" },
{ label: "139", id: "d139" },
{ label: "140", id: "d140" },
{ label: "141", id: "d141" },
{ label: "142", id: "d142" },
{ label: "143", id: "d143" },
{ label: "144", id: "d144" },
{ label: "145", id: "d145" },
{ label: "146", id: "d146" },
{ label: "147", id: "d147" },
{ label: "148", id: "d148" },
{ label: "149", id: "d149" },
{ label: "150", id: "d150" },
{ label: "151", id: "d151" },
{ label: "152", id: "d152" },
{ label: "153", id: "d153" },
{ label: "154", id: "d154" },
{ label: "155", id: "d155" },
{ label: "156", id: "d156" },
{ label: "157", id: "d157" },
{ label: "158", id: "d158" },
{ label: "159", id: "d159" },
{ label: "160", id: "d160" },
{ label: "161", id: "d161" },
{ label: "162", id: "d162" },
{ label: "163", id: "d163" }
]);
edges2 = new vis.DataSet([
{ from: "d0", to: "d2" },
{ from: "d1", to: "d0" },
{ from: "d2", to: "d1" },
{ from: "d3", to: "d2" },
{ from: "d4", to: "d3" },
{ from: "d5", to: "d4" },
{ from: "d6", to: "d5" },
{ from: "d7", to: "d6" },
{ from: "d8", to: "d7" },
{ from: "d9", to: "d8" },
{ from: "d10", to: "d9" },
{ from: "d11", to: "d10" },
{ from: "d12", to: "d11" },
{ from: "d13", to: "d12" },
{ from: "d14", to: "d13" },
{ from: "d15", to: "d14" },
{ from: "d16", to: "d15" },
{ from: "d17", to: "d16" },
{ from: "d18", to: "d17" },
{ from: "d19", to: "d18" },
{ from: "d20", to: "d19" },
{ from: "d21", to: "d20" },
{ from: "d22", to: "d21" },
{ from: "d23", to: "d22" },
{ from: "d24", to: "d23" },
{ from: "d25", to: "d24" },
{ from: "d26", to: "d25" },
{ from: "d27", to: "d26" },
{ from: "d28", to: "d27" },
{ from: "d29", to: "d28" },
{ from: "d30", to: "d29" },
{ from: "d31", to: "d30" },
{ from: "d32", to: "d31" },
{ from: "d33", to: "d32" },
{ from: "d34", to: "d33" },
{ from: "d35", to: "d34" },
{ from: "d36", to: "d35" },
{ from: "d37", to: "d36" },
{ from: "d38", to: "d37" },
{ from: "d39", to: "d38" },
{ from: "d40", to: "d39" },
{ from: "d41", to: "d40" },
{ from: "d42", to: "d41" },
{ from: "d43", to: "d42" },
{ from: "d44", to: "d43" },
{ from: "d45", to: "d44" },
{ from: "d46", to: "d45" },
{ from: "d47", to: "d46" },
{ from: "d48", to: "d47" },
{ from: "d49", to: "d48" },
{ from: "d50", to: "d49" },
{ from: "d51", to: "d50" },
{ from: "d52", to: "d51" },
{ from: "d53", to: "d52" },
{ from: "d54", to: "d53" },
{ from: "d55", to: "d54" },
{ from: "d56", to: "d55" },
{ from: "d57", to: "d56" },
{ from: "d58", to: "d57" },
{ from: "d59", to: "d58" },
{ from: "d60", to: "d59" },
{ from: "d61", to: "d60" },
{ from: "d62", to: "d61" },
{ from: "d63", to: "d62" },
{ from: "d64", to: "d63" },
{ from: "d65", to: "d64" },
{ from: "d66", to: "d65" },
{ from: "d67", to: "d66" },
{ from: "d68", to: "d67" },
{ from: "d69", to: "d68" },
{ from: "d70", to: "d69" },
{ from: "d71", to: "d70" },
{ from: "d72", to: "d71" },
{ from: "d73", to: "d72" },
{ from: "d74", to: "d73" },
{ from: "d75", to: "d74" },
{ from: "d76", to: "d75" },
{ from: "d77", to: "d76" },
{ from: "d78", to: "d77" },
{ from: "d79", to: "d78" },
{ from: "d80", to: "d79" },
{ from: "d81", to: "d80" },
{ from: "d82", to: "d81" },
{ from: "d83", to: "d82" },
{ from: "d84", to: "d83" },
{ from: "d85", to: "d84" },
{ from: "d86", to: "d85" },
{ from: "d87", to: "d86" },
{ from: "d88", to: "d87" },
{ from: "d89", to: "d88" },
{ from: "d90", to: "d89" },
{ from: "d91", to: "d90" },
{ from: "d92", to: "d91" },
{ from: "d93", to: "d92" },
{ from: "d94", to: "d93" },
{ from: "d95", to: "d94" },
{ from: "d96", to: "d95" },
{ from: "d97", to: "d96" },
{ from: "d98", to: "d97" },
{ from: "d99", to: "d98" },
{ from: "d100", to: "d99" },
{ from: "d101", to: "d100" },
{ from: "d102", to: "d101" },
{ from: "d103", to: "d102" },
{ from: "d104", to: "d103" },
{ from: "d105", to: "d104" },
{ from: "d106", to: "d105" },
{ from: "d107", to: "d106" },
{ from: "d108", to: "d107" },
{ from: "d109", to: "d108" },
{ from: "d110", to: "d109" },
{ from: "d111", to: "d110" },
{ from: "d112", to: "d111" },
{ from: "d113", to: "d112" },
{ from: "d114", to: "d113" },
{ from: "d115", to: "d114" },
{ from: "d116", to: "d115" },
{ from: "d117", to: "d116" },
{ from: "d118", to: "d117" },
{ from: "d119", to: "d118" },
{ from: "d120", to: "d119" },
{ from: "d121", to: "d120" },
{ from: "d122", to: "d121" },
{ from: "d123", to: "d122" },
{ from: "d124", to: "d123" },
{ from: "d125", to: "d124" },
{ from: "d126", to: "d125" },
{ from: "d127", to: "d126" },
{ from: "d128", to: "d127" },
{ from: "d129", to: "d128" },
{ from: "d130", to: "d129" },
{ from: "d131", to: "d130" },
{ from: "d132", to: "d131" },
{ from: "d133", to: "d132" },
{ from: "d134", to: "d133" },
{ from: "d135", to: "d134" },
{ from: "d136", to: "d135" },
{ from: "d137", to: "d136" },
{ from: "d138", to: "d137" },
{ from: "d139", to: "d138" },
{ from: "d140", to: "d139" },
{ from: "d141", to: "d140" },
{ from: "d142", to: "d141" },
{ from: "d143", to: "d142" },
{ from: "d144", to: "d143" },
{ from: "d145", to: "d144" },
{ from: "d146", to: "d145" },
{ from: "d147", to: "d146" },
{ from: "d148", to: "d147" },
{ from: "d149", to: "d148" },
{ from: "d150", to: "d149" },
{ from: "d151", to: "d150" },
{ from: "d152", to: "d151" },
{ from: "d153", to: "d152" },
{ from: "d154", to: "d153" },
{ from: "d155", to: "d154" },
{ from: "d156", to: "d155" },
{ from: "d157", to: "d156" },
{ from: "d158", to: "d157" }
]);
nodes = nodes2;
edges = edges2;
this.totalEdgesAdded = 0;
this.fitOnDone = false;
this.fitOnDoneIterations = 0;
this.options = {
nodes: {
shape: 'dot',
scaling: {
label: {
min: weve.cg.view.ChartVisual.minFont_largeGroup,
max: weve.cg.view.ChartVisual.maxFont_largeGroup
},
}
},
edges: {
smooth: {
enabled: true,
type: "dynamic",
// roundness: 0.5
},
},
interaction: {
multiselect: false,
navigationButtons: true,
selectable: true,
selectConnectedEdges: true,
tooltipDelay: 100,
zoomView: true
},
physics: {
stabilization: {
enabled: true,
iterations: 250, // maximum number of iteration to stabilize
updateInterval: 5,
onlyDynamicEdges: false,
fit: true
}
}
};
};
weve.cg.view.ChartVisual.minFont_smallGroup = 7;
weve.cg.view.ChartVisual.maxFont_smallGroup = 10;
weve.cg.view.ChartVisual.minFont_largeGroup = 20;
weve.cg.view.ChartVisual.maxFont_largeGroup = 25;
weve.cg.view.ChartVisual.prototype.LoadChart = function (viewModel, callBack) {
var g = this;
var container = document.getElementById('kl');
var data = {
nodes: nodes,
edges: edges
};
this.viewModel = viewModel;
network = new vis.Network(container, data, this.options);
this.startpercentage = 60;
network.on("click",this.neighbourhoodHighlight);
}
weve.cg.view.ChartVisual.prototype.neighbourhoodHighlight = function(params) {
// if something is selected:
var allNodes = nodes.get({returnType:"Object"});
var allEdges = edges.get({returnType:"Object"});
if (params.nodes.length > 0) {
this.highlightActive = true;
var i,j;
var selectedNode = params.nodes[0];
var degrees = 1;
// mark all nodes as hard to read.
for (var nodeId in allNodes) {
allNodes[nodeId].originalColor = allNodes[nodeId].color;
allNodes[nodeId].color = 'rgba(200,200,200,0.5)';
allNodes[nodeId].hiddenLabel = allNodes[nodeId].label;
allNodes[nodeId].label = undefined;
//allNodes[nodeId].hidden = true;
}
var connectedNodes = network.getConnectedNodes(selectedNode);
var allConnectedNodes = [];
// all first degree nodes get their own color and their label back
for (i = 0; i < connectedNodes.length; i++) {
allNodes[connectedNodes[i]].color = allNodes[connectedNodes[i]].originalColor;
if (allNodes[connectedNodes[i]].hiddenLabel !== undefined) {
allNodes[connectedNodes[i]].label = allNodes[connectedNodes[i]].hiddenLabel;
allNodes[connectedNodes[i]].hiddenLabel = undefined;
//allNodes[nodeId].hidden = false;
}
}
// the main node gets its own color and its label back.
allNodes[selectedNode].color = allNodes[selectedNode].originalColor;
if (allNodes[selectedNode].hiddenLabel !== undefined) {
allNodes[selectedNode].label = allNodes[selectedNode].hiddenLabel;
allNodes[selectedNode].hiddenLabel = undefined;
//allNodes[nodeId].hidden = false;
}
var selectedEdges = params.edges;
for (var edgeId in allEdges) {
var edge = allEdges[edgeId];
edge.regularWidth = edge.width; //store the original width.
if (selectedEdges.indexOf(edge.id) == -1)
{
edge.width = 0.01;
//edge.hidden = true;
}
}
}
else if (this.highlightActive === true) {
// reset all nodes
for (var nodeId in allNodes) {
allNodes[nodeId].color = allNodes[nodeId].originalColor;
if (allNodes[nodeId].hiddenLabel !== undefined) {
allNodes[nodeId].label = allNodes[nodeId].hiddenLabel;
allNodes[nodeId].hiddenLabel = undefined;
//allNodes[nodeId].hidden = false;
}
}
for (var edgeIds in allEdges) {
var edge = allEdges[edgeIds];
edge.width = edge.regularWidth; //restore the original width.
//edge.hidden= false;
}
this.highlightActive = false
}
// transform the object into an array
var updateArray = [];
for (nodeId in allNodes) {
if (allNodes.hasOwnProperty(nodeId)) {
updateArray.push(allNodes[nodeId]);
}
}
function draw() {
destroy();
// randomly create some nodes and edges
var nodeCount = document.getElementById('nodeCount').value;
//var data = getScaleFreeNetwork(nodeCount)
var nodes = [
{id: 4, label: '4'},
{id: 5, label: '5'},
{id: 7, label: '7'},
{id: 9, label: '9'},
{id: 10, label: '10'},
{id: 101, label: '101'},
{id: 102, label: '102'},
{id: 103, label: '103'}
];
// create an array with edges
var edges = [
{from: 10, to: 4},
{from: 7, to: 5},
{from: 9, to: 7},
{from: 10, to: 9},
{from: 101, to: 102},
{from: 102, to: 103}
];
var data = {
nodes: nodes,
edges: edges
};
// create a network
var container = document.getElementById('mynetwork');
var directionInput = document.getElementById("direction").value;
var options = {
interaction: {
navigationButtons: true
},
layout: {
hierarchical: {
direction: directionInput
}
}
};
network = new vis.Network(container, data, options);
// add event listeners
network.on('select', function (params) {
document.getElementById('selection').innerHTML = 'Selection: ' + params.nodes;
});
// transform the object into an array
var updateEdgesArray = [];
for (edgeId in allEdges) {
if (allEdges.hasOwnProperty(edgeId)) {
updateEdgesArray.push(allEdges[edgeId]);
}
}
function changeOptions(directionInputValue) {
network.setOptions({
layout: {
hierarchical: {
direction: directionInputValue
}
}
});
}
nodes.update(updateArray);
edges.update(updateEdgesArray);
console.log("edges", updateEdgesArray)
</script>
</head>
<body onload="draw();">
<h2>Hierarchical Layout - Scale-Free-Network</h2>
<div style="width:700px; font-size:14px; text-align: justify;">
This example shows the randomly generated <b>scale-free-network</b> set of nodes and connected edges from example 2.
In this example, hierarchical layout has been enabled and the vertical levels are determined automatically.
</div>
<br/>
<form onsubmit="draw(); return false;">
<label for="nodeCount">Number of nodes:</label>
<input id="nodeCount" type="text" value="25" style="width: 50px;">
<input type="submit" value="Go">
</form>
<p>
<input type="button" id="btn-UD" value="Up-Down">
<input type="button" id="btn-DU" value="Down-Up">
<input type="button" id="btn-LR" value="Left-Right">
<input type="button" id="btn-RL" value="Right-Left">
<input type="hidden" id='direction' value="UD">
</p>
<script language="javascript">
var directionInput = document.getElementById("direction");
var btnUD = document.getElementById("btn-UD");
btnUD.onclick = function () {
directionInput.value = "UD";
changeOptions(directionInput.value);
//this.refreshUILayout(1000);
}
var btnDU = document.getElementById("btn-DU");
btnDU.onclick = function () {
directionInput.value = "DU";
changeOptions(directionInput.value);
};
var btnLR = document.getElementById("btn-LR");
btnLR.onclick = function () {
directionInput.value = "LR";
changeOptions(directionInput.value);
};
var btnRL = document.getElementById("btn-RL");
btnRL.onclick = function () {
directionInput.value = "RL";
changeOptions(directionInput.value);
};
</script>
<br>
<div id="mynetwork"></div>
<p id="selection"></p>
</body>
</html>
+40 -29
View File
@@ -176,36 +176,46 @@
var amountOfOptionChecks = 200;
var optionCheckTime = 150;
var amountOfOptionChecks = 50;
var optionsThreshold = 0.8;
function checkOptions(i) {
// console.log('checking Options iteration:',i)
var allOptions = vis.network.allOptions.allOptions;
var testOptions = {};
constructOptions(allOptions, testOptions);
var failed = setTimeout(function() {console.error("FAILED",JSON.stringify(testOptions,null,4))}, 0.9*optionCheckTime);
var counter = 0;
drawQuick();
network.on("afterDrawing", function() {
counter++;
if (counter > 2) {
counter = 0;
network.off('afterDrawing');
var optionGlobalCount = 0;
function checkOptions() {
optionGlobalCount++;
if (optionGlobalCount == amountOfOptionChecks) {
checkMethods();
}
else {
var allOptions = vis.network.allOptions.allOptions;
var testOptions = {};
constructOptions(allOptions, testOptions);
var failed = setTimeout(function () {
console.error("FAILED", JSON.stringify(testOptions, null, 4))
}, 500);
var counter = 0;
drawQuick();
network.on("afterDrawing", function () {
counter++;
if (counter > 2) {
counter = 0;
network.off('afterDrawing');
clearTimeout(failed);
network.destroy();
}
})
network.on("stabilized", function () {
clearTimeout(failed);
}
})
network.on("stabilized", function() {
clearTimeout(failed);
});
network.on("destroy", function() {
clearTimeout(failed);
})
network.setOptions(testOptions);
network.destroy();
});
network.once("destroy", function () {
clearTimeout(failed);
setTimeout(checkOptions, 100);
})
console.log("now testing:",testOptions)
network.setOptions(testOptions);
}
}
function constructOptions(allOptions, testOptions) {
for (var option in allOptions) {
if (Math.random() < optionsThreshold) {
if (option !== "__type__" && option !== '__any__' && option !== 'locales' && option !== 'image' && option !== 'id') {
@@ -254,10 +264,11 @@
}
for (var i = 0; i < amountOfOptionChecks; i++) {
setTimeout(checkOptions.bind(this,i), i*optionCheckTime);
}
setTimeout(checkMethods, amountOfOptionChecks*optionCheckTime);
checkOptions();
// for (var i = 0; i < amountOfOptionChecks; i++) {
// setTimeout(checkOptions.bind(this,i), i*optionCheckTime);
// }
// setTimeout(checkMethods, amountOfOptionChecks*optionCheckTime);
</script>
</body>
</html>