Relationships between nodes that represent a large data hierarchy can be shown effectively using angled links. These reflect the tree-like structure of the data so that relationships can be visualized more clearly.
Click two nodes in the chart to highlight and view the shortest path between them.
Click the chart background to deselect everything.
See also
import React, { useEffect, useRef, useState } from "react";
import { createRoot } from "react-dom/client";
import forEach from "lodash/forEach";
import { Chart } from "regraph";
import { shortestPaths } from "regraph/analysis";
import { items } from "./data";
import { style } from "@ci/theme/rg/js/storyStyles";
import "@fortawesome/fontawesome-free/css/fontawesome.css";
import "@fortawesome/fontawesome-free/css/solid.css";
import "@ci/theme/rg/css/button.css";
const accentColor = style.colors.teal2;
const selectionColor = style.colors.red2;
const options = {
backgroundColor: "white",
handMode: true,
overview: false,
navigation: false,
iconFontFamily: "Font Awesome 5 Free",
selection: { color: selectionColor },
imageAlignment: {
"fas fa-building": { size: 0.9 },
"fas fa-store": { size: 0.85 },
"fas fa-industry": { size: 0.85 },
"fas fa-warehouse": { size: 0.75, dy: -4 },
},
};
const layout = { name: "sequential", linkShape: "angled" };
const initialPathEnds = ["Company 46", "Company 19"];
function getStyledItems(allItems, path) {
const styledItems = { ...allItems };
forEach(path.items, (item, id) => {
styledItems[id] = {
...item,
priority: 1,
color: accentColor,
};
});
[0, 1].forEach((i) => {
if (path.ends[i] !== null) {
styledItems[path.ends[i]].halos = [
{
color: selectionColor,
radius: 30,
width: 4,
},
];
}
});
return styledItems;
}
function AngledLinks() {
const [path, setPath] = useState({
ends: [null, null],
items: {},
});
const [selectedIds, setSelectedIds] = useState([]);
const chartRef = useRef(null);
useEffect(() => {
const setInitialPath = async () => {
const initialPath = await shortestPaths(items, initialPathEnds[0], initialPathEnds[1]);
setPath({
ends: initialPathEnds,
items: initialPath.items,
});
};
setInitialPath();
}, [items]);
const fit = (fitType) => {
if (chartRef.current !== null) {
chartRef.current.fit(fitType);
}
};
async function onChangeHandler({ selection }) {
// prevent the selection of more than one node
setSelectedIds([]);
if (selection === undefined) {
return;
}
const selectedId = Object.keys(selection)[0];
if (path.ends.indexOf(selectedId) === -1) {
const validatedItem = items[selectedId];
// check the item is a node
if (validatedItem && validatedItem.id1 === undefined) {
const newPathEnds = [path.ends[1], selectedId];
const newPath =
newPathEnds[0] !== null
? await shortestPaths(items, newPathEnds[0], newPathEnds[1])
: { items: { [selectedId]: validatedItem } };
setPath({
ends: newPathEnds,
items: newPath.items,
});
}
}
}
function onClickHandler({ id }) {
if (id === null) {
setPath({
ends: [null, null],
items: {},
});
}
}
return (
<div className="story">
<div className="options">
<button type="button" onClick={() => fit("all")}>
Fit Chart
</button>
<button type="button" onClick={() => fit(path.items)}>
Fit Selection
</button>
</div>
<div className="chart-wrapper">
<Chart
ref={chartRef}
items={ }
selection={selectedIds}
layout={layout}
options={options}
animation={{ animate: false }}
onChange={onChangeHandler}
onClick={onClickHandler}
/>
</div>
</div>
);
}
const FontReadyChart = React.lazy(() =>
document.fonts.load("900 24px 'Font Awesome 5 Free'").then(() => ({
default: AngledLinks,
}))
);
export function Demo() {
return (
<React.Suspense fallback="">
<FontReadyChart />
</React.Suspense>
);
}
const root = createRoot(document.getElementById("regraph"));
root.render(<Demo />); import { style } from "@ci/theme/rg/js/storyStyles";
const primaryColor = style.colors.gradient00;
const textColor = style.colors.teal2;
const fontIconColor = style.colors.pureWhite;
const icons = ["fas fa-warehouse", "fas fa-store", "fas fa-industry", "fas fa-building"];
const linkColor = style.colors.gradient00;
const linkWidth = 3;
function makeNode(id, icon) {
return {
label: [
{
text: id,
position: "ne",
color: textColor,
backgroundColor: "rgba(0,0,0,0)",
},
],
color: primaryColor,
fontIcon: {
text: icon,
color: fontIconColor,
},
data: {
type: "company",
},
};
}
function makeLink(id1, id2) {
return {
id1,
id2,
color: linkColor,
width: linkWidth,
end2: { arrow: true },
};
}
export const items = {
"Company 85-Company 74": makeLink("Company 85", "Company 74"),
"Company 34-Company 49": makeLink("Company 34", "Company 49"),
"Company 16-Company 51": makeLink("Company 16", "Company 51"),
"Company 42-Company 36": makeLink("Company 42", "Company 36"),
"Company 98-Company 67": makeLink("Company 98", "Company 67"),
"Company 4-Company 48": makeLink("Company 4", "Company 48"),
"Company 64-Company 1": makeLink("Company 64", "Company 1"),
"Company 45-Company 50": makeLink("Company 45", "Company 50"),
"Company 13-Company 23": makeLink("Company 13", "Company 23"),
"Company 37-Company 39": makeLink("Company 37", "Company 39"),
"Company 35-Company 83": makeLink("Company 35", "Company 83"),
"Company 12-Company 27": makeLink("Company 12", "Company 27"),
"Company 87-Company 38": makeLink("Company 87", "Company 38"),
"Company 52-Company 79": makeLink("Company 52", "Company 79"),
"Company 32-Company 53": makeLink("Company 32", "Company 53"),
"Company 46-Company 76": makeLink("Company 46", "Company 76"),
"Company 98-Company 93": makeLink("Company 98", "Company 93"),
"Company 95-Company 45": makeLink("Company 95", "Company 45"),
"Company 57-Company 75": makeLink("Company 57", "Company 75"),
"Company 35-Company 62": makeLink("Company 35", "Company 62"),
"Company 94-Company 84": makeLink("Company 94", "Company 84"),
"Company 42-Company 90": makeLink("Company 42", "Company 90"),
"Company 72-Company 70": makeLink("Company 72", "Company 70"),
"Company 83-Company 60": makeLink("Company 83", "Company 60"),
"Company 79-Company 32": makeLink("Company 79", "Company 32"),
"Company 19-Company 59": makeLink("Company 19", "Company 59"),
"Company 16-Company 100": makeLink("Company 16", "Company 100"),
"Company 4-Company 81": makeLink("Company 4", "Company 81"),
"Company 17-Company 20": makeLink("Company 17", "Company 20"),
"Company 40-Company 95": makeLink("Company 40", "Company 95"),
"Company 72-Company 56": makeLink("Company 72", "Company 56"),
"Company 81-Company 46": makeLink("Company 81", "Company 46"),
"Company 48-Company 28": makeLink("Company 48", "Company 28"),
"Company 54-Company 6": makeLink("Company 54", "Company 6"),
"Company 83-Company 69": makeLink("Company 83", "Company 69"),
"Company 72-Company 98": makeLink("Company 72", "Company 98"),
"Company 94-Company 87": makeLink("Company 94", "Company 87"),
"Company 23-Company 37": makeLink("Company 23", "Company 37"),
"Company 95-Company 17": makeLink("Company 95", "Company 17"),
"Company 96-Company 44": makeLink("Company 96", "Company 44"),
"Company 86-Company 57": makeLink("Company 86", "Company 57"),
"Company 19-Company 26": makeLink("Company 19", "Company 26"),
"Company 81-Company 65": makeLink("Company 81", "Company 65"),
"Company 73-Company 99": makeLink("Company 73", "Company 99"),
"Company 96-Company 43": makeLink("Company 96", "Company 43"),
"Company 60-Company 54": makeLink("Company 60", "Company 54"),
"Company 48-Company 94": makeLink("Company 48", "Company 94"),
"Company 60-Company 68": makeLink("Company 60", "Company 68"),
"Company 73-Company 41": makeLink("Company 73", "Company 41"),
"Company 17-Company 18": makeLink("Company 17", "Company 18"),
"Company 64-Company 19": makeLink("Company 64", "Company 19"),
"Company 81-Company 13": makeLink("Company 81", "Company 13"),
"Company 42-Company 91": makeLink("Company 42", "Company 91"),
"Company 6-Company 72": makeLink("Company 6", "Company 72"),
"Company 78-Company 15": makeLink("Company 78", "Company 15"),
"Company 97-Company 78": makeLink("Company 97", "Company 78"),
"Company 82-Company 35": makeLink("Company 82", "Company 35"),
"Company 78-Company 92": makeLink("Company 78", "Company 92"),
"Company 46-Company 82": makeLink("Company 46", "Company 82"),
"Company 13-Company 47": makeLink("Company 13", "Company 47"),
"Company 100-Company 7": makeLink("Company 100", "Company 7"),
"Company 73-Company 4": makeLink("Company 73", "Company 4"),
"Company 73-Company 97": makeLink("Company 73", "Company 97"),
"Company 75-Company 30": makeLink("Company 75", "Company 30"),
"Company 50-Company 64": makeLink("Company 50", "Company 64"),
"Company 77-Company 34": makeLink("Company 77", "Company 34"),
"Company 6-Company 58": makeLink("Company 6", "Company 58"),
"Company 26-Company 31": makeLink("Company 26", "Company 31"),
"Company 99-Company 73": makeLink("Company 99", "Company 73"),
"Company 2-Company 55": makeLink("Company 2", "Company 55"),
"Company 43-Company 42": makeLink("Company 43", "Company 42"),
"Company 78-Company 12": makeLink("Company 78", "Company 12"),
"Company 94-Company 86": makeLink("Company 94", "Company 86"),
"Company 31-Company 9": makeLink("Company 31", "Company 9"),
"Company 92-Company 29": makeLink("Company 92", "Company 29"),
"Company 43-Company 10": makeLink("Company 43", "Company 10"),
"Company 77-Company 85": makeLink("Company 77", "Company 85"),
"Company 6-Company 16": makeLink("Company 6", "Company 16"),
"Company 47-Company 25": makeLink("Company 47", "Company 25"),
"Company 44-Company 52": makeLink("Company 44", "Company 52"),
"Company 15-Company 71": makeLink("Company 15", "Company 71"),
"Company 16-Company 24": makeLink("Company 16", "Company 24"),
"Company 94-Company 61": makeLink("Company 94", "Company 61"),
"Company 81-Company 96": makeLink("Company 81", "Company 96"),
"Company 15-Company 77": makeLink("Company 15", "Company 77"),
"Company 1-Company 11": makeLink("Company 1", "Company 11"),
"Company 86-Company 2": makeLink("Company 86", "Company 2"),
"Company 96-Company 40": makeLink("Company 96", "Company 40"),
"Company 53-Company 63": makeLink("Company 53", "Company 63"),
"Company 85": makeNode("Company 85", icons[85 % icons.length]),
"Company 74": makeNode("Company 74", icons[74 % icons.length]),
"Company 34": makeNode("Company 34", icons[34 % icons.length]),
"Company 49": makeNode("Company 49", icons[49 % icons.length]),
"Company 16": makeNode("Company 16", icons[16 % icons.length]),
"Company 51": makeNode("Company 51", icons[51 % icons.length]),
"Company 42": makeNode("Company 42", icons[42 % icons.length]),
"Company 36": makeNode("Company 36", icons[36 % icons.length]),
"Company 98": makeNode("Company 98", icons[98 % icons.length]),
"Company 67": makeNode("Company 67", icons[67 % icons.length]),
"Company 4": makeNode("Company 4", icons[4 % icons.length]),
"Company 48": makeNode("Company 48", icons[48 % icons.length]),
"Company 64": makeNode("Company 64", icons[64 % icons.length]),
"Company 1": makeNode("Company 1", icons[1 % icons.length]),
"Company 45": makeNode("Company 45", icons[45 % icons.length]),
"Company 50": makeNode("Company 50", icons[50 % icons.length]),
"Company 13": makeNode("Company 13", icons[13 % icons.length]),
"Company 23": makeNode("Company 23", icons[23 % icons.length]),
"Company 37": makeNode("Company 37", icons[37 % icons.length]),
"Company 39": makeNode("Company 39", icons[39 % icons.length]),
"Company 35": makeNode("Company 35", icons[35 % icons.length]),
"Company 83": makeNode("Company 83", icons[83 % icons.length]),
"Company 12": makeNode("Company 12", icons[12 % icons.length]),
"Company 27": makeNode("Company 27", icons[27 % icons.length]),
"Company 87": makeNode("Company 87", icons[87 % icons.length]),
"Company 38": makeNode("Company 38", icons[38 % icons.length]),
"Company 52": makeNode("Company 52", icons[52 % icons.length]),
"Company 79": makeNode("Company 79", icons[79 % icons.length]),
"Company 32": makeNode("Company 32", icons[32 % icons.length]),
"Company 53": makeNode("Company 53", icons[53 % icons.length]),
"Company 46": makeNode("Company 46", icons[46 % icons.length]),
"Company 76": makeNode("Company 76", icons[76 % icons.length]),
"Company 93": makeNode("Company 93", icons[93 % icons.length]),
"Company 95": makeNode("Company 95", icons[95 % icons.length]),
"Company 57": makeNode("Company 57", icons[57 % icons.length]),
"Company 75": makeNode("Company 75", icons[75 % icons.length]),
"Company 62": makeNode("Company 62", icons[62 % icons.length]),
"Company 94": makeNode("Company 94", icons[94 % icons.length]),
"Company 84": makeNode("Company 84", icons[84 % icons.length]),
"Company 90": makeNode("Company 90", icons[90 % icons.length]),
"Company 72": makeNode("Company 72", icons[72 % icons.length]),
"Company 70": makeNode("Company 70", icons[70 % icons.length]),
"Company 60": makeNode("Company 60", icons[60 % icons.length]),
"Company 19": makeNode("Company 19", icons[19 % icons.length]),
"Company 59": makeNode("Company 59", icons[59 % icons.length]),
"Company 100": makeNode("Company 100", icons[100 % icons.length]),
"Company 81": makeNode("Company 81", icons[81 % icons.length]),
"Company 17": makeNode("Company 17", icons[17 % icons.length]),
"Company 20": makeNode("Company 20", icons[20 % icons.length]),
"Company 40": makeNode("Company 40", icons[40 % icons.length]),
"Company 56": makeNode("Company 56", icons[56 % icons.length]),
"Company 28": makeNode("Company 28", icons[28 % icons.length]),
"Company 54": makeNode("Company 54", icons[54 % icons.length]),
"Company 6": makeNode("Company 6", icons[6 % icons.length]),
"Company 69": makeNode("Company 69", icons[69 % icons.length]),
"Company 96": makeNode("Company 96", icons[96 % icons.length]),
"Company 44": makeNode("Company 44", icons[44 % icons.length]),
"Company 86": makeNode("Company 86", icons[86 % icons.length]),
"Company 26": makeNode("Company 26", icons[26 % icons.length]),
"Company 65": makeNode("Company 65", icons[65 % icons.length]),
"Company 73": makeNode("Company 73", icons[73 % icons.length]),
"Company 99": makeNode("Company 99", icons[99 % icons.length]),
"Company 43": makeNode("Company 43", icons[43 % icons.length]),
"Company 68": makeNode("Company 68", icons[68 % icons.length]),
"Company 41": makeNode("Company 41", icons[41 % icons.length]),
"Company 18": makeNode("Company 18", icons[18 % icons.length]),
"Company 91": makeNode("Company 91", icons[91 % icons.length]),
"Company 78": makeNode("Company 78", icons[78 % icons.length]),
"Company 15": makeNode("Company 15", icons[15 % icons.length]),
"Company 97": makeNode("Company 97", icons[97 % icons.length]),
"Company 82": makeNode("Company 82", icons[82 % icons.length]),
"Company 92": makeNode("Company 92", icons[92 % icons.length]),
"Company 47": makeNode("Company 47", icons[47 % icons.length]),
"Company 7": makeNode("Company 7", icons[7 % icons.length]),
"Company 30": makeNode("Company 30", icons[30 % icons.length]),
"Company 77": makeNode("Company 77", icons[77 % icons.length]),
"Company 58": makeNode("Company 58", icons[58 % icons.length]),
"Company 31": makeNode("Company 31", icons[31 % icons.length]),
"Company 2": makeNode("Company 2", icons[2 % icons.length]),
"Company 55": makeNode("Company 55", icons[55 % icons.length]),
"Company 9": makeNode("Company 9", icons[9 % icons.length]),
"Company 29": makeNode("Company 29", icons[29 % icons.length]),
"Company 10": makeNode("Company 10", icons[10 % icons.length]),
"Company 25": makeNode("Company 25", icons[25 % icons.length]),
"Company 71": makeNode("Company 71", icons[71 % icons.length]),
"Company 24": makeNode("Company 24", icons[24 % icons.length]),
"Company 61": makeNode("Company 61", icons[61 % icons.length]),
"Company 11": makeNode("Company 11", icons[11 % icons.length]),
"Company 63": makeNode("Company 63", icons[63 % icons.length]),
}; <!doctype html>
<html>
<body>
<div id="regraph" style="height: 100vh"></div>
<script type="module" src="./code.jsx"></script>
</body>
</html>