From 8c16dad1ca3e50215ad5107f92ca2ed850e4c0cf Mon Sep 17 00:00:00 2001 From: josdejong Date: Mon, 31 Mar 2014 16:30:02 +0200 Subject: [PATCH] Simplified TimeAxis --- src/timeline/Controller.js | 3 +- src/timeline/component/Group.js | 25 +- src/timeline/component/ItemSet.js | 9 - src/timeline/component/TimeAxis.js | 390 +++++++++++------------- src/timeline/component/css/timeaxis.css | 2 +- src/timeline/component/item/Item.js | 10 - src/timeline/component/item/ItemBox.js | 2 +- 7 files changed, 186 insertions(+), 255 deletions(-) diff --git a/src/timeline/Controller.js b/src/timeline/Controller.js index 29062668..3204851d 100644 --- a/src/timeline/Controller.js +++ b/src/timeline/Controller.js @@ -176,7 +176,8 @@ Controller.prototype.reflow = function reflow() { this.emit('reflow'); // immediately repaint when needed - if (resized) { + //if (resized) { + if (true) { // TODO: fix this loop this.repaint(); } // TODO: limit the number of nested reflows/repaints, prevent loop diff --git a/src/timeline/component/Group.js b/src/timeline/component/Group.js index bf95c0c8..6446ab16 100644 --- a/src/timeline/component/Group.js +++ b/src/timeline/component/Group.js @@ -99,31 +99,20 @@ Group.prototype.getSelection = function getSelection() { * @return {Boolean} changed */ Group.prototype.repaint = function repaint() { - return false; -}; + var update = util.updateProperty; -/** - * Reflow the item - * @return {Boolean} resized - */ -Group.prototype.reflow = function reflow() { - var changed = 0, - update = util.updateProperty; - - changed += update(this, 'top', this.itemset ? this.itemset.top : 0); - changed += update(this, 'height', this.itemset ? this.itemset.height : 0); + this.top = this.itemset ? this.itemset.top : 0; + this.height = this.itemset ? this.itemset.height : 0; // TODO: reckon with the height of the group label if (this.label) { var inner = this.label.firstChild; - changed += update(this.props.label, 'width', inner.clientWidth); - changed += update(this.props.label, 'height', inner.clientHeight); + this.props.label.width = inner.clientWidth; + this.props.label.height = inner.clientHeight; } else { - changed += update(this.props.label, 'width', 0); - changed += update(this.props.label, 'height', 0); + this.props.label.width = 0; + this.props.label.height = 0; } - - return (changed > 0); }; diff --git a/src/timeline/component/ItemSet.js b/src/timeline/component/ItemSet.js index e42ac4ad..e1852c55 100644 --- a/src/timeline/component/ItemSet.js +++ b/src/timeline/component/ItemSet.js @@ -423,15 +423,6 @@ ItemSet.prototype.getAxis = function getAxis() { return this.dom.axis; }; -/** - * Reflow the component - * @return {Boolean} resized - */ -ItemSet.prototype.reflow = function reflow () { - // TODO: remove this function - return true; -}; - /** * Hide this component from the DOM */ diff --git a/src/timeline/component/TimeAxis.js b/src/timeline/component/TimeAxis.js index cfee216d..16be0662 100644 --- a/src/timeline/component/TimeAxis.js +++ b/src/timeline/component/TimeAxis.js @@ -90,23 +90,21 @@ TimeAxis.prototype.toScreen = function(time) { * @return {Boolean} changed */ TimeAxis.prototype.repaint = function () { - var changed = 0, - update = util.updateProperty, - asSize = util.option.asSize, + var asSize = util.option.asSize, options = this.options, - orientation = this.getOption('orientation'), - props = this.props, - step = this.step; + props = this.props; var frame = this.frame; if (!frame) { frame = document.createElement('div'); this.frame = frame; - changed += 1; } frame.className = 'axis'; // TODO: custom className? + // update its size + this.width = frame.offsetWidth; // TODO: only update the width when the frame is resized + if (!frame.parentNode) { if (!this.parent) { throw new Error('Cannot repaint time axis: no parent attached'); @@ -116,73 +114,48 @@ TimeAxis.prototype.repaint = function () { throw new Error('Cannot repaint time axis: parent has no container element'); } parentContainer.appendChild(frame); - - changed += 1; } var parent = frame.parentNode; if (parent) { - var beforeChild = frame.nextSibling; - parent.removeChild(frame); // take frame offline while updating (is almost twice as fast) - - var defaultTop = (orientation == 'bottom' && this.props.parentHeight && this.height) ? - (this.props.parentHeight - this.height) + 'px' : - '0px'; - changed += update(frame.style, 'top', asSize(options.top, defaultTop)); - changed += update(frame.style, 'left', asSize(options.left, '0px')); - changed += update(frame.style, 'width', asSize(options.width, '100%')); - changed += update(frame.style, 'height', asSize(options.height, this.height + 'px')); - - // get characters width and height - this._repaintMeasureChars(); - - if (this.step) { - this._repaintStart(); - - step.first(); - var xFirstMajorLabel = undefined; - var max = 0; - while (step.hasNext() && max < 1000) { - max++; - var cur = step.getCurrent(), - x = this.toScreen(cur), - isMajor = step.isMajor(); - - // TODO: lines must have a width, such that we can create css backgrounds - - if (this.getOption('showMinorLabels')) { - this._repaintMinorText(x, step.getLabelMinor()); - } - - if (isMajor && this.getOption('showMajorLabels')) { - if (x > 0) { - if (xFirstMajorLabel == undefined) { - xFirstMajorLabel = x; - } - this._repaintMajorText(x, step.getLabelMajor()); - } - this._repaintMajorLine(x); - } - else { - this._repaintMinorLine(x); - } - - step.next(); - } + // calculate character width and height + this._calculateCharSize(); - // create a major label on the left when needed - if (this.getOption('showMajorLabels')) { - var leftTime = this.toTime(0), - leftText = step.getLabelMajor(leftTime), - widthText = leftText.length * (props.majorCharWidth || 10) + 10; // upper bound estimation - - if (xFirstMajorLabel == undefined || widthText < xFirstMajorLabel) { - this._repaintMajorText(0, leftText); - } - } - - this._repaintEnd(); + // TODO: recalculate sizes only needed when parent is resized or options is changed + var orientation = this.getOption('orientation'), + showMinorLabels = this.getOption('showMinorLabels'), + showMajorLabels = this.getOption('showMajorLabels'); + + // determine the width and height of the elemens for the axis + var parentHeight = this.parent.height; + props.minorLabelHeight = showMinorLabels ? props.minorCharHeight : 0; + props.majorLabelHeight = showMajorLabels ? props.majorCharHeight : 0; + this.height = props.minorLabelHeight + props.majorLabelHeight; + props.minorLineHeight = parentHeight + props.minorLabelHeight; + props.minorLineWidth = 1; // TODO: really calculate width + props.majorLineHeight = parentHeight + this.height; + props.majorLineWidth = 1; // TODO: really calculate width + + // take frame offline while updating (is almost twice as fast) + var beforeChild = frame.nextSibling; + parent.removeChild(frame); + + if (orientation == 'top') { + frame.style.top = '0'; + frame.style.left = '0'; + frame.style.bottom = ''; + frame.style.width = asSize(options.width, '100%'); + frame.style.height = this.height + 'px'; } + else { // bottom + frame.style.top = ''; + frame.style.bottom = '0'; + frame.style.left = '0'; + frame.style.width = asSize(options.width, '100%'); + frame.style.height = this.height + 'px'; + } + + this._repaintLabels(); this._repaintLine(); @@ -194,35 +167,81 @@ TimeAxis.prototype.repaint = function () { parent.appendChild(frame) } } - - return (changed > 0); }; /** - * Start a repaint. Move all DOM elements to a redundant list, where they - * can be picked for re-use, or can be cleaned up in the end + * Repaint major and minor text labels and vertical grid lines * @private */ -TimeAxis.prototype._repaintStart = function () { - var dom = this.dom, - redundant = dom.redundant; - - redundant.majorLines = dom.majorLines; - redundant.majorTexts = dom.majorTexts; - redundant.minorLines = dom.minorLines; - redundant.minorTexts = dom.minorTexts; - +TimeAxis.prototype._repaintLabels = function () { + var orientation = this.getOption('orientation'); + + // calculate range and step + this._updateConversion(); + var start = util.convert(this.range.start, 'Number'), + end = util.convert(this.range.end, 'Number'), + minimumStep = this.toTime((this.props.minorCharWidth || 10) * 5).valueOf() + -this.toTime(0).valueOf(); + var step = new TimeStep(new Date(start), new Date(end), minimumStep); + this.step = step; + + + // Move all DOM elements to a "redundant" list, where they + // can be picked for re-use, and clear the lists with lines and texts. + // At the end of the function _repaintLabels, left over elements will be cleaned up + var dom = this.dom; + dom.redundant.majorLines = dom.majorLines; + dom.redundant.majorTexts = dom.majorTexts; + dom.redundant.minorLines = dom.minorLines; + dom.redundant.minorTexts = dom.minorTexts; dom.majorLines = []; dom.majorTexts = []; dom.minorLines = []; dom.minorTexts = []; -}; -/** - * End a repaint. Cleanup leftover DOM elements in the redundant list - * @private - */ -TimeAxis.prototype._repaintEnd = function () { + step.first(); + var xFirstMajorLabel = undefined; + var max = 0; + while (step.hasNext() && max < 1000) { + max++; + var cur = step.getCurrent(), + x = this.toScreen(cur), + isMajor = step.isMajor(); + + // TODO: lines must have a width, such that we can create css backgrounds + + if (this.getOption('showMinorLabels')) { + this._repaintMinorText(x, step.getLabelMinor(), orientation); + } + + if (isMajor && this.getOption('showMajorLabels')) { + if (x > 0) { + if (xFirstMajorLabel == undefined) { + xFirstMajorLabel = x; + } + this._repaintMajorText(x, step.getLabelMajor(), orientation); + } + this._repaintMajorLine(x, orientation); + } + else { + this._repaintMinorLine(x, orientation); + } + + step.next(); + } + + // create a major label on the left when needed + if (this.getOption('showMajorLabels')) { + var leftTime = this.toTime(0), + leftText = step.getLabelMajor(leftTime), + widthText = leftText.length * (this.props.majorCharWidth || 10) + 10; // upper bound estimation + + if (xFirstMajorLabel == undefined || widthText < xFirstMajorLabel) { + this._repaintMajorText(0, leftText, orientation); + } + } + + // Cleanup leftover DOM elements from the redundant list util.forEach(this.dom.redundant, function (arr) { while (arr.length) { var elem = arr.pop(); @@ -233,14 +252,14 @@ TimeAxis.prototype._repaintEnd = function () { }); }; - /** * Create a minor label for the axis at position x * @param {Number} x * @param {String} text + * @param {String} orientation "top" or "bottom" (default) * @private */ -TimeAxis.prototype._repaintMinorText = function (x, text) { +TimeAxis.prototype._repaintMinorText = function (x, text, orientation) { // reuse redundant label var label = this.dom.redundant.minorTexts.shift(); @@ -255,8 +274,16 @@ TimeAxis.prototype._repaintMinorText = function (x, text) { this.dom.minorTexts.push(label); label.childNodes[0].nodeValue = text; + + if (orientation == 'top') { + label.style.top = this.props.minorLabelHeight + 'px'; + label.style.bottom = ''; + } + else { + label.style.top = ''; + label.style.bottom = this.props.minorLabelHeight + 'px'; + } label.style.left = x + 'px'; - label.style.top = this.props.minorLabelTop + 'px'; //label.title = title; // TODO: this is a heavy operation }; @@ -264,9 +291,10 @@ TimeAxis.prototype._repaintMinorText = function (x, text) { * Create a Major label for the axis at position x * @param {Number} x * @param {String} text + * @param {String} orientation "top" or "bottom" (default) * @private */ -TimeAxis.prototype._repaintMajorText = function (x, text) { +TimeAxis.prototype._repaintMajorText = function (x, text, orientation) { // reuse redundant label var label = this.dom.redundant.majorTexts.shift(); @@ -281,17 +309,26 @@ TimeAxis.prototype._repaintMajorText = function (x, text) { this.dom.majorTexts.push(label); label.childNodes[0].nodeValue = text; - label.style.top = this.props.majorLabelTop + 'px'; - label.style.left = x + 'px'; //label.title = title; // TODO: this is a heavy operation + + if (orientation == 'top') { + label.style.top = '0px'; + label.style.bottom = ''; + } + else { + label.style.top = ''; + label.style.bottom = '0px'; + } + label.style.left = x + 'px'; }; /** * Create a minor line for the axis at position x * @param {Number} x + * @param {String} orientation "top" or "bottom" (default) * @private */ -TimeAxis.prototype._repaintMinorLine = function (x) { +TimeAxis.prototype._repaintMinorLine = function (x, orientation) { // reuse redundant line var line = this.dom.redundant.minorLines.shift(); @@ -304,7 +341,14 @@ TimeAxis.prototype._repaintMinorLine = function (x) { this.dom.minorLines.push(line); var props = this.props; - line.style.top = props.minorLineTop + 'px'; + if (orientation == 'top') { + line.style.top = this.props.minorLabelHeight + 'px'; + line.style.bottom = ''; + } + else { + line.style.top = ''; + line.style.bottom = this.props.minorLabelHeight + 'px'; + } line.style.height = props.minorLineHeight + 'px'; line.style.left = (x - props.minorLineWidth / 2) + 'px'; }; @@ -312,9 +356,10 @@ TimeAxis.prototype._repaintMinorLine = function (x) { /** * Create a Major line for the axis at position x * @param {Number} x + * @param {String} orientation "top" or "bottom" (default) * @private */ -TimeAxis.prototype._repaintMajorLine = function (x) { +TimeAxis.prototype._repaintMajorLine = function (x, orientation) { // reuse redundant line var line = this.dom.redundant.majorLines.shift(); @@ -327,7 +372,14 @@ TimeAxis.prototype._repaintMajorLine = function (x) { this.dom.majorLines.push(line); var props = this.props; - line.style.top = props.majorLineTop + 'px'; + if (orientation == 'top') { + line.style.top = '0px'; + line.style.bottom = ''; + } + else { + line.style.top = ''; + line.style.bottom = '0px'; + } line.style.left = (x - props.majorLineWidth / 2) + 'px'; line.style.height = props.majorLineHeight + 'px'; }; @@ -340,7 +392,7 @@ TimeAxis.prototype._repaintMajorLine = function (x) { TimeAxis.prototype._repaintLine = function() { var line = this.dom.line, frame = this.frame, - options = this.options; + orientation = this.getOption('orientation'); // line before all axis elements if (this.getOption('showMinorLabels') || this.getOption('showMajorLabels')) { @@ -357,7 +409,14 @@ TimeAxis.prototype._repaintLine = function() { this.dom.line = line; } - line.style.top = this.props.lineTop + 'px'; + if (orientation == 'top') { + line.style.top = this.height + 'px'; + line.style.bottom = ''; + } + else { + line.style.top = ''; + line.style.bottom = this.height + 'px'; + } } else { if (line && line.parentElement) { @@ -368,136 +427,37 @@ TimeAxis.prototype._repaintLine = function() { }; /** - * Create characters used to determine the size of text on the axis + * Determine the size of text on the axis (both major and minor axis). + * The size is calculated only once and then cached in this.props. * @private */ -TimeAxis.prototype._repaintMeasureChars = function () { - // calculate the width and height of a single character - // this is used to calculate the step size, and also the positioning of the - // axis - var dom = this.dom, - text; - - if (!dom.measureCharMinor) { - text = document.createTextNode('0'); +TimeAxis.prototype._calculateCharSize = function () { + // determine the char width and height on the minor axis + if (!('minorCharHeight' in this.props)) { + var textMinor = document.createTextNode('0'); var measureCharMinor = document.createElement('DIV'); measureCharMinor.className = 'text minor measure'; - measureCharMinor.appendChild(text); + measureCharMinor.appendChild(textMinor); this.frame.appendChild(measureCharMinor); - dom.measureCharMinor = measureCharMinor; + this.props.minorCharHeight = measureCharMinor.clientHeight; + this.props.minorCharWidth = measureCharMinor.clientWidth; + + this.frame.removeChild(measureCharMinor); } - if (!dom.measureCharMajor) { - text = document.createTextNode('0'); + if (!('majorCharHeight' in this.props)) { + var textMajor = document.createTextNode('0'); var measureCharMajor = document.createElement('DIV'); measureCharMajor.className = 'text major measure'; - measureCharMajor.appendChild(text); + measureCharMajor.appendChild(textMajor); this.frame.appendChild(measureCharMajor); - dom.measureCharMajor = measureCharMajor; - } -}; + this.props.majorCharHeight = measureCharMajor.clientHeight; + this.props.majorCharWidth = measureCharMajor.clientWidth; -/** - * Reflow the component - * @return {Boolean} resized - */ -TimeAxis.prototype.reflow = function () { - var changed = 0, - update = util.updateProperty, - frame = this.frame, - range = this.range; - - if (!range) { - throw new Error('Cannot repaint time axis: no range configured'); + this.frame.removeChild(measureCharMajor); } - - if (frame) { - changed += update(this, 'top', frame.offsetTop); - changed += update(this, 'left', frame.offsetLeft); - - // calculate size of a character - var props = this.props, - showMinorLabels = this.getOption('showMinorLabels'), - showMajorLabels = this.getOption('showMajorLabels'), - measureCharMinor = this.dom.measureCharMinor, - measureCharMajor = this.dom.measureCharMajor; - if (measureCharMinor) { - props.minorCharHeight = measureCharMinor.clientHeight; - props.minorCharWidth = measureCharMinor.clientWidth; - } - if (measureCharMajor) { - props.majorCharHeight = measureCharMajor.clientHeight; - props.majorCharWidth = measureCharMajor.clientWidth; - } - - var parentHeight = frame.parentNode ? frame.parentNode.offsetHeight : 0; - if (parentHeight != props.parentHeight) { - props.parentHeight = parentHeight; - changed += 1; - } - switch (this.getOption('orientation')) { - case 'bottom': - props.minorLabelHeight = showMinorLabels ? props.minorCharHeight : 0; - props.majorLabelHeight = showMajorLabels ? props.majorCharHeight : 0; - - props.minorLabelTop = 0; - props.majorLabelTop = props.minorLabelTop + props.minorLabelHeight; - - props.minorLineTop = -this.top; - props.minorLineHeight = Math.max(this.top + props.majorLabelHeight, 0); - props.minorLineWidth = 1; // TODO: really calculate width - - props.majorLineTop = -this.top; - props.majorLineHeight = Math.max(this.top + props.minorLabelHeight + props.majorLabelHeight, 0); - props.majorLineWidth = 1; // TODO: really calculate width - - props.lineTop = 0; - - break; - - case 'top': - props.minorLabelHeight = showMinorLabels ? props.minorCharHeight : 0; - props.majorLabelHeight = showMajorLabels ? props.majorCharHeight : 0; - - props.majorLabelTop = 0; - props.minorLabelTop = props.majorLabelTop + props.majorLabelHeight; - - props.minorLineTop = props.minorLabelTop; - props.minorLineHeight = Math.max(parentHeight - props.majorLabelHeight - this.top); - props.minorLineWidth = 1; // TODO: really calculate width - - props.majorLineTop = 0; - props.majorLineHeight = Math.max(parentHeight - this.top); - props.majorLineWidth = 1; // TODO: really calculate width - - props.lineTop = props.majorLabelHeight + props.minorLabelHeight; - - break; - - default: - throw new Error('Unkown orientation "' + this.getOption('orientation') + '"'); - } - - var height = props.minorLabelHeight + props.majorLabelHeight; - changed += update(this, 'width', frame.offsetWidth); - changed += update(this, 'height', height); - - // calculate range and step - this._updateConversion(); - - var start = util.convert(range.start, 'Number'), - end = util.convert(range.end, 'Number'), - minimumStep = this.toTime((props.minorCharWidth || 10) * 5).valueOf() - -this.toTime(0).valueOf(); - this.step = new TimeStep(new Date(start), new Date(end), minimumStep); - changed += update(props.range, 'start', start); - changed += update(props.range, 'end', end); - changed += update(props.range, 'minimumStep', minimumStep.valueOf()); - } - - return (changed > 0); }; /** diff --git a/src/timeline/component/css/timeaxis.css b/src/timeline/component/css/timeaxis.css index 91b655b7..dd02204a 100644 --- a/src/timeline/component/css/timeaxis.css +++ b/src/timeline/component/css/timeaxis.css @@ -1,5 +1,5 @@ .vis.timeline .axis { - position: relative; + position: absolute; } .vis.timeline .axis .text { diff --git a/src/timeline/component/item/Item.js b/src/timeline/component/item/Item.js index 4bafabca..64fd43e9 100644 --- a/src/timeline/component/item/Item.js +++ b/src/timeline/component/item/Item.js @@ -65,16 +65,6 @@ Item.prototype.repaint = function repaint() { return false; }; -/** - * Reflow the item - * @return {Boolean} resized - */ -// TODO: cleanup redundant function -Item.prototype.reflow = function reflow() { - // should be implemented by the item - return false; -}; - /** * Reposition the Item horizontally */ diff --git a/src/timeline/component/item/ItemBox.js b/src/timeline/component/item/ItemBox.js index 2f6e6e2a..bd58d7c1 100644 --- a/src/timeline/component/item/ItemBox.js +++ b/src/timeline/component/item/ItemBox.js @@ -43,7 +43,7 @@ ItemBox.prototype.isVisible = function isVisible (range) { var interval = (range.end - range.start) / 4; interval = 0; // TODO: remove return (this.data.start > range.start - interval) && (this.data.start < range.end + interval); -} +}; /** * Repaint the item