Browse Source

Merged PR #1729

codeClimate
jos 8 years ago
parent
commit
a12b82e9f5
24 changed files with 856 additions and 286 deletions
  1. +16
    -0
      dist/vis.css
  2. +387
    -161
      dist/vis.js
  3. +1
    -1
      dist/vis.min.css
  4. +1
    -1
      docs/data/dataset.html
  5. +1
    -0
      examples/timeline/groups/groupsEditable.html
  6. +77
    -0
      examples/timeline/other/rtl.html
  7. +6
    -1
      lib/network/modules/ManipulationSystem.js
  8. +9
    -2
      lib/timeline/Core.js
  9. +28
    -11
      lib/timeline/Range.js
  10. +16
    -8
      lib/timeline/Stack.js
  11. +29
    -12
      lib/timeline/Timeline.js
  12. +7
    -2
      lib/timeline/component/CurrentTime.js
  13. +2
    -2
      lib/timeline/component/DataAxis.js
  14. +2
    -3
      lib/timeline/component/Group.js
  15. +99
    -29
      lib/timeline/component/ItemSet.js
  16. +34
    -9
      lib/timeline/component/TimeAxis.js
  17. +11
    -0
      lib/timeline/component/css/item.css
  18. +5
    -0
      lib/timeline/component/css/timeaxis.css
  19. +46
    -19
      lib/timeline/component/item/BoxItem.js
  20. +7
    -1
      lib/timeline/component/item/Item.js
  21. +24
    -9
      lib/timeline/component/item/PointItem.js
  22. +42
    -14
      lib/timeline/component/item/RangeItem.js
  23. +2
    -1
      lib/timeline/optionsTimeline.js
  24. +4
    -0
      lib/util.js

+ 16
- 0
dist/vis.css View File

@ -565,6 +565,17 @@ input.vis-configuration.vis-config-range:focus::-ms-fill-upper {
cursor: pointer;
}
.vis-item .vis-delete-rtl {
background: url('img/timeline/delete.png') no-repeat center;
position: absolute;
width: 24px;
height: 24px;
top: -4px;
left: -24px;
cursor: pointer;
}
.vis-item.vis-range .vis-drag-left {
position: absolute;
width: 24px;
@ -637,6 +648,11 @@ input.vis-configuration.vis-config-range:focus::-ms-fill-upper {
border-left: 1px solid;
}
.vis-time-axis .vis-grid.vis-vertical-rtl {
position: absolute;
border-right: 1px solid;
}
.vis-time-axis .vis-grid.vis-minor {
border-color: #e5e5e5;
}

+ 387
- 161
dist/vis.js
File diff suppressed because it is too large
View File


+ 1
- 1
dist/vis.min.css
File diff suppressed because it is too large
View File


+ 1
- 1
docs/data/dataset.html View File

@ -431,7 +431,7 @@ var data = new vis.DataSet([data] [, options])
</td>
<td>Number[]</td>
<td>
Update on ore multiple existing items. <code>data</code> can be a single item or an array with items. When an item doesn't exist, it will be created. Returns an array with the ids of the removed items. See section <a href="#Data_Manipulation">Data Manipulation</a>.
Update one or multiple existing items. <code>data</code> can be a single item or an array with items. When an item doesn't exist, it will be created. Returns an array with the ids of the removed items. See section <a href="#Data_Manipulation">Data Manipulation</a>.
</td>
</tr>

+ 1
- 0
examples/timeline/groups/groupsEditable.html View File

@ -299,6 +299,7 @@
a.value = b.value;
b.value = v;
},
orientation: 'both',
editable: true,
groupEditable: true,
start: new Date(2015, 6, 1),

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

@ -0,0 +1,77 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Timeline | RTL example</title>
<style>
body, html {
font-family: arial, sans-serif;
font-size: 11pt;
}
</style>
<script src="../../../dist/vis.js"></script>
<link href="../../../dist/vis.css" rel="stylesheet" type="text/css" />
<script src="../../googleAnalytics.js"></script>
</head>
<body>
<p>An editable timeline allows to drag items around, create new items, and remove items. Changes are logged in the browser console.</p>
<div id="visualization"></div>
<script>
// create a dataset with items
// we specify the type of the fields `start` and `end` here to be strings
// containing an ISO date. The fields will be outputted as ISO dates
// automatically getting data from the DataSet via items.get().
var items = new vis.DataSet({
type: { start: 'ISODate', end: 'ISODate' }
});
// add items to the DataSet
items.add([
{id: 1, content: '2014-01-23 <br>start', start: '2014-01-23'},
{id: 2, content: '2014-01-18', start: '2014-01-18'},
{id: 3, content: '2014-01-21', start: '2014-01-21'},
{id: 4, content: '2014-01-19 - 2014-01-24', start: '2014-01-19', end: '2014-01-24'},
{id: 5, content: '2014-01-28', start: '2014-01-28', type:'point'},
{id: 6, content: '2014-01-26', start: '2014-01-26'}
]);
// log changes to the console
items.on('*', function (event, properties) {
console.log(event, properties.items);
});
var container = document.getElementById('visualization');
var options = {
start: '2014-01-10',
end: '2014-02-10',
height: '300px',
rtl: true,
// allow selecting multiple items using ctrl+click, shift+click, or hold.
multiselect: true,
// allow manipulation of items
editable: true,
/* alternatively, enable/disable individual actions:
editable: {
add: true,
updateTime: true,
updateGroup: true,
remove: true
},
*/
showCurrentTime: true
};
var timeline = new vis.Timeline(container, items, options);
</script>
</body>
</html>

