Browse Source

Implemented snapping of items when dragging them

css_transitions
josdejong 10 years ago
parent
commit
b5085d9703
4 changed files with 104 additions and 64 deletions
  1. +43
    -38
      src/timeline/TimeStep.js
  2. +10
    -8
      src/timeline/Timeline.js
  3. +41
    -18
      src/timeline/component/ItemSet.js
  4. +10
    -0
      src/timeline/component/TimeAxis.js

+ 43
- 38
src/timeline/TimeStep.js View File

@ -281,35 +281,38 @@ 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
* 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
*/ */
TimeStep.prototype.snap = function(date) { TimeStep.prototype.snap = function(date) {
var clone = new Date(date.valueOf());
if (this.scale == TimeStep.SCALE.YEAR) { if (this.scale == TimeStep.SCALE.YEAR) {
var year = date.getFullYear() + Math.round(date.getMonth() / 12);
date.setFullYear(Math.round(year / this.step) * this.step);
date.setMonth(0);
date.setDate(0);
date.setHours(0);
date.setMinutes(0);
date.setSeconds(0);
date.setMilliseconds(0);
var year = clone.getFullYear() + Math.round(clone.getMonth() / 12);
clone.setFullYear(Math.round(year / this.step) * this.step);
clone.setMonth(0);
clone.setDate(0);
clone.setHours(0);
clone.setMinutes(0);
clone.setSeconds(0);
clone.setMilliseconds(0);
} }
else if (this.scale == TimeStep.SCALE.MONTH) { else if (this.scale == TimeStep.SCALE.MONTH) {
if (date.getDate() > 15) {
date.setDate(1);
date.setMonth(date.getMonth() + 1);
if (clone.getDate() > 15) {
clone.setDate(1);
clone.setMonth(clone.getMonth() + 1);
// important: first set Date to 1, after that change the month. // important: first set Date to 1, after that change the month.
} }
else { else {
date.setDate(1);
clone.setDate(1);
} }
date.setHours(0);
date.setMinutes(0);
date.setSeconds(0);
date.setMilliseconds(0);
clone.setHours(0);
clone.setMinutes(0);
clone.setSeconds(0);
clone.setMilliseconds(0);
} }
else if (this.scale == TimeStep.SCALE.DAY || else if (this.scale == TimeStep.SCALE.DAY ||
this.scale == TimeStep.SCALE.WEEKDAY) { this.scale == TimeStep.SCALE.WEEKDAY) {
@ -317,56 +320,58 @@ TimeStep.prototype.snap = function(date) {
switch (this.step) { switch (this.step) {
case 5: case 5:
case 2: case 2:
date.setHours(Math.round(date.getHours() / 24) * 24); break;
clone.setHours(Math.round(clone.getHours() / 24) * 24); break;
default: default:
date.setHours(Math.round(date.getHours() / 12) * 12); break;
clone.setHours(Math.round(clone.getHours() / 12) * 12); break;
} }
date.setMinutes(0);
date.setSeconds(0);
date.setMilliseconds(0);
clone.setMinutes(0);
clone.setSeconds(0);
clone.setMilliseconds(0);
} }
else if (this.scale == TimeStep.SCALE.HOUR) { else if (this.scale == TimeStep.SCALE.HOUR) {
switch (this.step) { switch (this.step) {
case 4: case 4:
date.setMinutes(Math.round(date.getMinutes() / 60) * 60); break;
clone.setMinutes(Math.round(clone.getMinutes() / 60) * 60); break;
default: default:
date.setMinutes(Math.round(date.getMinutes() / 30) * 30); break;
clone.setMinutes(Math.round(clone.getMinutes() / 30) * 30); break;
} }
date.setSeconds(0);
date.setMilliseconds(0);
clone.setSeconds(0);
clone.setMilliseconds(0);
} else if (this.scale == TimeStep.SCALE.MINUTE) { } else if (this.scale == TimeStep.SCALE.MINUTE) {
//noinspection FallthroughInSwitchStatementJS //noinspection FallthroughInSwitchStatementJS
switch (this.step) { switch (this.step) {
case 15: case 15:
case 10: case 10:
date.setMinutes(Math.round(date.getMinutes() / 5) * 5);
date.setSeconds(0);
clone.setMinutes(Math.round(clone.getMinutes() / 5) * 5);
clone.setSeconds(0);
break; break;
case 5: case 5:
date.setSeconds(Math.round(date.getSeconds() / 60) * 60); break;
clone.setSeconds(Math.round(clone.getSeconds() / 60) * 60); break;
default: default:
date.setSeconds(Math.round(date.getSeconds() / 30) * 30); break;
clone.setSeconds(Math.round(clone.getSeconds() / 30) * 30); break;
} }
date.setMilliseconds(0);
clone.setMilliseconds(0);
} }
else if (this.scale == TimeStep.SCALE.SECOND) { else if (this.scale == TimeStep.SCALE.SECOND) {
//noinspection FallthroughInSwitchStatementJS //noinspection FallthroughInSwitchStatementJS
switch (this.step) { switch (this.step) {
case 15: case 15:
case 10: case 10:
date.setSeconds(Math.round(date.getSeconds() / 5) * 5);
date.setMilliseconds(0);
clone.setSeconds(Math.round(clone.getSeconds() / 5) * 5);
clone.setMilliseconds(0);
break; break;
case 5: case 5:
date.setMilliseconds(Math.round(date.getMilliseconds() / 1000) * 1000); break;
clone.setMilliseconds(Math.round(clone.getMilliseconds() / 1000) * 1000); break;
default: default:
date.setMilliseconds(Math.round(date.getMilliseconds() / 500) * 500); break;
clone.setMilliseconds(Math.round(clone.getMilliseconds() / 500) * 500); break;
} }
} }
else if (this.scale == TimeStep.SCALE.MILLISECOND) { else if (this.scale == TimeStep.SCALE.MILLISECOND) {
var step = this.step > 5 ? this.step / 2 : 1; var step = this.step > 5 ? this.step / 2 : 1;
date.setMilliseconds(Math.round(date.getMilliseconds() / step) * step);
clone.setMilliseconds(Math.round(clone.getMilliseconds() / step) * step);
} }
return clone;
}; };
/** /**

+ 10
- 8
src/timeline/Timeline.js View File

@ -13,6 +13,7 @@ function Timeline (container, items, options) {
autoResize: true, autoResize: true,
editable: true, editable: true,
selectable: true, selectable: true,
snap: null, // will be specified after timeaxis is created
min: null, min: null,
max: null, max: null,
@ -24,7 +25,7 @@ function Timeline (container, items, options) {
showMinorLabels: true, showMinorLabels: true,
showMajorLabels: true, showMajorLabels: true,
showCurrentTime: false, showCurrentTime: false,
showCustomTime: false,
showCustomTime: false
}; };
// controller // controller
@ -119,6 +120,7 @@ function Timeline (container, items, options) {
this.timeaxis = new TimeAxis(this.itemPanel, [], timeaxisOptions); this.timeaxis = new TimeAxis(this.itemPanel, [], timeaxisOptions);
this.timeaxis.setRange(this.range); this.timeaxis.setRange(this.range);
this.controller.add(this.timeaxis); this.controller.add(this.timeaxis);
this.options.snap = this.timeaxis.snap.bind(this.timeaxis);
// current time bar // current time bar
this.currenttime = new CurrentTime(this.timeaxis, [], rootOptions); this.currenttime = new CurrentTime(this.timeaxis, [], rootOptions);
@ -211,26 +213,26 @@ Timeline.prototype.setItems = function(items) {
var initialLoad = (this.itemsData == null); var initialLoad = (this.itemsData == null);
// convert to type DataSet when needed // convert to type DataSet when needed
var newItemSet;
var newDataSet;
if (!items) { if (!items) {
newItemSet = null;
newDataSet = null;
} }
else if (items instanceof DataSet) { else if (items instanceof DataSet) {
newItemSet = items;
newDataSet = items;
} }
if (!(items instanceof DataSet)) { if (!(items instanceof DataSet)) {
newItemSet = new DataSet({
newDataSet = new DataSet({
convert: { convert: {
start: 'Date', start: 'Date',
end: 'Date' end: 'Date'
} }
}); });
newItemSet.add(items);
newDataSet.add(items);
} }
// set items // set items
this.itemsData = newItemSet;
this.content.setItems(newItemSet);
this.itemsData = newDataSet;
this.content.setItems(newDataSet);
if (initialLoad && (this.options.start == undefined || this.options.end == undefined)) { if (initialLoad && (this.options.start == undefined || this.options.end == undefined)) {
// apply the data range as range // apply the data range as range

+ 41
- 18
src/timeline/component/ItemSet.js View File

@ -106,6 +106,9 @@ ItemSet.types = {
* {Number} padding * {Number} padding
* Padding of the contents of an item in pixels. * Padding of the contents of an item in pixels.
* Must correspond with the items css. Default is 5. * Must correspond with the items css. Default is 5.
* {Function} snap
* Function to let items snap to nice dates when
* dragging items.
*/ */
ItemSet.prototype.setOptions = Component.prototype.setOptions; ItemSet.prototype.setOptions = Component.prototype.setOptions;
@ -688,9 +691,9 @@ ItemSet.prototype._onDragStart = function (event) {
me = this; me = this;
if (item && item.selected) { if (item && item.selected) {
var dragLeftItem = event.target.dragLeftItem; var dragLeftItem = event.target.dragLeftItem;
var dragRightItem = event.target.dragRightItem; var dragRightItem = event.target.dragRightItem;
if (dragLeftItem) { if (dragLeftItem) {
this.touchParams.itemProps = [{ this.touchParams.itemProps = [{
item: dragLeftItem, item: dragLeftItem,
@ -710,8 +713,12 @@ ItemSet.prototype._onDragStart = function (event) {
item: item item: item
}; };
if ('start' in item.data) { props.start = item.data.start.valueOf() }
if ('end' in item.data) { props.end = item.data.end.valueOf() }
if ('start' in item.data) {
props.start = item.data.start.valueOf()
}
if ('end' in item.data) {
props.end = item.data.end.valueOf()
}
return props; return props;
}); });
@ -728,17 +735,22 @@ ItemSet.prototype._onDragStart = function (event) {
*/ */
ItemSet.prototype._onDrag = function (event) { ItemSet.prototype._onDrag = function (event) {
if (this.touchParams.itemProps) { if (this.touchParams.itemProps) {
var deltaX = event.gesture.deltaX,
var snap = this.options.snap || null,
deltaX = event.gesture.deltaX,
offset = deltaX / this.conversion.scale; offset = deltaX / this.conversion.scale;
// move // move
this.touchParams.itemProps.forEach(function (props) { this.touchParams.itemProps.forEach(function (props) {
if ('start' in props) { props.item.data.start = new Date(props.start + offset); }
if ('end' in props) { props.item.data.end = new Date(props.end + offset); }
if ('start' in props) {
var start = new Date(props.start + offset);
props.item.data.start = snap ? snap(start) : start;
}
if ('end' in props) {
var end = new Date(props.end + offset);
props.item.data.end = snap ? snap(end) : end;
}
}); });
// TODO: implement snapping to nice dates
// TODO: implement dragging from one group to another // TODO: implement dragging from one group to another
this.requestReflow(); this.requestReflow();
@ -754,24 +766,35 @@ ItemSet.prototype._onDrag = function (event) {
*/ */
ItemSet.prototype._onDragEnd = function (event) { ItemSet.prototype._onDragEnd = function (event) {
if (this.touchParams.itemProps) { if (this.touchParams.itemProps) {
// prepare a changeset for the changed items
var changes = this.touchParams.itemProps.map(function (props) {
// prepare a change set for the changed items
var changes = [];
this.touchParams.itemProps.forEach(function (props) {
var change = { var change = {
id: props.item.id id: props.item.id
}; };
if ('start' in props.item.data) { change.start = props.item.data.start; }
if ('end' in props.item.data) { change.end = props.item.data.end; }
// TODO: only fire changes when start or end is actually changed
var changed = false;
if ('start' in props.item.data) {
changed = (props.start != props.item.data.start.valueOf());
change.start = props.item.data.start;
}
if ('end' in props.item.data) {
changed = changed || (props.end != props.item.data.end.valueOf());
change.end = props.item.data.end;
}
return change;
// only add changes when start or end is actually changed
if (changed) {
changes.push(change);
}
}); });
this.touchParams.itemProps = null; this.touchParams.itemProps = null;
// apply the changes to the data
var dataset = this._myDataSet();
dataset.update(changes);
// apply the changes to the data (if there are changes)
if (changes.length) {
var dataset = this._myDataSet();
dataset.update(changes);
}
event.stopPropagation(); event.stopPropagation();
} }

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

@ -520,3 +520,13 @@ TimeAxis.prototype._updateConversion = function() {
this.conversion = Range.conversion(range.start, range.end, this.width); this.conversion = Range.conversion(range.start, range.end, this.width);
} }
}; };
/**
* 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 snap (date) {
return this.step.snap(date);
};

Loading…
Cancel
Save