Browse Source

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

revert-3409-performance
Yotam Berkowitz 7 years ago
parent
commit
9a27aa7a6e
11 changed files with 205 additions and 43 deletions
  1. +24
    -0
      docs/timeline/index.html
  2. +35
    -0
      examples/timeline/items/tooltip.html
  3. +2
    -0
      examples/timeline/other/usingReact.html
  4. +1
    -1
      lib/DataSet.js
  5. +1
    -1
      lib/DataView.js
  6. +50
    -20
      lib/shared/Popup.js
  7. +2
    -2
      lib/timeline/Stack.js
  8. +3
    -5
      lib/timeline/Timeline.js
  9. +77
    -13
      lib/timeline/component/ItemSet.js
  10. +9
    -0
      lib/timeline/optionsTimeline.js
  11. +1
    -1
      lib/util.js

+ 24
- 0
docs/timeline/index.html View File

@ -1096,6 +1096,30 @@ function (option, path) {
</td> </td>
</tr> </tr>
<tr class='toggle collapsible' onclick="toggleTable('optionTable','tooltip', this);">
<td><span parent="tooltip" class="right-caret"></span> tooltip</td>
<td>Object</td>
<td><code>Object</code></td>
<td>Specify how the tooltip is positioned.</td>
</tr>
<tr parent="tooltip" class="hidden">
<td class="indent">tooltip.followMouse</td>
<td>boolean</td>
<td><code>false</code></td>
<td>If true, tooltips will follow the mouse as they move around in the item.</td>
</tr>
<tr parent="tooltip" class="hidden">
<td class="indent">tooltip.overflowMethod</td>
<td>String</td>
<td><code>'flip'</code></td>
<td>
Set how the tooltip should act if it is about to overflow out of the timeline.<br />
Choose from <code>'cap'</code> and <code>'flip'</code>. <br />
If it is set to <code>'cap'</code>, the tooltip will just cap its position to inside to timeline. <br />
While if it is set to <code>'flip'</code>, the position of the tooltip will flip around the cursor so that a corner is at the cursor, and the rest of it is visible. <br />
</tr>
<tr class='toggle collapsible' onclick="toggleTable('optionTable','tooltipOnItemUpdateTime', this);"> <tr class='toggle collapsible' onclick="toggleTable('optionTable','tooltipOnItemUpdateTime', this);">
<td><span parent="tooltipOnItemUpdateTime" class="right-caret"></span> tooltipOnItemUpdateTime</td> <td><span parent="tooltipOnItemUpdateTime" class="right-caret"></span> tooltipOnItemUpdateTime</td>
<td>Object/Boolean</td> <td>Object/Boolean</td>

+ 35
- 0
examples/timeline/items/tooltip.html View File

@ -24,6 +24,20 @@
<div id="tooltips"></div> <div id="tooltips"></div>
<p>
The example below has the tooltip follow the mouse.
</p>
<div id="tooltips-follow"></div>
<p>
The example below has the tooltip overflow set to 'cap'. Compare this to the one above,
to see how they differ. For the best results, move the cursor to the top right,
where the tool-tip is going to overflow out of the timeline.
</p>
<div id="tooltips-cap"></div>
<script type="text/javascript"> <script type="text/javascript">
// Create a DataSet (allows two way data-binding) // Create a DataSet (allows two way data-binding)
var items = new vis.DataSet([ var items = new vis.DataSet([
@ -43,6 +57,27 @@
var timelineTooltips = new vis.Timeline(document.getElementById('tooltips'), var timelineTooltips = new vis.Timeline(document.getElementById('tooltips'),
items, options items, options
); );
// Follow options
var follow_options = {
tooltip: {
followMouse: true
}
};
var timelineFollow = new vis.Timeline(document.getElementById('tooltips-follow'),
items, follow_options);
// Cap options
var cap_options = {
tooltip: {
followMouse: true,
overflowMethod: 'cap'
}
}
var timelineCap = new vis.Timeline(document.getElementById('tooltips-cap'),
items, cap_options);
</script> </script>
</body> </body>

+ 2
- 0
examples/timeline/other/usingReact.html View File

@ -88,10 +88,12 @@
end: new Date(1000*60*60*24 + (new Date()).valueOf()), end: new Date(1000*60*60*24 + (new Date()).valueOf()),
editable: true, editable: true,
template: function (item, element) { template: function (item, element) {
if (!item) { return }
ReactDOM.unmountComponentAtNode(element); ReactDOM.unmountComponentAtNode(element);
return ReactDOM.render(<ItemTemplate item={item} />, element); return ReactDOM.render(<ItemTemplate item={item} />, element);
}, },
groupTemplate: function (group, element) { groupTemplate: function (group, element) {
if (!item) { return }
ReactDOM.unmountComponentAtNode(element); ReactDOM.unmountComponentAtNode(element);
return ReactDOM.render(<GroupTemplate group={group} />, element); return ReactDOM.render(<GroupTemplate group={group} />, element);
} }

+ 1
- 1
lib/DataSet.js View File

@ -679,7 +679,7 @@ DataSet.prototype.remove = function (id, senderId) {
item = this._remove(ids[i]); item = this._remove(ids[i]);
if (item) { if (item) {
itemId = item[this._fieldId]; itemId = item[this._fieldId];
if (itemId) {
if (itemId != undefined) {
removedIds.push(itemId); removedIds.push(itemId);
removedItems.push(item); removedItems.push(item);
} }

+ 1
- 1
lib/DataView.js View File

@ -108,7 +108,7 @@ DataView.prototype.refresh = function () {
id = oldIds[i]; id = oldIds[i];
if (!newIds[id]) { if (!newIds[id]) {
removedIds.push(id); removedIds.push(id);
removedItems.push(this._data[id]);
removedItems.push(this._data._data[id]);
delete this._ids[id]; delete this._ids[id];
} }
} }

+ 50
- 20
lib/shared/Popup.js View File

@ -1,15 +1,12 @@
/** /**
* Popup is a class to create a popup window with some text * Popup is a class to create a popup window with some text
* @param {Element} container The container object.
* @param {Number} [x]
* @param {Number} [y]
* @param {String} [text]
* @param {Object} [style] An object containing borderColor,
* backgroundColor, etc.
* @param {Element} container The container object.
* @param {string} overflowMethod How the popup should act to overflowing ('flip' or 'cap')
*/ */
class Popup { class Popup {
constructor(container) {
constructor(container, overflowMethod) {
this.container = container; this.container = container;
this.overflowMethod = overflowMethod || 'cap';
this.x = 0; this.x = 0;
this.y = 0; this.y = 0;
@ -60,20 +57,46 @@ class Popup {
var maxHeight = this.frame.parentNode.clientHeight; var maxHeight = this.frame.parentNode.clientHeight;
var maxWidth = this.frame.parentNode.clientWidth; var maxWidth = this.frame.parentNode.clientWidth;
var top = (this.y - height);
if (top + height + this.padding > maxHeight) {
top = maxHeight - height - this.padding;
}
if (top < this.padding) {
top = this.padding;
}
var left = 0, top = 0;
var left = this.x;
if (left + width + this.padding > maxWidth) {
left = maxWidth - width - this.padding;
}
if (left < this.padding) {
left = this.padding;
if (this.overflowMethod == 'flip') {
var isLeft = false, isTop = true; // Where around the position it's located
if (this.y - height < this.padding) {
isTop = false;
}
if (this.x + width > maxWidth - this.padding) {
isLeft = true;
}
if (isLeft) {
left = this.x - width;
} else {
left = this.x;
}
if (isTop) {
top = this.y - height;
} else {
top = this.y;
}
} else {
top = (this.y - height);
if (top + height + this.padding > maxHeight) {
top = maxHeight - height - this.padding;
}
if (top < this.padding) {
top = this.padding;
}
left = this.x;
if (left + width + this.padding > maxWidth) {
left = maxWidth - width - this.padding;
}
if (left < this.padding) {
left = this.padding;
}
} }
this.frame.style.left = left + "px"; this.frame.style.left = left + "px";
@ -93,6 +116,13 @@ class Popup {
this.hidden = true; this.hidden = true;
this.frame.style.visibility = "hidden"; this.frame.style.visibility = "hidden";
} }
/**
* Remove the popup window
*/
destroy() {
this.frame.parentNode.removeChild(this.frame); // Remove element from DOM
}
} }
export default Popup; export default Popup;

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

@ -177,7 +177,7 @@ exports.collision = function(a, b, margin, rtl) {
*/ */
exports.collisionByTimes = function(a, b) { exports.collisionByTimes = function(a, b) {
return ( return (
(a.start < b.start && a.end > b.start && a.top < (b.top + b.height) && (a.top + a.height) > b.top ) ||
(b.start < a.start && b.end > a.start && b.top < (a.top + a.height) && (b.top + b.height) > a.top )
(a.start <= b.start && a.end >= b.start && a.top < (b.top + b.height) && (a.top + a.height) > b.top ) ||
(b.start <= a.start && b.end >= a.start && b.top < (a.top + a.height) && (b.top + b.height) > a.top )
) )
} }

+ 3
- 5
lib/timeline/Timeline.js View File

@ -271,13 +271,11 @@ Timeline.prototype.setGroups = function(groups) {
newDataSet = null; newDataSet = null;
} }
else { else {
var filter = {
filter : function(group){
return group.visible !== false;
}
var filter = function(group) {
return group.visible !== false;
} }
if (groups instanceof DataSet || groups instanceof DataView) { if (groups instanceof DataSet || groups instanceof DataView) {
newDataSet = new DataView(groups,filter);
newDataSet = new DataView(groups,{filter: filter});
} }
else { else {
// turn an array into a dataset // turn an array into a dataset

+ 77
- 13
lib/timeline/component/ItemSet.js View File

@ -95,6 +95,11 @@ function ItemSet(body, options) {
axis: 20 axis: 20
}, },
tooltip: {
followMouse: false,
overflowMethod: 'flip'
},
tooltipOnItemUpdateTime: false tooltipOnItemUpdateTime: false
}; };
@ -250,6 +255,7 @@ ItemSet.prototype._create = function(){
this.body.dom.centerContainer.addEventListener('mouseover', this._onMouseOver.bind(this)); this.body.dom.centerContainer.addEventListener('mouseover', this._onMouseOver.bind(this));
this.body.dom.centerContainer.addEventListener('mouseout', this._onMouseOut.bind(this)); this.body.dom.centerContainer.addEventListener('mouseout', this._onMouseOut.bind(this));
this.body.dom.centerContainer.addEventListener('mousemove', this._onMouseMove.bind(this));
// attach to the DOM // attach to the DOM
this.show(); this.show();
@ -325,7 +331,7 @@ ItemSet.prototype.setOptions = function(options) {
var fields = [ var fields = [
'type', 'rtl', 'align', 'order', 'stack', 'stackSubgroups', 'selectable', 'multiselect', 'itemsAlwaysDraggable', 'type', 'rtl', 'align', 'order', 'stack', 'stackSubgroups', 'selectable', 'multiselect', 'itemsAlwaysDraggable',
'multiselectPerGroup', 'groupOrder', 'dataAttributes', 'template', 'groupTemplate', 'visibleFrameTemplate', 'multiselectPerGroup', 'groupOrder', 'dataAttributes', 'template', 'groupTemplate', 'visibleFrameTemplate',
'hide', 'snap', 'groupOrderSwap', 'tooltipOnItemUpdateTime'
'hide', 'snap', 'groupOrderSwap', 'tooltip', 'tooltipOnItemUpdateTime'
]; ];
util.selectiveExtend(fields, this.options, options); util.selectiveExtend(fields, this.options, options);
@ -869,7 +875,8 @@ ItemSet.prototype.getGroups = function() {
*/ */
ItemSet.prototype.removeItem = function(id) { ItemSet.prototype.removeItem = function(id) {
var item = this.itemsData.get(id), var item = this.itemsData.get(id),
dataset = this.itemsData.getDataSet();
dataset = this.itemsData.getDataSet(),
itemObj = this.items[id];
if (item) { if (item) {
// confirm deletion // confirm deletion
@ -878,6 +885,12 @@ ItemSet.prototype.removeItem = function(id) {
// remove by id here, it is possible that an item has no id defined // remove by id here, it is possible that an item has no id defined
// itself, so better not delete by the item itself // itself, so better not delete by the item itself
dataset.remove(id); dataset.remove(id);
// Remove it's popup
if (itemObj.popup) {
itemObj.popup.destroy();
itemObj.popup = null;
}
} }
}); });
} }
@ -1875,15 +1888,22 @@ ItemSet.prototype._onMouseOver = function (event) {
var item = this.itemFromTarget(event); var item = this.itemFromTarget(event);
if (!item) return; if (!item) return;
// Item we just left
var related = this.itemFromRelatedTarget(event);
if (item === related) {
// We haven't changed item, just element in the item
return;
}
if (item.getTitle()) { if (item.getTitle()) {
if (item.popup == null) { if (item.popup == null) {
item.setPopup(new Popup(this.body.dom.root));
item.setPopup(new Popup(this.body.dom.root, this.options.tooltip.overflowMethod || 'flip'));
} }
var container = this.body.dom.centerContainer; var container = this.body.dom.centerContainer;
item.popup.setPosition( item.popup.setPosition(
event.clientX - util.getAbsoluteLeft(container),
event.clientY - util.getAbsoluteTop(container)
event.clientX - util.getAbsoluteLeft(container) + container.offsetLeft,
event.clientY - util.getAbsoluteTop(container) + container.offsetTop
); );
item.popup.show(); item.popup.show();
} }
@ -1897,6 +1917,13 @@ ItemSet.prototype._onMouseOut = function (event) {
var item = this.itemFromTarget(event); var item = this.itemFromTarget(event);
if (!item) return; if (!item) return;
// Item we are going to
var related = this.itemFromRelatedTarget(event);
if (item === related) {
// We aren't changing item, just element in the item
return;
}
if (item.popup != null) { if (item.popup != null) {
item.popup.hide(); item.popup.hide();
} }
@ -1906,6 +1933,23 @@ ItemSet.prototype._onMouseOut = function (event) {
event: util.elementsCensor(event) event: util.elementsCensor(event)
}); });
}; };
ItemSet.prototype._onMouseMove = function (event) {
var item = this.itemFromTarget(event);
if (!item) return;
if (this.options.tooltip.followMouse) {
if (item.popup) {
if (!item.popup.hidden) {
var container = this.body.dom.centerContainer;
item.popup.setPosition(
event.clientX - util.getAbsoluteLeft(container) + container.offsetLeft,
event.clientY - util.getAbsoluteTop(container) + container.offsetTop
);
item.popup.show(); // Redraw
}
}
}
};
/** /**
@ -2118,6 +2162,24 @@ ItemSet._getItemRange = function(itemsData) {
} }
}; };
/**
* Find an item from an element:
* searches for the attribute 'timeline-item' in the element's tree
* @param {HTMLElement} element
* @return {Item | null} item
*/
ItemSet.prototype.itemFromElement = function(element) {
var cur = element;
while (cur) {
if (cur.hasOwnProperty('timeline-item')) {
return cur['timeline-item'];
}
cur = cur.parentNode;
}
return null;
};
/** /**
* Find an item from an event target: * Find an item from an event target:
* searches for the attribute 'timeline-item' in the event target's element tree * searches for the attribute 'timeline-item' in the event target's element tree
@ -2125,15 +2187,17 @@ ItemSet._getItemRange = function(itemsData) {
* @return {Item | null} item * @return {Item | null} item
*/ */
ItemSet.prototype.itemFromTarget = function(event) { ItemSet.prototype.itemFromTarget = function(event) {
var target = event.target;
while (target) {
if (target.hasOwnProperty('timeline-item')) {
return target['timeline-item'];
}
target = target.parentNode;
}
return this.itemFromElement(event.target);
};
return null;
/**
* Find an item from an event's related target:
* searches for the attribute 'timeline-item' in the related target's element tree
* @param {Event} event
* @return {Item | null} item
*/
ItemSet.prototype.itemFromRelatedTarget = function(event) {
return this.itemFromElement(event.relatedTarget);
}; };
/** /**

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

@ -131,6 +131,11 @@ let allOptions = {
template: {'function': 'function'}, template: {'function': 'function'},
groupTemplate: {'function': 'function'}, groupTemplate: {'function': 'function'},
visibleFrameTemplate: {string, 'function': 'function'}, visibleFrameTemplate: {string, 'function': 'function'},
tooltip: {
followMouse: { 'boolean': bool },
overflowMethod: { 'string': ['cap', 'flip'] },
__type__: {object}
},
tooltipOnItemUpdateTime: { tooltipOnItemUpdateTime: {
template: {'function': 'function'}, template: {'function': 'function'},
__type__: { 'boolean': bool, object} __type__: { 'boolean': bool, object}
@ -230,6 +235,10 @@ let configureOptions = {
// scale: ['millisecond', 'second', 'minute', 'hour', 'weekday', 'day', 'month', 'year'], // scale: ['millisecond', 'second', 'minute', 'hour', 'weekday', 'day', 'month', 'year'],
// step: [1, 1, 10, 1] // step: [1, 1, 10, 1]
//}, //},
tooltip: {
followMouse: false,
overflowMethod: 'flip'
},
tooltipOnItemUpdateTime: false, tooltipOnItemUpdateTime: false,
type: ['box', 'point', 'range', 'background'], type: ['box', 'point', 'range', 'background'],
width: '100%', width: '100%',

+ 1
- 1
lib/util.js View File

@ -409,7 +409,7 @@ exports.convert = function (object, type) {
case 'number': case 'number':
case 'Number': case 'Number':
if (!isNaN(Date.parse(object))) {
if (exports.isString(object) && !isNaN(Date.parse(object))) {
return moment(object).valueOf(); return moment(object).valueOf();
} else { } else {
return Number(object.valueOf()); return Number(object.valueOf());

Loading…
Cancel
Save