Browse Source

Moved the dots and vertical lines of box items to a separate panel

css_transitions
jos 10 years ago
parent
commit
4ab1d08876
13 changed files with 174 additions and 57 deletions
  1. +41
    -3
      src/timeline/Timeline.js
  2. +2
    -2
      src/timeline/component/CurrentTime.js
  3. +23
    -10
      src/timeline/component/Group.js
  4. +17
    -12
      src/timeline/component/GroupSet.js
  5. +41
    -5
      src/timeline/component/ItemSet.js
  6. +8
    -1
      src/timeline/component/Panel.js
  7. +2
    -1
      src/timeline/component/TimeAxis.js
  8. +8
    -8
      src/timeline/component/css/groupset.css
  9. +2
    -0
      src/timeline/component/css/item.css
  10. +4
    -1
      src/timeline/component/css/itemset.css
  11. +7
    -7
      src/timeline/component/css/timeaxis.css
  12. +6
    -7
      src/timeline/component/item/ItemBox.js
  13. +13
    -0
      test/timeline.html

+ 41
- 3
src/timeline/Timeline.js View File

@ -176,6 +176,41 @@ function Timeline (container, items, options) {
this.contentPanel = new Panel(contentOptions);
this.mainPanel.appendChild(this.contentPanel);
// content panel (contains the vertical lines of box items)
var backgroundOptions = util.extend(Object.create(this.options), {
top: function () {
return (me.options.orientation == 'top') ? (me.timeAxis.height + 'px') : '';
},
bottom: function () {
return (me.options.orientation == 'top') ? '' : (me.timeAxis.height + 'px');
},
left: null,
right: null,
height: function () {
return me.contentPanel.height;
},
width: null,
className: 'background'
});
this.backgroundPanel = new Panel(backgroundOptions);
this.mainPanel.insertBefore(this.backgroundPanel, this.contentPanel);
// panel with axis holding the dots of item boxes
var axisPanelOptions = util.extend(Object.create(rootOptions), {
left: 0,
top: function () {
return (me.options.orientation == 'top') ? (me.timeAxis.height + 'px') : '';
},
bottom: function () {
return (me.options.orientation == 'top') ? '' : (me.timeAxis.height + 'px');
},
width: '100%',
height: 0,
className: 'axis'
});
this.axisPanel = new Panel(axisPanelOptions);
this.mainPanel.appendChild(this.axisPanel);
// content panel (contains itemset(s))
var sideContentOptions = util.extend(Object.create(this.options), {
top: function () {
@ -398,7 +433,7 @@ Timeline.prototype.setGroups = function(groupSet) {
// remove itemset if existing
if (this.itemSet) {
//this.itemSet.hide(); // TODO: not so nice having to hide here
this.itemSet.hide(); // TODO: not so nice having to hide here
this.contentPanel.removeChild(this.itemSet);
this.itemSet.setItems(); // disconnect from itemset
this.itemSet = null;
@ -406,7 +441,7 @@ Timeline.prototype.setGroups = function(groupSet) {
// create new GroupSet when needed
if (!this.groupSet) {
this.groupSet = new GroupSet(this.contentPanel, this.sideContentPanel, options);
this.groupSet = new GroupSet(this.contentPanel, this.sideContentPanel, this.backgroundPanel, this.axisPanel, options);
this.groupSet.on('change', this.rootPanel.repaint.bind(this.rootPanel));
this.groupSet.setRange(this.range);
this.groupSet.setItems(this.itemsData);
@ -428,7 +463,7 @@ Timeline.prototype.setGroups = function(groupSet) {
}
// create new items
this.itemSet = new ItemSet(options);
this.itemSet = new ItemSet(this.backgroundPanel, this.axisPanel, options);
this.itemSet.setRange(this.range);
this.itemSet.setItems(this.itemsData);
this.itemSet.on('change', me.rootPanel.repaint.bind(me.rootPanel));
@ -504,6 +539,8 @@ Timeline.prototype.getSelection = function getSelection() {
* @param {Date | Number | String} [start] Start date of visible window
* @param {Date | Number | String} [end] End date of visible window
*/
// TODO: implement support for setWindow({start: ..., end: ...})
// TODO: rename setWindow to setRange?
Timeline.prototype.setWindow = function setWindow(start, end) {
this.range.setRange(start, end);
};
@ -512,6 +549,7 @@ Timeline.prototype.setWindow = function setWindow(start, end) {
* Get the visible window
* @return {{start: Date, end: Date}} Visible range
*/
// TODO: rename getWindow to getRange?
Timeline.prototype.getWindow = function setWindow() {
var range = this.range.getRange();
return {

+ 2
- 2
src/timeline/component/CurrentTime.js View File

@ -71,8 +71,8 @@ CurrentTime.prototype.start = function start() {
me.stop();
// determine interval to refresh
var scale = me.range.conversion(parent.width).scale;
var interval = 1 / scale / 2;
var scale = me.range.conversion(me.parent.width).scale;
var interval = 1 / scale / 10;
if (interval < 30) interval = 30;
if (interval > 1000) interval = 1000;

+ 23
- 10
src/timeline/component/Group.js View File

@ -1,16 +1,20 @@
/**
* @constructor Group
* @param {Element} groupFrame
* @param {Element} labelFrame
* @param {Panel} groupPanel
* @param {Panel} labelPanel
* @param {Panel} backgroundPanel
* @param {Panel} axisPanel
* @param {Number | String} groupId
* @param {Object} [options] Options to set initial property values
* // TODO: describe available options
* @extends Component
*/
function Group (groupFrame, labelFrame, groupId, options) {
function Group (groupPanel, labelPanel, backgroundPanel, axisPanel, groupId, options) {
this.id = util.randomUUID();
this.groupFrame = groupFrame;
this.labelFrame = labelFrame;
this.groupPanel = groupPanel;
this.labelPanel = labelPanel;
this.backgroundPanel = backgroundPanel;
this.axisPanel = axisPanel;
this.groupId = groupId;
this.itemSet = null; // ItemSet
@ -87,7 +91,7 @@ Group.prototype.setItems = function setItems(itemsData) {
if (this.itemSet) {
// remove current item set
this.itemSet.setItems();
this.groupFrame.removeChild(this.itemSet.getFrame());
this.groupPanel.frame.removeChild(this.itemSet.getFrame());
this.itemSet = null;
}
@ -101,9 +105,10 @@ Group.prototype.setItems = function setItems(itemsData) {
return Math.max(me.props.label.height, me.itemSet.height);
}
});
this.itemSet = new ItemSet(itemSetOptions);
this.itemSet = new ItemSet(this.backgroundPanel, this.axisPanel, itemSetOptions);
this.itemSet.on('change', this.emit.bind(this, 'change')); // propagate change event
this.groupFrame.appendChild(this.itemSet.getFrame());
this.itemSet.parent = this;
this.groupPanel.frame.appendChild(this.itemSet.getFrame());
if (this.range) this.itemSet.setRange(this.range);
@ -121,7 +126,11 @@ Group.prototype.setItems = function setItems(itemsData) {
*/
Group.prototype.show = function show() {
if (!this.dom.label.parentNode) {
this.labelFrame.appendChild(this.dom.label);
this.labelPanel.frame.appendChild(this.dom.label);
}
if (this.itemSet) {
this.itemSet.show();
}
var itemSetFrame = this.itemSet && this.itemSet.getFrame();
@ -129,7 +138,7 @@ Group.prototype.show = function show() {
if (itemSetFrame.parentNode) {
itemSetFrame.parentNode.removeChild(itemSetFrame);
}
this.groupFrame.appendChild(itemSetFrame);
this.groupPanel.frame.appendChild(itemSetFrame);
}
};
@ -141,6 +150,10 @@ Group.prototype.hide = function hide() {
this.dom.label.parentNode.removeChild(this.dom.label);
}
if (this.itemSet) {
this.itemSet.hide();
}
var itemSetFrame = this.itemset && this.itemSet.getFrame();
if (itemSetFrame && itemSetFrame.parentNode) {
itemSetFrame.parentNode.removeChild(itemSetFrame);

+ 17
- 12
src/timeline/component/GroupSet.js View File

@ -2,16 +2,22 @@
* An GroupSet holds a set of groups
* @param {Panel} contentPanel Panel where the ItemSets will be created
* @param {Panel} labelPanel Panel where the labels will be created
* @param {Panel} backgroundPanel Panel where the vertical lines of box
* items are created
* @param {Panel} axisPanel Panel on the axis where the dots of box
* items will be created
* @param {Object} [options] See GroupSet.setOptions for the available
* options.
* @constructor GroupSet
* @extends Panel
*/
function GroupSet(contentPanel, labelPanel, options) {
function GroupSet(contentPanel, labelPanel, backgroundPanel, axisPanel, options) {
this.id = util.randomUUID();
this.contentPanel = contentPanel;
this.labelPanel = labelPanel;
this.backgroundPanel = backgroundPanel;
this.axisPanel = axisPanel;
this.options = options || {};
this.range = null; // Range or Object {start: number, end: number}
@ -64,7 +70,7 @@ GroupSet.prototype._create = function _create () {
this.frame = frame;
this.labelSet = new Panel({
className: 'labelSet',
className: 'labelset',
width: '100%',
height: '100%'
});
@ -272,11 +278,12 @@ GroupSet.prototype.repaint = function repaint() {
// TODO: do not recreate the group with every update
group = new Group(me.frame, me.labelSet.frame, id, groupOptions);
group = new Group(me, me.labelSet, me.backgroundPanel, me.axisPanel, id, groupOptions);
group.on('change', me.emit.bind(me, 'change')); // propagate change event
group.setRange(me.range);
group.setItems(me.itemsData); // attach items data
groups[id] = group;
group.parent = me;
// Note: it is important to add the binding after group.setItems
// is executed, because that will start an infinite loop
@ -323,7 +330,6 @@ GroupSet.prototype.repaint = function repaint() {
// reposition the labels and calculate the maximum label width
// TODO: labels are not displayed correctly when orientation=='top'
// TODO: width of labelPanel is not immediately updated on a change in groups
var maxWidth = 0;
for (id in groups) {
if (groups.hasOwnProperty(id)) {
@ -333,19 +339,19 @@ GroupSet.prototype.repaint = function repaint() {
}
resized = util.updateProperty(this.props.labels, 'width', maxWidth) || resized;
// recalculate the height of the groupset
// recalculate the height of the groupset, and recalculate top positions of the groups
var fixedHeight = (asSize(options.height) != null);
var height;
if (!fixedHeight) {
// height is not specified, calculate the sum of the height of all groups
height = 0;
for (id in this.groups) {
if (this.groups.hasOwnProperty(id)) {
group = this.groups[id];
height += group.height;
}
}
this.groupIds.forEach(function (id) {
var group = groups[id];
group.top = height;
if (group.itemSet) group.itemSet.top = group.top; // TODO: this is an ugly hack
height += group.height;
});
}
// update classname
@ -385,7 +391,6 @@ GroupSet.prototype.hide = function hide() {
/**
* Show the component in the DOM (when not already visible).
* A repaint will be executed when the component is not visible
* @return {Boolean} changed
*/
GroupSet.prototype.show = function show() {

+ 41
- 5
src/timeline/component/ItemSet.js View File

@ -2,17 +2,22 @@
* An ItemSet holds a set of items and ranges which can be displayed in a
* range. The width is determined by the parent of the ItemSet, and the height
* is determined by the size of the items.
* @param {Object} [options] See ItemSet.setOptions for the available
* options.
* @param {Panel} backgroundPanel Panel which can be used to display the
* vertical lines of box items.
* @param {Panel} axisPanel Panel on the axis where the dots of box-items
* can be displayed.
* @param {Object} [options] See ItemSet.setOptions for the available options.
* @constructor ItemSet
* @extends Panel
*/
// TODO: improve performance by replacing all Array.forEach with a for loop
function ItemSet(options) {
function ItemSet(backgroundPanel, axisPanel, options) {
this.id = util.randomUUID();
// one options object is shared by this itemset and all its items
this.options = options || {};
this.backgroundPanel = backgroundPanel;
this.axisPanel = axisPanel;
this.itemOptions = Object.create(this.options);
this.dom = {};
this.hammer = null;
@ -76,7 +81,7 @@ ItemSet.prototype._create = function _create(){
// create background panel
var background = document.createElement('div');
background.className = 'background';
frame.appendChild(background);
this.backgroundPanel.frame.appendChild(background);
this.dom.background = background;
// create foreground panel
@ -89,7 +94,7 @@ ItemSet.prototype._create = function _create(){
var axis = document.createElement('div');
axis.className = 'axis';
this.dom.axis = axis;
frame.appendChild(axis);
this.axisPanel.frame.appendChild(axis);
// attach event listeners
// TODO: use event listeners from the rootpanel to improve performance
@ -131,6 +136,37 @@ ItemSet.prototype._create = function _create(){
*/
ItemSet.prototype.setOptions = Component.prototype.setOptions;
/**
* Hide the component from the DOM
*/
ItemSet.prototype.hide = function hide() {
// remove the axis with dots
if (this.dom.axis.parentNode) {
this.dom.axis.parentNode.removeChild(this.dom.axis);
}
// remove the background with vertical lines
if (this.dom.background.parentNode) {
this.dom.background.parentNode.removeChild(this.dom.background);
}
};
/**
* Show the component in the DOM (when not already visible).
* @return {Boolean} changed
*/
ItemSet.prototype.show = function show() {
// show axis with dots
if (!this.dom.axis.parentNode) {
this.axisPanel.frame.appendChild(this.dom.axis);
}
// show background with vertical lines
if (!this.dom.background.parentNode) {
this.backgroundPanel.frame.appendChild(this.dom.background);
}
};
/**
* Set range (start and end).
* @param {Range | Object} range A Range or an object containing start and end.

+ 8
- 1
src/timeline/component/Panel.js View File

@ -76,7 +76,14 @@ Panel.prototype.insertBefore = function (child, beforeChild) {
if (frame.parentNode) {
frame.parentNode.removeChild(frame);
}
this.frame.appendChild(frame);
var beforeFrame = beforeChild.getFrame();
if (beforeFrame) {
this.frame.insertBefore(frame, beforeFrame);
}
else {
this.frame.appendChild(frame);
}
}
}
};

+ 2
- 1
src/timeline/component/TimeAxis.js View File

@ -86,7 +86,7 @@ TimeAxis.prototype.repaint = function () {
frame = this.frame;
// update classname
frame.className = 'axis'; // TODO: add className from options if defined
frame.className = 'timeaxis'; // TODO: add className from options if defined
var parent = frame.parentNode;
if (parent) {
@ -114,6 +114,7 @@ TimeAxis.prototype.repaint = function () {
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';

+ 8
- 8
src/timeline/component/css/groupset.css View File

@ -1,6 +1,6 @@
/* TODO: cleanup
.vis.timeline .groupSet {
.vis.timeline .groupset {
position: relative;
padding: 0;
margin: 0;
@ -18,7 +18,7 @@
}
*/
.vis.timeline .labelSet {
.vis.timeline .labelset {
position: relative;
width: 100%;
@ -28,7 +28,7 @@
box-sizing: border-box;
}
.vis.timeline .labelSet .vlabel {
.vis.timeline .labelset .vlabel {
position: relative;
left: 0;
top: 0;
@ -39,21 +39,21 @@
box-sizing: border-box;
}
.vis.timeline.bottom .labelSet .vlabel,
.vis.timeline.bottom .labelset .vlabel,
.vis.timeline.top .vpanel.side-content,
.vis.timeline.top .itemset .axis {
.vis.timeline.top .groupset .itemset {
border-top: 1px solid #bfbfbf;
border-bottom: none;
}
.vis.timeline.top .labelSet .vlabel,
.vis.timeline.top .labelset .vlabel,
.vis.timeline.bottom .vpanel.side-content,
.vis.timeline.bottom .itemset .axis {
.vis.timeline.bottom .groupset .itemset {
border-top: none;
border-bottom: 1px solid #bfbfbf;
}
.vis.timeline .labelSet .vlabel .inner {
.vis.timeline .labelset .vlabel .inner {
display: inline-block;
padding: 5px;
}

+ 2
- 0
src/timeline/component/css/item.css View File

@ -90,6 +90,8 @@
-webkit-transition: height .4s ease-in-out;
transition: height .4s ease-in-out;
color: red;
}
.vis.timeline .item .content {

+ 4
- 1
src/timeline/component/css/itemset.css View File

@ -4,6 +4,9 @@
padding: 0;
margin: 0;
-moz-box-sizing: border-box;
box-sizing: border-box;
/* FIXME: get transoition working for rootpanel and itemset
-webkit-transition: height 4s ease-in-out;
transition: height 4s ease-in-out;
@ -17,5 +20,5 @@
}
.vis.timeline .axis {
position: absolute;
overflow: visible;
}

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

@ -1,15 +1,15 @@
.vis.timeline .axis {
.vis.timeline .timeaxis {
position: absolute;
}
.vis.timeline .axis .text {
.vis.timeline .timeaxis .text {
position: absolute;
color: #4d4d4d;
padding: 3px;
white-space: nowrap;
}
.vis.timeline .axis .text.measure {
.vis.timeline .timeaxis .text.measure {
position: absolute;
padding-left: 0;
padding-right: 0;
@ -18,13 +18,13 @@
visibility: hidden;
}
.vis.timeline .axis .grid.vertical {
.vis.timeline .timeaxis .grid.vertical {
position: absolute;
width: 0;
border-right: 1px solid;
}
.vis.timeline .axis .grid.horizontal {
.vis.timeline .timeaxis .grid.horizontal {
position: absolute;
left: 0;
width: 100%;
@ -32,10 +32,10 @@
border-bottom: 1px solid;
}
.vis.timeline .axis .grid.minor {
.vis.timeline .timeaxis .grid.minor {
border-color: #e5e5e5;
}
.vis.timeline .axis .grid.major {
.vis.timeline .timeaxis .grid.major {
border-color: #bfbfbf;
}

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

@ -39,9 +39,8 @@ ItemBox.prototype = new Item (null, null);
*/
ItemBox.prototype.isVisible = function isVisible (range) {
// determine visibility
// TODO: account for the width of the item. Right now we add 1/4 to the window
// TODO: account for the real width of the item. Right now we just add 1/4 to the window
var interval = (range.end - range.start) / 4;
interval = 0; // TODO: remove
return (this.data.start > range.start - interval) && (this.data.start < range.end + interval);
};
@ -215,17 +214,17 @@ ItemBox.prototype.repositionY = function repositionY () {
box.style.top = (this.top || 0) + 'px';
box.style.bottom = '';
line.style.top = '0px';
line.style.top = '0';
line.style.bottom = '';
line.style.height = this.top + 'px';
line.style.height = (this.parent.top + this.top + 1) + 'px';
}
else { // orientation 'bottom'
box.style.top = '';
box.style.bottom = (this.top || 0) + 'px';
line.style.top = '';
line.style.bottom = '0px';
line.style.height = this.top + 'px';
line.style.top = (this.parent.top + this.parent.height - this.top - 1) + 'px';
line.style.bottom = '0';
line.style.height = '';
}
dot.style.top = (-this.props.dot.height / 2) + 'px';

+ 13
- 0
test/timeline.html View File

@ -35,6 +35,19 @@
});
};
</script>
<div>
<label for="currenttime"><input id="currenttime" type="checkbox" checked="true"> Show current time</label>
</div>
<script>
var currenttime = document.getElementById('currenttime');
currenttime.onchange = function () {
timeline.setOptions({
showCurrentTime: currenttime.checked
});
};
</script>
<br>
<div id="visualization"></div>

Loading…
Cancel
Save