Skip to content

Commit a04a903

Browse files
committed
feat: Implement new options 'horizontalScrollKey' (#1670, #1323) and 'horizontalScrollInvert' (#1595) to allow for both vertical and horizontal scrolling & invert the horizontal scroll direction.
Fix eslint errors modified: docs/timeline/index.html | Add documentation for options 'horizontalScrollKey' and 'horizontalScrollInvert'. modified: examples/timeline/other/horizontalScroll.html | Add new options 'horizontalScrollKey' and 'horizontalScrollInvert' to horizontal scroll example. modified: lib/timeline/Core.js | Implement new options to allow for both vertical and horizontal scrolling & invert the horizontal scroll direction. modified: lib/timeline/optionsTimeline.js | Add options 'horizontalScrollKey' and 'horizontalScrollInvert'. modified: rollup.config.js | lint modified: types/index.d.ts | Add type definitions for options 'horizontalScrollKey' and 'horizontalScrollInvert'.
1 parent 6586430 commit a04a903

23 files changed

+197
-82
lines changed

.eslintrc.js

+5
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,15 @@ module.exports = {
88

99
parserOptions: {
1010
sourceType: "module",
11+
"ecmaVersion": 2020
1112
},
1213

1314
extends: "eslint:recommended",
1415

16+
ignorePatterns: [
17+
"types/*.*"
18+
],
19+
1520
// For the full list of rules, see: http://eslint.org/docs/rules/
1621
rules: {
1722
complexity: [2, 55],

docs/timeline/index.html

+27-4
Original file line numberDiff line numberDiff line change
@@ -766,11 +766,34 @@ <h2 id="Configuration_Options">Configuration Options</h2>
766766
</tr>
767767

768768
<tr>
769-
<td>horizontalScroll</td>
769+
<td><code>horizontalScroll</code></td>
770770
<td>Boolean</td>
771-
<td>false</td>
772-
<td>This option allows you to scroll horizontally to move backwards and forwards in the time range.
773-
Only applicable when option <code>zoomKey</code> is defined or <code>zoomable</code> is <code>false</code>.
771+
<td><code>false</code></td>
772+
<td>
773+
This option allows you to scroll horizontally to move backwards and forwards in the time range.<br />
774+
Only applicable when option <code>zoomKey</code> is defined or <code>zoomable</code> is <code>false</code>.
775+
</td>
776+
</tr>
777+
778+
<tr>
779+
<td><code>horizontalScrollKey</code></td>
780+
<td>String</td>
781+
<td><code>''</code></td>
782+
<td>
783+
This option allows you to scroll horizontally while holding down a specific key.<br/>
784+
Available values are <code>''</code> (does not apply), <code>'altKey'</code>, <code>'ctrlKey'</code>, <code>'shiftKey'</code> or <code>'metaKey'</code>.<br />
785+
Only applicable when option <code>horizontalScroll</code> is defined.
786+
</td>
787+
</tr>
788+
789+
<tr>
790+
<td><code>horizontalScrollInvert</code></td>
791+
<td>Boolean</td>
792+
<td><code>false</code></td>
793+
<td>
794+
This option allows you to invert the horizontal scroll direction.<br />
795+
By default scroll-up will move the view to the right and scroll-down to the left.<br />
796+
Only applicable when option <code>horizontalScroll</code> is defined.
774797
</td>
775798
</tr>
776799

examples/timeline/other/horizontalScroll.html

+29
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,32 @@
1212

1313
<h1>Timeline horizontal scroll option</h1>
1414

15+
<div style="border: 1px solid #999; border-radius: 0.25rem; padding: .5rem 3rem .5rem 1rem; width: fit-content;">
16+
<code style="white-space: pre-wrap;">// specified options
17+
var options = {
18+
stack: true,
19+
horizontalScroll: true,
20+
// horizontalScrollKey: 'shiftKey', // If you want to enable both vertical and horizontal scrolling, uncomment this and
21+
the line below.
22+
// verticalScroll: true // Uncomment this line with the line above to allow for both scroll-axis.
23+
// horizontalScrollInvert: true, // Invert the horizontal scroll direction by uncommenting this line.
24+
zoomKey: 'ctrlKey',
25+
maxHeight: 400,
26+
start: new Date(),
27+
end: new Date(1000*60*60*24 + (new Date()).valueOf()),
28+
editable: true,
29+
margin: {
30+
item: 10, // minimal margin between items
31+
axis: 5 // minimal margin between items and the axis
32+
},
33+
orientation: 'top'
34+
};
35+
36+
// create a Timeline
37+
var container = document.getElementById('visualization');
38+
timeline = new vis.Timeline(container, items, groups, options);</code>
39+
</div>
40+
1541
<div id="visualization"></div>
1642

1743
<script>
@@ -55,6 +81,9 @@ <h1>Timeline horizontal scroll option</h1>
5581
var options = {
5682
stack: true,
5783
horizontalScroll: true,
84+
// horizontalScrollKey: 'shiftKey', // If you want to enable both vertical and horizontal scrolling, uncomment this and the line below.
85+
// verticalScroll: true // Uncomment this line with the line above to allow for both scroll-axis.
86+
// horizontalScrollInvert: true, // Invert the horizontal scroll direction by uncommenting this line.
5887
zoomKey: 'ctrlKey',
5988
maxHeight: 400,
6089
start: new Date(),

lib/DOMutil.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
export function prepareElements(JSONcontainer) {
99
// cleanup the redundant svgElements;
1010
for (var elementType in JSONcontainer) {
11-
if (JSONcontainer.hasOwnProperty(elementType)) {
11+
if (Object.prototype.hasOwnProperty.call(JSONcontainer, elementType)) {
1212
JSONcontainer[elementType].redundant = JSONcontainer[elementType].used;
1313
JSONcontainer[elementType].used = [];
1414
}
@@ -25,7 +25,7 @@ export function prepareElements(JSONcontainer) {
2525
export function cleanupElements(JSONcontainer) {
2626
// cleanup the redundant svgElements;
2727
for (var elementType in JSONcontainer) {
28-
if (JSONcontainer.hasOwnProperty(elementType)) {
28+
if (Object.prototype.hasOwnProperty.call(JSONcontainer, elementType)) {
2929
if (JSONcontainer[elementType].redundant) {
3030
for (var i = 0; i < JSONcontainer[elementType].redundant.length; i++) {
3131
JSONcontainer[elementType].redundant[i].parentNode.removeChild(JSONcontainer[elementType].redundant[i]);
@@ -59,7 +59,7 @@ export function resetElements(JSONcontainer) {
5959
export function getSVGElement(elementType, JSONcontainer, svgContainer) {
6060
var element;
6161
// allocate SVG element, if it doesnt yet exist, create one.
62-
if (JSONcontainer.hasOwnProperty(elementType)) { // this element has been created before
62+
if (Object.prototype.hasOwnProperty.call(JSONcontainer, elementType)) { // this element has been created before
6363
// check if there is an redundant element
6464
if (JSONcontainer[elementType].redundant.length > 0) {
6565
element = JSONcontainer[elementType].redundant[0];
@@ -95,7 +95,7 @@ export function getSVGElement(elementType, JSONcontainer, svgContainer) {
9595
export function getDOMElement(elementType, JSONcontainer, DOMContainer, insertBefore) {
9696
var element;
9797
// allocate DOM element, if it doesnt yet exist, create one.
98-
if (JSONcontainer.hasOwnProperty(elementType)) { // this element has been created before
98+
if (Object.prototype.hasOwnProperty.call(JSONcontainer, elementType)) { // this element has been created before
9999
// check if there is an redundant element
100100
if (JSONcontainer[elementType].redundant.length > 0) {
101101
element = JSONcontainer[elementType].redundant[0];

lib/shared/Configurator.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ class Configurator {
127127
let counter = 0;
128128
let show = false;
129129
for (let option in this.configureOptions) {
130-
if (this.configureOptions.hasOwnProperty(option)) {
130+
if (Object.prototype.hasOwnProperty.call(this.configureOptions, option)) {
131131
this.allowCreation = false;
132132
show = false;
133133
if (typeof filter === 'function') {
@@ -579,7 +579,7 @@ class Configurator {
579579
let filter = this.options.filter;
580580
let visibleInSet = false;
581581
for (let subObj in obj) {
582-
if (obj.hasOwnProperty(subObj)) {
582+
if (Object.prototype.hasOwnProperty.call(obj, subObj)) {
583583
show = true;
584584
let item = obj[subObj];
585585
let newPath = util.copyAndExtendArray(path, subObj);

lib/shared/Validator.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class Validator {
4242
*/
4343
static parse(options, referenceOptions, path) {
4444
for (let option in options) {
45-
if (options.hasOwnProperty(option)) {
45+
if (Object.prototype.hasOwnProperty.call(options, option)) {
4646
Validator.check(option, options, referenceOptions, path);
4747
}
4848
}

lib/timeline/Core.js

+18-4
Original file line numberDiff line numberDiff line change
@@ -230,16 +230,20 @@ class Core {
230230
deltaY *= PAGE_HEIGHT;
231231
}
232232
}
233+
233234
// Prevent scrolling when zooming (no zoom key, or pressing zoom key)
234235
if (this.options.preferZoom) {
235236
if (!this.options.zoomKey || event[this.options.zoomKey]) return;
236237
} else {
237238
if (this.options.zoomKey && event[this.options.zoomKey]) return
238239
}
240+
239241
// Don't preventDefault if you can't scroll
240242
if (!this.options.verticalScroll && !this.options.horizontalScroll) return;
241243

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

@@ -255,11 +259,20 @@ class Core {
255259
event.preventDefault();
256260
}
257261
}
262+
263+
// If verticalScroll disabled or horizontalScrollKey is set and horizontalScrollKey is pressed
258264
} else if (this.options.horizontalScroll) {
265+
this.range.stopRolling();
266+
259267
const delta = Math.abs(deltaX) >= Math.abs(deltaY) ? deltaX : deltaY;
260268

261269
// calculate a single scroll jump relative to the range scale
262-
const diff = (delta / 120) * (this.range.end - this.range.start) / 20;
270+
let diff = (delta / 120) * (this.range.end - this.range.start) / 20;
271+
272+
// Invert scroll direction if configured.
273+
if (this.options.horizontalScrollInvert)
274+
diff = -diff;
275+
263276
// calculate new start and end
264277
const newStart = this.range.start + diff;
265278
const newEnd = this.range.end + diff;
@@ -418,7 +431,7 @@ class Core {
418431
'width', 'height', 'minHeight', 'maxHeight', 'autoResize',
419432
'start', 'end', 'clickToUse', 'dataAttributes', 'hiddenDates',
420433
'locale', 'locales', 'moment', 'preferZoom', 'rtl', 'zoomKey',
421-
'horizontalScroll', 'verticalScroll', 'longSelectPressTime', 'snap'
434+
'horizontalScroll', 'horizontalScrollKey', 'horizontalScrollInvert', 'verticalScroll', 'longSelectPressTime', 'snap'
422435
];
423436
util.selectiveExtend(fields, this.options, options);
424437
this.dom.rollingModeBtn.style.visibility = 'hidden';
@@ -562,7 +575,7 @@ class Core {
562575

563576
// cleanup hammer touch events
564577
for (const event in this.timelineListeners) {
565-
if (this.timelineListeners.hasOwnProperty(event)) {
578+
if (Object.prototype.hasOwnProperty.call(this.timelineListeners, event)) {
566579
delete this.timelineListeners[event];
567580
}
568581
}
@@ -713,6 +726,7 @@ class Core {
713726

714727
/**
715728
* Get the id's of the items at specific time, where a click takes place on the timeline.
729+
* @param {Date} timeOfEvent Point in time to query items.
716730
* @returns {Array} The ids of all items in existence at the time of event.
717731
*/
718732
getItemsAtCurrentTime(timeOfEvent) {

lib/timeline/Graph2d.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import moment from '../module/moment';
22
import util, { typeCoerceDataSet, isDataViewLike } from '../util';
3-
import { DataSet, DataView } from 'vis-data/esnext';
3+
// eslint-disable-next-line no-unused-vars
4+
import { DataSet , DataView } from 'vis-data/esnext';
45
import Range from './Range';
56
import Core from './Core';
67
import TimeAxis from './component/TimeAxis';
@@ -293,7 +294,7 @@ Graph2d.prototype.getDataRange = function() {
293294

294295
// calculate min from start filed
295296
for (var groupId in this.linegraph.groups) {
296-
if (this.linegraph.groups.hasOwnProperty(groupId)) {
297+
if (Object.prototype.hasOwnProperty.call(this.linegraph.groups, groupId)) {
297298
if (this.linegraph.groups[groupId].visible == true) {
298299
for (var i = 0; i < this.linegraph.groups[groupId].itemsData.length; i++) {
299300
var item = this.linegraph.groups[groupId].itemsData[i];

lib/timeline/Stack.js

+12-5
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export function stack(items, margin, force, shouldBailItemsRedrawFunction) {
4444
false,
4545
item => item.stack && (force || item.top === null),
4646
item => item.stack,
47+
// eslint-disable-next-line no-unused-vars
4748
item => margin.axis,
4849
shouldBailItemsRedrawFunction
4950
);
@@ -68,6 +69,7 @@ export function substack(items, margin, subgroup) {
6869
margin.item,
6970
false,
7071
item => item.stack,
72+
// eslint-disable-next-line no-unused-vars
7173
item => true,
7274
item => item.baseTop
7375
);
@@ -91,7 +93,7 @@ export function nostack(items, margin, subgroups, isStackSubgroups) {
9193
} else if (items[i].data.subgroup !== undefined && isStackSubgroups) {
9294
let newTop = 0;
9395
for (const subgroup in subgroups) {
94-
if (subgroups.hasOwnProperty(subgroup)) {
96+
if (Object.prototype.hasOwnProperty.call(subgroups, subgroup)) {
9597
if (subgroups[subgroup].visible == true && subgroups[subgroup].index < subgroups[items[i].data.subgroup].index) {
9698
newTop += subgroups[subgroup].height;
9799
subgroups[items[i].data.subgroup].top = newTop;
@@ -125,8 +127,11 @@ export function stackSubgroups(items, margin, subgroups) {
125127
vertical: 0
126128
},
127129
true,
130+
// eslint-disable-next-line no-unused-vars
128131
item => true,
132+
// eslint-disable-next-line no-unused-vars
129133
item => true,
134+
// eslint-disable-next-line no-unused-vars
130135
item => 0
131136
);
132137

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

156161
for(var subgroup in subgroups) {
157-
if (subgroups[subgroup].hasOwnProperty("index")) {
162+
if (Object.prototype.hasOwnProperty.call(subgroups[subgroup], "index")) {
158163
subgroupOrder[subgroups[subgroup].index] = subgroup;
159164
}
160165
else {
@@ -164,7 +169,7 @@ export function stackSubgroupsWithInnerStack(subgroupItems, margin, subgroups) {
164169

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

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

196201

197-
202+
// eslint-disable-next-line valid-jsdoc
198203
/**
199204
* Reusable stacking function
200205
*
@@ -360,6 +365,7 @@ function checkVerticalSpatialCollision(a, b, margin) {
360365
}
361366

362367

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

390+
// eslint-disable-next-line valid-jsdoc
384391
/**
385392
* Find index of last item to meet predicate within a given range.
386393
* If no such item is found, returns the index prior to the start of the range.
@@ -399,7 +406,7 @@ function findLastIndexBetween(arr, predicate, startIndex, endIndex) {
399406
if(!endIndex) {
400407
endIndex = arr.length;
401408
}
402-
for(i = endIndex - 1; i >= startIndex; i--) {
409+
for(let i = endIndex - 1; i >= startIndex; i--) {
403410
if(predicate(arr[i])) {
404411
return i;
405412
}

lib/timeline/TimeStep.js

+10-10
Original file line numberDiff line numberDiff line change
@@ -138,19 +138,19 @@ class TimeStep {
138138
case 'year':
139139
this.current = this.current
140140
.year(this.step * Math.floor(this.current.year() / this.step))
141-
.month(0); // eslint-disable-line no-fallthrough
142-
case 'month':
143-
this.current = this.current.date(1); // eslint-disable-line no-fallthrough
141+
.month(0);
142+
case 'month': // eslint-disable-line no-fallthrough
143+
this.current = this.current.date(1);
144144
case 'week': // eslint-disable-line no-fallthrough
145145
case 'day': // eslint-disable-line no-fallthrough
146146
case 'weekday':
147-
this.current = this.current.hours(0); // eslint-disable-line no-fallthrough
148-
case 'hour':
149-
this.current = this.current.minutes(0); // eslint-disable-line no-fallthrough
150-
case 'minute':
151-
this.current = this.current.seconds(0); // eslint-disable-line no-fallthrough
152-
case 'second':
153-
this.current = this.current.milliseconds(0); // eslint-disable-line no-fallthrough
147+
this.current = this.current.hours(0);
148+
case 'hour': // eslint-disable-line no-fallthrough
149+
this.current = this.current.minutes(0);
150+
case 'minute': // eslint-disable-line no-fallthrough
151+
this.current = this.current.seconds(0);
152+
case 'second': // eslint-disable-line no-fallthrough
153+
this.current = this.current.milliseconds(0);
154154
//case 'millisecond': // nothing to do for milliseconds
155155
}
156156

lib/timeline/Timeline.js

+5
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,11 @@ export default class Timeline extends Core {
151151
this.itemsData = null; // DataSet
152152
this.groupsData = null; // DataSet
153153

154+
/**
155+
* Emit an event.
156+
* @param {string} eventName Name of event.
157+
* @param {Event} event The event object.
158+
*/
154159
function emit(eventName, event) {
155160
if (!me.hasListeners(eventName)) {
156161
return;

lib/timeline/component/ClusterGenerator.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ export default class ClusterGenerator {
108108
if (!clusters) {
109109
clusters = [];
110110
for (let groupName in this.groups) {
111-
if (this.groups.hasOwnProperty(groupName)) {
111+
if (Object.prototype.hasOwnProperty.call(this.groups, groupName)) {
112112
const items = this.groups[groupName];
113113
const iMax = items.length;
114114
let i = 0;
@@ -214,7 +214,7 @@ export default class ClusterGenerator {
214214

215215
// sort the items per group
216216
for (let currentGroupName in groups) {
217-
if (groups.hasOwnProperty(currentGroupName)) {
217+
if (Object.prototype.hasOwnProperty.call(groups, currentGroupName)) {
218218
groups[currentGroupName].sort((a, b) => a.center - b.center);
219219
}
220220
}

0 commit comments

Comments
 (0)