Browse Source

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

Conflicts:
	vis.min.js
css_transitions
Eric Gillingham 11 years ago
parent
commit
ef111ddd5c
25 changed files with 1441 additions and 307 deletions
  1. +1
    -1
      .npmignore
  2. +8
    -6
      HISTORY.md
  3. +4
    -4
      README.md
  4. +1
    -1
      bower.json
  5. +682
    -2
      docs/dataset.html
  6. +222
    -0
      docs/dataview.html
  7. +2
    -17
      docs/graph.html
  8. +173
    -5
      docs/index.html
  9. +2
    -17
      docs/timeline.html
  10. +1
    -1
      examples/timeline/02_dataset.html
  11. +1
    -1
      examples/timeline/03_much_data.html
  12. +1
    -1
      package.json
  13. +109
    -84
      src/DataSet.js
  14. +1
    -1
      src/DataView.js
  15. +3
    -3
      src/graph/dotparser.js
  16. +2
    -2
      src/timeline/Range.js
  17. +1
    -1
      src/timeline/Timeline.js
  18. +1
    -1
      src/timeline/component/GroupSet.js
  19. +2
    -2
      src/timeline/component/TimeAxis.js
  20. +46
    -25
      src/util.js
  21. +1
    -1
      test/dataset.html
  22. +4
    -4
      test/dataset.js
  23. +1
    -1
      test/timeline.html
  24. +167
    -121
      vis.js
  25. +5
    -5
      vis.min.js

+ 1
- 1
.npmignore View File

@ -3,7 +3,7 @@ src
test test
tools tools
.idea .idea
component.json
bower.json
Jakefile.js Jakefile.js
.npmignore .npmignore
.gitignore .gitignore

+ 8
- 6
HISTORY.md View File

@ -2,17 +2,19 @@ vis.js history
http://visjs.org http://visjs.org
## version 0.1.0
## 2013-06-20, version 0.1.0
- Graph now uses an id based set of nodes and edges instead of a row based array
internally.
- Added support for DataSet to Graph. Graph now uses an id based set of nodes
and edges instead of a row based array internally. Methods getSelection and
setSelection of Graph now accept a list with ids instead of rows.
- Graph is now robust against edges pointing to non-existing nodes, which - Graph is now robust against edges pointing to non-existing nodes, which
can occur easily while dynamically adding/removing nodes and edges. can occur easily while dynamically adding/removing nodes and edges.
- Added support for DataSet to Graph.
- Methods getSelection and setSelection of Graph now accept a list with ids
instead of rows.
- Implemented basic support for groups in the Timeline. - Implemented basic support for groups in the Timeline.
- Added documentation on DataSet and DataView.
- Fixed selection of nodes in a Graph when the containing web page is scrolled. - Fixed selection of nodes in a Graph when the containing web page is scrolled.
- Improved date conversion.
- Renamed DataSet option `fieldTypes` to `convert`.
- Renamed function `vis.util.cast` to `vis.util.convert`.
## 2013-06-07, version 0.0.9 ## 2013-06-07, version 0.0.9

+ 4
- 4
README.md View File

@ -6,13 +6,13 @@ The library is designed to be easy to use, handle large amounts
of dynamic data, and enable manipulation of the data. of dynamic data, and enable manipulation of the data.
The library consists of the following components: 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.
Filter and order items and convert fields of items.
- Timeline. Display different types of data on a timeline. - Timeline. Display different types of data on a timeline.
The timeline and the items on the timeline can be interactively moved, The timeline and the items on the timeline can be interactively moved,
zoomed, and manipulated. zoomed, and manipulated.
- Graph. Display a graph of network with nodes and edges.
- DataSet and DataView. A flexible key/value based data set.
Add, update, and remove items. Subscribe on changes in the data set.
Filter, order, and cast items.
- Graph. Display an interactive graph or network with nodes and edges.
The vis.js library is developed by [Almende B.V](http://almende.com). The vis.js library is developed by [Almende B.V](http://almende.com).

component.json → bower.json View File

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

+ 682
- 2
docs/dataset.html View File

@ -11,12 +11,692 @@
<script type="text/javascript" src="lib/prettify/prettify.js"></script> <script type="text/javascript" src="lib/prettify/prettify.js"></script>
</head> </head>
<body>
<body onload="prettyPrint();">
<div id="container"> <div id="container">
<h1>DataSet documentation</h1> <h1>DataSet documentation</h1>
<p>coming soon...</p>
<h2 id="Contents">Contents</h2>
<ul>
<li><a href="#Overview">Overview</a></li>
<li><a href="#Example">Example</a></li>
<li><a href="#Construction">Construction</a></li>
<li><a href="#Data_Manipulation">Data Manipulation</a></li>
<li><a href="#Data_Filtering">Data Filtering</a></li>
<li><a href="#Data_Formatting">Data Formatting</a></li>
<li><a href="#Subscriptions">Subscriptions</a></li>
<li><a href="#Data_Policy">Data Policy</a></li>
</ul>
<h2 id="Overview">Overview</h2>
<p>
Vis.js comes with a flexible DataSet, which can be used to hold and
manipulate unstructured data and listen for changes in the data.
The DataSet is key/value based. Data items can be added, updated and
removed from the DatSet, and one can subscribe to changes in the DataSet.
The data in the DataSet can be filtered and ordered, and fields (like
dates) can be converted to a specific type. Data can be normalized when
appending it to the DataSet as well.
</p>
<h2 id="Example">Example</h2>
<p>
The following example shows how to use a DataSet.
</p>
<pre class="prettyprint lang-js">
// create a DataSet
var options = {};
var data = new vis.DataSet(options);
// add items
// note that the data items can contain different properties and data formats
data.add([
{id: 1, text: 'item 1', date: new Date(2013, 6, 20), group: 1, first: true},
{id: 2, text: 'item 2', date: '2013-06-23', group: 2},
{id: 3, text: 'item 3', date: '2013-06-25', group: 2},
{id: 4, text: 'item 4'}
]);
// subscribe to any change in the DataSet
data.subscribe('*', function (event, params, senderId) {
console.log('event', event, params);
});
// update an existing item
data.update({id: 2, group: 1});
// remove an item
data.remove(4);
// get all ids
var ids = data.getIds();
console.log('ids', ids);
// get a specific item
var item1 = data.get(1);
console.log('item1', item1);
// retrieve a filtered subset of the data
var items = data.get({
filter: function (item) {
return item.group == 1;
}
});
console.log('filtered items', items);
// retrieve formatted items
var items = data.get({
fields: ['id', 'date'],
convert: {
date: 'ISODate'
}
});
console.log('formatted items', items);
</pre>
<h2 id="Construction">Construction</h2>
<p>
A DataSet can be constructed as:
</p>
<pre class="prettyprint lang-js">
var data = new vis.DataSet(options)
</pre>
<p>
After construction, data can be added to the DataSet using the methods
<code>add</code> and <code>update</code>, as described in section
<a href="#Data_Manipulation">Data Manipulation</a>.
</p>
<p>
The parameter <code>options</code> is optional and is an object which can
contain the following properties:
</p>
<table>
<tr>
<th>Name</th>
<th>Type</th>
<th>Default value</th>
<th>Description</th>
</tr>
<tr>
<td>fieldId</td>
<td>String</td>
<td>"id"</td>
<td>
The name of the field containing the id of the items.
When data is fetched from a server which uses some specific
field to identify items, this field name can be specified
in the DataSet using the option <code>fieldId</code>.
For example <a href="http://couchdb.apache.org/"
target="_blank">CouchDB</a> uses the field
<code>"_id"</code> to identify documents.
</td>
</tr>
<tr>
<td>convert</td>
<td>Object.&lt;String,&nbsp;String&gt;</td>
<td>none</td>
<td>
An object containing field names as key, and data types as
value. By default, the type of the properties of items are left
unchanged. Item properties can be normalized by specifying a
field type. This is useful for example to automatically convert
stringified dates coming from a server into JavaScript Date
objects. The available data types are listed in section
<a href="#Data_Types">Data Types</a>.
</td>
</tr>
</table>
<h2 id="Data_Manipulation">Data Manipulation</h2>
<p>
The data in a DataSet can be manipulated using the methods
<a href="#Add"><code>add</code></a>,
<a href="#Update"><code>update</code></a>,
and <a href="#Remove"><code>remove</code></a>.
The DataSet can be emptied using the method
<a href="#Clear"><code>clear</code></a>.
</p>
<pre class="prettyprint lang-js">
// create a DataSet
var data = new vis.DataSet();
// add items
data.add([
{id: 1, text: 'item 1'},
{id: 2, text: 'item 2'},
{id: 3, text: 'item 3'}
]);
// update an item
data.update({id: 2, text: 'item 2 (updated)'});
// remove an item
data.remove(3);
</pre>
<h3 id="Add">Add</h3>
<p>
Add a data item or an array with items.
</p>
Syntax:
<pre class="prettyprint lang-js">var addedIds = DataSet.add(data [, senderId])</pre>
The argument <code>data</code> can contain:
<ul>
<li>
An <code>Object</code> containing a single item to be
added. The item must contain an id.
</li>
<li>
An <code>Array</code> or
<code>google.visualization.DataTable</code> containing
a list with items to be added. Each item must contain
an id.
</li>
</ul>
<p>
After the items are added to the DataSet, the DataSet will
trigger an event <code>add</code>. When a <code>senderId</code>
is provided, this id will be passed with the triggered
event to all subscribers.
</p>
<p>
The method will throw an Error when an item with the same id
as any of the added items already exists.
</p>
<h3 id="Update">Update</h3>
<p>
Update a data item or an array with items.
</p>
Syntax:
<pre class="prettyprint lang-js">var updatedIds = DataSet.update(data [, senderId])</pre>
The argument <code>data</code> can contain:
<ul>
<li>
An <code>Object</code> containing a single item to be
updated. The item must contain an id.
</li>
<li>
An <code>Array</code> or
<code>google.visualization.DataTable</code> containing
a list with items to be updated. Each item must contain
an id.
</li>
</ul>
<p>
The provided properties will be merged in the existing item.
When an item does not exist, it will be created.
</p>
<p>
After the items are updated, the DataSet will
trigger an event <code>add</code> for the added items, and
an event <code>update</code>. When a <code>senderId</code>
is provided, this id will be passed with the triggered
event to all subscribers.
</p>
<h3 id="Remove">Remove</h3>
<p>
Remove a data item or an array with items.
</p>
Syntax:
<pre class="prettyprint lang-js">var removedIds = DataSet.remove(id [, senderId])</pre>
<p>
The argument <code>id</code> can be:
</p>
<ul>
<li>
A <code>Number</code> or <code>String</code> containing the id
of a single item to be removed.
</li>
<li>
An <code>Object</code> containing the item to be deleted.
The item will be deleted by its id.
</li>
<li>
An Array containing ids or items to be removed.
</li>
</ul>
<p>
The method ignores removal of non-existing items, and returns an array
containing the ids of the items which are actually removed from the
DataSet.
</p>
<p>
After the items are removed, the DataSet will
trigger an event <code>remove</code> for the removed items.
When a <code>senderId</code> is provided, this id will be passed with
the triggered event to all subscribers.
</p>
<h3 id="Clear">Clear</h3>
<p>
Clear the complete DataSet.
</p>
Syntax:
<pre class="prettyprint lang-js">var removedIds = DataSet.clear([senderId])</pre>
<p>
After the items are removed, the DataSet will
trigger an event <code>remove</code> for all removed items.
When a <code>senderId</code> is provided, this id will be passed with
the triggered event to all subscribers.
</p>
<h2 id="Data_Filtering">Data Filtering</h2>
<p>
Data can be retrieved from the DataSet using the method <code>get</code>.
This method can return a single item or a list with items.
</p>
<p>A single item can be retrieved by its id:</p>
<pre class="prettyprint lang-js">
var item1 = dataset.get(1);
</pre>
<p>A selection of items can be retrieved by providing an array with ids:</p>
<pre class="prettyprint lang-js">
var items = dataset.get([1, 3, 4]); // retrieve items 1, 3, and 4
</pre>
<p>All items can be retrieved by simply calling <code>get</code> without
specifying an id:</p>
<pre class="prettyprint lang-js">
var items = dataset.get(); // retrieve all items
</pre>
<p>
Items can be filtered on specific properties by providing a filter
function. A filter function is executed for each of the items in the
DataSet, and is called with the item as parameter. The function must
return a boolean. All items for which the filter function returns
true will be emitted.
</p>
<pre class="prettyprint lang-js">
// retrieve all items having a property group with value 2
var group2 = dataset.get({
filter: function (item) {
return (item.group == 2);
}
});
// retrieve all items having a property balance with a value above zero
var positiveBalance = dataset.get({
filter: function (item) {
return (item.balance > 0);
}
});
</pre>
<h2 id="Data_Formatting">Data Formatting</h2>
<p>
The DataSet contains functionality to format data retrieved via the
method <code>get</code>. The method <code>get</code> has the following
syntax:
</p>
<pre class="prettyprint lang-js">
var item = DataSet.get(id, options); // retrieve a single item
var items = DataSet.get(ids, options); // retrieve a selection of items
var items = DataSet.get(options); // retrieve all items or a filtered set
</pre>
<p>
Where <code>options</code> is an Object which can have the following
properties:
</p>
<table>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
</tr>
<tr>
<td>fields</td>
<td>String[&nbsp;]</td>
<td>
An array with field names.
By default, all properties of the items are emitted.
When <code>fields</code> is defined, only the properties
whose name is specified in <code>fields</code> will be included
in the returned items.
</td>
</tr>
<tr>
<td>convert</td>
<td>Object.&lt;String,&nbsp;String&gt;</td>
<td>
An object containing field names as key, and data types as value.
By default, the type of the properties of an item are left
unchanged. When a field type is specified, this field in the
items will be converted to the specified type. This can be used
for example to convert ISO strings containing a date to a
JavaScript Date object, or convert strings to numbers or vice
versa. The available data types are listed in section
<a href="#Data_Types">Data Types</a>.
</td>
</tr>
<tr>
<td>filter</td>
<td>function</td>
<td>Items can be filtered on specific properties by providing a filter
function. A filter function is executed for each of the items in the
DataSet, and is called with the item as parameter. The function must
return a boolean. All items for which the filter function returns
true will be emitted.
See section <a href="#Data_Filtering">Data Filtering</a>.</td>
</tr>
</table>
<p>
The following example demonstrates formatting properties and filtering
properties from items.
</p>
<pre class="prettyprint lang-js">
// create a DataSet
var data = new vis.DataSet();
data.add([
{id: 1, text: 'item 1', date: '2013-06-20', group: 1, first: true},
{id: 2, text: 'item 2', date: '2013-06-23', group: 2},
{id: 3, text: 'item 3', date: '2013-06-25', group: 2},
{id: 4, text: 'item 4'}
]);
// retrieve formatted items
var items = data.get({
fields: ['id', 'date', 'group'], // output the specified fields only
convert: {
date: 'Date', // convert the date fields to Date objects
group: 'String' // convert the group fields to Strings
}
});
</pre>
<h3 id="Data_Types">Data Types</h3>
<p>
DataSet supports the following data types:
</p>
<table style="width: 100%">
<tr>
<th>Name</th>
<th>Description</th>
<th>Examples</th>
</tr>
<tr>
<td>Boolean</td>
<td>A JavaScript Boolean</td>
<td>
<code>true</code><br>
<code>false</code>
</td>
</tr>
<tr>
<td>Number</td>
<td>A JavaScript Number</td>
<td>
<code>32</code><br>
<code>2.4</code>
</td>
</tr>
<tr>
<td>String</td>
<td>A JavaScript String</td>
<td>
<code>"hello world"</code><br>
<code>"2013-06-28"</code>
</td>
</tr>
<tr>
<td>Date</td>
<td>A JavaScript Date object</td>
<td>
<code>new Date()</code><br>
<code>new Date(2013, 5, 28)</code><br>
<code>new Date(1372370400000)</code>
</td>
</tr>
<tr>
<td>Moment</td>
<td>A Moment object, created with
<a href="http://momentjs.com/" target="_blank">moment.js</a></td>
<td>
<code>moment()</code><br>
<code>moment('2013-06-28')</code>
</td>
</tr>
<tr>
<td>ISODate</td>
<td>A string containing an ISO Date</td>
<td>
<code>new Date().toISOString()</code><br>
<code>"2013-06-27T22:00:00.000Z"</code>
</td>
</tr>
<tr>
<td>ASPDate</td>
<td>A string containing an ASP Date</td>
<td>
<code>"/Date(1372370400000)/"</code><br>
<code>"/Date(1198908717056-0700)/"</code>
</td>
</tr>
</table>
<h2 id="Subscriptions">Subscriptions</h2>
<p>
One can subscribe on changes in a DataSet.
A subscription can be created using the method <code>subscribe</code>,
and removed with <code>unsubscribe</code>.
</p>
<pre class="prettyprint lang-js">
// create a DataSet
var data = new vis.DataSet();
// subscribe to any change in the DataSet
data.subscribe('*', function (event, params, senderId) {
console.log('event:', event, 'params:', params, 'senderId:', senderId);
});
// add an item
data.add({id: 1, text: 'item 1'}); // triggers an 'add' event
data.update({id: 1, text: 'item 1 (updated)'}); // triggers an 'update' event
data.remove(1); // triggers an 'remove' event
</pre>
<h3 id="Subscribe">Subscribe</h3>
<p>
Subscribe to an event.
</p>
Syntax:
<pre class="prettyprint lang-js">DataSet.subscribe(event, callback)</pre>
Where:
<ul>
<li>
<code>event</code> is a String containing any of the events listed
in section <a href="#Events">Events</a>.
</li>
<li>
<code>callback</code> is a callback function which will be called
each time the event occurs. The callback function is described in
section <a href="#Callback">Callback</a>.
</li>
</ul>
<h3 id="Unsubscribe">Unsubscribe</h3>
<p>
Unsubscribe from an event.
</p>
Syntax:
<pre class="prettyprint lang-js">DataSet.unsubscribe(event, callback)</pre>
Where <code>event</code> and <code>callback</code> correspond with the
parameters used to <a href="#Subscribe">subscribe</a> to the event.
<h3 id="Events">Events</h3>
<p>
The following events are available for subscription:
</p>
<table>
<tr>
<th>Event</th>
<th>Description</th>
</tr>
<tr>
<td>add</td>
<td>
The <code>add</code> event is triggered when an item
or a set of items is added, or when an item is updated while
not yet existing.
</td>
</tr>
<tr>
<td>update</td>
<td>
The <code>update</code> event is triggered when an existing item
or a set of existing items is updated.
</td>
</tr>
<tr>
<td>remove</td>
<td>
The <code>remove</code> event is triggered when an item
or a set of items is removed.
</td>
</tr>
<tr>
<td>*</td>
<td>
The <code>*</code> event is triggered when any of the events
<code>add</code>, <code>update</code>, and <code>remove</code>
occurs.
</td>
</tr>
</table>
<h3 id="Callback">Callback</h3>
<p>
The callback functions of subscribers are called with the following
parameters:
</p>
<pre class="prettyprint lang-js">
function (event, params, senderId) {
// handle the event
});
</pre>
<p>
where the parameters are defined as
</p>
<table>
<tr>
<th>Parameter</th>
<th>Type</th>
<th>Description</th>
</tr>
<tr>
<td>event</td>
<td>String</td>
<td>
Any of the available events: <code>add</code>,
<code>update</code>, or <code>remove</code>.
</td>
</tr>
<tr>
<td>params</td>
<td>Object&nbsp;|&nbsp;null</td>
<td>
Optional parameters providing more information on the event.
In case of the events <code>add</code>,
<code>update</code>, and <code>remove</code>,
<code>params</code> is always an object containing a property
items, which contains an array with the ids of the affected
items.
</td>
</tr>
<tr>
<td>senderId</td>
<td>String&nbsp;|&nbsp;Number</td>
<td>
An senderId, optionally provided by the application code
which triggered the event. If senderId is not provided, the
argument will be <code>null</code>.
</td>
</tr>
</table>
<h2 id="Data_Policy">Data Policy</h2>
<p>
All code and data is processed and rendered in the browser.
No data is sent to any server.
</p>
</div> </div>
</body> </body>

