Browse Source

Implemented option `snap`

v3_develop
jos 9 years ago
parent
commit
1aea9fed30
11 changed files with 113 additions and 61 deletions
  1. +2
    -0
      HISTORY.md
  2. +11
    -0
      docs/timeline.html
  3. +54
    -0
      examples/timeline/33_custom_snapping.html
  4. +1
    -0
      examples/timeline/index.html
  5. +0
    -12
      lib/timeline/DataStep.js
  6. +1
    -2
      lib/timeline/Graph2d.js
  7. +22
    -18
      lib/timeline/TimeStep.js
  8. +7
    -2
      lib/timeline/Timeline.js
  9. +0
    -10
      lib/timeline/component/DataAxis.js
  10. +15
    -7
      lib/timeline/component/ItemSet.js
  11. +0
    -10
      lib/timeline/component/TimeAxis.js

+ 2
- 0
HISTORY.md View File

@ -30,6 +30,8 @@ http://visjs.org
### Timeline
- `Timeline.redraw()` now also recalculates the size of items.
- Implemented option `snap: function` to customize snapping to nice dates
when dragging items.
- Implemented option `timeAxis: {scale: string, step: number}` to set a
fixed scale.
- Fixed width of range items not always being maintained when moving due to

+ 11
- 0
docs/timeline.html View File

