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
The source for this demo and its React chart component is available in the code tabs below.
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";
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: "/public/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>
<p>
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";
// Supplied by the KeyLines site build; the public site sets it to "true".
const isPublic = "true";
/**
* @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="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={`/public/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,
// ...truncated 291 lines<!doctype html>
<html lang="en">
<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="@ci/theme/kl/css/examples-chrome.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>/* --- reactchartfilter.css --- */
#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;
}