diff --git a/docs/timeline/index.html b/docs/timeline/index.html index 9a623722..321ef258 100644 --- a/docs/timeline/index.html +++ b/docs/timeline/index.html @@ -943,7 +943,14 @@ function (option, path) { Orientation of the timeline items: 'top' or 'bottom' (default). Determines whether items are aligned to the top or bottom of the Timeline. - + + rollingMode + boolean + false + If true, the timeline will initial in a rolling mode - the current time will always be centered. I the user drags the timeline, the timeline will go out of rolling mode and a toggle button will appear. Clicking that button will go back to rolling mode. Zooming in rolling mode will zoom in to the center without consideration of the mouse position. + + + rtl boolean false diff --git a/examples/timeline/interaction/rollingMode.html b/examples/timeline/interaction/rollingMode.html new file mode 100644 index 00000000..80b599f4 --- /dev/null +++ b/examples/timeline/interaction/rollingMode.html @@ -0,0 +1,45 @@ + + + Timeline | rolling mode Option + + + + + + + + + +

Timeline rolling mode option

+ +
+ + + + + + diff --git a/lib/timeline/Core.js b/lib/timeline/Core.js index a5034479..9441b77c 100644 --- a/lib/timeline/Core.js +++ b/lib/timeline/Core.js @@ -50,6 +50,7 @@ Core.prototype._create = function (container) { this.dom.shadowBottomLeft = document.createElement('div'); this.dom.shadowTopRight = document.createElement('div'); this.dom.shadowBottomRight = document.createElement('div'); + this.dom.rollingModeBtn = document.createElement('div'); this.dom.root.className = 'vis-timeline'; this.dom.background.className = 'vis-panel vis-background'; @@ -69,6 +70,7 @@ Core.prototype._create = function (container) { this.dom.shadowBottomLeft.className = 'vis-shadow vis-bottom'; this.dom.shadowTopRight.className = 'vis-shadow vis-top'; this.dom.shadowBottomRight.className = 'vis-shadow vis-bottom'; + this.dom.rollingModeBtn.className = 'vis-rolling-mode-btn'; this.dom.root.appendChild(this.dom.background); this.dom.root.appendChild(this.dom.backgroundVertical); @@ -78,6 +80,8 @@ Core.prototype._create = function (container) { this.dom.root.appendChild(this.dom.rightContainer); this.dom.root.appendChild(this.dom.top); this.dom.root.appendChild(this.dom.bottom); + this.dom.root.appendChild(this.dom.bottom); + this.dom.root.appendChild(this.dom.rollingModeBtn); this.dom.centerContainer.appendChild(this.dom.center); this.dom.leftContainer.appendChild(this.dom.left); @@ -311,6 +315,8 @@ Core.prototype.setOptions = function (options) { ]; util.selectiveExtend(fields, this.options, options); + this.dom.rollingModeBtn.style.visibility = 'hidden'; + if (this.options.rtl) { this.dom.container.style.direction = "rtl"; this.dom.backgroundVertical.className = 'vis-panel vis-background vis-vertical-rtl'; diff --git a/lib/timeline/Range.js b/lib/timeline/Range.js index 3e250f38..577e0863 100644 --- a/lib/timeline/Range.js +++ b/lib/timeline/Range.js @@ -14,8 +14,9 @@ var DateUtil = require('./DateUtil'); */ function Range(body, options) { var now = moment().hours(0).minutes(0).seconds(0).milliseconds(0); - this.start = now.clone().add(-3, 'days').valueOf(); // Number - this.end = now.clone().add(4, 'days').valueOf(); // Number + this.start = options.start || now.clone().add(-3, 'days').valueOf(); + this.end = options.end || now.clone().add(4, 'days').valueOf(); + this.rolling = false; this.body = body; this.deltaDifference = 0; @@ -55,6 +56,9 @@ function Range(body, options) { this.body.emitter.on('touch', this._onTouch.bind(this)); this.body.emitter.on('pinch', this._onPinch.bind(this)); + // on click of rolling mode button + this.body.dom.rollingModeBtn.addEventListener('click', this.startRolling.bind(this)); + this.setOptions(options); } @@ -80,11 +84,14 @@ Range.prototype.setOptions = function (options) { if (options) { // copy the options that we know var fields = [ - 'direction', 'min', 'max', 'zoomMin', 'zoomMax', 'moveable', 'zoomable', - 'moment', 'activate', 'hiddenDates', 'zoomKey', 'rtl', 'horizontalScroll' + 'animation', 'direction', 'min', 'max', 'zoomMin', 'zoomMax', 'moveable', 'zoomable', + 'moment', 'activate', 'hiddenDates', 'zoomKey', 'rtl', 'showCurrentTime', 'rollMode', 'horizontalScroll' ]; util.selectiveExtend(fields, this.options, options); + if (options.rollingMode) { + this.startRolling(); + } if ('start' in options || 'end' in options) { // apply a new range. both start and end are optional this.setRange(options.start, options.end); @@ -103,6 +110,52 @@ function validateDirection (direction) { } } +/** + * Start auto refreshing the current time bar + */ +Range.prototype.startRolling = function() { + var me = this; + + + function update () { + me.stopRolling(); + me.rolling = true; + + + var interval = me.end - me.start; + var t = util.convert(new Date(), 'Date').valueOf(); + + var start = t - interval / 2; + var end = t + interval / 2; + var animation = (me.options && me.options.animation !== undefined) ? me.options.animation : true; + + me.setRange(start, end, false); + + // determine interval to refresh + var scale = me.conversion(me.body.domProps.center.width).scale; + var interval = 1 / scale / 10; + if (interval < 30) interval = 30; + if (interval > 1000) interval = 1000; + + me.body.dom.rollingModeBtn.style.visibility = "hidden"; + // start a renderTimer to adjust for the new time + me.currentTimeTimer = setTimeout(update, interval); + } + + update(); +}; + +/** + * Stop auto refreshing the current time bar + */ +Range.prototype.stopRolling = function() { + if (this.currentTimeTimer !== undefined) { + clearTimeout(this.currentTimeTimer); + this.rolling = false; + this.body.dom.rollingModeBtn.style.visibility = "visible"; + } +}; + /** * Set a new start and end range * @param {Date | Number | String} [start] @@ -388,6 +441,8 @@ Range.prototype._onDragStart = function(event) { // when releasing the fingers in opposite order from the touch screen if (!this.props.touch.allowDragging) return; + this.stopRolling(); + this.props.touch.start = this.start; this.props.touch.end = this.end; this.props.touch.dragging = true; @@ -520,7 +575,7 @@ Range.prototype._onMouseWheel = function(event) { // Prevent default actions caused by mouse wheel // (else the page and timeline both scroll) event.preventDefault(); - + // calculate a single scroll jump relative to the range scale var diff = delta * (this.end - this.start) / 20; // calculate new start and end @@ -555,9 +610,13 @@ Range.prototype._onMouseWheel = function(event) { } // calculate center, the date to zoom around - var pointer = this.getPointer({x: event.clientX, y: event.clientY}, this.body.dom.center); - var pointerDate = this._pointerToDate(pointer); - + var pointerDate + if (this.rolling) { + pointerDate = (this.start + this.end) / 2; + } else { + var pointer = this.getPointer({x: event.clientX, y: event.clientY}, this.body.dom.center); + pointerDate = this._pointerToDate(pointer); + } this.zoom(scale, pointerDate, delta, event); // Prevent default actions caused by mouse wheel @@ -594,6 +653,8 @@ Range.prototype._onPinch = function (event) { this.props.touch.center = this.getPointer(event.center, this.body.dom.center); } + this.stopRolling(); + var scale = 1 / (event.scale + this.scaleOffset); var centerDate = this._pointerToDate(this.props.touch.center); diff --git a/lib/timeline/Timeline.js b/lib/timeline/Timeline.js index 7f5d463c..8abe0182 100644 --- a/lib/timeline/Timeline.js +++ b/lib/timeline/Timeline.js @@ -58,10 +58,8 @@ function Timeline (container, items, groups, options) { }; this.options = util.deepExtend({}, this.defaultOptions); - // Create the DOM, props, and emitter this._create(container); - if (!options || (options && typeof options.rtl == "undefined")) { var directionFromDom, domNode = this.dom.root; while (!directionFromDom && domNode) { @@ -72,6 +70,7 @@ function Timeline (container, items, groups, options) { } else { this.options.rtl = options.rtl; } + this.options.rollingMode = options.rollingMode; // all components listed here will be repainted automatically this.components = []; @@ -134,7 +133,7 @@ function Timeline (container, items, groups, options) { //Single time autoscale/fit this.fitDone = false; this.on('changed', function (){ - if (this.itemsData == null) return; + if (this.itemsData == null || this.options.rollingMode) return; if (!me.fitDone) { me.fitDone = true; if (me.options.start != undefined || me.options.end != undefined) { @@ -144,7 +143,6 @@ function Timeline (container, items, groups, options) { var start = me.options.start != undefined ? me.options.start : range.min; var end = me.options.end != undefined ? me.options.end : range.max; - me.setWindow(start, end, {animation: false}); } else { diff --git a/lib/timeline/component/css/currenttime.css b/lib/timeline/component/css/currenttime.css index 7d3547a1..658fdfef 100644 --- a/lib/timeline/component/css/currenttime.css +++ b/lib/timeline/component/css/currenttime.css @@ -3,4 +3,27 @@ width: 2px; z-index: 1; pointer-events: none; +} + +.vis-rolling-mode-btn { + height: 40px; + width: 40px; + position: absolute; + top: 7px; + right: 20px; + border-radius: 50%; + font-size: 28px; + cursor: pointer; + opacity: 0.8; + color: white; + font-weight: bold; + text-align: center; + background: #3876c2; +} +.vis-rolling-mode-btn:before { + content: "\26F6"; +} + +.vis-rolling-mode-btn:hover { + opacity: 1; } \ No newline at end of file diff --git a/lib/timeline/optionsTimeline.js b/lib/timeline/optionsTimeline.js index aadcf2ff..4354a213 100644 --- a/lib/timeline/optionsTimeline.js +++ b/lib/timeline/optionsTimeline.js @@ -26,6 +26,7 @@ let allOptions = { //globals : align: {string}, rtl: {boolean, 'undefined': 'undefined'}, + rollingMode: {boolean, 'undefined': 'undefined'}, verticalScroll: {boolean, 'undefined': 'undefined'}, horizontalScroll: {boolean, 'undefined': 'undefined'}, autoResize: {boolean},