From 1aea9fed3012036ec0354f997ef4056051072ac7 Mon Sep 17 00:00:00 2001 From: jos Date: Tue, 10 Feb 2015 11:19:25 +0100 Subject: [PATCH] Implemented option `snap` --- HISTORY.md | 2 + docs/timeline.html | 11 +++++ examples/timeline/33_custom_snapping.html | 54 +++++++++++++++++++++++ examples/timeline/index.html | 1 + lib/timeline/DataStep.js | 12 ----- lib/timeline/Graph2d.js | 3 +- lib/timeline/TimeStep.js | 40 +++++++++-------- lib/timeline/Timeline.js | 9 +++- lib/timeline/component/DataAxis.js | 10 ----- lib/timeline/component/ItemSet.js | 22 ++++++--- lib/timeline/component/TimeAxis.js | 10 ----- 11 files changed, 113 insertions(+), 61 deletions(-) create mode 100644 examples/timeline/33_custom_snapping.html diff --git a/HISTORY.md b/HISTORY.md index f261262b..1096ce5d 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -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 diff --git a/docs/timeline.html b/docs/timeline.html index 0074ee8e..87102bb3 100644 --- a/docs/timeline.html +++ b/docs/timeline.html @@ -742,6 +742,7 @@ var options = { showMinorLabels are false, no horizontal axis will be visible. + stack Boolean @@ -749,6 +750,16 @@ var options = { If true (default), items will be stacked on top of each other such that they do not overlap. + + snap + function | null + function + When moving items on the Timeline, they will be snapped to nice dates like full hours or days, depending on the current scale. The snap function can be replaced with a custom function, or can be set to null to disable snapping. The signature of the snap function is: +
function snap(date: Date, scale: string, step: number) : Date | number
+ + + + start Date | Number | String diff --git a/examples/timeline/33_custom_snapping.html b/examples/timeline/33_custom_snapping.html new file mode 100644 index 00000000..53c496ec --- /dev/null +++ b/examples/timeline/33_custom_snapping.html @@ -0,0 +1,54 @@ + + + + Timeline | Custom snapping + + + + + +

+ When moving the items in on the Timeline below, they will snap to full hours, + independent of being zoomed in or out. +

+
+ + + + \ No newline at end of file diff --git a/examples/timeline/index.html b/examples/timeline/index.html index 37912cd4..c21533dc 100644 --- a/examples/timeline/index.html +++ b/examples/timeline/index.html @@ -43,6 +43,7 @@

30_subgroups.html

31_background_areas_with_groups.html

32_grid_styling.html

+

33_custom_snapping.html

requirejs_example.html

diff --git a/lib/timeline/DataStep.js b/lib/timeline/DataStep.js index 752e0785..bce96b5a 100644 --- a/lib/timeline/DataStep.js +++ b/lib/timeline/DataStep.js @@ -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) diff --git a/lib/timeline/Graph2d.js b/lib/timeline/Graph2d.js index 78003721..07fe656d 100644 --- a/lib/timeline/Graph2d.js +++ b/lib/timeline/Graph2d.js @@ -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); diff --git a/lib/timeline/TimeStep.js b/lib/timeline/TimeStep.js index a0772f7f..8d8bc7cb 100644 --- a/lib/timeline/TimeStep.js +++ b/lib/timeline/TimeStep.js @@ -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; diff --git a/lib/timeline/Timeline.js b/lib/timeline/Timeline.js index 7d70b0b5..6deea30a 100644 --- a/lib/timeline/Timeline.js +++ b/lib/timeline/Timeline.js @@ -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); diff --git a/lib/timeline/component/DataAxis.js b/lib/timeline/component/DataAxis.js index c82c8361..0af46490 100644 --- a/lib/timeline/component/DataAxis.js +++ b/lib/timeline/component/DataAxis.js @@ -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; diff --git a/lib/timeline/component/ItemSet.js b/lib/timeline/component/ItemSet.js index 480a5806..05c45f39 100644 --- a/lib/timeline/component/ItemSet.js +++ b/lib/timeline/component/ItemSet.js @@ -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(); diff --git a/lib/timeline/component/TimeAxis.js b/lib/timeline/component/TimeAxis.js index 92cd3898..f7433ce9 100644 --- a/lib/timeline/component/TimeAxis.js +++ b/lib/timeline/component/TimeAxis.js @@ -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;