@ -742,6 +742,7 @@ var options = {
<code>showMinorLabels</code> are false, no horizontal axis will be
visible.</td>
</tr>
<tr>
<td>stack</td>
<td>Boolean</td>
@ -749,6 +750,16 @@ var options = {
<td>If true (default), items will be stacked on top of each other such that they do not overlap.</td>
</tr>
<tr>
<td>snap</td>
<td>function | null</td>
<td>function</td>
<td>When moving items on the Timeline, they will be snapped to nice dates like full hours or days, depending on the current scale. The <code>snap</code> function can be replaced with a custom function, or can be set to <code>null</code> to disable snapping. The signature of the snap function is:
<pre class="prettyprint lang-js">function snap(date: Date, scale: string, step: number) : Date | number</pre>
</td>
</tr>
<tr>
<td>start</td>
<td>Date | Number | String</td>

+ 54
- 0
examples/timeline/33_custom_snapping.html View File

@ -0,0 +1,54 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Timeline | Custom snapping</title>
<script src="../../dist/vis.js"></script>
<link href="../../dist/vis.css" rel="stylesheet" type="text/css" />
</head>
<body>
<p>
When moving the items in on the Timeline below, they will snap to full hours,
independent of being zoomed in or out.
</p>
<div id="visualization"></div>
<script type="text/javascript">
// DOM element where the Timeline will be attached
var container = document.getElementById('visualization');
// Create a DataSet (allows two way data-binding)
var items = new vis.DataSet([
{id: 1, content: 'A', start: '2015-02-09T04:00:00'},
{id: 2, content: 'B', start: '2015-02-09T14:00:00'},
{id: 3, content: 'C', start: '2015-02-09T16:00:00'},
{id: 4, content: 'D', start: '2015-02-09T17:00:00'},
{id: 5, content: 'E', start: '2015-02-10T03:00:00'}
]);
// Configuration for the Timeline
var options = {
editable: true,
// always snap to full hours, independent of the scale
snap: function (date, scale, step) {
var hour = 60 * 60 * 1000;
return Math.round(date / hour) * hour;
}
// to configure no snapping at all:
//
// snap: null
//
// or let the snap function return the date unchanged:
//
// snap: function (date, scale, step) {
// return date;
// }
};
// Create a Timeline
var timeline = new vis.Timeline(container, items, options);
</script>
</body>
</html>

+ 1
- 0
examples/timeline/index.html View File

@ -43,6 +43,7 @@
<p><a href="30_subgroups.html">30_subgroups.html</a></p>
<p><a href="31_background_areas_with_groups.html">31_background_areas_with_groups.html</a></p>
<p><a href="32_grid_styling.html">32_grid_styling.html</a></p>
<p><a href="33_custom_snapping.html">33_custom_snapping.html</a></p>
<p><a href="requirejs/requirejs_example.html">requirejs_example.html</a></p>

+ 0
- 12
lib/timeline/DataStep.js View File

@ -251,18 +251,6 @@ DataStep.prototype.getCurrent = function(decimals) {
return toPrecision;
};
/**
* Snap a date to a rounded value.
* The snap intervals are dependent on the current scale and step.
* @param {Date} date the date to be snapped.
* @return {Date} snappedDate
*/
DataStep.prototype.snap = function(date) {
};
/**
* Check if the current value is a major value (for example when the step
* is DAY, a major value is each first day of the MONTH)

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

@ -57,7 +57,6 @@ function Graph2d (container, items, groups, options) {
},
hiddenDates: [],
util: {
snap: null, // will be specified after TimeAxis is created
toScreen: me._toScreen.bind(me),
toGlobalScreen: me._toGlobalScreen.bind(me), // this refers to the root.width
toTime: me._toTime.bind(me),
@ -73,7 +72,7 @@ function Graph2d (container, items, groups, options) {
// time axis
this.timeAxis = new TimeAxis(this.body);
this.components.push(this.timeAxis);
this.body.util.snap = this.timeAxis.snap.bind(this.timeAxis);
//this.body.util.snap = this.timeAxis.snap.bind(this.timeAxis);
// current time bar
this.currentTime = new CurrentTime(this.body);

+ 22
- 18
lib/timeline/TimeStep.js View File

@ -321,15 +321,19 @@ TimeStep.prototype.setMinimumStep = function(minimumStep) {
/**
* Snap a date to a rounded value.
* The snap intervals are dependent on the current scale and step.
* @param {Date} date the date to be snapped.
* Static function
* @param {Date} date the date to be snapped.
* @param {string} scale Current scale, can be 'millisecond', 'second',
* 'minute', 'hour', 'weekday, 'day, 'month, 'year'.
* @param {number} step Current step (1, 2, 4, 5, ...
* @return {Date} snappedDate
*/
TimeStep.prototype.snap = function(date) {
TimeStep.snap = function(date, scale, step) {
var clone = new Date(date.valueOf());
if (this.scale == 'year') {
if (scale == 'year') {
var year = clone.getFullYear() + Math.round(clone.getMonth() / 12);
clone.setFullYear(Math.round(year / this.step) * this.step);
clone.setFullYear(Math.round(year / step) * step);
clone.setMonth(0);
clone.setDate(0);
clone.setHours(0);
@ -337,7 +341,7 @@ TimeStep.prototype.snap = function(date) {
clone.setSeconds(0);
clone.setMilliseconds(0);
}
else if (this.scale == 'month') {
else if (scale == 'month') {
if (clone.getDate() > 15) {
clone.setDate(1);
clone.setMonth(clone.getMonth() + 1);
@ -352,9 +356,9 @@ TimeStep.prototype.snap = function(date) {
clone.setSeconds(0);
clone.setMilliseconds(0);
}
else if (this.scale == 'day') {
else if (scale == 'day') {
//noinspection FallthroughInSwitchStatementJS
switch (this.step) {
switch (step) {
case 5:
case 2:
clone.setHours(Math.round(clone.getHours() / 24) * 24); break;
@ -365,9 +369,9 @@ TimeStep.prototype.snap = function(date) {
clone.setSeconds(0);
clone.setMilliseconds(0);
}
else if (this.scale == 'weekday') {
else if (scale == 'weekday') {
//noinspection FallthroughInSwitchStatementJS
switch (this.step) {
switch (step) {
case 5:
case 2:
clone.setHours(Math.round(clone.getHours() / 12) * 12); break;
@ -378,8 +382,8 @@ TimeStep.prototype.snap = function(date) {
clone.setSeconds(0);
clone.setMilliseconds(0);
}
else if (this.scale == 'hour') {
switch (this.step) {
else if (scale == 'hour') {
switch (step) {
case 4:
clone.setMinutes(Math.round(clone.getMinutes() / 60) * 60); break;
default:
@ -387,9 +391,9 @@ TimeStep.prototype.snap = function(date) {
}
clone.setSeconds(0);
clone.setMilliseconds(0);
} else if (this.scale == 'minute') {
} else if (scale == 'minute') {
//noinspection FallthroughInSwitchStatementJS
switch (this.step) {
switch (step) {
case 15:
case 10:
clone.setMinutes(Math.round(clone.getMinutes() / 5) * 5);
@ -402,9 +406,9 @@ TimeStep.prototype.snap = function(date) {
}
clone.setMilliseconds(0);
}
else if (this.scale == 'second') {
else if (scale == 'second') {
//noinspection FallthroughInSwitchStatementJS
switch (this.step) {
switch (step) {
case 15:
case 10:
clone.setSeconds(Math.round(clone.getSeconds() / 5) * 5);
@ -416,9 +420,9 @@ TimeStep.prototype.snap = function(date) {
clone.setMilliseconds(Math.round(clone.getMilliseconds() / 500) * 500); break;
}
}
else if (this.scale == 'millisecond') {
var step = this.step > 5 ? this.step / 2 : 1;
clone.setMilliseconds(Math.round(clone.getMilliseconds() / step) * step);
else if (scale == 'millisecond') {
var _step = step > 5 ? step / 2 : 1;
clone.setMilliseconds(Math.round(clone.getMilliseconds() / _step) * _step);
}
return clone;

+ 7
- 2
lib/timeline/Timeline.js View File

@ -62,7 +62,13 @@ function Timeline (container, items, groups, options) {
},
hiddenDates: [],
util: {
snap: null, // will be specified after TimeAxis is created
getScale: function () {
return me.timeAxis.step.scale;
},
getStep: function () {
return me.timeAxis.step.step;
},
toScreen: me._toScreen.bind(me),
toGlobalScreen: me._toGlobalScreen.bind(me), // this refers to the root.width
toTime: me._toTime.bind(me),
@ -78,7 +84,6 @@ function Timeline (container, items, groups, options) {
// time axis
this.timeAxis = new TimeAxis(this.body);
this.components.push(this.timeAxis);
this.body.util.snap = this.timeAxis.snap.bind(this.timeAxis);
// current time bar
this.currentTime = new CurrentTime(this.body);

+ 0
- 10
lib/timeline/component/DataAxis.js View File

@ -626,14 +626,4 @@ DataAxis.prototype._calculateCharSize = function () {
}
};
/**
* Snap a date to a rounded value.
* The snap intervals are dependent on the current scale and step.
* @param {Date} date the date to be snapped.
* @return {Date} snappedDate
*/
DataAxis.prototype.snap = function(date) {
return this.step.snap(date);
};
module.exports = DataAxis;

+ 15
- 7
lib/timeline/component/ItemSet.js View File

@ -2,6 +2,7 @@ var Hammer = require('../../module/hammer');
var util = require('../../util');
var DataSet = require('../../DataSet');
var DataView = require('../../DataView');
var TimeStep = require('../TimeStep');
var Component = require('./Component');
var Group = require('./Group');
var BackgroundGroup = require('./BackgroundGroup');
@ -41,6 +42,8 @@ function ItemSet(body, options) {
remove: false
},
snap: TimeStep.snap,
onAdd: function (item, callback) {
callback(item);
},
@ -271,7 +274,7 @@ ItemSet.prototype._create = function(){
ItemSet.prototype.setOptions = function(options) {
if (options) {
// copy all options that we know
var fields = ['type', 'align', 'orientation', 'padding', 'stack', 'selectable', 'groupOrder', 'dataAttributes', 'template','hide'];
var fields = ['type', 'align', 'orientation', 'padding', 'stack', 'selectable', 'groupOrder', 'dataAttributes', 'template','hide', 'snap'];
util.selectiveExtend(fields, this.options, options);
if ('margin' in options) {
@ -1171,8 +1174,10 @@ ItemSet.prototype._onDrag = function (event) {
if (this.touchParams.itemProps) {
var me = this;
var snap = this.body.util.snap || null;
var snap = this.options.snap || null;
var xOffset = this.body.dom.root.offsetLeft + this.body.domProps.left.width;
var scale = this.body.util.getScale();
var step = this.body.util.getStep();
// move
this.touchParams.itemProps.forEach(function (props) {
@ -1183,12 +1188,12 @@ ItemSet.prototype._onDrag = function (event) {
if ('start' in props) {
var start = new Date(props.start + offset);
newProps.start = snap ? snap(start) : start;
newProps.start = snap ? snap(start, scale, step) : start;
}
if ('end' in props) {
var end = new Date(props.end + offset);
newProps.end = snap ? snap(end) : end;
newProps.end = snap ? snap(end, scale, step) : end;
}
else if ('duration' in props) {
newProps.end = new Date(newProps.start.valueOf() + props.duration);
@ -1356,7 +1361,7 @@ ItemSet.prototype._onAddItem = function (event) {
if (!this.options.editable.add) return;
var me = this,
snap = this.body.util.snap || null,
snap = this.options.snap || null,
item = ItemSet.itemFromTarget(event);
if (item) {
@ -1375,15 +1380,18 @@ ItemSet.prototype._onAddItem = function (event) {
var xAbs = util.getAbsoluteLeft(this.dom.frame);
var x = event.gesture.center.pageX - xAbs;
var start = this.body.util.toTime(x);
var scale = this.body.util.getScale();
var step = this.body.util.getStep();
var newItem = {
start: snap ? snap(start) : start,
start: snap ? snap(start, scale, step) : start,
content: 'new item'
};
// when default type is a range, add a default end date to the new item
if (this.options.type === 'range') {
var end = this.body.util.toTime(x + this.props.width / 5);
newItem.end = snap ? snap(end) : end;
newItem.end = snap ? snap(end, scale, step) : end;
}
newItem[this.itemsData._fieldId] = util.randomUUID();

+ 0
- 10
lib/timeline/component/TimeAxis.js View File

@ -433,14 +433,4 @@ TimeAxis.prototype._calculateCharSize = function () {
this.props.majorCharWidth = this.dom.measureCharMajor.clientWidth;
};
/**
* Snap a date to a rounded value.
* The snap intervals are dependent on the current scale and step.
* @param {Date} date the date to be snapped.
* @return {Date} snappedDate
*/
TimeAxis.prototype.snap = function(date) {
return this.step.snap(date);
};
module.exports = TimeAxis;

Loading…
Cancel
Save