Temporeum Timelinev1.0.2

Migrating from vis-timeline

Most vis-timeline setups move over by changing the shape of the data and renaming a handful of options — or in one call: fromVis from @temporeum/timeline-core/vis-compat reads vis-timeline's items, groups and options by vis-timeline's own rules and returns the configuration, with a warning for everything that did not carry over. This page maps every vis-timeline option, item field, method and event that has an equivalent, and lists, honestly, the ones that don't have one yet. Each mapping in the tables below is a running example in the test suite, and each "Not yet" row is checked against the configuration's types. A row that maps to something that doesn't exist fails the build.

Live example: vis-timeline data, converted

Loading…

Run “vis-timeline data, converted” on your own machine
  1. Make an empty folder and put this example’s two files in it:

    index.html — the page the code runs in, its markup and styles:

    <div id="timeline" style="height: 240px"></div>
    <pre class="log" id="log" tabindex="0" aria-live="polite"></pre>
    

    main.js — the code, as it is in the editor now, your edits included:

  2. In a terminal in that folder, install the library and Vite (it lets the browser follow the imports), then start it:

    npm init -y
    npm install @temporeum/timeline-element @temporeum/timeline-core
    npm install --save-dev vite
    npx vite
  3. Open the address it prints, http://localhost:5173/.

The plugins

vis-timeline draws everything it can draw on every page. Here the parts a plain timeline does not need are plugins, each on its own entry point, registered once before the first timeline:

import { createTimeline, registerPlugin } from "@temporeum/timeline-element";
import { itemsPlugin } from "@temporeum/timeline-element/items";
import { fromVis } from "@temporeum/timeline-core/vis-compat";

// vis-timeline draws every kind of item and lets the reader remove one:
// here those are the items plugin's. Groups, the time tools and right to
// left are plugins too (`/groups`, `/tools`, `/intl`) — register what the
// converted configuration uses.
registerPlugin(itemsPlugin);

From the running example example/migration-vis-timeline

What fromVis can put in a configuration, and the plugin each needs:

