Skip to content

Commit

Permalink
feat: Implement new options 'horizontalScrollKey' (visjs#1670, visjs#…
Browse files Browse the repository at this point in the history
…1323) and 'horizontalScrollInvert' (visjs#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'.
  • Loading branch information
LukasWillin committed Jan 10, 2025
1 parent cf9c56f commit 5fff106
Show file tree
Hide file tree
Showing 23 changed files with 197 additions and 82 deletions.
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
29 changes: 29 additions & 0 deletions examples/timeline/other/horizontalScroll.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,32 @@

<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 +81,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

0 comments on commit 5fff106

Please sign in to comment.