Choose the settings using the controls below the timeline.
Use timeline.options() and timeline.labelAreaWidth() functions to customize the timeline.
See also:
Choose the settings using the controls below the timeline.
Use the options and labelAreaWidth Props to customize the timeline.
See also:
import { createTimeline } from "kronograph";
import data, { markers, annotations } from "./data";
import { cloneDeep, merge } from "lodash";
const timeline = createTimeline("my-timeline");
const DEFAULT_THEME = {
options: {
backgroundColor: undefined,
focus: {
backgroundColor: undefined,
},
scales: {
borderColor: undefined,
textColor: undefined,
},
highlightColor: undefined,
scaleGuide: {
lineColor: undefined,
textColor: undefined,
},
entities: {
labelBackgroundColor: undefined,
},
events: {
heatmapColor: undefined,
},
controls: {
deleteAnnotation: {
backgroundColor: undefined,
color: undefined,
hoverBackgroundColor: undefined,
hoverColor: undefined,
},
editAnnotation: {
backgroundColor: undefined,
color: undefined,
hoverBackgroundColor: undefined,
hoverColor: undefined,
},
lens: {
color: undefined,
},
},
},
entityTypes: {},
eventTypes: {},
charts: {},
annotations: {},
};
const DARK_THEME = {
options: {
backgroundColor: "#0a0a0a",
focus: {
backgroundColor: "#421900",
},
scales: {
borderColor: "#777777",
textColor: "#ffffff",
},
highlightColor: "#ffffff",
scaleGuide: {
lineColor: "#ffffff",
textColor: "#ffffff",
},
entities: {
labelBackgroundColor: "#0a0a0a",
},
events: {
heatmapColor: "#E6C700",
},
controls: {
deleteAnnotation: {
backgroundColor: "#2f2f2f",
color: "#ffffff",
hoverBackgroundColor: "#421900",
hoverColor: "#D15000",
},
editAnnotation: {
backgroundColor: "#2f2f2f",
color: "#ffffff",
hoverBackgroundColor: "#421900",
hoverColor: "#D15000",
},
lens: {
color: "#D15000",
},
},
},
entityTypes: {
default: {
color: "#aaaaaa",
labelColor: "#ffffff",
},
},
eventTypes: {
default: {
color: "#E6C700",
},
},
charts: {
color: "#FFDD00",
labelColor: "#ffffff",
},
annotations: {
borderColor: "#1f1f1f",
fillColor: "#1f1f1f",
labelColor: "#FFFFFF",
borderWidth: 2,
connectorStyle: {
color: "#FFFFFF",
},
},
};
const LIGHT_THEME = {
options: {
backgroundColor: "#FFFFFF",
focus: {
backgroundColor: "#E5EDFF",
},
scales: {
borderColor: "#aaaaaa",
textColor: "#000000",
},
highlightColor: "#888888",
scaleGuide: {
lineColor: "#000000",
textColor: "#000000",
},
controls: {
deleteAnnotation: {
backgroundColor: "#dddddd",
color: "#000000",
hoverBackgroundColor: "#E5EDFF",
hoverColor: "#0035BD",
},
editAnnotation: {
backgroundColor: "#dddddd",
color: "#000000",
hoverBackgroundColor: "#E5EDFF",
hoverColor: "#0035BD",
},
lens: {
color: "#0035BD",
},
},
entities: {
labelBackgroundColor: "#FFFFFF",
},
events: {
heatmapColor: "#8747A9",
},
},
entityTypes: {
default: {
color: "#888888",
labelColor: "#000000",
},
},
eventTypes: {
default: {
color: "#8747A9",
},
},
charts: {
color: "#8747A9",
labelColor: "#000000",
},
annotations: {
borderColor: "#eeeeee",
fillColor: "#eeeeee",
labelColor: "#000000",
borderWidth: 2,
connectorStyle: {
color: "#000000",
},
},
};
const timelineOptions = {
entities: {
showLines: "individualEvents",
},
events: {
heatmapPadding: true,
heatmapThreshold: 100,
},
};
let currentTheme = DEFAULT_THEME;
let timelineData = data;
let annotationData = cloneDeep(annotations);
timeline.options(timelineOptions);
timeline.set(timelineData);
timeline.annotations(annotationData);
timeline.markers(markers);
timeline.fit();
function handleSelectOption(id, setOptionFn) {
document.getElementById(id)?.addEventListener("change", (e) => {
if (!(e.target instanceof HTMLSelectElement)) {
return;
}
const { value } = e.target;
// Update the timelineOptions object with the user's selection and pass it to the Timeline's options function
setOptionFn(value);
timeline.options(merge({}, currentTheme.options, timelineOptions));
timeline.set(timelineData);
timeline.annotations(annotationData);
});
}
handleSelectOption("theme-select", (value) => {
let theme;
switch (value) {
case "light":
theme = LIGHT_THEME;
break;
case "dark":
theme = DARK_THEME;
break;
case "default":
default:
theme = DEFAULT_THEME;
}
currentTheme = theme;
const charts = cloneDeep(data.timeSeriesCharts);
Object.keys(charts).forEach((x) => {
const key = x;
charts[key] = { ...charts[key], ...theme.charts };
});
annotationData = cloneDeep(annotations);
Object.keys(annotationData).forEach((x) => {
const key = x;
annotationData[key] = { ...annotationData[key], ...theme.annotations };
});
timelineData = merge({}, data, {
timeSeriesCharts: charts,
entityTypes: theme.entityTypes,
eventTypes: theme.eventTypes,
});
});
handleSelectOption("heatmap-padding-select", (value) => {
timelineOptions.events.heatmapPadding = value === "true";
});
handleSelectOption("show-entity-lines-select", (value) => {
timelineOptions.entities.showLines = value;
});
handleSelectOption("font-family-select", (value) => {
timelineOptions.fontFamily = value;
});
handleSelectOption("label-area-select", (value) => {
timeline.labelAreaWidth(value);
}); const entities = {
"smith-johnathan-151": {
label: "John Smith",
},
"west-josephine-126": {
label: "Josephine West",
},
"roberts-nathaniel-023": {
label: "Nathan Roberts",
},
"baxter-eleanor-004": {
label: "Ella Baxter",
},
"baxter-josh-006": {
label: "Josh Baxter",
},
"carpenter-alan-007": {
label: "Alan Carpenter",
},
"carpenter-alan-009": {
label: "Vittorio Perrier",
},
"carpenter-alan-019": {
label: "Jehanne Alden",
},
"carpenter-alan-020": {
label: "Zita Guillot",
},
"carpenter-alan-021": {
label: "Romanos Stojanov",
},
"carpenter-alan-022": {
label: "Yaeko Östberg",
},
"carpenter-alan-023": {
label: "Pravin O'Keefe",
},
"carpenter-alan-024": {
label: "Aureliano Wellington",
},
"carpenter-alan-025": {
label: "Demetrios Marvin",
},
"carpenter-alan-026": {
label: "Erik Little",
},
"carpenter-alan-027": {
label: "Raymond May",
},
"carpenter-alan-028": {
label: "Harland Stephenson",
},
"carpenter-alan-029": {
label: "Wendell Camacho",
},
"carpenter-alan-030": {
label: "Chauncey Li",
},
"carpenter-alan-031": {
label: "Julia Pena",
},
"carpenter-alan-032": {
label: "Ike Fuentes",
},
"carpenter-alan-033": {
label: "Wyatt Galvan",
},
"carpenter-alan-034": {
label: "Joseph Hartman",
},
"carpenter-alan-035": {
label: "Felton Ray",
},
"carpenter-alan-036": {
label: "Tasha Boyer",
},
"carpenter-alan-037": {
label: "Jame Allison",
},
"carpenter-alan-038": {
label: "Jesse Hayes",
},
"carpenter-alan-039": {
label: "Ambrose Mcguire",
},
"carpenter-alan-040": {
label: "Cody Yang",
},
"carpenter-alan-041": {
label: "Malcom Ware",
},
"carpenter-alan-042": {
label: "Dion Gay",
},
"carpenter-alan-043": {
label: "Jimmie Collins",
},
};
const eventCount = 200;
const baseTime = Date.UTC(2025, 7, 14, 8);
const timeRange = 10 * 60 * 60 * 1000;
const ordering = "alphabetical";
const entityIds = Object.keys(entities);
function randomMemberOf(arr) {
return arr[Math.floor(Math.random() * arr.length)];
}
function randomTime() {
return baseTime + Math.round(timeRange * Math.random());
}
function generateEvents() {
const events = {};
for (let i = 1; i <= eventCount; i++) {
const id = `Email ${i}`;
const fromId = randomMemberOf(entityIds);
let toId;
do {
toId = randomMemberOf(entityIds);
} while (fromId === toId);
const time = randomTime();
events[id] = { entityIds: [fromId, toId], time };
}
return events;
}
function generateTimeSeriesChart() {
const data = [];
let value = 0;
for (let time = baseTime; time < baseTime + timeRange; time += 1000) {
data.push({ time, value });
value += Math.random() - 0.5;
}
return { label: "Total Activity", data };
}
const events = generateEvents();
export default {
entities,
events,
ordering,
timeSeriesCharts: { chartA: generateTimeSeriesChart() },
};
export const annotations = {
annotation1: {
label: "This is an email",
subject: Object.keys(events)[0],
},
};
export const markers = [{ time: randomTime(), label: "Marker 1" }]; <!doctype html>
<html>
<head>
<link rel="stylesheet" href="@ci/theme/kg/css/examples.css" />
</head>
<body>
<div class="story">
<div class="story__timeline" id="my-timeline"></div>
<div class="story__controls story__controls--row">
<div class="stack">
<h2 class="story__controls__heading">Timeline</h2>
<div class="story__controls__item">
<label for="theme-select">Theme</label>
<select id="theme-select" class="select">
<option value="default">Default</option>
<option value="dark">Dark</option>
<option value="light">Light</option>
</select>
</div>
<div class="story__controls__item">
<label for="label-area-select">Label Area Width</label>
<select id="label-area-select" class="select">
<option value="auto" selected>Auto</option>
<option value="fixed">Fixed</option>
</select>
</div>
</div>
<div class="stack">
<h2 class="story__controls__heading"> </h2>
<div class="story__controls__item">
<label for="show-entity-lines-select">Entity Lines</label>
<select id="show-entity-lines-select" class="select">
<option value="always">Always</option>
<option value="individualEvents" selected>Individual Events</option>
<option value="never">Never</option>
</select>
</div>
<div class="story__controls__item">
<label for="font-family-select">Font Family</label>
<select id="font-family-select" class="select">
<option value="Arial">Arial</option>
<option value="Brush Script MT">Brush Script MT</option>
<option value="Comic Sans MS">Comic Sans MS</option>
<option value="Courier New">Courier New</option>
<option value="Garamond">Garamond</option>
<option value="Georgia">Georgia</option>
<option value="Helvetica">Helvetica</option>
<option value="sans-serif" selected>sans-serif</option>
<option value="serif">serif</option>
<option value="Tahoma">Tahoma</option>
<option value="Times New Roman">Times New Roman</option>
<option value="Trebuchet MS">Trebuchet MS</option>
<option value="Verdana">Verdana</option>
</select>
</div>
</div>
<div class="stack">
<h2 class="story__controls__heading"> </h2>
<div class="story__controls__item">
<label for="heatmap-padding-select">Heatmap Padding</label>
<select id="heatmap-padding-select" class="select">
<option value="true" selected>True</option>
<option value="false">False</option>
</select>
</div>
</div>
</div>
</div>
<script type="module" src="./code.js"></script>
</body>
</html> import React, { useMemo, useState } from "react";
import { createRoot } from "react-dom/client";
import Timeline from "kronograph/react/Timeline";
import data, { markers, annotations } from "./data";
import { merge } from "lodash";
const DEFAULT_THEME = {
options: {},
entityTypes: {},
eventTypes: {},
charts: {},
annotations: {},
};
const DARK_THEME = {
options: {
backgroundColor: "#0a0a0a",
focus: {
backgroundColor: "#421900",
},
scales: {
borderColor: "#777777",
textColor: "#ffffff",
},
highlightColor: "#ffffff",
scaleGuide: {
lineColor: "#ffffff",
textColor: "#ffffff",
},
entities: {
labelBackgroundColor: "#0a0a0a",
},
events: {
heatmapColor: "#E6C700",
},
controls: {
deleteAnnotation: {
backgroundColor: "#2f2f2f",
color: "#ffffff",
hoverBackgroundColor: "#421900",
hoverColor: "#D15000",
},
editAnnotation: {
backgroundColor: "#2f2f2f",
color: "#ffffff",
hoverBackgroundColor: "#421900",
hoverColor: "#D15000",
},
lens: {
color: "#D15000",
},
},
},
entityTypes: {
default: {
color: "#aaaaaa",
labelColor: "#ffffff",
},
},
eventTypes: {
default: {
color: "#E6C700",
},
},
charts: {
color: "#FFDD00",
labelColor: "#ffffff",
},
annotations: {
borderColor: "#1f1f1f",
fillColor: "#1f1f1f",
labelColor: "#FFFFFF",
borderWidth: 2,
connectorStyle: {
color: "#FFFFFF",
},
},
};
const LIGHT_THEME = {
options: {
backgroundColor: "#FFFFFF",
focus: {
backgroundColor: "#E5EDFF",
},
scales: {
borderColor: "#aaaaaa",
textColor: "#000000",
},
highlightColor: "#888888",
scaleGuide: {
lineColor: "#000000",
textColor: "#000000",
},
controls: {
deleteAnnotation: {
backgroundColor: "#dddddd",
color: "#000000",
hoverBackgroundColor: "#E5EDFF",
hoverColor: "#0035BD",
},
editAnnotation: {
backgroundColor: "#dddddd",
color: "#000000",
hoverBackgroundColor: "#E5EDFF",
hoverColor: "#0035BD",
},
lens: {
color: "#0035BD",
},
},
entities: {
labelBackgroundColor: "#FFFFFF",
},
events: {
heatmapColor: "#8747A9",
},
},
entityTypes: {
default: {
color: "#888888",
labelColor: "#000000",
},
},
eventTypes: {
default: {
color: "#8747A9",
},
},
charts: {
color: "#8747A9",
labelColor: "#000000",
},
annotations: {
borderColor: "#eeeeee",
fillColor: "#eeeeee",
labelColor: "#000000",
borderWidth: 2,
connectorStyle: {
color: "#000000",
},
},
};
export const Demo = () => {
const [theme, setTheme] = useState("default");
const [fontFamily, setFontFamily] = useState("sans-serif");
const [showEntityLines, setShowEntityLines] = useState("individualEvents");
const [showHeatmapPadding, setShowHeatmapPadding] = useState(true);
const [labelAreaWidth, setLabelAreaWidth] = useState({ mode: "auto" });
const selectedTheme = useMemo(() => {
switch (theme) {
case "dark":
return DARK_THEME;
case "light":
return LIGHT_THEME;
case "default":
default:
return DEFAULT_THEME;
}
}, [theme]);
const timeSeriesCharts = useMemo(() => {
const result = {};
const keys = Object.keys(data.timeSeriesCharts);
let id;
for (id of keys) {
result[id] = { ...data.timeSeriesCharts[id], ...selectedTheme.charts };
}
return result;
}, [selectedTheme, data.timeSeriesCharts]);
const finalAnnotations = useMemo(() => {
const result = {};
const keys = Object.keys(annotations);
let id;
for (id of keys) {
result[id] = { ...annotations[id], ...selectedTheme.annotations };
}
return result;
}, [selectedTheme, annotations]);
const options = {
fontFamily,
entities: {
showLines: showEntityLines,
},
events: {
heatmapPadding: showHeatmapPadding,
heatmapThreshold: 100,
},
};
return (
<div className="story">
<Timeline
className="story__timeline"
entities={data.entities}
events={data.events}
entityTypes={selectedTheme.entityTypes}
eventTypes={selectedTheme.eventTypes}
markers={markers}
options={merge(selectedTheme.options, options)}
timeSeriesCharts={timeSeriesCharts}
labelAreaWidth={labelAreaWidth}
annotations={finalAnnotations}
/>
<div className="story__controls story__controls--row">
<div className="stack">
<h2 className="story__controls__heading">Timeline</h2>
<div className="story__controls__item">
<label htmlFor="theme-select">Theme</label>
<select
id="theme-select"
className="select"
defaultValue="default"
onChange={(e) => {
setTheme(e.target.value);
}}
>
<option value="default">Default</option>
<option value="dark">Dark</option>
<option value="light">Light</option>
</select>
</div>
<div className="story__controls__item">
<label htmlFor="label-area-select">Label Area Width</label>
<select
id="label-area-select"
className="select"
defaultValue="auto"
onChange={(e) => {
setLabelAreaWidth({ mode: e.target.value });
}}
>
<option value="auto">Auto</option>
<option value="fixed">Fixed</option>
</select>
</div>
</div>
<div className="stack">
<h2 className="story__controls__heading"> </h2>
<div className="story__controls__item">
<label htmlFor="show-entity-lines-select">Entity Lines</label>
<select
id="show-entity-lines-select"
className="select"
defaultValue="individualEvents"
onChange={(e) => {
setShowEntityLines(e.target.value);
}}
>
<option value="always">Always</option>
<option value="individualEvents">Individual Events</option>
<option value="never">Never</option>
</select>
</div>
<div className="story__controls__item">
<label htmlFor="font-family-select">Font Family</label>
<select
id="font-family-select"
className="select"
defaultValue="sans-serif"
onChange={(e) => {
setFontFamily(e.target.value);
}}
>
<option value="Arial">Arial</option>
<option value="Brush Script MT">Brush Script MT</option>
<option value="Comic Sans MS">Comic Sans MS</option>
<option value="Courier New">Courier New</option>
<option value="Garamond">Garamond</option>
<option value="Georgia">Georgia</option>
<option value="Helvetica">Helvetica</option>
<option value="sans-serif">sans-serif</option>
<option value="serif">serif</option>
<option value="Tahoma">Tahoma</option>
<option value="Times New Roman">Times New Roman</option>
<option value="Trebuchet MS">Trebuchet MS</option>
<option value="Verdana">Verdana</option>
</select>
</div>
</div>
<div className="stack">
<h2 className="story__controls__heading"> </h2>
<div className="story__controls__item">
<label htmlFor="heatmap-padding-select">Heatmap Padding</label>
<select
id="heatmap-padding-select"
className="select"
defaultValue="true"
onChange={(e) => {
setShowHeatmapPadding(e.target.value === "true");
}}
>
<option value="true">True</option>
<option value="false">False</option>
</select>
</div>
</div>
</div>
</div>
);
};
const root = createRoot(document.getElementById("my-timeline"));
root.render(<Demo />); const entities = {
"smith-johnathan-151": {
label: "John Smith",
},
"west-josephine-126": {
label: "Josephine West",
},
"roberts-nathaniel-023": {
label: "Nathan Roberts",
},
"baxter-eleanor-004": {
label: "Ella Baxter",
},
"baxter-josh-006": {
label: "Josh Baxter",
},
"carpenter-alan-007": {
label: "Alan Carpenter",
},
"carpenter-alan-009": {
label: "Vittorio Perrier",
},
"carpenter-alan-019": {
label: "Jehanne Alden",
},
"carpenter-alan-020": {
label: "Zita Guillot",
},
"carpenter-alan-021": {
label: "Romanos Stojanov",
},
"carpenter-alan-022": {
label: "Yaeko Östberg",
},
"carpenter-alan-023": {
label: "Pravin O'Keefe",
},
"carpenter-alan-024": {
label: "Aureliano Wellington",
},
"carpenter-alan-025": {
label: "Demetrios Marvin",
},
"carpenter-alan-026": {
label: "Erik Little",
},
"carpenter-alan-027": {
label: "Raymond May",
},
"carpenter-alan-028": {
label: "Harland Stephenson",
},
"carpenter-alan-029": {
label: "Wendell Camacho",
},
"carpenter-alan-030": {
label: "Chauncey Li",
},
"carpenter-alan-031": {
label: "Julia Pena",
},
"carpenter-alan-032": {
label: "Ike Fuentes",
},
"carpenter-alan-033": {
label: "Wyatt Galvan",
},
"carpenter-alan-034": {
label: "Joseph Hartman",
},
"carpenter-alan-035": {
label: "Felton Ray",
},
"carpenter-alan-036": {
label: "Tasha Boyer",
},
"carpenter-alan-037": {
label: "Jame Allison",
},
"carpenter-alan-038": {
label: "Jesse Hayes",
},
"carpenter-alan-039": {
label: "Ambrose Mcguire",
},
"carpenter-alan-040": {
label: "Cody Yang",
},
"carpenter-alan-041": {
label: "Malcom Ware",
},
"carpenter-alan-042": {
label: "Dion Gay",
},
"carpenter-alan-043": {
label: "Jimmie Collins",
},
};
const eventCount = 200;
const baseTime = Date.UTC(2025, 7, 14, 8);
const timeRange = 10 * 60 * 60 * 1000;
const ordering = "alphabetical";
const entityIds = Object.keys(entities);
function randomMemberOf(arr) {
return arr[Math.floor(Math.random() * arr.length)];
}
function randomTime() {
return baseTime + Math.round(timeRange * Math.random());
}
function generateEvents() {
const events = {};
for (let i = 1; i <= eventCount; i++) {
const id = `Email ${i}`;
const fromId = randomMemberOf(entityIds);
let toId;
do {
toId = randomMemberOf(entityIds);
} while (fromId === toId);
const time = randomTime();
events[id] = { entityIds: [fromId, toId], time };
}
return events;
}
function generateTimeSeriesChart() {
const data = [];
let value = 0;
for (let time = baseTime; time < baseTime + timeRange; time += 1000) {
data.push({ time, value });
value += Math.random() - 0.5;
}
return { label: "Total Activity", data };
}
const events = generateEvents();
export default {
entities,
events,
ordering,
timeSeriesCharts: { chartA: generateTimeSeriesChart() },
};
export const annotations = {
annotation1: {
label: "This is an email",
subject: Object.keys(events)[0],
},
};
export const markers = [{ time: randomTime(), label: "Marker 1" }]; <!doctype html>
<html>
<head>
<link rel="stylesheet" href="@ci/theme/kg/css/examples.css" />
</head>
<body>
<div id="my-timeline" style="height: 100vh"></div>
<script type="module" src="./code.jsx"></script>
</body>
</html>