+ 6
- 1
lib/network/modules/ManipulationSystem.js View File

@ -689,7 +689,12 @@ class ManipulationSystem {
}
_createDeleteButton(locale) {
let button = this._createButton('delete', 'vis-button vis-delete', locale['del'] || this.options.locales['en']['del']);
if (this.options.rtl) {
var deleteBtnClass = 'vis-button vis-delete-rtl';
} else {
var deleteBtnClass = 'vis-button vis-delete';
}
let button = this._createButton('delete', deleteBtnClass, locale['del'] || this.options.locales['en']['del']);
this.manipulationDiv.appendChild(button);
this._bindHammerToDiv(button, this.deleteSelected.bind(this));
}

+ 9
- 2
lib/timeline/Core.js View File

@ -222,11 +222,19 @@ Core.prototype.setOptions = function (options) {
var fields = [
'width', 'height', 'minHeight', 'maxHeight', 'autoResize',
'start', 'end', 'clickToUse', 'dataAttributes', 'hiddenDates',
'locale', 'locales', 'moment',
'locale', 'locales', 'moment', 'rtl',
'throttleRedraw'
];
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.options.orientation = {item:undefined,axis:undefined};
if ('orientation' in options) {
if (typeof options.orientation === 'string') {
@ -529,7 +537,6 @@ Core.prototype.fit = function(options) {
var interval = range.max - range.min;
var min = new Date(range.min.valueOf() - interval * 0.01);
var max = new Date(range.max.valueOf() + interval * 0.01);
var animation = (options && options.animation !== undefined) ? options.animation : true;
this.range.setRange(min, max, animation);
};

+ 28
- 11
lib/timeline/Range.js View File

@ -25,6 +25,7 @@ function Range(body, options) {
// default options
this.defaultOptions = {
rtl: false,
start: null,
end: null,
moment: moment,
@ -37,7 +38,6 @@ function Range(body, options) {
zoomMax: 1000 * 60 * 60 * 24 * 365 * 10000 // milliseconds
};
this.options = util.extend({}, this.defaultOptions);
this.props = {
touch: {}
};
@ -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'
'moment', 'activate', 'hiddenDates', 'zoomKey', 'rtl'
];
util.selectiveExtend(fields, this.options, options);
@ -411,7 +411,13 @@ Range.prototype._onDrag = function (event) {
interval -= duration;
var width = (direction == 'horizontal') ? this.body.domProps.center.width : this.body.domProps.center.height;
var diffRange = -delta / width * interval;
if (this.options.rtl) {
var diffRange = delta / width * interval;
} else {
var diffRange = -delta / width * interval;
}
var newStart = this.props.touch.start + diffRange;
var newEnd = this.props.touch.end + diffRange;
@ -513,7 +519,7 @@ Range.prototype._onMouseWheel = function(event) {
}
// calculate center, the date to zoom around
var pointer = getPointer({x: event.clientX, y: event.clientY}, this.body.dom.center);
var pointer = this.getPointer({x: event.clientX, y: event.clientY}, this.body.dom.center);
var pointerDate = this._pointerToDate(pointer);
this.zoom(scale, pointerDate, delta);
@ -549,7 +555,7 @@ Range.prototype._onPinch = function (event) {
this.props.touch.allowDragging = false;
if (!this.props.touch.center) {
this.props.touch.center = getPointer(event.center, this.body.dom.center);
this.props.touch.center = this.getPointer(event.center, this.body.dom.center);
}
var scale = 1 / (event.scale + this.scaleOffset);
@ -594,7 +600,11 @@ Range.prototype._isInsideRange = function(event) {
// calculate the time where the mouse is, check whether inside
// and no scroll action should happen.
var clientX = event.center ? event.center.x : event.clientX;
var x = clientX - util.getAbsoluteLeft(this.body.dom.centerContainer);
if (this.options.rtl) {
var x = clientX - util.getAbsoluteLeft(this.body.dom.centerContainer);
} else {
var x = util.getAbsoluteRight(this.body.dom.centerContainer) - clientX;
}
var time = this.body.util.toTime(x);
return time >= this.start && time <= this.end;
@ -629,11 +639,18 @@ Range.prototype._pointerToDate = function (pointer) {
* @return {{x: Number, y: Number}} pointer
* @private
*/
function getPointer (touch, element) {
return {
x: touch.x - util.getAbsoluteLeft(element),
y: touch.y - util.getAbsoluteTop(element)
};
Range.prototype.getPointer = function (touch, element) {
if (this.options.rtl) {
return {
x: util.getAbsoluteRight(element) - touch.x,
y: touch.y - util.getAbsoluteTop(element)
};
} else {
return {
x: touch.x - util.getAbsoluteLeft(element),
y: touch.y - util.getAbsoluteTop(element)
};
}
}
/**

+ 16
- 8
lib/timeline/Stack.js View File

@ -37,8 +37,7 @@ exports.orderByEnd = function(items) {
* items having a top===null will be re-stacked
*/
exports.stack = function(items, margin, force) {
var i, iMax;
var i, iMax;
if (force) {
// reset top position of all items
for (i = 0, iMax = items.length; i < iMax; i++) {
@ -59,7 +58,7 @@ exports.stack = function(items, margin, force) {
var collidingItem = null;
for (var j = 0, jj = items.length; j < jj; j++) {
var other = items[j];
if (other.top !== null && other !== item && other.stack && exports.collision(item, other, margin.item)) {
if (other.top !== null && other !== item && other.stack && exports.collision(item, other, margin.item, other.options.rtl)) {
collidingItem = other;
break;
}
@ -114,9 +113,18 @@ exports.nostack = function(items, margin, subgroups) {
* minimum required margin.
* @return {boolean} true if a and b collide, else false
*/
exports.collision = function(a, b, margin) {
return ((a.left - margin.horizontal + EPSILON) < (b.left + b.width) &&
(a.left + a.width + margin.horizontal - EPSILON) > b.left &&
(a.top - margin.vertical + EPSILON) < (b.top + b.height) &&
(a.top + a.height + margin.vertical - EPSILON) > b.top);
exports.collision = function(a, b, margin, rtl) {
var isCollision = null;
if (rtl) {
isCollision = ((a.right - margin.horizontal + EPSILON) < (b.right + b.width) &&
(a.right + a.width + margin.horizontal - EPSILON) > b.right &&
(a.top - margin.vertical + EPSILON) < (b.top + b.height) &&
(a.top + a.height + margin.vertical - EPSILON) > b.top);
} else {
((a.left - margin.horizontal + EPSILON) < (b.left + b.width) &&
(a.left + a.width + margin.horizontal - EPSILON) > b.left &&
(a.top - margin.vertical + EPSILON) < (b.top + b.height) &&
(a.top + a.height + margin.vertical - EPSILON) > b.top);
}
return isCollision;
};

+ 29
- 12
lib/timeline/Timeline.js View File

@ -29,6 +29,7 @@ import Validator from '../shared/Validator';
* @extends Core
*/
function Timeline (container, items, groups, options) {
if (!(this instanceof Timeline)) {
throw new SyntaxError('Constructor must be called with the new operator');
}
@ -52,7 +53,7 @@ function Timeline (container, items, groups, options) {
axis: 'bottom', // axis orientation: 'bottom', 'top', or 'both'
item: 'bottom' // not relevant
},
rtl: false,
moment: moment,
width: null,
@ -107,7 +108,7 @@ function Timeline (container, items, groups, options) {
this.components.push(this.currentTime);
// item set
this.itemSet = new ItemSet(this.body);
this.itemSet = new ItemSet(this.body, this.options);
this.components.push(this.itemSet);
this.itemsData = null; // DataSet
@ -191,6 +192,7 @@ Timeline.prototype.redraw = function() {
Timeline.prototype.setOptions = function (options) {
// validate options
let errorFound = Validator.validate(options, allOptions);
if (errorFound === true) {
console.log('%cErrors have been found in the supplied options object.', printStyle);
}
@ -424,16 +426,22 @@ Timeline.prototype.getItemRange = function () {
var start = getStart(item);
var end = getEnd(item);
if (this.options.rtl) {
var startSide = start - (item.getWidthRight() + 10) * factor;
var endSide = end + (item.getWidthLeft() + 10) * factor;
} else {
var startSide = start - (item.getWidthLeft() + 10) * factor;
var endSide = end + (item.getWidthRight() + 10) * factor;
}
var left = start - (item.getWidthLeft() + 10) * factor;
var right = end + (item.getWidthRight() + 10) * factor;
if (left < min) {
min = left;
if (startSide < min) {
min = startSide;
minItem = item;
}
if (right > max) {
max = right;
if (endSide > max) {
max = endSide;
maxItem = item;
}
}.bind(this));
@ -444,8 +452,13 @@ Timeline.prototype.getItemRange = function () {
var delta = this.props.center.width - lhs - rhs; // px
if (delta > 0) {
min = getStart(minItem) - lhs * interval / delta; // ms
max = getEnd(maxItem) + rhs * interval / delta; // ms
if (this.options.rtl) {
min = getStart(minItem) - rhs * interval / delta; // ms
max = getEnd(maxItem) + lhs * interval / delta; // ms
} else {
min = getStart(minItem) - lhs * interval / delta; // ms
max = getEnd(maxItem) + rhs * interval / delta; // ms
}
}
}
}
@ -493,7 +506,11 @@ Timeline.prototype.getDataRange = function() {
Timeline.prototype.getEventProperties = function (event) {
var clientX = event.center ? event.center.x : event.clientX;
var clientY = event.center ? event.center.y : event.clientY;
var x = clientX - util.getAbsoluteLeft(this.dom.centerContainer);
if (this.options.rtl) {
var x = util.getAbsoluteRight(this.dom.centerContainer) - clientX;
} else {
var x = clientX - util.getAbsoluteLeft(this.dom.centerContainer);
}
var y = clientY - util.getAbsoluteTop(this.dom.centerContainer);
var item = this.itemSet.itemFromTarget(event);

+ 7
- 2
lib/timeline/component/CurrentTime.js View File

@ -16,6 +16,7 @@ function CurrentTime (body, options) {
// default options
this.defaultOptions = {
rtl: false,
showCurrentTime: true,
moment: moment,
@ -64,7 +65,7 @@ CurrentTime.prototype.destroy = function () {
CurrentTime.prototype.setOptions = function(options) {
if (options) {
// copy all options that we know
util.selectiveExtend(['showCurrentTime', 'moment', 'locale', 'locales'], this.options, options);
util.selectiveExtend(['rtl', 'showCurrentTime', 'moment', 'locale', 'locales'], this.options, options);
}
};
@ -99,7 +100,11 @@ CurrentTime.prototype.redraw = function() {
var title = locale.current + ' ' + locale.time + ': ' + now.format('dddd, MMMM Do YYYY, H:mm:ss');
title = title.charAt(0).toUpperCase() + title.substring(1);
this.bar.style.left = x + 'px';
if (this.options.rtl) {
this.bar.style.right = x + 'px';
} else {
this.bar.style.left = x + 'px';
}
this.bar.title = title;
}
else {

+ 2
- 2
lib/timeline/component/DataAxis.js View File

@ -212,11 +212,11 @@ DataAxis.prototype._cleanupIcons = function() {
DataAxis.prototype.show = function() {
this.hidden = false;
if (!this.dom.frame.parentNode) {
if (this.options.orientation === 'left') {
if (this.options.rtl) {
this.body.dom.left.appendChild(this.dom.frame);
}
else {
this.body.dom.right.appendChild(this.dom.frame);
this.body.dom.left.appendChild(this.dom.frame);
}
}

+ 2
- 3
lib/timeline/component/Group.js View File

@ -209,8 +209,8 @@ Group.prototype.redraw = function(range, margin, restack) {
}
else {
// no custom order function, lazy stacking
this.visibleItems = this._updateVisibleItems(this.orderedItems, this.visibleItems, range);
this.visibleItems = this._updateVisibleItems(this.orderedItems, this.visibleItems, range);
if (this.itemSet.options.stack) { // TODO: ugly way to access options...
stack.stack(this.visibleItems, margin, restack);
}
@ -225,10 +225,9 @@ Group.prototype.redraw = function(range, margin, restack) {
// calculate actual size and position
var foreground = this.dom.foreground;
this.top = foreground.offsetTop;
this.left = foreground.offsetLeft;
this.right = foreground.offsetLeft;
this.width = foreground.offsetWidth;
resized = util.updateProperty(this, 'height', height) || resized;
// recalculate size of label
resized = util.updateProperty(this.props.label, 'width', this.dom.inner.clientWidth) || resized;
resized = util.updateProperty(this.props.label, 'height', this.dom.inner.clientHeight) || resized;

+ 99
- 29
lib/timeline/component/ItemSet.js View File

@ -26,8 +26,8 @@ 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 +96,7 @@ function ItemSet(body, options) {
// options is shared by this ItemSet and all its items
this.options = util.extend({}, this.defaultOptions);
// options for getting items from the DataSet with the correct type
this.itemOptions = {
type: {start: 'Date', end: 'Date'}
@ -230,8 +230,8 @@ ItemSet.prototype._create = function(){
// add item on doubletap
this.hammer.on('doubletap', this._onAddItem.bind(this));
this.groupHammer = new Hammer(this.body.dom.leftContainer);
this.groupHammer.on('panstart', this._onGroupDragStart.bind(this));
this.groupHammer.on('panmove', this._onGroupDrag.bind(this));
this.groupHammer.on('panend', this._onGroupDragEnd.bind(this));
@ -308,7 +308,7 @@ ItemSet.prototype._create = function(){
ItemSet.prototype.setOptions = function(options) {
if (options) {
// copy all options that we know
var fields = ['type', 'align', 'order', 'stack', 'selectable', 'multiselect', 'itemsAlwaysDraggable', 'multiselectPerGroup', 'groupOrder', 'dataAttributes', 'template', 'groupTemplate', 'hide', 'snap', 'groupOrderSwap'];
var fields = ['type', 'rtl', 'align', 'order', 'stack', 'selectable', 'multiselect', 'itemsAlwaysDraggable', 'multiselectPerGroup', 'groupOrder', 'dataAttributes', 'template', 'groupTemplate', 'hide', 'snap', 'groupOrderSwap'];
util.selectiveExtend(fields, this.options, options);
if ('orientation' in options) {
@ -498,8 +498,14 @@ ItemSet.prototype.getSelection = function() {
*/
ItemSet.prototype.getVisibleItems = function() {
var range = this.body.range.getRange();
var left = this.body.util.toScreen(range.start);
var right = this.body.util.toScreen(range.end);
if (this.options.rtl) {
var right = this.body.util.toScreen(range.start);
var left = this.body.util.toScreen(range.end);
} else {
var left = this.body.util.toScreen(range.start);
var right = this.body.util.toScreen(range.end);
}
var ids = [];
for (var groupId in this.groups) {
@ -512,8 +518,14 @@ ItemSet.prototype.getVisibleItems = function() {
for (var i = 0; i < rawVisibleItems.length; i++) {
var item = rawVisibleItems[i];
// TODO: also check whether visible vertically
if ((item.left < right) && (item.left + item.width > left)) {
ids.push(item.id);
if (this.options.rtl) {
if ((item.right < left) && (item.right + item.width > right)) {
ids.push(item.id);
}
} else {
if ((item.left < right) && (item.left + item.width > left)) {
ids.push(item.id);
}
}
}
}
@ -552,7 +564,12 @@ ItemSet.prototype.redraw = function() {
// recalculate absolute position (before redrawing groups)
this.props.top = this.body.domProps.top.height + this.body.domProps.border.top;
this.props.left = this.body.domProps.left.width + this.body.domProps.border.left;
if (this.options.rtl) {
this.props.right = this.body.domProps.right.width + this.body.domProps.border.right;
} else {
this.props.left = this.body.domProps.left.width + this.body.domProps.border.left;
}
// update class name
frame.className = 'vis-itemset';
@ -605,7 +622,11 @@ ItemSet.prototype.redraw = function() {
this.dom.axis.style.top = asSize((orientation == 'top') ?
(this.body.domProps.top.height + this.body.domProps.border.top) :
(this.body.domProps.top.height + this.body.domProps.centerContainer.height));
this.dom.axis.style.left = '0';
if (this.options.rtl) {
this.dom.axis.style.right = '0';
} else {
this.dom.axis.style.left = '0';
}
// check if this component is resized
resized = this._isResized() || resized;
@ -1245,8 +1266,15 @@ ItemSet.prototype._onDragStart = function (event) {
*/
ItemSet.prototype._onDragStartAddItem = function (event) {
var snap = this.options.snap || null;
var xAbs = util.getAbsoluteLeft(this.dom.frame);
var x = event.center.x - xAbs - 10; // minus 10 to compensate for the drag starting as soon as you've moved 10px
if (this.options.rtl) {
var xAbs = util.getAbsoluteRight(this.dom.frame);
var x = xAbs - event.center.x + 10; // plus 10 to compensate for the drag starting as soon as you've moved 10px
} else {
var xAbs = util.getAbsoluteLeft(this.dom.frame);
var x = event.center.x - xAbs - 10; // minus 10 to compensate for the drag starting as soon as you've moved 10px
}
var time = this.body.util.toTime(x);
var scale = this.body.util.getScale();
var step = this.body.util.getStep();
@ -1267,7 +1295,6 @@ ItemSet.prototype._onDragStartAddItem = function (event) {
if (group) {
itemData.group = group.groupId;
}
var newItem = new RangeItem(itemData, this.conversion, this.options);
newItem.id = id; // TODO: not so nice setting id afterwards
newItem.data = this._cloneItemData(itemData);
@ -1275,10 +1302,15 @@ ItemSet.prototype._onDragStartAddItem = function (event) {
var props = {
item: newItem,
dragRight: true,
initialX: event.center.x,
data: newItem.data
};
if (this.options.rtl) {
props.dragLeft = true;
} else {
props.dragRight = true;
}
this.touchParams.itemProps = [props];
event.stopPropagation();
@ -1295,7 +1327,13 @@ ItemSet.prototype._onDrag = function (event) {
var me = this;
var snap = this.options.snap || null;
var xOffset = this.body.dom.root.offsetLeft + this.body.domProps.left.width;
if (this.options.rtl) {
var xOffset = this.body.dom.root.offsetLeft + this.body.domProps.right.width;
} else {
var xOffset = this.body.dom.root.offsetLeft + this.body.domProps.left.width;
}
var scale = this.body.util.getScale();
var step = this.body.util.getStep();
@ -1319,7 +1357,12 @@ ItemSet.prototype._onDrag = function (event) {
this.touchParams.itemProps.forEach(function (props) {
var current = me.body.util.toTime(event.center.x - xOffset);
var initial = me.body.util.toTime(props.initialX - xOffset);
var offset = current - initial; // ms
if (this.options.rtl) {
var offset = -(current - initial); // ms
} else {
var offset = (current - initial); // ms
}
var itemData = this._cloneItemData(props.item.data); // clone the data
if (props.item.editable === false) {
@ -1328,29 +1371,47 @@ ItemSet.prototype._onDrag = function (event) {
var updateTimeAllowed = me.options.editable.updateTime ||
props.item.editable === true;
if (updateTimeAllowed) {
if (props.dragLeft) {
// drag left side of a range item
if (itemData.start != undefined) {
var initialStart = util.convert(props.data.start, 'Date');
var start = new Date(initialStart.valueOf() + offset);
// TODO: pass a Moment instead of a Date to snap(). (Breaking change)
itemData.start = snap ? snap(start, scale, step) : start;
if (this.options.rtl) {
if (itemData.end != undefined) {
var initialEnd = util.convert(props.data.end, 'Date');
var end = new Date(initialEnd.valueOf() + offset);
// TODO: pass a Moment instead of a Date to snap(). (Breaking change)
itemData.end = snap ? snap(end, scale, step) : end;
}
} else {
if (itemData.start != undefined) {
var initialStart = util.convert(props.data.start, 'Date');
var start = new Date(initialStart.valueOf() + offset);
// TODO: pass a Moment instead of a Date to snap(). (Breaking change)
itemData.start = snap ? snap(start, scale, step) : start;
}
}
}
else if (props.dragRight) {
// drag right side of a range item
if (itemData.end != undefined) {
var initialEnd = util.convert(props.data.end, 'Date');
var end = new Date(initialEnd.valueOf() + offset);
// TODO: pass a Moment instead of a Date to snap(). (Breaking change)
itemData.end = snap ? snap(end, scale, step) : end;
if (this.options.rtl) {
if (itemData.start != undefined) {
var initialStart = util.convert(props.data.start, 'Date');
var start = new Date(initialStart.valueOf() + offset);
// TODO: pass a Moment instead of a Date to snap(). (Breaking change)
itemData.start = snap ? snap(start, scale, step) : start;
}
} else {
if (itemData.end != undefined) {
var initialEnd = util.convert(props.data.end, 'Date');
var end = new Date(initialEnd.valueOf() + offset);
// TODO: pass a Moment instead of a Date to snap(). (Breaking change)
itemData.end = snap ? snap(end, scale, step) : end;
}
}
}
else {
// drag both start and end
if (itemData.start != undefined) {
var initialStart = util.convert(props.data.start, 'Date').valueOf();
var start = new Date(initialStart + offset);
@ -1366,6 +1427,8 @@ ItemSet.prototype._onDrag = function (event) {
// TODO: pass a Moment instead of a Date to snap(). (Breaking change)
itemData.start = snap ? snap(start, scale, step) : start;
}
}
}
}
@ -1706,8 +1769,15 @@ ItemSet.prototype._onAddItem = function (event) {
}
else {
// add item
var xAbs = util.getAbsoluteLeft(this.dom.frame);
var x = event.center.x - xAbs;
if (this.options.rtl) {
var xAbs = util.getAbsoluteRight(this.dom.frame);
var x = xAbs - event.center.x;
} else {
var xAbs = util.getAbsoluteLeft(this.dom.frame);
var x = event.center.x - xAbs;
}
// var xAbs = util.getAbsoluteLeft(this.dom.frame);
// var x = event.center.x - xAbs;
var start = this.body.util.toTime(x);
var scale = this.body.util.getScale();
var step = this.body.util.getStep();

+ 34
- 9
lib/timeline/component/TimeAxis.js View File

@ -73,7 +73,8 @@ TimeAxis.prototype.setOptions = function(options) {
'maxMinorChars',
'hiddenDates',
'timeAxis',
'moment'
'moment',
'rtl'
], this.options, options);
// deep copy the format options
@ -183,7 +184,6 @@ TimeAxis.prototype.redraw = function () {
else {
this.body.dom.backgroundVertical.appendChild(background)
}
return this._isResized() || parentChanged;
};
@ -336,7 +336,13 @@ TimeAxis.prototype._repaintMinorText = function (x, text, orientation, className
label.childNodes[0].nodeValue = text;
label.style.top = (orientation == 'top') ? (this.props.majorLabelHeight + 'px') : '0';
label.style.left = x + 'px';
if (this.options.rtl) {
label.style.left = "";
label.style.right = x + 'px';
} else {
label.style.left = x + 'px';
};
label.className = 'vis-text vis-minor ' + className;
//label.title = title; // TODO: this is a heavy operation
@ -370,7 +376,12 @@ TimeAxis.prototype._repaintMajorText = function (x, text, orientation, className
//label.title = title; // TODO: this is a heavy operation
label.style.top = (orientation == 'top') ? '0' : (this.props.minorLabelHeight + 'px');
label.style.left = x + 'px';
if (this.options.rtl) {
label.style.left = "";
label.style.right = x + 'px';
} else {
label.style.left = x + 'px';
};
return label;
};
@ -402,10 +413,17 @@ TimeAxis.prototype._repaintMinorLine = function (x, width, orientation, classNam
line.style.top = this.body.domProps.top.height + 'px';
}
line.style.height = props.minorLineHeight + 'px';
line.style.left = (x - props.minorLineWidth / 2) + 'px';
if (this.options.rtl) {
line.style.left = "";
line.style.right = (x - props.minorLineWidth / 2) + 'px';
line.className = 'vis-grid vis-vertical-rtl vis-minor ' + className;
} else {
line.style.left = (x - props.minorLineWidth / 2) + 'px';
line.className = 'vis-grid vis-vertical vis-minor ' + className;
};
line.style.width = width + 'px';
line.className = 'vis-grid vis-vertical vis-minor ' + className;
return line;
};
@ -436,12 +454,19 @@ TimeAxis.prototype._repaintMajorLine = function (x, width, orientation, classNam
else {
line.style.top = this.body.domProps.top.height + 'px';
}
line.style.left = (x - props.majorLineWidth / 2) + 'px';
if (this.options.rtl) {
line.style.left = "";
line.style.right = (x - props.majorLineWidth / 2) + 'px';
line.className = 'vis-grid vis-vertical-rtl vis-major ' + className;
} else {
line.style.left = (x - props.majorLineWidth / 2) + 'px';
line.className = 'vis-grid vis-vertical vis-major ' + className;
}
line.style.height = props.majorLineHeight + 'px';
line.style.width = width + 'px';
line.className = 'vis-grid vis-vertical vis-major ' + className;
return line;
};

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

@ -100,6 +100,17 @@
cursor: pointer;
}
.vis-item .vis-delete-rtl {
background: url('img/timeline/delete.png') no-repeat center;
position: absolute;
width: 24px;
height: 24px;
top: -4px;
left: -24px;
cursor: pointer;
}
.vis-item.vis-range .vis-drag-left {
position: absolute;
width: 24px;

+ 5
- 0
lib/timeline/component/css/timeaxis.css View File

@ -41,6 +41,11 @@
border-left: 1px solid;
}
.vis-time-axis .vis-grid.vis-vertical-rtl {
position: absolute;
border-right: 1px solid;
}
.vis-time-axis .vis-grid.vis-minor {
border-color: #e5e5e5;
}

+ 46
- 19
lib/timeline/component/item/BoxItem.js View File

@ -22,7 +22,7 @@ function BoxItem (data, conversion, options) {
height: 0
}
};
this.options = options;
// validate data
if (data) {
if (data.start == undefined) {
@ -171,29 +171,56 @@ BoxItem.prototype.repositionX = function() {
// calculate left position of the box
if (align == 'right') {
this.left = start - this.width;
// reposition box, line, and dot
this.dom.box.style.left = this.left + 'px';
this.dom.line.style.left = (start - this.props.line.width) + 'px';
this.dom.dot.style.left = (start - this.props.line.width / 2 - this.props.dot.width / 2) + 'px';
if (this.options.rtl) {
this.right = start - this.width;
// reposition box, line, and dot
this.dom.box.style.right = this.right + 'px';
this.dom.line.style.right = (start - this.props.line.width) + 'px';
this.dom.dot.style.right = (start - this.props.line.width / 2 - this.props.dot.width / 2) + 'px';
} else {
this.left = start - this.width;
// reposition box, line, and dot
this.dom.box.style.left = this.left + 'px';
this.dom.line.style.left = (start - this.props.line.width) + 'px';
this.dom.dot.style.left = (start - this.props.line.width / 2 - this.props.dot.width / 2) + 'px';
}
}
else if (align == 'left') {
this.left = start;
// reposition box, line, and dot
this.dom.box.style.left = this.left + 'px';
this.dom.line.style.left = start + 'px';
this.dom.dot.style.left = (start + this.props.line.width / 2 - this.props.dot.width / 2) + 'px';
if (this.options.rtl) {
this.right = start;
// reposition box, line, and dot
this.dom.box.style.right = this.right + 'px';
this.dom.line.style.right = start + 'px';
this.dom.dot.style.right = (start + this.props.line.width / 2 - this.props.dot.width / 2) + 'px';
} else {
this.left = start;
// reposition box, line, and dot
this.dom.box.style.left = this.left + 'px';
this.dom.line.style.left = start + 'px';
this.dom.dot.style.left = (start + this.props.line.width / 2 - this.props.dot.width / 2) + 'px';
}
}
else {
// default or 'center'
this.left = start - this.width / 2;
// reposition box, line, and dot
this.dom.box.style.left = this.left + 'px';
this.dom.line.style.left = (start - this.props.line.width / 2) + 'px';
this.dom.dot.style.left = (start - this.props.dot.width / 2) + 'px';
if (this.options.rtl) {
this.right = start - this.width / 2;
// reposition box, line, and dot
this.dom.box.style.right = this.right + 'px';
this.dom.line.style.right = (start - this.props.line.width) + 'px';
this.dom.dot.style.right = (start - this.props.dot.width / 2) + 'px';
} else {
this.left = start - this.width / 2;
// reposition box, line, and dot
this.dom.box.style.left = this.left + 'px';
this.dom.line.style.left = (start - this.props.line.width / 2) + 'px';
this.dom.dot.style.left = (start - this.props.dot.width / 2) + 'px';
}
}
};

+ 7
- 1
lib/timeline/component/item/Item.js View File

@ -23,6 +23,7 @@ function Item (data, conversion, options) {
this.dirty = true;
this.top = null;
this.right = null;
this.left = null;
this.width = null;
this.height = null;
@ -154,7 +155,12 @@ Item.prototype._repaintDeleteButton = function (anchor) {
var me = this;
var deleteButton = document.createElement('div');
deleteButton.className = 'vis-delete';
if (this.options.rtl) {
deleteButton.className = 'vis-delete-rtl';
} else {
deleteButton.className = 'vis-delete';
}
deleteButton.title = 'Delete this item';
// TODO: be able to destroy the delete button

+ 24
- 9
lib/timeline/component/item/PointItem.js View File

@ -19,10 +19,11 @@ function PointItem (data, conversion, options) {
},
content: {
height: 0,
marginLeft: 0
marginLeft: 0,
marginRight: 0
}
};
this.options = options;
// validate data
if (data) {
if (data.start == undefined) {
@ -117,7 +118,11 @@ PointItem.prototype.redraw = function() {
this.props.content.height = dom.content.offsetHeight;
// resize contents
dom.content.style.marginLeft = 2 * this.props.dot.width + 'px';
if (this.options.rtl) {
dom.content.style.marginRight = 2 * this.props.dot.width + 'px';
} else {
dom.content.style.marginLeft = 2 * this.props.dot.width + 'px';
}
//dom.content.style.marginRight = ... + 'px'; // TODO: margin right
// recalculate size
@ -126,7 +131,11 @@ PointItem.prototype.redraw = function() {
// reposition the dot
dom.dot.style.top = ((this.height - this.props.dot.height) / 2) + 'px';
dom.dot.style.left = (this.props.dot.width / 2) + 'px';
if (this.options.rtl) {
dom.dot.style.right = (this.props.dot.width / 2) + 'px';
} else {
dom.dot.style.left = (this.props.dot.width / 2) + 'px';
}
this.dirty = false;
}
@ -164,10 +173,17 @@ PointItem.prototype.hide = function() {
PointItem.prototype.repositionX = function() {
var start = this.conversion.toScreen(this.data.start);
this.left = start - this.props.dot.width;
if (this.options.rtl) {
this.right = start - this.props.dot.width;
// reposition point
this.dom.point.style.right = this.right + 'px';
} else {
this.left = start - this.props.dot.width;
// reposition point
this.dom.point.style.left = this.left + 'px';
// reposition point
this.dom.point.style.left = this.left + 'px';
}
};
/**
@ -177,7 +193,6 @@ PointItem.prototype.repositionX = function() {
PointItem.prototype.repositionY = function() {
var orientation = this.options.orientation.item;
var point = this.dom.point;
if (orientation == 'top') {
point.style.top = this.top + 'px';
}
@ -199,7 +214,7 @@ PointItem.prototype.getWidthLeft = function () {
* @return {number}
*/
PointItem.prototype.getWidthRight = function () {
return this.width - this.props.dot.width;
return this.props.dot.width;
};
module.exports = PointItem;

+ 42
- 14
lib/timeline/component/item/RangeItem.js View File

@ -18,7 +18,7 @@ function RangeItem (data, conversion, options) {
}
};
this.overflow = false; // if contents can overflow (css styling), this flag is set to true
this.options = options;
// validate data
if (data) {
if (data.start == undefined) {
@ -123,7 +123,6 @@ RangeItem.prototype.redraw = function() {
this.dirty = false;
}
this._repaintDeleteButton(dom.box);
this._repaintDragLeft();
this._repaintDragRight();
@ -168,7 +167,7 @@ RangeItem.prototype.repositionX = function(limitSize) {
var parentWidth = this.parent.width;
var start = this.conversion.toScreen(this.data.start);
var end = this.conversion.toScreen(this.data.end);
var contentLeft;
var contentStartPosition;
var contentWidth;
// limit the width of the range, as browsers cannot draw very wide divs
@ -183,7 +182,11 @@ RangeItem.prototype.repositionX = function(limitSize) {
var boxWidth = Math.max(end - start, 1);
if (this.overflow) {
this.left = start;
if (this.options.rtl) {
this.right = start;
} else {
this.left = start;
}
this.width = boxWidth + this.props.content.width;
contentWidth = this.props.content.width;
@ -192,46 +195,71 @@ RangeItem.prototype.repositionX = function(limitSize) {
// So no re-stacking needed, which is nicer for the eye;
}
else {
this.left = start;
if (this.options.rtl) {
this.right = start;
} else {
this.left = start;
}
this.width = boxWidth;
contentWidth = Math.min(end - start, this.props.content.width);
}
this.dom.box.style.left = this.left + 'px';
if (this.options.rtl) {
this.dom.box.style.right = this.right + 'px';
} else {
this.dom.box.style.left = this.left + 'px';
}
this.dom.box.style.width = boxWidth + 'px';
switch (this.options.align) {
case 'left':
this.dom.content.style.left = '0';
if (this.options.rtl) {
this.dom.content.style.right = '0';
} else {
this.dom.content.style.left = '0';
}
break;
case 'right':
this.dom.content.style.left = Math.max((boxWidth - contentWidth), 0) + 'px';
if (this.options.rtl) {
this.dom.content.style.right = Math.max((boxWidth - contentWidth), 0) + 'px';
} else {
this.dom.content.style.left = Math.max((boxWidth - contentWidth), 0) + 'px';
}
break;
case 'center':
this.dom.content.style.left = Math.max((boxWidth - contentWidth) / 2, 0) + 'px';
if (this.options.rtl) {
this.dom.content.style.right = Math.max((boxWidth - contentWidth) / 2, 0) + 'px';
} else {
this.dom.content.style.left = Math.max((boxWidth - contentWidth) / 2, 0) + 'px';
}
break;
default: // 'auto'
// when range exceeds left of the window, position the contents at the left of the visible area
if (this.overflow) {
if (end > 0) {
contentLeft = Math.max(-start, 0);
contentStartPosition = Math.max(-start, 0);
}
else {
contentLeft = -contentWidth; // ensure it's not visible anymore
contentStartPosition = -contentWidth; // ensure it's not visible anymore
}
}
else {
if (start < 0) {
contentLeft = -start;
contentStartPosition = -start;
}
else {
contentLeft = 0;
contentStartPosition = 0;
}
}
this.dom.content.style.left = contentLeft + 'px';
if (this.options.rtl) {
this.dom.content.style.right = contentStartPosition + 'px';
} else {
this.dom.content.style.left = contentStartPosition + 'px';
}
}
};

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

@ -15,7 +15,6 @@ let dom = 'dom';
let moment = 'moment';
let any = 'any';
let allOptions = {
configure: {
enabled: {boolean},
@ -26,6 +25,7 @@ let allOptions = {
//globals :
align: {string},
rtl: {boolean, 'undefined': 'undefined'},
autoResize: {boolean},
throttleRedraw: {number},
clickToUse: {boolean},
@ -143,6 +143,7 @@ let allOptions = {
let configureOptions = {
global: {
align: ['center', 'left', 'right'],
direction: false,
autoResize: true,
throttleRedraw: [10, 0, 1000, 10],
clickToUse: false,

+ 4
- 0
lib/util.js View File

@ -599,6 +599,10 @@ exports.getAbsoluteLeft = function (elem) {
return elem.getBoundingClientRect().left;
};
exports.getAbsoluteRight = function (elem) {
return elem.getBoundingClientRect().right;
};
/**
* Retrieve the absolute top value of a DOM element
* @param {Element} elem A dom element, for example a div

Loading…
Cancel
Save