A high-performance, canvas-based fork of
vis-timeline. It keeps the familiar data model
(plain arrays or vis-data DataSets of items and groups) but replaces the
one-DOM-node-per-item renderer with a single <canvas>, so it stays fast into the
tens of thousands of items.
Migrating an existing integration? See transition.html. Go/no-go analysis: risks.html. Deferred perf work: optimizations.html.
An ESM package, installable from source — a git URL, a local path, or a packed tarball:
# from the git repo
npm install git+ssh://git@gitlab.com:willjessop12/canvas-timeline.git
# or from a local checkout / packed tarball
npm install /path/to/canvas-timeline
npm pack # -> vis-timeline-canvas-x.y.z.tgz, then `npm install ./that.tgz`
The package depends on vis-timeline and
vis-data so it stays consistent and interoperable with an existing
vis-timeline / vis-data integration. It does not import vis-timeline's
source (renderer) — it only duck-types DataSets — so it stays light;
vis-timeline is declared for ecosystem consistency and the parallel-run migration path
(see transition.html).
For convenience the package re-exports the data containers, so you can import the timeline and its
DataSet from one place:
import { CanvasTimeline, DataSet } from 'vis-timeline-canvas';
To pull in only the renderer (no vis-data re-export), use the subpath:
import { CanvasTimeline } from 'vis-timeline-canvas/renderer';
import { CanvasTimeline, DataSet } from 'vis-timeline-canvas';
const items = new DataSet([
{ id: 1, content: 'Kickoff', start: '2024-01-01T09:00', end: '2024-01-01T11:00', group: 'a' },
{ id: 2, content: 'Review', start: '2024-01-01T13:00', end: '2024-01-01T15:00', group: 'b' },
]);
const groups = [{ id: 'a', content: 'Team A' }, { id: 'b', content: 'Team B' }];
const timeline = new CanvasTimeline('#timeline', items, {
groups,
theme: 'dark',
height: 500,
});
timeline.on('select', ({ items }) => console.log('selected', items));
timeline.on('itemMove', ({ items }) => items.forEach(m => dataset.update(m)));
Note the two API differences from vis-timeline highlighted up front:
{ groups }), not as a
third constructor argument.itemMove / itemResize;
the renderer never writes back to your DataSet on its own.Every option is optional. Defaults are shown in parentheses.
| Option | Default | What it does |
|---|---|---|
height | 400 | Body height in px (the axis and minimap are drawn outside this). |
itemHeight | 32 | Height of a single item row in px. |
itemMargin | 4 | Vertical gap between stacked rows in px. Set 0 for dense rows. |
itemSpacing | 2 | Horizontal gap reserved between items when deciding if they fit on the same row. 0 lets adjacent items touch. |
itemBorderRadius | null | Global corner radius override. 0 = square corners; null keeps the per-item radius. |
groupMinHeight | 40 | Minimum height of a group band in px. |
groupLabelWidth | 150 | Width of the left group-label panel in px. |
groupOrder | 'order' | Field name to sort groups by, or a comparator (a, b) => number over the raw group objects. |
| Option | Default | What it does |
|---|---|---|
start / end | data range | Initial visible window (use both). |
min / max | null | Hard pan/zoom bounds. If both are set, the bounds are exactly this range. |
minZoom | 60000 | Smallest visible duration in ms (max zoom-in). |
maxZoom | null | Largest visible duration in ms (max zoom-out). null = the full data range. |
zoomSpeed | 0.002 | Wheel-zoom sensitivity. |
With nomin/max, the bounds are padded ~5% around the data and expanded to a 24-hour minimum span so single-item timelines still open at a sane scale.
| Option | Default | What it does |
|---|---|---|
stack | true | Stack overlapping items into rows. false overlaps them on one row. |
timeResolution | 60000 | The time-resolution unit in ms for manual edits. Drag/resize/keyboard-nudge are quantized to multiples of this, and committed values snap to the nearest unit on mouseup. Default one minute. |
snap | 'minute' | Edit quantization: 'minute' snaps to timeResolution; false = free editing; or a function (ms) => ms. |
snapToItems | true | Magnetically snap edited edges to nearby item edges and the now-line. |
snapDistance | 6 | Magnetic snap radius in px. |
| Option | Default | What it does |
|---|---|---|
tooltipDelay | 300 | Hover delay (ms) before the title tooltip shows. |
verticalScrollbar | true | Show the draggable scrollbar on the left of the body (auto-hides when content fits). |
scrollbarWidth | 12 | Scrollbar width in px. |
keyboard | true | Enable desktop keyboard navigation + the focus ring (see below). |
fullscreenButton | true | Show a fullscreen toggle button in the zoom-control overlay. |
| Option | Default | What it does |
|---|---|---|
theme | 'light' | 'light', 'dark', or a partial object merged over the light preset. |
hour24 | true | Show axis/probe time-of-day on a 24-hour clock (13:00) instead of 12-hour with AM/PM. |
fontFamily | system sans | CSS font-family for all canvas text. |
fontSize | 12 | Base font size in px (axis/probe use one px smaller). |
uniformItemColor | false | Draw every item in one grey color (uniformItemBg) with its own color as a bottom accent strip. |
uniformItemBg | '#9e9e9e' | The uniform fill color used when uniformItemColor is on. |
accentBorderHeight | 3 | Height of the bottom accent strip in px. |
| Option | Default | What it does |
|---|---|---|
icons | null | Registry of named icons: { name: { glyph, color } } or { name: { src } }. |
iconAtlas | null | Sprite sheet: { src | image, icons: { name: {x,y,w,h} } }. |
iconSize | 14 | Icon draw size in px. |
iconGap | 3 | Gap between leading icons in px. |
| Option | Default | What it does |
|---|---|---|
timeProbe | false | Vertical line following the mouse; the time chip shows on the minimap (or the bottom axis if there's no minimap). |
minimap | false | Overview minimap with drag-to-zoom. |
minimapHeight | 48 | Minimap height in px. |
topAxis | false | Also draw the time axis above the body (in addition to below). |
followInterval | 1000 | Follow-now tick interval in ms (see setFollowNow). |
{
id, start, // required
end, // omit => a "box" item (point-in-time)
content, // plain text label (HTML is NOT parsed)
group, // group id
type, // 'range' | 'box' | 'block'
title, // plain-text tooltip
// colors (preferred over a CSS `style` string)
backgroundColor, borderColor, color,
// canvas extras
accentColor, // bottom strip color in uniform mode
pattern, // true | 'stripes' — diagonal stripe overlay (errors)
patternColor,
noText, // hide the label; renders a compact marker
icon, icons, // leading icon name(s)
// block-only (type:'block')
layer, // 'back' (default) | 'front' (covers items)
onTop, // alias for layer:'front'
opacity, // override block fill alpha
gradient, // true (auto) | ['#a','#b'] color stops
gradientDirection, // 'vertical' (default) | 'horizontal'
}
{
id, content,
order, // sort key
nestedGroups, // [childId, ...] to nest groups
showNested, // start expanded (default true)
visible, // hide the group
backgroundColor, color,
accentColor, // highlight tint
}
Subscribe with timeline.on(name, cb) / unsubscribe with off.
| Event | Payload |
|---|---|
select | { items: id[] } |
doubleClick | { item, event } (also fired by Enter on the focused item) |
itemMove | { items: [{ id, start, end }] } — persist these |
itemResize | { item, start, end, edge } — persist these |
groupClick | { group, event } |
groupCollapse | { groupId, collapsed } |
followNow | { enabled } |
rangechange | { start, end } (continuous during pan/zoom) |
rangechanged | { start, end } (debounced after motion settles) |
itemover / itemout | { item, event } |
contextmenu | { item | null, event } (right-click) |
render | { visibleCount } |
setItems · setGroups · getGroups · getItemCount ·
fit · setWindow · getWindow · moveTo ·
zoomIn · zoomOut · toggleFullscreen · isFullscreen ·
setSelection · getSelection · toggleGroupCollapse ·
setGroupHighlight · clearGroupHighlights · setFollowNow ·
isFollowingNow · setTheme · on · off ·
redraw · destroy. See
src/canvas-timeline.d.ts for full signatures.
keyboard is on)Click the timeline to focus it, then:
doubleClick).These are the behaviors most likely to surprise you when migrating. Worked examples are in EXAMPLES.html.
template/groupTemplate are not
supported — the canvas draws plain text + shapes. Convert template logic into data-driven fields
(backgroundColor, pattern, icons, …).className / CSS classes do nothing. Style via data fields instead.itemMove/itemResize.box, range, and the new block.
point and background are not implemented.title field). For rich content, render
your own overlay on doubleClick/contextmenu.See transition.html for the full migration plan and a parallel-run harness.
The renderer is one class spread across focused modules. The big method groups are defined as
"mixin" classes and copied onto CanvasTimeline.prototype at load (see
src/mixin.js), so they share one this while living in separate, readable files.
index.js package entry: re-exports CanvasTimeline + DataSet/DataView
index.d.ts package types
src/
canvas-timeline.js core: constructor, options, public API, setup, data
mixin.js applyMixins(target, ...sources)
timeline/
layout.js viewport/time-bounds math, stacking, snapping
rendering.js the canvas draw path (grid, items, blocks, axis, minimap)
interaction.js pointer + keyboard input, drag/resize, hit-testing
theme.js light/dark presets + buildTheme()
colors.js lighten / darken / withAlpha
canvas-draw.js roundRect / truncateText
time-format.js axis + probe label formatting
canvas-timeline.d.ts TypeScript declarations
Run the tests with npm test (layout/unit assertions + a headless render smoke test).