Browse Source

Merge branch 'develop' of https://github.com/almende/vis into develop

revert-3409-performance
Yotam Berkowitz 8 years ago
parent
commit
ffa8e0ee1d
14 changed files with 298 additions and 25 deletions
  1. +1
    -1
      docs/graph2d/index.html
  2. +19
    -4
      docs/timeline/index.html
  3. +45
    -0
      examples/timeline/interaction/rollingMode.html
  4. +67
    -0
      examples/timeline/items/visibleFrameTemplateContent.html
  5. +7
    -1
      lib/timeline/Core.js
  6. +2
    -2
      lib/timeline/Graph2d.js
  7. +78
    -8
      lib/timeline/Range.js
  8. +2
    -4
      lib/timeline/Timeline.js
  9. +4
    -3
      lib/timeline/component/ItemSet.js
  10. +23
    -0
      lib/timeline/component/css/currenttime.css
  11. +4
    -0
      lib/timeline/component/css/item.css
  12. +38
    -1
      lib/timeline/component/item/Item.js
  13. +6
    -1
      lib/timeline/component/item/RangeItem.js
  14. +2
    -0
      lib/timeline/optionsTimeline.js

+ 1
- 1
docs/graph2d/index.html View File

@ -1162,7 +1162,7 @@ function (option, path) {
<li><code>x</code> (Number): relative horizontal position of the click event.</li>
<li><code>y</code> (Number): relative vertical position of the click event.</li>
<li><code>time</code> (Date): Date of the clicked event.</li>
<li><code>value</code> (Number[]): The data value of the click event. The array contains one value when there is one data axis visible, and two values when there are two visible data axes.</li>
<li><code>value</code> (Number[]): The data value of the click event. The array contains one value when there is one data axis visible, and two values when there are two visible data axes. It is empty when no data is provided.</li>
<li><code>what</code> (String or null): name of the clicked thing: <code>background</code>, <code>axis</code>, <code>dat-axis</code>, <code>custom-time</code>, or <code>current-time</code>, <code>legend</code>.</li>
<li><code>event</code> (Object): the original click event.</li>
</ul>

+ 19
- 4
docs/timeline/index.html View File

@ -679,7 +679,7 @@ function (option, path) {
<td>groupTemplate</td>
<td>function</td>
<td>none</td>
<td>A template function used to generate the contents of the groups. The function is called by the Timeline with a groups data as the first argument and the group element as the second, and must return HTML code as result. When the option groupTemplate is specified, the groups do not need to have a field <code>content</code>. See section <a href="#Templates">Templates</a> for a detailed explanation.</td>
<td>A template function used to generate the contents of the groups. The function is called by the Timeline with a groups data as the first argument and the group element as the second, and must return HTML code, a string or a template as result. When the option groupTemplate is specified, the groups do not need to have a field <code>content</code>. See section <a href="#Templates">Templates</a> for a detailed explanation.</td>
</tr>
<tr>
@ -943,7 +943,14 @@ function (option, path) {
<td>Orientation of the timeline items: 'top' or 'bottom' (default). Determines whether items are aligned to the top or bottom of the Timeline.</td>
</tr>
<tr>
<tr>
<td>rollingMode</td>
<td>boolean</td>
<td><code>false</code></td>
<td>If true, the timeline will initial in a rolling mode - the current time will always be centered. I the user drags the timeline, the timeline will go out of rolling mode and a toggle button will appear. Clicking that button will go back to rolling mode. Zooming in rolling mode will zoom in to the center without consideration of the mouse position.</td>
</tr>
<tr>
<td>rtl</td>
<td>boolean</td>
<td><code>false</code></td>
@ -1018,7 +1025,15 @@ function (option, path) {
<td>template</td>
<td>function</td>
<td>none</td>
<td>A template function used to generate the contents of the items. The function is called by the Timeline with an items data as the first argument and the item element as the second, and must return HTML code as result. When the option template is specified, the items do not need to have a field <code>content</code>. See section <a href="#Templates">Templates</a> for a detailed explanation.</td>
<td>A template function used to generate the contents of the items. The function is called by the Timeline with an items' data as the first argument and the item element as the second, and must return HTML code, a string or a template as result. When the option template is specified, the items do not need to have a field <code>content</code>. See section <a href="#Templates">Templates</a> for a detailed explanation.</td>
</tr>
<tr>
<td>visibleFrameTemplate</td>
<td>function</td>
<td>none</td>
<td>A template function used to generate the visible frame of the items. The function is called by the Timeline with an items' data as the first argument and the item frame element as the second, and must return HTML code, a string or a template as result. When the option template is specified, the items do not need to have a field <code>content</code>. See section <a href="#Templates">Templates</a> for a detailed explanation.
This would be used as an additional way to add content that is constant in size with the visible frame of the item and does not get visibly hidden with the item's internal container: <code>vis-item-overflow</code> which is <code>overflow:hidden</code>.</td>
</tr>
<tr class='toggle collapsible' onclick="toggleTable('optionTable','timeAxis', this);">
@ -1065,7 +1080,7 @@ function (option, path) {
<td class="indent">template</td>
<td>Function</td>
<td>none</td>
<td>A template function used to generate the contents of the tooltip. The function is called by the Timeline with an item data as the first argument, and must return HTML code as result. See section <a href="#Templates">Templates</a> for a detailed explanation.
<td>A template function used to generate the contents of the tooltip. The function is called by the Timeline with an item data as the first argument, and must return HTML code, a string or a template as result. See section <a href="#Templates">Templates</a> for a detailed explanation.
</td>
</tr>
<tr>

+ 45
- 0
examples/timeline/interaction/rollingMode.html View File

@ -0,0 +1,45 @@
<html>
<head>
<title>Timeline | rolling mode Option</title>
<meta charset="utf-8">
<script src="../../../dist/vis.js"></script>
<link href="../../../dist/vis.css" rel="stylesheet" type="text/css" />
<script src="../../googleAnalytics.js"></script>
</head>
<body>
<h1><i id="icon">&#9974;</i>Timeline rolling mode option</h1>
<div id="mytimeline"></div>
<script>
var container = document.getElementById('mytimeline');
var items = new vis.DataSet();
for (var i = 10; i >= 0; i--) {
items.add({
id: i,
content: "item " + i,
start: new Date(new Date().getTime() + i*100000)
});
}
// Configuration for the Timeline
// specify options
var options = {
start: new Date(),
end: new Date(new Date().getTime() + 1000000),
rollingMode: true
};
// create a Timeline
timeline = new vis.Timeline(container, items, null, options);
</script>
</body>
</html>

+ 67
- 0
examples/timeline/items/visibleFrameTemplateContent.html View File

@ -0,0 +1,67 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Timeline | Dynamic Content</title>
<script src="../../../dist/vis.js"></script>
<link href="../../../dist/vis.min.css" rel="stylesheet" type="text/css" />
<style type="text/css">
.progress-wrapper {
background: white;
width: 100%;
height: 18px;
text-align: center;
position: relative;
overflow: hidden;
}
.progress {
height: 100%;
width: 60%;
position: absolute;
left: 0px;
top: 0px;
background: #63ed63;
}
.progress-label {
position: absolute;
z-index: 1;
}
</style>
</head>
<body>
<div id="myTimeline"></div>
<script type="text/javascript">
// DOM element where the Timeline will be attached
var container = document.getElementById('myTimeline');
// Create a DataSet (allows two way data-binding)
var items = new vis.DataSet([
{id: 1, value: 0.2, content: 'item 1', start: '2014-04-20', end: '2014-04-26'},
{id: 2, value: 0.6, content: 'item 2', start: '2014-05-14', end: '2014-05-18'},
{id: 3, type: 'point', content: 'item 3', start: '2014-04-15', end: '2014-05-18'},
{id: 4, content: 'item 4 with visibleFramTemplate in item', start: '2014-04-16', end: '2014-04-26', visibleFramTemplate: '<div class="progress-wrapper"><div class="progress"></div><label class="progress-label">80%<label></div>'
}
]);
// Configuration for the Timeline
var options = {
visibleFrameTemplate: function(item) {
if (item.visibleFramTemplate) {
return item.visibleFramTemplate;
}
var percentage = item.value * 100 + '%';
return '<div class="progress-wrapper"><div class="progress"></div><label class="progress-label">' + percentage + '<label></div>';
}
};
// Create a Timeline
var timeline = new vis.Timeline(container, items, options);
</script>
</body>
</html>

+ 7
- 1
lib/timeline/Core.js View File

@ -50,6 +50,7 @@ Core.prototype._create = function (container) {
this.dom.shadowBottomLeft = document.createElement('div');
this.dom.shadowTopRight = document.createElement('div');
this.dom.shadowBottomRight = document.createElement('div');
this.dom.rollingModeBtn = document.createElement('div');
this.dom.root.className = 'vis-timeline';
this.dom.background.className = 'vis-panel vis-background';
@ -69,6 +70,7 @@ Core.prototype._create = function (container) {
this.dom.shadowBottomLeft.className = 'vis-shadow vis-bottom';
this.dom.shadowTopRight.className = 'vis-shadow vis-top';
this.dom.shadowBottomRight.className = 'vis-shadow vis-bottom';
this.dom.rollingModeBtn.className = 'vis-rolling-mode-btn';
this.dom.root.appendChild(this.dom.background);
this.dom.root.appendChild(this.dom.backgroundVertical);
@ -78,6 +80,8 @@ Core.prototype._create = function (container) {
this.dom.root.appendChild(this.dom.rightContainer);
this.dom.root.appendChild(this.dom.top);
this.dom.root.appendChild(this.dom.bottom);
this.dom.root.appendChild(this.dom.bottom);
this.dom.root.appendChild(this.dom.rollingModeBtn);
this.dom.centerContainer.appendChild(this.dom.center);
this.dom.leftContainer.appendChild(this.dom.left);
@ -120,7 +124,7 @@ Core.prototype._create = function (container) {
this._redraw = util.throttle(this._origRedraw);
this.on('_change', function (properties) {
if (me.itemSet.initialItemSetDrawn && properties && properties.queue == true) {
if (me.itemSet && me.itemSet.initialItemSetDrawn && properties && properties.queue == true) {
me._redraw()
} else {
me._origRedraw();
@ -311,6 +315,8 @@ Core.prototype.setOptions = function (options) {
];
util.selectiveExtend(fields, this.options, options);
this.dom.rollingModeBtn.style.visibility = 'hidden';
if (this.options.rtl) {
this.dom.container.style.direction = "rtl";
this.dom.backgroundVertical.className = 'vis-panel vis-background vis-vertical-rtl';

+ 2
- 2
lib/timeline/Graph2d.js View File

@ -299,10 +299,10 @@ Graph2d.prototype.getEventProperties = function (event) {
var value = [];
var yAxisLeft = this.linegraph.yAxisLeft;
var yAxisRight = this.linegraph.yAxisRight;
if (!yAxisLeft.hidden) {
if (!yAxisLeft.hidden && this.itemsData.length > 0) {
value.push(yAxisLeft.screenToValue(y));
}
if (!yAxisRight.hidden) {
if (!yAxisRight.hidden && this.itemsData.length > 0) {
value.push(yAxisRight.screenToValue(y));
}

+ 78
- 8
lib/timeline/Range.js View File

@ -14,8 +14,18 @@ var DateUtil = require('./DateUtil');
*/
function Range(body, options) {
var now = moment().hours(0).minutes(0).seconds(0).milliseconds(0);
this.start = now.clone().add(-3, 'days').valueOf(); // Number
this.end = now.clone().add(4, 'days').valueOf(); // Number
var start = now.clone().add(-3, 'days').valueOf();
var end = now.clone().add(-3, 'days').valueOf();
if(options === undefined) {
this.start = start;
this.end = end;
} else {
this.start = options.start || start
this.end = options.end || end
}
this.rolling = false;
this.body = body;
this.deltaDifference = 0;
@ -55,6 +65,9 @@ function Range(body, options) {
this.body.emitter.on('touch', this._onTouch.bind(this));
this.body.emitter.on('pinch', this._onPinch.bind(this));
// on click of rolling mode button
this.body.dom.rollingModeBtn.addEventListener('click', this.startRolling.bind(this));
this.setOptions(options);
}
@ -80,11 +93,14 @@ Range.prototype.setOptions = function (options) {
if (options) {
// copy the options that we know
var fields = [
'direction', 'min', 'max', 'zoomMin', 'zoomMax', 'moveable', 'zoomable',
'moment', 'activate', 'hiddenDates', 'zoomKey', 'rtl', 'horizontalScroll'
'animation', 'direction', 'min', 'max', 'zoomMin', 'zoomMax', 'moveable', 'zoomable',
'moment', 'activate', 'hiddenDates', 'zoomKey', 'rtl', 'showCurrentTime', 'rollMode', 'horizontalScroll'
];
util.selectiveExtend(fields, this.options, options);
if (options.rollingMode) {
this.startRolling();
}
if ('start' in options || 'end' in options) {
// apply a new range. both start and end are optional
this.setRange(options.start, options.end);
@ -103,6 +119,52 @@ function validateDirection (direction) {
}
}
/**
* Start auto refreshing the current time bar
*/
Range.prototype.startRolling = function() {
var me = this;
function update () {
me.stopRolling();
me.rolling = true;
var interval = me.end - me.start;
var t = util.convert(new Date(), 'Date').valueOf();
var start = t - interval / 2;
var end = t + interval / 2;
var animation = (me.options && me.options.animation !== undefined) ? me.options.animation : true;
me.setRange(start, end, false);
// determine interval to refresh
var scale = me.conversion(me.body.domProps.center.width).scale;
var interval = 1 / scale / 10;
if (interval < 30) interval = 30;
if (interval > 1000) interval = 1000;
me.body.dom.rollingModeBtn.style.visibility = "hidden";
// start a renderTimer to adjust for the new time
me.currentTimeTimer = setTimeout(update, interval);
}
update();
};
/**
* Stop auto refreshing the current time bar
*/
Range.prototype.stopRolling = function() {
if (this.currentTimeTimer !== undefined) {
clearTimeout(this.currentTimeTimer);
this.rolling = false;
this.body.dom.rollingModeBtn.style.visibility = "visible";
}
};
/**
* Set a new start and end range
* @param {Date | Number | String} [start]
@ -388,6 +450,8 @@ Range.prototype._onDragStart = function(event) {
// when releasing the fingers in opposite order from the touch screen
if (!this.props.touch.allowDragging) return;
this.stopRolling();
this.props.touch.start = this.start;
this.props.touch.end = this.end;
this.props.touch.dragging = true;
@ -520,7 +584,7 @@ Range.prototype._onMouseWheel = function(event) {
// Prevent default actions caused by mouse wheel
// (else the page and timeline both scroll)
event.preventDefault();
// calculate a single scroll jump relative to the range scale
var diff = delta * (this.end - this.start) / 20;
// calculate new start and end
@ -555,9 +619,13 @@ Range.prototype._onMouseWheel = function(event) {
}
// calculate center, the date to zoom around
var pointer = this.getPointer({x: event.clientX, y: event.clientY}, this.body.dom.center);
var pointerDate = this._pointerToDate(pointer);
var pointerDate
if (this.rolling) {
pointerDate = (this.start + this.end) / 2;
} else {
var pointer = this.getPointer({x: event.clientX, y: event.clientY}, this.body.dom.center);
pointerDate = this._pointerToDate(pointer);
}
this.zoom(scale, pointerDate, delta, event);
// Prevent default actions caused by mouse wheel
@ -594,6 +662,8 @@ Range.prototype._onPinch = function (event) {
this.props.touch.center = this.getPointer(event.center, this.body.dom.center);
}
this.stopRolling();
var scale = 1 / (event.scale + this.scaleOffset);
var centerDate = this._pointerToDate(this.props.touch.center);

+ 2
- 4
lib/timeline/Timeline.js View File

@ -58,10 +58,8 @@ function Timeline (container, items, groups, options) {
};
this.options = util.deepExtend({}, this.defaultOptions);
// Create the DOM, props, and emitter
this._create(container);
if (!options || (options && typeof options.rtl == "undefined")) {
var directionFromDom, domNode = this.dom.root;
while (!directionFromDom && domNode) {
@ -72,6 +70,7 @@ function Timeline (container, items, groups, options) {
} else {
this.options.rtl = options.rtl;
}
this.options.rollingMode = options.rollingMode;
// all components listed here will be repainted automatically
this.components = [];
@ -134,7 +133,7 @@ function Timeline (container, items, groups, options) {
//Single time autoscale/fit
this.fitDone = false;
this.on('changed', function (){
if (this.itemsData == null) return;
if (this.itemsData == null || this.options.rollingMode) return;
if (!me.fitDone) {
me.fitDone = true;
if (me.options.start != undefined || me.options.end != undefined) {
@ -144,7 +143,6 @@ function Timeline (container, items, groups, options) {
var start = me.options.start != undefined ? me.options.start : range.min;
var end = me.options.end != undefined ? me.options.end : range.max;
me.setWindow(start, end, {animation: false});
}
else {

+ 4
- 3
lib/timeline/component/ItemSet.js View File

@ -321,8 +321,8 @@ ItemSet.prototype.setOptions = function(options) {
// copy all options that we know
var fields = [
'type', 'rtl', 'align', 'order', 'stack', 'selectable', 'multiselect', 'itemsAlwaysDraggable',
'multiselectPerGroup', 'groupOrder', 'dataAttributes', 'template', 'groupTemplate', 'hide', 'snap',
'groupOrderSwap', 'tooltipOnItemUpdateTime'
'multiselectPerGroup', 'groupOrder', 'dataAttributes', 'template', 'groupTemplate', 'visibleFrameTemplate',
'hide', 'snap', 'groupOrderSwap', 'tooltipOnItemUpdateTime'
];
util.selectiveExtend(fields, this.options, options);
@ -1213,6 +1213,7 @@ ItemSet.prototype._getGroupIndex = function(groupId) {
* @private
*/
ItemSet.prototype._onDragStart = function (event) {
if (this.touchParams.itemIsDragging) { return; }
var item = this.touchParams.item || null;
var me = this;
var props;
@ -1850,7 +1851,7 @@ ItemSet.prototype._onAddItem = function (event) {
};
if (event.type == 'drop') {
var itemData = JSON.parse(event.dataTransfer.getData("text/plain"))
var itemData = JSON.parse(event.dataTransfer.getData("text"))
newItemData.content = itemData.content; // content is required
newItemData.type = itemData.type || 'box';
newItemData[this.itemsData._fieldId] = itemData.id || util.randomUUID();

+ 23
- 0
lib/timeline/component/css/currenttime.css View File

@ -3,4 +3,27 @@
width: 2px;
z-index: 1;
pointer-events: none;
}
.vis-rolling-mode-btn {
height: 40px;
width: 40px;
position: absolute;
top: 7px;
right: 20px;
border-radius: 50%;
font-size: 28px;
cursor: pointer;
opacity: 0.8;
color: white;
font-weight: bold;
text-align: center;
background: #3876c2;
}
.vis-rolling-mode-btn:before {
content: "\26F6";
}
.vis-rolling-mode-btn:hover {
opacity: 1;
}

+ 4
- 0
lib/timeline/component/css/item.css View File

@ -66,6 +66,10 @@
overflow: hidden;
}
.vis-item-visible-frame {
white-space: nowrap;
}
.vis-item.vis-range .vis-item-content {
position: relative;
display: inline-block;

+ 38
- 1
lib/timeline/component/item/Item.js View File

@ -325,9 +325,46 @@ Item.prototype._repaintOnItemUpdateTimeTooltip = function (anchor) {
Item.prototype._updateContents = function (element) {
var content;
var templateFunction;
var itemVisibleFrameContent;
var visibleFrameTemplateFunction;
var itemData = this.parent.itemSet.itemsData.get(this.id); // get a clone of the data from the dataset
var frameElement = this.dom.box || this.dom.point;
var itemVisibleFrameContentElement = frameElement.getElementsByClassName('vis-item-visible-frame')[0]
if (this.options.visibleFrameTemplate) {
visibleFrameTemplateFunction = this.options.visibleFrameTemplate.bind(this);
itemVisibleFrameContent = visibleFrameTemplateFunction(itemData, frameElement);
} else {
itemVisibleFrameContent = '';
}
if (itemVisibleFrameContentElement) {
if ((itemVisibleFrameContent instanceof Object) && !(itemVisibleFrameContent instanceof Element)) {
visibleFrameTemplateFunction(itemData, itemVisibleFrameContentElement)
} else {
var changed = this._contentToString(this.itemVisibleFrameContent) !== this._contentToString(itemVisibleFrameContent);
if (changed) {
// only replace the content when changed
if (itemVisibleFrameContent instanceof Element) {
itemVisibleFrameContentElement.innerHTML = '';
itemVisibleFrameContentElement.appendChild(itemVisibleFrameContent);
}
else if (itemVisibleFrameContent != undefined) {
itemVisibleFrameContentElement.innerHTML = itemVisibleFrameContent;
}
else {
if (!(this.data.type == 'background' && this.data.content === undefined)) {
throw new Error('Property "content" missing in item ' + this.id);
}
}
this.itemVisibleFrameContent = itemVisibleFrameContent;
}
}
}
if (this.options.template) {
var itemData = this.parent.itemSet.itemsData.get(this.id); // get a clone of the data from the dataset
templateFunction = this.options.template.bind(this);
content = templateFunction(itemData, element);
} else {

+ 6
- 1
lib/timeline/component/item/RangeItem.js View File

@ -60,10 +60,15 @@ RangeItem.prototype.redraw = function() {
dom.box = document.createElement('div');
// className is updated in redraw()
// frame box (to prevent the item contents from overflowing
// frame box (to prevent the item contents from overflowing)
dom.frame = document.createElement('div');
dom.frame.className = 'vis-item-overflow';
dom.box.appendChild(dom.frame);
// visible frame box (showing the frame that is always visible)
dom.visibleFrame = document.createElement('div');
dom.visibleFrame.className = 'vis-item-visible-frame';
dom.box.appendChild(dom.visibleFrame);
// contents box
dom.content = document.createElement('div');

+ 2
- 0
lib/timeline/optionsTimeline.js View File

@ -26,6 +26,7 @@ let allOptions = {
//globals :
align: {string},
rtl: {boolean, 'undefined': 'undefined'},
rollingMode: {boolean, 'undefined': 'undefined'},
verticalScroll: {boolean, 'undefined': 'undefined'},
horizontalScroll: {boolean, 'undefined': 'undefined'},
autoResize: {boolean},
@ -127,6 +128,7 @@ let allOptions = {
start: {date, number, string, moment},
template: {'function': 'function'},
groupTemplate: {'function': 'function'},
visibleFrameTemplate: {string, 'function': 'function'},
tooltipOnItemUpdateTime: {
template: {'function': 'function'},
__type__: {boolean, object}

Loading…
Cancel
Save