+ 222
- 0
docs/dataview.html View File

@ -0,0 +1,222 @@
<!doctype html>
<html>
<head>
<title>vis.js | DataView documentation</title>
<link rel='stylesheet' href='css/style.css' type='text/css' />
<link href="css/prettify.css" type="text/css" rel="stylesheet" />
<link href='css/style.css' type='text/css' rel='stylesheet'>
<script type="text/javascript" src="lib/prettify/prettify.js"></script>
</head>
<body onload="prettyPrint();">
<div id="container">
<h1>DataView documentation</h1>
<h2 id="Contents">Contents</h2>
<ul>
<li><a href="#Overview">Overview</a></li>
<li><a href="#Example">Example</a></li>
<li><a href="#Construction">Construction</a></li>
<li><a href="#Getting_Data">Getting Data</a></li>
<li><a href="#Subscriptions">Subscriptions</a></li>
<li><a href="#Data_Policy">Data Policy</a></li>
</ul>
<h2 id="Overview">Overview</h2>
<p>
A DataView offers a filtered and/or formatted view on a
<a href="dataset.html">DataSet</a>.
One can subscribe on changes in a DataView, and easily get filtered or
formatted data without having to specify filters and field types all
the time.
</p>
<h2 id="Example">Example</h2>
<p>
The following example shows how to use a DataView.
</p>
<pre class="prettyprint lang-js">
// create a DataSet
var data = new vis.DataSet();
data.add([
{id: 1, text: 'item 1', date: new Date(2013, 6, 20), group: 1, first: true},
{id: 2, text: 'item 2', date: '2013-06-23', group: 2},
{id: 3, text: 'item 3', date: '2013-06-25', group: 2},
{id: 4, text: 'item 4'}
]);
// create a DataView
// the view will only contain items having a property group with value 1,
// and will only output fields id, text, and date.
var view = new vis.DataView(data, {
filter: function (item) {
return (item.group == 1);
},
fields: ['id', 'text', 'date']
});
// subscribe to any change in the DataView
view.subscribe('*', function (event, params, senderId) {
console.log('event', event, params);
});
// update an item in the data set
data.update({id: 2, group: 1});
// get all ids in the view
var ids = view.getIds();
console.log('ids', ids); // will output [1, 2]
// get all items in the view
var items = view.get();
</pre>
<h2 id="Construction">Construction</h2>
<p>
A DataView can be constructed as:
</p>
<pre class="prettyprint lang-js">
var data = new vis.DataView(dataset, options)
</pre>
<p>
where:
</p>
<ul>
<li>
<code>dataset</code> is a DataSet or DataView.
</li>
<li>
<code>options</code> is an object which can
contain the following properties. Note that these properties
are exactly the same as the properties available in methods
<code>DataSet.get</code> and <code>DataView.get</code>.
<table>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
</tr>
<tr>
<td>convert</td>
<td>Object.&lt;String,&nbsp;String&gt;</td>
<td>
An object containing field names as key, and data types as value.
By default, the type of the properties of an item are left
unchanged. When a field type is specified, this field in the
items will be converted to the specified type. This can be used
for example to convert ISO strings containing a date to a
JavaScript Date object, or convert strings to numbers or vice
versa. The available data types are listed in section
<a href="dataset.html#Data_Types">Data Types</a>.
</td>
</tr>
<tr>
<td>fields</td>
<td>String[&nbsp;]</td>
<td>
An array with field names.
By default, all properties of the items are emitted.
When <code>fields</code> is defined, only the properties
whose name is specified in <code>fields</code> will be included
in the returned items.
</td>
</tr>
<tr>
<td>filter</td>
<td>function</td>
<td>Items can be filtered on specific properties by providing a filter
function. A filter function is executed for each of the items in the
DataSet, and is called with the item as parameter. The function must
return a boolean. All items for which the filter function returns
true will be emitted.
See also section <a href="dataset.html#Data_Filtering">Data Filtering</a>.</td>
</tr>
</table>
</li>
</ul>
<h2 id="Getting_Data">Getting Data</h2>
<p>
Data of the DataView can be retrieved using the method <code>get</code>.
</p>
<pre class="prettyprint lang-js">
var items = view.get();
</pre>
<p>
Data of a DataView can be filtered and formatted again, in exactly the
same way as in a DataSet. See sections
<a href="dataset.html#Data_Filtering">Data Filtering</a> and
<a href="dataset.html#Data_Formatting">Data Formatting</a> for more
information.
</p>
<pre class="prettyprint lang-js">
var items = view.get({
fields: ['id', 'score'],
filter: function (item) {
return (item.score > 50);
}
});
</pre>
<h2 id="Subscriptions">Subscriptions</h2>
<p>
One can subscribe on changes in the DataView. Subscription works exactly
the same as for DataSets. See the documentation on
<a href="dataset.html#Subscriptions">subscriptions in a DataSet</a>
for more information.
</p>
<pre class="prettyprint lang-js">
// create a DataSet and a view on the data set
var data = new vis.DataSet();
var view = new vis.DataView({
filter: function (item) {
return (item.group == 2);
}
});
// subscribe to any change in the DataView
view.subscribe('*', function (event, params, senderId) {
console.log('event:', event, 'params:', params, 'senderId:', senderId);
});
// add, update, and remove data in the DataSet...
</pre>
<h2 id="Data_Policy">Data Policy</h2>
<p>
All code and data is processed and rendered in the browser.
No data is sent to any server.
</p>
</div>
</body>
</html>

