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>
</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);">
<td><span parent="tooltipOnItemUpdateTime" class="right-caret"></span> tooltipOnItemUpdateTime</td>
<td>Object/Boolean</td>

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

@ -24,6 +24,20 @@
<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">
// Create a DataSet (allows two way data-binding)
var items = new vis.DataSet([
@ -43,6 +57,27 @@
var timelineTooltips = new vis.Timeline(document.getElementById('tooltips'),
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>
</body>

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

@ -88,10 +88,12 @@
end: new Date(1000*60*60*24 + (new Date()).valueOf()),
editable: true,
template: function (item, element) {
if (!item) { return }
ReactDOM.unmountComponentAtNode(element);
return ReactDOM.render(<ItemTemplate item={item} />, element);
},
groupTemplate: function (group, element) {
if (!item) { return }
ReactDOM.unmountComponentAtNode(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]);
if (item) {
itemId = item[this._fieldId];
if (itemId) {
if (itemId != undefined) {
removedIds.push(itemId);
removedItems.push(item);
}

+ 1
- 1
lib/DataView.js View File

@ -108,7 +108,7 @@ DataView.prototype.refresh = function () {
id = oldIds[i];
if (!newIds[id]) {
removedIds.push(id);
removedItems.push(this._data[id]);
removedItems.push(this._data._data[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
* @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 {
constructor(container) {
constructor(container, overflowMethod) {
this.container = container;
this.overflowMethod = overflowMethod || 'cap';
this.x = 0;
this.y = 0;
@ -60,20 +57,46 @@ class Popup {
var maxHeight = this.frame.parentNode.clientHeight;
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";
@ -93,6 +116,13 @@ class Popup {
this.hidden = true;
this.frame.style.visibility = "hidden";
}
/**
* Remove the popup window
*/
destroy() {
this.frame.parentNode.removeChild(this.frame); // Remove element from DOM
}
}
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) {
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;
}
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) {
newDataSet = new DataView(groups,filter);
newDataSet = new DataView(groups,{filter: filter});
}
else {
// 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
},
tooltip: {
followMouse: false,
overflowMethod: 'flip'
},
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('mouseout', this._onMouseOut.bind(this));
this.body.dom.centerContainer.addEventListener('mousemove', this._onMouseMove.bind(this));
// attach to the DOM
this.show();
@ -325,7 +331,7 @@ ItemSet.prototype.setOptions = function(options) {
var fields = [
'type', 'rtl', 'align', 'order', 'stack', 'stackSubgroups', 'selectable', 'multiselect', 'itemsAlwaysDraggable',
'multiselectPerGroup', 'groupOrder', 'dataAttributes', 'template', 'groupTemplate', 'visibleFrameTemplate',
'hide', 'snap', 'groupOrderSwap', 'tooltipOnItemUpdateTime'
'hide', 'snap', 'groupOrderSwap', 'tooltip', 'tooltipOnItemUpdateTime'
];
util.selectiveExtend(fields, this.options, options);
@ -869,7 +875,8 @@ ItemSet.prototype.getGroups = function() {
*/
ItemSet.prototype.removeItem = function(id) {
var item = this.itemsData.get(id),
dataset = this.itemsData.getDataSet();
dataset = this.itemsData.getDataSet(),
itemObj = this.items[id];
if (item) {
// 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
// itself, so better not delete by the item itself
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);
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.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;
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();
}
@ -1897,6 +1917,13 @@ ItemSet.prototype._onMouseOut = function (event) {
var item = this.itemFromTarget(event);
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) {
item.popup.hide();
}
@ -1906,6 +1933,23 @@ ItemSet.prototype._onMouseOut = function (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:
* 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
*/
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'},
groupTemplate: {'function': 'function'},
visibleFrameTemplate: {string, 'function': 'function'},
tooltip: {
followMouse: { 'boolean': bool },
overflowMethod: { 'string': ['cap', 'flip'] },
__type__: {object}
},
tooltipOnItemUpdateTime: {
template: {'function': 'function'},
__type__: { 'boolean': bool, object}
@ -230,6 +235,10 @@ let configureOptions = {
// scale: ['millisecond', 'second', 'minute', 'hour', 'weekday', 'day', 'month', 'year'],
// step: [1, 1, 10, 1]
//},
tooltip: {
followMouse: false,
overflowMethod: 'flip'
},
tooltipOnItemUpdateTime: false,
type: ['box', 'point', 'range', 'background'],
width: '100%',

+ 1
- 1
lib/util.js View File

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

Loading…
Cancel
Save