Browse Source

Fix conflict

codeClimate
Yotam Berkowitz 8 years ago
parent
commit
e3fdbcf535
27 changed files with 1727 additions and 1046 deletions
  1. +1
    -1
      README.md
  2. +5
    -3
      docs/data/dataset.html
  3. +1
    -1
      docs/graph2d/index.html
  4. +26
    -7
      docs/timeline/index.html
  5. +16
    -9
      examples/graph3d/11_tooltips.html
  6. +1
    -1
      examples/timeline/items/pointItems.html
  7. +77
    -0
      examples/timeline/other/horizontalScroll.html
  8. +123
    -0
      examples/timeline/other/usingReact.html
  9. +92
    -0
      examples/timeline/other/verticalScroll.html
  10. +36
    -26
      lib/DataSet.js
  11. +46
    -36
      lib/DataView.js
  12. +588
    -841
      lib/graph3d/Graph3d.js
  13. +436
    -0
      lib/graph3d/Settings.js
  14. +104
    -45
      lib/timeline/Core.js
  15. +30
    -13
      lib/timeline/Range.js
  16. +6
    -28
      lib/timeline/Timeline.js
  17. +9
    -7
      lib/timeline/component/Group.js
  18. +13
    -4
      lib/timeline/component/ItemSet.js
  19. +9
    -0
      lib/timeline/component/css/item.css
  20. +22
    -1
      lib/timeline/component/css/panel.css
  21. +1
    -0
      lib/timeline/component/item/BoxItem.js
  22. +48
    -18
      lib/timeline/component/item/Item.js
  23. +2
    -1
      lib/timeline/component/item/PointItem.js
  24. +1
    -0
      lib/timeline/component/item/RangeItem.js
  25. +2
    -0
      lib/timeline/optionsTimeline.js
  26. +26
    -0
      lib/util.js
  27. +6
    -4
      misc/how_to_help.md

+ 1
- 1
README.md View File