In the converted configurationPluginFrom
groups (from vis-timeline's groups with nestedGroups)groupsPlugin@temporeum/timeline-element/groups
item kinds box (vis-timeline's kind for an item without an end), point, background; template; editing.add, editing.remove (editable: true sets both, as in vis-timeline)itemsPlugin@temporeum/timeline-element/items
hiddenDates, rolling, tooltiptoolsPlugin@temporeum/timeline-element/tools
direction: "rtl"intlPlugin@temporeum/timeline-element/intl

A converted configuration that needs a plugin that is not registered throws a TypeError where the timeline is created, naming the import that is missing — it does not draw a timeline that quietly lacks the option. When in doubt, register all four; the <script>-tag build registers every plugin on load.

What changes

  • No DataSet. Items and groups are plain arrays. To change them, give the timeline the next array with setItems or update({ groups }).
  • Ids are strings and times are Date objects or epoch milliseconds. vis-timeline also accepts date strings, and reads them with moment: "2026-03-02" is local midnight there, but UTC midnight for new Date("2026-03-02"). fromVis reads them the way vis-timeline does — in the runtime's zone, or the timeZone it is given — and sets that zone as the config's timeZone, so the axis draws in it as vis-timeline's does; its tests hold it to vis-timeline 8.5.6 itself, on every example of vis-timeline's own documentation.
  • Groups name their parent. An item's group becomes laneId, a group's content becomes its label, and a nested group says parentId instead of its parent listing nestedGroups (groups).
  • Labels are text; templates are sanitised HTML. A template is run through the library's own sanitiser, which drops scripts and handlers, so no separate XSS filter is needed. For full control, hooks.renderItem returns scene nodes. Both also render on the server.
  • Editing produces commands. A drag or a key press produces one reversible command, and on("command") receives it. The library never saves; what happens next is up to you (editing).
  • The keyboard works by default. Every gesture has a key, and screen readers see the timeline as a listbox (accessibility).

The mapping

The timeline in each row's example has five items in two lanes; the row's code is the only difference.

Setup
vis-timelineTemporeumStatusNotes
new Timeline(container, items, groups, options)createTimeline(container, { items, lanes, ...config })RenamedPlain arrays, no DataSet. One configuration object holds everything.
the whole setup at oncefromVis(items, groups, options) from /vis-compatDifferent@temporeum/timeline-core/vis-compat reads vis-timeline's items, groups and options by vis-timeline's own rules — date strings as moment reads them, item types, nested and hidden groups, groupOrder — and returns the configuration, and a warning for everything that did not carry over.
DataSet add / update / removetimeline.setItems(nextItems)DifferentItems are immutable data: hand the timeline the next array. For data that is too big to hold, items can be a function that loads a window.
setOptions(options)timeline.update(config)RenamedA key given as undefined goes back to its default; a key left out keeps its value.
autoResizealways onSameThe timeline follows its container's size. Below breakpoint it turns vertical when orientation is "auto".
destroy()timeline.destroy()Same
Window
vis-timelineTemporeumStatusNotes
start, enddomain: [start, end]RenamedWithout a domain the view fits the items, with a margin.
min, maxlimits: { min, max }Renamed
zoomMin, zoomMaxlimits: { minSpanMs, maxSpanMs }RenamedThe shortest and longest visible span, in milliseconds.
setWindow(start, end)timeline.zoomTo([start, end])Renamed
getWindow()timeline.getSnapshot().domainRenamedThe snapshot is also what subscribe listeners read: it fits React's useSyncExternalStore.
moveTo(time)timeline.scrollTo(time)Renamed
zoomIn(percentage), zoomOut(percentage)timeline.zoomBy(factor, around?)DifferentA factor above 1 zooms in. around is the time that stays put; the centre by default.
fit()timeline.zoomTo([first, last])DifferentThere is no fit(): the first view fits the items when no domain is given; later, zoom to their extent.
zoomable: falseinteractions: { zoom: false }Renamed
moveable: falseinteractions: { pan: false }Renamed
zoomKeyctrl + wheel, or a pinchDifferentFixed, not an option: a plain wheel pans or scrolls, ctrl + wheel (and a trackpad pinch, which browsers report as one) zooms.
horizontalScroll, verticalScrolltouchAction: "cooperative"DifferentA plain wheel already pans along time and scrolls the lanes. On touch, "cooperative" leaves vertical page scrolling to the page, for a timeline inside an article.
rollingModerolling: { offset }RenamedThe view keeps its span and keeps now at offset (the middle by default), reading the clock every intervalMs. A pan or a zoom of the reader's lets go of it; follow() picks it up again.
hiddenDateshiddenDates: [{ start, end, repeat }]DifferentThe same shape. start and end are Dates or epoch milliseconds, and a repeat is a calendar repeat in timeZone: a nightly range stays on the wall clock across a change of daylight saving.
Size and look
vis-timelineTemporeumStatusNotes
heightheightSameA number of CSS pixels. Without one, the container's own height when a stylesheet gives it one.
width, minHeight, maxHeightthe container's CSSDifferentThe timeline fills its container; size the container.
margindensity: "comfortable" | "compact" | "dense"DifferentSpacing comes in three densities, picked automatically by default to fit the items.
orientation: "top" | "bottom" (axis position)—Not yetNot to be confused with Temporeum's orientation, which is horizontal or vertical.
stack: falsepackMode: "overlap"Renamed"pack" (the default) is vis's stack: true; "single" gives every item its own row. Also per lane.
align—Not yetLabels start at the item's start, and move after it when they do not fit inside.
templatetemplate: (item) => htmlSameSanitised by default: scripts, handlers and anything outside an allow-list are dropped (templateOptions). For full control over the SVG, hooks.renderItem returns scene nodes.
clustercluster: true | { minCount, gap, itemSize }SameA cluster is one keyboard stop that opens into its items.
xsssanitised by defaultDifferentLabels and tooltips are text, never HTML. A template is HTML, run through the library's own sanitiser unless templateOptions.sanitize is false.
Axis
vis-timelineTemporeumStatusNotes
localelocaleSameA BCP 47 tag, through Intl. timeZone sets the zone the axis is drawn in.
localesstringsDifferentMonth and day names come from Intl for any locale. The timeline's own words — its name, the zoom buttons, "New item" — come from a dictionary: strings over DEFAULT_STRINGS (@temporeum/timeline-core/intl).
formatformatTick(tick, timeZone)DifferentOne function for every tick label; tick.major tells the major ones apart.
timeAxis: { scale, step }—Not yetTicks are chosen automatically for the zoom level.
showMajorLabels, showMinorLabels—Not yetformatTick can return an empty string for the labels to hide.
showCurrentTimedecorations: [{ kind: "now", time }]DifferentDrawn where you put it; update it with timeline.update when it should move.
addCustomTime(time, id)timeBars: [{ id, time }]DifferentA time bar is a slider: drag it, or Tab to it and press an arrow. A move is timeBarMoved — vis's timechanged — and timeline.timeBars is where the reader left them.
rtldirection: "rtl"RenamedTime runs from right to left, the lane labels and the keyboard's arrows follow. "auto" takes the container's dir.
Items
vis-timelineTemporeumStatusNotes
item.ididDifferentA string. Convert numeric ids with String(id).
item.contentlabelRenamedPlain text. A label that does not fit is shortened with an ellipsis, up to 1 000 items per screen width.
item.start, item.endstart, endDifferentA Date or epoch milliseconds — not a string. Convert with new Date(value).
item.grouplaneIdRenamed
item.classNameclassNameSame
item.style—Not yetUse className and a stylesheet; the theme's custom properties style the rest.
item.titletooltip: (item) => textDifferentShown on hover and on keyboard focus. The item's accessible name stays its label, the dates and the lane; hooks.describeItem changes that.
item.editable: falsemovable: false, resizable: falseDifferentMoving and resizing are separate. An item's editable: true on a timeline that is not editable turns editing on in fromVis, with every other item held still, as vis-timeline lets an item opt in.
item.selectableselectableSame
item.type: "point"kind: "point"RenamedA dot at its start with a line to the axis. An item without end and without a kind is an instant drawn as a thin bar.
item.type: "background"kind: "background"RenamedA labelled band behind every lane; nothing can hit it. A background bound to one group's rows is not here yet.
item.type: "box", type optionkind: "box"RenamedThe item's bar with a line to the axis. Set it per item; there is no timeline-wide default type.
item.subgroup, stackSubgroups—Not yetRows inside a group's rows are not supported; nest a group instead (groups with parentId).
Groups
vis-timelineTemporeumStatusNotes
groupsgroups (or lanes)RenamedAn array of { id, label, parentId? }; a group's content is its label, and an item names its group with laneId. Each group gets a header row you can collapse. Flat lanes without headers still work.
setGroups(groups)timeline.update({ groups })Renamed
groupOrderthe order of `groups`DifferentSort the array. A reader can reorder the groups too; that comes out as a groupMoved.
nestedGroups, showNestedgroups: [{ id, parentId }]DifferentA group names its parent instead of its children. Collapsing is a click on the header, Enter, or timeline.toggleGroup(id); a group starts collapsed with collapsed: true.
groupEditable (drag to reorder)drag a group header, or Alt + arrowDifferentAlways on; the reorder is reported as a reversible groupMoved and the library does not save it.
groupTemplate—Not yetLane labels are text.
Editing
vis-timelineTemporeumStatusNotes
editable: trueediting: { enabled: true }RenamedMove and resize, with a key for every gesture. Every edit is a reversible command.
editable: { updateGroup: true }editing: { crossLane: true }Renamed
editable: { add, remove }, onAdd, onRemoveediting: { add, remove, createItem, canAdd, canRemove }RenamedA double click on empty row space or n adds; the remove handle on a selected item or Delete removes. canAdd and canRemove are synchronous vetoes; createItem makes the new item.
snapediting: { snapMs, snapOrigin }DifferentA fixed grid instead of a function. A grid of whole days lies on the timeline's timeZone midnights, all year.
onMoving, onMoveediting.canMove(item, next)DifferentA synchronous yes or no for every step of a drag. The finished edit arrives as itemMoved with its command.
onUpdateon("itemDblClick")DifferentOpen your own editor on a double click.
multiselectediting: { multiSelect: true }Renamed
selectable: falseselectable: false on each itemDifferent
Selection
vis-timelineTemporeumStatusNotes
setSelection(ids)timeline.select(ids)RenamedOr own it: selectedIds in the configuration.
getSelection()timeline.getSnapshot().selectedIdsRenamed
focus(id)select([id]) + scrollTo(start)Different
Events
vis-timelineTemporeumStatusNotes
on("select")on("selectionChanged")Renamed{ ids, previousIds }.
on("rangechange"), on("rangechanged")on("viewportChanged")Renamed{ viewport, reason }; viewport.domain is the window.
on("click") on an itemon("itemClick")Renamed
on("doubleClick")on("itemDblClick")Renamed
on("itemover"), on("itemout")on("itemHover")RenamedOne event; its payload says which item, or none.
on("changed") after an editon("itemMoved"), on("itemResized"), on("command")Differentcommand carries every edit as a reversible command — the undo stack's input.
on("timechange"), addCustomTimetimeBars, on("timeBarMoved")RenamedA time bar is a slider: dragged, or moved with the arrows when it has the focus. The event says where it went, and where it was.
on("contextmenu")—Not yetListen for contextmenu on the container.

What is not here yet

The "Not yet" rows above, in short:

  • rows inside a group's rows (vis-timeline's subgroup), and a background bound to one group;
  • the axis on top, item alignment, a custom time axis, hiding the major or minor labels;
  • inline item styles, group templates and context-menu events.

Nested groups, item types and adding and removing from the timeline are plugins; so are hidden dates, draggable time bars, rolling mode, tooltips (time tools), and right to left and a dictionary for the timeline's words (internationalisation). Live changes without a reload are in the base, and fromVis converts a setup in one call. If your app depends on one of the rest, say so in an issue: the order is decided by what users need.