Browse Source

Merge branch 'develop' of https://github.com/almende/vis into develop

revert-3409-performance
Yotam Berkowitz 7 years ago
parent
commit
e9d1ebf29f
88 changed files with 2175 additions and 50050 deletions
  1. +5
    -1
      .babelrc
  2. +1
    -0
      .gitignore
  3. +34
    -57
      README.md
  4. BIN
      dist/img/network/acceptDeleteIcon.png
  5. BIN
      dist/img/network/addNodeIcon.png
  6. BIN
      dist/img/network/backIcon.png
  7. BIN
      dist/img/network/connectIcon.png
  8. BIN
      dist/img/network/cross.png
  9. BIN
      dist/img/network/cross2.png
  10. BIN
      dist/img/network/deleteIcon.png
  11. BIN
      dist/img/network/downArrow.png
  12. BIN
      dist/img/network/editIcon.png
  13. BIN
      dist/img/network/leftArrow.png
  14. BIN
      dist/img/network/minus.png
  15. BIN
      dist/img/network/plus.png
  16. BIN
      dist/img/network/rightArrow.png
  17. BIN
      dist/img/network/upArrow.png
  18. BIN
      dist/img/network/zoomExtends.png
  19. +0
    -33
      dist/vis-graph3d.min.js
  20. +0
    -1
      dist/vis-network.min.css
  21. +0
    -41
      dist/vis-network.min.js
  22. +0
    -1
      dist/vis-timeline-graph2d.min.css
  23. +0
    -39
      dist/vis-timeline-graph2d.min.js
  24. +0
    -1389
      dist/vis.css
  25. +0
    -47739
      dist/vis.js
  26. +0
    -1
      dist/vis.min.css
  27. +0
    -45
      dist/vis.min.js
  28. +7
    -0
      docs/css/style.css
  29. +97
    -1
      docs/network/edges.html
  30. +85
    -0
      docs/network/nodes.html
  31. +28
    -2
      docs/timeline/index.html
  32. +6
    -2
      examples/graph3d/07_dot_cloud_colors.html
  33. +466
    -0
      examples/network/other/chosen.html
  34. +113
    -0
      examples/timeline/groups/nestedGroups.html
  35. +11
    -6
      examples/timeline/groups/subgroups.html
  36. +111
    -0
      examples/timeline/items/expectedVsActualTimesItems.html
  37. +49
    -0
      examples/timeline/items/tooltip.html
  38. +4
    -5
      gulpfile.js
  39. +69
    -34
      lib/graph3d/Graph3d.js
  40. +5
    -1
      lib/graph3d/Settings.js
  41. +4
    -6
      lib/network/modules/CanvasRenderer.js
  42. +0
    -13
      lib/network/modules/EdgesHandler.js
  43. +1
    -1
      lib/network/modules/InteractionHandler.js
  44. +166
    -59
      lib/network/modules/components/Edge.js
  45. +67
    -2
      lib/network/modules/components/Node.js
  46. +3
    -3
      lib/network/modules/components/edges/BezierEdgeDynamic.js
  47. +3
    -3
      lib/network/modules/components/edges/BezierEdgeStatic.js
  48. +3
    -3
      lib/network/modules/components/edges/CubicBezierEdge.js
  49. +3
    -3
      lib/network/modules/components/edges/StraightEdge.js
  50. +74
    -101
      lib/network/modules/components/edges/util/EdgeBase.js
  51. +18
    -22
      lib/network/modules/components/nodes/shapes/Box.js
  52. +11
    -11
      lib/network/modules/components/nodes/shapes/Circle.js
  53. +16
    -16
      lib/network/modules/components/nodes/shapes/CircularImage.js
  54. +16
    -18
      lib/network/modules/components/nodes/shapes/Database.js
  55. +5
    -5
      lib/network/modules/components/nodes/shapes/Diamond.js
  56. +5
    -5
      lib/network/modules/components/nodes/shapes/Dot.js
  57. +16
    -18
      lib/network/modules/components/nodes/shapes/Ellipse.js
  58. +11
    -12
      lib/network/modules/components/nodes/shapes/Icon.js
  59. +4
    -4
      lib/network/modules/components/nodes/shapes/Image.js
  60. +3
    -3
      lib/network/modules/components/nodes/shapes/Square.js
  61. +5
    -5
      lib/network/modules/components/nodes/shapes/Star.js
  62. +11
    -11
      lib/network/modules/components/nodes/shapes/Text.js
  63. +3
    -3
      lib/network/modules/components/nodes/shapes/Triangle.js
  64. +3
    -3
      lib/network/modules/components/nodes/shapes/TriangleDown.js
  65. +16
    -18
      lib/network/modules/components/nodes/util/CircleImageBase.js
  66. +15
    -13
      lib/network/modules/components/nodes/util/NodeBase.js
  67. +15
    -17
      lib/network/modules/components/nodes/util/ShapeBase.js
  68. +83
    -53
      lib/network/modules/components/shared/Label.js
  69. +88
    -78
      lib/network/options.js
  70. +1
    -1
      lib/shared/Popup.js
  71. +4
    -2
      lib/shared/tooltip.css
  72. +1
    -2
      lib/timeline/Core.js
  73. +6
    -0
      lib/timeline/Graph2d.js
  74. +69
    -15
      lib/timeline/Stack.js
  75. +12
    -4
      lib/timeline/Timeline.js
  76. +92
    -10
      lib/timeline/component/Group.js
  77. +131
    -18
      lib/timeline/component/ItemSet.js
  78. +20
    -0
      lib/timeline/component/css/itemset.css
  79. +12
    -43
      lib/timeline/component/item/BackgroundItem.js
  80. +1
    -1
      lib/timeline/component/item/BoxItem.js
  81. +23
    -6
      lib/timeline/component/item/Item.js
  82. +1
    -1
      lib/timeline/component/item/PointItem.js
  83. +1
    -1
      lib/timeline/component/item/RangeItem.js
  84. +1
    -0
      lib/timeline/optionsGraph2d.js
  85. +33
    -30
      lib/timeline/optionsTimeline.js
  86. +2
    -2
      lib/util.js
  87. +0
    -1
      misc/we_need_help.md
  88. +6
    -6
      package.json

+ 5
- 1
.babelrc View File

@ -1,3 +1,7 @@
{
"presets": ["es2015"]
"presets": ["es2015"],
"plugins": [
"transform-es3-property-literals",
"transform-es3-member-expression-literals"
]
}

+ 1
- 0
.gitignore View File

@ -1,4 +1,5 @@
# vis.js files
dist/
test/
dist/

+ 34
- 57
README.md View File

