Browse Source

TimeAxis starting to work again

css_transitions
jos 10 years ago
parent
commit
17fc182f5b
6 changed files with 176 additions and 176 deletions
  1. +25
    -35
      src/timeline/Range.js
  2. +81
    -27
      src/timeline/Timeline.js
  3. +5
    -23
      src/timeline/component/Component.js
  4. +63
    -88
      src/timeline/component/TimeAxis.js
  5. +1
    -1
      src/timeline/component/css/panel.css
  6. +1
    -2
      src/timeline/component/css/timeaxis.css

+ 25
- 35
src/timeline/Range.js View File

@ -3,41 +3,36 @@
* A Range controls a numeric range with a start and end value.
* The Range adjusts the range based on mouse events or programmatic changes,
* and triggers events when the range is changing or has been changed.
* @param {RootPanel} root Root panel, used to subscribe to events
* @param {Panel} parent Parent panel, used to attach to the DOM
* @param {{dom: Object, props: Object, emitter: Emitter, range: Range}} timeline
* @param {Object} [options] See description at Range.setOptions
*/
function Range(root, parent, options) {
function Range(timeline, options) {
this.id = util.randomUUID();
this.start = null; // Number
this.end = null; // Number
this.root = root;
this.parent = parent;
this.timeline = timeline;
this.options = options || {};
// drag listeners for dragging
this.root.on('dragstart', this._onDragStart.bind(this));
this.root.on('drag', this._onDrag.bind(this));
this.root.on('dragend', this._onDragEnd.bind(this));
this.timeline.emitter.on('dragstart', this._onDragStart.bind(this));
this.timeline.emitter.on('drag', this._onDrag.bind(this));
this.timeline.emitter.on('dragend', this._onDragEnd.bind(this));
// ignore dragging when holding
this.root.on('hold', this._onHold.bind(this));
this.timeline.emitter.on('hold', this._onHold.bind(this));
// mouse wheel for zooming
this.root.on('mousewheel', this._onMouseWheel.bind(this));
this.root.on('DOMMouseScroll', this._onMouseWheel.bind(this)); // For FF
this.timeline.emitter.on('mousewheel', this._onMouseWheel.bind(this));
this.timeline.emitter.on('DOMMouseScroll', this._onMouseWheel.bind(this)); // For FF
// pinch to zoom
this.root.on('touch', this._onTouch.bind(this));
this.root.on('pinch', this._onPinch.bind(this));
this.timeline.emitter.on('touch', this._onTouch.bind(this));
this.timeline.emitter.on('pinch', this._onPinch.bind(this));
this.setOptions(options);
}
// turn Range into an event emitter
Emitter(Range.prototype);
/**
* Set options for the range controller
* @param {Object} options Available options:
@ -80,8 +75,8 @@ Range.prototype.setRange = function(start, end) {
start: new Date(this.start),
end: new Date(this.end)
};
this.emit('rangechange', params);
this.emit('rangechanged', params);
this.timeline.emitter.emit('rangechange', params);
this.timeline.emitter.emit('rangechanged', params);
}
};
@ -258,9 +253,8 @@ Range.prototype._onDragStart = function(event) {
touchParams.start = this.start;
touchParams.end = this.end;
var frame = this.parent.frame;
if (frame) {
frame.style.cursor = 'move';
if (this.timeline.dom.root) {
this.timeline.dom.root.style.cursor = 'move';
}
};
@ -282,12 +276,12 @@ Range.prototype._onDrag = function (event) {
var delta = (direction == 'horizontal') ? event.gesture.deltaX : event.gesture.deltaY,
interval = (touchParams.end - touchParams.start),
width = (direction == 'horizontal') ? this.parent.width : this.parent.height,
width = (direction == 'horizontal') ? this.timeline.props.center.width : this.timeline.props.center.height,
diffRange = -delta / width * interval;
this._applyRange(touchParams.start + diffRange, touchParams.end + diffRange);
this.emit('rangechange', {
this.timeline.emitter.emit('rangechange', {
start: new Date(this.start),
end: new Date(this.end)
});
@ -305,12 +299,12 @@ Range.prototype._onDragEnd = function (event) {
// TODO: reckon with option movable
if (this.parent.frame) {
this.parent.frame.style.cursor = 'auto';
if (this.timeline.dom.root) {
this.timeline.dom.root.style.cursor = 'auto';
}
// fire a rangechanged event
this.emit('rangechanged', {
this.timeline.emitter.emit('rangechanged', {
start: new Date(this.start),
end: new Date(this.end)
});
@ -353,7 +347,7 @@ Range.prototype._onMouseWheel = function(event) {
// calculate center, the date to zoom around
var gesture = util.fakeGesture(this, event),
pointer = getPointer(gesture.center, this.parent.frame),
pointer = getPointer(gesture.center, this.timeline.dom.root),
pointerDate = this._pointerToDate(pointer);
this.zoom(scale, pointerDate);
@ -396,21 +390,17 @@ Range.prototype._onHold = function () {
* @private
*/
Range.prototype._onPinch = function (event) {
var direction = this.options.direction;
touchParams.ignore = true;
// TODO: reckon with option zoomable
if (event.gesture.touches.length > 1) {
if (!touchParams.center) {
touchParams.center = getPointer(event.gesture.center, this.parent.frame);
touchParams.center = getPointer(event.gesture.center, this.timeline.dom.root);
}
var scale = 1 / event.gesture.scale,
initDate = this._pointerToDate(touchParams.center),
center = getPointer(event.gesture.center, this.parent.frame),
date = this._pointerToDate(this.parent, center),
delta = date - initDate; // TODO: utilize delta
initDate = this._pointerToDate(touchParams.center);
// calculate new start and end
var newStart = parseInt(initDate + (touchParams.start - initDate) * scale);
@ -434,12 +424,12 @@ Range.prototype._pointerToDate = function (pointer) {
validateDirection(direction);
if (direction == 'horizontal') {
var width = this.parent.width;
var width = this.timeline.props.center.width;
conversion = this.conversion(width);
return pointer.x / conversion.scale + conversion.offset;
}
else {
var height = this.parent.height;
var height = this.timeline.props.center.height;
conversion = this.conversion(height);
return pointer.y / conversion.scale + conversion.offset;
}

+ 81
- 27
src/timeline/Timeline.js View File

@ -72,13 +72,14 @@ function Timeline (container, items, options) {
this.options = {};
util.deepExtend(this.options, this.defaultOptions);
util.deepExtend(this.options, {
// FIXME: not nice passing these functions via the options
snap: null, // will be specified after timeaxis is created
toScreen: me._toScreen.bind(me),
toTime: me._toTime.bind(me)
});
// Create the main DOM
// Create the DOM, props, and emitter
this._create();
// attach the root panel to the provided container
@ -86,18 +87,37 @@ function Timeline (container, items, options) {
container.appendChild(this.dom.root);
// TODO: remove temporary contents
this.dom.background.innerHTML = '<span style="color: #d3d3d3;">background</span>';
this.dom.center.innerHTML = 'center';
this.dom.center.innerHTML = 'center<br>center<br>center<br>center<br>center<br>center<br>center<br>center<br>center<br>center<br>center<br>center<br>center<br>center<br>center<br>center<br>center<br>center<br>center<br>center<br>center<br>center<br>center<br>center<br>center<br>center<br>center<br>';
this.dom.left.innerHTML = 'left';
this.dom.right.innerHTML = 'right';
this.dom.top.innerHTML = 'top';
this.dom.bottom.innerHTML = 'bottom';
//this.dom.background.innerHTML = '<span style="color: red;">background</span>';
this.dom.center.innerHTML = '<span style="color: red;">center</span>';
this.dom.center.innerHTML = 'center<br>center<br>center<br>center<br>center<br>center<br>center<br>center<br>center<br>center<br>center<br>center<br>center<br>center<br>center';
this.dom.left.innerHTML = '<span style="color: red;">left</span>';
this.dom.right.innerHTML = '<span style="color: red;">right</span>';
this.dom.top.innerHTML = '<span style="color: red;">top</span>';
//this.dom.bottom.innerHTML = '<span style="color: red;">bottom</span>';
this.components = [];
// create a Range
this.range = new Range(this, this.options);
this.range.setRange(
now.clone().add('days', -3).valueOf(),
now.clone().add('days', 4).valueOf()
);
this.repaint();
// Create a TimeAxis
var timeAxis = new TimeAxis(this, this.options);
this.components.push(timeAxis);
/* TODO
// re-emit public events
this.emitter.on('rangechange', function (properties) {
me.emit('rangechange', properties);
});
this.emitter.on('rangechanged', function (properties) {
me.emit('rangechanged', properties);
});
/* TODO
// root panel
var rootOptions = util.extend(Object.create(this.options), {
height: function () {
@ -291,7 +311,7 @@ function Timeline (container, items, options) {
this.itemSet.setRange(this.range);
this.itemSet.on('change', me.rootPanel.repaint.bind(me.rootPanel));
this.contentPanel.appendChild(this.itemSet);
*/
this.itemsData = null; // DataSet
this.groupsData = null; // DataSet
@ -304,7 +324,6 @@ function Timeline (container, items, options) {
if (items) {
this.setItems(items);
}
*/
}
// turn Timeline into an event emitter
@ -350,6 +369,36 @@ Timeline.prototype._create = function () {
this.dom.leftContainer.appendChild(this.dom.left);
this.dom.rightContainer.appendChild(this.dom.right);
// TODO: move watch from RootPanel to here
// create a central event bus
this.emitter = new Emitter();
this.emitter.on('rangechange', this.repaint.bind(this));
// create event listeners for all interesting events, these events will be
// emitted via emitter
this.hammer = Hammer(this.dom.root, {
prevent_default: true
});
this.listeners = {};
var me = this;
var events = [
'touch', 'pinch', 'tap', 'doubletap', 'hold',
'dragstart', 'drag', 'dragend',
'mousewheel', 'DOMMouseScroll' // DOMMouseScroll is needed for Firefox
];
events.forEach(function (event) {
var listener = function () {
var args = [event].concat(Array.prototype.slice.call(arguments, 0));
me.emitter.emit.apply(me.emitter, args);
};
me.hammer.on(event, listener);
me.listeners[event] = listener;
});
// size properties of each of the panels
this.props = {
root: {},
background: {},
@ -382,11 +431,11 @@ Timeline.prototype.setOptions = function (options) {
remove: isBoolean ? options.editable : (options.editable.remove || false)
};
}
/* TODO
// force update of range (apply new min/max etc.)
// both start and end are optional
this.range.setRange(options.start, options.end);
*/
if ('editable' in options || 'selectable' in options) {
if (this.options.selectable) {
// force update of selection
@ -397,10 +446,10 @@ Timeline.prototype.setOptions = function (options) {
this.setSelection([]);
}
}
/* TODO
// force the itemSet to refresh: options like orientation and margins may be changed
this.itemSet.markDirty();
*/
// validate the callback functions
var validateCallback = (function (fn) {
if (!(this.options[fn] instanceof Function) || this.options[fn].length != 2) {
@ -409,6 +458,7 @@ Timeline.prototype.setOptions = function (options) {
}).bind(this);
['onAdd', 'onUpdate', 'onRemove', 'onMove'].forEach(validateCallback);
/* TODO
// add/remove the current time bar
if (this.options.showCurrentTime) {
if (!this.mainPanel.hasChild(this.currentTime)) {
@ -434,7 +484,7 @@ Timeline.prototype.setOptions = function (options) {
this.mainPanel.removeChild(this.customTime);
}
}
*/
// TODO: remove deprecation error one day (deprecated since version 0.8.0)
if (options && options.order) {
throw new Error('Option order is deprecated. There is no replacement for this feature.');
@ -495,7 +545,7 @@ Timeline.prototype.setItems = function(items) {
// set items
this.itemsData = newDataSet;
this.itemSet.setItems(newDataSet);
this.itemSet && this.itemSet.setItems(newDataSet);
if (initialLoad && (this.options.start == undefined || this.options.end == undefined)) {
this.fit();
@ -631,7 +681,7 @@ Timeline.prototype.getItemRange = function getItemRange() {
* unselected.
*/
Timeline.prototype.setSelection = function setSelection (ids) {
this.itemSet.setSelection(ids);
this.itemSet && this.itemSet.setSelection(ids);
};
/**
@ -639,7 +689,7 @@ Timeline.prototype.setSelection = function setSelection (ids) {
* @return {Array} ids The ids of the selected items
*/
Timeline.prototype.getSelection = function getSelection() {
return this.itemSet.getSelection();
return this.itemSet && this.itemSet.getSelection() || [];
};
/**
@ -746,8 +796,6 @@ Timeline.prototype.repaint = function repaint() {
dom.background.style.width = props.background.width + 'px';
dom.centerContainer.style.width = props.center.width + 'px';
dom.leftContainer.style.width = (props.left.width + props.border.left) + 'px';
dom.rightContainer.style.width = (props.right.width + props.border.right) + 'px';
dom.top.style.width = props.top.width + 'px';
dom.bottom.style.width = props.bottom.width + 'px';
@ -765,9 +813,15 @@ Timeline.prototype.repaint = function repaint() {
dom.bottom.style.left = props.left.width + 'px';
dom.bottom.style.top = (props.top.height + props.centerContainer.height) + 'px';
/* TODO: repaint contents
this.rootPanel.repaint();
*/
// repaint all components
var resized = false;
this.components.forEach(function (component) {
resized = component.repaint() || resized;
});
if (resized) {
// keep repainting until all sizes are settled
this.repaint();
}
};
/**
@ -900,7 +954,7 @@ Timeline.prototype._onMultiSelectItem = function (event) {
* @private
*/
Timeline.prototype._toTime = function _toTime(x) {
var conversion = this.range.conversion(this.mainPanel.width);
var conversion = this.range.conversion(this.props.center.width);
return new Date(x / conversion.scale + conversion.offset);
};
@ -912,6 +966,6 @@ Timeline.prototype._toTime = function _toTime(x) {
* @private
*/
Timeline.prototype._toScreen = function _toScreen(time) {
var conversion = this.range.conversion(this.mainPanel.width);
var conversion = this.range.conversion(this.props.center.width);
return (time.valueOf() - conversion.offset) * conversion.scale;
};

+ 5
- 23
src/timeline/component/Component.js View File

@ -2,20 +2,10 @@
* Prototype for visual components
*/
function Component () {
this.id = null;
this.parent = null;
this.childs = null;
this.options = null;
this.top = 0;
this.left = 0;
this.width = 0;
this.height = 0;
this.props = null;
}
// Turn the Component into an event emitter
Emitter(Component.prototype);
/**
* Set parameters for the frame. Parameters will be merged in current parameter
* set.
@ -52,15 +42,6 @@ Component.prototype.getOption = function getOption(name) {
return value;
};
/**
* Get the frame element of the component, the outer HTML DOM element.
* @returns {HTMLElement | null} frame
*/
Component.prototype.getFrame = function getFrame() {
// should be implemented by the component
return null;
};
/**
* Repaint the component
* @return {boolean} Returns true if the component is resized
@ -77,10 +58,11 @@ Component.prototype.repaint = function repaint() {
* @protected
*/
Component.prototype._isResized = function _isResized() {
var resized = (this._previousWidth !== this.width || this._previousHeight !== this.height);
var resized = (this.props._previousWidth !== this.props.width ||
this.props._previousHeight !== this.props.height);
this._previousWidth = this.width;
this._previousHeight = this.height;
this.props._previousWidth = this.props.width;
this.props._previousHeight = this.props.height;
return resized;
};

+ 63
- 88
src/timeline/component/TimeAxis.js View File

@ -1,14 +1,14 @@
/**
* A horizontal time axis
* @param {{dom: Object, props: Object, emitter: Emitter, range: Range}} timeline
* @param {Object} [options] See TimeAxis.setOptions for the available
* options.
* @constructor TimeAxis
* @extends Component
*/
function TimeAxis (options) {
this.id = util.randomUUID();
function TimeAxis (timeline, options) {
this.dom = {
frame: null,
majorLines: [],
majorTexts: [],
minorLines: [],
@ -37,7 +37,7 @@ function TimeAxis (options) {
showMajorLabels: true
};
this.range = null;
this.timeline = timeline;
// create the HTML DOM
this._create();
@ -45,34 +45,21 @@ function TimeAxis (options) {
TimeAxis.prototype = new Component();
// TODO: comment options
/**
* Set parameters for the timeaxis.
* Parameters will be merged in current parameter set.
* @param {Object} options Available parameters:
* {string} [orientation]
* {boolean} [showMinorLabels]
* {boolean} [showMajorLabels]
*/
TimeAxis.prototype.setOptions = Component.prototype.setOptions;
/**
* Create the HTML DOM for the TimeAxis
*/
TimeAxis.prototype._create = function _create() {
this.frame = document.createElement('div');
};
/**
* Set a range (start and end)
* @param {Range | Object} range A Range or an object containing start and end.
*/
TimeAxis.prototype.setRange = function (range) {
if (!(range instanceof Range) && (!range || !range.start || !range.end)) {
throw new TypeError('Range must be an instance of Range, ' +
'or an object containing start and end.');
}
this.range = range;
};
/**
* Get the outer frame of the time axis
* @return {HTMLElement} frame
*/
TimeAxis.prototype.getFrame = function getFrame() {
return this.frame;
this.dom.frame = document.createElement('div');
};
/**
@ -80,67 +67,55 @@ TimeAxis.prototype.getFrame = function getFrame() {
* @return {boolean} Returns true if the component is resized
*/
TimeAxis.prototype.repaint = function () {
var asSize = util.option.asSize,
options = this.options,
var options = this.options,
props = this.props,
frame = this.frame;
frame = this.dom.frame;
// determine the correct parent DOM element (depending on option orientation)
var parent = (options.orientation == 'top') ? this.timeline.dom.top : this.timeline.dom.bottom;
// update classname
frame.className = 'timeaxis'; // TODO: add className from options if defined
var parent = frame.parentNode;
if (parent) {
// calculate character width and height
this._calculateCharSize();
// 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;
this.width = frame.offsetWidth; // TODO: only update the width when the frame is resized?
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);
// TODO: top/bottom positioning should be determined by options set in the Timeline, not here
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';
}
// calculate character width and height
this._calculateCharSize();
this._repaintLabels();
// 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');
this._repaintLine();
// determine the width and height of the elemens for the axis
var backgroundHeight = this.timeline.props.background.height;
props.minorLabelHeight = showMinorLabels ? props.minorCharHeight : 0;
props.majorLabelHeight = showMajorLabels ? props.majorCharHeight : 0;
props.height = props.minorLabelHeight + props.majorLabelHeight;
props.width = frame.offsetWidth; // TODO: only update the width when the frame is resized?
// put frame online again
if (beforeChild) {
parent.insertBefore(frame, beforeChild);
}
else {
parent.appendChild(frame)
}
props.minorLineHeight = backgroundHeight + props.minorLabelHeight;
props.minorLineWidth = 1; // TODO: really calculate width
props.majorLineHeight = backgroundHeight + this.props.height;
props.majorLineWidth = 1; // TODO: really calculate width
// take frame offline while updating (is almost twice as fast)
var beforeChild = frame.nextSibling;
frame.parentNode && frame.parentNode.removeChild(frame);
frame.style.top = '0';
frame.style.left = '0';
frame.style.width = '100%';
frame.style.height = this.props.height + 'px';
this._repaintLabels();
this._repaintLine();
// put frame online again at the same place
if (beforeChild) {
parent.insertBefore(frame, beforeChild);
}
else {
parent.appendChild(frame)
}
return this._isResized();
@ -154,8 +129,8 @@ TimeAxis.prototype._repaintLabels = function () {
var orientation = this.getOption('orientation');
// calculate range and step (step such that we have space for 7 characters per label)
var start = util.convert(this.range.start, 'Number'),
end = util.convert(this.range.end, 'Number'),
var start = util.convert(this.timeline.range.start, 'Number'),
end = util.convert(this.timeline.range.end, 'Number'),
minimumStep = this.options.toTime((this.props.minorCharWidth || 10) * 7).valueOf()
-this.options.toTime(0).valueOf();
var step = new TimeStep(new Date(start), new Date(end), minimumStep);
@ -244,7 +219,7 @@ TimeAxis.prototype._repaintMinorText = function (x, text, orientation) {
label = document.createElement('div');
label.appendChild(content);
label.className = 'text minor';
this.frame.appendChild(label);
this.dom.frame.appendChild(label);
}
this.dom.minorTexts.push(label);
@ -279,7 +254,7 @@ TimeAxis.prototype._repaintMajorText = function (x, text, orientation) {
label = document.createElement('div');
label.className = 'text major';
label.appendChild(content);
this.frame.appendChild(label);
this.dom.frame.appendChild(label);
}
this.dom.majorTexts.push(label);
@ -311,7 +286,7 @@ TimeAxis.prototype._repaintMinorLine = function (x, orientation) {
// create vertical line
line = document.createElement('div');
line.className = 'grid vertical minor';
this.frame.appendChild(line);
this.dom.frame.appendChild(line);
}
this.dom.minorLines.push(line);
@ -342,7 +317,7 @@ TimeAxis.prototype._repaintMajorLine = function (x, orientation) {
// create vertical line
line = document.createElement('DIV');
line.className = 'grid vertical major';
this.frame.appendChild(line);
this.dom.frame.appendChild(line);
}
this.dom.majorLines.push(line);
@ -366,7 +341,7 @@ TimeAxis.prototype._repaintMajorLine = function (x, orientation) {
*/
TimeAxis.prototype._repaintLine = function() {
var line = this.dom.line,
frame = this.frame,
frame = this.dom.frame,
orientation = this.getOption('orientation');
// line before all axis elements
@ -417,7 +392,7 @@ TimeAxis.prototype._calculateCharSize = function () {
this.dom.measureCharMinor.style.position = 'absolute';
this.dom.measureCharMinor.appendChild(document.createTextNode('0'));
this.frame.appendChild(this.dom.measureCharMinor);
this.dom.frame.appendChild(this.dom.measureCharMinor);
}
this.props.minorCharHeight = this.dom.measureCharMinor.clientHeight;
this.props.minorCharWidth = this.dom.measureCharMinor.clientWidth;
@ -429,7 +404,7 @@ TimeAxis.prototype._calculateCharSize = function () {
this.dom.measureCharMajor.style.position = 'absolute';
this.dom.measureCharMajor.appendChild(document.createTextNode('0'));
this.frame.appendChild(this.dom.measureCharMajor);
this.dom.frame.appendChild(this.dom.measureCharMajor);
}
this.props.majorCharHeight = this.dom.measureCharMajor.clientHeight;
this.props.majorCharWidth = this.dom.measureCharMajor.clientWidth;

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

@ -12,7 +12,7 @@
.vis.timeline .vispanel {
position: absolute;
overflow: hidden;
padding: 0;
margin: 0;

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

@ -1,5 +1,5 @@
.vis.timeline .timeaxis {
position: absolute;
position: relative;
}
.vis.timeline .timeaxis .text {
@ -29,7 +29,6 @@
left: 0;
width: 100%;
height: 0;
border-bottom: 1px solid;
}
.vis.timeline .timeaxis .grid.minor {

Loading…
Cancel
Save