From ca29945feddc9e5dc755d754fc5aeb3dcc0f8def Mon Sep 17 00:00:00 2001 From: Alex de Mulder Date: Wed, 5 Nov 2014 15:04:20 +0100 Subject: [PATCH] Added new verticalDrag event for internal use, allowing the vertical scrolling of the grid lines on drag. --- HISTORY.md | 1 + dist/vis.js | 489 ++++++++++--------- examples/graph2d/07_scrollingAndSorting.html | 2 +- lib/timeline/Core.js | 2 + lib/timeline/component/DataAxis.js | 11 + 5 files changed, 266 insertions(+), 239 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index c35288d5..c2ab084d 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -19,6 +19,7 @@ http://visjs.org - Fixed cleaning up of groups. - Throw error message when items are added before groups. - Made graphHeight automatic if height is defined AND if graphHeight is smaller than the center panel when height is defined as well. +- Added new verticalDrag event for internal use, allowing the vertical scrolling of the grid lines on drag. ## 2014-10-28, version 3.6.3 diff --git a/dist/vis.js b/dist/vis.js index 0293b4dc..e300b542 100644 --- a/dist/vis.js +++ b/dist/vis.js @@ -104,7 +104,7 @@ return /******/ (function(modules) { // webpackBootstrap exports.Graph2d = __webpack_require__(42); exports.timeline = { DateUtil: __webpack_require__(24), - DataStep: __webpack_require__(44), + DataStep: __webpack_require__(47), Range: __webpack_require__(21), stack: __webpack_require__(33), TimeStep: __webpack_require__(27), @@ -121,12 +121,12 @@ return /******/ (function(modules) { // webpackBootstrap Component: __webpack_require__(23), CurrentTime: __webpack_require__(28), CustomTime: __webpack_require__(30), - DataAxis: __webpack_require__(45), - GraphGroup: __webpack_require__(46), + DataAxis: __webpack_require__(44), + GraphGroup: __webpack_require__(45), Group: __webpack_require__(32), BackgroundGroup: __webpack_require__(36), ItemSet: __webpack_require__(31), - Legend: __webpack_require__(47), + Legend: __webpack_require__(46), LineGraph: __webpack_require__(43), TimeAxis: __webpack_require__(26) } @@ -14046,8 +14046,10 @@ return /******/ (function(modules) { // webpackBootstrap var oldScrollTop = this._getScrollTop(); var newScrollTop = this._setScrollTop(this.touch.initialScrollTop + delta); + if (newScrollTop != oldScrollTop) { this.redraw(); // TODO: this causes two redraws when dragging, the other is triggered by rangechange already + this.emit("verticalDrag"); } }; @@ -20031,9 +20033,9 @@ return /******/ (function(modules) { // webpackBootstrap var DataSet = __webpack_require__(7); var DataView = __webpack_require__(9); var Component = __webpack_require__(23); - var DataAxis = __webpack_require__(45); - var GraphGroup = __webpack_require__(46); - var Legend = __webpack_require__(47); + var DataAxis = __webpack_require__(44); + var GraphGroup = __webpack_require__(45); + var Legend = __webpack_require__(46); var UNGROUPED = '__ungrouped__'; // reserved group id for ungrouped items @@ -21350,240 +21352,12 @@ return /******/ (function(modules) { // webpackBootstrap /***/ }, /* 44 */ -/***/ function(module, exports, __webpack_require__) { - - /** - * @constructor DataStep - * The class DataStep is an iterator for data for the lineGraph. You provide a start data point and an - * end data point. The class itself determines the best scale (step size) based on the - * provided start Date, end Date, and minimumStep. - * - * If minimumStep is provided, the step size is chosen as close as possible - * to the minimumStep but larger than minimumStep. If minimumStep is not - * provided, the scale is set to 1 DAY. - * The minimumStep should correspond with the onscreen size of about 6 characters - * - * Alternatively, you can set a scale by hand. - * After creation, you can initialize the class by executing first(). Then you - * can iterate from the start date to the end date via next(). You can check if - * the end date is reached with the function hasNext(). After each step, you can - * retrieve the current date via getCurrent(). - * The DataStep has scales ranging from milliseconds, seconds, minutes, hours, - * days, to years. - * - * Version: 1.2 - * - * @param {Date} [start] The start date, for example new Date(2010, 9, 21) - * or new Date(2010, 9, 21, 23, 45, 00) - * @param {Date} [end] The end date - * @param {Number} [minimumStep] Optional. Minimum step size in milliseconds - */ - function DataStep(start, end, minimumStep, containerHeight, customRange) { - // variables - this.current = 0; - - this.autoScale = true; - this.stepIndex = 0; - this.step = 1; - this.scale = 1; - - this.marginStart; - this.marginEnd; - this.deadSpace = 0; - - this.majorSteps = [1, 2, 5, 10]; - this.minorSteps = [0.25, 0.5, 1, 2]; - - this.setRange(start, end, minimumStep, containerHeight, customRange); - } - - - - /** - * Set a new range - * If minimumStep is provided, the step size is chosen as close as possible - * to the minimumStep but larger than minimumStep. If minimumStep is not - * provided, the scale is set to 1 DAY. - * The minimumStep should correspond with the onscreen size of about 6 characters - * @param {Number} [start] The start date and time. - * @param {Number} [end] The end date and time. - * @param {Number} [minimumStep] Optional. Minimum step size in milliseconds - */ - DataStep.prototype.setRange = function(start, end, minimumStep, containerHeight, customRange) { - this._start = customRange.min === undefined ? start : customRange.min; - this._end = customRange.max === undefined ? end : customRange.max; - - if (this._start == this._end) { - this._start -= 0.75; - this._end += 1; - } - - if (this.autoScale) { - this.setMinimumStep(minimumStep, containerHeight); - } - this.setFirst(customRange); - }; - - /** - * Automatically determine the scale that bests fits the provided minimum step - * @param {Number} [minimumStep] The minimum step size in milliseconds - */ - DataStep.prototype.setMinimumStep = function(minimumStep, containerHeight) { - // round to floor - var size = this._end - this._start; - var safeSize = size * 1.2; - var minimumStepValue = minimumStep * (safeSize / containerHeight); - var orderOfMagnitude = Math.round(Math.log(safeSize)/Math.LN10); - - var minorStepIdx = -1; - var magnitudefactor = Math.pow(10,orderOfMagnitude); - - var start = 0; - if (orderOfMagnitude < 0) { - start = orderOfMagnitude; - } - - var solutionFound = false; - for (var i = start; Math.abs(i) <= Math.abs(orderOfMagnitude); i++) { - magnitudefactor = Math.pow(10,i); - for (var j = 0; j < this.minorSteps.length; j++) { - var stepSize = magnitudefactor * this.minorSteps[j]; - if (stepSize >= minimumStepValue) { - solutionFound = true; - minorStepIdx = j; - break; - } - } - if (solutionFound == true) { - break; - } - } - this.stepIndex = minorStepIdx; - this.scale = magnitudefactor; - this.step = magnitudefactor * this.minorSteps[minorStepIdx]; - }; - - - - /** - * Round the current date to the first minor date value - * This must be executed once when the current date is set to start Date - */ - DataStep.prototype.setFirst = function(customRange) { - if (customRange === undefined) { - customRange = {}; - } - var niceStart = customRange.min === undefined ? this._start - (this.scale * 2 * this.minorSteps[this.stepIndex]) : customRange.min; - var niceEnd = customRange.max === undefined ? this._end + (this.scale * this.minorSteps[this.stepIndex]) : customRange.max; - - this.marginEnd = customRange.max === undefined ? this.roundToMinor(niceEnd) : customRange.max; - this.marginStart = customRange.min === undefined ? this.roundToMinor(niceStart) : customRange.min; - this.deadSpace = this.roundToMinor(niceEnd) - niceEnd + this.roundToMinor(niceStart) - niceStart; - this.marginRange = this.marginEnd - this.marginStart; - - this.current = this.marginEnd; - - }; - - DataStep.prototype.roundToMinor = function(value) { - var rounded = value - (value % (this.scale * this.minorSteps[this.stepIndex])); - if (value % (this.scale * this.minorSteps[this.stepIndex]) > 0.5 * (this.scale * this.minorSteps[this.stepIndex])) { - return rounded + (this.scale * this.minorSteps[this.stepIndex]); - } - else { - return rounded; - } - } - - - /** - * Check if the there is a next step - * @return {boolean} true if the current date has not passed the end date - */ - DataStep.prototype.hasNext = function () { - return (this.current >= this.marginStart); - }; - - /** - * Do the next step - */ - DataStep.prototype.next = function() { - var prev = this.current; - this.current -= this.step; - - // safety mechanism: if current time is still unchanged, move to the end - if (this.current == prev) { - this.current = this._end; - } - }; - - /** - * Do the next step - */ - DataStep.prototype.previous = function() { - this.current += this.step; - this.marginEnd += this.step; - this.marginRange = this.marginEnd - this.marginStart; - }; - - - - /** - * Get the current datetime - * @return {String} current The current date - */ - DataStep.prototype.getCurrent = function() { - var toPrecision = '' + Number(this.current).toPrecision(5); - if (toPrecision.indexOf(",") != -1 || toPrecision.indexOf(".") != -1) { - for (var i = toPrecision.length-1; i > 0; i--) { - if (toPrecision[i] == "0") { - toPrecision = toPrecision.slice(0,i); - } - else if (toPrecision[i] == "." || toPrecision[i] == ",") { - toPrecision = toPrecision.slice(0,i); - break; - } - else{ - break; - } - } - } - - 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) - * @return {boolean} true if current date is major, else false. - */ - DataStep.prototype.isMajor = function() { - return (this.current % (this.scale * this.majorSteps[this.stepIndex]) == 0); - }; - - module.exports = DataStep; - - -/***/ }, -/* 45 */ /***/ function(module, exports, __webpack_require__) { var util = __webpack_require__(1); var DOMutil = __webpack_require__(6); var Component = __webpack_require__(23); - var DataStep = __webpack_require__(44); + var DataStep = __webpack_require__(47); /** * A horizontal time axis @@ -21647,6 +21421,11 @@ return /******/ (function(modules) { // webpackBootstrap // create the HTML DOM this._create(); + + var me = this; + this.body.emitter.on("verticalDrag", function() { + me.dom.lineContainer.style.top = me.body.domProps.scrollTop + 'px'; + }); } DataAxis.prototype = new Component(); @@ -21715,6 +21494,7 @@ return /******/ (function(modules) { // webpackBootstrap this.dom.lineContainer = document.createElement('div'); this.dom.lineContainer.style.width = '100%'; this.dom.lineContainer.style.height = this.height; + this.dom.lineContainer.style.position = 'relative'; // create svg element for graph drawing. this.svg = document.createElementNS('http://www.w3.org/2000/svg',"svg"); @@ -21803,6 +21583,9 @@ return /******/ (function(modules) { // webpackBootstrap DataAxis.prototype.redraw = function () { var changeCalled = false; var activeGroups = 0; + + this.dom.lineContainer.style.top = this.body.domProps.scrollTop + 'px'; + for (var groupId in this.groups) { if (this.groups.hasOwnProperty(groupId)) { if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) { @@ -21911,6 +21694,7 @@ return /******/ (function(modules) { // webpackBootstrap this.maxLabelSize = 0; var y = 0; while (max < Math.round(amountOfSteps)) { + step.next(); y = Math.round(max * stepPixels); marginStartPos = max * stepPixels; @@ -21942,6 +21726,7 @@ return /******/ (function(modules) { // webpackBootstrap } var offset = this.options.icons == true ? this.options.iconWidth + this.options.labelOffsetX + 15 : this.options.labelOffsetX + 15; + // this will resize the yAxis to accomodate the labels. if (this.maxLabelSize > (this.width - offset) && this.options.visible == true) { this.width = this.maxLabelSize + offset; @@ -22084,7 +21869,7 @@ return /******/ (function(modules) { // webpackBootstrap /***/ }, -/* 46 */ +/* 45 */ /***/ function(module, exports, __webpack_require__) { var util = __webpack_require__(1); @@ -22225,7 +22010,7 @@ return /******/ (function(modules) { // webpackBootstrap /***/ }, -/* 47 */ +/* 46 */ /***/ function(module, exports, __webpack_require__) { var util = __webpack_require__(1); @@ -22432,6 +22217,234 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = Legend; +/***/ }, +/* 47 */ +/***/ function(module, exports, __webpack_require__) { + + /** + * @constructor DataStep + * The class DataStep is an iterator for data for the lineGraph. You provide a start data point and an + * end data point. The class itself determines the best scale (step size) based on the + * provided start Date, end Date, and minimumStep. + * + * If minimumStep is provided, the step size is chosen as close as possible + * to the minimumStep but larger than minimumStep. If minimumStep is not + * provided, the scale is set to 1 DAY. + * The minimumStep should correspond with the onscreen size of about 6 characters + * + * Alternatively, you can set a scale by hand. + * After creation, you can initialize the class by executing first(). Then you + * can iterate from the start date to the end date via next(). You can check if + * the end date is reached with the function hasNext(). After each step, you can + * retrieve the current date via getCurrent(). + * The DataStep has scales ranging from milliseconds, seconds, minutes, hours, + * days, to years. + * + * Version: 1.2 + * + * @param {Date} [start] The start date, for example new Date(2010, 9, 21) + * or new Date(2010, 9, 21, 23, 45, 00) + * @param {Date} [end] The end date + * @param {Number} [minimumStep] Optional. Minimum step size in milliseconds + */ + function DataStep(start, end, minimumStep, containerHeight, customRange) { + // variables + this.current = 0; + + this.autoScale = true; + this.stepIndex = 0; + this.step = 1; + this.scale = 1; + + this.marginStart; + this.marginEnd; + this.deadSpace = 0; + + this.majorSteps = [1, 2, 5, 10]; + this.minorSteps = [0.25, 0.5, 1, 2]; + + this.setRange(start, end, minimumStep, containerHeight, customRange); + } + + + + /** + * Set a new range + * If minimumStep is provided, the step size is chosen as close as possible + * to the minimumStep but larger than minimumStep. If minimumStep is not + * provided, the scale is set to 1 DAY. + * The minimumStep should correspond with the onscreen size of about 6 characters + * @param {Number} [start] The start date and time. + * @param {Number} [end] The end date and time. + * @param {Number} [minimumStep] Optional. Minimum step size in milliseconds + */ + DataStep.prototype.setRange = function(start, end, minimumStep, containerHeight, customRange) { + this._start = customRange.min === undefined ? start : customRange.min; + this._end = customRange.max === undefined ? end : customRange.max; + + if (this._start == this._end) { + this._start -= 0.75; + this._end += 1; + } + + if (this.autoScale) { + this.setMinimumStep(minimumStep, containerHeight); + } + this.setFirst(customRange); + }; + + /** + * Automatically determine the scale that bests fits the provided minimum step + * @param {Number} [minimumStep] The minimum step size in milliseconds + */ + DataStep.prototype.setMinimumStep = function(minimumStep, containerHeight) { + // round to floor + var size = this._end - this._start; + var safeSize = size * 1.2; + var minimumStepValue = minimumStep * (safeSize / containerHeight); + var orderOfMagnitude = Math.round(Math.log(safeSize)/Math.LN10); + + var minorStepIdx = -1; + var magnitudefactor = Math.pow(10,orderOfMagnitude); + + var start = 0; + if (orderOfMagnitude < 0) { + start = orderOfMagnitude; + } + + var solutionFound = false; + for (var i = start; Math.abs(i) <= Math.abs(orderOfMagnitude); i++) { + magnitudefactor = Math.pow(10,i); + for (var j = 0; j < this.minorSteps.length; j++) { + var stepSize = magnitudefactor * this.minorSteps[j]; + if (stepSize >= minimumStepValue) { + solutionFound = true; + minorStepIdx = j; + break; + } + } + if (solutionFound == true) { + break; + } + } + this.stepIndex = minorStepIdx; + this.scale = magnitudefactor; + this.step = magnitudefactor * this.minorSteps[minorStepIdx]; + }; + + + + /** + * Round the current date to the first minor date value + * This must be executed once when the current date is set to start Date + */ + DataStep.prototype.setFirst = function(customRange) { + if (customRange === undefined) { + customRange = {}; + } + var niceStart = customRange.min === undefined ? this._start - (this.scale * 2 * this.minorSteps[this.stepIndex]) : customRange.min; + var niceEnd = customRange.max === undefined ? this._end + (this.scale * this.minorSteps[this.stepIndex]) : customRange.max; + + this.marginEnd = customRange.max === undefined ? this.roundToMinor(niceEnd) : customRange.max; + this.marginStart = customRange.min === undefined ? this.roundToMinor(niceStart) : customRange.min; + this.deadSpace = this.roundToMinor(niceEnd) - niceEnd + this.roundToMinor(niceStart) - niceStart; + this.marginRange = this.marginEnd - this.marginStart; + + this.current = this.marginEnd; + + }; + + DataStep.prototype.roundToMinor = function(value) { + var rounded = value - (value % (this.scale * this.minorSteps[this.stepIndex])); + if (value % (this.scale * this.minorSteps[this.stepIndex]) > 0.5 * (this.scale * this.minorSteps[this.stepIndex])) { + return rounded + (this.scale * this.minorSteps[this.stepIndex]); + } + else { + return rounded; + } + } + + + /** + * Check if the there is a next step + * @return {boolean} true if the current date has not passed the end date + */ + DataStep.prototype.hasNext = function () { + return (this.current >= this.marginStart); + }; + + /** + * Do the next step + */ + DataStep.prototype.next = function() { + var prev = this.current; + this.current -= this.step; + + // safety mechanism: if current time is still unchanged, move to the end + if (this.current == prev) { + this.current = this._end; + } + }; + + /** + * Do the next step + */ + DataStep.prototype.previous = function() { + this.current += this.step; + this.marginEnd += this.step; + this.marginRange = this.marginEnd - this.marginStart; + }; + + + + /** + * Get the current datetime + * @return {String} current The current date + */ + DataStep.prototype.getCurrent = function() { + var toPrecision = '' + Number(this.current).toPrecision(5); + if (toPrecision.indexOf(",") != -1 || toPrecision.indexOf(".") != -1) { + for (var i = toPrecision.length-1; i > 0; i--) { + if (toPrecision[i] == "0") { + toPrecision = toPrecision.slice(0,i); + } + else if (toPrecision[i] == "." || toPrecision[i] == ",") { + toPrecision = toPrecision.slice(0,i); + break; + } + else{ + break; + } + } + } + + 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) + * @return {boolean} true if current date is major, else false. + */ + DataStep.prototype.isMajor = function() { + return (this.current % (this.scale * this.majorSteps[this.stepIndex]) == 0); + }; + + module.exports = DataStep; + + /***/ }, /* 48 */ /***/ function(module, exports, __webpack_require__) { diff --git a/examples/graph2d/07_scrollingAndSorting.html b/examples/graph2d/07_scrollingAndSorting.html index 98e8629d..7ea8e5f0 100644 --- a/examples/graph2d/07_scrollingAndSorting.html +++ b/examples/graph2d/07_scrollingAndSorting.html @@ -59,7 +59,7 @@ var dataset = new vis.DataSet(items); var options = { - legend: true, + legend: {left: {position: 'bottom-left'}}, sort: false, defaultGroup: 'doodle', graphHeight: '1500px', diff --git a/lib/timeline/Core.js b/lib/timeline/Core.js index ea02fd6a..a8380dda 100644 --- a/lib/timeline/Core.js +++ b/lib/timeline/Core.js @@ -792,8 +792,10 @@ Core.prototype._onDrag = function (event) { var oldScrollTop = this._getScrollTop(); var newScrollTop = this._setScrollTop(this.touch.initialScrollTop + delta); + if (newScrollTop != oldScrollTop) { this.redraw(); // TODO: this causes two redraws when dragging, the other is triggered by rangechange already + this.emit("verticalDrag"); } }; diff --git a/lib/timeline/component/DataAxis.js b/lib/timeline/component/DataAxis.js index 31c486f2..8e23d411 100644 --- a/lib/timeline/component/DataAxis.js +++ b/lib/timeline/component/DataAxis.js @@ -65,6 +65,11 @@ function DataAxis (body, options, svg, linegraphOptions) { // create the HTML DOM this._create(); + + var me = this; + this.body.emitter.on("verticalDrag", function() { + me.dom.lineContainer.style.top = me.body.domProps.scrollTop + 'px'; + }); } DataAxis.prototype = new Component(); @@ -133,6 +138,7 @@ DataAxis.prototype._create = function() { this.dom.lineContainer = document.createElement('div'); this.dom.lineContainer.style.width = '100%'; this.dom.lineContainer.style.height = this.height; + this.dom.lineContainer.style.position = 'relative'; // create svg element for graph drawing. this.svg = document.createElementNS('http://www.w3.org/2000/svg',"svg"); @@ -221,6 +227,9 @@ DataAxis.prototype.setRange = function (start, end) { DataAxis.prototype.redraw = function () { var changeCalled = false; var activeGroups = 0; + + this.dom.lineContainer.style.top = this.body.domProps.scrollTop + 'px'; + for (var groupId in this.groups) { if (this.groups.hasOwnProperty(groupId)) { if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) { @@ -329,6 +338,7 @@ DataAxis.prototype._redrawLabels = function () { this.maxLabelSize = 0; var y = 0; while (max < Math.round(amountOfSteps)) { + step.next(); y = Math.round(max * stepPixels); marginStartPos = max * stepPixels; @@ -360,6 +370,7 @@ DataAxis.prototype._redrawLabels = function () { } var offset = this.options.icons == true ? this.options.iconWidth + this.options.labelOffsetX + 15 : this.options.labelOffsetX + 15; + // this will resize the yAxis to accomodate the labels. if (this.maxLabelSize > (this.width - offset) && this.options.visible == true) { this.width = this.maxLabelSize + offset;