@ -1,9 +1,7 @@
vis.js
==================
[![Join the chat at https://gitter.im/vis-js/Lobby](https://badges.gitter.im/vis-js/Lobby.svg)](https://gitter.im/vis-js/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
<a href="https://github.com/almende/vis/issues/1781" target="_blank">
<a href="https://github.com/almende/vis/blob/develop/misc/we_need_help.md">
<img align="right" src="https://raw.githubusercontent.com/almende/vis/master/misc/we_need_help.png">
</a>
@ -12,8 +10,8 @@ The library is designed to be easy to use, handle large amounts
of dynamic data, and enable manipulation of the data.
The library consists of the following components:
- DataSet and DataView. A flexible key/value based data set. Add, update, and
remove items. Subscribe on changes in the data set. A DataSet can filter and
- DataSet and DataView. A flexible key/value based data set. Add, update, and
remove items. Subscribe on changes in the data set. A DataSet can filter and
order items, and convert fields of items.
- DataView. A filtered and/or formatted view on a DataSet.
- Graph2d. Plot data on a timeline with lines or barcharts.
@ -21,22 +19,7 @@ The library consists of the following components:
- Network. Display a network (force directed graph) with nodes and edges.
- Timeline. Display different types of data on a timeline.
The vis.js library was initialy developed by [Almende B.V](http://almende.com).
## Badges
[![NPM](https://nodei.co/npm/vis.png?downloads=true&downloadRank=true)](https://nodei.co/npm/vis/)
[![Dependency Status](https://david-dm.org/almende/vis/status.svg)](https://david-dm.org/almende/vis)
[![devDependency Status](https://david-dm.org/almende/vis/dev-status.svg)](https://david-dm.org/almende/vis?type=dev)
[![last version on CDNJS](https://img.shields.io/cdnjs/v/vis.svg)](https://cdnjs.com/libraries/vis)
[![GitHub contributors](https://img.shields.io/github/contributors/almende/vis.svg)](https://github.com/almende/vis/graphs/contributors)
[![GitHub stars](https://img.shields.io/github/stars/almende/vis.svg)](https://github.com/almende/vis/stargazers)
[![GitHub issues](https://img.shields.io/github/issues/almende/vis.svg)](https://github.com/almende/vis/issues)
[![Pending Pull-Requests](http://githubbadges.herokuapp.com/almende/vis/pulls.svg)](https://github.com/almende/vis/pulls)
[![Code Climate](https://codeclimate.com/github/almende/vis/badges/gpa.svg)](https://codeclimate.com/github/almende/vis)
The vis.js library was initially developed by [Almende B.V](http://almende.com).
## Install
@ -53,18 +36,16 @@ Link via cdnjs: http://cdnjs.com
Or download the library from the github project:
[https://github.com/almende/vis.git](https://github.com/almende/vis.git).
## Load
To use a component, include the javascript and css files of vis in your web page:
```html
<!DOCTYPE HTML>
<html>
<head>
<script src="components/vis/dist/vis.js"></script>
<link href="components/vis/dist/vis.css" rel="stylesheet" type="text/css" />
<script src="webroot/vis/dist/vis.js"></script>
<link href="webroot/vis/dist/vis.css" rel="stylesheet" type="text/css" />
</head>
<body>
<script type="text/javascript">
@ -139,7 +120,6 @@ of the project.
</html>
```
## Build
To build the library from source, clone the project from github
@ -147,7 +127,7 @@ To build the library from source, clone the project from github
$ git clone git://github.com/almende/vis.git
The source code uses the module style of node (require and module.exports) to
organize dependencies. To install all dependencies and build the library,
organize dependencies. To install all dependencies and build the library,
run `npm install` in the root of the project.
$ cd vis
@ -162,32 +142,30 @@ To automatically rebuild on changes in the source files, once can use
$ npm run watch
This will both build and minify the library on changes. Minifying is relatively
slow, so when only the non-minified library is needed, one can use the
slow, so when only the non-minified library is needed, one can use the
`watch-dev` script instead:
$ npm run watch-dev
## Custom builds
The folder `dist` contains bundled versions of vis.js for direct use in the browser. These bundles contain all the visualizations and include external dependencies such as hammer.js and moment.js.
The folder `dist` contains bundled versions of vis.js for direct use in the browser. These bundles contain all the visualizations and include external dependencies such as *hammer.js* and *moment.js*.
The source code of vis.js consists of commonjs modules, which makes it possible to create custom bundles using tools like [Browserify](http://browserify.org/) or [Webpack](http://webpack.github.io/). This can be bundling just one visualization like the Timeline, or bundling vis.js as part of your own browserified web application.
The source code of vis.js consists of commonjs modules, which makes it possible to create custom bundles using tools like [Browserify](http://browserify.org/) or [Webpack](http://webpack.github.io/). This can be bundling just one visualization like the Timeline, or bundling vis.js as part of your own browserified web application.
*Note that hammer.js version 2 is required as of v4.*
#### Prerequisites
### Prerequisites
Before you can do a build:
- Install node.js and npm on your system: https://nodejs.org/
- Install *node.js* and *npm* on your system: https://nodejs.org/
- Install the following modules using npm: `browserify`, `babelify`, and `uglify-js`:
```
$ [sudo] npm install -g browserify babelify uglify-js
```
- Download or clone the vis.js project:
```
@ -201,9 +179,11 @@ Before you can do a build:
$ npm install
```
#### Example 1: Bundle a single visualization
### Examples of custom builds
For example, to create a bundle with just the Timeline and DataSet, create an index file named **custom.js** in the root of the project, containing:
#### Example 1: Bundle only a single visualization type
For example, to create a bundle with just the Timeline and DataSet, create an index file named **custom.js** in the root of the project, containing:
```js
exports.DataSet = require('./lib/DataSet');
@ -212,11 +192,11 @@ exports.Timeline = require('./lib/timeline/Timeline');
Then create a custom bundle using browserify, like:
$ browserify custom.js -t babelify -o vis-custom.js -s vis
$ browserify custom.js -t babelify -o dist/vis-custom.js -s vis
This will generate a custom bundle *vis-custom.js*, which exposes the namespace `vis` containing only `DataSet` and `Timeline`. The generated bundle can be minified using uglifyjs:
$ uglifyjs vis-custom.js -o vis-custom.min.js
$ uglifyjs dist/vis-custom.js -o dist/vis-custom.min.js
The custom bundle can now be loaded like:
@ -224,7 +204,7 @@ The custom bundle can now be loaded like:
<!DOCTYPE HTML>
<html>
<head>
<script src="vis-custom.min.js"></script>
<script src="dist/vis-custom.min.js"></script>
<link href="dist/vis.min.css" rel="stylesheet" type="text/css" />
</head>
<body>
@ -235,13 +215,13 @@ The custom bundle can now be loaded like:
#### Example 2: Exclude external libraries
The default bundle `vis.js` is standalone and includes external dependencies such as hammer.js and moment.js. When these libraries are already loaded by the application, vis.js does not need to include these dependencies itself too. To build a custom bundle of vis.js excluding moment.js and hammer.js, run browserify in the root of the project:
The default bundle `vis.js` is standalone and includes external dependencies such as *hammer.js* and *moment.js*. When these libraries are already loaded by the application, vis.js does not need to include these dependencies itself too. To build a custom bundle of vis.js excluding *moment.js* and *hammer.js*, run browserify in the root of the project:
$ browserify index.js -t babelify -o dist/vis-custom.js -s vis -x moment -x hammerjs
$ browserify index.js -t babelify -o vis-custom.js -s vis -x moment -x hammerjs
This will generate a custom bundle *vis-custom.js*, which exposes the namespace `vis`, and has moment and hammerjs excluded. The generated bundle can be minified with uglifyjs:
This will generate a custom bundle *vis-custom.js*, which exposes the namespace `vis`, and has *moment.js* and *hammer.js* excluded. The generated bundle can be minified with uglifyjs:
$ uglifyjs vis-custom.js -o vis-custom.min.js
$ uglifyjs dist/vis-custom.js -o dist/vis-custom.min.js
The custom bundle can now be loaded as:
@ -250,11 +230,11 @@ The custom bundle can now be loaded as:
<html>
<head>
<!-- load external dependencies -->
<script src="http://cdnjs.cloudflare.com/ajax/libs/moment.js/2.7.0/moment.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/hammer.js/1.1.3/hammer.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/moment.js/2.17.1/moment.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/hammer.js/2.0.8/hammer.min.js"></script>
<!-- load vis.js -->
<script src="vis-custom.min.js"></script>
<script src="dist/vis-custom.min.js"></script>
<link href="dist/vis.min.css" rel="stylesheet" type="text/css" />
</head>
<body>
@ -291,8 +271,8 @@ Install the application dependencies via npm:
The application can be bundled and minified:
$ browserify app.js -o app-bundle.js -t babelify
$ uglifyjs app-bundle.js -o app-bundle.min.js
$ browserify app.js -o dist/app-bundle.js -t babelify
$ uglifyjs dist/app-bundle.js -o dist/app-bundle.min.js
And loaded into a webpage:
@ -304,13 +284,11 @@ And loaded into a webpage:
</head>
<body>
<div id="visualization"></div>
<script src="app-bundle.min.js"></script>
<script src="dist/app-bundle.min.js"></script>
</body>
</html>
```
## Test
To test the library, install the project dependencies once:
@ -319,12 +297,11 @@ To test the library, install the project dependencies once:
Then run the tests:
$ npm test
$ npm run test
## License
Copyright (C) 2010-2015 Almende B.V.
Copyright (C) 2010-2016 Almende B.V. and Contributors
Vis.js is dual licensed under both

BIN
dist/img/network/acceptDeleteIcon.png View File

Before After
Width: 24  |  Height: 24  |  Size: 20 KiB

BIN
dist/img/network/addNodeIcon.png View File

Before After
Width: 24  |  Height: 24  |  Size: 20 KiB

BIN
dist/img/network/backIcon.png View File

Before After
Width: 24  |  Height: 24  |  Size: 20 KiB

BIN
dist/img/network/connectIcon.png View File

Before After
Width: 24  |  Height: 24  |  Size: 20 KiB

BIN
dist/img/network/cross.png View File

Before After
Width: 7  |  Height: 7  |  Size: 18 KiB

BIN
dist/img/network/cross2.png View File

Before After
Width: 5  |  Height: 5  |  Size: 17 KiB

BIN
dist/img/network/deleteIcon.png View File

Before After
Width: 24  |  Height: 24  |  Size: 20 KiB

BIN
dist/img/network/downArrow.png View File

Before After
Width: 30  |  Height: 30  |  Size: 4.4 KiB

BIN
dist/img/network/editIcon.png View File

Before After
Width: 24  |  Height: 24  |  Size: 20 KiB

BIN
dist/img/network/leftArrow.png View File

Before After
Width: 30  |  Height: 30  |  Size: 4.4 KiB

BIN
dist/img/network/minus.png View File

Before After
Width: 30  |  Height: 30  |  Size: 4.0 KiB

BIN
dist/img/network/plus.png View File

Before After
Width: 30  |  Height: 30  |  Size: 4.2 KiB

BIN
dist/img/network/rightArrow.png View File

Before After
Width: 30  |  Height: 30  |  Size: 4.4 KiB

BIN
dist/img/network/upArrow.png View File

Before After
Width: 30  |  Height: 30  |  Size: 4.4 KiB

BIN
dist/img/network/zoomExtends.png View File

Before After
Width: 30  |  Height: 30  |  Size: 4.4 KiB

+ 0
- 33
dist/vis-graph3d.min.js
File diff suppressed because it is too large
View File


+ 0
- 1
dist/vis-network.min.css
File diff suppressed because it is too large
View File


+ 0
- 41
dist/vis-network.min.js
File diff suppressed because it is too large
View File


+ 0
- 1
dist/vis-timeline-graph2d.min.css
File diff suppressed because it is too large
View File


+ 0
- 39
dist/vis-timeline-graph2d.min.js
File diff suppressed because it is too large
View File


+ 0
- 1389
dist/vis.css
File diff suppressed because it is too large
View File


+ 0
- 47739
dist/vis.js
File diff suppressed because it is too large
View File


+ 0
- 1
dist/vis.min.css
File diff suppressed because it is too large
View File


+ 0
- 45
dist/vis.min.js
File diff suppressed because it is too large
View File


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

@ -128,6 +128,13 @@ tr.visible {
fadeIn 250ms ease-in;
}
tr.depricated td {
color: #AAA;
text-decoration: line-through;
}
tr.depricated td:last-child {
text-decoration: none;
}
@-webkit-keyframes fadeIn {
0% {

+ 97
- 1
docs/network/edges.html View File

@ -120,6 +120,7 @@ var options = {
from: {enabled: false, scaleFactor:1, type:'arrow'}
},
arrowStrikethrough: true,
chosen: true,
color: {
color:'#848484',
highlight:'#848484',
@ -298,6 +299,101 @@ network.setOptions(options);
<td><code>true</code></td>
<td>When false, the edge stops at the arrow. This can be useful if you have thick lines and you want the arrow to end in a point. Middle arrows are not affected by this.</td>
</tr>
<tr class='toggle collapsible' onclick="toggleTable('optionTable','chosen', this);">
<td><span parent="chosen" class="right-caret"></span> chosen</td>
<td>Object or Boolean</td>
<td><code>true</code></td>
<td>
When true, selecting or hovering on an edge will change it and its label's characteristics according the default.
When false, no change to the edge or its label will occur when the edge is chosen.
If an object is supplied, finer-grained adjustment of edge and label characteristics is available when an edge is chosen.
</td>
</tr>
<tr parent="chosen" class="hidden">
<td class="indent">chosen.edge</td>
<td>Function or Boolean</td>
<td>undefined</td>
<td>
When true, selecting or hovering on an edge will change its characteristics according the default.
When false, no change to the edge will occur when the edge is chosen.
<p>
If a function is supplied, it is called when the edge is chosen.
<pre class="code">
function(values, id, selected, hovering) {
values.<i>property</i> = <i>chosenValue</i>;
}</pre>
</p>
<p>
Any of the incoming arguments may be used to determine characteristic changes.
If a property is not specifically assigned by the supplied function, it will be left unchanged.
A specific function may be assigned to each particular edge in its options, or to all in the network's <code>edges</code> options.
</p>
<p>
The properties define the characteristics that can be changed as follows:
</p>
<table>
<tr><th>Property</th><th>Edge Reference</th></tr>
<tr><td>dashes</td><td>see dashes</td></tr>
<tr><td>toArrow</td><td>see arrows.to.enabled</td></tr>
<tr><td>toArrowScale</td><td>see arrows.to.scaleFactor</td></tr>
<tr><td>toArrowType</td><td>see arrows.to.type</td></tr>
<tr><td>middleArrow</td><td>see arrows.middle.enabled</td></tr>
<tr><td>middleArrowScale</td><td>see arrows.middle.scaleFactor</td></tr>
<tr><td>middleArrowType</td><td>see arrows.middle.type</td></tr>
<tr><td>fromArrow</td><td>see arrows.from.enabled</td></tr>
<tr><td>fromArrowScale</td><td>see arrows.from.scaleFactor</td></tr>
<tr><td>fromArrowType</td><td>see arrows.from.type</td></tr>
<tr><td>arrowStrikethrough</td><td>see arrowStrikethrough</td></tr>
<tr><td>color</td><td>see color.color</td></tr>
<tr><td>inheritsColor</td><td>see color.inherit</td></tr>
<tr><td>opacity</td><td>see color.opacity</td></tr>
<tr><td>hidden</td><td>see hidden</td></tr>
<tr><td>length</td><td>see length</td></tr>
<tr><td>shadow</td><td>see shadow.enabled</td></tr>
<tr><td>shadowColor</td><td>see shadow.color</td></tr>
<tr><td>shadowSize</td><td>see shadow.size</td></tr>
<tr><td>shadowX</td><td>see shadow.x</td></tr>
<tr><td>shadowY</td><td>see shadow.y</td></tr>
<tr><td>width</td><td>see width</td></tr>
</table>
<br/>
</td>
</tr>
<tr parent="chosen" class="hidden">
<td class="indent">chosen.label</td>
<td>Function or Boolean</td>
<td>undefined</td>
<td>
When true, selecting or hovering on an edge will change its label's characteristics according the default.
When false, no change to the edge's label will occur when the edge is chosen.
<p>
If a function is supplied, it is called when the edge is chosen.
<pre class="code">
function(values, id, selected, hovering) {
values.<i>property</i> = <i>chosenValue</i>;
}</pre>
</p>
<p>
Any of the incoming arguments may be used to determine characteristic changes.
If a property is not specifically assigned by the supplied function, it will be left unchanged.
A specific function may be assigned to each particular edge in its options, or to all in the network's <code>edges</code> options.
</p>
<p>
The properties define the characteristics that can be changed as follows:
</p>
<table>
<tr><th>Property</th><th>Edge Reference</th></tr>
<tr><td>color</td><td>see font.color</td></tr>
<tr><td>size</td><td>see font.size</td></tr>
<tr><td>face</td><td>see font.face</td></tr>
<tr><td>mod</td><td>font modifier ('bold', 'italic', etc.)</td></tr>
<tr><td>vadjust</td><td>see font.vadjust</td></tr>
<tr><td>strokeWidth</td><td>see font.strokeWidth</td></tr>
<tr><td>strokeColor</td><td>see font.strokeColor</td></tr>
</table>
<br/>
</td>
</tr>
<tr class='toggle collapsible' onclick="toggleTable('optionTable','color', this);">
<td><span parent="color" class="right-caret"></span> color</td>
<td>Object or String</td>
@ -311,7 +407,7 @@ network.setOptions(options);
<td class="indent">color.color</td>
<td>String</td>
<td><code>'#848484'</code></td>
<td>The color of the border of the node when it is not selected or hovered over <i>(assuming hover is
<td>The color of the edge when it is not selected or hovered over <i>(assuming hover is
enabled in the interaction module)</i>.
</td>
</tr>

+ 85
- 0
docs/network/nodes.html View File

@ -110,6 +110,7 @@ var options = {
borderWidth: 1,
borderWidthSelected: 2,
brokenImage:undefined,
chosen: true,
color: {
border: '#2B7CE9',
background: '#97C2FC',
@ -271,6 +272,90 @@ network.setOptions(options);
a backup image in case the URL supplied in the image option cannot be resolved.
</td>
</tr>
<tr class='toggle collapsible' onclick="toggleTable('optionTable','chosen', this);">
<td><span parent="chosen" class="right-caret"></span> chosen</td>
<td>Object or Boolean</td>
<td><code>true</code></td>
<td>
When true, selecting or hovering on a node will change it and its label's characteristics according the default.
When false, no change to the node or its label will occur when the node is chosen.
If an object is supplied, finer-grained adjustment of node and label characteristics is available when a node is chosen.
</td>
</tr>
<tr parent="chosen" class="hidden">
<td class="indent">chosen.node</td>
<td>Function or Boolean</td>
<td>undefined</td>
<td>
When true, selecting or hovering on a node will change its characteristics according the default.
When false, no change to the node will occur when the node is chosen.
<p>
If a function is supplied, it is called when the node is chosen.
<pre class="code">
function(values, id, selected, hovering) {
values.<i>property</i> = <i>chosenValue</i>;
}</pre>
</p>
<p>
Any of the incoming arguments may be used to determine characteristic changes.
If a property is not specifically assigned by the supplied function, it will be left unchanged.
A specific function may be assigned to each particular node in its options, or to all in the network's <code>nodes</code> options.
</p>
<p>
The properties define the characteristics that can be changed as follows:
</p>
<table>
<tr><th>Property</th><th>Node Reference</th></tr>
<tr><td>color</td><td>see color.background</td></tr>
<tr><td>borderWidth</td><td>see borderWidth</td></tr>
<tr><td>borderColor</td><td>see color.border</td></tr>
<tr><td>size</td><td>see size</td></tr>
<tr><td>borderDashes</td><td>see shapeProperties.borderDashes</td></tr>
<tr><td>borderRadius</td><td>see shapeProperties.borderRadius</td></tr>
<tr><td>shadow</td><td>see shadow.enabled</td></tr>
<tr><td>shadowColor</td><td>see shadow.color</td></tr>
<tr><td>shadowSize</td><td>see shadow.size</td></tr>
<tr><td>shadowX</td><td>see shadow.x</td></tr>
<tr><td>shadowY</td><td>see shadow.y</td></tr>
</table>
<br/>
</td>
</tr>
<tr parent="chosen" class="hidden">
<td class="indent">chosen.label</td>
<td>Function or Boolean</td>
<td>undefined</td>
<td>
When true, selecting or hovering on a node will change its label's characteristics according the default.
When false, no change to the node's label will occur when the node is chosen.
<p>
If a function is supplied, it is called when the node is chosen.
<pre class="code">
function(values, id, selected, hovering) {
values.<i>property</i> = <i>chosenValue</i>;
}</pre>
</p>
<p>
Any of the incoming arguments may be used to determine characteristic changes.
If a property is not specifically assigned by the supplied function, it will be left unchanged.
A specific function may be assigned to each particular node in its options, or to all in the network's <code>nodes</code> options.
</p>
<p>
The properties define the characteristics that can be changed as follows:
</p>
<table>
<tr><th>Property</th><th>Node Reference</th></tr>
<tr><td>color</td><td>see font.color</td></tr>
<tr><td>size</td><td>see font.size</td></tr>
<tr><td>face</td><td>see font.face</td></tr>
<tr><td>mod</td><td>font modifier ('bold', 'italic', etc.)</td></tr>
<tr><td>vadjust</td><td>see font.vadjust</td></tr>
<tr><td>strokeWidth</td><td>see font.strokeWidth</td></tr>
<tr><td>strokeColor</td><td>see font.strokeColor</td></tr>
</table>
<br/>
</td>
</tr>
<tr class='toggle collapsible' onclick="toggleTable('optionTable','color', this);">
<td><span parent="color" class="right-caret"></span> color</td>
<td>Object or String</td>

+ 28
- 2
docs/timeline/index.html View File

@ -324,7 +324,7 @@ var items = new vis.DataSet([
<td>String</td>
<td>none</td>
<td>Add a title for the item, displayed when holding the mouse on the item.
The title can only contain plain text.
The title can be an HTML element or a string containing plain text or HTML.
</td>
</tr>
<tr>
@ -382,7 +382,7 @@ var groups = [
{
id: 1,
content: 'Group 1'
// Optional: a field 'className', 'style'
// Optional: a field 'className', 'style', 'order', [properties]
}
// more groups...
]);
@ -461,6 +461,18 @@ var groups = [
<td>no</td>
<td>Provides a means to toggle the whether a group is displayed or not. Defaults to <code>true</code>.</td>
</tr>
<tr>
<td>nestedGroups</td>
<td>Array</td>
<td>no</td>
<td>Array of group ids nested in the group. Nested groups will appear under this nesting group.</td>
</tr>
<tr>
<td>showNestedGroups</td>
<td>Boolean</td>
<td>no</td>
<td>Assuming the group has nested groups, this will set the initial state of the group - shown or collapsed. The <code>showNestedGroups</code> is defaulted to <code>true</code>.</td>
</tr>
</table>
@ -1003,6 +1015,13 @@ function (option, path) {
<td>If true (default), items will be stacked on top of each other such that they do not overlap.</td>
</tr>
<tr>
<td>stackSubgroups</td>
<td>boolean</td>
<td><code>true</code></td>
<td>If true (default), subgroups will be stacked on top of each other such that they do not overlap.</td>
</tr>
<tr>
<td>snap</td>
<td>function or null</td>
@ -1036,6 +1055,13 @@ function (option, path) {
This would be used as an additional way to add content that is constant in size with the visible frame of the item and does not get visibly hidden with the item's internal container: <code>vis-item-overflow</code> which is <code>overflow:hidden</code>.</td>
</tr>
<tr class='depricated'>
<td>throttleRedraw</td>
<td>number</td>
<td><code>0</code></td>
<td>This option is <b>DEPRICATED</b> and no longer supported. It will be removed in the next MAJOR release.</td>
</tr>
<tr class='toggle collapsible' onclick="toggleTable('optionTable','timeAxis', this);">
<td><span parent="timeAxis" class="right-caret"></span> timeAxis</td>
<td>Object</td>

+ 6
- 2
examples/graph3d/07_dot_cloud_colors.html View File

@ -13,6 +13,9 @@
var data = null;
var graph = null;
function onclick(point) {
console.log(point);
}
// Called when the Visualization API is loaded.
function drawVisualization() {
@ -30,9 +33,9 @@
var x = pow(random(), 2);
var y = pow(random(), 2);
var z = pow(random(), 2);
var dist = sqrt(pow(x, 2) + pow(y, 2) + pow(z, 2));
var style = (i%2==0) ? sqrt(pow(x, 2) + pow(y, 2) + pow(z, 2)) : "#00ffff";
data.add({x:x,y:y,z:z,style:dist});
data.add({x:x,y:y,z:z,style:style});
}
// specify options
@ -45,6 +48,7 @@
keepAspectRatio: true,
verticalRatio: 1.0,
legendLabel: 'distance',
onclick: onclick,
cameraPosition: {
horizontal: -0.35,
vertical: 0.22,

+ 466
- 0
examples/network/other/chosen.html View File

@ -0,0 +1,466 @@
<!doctype html>
<html>
<head>
<title>Network | Chosen Elements</title>
<script type="text/javascript" src="../../../dist/vis.js"></script>
<link href="../../../dist/vis-network.min.css" rel="stylesheet" type="text/css" />
<style type="text/css">
#mynetwork {
width: 900px;
height: 600px;
border: 1px solid lightgray;
}
code {
font-size: 14px;
background: #dddddd;
}
p {
max-width: 600px;
}
.indented {
margin-left: 30px;
}
.sep {
height: 1px;
width: 35%;
margin-left: 40px;
background-color: #dddddd;
}
</style>
<script src="../../googleAnalytics.js"></script>
</head>
<body>
<p>When a node or edge is selected or hovered its visible characteristics can be changed.</p>
<div id="mynetwork"></div>
<p>In this network, an element (node, edge or label) will change a characteristic when hovered, and it will be locked in when selected.
This is managed by setting up a 'chosen' function that will be called when the element containing the function is chosen.
These functions may be set on nodes, edges and labels, at the individual or group level.</p>
<p>All states (unselected, hover-over-unselected, selected, and hover-over selected) may be handled as needed by the application using vis, as the select and hover states are passed to the chosen function when called.
Additionally, the id of the element is passed to allow context-specific characteristic adjustment on select or hover as needed.</p>
<p><i>It should be noted that the characteristics which might affect the position of elements have been left out on purpose.
While it might be interesting to make them changeable, this is problematic on hovering.
Consider that the user hovers over an object.
If it changed characteristics that moved it outside of the hover-distance, it would then no longer be hovering.
So it would be moved back to its original prosition, within the hover-distance and then again be hovering over the object.
This hysteresis loop is kept from occurring by leaving out the characteristics that could cause it.
Some seemingly innocuous changes (such as resizing a node's label on hover that would in turn cause the node to resize and move out of hover-distance) may still cause hysteresis, but with care they should be avoidable.</i></p>
<script type="text/javascript">
var changeChosenLabelColor =
function(ctx, values, id) {
values.color = "#ff0000";
}
var changeChosenLabelSize =
function(ctx, values, id) {
values.size += 1;
}
var changeChosenLabelFace =
function(ctx, values, id) {
values.face = "serif";
}
var changeChosenLabelMod =
function(ctx, values, id) {
values.mod = "bold italic";
}
var changeChosenLabelStrokeWidth =
function(ctx, values, id) {
values.strokeWidth = 5;
}
var changeChosenLabelStrokeColor =
function(ctx, values, id) {
values.strokeColor = "#00ff00";
}
var changeChosenNodeColor =
function(values, id, selected, hovering) {
values.color = "#ffdd88";
}
var changeChosenNodeBorderWidth =
function(values, id, selected, hovering) {
values.borderWidth = 3;
}
var changeChosenNodeBorderColor =
function(values, id, selected, hovering) {
values.borderColor = "#ff0000";
}
var changeChosenNodeSize =
function(values, id, selected, hovering) {
values.size = 30;
}
var changeChosenNodeBorderDashes =
function(values, id, selected, hovering) {
values.borderDashes = [ 10, 10 ];
}
var changeChosenNodeBorderRadius =
function(values, id, selected, hovering) {
values.borderRadius = 15;
}
var changeChosenNodeShadow =
function(values, id, selected, hovering) {
values.shadow = true;
}
var changeChosenNodeShadowColor =
function(values, id, selected, hovering) {
values.shadowColor = "#ff0000";
}
var changeChosenNodeShadowSize =
function(values, id, selected, hovering) {
values.shadowSize = 30;
}
var changeChosenNodeShadowX =
function(values, id, selected, hovering) {
values.shadowX = -5;
}
var changeChosenNodeShadowY =
function(values, id, selected, hovering) {
values.shadowY = -5;
}
var changeChosenEdgeToArrow =
function(values, id, selected, hovering) {
values.toArrow = true;
}
var changeChosenEdgeToArrowScale =
function(values, id, selected, hovering) {
values.toArrowScale = 2;
}
var changeChosenEdgeToArrowType =
function(values, id, selected, hovering) {
values.toArrowType = "circle";
}
var changeChosenEdgeMiddleArrow =
function(values, id, selected, hovering) {
values.middleArrow = true;
}
var changeChosenEdgeMiddleArrowScale =
function(values, id, selected, hovering) {
values.middleArrowScale = 2;
}
var changeChosenEdgeMiddleArrowType =
function(values, id, selected, hovering) {
values.middleArrowType = "circle";
}
var changeChosenEdgeFromArrow =
function(values, id, selected, hovering) {
values.fromArrow = true;
}
var changeChosenEdgeFromArrowScale =
function(values, id, selected, hovering) {
values.fromArrowScale = 2;
}
var changeChosenEdgeFromArrowType =
function(values, id, selected, hovering) {
values.fromArrowType = "circle";
}
var changeChosenEdgeArrowStrikethrough =
function(values, id, selected, hovering) {
values.arrowStrikethrough = false;
}
var changeChosenEdgeColor =
function(values, id, selected, hovering) {
values.color = "#00ff00";
}
var changeChosenEdgeInheritsColor =
function(values, id, selected, hovering) {
values.inheritsColor = "both";
}
var changeChosenEdgeOpacity =
function(values, id, selected, hovering) {
values.opacity = 0.25;
}
var changeChosenEdgeHidden =
function(values, id, selected, hovering) {
values.hidden = true;
}
var changeChosenEdgeShadow =
function(values, id, selected, hovering) {
values.shadow = true;
}
var changeChosenEdgeShadowColor =
function(values, id, selected, hovering) {
values.shadowColor = "#00ffff";
}
var changeChosenEdgeShadowSize =
function(values, id, selected, hovering) {
values.shadowSize = 20;
}
var changeChosenEdgeShadowX =
function(values, id, selected, hovering) {
values.shadowX = -5;
}
var changeChosenEdgeShadowY =
function(values, id, selected, hovering) {
values.shadowY = -5;
}
var changeChosenEdgeWidth =
function(values, id, selected, hovering) {
values.width = 5;
}
var changeChosenEdgeDashes =
function(values, id, selected, hovering) {
values.dashes = [10, 10];
}
var nodes = [
{ id: 1000, label: "label does not change",
x: -400, y: -300,
chosen: { label: false, node: false } },
{ id: 1010, label: "label default settings",
x: -400, y: -225,
chosen: { label: true, node: false } },
{ id: 1020, label: "label changes color",
x: -400, y: -150,
chosen: { label: changeChosenLabelColor, node: false } },
{ id: 1030, label: "label changes size",
x: -400, y: -75,
chosen: { label: changeChosenLabelSize, node: false } },
{ id: 1040, label: "label changes face",
x: -400, y: 0,
chosen: { label: changeChosenLabelFace, node: false } },
{ id: 1050, label: "label changes modifier",
x: -400, y: 75,
chosen: { label: changeChosenLabelMod, node: false } },
{ id: 1060, label: "label changes stokeWidth",
x: -400, y: 150,
chosen: { label: changeChosenLabelStrokeWidth, node: false } },
{ id: 1070, label: "label changes stokeColor",
x: -400, y: 225, font: { strokeWidth: 2 },
chosen: { label: changeChosenLabelStrokeColor, node: false } },
{ id: 2000, label: "node does not change",
x: 0, y: -300,
chosen: { label: false, node: false } },
{ id: 2010, label: "node default settings",
x: 0, y: -225,
chosen: { label: false, node: true } },
{ id: 2020, label: "node changes color",
x: 0, y: -150,
chosen: { label: false, node: changeChosenNodeColor } },
{ id: 2030, label: "node changes borderWidth",
x: 0, y: -75,
chosen: { label: false, node: changeChosenNodeBorderWidth } },
{ id: 2040, label: "node changes borderColor",
x: 0, y: 0,
chosen: { label: false, node: changeChosenNodeBorderColor } },
{ id: 2050, label: "shaped nodes change size",
x: 0, y: 70, shape: 'star',
chosen: { label: false, node: changeChosenNodeSize } },
{ id: 2051, x: -60, y: 70, shape: 'dot',
chosen: { label: false, node: changeChosenNodeSize } },
{ id: 2052, x: -120, y: 70, shape: 'diamond',
chosen: { label: false, node: changeChosenNodeSize } },
{ id: 2053, x: 60, y: 70, shape: 'square',
chosen: { label: false, node: changeChosenNodeSize } },
{ id: 2054, x: 120, y: 70, shape: 'triangle',
chosen: { label: false, node: changeChosenNodeSize } },
{ id: 2055, x: 165, y: 70, shape: 'triangleDown',
chosen: { label: false, node: changeChosenNodeSize } },
{ id: 2060, label: "node changes borderDashes",
x: 0, y: 150,
chosen: { label: false, node: changeChosenNodeBorderDashes } },
{ id: 2070, label: "node changes borderRadius",
x: 0, y: 225,
chosen: { label: false, node: changeChosenNodeBorderRadius } },
{ id: 2080, label: "node changes shadow",
x: 0, y: 300,
chosen: { label: false, node: changeChosenNodeShadow } },
{ id: 2090, label: "node changes shadowColor",
x: 0, y: 375, shadow: true,
chosen: { label: false, node: changeChosenNodeShadowColor } },
{ id: 2100, label: "node changes shadowSize",
x: 0, y: 450, shadow: true,
chosen: { label: false, node: changeChosenNodeShadowSize } },
{ id: 2110, label: "node changes shadowX",
x: 0, y: 525, shadow: true,
chosen: { label: false, node: changeChosenNodeShadowX } },
{ id: 2120, label: "node changes shadowY",
x: 0, y: 600, shadow: true,
chosen: { label: false, node: changeChosenNodeShadowY } },
{ id: 3000, x: 275, y: -310, chosen: false, shape: "dot", size: 10, color: "#dd6644", borderWidth: 0 },
{ id: 3001, x: 525, y: -210, chosen: false, shape: "dot", size: 10, color: "#6699dd", borderWidth: 0 },
{ id: 3010, x: 275, y: -235, chosen: false, shape: "dot", size: 10, color: "#dd6644", borderWidth: 0 },
{ id: 3011, x: 525, y: -135, chosen: false, shape: "dot", size: 10, color: "#6699dd", borderWidth: 0 },
{ id: 3020, x: 275, y: -160, chosen: false, shape: "dot", size: 10, color: "#dd6644", borderWidth: 0 },
{ id: 3021, x: 525, y: -60, chosen: false, shape: "dot", size: 10, color: "#6699dd", borderWidth: 0 },
{ id: 3030, x: 275, y: -85, chosen: false, shape: "dot", size: 10, color: "#dd6644", borderWidth: 0 },
{ id: 3031, x: 525, y: 15, chosen: false, shape: "dot", size: 10, color: "#6699dd", borderWidth: 0 },
{ id: 3040, x: 275, y: -10, chosen: false, shape: "dot", size: 10, color: "#dd6644", borderWidth: 0 },
{ id: 3041, x: 525, y: 90, chosen: false, shape: "dot", size: 10, color: "#6699dd", borderWidth: 0 },
{ id: 3050, x: 275, y: 65, chosen: false, shape: "dot", size: 10, color: "#dd6644", borderWidth: 0 },
{ id: 3051, x: 525, y: 165, chosen: false, shape: "dot", size: 10, color: "#6699dd", borderWidth: 0 },
{ id: 3060, x: 275, y: 140, chosen: false, shape: "dot", size: 10, color: "#dd6644", borderWidth: 0 },
{ id: 3061, x: 525, y: 240, chosen: false, shape: "dot", size: 10, color: "#6699dd", borderWidth: 0 },
{ id: 3070, x: 275, y: 215, chosen: false, shape: "dot", size: 10, color: "#dd6644", borderWidth: 0 },
{ id: 3071, x: 525, y: 315, chosen: false, shape: "dot", size: 10, color: "#6699dd", borderWidth: 0 },
{ id: 3080, x: 275, y: 290, chosen: false, shape: "dot", size: 10, color: "#dd6644", borderWidth: 0},
{ id: 3081, x: 525, y: 390, chosen: false, shape: "dot", size: 10, color: "#6699dd", borderWidth: 0 },
{ id: 3090, x: 275, y: 365, chosen: false, shape: "dot", size: 10, color: "#dd6644", borderWidth: 0 },
{ id: 3091, x: 525, y: 465, chosen: false, shape: "dot", size: 10, color: "#6699dd", borderWidth: 0 },
{ id: 3100, x: 275, y: 440, chosen: false, shape: "dot", size: 10, color: "#dd6644", borderWidth: 0 },
{ id: 3101, x: 525, y: 540, chosen: false, shape: "dot", size: 10, color: "#6699dd", borderWidth: 0 },
{ id: 3110, x: 275, y: 515, chosen: false, shape: "dot", size: 10, color: "#dd6644", borderWidth: 0 },
{ id: 3111, x: 525, y: 615, chosen: false, shape: "dot", size: 10, color: "#6699dd", borderWidth: 0 },
{ id: 3120, x: 575, y: -310, chosen: false, shape: "dot", size: 10, color: "#dd6644", borderWidth: 0 },
{ id: 3121, x: 825, y: -210, chosen: false, shape: "dot", size: 10, color: "#6699dd", borderWidth: 0 },
{ id: 3130, x: 575, y: -235, chosen: false, shape: "dot", size: 10, color: "#dd6644", borderWidth: 0 },
{ id: 3131, x: 825, y: -135, chosen: false, shape: "dot", size: 10, color: "#6699dd", borderWidth: 0 },
{ id: 3140, x: 575, y: -160, chosen: false, shape: "dot", size: 10, color: "#dd6644", borderWidth: 0 },
{ id: 3141, x: 825, y: -60, chosen: false, shape: "dot", size: 10, color: "#6699dd", borderWidth: 0 },
{ id: 3150, x: 575, y: -85, chosen: false, shape: "dot", size: 10, color: "#dd6644", borderWidth: 0 },
{ id: 3151, x: 825, y: 15, chosen: false, shape: "dot", size: 10, color: "#6699dd", borderWidth: 0 },
{ id: 3160, x: 575, y: -10, chosen: false, shape: "dot", size: 10, color: "#dd6644", borderWidth: 0 },
{ id: 3161, x: 825, y: 90, chosen: false, shape: "dot", size: 10, color: "#6699dd", borderWidth: 0 },
{ id: 3170, x: 575, y: 65, chosen: false, shape: "dot", size: 10, color: "#dd6644", borderWidth: 0 },
{ id: 3171, x: 825, y: 165, chosen: false, shape: "dot", size: 10, color: "#6699dd", borderWidth: 0 },
{ id: 3180, x: 575, y: 140, chosen: false, shape: "dot", size: 10, color: "#dd6644", borderWidth: 0 },
{ id: 3181, x: 825, y: 240, chosen: false, shape: "dot", size: 10, color: "#6699dd", borderWidth: 0 },
{ id: 3190, x: 575, y: 215, chosen: false, shape: "dot", size: 10, color: "#dd6644", borderWidth: 0 },
{ id: 3191, x: 825, y: 315, chosen: false, shape: "dot", size: 10, color: "#6699dd", borderWidth: 0 },
{ id: 3200, x: 575, y: 290, chosen: false, shape: "dot", size: 10, color: "#dd6644", borderWidth: 0 },
{ id: 3201, x: 825, y: 390, chosen: false, shape: "dot", size: 10, color: "#6699dd", borderWidth: 0 },
{ id: 3210, x: 575, y: 365, chosen: false, shape: "dot", size: 10, color: "#dd6644", borderWidth: 0 },
{ id: 3211, x: 825, y: 465, chosen: false, shape: "dot", size: 10, color: "#6699dd", borderWidth: 0 },
{ id: 3220, x: 575, y: 440, chosen: false, shape: "dot", size: 10, color: "#dd6644", borderWidth: 0 },
{ id: 3221, x: 825, y: 540, chosen: false, shape: "dot", size: 10, color: "#6699dd", borderWidth: 0 },
];
var edges = [
{ id: 4000, from: 3000, to: 3001, label: "edge does not change",
chosen: false },
{ id: 4010, from: 3010, to: 3011, label: "edge has default settings",
chosen: { label: false, edge: true } },
{ id: 4020, from: 3020, to: 3021, label: "edge changes toArrow",
chosen: { label: false, edge: changeChosenEdgeToArrow } },
{ id: 4030, from: 3030, to: 3031, label: "edge changes toArrowScale",
arrows: { to: true },
chosen: { label: false, edge: changeChosenEdgeToArrowScale } },
{ id: 4040, from: 3040, to: 3041, label: "edge changes toArrowType",
arrows: { to: true },
chosen: { label: false, edge: changeChosenEdgeToArrowType } },
{ id: 4050, from: 3050, to: 3051, label: "edge changes middleArrow",
chosen: { label: false, edge: changeChosenEdgeMiddleArrow } },
{ id: 4060, from: 3060, to: 3061, label: "edge changes middleArrowScale",
arrows: { middle: true },
chosen: { label: false, edge: changeChosenEdgeMiddleArrowScale } },
{ id: 4070, from: 3070, to: 3071, label: "edge changes middleArrowType",
arrows: { middle: true },
chosen: { label: false, edge: changeChosenEdgeMiddleArrowType } },
{ id: 4080, from: 3080, to: 3081, label: "edge changes fromArrow",
chosen: { label: false, edge: changeChosenEdgeFromArrow } },
{ id: 4090, from: 3090, to: 3091, label: "edge changes fromArrowScale",
arrows: { from: true },
chosen: { label: false, edge: changeChosenEdgeFromArrowScale } },
{ id: 4100, from: 3100, to: 3101, label: "edge changes fromArrowType",
arrows: { from: true },
chosen: { label: false, edge: changeChosenEdgeFromArrowType } },
{ id: 4110, from: 3110, to: 3111, label: "edge changes arrowStrikethrough",
arrows: { to: true, from: true }, width: 7,
chosen: { label: false, edge: changeChosenEdgeArrowStrikethrough } },
{ id: 4120, from: 3120, to: 3121, label: "edge changes color",
chosen: { label: false, edge: changeChosenEdgeColor } },
{ id: 4130, from: 3130, to: 3131, label: "edge inherits color",
chosen: { label: false, edge: changeChosenEdgeInheritsColor } },
{ id: 4140, from: 3140, to: 3141, label: "edge changes opacity",
chosen: { label: false, edge: changeChosenEdgeOpacity } },
{ id: 4150, from: 3150, to: 3151, label: "edge changes hidden",
chosen: { label: false, edge: changeChosenEdgeHidden } },
{ id: 4160, from: 3160, to: 3161, label: "edge changes shadow",
width: 7,
chosen: { label: false, edge: changeChosenEdgeShadow } },
{ id: 4170, from: 3170, to: 3171, label: "edge changes shadowColor",
shadow: true, width: 7,
chosen: { label: false, edge: changeChosenEdgeShadowColor } },
{ id: 4180, from: 3180, to: 3181, label: "edge changes shadowSize",
shadow: true, width: 7,
chosen: { label: false, edge: changeChosenEdgeShadowSize } },
{ id: 4190, from: 3190, to: 3191, label: "edge changes shadowX",
shadow: true, width: 7,
chosen: { label: false, edge: changeChosenEdgeShadowX } },
{ id: 4200, from: 3200, to: 3201, label: "edge changes shadowY",
shadow: true, width: 7,
chosen: { label: false, edge: changeChosenEdgeShadowY } },
{ id: 4210, from: 3210, to: 3211, label: "edge changes width",
chosen: { label: false, edge: changeChosenEdgeWidth } },
{ id: 4220, from: 3220, to: 3221, label: "edge changes dashes",
chosen: { label: false, edge: changeChosenEdgeDashes } },
];
var container = document.getElementById('mynetwork');
var data = {
nodes: nodes,
edges: edges
};
var options = {
edges: {
font: {
size: 16
},
selfReferenceSize: 30
},
nodes: {
shape: 'box',
widthConstraint: 250,
color: "#cccccc",
margin: 10,
font: {
size: 16
}
},
physics: {
enabled: false
},
interaction: {
hover: true
}
};
var network = new vis.Network(container, data, options);
</script>
</body>
</html>

+ 113
- 0
examples/timeline/groups/nestedGroups.html View File

@ -0,0 +1,113 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Timeline | Nested Groups example</title>
<style>
body, html {
font-family: arial, sans-serif;
font-size: 11pt;
}
#visualization {
box-sizing: border-box;
width: 100%;
height: 300px;
}
</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.8.4/moment.min.js"></script>
<script src="../../../dist/vis.js"></script>
<link href="../../../dist/vis.min.css" rel="stylesheet" type="text/css" />
</head>
<body>
<p>
This example demonstrate using groups. Note that a DataSet is used for both
items and groups, allowing to dynamically add, update or remove both items
and groups via the DataSet.
</p>
<div id="visualization"></div>
<script>
var now = moment().minutes(0).seconds(0).milliseconds(0);
var itemCount = 60;
// create a data set with groups
var groups = new vis.DataSet();
groups.add([
{
id: 1,
content: "Lee",
nestedGroups: [11,12,13]
},
{
id: 2,
content: "invisible group",
visible: false
},
{
id: 3,
content: "John",
nestedGroups: [14],
showNested: false
},
{
id: 4,
content: "Alson"
},
]);
groups.add([
{
id: 11,
content: "cook",
},
{
id: 12,
content: "shop",
},
{
id: 13,
content: "clean house",
},
{
id: 14,
content: "wash dishes",
}
]);
// create a dataset with items
var items = new vis.DataSet();
var groupIds = groups.getIds();
var types = [ 'box', 'point', 'range', 'background']
for (var i = 0; i < itemCount; i++) {
var start = now.clone().add(Math.random() * 200, 'hours');
var end = start.clone().add(2, 'hours');
var randomGroupId = groupIds[Math.floor(Math.random() * groupIds.length)];
var type = types[Math.floor(4 * Math.random())]
items.add({
id: i,
group: randomGroupId,
content: 'item ' + i,
start: start,
end: end,
type: type
});
}
// create visualization
var container = document.getElementById('visualization');
var options = {
groupOrder: 'content' // groupOrder can be a property name or a sorting function
};
var timeline = new vis.Timeline(container, items, groups, options);
</script>
</body>
</html>

+ 11
- 6
examples/timeline/groups/subgroups.html View File

@ -1,7 +1,10 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Timeline | Background areas</title>
<title>Timeline | Subgroups</title>
<script src="../../../dist/vis.js"></script>
<link href="../../../dist/vis.css" rel="stylesheet" type="text/css" />
<style>
body, html {
@ -19,14 +22,11 @@
border-left: 2px solid green;
}
</style>
<script src="../../../dist/vis.js"></script>
<link href="../../../dist/vis-timeline-graph2d.min.css" rel="stylesheet" type="text/css" />
<script src="../../googleAnalytics.js"></script>
</head>
<body>
<p>This example shows the workings of the subgroups. Subgroups do not use stacking, and only work when stacking is disabled.</p>
<button onclick="toggleStackSubgroups()">Toggle stackSubgroups</button>
<div id="visualization"></div>
@ -66,11 +66,16 @@
start: '2014-01-10',
end: '2014-02-10',
editable: true,
stack: false
stack: false,
stackSubgroups: true
};
var timeline = new vis.Timeline(container, items, groups, options);
function toggleStackSubgroups() {
options.stackSubgroups = !options.stackSubgroups;
timeline.setOptions(options);
}
</script>
</body>
</html>

+ 111
- 0
examples/timeline/items/expectedVsActualTimesItems.html View File

@ -0,0 +1,111 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Timeline | expected vs actual times items</title>
<script src="../../../dist/vis.js"></script>
<link href="../../../dist/vis.css" rel="stylesheet" type="text/css" />
<style>
body, html {
font-family: arial, sans-serif;
font-size: 11pt;
}
.vis-item.expected {
background-color: transparent;
border-style: dashed!important;
z-index: 0;
}
.vis-item.vis-selected {
opacity: 0.6;
}
.vis-item.vis-background.marker {
border-left: 2px solid green;
}
</style>
</head>
<body>
<div id="visualization"></div>
<script>
// create a dataset with items
// we specify the type of the fields `start` and `end` here to be strings
// containing an ISO date. The fields will be outputted as ISO dates
// automatically getting data from the DataSet via items.get().
var items = new vis.DataSet({
type: { start: 'ISODate', end: 'ISODate' }
});
var groups = new vis.DataSet([
{
id: 'group1',
content:'group1'
}
]);
// add items to the DataSet
items.add([
// group 1
{
id: 'background1',
start: '2014-01-21',
end: '2014-01-22',
type: 'background',
group:'group1'
},
// subgroup 1
{
id: 1,
content: 'item 1 (expected time)',
className: 'expected',
start: '2014-01-23 12:00:00',
end: '2014-01-26 12:00:00',
group:'group1',
subgroup:'sg_1'
},
{
id: 2,
content: 'item 1 (actual time)',
start: '2014-01-24 12:00:00',
end: '2014-01-27 12:00:00',
group:'group1',
subgroup:'sg_1'
},
// subgroup 2
{
id: 3,
content: 'item 2 (expected time)',
className: 'expected',
start: '2014-01-13 12:00:00',
end: '2014-01-16 12:00:00',
group:'group1',
subgroup:'sg_2'
},
{
id: 4,
content: 'item 2 (actual time)',
start: '2014-01-14 12:00:00',
end: '2014-01-17 12:00:00',
group:'group1',
subgroup:'sg_2'
}
]);
var container = document.getElementById('visualization');
var options = {
start: '2014-01-10',
end: '2014-02-10',
editable: true,
stack: false,
stackSubgroups: false
};
var timeline = new vis.Timeline(container, items, groups, options);
</script>
</body>
</html>

+ 49
- 0
examples/timeline/items/tooltip.html View File

@ -0,0 +1,49 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Timeline | Tooltips</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-timeline-graph2d.min.css" rel="stylesheet" type="text/css" />
<script src="../../googleAnalytics.js"></script>
</head>
<body>
<h1>Tooltips</h1>
<p>
Setting the tooltip in various ways.
</p>
<div id="tooltips"></div>
<script type="text/javascript">
// Create a DataSet (allows two way data-binding)
var items = new vis.DataSet([
{id: 1, content: 'Item 1', start: '2016-01-01', end: '2016-01-02',
title: 'Normal text'},
{id: 2, content: 'Item 2', start: '2016-01-02', title: '<b>Bold</b>'},
{id: 3, content: 'Item 3', start: '2016-01-03', type: 'point',
title: '<span style="color: red">Red</span> text'},
{id: 4, content: '<h1>HTML</h1> Item', start: '2016-01-03', end: '2016-01-04',
title: '<table border="1"><tr><td>Cell 1</td><td>Cell 2</td></tr></table>'}
]);
// Options
var options = {};
// Timeline object
var timelineTooltips = new vis.Timeline(document.getElementById('tooltips'),
items, options
);
</script>
</body>
</html>

+ 4
- 5
gulpfile.js View File

@ -48,11 +48,10 @@ var webpackModule = {
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel',
loader: 'babel-loader',
query: {
cacheDirectory: true,
presets: ['es2015'],
plugins: ['transform-es3-property-literals', 'transform-es3-member-expression-literals']
cacheDirectory: true, // use cache to improve speed
babelrc: true // use the .baberc file
}
}
],
@ -75,7 +74,7 @@ var webpackConfig = {
cache: true,
// generate details sourcempas of webpack modules
//devtool: 'source-map'
devtool: 'source-map'
//debug: true,
//bail: true

+ 69
- 34
lib/graph3d/Graph3d.js View File

@ -693,6 +693,7 @@ Graph3d.prototype.create = function () {
var ontouchstart = function (event) {me._onTouchStart(event);};
var onmousewheel = function (event) {me._onWheel(event);};
var ontooltip = function (event) {me._onTooltip(event);};
var onclick = function(event) {me._onClick(event);};
// TODO: these events are never cleaned up... can give a 'memory leakage'
util.addEventListener(this.frame.canvas, 'keydown', onkeydown);
@ -700,6 +701,7 @@ Graph3d.prototype.create = function () {
util.addEventListener(this.frame.canvas, 'touchstart', ontouchstart);
util.addEventListener(this.frame.canvas, 'mousewheel', onmousewheel);
util.addEventListener(this.frame.canvas, 'mousemove', ontooltip);
util.addEventListener(this.frame.canvas, 'click', onclick);
// add the new graph to the container element
this.containerElement.appendChild(this.frame);
@ -956,52 +958,52 @@ Graph3d.prototype._dotSize = function() {
/**
* Get legend width
* Get legend width
*/
Graph3d.prototype._getLegendWidth = function() {
var width;
var width;
if (this.style === Graph3d.STYLE.DOTSIZE) {
var dotSize = this._dotSize();
width = dotSize / 2 + dotSize * 2;
width = dotSize / 2 + dotSize * 2;
} else if (this.style === Graph3d.STYLE.BARSIZE) {
width = this.xBarWidth ;
} else {
width = 20;
width = 20;
}
return width;
}
/**
* Redraw the legend based on size, dot color, or surface height
* Redraw the legend based on size, dot color, or surface height
*/
Graph3d.prototype._redrawLegend = function() {
//Return without drawing anything, if no legend is specified
//Return without drawing anything, if no legend is specified
if (this.showLegend !== true) {
return;
}
}
// Do not draw legend when graph style does not support
if (this.style === Graph3d.STYLE.LINE
|| this.style === Graph3d.STYLE.BARSIZE //TODO add legend support for BARSIZE
|| this.style === Graph3d.STYLE.BARSIZE //TODO add legend support for BARSIZE
){
return;
}
}
// Legend types - size and color. Determine if size legend.
var isSizeLegend = (this.style === Graph3d.STYLE.BARSIZE
// Legend types - size and color. Determine if size legend.
var isSizeLegend = (this.style === Graph3d.STYLE.BARSIZE
|| this.style === Graph3d.STYLE.DOTSIZE) ;
// Legend is either tracking z values or style values. This flag if false means use z values.
var isValueLegend = (this.style === Graph3d.STYLE.DOTSIZE
|| this.style === Graph3d.STYLE.DOTCOLOR
// Legend is either tracking z values or style values. This flag if false means use z values.
var isValueLegend = (this.style === Graph3d.STYLE.DOTSIZE
|| this.style === Graph3d.STYLE.DOTCOLOR
|| this.style === Graph3d.STYLE.BARCOLOR);
var height = Math.max(this.frame.clientHeight * 0.25, 100);
var top = this.margin;
var width = this._getLegendWidth() ; // px - overwritten by size legend
var width = this._getLegendWidth() ; // px - overwritten by size legend
var right = this.frame.clientWidth - this.margin;
var left = right - width;
var bottom = top + height;
@ -1031,14 +1033,14 @@ Graph3d.prototype._redrawLegend = function() {
ctx.strokeRect(left, top, width, height);
} else {
// draw the size legend box
// draw the size legend box
var widthMin;
if (this.style === Graph3d.STYLE.DOTSIZE) {
if (this.style === Graph3d.STYLE.DOTSIZE) {
var dotSize = this._dotSize();
widthMin = dotSize / 2; // px
} else if (this.style === Graph3d.STYLE.BARSIZE) {
//widthMin = this.xBarWidth * 0.2 this is wrong - barwidth measures in terms of xvalues
} else if (this.style === Graph3d.STYLE.BARSIZE) {
//widthMin = this.xBarWidth * 0.2 this is wrong - barwidth measures in terms of xvalues
}
ctx.strokeStyle = this.axisColor;
ctx.fillStyle = this.dataColor.fill;
@ -1052,10 +1054,10 @@ Graph3d.prototype._redrawLegend = function() {
ctx.stroke();
}
// print value text along the legend edge
// print value text along the legend edge
var gridLineLen = 5; // px
var legendMin = isValueLegend ? this.valueRange.min : this.zRange.min;
var legendMin = isValueLegend ? this.valueRange.min : this.zRange.min;
var legendMax = isValueLegend ? this.valueRange.max : this.zRange.max;
var step = new StepNumber(legendMin, legendMax, (legendMax-legendMin)/5, true);
step.start(true);
@ -1330,7 +1332,7 @@ Graph3d.prototype._redrawAxis = function() {
xText = (armVector.y > 0) ? xRange.min : xRange.max;
point3d = new Point3d(xText, y, zRange.min);
var msg = ' ' + this.yValueLabel(y) + ' ';
var msg = ' ' + this.yValueLabel(y) + ' ';
this.drawAxisLabelY(ctx, point3d, msg, armAngle, textMargin);
step.next();
@ -1550,7 +1552,7 @@ Graph3d.prototype._redrawBar = function(ctx, point, xWidth, yWidth, color, borde
/**
* Draw a polygon using the passed points and fill it with the passed style and stroke.
*
* @param points an array of points.
* @param points an array of points.
* @param fillStyle optional; the fill style to set
* @param strokeStyle optional; the stroke style to set
*/
@ -1614,16 +1616,28 @@ Graph3d.prototype._getColorsRegular = function(point) {
/**
* Get the colors for the 'color' graph styles.
* These styles are currently: 'bar-color' and 'dot-color'
* Color may be set as a string representation of HTML color, like #ff00ff,
* or calculated from a number, for example, distance from this point
* The first option is useful when we have some pre-given legend, to which we have to adjust ourselves
* The second option is useful when we are interested in automatically setting the color, from some value,
* using some color scale
*/
Graph3d.prototype._getColorsColor = function(point) {
// calculate the color based on the value
var hue = (1 - (point.point.value - this.valueRange.min) * this.scale.value) * 240;
var color = this._hsv2rgb(hue, 1, 1);
var borderColor = this._hsv2rgb(hue, 1, 0.8);
var color, borderColor;
if (typeof point.point.value === "string") {
color = point.point.value;
borderColor = point.point.value;
}
else {
var hue = (1 - (point.point.value - this.valueRange.min) * this.scale.value) * 240;
color = this._hsv2rgb(hue, 1, 1);
borderColor = this._hsv2rgb(hue, 1, 0.8);
}
return {
fill : color,
border : borderColor
border : borderColor
};
};
@ -1897,7 +1911,7 @@ Graph3d.prototype._storeMousePosition = function(event) {
// get mouse position (different code for IE and all other browsers)
this.startMouseX = getMouseX(event);
this.startMouseY = getMouseY(event);
this._startCameraOffset = this.camera.getOffset();
};
@ -1946,12 +1960,13 @@ Graph3d.prototype._onMouseDown = function(event) {
* @param {Event} event Well, eehh, the event
*/
Graph3d.prototype._onMouseMove = function (event) {
this.moving = true;
event = event || window.event;
// calculate change in mouse position
var diffX = parseFloat(getMouseX(event)) - this.startMouseX;
var diffY = parseFloat(getMouseY(event)) - this.startMouseY;
// move with ctrl or rotate by other
if (event && event.ctrlKey === true) {
// calculate change in mouse position
@ -1960,7 +1975,7 @@ Graph3d.prototype._onMouseMove = function (event) {
var offXNew = (this._startCameraOffset.x || 0) - ((diffX / scaleX) * this.camera.armLength) * 0.8;
var offYNew = (this._startCameraOffset.y || 0) + ((diffY / scaleY) * this.camera.armLength) * 0.8;
this.camera.setOffset(offXNew, offYNew);
this._storeMousePosition(event);
} else {
@ -2014,6 +2029,26 @@ Graph3d.prototype._onMouseUp = function (event) {
util.preventDefault(event);
};
/**
* @param {event} event The event
*/
Graph3d.prototype._onClick = function (event) {
if (!this.onclick_callback)
return;
if (!this.moving) {
var boundingRect = this.frame.getBoundingClientRect();
var mouseX = getMouseX(event) - boundingRect.left;
var mouseY = getMouseY(event) - boundingRect.top;
var dataPoint = this._dataPointFromXY(mouseX, mouseY);
if (dataPoint)
this.onclick_callback(dataPoint.point.data);
}
else { // disable onclick callback, if it came immediately after rotate/pan
this.moving = false;
}
util.preventDefault(event);
};
/**
* After having moved the mouse, a tooltip should pop up when the mouse is resting on a data point
* @param {Event} event A mouse move event
@ -2403,4 +2438,4 @@ Graph3d.prototype.setSize = function(width, height) {
// -----------------------------------------------------------------------------
module.exports = Graph3d;
module.exports = Graph3d;

+ 5
- 1
lib/graph3d/Settings.js View File

@ -119,7 +119,7 @@ function isEmpty(obj) {
* Source: http://stackoverflow.com/a/1026087
*/
function capitalize(str) {
if (str === undefined || str === "") {
if (str === undefined || str === "" || typeof str != "string") {
return str;
}
@ -221,6 +221,7 @@ function setDefaults(src, dst) {
dst.margin = 10; // px
dst.showGrayBottom = false; // TODO: this does not work correctly
dst.showTooltip = false;
dst.onclick_callback = null;
dst.eye = new Point3d(0, 0, -1); // TODO: set eye.z about 3/4 of the width of the window?
}
@ -267,6 +268,9 @@ function setSpecialSettings(src, dst) {
if (src.tooltip !== undefined) {
dst.showTooltip = src.tooltip;
}
if (src.onclick != undefined) {
dst.onclick_callback = src.onclick;
}
}

+ 4
- 6
lib/network/modules/CanvasRenderer.js View File

@ -32,11 +32,9 @@ class CanvasRenderer {
}
bindEventListeners() {
this.body.emitter.on("dragStart", () => {
this.dragging = true;
});
this.body.emitter.on("dragEnd", () => this.dragging = false);
this.body.emitter.on("_resizeNodes", () => this._resizeNodes());
this.body.emitter.on("dragStart", () => { this.dragging = true; });
this.body.emitter.on("dragEnd", () => { this.dragging = false; });
this.body.emitter.on("_resizeNodes", () => { this._resizeNodes(); });
this.body.emitter.on("_redraw", () => {
if (this.renderingActive === false) {
this._redraw();
@ -327,4 +325,4 @@ class CanvasRenderer {
}
export default CanvasRenderer;
export default CanvasRenderer;

+ 0
- 13
lib/network/modules/EdgesHandler.js View File

@ -151,7 +151,6 @@ class EdgesHandler {
// this is called when options of EXISTING nodes or edges have changed.
this.body.emitter.on("_dataUpdated", () => {
this.reconnectEdges();
this.markAllEdgesAsDirty();
});
// refresh the edges. Used when reverting from hierarchical layout
@ -177,11 +176,6 @@ class EdgesHandler {
// use the parser from the Edge class to fill in all shorthand notations
Edge.parseOptions(this.options, options);
// handle multiple input cases for color
if (options.color !== undefined) {
this.markAllEdgesAsDirty();
}
// update smooth settings in all edges
let dataChanged = false;
if (options.smooth !== undefined) {
@ -361,13 +355,6 @@ class EdgesHandler {
return new Edge(properties, this.body, this.options, this.defaultOptions, this.edgeOptions)
}
markAllEdgesAsDirty() {
for (var edgeId in this.body.edges) {
this.body.edges[edgeId].edgeType.colorDirty = true;
}
}
/**
* Reconnect all edges
* @private

+ 1
- 1
lib/network/modules/InteractionHandler.js View File

@ -1,7 +1,7 @@
let util = require('../../util');
import NavigationHandler from './components/NavigationHandler'
import Popup from './components/Popup'
import Popup from './../../shared/Popup'
class InteractionHandler {
constructor(body, canvas, selectionHandler) {

+ 166
- 59
lib/network/modules/components/Edge.js View File

@ -39,7 +39,6 @@ class Edge {
this.selected = false;
this.hover = false;
this.labelDirty = true;
this.colorDirty = true;
this.baseWidth = this.options.width;
this.baseFontSize = this.options.font.size;
@ -65,16 +64,26 @@ class Edge {
if (!options) {
return;
}
this.colorDirty = true;
Edge.parseOptions(this.options, options, true, this.globalOptions);
if (options.id !== undefined) {this.id = options.id;}
if (options.from !== undefined) {this.fromId = options.from;}
if (options.to !== undefined) {this.toId = options.to;}
if (options.title !== undefined) {this.title = options.title;}
if (options.value !== undefined) {options.value = parseFloat(options.value);}
if (options.id !== undefined) {
this.id = options.id;
}
if (options.from !== undefined) {
this.fromId = options.from;
}
if (options.to !== undefined) {
this.toId = options.to;
}
if (options.title !== undefined) {
this.title = options.title;
}
if (options.value !== undefined) {
options.value = parseFloat(options.value);
}
this.choosify(options);
// update label Module
this.updateLabelModule(options);
@ -197,6 +206,94 @@ class Edge {
}
}
choosify(options) {
this.chooser = true;
let pile = [options, this.options, this.defaultOptions];
let chosen = util.topMost(pile, 'chosen');
if (typeof chosen === 'boolean') {
this.chooser = chosen;
} else if (typeof chosen === 'object') {
let chosenEdge = util.topMost(pile, ['chosen', 'edge']);
if ((typeof chosenEdge === 'boolean') || (typeof chosenEdge === 'function')) {
this.chooser = chosenEdge;
}
}
}
getFormattingValues() {
let toArrow = (this.options.arrows.to === true) || (this.options.arrows.to.enabled === true)
let fromArrow = (this.options.arrows.from === true) || (this.options.arrows.from.enabled === true)
let middleArrow = (this.options.arrows.middle === true) || (this.options.arrows.middle.enabled === true)
let inheritsColor = this.options.color.inherit;
let values = {
toArrow: toArrow,
toArrowScale: this.options.arrows.to.scaleFactor,
toArrowType: this.options.arrows.to.type,
middleArrow: middleArrow,
middleArrowScale: this.options.arrows.middle.scaleFactor,
middleArrowType: this.options.arrows.middle.type,
fromArrow: fromArrow,
fromArrowScale: this.options.arrows.from.scaleFactor,
fromArrowType: this.options.arrows.from.type,
arrowStrikethrough: this.options.arrowStrikethrough,
color: (inheritsColor? undefined : this.options.color.color),
inheritsColor: inheritsColor,
opacity: this.options.color.opacity,
hidden: this.options.hidden,
length: this.options.length,
shadow: this.options.shadow.enabled,
shadowColor: this.options.shadow.color,
shadowSize: this.options.shadow.size,
shadowX: this.options.shadow.x,
shadowY: this.options.shadow.y,
dashes: this.options.dashes,
width: this.options.width
};
if (this.selected || this.hover) {
if (this.chooser === true) {
if (this.selected) {
let selectedWidth = this.options.selectionWidth;
if (typeof selectedWidth === 'function') {
values.width = selectedWidth(values.width);
} else if (typeof selectedWidth === 'number') {
values.width += selectedWidth;
}
values.width = Math.max(values.width, 0.3 / this.body.view.scale);
values.color = this.options.color.highlight;
values.shadow = this.options.shadow.enabled;
} else if (this.hover) {
let hoverWidth = this.options.hoverWidth;
if (typeof hoverWidth === 'function') {
values.width = hoverWidth(values.width);
} else if (typeof hoverWidth === 'number') {
values.width += hoverWidth;
}
values.width = Math.max(values.width, 0.3 / this.body.view.scale);
values.color = this.options.color.hover;
values.shadow = this.options.shadow.enabled;
}
} else if (typeof this.chooser === 'function') {
this.chooser(values, this.options.id, this.selected, this.hover);
if (values.color !== undefined) {
values.inheritsColor = false;
}
if (values.shadow === false) {
if ((values.shadowColor !== this.options.shadow.color) ||
(values.shadowSize !== this.options.shadow.size) ||
(values.shadowX !== this.options.shadow.x) ||
(values.shadowY !== this.options.shadow.y)) {
values.shadow = true;
}
}
}
} else {
values.shadow = this.options.shadow.enabled;
values.width = Math.max(values.width, 0.3 / this.body.view.scale);
}
return values;
}
/**
* update the options in the label module
@ -207,6 +304,7 @@ class Edge {
this.baseFontSize = this.labelModule.baseSize;
}
this.labelModule.constrain(this.edgeOptions, options, this.defaultOptions);
this.labelModule.choosify(this.edgeOptions, options, this.defaultOptions);
}
/**
@ -214,46 +312,47 @@ class Edge {
* @returns {boolean}
*/
updateEdgeType() {
let smooth = this.options.smooth;
let dataChanged = false;
let changeInType = true;
let smooth = this.options.smooth;
if (this.edgeType !== undefined) {
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 ((((this.edgeType instanceof BezierEdgeDynamic) &&
(smooth.enabled === true) &&
(smooth.type === 'dynamic'))) ||
(((this.edgeType instanceof CubicBezierEdge) &&
(smooth.enabled === true) &&
(smooth.type === 'cubicBezier'))) ||
(((this.edgeType instanceof BezierEdgeStatic) &&
(smooth.enabled === true) &&
(smooth.type !== 'dynamic') &&
(smooth.type !== 'cubicBezier'))) ||
(((this.edgeType instanceof StraightEdge) &&
(smooth.type.enabled === false)))) {
changeInType = false;
}
if (changeInType === true) {
dataChanged = this.cleanup();
}
}
if (changeInType === true) {
if (this.options.smooth.enabled === true) {
if (this.options.smooth.type === 'dynamic') {
if (smooth.enabled === true) {
if (smooth.type === 'dynamic') {
dataChanged = true;
this.edgeType = new BezierEdgeDynamic(this.options, this.body, this.labelModule);
}
else if (this.options.smooth.type === 'cubicBezier') {
} else if (smooth.type === 'cubicBezier') {
this.edgeType = new CubicBezierEdge(this.options, this.body, this.labelModule);
}
else {
} else {
this.edgeType = new BezierEdgeStatic(this.options, this.body, this.labelModule);
}
}
else {
} else {
this.edgeType = new StraightEdge(this.options, this.body, this.labelModule);
}
}
else {
// if nothing changes, we just set the options.
} else { // if nothing changes, we just set the options.
this.edgeType.setOptions(this.options);
}
return dataChanged;
}
/**
* Connect an edge to its nodes
*/
@ -353,21 +452,18 @@ class Edge {
this.updateLabelModule();
}
_setInteractionWidths() {
if (typeof this.options.hoverWidth === 'function') {
this.edgeType.hoverWidth = this.options.hoverWidth(this.options.width);
}
else {
this.edgeType.hoverWidth = this.options.hoverWidth + this.options.width;
}
if (typeof this.options.selectionWidth === 'function') {
this.edgeType.selectionWidth = this.options.selectionWidth(this.options.width);
}
else {
this.edgeType.selectionWidth = this.options.selectionWidth + this.options.width;
}
}
_setInteractionWidths() {
if (typeof this.options.hoverWidth === 'function') {
this.edgeType.hoverWidth = this.options.hoverWidth(this.options.width);
} else {
this.edgeType.hoverWidth = this.options.hoverWidth + this.options.width;
}
if (typeof this.options.selectionWidth === 'function') {
this.edgeType.selectionWidth = this.options.selectionWidth(this.options.width);
} else {
this.edgeType.selectionWidth = this.options.selectionWidth + this.options.width;
}
}
/**
@ -377,6 +473,11 @@ class Edge {
* @param {CanvasRenderingContext2D} ctx
*/
draw(ctx) {
let values = this.getFormattingValues();
if (values.hidden) {
return;
}
// get the via node from the edge type
let viaNode = this.edgeType.getViaNode();
let arrowData = {};
@ -386,33 +487,39 @@ class Edge {
this.edgeType.toPoint = this.edgeType.to;
// from and to arrows give a different end point for edges. we set them here
if (this.options.arrows.from.enabled === true) {
arrowData.from = this.edgeType.getArrowData(ctx,'from', viaNode, this.selected, this.hover);
if (this.options.arrowStrikethrough === false)
if (values.fromArrow) {
arrowData.from = this.edgeType.getArrowData(ctx, 'from', viaNode, this.selected, this.hover, values);
if (values.arrowStrikethrough === false)
this.edgeType.fromPoint = arrowData.from.core;
}
if (this.options.arrows.to.enabled === true) {
arrowData.to = this.edgeType.getArrowData(ctx,'to', viaNode, this.selected, this.hover);
if (this.options.arrowStrikethrough === false)
if (values.toArrow) {
arrowData.to = this.edgeType.getArrowData(ctx, 'to', viaNode, this.selected, this.hover, values);
if (values.arrowStrikethrough === false)
this.edgeType.toPoint = arrowData.to.core;
}
// the middle arrow depends on the line, which can depend on the to and from arrows so we do this one lastly.
if (this.options.arrows.middle.enabled === true) {
arrowData.middle = this.edgeType.getArrowData(ctx,'middle', viaNode, this.selected, this.hover);
if (values.middleArrow) {
arrowData.middle = this.edgeType.getArrowData(ctx,'middle', viaNode, this.selected, this.hover, values);
}
// draw everything
this.edgeType.drawLine(ctx, this.selected, this.hover, viaNode);
this.drawArrows(ctx, arrowData);
this.edgeType.drawLine(ctx, values, this.selected, this.hover, viaNode);
this.drawArrows(ctx, arrowData, values);
this.drawLabel (ctx, viaNode);
}
drawArrows(ctx, arrowData) {
if (this.options.arrows.from.enabled === true) {this.edgeType.drawArrowHead(ctx, this.selected, this.hover, arrowData.from);}
if (this.options.arrows.middle.enabled === true) {this.edgeType.drawArrowHead(ctx, this.selected, this.hover, arrowData.middle);}
if (this.options.arrows.to.enabled === true) {this.edgeType.drawArrowHead(ctx, this.selected, this.hover, arrowData.to);}
drawArrows(ctx, arrowData, values) {
if (values.fromArrow) {
this.edgeType.drawArrowHead(ctx, values, this.selected, this.hover, arrowData.from);
}
if (values.middleArrow) {
this.edgeType.drawArrowHead(ctx, values, this.selected, this.hover, arrowData.middle);
}
if (values.toArrow) {
this.edgeType.drawArrowHead(ctx, values, this.selected, this.hover, arrowData.to);
}
}
@ -429,13 +536,13 @@ class Edge {
// if the label has to be rotated:
if (this.options.font.align !== "horizontal") {
this.labelModule.calculateLabelSize(ctx,selected,point.x,point.y);
this.labelModule.calculateLabelSize(ctx, selected, this.hover, point.x, point.y);
ctx.translate(point.x, this.labelModule.size.yLine);
this._rotateForLabelAlignment(ctx);
}
// draw the label
this.labelModule.draw(ctx, point.x, point.y, selected);
this.labelModule.draw(ctx, point.x, point.y, selected, this.hover);
ctx.restore();
}
else {
@ -452,7 +559,7 @@ class Edge {
y = node1.y - node1.shape.height * 0.5;
}
point = this._pointOnCircle(x, y, radius, 0.125);
this.labelModule.draw(ctx, point.x, point.y, selected);
this.labelModule.draw(ctx, point.x, point.y, selected, this.hover);
}
}
}

+ 67
- 2
lib/network/modules/components/Node.js View File

@ -139,6 +139,8 @@ class Node {
// this transforms all shorthands into fully defined options
Node.parseOptions(this.options, options, true, this.globalOptions);
this.choosify(options);
// load the images
if (this.options.image !== undefined) {
if (this.imagelist) {
@ -219,6 +221,66 @@ class Node {
}
}
choosify(options) {
this.chooser = true;
let pile = [options, this.options, this.defaultOptions];
let chosen = util.topMost(pile, 'chosen');
if (typeof chosen === 'boolean') {
this.chooser = chosen;
} else if (typeof chosen === 'object') {
let chosenNode = util.topMost(pile, ['chosen', 'node']);
if ((typeof chosenNode === 'boolean') || (typeof chosenNode === 'function')) {
this.chooser = chosenNode;
}
}
}
getFormattingValues() {
let values = {
color: this.options.color.background,
borderWidth: this.options.borderWidth,
borderColor: this.options.color.border,
size: this.options.size,
borderDashes: this.options.shapeProperties.borderDashes,
borderRadius: this.options.shapeProperties.borderRadius,
shadow: this.options.shadow.enabled,
shadowColor: this.options.shadow.color,
shadowSize: this.options.shadow.size,
shadowX: this.options.shadow.x,
shadowY: this.options.shadow.y
};
if (this.selected || this.hover) {
if (this.chooser === true) {
if (this.selected) {
values.borderWidth *= 2;
values.color = this.options.color.highlight.background;
values.borderColor = this.options.color.highlight.border;
values.shadow = this.options.shadow.enabled;
} else if (this.hover) {
values.color = this.options.color.hover.background;
values.borderColor = this.options.color.hover.border;
values.shadow = this.options.shadow.enabled;
}
} else if (typeof this.chooser === 'function') {
this.chooser(values, this.options.id, this.selected, this.hover);
if (values.shadow === false) {
if ((values.shadowColor !== this.options.shadow.color) ||
(values.shadowSize !== this.options.shadow.size) ||
(values.shadowX !== this.options.shadow.x) ||
(values.shadowY !== this.options.shadow.y)) {
values.shadow = true;
}
}
}
} else {
values.shadow = this.options.shadow.enabled;
}
return values;
}
updateLabelModule(options) {
if (this.options.label === undefined || this.options.label === null) {
this.options.label = '';
@ -228,6 +290,7 @@ class Node {
this.baseFontSize = this.labelModule.baseSize;
}
this.labelModule.constrain(this.nodeOptions, options, this.defaultOptions);
this.labelModule.choosify(this.nodeOptions, options, this.defaultOptions);
}
updateShape(currentShape) {
@ -396,7 +459,8 @@ class Node {
* @param {CanvasRenderingContext2D} ctx
*/
draw(ctx) {
this.shape.draw(ctx, this.x, this.y, this.selected, this.hover);
let values = this.getFormattingValues();
this.shape.draw(ctx, this.x, this.y, this.selected, this.hover, values);
}
@ -413,7 +477,8 @@ class Node {
* @param {CanvasRenderingContext2D} ctx
*/
resize(ctx) {
this.shape.resize(ctx, this.selected);
let values = this.getFormattingValues();
this.shape.resize(ctx, this.selected, this.hover, values);
}

+ 3
- 3
lib/network/modules/components/edges/BezierEdgeDynamic.js View File

@ -102,7 +102,7 @@ class BezierEdgeDynamic extends BezierEdgeBase {
* @param {CanvasRenderingContext2D} ctx
* @private
*/
_line(ctx, viaNode) {
_line(ctx, values, viaNode) {
// draw a straight line
ctx.beginPath();
ctx.moveTo(this.fromPoint.x, this.fromPoint.y);
@ -114,9 +114,9 @@ class BezierEdgeDynamic extends BezierEdgeBase {
ctx.quadraticCurveTo(viaNode.x, viaNode.y, this.toPoint.x, this.toPoint.y);
}
// draw shadow if enabled
this.enableShadow(ctx);
this.enableShadow(ctx, values);
ctx.stroke();
this.disableShadow(ctx);
this.disableShadow(ctx, values);
}
getViaNode() {

+ 3
- 3
lib/network/modules/components/edges/BezierEdgeStatic.js View File

@ -10,7 +10,7 @@ class BezierEdgeStatic extends BezierEdgeBase {
* @param {CanvasRenderingContext2D} ctx
* @private
*/
_line(ctx, viaNode) {
_line(ctx, values, viaNode) {
// draw a straight line
ctx.beginPath();
ctx.moveTo(this.fromPoint.x, this.fromPoint.y);
@ -23,9 +23,9 @@ class BezierEdgeStatic extends BezierEdgeBase {
ctx.quadraticCurveTo(viaNode.x, viaNode.y, this.toPoint.x, this.toPoint.y);
}
// draw shadow if enabled
this.enableShadow(ctx);
this.enableShadow(ctx, values);
ctx.stroke();
this.disableShadow(ctx);
this.disableShadow(ctx, values);
}
getViaNode() {

+ 3
- 3
lib/network/modules/components/edges/CubicBezierEdge.js View File

@ -10,7 +10,7 @@ class CubicBezierEdge extends CubicBezierEdgeBase {
* @param {CanvasRenderingContext2D} ctx
* @private
*/
_line(ctx, viaNodes) {
_line(ctx, values, viaNodes) {
// get the coordinates of the support points.
let via1 = viaNodes[0];
let via2 = viaNodes[1];
@ -27,9 +27,9 @@ class CubicBezierEdge extends CubicBezierEdgeBase {
ctx.bezierCurveTo(via1.x, via1.y, via2.x, via2.y, this.toPoint.x, this.toPoint.y);
}
// draw shadow if enabled
this.enableShadow(ctx);
this.enableShadow(ctx, values);
ctx.stroke();
this.disableShadow(ctx);
this.disableShadow(ctx, values);
}
_getViaCoordinates() {

+ 3
- 3
lib/network/modules/components/edges/StraightEdge.js View File

@ -10,15 +10,15 @@ class StraightEdge extends EdgeBase {
* @param {CanvasRenderingContext2D} ctx
* @private
*/
_line(ctx) {
_line(ctx, values) {
// draw a straight line
ctx.beginPath();
ctx.moveTo(this.fromPoint.x, this.fromPoint.y);
ctx.lineTo(this.toPoint.x, this.toPoint.y);
// draw shadow if enabled
this.enableShadow(ctx);
this.enableShadow(ctx, values);
ctx.stroke();
this.disableShadow(ctx);
this.disableShadow(ctx, values);
}
getViaNode() {

+ 74
- 101
lib/network/modules/components/edges/util/EdgeBase.js View File

@ -18,7 +18,10 @@ class EdgeBase {
this.from = this.body.nodes[this.options.from];
this.to = this.body.nodes[this.options.to];
}
cleanup() {return false}
cleanup() {
return false;
}
setOptions(options) {
this.options = options;
@ -34,36 +37,36 @@ class EdgeBase {
* @param {CanvasRenderingContext2D} ctx
* @private
*/
drawLine(ctx, selected, hover, viaNode) {
drawLine(ctx, values, selected, hover, viaNode) {
// set style
ctx.strokeStyle = this.getColor(ctx, selected, hover);
ctx.lineWidth = this.getLineWidth(selected, hover);
ctx.strokeStyle = this.getColor(ctx, values, selected, hover);
ctx.lineWidth = values.width;
if (this.options.dashes !== false) {
this._drawDashedLine(ctx, viaNode);
if (values.dashes !== false) {
this._drawDashedLine(ctx, values, viaNode);
}
else {
this._drawLine(ctx, viaNode);
this._drawLine(ctx, values, viaNode);
}
}
_drawLine(ctx, viaNode, fromPoint, toPoint) {
_drawLine(ctx, values, viaNode, fromPoint, toPoint) {
if (this.from != this.to) {
// draw line
this._line(ctx, viaNode, fromPoint, toPoint);
this._line(ctx, values, viaNode, fromPoint, toPoint);
}
else {
let [x,y,radius] = this._getCircleData(ctx);
this._circle(ctx, x, y, radius);
let [x,y,radius] = this._getCircleData(ctx, values);
this._circle(ctx, values, x, y, radius);
}
}
_drawDashedLine(ctx, viaNode, fromPoint, toPoint) {
_drawDashedLine(ctx, values, viaNode, fromPoint, toPoint) {
ctx.lineCap = 'round';
let pattern = [5,5];
if (Array.isArray(this.options.dashes) === true) {
pattern = this.options.dashes;
if (Array.isArray(values.dashes) === true) {
pattern = values.dashes;
}
// only firefox and chrome support this method, else we use the legacy one.
@ -77,11 +80,11 @@ class EdgeBase {
// draw the line
if (this.from != this.to) {
// draw line
this._line(ctx, viaNode);
this._line(ctx, values, viaNode);
}
else {
let [x,y,radius] = this._getCircleData(ctx);
this._circle(ctx, x, y, radius);
let [x,y,radius] = this._getCircleData(ctx, values);
this._circle(ctx, values, x, y, radius);
}
// restore the dash settings.
@ -95,16 +98,16 @@ class EdgeBase {
ctx.dashedLine(this.from.x, this.from.y, this.to.x, this.to.y, pattern);
}
else {
let [x,y,radius] = this._getCircleData(ctx);
this._circle(ctx, x, y, radius);
let [x,y,radius] = this._getCircleData(ctx, values);
this._circle(ctx, values, x, y, radius);
}
// draw shadow if enabled
this.enableShadow(ctx);
this.enableShadow(ctx, values);
ctx.stroke();
// disable shadows for other elements.
this.disableShadow(ctx);
this.disableShadow(ctx, values);
}
}
@ -252,24 +255,23 @@ class EdgeBase {
}
getColor(ctx, selected, hover) {
let colorOptions = this.options.color;
if (colorOptions.inherit !== false) {
getColor(ctx, values, selected, hover) {
if (values.inheritsColor !== false) {
// when this is a loop edge, just use the 'from' method
if (colorOptions.inherit === 'both' && this.from.id !== this.to.id) {
if ((values.inheritsColor === 'both') && (this.from.id !== this.to.id)) {
let grd = ctx.createLinearGradient(this.from.x, this.from.y, this.to.x, this.to.y);
let fromColor, toColor;
fromColor = this.from.options.color.highlight.border;
toColor = this.to.options.color.highlight.border;
if (this.from.selected === false && this.to.selected === false) {
fromColor = util.overrideOpacity(this.from.options.color.border, this.options.color.opacity);
toColor = util.overrideOpacity(this.to.options.color.border, this.options.color.opacity);
if ((this.from.selected === false) && (this.to.selected === false)) {
fromColor = util.overrideOpacity(this.from.options.color.border, values.opacity);
toColor = util.overrideOpacity(this.to.options.color.border, values.opacity);
}
else if (this.from.selected === true && this.to.selected === false) {
else if ((this.from.selected === true) && (this.to.selected === false)) {
toColor = this.to.options.color.border;
}
else if (this.from.selected === false && this.to.selected === true) {
else if ((this.from.selected === false) && (this.to.selected === true)) {
fromColor = this.from.options.color.border;
}
grd.addColorStop(0, fromColor);
@ -279,37 +281,13 @@ class EdgeBase {
return grd;
}
if (this.colorDirty === true) {
if (colorOptions.inherit === "to") {
this.color.highlight = this.to.options.color.highlight.border;
this.color.hover = this.to.options.color.hover.border;
this.color.color = util.overrideOpacity(this.to.options.color.border, colorOptions.opacity);
}
else { // (this.options.color.inherit.source === "from") {
this.color.highlight = this.from.options.color.highlight.border;
this.color.hover = this.from.options.color.hover.border;
this.color.color = util.overrideOpacity(this.from.options.color.border, colorOptions.opacity);
}
if (values.inheritsColor === "to") {
return util.overrideOpacity(this.to.options.color.border, values.opacity);
} else { // "from"
return util.overrideOpacity(this.from.options.color.border, values.opacity);
}
}
else if (this.colorDirty === true) {
this.color.highlight = colorOptions.highlight;
this.color.hover = colorOptions.hover;
this.color.color = util.overrideOpacity(colorOptions.color, colorOptions.opacity);
}
// if color inherit is on and gradients are used, the function has already returned by now.
this.colorDirty = false;
if (selected === true) {
return this.color.highlight;
}
else if (hover === true) {
return this.color.hover;
}
else {
return this.color.color;
} else {
return util.overrideOpacity(values.color, values.opacity);
}
}
@ -321,9 +299,9 @@ class EdgeBase {
* @param {Number} radius
* @private
*/
_circle(ctx, x, y, radius) {
_circle(ctx, values, x, y, radius) {
// draw shadow if enabled
this.enableShadow(ctx);
this.enableShadow(ctx, values);
// draw a circle
ctx.beginPath();
@ -331,7 +309,7 @@ class EdgeBase {
ctx.stroke();
// disable shadows for other elements.
this.disableShadow(ctx);
this.disableShadow(ctx, values);
}
@ -347,13 +325,13 @@ class EdgeBase {
* @param {number} y3
* @private
*/
getDistanceToEdge(x1, y1, x2, y2, x3, y3, via) { // x3,y3 is the point
getDistanceToEdge(x1, y1, x2, y2, x3, y3, via, values) { // x3,y3 is the point
let returnValue = 0;
if (this.from != this.to) {
returnValue = this._getDistanceToEdge(x1, y1, x2, y2, x3, y3, via)
}
else {
let [x,y,radius] = this._getCircleData();
let [x,y,radius] = this._getCircleData(undefined, values);
let dx = x - x3;
let dy = y - y3;
returnValue = Math.abs(Math.sqrt(dx * dx + dy * dy) - radius);
@ -404,7 +382,7 @@ class EdgeBase {
* @param position
* @param viaNode
*/
getArrowData(ctx, position, viaNode, selected, hover) {
getArrowData(ctx, position, viaNode, selected, hover, values) {
// set lets
let angle;
let arrowPoint;
@ -413,27 +391,27 @@ class EdgeBase {
let guideOffset;
let scaleFactor;
let type;
let lineWidth = this.getLineWidth(selected, hover);
let lineWidth = values.width;
if (position === 'from') {
node1 = this.from;
node2 = this.to;
guideOffset = 0.1;
scaleFactor = this.options.arrows.from.scaleFactor;
type = this.options.arrows.from.type;
scaleFactor = values.fromArrowScale;
type = values.fromArrowType;
}
else if (position === 'to') {
node1 = this.to;
node2 = this.from;
guideOffset = -0.1;
scaleFactor = this.options.arrows.to.scaleFactor;
type = this.options.arrows.to.type;
scaleFactor = values.toArrowScale;
type = values.toArrowType;
}
else {
node1 = this.to;
node2 = this.from;
scaleFactor = this.options.arrows.middle.scaleFactor;
type = this.options.arrows.middle.type;
scaleFactor = values.middleArrowScale;
type = values.middleArrowType;
}
// if not connected to itself
@ -441,33 +419,28 @@ class EdgeBase {
if (position !== 'middle') {
// draw arrow head
if (this.options.smooth.enabled === true) {
arrowPoint = this.findBorderPosition(node1, ctx, {via: viaNode});
arrowPoint = this.findBorderPosition(node1, ctx, { via: viaNode });
let guidePos = this.getPoint(Math.max(0.0, Math.min(1.0, arrowPoint.t + guideOffset)), viaNode);
angle = Math.atan2((arrowPoint.y - guidePos.y), (arrowPoint.x - guidePos.x));
}
else {
} else {
angle = Math.atan2((node1.y - node2.y), (node1.x - node2.x));
arrowPoint = this.findBorderPosition(node1, ctx);
}
}
else {
} else {
angle = Math.atan2((node1.y - node2.y), (node1.x - node2.x));
arrowPoint = this.getPoint(0.5, viaNode); // this is 0.6 to account for the size of the arrow.
}
}
else {
} else {
// draw circle
let [x,y,radius] = this._getCircleData(ctx);
if (position === 'from') {
arrowPoint = this.findBorderPosition(this.from, ctx, {x, y, low:0.25, high:0.6, direction:-1});
arrowPoint = this.findBorderPosition(this.from, ctx, { x, y, low: 0.25, high: 0.6, direction: -1 });
angle = arrowPoint.t * -2 * Math.PI + 1.5 * Math.PI + 0.1 * Math.PI;
}
else if (position === 'to') {
arrowPoint = this.findBorderPosition(this.from, ctx, {x, y, low:0.6, high:1.0, direction:1});
} else if (position === 'to') {
arrowPoint = this.findBorderPosition(this.from, ctx, { x, y, low: 0.6, high: 1.0, direction: 1 });
angle = arrowPoint.t * -2 * Math.PI + 1.5 * Math.PI - 1.1 * Math.PI;
}
else {
} else {
arrowPoint = this._pointOnCircle(x, y, radius, 0.175);
angle = 3.9269908169872414; // === 0.175 * -2 * Math.PI + 1.5 * Math.PI + 0.1 * Math.PI;
}
@ -477,9 +450,9 @@ class EdgeBase {
var xi = arrowPoint.x - length * 0.9 * Math.cos(angle);
var yi = arrowPoint.y - length * 0.9 * Math.sin(angle);
let arrowCore = {x: xi, y: yi};
let arrowCore = { x: xi, y: yi };
return {point: arrowPoint, core: arrowCore, angle: angle, length: length, type: type};
return { point: arrowPoint, core: arrowCore, angle: angle, length: length, type: type };
}
/**
@ -489,11 +462,11 @@ class EdgeBase {
* @param hover
* @param arrowData
*/
drawArrowHead(ctx, selected, hover, arrowData) {
drawArrowHead(ctx, values, selected, hover, arrowData) {
// set style
ctx.strokeStyle = this.getColor(ctx, selected, hover);
ctx.strokeStyle = this.getColor(ctx, values, selected, hover);
ctx.fillStyle = ctx.strokeStyle;
ctx.lineWidth = this.getLineWidth(selected, hover);
ctx.lineWidth = values.width;
if (arrowData.type && arrowData.type.toLowerCase() === 'circle') {
// draw circle at the end of the line
@ -504,24 +477,24 @@ class EdgeBase {
}
// draw shadow if enabled
this.enableShadow(ctx);
this.enableShadow(ctx, values);
ctx.fill();
// disable shadows for other elements.
this.disableShadow(ctx);
this.disableShadow(ctx, values);
}
enableShadow(ctx) {
if (this.options.shadow.enabled === true) {
ctx.shadowColor = this.options.shadow.color;
ctx.shadowBlur = this.options.shadow.size;
ctx.shadowOffsetX = this.options.shadow.x;
ctx.shadowOffsetY = this.options.shadow.y;
enableShadow(ctx, values) {
if (values.shadow === true) {
ctx.shadowColor = values.shadowColor;
ctx.shadowBlur = values.shadowSize;
ctx.shadowOffsetX = values.shadowX;
ctx.shadowOffsetY = values.shadowY;
}
}
disableShadow(ctx) {
if (this.options.shadow.enabled === true) {
disableShadow(ctx, values) {
if (values.shadow === true) {
ctx.shadowColor = 'rgba(0,0,0,0)';
ctx.shadowBlur = 0;
ctx.shadowOffsetX = 0;

+ 18
- 22
lib/network/modules/components/nodes/shapes/Box.js View File

@ -8,59 +8,55 @@ class Box extends NodeBase {
this._setMargins(labelModule);
}
resize(ctx, selected) {
if (this.width === undefined) {
this.textSize = this.labelModule.getTextSize(ctx,selected);
resize(ctx, selected = this.selected, hover = this.hover) {
if ((this.width === undefined) || this.labelModule.differentState(selected, hover)) {
this.textSize = this.labelModule.getTextSize(ctx, selected, hover);
this.width = this.textSize.width + this.margin.right + this.margin.left;
this.height = this.textSize.height + this.margin.top + this.margin.bottom;
this.radius = this.width / 2;
}
}
draw(ctx, x, y, selected, hover) {
this.resize(ctx, selected);
draw(ctx, x, y, selected, hover, values) {
this.resize(ctx, selected, hover);
this.left = x - this.width / 2;
this.top = y - this.height / 2;
let borderWidth = this.options.borderWidth;
let selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
ctx.strokeStyle = selected ? this.options.color.highlight.border : hover ? this.options.color.hover.border : this.options.color.border;
ctx.lineWidth = (selected ? selectionLineWidth : borderWidth);
ctx.strokeStyle = values.borderColor;
ctx.lineWidth = values.borderWidth;
ctx.lineWidth /= this.body.view.scale;
ctx.lineWidth = Math.min(this.width, ctx.lineWidth);
ctx.fillStyle = selected ? this.options.color.highlight.background : hover ? this.options.color.hover.background : this.options.color.background;
ctx.fillStyle = values.color;
let borderRadius = this.options.shapeProperties.borderRadius; // only effective for box
ctx.roundRect(this.left, this.top, this.width, this.height, borderRadius);
ctx.roundRect(this.left, this.top, this.width, this.height, values.borderRadius);
// draw shadow if enabled
this.enableShadow(ctx);
this.enableShadow(ctx, values);
// draw the background
ctx.fill();
// disable shadows for other elements.
this.disableShadow(ctx);
this.disableShadow(ctx, values);
//draw dashed border if enabled, save and restore is required for firefox not to crash on unix.
ctx.save();
// if borders are zero width, they will be drawn with width 1 by default. This prevents that
if (borderWidth > 0) {
this.enableBorderDashes(ctx);
if (values.borderWidth > 0) {
this.enableBorderDashes(ctx, values);
//draw the border
ctx.stroke();
//disable dashed border for other elements
this.disableBorderDashes(ctx);
this.disableBorderDashes(ctx, values);
}
ctx.restore();
this.updateBoundingBox(x,y,ctx,selected);
this.updateBoundingBox(x, y, ctx, selected, hover);
this.labelModule.draw(ctx, this.left + this.textSize.width / 2 + this.margin.left,
this.top + this.textSize.height / 2 + this.margin.top, selected);
this.top + this.textSize.height / 2 + this.margin.top, selected, hover);
}
updateBoundingBox(x,y, ctx, selected) {
this.resize(ctx, selected);
updateBoundingBox(x, y, ctx, selected, hover) {
this.resize(ctx, selected, hover);
this.left = x - this.width / 2;
this.top = y - this.height / 2;

+ 11
- 11
lib/network/modules/components/nodes/shapes/Circle.js View File

@ -8,9 +8,9 @@ class Circle extends CircleImageBase {
this._setMargins(labelModule);
}
resize(ctx, selected) {
if (this.width === undefined) {
this.textSize = this.labelModule.getTextSize(ctx, selected);
resize(ctx, selected = this.selected, hover = this.hover, values = { size: this.options.size}) {
if ((this.width === undefined) || (this.labelModule.differentState(selected, hover))) {
this.textSize = this.labelModule.getTextSize(ctx, selected, hover);
var diameter = Math.max(this.textSize.width + this.margin.right + this.margin.left,
this.textSize.height + this.margin.top + this.margin.bottom);
this.options.size = diameter / 2;
@ -21,21 +21,21 @@ class Circle extends CircleImageBase {
}
}
draw(ctx, x, y, selected, hover) {
this.resize(ctx, selected);
draw(ctx, x, y, selected, hover, values) {
this.resize(ctx, selected, hover);
this.left = x - this.width / 2;
this.top = y - this.height / 2;
this._drawRawCircle(ctx, x, y, selected, hover, this.options.size);
this._drawRawCircle(ctx, x, y, selected, hover, values);
this.boundingBox.top = y - this.options.size;
this.boundingBox.left = x - this.options.size;
this.boundingBox.right = x + this.options.size;
this.boundingBox.bottom = y + this.options.size;
this.boundingBox.top = y - values.size;
this.boundingBox.left = x - values.size;
this.boundingBox.right = x + values.size;
this.boundingBox.bottom = y + values.size;
this.updateBoundingBox(x,y);
this.labelModule.draw(ctx, this.left + this.textSize.width / 2 + this.margin.left,
this.top + this.textSize.height / 2 + this.margin.top, selected);
this.top + this.textSize.height / 2 + this.margin.top, selected, hover);
}
updateBoundingBox(x,y) {

+ 16
- 16
lib/network/modules/components/nodes/shapes/CircularImage.js View File

@ -10,17 +10,17 @@ class CircularImage extends CircleImageBase {
this._swapToImageResizeWhenImageLoaded = true;
}
resize() {
if (this.imageObj.src === undefined || this.imageObj.width === undefined || this.imageObj.height === undefined ) {
if (!this.width) {
var diameter = this.options.size * 2;
this.width = diameter;
this.height = diameter;
this._swapToImageResizeWhenImageLoaded = true;
this.radius = 0.5*this.width;
}
}
else {
resize(ctx, selected = this.selected, hover = this.hover) {
if ((this.imageObj.src === undefined) ||
(this.imageObj.width === undefined) ||
(this.imageObj.height === undefined) ||
(this.labelModule.differentState(selected, hover))) {
var diameter = this.options.size * 2;
this.width = diameter;
this.height = diameter;
this._swapToImageResizeWhenImageLoaded = true;
this.radius = 0.5*this.width;
} else {
if (this._swapToImageResizeWhenImageLoaded) {
this.width = undefined;
this.height = undefined;
@ -30,7 +30,7 @@ class CircularImage extends CircleImageBase {
}
}
draw(ctx, x, y, selected, hover) {
draw(ctx, x, y, selected, hover, values) {
this.resize();
this.left = x - this.width / 2;
@ -39,18 +39,18 @@ class CircularImage extends CircleImageBase {
let size = Math.min(0.5*this.height, 0.5*this.width);
// draw the background circle. IMPORTANT: the stroke in this method is used by the clip method below.
this._drawRawCircle(ctx, x, y, selected, hover, size);
this._drawRawCircle(ctx, x, y, selected, hover, values);
// now we draw in the circle, we save so we can revert the clip operation after drawing.
ctx.save();
// 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);
this._drawImageAtPosition(ctx, values);
// restore so we can again draw on the full canvas
ctx.restore();
this._drawImageLabel(ctx, x, y, selected);
this._drawImageLabel(ctx, x, y, selected, hover);
this.updateBoundingBox(x,y);
}
@ -72,4 +72,4 @@ class CircularImage extends CircleImageBase {
}
}
export default CircularImage;
export default CircularImage;

+ 16
- 18
lib/network/modules/components/nodes/shapes/Database.js View File

@ -8,9 +8,9 @@ class Database extends NodeBase {
this._setMargins(labelModule);
}
resize(ctx, selected) {
if (this.width === undefined) {
this.textSize = this.labelModule.getTextSize(ctx, selected);
resize(ctx, selected, hover) {
if ((this.width === undefined) || (this.labelModule.differentState(selected, hover))) {
this.textSize = this.labelModule.getTextSize(ctx, selected, hover);
var size = this.textSize.width + this.margin.right + this.margin.left;
this.width = size;
this.height = size;
@ -18,47 +18,45 @@ class Database extends NodeBase {
}
}
draw(ctx, x, y, selected, hover) {
this.resize(ctx, selected);
draw(ctx, x, y, selected, hover, values) {
this.resize(ctx, selected, hover);
this.left = x - this.width / 2;
this.top = y - this.height / 2;
var neutralborderWidth = this.options.borderWidth;
var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
var borderWidth = (selected ? selectionLineWidth : neutralborderWidth) / this.body.view.scale;
var borderWidth = values.borderWidth / this.body.view.scale;
ctx.lineWidth = Math.min(this.width, borderWidth);
ctx.strokeStyle = selected ? this.options.color.highlight.border : hover ? this.options.color.hover.border : this.options.color.border;
ctx.strokeStyle = values.borderColor;
ctx.fillStyle = selected ? this.options.color.highlight.background : hover ? this.options.color.hover.background : this.options.color.background;
ctx.fillStyle = values.color;
ctx.database(x - this.width / 2, y - this.height / 2, this.width, this.height);
// draw shadow if enabled
this.enableShadow(ctx);
this.enableShadow(ctx, values);
// draw the background
ctx.fill();
// disable shadows for other elements.
this.disableShadow(ctx);
this.disableShadow(ctx, values);
//draw dashed border if enabled, save and restore is required for firefox not to crash on unix.
ctx.save();
// if borders are zero width, they will be drawn with width 1 by default. This prevents that
if (borderWidth > 0) {
this.enableBorderDashes(ctx);
this.enableBorderDashes(ctx, values);
//draw the border
ctx.stroke();
//disable dashed border for other elements
this.disableBorderDashes(ctx);
this.disableBorderDashes(ctx, values);
}
ctx.restore();
this.updateBoundingBox(x,y,ctx,selected);
this.updateBoundingBox(x, y, ctx, selected, hover);
this.labelModule.draw(ctx, this.left + this.textSize.width / 2 + this.margin.left,
this.top + this.textSize.height / 2 + this.margin.top, selected);
this.top + this.textSize.height / 2 + this.margin.top, selected, hover);
}
updateBoundingBox(x,y,ctx, selected) {
this.resize(ctx, selected);
updateBoundingBox(x, y, ctx, selected, hover) {
this.resize(ctx, selected, hover);
this.left = x - this.width * 0.5;
this.top = y - this.height * 0.5;

+ 5
- 5
lib/network/modules/components/nodes/shapes/Diamond.js View File

@ -7,12 +7,12 @@ class Diamond extends ShapeBase {
super(options, body, labelModule)
}
resize(ctx) {
this._resizeShape();
resize(ctx, values, selected = this.selected, hover = this.hover) {
this._resizeShape(selected, hover, values);
}
draw(ctx, x, y, selected, hover) {
this._drawShape(ctx, 'diamond', 4, x, y, selected, hover);
draw(ctx, x, y, selected, hover, values) {
this._drawShape(ctx, 'diamond', 4, x, y, selected, hover, values);
}
distanceToBorder(ctx, angle) {
@ -20,4 +20,4 @@ class Diamond extends ShapeBase {
}
}
export default Diamond;
export default Diamond;

+ 5
- 5
lib/network/modules/components/nodes/shapes/Dot.js View File

@ -7,12 +7,12 @@ class Dot extends ShapeBase {
super(options, body, labelModule)
}
resize(ctx) {
this._resizeShape();
resize(ctx, values, selected = this.selected, hover = this.hover) {
this._resizeShape(selected, hover, values);
}
draw(ctx, x, y, selected, hover) {
this._drawShape(ctx, 'circle', 2, x, y, selected, hover);
draw(ctx, x, y, selected, hover, values) {
this._drawShape(ctx, 'circle', 2, x, y, selected, hover, values);
}
distanceToBorder(ctx, angle) {
@ -21,4 +21,4 @@ class Dot extends ShapeBase {
}
}
export default Dot;
export default Dot;

+ 16
- 18
lib/network/modules/components/nodes/shapes/Ellipse.js View File

@ -7,9 +7,9 @@ class Ellipse extends NodeBase {
super(options, body, labelModule);
}
resize(ctx, selected) {
if (this.width === undefined) {
var textSize = this.labelModule.getTextSize(ctx, selected);
resize(ctx, selected = this.selected, hover = this.hover) {
if ((this.width === undefined) || (this.labelModule.differentState(selected, hover))) {
var textSize = this.labelModule.getTextSize(ctx, selected, hover);
this.height = textSize.height * 2;
this.width = textSize.width + this.height;
@ -17,48 +17,46 @@ class Ellipse extends NodeBase {
}
}
draw(ctx, x, y, selected, hover) {
this.resize(ctx, selected);
draw(ctx, x, y, selected, hover, values) {
this.resize(ctx, selected, hover);
this.left = x - this.width * 0.5;
this.top = y - this.height * 0.5;
var neutralborderWidth = this.options.borderWidth;
var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
var borderWidth = (selected ? selectionLineWidth : neutralborderWidth) / this.body.view.scale;
var borderWidth = values.borderWidth / this.body.view.scale;
ctx.lineWidth = Math.min(this.width, borderWidth);
ctx.strokeStyle = selected ? this.options.color.highlight.border : hover ? this.options.color.hover.border : this.options.color.border;
ctx.strokeStyle = values.borderColor;
ctx.fillStyle = selected ? this.options.color.highlight.background : hover ? this.options.color.hover.background : this.options.color.background;
ctx.fillStyle = values.color;
ctx.ellipse(this.left, this.top, this.width, this.height);
// draw shadow if enabled
this.enableShadow(ctx);
this.enableShadow(ctx, values);
// draw the background
ctx.fill();
// disable shadows for other elements.
this.disableShadow(ctx);
this.disableShadow(ctx, values);
//draw dashed border if enabled, save and restore is required for firefox not to crash on unix.
ctx.save();
// if borders are zero width, they will be drawn with width 1 by default. This prevents that
if (borderWidth > 0) {
this.enableBorderDashes(ctx);
this.enableBorderDashes(ctx, values);
//draw the border
ctx.stroke();
//disable dashed border for other elements
this.disableBorderDashes(ctx);
this.disableBorderDashes(ctx, values);
}
ctx.restore();
this.updateBoundingBox(x, y, ctx, selected);
this.labelModule.draw(ctx, x, y, selected);
this.updateBoundingBox(x, y, ctx, selected, hover);
this.labelModule.draw(ctx, x, y, selected, hover);
}
updateBoundingBox(x, y, ctx, selected) {
this.resize(ctx, selected); // just in case
updateBoundingBox(x, y, ctx, selected, hover) {
this.resize(ctx, selected, hover); // just in case
this.left = x - this.width * 0.5;
this.top = y - this.height * 0.5;

+ 11
- 12
lib/network/modules/components/nodes/shapes/Icon.js View File

@ -8,8 +8,8 @@ class Icon extends NodeBase {
this._setMargins(labelModule);
}
resize(ctx) {
if (this.width === undefined) {
resize(ctx, selected, hover) {
if ((this.width === undefined) || (this.labelModule.differentState(selected, hover))) {
this.iconSize = {
width: Number(this.options.icon.size),
height: Number(this.options.icon.size)
@ -20,13 +20,13 @@ class Icon extends NodeBase {
}
}
draw(ctx, x, y, selected, hover) {
this.resize(ctx);
draw(ctx, x, y, selected, hover, values) {
this.resize(ctx, selected, hover);
this.options.icon.size = this.options.icon.size || 50;
this.left = x - this.width / 2;
this.top = y - this.height / 2;
this._icon(ctx, x, y, selected);
this._icon(ctx, x, y, selected, hover, values);
if (this.options.label !== undefined) {
var iconTextSpacing = 5;
@ -34,10 +34,10 @@ class Icon extends NodeBase {
y + this.height / 2 + iconTextSpacing, selected);
}
this.updateBoundingBox(x,y)
this.updateBoundingBox(x, y)
}
updateBoundingBox(x,y) {
updateBoundingBox(x, y) {
this.boundingBox.top = y - this.options.icon.size * 0.5;
this.boundingBox.left = x - this.options.icon.size * 0.5;
this.boundingBox.right = x + this.options.icon.size * 0.5;
@ -51,7 +51,7 @@ class Icon extends NodeBase {
}
}
_icon(ctx, x, y, selected) {
_icon(ctx, x, y, selected, hover, values) {
let iconSize = Number(this.options.icon.size);
if (this.options.icon.code !== undefined) {
@ -63,13 +63,12 @@ class Icon extends NodeBase {
ctx.textBaseline = "middle";
// draw shadow if enabled
this.enableShadow(ctx);
this.enableShadow(ctx, values);
ctx.fillText(this.options.icon.code, x, y);
// disable shadows for other elements.
this.disableShadow(ctx);
}
else {
this.disableShadow(ctx, values);
} else {
console.error('When using the icon shape, you need to define the code in the icon options object. This can be done per node or globally.')
}

+ 4
- 4
lib/network/modules/components/nodes/shapes/Image.js View File

@ -12,7 +12,7 @@ class Image extends CircleImageBase {
this._resizeImage();
}
draw(ctx, x, y, selected, hover) {
draw(ctx, x, y, selected, hover, values) {
this.resize();
this.left = x - this.width / 2;
this.top = y - this.height / 2;
@ -53,9 +53,9 @@ class Image extends CircleImageBase {
ctx.closePath();
}
this._drawImageAtPosition(ctx);
this._drawImageAtPosition(ctx, values);
this._drawImageLabel(ctx, x, y, selected || hover);
this._drawImageLabel(ctx, x, y, selected, hover);
this.updateBoundingBox(x,y);
}
@ -82,4 +82,4 @@ class Image extends CircleImageBase {
}
}
export default Image;
export default Image;

+ 3
- 3
lib/network/modules/components/nodes/shapes/Square.js View File

@ -11,8 +11,8 @@ class Square extends ShapeBase {
this._resizeShape();
}
draw(ctx, x, y, selected, hover) {
this._drawShape(ctx, 'square', 2, x, y, selected, hover);
draw(ctx, x, y, selected, hover, values) {
this._drawShape(ctx, 'square', 2, x, y, selected, hover, values);
}
distanceToBorder(ctx, angle) {
@ -20,4 +20,4 @@ class Square extends ShapeBase {
}
}
export default Square;
export default Square;

+ 5
- 5
lib/network/modules/components/nodes/shapes/Star.js View File

@ -7,12 +7,12 @@ class Star extends ShapeBase {
super(options, body, labelModule)
}
resize(ctx) {
this._resizeShape();
resize(ctx, values, selected, hover) {
this._resizeShape(selected, hover, values);
}
draw(ctx, x, y, selected, hover) {
this._drawShape(ctx, 'star', 4, x, y, selected, hover);
draw(ctx, x, y, selected, hover, values) {
this._drawShape(ctx, 'star', 4, x, y, selected, hover, values);
}
distanceToBorder(ctx, angle) {
@ -20,4 +20,4 @@ class Star extends ShapeBase {
}
}
export default Star;
export default Star;

+ 11
- 11
lib/network/modules/components/nodes/shapes/Text.js View File

@ -8,33 +8,33 @@ class Text extends NodeBase {
this._setMargins(labelModule);
}
resize(ctx, selected) {
if (this.width === undefined) {
this.textSize = this.labelModule.getTextSize(ctx,selected);
resize(ctx, selected, hover) {
if ((this.width === undefined) || this.labelModule.differentState(selected, hover)) {
this.textSize = this.labelModule.getTextSize(ctx, selected, hover);
this.width = this.textSize.width + this.margin.right + this.margin.left;
this.height = this.textSize.height + this.margin.top + this.margin.bottom;
this.radius = 0.5*this.width;
}
}
draw(ctx, x, y, selected, hover) {
this.resize(ctx, selected || hover);
draw(ctx, x, y, selected, hover, values) {
this.resize(ctx, selected, hover);
this.left = x - this.width / 2;
this.top = y - this.height / 2;
// draw shadow if enabled
this.enableShadow(ctx);
this.enableShadow(ctx, values);
this.labelModule.draw(ctx, this.left + this.textSize.width / 2 + this.margin.left,
this.top + this.textSize.height / 2 + this.margin.top, selected || hover);
this.top + this.textSize.height / 2 + this.margin.top, selected, hover);
// disable shadows for other elements.
this.disableShadow(ctx);
this.disableShadow(ctx, values);
this.updateBoundingBox(x, y, ctx, selected);
this.updateBoundingBox(x, y, ctx, selected, hover);
}
updateBoundingBox(x, y, ctx, selected) {
this.resize(ctx, selected);
updateBoundingBox(x, y, ctx, selected, hover) {
this.resize(ctx, selected, hover);
this.left = x - this.width / 2;
this.top = y - this.height / 2;

+ 3
- 3
lib/network/modules/components/nodes/shapes/Triangle.js View File

@ -11,8 +11,8 @@ class Triangle extends ShapeBase {
this._resizeShape();
}
draw(ctx, x, y, selected, hover) {
this._drawShape(ctx, 'triangle', 3, x, y, selected, hover);
draw(ctx, x, y, selected, hover, values) {
this._drawShape(ctx, 'triangle', 3, x, y, selected, hover, values);
}
distanceToBorder(ctx, angle) {
@ -20,4 +20,4 @@ class Triangle extends ShapeBase {
}
}
export default Triangle;
export default Triangle;

+ 3
- 3
lib/network/modules/components/nodes/shapes/TriangleDown.js View File

@ -11,8 +11,8 @@ class TriangleDown extends ShapeBase {
this._resizeShape();
}
draw(ctx, x, y, selected, hover) {
this._drawShape(ctx, 'triangleDown', 3, x, y, selected, hover);
draw(ctx, x, y, selected, hover, values) {
this._drawShape(ctx, 'triangleDown', 3, x, y, selected, hover, values);
}
distanceToBorder(ctx, angle) {
@ -20,4 +20,4 @@ class TriangleDown extends ShapeBase {
}
}
export default TriangleDown;
export default TriangleDown;

+ 16
- 18
lib/network/modules/components/nodes/util/CircleImageBase.js View File

@ -65,43 +65,41 @@ class CircleImageBase extends NodeBase {
}
_drawRawCircle(ctx, x, y, selected, hover, size) {
var neutralborderWidth = this.options.borderWidth;
var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
var borderWidth = (selected ? selectionLineWidth : neutralborderWidth) / this.body.view.scale;
_drawRawCircle(ctx, x, y, selected, hover, values) {
var borderWidth = values.borderWidth / this.body.view.scale;
ctx.lineWidth = Math.min(this.width, borderWidth);
ctx.strokeStyle = selected ? this.options.color.highlight.border : hover ? this.options.color.hover.border : this.options.color.border;
ctx.fillStyle = selected ? this.options.color.highlight.background : hover ? this.options.color.hover.background : this.options.color.background;
ctx.circle(x, y, size);
ctx.strokeStyle = values.borderColor;
ctx.fillStyle = values.color;
ctx.circle(x, y, values.size);
// draw shadow if enabled
this.enableShadow(ctx);
this.enableShadow(ctx, values);
// draw the background
ctx.fill();
// disable shadows for other elements.
this.disableShadow(ctx);
this.disableShadow(ctx, values);
//draw dashed border if enabled, save and restore is required for firefox not to crash on unix.
ctx.save();
// if borders are zero width, they will be drawn with width 1 by default. This prevents that
if (borderWidth > 0) {
this.enableBorderDashes(ctx);
this.enableBorderDashes(ctx, values);
//draw the border
ctx.stroke();
//disable dashed border for other elements
this.disableBorderDashes(ctx);
this.disableBorderDashes(ctx, values);
}
ctx.restore();
}
_drawImageAtPosition(ctx) {
_drawImageAtPosition(ctx, values) {
if (this.imageObj.width != 0) {
// draw the image
ctx.globalAlpha = 1.0;
// draw shadow if enabled
this.enableShadow(ctx);
this.enableShadow(ctx, values);
let factor = (this.imageObj.width / this.width) / this.body.view.scale;
if (factor > 2 && this.options.shapeProperties.interpolation === true) {
@ -136,17 +134,17 @@ class CircleImageBase extends NodeBase {
// disable shadows for other elements.
this.disableShadow(ctx);
this.disableShadow(ctx, values);
}
}
_drawImageLabel(ctx, x, y, selected) {
_drawImageLabel(ctx, x, y, selected, hover) {
var yLabel;
var offset = 0;
if (this.height !== undefined) {
offset = this.height * 0.5;
var labelDimensions = this.labelModule.getTextSize(ctx);
var labelDimensions = this.labelModule.getTextSize(ctx, selected, hover);
if (labelDimensions.lineCount >= 1) {
offset += labelDimensions.height / 2;
}
@ -157,8 +155,8 @@ class CircleImageBase extends NodeBase {
if (this.options.label) {
this.labelOffset = offset;
}
this.labelModule.draw(ctx, x, yLabel, selected, 'hanging');
this.labelModule.draw(ctx, x, yLabel, selected, hover, 'hanging');
}
}
export default CircleImageBase;
export default CircleImageBase;

+ 15
- 13
lib/network/modules/components/nodes/util/NodeBase.js View File

@ -42,17 +42,17 @@ class NodeBase {
Math.abs(this.height / 2 / Math.sin(angle))) + borderWidth;
}
enableShadow(ctx) {
if (this.options.shadow.enabled === true) {
ctx.shadowColor = this.options.shadow.color;
ctx.shadowBlur = this.options.shadow.size;
ctx.shadowOffsetX = this.options.shadow.x;
ctx.shadowOffsetY = this.options.shadow.y;
enableShadow(ctx, values) {
if (values.shadow) {
ctx.shadowColor = values.shadowColor;
ctx.shadowBlur = values.shadowSize;
ctx.shadowOffsetX = values.shadowX;
ctx.shadowOffsetY = values.shadowY;
}
}
disableShadow(ctx) {
if (this.options.shadow.enabled === true) {
disableShadow(ctx, values) {
if (values.shadow) {
ctx.shadowColor = 'rgba(0,0,0,0)';
ctx.shadowBlur = 0;
ctx.shadowOffsetX = 0;
@ -60,10 +60,10 @@ class NodeBase {
}
}
enableBorderDashes(ctx) {
if (this.options.shapeProperties.borderDashes !== false) {
enableBorderDashes(ctx, values) {
if (values.borderDashes !== false) {
if (ctx.setLineDash !== undefined) {
let dashes = this.options.shapeProperties.borderDashes;
let dashes = values.borderDashes;
if (dashes === true) {
dashes = [5,15]
}
@ -72,18 +72,20 @@ class NodeBase {
else {
console.warn("setLineDash is not supported in this browser. The dashed borders cannot be used.");
this.options.shapeProperties.borderDashes = false;
values.borderDashes = false;
}
}
}
disableBorderDashes(ctx) {
if (this.options.shapeProperties.borderDashes !== false) {
disableBorderDashes(ctx, values) {
if (values.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;
values.borderDashes = false;
}
}
}

+ 15
- 17
lib/network/modules/components/nodes/util/ShapeBase.js View File

@ -5,52 +5,50 @@ class ShapeBase extends NodeBase {
super(options, body, labelModule)
}
_resizeShape() {
if (this.width === undefined) {
var size = 2 * this.options.size;
_resizeShape(selected = this.selected, hover = this.hover, values = { size: this.options.size }) {
if ((this.width === undefined) || (this.labelModule.differentState(selected, hover))) {
var size = 2 * values.size;
this.width = size;
this.height = size;
this.radius = 0.5*this.width;
}
}
_drawShape(ctx, shape, sizeMultiplier, x, y, selected, hover) {
this._resizeShape();
_drawShape(ctx, shape, sizeMultiplier, x, y, selected, hover, values) {
this._resizeShape(selected, hover, values);
this.left = x - this.width / 2;
this.top = y - this.height / 2;
var neutralborderWidth = this.options.borderWidth;
var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
var borderWidth = (selected ? selectionLineWidth : neutralborderWidth) / this.body.view.scale;
var borderWidth = values.borderWidth / this.body.view.scale;
ctx.lineWidth = Math.min(this.width, borderWidth);
ctx.strokeStyle = selected ? this.options.color.highlight.border : hover ? this.options.color.hover.border : this.options.color.border;
ctx.fillStyle = selected ? this.options.color.highlight.background : hover ? this.options.color.hover.background : this.options.color.background;
ctx[shape](x, y, this.options.size);
ctx.strokeStyle = values.borderColor;
ctx.fillStyle = values.color;
ctx[shape](x, y, values.size);
// draw shadow if enabled
this.enableShadow(ctx);
this.enableShadow(ctx, values);
// draw the background
ctx.fill();
// disable shadows for other elements.
this.disableShadow(ctx);
this.disableShadow(ctx, values);
//draw dashed border if enabled, save and restore is required for firefox not to crash on unix.
ctx.save();
// if borders are zero width, they will be drawn with width 1 by default. This prevents that
if (borderWidth > 0) {
this.enableBorderDashes(ctx);
this.enableBorderDashes(ctx, values);
//draw the border
ctx.stroke();
//disable dashed border for other elements
this.disableBorderDashes(ctx);
this.disableBorderDashes(ctx, values);
}
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.
this.labelModule.draw(ctx, x, yLabel, selected, 'hanging');
this.labelModule.draw(ctx, x, yLabel, selected, hover, 'hanging');
}
this.updateBoundingBox(x,y);
@ -73,4 +71,4 @@ class ShapeBase extends NodeBase {
}
export default ShapeBase;
export default ShapeBase;

+ 83
- 53
lib/network/modules/components/shared/Label.js View File

@ -95,6 +95,23 @@ class Label {
}
}
// set the selected functions based on 'nearest' value
choosify(elementOptions, options, defaultOptions) {
this.fontOptions.chooser = true;
let pile = [options, elementOptions, defaultOptions];
let chosen = util.topMost(pile, 'chosen');
if (typeof chosen === 'boolean') {
this.fontOptions.chooser = chosen;
} else if (typeof chosen === 'object') {
let chosenLabel = util.topMost(pile, ['chosen', 'label']);
if ((typeof chosenLabel === 'boolean') || (typeof chosenLabel === 'function')) {
this.fontOptions.chooser = chosenLabel;
}
}
}
// When margins are set in an element, adjust sizes is called to remove them
// from the width/height constraints. This must be done prior to label sizing.
adjustSizes(margins) {
@ -239,7 +256,7 @@ class Label {
* @param selected
* @param baseline
*/
draw(ctx, x, y, selected, baseline = 'middle') {
draw(ctx, x, y, selected, hover, baseline = 'middle') {
// if no label, return
if (this.elementOptions.label === undefined)
return;
@ -250,12 +267,12 @@ class Label {
return;
// update the size cache if required
this.calculateLabelSize(ctx, selected, x, y, baseline);
this.calculateLabelSize(ctx, selected, hover, x, y, baseline);
// create the fontfill background
this._drawBackground(ctx);
// draw text
this._drawText(ctx, selected, x, y, baseline);
this._drawText(ctx, selected, hover, x, y, baseline);
}
/**
@ -298,7 +315,7 @@ class Label {
* @param baseline
* @private
*/
_drawText(ctx, selected, x, y, baseline = 'middle') {
_drawText(ctx, selected, hover, x, y, baseline = 'middle') {
let fontSize = this.fontOptions.size;
let viewFontSize = fontSize * this.body.view.scale;
// this ensures that there will not be HUGE letters on screen by setting an upper limit on the visible text size (regardless of zoomLevel)
@ -332,14 +349,17 @@ class Label {
for (let j = 0; j < this.lines[i].blocks.length; j++) {
let block = this.lines[i].blocks[j];
ctx.font = block.font;
let [fontColor, strokeColor] = this._getColor(block.color, viewFontSize);
if (this.fontOptions.strokeWidth > 0) {
ctx.lineWidth = this.fontOptions.strokeWidth;
let [fontColor, strokeColor] = this._getColor(block.color, viewFontSize, block.strokeColor);
if (block.strokeWidth > 0) {
ctx.lineWidth = block.strokeWidth;
ctx.strokeStyle = strokeColor;
ctx.lineJoin = 'round';
ctx.strokeText(block.text, x + width, yLine + block.vadjust);
}
ctx.fillStyle = fontColor;
if (block.strokeWidth > 0) {
ctx.strokeText(block.text, x + width, yLine + block.vadjust);
}
ctx.fillText(block.text, x + width, yLine + block.vadjust);
width += block.width;
}
@ -382,9 +402,9 @@ class Label {
* @returns {*[]}
* @private
*/
_getColor(color, viewFontSize) {
_getColor(color, viewFontSize, initialStrokeColor) {
let fontColor = color || '#000000';
let strokeColor = this.fontOptions.strokeColor || '#ffffff';
let strokeColor = initialStrokeColor || '#ffffff';
if (viewFontSize <= this.elementOptions.scaling.label.drawThreshold) {
let opacity = Math.max(0, Math.min(1, 1 - (this.elementOptions.scaling.label.drawThreshold - viewFontSize)));
fontColor = util.overrideOpacity(fontColor, opacity);
@ -400,8 +420,8 @@ class Label {
* @param selected
* @returns {{width: number, height: number}}
*/
getTextSize(ctx, selected = false) {
this._processLabel(ctx, selected);
getTextSize(ctx, selected = false, hover = false) {
this._processLabel(ctx, selected, hover);
return {
width: this.size.width,
height: this.size.height,
@ -418,9 +438,9 @@ class Label {
* @param y
* @param baseline
*/
calculateLabelSize(ctx, selected, x = 0, y = 0, baseline = 'middle') {
calculateLabelSize(ctx, selected, hover, x = 0, y = 0, baseline = 'middle') {
if (this.labelDirty === true) {
this._processLabel(ctx, selected);
this._processLabel(ctx, selected, hover);
}
this.size.left = x - this.size.width * 0.5;
this.size.top = y - this.size.height * 0.5;
@ -696,29 +716,37 @@ class Label {
return blocks;
}
setFont(ctx, selected, mod) {
let height
let vadjust
let color
if (mod === 'normal') {
ctx.font = (selected && this.elementOptions.labelHighlightBold ? 'bold ' : '') +
this.fontOptions.size + "px " + this.fontOptions.face;
color = this.fontOptions.color;
height = this.fontOptions.size;
vadjust = this.fontOptions.vadjust;
getFormattingValues(ctx, selected, hover, mod) {
let values = {
color: (mod === "normal") ? this.fontOptions.color : this.fontOptions[mod].color,
size: (mod === "normal") ? this.fontOptions.size : this.fontOptions[mod].size,
face: (mod === "normal") ? this.fontOptions.face : this.fontOptions[mod].face,
mod: (mod === "normal") ? "" : this.fontOptions[mod].mod,
vadjust: (mod === "normal") ? this.fontOptions.vadjust : this.fontOptions[mod].vadjust,
strokeWidth: this.fontOptions.strokeWidth,
strokeColor: this.fontOptions.strokeColor
};
if (mod === "normal") {
if (selected || hover) {
if ((this.fontOptions.chooser === true) && (this.elementOptions.labelHighlightBold)) {
values.mod = 'bold';
} else if (typeof this.fontOptions.chooser === 'function') {
this.fontOptions.chooser(ctx, values, this.elementOptions.id, selected, hover);
}
}
} else {
ctx.font = this.fontOptions[mod].mod + " " +
this.fontOptions[mod].size + "px " + this.fontOptions[mod].face;
color = this.fontOptions[mod].color;
height = this.fontOptions[mod].size;
vadjust = this.fontOptions[mod].vadjust || 0;
}
return {
font: ctx.font.replace(/"/g, ""),
color: color,
height: height,
vadjust: vadjust
if ((selected || hover) && (typeof this.fontOptions.chooser === 'function')) {
this.fontOptions.chooser(ctx, values, this.elementOptions.id, selected, hover);
}
}
ctx.font = (values.mod + " " + values.size + "px " + values.face).replace(/"/g, "");
values.font = ctx.font;
values.height = values.size;
return values;
}
differentState(selected, hover) {
return ((selected !== this.fontOptions.selectedState) && (hover !== this.fontOptions.hoverState));
}
/**
@ -727,24 +755,24 @@ class Label {
* @param selected
* @private
*/
_processLabel(ctx, selected) {
_processLabel(ctx, selected, hover) {
let width = 0;
let height = 0;
let nlLines = [];
let lines = [];
let k = 0;
lines.add = function(l, text, font, color, width, height, vadjust) {
lines.add = function(l, text, font, color, width, height, vadjust, mod, strokeWidth, strokeColor) {
if (this.length == l) {
this[l] = { width: 0, height: 0, blocks: [] };
}
this[l].blocks.push({ text, font, color, width, height, vadjust });
this[l].blocks.push({ text, font, color, width, height, vadjust, mod, strokeWidth, strokeColor });
}
lines.accumulate = function(l, width, height) {
this[l].width += width;
this[l].height = height > this[l].height ? height : this[l].height;
}
lines.addAndAccumulate = function(l, text, font, color, width, height, vadjust) {
this.add(l, text, font, color, width, height, vadjust);
lines.addAndAccumulate = function(l, text, font, color, width, height, vadjust, mod, strokeWidth, strokeColor) {
this.add(l, text, font, color, width, height, vadjust, mod, strokeWidth, strokeColor);
this.accumulate(l, width, height);
}
if (this.elementOptions.label !== undefined) {
@ -757,15 +785,15 @@ class Label {
let lineHeight = 0;
if (blocks) {
if (blocks.length == 0) {
this.setFont(ctx, selected, "normal");
lines.addAndAccumulate(k, "", ctx.font, "#000000", 0, this.fontOptions.size, this.fontOptions.vadjust);
let values = this.getFormattingValues(ctx, selected, hover, "normal");
lines.addAndAccumulate(k, "", values.font, values.color, 0, values.size, values.vadjust, "normal", values.strokeWidth, values.strokeColor);
height += lines[k].height;
k++;
continue;
}
for (let j = 0; j < blocks.length; j++) {
if (this.fontOptions.maxWdt > 0) {
let metrics = this.setFont(ctx, selected, blocks[j].mod);
let values = this.getFormattingValues(ctx, selected, hover, blocks[j].mod);
let words = blocks[j].text.split(" ");
let atStart = true
let text = "";
@ -777,8 +805,8 @@ class Label {
lastMeasure = measure;
measure = ctx.measureText(text + pre + words[w]);
if (lineWidth + measure.width > this.fontOptions.maxWdt) {
lineHeight = (metrics.height > lineHeight) ? metrics.height : lineHeight;
lines.add(k, text, ctx.font, metrics.color, lastMeasure.width, metrics.height, metrics.vadjust);
lineHeight = (values.height > lineHeight) ? values.height : lineHeight;
lines.add(k, text, values.font, values.color, lastMeasure.width, values.height, values.vadjust, blocks[j].mod, values.strokeWidth, values.strokeColor);
lines.accumulate(k, lastMeasure.width, lineHeight);
text = "";
atStart = true;
@ -789,9 +817,9 @@ class Label {
} else {
text = text + pre + words[w];
if (w === words.length-1) {
lineHeight = (metrics.height > lineHeight) ? metrics.height : lineHeight;
lineHeight = (values.height > lineHeight) ? values.height : lineHeight;
lineWidth += measure.width;
lines.add(k, text, ctx.font, metrics.color, measure.width, metrics.height, metrics.vadjust);
lines.add(k, text, values.font, values.color, measure.width, values.height, values.vadjust, blocks[j].mod, values.strokeWidth, values.strokeColor);
lines.accumulate(k, measure.width, lineHeight);
if (j === blocks.length-1) {
width = lines[k].width > width ? lines[k].width : width;
@ -804,9 +832,9 @@ class Label {
}
}
} else {
let metrics = this.setFont(ctx, selected, blocks[j].mod)
let values = this.getFormattingValues(ctx, selected, hover, blocks[j].mod);
let measure = ctx.measureText(blocks[j].text);
lines.addAndAccumulate(k, blocks[j].text, ctx.font, metrics.color, measure.width, metrics.height, metrics.vadjust);
lines.addAndAccumulate(k, blocks[j].text, values.font, values.color, measure.width, values.height, values.vadjust, blocks[j].mod, values.strokeWidth, values.strokeColor);
width = lines[k].width > width ? lines[k].width : width;
if (blocks.length-1 === j) {
height += lines[k].height;
@ -818,7 +846,7 @@ class Label {
}
} else {
for (let i = 0; i < lineCount; i++) {
ctx.font = (selected && this.elementOptions.labelHighlightBold ? 'bold ' : '') + this.fontOptions.size + "px " + this.fontOptions.face;
let values = this.getFormattingValues(ctx, selected, hover, "normal");
if (this.fontOptions.maxWdt > 0) {
let words = nlLines[i].split(" ");
let text = "";
@ -830,7 +858,7 @@ class Label {
lastMeasure = measure;
measure = ctx.measureText(text + pre + words[w]);
if (measure.width > this.fontOptions.maxWdt) {
lines.addAndAccumulate(k, text, ctx.font, this.fontOptions.color, lastMeasure.width, this.fontOptions.size, this.fontOptions.vadjust)
lines.addAndAccumulate(k, text, values.font, values.color, lastMeasure.width, values.size, values.vadjust, "normal", values.strokeWidth, values.strokeColor)
width = lines[k].width > width ? lines[k].width : width;
height += lines[k].height;
text = "";
@ -838,7 +866,7 @@ class Label {
} else {
text = text + pre + words[w];
if (w === words.length-1) {
lines.addAndAccumulate(k, text, ctx.font, this.fontOptions.color, measure.width, this.fontOptions.size, this.fontOptions.vadjust)
lines.addAndAccumulate(k, text, values.font, values.color, measure.width, values.size, values.vadjust, "normal", values.strokeWidth, values.strokeColor)
width = lines[k].width > width ? lines[k].width : width;
height += lines[k].height;
k++;
@ -849,7 +877,7 @@ class Label {
} else {
let text = nlLines[i];
let measure = ctx.measureText(text);
lines.addAndAccumulate(k, text, ctx.font, this.fontOptions.color, measure.width, this.fontOptions.size, this.fontOptions.vadjust);
lines.addAndAccumulate(k, text, values.font, values.color, measure.width, values.size, values.vadjust, "normal", values.strokeWidth, values.strokeColor);
width = lines[k].width > width ? lines[k].width : width;
height += lines[k].height;
k++;
@ -868,6 +896,8 @@ class Label {
this.lineCount = lines.length;
this.size.width = width;
this.size.height = height;
this.selectedState = selected;
this.hoverState = hover;
}
}

+ 88
- 78
lib/network/options.js View File

@ -16,29 +16,34 @@ let any = 'any';
let allOptions = {
configure: {
enabled: { bool },
filter: { bool, string, array, 'function': 'function' },
enabled: { boolean: bool },
filter: { boolean: bool, string, array, 'function': 'function' },
container: { dom },
showButton: { bool },
__type__: { object, bool, string, array, 'function': 'function' }
showButton: { boolean: bool },
__type__: { object, boolean: bool, string, array, 'function': 'function' }
},
edges: {
arrows: {
to: { enabled: { bool }, scaleFactor: { number }, type: { string: ['arrow', 'circle'] }, __type__: { object, bool } },
middle: { enabled: { bool }, scaleFactor: { number }, type: { string: ['arrow', 'circle'] }, __type__: { object, bool } },
from: { enabled: { bool }, scaleFactor: { number }, type: { string: ['arrow', 'circle'] }, __type__: { object, bool } },
to: { enabled: { boolean: bool }, scaleFactor: { number }, type: { string: ['arrow', 'circle'] }, __type__: { object, boolean: bool } },
middle: { enabled: { boolean: bool }, scaleFactor: { number }, type: { string: ['arrow', 'circle'] }, __type__: { object, boolean: bool } },
from: { enabled: { boolean: bool }, scaleFactor: { number }, type: { string: ['arrow', 'circle'] }, __type__: { object, boolean: bool } },
__type__: { string: ['from', 'to', 'middle'], object }
},
arrowStrikethrough: { bool },
arrowStrikethrough: { boolean: bool },
chosen: {
label: { boolean: bool, 'function': 'function' },
edge: { boolean: bool, 'function': 'function' },
__type__: { object, boolean: bool }
},
color: {
color: { string },
highlight: { string },
hover: { string },
inherit: { string: ['from', 'to', 'both'], bool },
inherit: { string: ['from', 'to', 'both'], boolean: bool },
opacity: { number },
__type__: { object, string }
},
dashes: { bool, array },
dashes: { boolean: bool, array },
font: {
color: { string },
size: { number }, // px
@ -48,7 +53,7 @@ let allOptions = {
strokeColor: { string },
align: { string: ['horizontal', 'top', 'middle', 'bottom'] },
vadjust: { number },
multi: { bool, string },
multi: { boolean: bool, string },
bold: {
color: { string },
size: { number }, // px
@ -83,22 +88,22 @@ let allOptions = {
},
__type__: { object, string }
},
hidden: { bool },
hidden: { boolean: bool },
hoverWidth: { 'function': 'function', number },
label: { string, 'undefined': 'undefined' },
labelHighlightBold: { bool },
labelHighlightBold: { boolean: bool },
length: { number, 'undefined': 'undefined' },
physics: { bool },
physics: { boolean: bool },
scaling: {
min: { number },
max: { number },
label: {
enabled: { bool },
enabled: { boolean: bool },
min: { number },
max: { number },
maxVisible: { number },
drawThreshold: { number },
__type__: { object, bool }
__type__: { object, boolean: bool }
},
customScalingFunction: { 'function': 'function' },
__type__: { object }
@ -106,91 +111,96 @@ let allOptions = {
selectionWidth: { 'function': 'function', number },
selfReferenceSize: { number },
shadow: {
enabled: { bool },
enabled: { boolean: bool },
color: { string },
size: { number },
x: { number },
y: { number },
__type__: { object, bool }
__type__: { object, boolean: bool }
},
smooth: {
enabled: { bool },
enabled: { boolean: bool },
type: { string: ['dynamic', 'continuous', 'discrete', 'diagonalCross', 'straightCross', 'horizontal', 'vertical', 'curvedCW', 'curvedCCW', 'cubicBezier'] },
roundness: { number },
forceDirection: { string: ['horizontal', 'vertical', 'none'], bool },
__type__: { object, bool }
forceDirection: { string: ['horizontal', 'vertical', 'none'], boolean: bool },
__type__: { object, boolean: bool }
},
title: { string, 'undefined': 'undefined' },
width: { number },
widthConstraint: {
maximum: { number },
__type__: { object, bool, number }
__type__: { object, boolean: bool, number }
},
value: { number, 'undefined': 'undefined' },
__type__: { object }
},
groups: {
useDefaultGroups: { bool },
useDefaultGroups: { boolean: bool },
__any__: 'get from nodes, will be overwritten below',
__type__: { object }
},
interaction: {
dragNodes: { bool },
dragView: { bool },
hideEdgesOnDrag: { bool },
hideNodesOnDrag: { bool },
hover: { bool },
dragNodes: { boolean: bool },
dragView: { boolean: bool },
hideEdgesOnDrag: { boolean: bool },
hideNodesOnDrag: { boolean: bool },
hover: { boolean: bool },
keyboard: {
enabled: { bool },
enabled: { boolean: bool },
speed: { x: { number }, y: { number }, zoom: { number }, __type__: { object } },
bindToWindow: { bool },
__type__: { object, bool }
},
multiselect: { bool },
navigationButtons: { bool },
selectable: { bool },
selectConnectedEdges: { bool },
hoverConnectedEdges: { bool },
bindToWindow: { boolean: bool },
__type__: { object, boolean: bool }
},
multiselect: { boolean: bool },
navigationButtons: { boolean: bool },
selectable: { boolean: bool },
selectConnectedEdges: { boolean: bool },
hoverConnectedEdges: { boolean: bool },
tooltipDelay: { number },
zoomView: { bool },
zoomView: { boolean: bool },
__type__: { object }
},
layout: {
randomSeed: { 'undefined': 'undefined', number },
improvedLayout: { bool },
improvedLayout: { boolean: bool },
hierarchical: {
enabled: { bool },
enabled: { boolean: bool },
levelSeparation: { number },
nodeSpacing: { number },
treeSpacing: { number },
blockShifting: { bool },
edgeMinimization: { bool },
parentCentralization: { bool },
blockShifting: { boolean: bool },
edgeMinimization: { boolean: bool },
parentCentralization: { boolean: bool },
direction: { string: ['UD', 'DU', 'LR', 'RL'] }, // UD, DU, LR, RL
sortMethod: { string: ['hubsize', 'directed'] }, // hubsize, directed
__type__: { object, bool }
__type__: { object, boolean: bool }
},
__type__: { object }
},
manipulation: {
enabled: { bool },
initiallyActive: { bool },
addNode: { bool, 'function': 'function' },
addEdge: { bool, 'function': 'function' },
enabled: { boolean: bool },
initiallyActive: { boolean: bool },
addNode: { boolean: bool, 'function': 'function' },
addEdge: { boolean: bool, 'function': 'function' },
editNode: { 'function': 'function' },
editEdge: {
editWithoutDrag: { 'function' : 'function' },
__type__: {object, bool, 'function': 'function' }
__type__: {object, boolean: bool, 'function': 'function' }
},
deleteNode: { bool, 'function': 'function' },
deleteEdge: { bool, 'function': 'function' },
deleteNode: { boolean: bool, 'function': 'function' },
deleteEdge: { boolean: bool, 'function': 'function' },
controlNodeStyle: 'get from nodes, will be overwritten below',
__type__: { object, bool }
__type__: { object, boolean: bool }
},
nodes: {
borderWidth: { number },
borderWidthSelected: { number, 'undefined': 'undefined' },
brokenImage: { string, 'undefined': 'undefined' },
chosen: {
label: { boolean: bool, 'function': 'function' },
node: { boolean: bool, 'function': 'function' },
__type__: { object, boolean: bool }
},
color: {
border: { string },
background: { string },
@ -207,9 +217,9 @@ let allOptions = {
__type__: { object, string }
},
fixed: {
x: { bool },
y: { bool },
__type__: { object, bool }
x: { boolean: bool },
y: { boolean: bool },
__type__: { object, boolean: bool }
},
font: {
align: { string },
@ -220,7 +230,7 @@ let allOptions = {
strokeWidth: { number }, // px
strokeColor: { string },
vadjust: { number },
multi: { bool, string },
multi: { boolean: bool, string },
bold: {
color: { string },
size: { number }, // px
@ -259,9 +269,9 @@ let allOptions = {
heightConstraint: {
minimum: { number },
valign: { string },
__type__: { object, bool, number }
__type__: { object, boolean: bool, number }
},
hidden: { bool },
hidden: { boolean: bool },
icon: {
face: { string },
code: { string }, //'\uf007',
@ -272,7 +282,7 @@ let allOptions = {
id: { string, number },
image: { string, 'undefined': 'undefined' }, // --> URL
label: { string, 'undefined': 'undefined' },
labelHighlightBold: { bool },
labelHighlightBold: { boolean: bool },
level: { number, 'undefined': 'undefined' },
margin: {
top: { number },
@ -282,36 +292,36 @@ let allOptions = {
__type__: { object, number }
},
mass: { number },
physics: { bool },
physics: { boolean: bool },
scaling: {
min: { number },
max: { number },
label: {
enabled: { bool },
enabled: { boolean: bool },
min: { number },
max: { number },
maxVisible: { number },
drawThreshold: { number },
__type__: { object, bool }
__type__: { object, boolean: bool }
},
customScalingFunction: { 'function': 'function' },
__type__: { object }
},
shadow: {
enabled: { bool },
enabled: { boolean: bool },
color: { string },
size: { number },
x: { number },
y: { number },
__type__: { object, bool }
__type__: { object, boolean: bool }
},
shape: { string: ['ellipse', 'circle', 'database', 'box', 'text', 'image', 'circularImage', 'diamond', 'dot', 'star', 'triangle', 'triangleDown', 'square', 'icon'] },
shapeProperties: {
borderDashes: { bool, array },
borderDashes: { boolean: bool, array },
borderRadius: { number },
interpolation: { bool },
useImageSize: { bool },
useBorderWithImage: { bool },
interpolation: { boolean: bool },
useImageSize: { boolean: bool },
useBorderWithImage: { boolean: bool },
__type__: { object }
},
size: { number },
@ -320,14 +330,14 @@ let allOptions = {
widthConstraint: {
minimum: { number },
maximum: { number },
__type__: { object, bool, number }
__type__: { object, boolean: bool, number }
},
x: { number },
y: { number },
__type__: { object }
},
physics: {
enabled: { bool },
enabled: { boolean: bool },
barnesHut: {
gravitationalConstant: { number },
centralGravity: { number },
@ -366,21 +376,21 @@ let allOptions = {
minVelocity: { number }, // px/s
solver: { string: ['barnesHut', 'repulsion', 'hierarchicalRepulsion', 'forceAtlas2Based'] },
stabilization: {
enabled: { bool },
enabled: { boolean: bool },
iterations: { number }, // maximum number of iteration to stabilize
updateInterval: { number },
onlyDynamicEdges: { bool },
fit: { bool },
__type__: { object, bool }
onlyDynamicEdges: { boolean: bool },
fit: { boolean: bool },
__type__: { object, boolean: bool }
},
timestep: { number },
adaptiveTimestep: { bool },
__type__: { object, bool }
adaptiveTimestep: { boolean: bool },
__type__: { object, boolean: bool }
},
//globals :
autoResize: { bool },
clickToUse: { bool },
autoResize: { boolean: bool },
clickToUse: { boolean: bool },
locale: { string },
locales: {
__any__: { any },

lib/network/modules/components/Popup.js → lib/shared/Popup.js View File

@ -18,7 +18,7 @@ class Popup {
// create the frame
this.frame = document.createElement('div');
this.frame.className = 'vis-network-tooltip';
this.frame.className = 'vis-tooltip';
this.container.appendChild(this.frame);
}

lib/network/css/network-tooltip.css → lib/shared/tooltip.css View File

@ -1,4 +1,4 @@
div.vis-network-tooltip {
div.vis-tooltip {
position: absolute;
visibility: hidden;
padding: 5px;
@ -16,4 +16,6 @@ div.vis-network-tooltip {
box-shadow: 3px 3px 10px rgba(0, 0, 0, 0.2);
pointer-events: none;
}
z-index: 5;
}

+ 1
- 2
lib/timeline/Core.js View File

@ -119,10 +119,9 @@ Core.prototype._create = function (container) {
this.on('panmove', this._onDrag.bind(this));
var me = this;
this._origRedraw = this._redraw.bind(this);
this._redraw = util.throttle(this._origRedraw);
this.on('_change', function (properties) {
if (me.itemSet && me.itemSet.initialItemSetDrawn && properties && properties.queue == true) {
me._redraw()

+ 6
- 0
lib/timeline/Graph2d.js View File

@ -34,6 +34,12 @@ function Graph2d (container, items, groups, options) {
groups = forthArgument;
}
// TODO: REMOVE THIS in the next MAJOR release
// see https://github.com/almende/vis/issues/2511
if (options && options.throttleRedraw) {
console.warn("Graph2d option \"throttleRedraw\" is DEPRICATED and no longer supported. It will be removed in the next MAJOR release.");
}
var me = this;
this.defaultOptions = {
start: null,

+ 69
- 15
lib/timeline/Stack.js View File

@ -37,16 +37,15 @@ exports.orderByEnd = function(items) {
* items having a top===null will be re-stacked
*/
exports.stack = function(items, margin, force) {
var i, iMax;
if (force) {
// reset top position of all items
for (i = 0, iMax = items.length; i < iMax; i++) {
for (var i = 0; i < items.length; i++) {
items[i].top = null;
}
}
// calculate new, non-overlapping positions
for (i = 0, iMax = items.length; i < iMax; i++) {
for (var i = 0; i < items.length; i++) {
var item = items[i];
if (item.stack && item.top === null) {
// initialize top position
@ -80,29 +79,70 @@ exports.stack = function(items, margin, force) {
* All visible items
* @param {{item: {horizontal: number, vertical: number}, axis: number}} margin
* Margins between items and between items and the axis.
* @param {subgroups[]} subgroups
* All subgroups
*/
exports.nostack = function(items, margin, subgroups) {
var i, iMax, newTop;
// reset top position of all items
for (i = 0, iMax = items.length; i < iMax; i++) {
if (items[i].data.subgroup !== undefined) {
newTop = margin.axis;
exports.nostack = function(items, margin, subgroups, stackSubgroups) {
for (var i = 0; i < items.length; i++) {
if (items[i].data.subgroup == undefined) {
items[i].top = margin.item.vertical;
} else if (items[i].data.subgroup !== undefined && stackSubgroups) {
var newTop = 0;
for (var subgroup in subgroups) {
if (subgroups.hasOwnProperty(subgroup)) {
if (subgroups[subgroup].visible == true && subgroups[subgroup].index < subgroups[items[i].data.subgroup].index) {
newTop += subgroups[subgroup].height + margin.item.vertical;
newTop += subgroups[subgroup].height;
subgroups[items[i].data.subgroup].top = newTop;
}
}
}
items[i].top = newTop;
}
else {
items[i].top = margin.axis;
items[i].top = newTop + 0.5 * margin.item.vertical;
}
}
if (!stackSubgroups) {
exports.stackSubgroups(items, margin, subgroups)
}
};
/**
* Adjust vertical positions of the subgroups such that they don't overlap each
* other.
* @param {subgroups[]} subgroups
* All subgroups
* @param {{item: {horizontal: number, vertical: number}, axis: number}} margin
* Margins between items and between items and the axis.
*/
exports.stackSubgroups = function(items, margin, subgroups) {
for (var subgroup in subgroups) {
if (subgroups.hasOwnProperty(subgroup)) {
subgroups[subgroup].top = 0;
do {
// TODO: optimize checking for overlap. when there is a gap without items,
// you only need to check for items from the next item on, not from zero
var collidingItem = null;
for (var otherSubgroup in subgroups) {
if (subgroups[otherSubgroup].top !== null && otherSubgroup !== subgroup && subgroups[subgroup].index > subgroups[otherSubgroup].index && exports.collisionByTimes(subgroups[subgroup], subgroups[otherSubgroup])) {
collidingItem = subgroups[otherSubgroup];
break;
}
}
if (collidingItem != null) {
// There is a collision. Reposition the subgroups above the colliding element
subgroups[subgroup].top = collidingItem.top + collidingItem.height;
}
} while (collidingItem);
}
}
for (var i = 0; i < items.length; i++) {
if (items[i].data.subgroup !== undefined) {
items[i].top = subgroups[items[i].data.subgroup].top + 0.5 * margin.item.vertical;
}
}
}
/**
* Test if the two provided items collide
* The items must have parameters left, width, top, and height.
@ -127,3 +167,17 @@ exports.collision = function(a, b, margin, rtl) {
(a.top + a.height + margin.vertical - EPSILON) > b.top);
}
};
/**
* Test if the two provided objects collide
* The objects must have parameters start, end, top, and height.
* @param {Object} a The first Object
* @param {Object} b The second Object
* @return {boolean} true if a and b collide, else false
*/
exports.collisionByTimes = function(a, b) {
return (
(a.start < b.start && a.end > b.start && a.top < (b.top + b.height) && (a.top + a.height) > b.top ) ||
(b.start < a.start && b.end > a.start && b.top < (a.top + a.height) && (b.top + b.height) > a.top )
)
}

+ 12
- 4
lib/timeline/Timeline.js View File

@ -41,6 +41,12 @@ function Timeline (container, items, groups, options) {
groups = forthArgument;
}
// TODO: REMOVE THIS in the next MAJOR release
// see https://github.com/almende/vis/issues/2511
if (options && options.throttleRedraw) {
console.warn("Timeline option \"throttleRedraw\" is DEPRICATED and no longer supported. It will be removed in the next MAJOR release.");
}
var me = this;
this.defaultOptions = {
start: null,
@ -69,9 +75,9 @@ function Timeline (container, items, groups, options) {
this.options.rtl = (directionFromDom && (directionFromDom.toLowerCase() == "rtl"));
} else {
this.options.rtl = options.rtl;
}
}
this.options.rollingMode = options && options.rollingMode;
this.options.rollingMode = options && options.rollingMode;
// all components listed here will be repainted automatically
this.components = [];
@ -434,8 +440,10 @@ Timeline.prototype.getItemRange = function () {
// calculate the date of the left side and right side of the items given
util.forEach(this.itemSet.items, function (item) {
item.show();
item.repositionX();
if (item.groupShowing) {
item.show();
item.repositionX();
}
var start = getStart(item);
var end = getEnd(item);

+ 92
- 10
lib/timeline/component/Group.js View File

@ -15,6 +15,17 @@ function Group (groupId, data, itemSet) {
this.subgroupOrderer = data && data.subgroupOrder;
this.itemSet = itemSet;
this.isVisible = null;
if (data && data.nestedGroups) {
this.nestedGroups = data.nestedGroups;
if (data.showNested == false) {
this.showNested = false;
} else {
this.showNested = true;
}
}
this.nestedInGroup = null;
this.dom = {};
this.props = {
@ -50,9 +61,9 @@ function Group (groupId, data, itemSet) {
Group.prototype._create = function() {
var label = document.createElement('div');
if (this.itemSet.options.groupEditable.order) {
label.className = 'vis-label draggable';
label.className = 'vis-label draggable';
} else {
label.className = 'vis-label';
label.className = 'vis-label';
}
this.dom.label = label;
@ -77,7 +88,8 @@ Group.prototype._create = function() {
// display:none is changed to visible.
this.dom.marker = document.createElement('div');
this.dom.marker.style.visibility = 'hidden';
this.dom.marker.innerHTML = '?';
this.dom.marker.style.position = 'absolute';
this.dom.marker.innerHTML = '';
this.dom.background.appendChild(this.dom.marker);
};
@ -113,7 +125,6 @@ Group.prototype.setData = function(data) {
// update title
this.dom.label.title = data && data.title || '';
if (!this.dom.inner.firstChild) {
util.addClassName(this.dom.inner, 'vis-hidden');
}
@ -121,6 +132,33 @@ Group.prototype.setData = function(data) {
util.removeClassName(this.dom.inner, 'vis-hidden');
}
if (data && data.nestedGroups) {
if (data.showNested == false) {
this.showNested = false;
} else {
this.showNested = true;
}
util.addClassName(this.dom.label, 'vis-nesting-group');
if (this.showNested) {
util.removeClassName(this.dom.label, 'collapsed');
util.addClassName(this.dom.label, 'expanded');
} else {
util.removeClassName(this.dom.label, 'expanded');
var collapsedDirClassName = this.itemSet.options.rtl ? 'collapsed-rtl' : 'collapsed'
util.addClassName(this.dom.label, collapsedDirClassName);
}
}
if (data && data.nestedInGroup) {
util.addClassName(this.dom.label, 'vis-nested-group');
if (this.itemSet.options && this.itemSet.options.rtl) {
this.dom.inner.style.paddingRight = '30px';
} else {
this.dom.inner.style.paddingLeft = '30px';
}
}
// update className
var className = data && data.className || null;
if (className != this.className) {
@ -172,7 +210,6 @@ Group.prototype.redraw = function(range, margin, restack) {
var markerHeight = this.dom.marker.clientHeight;
if (markerHeight != this.lastMarkerHeight) {
this.lastMarkerHeight = markerHeight;
util.forEach(this.items, function (item) {
item.dirty = true;
if (item.displayed) item.redraw();
@ -182,7 +219,7 @@ Group.prototype.redraw = function(range, margin, restack) {
}
// recalculate the height of the subgroups
this._calculateSubGroupHeights();
this._calculateSubGroupHeights(margin);
// calculate actual size and position
var foreground = this.dom.foreground;
@ -222,14 +259,17 @@ Group.prototype.redraw = function(range, margin, restack) {
// no custom order function, lazy stacking
this.visibleItems = this._updateItemsInRange(this.orderedItems, this.visibleItems, range);
if (this.itemSet.options.stack) { // TODO: ugly way to access options...
stack.stack(this.visibleItems, margin, restack);
}
else { // no stacking
stack.nostack(this.visibleItems, margin, this.subgroups);
stack.nostack(this.visibleItems, margin, this.subgroups, this.itemSet.options.stackSubgroups);
}
}
this._updateSubgroupsSizes();
// recalculate the height of the group
var height = this._calculateHeight(margin);
@ -268,7 +308,7 @@ Group.prototype.redraw = function(range, margin, restack) {
* recalculate the height of the subgroups
* @private
*/
Group.prototype._calculateSubGroupHeights = function () {
Group.prototype._calculateSubGroupHeights = function (margin) {
if (Object.keys(this.subgroups).length > 0) {
var me = this;
@ -276,7 +316,7 @@ Group.prototype._calculateSubGroupHeights = function () {
util.forEach(this.visibleItems, function (item) {
if (item.data.subgroup !== undefined) {
me.subgroups[item.data.subgroup].height = Math.max(me.subgroups[item.data.subgroup].height, item.height);
me.subgroups[item.data.subgroup].height = Math.max(me.subgroups[item.data.subgroup].height, item.height + margin.item.vertical);
me.subgroups[item.data.subgroup].visible = true;
}
});
@ -386,9 +426,26 @@ Group.prototype.add = function(item) {
// add to
if (item.data.subgroup !== undefined) {
if (this.subgroups[item.data.subgroup] === undefined) {
this.subgroups[item.data.subgroup] = {height:0, visible: false, index:this.subgroupIndex, items: []};
this.subgroups[item.data.subgroup] = {
height:0,
top: 0,
start: item.data.start,
end: item.data.end,
visible: false,
index:this.subgroupIndex,
items: []
};
this.subgroupIndex++;
}
if (new Date(item.data.start) < new Date(this.subgroups[item.data.subgroup].start)) {
this.subgroups[item.data.subgroup].start = item.data.start;
}
if (new Date(item.data.end) > new Date(this.subgroups[item.data.subgroup].end)) {
this.subgroups[item.data.subgroup].end = item.data.end;
}
this.subgroups[item.data.subgroup].items.push(item);
}
this.orderSubgroups();
@ -399,6 +456,29 @@ Group.prototype.add = function(item) {
}
};
Group.prototype._updateSubgroupsSizes = function () {
var me = this;
if (me.subgroups) {
for (var subgroup in me.subgroups) {
var newStart = me.subgroups[subgroup].items[0].data.start;
var newEnd = me.subgroups[subgroup].items[0].data.end;
me.subgroups[subgroup].items.forEach(function(item) {
if (new Date(item.data.start) < new Date(newStart)) {
newStart = item.data.start;
}
if (new Date(item.data.end) > new Date(newEnd)) {
newEnd = item.data.end;
}
})
me.subgroups[subgroup].start = newStart;
me.subgroups[subgroup].end = newEnd;
}
}
}
Group.prototype.orderSubgroups = function() {
if (this.subgroupOrderer !== undefined) {
var sortArray = [];
@ -453,6 +533,8 @@ Group.prototype.remove = function(item) {
if (!subgroup.items.length){
delete this.subgroups[item.data.subgroup];
this.subgroupIndex--;
} else {
this._updateSubgroupsSizes();
}
this.orderSubgroups();
}

+ 131
- 18
lib/timeline/component/ItemSet.js View File

@ -10,6 +10,7 @@ var BoxItem = require('./item/BoxItem');
var PointItem = require('./item/PointItem');
var RangeItem = require('./item/RangeItem');
var BackgroundItem = require('./item/BackgroundItem');
import Popup from '../../shared/Popup';
var UNGROUPED = '__ungrouped__'; // reserved group id for ungrouped items
@ -33,6 +34,7 @@ function ItemSet(body, options) {
},
align: 'auto', // alignment of box items
stack: true,
stackSubgroups: true,
groupOrderSwap: function(fromGroup, toGroup, groups) {
var targetOrder = toGroup.order;
toGroup.order = fromGroup.order;
@ -240,6 +242,7 @@ ItemSet.prototype._create = function(){
this.groupHammer = new Hammer(this.body.dom.leftContainer);
}
this.groupHammer.on('tap', this._onGroupClick.bind(this));
this.groupHammer.on('panstart', this._onGroupDragStart.bind(this));
this.groupHammer.on('panmove', this._onGroupDrag.bind(this));
this.groupHammer.on('panend', this._onGroupDragEnd.bind(this));
@ -320,7 +323,7 @@ ItemSet.prototype.setOptions = function(options) {
if (options) {
// copy all options that we know
var fields = [
'type', 'rtl', 'align', 'order', 'stack', 'selectable', 'multiselect', 'itemsAlwaysDraggable',
'type', 'rtl', 'align', 'order', 'stack', 'stackSubgroups', 'selectable', 'multiselect', 'itemsAlwaysDraggable',
'multiselectPerGroup', 'groupOrder', 'dataAttributes', 'template', 'groupTemplate', 'visibleFrameTemplate',
'hide', 'snap', 'groupOrderSwap', 'tooltipOnItemUpdateTime'
];
@ -812,6 +815,26 @@ ItemSet.prototype.setGroups = function(groups) {
}
if (this.groupsData) {
// go over all groups nesting
var groupsData = this.groupsData;
if (this.groupsData instanceof DataView) {
groupsData = this.groupsData.getDataSet()
}
groupsData.get().forEach(function(group){
if (group.nestedGroups) {
group.nestedGroups.forEach(function(nestedGroupId) {
var updatedNestedGroup = groupsData.get(nestedGroupId);
updatedNestedGroup.nestedInGroup = group.id;
if (group.showNested == false) {
updatedNestedGroup.visible = false;
}
groupsData.update(updatedNestedGroup);
})
}
})
// subscribe to new dataset
var id = this.id;
util.forEach(this.groupListeners, function (callback, event) {
@ -904,7 +927,7 @@ ItemSet.prototype._onUpdate = function(ids) {
var selected;
if (item) {
// update item
// update item
if (!constructor || !(item instanceof constructor)) {
// item type has changed, delete the item and recreate it
selected = item.selected; // preserve selection of this item
@ -921,6 +944,7 @@ ItemSet.prototype._onUpdate = function(ids) {
if (constructor) {
item = new constructor(itemData, me.conversion, me.options);
item.id = id; // TODO: not so nice setting id afterwards
me._addItem(item);
if (selected) {
this.selection.push(id);
@ -1076,6 +1100,8 @@ ItemSet.prototype._orderGroups = function () {
order: this.options.groupOrder
});
groupIds = this._orderNestedGroups(groupIds);
var changed = !util.equalArray(groupIds, this.groupIds);
if (changed) {
// hide all groups, removes them from the DOM
@ -1099,6 +1125,33 @@ ItemSet.prototype._orderGroups = function () {
}
};
/**
* Reorder the nested groups
* @return {boolean} changed
* @private
*/
ItemSet.prototype._orderNestedGroups = function(groupIds) {
var newGroupIdsOrder = [];
groupIds.forEach(function(groupId){
var groupData = this.groupsData.get(groupId);
if (!groupData.nestedInGroup) {
newGroupIdsOrder.push(groupId)
}
if (groupData.nestedGroups) {
var nestedGroups = this.groupsData.get({
filter: function(nestedGroup) {
return nestedGroup.nestedInGroup == groupId;
}
});
var nestedGroupIds = nestedGroups.map(function(nestedGroup) { return nestedGroup.id })
newGroupIdsOrder = newGroupIdsOrder.concat(nestedGroupIds);
}
}, this)
return newGroupIdsOrder;
}
/**
* Add a new item
* @param {Item} item
@ -1110,6 +1163,13 @@ ItemSet.prototype._addItem = function(item) {
// add to group
var groupId = this._getGroupId(item.data);
var group = this.groups[groupId];
if (!group) {
item.groupShowing = false;
} else if (group && group.data && group.data.showNested) {
item.groupShowing = true;
}
if (group) group.add(item);
};
@ -1126,13 +1186,17 @@ ItemSet.prototype._updateItem = function(item, itemData) {
// update the items data (will redraw the item when displayed)
item.setData(itemData);
var groupId = this._getGroupId(item.data);
var group = this.groups[groupId];
if (!group) {
item.groupShowing = false;
} else if (group && group.data && group.data.showNested) {
item.groupShowing = true;
}
// update group
if (oldGroupId != item.data.group || oldSubGroupId != item.data.subgroup) {
var oldGroup = this.groups[oldGroupId];
if (oldGroup) oldGroup.remove(item);
var groupId = this._getGroupId(item.data);
var group = this.groups[groupId];
if (group) group.add(item);
}
};
@ -1561,6 +1625,35 @@ ItemSet.prototype._onDragEnd = function (event) {
}
};
ItemSet.prototype._onGroupClick = function (event) {
var group = this.groupFromTarget(event);
if (!group.nestedGroups) return;
var groupsData = this.groupsData;
if (this.groupsData instanceof DataView) {
groupsData = this.groupsData.getDataSet()
}
group.showNested = !group.showNested;
var nestedGroups = groupsData.get(group.nestedGroups).map(function(nestedGroup) {
if (nestedGroup.visible == undefined) { nestedGroup.visible = true; }
nestedGroup.visible = !!group.showNested;
return nestedGroup;
});
groupsData.update(nestedGroups);
if (group.showNested) {
util.removeClassName(group.dom.label, 'collapsed');
util.addClassName(group.dom.label, 'expanded');
} else {
util.removeClassName(group.dom.label, 'expanded');
var collapsedDirClassName = this.options.rtl ? 'collapsed-rtl' : 'collapsed'
util.addClassName(group.dom.label, collapsedDirClassName);
}
}
ItemSet.prototype._onGroupDragStart = function (event) {
if (this.options.groupEditable.order) {
this.groupTouchParams.group = this.groupFromTarget(event);
@ -1612,15 +1705,16 @@ ItemSet.prototype._onGroupDrag = function (event) {
// switch groups
if (draggedGroup && targetGroup) {
this.options.groupOrderSwap(draggedGroup, targetGroup, this.groupsData);
groupsData.update(draggedGroup);
groupsData.update(targetGroup);
this.options.groupOrderSwap(draggedGroup, targetGroup, groupsData);
groupsData.update(draggedGroup);
groupsData.update(targetGroup);
}
// fetch current order of groups
var newOrder = groupsData.getIds({
order: this.options.groupOrder
});
order: this.options.groupOrder
});
// in case of changes since _onGroupDragStart
if (!util.equalArray(newOrder, this.groupTouchParams.originalOrder)) {
@ -1715,14 +1809,14 @@ ItemSet.prototype._onGroupDragEnd = function (event) {
break;
}
// found a group that has the wrong position -> switch with the
// group at the position where other one should be, fix index arrays and continue
var slippedPosition = newOrder.indexOf(origOrder[curPos])
var switchGroup = dataset.get(newOrder[curPos]);
var shouldBeGroup = dataset.get(origOrder[curPos]);
me.options.groupOrderSwap(switchGroup, shouldBeGroup, dataset);
groupsData.update(switchGroup);
groupsData.update(shouldBeGroup);
// found a group that has the wrong position -> switch with the
// group at the position where other one should be, fix index arrays and continue
var slippedPosition = newOrder.indexOf(origOrder[curPos])
var switchGroup = dataset.get(newOrder[curPos]);
var shouldBeGroup = dataset.get(origOrder[curPos]);
me.options.groupOrderSwap(switchGroup, shouldBeGroup, dataset);
groupsData.update(switchGroup);
groupsData.update(shouldBeGroup);
var switchGroupId = newOrder[curPos];
newOrder[curPos] = origOrder[curPos];
@ -1780,6 +1874,20 @@ ItemSet.prototype._onSelectItem = function (event) {
ItemSet.prototype._onMouseOver = function (event) {
var item = this.itemFromTarget(event);
if (!item) return;
if (item.getTitle()) {
if (item.popup == null) {
item.setPopup(new Popup(this.body.dom.root));
}
var container = this.body.dom.centerContainer;
item.popup.setPosition(
event.clientX - util.getAbsoluteLeft(container),
event.clientY - util.getAbsoluteTop(container)
);
item.popup.show();
}
this.body.emitter.emit('itemover', {
item: item.id,
event: util.elementsCensor(event)
@ -1788,6 +1896,11 @@ ItemSet.prototype._onMouseOver = function (event) {
ItemSet.prototype._onMouseOut = function (event) {
var item = this.itemFromTarget(event);
if (!item) return;
if (item.popup != null) {
item.popup.hide();
}
this.body.emitter.emit('itemout', {
item: item.id,
event: util.elementsCensor(event)

+ 20
- 0
lib/timeline/component/css/itemset.css View File

@ -33,6 +33,26 @@
border-bottom: none;
}
.vis-nesting-group {
cursor: pointer;
}
.vis-nested-group {
background: #f5f5f5;
}
.vis-label.vis-nesting-group.expanded:before {
content: "\25BC";
}
.vis-label.vis-nesting-group.collapsed-rtl:before {
content: "\25C0";
}
.vis-label.vis-nesting-group.collapsed:before {
content: "\25B6";
}
.vis-overlay {
position: absolute;
top: 0;

+ 12
- 43
lib/timeline/component/item/BackgroundItem.js View File

@ -100,7 +100,7 @@ BackgroundItem.prototype.redraw = function() {
// - the item is selected/deselected
if (this.dirty) {
this._updateContents(this.dom.content);
this._updateTitle(this.dom.content);
this._updateTitle();
this._updateDataAttributes(this.dom.content);
this._updateStyle(this.dom.box);
@ -143,9 +143,6 @@ BackgroundItem.prototype.repositionX = RangeItem.prototype.repositionX;
* @Override
*/
BackgroundItem.prototype.repositionY = function(margin) {
var onTop = this.options.orientation.item === 'top';
this.dom.content.style.top = onTop ? '' : '0';
this.dom.content.style.bottom = onTop ? '0' : '';
var height;
// special positioning for subgroups
@ -155,44 +152,16 @@ BackgroundItem.prototype.repositionY = function(margin) {
var itemSubgroup = this.data.subgroup;
var subgroups = this.parent.subgroups;
var subgroupIndex = subgroups[itemSubgroup].index;
// if the orientation is top, we need to take the difference in height into account.
if (onTop == true) {
// the first subgroup will have to account for the distance from the top to the first item.
height = this.parent.subgroups[itemSubgroup].height + margin.item.vertical;
height += subgroupIndex == 0 ? margin.axis - 0.5*margin.item.vertical : 0;
var newTop = this.parent.top;
for (var subgroup in subgroups) {
if (subgroups.hasOwnProperty(subgroup)) {
if (subgroups[subgroup].visible == true && subgroups[subgroup].index < subgroupIndex) {
newTop += subgroups[subgroup].height + margin.item.vertical;
}
}
}
// the others will have to be offset downwards with this same distance.
newTop += subgroupIndex != 0 ? margin.axis - 0.5 * margin.item.vertical : 0;
this.dom.box.style.top = newTop + 'px';
this.dom.box.style.bottom = '';
}
// and when the orientation is bottom:
else {
var newTop = this.parent.top;
var totalHeight = 0;
for (var subgroup in subgroups) {
if (subgroups.hasOwnProperty(subgroup)) {
if (subgroups[subgroup].visible == true) {
var newHeight = subgroups[subgroup].height + margin.item.vertical;
totalHeight += newHeight;
if (subgroups[subgroup].index > subgroupIndex) {
newTop += newHeight;
}
}
}
}
height = this.parent.subgroups[itemSubgroup].height + margin.item.vertical;
this.dom.box.style.top = (this.parent.height - totalHeight + newTop) + 'px';
this.dom.box.style.bottom = '';
this.dom.box.style.height = this.parent.subgroups[itemSubgroup].height + 'px';
var orientation = this.options.orientation.item;
if (orientation == 'top') {
this.dom.box.style.top = this.parent.top + this.parent.subgroups[itemSubgroup].top + 'px';
} else {
this.dom.box.style.top = (this.parent.top + this.parent.height - this.parent.subgroups[itemSubgroup].top - this.parent.subgroups[itemSubgroup].height) + 'px';
}
this.dom.box.style.bottom = '';
}
// and in the case of no subgroups:
else {
@ -202,8 +171,8 @@ BackgroundItem.prototype.repositionY = function(margin) {
height = Math.max(this.parent.height,
this.parent.itemSet.body.domProps.center.height,
this.parent.itemSet.body.domProps.centerContainer.height);
this.dom.box.style.top = onTop ? '0' : '';
this.dom.box.style.bottom = onTop ? '' : '0';
this.dom.box.style.top = orientation == 'top' ? '0' : '';
this.dom.box.style.bottom = orientation == 'top' ? '' : '0';
}
else {
height = this.parent.height;

+ 1
- 1
lib/timeline/component/item/BoxItem.js View File

@ -119,7 +119,7 @@ BoxItem.prototype.redraw = function() {
// - the item is selected/deselected
if (this.dirty) {
this._updateContents(this.dom.content);
this._updateTitle(this.dom.box);
this._updateTitle();
this._updateDataAttributes(this.dom.box);
this._updateStyle(this.dom.box);

+ 23
- 6
lib/timeline/component/item/Item.js View File

@ -21,7 +21,9 @@ function Item (data, conversion, options) {
this.options = options || {};
this.selected = false;
this.displayed = false;
this.groupShowing = true;
this.dirty = true;
this.popup = null;
this.top = null;
this.right = null;
@ -397,15 +399,13 @@ Item.prototype._updateContents = function (element) {
/**
* Set HTML contents for the item
* @param {Element} element HTML element to fill with the contents
* @private
*/
Item.prototype._updateTitle = function (element) {
Item.prototype._updateTitle = function () {
if (this.data.title != null) {
element.title = this.data.title || '';
}
else {
element.removeAttribute('vis-title');
if (this.popup != null) {
this.popup.setText(this.data.title || '');
}
}
};
@ -490,4 +490,21 @@ Item.prototype.getWidthRight = function () {
return 0;
};
/**
* Return the title of the item
* @return {string | undefined}
*/
Item.prototype.getTitle = function () {
return this.data.title;
};
/**
* Set the popup object, and update the title
* @param {Popup} popup
*/
Item.prototype.setPopup = function (popup) {
this.popup = popup;
this._updateTitle();
};
module.exports = Item;

+ 1
- 1
lib/timeline/component/item/PointItem.js View File

@ -97,7 +97,7 @@ PointItem.prototype.redraw = function() {
// - the item is selected/deselected
if (this.dirty) {
this._updateContents(this.dom.content);
this._updateTitle(this.dom.point);
this._updateTitle();
this._updateDataAttributes(this.dom.point);
this._updateStyle(this.dom.point);

+ 1
- 1
lib/timeline/component/item/RangeItem.js View File

@ -100,7 +100,7 @@ RangeItem.prototype.redraw = function() {
// - the item is selected/deselected
if (this.dirty) {
this._updateContents(this.dom.content);
this._updateTitle(this.dom.box);
this._updateTitle();
this._updateDataAttributes(this.dom.box);
this._updateStyle(this.dom.box);

+ 1
- 0
lib/timeline/optionsGraph2d.js View File

@ -100,6 +100,7 @@ let allOptions = {
},
autoResize: {bool},
throttleRedraw: {number}, // TODO: DEPRICATED see https://github.com/almende/vis/issues/2511
clickToUse: {bool},
end: {number, date, string, moment},
format: {

+ 33
- 30
lib/timeline/optionsTimeline.js View File

@ -17,28 +17,29 @@ let any = 'any';
let allOptions = {
configure: {
enabled: {bool},
filter: {bool,'function': 'function'},
enabled: { 'boolean': bool},
filter: { 'boolean': bool,'function': 'function'},
container: {dom},
__type__: {object,bool,'function': 'function'}
__type__: {object, 'boolean': bool,'function': 'function'}
},
//globals :
align: {string},
rtl: {bool, 'undefined': 'undefined'},
rollingMode: {bool, 'undefined': 'undefined'},
verticalScroll: {bool, 'undefined': 'undefined'},
horizontalScroll: {bool, 'undefined': 'undefined'},
autoResize: {bool},
clickToUse: {bool},
rtl: { 'boolean': bool, 'undefined': 'undefined'},
rollingMode: { 'boolean': bool, 'undefined': 'undefined'},
verticalScroll: { 'boolean': bool, 'undefined': 'undefined'},
horizontalScroll: { 'boolean': bool, 'undefined': 'undefined'},
autoResize: { 'boolean': bool},
throttleRedraw: {number}, // TODO: DEPRICATED see https://github.com/almende/vis/issues/2511
clickToUse: { 'boolean': bool},
dataAttributes: {string, array},
editable: {
add: {bool, 'undefined': 'undefined'},
remove: {bool, 'undefined': 'undefined'},
updateGroup: {bool, 'undefined': 'undefined'},
updateTime: {bool, 'undefined': 'undefined'},
overrideItems: {bool, 'undefined': 'undefined'},
__type__: {bool, object}
add: { 'boolean': bool, 'undefined': 'undefined'},
remove: { 'boolean': bool, 'undefined': 'undefined'},
updateGroup: { 'boolean': bool, 'undefined': 'undefined'},
updateTime: { 'boolean': bool, 'undefined': 'undefined'},
overrideItems: { 'boolean': bool, 'undefined': 'undefined'},
__type__: { 'boolean': bool, object}
},
end: {number, date, string, moment},
format: {
@ -69,10 +70,10 @@ let allOptions = {
moment: {'function': 'function'},
groupOrder: {string, 'function': 'function'},
groupEditable: {
add: {bool, 'undefined': 'undefined'},
remove: {bool, 'undefined': 'undefined'},
order: {bool, 'undefined': 'undefined'},
__type__: {bool, object}
add: { 'boolean': bool, 'undefined': 'undefined'},
remove: { 'boolean': bool, 'undefined': 'undefined'},
order: { 'boolean': bool, 'undefined': 'undefined'},
__type__: { 'boolean': bool, object}
},
groupOrderSwap: {'function': 'function'},
height: {string, number},
@ -82,7 +83,7 @@ let allOptions = {
repeat: {string},
__type__: {object, array}
},
itemsAlwaysDraggable: {bool},
itemsAlwaysDraggable: { 'boolean': bool},
locale:{string},
locales:{
__any__: {any},
@ -102,9 +103,9 @@ let allOptions = {
maxMinorChars: {number},
min: {date, number, string, moment},
minHeight: {number, string},
moveable: {bool},
multiselect: {bool},
multiselectPerGroup: {bool},
moveable: { 'boolean': bool},
multiselect: { 'boolean': bool},
multiselectPerGroup: { 'boolean': bool},
onAdd: {'function': 'function'},
onUpdate: {'function': 'function'},
onMove: {'function': 'function'},
@ -119,11 +120,12 @@ let allOptions = {
item: {string,'undefined': 'undefined'},
__type__: {string, object}
},
selectable: {bool},
showCurrentTime: {bool},
showMajorLabels: {bool},
showMinorLabels: {bool},
stack: {bool},
selectable: { 'boolean': bool},
showCurrentTime: { 'boolean': bool},
showMajorLabels: { 'boolean': bool},
showMinorLabels: { 'boolean': bool},
stack: { 'boolean': bool},
stackSubgroups: { 'boolean': bool},
snap: {'function': 'function', 'null': 'null'},
start: {date, number, string, moment},
template: {'function': 'function'},
@ -131,7 +133,7 @@ let allOptions = {
visibleFrameTemplate: {string, 'function': 'function'},
tooltipOnItemUpdateTime: {
template: {'function': 'function'},
__type__: {bool, object}
__type__: { 'boolean': bool, object}
},
timeAxis: {
scale: {string,'undefined': 'undefined'},
@ -140,7 +142,7 @@ let allOptions = {
},
type: {string},
width: {string, number},
zoomable: {bool},
zoomable: { 'boolean': bool},
zoomKey: {string: ['ctrlKey', 'altKey', 'metaKey', '']},
zoomMax: {number},
zoomMin: {number},
@ -220,6 +222,7 @@ let configureOptions = {
showMajorLabels: true,
showMinorLabels: true,
stack: true,
stackSubgroups: true,
//snap: {'function': 'function', nada},
start: '',
//template: {'function': 'function'},

+ 2
- 2
lib/util.js View File

@ -409,7 +409,7 @@ exports.convert = function (object, type) {
case 'number':
case 'Number':
if (exports.isString(object) && !isNaN(Date.parse(object))) {
if (!isNaN(Date.parse(object))) {
return moment(object).valueOf();
} else {
return Number(object.valueOf());
@ -1514,7 +1514,7 @@ exports.topMost = function (pile, accessors) {
continue;
}
}
if (candidate) {
if (typeof candidate != 'undefined') {
break;
}
}

+ 0
- 1
misc/we_need_help.md View File

@ -13,4 +13,3 @@ If you have shown some commitment to the project you can ask [@ludost](//github.
* [@ludost](//github.com/ludost) (almende maintainer)
* [@mojoaxel](//github.com/mojoaxel)
* [@yotamberk](//github.com/yotamberk)
* [@Tooa](//github.com/Tooa)

+ 6
- 6
package.json View File

@ -37,12 +37,12 @@
},
"devDependencies": {
"async": "^2.0.0-rc.2",
"babel-core": "^6.6.5",
"babel-loader": "^6.2.4",
"babel-preset-es2015": "^6.6.0",
"babel-plugin-transform-es3-property-literals": "^6.8.0",
"babel-core": "^6.21.0",
"babel-loader": "^6.2.10",
"babel-plugin-transform-es3-member-expression-literals": "^6.8.0",
"babelify": "^7.2.0",
"babel-plugin-transform-es3-property-literals": "^6.8.0",
"babel-preset-es2015": "^6.18.0",
"babelify": "^7.3.0",
"clean-css": "^3.4.10",
"gulp": "^3.9.1",
"gulp-clean-css": "^2.0.11",
@ -52,7 +52,7 @@
"mocha": "^3.1.2",
"rimraf": "^2.5.2",
"uglify-js": "^2.6.2",
"uuid": "^2.0.1",
"uuid": "^3.0.1",
"webpack": "^1.12.14",
"yargs": "^6.3.0"
}

Loading…
Cancel
Save