+ 2
- 17
docs/graph.html View File

@ -15,22 +15,6 @@
<h1>Graph documentation</h1> <h1>Graph documentation</h1>
<table>
<tr>
<td>Author</td>
<td>Jos de Jong, <a href="http://www.almende.com" target="_blank">Almende B.V.</a></td>
</tr>
<tr>
<td>Webpage</td>
<td><a href="http://visjs.org" target="_blank">http://visjs.org</a></td>
</tr>
<tr>
<td>License</td>
<td> <a href="http://www.apache.org/licenses/LICENSE-2.0" target="_blank">Apache License, Version 2.0</a></td>
</tr>
</table>
<h2><a name="Contents"></a>Contents</h2> <h2><a name="Contents"></a>Contents</h2>
<ul> <ul>
<li><a href="#Overview">Overview</a></li> <li><a href="#Overview">Overview</a></li>
@ -1121,7 +1105,8 @@ vis.events.addListener(graph, 'select', onSelect);
<h2 id="Data_Policy">Data Policy</h2> <h2 id="Data_Policy">Data Policy</h2>
<p> <p>
All code and data are processed and rendered in the browser. No data is sent to any server.
All code and data is processed and rendered in the browser.
No data is sent to any server.
</p> </p>
</div> </div>

+ 173
- 5
docs/index.html View File

@ -11,19 +11,187 @@
<script type="text/javascript" src="lib/prettify/prettify.js"></script> <script type="text/javascript" src="lib/prettify/prettify.js"></script>
</head> </head>
<body>
<body onload="prettyPrint();">
<div id="container"> <div id="container">
<h1>vis.js documentation</h1> <h1>vis.js documentation</h1>
<p>Vis.js contains the following components:</p>
<p>
Vis.js is a dynamic, browser based visualization library.
The library is designed to be easy to use, handle large amounts
of dynamic data, and enable manipulation of the data.
</p>
<p>
The library is developed by
<a href="http://almende.com" target="_blank">Almende B.V.</a>
</p>
<h2 id="Components">Components</h2>
<p>
Vis.js contains of the following components:
</p>
<ul> <ul>
<li><a href="dataset.html">DataSet</a></li>
<li><a href="graph.html">Graph</a></li>
<li><a href="timeline.html">Timeline</a></li>
<li>
<a href="dataset.html"><b>DataSet</b></a>.
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.
</li>
<li>
<a href="dataview.html"><b>DataView</b></a>.
A filtered and/or formatted view on a DataSet.
</li>
<li>
<a href="graph.html"><b>Graph</b></a>.
Display a graph or network with nodes and edges.
</li>
<li>
<a href="timeline.html"><b>Timeline</b></a>.
Display different types of data on a timeline. The timeline and the
items on the timeline can be interactively moved, zoomed, and
manipulated.
</li>
</ul> </ul>
<h2 id="Install">Install</h2>
<h3>npm</h3>
<pre class="prettyprint">
npm install vis
</pre>
<h3>bower</h3>
<pre class="prettyprint">
bower install vis
</pre>
<h3>download</h3>
Download the library from the website:
<a href="http://visjs.org" target="_blank">http://visjs.org</a>.
<h2 id="Load">Load</h2>
<p>
To use a component, include the javascript file of vis in your web page:
</p>
<pre class="prettyprint lang-html">&lt;!DOCTYPE HTML&gt;
&lt;html&gt;
&lt;head&gt;
&lt;script src="components/vis/vis.js"&gt;&lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;script type="text/javascript"&gt;
// ... load a visualization
&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre>
<p>
or load vis.js using require.js:
</p>
<pre class="prettyprint lang-js">
require.config({
paths: {
vis: 'path/to/vis',
}
});
require(['vis'], function (math) {
// ... load a visualization
});
</pre>
<p>
A timeline can be instantiated as follows. Other components can be
created in a similar way.
</p>
<pre class="prettyprint lang-js">
var timeline = new vis.Timeline(container, data, options);
</pre>
<p>
Where <code>container</code> is an HTML element, <code>data</code> is
an Array with data or a DataSet, and <code>options</code> is an optional
object with configuration options for the component.
</p>
<h2 id="Use">Use</h2>
<p>
A basic example on using a Timeline is shown below. More examples can be
found in the <a href="https://github.com/almende/vis/tree/master/examples"
target="_blank">examples directory</a> of the project.
</p>
<pre class="prettyprint lang-html">
&lt;!DOCTYPE HTML&gt;
&lt;html&gt;
&lt;head&gt;
&lt;title&gt;Timeline basic demo&lt;/title&gt;
&lt;script src="components/vis/vis.js"&gt;&lt;/script&gt;
&lt;style type="text/css"&gt;
body, html {
font-family: sans-serif;
}
&lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;div id="visualization"&gt;&lt;/div&gt;
&lt;script type="text/javascript"&gt;
var container = document.getElementById('visualization');
var data = [
{id: 1, content: 'item 1', start: '2013-04-20'},
{id: 2, content: 'item 2', start: '2013-04-14'},
{id: 3, content: 'item 3', start: '2013-04-18'},
{id: 4, content: 'item 4', start: '2013-04-16', end: '2013-04-19'},
{id: 5, content: 'item 5', start: '2013-04-25'},
{id: 6, content: 'item 6', start: '2013-04-27'}
];
var options = {};
var timeline = new vis.Timeline(container, data, options);
&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre>
<h2 id="license">License</h2>
<p>
Copyright (C) 2010-2013 Almende B.V.
</p>
<p>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
</p>
<p>
<a href="http://www.apache.org/licenses/LICENSE-2.0"
target="_blank">http://www.apache.org/licenses/LICENSE-2.0</a>
</p>
<p>
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
</p>
</div> </div>
</body> </body>
</html> </html>

+ 2
- 17
docs/timeline.html View File

