Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(timeline): Implement new options 'horizontalScrollKey' (#1670, #1323) and 'horizontalScrollInvert' (#1595) #1852

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,15 @@ module.exports = {

parserOptions: {
sourceType: "module",
"ecmaVersion": 2020
},

extends: "eslint:recommended",

ignorePatterns: [
"types/*.*"
],

// For the full list of rules, see: http://eslint.org/docs/rules/
rules: {
complexity: [2, 55],
Expand Down
31 changes: 27 additions & 4 deletions docs/timeline/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -766,11 +766,34 @@ <h2 id="Configuration_Options">Configuration Options</h2>
</tr>

<tr>
<td>horizontalScroll</td>
<td><code>horizontalScroll</code></td>
<td>Boolean</td>
<td>false</td>
<td>This option allows you to scroll horizontally to move backwards and forwards in the time range.
Only applicable when option <code>zoomKey</code> is defined or <code>zoomable</code> is <code>false</code>.
<td><code>false</code></td>
<td>
This option allows you to scroll horizontally to move backwards and forwards in the time range.<br />
Only applicable when option <code>zoomKey</code> is defined or <code>zoomable</code> is <code>false</code>.
</td>
</tr>

<tr>
<td><code>horizontalScrollKey</code></td>
<td>String</td>
<td><code>''</code></td>
<td>
This option allows you to scroll horizontally while holding down a specific key.<br/>
Available values are <code>''</code> (does not apply), <code>'altKey'</code>, <code>'ctrlKey'</code>, <code>'shiftKey'</code> or <code>'metaKey'</code>.<br />
Only applicable when option <code>horizontalScroll</code> is defined.
</td>
</tr>

<tr>
<td><code>horizontalScrollInvert</code></td>
<td>Boolean</td>
<td><code>false</code></td>
<td>
This option allows you to invert the horizontal scroll direction.<br />
By default scroll-up will move the view to the right and scroll-down to the left.<br />
Only applicable when option <code>horizontalScroll</code> is defined.
</td>
</tr>

Expand Down
28 changes: 28 additions & 0 deletions examples/timeline/other/horizontalScroll.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,31 @@

<h1>Timeline horizontal scroll option</h1>

<div style="border: 1px solid #999; border-radius: 0.25rem; padding: .5rem 3rem .5rem 1rem; width: fit-content;">
<code style="white-space: pre-wrap;">// specified options
var options = {
stack: true,
horizontalScroll: true,
// horizontalScrollKey: 'shiftKey', /* If you want to enable both vertical and horizontal scrolling, uncomment this and the line below. */
// verticalScroll: true, /* Uncomment this line with the line above to allow for both scroll-axis. */
// horizontalScrollInvert: true, /* Invert the horizontal scroll direction by uncommenting this line. */
zoomKey: 'ctrlKey',
maxHeight: 400,
start: new Date(),
end: new Date(1000*60*60*24 + (new Date()).valueOf()),
editable: true,
margin: {
item: 10, // minimal margin between items
axis: 5 // minimal margin between items and the axis
},
orientation: 'top'
};

// create a Timeline
var container = document.getElementById('visualization');
timeline = new vis.Timeline(container, items, groups, options);</code>
</div>

<div id="visualization"></div>

<script>
Expand Down Expand Up @@ -55,6 +80,9 @@ <h1>Timeline horizontal scroll option</h1>
var options = {
stack: true,
horizontalScroll: true,
// horizontalScrollKey: 'shiftKey', // If you want to enable both vertical and horizontal scrolling, uncomment this and the line below.
// verticalScroll: true // Uncomment this line with the line above to allow for both scroll-axis.
// horizontalScrollInvert: true, // Invert the horizontal scroll direction by uncommenting this line.
zoomKey: 'ctrlKey',
maxHeight: 400,
start: new Date(),
Expand Down
8 changes: 4 additions & 4 deletions lib/DOMutil.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
export function prepareElements(JSONcontainer) {
// cleanup the redundant svgElements;
for (var elementType in JSONcontainer) {
if (JSONcontainer.hasOwnProperty(elementType)) {
if (Object.prototype.hasOwnProperty.call(JSONcontainer, elementType)) {
JSONcontainer[elementType].redundant = JSONcontainer[elementType].used;
JSONcontainer[elementType].used = [];
}
Expand All @@ -25,7 +25,7 @@ export function prepareElements(JSONcontainer) {
export function cleanupElements(JSONcontainer) {
// cleanup the redundant svgElements;
for (var elementType in JSONcontainer) {
if (JSONcontainer.hasOwnProperty(elementType)) {
if (Object.prototype.hasOwnProperty.call(JSONcontainer, elementType)) {
if (JSONcontainer[elementType].redundant) {
for (var i = 0; i < JSONcontainer[elementType].redundant.length; i++) {
JSONcontainer[elementType].redundant[i].parentNode.removeChild(JSONcontainer[elementType].redundant[i]);
Expand Down Expand Up @@ -59,7 +59,7 @@ export function resetElements(JSONcontainer) {
export function getSVGElement(elementType, JSONcontainer, svgContainer) {
var element;
// allocate SVG element, if it doesnt yet exist, create one.
if (JSONcontainer.hasOwnProperty(elementType)) { // this element has been created before
if (Object.prototype.hasOwnProperty.call(JSONcontainer, elementType)) { // this element has been created before
// check if there is an redundant element
if (JSONcontainer[elementType].redundant.length > 0) {
element = JSONcontainer[elementType].redundant[0];
Expand Down Expand Up @@ -95,7 +95,7 @@ export function getSVGElement(elementType, JSONcontainer, svgContainer) {
export function getDOMElement(elementType, JSONcontainer, DOMContainer, insertBefore) {
var element;
// allocate DOM element, if it doesnt yet exist, create one.
if (JSONcontainer.hasOwnProperty(elementType)) { // this element has been created before
if (Object.prototype.hasOwnProperty.call(JSONcontainer, elementType)) { // this element has been created before
// check if there is an redundant element
if (JSONcontainer[elementType].redundant.length > 0) {
element = JSONcontainer[elementType].redundant[0];
Expand Down
4 changes: 2 additions & 2 deletions lib/shared/Configurator.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ class Configurator {
let counter = 0;
let show = false;
for (let option in this.configureOptions) {
if (this.configureOptions.hasOwnProperty(option)) {
if (Object.prototype.hasOwnProperty.call(this.configureOptions, option)) {
this.allowCreation = false;
show = false;
if (typeof filter === 'function') {
Expand Down Expand Up @@ -579,7 +579,7 @@ class Configurator {
let filter = this.options.filter;
let visibleInSet = false;
for (let subObj in obj) {
if (obj.hasOwnProperty(subObj)) {
if (Object.prototype.hasOwnProperty.call(obj, subObj)) {
show = true;
let item = obj[subObj];
let newPath = util.copyAndExtendArray(path, subObj);
Expand Down
2 changes: 1 addition & 1 deletion lib/shared/Validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class Validator {
*/
static parse(options, referenceOptions, path) {
for (let option in options) {
if (options.hasOwnProperty(option)) {
if (Object.prototype.hasOwnProperty.call(options, option)) {
Validator.check(option, options, referenceOptions, path);
}
}
Expand Down
22 changes: 18 additions & 4 deletions lib/timeline/Core.js
Original file line number Diff line number Diff line change
Expand Up @@ -230,16 +230,20 @@ class Core {
deltaY *= PAGE_HEIGHT;
}
}

// Prevent scrolling when zooming (no zoom key, or pressing zoom key)
if (this.options.preferZoom) {
if (!this.options.zoomKey || event[this.options.zoomKey]) return;
} else {
if (this.options.zoomKey && event[this.options.zoomKey]) return
}

// Don't preventDefault if you can't scroll
if (!this.options.verticalScroll && !this.options.horizontalScroll) return;

if (this.options.verticalScroll && Math.abs(deltaY) >= Math.abs(deltaX)) {
// Vertical scroll preferred unless 'horizontalScrollKey' is configured and pressed.
if (!(this.options.horizontalScroll && this.options.horizontalScrollKey && event[this.options.horizontalScrollKey])
&& this.options.verticalScroll && Math.abs(deltaY) >= Math.abs(deltaX)) {
const current = this.props.scrollTop;
const adjusted = current + deltaY;

Expand All @@ -255,11 +259,20 @@ class Core {
event.preventDefault();
}
}

// If verticalScroll disabled or horizontalScrollKey is set and horizontalScrollKey is pressed
} else if (this.options.horizontalScroll) {
this.range.stopRolling();

const delta = Math.abs(deltaX) >= Math.abs(deltaY) ? deltaX : deltaY;

// calculate a single scroll jump relative to the range scale
const diff = (delta / 120) * (this.range.end - this.range.start) / 20;
let diff = (delta / 120) * (this.range.end - this.range.start) / 20;

// Invert scroll direction if configured.
if (this.options.horizontalScrollInvert)
diff = -diff;

// calculate new start and end
const newStart = this.range.start + diff;
const newEnd = this.range.end + diff;
Expand Down Expand Up @@ -418,7 +431,7 @@ class Core {
'width', 'height', 'minHeight', 'maxHeight', 'autoResize',
'start', 'end', 'clickToUse', 'dataAttributes', 'hiddenDates',
'locale', 'locales', 'moment', 'preferZoom', 'rtl', 'zoomKey',
'horizontalScroll', 'verticalScroll', 'longSelectPressTime', 'snap'
'horizontalScroll', 'horizontalScrollKey', 'horizontalScrollInvert', 'verticalScroll', 'longSelectPressTime', 'snap'
];
util.selectiveExtend(fields, this.options, options);
this.dom.rollingModeBtn.style.visibility = 'hidden';
Expand Down Expand Up @@ -562,7 +575,7 @@ class Core {

// cleanup hammer touch events
for (const event in this.timelineListeners) {
if (this.timelineListeners.hasOwnProperty(event)) {
if (Object.prototype.hasOwnProperty.call(this.timelineListeners, event)) {
delete this.timelineListeners[event];
}
}
Expand Down Expand Up @@ -713,6 +726,7 @@ class Core {

/**
* Get the id's of the items at specific time, where a click takes place on the timeline.
* @param {Date} timeOfEvent Point in time to query items.
* @returns {Array} The ids of all items in existence at the time of event.
*/
getItemsAtCurrentTime(timeOfEvent) {
Expand Down
5 changes: 3 additions & 2 deletions lib/timeline/Graph2d.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import moment from '../module/moment';
import util, { typeCoerceDataSet, isDataViewLike } from '../util';
import { DataSet, DataView } from 'vis-data/esnext';
// eslint-disable-next-line no-unused-vars
import { DataSet , DataView } from 'vis-data/esnext';
import Range from './Range';
import Core from './Core';
import TimeAxis from './component/TimeAxis';
Expand Down Expand Up @@ -293,7 +294,7 @@ Graph2d.prototype.getDataRange = function() {

// calculate min from start filed
for (var groupId in this.linegraph.groups) {
if (this.linegraph.groups.hasOwnProperty(groupId)) {
if (Object.prototype.hasOwnProperty.call(this.linegraph.groups, groupId)) {
if (this.linegraph.groups[groupId].visible == true) {
for (var i = 0; i < this.linegraph.groups[groupId].itemsData.length; i++) {
var item = this.linegraph.groups[groupId].itemsData[i];
Expand Down
17 changes: 12 additions & 5 deletions lib/timeline/Stack.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export function stack(items, margin, force, shouldBailItemsRedrawFunction) {
false,
item => item.stack && (force || item.top === null),
item => item.stack,
// eslint-disable-next-line no-unused-vars
item => margin.axis,
shouldBailItemsRedrawFunction
);
Expand All @@ -68,6 +69,7 @@ export function substack(items, margin, subgroup) {
margin.item,
false,
item => item.stack,
// eslint-disable-next-line no-unused-vars
item => true,
item => item.baseTop
);
Expand All @@ -91,7 +93,7 @@ export function nostack(items, margin, subgroups, isStackSubgroups) {
} else if (items[i].data.subgroup !== undefined && isStackSubgroups) {
let newTop = 0;
for (const subgroup in subgroups) {
if (subgroups.hasOwnProperty(subgroup)) {
if (Object.prototype.hasOwnProperty.call(subgroups, subgroup)) {
if (subgroups[subgroup].visible == true && subgroups[subgroup].index < subgroups[items[i].data.subgroup].index) {
newTop += subgroups[subgroup].height;
subgroups[items[i].data.subgroup].top = newTop;
Expand Down Expand Up @@ -125,8 +127,11 @@ export function stackSubgroups(items, margin, subgroups) {
vertical: 0
},
true,
// eslint-disable-next-line no-unused-vars
item => true,
// eslint-disable-next-line no-unused-vars
item => true,
// eslint-disable-next-line no-unused-vars
item => 0
);

Expand Down Expand Up @@ -154,7 +159,7 @@ export function stackSubgroupsWithInnerStack(subgroupItems, margin, subgroups) {
const subgroupOrder = [];

for(var subgroup in subgroups) {
if (subgroups[subgroup].hasOwnProperty("index")) {
if (Object.prototype.hasOwnProperty.call(subgroups[subgroup], "index")) {
subgroupOrder[subgroups[subgroup].index] = subgroup;
}
else {
Expand All @@ -164,7 +169,7 @@ export function stackSubgroupsWithInnerStack(subgroupItems, margin, subgroups) {

for(let j = 0; j < subgroupOrder.length; j++) {
subgroup = subgroupOrder[j];
if (subgroups.hasOwnProperty(subgroup)) {
if (Object.prototype.hasOwnProperty.call(subgroups, subgroup)) {

doSubStack = doSubStack || subgroups[subgroup].stack;
subgroups[subgroup].top = 0;
Expand Down Expand Up @@ -194,7 +199,7 @@ export function stackSubgroupsWithInnerStack(subgroupItems, margin, subgroups) {
}



// eslint-disable-next-line valid-jsdoc
/**
* Reusable stacking function
*
Expand Down Expand Up @@ -360,6 +365,7 @@ function checkVerticalSpatialCollision(a, b, margin) {
}


// eslint-disable-next-line valid-jsdoc
/**
* Find index of first item to meet predicate after a certain index.
* If no such item is found, returns the length of the array.
Expand All @@ -381,6 +387,7 @@ function findIndexFrom(arr, predicate, startIndex) {
return matchIndex + startIndex;
}

// eslint-disable-next-line valid-jsdoc
/**
* Find index of last item to meet predicate within a given range.
* If no such item is found, returns the index prior to the start of the range.
Expand All @@ -399,7 +406,7 @@ function findLastIndexBetween(arr, predicate, startIndex, endIndex) {
if(!endIndex) {
endIndex = arr.length;
}
for(i = endIndex - 1; i >= startIndex; i--) {
for(let i = endIndex - 1; i >= startIndex; i--) {
if(predicate(arr[i])) {
return i;
}
Expand Down
20 changes: 10 additions & 10 deletions lib/timeline/TimeStep.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,19 +138,19 @@ class TimeStep {
case 'year':
this.current = this.current
.year(this.step * Math.floor(this.current.year() / this.step))
.month(0); // eslint-disable-line no-fallthrough
case 'month':
this.current = this.current.date(1); // eslint-disable-line no-fallthrough
.month(0);
case 'month': // eslint-disable-line no-fallthrough
this.current = this.current.date(1);
case 'week': // eslint-disable-line no-fallthrough
case 'day': // eslint-disable-line no-fallthrough
case 'weekday':
this.current = this.current.hours(0); // eslint-disable-line no-fallthrough
case 'hour':
this.current = this.current.minutes(0); // eslint-disable-line no-fallthrough
case 'minute':
this.current = this.current.seconds(0); // eslint-disable-line no-fallthrough
case 'second':
this.current = this.current.milliseconds(0); // eslint-disable-line no-fallthrough
this.current = this.current.hours(0);
case 'hour': // eslint-disable-line no-fallthrough
this.current = this.current.minutes(0);
case 'minute': // eslint-disable-line no-fallthrough
this.current = this.current.seconds(0);
case 'second': // eslint-disable-line no-fallthrough
this.current = this.current.milliseconds(0);
//case 'millisecond': // nothing to do for milliseconds
}

Expand Down
5 changes: 5 additions & 0 deletions lib/timeline/Timeline.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,11 @@ export default class Timeline extends Core {
this.itemsData = null; // DataSet
this.groupsData = null; // DataSet

/**
* Emit an event.
* @param {string} eventName Name of event.
* @param {Event} event The event object.
*/
function emit(eventName, event) {
if (!me.hasListeners(eventName)) {
return;
Expand Down
4 changes: 2 additions & 2 deletions lib/timeline/component/ClusterGenerator.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export default class ClusterGenerator {
if (!clusters) {
clusters = [];
for (let groupName in this.groups) {
if (this.groups.hasOwnProperty(groupName)) {
if (Object.prototype.hasOwnProperty.call(this.groups, groupName)) {
const items = this.groups[groupName];
const iMax = items.length;
let i = 0;
Expand Down Expand Up @@ -214,7 +214,7 @@ export default class ClusterGenerator {

// sort the items per group
for (let currentGroupName in groups) {
if (groups.hasOwnProperty(currentGroupName)) {
if (Object.prototype.hasOwnProperty.call(groups, currentGroupName)) {
groups[currentGroupName].sort((a, b) => a.center - b.center);
}
}
Expand Down
Loading