This demo is a version of Time Periods demo and uses React version 16.
This demo shows how to use the KeyLines React component to:
- Declare multiple functional components
- Add font icons and tooltips
- Set options on the chart
- Use chart events to control the tooltip's state
- Listen for the change events on time bar and capture them on the controller to filter the chart
React Component
We've written the source code and chart component in ES6/JSX. We've also bundled the demo, chart component, React and ReactDOM into a single file using Webpack, and transpiled the code to JavaScript using Babel.
See also
Tutorial: React Tutorial
KeyLines React component: React Reference
React official page: reactjs.org
Key functions used:
- chart.filter
- chart.layout
- chart.getItem
- chart.selection
- chart.setProperties
- chart.graph().neighbours
- Chart Events
- timebar.inRange
- Time bar Events
import React, { useState, useRef, useEffect } from "react";
import { data } from "./data.js";
import { render } from "react-dom";
import { Chart, Timebar } from "./react-keylines";
import { LeftPanel, RightPanel } from "./react-demo";
import KeyLines from "keylines";
const Tooltip = ({ tooltipState }) => {
return (
<span
id="tooltip"
className={tooltipState.tooltipClass}
style={{ left: tooltipState.left, top: tooltipState.top }}
>
<div className="arrow"></div>
<table>
<tbody>
<tr>
<td className="table-cell">
<b>Appointed on: </b>
</td>
<td>{tooltipState.start}</td>
</tr>
<tr>
<td className="table-cell">
<b>Resigned on: </b>
</td>
<td>{tooltipState.end}</td>
</tr>
</tbody>
</table>
</span>
);
};
const ReactDemo = () => {
// data is a global variable stored in reactchartfilter-data.js
const [state, setState] = useState(data);
const [handMode, setHandMode] = useState(true);
const [timebarSelection, setTimebarSelection] = useState([]);
const [tooltipState, setTooltipState] = useState({
start: "",
end: "",
left: "",
top: "",
tooltipClass: "hidden",
});
// here we save the reference to the chart and time bar so we can call KeyLines' methods on them
const chart = useRef(null);
const timebar = useRef(null);
const greyColours = ["rgb(105, 105, 105)", "rgb(192, 192, 192)"];
// only styling the first 3 selected items as the timebar can only show three selection lines
const selectionColours = [
["#9600d5", "#d0a3cb"],
["#0044ff", "#6b93ff"],
["#ff0000", "#ff9e9e"],
];
const updateHandMode = ({ id, preventDefault }) => {
if (id === "_toggleMode") {
preventDefault();
setHandMode(!handMode);
}
};
const toggleTooltip = ({ id, x, y }) => {
const getDateFromTimestamp = (timestamp) => {
return timestamp ? new Date(timestamp).toDateString().slice(4) : "n/a";
};
if (chart.current) {
const item = id ? chart.current.getItem(id) : null;
const viewWidth = chart.current.viewOptions().width;
let newTooltipState = {};
if (item && item.type === "link") {
const timebarItem = data.items.filter((tbItem) => {
if (tbItem.id === item.id) {
return tbItem;
}
});
newTooltipState.start = getDateFromTimestamp(timebarItem[0].dt[0].dt1);
newTooltipState.end = getDateFromTimestamp(timebarItem[0].dt[0].dt2);
// style tooltip to left of cursor if it's x position + width exceeds
// the remaining chart space.
if (viewWidth && viewWidth > x + 250) {
newTooltipState.top = `${y - 24}px`;
newTooltipState.left = `${x + 22}px`;
newTooltipState.tooltipClass = "";
} else {
newTooltipState.top = `${y - 24}px`;
newTooltipState.left = `${x - 218}px`;
newTooltipState.tooltipClass = "right";
}
} else {
newTooltipState.tooltipClass = "hidden";
}
setTooltipState(newTooltipState);
}
};
// filter the items on the chart according to the range of the timebar
const onTimebarChange = () => {
// if all our components are loaded
if (chart.current && timebar.current) {
// filter the chart to show only items in the new range
chart.current.filter(timebar.current.inRange, { animate: false, type: "link" }).then(() => {
// and then adjust the chart's layout
chart.current.layout("organic", { time: 500, mode: "adaptive", easing: "linear" });
});
}
};
const buildItemStyling = (item, width, colour, border = false) => {
if (item.type === "node") {
return {
id: item.id,
fi: {
c: colour,
t: item.d.type === "person" ? "fas fa-user" : "fas fa-building",
},
b: border ? colour : null,
};
}
// else it's a link
return { id: item.id, w: width || 4, c: colour };
};
const highlightItemsOnSelection = () => {
const timebarSelection = [];
const chartProperties = {};
// set default colouring
chart.current.each({ type: "all" }, (item) => {
chartProperties[item.id] = buildItemStyling(
item,
3,
item.type === "node" ? greyColours[0] : greyColours[1]
);
});
let selectedIds = chart.current.selection();
if (selectedIds) {
// prevent the selection of more than 3 items as the timebar can only show three selection lines
if (selectedIds.length > selectionColours.length) {
selectedIds = selectedIds.slice(0, selectionColours.length);
chart.current.selection(selectedIds);
}
selectedIds.forEach((id, index) => {
let neighbouringNodes = [];
let links = [];
const item = chart.current?.getItem(id);
const colour =
index < selectionColours.length ? selectionColours[index][0] : greyColours[1];
const linkColour = selectionColours[index][1];
if (item && item.type === "node") {
// all: true - applies styles to hidden nodes + links not in current time range
const neighbours = chart.current?.graph().neighbours(id, { all: true });
if (neighbours && neighbours.links) {
links = neighbours.links;
neighbouringNodes = neighbours.nodes;
}
// colour node
chartProperties[id] = buildItemStyling(item, 3, colour, true);
links.forEach((link) => {
const linkItem = chart.current?.getItem(link);
chartProperties[link] = buildItemStyling(linkItem, 5, linkColour);
});
} else if (item && item.type === "link") {
links.push(id);
neighbouringNodes.push(item.id1, item.id2);
// colour link
chartProperties[id] = buildItemStyling(item, 5, colour);
}
// colour neighbour nodes
neighbouringNodes.forEach((neighbourId) => {
if (
chartProperties[neighbourId].fi.c === greyColours[0] &&
index < selectionColours.length
) {
chartProperties[neighbourId] = buildItemStyling(
chart.current?.getItem(neighbourId),
3,
selectionColours[index][1]
);
}
});
// and select in the timebar
timebarSelection.push({
id: links,
index,
c: colour,
});
});
setTimebarSelection(timebarSelection);
chart.current.setProperties(Object.keys(chartProperties).map((key) => chartProperties[key]));
}
};
// get a reference to the loaded timebar
const loadedTimebar = (newTimebar) => {
timebar.current = newTimebar;
timebar.current.mark([
{
dt1: new Date("01 Jan 2018 01:00:00"),
dt2: new Date("01 Jan 2050 01:00:00"),
},
]);
};
// get a reference to the loaded chart
const loadedChart = (newChart) => {
chart.current = newChart;
};
const loadFontIcons = () => {
let newItems = [];
state.items.forEach((item) => {
if (item.type === "node") {
newItems = [
...newItems,
{
...item,
fi: {
c: greyColours[0],
t: item.d.type === "person" ? "fas fa-user" : "fas fa-building",
},
},
];
} else {
newItems.push(item);
}
});
setState({ ...state, items: newItems });
};
useEffect(() => {
document.fonts.load('24px "Font Awesome 5 Free"').then(loadFontIcons);
}, []);
return (
<>
<LeftPanel name="reactchartfilter">
<Tooltip tooltipState={tooltipState} />
<Chart
ready={loadedChart}
data={state}
animateOnLoad={true}
options={{
logo: { u: "/images/Logo.png" },
handMode,
iconFontFamily: "Font Awesome 5 Free",
selectionColour: "#35495E",
hover: 0,
selectedNode: {
fbc: "rgba(0,0,0,0)",
},
selectedLink: {
fbc: "rgba(0,0,0,0)",
},
}}
containerClassName="klchart klchart-timebar"
selection-change={highlightItemsOnSelection}
hover={toggleTooltip}
drag-move={toggleTooltip}
click={updateHandMode}
/>
<Timebar
ready={loadedTimebar}
data={state}
selection={timebarSelection}
change={onTimebarChange}
animateOnLoad={true}
options={{
histogram: {
colour: "#61dbfb",
highlightColour: "#3e8496",
markColour: "rgb(192, 192, 192)",
markHiColour: "rgb(105, 105, 105)",
},
playSpeed: 30,
}}
containerClassName="kltimebar"
/>
</LeftPanel>
<RightPanel
name="reactchartfilter"
title="React Integration"
description="Use React and the time bar to filter chart items."
>
<fieldset>
<legend>Interaction</legend>
<ul>
<li>Use the time bar controls to see changes over time</li>
<li>Hover/click a link to to show the duration of an individual's directorship</li>
<li>Multi-select up to three items to highlight their neighbours</li>
</ul>
</fieldset>
<fieldset>
<legend>Data Format</legend>
The time bar and chart data formats are similar, so you can pass the same data object to
both components.
</p>
</fieldset>
</RightPanel>
</>
);
};
// render our top-level component into the DOM
render(<ReactDemo />, document.querySelector(".chart-wrapper")); import React from "react";
import KeyLines from "keylines";
function invoke(fn, ...args) {
return typeof fn === "function" ? fn(...args) : undefined;
}
// This is the lowest level wrapper of the KeyLines integration - it deals with loading of the
// KeyLines component, options, resizing and raising KeyLines events up
function createKeyLinesComponent(type, onLoadFn) {
class KlComponent extends React.Component {
component = undefined;
componentLoading = false;
type = type;
onLoad = onLoadFn;
constructor(props) {
super(props);
// Bind our class specific functions to this
// See: https://facebook.github.io/react/docs/react-without-es6.html#autobinding
["applyProps", "setSelection", "onEvent", "onLoad"].forEach((prop) => {
this[prop] = this[prop].bind(this);
});
}
componentDidMount() {
if (this.componentLoading) {
return;
}
this.componentLoading = true;
const componentDefinition = {
container: this.klElement,
type: this.type,
options: this.props.options,
};
KeyLines.create(componentDefinition)
.then((component) => {
this.componentLoading = false;
this.component = component;
component.on("all", this.onEvent);
this.applyProps().then(() => {
// Finally, tell the parent about the component so it can call functions on it
invoke(this.props.ready, component, this.klElement);
});
})
.catch(() => {
this.componentLoading = false;
});
}
componentWillUnmount() {
if (this.component) {
// Clean up the component
this.component.destroy();
this.component = undefined;
}
}
setSelection() {
// This works because the selectionchange event is not raised when changing selection
// programmatically
const selectedItems = this.component.selection(this.props.selection);
if (this.type === "chart" && selectedItems.length > 0) {
this.component.zoom("selection", { animate: true, time: 250 });
}
}
componentDidUpdate(prevProps) {
if (this.component) {
// we need to intercept the options being set and pass them on to the component manually
if (this.props.options && this.props.options !== prevProps.options) {
this.component.options(this.props.options); // don't worry about callback here
}
const reload = this.props.data !== prevProps.data;
if (reload) {
// note applyProps also deals with selection
return this.applyProps();
} else if (this.props.selection || prevProps.selection) {
if (this.props.selection !== prevProps.selection) {
this.setSelection();
}
}
}
}
// this looks for a handler with the right name on the props, and if it finds
// one, it will call it with the event arguments.
onEvent(props) {
return invoke(this.props[props.name], props.event);
}
// this applies all the component related props except the options which are
// handled differently
applyProps() {
return new Promise((resolve) => {
this.component.load(this.props.data).then(() => {
this.onLoad({ animate: !!this.props.animateOnLoad }).then(() => {
this.component.selection(this.props.selection);
resolve();
});
});
});
}
render() {
const { containerClassName, style, children } = this.props;
return (
<div
ref={(klElement) => {
this.klElement = klElement;
}}
className={containerClassName}
style={style}
>
{children}
</div>
);
}
}
// defaultProps has to be a static property of the component class
KlComponent.defaultProps = {
data: {},
animateOnLoad: false,
options: {},
selection: [],
};
return KlComponent;
}
function createChartComponent() {
function onLoad(options) {
// default behaviour when loading data in the chart
return this.component.layout("organic", options);
}
return createKeyLinesComponent("chart", onLoad);
}
// Define the Chart component
export const Chart = createChartComponent();
function createTimebarComponent() {
function onLoad(options) {
// default behaviour when loading data in the timebar
return this.component.zoom("fit", options);
}
return createKeyLinesComponent("timebar", onLoad);
}
// Define the Timebar component
export const Timebar = createTimebarComponent(); import React, { useEffect, useRef } from "react";
/**
* @typedef {Object} RightPanelProps
* @property {string} name - The name of the demo.
* @property {string} title - The title of the demo.
* @property {string} description - The short description of the demo.
*/
/**
* @param {RightPanelProps} props
*/
const RightPanel = (props) => {
const sourceTab = useRef(null);
const aboutTab = useRef(null);
useEffect(() => {
const sourcePanel = document.getElementById("sourcePanel");
const aboutPanel = document.getElementById("aboutPanel");
if (sourcePanel && aboutPanel) {
// re-assign parent of child elements, rather than copy HTML so as to not remove any event handlers
sourceTab.current.appendChild(sourcePanel);
for (const child of Array.from(sourcePanel.children)) {
sourceTab.current.appendChild(child);
}
const aboutTemplate = aboutPanel.innerHTML;
aboutTab.current.innerHTML = aboutTemplate;
}
}, []);
return (
<div className="rhs citext flex-column flex">
<div className="title-bar">
<h3 className="flex1">{document.title.split("|")[0]}</h3>
<div className="buttons flex" id="titlebarbuttons">
<a
className="btn btn-label disabled fshide"
href="#"
data-toggle="tooltip"
id="fullscreenDisabled"
>
<i className="fa fa-compress"></i>
</a>
<a className="btn btn-label" href="#" data-toggle="tooltip" id="fullscreenButton">
<i className="fa fa-expand"></i>
</a>
</div>
</div>
<div className="notice">
<div className="boxout">
<div className="boxout-image">
<img src={`/images/${props.name}/ReGraph.png`}></img>
</div>
<p className="boxout-body" style={{ paddingLeft: 42, minHeight: 40 }}>
Using React? Check out ReGraph, our React graph visualisation toolkit.
<a href="mailto:[email protected]">Contact us</a>
<span> or </span>
<a
href={`https://cambridge-intelligence.com/regraph/?utm_source=keylines&utm_medium=${isPublic === "true" ? "public-sdk-site" : "sdk-site"}&utm_campaign=keylines-sdk-referral-link`}
>
learn more
</a>
.
</p>
</div>
</div>
<div className="tabsdiv tab" data-tab-group="rhs">
<a
className="btn btn-spaced fshide rhs-toggle fancy-button"
id="closeRHS"
href="#"
data-toggle="tooltip"
>
<i className="fa fa-chevron-right"></i>
</a>
<button className="tablinks active" name="controlsTab" type="button">
Controls
</button>
<button className="tablinks" name="aboutTab" type="button">
About
</button>
<button className="tablinks" name="sourceTab" type="button">
Source
</button>
</div>
<div className="tab-content-panel flex1" data-tab-group="rhs">
<div className="toggle-content is-visible tabcontent" id="controlsTab">
<div className="cicontent">
<img className="kl-center" src={`/images/${props.name}/reactjs.png`} width="75%" />
{props.children}
</div>
</div>
<div ref={aboutTab} className="toggle-content tabcontent" id="aboutTab"></div>
<div ref={sourceTab} className="toggle-content tabcontent" id="sourceTab"></div>
</div>
</div>
);
};
/**
* @typedef {Object} LeftPanelProps
* @property {string} name
*/
/**
* @param {LeftPanelProps} props
*/
const LeftPanel = (props) => {
return (
<div className="flex1" id="lhs">
<div id="lhsVisual" className="toggle-content is-visible">
{props.children}
</div>
<div id="lhsSource" className="toggle-content">
<iframe
id="sourceView"
style={{ width: "100%", height: "100%", border: "none" }}
src={`source-${props.name}.js.htm`}
scrolling="no"
></iframe>
</div>
</div>
);
};
export { LeftPanel, RightPanel }; export const data = {
type: "LinkChart",
items: [
{
type: "node",
id: "Brooke Fields",
t: "Brooke Fields",
d: {
type: "person",
},
},
{
type: "node",
id: "Gina Carlson",
t: "Gina Carlson",
d: {
type: "person",
},
},
{
type: "node",
id: "Guillermo Vaughn",
t: "Guillermo Vaughn",
d: {
type: "person",
},
},
{
type: "node",
id: "Rose Ingram",
t: "Rose Ingram",
d: {
type: "person",
},
},
{
type: "node",
id: "Jeannie Wolfe",
t: "Jeannie Wolfe",
d: {
type: "person",
},
},
{
type: "node",
id: "Dave Rice",
t: "Dave Rice",
d: {
type: "person",
},
},
{
type: "node",
id: "Paul Watts",
t: "Paul Watts",
d: {
type: "person",
},
},
{
type: "node",
id: "Danny Matthews",
t: "Danny Matthews",
d: {
type: "person",
},
},
{
type: "node",
id: "Anthony Mitchell",
t: "Anthony Mitchell",
d: {
type: "person",
},
},
{
type: "node",
id: "Alfred Hicks",
t: "Alfred Hicks",
d: {
type: "person",
},
},
{
type: "node",
id: "Adrian Mcdaniel",
t: "Adrian Mcdaniel",
d: {
type: "person",
},
},
{
type: "node",
id: "Kerry Waters",
t: "Kerry Waters",
d: {
type: "person",
},
},
{
type: "node",
id: "Felix Floyd",
t: "Felix Floyd",
d: {
type: "person",
},
},
{
type: "node",
id: "Noel Luna",
t: "Noel Luna",
d: {
type: "person",
},
},
{
type: "node",
id: "Tami Lindsey",
t: "Tami Lindsey",
d: {
type: "person",
},
},
{
type: "node",
id: "Damon Douglas",
t: "Damon Douglas",
d: {
type: "person",
},
},
{
type: "node",
id: "Diana Greene",
t: "Diana Greene",
d: {
type: "person",
},
},
{
type: "node",
id: "Gary King",
t: "Gary King",
d: {
type: "person",
},
},
{
type: "node",
id: "Phantom Corporation",
t: "Phantom Corporation",
d: {
type: "company",
},
},
{
type: "node",
id: "Melon Arts",
t: "Melon Arts",
d: {
type: "company",
},
},
{
type: "node",
id: "Green",
t: "Green",
d: {
type: "company",
},
},
{
type: "node",
id: "Marblightning",
t: "Marblightning",
d: {
type: "company",
},
},
{
type: "node",
id: "Cycloration",
t: "Cycloration",
d: {
type: "company",
},
},
{
type: "node",
id: "Bluetronics",
t: "Bluetronics",
d: {
type: "company",
},
},
{
type: "node",
id: "Summitechnologies",
t: "Summitechnologies",
d: {
type: "company",
},
},
{
type: "node",
id: "Cloudwalk",
t: "Cloudwalk",
d: {
type: "company",
},
},
{
type: "node",
id: "Jettechs",
t: "Jettechs",
d: {
type: "company",
},
},
{
type: "node",
id: "Quadbridge",
t: "Quadbridge",
d: {
type: "company",
},
},
{
type: "node",
id: "Crow Navigations",
t: "Crow Navigations",
d: {
type: "company",
},
},
{
type: "node",
id: "Grasshopper Co.",
t: "Grasshopper Co.",
d: {
type: "company",
},
},
{
type: "node",
id: "Daydream Security",
t: "Daydream Security",
d: {
type: "company",
},
},
{
type: "node",
id: "Joytechs",
t: "Joytechs",
d: {
type: "company",
},
},
{
type: "node",
id: "Dwarfoods",
t: "Dwarfoods",
d: {
type: "company",
},
},
{
type: "node",
id: "Cliffoods",
t: "Cliffoods",
d: {
type: "company",
},
},
{
type: "node",
id: "Vinedustries",
t: "Vinedustries",
d: {
type: "company",
},
},
{
type: "node",
id: "Bansheewares",
t: "Bansheewares",
d: {
type: "company",
},
},
{
type: "node",
id: "Moonshine",
t: "Moonshine",
d: {
type: "company",
},
},
{
type: "node",
id: "Mapleway",
t: "Mapleway",
d: {
type: "company",
},
},
{
type: "node",
id: "Diamond Aviation",
t: "Diamond Aviation",
d: {
type: "company",
},
},
{
type: "node",
id: "Grotto Corp",
t: "Grotto Corp",
d: {
type: "company",
},
},
{
type: "node",
id: "Root Aviation",
t: "Root Aviation",
d: {
type: "company",
},
},
{
type: "node",
id: "White Wolfoods",
t: "White Wolfoods",
d: {
type: "company",
},
},
{
type: "node",
id: "Greenetworks",
t: "Greenetworks",
d: {
type: "company",
},
},
{
type: "node",
id: "Honeytelligence",
t: "Honeytelligence",
d: {
type: "company",
},
},
{
type: "node",
id: "Tempestechnologies",
t: "Tempestechnologies",
d: {
type: "company",
},
},
{
type: "node",
id: "Phoenixland",
t: "Phoenixland",
d: {
type: "company",
},
},
{
type: "node",
id: "Crystalsun",
t: "Crystalsun",
d: {
type: "company",
},
},
{
type: "link",
id: "Brooke Fields-Phantom Corporation",
id1: "Brooke Fields",
id2: "Phantom Corporation",
dt: [
{
dt1: 1339014193119,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Brooke Fields-Melon Arts",
id1: "Brooke Fields",
id2: "Melon Arts",
dt: [
{
dt1: 1247271160185,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Brooke Fields-Green",
id1: "Brooke Fields",
id2: "Green",
dt: [
{
dt1: 1237397195464,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Brooke Fields-Marblightning",
id1: "Brooke Fields",
id2: "Marblightning",
dt: [
{
dt1: 1268721087691,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Brooke Fields-Cycloration",
id1: "Brooke Fields",
id2: "Cycloration",
dt: [
{
dt1: 1310952185992,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Gina Carlson-Bluetronics",
id1: "Gina Carlson",
id2: "Bluetronics",
dt: [
{
dt1: 1191238914316,
dt2: 1270569157085,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Gina Carlson-Summitechnologies",
id1: "Gina Carlson",
id2: "Summitechnologies",
dt: [
{
dt1: 1276109390365,
dt2: 1348661017536,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Gina Carlson-Cloudwalk",
id1: "Gina Carlson",
id2: "Cloudwalk",
dt: [
{
dt1: 1348270245074,
dt2: 1412908707443,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Gina Carlson-Jettechs",
id1: "Gina Carlson",
id2: "Jettechs",
dt: [
{
dt1: 1415551838802,
dt2: 1465786744438,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Gina Carlson-Quadbridge",
id1: "Gina Carlson",
id2: "Quadbridge",
dt: [
{
dt1: 1459806246849,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Guillermo Vaughn-Crow Navigations",
id1: "Guillermo Vaughn",
id2: "Crow Navigations",
dt: [
{
dt1: 1418792877334,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Rose Ingram-Grasshopper Co.",
id1: "Rose Ingram",
id2: "Grasshopper Co.",
dt: [
{
dt1: 1383109469104,
dt2: 1498149360662,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Rose Ingram-Daydream Security",
id1: "Rose Ingram",
id2: "Daydream Security",
dt: [
{
dt1: 1236890263269,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Jeannie Wolfe-Joytechs",
id1: "Jeannie Wolfe",
id2: "Joytechs",
dt: [
{
dt1: 1266649394407,
dt2: 1418563289105,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Dave Rice-Dwarfoods",
id1: "Dave Rice",
id2: "Dwarfoods",
dt: [
{
dt1: 1435235563645,
dt2: 1439593633782,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Paul Watts-Cliffoods",
id1: "Paul Watts",
id2: "Cliffoods",
dt: [
{
dt1: 1348460379175,
dt2: 1430497186231,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Paul Watts-Vinedustries",
id1: "Paul Watts",
id2: "Vinedustries",
dt: [
{
dt1: 1342980716145,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Danny Matthews-Bansheewares",
id1: "Danny Matthews",
id2: "Bansheewares",
dt: [
{
dt1: 1212592504689,
dt2: 1506411552619,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Anthony Mitchell-Moonshine",
id1: "Anthony Mitchell",
id2: "Moonshine",
dt: [
{
dt1: 1294680086436,
dt2: 1513775556429,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Anthony Mitchell-Mapleway",
id1: "Anthony Mitchell",
id2: "Mapleway",
dt: [
{
dt1: 1404224388156,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Alfred Hicks-Diamond Aviation",
id1: "Alfred Hicks",
id2: "Diamond Aviation",
dt: [
{
dt1: 1302567304700,
dt2: 1435579135260,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Adrian Mcdaniel-Grotto Corp",
id1: "Adrian Mcdaniel",
id2: "Grotto Corp",
dt: [
{
dt1: 1275605687635,
dt2: 1302441510735,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Kerry Waters-Root Aviation",
id1: "Kerry Waters",
id2: "Root Aviation",
dt: [
{
dt1: 1357157613919,
dt2: 1497198217881,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Kerry Waters-White Wolfoods",
id1: "Kerry Waters",
id2: "White Wolfoods",
dt: [
{
dt1: 1186565644914,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Felix Floyd-Greenetworks",
id1: "Felix Floyd",
id2: "Greenetworks",
dt: [
{
dt1: 1281749075420,
dt2: 1425911470613,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Noel Luna-Honeytelligence",
id1: "Noel Luna",
id2: "Honeytelligence",
dt: [
{
dt1: 1428549346398,
dt2: 1497935675353,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Tami Lindsey-Tempestechnologies",
id1: "Tami Lindsey",
id2: "Tempestechnologies",
dt: [
{
dt1: 1345276788162,
dt2: 1402559251417,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Tami Lindsey-Phoenixland",
id1: "Tami Lindsey",
id2: "Phoenixland",
dt: [
{
dt1: 1272650321596,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Damon Douglas-Crystalsun",
id1: "Damon Douglas",
id2: "Crystalsun",
dt: [
{
dt1: 1439239127664,
dt2: 1446474483161,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Damon Douglas-Phantom Corporation",
id1: "Damon Douglas",
id2: "Phantom Corporation",
dt: [
{
dt1: 1432259515171,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Diana Greene-Melon Arts",
id1: "Diana Greene",
id2: "Melon Arts",
dt: [
{
dt1: 1250937080868,
dt2: 1272043917343,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Diana Greene-Green",
id1: "Diana Greene",
id2: "Green",
dt: [
{
dt1: 1388980546097,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Gary King-Marblightning",
id1: "Gary King",
id2: "Marblightning",
dt: [
{
dt1: 1341532138270,
dt2: 1348971910953,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Brooke Fields-Cycloration",
id1: "Brooke Fields",
id2: "Cycloration",
dt: [
{
dt1: 1252224410783,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Brooke Fields-Bluetronics",
id1: "Brooke Fields",
id2: "Bluetronics",
dt: [
{
dt1: 1327698610449,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Brooke Fields-Summitechnologies",
id1: "Brooke Fields",
id2: "Summitechnologies",
dt: [
{
dt1: 1176476394850,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Brooke Fields-Cloudwalk",
id1: "Brooke Fields",
id2: "Cloudwalk",
dt: [
{
dt1: 1343570952015,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Brooke Fields-Jettechs",
id1: "Brooke Fields",
id2: "Jettechs",
dt: [
{
dt1: 1183735407623,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Gina Carlson-Quadbridge",
id1: "Gina Carlson",
id2: "Quadbridge",
dt: [
{
dt1: 1203779987419,
dt2: 1286114861583,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Gina Carlson-Crow Navigations",
id1: "Gina Carlson",
id2: "Crow Navigations",
dt: [
{
dt1: 1252864333111,
dt2: 1332066374345,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Gina Carlson-Grasshopper Co.",
id1: "Gina Carlson",
id2: "Grasshopper Co.",
dt: [
{
dt1: 1318370099782,
dt2: 1367155178043,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Gina Carlson-Daydream Security",
id1: "Gina Carlson",
id2: "Daydream Security",
dt: [
{
dt1: 1445086054970,
dt2: 1494164214919,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Gina Carlson-Joytechs",
id1: "Gina Carlson",
id2: "Joytechs",
dt: [
{
dt1: 1508949404869,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Guillermo Vaughn-Dwarfoods",
id1: "Guillermo Vaughn",
id2: "Dwarfoods",
dt: [
{
dt1: 1439419710533,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Rose Ingram-Cliffoods",
id1: "Rose Ingram",
id2: "Cliffoods",
dt: [
{
dt1: 1447743740784,
dt2: 1475561870381,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Jeannie Wolfe-Vinedustries",
id1: "Jeannie Wolfe",
id2: "Vinedustries",
dt: [
{
dt1: 1302350026690,
dt2: 1362601707946,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Jeannie Wolfe-Bansheewares",
id1: "Jeannie Wolfe",
id2: "Bansheewares",
dt: [
{
dt1: 1283669644884,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Dave Rice-Moonshine",
id1: "Dave Rice",
id2: "Moonshine",
dt: [
{
dt1: 1232018923653,
dt2: 1509860168360,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Paul Watts-Mapleway",
id1: "Paul Watts",
id2: "Mapleway",
dt: [
{
dt1: 1210590359697,
dt2: 1274000597065,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Danny Matthews-Diamond Aviation",
id1: "Danny Matthews",
id2: "Diamond Aviation",
dt: [
{
dt1: 1440723155554,
dt2: 1503407220276,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Danny Matthews-Grotto Corp",
id1: "Danny Matthews",
id2: "Grotto Corp",
dt: [
{
dt1: 1411610571654,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Anthony Mitchell-Root Aviation",
id1: "Anthony Mitchell",
id2: "Root Aviation",
dt: [
{
dt1: 1412918383186,
dt2: 1433401731854,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Anthony Mitchell-White Wolfoods",
id1: "Anthony Mitchell",
id2: "White Wolfoods",
dt: [
{
dt1: 1303392645020,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Alfred Hicks-Greenetworks",
id1: "Alfred Hicks",
id2: "Greenetworks",
dt: [
{
dt1: 1245395749674,
dt2: 1343518747478,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Adrian Mcdaniel-Honeytelligence",
id1: "Adrian Mcdaniel",
id2: "Honeytelligence",
dt: [
{
dt1: 1244519754341,
dt2: 1398795162709,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Adrian Mcdaniel-Tempestechnologies",
id1: "Adrian Mcdaniel",
id2: "Tempestechnologies",
dt: [
{
dt1: 1293685825988,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Kerry Waters-Phoenixland",
id1: "Kerry Waters",
id2: "Phoenixland",
dt: [
{
dt1: 1286331465363,
dt2: 1321863520043,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Felix Floyd-Crystalsun",
id1: "Felix Floyd",
id2: "Crystalsun",
dt: [
{
dt1: 1404871089274,
dt2: 1435383669902,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Felix Floyd-Phantom Corporation",
id1: "Felix Floyd",
id2: "Phantom Corporation",
dt: [
{
dt1: 1239896796491,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Noel Luna-Melon Arts",
id1: "Noel Luna",
id2: "Melon Arts",
dt: [
{
dt1: 1232612229592,
dt2: 1489184683838,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Noel Luna-Green",
id1: "Noel Luna",
id2: "Green",
dt: [
{
dt1: 1179384154182,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Tami Lindsey-Marblightning",
id1: "Tami Lindsey",
id2: "Marblightning",
dt: [
{
dt1: 1352564813420,
dt2: 1365113284879,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Tami Lindsey-Cycloration",
id1: "Tami Lindsey",
id2: "Cycloration",
dt: [
{
dt1: 1408263996000,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Damon Douglas-Bluetronics",
id1: "Damon Douglas",
id2: "Bluetronics",
dt: [
{
dt1: 1365560827793,
dt2: 1455292661790,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Damon Douglas-Summitechnologies",
id1: "Damon Douglas",
id2: "Summitechnologies",
dt: [
{
dt1: 1371837671366,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Diana Greene-Cloudwalk",
id1: "Diana Greene",
id2: "Cloudwalk",
dt: [
{
dt1: 1368713370177,
dt2: 1505960116022,
},
],
a2: true,
w: 3,
},
{
type: "link",
id: "Gary King-Jettechs",
id1: "Gary King",
id2: "Jettechs",
dt: [
{
dt1: 1212476185184,
dt2: 1303569548447,
},
],
a2: true,
w: 3,
},
],
}; <!doctype html>
<html lang="en" style="background-color: #2d383f">
<head>
<meta charset="utf-8" />
<title>React Integration</title>
<link rel="stylesheet" type="text/css" href="@ci/theme/kl/css/keylines.css" />
<link rel="stylesheet" type="text/css" href="@ci/theme/kl/css/minimalsdk.css" />
<link rel="stylesheet" type="text/css" href="@ci/theme/kl/css/sdk-layout.css" />
<link rel="stylesheet" type="text/css" href="@ci/theme/kl/css/demo.css" />
<link
rel="stylesheet"
type="text/css"
href="@fortawesome/[email protected]/css/fontawesome.css"
/>
<link
rel="stylesheet"
type="text/css"
href="@fortawesome/[email protected]/css/solid.css"
/>
<link rel="stylesheet" href="./style.css" />
</head>
<body>
<div class="chart-wrapper"></div>
<script type="module" src="./code.jsx"></script>
</body>
</html> #tooltip {
position: absolute;
padding: 5px;
display: block;
color: black;
float: left;
border: 1px solid #ccc;
background-color: #fff;
z-index: 1000;
width: 208px;
}
.arrow {
position: absolute;
top: 20px;
left: -9px;
border-width: 0px 0px 1px 1px;
border-style: solid;
border-color: #ccc;
background-color: #fff;
transform: rotateZ(45deg);
height: 15px;
width: 15px;
}
.right .arrow {
top: 20px;
left: 199px;
transform: rotateZ(225deg);
}
.table-cell {
text-align: right;
padding: 5px;
}