Interact with the timeline using mouse, keyboard and touch and see the object passed to the event handler in the Event Details tab.
You can also view the list of fired events or select which events you want to include or exclude during interactions.
See Also
Interact with the timeline using mouse, keyboard and touch and see the object passed to the event handler in the Event Details tab.
You can also view the list of fired events or select which events you want to include or exclude during interactions.
See Also
import { createTimeline } from "kronograph";
import data from "./data";
function byId(id) {
return document.getElementById(id);
}
function checkboxValue(id) {
return byId(id).checked === true;
}
function setEventSelectionMode() {
const eventSelectionMode = checkboxValue("event-selection-mode");
if (eventSelectionMode) {
timeline.eventSelectionMode(true);
} else {
timeline.eventSelection([]);
timeline.eventSelectionMode(false);
}
}
function setEntitySelectionEnabled() {
const entitySelectionEnabled = checkboxValue("entity-selection-enabled");
if (entitySelectionEnabled) {
timeline.entitySelectionEnabled(true);
} else {
timeline.entitySelection([]);
timeline.entitySelectionEnabled(false);
}
}
function setupTabPanels() {
const tabNames = ["last-event", "history", "filter-events"];
const tabs = tabNames.map((tabName) => document.getElementById(`${tabName}-tab`));
const panels = tabNames.map((tabName) => document.getElementById(`${tabName}-panel`));
tabs.forEach((tab, tabIdx) => {
const panel = panels[tabIdx];
tab.addEventListener("click", () => {
tabs.forEach((tabElement) => tabElement.classList.remove("tab--active"));
panels.forEach((panelElement) => panelElement.classList.remove("tab-panel--active"));
panel.classList.add("tab-panel--active");
tab.classList.add("tab--active");
});
});
}
function populateDetailsPanel({ name, details }) {
eventNameHeader.innerHTML = name;
eventDetailsElement.innerText = details;
}
function selectListElement(listElement) {
previousDisplayedListElement = displayedListElement;
displayedListElement = listElement;
displayedListElement.classList.add("list-item--active");
if (previousDisplayedListElement !== undefined) {
previousDisplayedListElement.classList.remove("list-item--active");
}
const { type: eventName, info: eventDetails } = JSON.parse(
displayedListElement.getAttribute("data-event-details")
);
populateDetailsPanel({
name: eventName,
details: eventDetails ? JSON.stringify(eventDetails, null, 2) : "",
});
}
function addEvent(event) {
const listElement = document.createElement("li");
listElement.classList.add("list-item");
listElement.innerText = event.type;
listElement.setAttribute("data-event-details", JSON.stringify(event));
eventListElement.insertBefore(listElement, eventListElement.querySelector("li"));
return listElement;
}
function setActiveModifiers(modifierKeys) {
Object.entries(modifierKeys).forEach(([modifierKey, isActive]) => {
const span = modifiersContainer.querySelector(`[data-event-name="${modifierKey}"]`);
if (isActive) {
span.classList.add("modifier-key--active");
} else {
span.classList.remove("modifier-key--active");
}
});
}
function genericEventHandler(eventName, event) {
if (selectedEvents[eventName]) {
const newListElement = addEvent({ type: eventName, info: event });
// display the details for the new event
selectListElement(newListElement);
// show which modifier keys are down
if (event && event.modifierKeys) {
setActiveModifiers(event.modifierKeys);
}
}
}
const modifiersContainer = document.getElementById("modifiers");
const eventListElement = document.getElementById("event-list");
const clearEventsListButton = document.getElementById("clear-event-list");
const eventNameHeader = document.querySelector(".header--plain");
const eventDetailsElement = document.querySelector(".json-block");
const eventCheckboxes = [...document.querySelectorAll('.event-filter-list input[type="checkbox"]')];
const selectAll = document.getElementById("select-all");
const deselectAll = document.getElementById("deselect-all");
const eventMap = {
annotations: true,
click: true,
"context-menu": true,
"double-click": true,
"drag-start": true,
"drag-move": true,
"drag-end": true,
draw: true,
"entity-selection-change": true,
"event-selection-change": true,
focus: true,
highlight: true,
hover: true,
"key-down": true,
"key-up": true,
lens: true,
pin: true,
"pointer-down": true,
"pointer-up": true,
"pointer-move": true,
range: true,
wheel: true,
};
const initialSelectedEvents = {
...eventMap,
draw: false,
highlight: false,
"pointer-move": false,
};
let selectedEvents = initialSelectedEvents;
let displayedListElement;
let previousDisplayedListElement;
setupTabPanels();
clearEventsListButton.addEventListener("click", () => {
eventListElement.innerHTML = "";
populateDetailsPanel({ name: " ", details: "{}" });
});
eventListElement.addEventListener("click", (evt) => {
if (evt.target instanceof HTMLLIElement) {
selectListElement(evt.target);
}
});
selectAll.addEventListener("click", () => {
eventCheckboxes.forEach((checkbox) => {
checkbox.checked = true;
});
selectedEvents = eventMap;
});
deselectAll.addEventListener("click", () => {
eventCheckboxes.forEach((checkbox) => {
checkbox.checked = false;
});
selectedEvents = {};
});
eventCheckboxes.forEach((inputElement) => {
inputElement.addEventListener("change", (evt) => {
const eventName = evt.target.getAttribute("data-event-name");
selectedEvents = { ...selectedEvents, [eventName]: evt.target.checked };
});
});
byId("event-selection-mode")?.addEventListener("click", setEventSelectionMode);
byId("entity-selection-enabled")?.addEventListener("click", setEntitySelectionEnabled);
const timeline = createTimeline("my-timeline");
timeline.set({
...data,
entityTypes: {
ip: {
groupBy: ["country", "continent"],
typeLabel: "IP Addresses",
},
},
});
timeline.options({ events: { heatmapThreshold: 30 } });
timeline.annotations({
annotation1: { label: "This is an annotation", subject: ["event-12"] },
});
timeline.fit();
setEventSelectionMode();
setEntitySelectionEnabled();
// register event handlers
Object.keys(eventMap).forEach((eventName) => {
timeline.on(eventName, (event) => genericEventHandler(eventName, event));
}); export default {
entities: {
"119.184.100.174": {
type: "ip",
data: {
continent: "South America",
country: "Brazil",
},
color: "#2676c7",
},
"80.255.245.230": {
type: "ip",
data: {
continent: "Europe",
country: "Germany",
},
color: "#f7d06e",
},
"78.248.27.2": {
type: "ip",
data: {
continent: "Europe",
country: "Spain",
},
color: "#f7d06e",
},
"27.195.134.201": {
type: "ip",
data: {
continent: "Asia",
country: "South Korea",
},
color: "#f86868",
},
"101.226.51.230": {
type: "ip",
data: {
continent: "Asia",
country: "South Korea",
},
color: "#f86868",
},
"123.184.218.198": {
type: "ip",
data: {
continent: "Asia",
country: "South Korea",
},
color: "#f86868",
},
"191.7.178.2": {
type: "ip",
data: {
continent: "South America",
country: "Brazil",
},
color: "#2676c7",
},
"118.37.6.215": {
type: "ip",
data: {
continent: "South America",
country: "Argentina",
},
color: "#2676c7",
},
"1.180.208.152": {
type: "ip",
data: {
continent: "Asia",
country: "Japan",
},
color: "#f86868",
},
"81.161.59.17": {
type: "ip",
data: {
continent: "Europe",
country: "Germany",
},
color: "#f7d06e",
},
"163.172.66.164": {
type: "ip",
data: {
continent: "Europe",
country: "United Kingdom",
},
color: "#f7d06e",
},
"65.48.139.133": {
type: "ip",
data: {
continent: "North America",
country: "United States",
},
color: "#5dbbb4",
},
"66.249.64.128": {
type: "ip",
data: {
continent: "North America",
country: "United States",
},
color: "#5dbbb4",
},
"117.144.246.77": {
type: "ip",
data: {
continent: "Asia",
country: "Thailand",
},
color: "#f86868",
},
"1.180.208.148": {
type: "ip",
data: {
continent: "Asia",
country: "Thailand",
},
color: "#f86868",
},
"213.184.105.182": {
type: "ip",
data: {
continent: "Europe",
country: "United Kingdom",
},
color: "#f7d06e",
},
"138.128.6.115": {
type: "ip",
data: {
continent: "North America",
country: "United States",
},
color: "#5dbbb4",
},
"163.172.66.171": {
type: "ip",
data: {
continent: "Europe",
country: "United Kingdom",
},
color: "#f7d06e",
},
"101.226.125.119": {
type: "ip",
data: {
continent: "Asia",
country: "Thailand",
},
color: "#f86868",
},
"163.172.66.15": {
type: "ip",
data: {
continent: "Europe",
country: "Spain",
},
color: "#f7d06e",
},
"101.226.33.217": {
type: "ip",
data: {
continent: "South America",
country: "Peru",
},
color: "#2676c7",
},
"131.161.224.94": {
type: "ip",
data: {
continent: "South America",
country: "Peru",
},
color: "#2676c7",
},
"179.181.219.24": {
type: "ip",
data: {
continent: "South America",
country: "Argentina",
},
color: "#2676c7",
},
"195.55.121.5": {
type: "ip",
data: {
continent: "Europe",
country: "Spain",
},
color: "#f7d06e",
},
"163.172.66.40": {
type: "ip",
data: {
continent: "Europe",
country: "Germany",
},
color: "#f7d06e",
},
"220.181.108.103": {
type: "ip",
data: {
continent: "Asia",
country: "Japan",
},
color: "#f86868",
},
"179.181.219.44": {
type: "ip",
data: {
continent: "South America",
country: "Peru",
},
color: "#2676c7",
},
"220.181.108.109": {
type: "ip",
data: {
continent: "Asia",
country: "Japan",
},
color: "#f86868",
},
"222.182.107.110": {
type: "ip",
data: {
continent: "North America",
country: "Mexico",
},
color: "#5dbbb4",
},
"222.184.107.114": {
type: "ip",
data: {
continent: "North America",
country: "Mexico",
},
color: "#5dbbb4",
},
"222.184.1033.144": {
type: "ip",
data: {
continent: "North America",
country: "Mexico",
},
color: "#5dbbb4",
},
"157.55.39.189": {
type: "ip",
data: {
continent: "South America",
country: "Argentina",
},
color: "#2676c7",
},
"157.55.39.1838": {
type: "ip",
data: {
continent: "North America",
country: "Canada",
},
color: "#5dbbb4",
},
"157.55.39.1828": {
type: "ip",
data: {
continent: "North America",
country: "Canada",
},
color: "#5dbbb4",
},
"157.55.39.122": {
type: "ip",
data: {
continent: "South America",
country: "Brazil",
},
color: "#2676c7",
},
"157.55.39.788": {
type: "ip",
data: {
continent: "North America",
country: "Canada",
},
color: "#5dbbb4",
},
},
events: {
"event-0": { entityIds: ["119.184.100.174", "123.184.218.198"], time: 1629372675131 },
"event-1": { entityIds: ["80.255.245.230", "27.195.134.201"], time: 1629372681796 },
"event-2": { entityIds: ["78.248.27.2", "163.172.66.40"], time: 1629372725809 },
"event-3": { entityIds: ["27.195.134.201", "27.195.134.201"], time: 1629372759572 },
"event-4": { entityIds: ["101.226.51.230", "157.55.39.189"], time: 1629372815459 },
"event-5": { entityIds: ["123.184.218.198", "101.226.33.217"], time: 1629372855089 },
"event-6": { entityIds: ["191.7.178.2", "191.7.178.2"], time: 1629372966112 },
"event-7": { entityIds: ["118.37.6.215", "81.161.59.17"], time: 1629372996298 },
"event-8": { entityIds: ["1.180.208.152", "101.226.33.217"], time: 1629373110447 },
"event-9": { entityIds: ["81.161.59.17", "220.181.108.103"], time: 1629373112501 },
"event-10": { entityIds: ["163.172.66.164", "65.48.139.133"], time: 1629373122325 },
"event-11": { entityIds: ["65.48.139.133", "220.181.108.109"], time: 1629373137553 },
"event-12": { entityIds: ["66.249.64.128", "131.161.224.94"], time: 1629373183424 },
"event-13": { entityIds: ["117.144.246.77", "119.184.100.174"], time: 1629373242803 },
"event-14": { entityIds: ["1.180.208.148", "191.7.178.2"], time: 1629373261835 },
"event-15": { entityIds: ["213.184.105.182", "157.55.39.1838"], time: 1629373292078 },
"event-16": { entityIds: ["138.128.6.115", "101.226.51.230"], time: 1629373304447 },
"event-17": { entityIds: ["163.172.66.171", "163.172.66.40"], time: 1629373309421 },
"event-18": { entityIds: ["101.226.125.119", "179.181.219.44"], time: 1629373309589 },
"event-19": { entityIds: ["163.172.66.15", "81.161.59.17"], time: 1629373367674 },
"event-20": { entityIds: ["101.226.33.217", "220.181.108.103"], time: 1629373392081 },
"event-21": { entityIds: ["131.161.224.94", "101.226.125.119"], time: 1629373402184 },
"event-22": { entityIds: ["179.181.219.24", "65.48.139.133"], time: 1629373427921 },
"event-23": {
entityIds: ["195.55.121.5", "157.55.39.122"],
time: 1629373485503,
label: { text: "This is an event label" },
},
"event-24": { entityIds: ["163.172.66.40", "157.55.39.1828"], time: 1629373527572 },
"event-25": { entityIds: ["220.181.108.103", "65.48.139.133"], time: 1629373563266 },
"event-26": { entityIds: ["179.181.219.44", "123.184.218.198"], time: 1629373632758 },
"event-27": { entityIds: ["220.181.108.109", "157.55.39.788"], time: 1629373671302 },
"event-28": { entityIds: ["222.182.107.110", "101.226.51.230"], time: 1629373689030 },
"event-29": {
entityIds: ["222.184.107.114", "222.182.107.110"],
time: 1629373711184,
},
"event-30": { entityIds: ["222.184.1033.144", "65.48.139.133"], time: 1629373763739 },
"event-31": { entityIds: ["157.55.39.189", "157.55.39.1838"], time: 1629373814735 },
"event-32": { entityIds: ["157.55.39.1838", "65.48.139.133"], time: 1629373970716 },
"event-33": { entityIds: ["157.55.39.1828", "157.55.39.1828"], time: 1629373997509 },
"event-34": { entityIds: ["157.55.39.122", "157.55.39.1838"], time: 1629374017194 },
"event-35": { entityIds: ["157.55.39.788", "195.55.121.5"], time: 1629374087269 },
"event-36": { entityIds: ["157.55.39.788", "195.55.121.5"], time: 1629374087269 },
},
timeSeriesCharts: {
networkPerformance: {
color: "#ff8838",
label: "Network Performance",
labelColor: "#ff8838",
data: [
{ time: 1629372675131, value: 28 },
{ time: 1629372681796, value: 28 },
{ time: 1629372701960, value: 27 },
{ time: 1629372725809, value: 25 },
{ time: 1629372759572, value: 27.5 },
{ time: 1629372815459, value: 28 },
{ time: 1629372855089, value: 27 },
{ time: 1629372920345, value: 26 },
{ time: 1629372966112, value: 23 },
{ time: 1629372996298, value: 22 },
{ time: 1629373026503, value: 24 },
{ time: 1629373093819, value: 24.5 },
{ time: 1629373110447, value: 24 },
{ time: 1629373112501, value: 23.5 },
{ time: 1629373122325, value: 23 },
{ time: 1629373134240, value: 23 },
{ time: 1629373137553, value: 22 },
{ time: 1629373140815, value: 22 },
{ time: 1629373183424, value: 21.5 },
{ time: 1629373242803, value: 28 },
{ time: 1629373261835, value: 27 },
{ time: 1629373292078, value: 28 },
{ time: 1629373304447, value: 28.5 },
{ time: 1629373309421, value: 28 },
{ time: 1629373309589, value: 28 },
{ time: 1629373353664, value: 27 },
{ time: 1629373367674, value: 26.5 },
{ time: 1629373392081, value: 25 },
{ time: 1629373402184, value: 24.8 },
{ time: 1629373427921, value: 23 },
{ time: 1629373485503, value: 24 },
{ time: 1629373506794, value: 23 },
{ time: 1629373514358, value: 22.5 },
{ time: 1629373527572, value: 21 },
{ time: 1629373540573, value: 21 },
{ time: 1629373563266, value: 20.5 },
{ time: 1629373566218, value: 20.5 },
{ time: 1629373632758, value: 19.5 },
{ time: 1629373660451, value: 20 },
{ time: 1629373671302, value: 21 },
{ time: 1629373689030, value: 20 },
{ time: 1629373699825, value: 22 },
{ time: 1629373711184, value: 21 },
{ time: 1629373736779, value: 22 },
{ time: 1629373763739, value: 22.5 },
{ time: 1629373806565, value: 21 },
{ time: 1629373814735, value: 23 },
{ time: 1629373913182, value: 28 },
{ time: 1629373970716, value: 28 },
{ time: 1629373996005, value: 27 },
{ time: 1629373997509, value: 26.5 },
{ time: 1629374017194, value: 27.5 },
{ time: 1629374048968, value: 28 },
{ time: 1629374087269, value: 27 },
],
},
},
}; <!doctype html>
<html>
<head>
<link rel="stylesheet" href="@ci/theme/kg/css/examples.css" />
<link rel="stylesheet" href="./style.css" />
</head>
<body>
<div class="story">
<div style="height: 100%; display: flex; flex-direction: column; max-height: 100%">
<div style="display: flex; flex: 1 1 auto; box-sizing: border-box; min-height: 0px">
<div id="modifiers" class="modifiers-container stack absolute">
<span class="modifier-key" data-event-name="alt">alt</span>
<span class="modifier-key" data-event-name="ctrl">ctrl</span>
<span class="modifier-key" data-event-name="consistentCtrl">consistentCtrl</span>
<span class="modifier-key" data-event-name="meta">meta</span>
<span class="modifier-key" data-event-name="shift">shift</span>
</div>
<div id="my-timeline" style="flex: 2 0 50%" class="story__timeline"></div>
<div class="tab-panel-container">
<div style="display: flex">
<button id="last-event-tab" class="tab tab--active">Event Details</button>
<button id="history-tab" class="tab">History</button>
<button id="filter-events-tab" class="tab">Filter Events</button>
</div>
<div id="last-event-panel" class="tab-panel tab-panel--active">
<h3 class="header--plain"> </h3>
<pre class="json-block">{}</pre>
</div>
<div
id="history-panel"
class="tab-panel"
style="flex-wrap: nowrap; align-items: center"
>
<button id="clear-event-list" class="button button--small" style="flex: 0 0 auto">
clear
</button>
<ul id="event-list" class="event-list"></ul>
</div>
<div id="filter-events-panel" class="tab-panel" style="flex-wrap: nowrap">
<div class="filter-buttons">
<button id="select-all" class="button button--small">select all</button>
<button id="deselect-all" class="button button--small">deselect all</button>
</div>
<div class="event-filter-list">
<label class="event-label">
<input checked="true" type="checkbox" data-event-name="annotations" />annotations
</label>
<label class="event-label">
<input checked="true" type="checkbox" data-event-name="click" />click
</label>
<label class="event-label">
<input
checked="true"
type="checkbox"
data-event-name="context-menu"
/>context-menu
</label>
<label class="event-label">
<input
checked="true"
type="checkbox"
data-event-name="double-click"
/>double-click
</label>
<label class="event-label">
<input checked="true" type="checkbox" data-event-name="drag-start" />drag-start
</label>
<label class="event-label">
<input checked="true" type="checkbox" data-event-name="drag-move" />drag-move
</label>
<label class="event-label">
<input checked="true" type="checkbox" data-event-name="drag-end" />drag-end
</label>
<label class="event-label">
<input type="checkbox" data-event-name="draw" />draw
</label>
<label class="event-label">
<input
checked="true"
type="checkbox"
data-event-name="entity-selection-change"
/>entity-selection-change
</label>
<label class="event-label">
<input
checked="true"
type="checkbox"
data-event-name="event-selection-change"
/>event-selection-change
</label>
<label class="event-label">
<input checked="true" type="checkbox" data-event-name="focus" />focus
</label>
<label class="event-label">
<input type="checkbox" data-event-name="highlight" />highlight
</label>
<label class="event-label">
<input checked="true" type="checkbox" data-event-name="hover" />hover
</label>
<label class="event-label">
<input checked="true" type="checkbox" data-event-name="key-down" />key-down
</label>
<label class="event-label">
<input checked="true" type="checkbox" data-event-name="key-up" />key-up
</label>
<label class="event-label">
<input checked="true" type="checkbox" data-event-name="lens" />lens
</label>
<label class="event-label">
<input checked="true" type="checkbox" data-event-name="pin" />pin
</label>
<label class="event-label">
<input
checked="true"
type="checkbox"
data-event-name="pointer-down"
/>pointer-down
</label>
<label class="event-label">
<input checked="true" type="checkbox" data-event-name="pointer-up" />pointer-up
</label>
<label class="event-label">
<input type="checkbox" data-event-name="pointer-move" />pointer-move
</label>
<label class="event-label">
<input checked="true" type="checkbox" data-event-name="range" />range
</label>
<label class="event-label">
<input checked="true" type="checkbox" data-event-name="wheel" />wheel
</label>
</div>
</div>
</div>
</div>
<div id="controls" class="controls-container">
<div class="row selection-switch gap-2">
Event Selection Mode
<label class="switch">
<input type="checkbox" id="event-selection-mode" />
<span class="slider round"></span>
</label>
</div>
<div class="row selection-switch gap-2">
Entity Selection Enabled
<label class="switch">
<input type="checkbox" id="entity-selection-enabled" />
<span class="slider round"></span>
</label>
</div>
</div>
</div>
</div>
<script type="module" src="./code.js"></script>
</body>
</html> #selection-switch {
display: flex;
flex-direction: row;
align-items: center;
white-space: nowrap;
}
.modifier-key {
margin: 0;
padding: 0px 10px;
}
.modifiers-container {
flex: 0 0 auto;
flex-wrap: wrap;
gap: 10px 2rem;
padding: 1rem;
align-items: start;
position: absolute;
top: 2rem;
display: flex;
flex-direction: column;
font-size: medium;
font-weight: bold;
pointer-events: none;
}
.controls-container {
display: flex;
flex-wrap: wrap;
gap: 10px 2rem;
padding: 0.5rem 0.5rem 0rem 0.5rem;
}
.row {
display: flex;
align-items: center;
}
.gap-2 {
column-gap: 1em;
}
.switch {
position: relative;
display: inline-block;
width: 25px;
height: 16px;
padding: 2px;
margin-left: 5px;
vertical-align: middle;
}
/* Hide default HTML checkbox */
.switch input {
opacity: 0;
width: 0;
height: 0;
}
/* The slider */
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var(--grey-color-300);
-webkit-transition: 0.4s;
transition: 0.4s;
}
.slider:before {
position: absolute;
content: "";
height: 12px;
width: 12px;
left: 2px;
bottom: 2px;
background-color: var(--white-color);
-webkit-transition: 0.4s;
transition: 0.4s;
}
input:checked + .slider {
background-color: var(--primary-color300);
}
input:focus + .slider {
box-shadow: 0 0 1px var(--primary-color300);
}
input:checked + .slider:before {
-webkit-transform: translateX(9px);
-ms-transform: translateX(9px);
transform: translateX(9px);
}
/* Rounded sliders */
.slider.round {
border-radius: 99px;
}
.slider.round:before {
border-radius: 50%;
}
.slider-label {
padding-left: 0.5rem;
vertical-align: middle;
} import React, { useRef, useState } from "react";
import { createRoot } from "react-dom/client";
import Timeline from "kronograph/react/Timeline";
import data from "./data";
const entityTypes = {
ip: {
groupBy: ["country", "continent"],
typeLabel: "IP Addresses",
},
};
const timelineOptions = { events: { heatmapThreshold: 30 } };
function FilterEvents({ changeHandler, selectedEvents }) {
return (
<div className="event-filter-list">
{Object.keys(eventMap).map((propName) => (
<label className="event-label" style={{ width: "265px" }} key={propName}>
<input
onChange={() => changeHandler(propName)}
checked={selectedEvents[propName]}
type="checkbox"
/>
{propName}
</label>
))}
</div>
);
}
function EventList({ events, handleClick, displayedEventIndex }) {
return (
<ul className="event-list">
{events.map((event, eventIdx) => (
<li
key={eventIdx}
className={`list-item${displayedEventIndex === eventIdx ? "list-item--active" : ""}`}
onClick={() => handleClick(eventIdx)}
>
{event.type}
</li>
))}
</ul>
);
}
function TabPanels({ tabs, setSelectedTab, selectedTab }) {
return (
<div className="tab-panel-container">
<div style={{ display: "flex" }}>
{tabs.map((tab) => (
<button
onClick={() => setSelectedTab(tab.id)}
className={`tab${selectedTab === tab.id ? "tab--active" : ""}`}
key={tab.id}
>
{tab.title}
</button>
))}
</div>
<div style={{ flexGrow: "1", position: "relative" }}>
<div style={{ position: "absolute", height: "100%", width: "100%" }}>
{tabs.map((tab) => (
<div
key={tab.id}
style={{ ...tab.panelStyle, overflow: "hidden" }}
className={`tab-panel${selectedTab === tab.id ? "tab-panel--active" : ""}`}
>
{tab.panel}
</div>
))}
</div>
</div>
</div>
);
}
const eventMap = {
onTimelineChange: true,
onTimelineContextMenu: true,
onTimelineClick: true,
onTimelineDoubleClick: true,
onTimelineDragEnd: true,
onTimelineDragMove: true,
onTimelineDragStart: true,
onTimelineDraw: true,
onTimelineHover: true,
onTimelineKeyDown: true,
onTimelineKeyUp: true,
onTimelinePointerDown: true,
onTimelinePointerMove: true,
onTimelinePointerUp: true,
onTimelineWheel: true,
};
export const Demo = () => {
const [eventList, setEventList] = useState([]);
const [activeModifiers, setActiveModifiers] = useState({});
const [selectedTab, setSelectedTab] = useState("event-details");
const [selectedEvents, setSelectedEvents] = useState({
...eventMap,
onTimelineDraw: false,
onTimelinePointerMove: false,
});
const [displayedEventIndex, setDisplayedEventIndex] = useState(0);
const [eventSelectionMode, setEventSelectionMode] = useState(false);
const [entitySelectionEnabled, setEntitySelectionEnabled] = useState(false);
const [annotations] = useState({
annotation1: { label: "This is an annotation", subject: ["event-12"] },
});
const eventSelectionSliderRef = useRef();
const entitySelectionSliderRef = useRef();
const timelineRef = useRef(null);
const displayedEvent = eventList[displayedEventIndex];
function handleSetEventSelectionMode() {
const eventSelectionModeValue = eventSelectionSliderRef.current.checked;
setEventSelectionMode(eventSelectionModeValue);
}
function handleSetEntitySelectionEnabled() {
const entitySelectionEnabledValue = entitySelectionSliderRef.current.checked;
setEntitySelectionEnabled(entitySelectionEnabledValue);
}
function Controls() {
return (
<div id="controls" className="controls-container">
<div className="row selection-switch gap-2">
Event Selection Mode
<label className="switch">
<input
type="checkbox"
id="selection-mode"
ref={eventSelectionSliderRef}
onChange={handleSetEventSelectionMode}
checked={eventSelectionMode}
/>
<span className="slider round"></span>
</label>
</div>
<div className="row selection-switch gap-2">
Entity Selection Enabled
<label className="switch">
<input
type="checkbox"
id="entity-selection-enabled"
ref={entitySelectionSliderRef}
onChange={handleSetEntitySelectionEnabled}
checked={entitySelectionEnabled}
/>
<span className="slider round"></span>
</label>
</div>
</div>
);
}
function Modifiers() {
return (
<div id="modifiers" className="modifiers-container">
{["alt", "ctrl", "consistentCtrl", "meta", "shift"].map((modifier) => (
<span
key={modifier}
className={`modifier-key${activeModifiers[modifier] ? "modifier-key--active" : ""}`}
>
{modifier}
</span>
))}
</div>
);
}
function genericEventHandler(propName, event) {
if (selectedEvents[propName]) {
setDisplayedEventIndex(0);
setEventList((prevEventList) => [{ type: propName, info: event }, ...prevEventList]);
if (event && event.modifierKeys) {
setActiveModifiers(event.modifierKeys);
}
}
}
const eventDetailsPanel = (
<>
<h3 className="header--plain">{displayedEvent ? displayedEvent.type : <br />}</h3>
<pre className="json-block">
{JSON.stringify(displayedEvent ? displayedEvent.info : {}, null, 2)}
</pre>
</>
);
const historyPanel = (
<>
<button
className="button button--small"
style={{ flex: "0 0 auto" }}
onClick={() => setEventList([])}
>
clear
</button>
<EventList
events={eventList}
handleClick={(idx) => setDisplayedEventIndex(idx)}
displayedEventIndex={displayedEventIndex}
/>
</>
);
const filterEventsPanel = (
<>
<div className="filter-buttons">
<button className="button button--small" onClick={() => setSelectedEvents(eventMap)}>
select all
</button>
<button className="button button--small" onClick={() => setSelectedEvents({})}>
deselect all
</button>
</div>
<FilterEvents
changeHandler={(propName) => {
setSelectedEvents({
...selectedEvents,
[propName]: !selectedEvents[propName],
});
}}
selectedEvents={selectedEvents}
/>
</>
);
return (
<div style={{ height: "100%", display: "flex", flexDirection: "column", maxHeight: "100%" }}>
<div
style={{
display: "flex",
flex: "1 0 auto",
boxSizing: "border-box",
}}
>
<div style={{ flex: "2 0 50%", position: "relative" }}>
<Timeline
style={{ position: "absolute", height: "100%", width: "100%" }}
className="story__timeline"
ref={timelineRef}
events={data.events}
entities={data.entities}
timeSeriesCharts={data.timeSeriesCharts}
entityTypes={entityTypes}
options={timelineOptions}
eventSelectionMode={eventSelectionMode}
entitySelectionEnabled={entitySelectionEnabled}
annotations={annotations}
onTimelineChange={(event) => genericEventHandler("onTimelineChange", event)}
onTimelineContextMenu={(event) => genericEventHandler("onTimelineContextMenu", event)}
onTimelineClick={(event) => genericEventHandler("onTimelineClick", event)}
onTimelineDoubleClick={(event) => genericEventHandler("onTimelineDoubleClick", event)}
onTimelineDragEnd={(event) => genericEventHandler("onTimelineDragEnd", event)}
onTimelineDragMove={(event) => genericEventHandler("onTimelineDragMove", event)}
onTimelineDragStart={(event) => genericEventHandler("onTimelineDragStart", event)}
onTimelineDraw={() => genericEventHandler("onTimelineDraw")}
onTimelineHover={(event) => genericEventHandler("onTimelineHover", event)}
onTimelineKeyDown={(event) => genericEventHandler("onTimelineKeyDown", event)}
onTimelineKeyUp={(event) => genericEventHandler("onTimelineKeyUp", event)}
onTimelinePointerDown={(event) => genericEventHandler("onTimelinePointerDown", event)}
onTimelinePointerMove={(event) => genericEventHandler("onTimelinePointerMove", event)}
onTimelinePointerUp={(event) => genericEventHandler("onTimelinePointerUp", event)}
onTimelineWheel={(event) => genericEventHandler("onTimelineWheel", event)}
/>
</div>
<TabPanels
tabs={[
{
title: "Event Details",
id: "event-details",
panel: eventDetailsPanel,
},
{
title: "History",
id: "event-history",
panelStyle: { flexWrap: "nowrap", alignItems: "center" },
panel: historyPanel,
},
{
title: "Filter Events",
id: "filter-events",
panelStyle: { flexWrap: "nowrap" },
panel: filterEventsPanel,
},
]}
setSelectedTab={setSelectedTab}
selectedTab={selectedTab}
/>
</div>
<Modifiers />
<Controls />
</div>
);
};
const root = createRoot(document.getElementById("my-timeline"));
root.render(<Demo />); export default {
entities: {
"119.184.100.174": {
type: "ip",
data: {
continent: "South America",
country: "Brazil",
},
color: "#2676c7",
},
"80.255.245.230": {
type: "ip",
data: {
continent: "Europe",
country: "Germany",
},
color: "#f7d06e",
},
"78.248.27.2": {
type: "ip",
data: {
continent: "Europe",
country: "Spain",
},
color: "#f7d06e",
},
"27.195.134.201": {
type: "ip",
data: {
continent: "Asia",
country: "South Korea",
},
color: "#f86868",
},
"101.226.51.230": {
type: "ip",
data: {
continent: "Asia",
country: "South Korea",
},
color: "#f86868",
},
"123.184.218.198": {
type: "ip",
data: {
continent: "Asia",
country: "South Korea",
},
color: "#f86868",
},
"191.7.178.2": {
type: "ip",
data: {
continent: "South America",
country: "Brazil",
},
color: "#2676c7",
},
"118.37.6.215": {
type: "ip",
data: {
continent: "South America",
country: "Argentina",
},
color: "#2676c7",
},
"1.180.208.152": {
type: "ip",
data: {
continent: "Asia",
country: "Japan",
},
color: "#f86868",
},
"81.161.59.17": {
type: "ip",
data: {
continent: "Europe",
country: "Germany",
},
color: "#f7d06e",
},
"163.172.66.164": {
type: "ip",
data: {
continent: "Europe",
country: "United Kingdom",
},
color: "#f7d06e",
},
"65.48.139.133": {
type: "ip",
data: {
continent: "North America",
country: "United States",
},
color: "#5dbbb4",
},
"66.249.64.128": {
type: "ip",
data: {
continent: "North America",
country: "United States",
},
color: "#5dbbb4",
},
"117.144.246.77": {
type: "ip",
data: {
continent: "Asia",
country: "Thailand",
},
color: "#f86868",
},
"1.180.208.148": {
type: "ip",
data: {
continent: "Asia",
country: "Thailand",
},
color: "#f86868",
},
"213.184.105.182": {
type: "ip",
data: {
continent: "Europe",
country: "United Kingdom",
},
color: "#f7d06e",
},
"138.128.6.115": {
type: "ip",
data: {
continent: "North America",
country: "United States",
},
color: "#5dbbb4",
},
"163.172.66.171": {
type: "ip",
data: {
continent: "Europe",
country: "United Kingdom",
},
color: "#f7d06e",
},
"101.226.125.119": {
type: "ip",
data: {
continent: "Asia",
country: "Thailand",
},
color: "#f86868",
},
"163.172.66.15": {
type: "ip",
data: {
continent: "Europe",
country: "Spain",
},
color: "#f7d06e",
},
"101.226.33.217": {
type: "ip",
data: {
continent: "South America",
country: "Peru",
},
color: "#2676c7",
},
"131.161.224.94": {
type: "ip",
data: {
continent: "South America",
country: "Peru",
},
color: "#2676c7",
},
"179.181.219.24": {
type: "ip",
data: {
continent: "South America",
country: "Argentina",
},
color: "#2676c7",
},
"195.55.121.5": {
type: "ip",
data: {
continent: "Europe",
country: "Spain",
},
color: "#f7d06e",
},
"163.172.66.40": {
type: "ip",
data: {
continent: "Europe",
country: "Germany",
},
color: "#f7d06e",
},
"220.181.108.103": {
type: "ip",
data: {
continent: "Asia",
country: "Japan",
},
color: "#f86868",
},
"179.181.219.44": {
type: "ip",
data: {
continent: "South America",
country: "Peru",
},
color: "#2676c7",
},
"220.181.108.109": {
type: "ip",
data: {
continent: "Asia",
country: "Japan",
},
color: "#f86868",
},
"222.182.107.110": {
type: "ip",
data: {
continent: "North America",
country: "Mexico",
},
color: "#5dbbb4",
},
"222.184.107.114": {
type: "ip",
data: {
continent: "North America",
country: "Mexico",
},
color: "#5dbbb4",
},
"222.184.1033.144": {
type: "ip",
data: {
continent: "North America",
country: "Mexico",
},
color: "#5dbbb4",
},
"157.55.39.189": {
type: "ip",
data: {
continent: "South America",
country: "Argentina",
},
color: "#2676c7",
},
"157.55.39.1838": {
type: "ip",
data: {
continent: "North America",
country: "Canada",
},
color: "#5dbbb4",
},
"157.55.39.1828": {
type: "ip",
data: {
continent: "North America",
country: "Canada",
},
color: "#5dbbb4",
},
"157.55.39.122": {
type: "ip",
data: {
continent: "South America",
country: "Brazil",
},
color: "#2676c7",
},
"157.55.39.788": {
type: "ip",
data: {
continent: "North America",
country: "Canada",
},
color: "#5dbbb4",
},
},
events: {
"event-0": { entityIds: ["119.184.100.174", "123.184.218.198"], time: 1629372675131 },
"event-1": { entityIds: ["80.255.245.230", "27.195.134.201"], time: 1629372681796 },
"event-2": { entityIds: ["78.248.27.2", "163.172.66.40"], time: 1629372725809 },
"event-3": { entityIds: ["27.195.134.201", "27.195.134.201"], time: 1629372759572 },
"event-4": { entityIds: ["101.226.51.230", "157.55.39.189"], time: 1629372815459 },
"event-5": { entityIds: ["123.184.218.198", "101.226.33.217"], time: 1629372855089 },
"event-6": { entityIds: ["191.7.178.2", "191.7.178.2"], time: 1629372966112 },
"event-7": { entityIds: ["118.37.6.215", "81.161.59.17"], time: 1629372996298 },
"event-8": { entityIds: ["1.180.208.152", "101.226.33.217"], time: 1629373110447 },
"event-9": { entityIds: ["81.161.59.17", "220.181.108.103"], time: 1629373112501 },
"event-10": { entityIds: ["163.172.66.164", "65.48.139.133"], time: 1629373122325 },
"event-11": { entityIds: ["65.48.139.133", "220.181.108.109"], time: 1629373137553 },
"event-12": { entityIds: ["66.249.64.128", "131.161.224.94"], time: 1629373183424 },
"event-13": { entityIds: ["117.144.246.77", "119.184.100.174"], time: 1629373242803 },
"event-14": { entityIds: ["1.180.208.148", "191.7.178.2"], time: 1629373261835 },
"event-15": { entityIds: ["213.184.105.182", "157.55.39.1838"], time: 1629373292078 },
"event-16": { entityIds: ["138.128.6.115", "101.226.51.230"], time: 1629373304447 },
"event-17": { entityIds: ["163.172.66.171", "163.172.66.40"], time: 1629373309421 },
"event-18": { entityIds: ["101.226.125.119", "179.181.219.44"], time: 1629373309589 },
"event-19": { entityIds: ["163.172.66.15", "81.161.59.17"], time: 1629373367674 },
"event-20": { entityIds: ["101.226.33.217", "220.181.108.103"], time: 1629373392081 },
"event-21": { entityIds: ["131.161.224.94", "101.226.125.119"], time: 1629373402184 },
"event-22": { entityIds: ["179.181.219.24", "65.48.139.133"], time: 1629373427921 },
"event-23": {
entityIds: ["195.55.121.5", "157.55.39.122"],
time: 1629373485503,
label: { text: "This is an event label" },
},
"event-24": { entityIds: ["163.172.66.40", "157.55.39.1828"], time: 1629373527572 },
"event-25": { entityIds: ["220.181.108.103", "65.48.139.133"], time: 1629373563266 },
"event-26": { entityIds: ["179.181.219.44", "123.184.218.198"], time: 1629373632758 },
"event-27": { entityIds: ["220.181.108.109", "157.55.39.788"], time: 1629373671302 },
"event-28": { entityIds: ["222.182.107.110", "101.226.51.230"], time: 1629373689030 },
"event-29": {
entityIds: ["222.184.107.114", "222.182.107.110"],
time: 1629373711184,
},
"event-30": { entityIds: ["222.184.1033.144", "65.48.139.133"], time: 1629373763739 },
"event-31": { entityIds: ["157.55.39.189", "157.55.39.1838"], time: 1629373814735 },
"event-32": { entityIds: ["157.55.39.1838", "65.48.139.133"], time: 1629373970716 },
"event-33": { entityIds: ["157.55.39.1828", "157.55.39.1828"], time: 1629373997509 },
"event-34": { entityIds: ["157.55.39.122", "157.55.39.1838"], time: 1629374017194 },
"event-35": { entityIds: ["157.55.39.788", "195.55.121.5"], time: 1629374087269 },
"event-36": { entityIds: ["157.55.39.788", "195.55.121.5"], time: 1629374087269 },
},
timeSeriesCharts: {
networkPerformance: {
color: "#ff8838",
label: "Network Performance",
labelColor: "#ff8838",
data: [
{ time: 1629372675131, value: 28 },
{ time: 1629372681796, value: 28 },
{ time: 1629372701960, value: 27 },
{ time: 1629372725809, value: 25 },
{ time: 1629372759572, value: 27.5 },
{ time: 1629372815459, value: 28 },
{ time: 1629372855089, value: 27 },
{ time: 1629372920345, value: 26 },
{ time: 1629372966112, value: 23 },
{ time: 1629372996298, value: 22 },
{ time: 1629373026503, value: 24 },
{ time: 1629373093819, value: 24.5 },
{ time: 1629373110447, value: 24 },
{ time: 1629373112501, value: 23.5 },
{ time: 1629373122325, value: 23 },
{ time: 1629373134240, value: 23 },
{ time: 1629373137553, value: 22 },
{ time: 1629373140815, value: 22 },
{ time: 1629373183424, value: 21.5 },
{ time: 1629373242803, value: 28 },
{ time: 1629373261835, value: 27 },
{ time: 1629373292078, value: 28 },
{ time: 1629373304447, value: 28.5 },
{ time: 1629373309421, value: 28 },
{ time: 1629373309589, value: 28 },
{ time: 1629373353664, value: 27 },
{ time: 1629373367674, value: 26.5 },
{ time: 1629373392081, value: 25 },
{ time: 1629373402184, value: 24.8 },
{ time: 1629373427921, value: 23 },
{ time: 1629373485503, value: 24 },
{ time: 1629373506794, value: 23 },
{ time: 1629373514358, value: 22.5 },
{ time: 1629373527572, value: 21 },
{ time: 1629373540573, value: 21 },
{ time: 1629373563266, value: 20.5 },
{ time: 1629373566218, value: 20.5 },
{ time: 1629373632758, value: 19.5 },
{ time: 1629373660451, value: 20 },
{ time: 1629373671302, value: 21 },
{ time: 1629373689030, value: 20 },
{ time: 1629373699825, value: 22 },
{ time: 1629373711184, value: 21 },
{ time: 1629373736779, value: 22 },
{ time: 1629373763739, value: 22.5 },
{ time: 1629373806565, value: 21 },
{ time: 1629373814735, value: 23 },
{ time: 1629373913182, value: 28 },
{ time: 1629373970716, value: 28 },
{ time: 1629373996005, value: 27 },
{ time: 1629373997509, value: 26.5 },
{ time: 1629374017194, value: 27.5 },
{ time: 1629374048968, value: 28 },
{ time: 1629374087269, value: 27 },
],
},
},
}; <!doctype html>
<html>
<head>
<link rel="stylesheet" href="@ci/theme/kg/css/examples.css" />
<link rel="stylesheet" href="./style.css" />
</head>
<body>
<div id="my-timeline" style="height: 100vh"></div>
<script type="module" src="./code.jsx"></script>
</body>
</html> #selection-switch {
display: flex;
flex-direction: row;
align-items: center;
white-space: nowrap;
}
.modifier-key {
margin: 0;
padding: 0px 10px;
}
.modifiers-container {
flex: 0 0 auto;
flex-wrap: wrap;
gap: 10px 2rem;
padding: 1rem;
align-items: start;
position: absolute;
top: 2rem;
display: flex;
flex-direction: column;
font-size: medium;
font-weight: bold;
pointer-events: none;
}
.controls-container {
display: flex;
flex-wrap: wrap;
gap: 10px 2rem;
padding: 0.5rem 0.5rem 0rem 0.5rem;
}
.row {
display: flex;
align-items: center;
}
.gap-2 {
column-gap: 1em;
}
.switch {
position: relative;
display: inline-block;
width: 25px;
height: 16px;
padding: 2px;
margin-left: 5px;
vertical-align: middle;
}
/* Hide default HTML checkbox */
.switch input {
opacity: 0;
width: 0;
height: 0;
}
/* The slider */
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var(--grey-color-300);
-webkit-transition: 0.4s;
transition: 0.4s;
}
.slider:before {
position: absolute;
content: "";
height: 12px;
width: 12px;
left: 2px;
bottom: 2px;
background-color: var(--white-color);
-webkit-transition: 0.4s;
transition: 0.4s;
}
input:checked + .slider {
background-color: var(--primary-color300);
}
input:focus + .slider {
box-shadow: 0 0 1px var(--primary-color300);
}
input:checked + .slider:before {
-webkit-transform: translateX(9px);
-ms-transform: translateX(9px);
transform: translateX(9px);
}
/* Rounded sliders */
.slider.round {
border-radius: 99px;
}
.slider.round:before {
border-radius: 50%;
}
.slider-label {
padding-left: 0.5rem;
vertical-align: middle;
}