Browse Source

Merge remote-tracking branch 'upstream/develop' into develop

Conflicts:
	vis.js
	vis.min.js
css_transitions
Eric Gillingham 11 years ago
parent
commit
447a08d2d0
28 changed files with 13131 additions and 11026 deletions
  1. +12
    -0
      HISTORY.md
  2. +4
    -1
      Jakefile.js
  3. +1
    -1
      bower.json
  4. +6
    -0
      docs/css/style.css
  5. +2
    -4
      docs/graph.html
  6. BIN
      docs/img/vis_overview.odg
  7. BIN
      docs/img/vis_overview.png
  8. +7
    -0
      docs/index.html
  9. +7
    -0
      docs/timeline.html
  10. +28
    -29
      examples/graph/17_network_info.html
  11. +5
    -1
      examples/timeline/02_dataset.html
  12. +3
    -0
      examples/timeline/03_much_data.html
  13. +3
    -0
      examples/timeline/05_groups.html
  14. +3
    -2
      package.json
  15. +305
    -334
      src/graph/Graph.js
  16. +5
    -0
      src/graph/Node.js
  17. +5
    -1
      src/module/imports.js
  18. +252
    -0
      src/shim.js
  19. +1
    -0
      src/timeline/Range.js
  20. +19
    -0
      src/timeline/Timeline.js
  21. +101
    -0
      src/timeline/component/CurrentTime.js
  22. +0
    -1
      src/timeline/component/Group.js
  23. +1
    -1
      src/timeline/component/ItemSet.js
  24. +5
    -0
      src/timeline/component/css/currenttime.css
  25. +1
    -1
      src/timeline/component/item/ItemRange.js
  26. +0
    -228
      src/util.js
  27. +12341
    -10421
      vis.js
  28. +14
    -1
      vis.min.js

+ 12
- 0
HISTORY.md View File

@ -1,6 +1,18 @@
vis.js history vis.js history
http://visjs.org http://visjs.org
## <not yet released>, version 0.3.0
- Implemented option `showCurrentTime`, displaying a red, vertical bar at
current time. Thanks fi0dor.
## 2013-09-20, version 0.2.0
- Implemented full touch support for Graph.
- Fixed initial empty range in the Timeline in case of a single item.
- Fixed field `className` not working for items.
## 2013-06-20, version 0.1.0 ## 2013-06-20, version 0.1.0

+ 4
- 1
Jakefile.js View File