@ -14,22 +14,6 @@
<h1>Timeline documentation</h1> <h1>Timeline documentation</h1>
<table>
<tr>
<td>Author</td>
<td>Jos de Jong, <a href="http://www.almende.com" target="_blank">Almende B.V.</a></td>
</tr>
<tr>
<td>Webpage</td>
<td><a href="http://visjs.org" target="_blank">http://visjs.org</a></td>
</tr>
<tr>
<td>License</td>
<td> <a href="http://www.apache.org/licenses/LICENSE-2.0" target="_blank">Apache License, Version 2.0</a></td>
</tr>
</table>
<h2 id="Contents">Contents</h2> <h2 id="Contents">Contents</h2>
<ul> <ul>
@ -575,7 +559,8 @@ var options = {
<h2 id="Data_Policy">Data Policy</h2> <h2 id="Data_Policy">Data Policy</h2>
<p> <p>
All code and data are processed and rendered in the browser. No data is sent to any server.
All code and data is processed and rendered in the browser.
No data is sent to any server.
</p> </p>
</div> </div>

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

@ -29,7 +29,7 @@
// create a dataset with items // create a dataset with items
var items = new vis.DataSet({ var items = new vis.DataSet({
fieldTypes: {
convert: {
start: 'Date', start: 'Date',
end: 'Date' end: 'Date'
} }

+ 1
- 1
examples/timeline/03_much_data.html View File

@ -27,7 +27,7 @@
// create a dataset with items // create a dataset with items
var now = moment().minutes(0).seconds(0).milliseconds(0); var now = moment().minutes(0).seconds(0).milliseconds(0);
var items = new vis.DataSet({ var items = new vis.DataSet({
fieldTypes: {
convert: {
start: 'Date', start: 'Date',
end: 'Date' end: 'Date'
} }

+ 1
- 1
package.json View File

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

+ 109
- 84
src/DataSet.js View File

@ -4,7 +4,7 @@
* Usage: * Usage:
* var dataSet = new DataSet({ * var dataSet = new DataSet({
* fieldId: '_id', * fieldId: '_id',
* fieldTypes: {
* convert: {
* // ... * // ...
* } * }
* }); * });
@ -29,7 +29,7 @@
* @param {Object} [options] Available options: * @param {Object} [options] Available options:
* {String} fieldId Field name of the id in the * {String} fieldId Field name of the id in the
* items, 'id' by default. * items, 'id' by default.
* {Object.<String, String} fieldTypes
* {Object.<String, String} convert
* A map with field names as key, * A map with field names as key,
* and the field type as value. * and the field type as value.
* @constructor DataSet * @constructor DataSet
@ -41,17 +41,17 @@ function DataSet (options) {
this.options = options || {}; this.options = options || {};
this.data = {}; // map with data indexed by id this.data = {}; // map with data indexed by id
this.fieldId = this.options.fieldId || 'id'; // name of the field containing id this.fieldId = this.options.fieldId || 'id'; // name of the field containing id
this.fieldTypes = {}; // field types by field name
this.convert = {}; // field types by field name
if (this.options.fieldTypes) {
for (var field in this.options.fieldTypes) {
if (this.options.fieldTypes.hasOwnProperty(field)) {
var value = this.options.fieldTypes[field];
if (this.options.convert) {
for (var field in this.options.convert) {
if (this.options.convert.hasOwnProperty(field)) {
var value = this.options.convert[field];
if (value == 'Date' || value == 'ISODate' || value == 'ASPDate') { if (value == 'Date' || value == 'ISODate' || value == 'ASPDate') {
this.fieldTypes[field] = 'Date';
this.convert[field] = 'Date';
} }
else { else {
this.fieldTypes[field] = value;
this.convert[field] = value;
} }
} }
} }
@ -70,11 +70,9 @@ function DataSet (options) {
* @param {function} callback Callback method. Called with three parameters: * @param {function} callback Callback method. Called with three parameters:
* {String} event * {String} event
* {Object | null} params * {Object | null} params
* {String} senderId
* @param {String} [id] Optional id for the sender, used to filter
* events triggered by the sender itself.
* {String | Number} senderId
*/ */
DataSet.prototype.subscribe = function (event, callback, id) {
DataSet.prototype.subscribe = function (event, callback) {
var subscribers = this.subscribers[event]; var subscribers = this.subscribers[event];
if (!subscribers) { if (!subscribers) {
subscribers = []; subscribers = [];
@ -82,7 +80,6 @@ DataSet.prototype.subscribe = function (event, callback, id) {
} }
subscribers.push({ subscribers.push({
id: id ? String(id) : null,
callback: callback callback: callback
}); });
}; };
@ -134,9 +131,10 @@ DataSet.prototype._trigger = function (event, params, senderId) {
* Adding an item will fail when there already is an item with the same id. * Adding an item will fail when there already is an item with the same id.
* @param {Object | Array | DataTable} data * @param {Object | Array | DataTable} data
* @param {String} [senderId] Optional sender id * @param {String} [senderId] Optional sender id
* @return {Array} addedIds Array with the ids of the added items
*/ */
DataSet.prototype.add = function (data, senderId) { DataSet.prototype.add = function (data, senderId) {
var addedItems = [],
var addedIds = [],
id, id,
me = this; me = this;
@ -144,7 +142,7 @@ DataSet.prototype.add = function (data, senderId) {
// Array // Array
for (var i = 0, len = data.length; i < len; i++) { for (var i = 0, len = data.length; i < len; i++) {
id = me._addItem(data[i]); id = me._addItem(data[i]);
addedItems.push(id);
addedIds.push(id);
} }
} }
else if (util.isDataTable(data)) { else if (util.isDataTable(data)) {
@ -158,31 +156,34 @@ DataSet.prototype.add = function (data, senderId) {
} }
id = me._addItem(item); id = me._addItem(item);
addedItems.push(id);
addedIds.push(id);
} }
} }
else if (data instanceof Object) { else if (data instanceof Object) {
// Single item // Single item
id = me._addItem(data); id = me._addItem(data);
addedItems.push(id);
addedIds.push(id);
} }
else { else {
throw new Error('Unknown dataType'); throw new Error('Unknown dataType');
} }
if (addedItems.length) {
this._trigger('add', {items: addedItems}, senderId);
if (addedIds.length) {
this._trigger('add', {items: addedIds}, senderId);
} }
return addedIds;
}; };
/** /**
* Update existing items. When an item does not exist, it will be created * Update existing items. When an item does not exist, it will be created
* @param {Object | Array | DataTable} data * @param {Object | Array | DataTable} data
* @param {String} [senderId] Optional sender id * @param {String} [senderId] Optional sender id
* @return {Array} updatedIds The ids of the added or updated items
*/ */
DataSet.prototype.update = function (data, senderId) { DataSet.prototype.update = function (data, senderId) {
var addedItems = [],
updatedItems = [],
var addedIds = [],
updatedIds = [],
me = this, me = this,
fieldId = me.fieldId; fieldId = me.fieldId;
@ -191,12 +192,12 @@ DataSet.prototype.update = function (data, senderId) {
if (me.data[id]) { if (me.data[id]) {
// update item // update item
id = me._updateItem(item); id = me._updateItem(item);
updatedItems.push(id);
updatedIds.push(id);
} }
else { else {
// add new item // add new item
id = me._addItem(item); id = me._addItem(item);
addedItems.push(id);
addedIds.push(id);
} }
}; };
@ -227,12 +228,14 @@ DataSet.prototype.update = function (data, senderId) {
throw new Error('Unknown dataType'); throw new Error('Unknown dataType');
} }
if (addedItems.length) {
this._trigger('add', {items: addedItems}, senderId);
if (addedIds.length) {
this._trigger('add', {items: addedIds}, senderId);
} }
if (updatedItems.length) {
this._trigger('update', {items: updatedItems}, senderId);
if (updatedIds.length) {
this._trigger('update', {items: updatedIds}, senderId);
} }
return addedIds.concat(updatedIds);
}; };
/** /**
@ -259,7 +262,7 @@ DataSet.prototype.update = function (data, senderId) {
* {Object} options An Object with options. Available options: * {Object} options An Object with options. Available options:
* {String} [type] Type of data to be returned. Can * {String} [type] Type of data to be returned. Can
* be 'DataTable' or 'Array' (default) * be 'DataTable' or 'Array' (default)
* {Object.<String, String>} [fieldTypes]
* {Object.<String, String>} [convert]
* {String[]} [fields] field names to be returned * {String[]} [fields] field names to be returned
* {function} [filter] filter items * {function} [filter] filter items
* {String | function} [order] Order the items by * {String | function} [order] Order the items by
@ -316,14 +319,14 @@ DataSet.prototype.get = function (args) {
} }
// build options // build options
var fieldTypes = options && options.fieldTypes || this.options.fieldTypes;
var convert = options && options.convert || this.options.convert;
var filter = options && options.filter; var filter = options && options.filter;
var items = [], item, itemId, i, len; var items = [], item, itemId, i, len;
// cast items
// convert items
if (id != undefined) { if (id != undefined) {
// return a single item // return a single item
item = me._getItem(id, fieldTypes);
item = me._getItem(id, convert);
if (filter && !filter(item)) { if (filter && !filter(item)) {
item = null; item = null;
} }
@ -331,7 +334,7 @@ DataSet.prototype.get = function (args) {
else if (ids != undefined) { else if (ids != undefined) {
// return a subset of items // return a subset of items
for (i = 0, len = ids.length; i < len; i++) { for (i = 0, len = ids.length; i < len; i++) {
item = me._getItem(ids[i], fieldTypes);
item = me._getItem(ids[i], convert);
if (!filter || filter(item)) { if (!filter || filter(item)) {
items.push(item); items.push(item);
} }
@ -341,7 +344,7 @@ DataSet.prototype.get = function (args) {
// return all items // return all items
for (itemId in this.data) { for (itemId in this.data) {
if (this.data.hasOwnProperty(itemId)) { if (this.data.hasOwnProperty(itemId)) {
item = me._getItem(itemId, fieldTypes);
item = me._getItem(itemId, convert);
if (!filter || filter(item)) { if (!filter || filter(item)) {
items.push(item); items.push(item);
} }
@ -417,7 +420,7 @@ DataSet.prototype.getIds = function (options) {
var data = this.data, var data = this.data,
filter = options && options.filter, filter = options && options.filter,
order = options && options.order, order = options && options.order,
fieldTypes = options && options.fieldTypes || this.options.fieldTypes,
convert = options && options.convert || this.options.convert,
i, i,
len, len,
id, id,
@ -432,7 +435,7 @@ DataSet.prototype.getIds = function (options) {
items = []; items = [];
for (id in data) { for (id in data) {
if (data.hasOwnProperty(id)) { if (data.hasOwnProperty(id)) {
item = this._getItem(id, fieldTypes);
item = this._getItem(id, convert);
if (filter(item)) { if (filter(item)) {
items.push(item); items.push(item);
} }
@ -449,7 +452,7 @@ DataSet.prototype.getIds = function (options) {
// create unordered list // create unordered list
for (id in data) { for (id in data) {
if (data.hasOwnProperty(id)) { if (data.hasOwnProperty(id)) {
item = this._getItem(id, fieldTypes);
item = this._getItem(id, convert);
if (filter(item)) { if (filter(item)) {
ids.push(item[this.fieldId]); ids.push(item[this.fieldId]);
} }
@ -493,7 +496,7 @@ DataSet.prototype.getIds = function (options) {
* The order of the items is not determined. * The order of the items is not determined.
* @param {function} callback * @param {function} callback
* @param {Object} [options] Available options: * @param {Object} [options] Available options:
* {Object.<String, String>} [fieldTypes]
* {Object.<String, String>} [convert]
* {String[]} [fields] filter fields * {String[]} [fields] filter fields
* {function} [filter] filter items * {function} [filter] filter items
* {String | function} [order] Order the items by * {String | function} [order] Order the items by
@ -501,7 +504,7 @@ DataSet.prototype.getIds = function (options) {
*/ */
DataSet.prototype.forEach = function (callback, options) { DataSet.prototype.forEach = function (callback, options) {
var filter = options && options.filter, var filter = options && options.filter,
fieldTypes = options && options.fieldTypes || this.options.fieldTypes,
convert = options && options.convert || this.options.convert,
data = this.data, data = this.data,
item, item,
id; id;
@ -520,7 +523,7 @@ DataSet.prototype.forEach = function (callback, options) {
// unordered // unordered
for (id in data) { for (id in data) {
if (data.hasOwnProperty(id)) { if (data.hasOwnProperty(id)) {
item = this._getItem(id, fieldTypes);
item = this._getItem(id, convert);
if (!filter || filter(item)) { if (!filter || filter(item)) {
callback(item, id); callback(item, id);
} }
@ -533,7 +536,7 @@ DataSet.prototype.forEach = function (callback, options) {
* Map every item in the dataset. * Map every item in the dataset.
* @param {function} callback * @param {function} callback
* @param {Object} [options] Available options: * @param {Object} [options] Available options:
* {Object.<String, String>} [fieldTypes]
* {Object.<String, String>} [convert]
* {String[]} [fields] filter fields * {String[]} [fields] filter fields
* {function} [filter] filter items * {function} [filter] filter items
* {String | function} [order] Order the items by * {String | function} [order] Order the items by
@ -542,15 +545,15 @@ DataSet.prototype.forEach = function (callback, options) {
*/ */
DataSet.prototype.map = function (callback, options) { DataSet.prototype.map = function (callback, options) {
var filter = options && options.filter, var filter = options && options.filter,
fieldTypes = options && options.fieldTypes || this.options.fieldTypes,
convert = options && options.convert || this.options.convert,
mappedItems = [], mappedItems = [],
data = this.data, data = this.data,
item; item;
// cast and filter items
// convert and filter items
for (var id in data) { for (var id in data) {
if (data.hasOwnProperty(id)) { if (data.hasOwnProperty(id)) {
item = this._getItem(id, fieldTypes);
item = this._getItem(id, convert);
if (!filter || filter(item)) { if (!filter || filter(item)) {
mappedItems.push(callback(item, id)); mappedItems.push(callback(item, id));
} }
@ -613,46 +616,66 @@ DataSet.prototype._sort = function (items, order) {
/** /**
* Remove an object by pointer or by id * Remove an object by pointer or by id
* @param {String | Number | Object | Array} id Object or id, or an array with
* objects or ids to be removed
* @param {String | Number | Object | Array} id Object or id, or an array with
* objects or ids to be removed
* @param {String} [senderId] Optional sender id * @param {String} [senderId] Optional sender id
* @return {Array} removedIds
*/ */
DataSet.prototype.remove = function (id, senderId) { DataSet.prototype.remove = function (id, senderId) {
var removedItems = [],
i, len;
var removedIds = [],
i, len, removedId;
if (util.isNumber(id) || util.isString(id)) {
delete this.data[id];
delete this.internalIds[id];
removedItems.push(id);
}
else if (id instanceof Array) {
if (id instanceof Array) {
for (i = 0, len = id.length; i < len; i++) { for (i = 0, len = id.length; i < len; i++) {
this.remove(id[i]);
removedId = this._remove(id[i]);
if (removedId != null) {
removedIds.push(removedId);
}
} }
removedItems = items.concat(id);
} }
else if (id instanceof Object) {
// search for the object
for (i in this.data) {
if (this.data.hasOwnProperty(i)) {
if (this.data[i] == id) {
delete this.data[i];
delete this.internalIds[i];
removedItems.push(i);
}
}
else {
removedId = this._remove(id);
if (removedId != null) {
removedIds.push(removedId);
} }
} }
if (removedItems.length) {
this._trigger('remove', {items: removedItems}, senderId);
if (removedIds.length) {
this._trigger('remove', {items: removedIds}, senderId);
}
return removedIds;
};
/**
* Remove an item by its id
* @param {Number | String | Object} id id or item
* @returns {Number | String | null} id
* @private
*/
DataSet.prototype._remove = function (id) {
if (util.isNumber(id) || util.isString(id)) {
if (this.data[id]) {
delete this.data[id];
delete this.internalIds[id];
return id;
}
}
else if (id instanceof Object) {
var itemId = id[this.fieldId];
if (itemId && this.data[itemId]) {
delete this.data[itemId];
delete this.internalIds[itemId];
return itemId;
}
} }
return null;
}; };
/** /**
* Clear the data * Clear the data
* @param {String} [senderId] Optional sender id * @param {String} [senderId] Optional sender id
* @return {Array} removedIds The ids of all removed items
*/ */
DataSet.prototype.clear = function (senderId) { DataSet.prototype.clear = function (senderId) {
var ids = Object.keys(this.data); var ids = Object.keys(this.data);
@ -661,6 +684,8 @@ DataSet.prototype.clear = function (senderId) {
this.internalIds = {}; this.internalIds = {};
this._trigger('remove', {items: ids}, senderId); this._trigger('remove', {items: ids}, senderId);
return ids;
}; };
/** /**
@ -722,13 +747,13 @@ DataSet.prototype.min = function (field) {
DataSet.prototype.distinct = function (field) { DataSet.prototype.distinct = function (field) {
var data = this.data, var data = this.data,
values = [], values = [],
fieldType = this.options.fieldTypes[field],
fieldType = this.options.convert[field],
count = 0; count = 0;
for (var prop in data) { for (var prop in data) {
if (data.hasOwnProperty(prop)) { if (data.hasOwnProperty(prop)) {
var item = data[prop]; var item = data[prop];
var value = util.cast(item[field], fieldType);
var value = util.convert(item[field], fieldType);
var exists = false; var exists = false;
for (var i = 0; i < count; i++) { for (var i = 0; i < count; i++) {
if (values[i] == value) { if (values[i] == value) {
@ -772,8 +797,8 @@ DataSet.prototype._addItem = function (item) {
var d = {}; var d = {};
for (var field in item) { for (var field in item) {
if (item.hasOwnProperty(field)) { if (item.hasOwnProperty(field)) {
var type = this.fieldTypes[field]; // type may be undefined
d[field] = util.cast(item[field], type);
var fieldType = this.convert[field]; // type may be undefined
d[field] = util.convert(item[field], fieldType);
} }
} }
this.data[id] = d; this.data[id] = d;
@ -782,13 +807,13 @@ DataSet.prototype._addItem = function (item) {
}; };
/** /**
* Get an item. Fields can be casted to a specific type
* Get an item. Fields can be converted to a specific type
* @param {String} id * @param {String} id
* @param {Object.<String, String>} [fieldTypes] Cast field types
* @param {Object.<String, String>} [convert] field types to convert
* @return {Object | null} item * @return {Object | null} item
* @private * @private
*/ */
DataSet.prototype._getItem = function (id, fieldTypes) {
DataSet.prototype._getItem = function (id, convert) {
var field, value; var field, value;
// get the item from the dataset // get the item from the dataset
@ -797,35 +822,35 @@ DataSet.prototype._getItem = function (id, fieldTypes) {
return null; return null;
} }
// cast the items field types
var casted = {},
// convert the items field types
var converted = {},
fieldId = this.fieldId, fieldId = this.fieldId,
internalIds = this.internalIds; internalIds = this.internalIds;
if (fieldTypes) {
if (convert) {
for (field in raw) { for (field in raw) {
if (raw.hasOwnProperty(field)) { if (raw.hasOwnProperty(field)) {
value = raw[field]; value = raw[field];
// output all fields, except internal ids // output all fields, except internal ids
if ((field != fieldId) || !(value in internalIds)) { if ((field != fieldId) || !(value in internalIds)) {
casted[field] = util.cast(value, fieldTypes[field]);
converted[field] = util.convert(value, convert[field]);
} }
} }
} }
} }
else { else {
// no field types specified, no casting needed
// no field types specified, no converting needed
for (field in raw) { for (field in raw) {
if (raw.hasOwnProperty(field)) { if (raw.hasOwnProperty(field)) {
value = raw[field]; value = raw[field];
// output all fields, except internal ids // output all fields, except internal ids
if ((field != fieldId) || !(value in internalIds)) { if ((field != fieldId) || !(value in internalIds)) {
casted[field] = value;
converted[field] = value;
} }
} }
} }
} }
return casted;
return converted;
}; };
/** /**
@ -850,8 +875,8 @@ DataSet.prototype._updateItem = function (item) {
// merge with current item // merge with current item
for (var field in item) { for (var field in item) {
if (item.hasOwnProperty(field)) { if (item.hasOwnProperty(field)) {
var type = this.fieldTypes[field]; // type may be undefined
d[field] = util.cast(item[field], type);
var fieldType = this.convert[field]; // type may be undefined
d[field] = util.convert(item[field], fieldType);
} }
} }

+ 1
- 1
src/DataView.js View File

@ -96,7 +96,7 @@ DataView.prototype.setData = function (data) {
* {Object} options An Object with options. Available options: * {Object} options An Object with options. Available options:
* {String} [type] Type of data to be returned. Can * {String} [type] Type of data to be returned. Can
* be 'DataTable' or 'Array' (default) * be 'DataTable' or 'Array' (default)
* {Object.<String, String>} [fieldTypes]
* {Object.<String, String>} [convert]
* {String[]} [fields] field names to be returned * {String[]} [fields] field names to be returned
* {function} [filter] filter items * {function} [filter] filter items
* {String | function} [order] Order the items by * {String | function} [order] Order the items by

+ 3
- 3
src/graph/dotparser.js View File

@ -327,13 +327,13 @@
next(); next();
} }
if (token == 'false') { if (token == 'false') {
token = false; // cast to boolean
token = false; // convert to boolean
} }
else if (token == 'true') { else if (token == 'true') {
token = true; // cast to boolean
token = true; // convert to boolean
} }
else if (!isNaN(Number(token))) { else if (!isNaN(Number(token))) {
token = Number(token); // cast to number
token = Number(token); // convert to number
} }
tokenType = TOKENTYPE.IDENTIFIER; tokenType = TOKENTYPE.IDENTIFIER;
return; return;

+ 2
- 2
src/timeline/Range.js View File

@ -139,8 +139,8 @@ Range.prototype.setRange = function(start, end) {
* @private * @private
*/ */
Range.prototype._applyRange = function(start, end) { Range.prototype._applyRange = function(start, end) {
var newStart = (start != null) ? util.cast(start, 'Number') : this.start;
var newEnd = (end != null) ? util.cast(end, 'Number') : this.end;
var newStart = (start != null) ? util.convert(start, 'Number') : this.start;
var newEnd = (end != null) ? util.convert(end, 'Number') : this.end;
var diff; var diff;
// check for valid number // check for valid number

+ 1
- 1
src/timeline/Timeline.js View File

@ -143,7 +143,7 @@ Timeline.prototype.setItems = function(items) {
} }
if (!(items instanceof DataSet)) { if (!(items instanceof DataSet)) {
newItemSet = new DataSet({ newItemSet = new DataSet({
fieldTypes: {
convert: {
start: 'Date', start: 'Date',
end: 'Date' end: 'Date'
} }

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

@ -120,7 +120,7 @@ GroupSet.prototype.setGroups = function setGroups(groups) {
} }
else { else {
this.groupsData = new DataSet({ this.groupsData = new DataSet({
fieldTypes: {
convert: {
start: 'Date', start: 'Date',
end: 'Date' end: 'Date'
} }

+ 2
- 2
src/timeline/component/TimeAxis.js View File

@ -487,8 +487,8 @@ TimeAxis.prototype.reflow = function () {
// calculate range and step // calculate range and step
this._updateConversion(); this._updateConversion();
var start = util.cast(range.start, 'Date'),
end = util.cast(range.end, 'Date'),
var start = util.convert(range.start, 'Date'),
end = util.convert(range.end, 'Date'),
minimumStep = this.toTime((props.minorCharWidth || 10) * 5) - this.toTime(0); minimumStep = this.toTime((props.minorCharWidth || 10) * 5) - this.toTime(0);
this.step = new TimeStep(start, end, minimumStep); this.step = new TimeStep(start, end, minimumStep);
changed += update(props.range, 'start', start.valueOf()); changed += update(props.range, 'start', start.valueOf());

+ 46
- 25
src/util.js View File

@ -98,7 +98,7 @@ util.extend = function (a, b) {
}; };
/** /**
* Cast an object to another type
* Convert an object to another type
* @param {Boolean | Number | String | Date | Moment | Null | undefined} object * @param {Boolean | Number | String | Date | Moment | Null | undefined} object
* @param {String | undefined} type Name of the type. Available types: * @param {String | undefined} type Name of the type. Available types:
* 'Boolean', 'Number', 'String', * 'Boolean', 'Number', 'String',
@ -106,7 +106,7 @@ util.extend = function (a, b) {
* @return {*} object * @return {*} object
* @throws Error * @throws Error
*/ */
util.cast = function cast(object, type) {
util.convert = function convert(object, type) {
var match; var match;
if (object === undefined) { if (object === undefined) {
@ -131,7 +131,7 @@ util.cast = function cast(object, type) {
case 'number': case 'number':
case 'Number': case 'Number':
return Number(object);
return Number(object.valueOf());
case 'string': case 'string':
case 'String': case 'String':
@ -148,11 +148,9 @@ util.cast = function cast(object, type) {
return new Date(object.valueOf()); return new Date(object.valueOf());
} }
if (util.isString(object)) { if (util.isString(object)) {
// parse ASP.Net Date pattern,
// for example '/Date(1198908717056)/' or '/Date(1198908717056-0700)/'
// code from http://momentjs.com/
match = ASPDateRegex.exec(object); match = ASPDateRegex.exec(object);
if (match) { if (match) {
// object is an ASP date
return new Date(Number(match[1])); // parse number return new Date(Number(match[1])); // parse number
} }
else { else {
@ -161,7 +159,7 @@ util.cast = function cast(object, type) {
} }
else { else {
throw new Error( throw new Error(
'Cannot cast object of type ' + util.getType(object) +
'Cannot convert object of type ' + util.getType(object) +
' to type Date'); ' to type Date');
} }
@ -176,11 +174,9 @@ util.cast = function cast(object, type) {
return moment.clone(); return moment.clone();
} }
if (util.isString(object)) { if (util.isString(object)) {
// parse ASP.Net Date pattern,
// for example '/Date(1198908717056)/' or '/Date(1198908717056-0700)/'
// code from http://momentjs.com/
match = ASPDateRegex.exec(object); match = ASPDateRegex.exec(object);
if (match) { if (match) {
// object is an ASP date
return moment(Number(match[1])); // parse number return moment(Number(match[1])); // parse number
} }
else { else {
@ -189,45 +185,70 @@ util.cast = function cast(object, type) {
} }
else { else {
throw new Error( throw new Error(
'Cannot cast object of type ' + util.getType(object) +
'Cannot convert object of type ' + util.getType(object) +
' to type Date'); ' to type Date');
} }
case 'ISODate': case 'ISODate':
if (object instanceof Date) {
if (util.isNumber(object)) {
return new Date(object);
}
else if (object instanceof Date) {
return object.toISOString(); return object.toISOString();
} }
else if (moment.isMoment(object)) { else if (moment.isMoment(object)) {
return object.toDate().toISOString(); return object.toDate().toISOString();
} }
else if (util.isNumber(object) || util.isString(object)) {
return moment(object).toDate().toISOString();
else if (util.isString(object)) {
match = ASPDateRegex.exec(object);
if (match) {
// object is an ASP date
return new Date(Number(match[1])).toISOString(); // parse number
}
else {
return new Date(object).toISOString(); // parse string
}
} }
else { else {
throw new Error( throw new Error(
'Cannot cast object of type ' + util.getType(object) +
'Cannot convert object of type ' + util.getType(object) +
' to type ISODate'); ' to type ISODate');
} }
case 'ASPDate': case 'ASPDate':
if (object instanceof Date) {
if (util.isNumber(object)) {
return '/Date(' + object + ')/';
}
else if (object instanceof Date) {
return '/Date(' + object.valueOf() + ')/'; return '/Date(' + object.valueOf() + ')/';
} }
else if (util.isNumber(object) || util.isString(object)) {
return '/Date(' + moment(object).valueOf() + ')/';
else if (util.isString(object)) {
match = ASPDateRegex.exec(object);
var value;
if (match) {
// object is an ASP date
value = new Date(Number(match[1])).valueOf(); // parse number
}
else {
value = new Date(object).valueOf(); // parse string
}
return '/Date(' + value + ')/';
} }
else { else {
throw new Error( throw new Error(
'Cannot cast object of type ' + util.getType(object) +
'Cannot convert object of type ' + util.getType(object) +
' to type ASPDate'); ' to type ASPDate');
} }
default: default:
throw new Error('Cannot cast object of type ' + util.getType(object) +
throw new Error('Cannot convert object of type ' + util.getType(object) +
' to type "' + type + '"'); ' to type "' + type + '"');
} }
}; };
// parse ASP.Net Date pattern,
// for example '/Date(1198908717056)/' or '/Date(1198908717056-0700)/'
// code from http://momentjs.com/
var ASPDateRegex = /^\/?Date\((\-?\d+)/i; var ASPDateRegex = /^\/?Date\((\-?\d+)/i;
/** /**
@ -547,7 +568,7 @@ util.preventDefault = function preventDefault (event) {
util.option = {}; util.option = {};
/** /**
* Cast a value as boolean
* Convert a value into a boolean
* @param {Boolean | function | undefined} value * @param {Boolean | function | undefined} value
* @param {Boolean} [defaultValue] * @param {Boolean} [defaultValue]
* @returns {Boolean} bool * @returns {Boolean} bool
@ -565,7 +586,7 @@ util.option.asBoolean = function (value, defaultValue) {
}; };
/** /**
* Cast a value as number
* Convert a value into a number
* @param {Boolean | function | undefined} value * @param {Boolean | function | undefined} value
* @param {Number} [defaultValue] * @param {Number} [defaultValue]
* @returns {Number} number * @returns {Number} number
@ -583,7 +604,7 @@ util.option.asNumber = function (value, defaultValue) {
}; };
/** /**
* Cast a value as string
* Convert a value into a string
* @param {String | function | undefined} value * @param {String | function | undefined} value
* @param {String} [defaultValue] * @param {String} [defaultValue]
* @returns {String} str * @returns {String} str
@ -601,7 +622,7 @@ util.option.asString = function (value, defaultValue) {
}; };
/** /**
* Cast a size or location in pixels or a percentage
* Convert a size or location into a string with pixels or a percentage
* @param {String | Number | function | undefined} value * @param {String | Number | function | undefined} value
* @param {String} [defaultValue] * @param {String} [defaultValue]
* @returns {String} size * @returns {String} size
@ -623,7 +644,7 @@ util.option.asSize = function (value, defaultValue) {
}; };
/** /**
* Cast a value as DOM element
* Convert a value into a DOM element
* @param {HTMLElement | function | undefined} value * @param {HTMLElement | function | undefined} value
* @param {HTMLElement} [defaultValue] * @param {HTMLElement} [defaultValue]
* @returns {HTMLElement | null} dom * @returns {HTMLElement | null} dom

+ 1
- 1
test/dataset.html View File

@ -9,7 +9,7 @@
<script> <script>
var dataset = new vis.DataSet({ var dataset = new vis.DataSet({
fieldId: 'id', fieldId: 'id',
fieldTypes: {
convert: {
start: 'Date' start: 'Date'
} }
}); });

+ 4
- 4
test/dataset.js View File

@ -6,7 +6,7 @@ var assert = require('assert'),
var now = new Date(); var now = new Date();
var data = new DataSet({ var data = new DataSet({
fieldTypes: {
convert: {
start: 'Date', start: 'Date',
end: 'Date' end: 'Date'
} }
@ -41,10 +41,10 @@ assert.deepEqual(data.get({
]); ]);
// cast dates
// convert dates
assert.deepEqual(data.get({ assert.deepEqual(data.get({
fields: ['id', 'start'], fields: ['id', 'start'],
fieldTypes: {start: 'Number'}
convert: {start: 'Number'}
}).sort(sort), [ }).sort(sort), [
{id: 1, start: now.valueOf()}, {id: 1, start: now.valueOf()},
{id: 2, start: now.valueOf()}, {id: 2, start: now.valueOf()},
@ -56,7 +56,7 @@ assert.deepEqual(data.get({
// get a single item // get a single item
assert.deepEqual(data.get(1, { assert.deepEqual(data.get(1, {
fields: ['id', 'start'], fields: ['id', 'start'],
fieldTypes: {start: 'ISODate'}
convert: {start: 'ISODate'}
}), { }), {
id: 1, id: 1,
start: now.toISOString() start: now.toISOString()

+ 1
- 1
test/timeline.html View File

@ -41,7 +41,7 @@
// create a dataset with items // create a dataset with items
var now = moment().minutes(0).seconds(0).milliseconds(0); var now = moment().minutes(0).seconds(0).milliseconds(0);
var items = new vis.DataSet({ var items = new vis.DataSet({
fieldTypes: {
convert: {
start: 'Date', start: 'Date',
end: 'Date' end: 'Date'
}, },

+ 167
- 121
vis.js View File

@ -4,8 +4,8 @@
* *
* A dynamic, browser-based visualization library. * A dynamic, browser-based visualization library.
* *
* @version 0.1.0-SNAPSHOT
* @date 2013-06-18
* @version 0.2.0-SNAPSHOT
* @date 2013-06-25
* *
* @license * @license
* Copyright (C) 2011-2013 Almende B.V, http://almende.com * Copyright (C) 2011-2013 Almende B.V, http://almende.com
@ -129,7 +129,7 @@ util.extend = function (a, b) {
}; };
/** /**
* Cast an object to another type
* Convert an object to another type
* @param {Boolean | Number | String | Date | Moment | Null | undefined} object * @param {Boolean | Number | String | Date | Moment | Null | undefined} object
* @param {String | undefined} type Name of the type. Available types: * @param {String | undefined} type Name of the type. Available types:
* 'Boolean', 'Number', 'String', * 'Boolean', 'Number', 'String',
@ -137,7 +137,7 @@ util.extend = function (a, b) {
* @return {*} object * @return {*} object
* @throws Error * @throws Error
*/ */
util.cast = function cast(object, type) {
util.convert = function convert(object, type) {
var match; var match;
if (object === undefined) { if (object === undefined) {
@ -162,7 +162,7 @@ util.cast = function cast(object, type) {
case 'number': case 'number':
case 'Number': case 'Number':
return Number(object);
return Number(object.valueOf());
case 'string': case 'string':
case 'String': case 'String':
@ -179,11 +179,9 @@ util.cast = function cast(object, type) {
return new Date(object.valueOf()); return new Date(object.valueOf());
} }
if (util.isString(object)) { if (util.isString(object)) {
// parse ASP.Net Date pattern,
// for example '/Date(1198908717056)/' or '/Date(1198908717056-0700)/'
// code from http://momentjs.com/
match = ASPDateRegex.exec(object); match = ASPDateRegex.exec(object);
if (match) { if (match) {
// object is an ASP date
return new Date(Number(match[1])); // parse number return new Date(Number(match[1])); // parse number
} }
else { else {
@ -192,7 +190,7 @@ util.cast = function cast(object, type) {
} }
else { else {
throw new Error( throw new Error(
'Cannot cast object of type ' + util.getType(object) +
'Cannot convert object of type ' + util.getType(object) +
' to type Date'); ' to type Date');
} }
@ -207,11 +205,9 @@ util.cast = function cast(object, type) {
return moment.clone(); return moment.clone();
} }
if (util.isString(object)) { if (util.isString(object)) {
// parse ASP.Net Date pattern,
// for example '/Date(1198908717056)/' or '/Date(1198908717056-0700)/'
// code from http://momentjs.com/
match = ASPDateRegex.exec(object); match = ASPDateRegex.exec(object);
if (match) { if (match) {
// object is an ASP date
return moment(Number(match[1])); // parse number return moment(Number(match[1])); // parse number
} }
else { else {
@ -220,45 +216,70 @@ util.cast = function cast(object, type) {
} }
else { else {
throw new Error( throw new Error(
'Cannot cast object of type ' + util.getType(object) +
'Cannot convert object of type ' + util.getType(object) +
' to type Date'); ' to type Date');
} }
case 'ISODate': case 'ISODate':
if (object instanceof Date) {
if (util.isNumber(object)) {
return new Date(object);
}
else if (object instanceof Date) {
return object.toISOString(); return object.toISOString();
} }
else if (moment.isMoment(object)) { else if (moment.isMoment(object)) {
return object.toDate().toISOString(); return object.toDate().toISOString();
} }
else if (util.isNumber(object) || util.isString(object)) {
return moment(object).toDate().toISOString();
else if (util.isString(object)) {
match = ASPDateRegex.exec(object);
if (match) {
// object is an ASP date
return new Date(Number(match[1])).toISOString(); // parse number
}
else {
return new Date(object).toISOString(); // parse string
}
} }
else { else {
throw new Error( throw new Error(
'Cannot cast object of type ' + util.getType(object) +
'Cannot convert object of type ' + util.getType(object) +
' to type ISODate'); ' to type ISODate');
} }
case 'ASPDate': case 'ASPDate':
if (object instanceof Date) {
if (util.isNumber(object)) {
return '/Date(' + object + ')/';
}
else if (object instanceof Date) {
return '/Date(' + object.valueOf() + ')/'; return '/Date(' + object.valueOf() + ')/';
} }
else if (util.isNumber(object) || util.isString(object)) {
return '/Date(' + moment(object).valueOf() + ')/';
else if (util.isString(object)) {
match = ASPDateRegex.exec(object);
var value;
if (match) {
// object is an ASP date
value = new Date(Number(match[1])).valueOf(); // parse number
}
else {
value = new Date(object).valueOf(); // parse string
}
return '/Date(' + value + ')/';
} }
else { else {
throw new Error( throw new Error(
'Cannot cast object of type ' + util.getType(object) +
'Cannot convert object of type ' + util.getType(object) +
' to type ASPDate'); ' to type ASPDate');
} }
default: default:
throw new Error('Cannot cast object of type ' + util.getType(object) +
throw new Error('Cannot convert object of type ' + util.getType(object) +
' to type "' + type + '"'); ' to type "' + type + '"');
} }
}; };
// parse ASP.Net Date pattern,
// for example '/Date(1198908717056)/' or '/Date(1198908717056-0700)/'
// code from http://momentjs.com/
var ASPDateRegex = /^\/?Date\((\-?\d+)/i; var ASPDateRegex = /^\/?Date\((\-?\d+)/i;
/** /**
@ -578,7 +599,7 @@ util.preventDefault = function preventDefault (event) {
util.option = {}; util.option = {};
/** /**
* Cast a value as boolean
* Convert a value into a boolean
* @param {Boolean | function | undefined} value * @param {Boolean | function | undefined} value
* @param {Boolean} [defaultValue] * @param {Boolean} [defaultValue]
* @returns {Boolean} bool * @returns {Boolean} bool
@ -596,7 +617,7 @@ util.option.asBoolean = function (value, defaultValue) {
}; };
/** /**
* Cast a value as number
* Convert a value into a number
* @param {Boolean | function | undefined} value * @param {Boolean | function | undefined} value
* @param {Number} [defaultValue] * @param {Number} [defaultValue]
* @returns {Number} number * @returns {Number} number
@ -614,7 +635,7 @@ util.option.asNumber = function (value, defaultValue) {
}; };
/** /**
* Cast a value as string
* Convert a value into a string
* @param {String | function | undefined} value * @param {String | function | undefined} value
* @param {String} [defaultValue] * @param {String} [defaultValue]
* @returns {String} str * @returns {String} str
@ -632,7 +653,7 @@ util.option.asString = function (value, defaultValue) {
}; };
/** /**
* Cast a size or location in pixels or a percentage
* Convert a size or location into a string with pixels or a percentage
* @param {String | Number | function | undefined} value * @param {String | Number | function | undefined} value
* @param {String} [defaultValue] * @param {String} [defaultValue]
* @returns {String} size * @returns {String} size
@ -654,7 +675,7 @@ util.option.asSize = function (value, defaultValue) {
}; };
/** /**
* Cast a value as DOM element
* Convert a value into a DOM element
* @param {HTMLElement | function | undefined} value * @param {HTMLElement | function | undefined} value
* @param {HTMLElement} [defaultValue] * @param {HTMLElement} [defaultValue]
* @returns {HTMLElement | null} dom * @returns {HTMLElement | null} dom
@ -1136,7 +1157,7 @@ EventBus.prototype.emit = function (event, data, source) {
* Usage: * Usage:
* var dataSet = new DataSet({ * var dataSet = new DataSet({
* fieldId: '_id', * fieldId: '_id',
* fieldTypes: {
* convert: {
* // ... * // ...
* } * }
* }); * });
@ -1161,7 +1182,7 @@ EventBus.prototype.emit = function (event, data, source) {
* @param {Object} [options] Available options: * @param {Object} [options] Available options:
* {String} fieldId Field name of the id in the * {String} fieldId Field name of the id in the
* items, 'id' by default. * items, 'id' by default.
* {Object.<String, String} fieldTypes
* {Object.<String, String} convert
* A map with field names as key, * A map with field names as key,
* and the field type as value. * and the field type as value.
* @constructor DataSet * @constructor DataSet
@ -1173,17 +1194,17 @@ function DataSet (options) {
this.options = options || {}; this.options = options || {};
this.data = {}; // map with data indexed by id this.data = {}; // map with data indexed by id
this.fieldId = this.options.fieldId || 'id'; // name of the field containing id this.fieldId = this.options.fieldId || 'id'; // name of the field containing id
this.fieldTypes = {}; // field types by field name
this.convert = {}; // field types by field name
if (this.options.fieldTypes) {
for (var field in this.options.fieldTypes) {
if (this.options.fieldTypes.hasOwnProperty(field)) {
var value = this.options.fieldTypes[field];
if (this.options.convert) {
for (var field in this.options.convert) {
if (this.options.convert.hasOwnProperty(field)) {
var value = this.options.convert[field];
if (value == 'Date' || value == 'ISODate' || value == 'ASPDate') { if (value == 'Date' || value == 'ISODate' || value == 'ASPDate') {
this.fieldTypes[field] = 'Date';
this.convert[field] = 'Date';
} }
else { else {
this.fieldTypes[field] = value;
this.convert[field] = value;
} }
} }
} }
@ -1202,11 +1223,9 @@ function DataSet (options) {
* @param {function} callback Callback method. Called with three parameters: * @param {function} callback Callback method. Called with three parameters:
* {String} event * {String} event
* {Object | null} params * {Object | null} params
* {String} senderId
* @param {String} [id] Optional id for the sender, used to filter
* events triggered by the sender itself.
* {String | Number} senderId
*/ */
DataSet.prototype.subscribe = function (event, callback, id) {
DataSet.prototype.subscribe = function (event, callback) {
var subscribers = this.subscribers[event]; var subscribers = this.subscribers[event];
if (!subscribers) { if (!subscribers) {
subscribers = []; subscribers = [];
@ -1214,7 +1233,6 @@ DataSet.prototype.subscribe = function (event, callback, id) {
} }
subscribers.push({ subscribers.push({
id: id ? String(id) : null,
callback: callback callback: callback
}); });
}; };
@ -1266,9 +1284,10 @@ DataSet.prototype._trigger = function (event, params, senderId) {
* Adding an item will fail when there already is an item with the same id. * Adding an item will fail when there already is an item with the same id.
* @param {Object | Array | DataTable} data * @param {Object | Array | DataTable} data
* @param {String} [senderId] Optional sender id * @param {String} [senderId] Optional sender id
* @return {Array} addedIds Array with the ids of the added items
*/ */
DataSet.prototype.add = function (data, senderId) { DataSet.prototype.add = function (data, senderId) {
var addedItems = [],
var addedIds = [],
id, id,
me = this; me = this;
@ -1276,7 +1295,7 @@ DataSet.prototype.add = function (data, senderId) {
// Array // Array
for (var i = 0, len = data.length; i < len; i++) { for (var i = 0, len = data.length; i < len; i++) {
id = me._addItem(data[i]); id = me._addItem(data[i]);
addedItems.push(id);
addedIds.push(id);
} }
} }
else if (util.isDataTable(data)) { else if (util.isDataTable(data)) {
@ -1290,31 +1309,34 @@ DataSet.prototype.add = function (data, senderId) {
} }
id = me._addItem(item); id = me._addItem(item);
addedItems.push(id);
addedIds.push(id);
} }
} }
else if (data instanceof Object) { else if (data instanceof Object) {
// Single item // Single item
id = me._addItem(data); id = me._addItem(data);
addedItems.push(id);
addedIds.push(id);
} }
else { else {
throw new Error('Unknown dataType'); throw new Error('Unknown dataType');
} }
if (addedItems.length) {
this._trigger('add', {items: addedItems}, senderId);
if (addedIds.length) {
this._trigger('add', {items: addedIds}, senderId);
} }
return addedIds;
}; };
/** /**
* Update existing items. When an item does not exist, it will be created * Update existing items. When an item does not exist, it will be created
* @param {Object | Array | DataTable} data * @param {Object | Array | DataTable} data
* @param {String} [senderId] Optional sender id * @param {String} [senderId] Optional sender id
* @return {Array} updatedIds The ids of the added or updated items
*/ */
DataSet.prototype.update = function (data, senderId) { DataSet.prototype.update = function (data, senderId) {
var addedItems = [],
updatedItems = [],
var addedIds = [],
updatedIds = [],
me = this, me = this,
fieldId = me.fieldId; fieldId = me.fieldId;
@ -1323,12 +1345,12 @@ DataSet.prototype.update = function (data, senderId) {
if (me.data[id]) { if (me.data[id]) {
// update item // update item
id = me._updateItem(item); id = me._updateItem(item);
updatedItems.push(id);
updatedIds.push(id);
} }
else { else {
// add new item // add new item
id = me._addItem(item); id = me._addItem(item);
addedItems.push(id);
addedIds.push(id);
} }
}; };
@ -1359,12 +1381,14 @@ DataSet.prototype.update = function (data, senderId) {
throw new Error('Unknown dataType'); throw new Error('Unknown dataType');
} }
if (addedItems.length) {
this._trigger('add', {items: addedItems}, senderId);
if (addedIds.length) {
this._trigger('add', {items: addedIds}, senderId);
} }
if (updatedItems.length) {
this._trigger('update', {items: updatedItems}, senderId);
if (updatedIds.length) {
this._trigger('update', {items: updatedIds}, senderId);
} }
return addedIds.concat(updatedIds);
}; };
/** /**
@ -1391,7 +1415,7 @@ DataSet.prototype.update = function (data, senderId) {
* {Object} options An Object with options. Available options: * {Object} options An Object with options. Available options:
* {String} [type] Type of data to be returned. Can * {String} [type] Type of data to be returned. Can
* be 'DataTable' or 'Array' (default) * be 'DataTable' or 'Array' (default)
* {Object.<String, String>} [fieldTypes]
* {Object.<String, String>} [convert]
* {String[]} [fields] field names to be returned * {String[]} [fields] field names to be returned
* {function} [filter] filter items * {function} [filter] filter items
* {String | function} [order] Order the items by * {String | function} [order] Order the items by
@ -1448,14 +1472,14 @@ DataSet.prototype.get = function (args) {
} }
// build options // build options
var fieldTypes = options && options.fieldTypes || this.options.fieldTypes;
var convert = options && options.convert || this.options.convert;
var filter = options && options.filter; var filter = options && options.filter;
var items = [], item, itemId, i, len; var items = [], item, itemId, i, len;
// cast items
// convert items
if (id != undefined) { if (id != undefined) {
// return a single item // return a single item
item = me._getItem(id, fieldTypes);
item = me._getItem(id, convert);
if (filter && !filter(item)) { if (filter && !filter(item)) {
item = null; item = null;
} }
@ -1463,7 +1487,7 @@ DataSet.prototype.get = function (args) {
else if (ids != undefined) { else if (ids != undefined) {
// return a subset of items // return a subset of items
for (i = 0, len = ids.length; i < len; i++) { for (i = 0, len = ids.length; i < len; i++) {
item = me._getItem(ids[i], fieldTypes);
item = me._getItem(ids[i], convert);
if (!filter || filter(item)) { if (!filter || filter(item)) {
items.push(item); items.push(item);
} }
@ -1473,7 +1497,7 @@ DataSet.prototype.get = function (args) {
// return all items // return all items
for (itemId in this.data) { for (itemId in this.data) {
if (this.data.hasOwnProperty(itemId)) { if (this.data.hasOwnProperty(itemId)) {
item = me._getItem(itemId, fieldTypes);
item = me._getItem(itemId, convert);
if (!filter || filter(item)) { if (!filter || filter(item)) {
items.push(item); items.push(item);
} }
@ -1549,7 +1573,7 @@ DataSet.prototype.getIds = function (options) {
var data = this.data, var data = this.data,
filter = options && options.filter, filter = options && options.filter,
order = options && options.order, order = options && options.order,
fieldTypes = options && options.fieldTypes || this.options.fieldTypes,
convert = options && options.convert || this.options.convert,
i, i,
len, len,
id, id,
@ -1564,7 +1588,7 @@ DataSet.prototype.getIds = function (options) {
items = []; items = [];
for (id in data) { for (id in data) {
if (data.hasOwnProperty(id)) { if (data.hasOwnProperty(id)) {
item = this._getItem(id, fieldTypes);
item = this._getItem(id, convert);
if (filter(item)) { if (filter(item)) {
items.push(item); items.push(item);
} }
@ -1581,7 +1605,7 @@ DataSet.prototype.getIds = function (options) {
// create unordered list // create unordered list
for (id in data) { for (id in data) {
if (data.hasOwnProperty(id)) { if (data.hasOwnProperty(id)) {
item = this._getItem(id, fieldTypes);
item = this._getItem(id, convert);
if (filter(item)) { if (filter(item)) {
ids.push(item[this.fieldId]); ids.push(item[this.fieldId]);
} }
@ -1625,7 +1649,7 @@ DataSet.prototype.getIds = function (options) {
* The order of the items is not determined. * The order of the items is not determined.
* @param {function} callback * @param {function} callback
* @param {Object} [options] Available options: * @param {Object} [options] Available options:
* {Object.<String, String>} [fieldTypes]
* {Object.<String, String>} [convert]
* {String[]} [fields] filter fields * {String[]} [fields] filter fields
* {function} [filter] filter items * {function} [filter] filter items
* {String | function} [order] Order the items by * {String | function} [order] Order the items by
@ -1633,7 +1657,7 @@ DataSet.prototype.getIds = function (options) {
*/ */
DataSet.prototype.forEach = function (callback, options) { DataSet.prototype.forEach = function (callback, options) {
var filter = options && options.filter, var filter = options && options.filter,
fieldTypes = options && options.fieldTypes || this.options.fieldTypes,
convert = options && options.convert || this.options.convert,
data = this.data, data = this.data,
item, item,
id; id;
@ -1652,7 +1676,7 @@ DataSet.prototype.forEach = function (callback, options) {
// unordered // unordered
for (id in data) { for (id in data) {
if (data.hasOwnProperty(id)) { if (data.hasOwnProperty(id)) {
item = this._getItem(id, fieldTypes);
item = this._getItem(id, convert);
if (!filter || filter(item)) { if (!filter || filter(item)) {
callback(item, id); callback(item, id);
} }
@ -1665,7 +1689,7 @@ DataSet.prototype.forEach = function (callback, options) {
* Map every item in the dataset. * Map every item in the dataset.
* @param {function} callback * @param {function} callback
* @param {Object} [options] Available options: * @param {Object} [options] Available options:
* {Object.<String, String>} [fieldTypes]
* {Object.<String, String>} [convert]
* {String[]} [fields] filter fields * {String[]} [fields] filter fields
* {function} [filter] filter items * {function} [filter] filter items
* {String | function} [order] Order the items by * {String | function} [order] Order the items by
@ -1674,15 +1698,15 @@ DataSet.prototype.forEach = function (callback, options) {
*/ */
DataSet.prototype.map = function (callback, options) { DataSet.prototype.map = function (callback, options) {
var filter = options && options.filter, var filter = options && options.filter,
fieldTypes = options && options.fieldTypes || this.options.fieldTypes,
convert = options && options.convert || this.options.convert,
mappedItems = [], mappedItems = [],
data = this.data, data = this.data,
item; item;
// cast and filter items
// convert and filter items
for (var id in data) { for (var id in data) {
if (data.hasOwnProperty(id)) { if (data.hasOwnProperty(id)) {
item = this._getItem(id, fieldTypes);
item = this._getItem(id, convert);
if (!filter || filter(item)) { if (!filter || filter(item)) {
mappedItems.push(callback(item, id)); mappedItems.push(callback(item, id));
} }
@ -1745,46 +1769,66 @@ DataSet.prototype._sort = function (items, order) {
/** /**
* Remove an object by pointer or by id * Remove an object by pointer or by id
* @param {String | Number | Object | Array} id Object or id, or an array with
* objects or ids to be removed
* @param {String | Number | Object | Array} id Object or id, or an array with
* objects or ids to be removed
* @param {String} [senderId] Optional sender id * @param {String} [senderId] Optional sender id
* @return {Array} removedIds
*/ */
DataSet.prototype.remove = function (id, senderId) { DataSet.prototype.remove = function (id, senderId) {
var removedItems = [],
i, len;
var removedIds = [],
i, len, removedId;
if (util.isNumber(id) || util.isString(id)) {
delete this.data[id];
delete this.internalIds[id];
removedItems.push(id);
}
else if (id instanceof Array) {
if (id instanceof Array) {
for (i = 0, len = id.length; i < len; i++) { for (i = 0, len = id.length; i < len; i++) {
this.remove(id[i]);
removedId = this._remove(id[i]);
if (removedId != null) {
removedIds.push(removedId);
}
} }
removedItems = items.concat(id);
} }
else if (id instanceof Object) {
// search for the object
for (i in this.data) {
if (this.data.hasOwnProperty(i)) {
if (this.data[i] == id) {
delete this.data[i];
delete this.internalIds[i];
removedItems.push(i);
}
}
else {
removedId = this._remove(id);
if (removedId != null) {
removedIds.push(removedId);
} }
} }
if (removedItems.length) {
this._trigger('remove', {items: removedItems}, senderId);
if (removedIds.length) {
this._trigger('remove', {items: removedIds}, senderId);
} }
return removedIds;
};
/**
* Remove an item by its id
* @param {Number | String | Object} id id or item
* @returns {Number | String | null} id
* @private
*/
DataSet.prototype._remove = function (id) {
if (util.isNumber(id) || util.isString(id)) {
if (this.data[id]) {
delete this.data[id];
delete this.internalIds[id];
return id;
}
}
else if (id instanceof Object) {
var itemId = id[this.fieldId];
if (itemId && this.data[itemId]) {
delete this.data[itemId];
delete this.internalIds[itemId];
return itemId;
}
}
return null;
}; };
/** /**
* Clear the data * Clear the data
* @param {String} [senderId] Optional sender id * @param {String} [senderId] Optional sender id
* @return {Array} removedIds The ids of all removed items
*/ */
DataSet.prototype.clear = function (senderId) { DataSet.prototype.clear = function (senderId) {
var ids = Object.keys(this.data); var ids = Object.keys(this.data);
@ -1793,6 +1837,8 @@ DataSet.prototype.clear = function (senderId) {
this.internalIds = {}; this.internalIds = {};
this._trigger('remove', {items: ids}, senderId); this._trigger('remove', {items: ids}, senderId);
return ids;
}; };
/** /**
@ -1854,13 +1900,13 @@ DataSet.prototype.min = function (field) {
DataSet.prototype.distinct = function (field) { DataSet.prototype.distinct = function (field) {
var data = this.data, var data = this.data,
values = [], values = [],
fieldType = this.options.fieldTypes[field],
fieldType = this.options.convert[field],
count = 0; count = 0;
for (var prop in data) { for (var prop in data) {
if (data.hasOwnProperty(prop)) { if (data.hasOwnProperty(prop)) {
var item = data[prop]; var item = data[prop];
var value = util.cast(item[field], fieldType);
var value = util.convert(item[field], fieldType);
var exists = false; var exists = false;
for (var i = 0; i < count; i++) { for (var i = 0; i < count; i++) {
if (values[i] == value) { if (values[i] == value) {
@ -1904,8 +1950,8 @@ DataSet.prototype._addItem = function (item) {
var d = {}; var d = {};
for (var field in item) { for (var field in item) {
if (item.hasOwnProperty(field)) { if (item.hasOwnProperty(field)) {
var type = this.fieldTypes[field]; // type may be undefined
d[field] = util.cast(item[field], type);
var fieldType = this.convert[field]; // type may be undefined
d[field] = util.convert(item[field], fieldType);
} }
} }
this.data[id] = d; this.data[id] = d;
@ -1914,13 +1960,13 @@ DataSet.prototype._addItem = function (item) {
}; };
/** /**
* Get an item. Fields can be casted to a specific type
* Get an item. Fields can be converted to a specific type
* @param {String} id * @param {String} id
* @param {Object.<String, String>} [fieldTypes] Cast field types
* @param {Object.<String, String>} [convert] field types to convert
* @return {Object | null} item * @return {Object | null} item
* @private * @private
*/ */
DataSet.prototype._getItem = function (id, fieldTypes) {
DataSet.prototype._getItem = function (id, convert) {
var field, value; var field, value;
// get the item from the dataset // get the item from the dataset
@ -1929,35 +1975,35 @@ DataSet.prototype._getItem = function (id, fieldTypes) {
return null; return null;
} }
// cast the items field types
var casted = {},
// convert the items field types
var converted = {},
fieldId = this.fieldId, fieldId = this.fieldId,
internalIds = this.internalIds; internalIds = this.internalIds;
if (fieldTypes) {
if (convert) {
for (field in raw) { for (field in raw) {
if (raw.hasOwnProperty(field)) { if (raw.hasOwnProperty(field)) {
value = raw[field]; value = raw[field];
// output all fields, except internal ids // output all fields, except internal ids
if ((field != fieldId) || !(value in internalIds)) { if ((field != fieldId) || !(value in internalIds)) {
casted[field] = util.cast(value, fieldTypes[field]);
converted[field] = util.convert(value, convert[field]);
} }
} }
} }
} }
else { else {
// no field types specified, no casting needed
// no field types specified, no converting needed
for (field in raw) { for (field in raw) {
if (raw.hasOwnProperty(field)) { if (raw.hasOwnProperty(field)) {
value = raw[field]; value = raw[field];
// output all fields, except internal ids // output all fields, except internal ids
if ((field != fieldId) || !(value in internalIds)) { if ((field != fieldId) || !(value in internalIds)) {
casted[field] = value;
converted[field] = value;
} }
} }
} }
} }
return casted;
return converted;
}; };
/** /**
@ -1982,8 +2028,8 @@ DataSet.prototype._updateItem = function (item) {
// merge with current item // merge with current item
for (var field in item) { for (var field in item) {
if (item.hasOwnProperty(field)) { if (item.hasOwnProperty(field)) {
var type = this.fieldTypes[field]; // type may be undefined
d[field] = util.cast(item[field], type);
var fieldType = this.convert[field]; // type may be undefined
d[field] = util.convert(item[field], fieldType);
} }
} }
@ -2118,7 +2164,7 @@ DataView.prototype.setData = function (data) {
* {Object} options An Object with options. Available options: * {Object} options An Object with options. Available options:
* {String} [type] Type of data to be returned. Can * {String} [type] Type of data to be returned. Can
* be 'DataTable' or 'Array' (default) * be 'DataTable' or 'Array' (default)
* {Object.<String, String>} [fieldTypes]
* {Object.<String, String>} [convert]
* {String[]} [fields] field names to be returned * {String[]} [fields] field names to be returned
* {function} [filter] filter items * {function} [filter] filter items
* {String | function} [order] Order the items by * {String | function} [order] Order the items by
@ -3097,8 +3143,8 @@ Range.prototype.setRange = function(start, end) {
* @private * @private
*/ */
Range.prototype._applyRange = function(start, end) { Range.prototype._applyRange = function(start, end) {
var newStart = (start != null) ? util.cast(start, 'Number') : this.start;
var newEnd = (end != null) ? util.cast(end, 'Number') : this.end;
var newStart = (start != null) ? util.convert(start, 'Number') : this.start;
var newEnd = (end != null) ? util.convert(end, 'Number') : this.end;
var diff; var diff;
// check for valid number // check for valid number
@ -4615,8 +4661,8 @@ TimeAxis.prototype.reflow = function () {
// calculate range and step // calculate range and step
this._updateConversion(); this._updateConversion();
var start = util.cast(range.start, 'Date'),
end = util.cast(range.end, 'Date'),
var start = util.convert(range.start, 'Date'),
end = util.convert(range.end, 'Date'),
minimumStep = this.toTime((props.minorCharWidth || 10) * 5) - this.toTime(0); minimumStep = this.toTime((props.minorCharWidth || 10) * 5) - this.toTime(0);
this.step = new TimeStep(start, end, minimumStep); this.step = new TimeStep(start, end, minimumStep);
changed += update(props.range, 'start', start.valueOf()); changed += update(props.range, 'start', start.valueOf());
@ -6321,7 +6367,7 @@ GroupSet.prototype.setGroups = function setGroups(groups) {
} }
else { else {
this.groupsData = new DataSet({ this.groupsData = new DataSet({
fieldTypes: {
convert: {
start: 'Date', start: 'Date',
end: 'Date' end: 'Date'
} }
@ -6838,7 +6884,7 @@ Timeline.prototype.setItems = function(items) {
} }
if (!(items instanceof DataSet)) { if (!(items instanceof DataSet)) {
newItemSet = new DataSet({ newItemSet = new DataSet({
fieldTypes: {
convert: {
start: 'Date', start: 'Date',
end: 'Date' end: 'Date'
} }
@ -7319,13 +7365,13 @@ Timeline.prototype.getItemRange = function getItemRange() {
next(); next();
} }
if (token == 'false') { if (token == 'false') {
token = false; // cast to boolean
token = false; // convert to boolean
} }
else if (token == 'true') { else if (token == 'true') {
token = true; // cast to boolean
token = true; // convert to boolean
} }
else if (!isNaN(Number(token))) { else if (!isNaN(Number(token))) {
token = Number(token); // cast to number
token = Number(token); // convert to number
} }
tokenType = TOKENTYPE.IDENTIFIER; tokenType = TOKENTYPE.IDENTIFIER;
return; return;

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


Loading…
Cancel
Save