A collection of copy-paste snippets, weighted toward the places where vis-timeline-canvas diverges from DOM vis-timeline. For the full option list see README.html.
Divergence: groups are passed in the options object, not as a third arg.
import { DataSet } from 'vis-data';
import { CanvasTimeline } from 'vis-timeline-canvas';
const items = new DataSet(rawItems);
const groups = new DataSet(rawGroups);
const tl = new CanvasTimeline(container, items, {
groups, // <-- in options, not the 3rd argument
height: 500,
start: '2024-01-01',
end: '2024-01-08',
});
Either a plain array or a vis-data DataSet works. With a DataSet, the
renderer subscribes to add/update/remove and redraws automatically.
Divergence: the renderer never mutates your data; you write edits back.
tl.on('itemMove', ({ items }) => {
items.forEach(m => dataset.update(m)); // m = { id, start, end }
});
tl.on('itemResize', ({ item, start, end }) => {
dataset.update({ id: item, start, end });
});
Magnetic snapping is on by default — dragged edges snap to nearby item edges and the now-line. Tune or disable it:
new CanvasTimeline(el, items, { snapToItems: true, snapDistance: 8 });
new CanvasTimeline(el, items, { snapToItems: false, snap: false }); // free drag
Divergence:template/className/ CSS are not supported. Precompute the visual outcome into fields the canvas understands.
// vis-timeline (before):
options.template = (item) => `<span class="badge ${item.sev}">${item.label}</span>`;
// canvas (after): compute fields up front.
const SEV_COLOR = { info: '#4a9eff', warn: '#f9a825', error: '#e53935' };
items.update(rawItems.map(it => ({
id: it.id,
content: it.label,
backgroundColor: SEV_COLOR[it.sev],
pattern: it.sev === 'error', // diagonal red stripes
icon: it.sev, // a leading icon (see Icons below)
})));
Uniform-grey mode turns each item's color into a bottom accent strip — handy when color is a category, not the item's identity:
new CanvasTimeline(el, items, {
uniformItemColor: true,
uniformItemBg: '#9e9e9e',
accentBorderHeight: 4,
});
Plain-text tooltips come from the title field. For anything richer, render your own
DOM overlay on an event:
tl.on('doubleClick', ({ item, event }) => openDetailPopover(item, event));
tl.on('contextmenu', ({ item, event }) => showContextMenu(item, event)); // right-click
Divergence (now closed): earlier versions had no rangechange.
They exist now, plus you can always read the window directly.
tl.on('rangechange', ({ start, end }) => updateUrlDebounced(start, end)); // continuous
tl.on('rangechanged', ({ start, end }) => fetchForWindow(start, end)); // settled
const { start, end } = tl.getWindow(); // pull on demand
Leading icons render at the start of an item. Reference them by name; a name that isn't registered is drawn as a raw glyph (so emoji work directly).
const tl = new CanvasTimeline(el, items, {
icons: {
error: { glyph: '⚠', color: '#e53935' },
done: { glyph: '✓', color: '#43a047' },
deploy: { src: '/icons/rocket.png' }, // standalone image
},
});
items.update([
{ id: 1, content: 'Build', start, end, icon: 'done' },
{ id: 2, content: 'Outage', start, end, icons: ['error', '🔥'] }, // mix + emoji
]);
Many icons in one texture? Use a sprite atlas:
new CanvasTimeline(el, items, {
iconAtlas: {
src: '/sprites/status.png',
icons: {
ok: { x: 0, y: 0, w: 16, h: 16 },
warn: { x: 16, y: 0, w: 16, h: 16 },
bad: { x: 32, y: 0, w: 16, h: 16 },
},
},
});
// item.icon = 'warn'
A block spans its parent group's whole band (the parent + all subgroups) for a time range — useful for maintenance windows, releases, incidents.
items.add({
id: 'mw1', type: 'block', group: 'platform',
content: 'Maintenance', start, end, backgroundColor: '#ff7043',
});
By default a block sits behind items (translucent). Put it on the front layer to cover the items beneath, and/or fill it with a gradient:
items.add({
id: 'mask', type: 'block', layer: 'front', opacity: 0.8, // covers items
group: 'platform', start, end, backgroundColor: '#37474f',
});
items.add({
id: 'rel', type: 'block', group: 'platform', start, end,
backgroundColor: '#ff7043',
gradient: ['#ff7043', '#e53935'], // or gradient: true (auto)
gradientDirection: 'vertical',
});
const tl = new CanvasTimeline(el, items, { theme: 'dark' });
tl.setTheme('light'); // switch at runtime
// Custom theme: a partial object merged over the light preset.
tl.setTheme({
canvasBg: '#0b0f17',
gridLine: '#1b2230',
selectionBorder: '#22d3ee',
});
Highlight specific groups (e.g. the one the user is focused on):
tl.setGroupHighlight('platform', '#ffd54f');
tl.clearGroupHighlights();
const tl = new CanvasTimeline(el, items, { minimap: true, timeProbe: true });
tl.setFollowNow(true, 0.85); // lock to now; "now" sits 85% across the window
tl.isFollowingNow(); // => true
// Grabbing the timeline to pan automatically turns follow off and emits followNow.
Both renderers consume the same DataSet, so you can mount them side by side behind a
flag during migration. See transition.html for the full adapter; the
short version:
const items = new DataSet(rawItems);
const groups = new DataSet(rawGroups);
new Timeline(document.getElementById('old'), items, groups, oldOptions);
new CanvasTimeline(document.getElementById('new'), items, { ...mapOptions(oldOptions), groups });
// One writer: edits flow to both renderers.
items.update({ id: 42, start: newStart });