@ -34,7 +34,8 @@ task('build', {async: true}, function () {
'./src/timeline/component/css/groupset.css', './src/timeline/component/css/groupset.css',
'./src/timeline/component/css/itemset.css', './src/timeline/component/css/itemset.css',
'./src/timeline/component/css/item.css', './src/timeline/component/css/item.css',
'./src/timeline/component/css/timeaxis.css'
'./src/timeline/component/css/timeaxis.css',
'./src/timeline/component/css/currenttime.css'
], ],
header: '/* vis.js stylesheet */', header: '/* vis.js stylesheet */',
separator: '\n' separator: '\n'
@ -47,6 +48,7 @@ task('build', {async: true}, function () {
src: [ src: [
'./src/module/imports.js', './src/module/imports.js',
'./src/shim.js',
'./src/util.js', './src/util.js',
'./src/events.js', './src/events.js',
'./src/EventBus.js', './src/EventBus.js',
@ -61,6 +63,7 @@ task('build', {async: true}, function () {
'./src/timeline/component/Panel.js', './src/timeline/component/Panel.js',
'./src/timeline/component/RootPanel.js', './src/timeline/component/RootPanel.js',
'./src/timeline/component/TimeAxis.js', './src/timeline/component/TimeAxis.js',
'./src/timeline/component/CurrentTime.js',
'./src/timeline/component/ItemSet.js', './src/timeline/component/ItemSet.js',
'./src/timeline/component/item/*.js', './src/timeline/component/item/*.js',
'./src/timeline/component/Group.js', './src/timeline/component/Group.js',

+ 1
- 1
bower.json View File

@ -1,6 +1,6 @@
{ {
"name": "vis", "name": "vis",
"version": "0.2.0-SNAPSHOT",
"version": "0.3.0-SNAPSHOT",
"description": "A dynamic, browser-based visualization library.", "description": "A dynamic, browser-based visualization library.",
"homepage": "http://visjs.org/", "homepage": "http://visjs.org/",
"repository": { "repository": {

+ 6
- 0
docs/css/style.css View File

@ -39,13 +39,19 @@ h3 {
font-size: 140%; font-size: 140%;
} }
a > img {
border: none;
}
a { a {
color: #2B7CE9; color: #2B7CE9;
text-decoration: none; text-decoration: none;
} }
a:visited { a:visited {
color: #2E60A4; color: #2E60A4;
} }
a:hover { a:hover {
color: red; color: red;
text-decoration: underline; text-decoration: underline;

+ 2
- 4
docs/graph.html View File

@ -831,10 +831,8 @@ var options = {
<td>selectable</td> <td>selectable</td>
<td>Boolean</td> <td>Boolean</td>
<td>true</td> <td>true</td>
<td>If true, nodes in the graph can be selected by clicking them, or
by keeping the <code>Shift</code> key down and dragging a selection area around them.
When the <code>Ctrl</code> key is down, the new selection is appended to the
previous selection. If not, the new selection replaces the previous selection.</td>
<td>If true, nodes in the graph can be selected by clicking them.
Long press can be used to select multiple nodes.</td>
</tr> </tr>
<tr> <tr>

BIN
docs/img/vis_overview.odg View File


BIN
docs/img/vis_overview.png View File

Before After
Width: 960  |  Height: 912  |  Size: 64 KiB Width: 768  |  Height: 912  |  Size: 48 KiB

+ 7
- 0
docs/index.html View File

@ -55,6 +55,13 @@
</li> </li>
</ul> </ul>
<div style="text-align: center;">
<a href="img/vis_overview.png" target="_blank">
<img src="img/vis_overview.png" style="width: 250px; "/><br>
(click for a larger view)
</a>
</div>
<h2 id="Install">Install</h2> <h2 id="Install">Install</h2>

+ 7
- 0
docs/timeline.html View File

@ -425,6 +425,13 @@ var options = {
of item ranges. Must correspond with the css of item ranges.</td> of item ranges. Must correspond with the css of item ranges.</td>
</tr> </tr>
<tr>
<td>showCurrentTime</td>
<td>boolean</td>
<td>false</td>
<td>Show a vertical bar at the current time.</td>
</tr>
<tr> <tr>
<td>showMajorLabels</td> <td>showMajorLabels</td>
<td>boolean</td> <td>boolean</td>

+ 28
- 29
examples/graph/17_network_info.html View File

@ -24,16 +24,14 @@
var edges = null; var edges = null;
var graph = null; var graph = null;
var DIR = 'img/refresh-cl/',
SWITCH = DIR + 'Network-Pipe-icon.png',
COMPUTER = DIR + 'Hardware-My-Computer-3-icon.png',
PRINTER = DIR + 'Hardware-Printer-Blue-icon.png',
LENGTH_MAIN = 350,
var LENGTH_MAIN = 350,
LENGTH_SERVER = 150, LENGTH_SERVER = 150,
LENGTH_SUB = 50, LENGTH_SUB = 50,
WIDTH_SCALE = 2,
GREEN = 'green', GREEN = 'green',
RED = '#FA0A10',
RED = '#C5000B',
ORANGE = 'orange', ORANGE = 'orange',
//GRAY = '#666666',
GRAY = 'gray', GRAY = 'gray',
BLACK = '#2B1B17'; BLACK = '#2B1B17';
@ -48,13 +46,13 @@
nodes.push({id: 1, label: '192.168.0.1', group: 'switch', value: 10}); nodes.push({id: 1, label: '192.168.0.1', group: 'switch', value: 10});
nodes.push({id: 2, label: '192.168.0.2', group: 'switch', value: 8}); nodes.push({id: 2, label: '192.168.0.2', group: 'switch', value: 8});
nodes.push({id: 3, label: '192.168.0.3', group: 'switch', value: 6}); nodes.push({id: 3, label: '192.168.0.3', group: 'switch', value: 6});
edges.push({from: 1, to: 2, length: LENGTH_MAIN, width: 6, label: '0.71 mbps'});
edges.push({from: 1, to: 3, length: LENGTH_MAIN, width: 4, label: '0.55 mbps'});
edges.push({from: 1, to: 2, length: LENGTH_MAIN, width: WIDTH_SCALE * 6, label: '0.71 mbps'});
edges.push({from: 1, to: 3, length: LENGTH_MAIN, width: WIDTH_SCALE * 4, label: '0.55 mbps'});
// group around 2 // group around 2
for (var i = 100; i <= 104; i++) { for (var i = 100; i <= 104; i++) {
var value = 1; var value = 1;
var width = 1;
var width = WIDTH_SCALE * 2;
var color = GRAY; var color = GRAY;
var label = null; var label = null;
@ -71,37 +69,38 @@
edges.push({from: 2, to: i, length: LENGTH_SUB, color: color, fontColor: color, width: width, label: label}); edges.push({from: 2, to: i, length: LENGTH_SUB, color: color, fontColor: color, width: width, label: label});
} }
nodes.push({id: 201, label: '192.168.0.201', group: 'desktop', value: 1}); nodes.push({id: 201, label: '192.168.0.201', group: 'desktop', value: 1});
edges.push({from: 2, to: 201, length: LENGTH_SUB, color: GRAY});
edges.push({from: 2, to: 201, length: LENGTH_SUB, color: GRAY, width: WIDTH_SCALE});
// group around 3 // group around 3
nodes.push({id: 202, label: '192.168.0.202', group: 'desktop', value: 4}); nodes.push({id: 202, label: '192.168.0.202', group: 'desktop', value: 4});
edges.push({from: 3, to: 202, length: LENGTH_SUB, color: GRAY, width: 2});
edges.push({from: 3, to: 202, length: LENGTH_SUB, color: GRAY, width: WIDTH_SCALE * 2});
for (var i = 230; i <= 231; i++ ) { for (var i = 230; i <= 231; i++ ) {
nodes.push({id: i, label: '192.168.0.' + i, group: 'mobile', value: 2}); nodes.push({id: i, label: '192.168.0.' + i, group: 'mobile', value: 2});
edges.push({from: 3, to: i, length: LENGTH_SUB, color: GRAY, fontColor: GRAY, width: 1});
edges.push({from: 3, to: i, length: LENGTH_SUB, color: GRAY, fontColor: GRAY, width: WIDTH_SCALE});
} }
// group around 1 // group around 1
nodes.push({id: 10, label: '192.168.0.10', group: 'server', value: 10}); nodes.push({id: 10, label: '192.168.0.10', group: 'server', value: 10});
edges.push({from: 1, to: 10, length: LENGTH_SERVER, color: GRAY, width: 6, label: '0.92 mbps'});
edges.push({from: 1, to: 10, length: LENGTH_SERVER, color: GRAY, width: WIDTH_SCALE * 6, label: '0.92 mbps'});
nodes.push({id: 11, label: '192.168.0.11', group: 'server', value: 7}); nodes.push({id: 11, label: '192.168.0.11', group: 'server', value: 7});
edges.push({from: 1, to: 11, length: LENGTH_SERVER, color: GRAY, width: 3, label: '0.68 mbps'});
edges.push({from: 1, to: 11, length: LENGTH_SERVER, color: GRAY, width: WIDTH_SCALE * 3, label: '0.68 mbps'});
nodes.push({id: 12, label: '192.168.0.12', group: 'server', value: 3}); nodes.push({id: 12, label: '192.168.0.12', group: 'server', value: 3});
edges.push({from: 1, to: 12, length: LENGTH_SERVER, color: GRAY, label: '0.3 mbps'});
edges.push({from: 1, to: 12, length: LENGTH_SERVER, color: GRAY, width: WIDTH_SCALE, label: '0.3 mbps'});
nodes.push({id: 204, label: 'Internet', group: 'internet', value: 10}); nodes.push({id: 204, label: 'Internet', group: 'internet', value: 10});
edges.push({from: 1, to: 204, length: 200, width: 3, label: '0.63 mbps'});
edges.push({from: 1, to: 204, length: 200, width: WIDTH_SCALE * 3, label: '0.63 mbps'});
// legend // legend
var mygraph = document.getElementById('mygraph'); var mygraph = document.getElementById('mygraph');
var x = - mygraph.clientWidth / 2 + 50; var x = - mygraph.clientWidth / 2 + 50;
var y = - mygraph.clientHeight / 2 + 20;
nodes.push({id: 1000, x: x, y: y + 0, label: 'Internet', group: 'internet'});
nodes.push({id: 1001, x: x, y: y + 50, label: 'Switch', group: 'switch'});
nodes.push({id: 1002, x: x, y: y + 100, label: 'Server', group: 'server'});
nodes.push({id: 1003, x: x, y: y + 150, label: 'Computer', group: 'desktop'});
nodes.push({id: 1004, x: x, y: y + 200, label: 'Smartphone', group: 'mobile'});
var y = - mygraph.clientHeight / 2 + 50;
var step = 70;
nodes.push({id: 1000, x: x, y: y, label: 'Internet', group: 'internet', value: 1});
nodes.push({id: 1001, x: x, y: y + step, label: 'Switch', group: 'switch', value: 1});
nodes.push({id: 1002, x: x, y: y + 2 * step, label: 'Server', group: 'server', value: 1});
nodes.push({id: 1003, x: x, y: y + 3 * step, label: 'Computer', group: 'desktop', value: 1});
nodes.push({id: 1004, x: x, y: y + 4 * step, label: 'Smartphone', group: 'mobile', value: 1});
// create a graph // create a graph
var container = document.getElementById('mygraph'); var container = document.getElementById('mygraph');
@ -112,8 +111,8 @@
var options = { var options = {
stabilize: false, // stabilize positions before displaying stabilize: false, // stabilize positions before displaying
nodes: { nodes: {
widthMin: 24,
maxWidth: 64,
radiusMin: 16,
radiusMax: 32,
fontColor: BLACK fontColor: BLACK
}, },
edges: { edges: {
@ -122,23 +121,23 @@
groups: { groups: {
'switch': { 'switch': {
shape: 'triangle', shape: 'triangle',
color: vis.graph.Groups.DEFAULT[1] // yellow
color: '#FF9900' // orange
}, },
desktop: { desktop: {
shape: 'dot', shape: 'dot',
color: vis.graph.Groups.DEFAULT[0] // blue
color: "#2B7CE9" // blue
}, },
mobile: { mobile: {
shape: 'dot', shape: 'dot',
color: vis.graph.Groups.DEFAULT[4] // purple
color: "#5A1E5C" // purple
}, },
server: { server: {
shape: 'square', shape: 'square',
color: vis.graph.Groups.DEFAULT[2] // red
color: "#C5000B" // red
}, },
internet: { internet: {
shape: 'square', shape: 'square',
color: vis.graph.Groups.DEFAULT[3] // green
color: "#109618" // green
} }
} }
}; };

+ 5
- 1
examples/timeline/02_dataset.html View File

@ -19,6 +19,9 @@
} }
</style> </style>
<!-- note: moment.js must be loaded before vis.js, else vis.js uses its embedded version of moment.js -->
<script src="http://cdnjs.cloudflare.com/ajax/libs/moment.js/2.3.1/moment.min.js"></script>
<script src="../../vis.js"></script> <script src="../../vis.js"></script>
</head> </head>
<body> <body>
@ -48,7 +51,8 @@
start: now.clone().add('days', -3), start: now.clone().add('days', -3),
end: now.clone().add('days', 7), end: now.clone().add('days', 7),
orientation: 'top', orientation: 'top',
height: '100%'
height: '100%',
showCurrentTime: true
}; };
var timeline = new vis.Timeline(container, items, options); var timeline = new vis.Timeline(container, items, options);

+ 3
- 0
examples/timeline/03_much_data.html View File

@ -10,6 +10,9 @@
} }
</style> </style>
<!-- note: moment.js must be loaded before vis.js, else vis.js uses its embedded version of moment.js -->
<script src="http://cdnjs.cloudflare.com/ajax/libs/moment.js/2.3.1/moment.min.js"></script>
<script src="../../vis.js"></script> <script src="../../vis.js"></script>
</head> </head>
<body> <body>

+ 3
- 0
examples/timeline/05_groups.html View File

@ -16,6 +16,9 @@
} }
</style> </style>
<!-- note: moment.js must be loaded before vis.js, else vis.js uses its embedded version of moment.js -->
<script src="http://cdnjs.cloudflare.com/ajax/libs/moment.js/2.3.1/moment.min.js"></script>
<script src="../../vis.js"></script> <script src="../../vis.js"></script>
</head> </head>
<body> <body>

+ 3
- 2
package.json View File

@ -1,6 +1,6 @@
{ {
"name": "vis", "name": "vis",
"version": "0.2.0-SNAPSHOT",
"version": "0.3.0-SNAPSHOT",
"description": "A dynamic, browser-based visualization library.", "description": "A dynamic, browser-based visualization library.",
"homepage": "http://visjs.org/", "homepage": "http://visjs.org/",
"repository": { "repository": {
@ -28,6 +28,7 @@
"jake": "latest", "jake": "latest",
"jake-utils": "latest", "jake-utils": "latest",
"browserify": "latest", "browserify": "latest",
"moment": "latest"
"moment": "latest",
"hammerjs": "latest"
} }
} }

+ 305
- 334
src/graph/Graph.js View File

@ -163,7 +163,6 @@ Graph.prototype.setData = function(data) {
this._setEdges(data && data.edges); this._setEdges(data && data.edges);
} }
// find a stable position or start animating to a stable position // find a stable position or start animating to a stable position
if (this.stabilize) { if (this.stabilize) {
this._doStabilize(); this._doStabilize();
@ -251,7 +250,7 @@ Graph.prototype.setOptions = function (options) {
/** /**
* fire an event * fire an event
* @param {String} event The name of an event, for example "select"
* @param {String} event The name of an event, for example 'select'
* @param {Object} params Optional object with event parameters * @param {Object} params Optional object with event parameters
* @private * @private
*/ */
@ -273,155 +272,169 @@ Graph.prototype._create = function () {
this.containerElement.removeChild(this.containerElement.firstChild); this.containerElement.removeChild(this.containerElement.firstChild);
} }
this.frame = document.createElement("div");
this.frame.className = "graph-frame";
this.frame.style.position = "relative";
this.frame.style.overflow = "hidden";
this.frame = document.createElement('div');
this.frame.className = 'graph-frame';
this.frame.style.position = 'relative';
this.frame.style.overflow = 'hidden';
// create the graph canvas (HTML canvas element) // create the graph canvas (HTML canvas element)
this.frame.canvas = document.createElement( "canvas" );
this.frame.canvas.style.position = "relative";
this.frame.canvas = document.createElement( 'canvas' );
this.frame.canvas.style.position = 'relative';
this.frame.appendChild(this.frame.canvas); this.frame.appendChild(this.frame.canvas);
if (!this.frame.canvas.getContext) { if (!this.frame.canvas.getContext) {
var noCanvas = document.createElement( "DIV" );
noCanvas.style.color = "red";
noCanvas.style.fontWeight = "bold" ;
noCanvas.style.padding = "10px";
noCanvas.innerHTML = "Error: your browser does not support HTML canvas";
var noCanvas = document.createElement( 'DIV' );
noCanvas.style.color = 'red';
noCanvas.style.fontWeight = 'bold' ;
noCanvas.style.padding = '10px';
noCanvas.innerHTML = 'Error: your browser does not support HTML canvas';
this.frame.canvas.appendChild(noCanvas); this.frame.canvas.appendChild(noCanvas);
} }
// create event listeners
var me = this; var me = this;
var onmousedown = function (event) {me._onMouseDown(event);};
var onmousemove = function (event) {me._onMouseMoveTitle(event);};
var onmousewheel = function (event) {me._onMouseWheel(event);};
var ontouchstart = function (event) {me._onTouchStart(event);};
vis.util.addEventListener(this.frame.canvas, "mousedown", onmousedown);
vis.util.addEventListener(this.frame.canvas, "mousemove", onmousemove);
vis.util.addEventListener(this.frame.canvas, "mousewheel", onmousewheel);
vis.util.addEventListener(this.frame.canvas, "touchstart", ontouchstart);
this.drag = {};
this.pinch = {};
this.hammer = Hammer(this.frame.canvas, {
prevent_default: true
});
this.hammer.on('tap', me._onTap.bind(me) );
this.hammer.on('hold', me._onHold.bind(me) );
this.hammer.on('pinch', me._onPinch.bind(me) );
this.hammer.on('touch', me._onTouch.bind(me) );
this.hammer.on('dragstart', me._onDragStart.bind(me) );
this.hammer.on('drag', me._onDrag.bind(me) );
this.hammer.on('dragend', me._onDragEnd.bind(me) );
this.hammer.on('mousewheel',me._onMouseWheel.bind(me) );
this.hammer.on('DOMMouseScroll',me._onMouseWheel.bind(me) ); // for FF
this.hammer.on('mousemove', me._onMouseMoveTitle.bind(me) );
// add the frame to the container element // add the frame to the container element
this.containerElement.appendChild(this.frame); this.containerElement.appendChild(this.frame);
}; };
/** /**
* handle on mouse down event
*
* @param {{x: Number, y: Number}} pointer
* @return {Number | null} node
* @private * @private
*/ */
Graph.prototype._onMouseDown = function (event) {
event = event || window.event;
if (!this.selectable) {
return;
}
// check if mouse is still down (may be up when focus is lost for example
// in an iframe)
if (this.leftButtonDown) {
this._onMouseUp(event);
}
// only react on left mouse button down
this.leftButtonDown = event.which ? (event.which == 1) : (event.button == 1);
if (!this.leftButtonDown && !this.touchDown) {
return;
}
// add event listeners to handle moving the contents
// we store the function onmousemove and onmouseup in the timeline, so we can
// remove the eventlisteners lateron in the function mouseUp()
var me = this;
if (!this.onmousemove) {
this.onmousemove = function (event) {me._onMouseMove(event);};
vis.util.addEventListener(document, "mousemove", me.onmousemove);
}
if (!this.onmouseup) {
this.onmouseup = function (event) {me._onMouseUp(event);};
vis.util.addEventListener(document, "mouseup", me.onmouseup);
}
vis.util.preventDefault(event);
// store the start x and y position of the mouse
this.startMouseX = util.getPageX(event);
this.startMouseY = util.getPageY(event);
this.startFrameLeft = vis.util.getAbsoluteLeft(this.frame.canvas);
this.startFrameTop = vis.util.getAbsoluteTop(this.frame.canvas);
this.startTranslation = this._getTranslation();
this.ctrlKeyDown = event.ctrlKey;
this.shiftKeyDown = event.shiftKey;
Graph.prototype._getNodeAt = function (pointer) {
var x = this._canvasToX(pointer.x);
var y = this._canvasToY(pointer.y);
var obj = { var obj = {
left: this._xToCanvas(this.startMouseX - this.startFrameLeft),
top: this._yToCanvas(this.startMouseY - this.startFrameTop),
right: this._xToCanvas(this.startMouseX - this.startFrameLeft),
bottom: this._yToCanvas(this.startMouseY - this.startFrameTop)
left: x,
top: y,
right: x,
bottom: y
}; };
var overlappingNodes = this._getNodesOverlappingWith(obj);
// if there are overlapping nodes, select the last one, this is the // if there are overlapping nodes, select the last one, this is the
// one which is drawn on top of the others // one which is drawn on top of the others
this.startClickedObj = (overlappingNodes.length > 0) ?
overlappingNodes[overlappingNodes.length - 1] : undefined;
if (this.startClickedObj) {
// move clicked node with the mouse
// make the clicked node temporarily fixed, and store their original state
var node = this.nodes[this.startClickedObj];
this.startClickedObj.xFixed = node.xFixed;
this.startClickedObj.yFixed = node.yFixed;
node.xFixed = true;
node.yFixed = true;
if (!this.ctrlKeyDown || !node.isSelected()) {
// select this node
this._selectNodes([this.startClickedObj], this.ctrlKeyDown);
}
else {
// unselect this node
this._unselectNodes([this.startClickedObj]);
}
var overlappingNodes = this._getNodesOverlappingWith(obj);
return (overlappingNodes.length > 0) ?
overlappingNodes[overlappingNodes.length - 1] : null;
};
if (!this.moving) {
this._redraw();
/**
* Get the pointer location from a touch location
* @param {{pageX: Number, pageY: Number}} touch
* @return {{x: Number, y: Number}} pointer
* @private
*/
Graph.prototype._getPointer = function (touch) {
return {
x: touch.pageX - vis.util.getAbsoluteLeft(this.frame.canvas),
y: touch.pageY - vis.util.getAbsoluteTop(this.frame.canvas)
};
};
/**
* On start of a touch gesture, store the pointer
* @param event
* @private
*/
Graph.prototype._onTouch = function (event) {
this.drag.pointer = this._getPointer(event.gesture.touches[0]);
this.drag.pinched = false;
this.pinch.scale = this._getScale();
};
/**
* handle drag start event
* @private
*/
Graph.prototype._onDragStart = function () {
var drag = this.drag;
drag.selection = [];
drag.translation = this._getTranslation();
drag.nodeId = this._getNodeAt(drag.pointer);
// note: drag.pointer is set in _onTouch to get the initial touch location
var node = this.nodes[drag.nodeId];
if (node) {
// select the clicked node if not yet selected
if (!node.isSelected()) {
this._selectNodes([drag.nodeId]);
} }
}
else if (this.shiftKeyDown) {
// start selection of multiple nodes
}
else {
// start moving the graph
this.moved = false;
// create an array with the selected nodes and their original location and status
var me = this;
this.selection.forEach(function (id) {
var node = me.nodes[id];
if (node) {
var s = {
id: id,
node: node,
// store original x, y, xFixed and yFixed, make the node temporarily Fixed
x: node.x,
y: node.y,
xFixed: node.xFixed,
yFixed: node.yFixed
};
node.xFixed = true;
node.yFixed = true;
drag.selection.push(s);
}
});
} }
}; };
/** /**
* handle on mouse move event
* @param {Event} event
* handle drag event
* @private * @private
*/ */
Graph.prototype._onMouseMove = function (event) {
event = event || window.event;
if (!this.selectable) {
Graph.prototype._onDrag = function (event) {
if (this.drag.pinched) {
return; return;
} }
var mouseX = util.getPageX(event);
var mouseY = util.getPageY(event);
this.mouseX = mouseX;
this.mouseY = mouseY;
var pointer = this._getPointer(event.gesture.touches[0]);
if (this.startClickedObj) {
var node = this.nodes[this.startClickedObj];
var me = this,
drag = this.drag,
selection = drag.selection;
if (selection && selection.length) {
// calculate delta's and new location
var deltaX = pointer.x - drag.pointer.x,
deltaY = pointer.y - drag.pointer.y;
if (!this.startClickedObj.xFixed)
node.x = this._xToCanvas(mouseX - this.startFrameLeft);
// update position of all selected nodes
selection.forEach(function (s) {
var node = s.node;
if (!this.startClickedObj.yFixed)
node.y = this._yToCanvas(mouseY - this.startFrameTop);
if (!s.xFixed) {
node.x = me._canvasToX(me._xToCanvas(s.x) + deltaX);
}
if (!s.yFixed) {
node.y = me._canvasToY(me._yToCanvas(s.y) + deltaY);
}
});
// start animation if not yet running // start animation if not yet running
if (!this.moving) { if (!this.moving) {
@ -429,119 +442,140 @@ Graph.prototype._onMouseMove = function (event) {
this.start(); this.start();
} }
} }
else if (this.shiftKeyDown) {
// draw a rect from start mouse location to current mouse location
if (this.frame.selRect == undefined) {
this.frame.selRect = document.createElement("DIV");
this.frame.appendChild(this.frame.selRect);
this.frame.selRect.style.position = "absolute";
this.frame.selRect.style.border = "1px dashed red";
}
var left = Math.min(this.startMouseX, mouseX) - this.startFrameLeft;
var top = Math.min(this.startMouseY, mouseY) - this.startFrameTop;
var right = Math.max(this.startMouseX, mouseX) - this.startFrameLeft;
var bottom = Math.max(this.startMouseY, mouseY) - this.startFrameTop;
this.frame.selRect.style.left = left + "px";
this.frame.selRect.style.top = top + "px";
this.frame.selRect.style.width = (right - left) + "px";
this.frame.selRect.style.height = (bottom - top) + "px";
}
else { else {
// move the graph // move the graph
var diffX = mouseX - this.startMouseX;
var diffY = mouseY - this.startMouseY;
var diffX = pointer.x - this.drag.pointer.x;
var diffY = pointer.y - this.drag.pointer.y;
this._setTranslation( this._setTranslation(
this.startTranslation.x + diffX,
this.startTranslation.y + diffY);
this.drag.translation.x + diffX,
this.drag.translation.y + diffY);
this._redraw(); this._redraw();
this.moved = true; this.moved = true;
} }
};
vis.util.preventDefault(event);
/**
* handle drag start event
* @private
*/
Graph.prototype._onDragEnd = function () {
var selection = this.drag.selection;
if (selection) {
selection.forEach(function (s) {
// restore original xFixed and yFixed
s.node.xFixed = s.xFixed;
s.node.yFixed = s.yFixed;
});
}
}; };
/** /**
* handle on mouse up event
* @param {Event} event
* handle tap/click event: select/unselect a node
* @private * @private
*/ */
Graph.prototype._onMouseUp = function (event) {
event = event || window.event;
Graph.prototype._onTap = function (event) {
var pointer = this._getPointer(event.gesture.touches[0]);
if (!this.selectable) {
return;
}
var nodeId = this._getNodeAt(pointer);
var node = this.nodes[nodeId];
if (node) {
// select this node
this._selectNodes([nodeId]);
// remove event listeners here, important for Safari
if (this.onmousemove) {
vis.util.removeEventListener(document, "mousemove", this.onmousemove);
this.onmousemove = undefined;
if (!this.moving) {
this._redraw();
}
} }
if (this.onmouseup) {
vis.util.removeEventListener(document, "mouseup", this.onmouseup);
this.onmouseup = undefined;
else {
// remove selection
this._unselectNodes();
this._redraw();
} }
vis.util.preventDefault(event);
// check selected nodes
var endMouseX = util.getPageX(event) || this.mouseX || 0;
var endMouseY = util.getPageY(event) || this.mouseY || 0;
};
var ctrlKey = event ? event.ctrlKey : window.event.ctrlKey;
/**
* handle long tap event: multi select nodes
* @private
*/
Graph.prototype._onHold = function (event) {
var pointer = this._getPointer(event.gesture.touches[0]);
var nodeId = this._getNodeAt(pointer);
var node = this.nodes[nodeId];
if (node) {
if (!node.isSelected()) {
// select this node, keep previous selection
var append = true;
this._selectNodes([nodeId], append);
}
else {
this._unselectNodes([nodeId]);
}
if (this.startClickedObj) {
// restore the original fixed state
var node = this.nodes[this.startClickedObj];
node.xFixed = this.startClickedObj.xFixed;
node.yFixed = this.startClickedObj.yFixed;
}
else if (this.shiftKeyDown) {
// select nodes inside selection area
var obj = {
"left": this._xToCanvas(Math.min(this.startMouseX, endMouseX) - this.startFrameLeft),
"top": this._yToCanvas(Math.min(this.startMouseY, endMouseY) - this.startFrameTop),
"right": this._xToCanvas(Math.max(this.startMouseX, endMouseX) - this.startFrameLeft),
"bottom": this._yToCanvas(Math.max(this.startMouseY, endMouseY) - this.startFrameTop)
};
var overlappingNodes = this._getNodesOverlappingWith(obj);
this._selectNodes(overlappingNodes, ctrlKey);
this.redraw();
// remove the selection rectangle
if (this.frame.selRect) {
this.frame.removeChild(this.frame.selRect);
this.frame.selRect = undefined;
if (!this.moving) {
this._redraw();
} }
} }
else { else {
if (!this.ctrlKeyDown && !this.moved) {
// remove selection
this._unselectNodes();
this._redraw();
}
// Do nothing
} }
};
/**
* Handle pinch event
* @param event
* @private
*/
Graph.prototype._onPinch = function (event) {
var pointer = this._getPointer(event.gesture.center);
this.leftButtonDown = false;
this.ctrlKeyDown = false;
this.drag.pinched = true;
if (!('scale' in this.pinch)) {
this.pinch.scale = 1;
}
// TODO: enable moving while pinching?
var scale = this.pinch.scale * event.gesture.scale;
this._zoom(scale, pointer)
}; };
/**
* Zoom the graph in or out
* @param {Number} scale a number around 1, and between 0.01 and 10
* @param {{x: Number, y: Number}} pointer
* @return {Number} appliedScale scale is limited within the boundaries
* @private
*/
Graph.prototype._zoom = function(scale, pointer) {
var scaleOld = this._getScale();
if (scale < 0.01) {
scale = 0.01;
}
if (scale > 10) {
scale = 10;
}
var translation = this._getTranslation();
var scaleFrac = scale / scaleOld;
var tx = (1 - scaleFrac) * pointer.x + translation.x * scaleFrac;
var ty = (1 - scaleFrac) * pointer.y + translation.y * scaleFrac;
this._setScale(scale);
this._setTranslation(tx, ty);
this._redraw();
return scale;
};
/** /**
* Event handler for mouse wheel event, used to zoom the timeline * Event handler for mouse wheel event, used to zoom the timeline
* Code from http://adomas.org/javascript-mouse-wheel/
* @param {Event} event
* See http://adomas.org/javascript-mouse-wheel/
* https://github.com/EightMedia/hammer.js/issues/256
* @param {MouseEvent} event
* @private * @private
*/ */
Graph.prototype._onMouseWheel = function(event) { Graph.prototype._onMouseWheel = function(event) {
event = event || window.event;
var mouseX = util.getPageX(event);
var mouseY = util.getPageY(event);
// retrieve delta // retrieve delta
var delta = 0; var delta = 0;
if (event.wheelDelta) { /* IE/Opera. */ if (event.wheelDelta) { /* IE/Opera. */
@ -556,41 +590,31 @@ Graph.prototype._onMouseWheel = function(event) {
// Basically, delta is now positive if wheel was scrolled up, // Basically, delta is now positive if wheel was scrolled up,
// and negative, if wheel was scrolled down. // and negative, if wheel was scrolled down.
if (delta) { if (delta) {
// determine zoom factor, and adjust the zoom factor such that zooming in
// and zooming out correspond wich each other
if (!('mouswheelScale' in this.pinch)) {
this.pinch.mouswheelScale = 1;
}
// calculate the new scale
var scale = this.pinch.mouswheelScale;
var zoom = delta / 10; var zoom = delta / 10;
if (delta < 0) { if (delta < 0) {
zoom = zoom / (1 - zoom); zoom = zoom / (1 - zoom);
} }
scale *= (1 + zoom);
var scaleOld = this._getScale();
var scaleNew = scaleOld * (1 + zoom);
if (scaleNew < 0.01) {
scaleNew = 0.01;
}
if (scaleNew > 10) {
scaleNew = 10;
}
// calculate the pointer location
var gesture = Hammer.event.collectEventData(this, 'scroll', event);
var pointer = this._getPointer(gesture.center);
var frameLeft = vis.util.getAbsoluteLeft(this.frame.canvas);
var frameTop = vis.util.getAbsoluteTop(this.frame.canvas);
var x = mouseX - frameLeft;
var y = mouseY - frameTop;
// apply the new scale
scale = this._zoom(scale, pointer);
var translation = this._getTranslation();
var scaleFrac = scaleNew / scaleOld;
var tx = (1 - scaleFrac) * x + translation.x * scaleFrac;
var ty = (1 - scaleFrac) * y + translation.y * scaleFrac;
this._setScale(scaleNew);
this._setTranslation(tx, ty);
this._redraw();
// store the new, applied scale
this.pinch.mouswheelScale = scale;
} }
// Prevent default actions caused by mouse wheel. // Prevent default actions caused by mouse wheel.
// That might be ugly, but we handle scrolls somehow
// anyway, so don't bother here...
vis.util.preventDefault(event);
event.preventDefault();
}; };
@ -600,26 +624,19 @@ Graph.prototype._onMouseWheel = function(event) {
* @private * @private
*/ */
Graph.prototype._onMouseMoveTitle = function (event) { Graph.prototype._onMouseMoveTitle = function (event) {
event = event || window.event;
var startMouseX = util.getPageX(event);
var startMouseY = util.getPageY(event);
this.startFrameLeft = this.startFrameLeft || vis.util.getAbsoluteLeft(this.frame.canvas);
this.startFrameTop = this.startFrameTop || vis.util.getAbsoluteTop(this.frame.canvas);
var x = startMouseX - this.startFrameLeft;
var y = startMouseY - this.startFrameTop;
var gesture = Hammer.event.collectEventData(this, 'mousemove', event);
var pointer = this._getPointer(gesture.center);
// check if the previously selected node is still selected // check if the previously selected node is still selected
if (this.popupNode) { if (this.popupNode) {
this._checkHidePopup(x, y);
this._checkHidePopup(pointer);
} }
// start a timeout that will check if the mouse is positioned above // start a timeout that will check if the mouse is positioned above
// an element // an element
var me = this; var me = this;
var checkShow = function() { var checkShow = function() {
me._checkShowPopup(x, y);
me._checkShowPopup(pointer);
}; };
if (this.popupTimer) { if (this.popupTimer) {
clearInterval(this.popupTimer); // stop any running timer clearInterval(this.popupTimer); // stop any running timer
@ -634,16 +651,15 @@ Graph.prototype._onMouseMoveTitle = function (event) {
* (a node or edge). If so, and if this element has a title, * (a node or edge). If so, and if this element has a title,
* show a popup window with its title. * show a popup window with its title.
* *
* @param {number} x
* @param {number} y
* @param {{x:Number, y:Number}} pointer
* @private * @private
*/ */
Graph.prototype._checkShowPopup = function (x, y) {
Graph.prototype._checkShowPopup = function (pointer) {
var obj = { var obj = {
"left" : this._xToCanvas(x),
"top" : this._yToCanvas(y),
"right" : this._xToCanvas(x),
"bottom" : this._yToCanvas(y)
left: this._canvasToX(pointer.x),
top: this._canvasToY(pointer.y),
right: this._canvasToX(pointer.x),
bottom: this._canvasToY(pointer.y)
}; };
var id; var id;
@ -689,7 +705,7 @@ Graph.prototype._checkShowPopup = function (x, y) {
// adjust a small offset such that the mouse cursor is located in the // adjust a small offset such that the mouse cursor is located in the
// bottom left location of the popup, and you can easily move over the // bottom left location of the popup, and you can easily move over the
// popup area // popup area
me.popup.setPosition(x - 3, y - 3);
me.popup.setPosition(pointer.x - 3, pointer.y - 3);
me.popup.setText(me.popupNode.getTitle()); me.popup.setText(me.popupNode.getTitle());
me.popup.show(); me.popup.show();
} }
@ -704,19 +720,11 @@ Graph.prototype._checkShowPopup = function (x, y) {
/** /**
* Check if the popup must be hided, which is the case when the mouse is no * Check if the popup must be hided, which is the case when the mouse is no
* longer hovering on the object * longer hovering on the object
* @param {number} x
* @param {number} y
* @param {{x:Number, y:Number}} pointer
* @private * @private
*/ */
Graph.prototype._checkHidePopup = function (x, y) {
var obj = {
"left" : x,
"top" : y,
"right" : x,
"bottom" : y
};
if (!this.popupNode || !this.popupNode.isOverlappingWith(obj) ) {
Graph.prototype._checkHidePopup = function (pointer) {
if (!this.popupNode || !this._getNodeAt(pointer) ) {
this.popupNode = undefined; this.popupNode = undefined;
if (this.popup) { if (this.popup) {
this.popup.hide(); this.popup.hide();
@ -724,66 +732,6 @@ Graph.prototype._checkHidePopup = function (x, y) {
} }
}; };
/**
* Event handler for touchstart event on mobile devices
* @param {Event} event
* @private
*/
Graph.prototype._onTouchStart = function(event) {
vis.util.preventDefault(event);
if (this.touchDown) {
// if already moving, return
return;
}
this.touchDown = true;
var me = this;
if (!this.ontouchmove) {
this.ontouchmove = function (event) {me._onTouchMove(event);};
vis.util.addEventListener(document, "touchmove", this.ontouchmove);
}
if (!this.ontouchend) {
this.ontouchend = function (event) {me._onTouchEnd(event);};
vis.util.addEventListener(document, "touchend", this.ontouchend);
}
this._onMouseDown(event);
};
/**
* Event handler for touchmove event on mobile devices
* @param {Event} event
* @private
*/
Graph.prototype._onTouchMove = function(event) {
vis.util.preventDefault(event);
this._onMouseMove(event);
};
/**
* Event handler for touchend event on mobile devices
* @param {Event} event
* @private
*/
Graph.prototype._onTouchEnd = function(event) {
vis.util.preventDefault(event);
this.touchDown = false;
if (this.ontouchmove) {
vis.util.removeEventListener(document, "touchmove", this.ontouchmove);
this.ontouchmove = undefined;
}
if (this.ontouchend) {
vis.util.removeEventListener(document, "touchend", this.ontouchend);
this.ontouchend = undefined;
}
this._onMouseUp(event);
};
/** /**
* Unselect selected nodes. If no selection array is provided, all nodes * Unselect selected nodes. If no selection array is provided, all nodes
* are unselected * are unselected
@ -893,8 +841,7 @@ Graph.prototype._selectNodes = function(selection, append) {
/** /**
* retrieve all nodes overlapping with given object * retrieve all nodes overlapping with given object
* @param {Object} obj An object with parameters left, top, right, bottom * @param {Object} obj An object with parameters left, top, right, bottom
* @return {Object[]} An array with selection objects containing
* the parameter row.
* @return {Number[]} An array with id's of the overlapping nodes
* @private * @private
*/ */
Graph.prototype._getNodesOverlappingWith = function (obj) { Graph.prototype._getNodesOverlappingWith = function (obj) {
@ -929,8 +876,8 @@ Graph.prototype.getSelection = function() {
Graph.prototype.setSelection = function(selection) { Graph.prototype.setSelection = function(selection) {
var i, iMax, id; var i, iMax, id;
if (selection.length == undefined)
throw "Selection must be an array with ids";
if (!selection || (selection.length == undefined))
throw 'Selection must be an array with ids';
// first unselect any selected node // first unselect any selected node
for (i = 0, iMax = this.selection.length; i < iMax; i++) { for (i = 0, iMax = this.selection.length; i < iMax; i++) {
@ -1053,17 +1000,17 @@ Graph.prototype._getConnectionCount = function(level) {
/** /**
* Set a new size for the graph * Set a new size for the graph
* @param {string} width Width in pixels or percentage (for example "800px"
* or "50%")
* @param {string} height Height in pixels or percentage (for example "400px"
* or "30%")
* @param {string} width Width in pixels or percentage (for example '800px'
* or '50%')
* @param {string} height Height in pixels or percentage (for example '400px'
* or '30%')
*/ */
Graph.prototype.setSize = function(width, height) { Graph.prototype.setSize = function(width, height) {
this.frame.style.width = width; this.frame.style.width = width;
this.frame.style.height = height; this.frame.style.height = height;
this.frame.canvas.style.width = "100%";
this.frame.canvas.style.height = "100%";
this.frame.canvas.style.width = '100%';
this.frame.canvas.style.height = '100%';
this.frame.canvas.width = this.frame.canvas.clientWidth; this.frame.canvas.width = this.frame.canvas.clientWidth;
this.frame.canvas.height = this.frame.canvas.clientHeight; this.frame.canvas.height = this.frame.canvas.clientHeight;
@ -1390,7 +1337,7 @@ Graph.prototype.redraw = function() {
* @private * @private
*/ */
Graph.prototype._redraw = function() { Graph.prototype._redraw = function() {
var ctx = this.frame.canvas.getContext("2d");
var ctx = this.frame.canvas.getContext('2d');
// clear the canvas // clear the canvas
var w = this.frame.canvas.width; var w = this.frame.canvas.width;
@ -1418,8 +1365,8 @@ Graph.prototype._redraw = function() {
Graph.prototype._setTranslation = function(offsetX, offsetY) { Graph.prototype._setTranslation = function(offsetX, offsetY) {
if (this.translation === undefined) { if (this.translation === undefined) {
this.translation = { this.translation = {
"x": 0,
"y": 0
x: 0,
y: 0
}; };
} }
@ -1438,8 +1385,8 @@ Graph.prototype._setTranslation = function(offsetX, offsetY) {
*/ */
Graph.prototype._getTranslation = function() { Graph.prototype._getTranslation = function() {
return { return {
"x": this.translation.x,
"y": this.translation.y
x: this.translation.x,
y: this.translation.y
}; };
}; };
@ -1460,25 +1407,49 @@ Graph.prototype._getScale = function() {
return this.scale; return this.scale;
}; };
Graph.prototype._xToCanvas = function(x) {
/**
* Convert a horizontal point on the HTML canvas to the x-value of the model
* @param {number} x
* @returns {number}
* @private
*/
Graph.prototype._canvasToX = function(x) {
return (x - this.translation.x) / this.scale; return (x - this.translation.x) / this.scale;
}; };
Graph.prototype._canvasToX = function(x) {
/**
* Convert an x-value in the model to a horizontal point on the HTML canvas
* @param {number} x
* @returns {number}
* @private
*/
Graph.prototype._xToCanvas = function(x) {
return x * this.scale + this.translation.x; return x * this.scale + this.translation.x;
}; };
Graph.prototype._yToCanvas = function(y) {
/**
* Convert a vertical point on the HTML canvas to the y-value of the model
* @param {number} y
* @returns {number}
* @private
*/
Graph.prototype._canvasToY = function(y) {
return (y - this.translation.y) / this.scale; return (y - this.translation.y) / this.scale;
}; };
Graph.prototype._canvasToY = function(y) {
/**
* Convert an y-value in the model to a vertical point on the HTML canvas
* @param {number} y
* @returns {number}
* @private
*/
Graph.prototype._yToCanvas = function(y) {
return y * this.scale + this.translation.y ; return y * this.scale + this.translation.y ;
}; };
/** /**
* Redraw all nodes * Redraw all nodes
* The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
* The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d');
* @param {CanvasRenderingContext2D} ctx * @param {CanvasRenderingContext2D} ctx
* @private * @private
*/ */
@ -1505,7 +1476,7 @@ Graph.prototype._drawNodes = function(ctx) {
/** /**
* Redraw all edges * Redraw all edges
* The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
* The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d');
* @param {CanvasRenderingContext2D} ctx * @param {CanvasRenderingContext2D} ctx
* @private * @private
*/ */
@ -1541,7 +1512,7 @@ Graph.prototype._doStabilize = function() {
var end = new Date(); var end = new Date();
// console.log("Stabilized in " + (end-start) + " ms, " + count + " iterations" ); // TODO: cleanup
// console.log('Stabilized in ' + (end-start) + ' ms, ' + count + ' iterations' ); // TODO: cleanup
}; };
/** /**
@ -1698,7 +1669,7 @@ Graph.prototype._calculateForces = function() {
/** /**
* Check if any of the nodes is still moving * Check if any of the nodes is still moving
* @param {number} vmin the minimum velocity considered as "moving"
* @param {number} vmin the minimum velocity considered as 'moving'
* @return {boolean} true if moving, false if non of the nodes is moving * @return {boolean} true if moving, false if non of the nodes is moving
* @private * @private
*/ */

+ 5
- 0
src/graph/Node.js View File

@ -397,8 +397,13 @@ Node.prototype.getDistance = function(x, y) {
*/ */
Node.prototype.setValueRange = function(min, max) { Node.prototype.setValueRange = function(min, max) {
if (!this.radiusFixed && this.value !== undefined) { if (!this.radiusFixed && this.value !== undefined) {
if (max == min) {
this.radius = (this.radiusMin + this.radiusMax) / 2;
}
else {
var scale = (this.radiusMax - this.radiusMin) / (max - min); var scale = (this.radiusMax - this.radiusMin) / (max - min);
this.radius = (this.value - min) * scale + this.radiusMin; this.radius = (this.value - min) * scale + this.radiusMin;
}
} }
}; };

+ 5
- 1
src/module/imports.js View File

@ -1,4 +1,8 @@
/** /**
* vis.js module imports * vis.js module imports
*/ */
var moment = require('moment');
// Try to load dependencies from the global window object.
// If not available there, load via require.
var moment = (typeof window !== 'undefined') && window['moment'] || require('moment');
var Hammer = (typeof window !== 'undefined') && window['Hammer'] || require('hammerjs');

+ 252
- 0
src/shim.js View File

@ -0,0 +1,252 @@
// Internet Explorer 8 and older does not support Array.indexOf, so we define
// it here in that case.
// http://soledadpenades.com/2007/05/17/arrayindexof-in-internet-explorer/
if(!Array.prototype.indexOf) {
Array.prototype.indexOf = function(obj){
for(var i = 0; i < this.length; i++){
if(this[i] == obj){
return i;
}
}
return -1;
};
try {
console.log("Warning: Ancient browser detected. Please update your browser");
}
catch (err) {
}
}
// Internet Explorer 8 and older does not support Array.forEach, so we define
// it here in that case.
// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/forEach
if (!Array.prototype.forEach) {
Array.prototype.forEach = function(fn, scope) {
for(var i = 0, len = this.length; i < len; ++i) {
fn.call(scope || this, this[i], i, this);
}
}
}
// Internet Explorer 8 and older does not support Array.map, so we define it
// here in that case.
// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/map
// Production steps of ECMA-262, Edition 5, 15.4.4.19
// Reference: http://es5.github.com/#x15.4.4.19
if (!Array.prototype.map) {
Array.prototype.map = function(callback, thisArg) {
var T, A, k;
if (this == null) {
throw new TypeError(" this is null or not defined");
}
// 1. Let O be the result of calling ToObject passing the |this| value as the argument.
var O = Object(this);
// 2. Let lenValue be the result of calling the Get internal method of O with the argument "length".
// 3. Let len be ToUint32(lenValue).
var len = O.length >>> 0;
// 4. If IsCallable(callback) is false, throw a TypeError exception.
// See: http://es5.github.com/#x9.11
if (typeof callback !== "function") {
throw new TypeError(callback + " is not a function");
}
// 5. If thisArg was supplied, let T be thisArg; else let T be undefined.
if (thisArg) {
T = thisArg;
}
// 6. Let A be a new array created as if by the expression new Array(len) where Array is
// the standard built-in constructor with that name and len is the value of len.
A = new Array(len);
// 7. Let k be 0
k = 0;
// 8. Repeat, while k < len
while(k < len) {
var kValue, mappedValue;
// a. Let Pk be ToString(k).
// This is implicit for LHS operands of the in operator
// b. Let kPresent be the result of calling the HasProperty internal method of O with argument Pk.
// This step can be combined with c
// c. If kPresent is true, then
if (k in O) {
// i. Let kValue be the result of calling the Get internal method of O with argument Pk.
kValue = O[ k ];
// ii. Let mappedValue be the result of calling the Call internal method of callback
// with T as the this value and argument list containing kValue, k, and O.
mappedValue = callback.call(T, kValue, k, O);
// iii. Call the DefineOwnProperty internal method of A with arguments
// Pk, Property Descriptor {Value: mappedValue, : true, Enumerable: true, Configurable: true},
// and false.
// In browsers that support Object.defineProperty, use the following:
// Object.defineProperty(A, Pk, { value: mappedValue, writable: true, enumerable: true, configurable: true });
// For best browser support, use the following:
A[ k ] = mappedValue;
}
// d. Increase k by 1.
k++;
}
// 9. return A
return A;
};
}
// Internet Explorer 8 and older does not support Array.filter, so we define it
// here in that case.
// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/filter
if (!Array.prototype.filter) {
Array.prototype.filter = function(fun /*, thisp */) {
"use strict";
if (this == null) {
throw new TypeError();
}
var t = Object(this);
var len = t.length >>> 0;
if (typeof fun != "function") {
throw new TypeError();
}
var res = [];
var thisp = arguments[1];
for (var i = 0; i < len; i++) {
if (i in t) {
var val = t[i]; // in case fun mutates this
if (fun.call(thisp, val, i, t))
res.push(val);
}
}
return res;
};
}
// Internet Explorer 8 and older does not support Object.keys, so we define it
// here in that case.
// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/keys
if (!Object.keys) {
Object.keys = (function () {
var hasOwnProperty = Object.prototype.hasOwnProperty,
hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString'),
dontEnums = [
'toString',
'toLocaleString',
'valueOf',
'hasOwnProperty',
'isPrototypeOf',
'propertyIsEnumerable',
'constructor'
],
dontEnumsLength = dontEnums.length;
return function (obj) {
if (typeof obj !== 'object' && typeof obj !== 'function' || obj === null) {
throw new TypeError('Object.keys called on non-object');
}
var result = [];
for (var prop in obj) {
if (hasOwnProperty.call(obj, prop)) result.push(prop);
}
if (hasDontEnumBug) {
for (var i=0; i < dontEnumsLength; i++) {
if (hasOwnProperty.call(obj, dontEnums[i])) result.push(dontEnums[i]);
}
}
return result;
}
})()
}
// Internet Explorer 8 and older does not support Array.isArray,
// so we define it here in that case.
// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/isArray
if(!Array.isArray) {
Array.isArray = function (vArg) {
return Object.prototype.toString.call(vArg) === "[object Array]";
};
}
// Internet Explorer 8 and older does not support Function.bind,
// so we define it here in that case.
// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind
if (!Function.prototype.bind) {
Function.prototype.bind = function (oThis) {
if (typeof this !== "function") {
// closest thing possible to the ECMAScript 5 internal IsCallable function
throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function () {},
fBound = function () {
return fToBind.apply(this instanceof fNOP && oThis
? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
}
// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/create
if (!Object.create) {
Object.create = function (o) {
if (arguments.length > 1) {
throw new Error('Object.create implementation only accepts the first parameter.');
}
function F() {}
F.prototype = o;
return new F();
};
}
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
if (!Function.prototype.bind) {
Function.prototype.bind = function (oThis) {
if (typeof this !== "function") {
// closest thing possible to the ECMAScript 5 internal IsCallable function
throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function () {},
fBound = function () {
return fToBind.apply(this instanceof fNOP && oThis
? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
}

+ 1
- 0
src/timeline/Range.js View File

@ -11,6 +11,7 @@ function Range(options) {
this.start = 0; // Number this.start = 0; // Number
this.end = 0; // Number this.end = 0; // Number
// this.options = options || {}; // TODO: fix range options
this.options = { this.options = {
min: null, min: null,
max: null, max: null,

+ 19
- 0
src/timeline/Timeline.js View File

@ -17,6 +17,7 @@ function Timeline (container, items, options) {
// zoomable: true, // TODO: option zoomable // zoomable: true, // TODO: option zoomable
showMinorLabels: true, showMinorLabels: true,
showMajorLabels: true, showMajorLabels: true,
showCurrentTime: false,
autoResize: false autoResize: false
}, options); }, options);
@ -76,6 +77,14 @@ function Timeline (container, items, options) {
start: now.clone().add('days', -3).valueOf(), start: now.clone().add('days', -3).valueOf(),
end: now.clone().add('days', 4).valueOf() end: now.clone().add('days', 4).valueOf()
}); });
/* TODO: fix range options
var rangeOptions = Object.create(this.options);
this.range = new Range(rangeOptions);
this.range.setRange(
now.clone().add('days', -3).valueOf(),
now.clone().add('days', 4).valueOf()
);
*/
// TODO: reckon with options moveable and zoomable // TODO: reckon with options moveable and zoomable
this.range.subscribe(this.rootPanel, 'move', 'horizontal'); this.range.subscribe(this.rootPanel, 'move', 'horizontal');
this.range.subscribe(this.rootPanel, 'zoom', 'horizontal'); this.range.subscribe(this.rootPanel, 'zoom', 'horizontal');
@ -101,6 +110,10 @@ function Timeline (container, items, options) {
this.timeaxis.setRange(this.range); this.timeaxis.setRange(this.range);
this.controller.add(this.timeaxis); this.controller.add(this.timeaxis);
// current time bar
this.currenttime = new CurrentTime(this.timeaxis, [], rootOptions);
this.controller.add(this.currenttime);
// create itemset or groupset // create itemset or groupset
this.setGroups(null); this.setGroups(null);
@ -122,6 +135,8 @@ Timeline.prototype.setOptions = function (options) {
util.extend(this.options, options); util.extend(this.options, options);
} }
// TODO: apply range min,max
this.controller.reflow(); this.controller.reflow();
this.controller.repaint(); this.controller.repaint();
}; };
@ -164,6 +179,10 @@ Timeline.prototype.setItems = function(items) {
var max = dataRange.max; var max = dataRange.max;
if (min != null && max != null) { if (min != null && max != null) {
var interval = (max.valueOf() - min.valueOf()); var interval = (max.valueOf() - min.valueOf());
if (interval <= 0) {
// prevent an empty interval
interval = 24 * 60 * 60 * 1000; // 1 day
}
min = new Date(min.valueOf() - interval * 0.05); min = new Date(min.valueOf() - interval * 0.05);
max = new Date(max.valueOf() + interval * 0.05); max = new Date(max.valueOf() + interval * 0.05);
} }

+ 101
- 0
src/timeline/component/CurrentTime.js View File

@ -0,0 +1,101 @@
/**
* A current time bar
* @param {Component} parent
* @param {Component[]} [depends] Components on which this components depends
* (except for the parent)
* @param {Object} [options] Available parameters:
* {Boolean} [showCurrentTime]
* @constructor CurrentTime
* @extends Component
*/
function CurrentTime (parent, depends, options) {
this.id = util.randomUUID();
this.parent = parent;
this.depends = depends;
this.options = options || {};
this.defaultOptions = {
showCurrentTime: false
};
}
CurrentTime.prototype = new Component();
CurrentTime.prototype.setOptions = Component.prototype.setOptions;
/**
* Get the container element of the bar, which can be used by a child to
* add its own widgets.
* @returns {HTMLElement} container
*/
CurrentTime.prototype.getContainer = function () {
return this.frame;
};
/**
* Repaint the component
* @return {Boolean} changed
*/
CurrentTime.prototype.repaint = function () {
var bar = this.frame,
parent = this.parent,
parentContainer = parent.parent.getContainer();
if (!parent) {
throw new Error('Cannot repaint bar: no parent attached');
}
if (!parentContainer) {
throw new Error('Cannot repaint bar: parent has no container element');
}
if (!this.getOption('showCurrentTime')) {
if (bar) {
parentContainer.removeChild(bar);
delete this.frame;
}
return;
}
if (!bar) {
bar = document.createElement('div');
bar.className = 'currenttime';
bar.style.position = 'absolute';
bar.style.top = '0px';
bar.style.height = '100%';
parentContainer.appendChild(bar);
this.frame = bar;
}
if (!parent.conversion) {
parent._updateConversion();
}
var now = new Date();
var x = parent.toScreen(now);
bar.style.left = x + 'px';
bar.title = 'Current time: ' + now;
// start a timer to adjust for the new time
if (this.currentTimeTimer !== undefined) {
clearTimeout(this.currentTimeTimer);
delete this.currentTimeTimer;
}
var timeline = this;
var interval = 1 / parent.conversion.factor / 2;
if (interval < 30) {
interval = 30;
}
this.currentTimeTimer = setTimeout(function() {
timeline.repaint();
}, interval);
return false;
};

+ 0
- 1
src/timeline/component/Group.js View File

@ -11,7 +11,6 @@ function Group (parent, groupId, options) {
this.parent = parent; this.parent = parent;
this.groupId = groupId; this.groupId = groupId;
this.itemsData = null; // DataSet
this.itemset = null; // ItemSet this.itemset = null; // ItemSet
this.options = options || {}; this.options = options || {};
this.options.top = 0; this.options.top = 0;

+ 1
- 1
src/timeline/component/ItemSet.js View File

@ -193,7 +193,7 @@ ItemSet.prototype.repaint = function repaint() {
items = this.items, items = this.items,
dataOptions = { dataOptions = {
// TODO: cleanup // TODO: cleanup
//fields: [(itemsData && itemsData.fieldId || 'id'), 'start', 'end', 'content', 'type']
// fields: [(itemsData && itemsData.fieldId || 'id'), 'start', 'end', 'content', 'type', 'className']
}; };
// show/hide added/changed/removed items // show/hide added/changed/removed items

+ 5
- 0
src/timeline/component/css/currenttime.css View File

@ -0,0 +1,5 @@
.vis.timeline .currenttime {
background-color: #FF7F6E;
width: 2px;
z-index: 9;
}

+ 1
- 1
src/timeline/component/item/ItemRange.js View File

@ -86,7 +86,7 @@ ItemRange.prototype.repaint = function repaint() {
} }
// update class // update class
var className = this.data.className ? ('' + this.data.className) : '';
var className = this.data.className ? (' ' + this.data.className) : '';
if (this.className != className) { if (this.className != className) {
this.className = className; this.className = className;
dom.box.className = 'item range' + className; dom.box.className = 'item range' + className;

+ 0
- 228
src/util.js View File

@ -684,231 +684,3 @@ util.loadCss = function (css) {
document.getElementsByTagName('head')[0].appendChild(style); document.getElementsByTagName('head')[0].appendChild(style);
}; };
// Internet Explorer 8 and older does not support Array.indexOf, so we define
// it here in that case.
// http://soledadpenades.com/2007/05/17/arrayindexof-in-internet-explorer/
if(!Array.prototype.indexOf) {
Array.prototype.indexOf = function(obj){
for(var i = 0; i < this.length; i++){
if(this[i] == obj){
return i;
}
}
return -1;
};
try {
console.log("Warning: Ancient browser detected. Please update your browser");
}
catch (err) {
}
}
// Internet Explorer 8 and older does not support Array.forEach, so we define
// it here in that case.
// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/forEach
if (!Array.prototype.forEach) {
Array.prototype.forEach = function(fn, scope) {
for(var i = 0, len = this.length; i < len; ++i) {
fn.call(scope || this, this[i], i, this);
}
}
}
// Internet Explorer 8 and older does not support Array.map, so we define it
// here in that case.
// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/map
// Production steps of ECMA-262, Edition 5, 15.4.4.19
// Reference: http://es5.github.com/#x15.4.4.19
if (!Array.prototype.map) {
Array.prototype.map = function(callback, thisArg) {
var T, A, k;
if (this == null) {
throw new TypeError(" this is null or not defined");
}
// 1. Let O be the result of calling ToObject passing the |this| value as the argument.
var O = Object(this);
// 2. Let lenValue be the result of calling the Get internal method of O with the argument "length".
// 3. Let len be ToUint32(lenValue).
var len = O.length >>> 0;
// 4. If IsCallable(callback) is false, throw a TypeError exception.
// See: http://es5.github.com/#x9.11
if (typeof callback !== "function") {
throw new TypeError(callback + " is not a function");
}
// 5. If thisArg was supplied, let T be thisArg; else let T be undefined.
if (thisArg) {
T = thisArg;
}
// 6. Let A be a new array created as if by the expression new Array(len) where Array is
// the standard built-in constructor with that name and len is the value of len.
A = new Array(len);
// 7. Let k be 0
k = 0;
// 8. Repeat, while k < len
while(k < len) {
var kValue, mappedValue;
// a. Let Pk be ToString(k).
// This is implicit for LHS operands of the in operator
// b. Let kPresent be the result of calling the HasProperty internal method of O with argument Pk.
// This step can be combined with c
// c. If kPresent is true, then
if (k in O) {
// i. Let kValue be the result of calling the Get internal method of O with argument Pk.
kValue = O[ k ];
// ii. Let mappedValue be the result of calling the Call internal method of callback
// with T as the this value and argument list containing kValue, k, and O.
mappedValue = callback.call(T, kValue, k, O);
// iii. Call the DefineOwnProperty internal method of A with arguments
// Pk, Property Descriptor {Value: mappedValue, : true, Enumerable: true, Configurable: true},
// and false.
// In browsers that support Object.defineProperty, use the following:
// Object.defineProperty(A, Pk, { value: mappedValue, writable: true, enumerable: true, configurable: true });
// For best browser support, use the following:
A[ k ] = mappedValue;
}
// d. Increase k by 1.
k++;
}
// 9. return A
return A;
};
}
// Internet Explorer 8 and older does not support Array.filter, so we define it
// here in that case.
// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/filter
if (!Array.prototype.filter) {
Array.prototype.filter = function(fun /*, thisp */) {
"use strict";
if (this == null) {
throw new TypeError();
}
var t = Object(this);
var len = t.length >>> 0;
if (typeof fun != "function") {
throw new TypeError();
}
var res = [];
var thisp = arguments[1];
for (var i = 0; i < len; i++) {
if (i in t) {
var val = t[i]; // in case fun mutates this
if (fun.call(thisp, val, i, t))
res.push(val);
}
}
return res;
};
}
// Internet Explorer 8 and older does not support Object.keys, so we define it
// here in that case.
// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/keys
if (!Object.keys) {
Object.keys = (function () {
var hasOwnProperty = Object.prototype.hasOwnProperty,
hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString'),
dontEnums = [
'toString',
'toLocaleString',
'valueOf',
'hasOwnProperty',
'isPrototypeOf',
'propertyIsEnumerable',
'constructor'
],
dontEnumsLength = dontEnums.length;
return function (obj) {
if (typeof obj !== 'object' && typeof obj !== 'function' || obj === null) {
throw new TypeError('Object.keys called on non-object');
}
var result = [];
for (var prop in obj) {
if (hasOwnProperty.call(obj, prop)) result.push(prop);
}
if (hasDontEnumBug) {
for (var i=0; i < dontEnumsLength; i++) {
if (hasOwnProperty.call(obj, dontEnums[i])) result.push(dontEnums[i]);
}
}
return result;
}
})()
}
// Internet Explorer 8 and older does not support Array.isArray,
// so we define it here in that case.
// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/isArray
if(!Array.isArray) {
Array.isArray = function (vArg) {
return Object.prototype.toString.call(vArg) === "[object Array]";
};
}
// Internet Explorer 8 and older does not support Function.bind,
// so we define it here in that case.
// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind
if (!Function.prototype.bind) {
Function.prototype.bind = function (oThis) {
if (typeof this !== "function") {
// closest thing possible to the ECMAScript 5 internal IsCallable function
throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function () {},
fBound = function () {
return fToBind.apply(this instanceof fNOP && oThis
? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
}
// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/create
if (!Object.create) {
Object.create = function (o) {
if (arguments.length > 1) {
throw new Error('Object.create implementation only accepts the first parameter.');
}
function F() {}
F.prototype = o;
return new F();
};
}

+ 12341
- 10421
vis.js
File diff suppressed because it is too large
View File


+ 14
- 1
vis.min.js
File diff suppressed because it is too large
View File


Loading…
Cancel
Save