Browse Source

Simplified TimeAxis

css_transitions
josdejong 10 years ago
parent
commit
8c16dad1ca
7 changed files with 186 additions and 255 deletions
  1. +2
    -1
      src/timeline/Controller.js
  2. +7
    -18
      src/timeline/component/Group.js
  3. +0
    -9
      src/timeline/component/ItemSet.js
  4. +175
    -215
      src/timeline/component/TimeAxis.js
  5. +1
    -1
      src/timeline/component/css/timeaxis.css
  6. +0
    -10
      src/timeline/component/item/Item.js
  7. +1
    -1
      src/timeline/component/item/ItemBox.js

+ 2
- 1
src/timeline/Controller.js View File

@ -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

+ 7
- 18
src/timeline/component/Group.js View File

@ -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);
};

+ 0
- 9
src/timeline/component/ItemSet.js View File

@ -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
*/

+ 175
- 215
src/timeline/component/TimeAxis.js View File

@ -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);
};
/**

+ 1
- 1
src/timeline/component/css/timeaxis.css View File

@ -1,5 +1,5 @@
.vis.timeline .axis {
position: relative;
position: absolute;
}
.vis.timeline .axis .text {

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

@ -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
*/

+ 1
- 1
src/timeline/component/item/ItemBox.js View File

@ -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

Loading…
Cancel
Save