@ -154,7 +154,7 @@ slow, so when only the non-minified library is needed, one can use the
## Custom builds
The folder `dist` contains bundled versions of vis.js for direct use in the browser. These bundles contain the all visualizations and includes external dependencies such as hammer.js and moment.js.
The folder `dist` contains bundled versions of vis.js for direct use in the browser. These bundles contain all the visualizations and include external dependencies such as hammer.js and moment.js.
The source code of vis.js consists of commonjs modules, which makes it possible to create custom bundles using tools like [Browserify](http://browserify.org/) or [Webpack](http://webpack.github.io/). This can be bundling just one visualization like the Timeline, or bundling vis.js as part of your own browserified web application.

+ 5
- 3
docs/data/dataset.html View File

@ -598,9 +598,11 @@ function (event, properties, senderId) {
<code>update</code>, and <code>remove</code>,
<code>properties</code> is always an object containing a property
<code>items</code>, which contains an array with the ids of the affected
items. The <code>update</code> event has an extra field <code>oldData</code>
containing the original data of the updated items, and a field <code>data</code>
containing the changes: the properties of the items that are being updated.
items. The <code>update</code> and <code>remove</code> events have an extra
field <code>oldData</code> containing the original data of the items in the
dataset before the items were updated or removed. The <code>update</code>
event also contains a field <code>data</code> containing the changes:
the properties of the items that are being updated.
</td>
</tr>
<tr>

+ 1
- 1
docs/graph2d/index.html View File

@ -1202,7 +1202,7 @@ function (option, path) {
<tr>
<td>on(event, callback)</td>
<td>none</td>
<td>Create an event listener. The callback function is invoked every time the event is triggered. Avialable events: <code>rangechange</code>, <code>rangechanged</code>, <code>select</code>. The callback function is invoked as <code>callback(properties)</code>, where <code>properties</code> is an object containing event specific properties. See section <a href="#Events">Events for more information</a>.</td>
<td>Create an event listener. The callback function is invoked every time the event is triggered. Available events: <code>rangechange</code>, <code>rangechanged</code>, <code>select</code>. The callback function is invoked as <code>callback(properties)</code>, where <code>properties</code> is an object containing event specific properties. See section <a href="#Events">Events for more information</a>.</td>
</tr>
<tr>

+ 26
- 7
docs/timeline/index.html View File

@ -675,6 +675,15 @@ function (option, path) {
</td>
</tr>
<tr>
<td>horizontalScroll</td>
<td>Boolean</td>
<td>false</td>
<td>This option allows you to scroll horizontally to move backwards and forwards in the time range.
Only applicable when option <code>zoomCtrl</code> is defined or <code>zoomable</code> is <code>false</code>. Notice that defining this option as <code>true</code> will override <code>verticalScroll</code> scroll event but not eliminate the vertical scrollbar.
</td>
</tr>
<tr>
<td>itemsAlwaysDraggable</td>
<td>boolean</td>
@ -1015,6 +1024,14 @@ function (option, path) {
</td>
</tr>
<tr>
<td>verticalScroll</td>
<td>Boolean</td>
<td>false</td>
<td> Show a vertical scroll on the side of the group list and link it to the scroll event when zoom is not triggered. Notice that defining this option as <code>true</code> will NOT override <code>horizontalScroll</code>. The scroll event will be vertically ignored, but a vertical scrollbar will be visible
</td>
</tr>
<tr>
<td>width</td>
<td>String or Number</td>
@ -1194,7 +1211,7 @@ document.getElementById('myTimeline').onclick = function (event) {
<tr>
<td>on(event, callback)</td>
<td>none</td>
<td>Create an event listener. The callback function is invoked every time the event is triggered. Avialable events: <code>rangechange</code>, <code>rangechanged</code>, <code>select</code>, <code>itemover</code>, <code>itemout</code>. The callback function is invoked as <code>callback(properties)</code>, where <code>properties</code> is an object containing event specific properties. See section <a href="#Events">Events for more information</a>.</td>
<td>Create an event listener. The callback function is invoked every time the event is triggered. Available events: <code>rangechange</code>, <code>rangechanged</code>, <code>select</code>, <code>itemover</code>, <code>itemout</code>. The callback function is invoked as <code>callback(properties)</code>, where <code>properties</code> is an object containing event specific properties. See section <a href="#Events">Events for more information</a>.</td>
</tr>
<tr>
@ -1233,7 +1250,7 @@ document.getElementById('myTimeline').onclick = function (event) {
<td>none</td>
<td>Adjust the time of a custom time bar.
Parameter <code>time</code> can be a Date object, numeric timestamp, or ISO date string.
Parameter <code>id</code> is the idof the custom time bar, and is <code>undefined</code> by default.
Parameter <code>id</code> is the id of the custom time bar, and is <code>undefined</code> by default.
</td>
</tr>
@ -1557,7 +1574,9 @@ var items = new vis.DataSet([
</p>
<ul>
<li><code>item</code>: the item being manipulated</li>
<li><code>callback</code>: a callback function which must be invoked to report back. The callback must be invoked as <code>callback(item or null)</code>. Here, <code>item</code> can contain changes to the passed item. Parameter `item` typically contains fields `content`, `start`, and optionally `end`. The type of `start` and `end` is determined by the DataSet type configuration and is `Date` by default. When invoked as <code>callback(null)</code>, the action will be cancelled.</li>
<li><code>callback</code>: a callback function which must be invoked to report back. The callback must be invoked as <code>callback(item)</code> or <code>callback(null)</code>.
Here, <code>item</code> can contain changes to the passed item. Parameter <code>item</code> typically contains fields `content`, `start`, and optionally `end`. The type of `start`
and `end` is determined by the DataSet type configuration and is `Date` by default. When invoked as <code>callback(null)</code>, the action will be cancelled.</li>
</ul>
<p>
@ -1583,7 +1602,7 @@ var items = new vis.DataSet([
<h2 id="Templates">Templates</h2>
<p>
Timeline supports templates to format item contents. Any template engine (such as <a href="http://handlebarsjs.com/">handlebars</a> or <a href="http://mustache.github.io/">mustache</a>) can be used, and one can also manually build HTML. In the options, one can provide a template handler. This handler is a function accepting an items data as argument, and outputs formatted HTML:
Timeline supports templates to format item contents. Any template engine (such as <a href="http://handlebarsjs.com/">handlebars</a> or <a href="http://mustache.github.io/">mustache</a>) can be used, and one can also manually build HTML. In the options, one can provide a template handler. This handler is a function accepting an item's data as argument, and outputs formatted HTML:
</p>
<pre class="prettyprint lang-js">var options = {
@ -1680,7 +1699,7 @@ var options = {
<h3>Create a new locale</h3>
To load a locale into the Timeline not supported by default, one can add a new locale to the option <code>locales</code>:
To load a locale (that is not supported by default) into the Timeline, one can add a new locale to the option <code>locales</code>:
<pre class="prettyprint lang-js">var options = {
locales: {
@ -1736,7 +1755,7 @@ var options = {
<h2 id="Time_zone">Time zone</h2>
<p>
By default, the Timeline displays time in local time. To display a Timeline in an other time zone or in UTC, the date constructor can be overloaded via the configuration option <code>moment</code>, which by default is the constructor function of moment.js. More information about UTC with moment.js can be found in the docs: <a href="http://momentjs.com/docs/#/parsing/utc/">http://momentjs.com/docs/#/parsing/utc/</a>.
By default, the Timeline displays time in local time. To display a Timeline in another time zone or in UTC, the date constructor can be overloaded via the configuration option <code>moment</code>, which by default is the constructor function of moment.js. More information about UTC with moment.js can be found in the docs: <a href="http://momentjs.com/docs/#/parsing/utc/">http://momentjs.com/docs/#/parsing/utc/</a>.
</p>
<p>
@ -1810,7 +1829,7 @@ var options = {
<td>Days</td><td><code>vis-date1</code>, <code>vis-date2</code>, ..., <code>vis-date31</code></td>
</tr>
<tr>
<td>Months</td><td><code>vis-januari</code>, <code>vis-februari</code>, <code>vis-march</code>, <code>vis-april</code>, <code>vis-may</code>, <code>vis-june</code>, <code>vis-july</code>, <code>vis-august</code>, <code>vis-september</code>, <code>vis-october</code>, <code>vis-november</code>, <code>vis-december</code></td>
<td>Months</td><td><code>vis-january</code>, <code>vis-february</code>, <code>vis-march</code>, <code>vis-april</code>, <code>vis-may</code>, <code>vis-june</code>, <code>vis-july</code>, <code>vis-august</code>, <code>vis-september</code>, <code>vis-october</code>, <code>vis-november</code>, <code>vis-december</code></td>
</tr>
<tr>
<td>Years</td><td><code>vis-year2014</code>, <code>vis-year2015</code>, ...</td>

+ 16
- 9
examples/graph3d/11_tooltips.html View File

@ -5,6 +5,12 @@
<style>
body {font: 10pt arial;}
div#info {
width : 600px;
text-align: center;
margin-top: 2em;
font-size : 1.2em;
}
</style>
<script type="text/javascript" src="../../dist/vis.js"></script>
@ -24,11 +30,11 @@
// Create and populate a data table.
data = new vis.DataSet();
var extra_content = [
'Arbitrary information',
'You can access data from the point source object',
'Tooltip example content',
];
var extra_content = [
'Arbitrary information',
'You can access data from the point source object',
'Tooltip example content',
];
// create some nice looking data with sin/cos
var steps = 5; // number of datapoints will be steps*steps
@ -42,7 +48,7 @@
data.add({x:x, y:y, z: z, style:value, extra: extra_content[(x*y) % extra_content.length]});
}
else {
data.add({x:x, y:y, z: z, extra: extra_content[(x*y) % extra_content.length]});
data.add({x:x, y:y, z: z, extra: extra_content[(x*y) % extra_content.length]});
}
}
}
@ -61,8 +67,8 @@
//tooltip: true,
tooltip: function (point) {
// parameter point contains properties x, y, z, and data
// data is the original object passed to the point constructor
return 'value: <b>' + point.z + '</b><br>' + point.data.extra;
// data is the original object passed to the point constructor
return 'value: <b>' + point.z + '</b><br>' + point.data.extra;
},
keepAspectRatio: true,
@ -106,6 +112,7 @@
<div id="mygraph"></div>
<div id="info"></div>
<div id="info">Hover the mouse cursor over the graph to see tooltips.</div>
</body>
</html>

+ 1
- 1
examples/timeline/items/pointItems.html View File

@ -49,7 +49,7 @@
var options = {
// Set global item type. Type can also be specified for items individually
// Available types: 'box' (default), 'point', 'range', 'rangeoverflow'
// Available types: 'box' (default), 'point', 'range'
type: 'point',
showMajorLabels: false
};

+ 77
- 0
examples/timeline/other/horizontalScroll.html View File

@ -0,0 +1,77 @@
<html>
<head>
<title>Timeline | Horizontal Scroll Option</title>
<script src="../../../dist/vis.js"></script>
<link href="../../../dist/vis.css" rel="stylesheet" type="text/css" />
<script src="../../googleAnalytics.js"></script>
</head>
<body>
<h1>Timeline horizontal scroll option</h1>
<div id="mytimeline"></div>
<script>
// create groups
var numberOfGroups = 25;
var groups = new vis.DataSet()
for (var i = 0; i < numberOfGroups; i++) {
groups.add({
id: i,
content: 'Truck&nbsp;' + i
})
}
// create items
var numberOfItems = 1000;
var items = new vis.DataSet();
var itemsPerGroup = Math.round(numberOfItems/numberOfGroups);
for (var truck = 0; truck < numberOfGroups; truck++) {
var date = new Date();
for (var order = 0; order < itemsPerGroup; order++) {
date.setHours(date.getHours() + 4 * (Math.random() < 0.2));
var start = new Date(date);
date.setHours(date.getHours() + 2 + Math.floor(Math.random()*4));
var end = new Date(date);
items.add({
id: order + itemsPerGroup * truck,
group: truck,
start: start,
end: end,
content: 'Order ' + order
});
}
}
// specify options
var options = {
stack: true,
horizontalScroll: true,
zoomKey: 'ctrlKey',
maxHeight: 400,
start: new Date(),
end: new Date(1000*60*60*24 + (new Date()).valueOf()),
editable: true,
margin: {
item: 10, // minimal margin between items
axis: 5 // minimal margin between items and the axis
},
orientation: 'top'
};
// create a Timeline
var container = document.getElementById('mytimeline');
timeline = new vis.Timeline(container, items, groups, options);
</script>
</body>
</html>

+ 123
- 0
examples/timeline/other/usingReact.html View File

@ -0,0 +1,123 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>React Components in templates</title>
</head>
<body>
<div id='root'></div>
<!--
For ease of use, we are including the React, ReactDOM and Babel CDN
builds to make getting started as fast as possible.
In production, you'll want to instead look at using something
like Gulp, Grunt or WebPack (my personal recommendation)
to compile JSX into JavaScript. Also, check out:
http://facebook.github.io/react/docs/tooling-integration.html
-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.2/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.2/react-dom.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script>
<script src="../../../dist/vis.js"></script>
<link href="../../../dist/vis.css" rel="stylesheet" type="text/css" />
<!--
This is where you link to your React code. Can be .js or .jsx
extension, doesn't really matter.
-->
<script type="text/babel">
var timeline;
// create groups
var numberOfGroups = 25;
var groups = new vis.DataSet()
for (var i = 0; i < numberOfGroups; i++) {
groups.add({
id: i,
content: 'Truck ' + i
})
}
// create items
var numberOfItems = 1000;
var items = new vis.DataSet();
var itemsPerGroup = Math.round(numberOfItems/numberOfGroups);
for (var truck = 0; truck < numberOfGroups; truck++) {
var date = new Date();
for (var order = 0; order < itemsPerGroup; order++) {
date.setHours(date.getHours() + 4 * (Math.random() < 0.2));
var start = new Date(date);
date.setHours(date.getHours() + 2 + Math.floor(Math.random()*4));
var end = new Date(date);
items.add({
id: order + itemsPerGroup * truck,
group: truck,
start: start,
end: end,
content: 'Order ' + order
});
}
}
var GroupTemplate = React.createClass({
render: function() {
var { group } = this.props;
return <div>
<label>{group.content}</label>
</div>
}
})
var ItemTemplate = React.createClass({
render: function() {
var { item } = this.props;
return <div>
<label>{item.content}</label>
</div>
}
})
// specify options
var options = {
orientation: 'top',
maxHeight: 400,
start: new Date(),
end: new Date(1000*60*60*24 + (new Date()).valueOf()),
editable: true,
template: function (item, element) {
ReactDOM.unmountComponentAtNode(element);
return ReactDOM.render(<ItemTemplate item={item} />, element);
},
groupTemplate: function (group, element) {
ReactDOM.unmountComponentAtNode(element);
return ReactDOM.render(<GroupTemplate group={group} />, element);
}
}
var VisTimeline = React.createClass({
componentDidMount: function() {
return initTimeline();
},
render: function() {
return <div>
<h1>Vis timline with React</h1>
<h2>Using react components for items and group templates</h2>
<div id="mytimeline"></div>
</div>
}
});
function initTimeline() {
var container = document.getElementById('mytimeline');
timeline = new vis.Timeline(container, items, groups, options);
}
ReactDOM.render(<VisTimeline />, document.getElementById('root'));
</script>
</body>
</html>

+ 92
- 0
examples/timeline/other/verticalScroll.html View File

@ -0,0 +1,92 @@
<html>
<head>
<title>Timeline | Vertical Scroll Option</title>
<script src="../../../dist/vis.js"></script>
<link href="../../../dist/vis.css" rel="stylesheet" type="text/css" />
<script src="../../googleAnalytics.js"></script>
</head>
<body>
<h1>Timeline vertical scroll option</h1>
<h2>With <code>
verticalScroll: true,
zoomKey: 'ctrlKey'</code>
</h2>
<div id="mytimeline1"></div>
<h2>With <code>
horizontalScroll: true,
verticalScroll: true,
zoomKey: 'ctrlKey'</code>
</h2>
<div id="mytimeline2"></div>
<script>
// create groups
var numberOfGroups = 25;
var groups = new vis.DataSet()
for (var i = 0; i < numberOfGroups; i++) {
groups.add({
id: i,
content: 'Truck&nbsp;' + i
})
}
// create items
var numberOfItems = 1000;
var items = new vis.DataSet();
var itemsPerGroup = Math.round(numberOfItems/numberOfGroups);
for (var truck = 0; truck < numberOfGroups; truck++) {
var date = new Date();
for (var order = 0; order < itemsPerGroup; order++) {
date.setHours(date.getHours() + 4 * (Math.random() < 0.2));
var start = new Date(date);
date.setHours(date.getHours() + 2 + Math.floor(Math.random()*4));
var end = new Date(date);
items.add({
id: order + itemsPerGroup * truck,
group: truck,
start: start,
end: end,
content: 'Order ' + order
});
}
}
// specify options
var options = {
stack: true,
verticalScroll: true,
zoomKey: 'ctrlKey',
maxHeight: 200,
start: new Date(),
end: new Date(1000*60*60*24 + (new Date()).valueOf()),
editable: true,
margin: {
item: 10, // minimal margin between items
axis: 5 // minimal margin between items and the axis
},
orientation: 'top'
};
// create a Timeline
options1 = Object.assign({}, options)
var container1 = document.getElementById('mytimeline1');
timeline1 = new vis.Timeline(container1, items, groups, options1);
options2 = Object.assign({horizontalScroll: true}, options)
var container2 = document.getElementById('mytimeline2');
timeline2 = new vis.Timeline(container2, items, groups, options2);
</script>
</body>
</html>

+ 36
- 26
lib/DataSet.js View File

@ -668,25 +668,26 @@ DataSet.prototype._sort = function (items, order) {
*/
DataSet.prototype.remove = function (id, senderId) {
var removedIds = [],
i, len, removedId;
if (Array.isArray(id)) {
for (i = 0, len = id.length; i < len; i++) {
removedId = this._remove(id[i]);
if (removedId != null) {
removedIds.push(removedId);
removedItems = [],
ids = [],
i, len, itemId, item;
// force everything to be an array for simplicity
ids = Array.isArray(id) ? id : [id];
for (i = 0, len = ids.length; i < len; i++) {
item = this._remove(ids[i]);
if (item) {
itemId = item[this._fieldId];
if (itemId) {
removedIds.push(itemId);
removedItems.push(item);
}
}
}
else {
removedId = this._remove(id);
if (removedId != null) {
removedIds.push(removedId);
}
}
if (removedIds.length) {
this._trigger('remove', {items: removedIds}, senderId);
this._trigger('remove', {items: removedIds, oldData: removedItems}, senderId);
}
return removedIds;
@ -699,20 +700,23 @@ DataSet.prototype.remove = function (id, senderId) {
* @private
*/
DataSet.prototype._remove = function (id) {
var item,
ident;
// confirm the id to use based on the args type
if (util.isNumber(id) || util.isString(id)) {
if (this._data[id]) {
delete this._data[id];
this.length--;
return id;
}
ident = id;
}
else if (id instanceof Object) {
var itemId = id[this._fieldId];
if (itemId !== undefined && this._data[itemId]) {
delete this._data[itemId];
this.length--;
return itemId;
}
ident = id[this._fieldId]; // look for the identifier field using _fieldId
}
// do the remove if the item is found
if (ident !== undefined && this._data[ident]) {
item = this._data[ident];
delete this._data[ident];
this.length--;
return item;
}
return null;
};
@ -723,12 +727,18 @@ DataSet.prototype._remove = function (id) {
* @return {Array} removedIds The ids of all removed items
*/
DataSet.prototype.clear = function (senderId) {
var i, len;
var ids = Object.keys(this._data);
var items = [];
for (i = 0, len = ids.length; i < len; i++) {
items.push(this._data[ids[i]]);
}
this._data = {};
this.length = 0;
this._trigger('remove', {items: ids}, senderId);
this._trigger('remove', {items: ids, oldData: items}, senderId);
return ids;
};

+ 46
- 36
lib/DataView.js View File

@ -35,7 +35,7 @@ function DataView (data, options) {
* @param {DataSet | DataView} data
*/
DataView.prototype.setData = function (data) {
var ids, id, i, len;
var ids, id, i, len, items;
if (this._data) {
// unsubscribe from current dataset
@ -44,10 +44,16 @@ DataView.prototype.setData = function (data) {
}
// trigger a remove of all items in memory
ids = Object.keys(this._ids);
ids = this._data.getIds({filter: this._options && this._options.filter});
items = [];
for (i = 0, len = ids.length; i < len; i++) {
items.push(this._data._data[ids[i]]);
}
this._ids = {};
this.length = 0;
this._trigger('remove', {items: ids});
this._trigger('remove', {items: ids, oldData: items});
}
this._data = data;
@ -80,18 +86,19 @@ DataView.prototype.setData = function (data) {
*/
DataView.prototype.refresh = function () {
var id, i, len;
var ids = this._data.getIds({filter: this._options && this._options.filter});
var oldIds = Object.keys(this._ids);
var newIds = {};
var added = [];
var removed = [];
var ids = this._data.getIds({filter: this._options && this._options.filter}),
oldIds = Object.keys(this._ids),
newIds = {},
addedIds = [],
removedIds = [],
removedItems = [];
// check for additions
for (i = 0, len = ids.length; i < len; i++) {
id = ids[i];
newIds[id] = true;
if (!this._ids[id]) {
added.push(id);
addedIds.push(id);
this._ids[id] = true;
}
}
@ -100,19 +107,20 @@ DataView.prototype.refresh = function () {
for (i = 0, len = oldIds.length; i < len; i++) {
id = oldIds[i];
if (!newIds[id]) {
removed.push(id);
removedIds.push(id);
removedItems.push(this._data[id]);
delete this._ids[id];
}
}
this.length += added.length - removed.length;
this.length += addedIds.length - removedIds.length;
// trigger events
if (added.length) {
if (addedIds.length) {
this._trigger('add', {items: added});
}
if (removed.length) {
this._trigger('remove', {items: removed});
if (removedIds.length) {
this._trigger('remove', {items: removedIds, oldData: removedItems});
}
};
@ -298,14 +306,14 @@ DataView.prototype.getDataSet = function () {
DataView.prototype._onEvent = function (event, params, senderId) {
var i, len, id, item;
var ids = params && params.items;
var data = this._data;
var updatedData = [];
var oldData = [];
var added = [];
var updated = [];
var removed = [];
if (ids && data) {
var addedIds = [],
updatedIds = [],
removedIds = [],
oldItems = [],
updatedItems = [],
removedItems = [];
if (ids && this._data) {
switch (event) {
case 'add':
// filter the ids of the added items
@ -314,7 +322,7 @@ DataView.prototype._onEvent = function (event, params, senderId) {
item = this.get(id);
if (item) {
this._ids[id] = true;
added.push(id);
addedIds.push(id);
}
}
@ -329,19 +337,20 @@ DataView.prototype._onEvent = function (event, params, senderId) {
if (item) {
if (this._ids[id]) {
updated.push(id);
updatedData.push(params.data[i]);
oldData.push(params.oldData[i]);
updatedIds.push(id);
updatedItems.push(params.data[i]);
oldItems.push(params.oldData[i]);
}
else {
this._ids[id] = true;
added.push(id);
addedIds.push(id);
}
}
else {
if (this._ids[id]) {
delete this._ids[id];
removed.push(id);
removedIds.push(id);
removedItems.push(params.oldData[i]);
}
else {
// nothing interesting for me :-(
@ -357,23 +366,24 @@ DataView.prototype._onEvent = function (event, params, senderId) {
id = ids[i];
if (this._ids[id]) {
delete this._ids[id];
removed.push(id);
removedIds.push(id);
removedItems.push(params.oldData[i]);
}
}
break;
}
this.length += added.length - removed.length;
this.length += addedIds.length - removedIds.length;
if (added.length) {
this._trigger('add', {items: added}, senderId);
if (addedIds.length) {
this._trigger('add', {items: addedIds}, senderId);
}
if (updated.length) {
this._trigger('update', {items: updated, oldData: oldData, data: updatedData}, senderId);
if (updatedIds.length) {
this._trigger('update', {items: updatedIds, oldData: oldItems, data: updatedItems}, senderId);
}
if (removed.length) {
this._trigger('remove', {items: removed}, senderId);
if (removedIds.length) {
this._trigger('remove', {items: removedIds, oldData: removedItems}, senderId);
}
}
};

+ 588
- 841
lib/graph3d/Graph3d.js
File diff suppressed because it is too large
View File


+ 436
- 0
lib/graph3d/Settings.js View File

@ -0,0 +1,436 @@
////////////////////////////////////////////////////////////////////////////////
// This modules handles the options for Graph3d.
//
////////////////////////////////////////////////////////////////////////////////
var Camera = require('./Camera');
var Point3d = require('./Point3d');
// enumerate the available styles
var STYLE = {
BAR : 0,
BARCOLOR: 1,
BARSIZE : 2,
DOT : 3,
DOTLINE : 4,
DOTCOLOR: 5,
DOTSIZE : 6,
GRID : 7,
LINE : 8,
SURFACE : 9
};
// The string representations of the styles
var STYLENAME = {
'dot' : STYLE.DOT,
'dot-line' : STYLE.DOTLINE,
'dot-color': STYLE.DOTCOLOR,
'dot-size' : STYLE.DOTSIZE,
'line' : STYLE.LINE,
'grid' : STYLE.GRID,
'surface' : STYLE.SURFACE,
'bar' : STYLE.BAR,
'bar-color': STYLE.BARCOLOR,
'bar-size' : STYLE.BARSIZE
};
/**
* Field names in the options hash which are of relevance to the user.
*
* Specifically, these are the fields which require no special handling,
* and can be directly copied over.
*/
var OPTIONKEYS = [
'width',
'height',
'filterLabel',
'legendLabel',
'xLabel',
'yLabel',
'zLabel',
'xValueLabel',
'yValueLabel',
'zValueLabel',
'showGrid',
'showPerspective',
'showShadow',
'keepAspectRatio',
'verticalRatio',
'dotSizeRatio',
'showAnimationControls',
'animationInterval',
'animationPreload',
'animationAutoStart',
'axisColor',
'gridColor',
'xCenter',
'yCenter'
];
/**
* Field names in the options hash which are of relevance to the user.
*
* Same as OPTIONKEYS, but internally these fields are stored with
* prefix 'default' in the name.
*/
var PREFIXEDOPTIONKEYS = [
'xBarWidth',
'yBarWidth',
'valueMin',
'valueMax',
'xMin',
'xMax',
'xStep',
'yMin',
'yMax',
'yStep',
'zMin',
'zMax',
'zStep'
];
// Placeholder for DEFAULTS reference
var DEFAULTS = undefined;
/**
* Check if given hash is empty.
*
* Source: http://stackoverflow.com/a/679937
*/
function isEmpty(obj) {
for(var prop in obj) {
if (obj.hasOwnProperty(prop))
return false;
}
return true;
}
/**
* Make first letter of parameter upper case.
*
* Source: http://stackoverflow.com/a/1026087
*/
function capitalize(str) {
if (str === undefined || str === "") {
return str;
}
return str.charAt(0).toUpperCase() + str.slice(1);
}
/**
* Add a prefix to a field name, taking style guide into account
*/
function prefixFieldName(prefix, fieldName) {
if (prefix === undefined || prefix === "") {
return fieldName;
}
return prefix + capitalize(fieldName);
}
/**
* Forcibly copy fields from src to dst in a controlled manner.
*
* A given field in dst will always be overwitten. If this field
* is undefined or not present in src, the field in dst will
* be explicitly set to undefined.
*
* The intention here is to be able to reset all option fields.
*
* Only the fields mentioned in array 'fields' will be handled.
*
* @param fields array with names of fields to copy
* @param prefix optional; prefix to use for the target fields.
*/
function forceCopy(src, dst, fields, prefix) {
var srcKey;
var dstKey;
for (var i in fields) {
srcKey = fields[i];
dstKey = prefixFieldName(prefix, srcKey);
dst[dstKey] = src[srcKey];
}
}
/**
* Copy fields from src to dst in a safe and controlled manner.
*
* Only the fields mentioned in array 'fields' will be copied over,
* and only if these are actually defined.
*
* @param fields array with names of fields to copy
* @param prefix optional; prefix to use for the target fields.
*/
function safeCopy(src, dst, fields, prefix) {
var srcKey;
var dstKey;
for (var i in fields) {
srcKey = fields[i];
if (src[srcKey] === undefined) continue;
dstKey = prefixFieldName(prefix, srcKey);
dst[dstKey] = src[srcKey];
}
}
/**
* Initialize dst with the values in src.
*
* src is the hash with the default values.
* A reference DEFAULTS to this hash is stored locally for
* further handling.
*
* For now, dst is assumed to be a Graph3d instance.
*/
function setDefaults(src, dst) {
if (src === undefined || isEmpty(src)) {
throw new Error('No DEFAULTS passed');
}
if (dst === undefined) {
throw new Error('No dst passed');
}
// Remember defaults for future reference
DEFAULTS = src;
// Handle the defaults which can be simply copied over
forceCopy(src, dst, OPTIONKEYS);
forceCopy(src, dst, PREFIXEDOPTIONKEYS, 'default');
// Handle the more complex ('special') fields
setSpecialSettings(src, dst);
// Following are internal fields, not part of the user settings
dst.margin = 10; // px
dst.showGrayBottom = false; // TODO: this does not work correctly
dst.showTooltip = false;
dst.eye = new Point3d(0, 0, -1); // TODO: set eye.z about 3/4 of the width of the window?
}
function setOptions(options, dst) {
if (options === undefined) {
return;
}
if (dst === undefined) {
throw new Error('No dst passed');
}
if (DEFAULTS === undefined || isEmpty(DEFAULTS)) {
throw new Error('DEFAULTS not set for module Settings');
}
// Handle the parameters which can be simply copied over
safeCopy(options, dst, OPTIONKEYS);
safeCopy(options, dst, PREFIXEDOPTIONKEYS, 'default');
// Handle the more complex ('special') fields
setSpecialSettings(options, dst);
}
/**
* Special handling for certain parameters
*
* 'Special' here means: setting requires more than a simple copy
*/
function setSpecialSettings(src, dst) {
if (src.backgroundColor !== undefined) {
setBackgroundColor(src.backgroundColor, dst);
}
setDataColor(src.dataColor, dst);
setStyle(src.style, dst);
setShowLegend(src.showLegend, dst);
setCameraPosition(src.cameraPosition, dst);
// As special fields go, this is an easy one; just a translation of the name.
// Can't use this.tooltip directly, because that field exists internally
if (src.tooltip !== undefined) {
dst.showTooltip = src.tooltip;
}
}
/**
* Set the value of setting 'showLegend'
*
* This depends on the value of the style fields, so it must be called
* after the style field has been initialized.
*/
function setShowLegend(showLegend, dst) {
if (showLegend === undefined) {
// If the default was auto, make a choice for this field
var isAutoByDefault = (DEFAULTS.showLegend === undefined);
if (isAutoByDefault) {
// these styles default to having legends
var isLegendGraphStyle = dst.style === STYLE.DOTCOLOR
|| dst.style === STYLE.DOTSIZE;
dst.showLegend = isLegendGraphStyle;
} else {
// Leave current value as is
}
} else {
dst.showLegend = showLegend;
}
}
/**
* Retrieve the style index from given styleName
* @param {string} styleName Style name such as 'dot', 'grid', 'dot-line'
* @return {Number} styleNumber Enumeration value representing the style, or -1
* when not found
*/
function getStyleNumberByName(styleName) {
var number = STYLENAME[styleName];
if (number === undefined) {
return -1;
}
return number;
}
/**
* Check if given number is a valid style number.
*
* @return true if valid, false otherwise
*/
function checkStyleNumber(style) {
var valid = false;
for (var n in STYLE) {
if (STYLE[n] === style) {
valid = true;
break;
}
}
return valid;
}
function setStyle(style, dst) {
if (style === undefined) {
return; // Nothing to do
}
var styleNumber;
if (typeof style === 'string') {
styleNumber = getStyleNumberByName(style);
if (styleNumber === -1 ) {
throw new Error('Style \'' + style + '\' is invalid');
}
} else {
// Do a pedantic check on style number value
if (!checkStyleNumber(style)) {
throw new Error('Style \'' + style + '\' is invalid');
}
styleNumber = style;
}
dst.style = styleNumber;
}
/**
* Set the background styling for the graph
* @param {string | {fill: string, stroke: string, strokeWidth: string}} backgroundColor
*/
function setBackgroundColor(backgroundColor, dst) {
var fill = 'white';
var stroke = 'gray';
var strokeWidth = 1;
if (typeof(backgroundColor) === 'string') {
fill = backgroundColor;
stroke = 'none';
strokeWidth = 0;
}
else if (typeof(backgroundColor) === 'object') {
if (backgroundColor.fill !== undefined) fill = backgroundColor.fill;
if (backgroundColor.stroke !== undefined) stroke = backgroundColor.stroke;
if (backgroundColor.strokeWidth !== undefined) strokeWidth = backgroundColor.strokeWidth;
}
else {
throw new Error('Unsupported type of backgroundColor');
}
dst.frame.style.backgroundColor = fill;
dst.frame.style.borderColor = stroke;
dst.frame.style.borderWidth = strokeWidth + 'px';
dst.frame.style.borderStyle = 'solid';
}
function setDataColor(dataColor, dst) {
if (dataColor === undefined) {
return; // Nothing to do
}
if (dst.dataColor === undefined) {
dst.dataColor = {};
}
if (typeof dataColor === 'string') {
dst.dataColor.fill = dataColor;
dst.dataColor.stroke = dataColor;
}
else {
if (dataColor.fill) {
dst.dataColor.fill = dataColor.fill;
}
if (dataColor.stroke) {
dst.dataColor.stroke = dataColor.stroke;
}
if (dataColor.strokeWidth !== undefined) {
dst.dataColor.strokeWidth = dataColor.strokeWidth;
}
}
}
function setCameraPosition(cameraPosition, dst) {
var camPos = cameraPosition;
if (camPos === undefined) {
return;
}
if (dst.camera === undefined) {
dst.camera = new Camera();
}
dst.camera.setArmRotation(camPos.horizontal, camPos.vertical);
dst.camera.setArmLength(camPos.distance);
}
module.exports.STYLE = STYLE;
module.exports.setDefaults = setDefaults;
module.exports.setOptions = setOptions;
module.exports.setCameraPosition = setCameraPosition;

+ 104
- 45
lib/timeline/Core.js View File

@ -82,7 +82,6 @@ Core.prototype._create = function (container) {
this.dom.centerContainer.appendChild(this.dom.center);
this.dom.leftContainer.appendChild(this.dom.left);
this.dom.rightContainer.appendChild(this.dom.right);
this.dom.centerContainer.appendChild(this.dom.shadowTop);
this.dom.centerContainer.appendChild(this.dom.shadowBottom);
this.dom.leftContainer.appendChild(this.dom.shadowTopLeft);
@ -90,13 +89,30 @@ Core.prototype._create = function (container) {
this.dom.rightContainer.appendChild(this.dom.shadowTopRight);
this.dom.rightContainer.appendChild(this.dom.shadowBottomRight);
// size properties of each of the panels
this.props = {
root: {},
background: {},
centerContainer: {},
leftContainer: {},
rightContainer: {},
center: {},
left: {},
right: {},
top: {},
bottom: {},
border: {},
scrollTop: 0,
scrollTopMin: 0
};
this.on('rangechange', function () {
if (this.initialDrawDone === true) {
this._redraw(); // this allows overriding the _redraw method
this._redraw();
}
}.bind(this));
this.on('touch', this._onTouch.bind(this));
this.on('pan', this._onDrag.bind(this));
this.on('panmove', this._onDrag.bind(this));
var me = this;
this.on('_change', function (properties) {
@ -154,16 +170,15 @@ Core.prototype._create = function (container) {
}.bind(this));
function onMouseWheel(event) {
if (me.isActive()) {
me.emit('mousewheel', event);
if (this.isActive()) {
this.emit('mousewheel', event);
}
}
this.dom.root.addEventListener('mousewheel', onMouseWheel);
this.dom.root.addEventListener('DOMMouseScroll', onMouseWheel);
function onMouseWheelCenter(event) {
// prevent scrolling when zoomKey defined or activated
if (!me.options.zoomKey || event[me.options.zoomKey]) return
if (!this.options.zoomKey || event[this.options.zoomKey]) return
// prevent scrolling vertically when horizontalScroll is true
if (this.options.horizontalScroll) return
var delta = 0;
if (event.wheelDelta) { /* IE/Opera. */
@ -174,12 +189,17 @@ Core.prototype._create = function (container) {
delta = -event.detail / 3;
}
var current = me.props.scrollTop;
var current = this.props.scrollTop;
var adjusted = current + delta * 120;
if (me.isActive()) {
me._setScrollTop(adjusted);
me._redraw();
me.emit('scroll', event);
if (this.isActive()) {
this._setScrollTop(adjusted);
if (this.options.verticalScroll) {
this.dom.left.parentNode.scrollTop = -adjusted;
this.dom.right.parentNode.scrollTop = -adjusted;
}
this._redraw();
this.emit('scroll', event);
}
// Prevent default actions caused by mouse wheel
@ -187,25 +207,29 @@ Core.prototype._create = function (container) {
event.preventDefault();
}
this.dom.center.addEventListener('mousewheel', onMouseWheelCenter);
this.dom.center.addEventListener('DOMMouseScroll', onMouseWheelCenter);
if (this.dom.center.addEventListener) {
// IE9, Chrome, Safari, Opera
this.dom.center.addEventListener("mousewheel", onMouseWheel.bind(this), false);
// Firefox
this.dom.center.addEventListener("DOMMouseScroll", onMouseWheel.bind(this), false);
} else {
// IE 6/7/8
this.dom.center.attachEvent("onmousewheel", onMouseWheel.bind(this));
}
// size properties of each of the panels
this.props = {
root: {},
background: {},
centerContainer: {},
leftContainer: {},
rightContainer: {},
center: {},
left: {},
right: {},
top: {},
bottom: {},
border: {},
scrollTop: 0,
scrollTopMin: 0
};
function onMouseScrollSide(event) {
if (!me.options.verticalScroll) return;
event.preventDefault();
if (me.isActive()) {
var adjusted = -event.target.scrollTop;
me._setScrollTop(adjusted);
me._redraw();
me.emit('scrollSide', event);
}
}
this.dom.left.parentNode.addEventListener('scroll', onMouseScrollSide.bind(this));
this.dom.right.parentNode.addEventListener('scroll', onMouseScrollSide.bind(this));
this.customTimes = [];
@ -251,17 +275,23 @@ Core.prototype.setOptions = function (options) {
var fields = [
'width', 'height', 'minHeight', 'maxHeight', 'autoResize',
'start', 'end', 'clickToUse', 'dataAttributes', 'hiddenDates',
'locale', 'locales', 'moment', 'rtl', 'zoomKey'
'locale', 'locales', 'moment', 'rtl', 'zoomKey', 'horizontalScroll', 'verticalScroll'
];
util.selectiveExtend(fields, this.options, options);
if (this.options.rtl) {
var contentContainer = this.dom.leftContainer;
this.dom.leftContainer = this.dom.rightContainer;
this.dom.rightContainer = contentContainer;
this.dom.container.style.direction = "rtl";
this.dom.backgroundVertical.className = 'vis-panel vis-background vis-vertical-rtl'; }
this.dom.backgroundVertical.className = 'vis-panel vis-background vis-vertical-rtl';
}
if (this.options.verticalScroll) {
if (this.options.rtl) {
this.dom.rightContainer.className = 'vis-panel vis-right vis-vertical-scroll';
} else {
this.dom.leftContainer.className = 'vis-panel vis-left vis-vertical-scroll';
}
}
this.options.orientation = {item:undefined,axis:undefined};
if ('orientation' in options) {
@ -734,9 +764,25 @@ Core.prototype._redraw = function() {
// calculate the widths of the panels
props.root.width = dom.root.offsetWidth;
props.background.width = props.root.width - borderRootWidth;
props.left.width = dom.leftContainer.clientWidth || -props.border.left;
if (!this.initialDrawDone) {
props.scrollbarWidth = util.getScrollBarWidth();
}
if (this.options.verticalScroll) {
if (this.options.rtl) {
props.left.width = dom.leftContainer.clientWidth || -props.border.left;
props.right.width = dom.rightContainer.clientWidth + props.scrollbarWidth || -props.border.right;
} else {
props.left.width = dom.leftContainer.clientWidth + props.scrollbarWidth || -props.border.left;
props.right.width = dom.rightContainer.clientWidth || -props.border.right;
}
} else {
props.left.width = dom.leftContainer.clientWidth || -props.border.left;
props.right.width = dom.rightContainer.clientWidth || -props.border.right;
}
props.leftContainer.width = props.left.width;
props.right.width = dom.rightContainer.clientWidth || -props.border.right;
props.rightContainer.width = props.right.width;
var centerWidth = props.root.width - props.left.width - props.right.width - borderRootWidth;
props.center.width = centerWidth;
@ -779,10 +825,9 @@ Core.prototype._redraw = function() {
// update the scrollTop, feasible range for the offset can be changed
// when the height of the Core or of the contents of the center changed
this._updateScrollTop();
var offset = this._updateScrollTop();
// reposition the scrollable contents
var offset = this.props.scrollTop;
if (options.orientation.item != 'top') {
offset += Math.max(this.props.centerContainer.height - this.props.center.height -
this.props.border.top - this.props.border.bottom, 0);
@ -790,10 +835,8 @@ Core.prototype._redraw = function() {
dom.center.style.left = '0';
dom.center.style.top = offset + 'px';
dom.left.style.left = '0';
dom.left.style.top = offset + 'px';
dom.right.style.left = '0';
dom.right.style.top = offset + 'px';
// show shadows when vertical scrolling is available
var visibilityTop = this.props.scrollTop == 0 ? 'hidden' : '';
var visibilityBottom = this.props.scrollTop == this.props.scrollTopMin ? 'hidden' : '';
@ -804,6 +847,16 @@ Core.prototype._redraw = function() {
dom.shadowTopRight.style.visibility = visibilityTop;
dom.shadowBottomRight.style.visibility = visibilityBottom;
if (this.options.verticalScroll) {
dom.shadowTopRight.style.visibility = "hidden";
dom.shadowBottomRight.style.visibility = "hidden";
dom.shadowTopLeft.style.visibility = "hidden";
dom.shadowBottomLeft.style.visibility = "hidden";
} else {
dom.left.style.top = offset + 'px';
dom.right.style.top = offset + 'px';
}
// enable/disable vertical panning
var contentsOverflow = this.props.center.height > this.props.centerContainer.height;
this.hammer.get('pan').set({
@ -826,6 +879,7 @@ Core.prototype._redraw = function() {
} else {
this.redrawCount = 0;
}
this.initialDrawDone = true;
//Emit public 'changed' event for UI updates, see issue #1592
@ -1018,6 +1072,7 @@ Core.prototype._onPinch = function (event) {
* @private
*/
Core.prototype._onDrag = function (event) {
if (!event) return
// refuse to drag when we where pinching to prevent the timeline make a jump
// when releasing the fingers in opposite order from the touch screen
if (!this.touch.allowDragging) return;
@ -1027,6 +1082,10 @@ Core.prototype._onDrag = function (event) {
var oldScrollTop = this._getScrollTop();
var newScrollTop = this._setScrollTop(this.touch.initialScrollTop + delta);
if (this.options.verticalScroll) {
this.dom.left.parentNode.scrollTop = -this.props.scrollTop;
this.dom.right.parentNode.scrollTop = -this.props.scrollTop;
}
if (newScrollTop != oldScrollTop) {
this.emit("verticalDrag");

+ 30
- 13
lib/timeline/Range.js View File

@ -81,7 +81,7 @@ Range.prototype.setOptions = function (options) {
// copy the options that we know
var fields = [
'direction', 'min', 'max', 'zoomMin', 'zoomMax', 'moveable', 'zoomable',
'moment', 'activate', 'hiddenDates', 'zoomKey', 'rtl'
'moment', 'activate', 'hiddenDates', 'zoomKey', 'rtl', 'horizontalScroll'
];
util.selectiveExtend(fields, this.options, options);
@ -390,6 +390,8 @@ Range.prototype._onDragStart = function(event) {
* @private
*/
Range.prototype._onDrag = function (event) {
if (!event) return
if (!this.props.touch.dragging) return;
// only allow dragging when configured as movable
@ -445,6 +447,9 @@ Range.prototype._onDrag = function (event) {
end: endDate,
byUser: true
});
// fire a panmove event
this.body.emitter.emit('panmove');
};
/**
@ -483,15 +488,10 @@ Range.prototype._onDragEnd = function (event) {
* @private
*/
Range.prototype._onMouseWheel = function(event) {
// only allow zooming when configured as zoomable and moveable
if (!(this.options.zoomable && this.options.moveable)) return;
// only zoom when the mouse is inside the current range
if (!this._isInsideRange(event)) return;
// Prevent default actions caused by mouse wheel
// (else the page and timeline both zoom and scroll)
event.preventDefault();
// only zoom when the according key is pressed and the zoomKey option is set
if (this.options.zoomKey && !event[this.options.zoomKey]) return;
// retrieve delta
var delta = 0;
if (event.wheelDelta) { /* IE/Opera. */
@ -502,6 +502,27 @@ Range.prototype._onMouseWheel = function(event) {
delta = -event.detail / 3;
}
// don't allow zoom when the according key is pressed and the zoomKey option or not zoomable but movable
if ((this.options.zoomKey && !event[this.options.zoomKey] && this.options.zoomable)
|| (!this.options.zoomable && this.options.moveable)) {
if (this.options.horizontalScroll) {
// calculate a single scroll jump relative to the range scale
var diff = delta * (this.end - this.start) / 20;
// calculate new start and end
var newStart = this.start - diff;
var newEnd = this.end - diff;
this.setRange(newStart, newEnd);
}
return;
}
// only allow zooming when configured as zoomable and moveable
if (!(this.options.zoomable && this.options.moveable)) return;
// only zoom when the mouse is inside the current range
if (!this._isInsideRange(event)) return;
// If delta is nonzero, handle it.
// Basically, delta is now positive if wheel was scrolled up,
// and negative, if wheel was scrolled down.
@ -524,10 +545,6 @@ Range.prototype._onMouseWheel = function(event) {
this.zoom(scale, pointerDate, delta);
}
// Prevent default actions caused by mouse wheel
// (else the page and timeline both zoom and scroll)
event.preventDefault();
};
/**

+ 6
- 28
lib/timeline/Timeline.js View File

@ -28,8 +28,8 @@ import Validator from '../shared/Validator';
* @constructor
* @extends Core
*/
function Timeline (container, items, groups, options) {
function Timeline (container, items, groups, options) {
if (!(this instanceof Timeline)) {
throw new SyntaxError('Constructor must be called with the new operator');
}
@ -52,7 +52,6 @@ function Timeline (container, items, groups, options) {
axis: 'bottom', // axis orientation: 'bottom', 'top', or 'both'
item: 'bottom' // not relevant
},
rtl: false,
moment: moment,
width: null,
@ -105,6 +104,11 @@ function Timeline (container, items, groups, options) {
// current time bar
this.currentTime = new CurrentTime(this.body);
this.components.push(this.currentTime);
// apply options
if (options) {
this.setOptions(options);
}
// item set
this.itemSet = new ItemSet(this.body, this.options);
@ -145,11 +149,6 @@ function Timeline (container, items, groups, options) {
}
});
// apply options
if (options) {
this.setOptions(options);
}
// IMPORTANT: THIS HAPPENS BEFORE SET ITEMS!
if (groups) {
this.setGroups(groups);
@ -546,25 +545,4 @@ Timeline.prototype.getEventProperties = function (event) {
}
};
/**
* Extend the drag event handler from Core, move the timeline vertically
* @param {Event} event
* @private
*/
Timeline.prototype._onDrag = function (event) {
// refuse to drag when we where pinching to prevent the timeline make a jump
// when releasing the fingers in opposite order from the touch screen, and refuse
// to drag when an item is already being dragged
if (!this.touch.allowDragging || this.itemSet.touchParams.itemIsDragging) return;
var delta = event.deltaY;
var oldScrollTop = this._getScrollTop();
var newScrollTop = this._setScrollTop(this.touch.initialScrollTop + delta);
if (newScrollTop != oldScrollTop) {
this.emit("verticalDrag");
}
};
module.exports = Timeline;

+ 9
- 7
lib/timeline/component/Group.js View File

@ -87,10 +87,12 @@ Group.prototype._create = function() {
Group.prototype.setData = function(data) {
// update contents
var content;
var templateFunction;
if (this.itemSet.options && this.itemSet.options.groupTemplate) {
content = this.itemSet.options.groupTemplate(data, this.dom.inner);
}
else {
templateFunction = this.itemSet.options.groupTemplate.bind(this);
content = templateFunction(data, this.dom.inner);
} else {
content = data && data.content;
}
@ -100,11 +102,11 @@ Group.prototype.setData = function(data) {
this.dom.inner.removeChild(this.dom.inner.firstChild);
}
this.dom.inner.appendChild(content);
}
else if (content !== undefined && content !== null) {
} else if (content instanceof Object) {
templateFunction(data, this.dom.inner);
} else if (content !== undefined && content !== null) {
this.dom.inner.innerHTML = content;
}
else {
} else {
this.dom.inner.innerHTML = this.groupId || ''; // groupId can be null
}

+ 13
- 4
lib/timeline/component/ItemSet.js View File

@ -27,7 +27,6 @@ var BACKGROUND = '__background__'; // reserved group id for background items wit
function ItemSet(body, options) {
this.body = body;
this.defaultOptions = {
rtl: false,
type: null, // 'box', 'point', 'range', 'background'
orientation: {
item: 'bottom' // item orientation: 'top' or 'bottom'
@ -96,7 +95,8 @@ function ItemSet(body, options) {
// options is shared by this ItemSet and all its items
this.options = util.extend({}, this.defaultOptions);
this.options.rtl = options.rtl;
// options for getting items from the DataSet with the correct type
this.itemOptions = {
type: {start: 'Date', end: 'Date'}
@ -230,7 +230,12 @@ ItemSet.prototype._create = function(){
// add item on doubletap
this.hammer.on('doubletap', this._onAddItem.bind(this));
this.groupHammer = new Hammer(this.body.dom.leftContainer);
if (this.options.rtl) {
this.groupHammer = new Hammer(this.body.dom.rightContainer);
} else {
this.groupHammer = new Hammer(this.body.dom.leftContainer);
}
this.groupHammer.on('panstart', this._onGroupDragStart.bind(this));
this.groupHammer.on('panmove', this._onGroupDrag.bind(this));
@ -451,7 +456,11 @@ ItemSet.prototype.show = function() {
// show labelset containing labels
if (!this.dom.labelSet.parentNode) {
this.body.dom.left.appendChild(this.dom.labelSet);
if (this.options.rtl) {
this.body.dom.right.appendChild(this.dom.labelSet);
} else {
this.body.dom.left.appendChild(this.dom.labelSet);
}
}
};

+ 9
- 0
lib/timeline/component/css/item.css View File

@ -136,6 +136,15 @@
color: white;
}
.vis-item .vis-drag-center {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0px;
cursor: move;
}
.vis-item.vis-range .vis-drag-left {
position: absolute;
width: 24px;

+ 22
- 1
lib/timeline/component/css/panel.css View File

@ -1,4 +1,3 @@
.vis-panel {
position: absolute;
@ -24,6 +23,28 @@
overflow: hidden;
}
.vis-left.vis-panel.vis-vertical-scroll, .vis-right.vis-panel.vis-vertical-scroll {
height: 100%;
overflow-x: hidden;
overflow-y: scroll;
}
.vis-left.vis-panel.vis-vertical-scroll {
direction: rtl;
}
.vis-left.vis-panel.vis-vertical-scroll .vis-content {
direction: ltr;
}
.vis-right.vis-panel.vis-vertical-scroll {
direction: ltr;
}
.vis-right.vis-panel.vis-vertical-scroll .vis-content {
direction: rtl;
}
.vis-panel.vis-center,
.vis-panel.vis-top,
.vis-panel.vis-bottom {

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

@ -164,6 +164,7 @@ BoxItem.prototype.redraw = function() {
this.dirty = false;
}
this._repaintDragCenter();
this._repaintDeleteButton(dom.box);
};

+ 48
- 18
lib/timeline/component/item/Item.js View File

@ -187,31 +187,37 @@ Item.prototype._repaintDeleteButton = function (anchor) {
*/
Item.prototype._updateContents = function (element) {
var content;
var templateFunction;
if (this.options.template) {
var itemData = this.parent.itemSet.itemsData.get(this.id); // get a clone of the data from the dataset
content = this.options.template(itemData, element);
}
else {
templateFunction = this.options.template.bind(this);
content = templateFunction(itemData, element);
} else {
content = this.data.content;
}
var changed = this._contentToString(this.content) !== this._contentToString(content);
if (changed) {
// only replace the content when changed
if (content instanceof Element) {
element.innerHTML = '';
element.appendChild(content);
}
else if (content != undefined) {
element.innerHTML = content;
}
else {
if (!(this.data.type == 'background' && this.data.content === undefined)) {
throw new Error('Property "content" missing in item ' + this.id);
if (content instanceof Object) {
templateFunction(itemData, element)
} else {
var changed = this._contentToString(this.content) !== this._contentToString(content);
if (changed) {
// only replace the content when changed
if (content instanceof Element) {
element.innerHTML = '';
element.appendChild(content);
}
else if (content != undefined) {
element.innerHTML = content;
}
else {
if (!(this.data.type == 'background' && this.data.content === undefined)) {
throw new Error('Property "content" missing in item ' + this.id);
}
}
}
this.content = content;
this.content = content;
}
}
};
@ -281,6 +287,7 @@ Item.prototype._updateStyle = function(element) {
}
};
/**
* Stringify the items contents
* @param {string | Element | undefined} content
@ -309,4 +316,27 @@ Item.prototype.getWidthRight = function () {
return 0;
};
/**
* Repaint a drag area on the center of the item when the item is selected
* @protected
*/
Item.prototype._repaintDragCenter = function () {
if (this.selected && this.options.editable.updateTime && !this.dom.dragCenter) {
// create and show drag area
var dragCenter = document.createElement('div');
dragCenter.className = 'vis-drag-center';
dragCenter.dragCenterItem = this;
this.dom.box.appendChild(dragCenter);
this.dom.dragCenter = dragCenter;
}
else if (!this.selected && this.dom.dragCenter) {
// delete drag area
if (this.dom.dragCenter.parentNode) {
this.dom.dragCenter.parentNode.removeChild(this.dom.dragCenter);
}
this.dom.dragCenter = null;
}
};
module.exports = Item;

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

@ -140,7 +140,8 @@ PointItem.prototype.redraw = function() {
this.dirty = false;
}
this._repaintDragCenter();
this._repaintDeleteButton(dom.point);
};

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

@ -124,6 +124,7 @@ RangeItem.prototype.redraw = function() {
this.dirty = false;
}
this._repaintDeleteButton(dom.box);
this._repaintDragCenter();
this._repaintDragLeft();
this._repaintDragRight();
};

+ 2
- 0
lib/timeline/optionsTimeline.js View File

@ -26,6 +26,8 @@ let allOptions = {
//globals :
align: {string},
rtl: {boolean, 'undefined': 'undefined'},
verticalScroll: {boolean, 'undefined': 'undefined'},
horizontalScroll: {boolean, 'undefined': 'undefined'},
autoResize: {boolean},
clickToUse: {boolean},
dataAttributes: {string, array},

+ 26
- 0
lib/util.js View File

@ -1452,3 +1452,29 @@ exports.easingFunctions = {
return t < .5 ? 16 * t * t * t * t * t : 1 + 16 * (--t) * t * t * t * t
}
};
exports.getScrollBarWidth = function () {
var inner = document.createElement('p');
inner.style.width = "100%";
inner.style.height = "200px";
var outer = document.createElement('div');
outer.style.position = "absolute";
outer.style.top = "0px";
outer.style.left = "0px";
outer.style.visibility = "hidden";
outer.style.width = "200px";
outer.style.height = "150px";
outer.style.overflow = "hidden";
outer.appendChild (inner);
document.body.appendChild (outer);
var w1 = inner.offsetWidth;
outer.style.overflow = 'scroll';
var w2 = inner.offsetWidth;
if (w1 == w2) w2 = outer.clientWidth;
document.body.removeChild (outer);
return (w1 - w2);
};

+ 6
- 4
misc/how_to_help.md View File

@ -6,13 +6,13 @@ The company that developed vis.js for the main part, *almende* is [not able to m
### Answering questions
There are new [issues with questions](//github.com/almende/vis/issues?q=is%3Aissue+is%3Aopen+label%3Aquestion+sort%3Acreated-desc) how to use vis.js opened almost every day. Be part of the community and help answer them!
There are new [issues with questions](//github.com/almende/vis/issues?q=is%3Aissue+is%3Aopen+label%3AQuestion+sort%3Acreated-desc) how to use vis.js opened almost every day. Be part of the community and help answer them!
A better way to ask questions on how to use vis.js is [stackoverflow](https://stackoverflow.com/tags/vis.js). Questions are posed here also and need to be answered by the community. [Please help answering questions](https://stackoverflow.com/tags/vis.js) here also.
### Closing old issues
A new issue is often opened fast and then forgotten. Please help go trough [the old issues](//github.com/almende/vis/issues?q=is%3Aissue+is%3Aopen+sort%3Acreated-asc) (especially the [questions](//github.com/almende/vis/issues?q=is%3Aissue+is%3Aopen+sort%3Acreated-asc+label%3Aquestion)) and ask the creator of the issues if the problem still exists before closing the issue. The support team uses the **issue inactive** label to mark these issues.
A new issue is often opened fast and then forgotten. Please help go trough [the old issues](//github.com/almende/vis/issues?q=is%3Aissue+is%3Aopen+sort%3Acreated-asc) (especially the [questions](//github.com/almende/vis/issues?q=is%3Aissue+is%3Aopen+sort%3Acreated-asc+label%3AQuestion)) and ask the creator of the issues if the problem still exists before closing the issue. The support team uses the **issue inactive** label to mark these issues.
### Improve the webpage
@ -28,14 +28,14 @@ If you use vis.js to develop something beautiful feel free to create a pull-requ
### Confirming and fixing bugs
Every software has bugs. We also have [quite a nice collection](https://github.com/almende/vis/issues?q=is%3Aissue+is%3Aopen+label%3Abug+sort%3Areactions-%2B1-desc) ;-)
Every software has bugs. We also have [quite a nice collection](https://github.com/almende/vis/issues?q=is%3Aissue+is%3Aopen+label%3ABug+sort%3Areactions-%2B1-desc) ;-)
Feel free to fix as many bugs as you want!
You can not only help by fixing bugs, but also by confirming the bug or even creating a minimal code example to prove this bug exists.
### Implementing Feature-Requests
A lot of people have a lot of ideas for improving vis.js. [We label these issues as **enhancement**](https://github.com/almende/vis/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc+label%3Aenhancement). Feel free to implement a new feature by creating a new Pull-Request.
A lot of people have a lot of ideas for improving vis.js. [We label these issues as **Feature-Request**](https://github.com/almende/vis/labels/Feature-Request). Feel free to implement a new feature by creating a new Pull-Request.
[Some issues are labeled **For everybody!**](//github.com/almende/vis/issues?q=is%3Aissue+is%3Aopen+label%3A%22For+everyone%21%22+sort%3Areactions-%2B1-desc). These are a good starting point.
@ -64,4 +64,6 @@ There are some rules for pull-request:
* Always adapt to the code style of the existing source. Never adapt existing code to your personal taste. :trollface:
* Pull-requests must be reviewed by at least two member of the support team. The First must approve the pull-request, the second can than merge after also checking it.
**Happy Helping!!**

Loading…
Cancel
Save