KeyLines provides different link shapes to show connections in the most intuitive way. Multiple link shapes can be used together in the same chart.
This demo shows how to use customised link shapes for different sets of links, and specify whether they override combo link shapes.
The chart shows three sets of coloured links, each with its own link shape.
It also has two combos using a sequential arrangement with curved links.
By default the chart shows the combos with curved links, while the remaining links have the specified, colour-specific, link shapes. Check the box to override the 'curved' setting for combo links, and so use the custom link shapes instead.
See also
Documentation: Link shapes
Demos: Cloud Security
Key functions used:
import KeyLines from "keylines";
import { comboIds, data } from "./data.js";
const customiseInComboCheckbox = document.getElementById("customise-in-combos");
let chart;
function getLinkShapeForColour(colour) {
return document.querySelector(`#${colour}-link-shape-btn-group .active`).value;
}
function setLinkShapes() {
const shape = {
red: getLinkShapeForColour("red"),
green: getLinkShapeForColour("green"),
blue: getLinkShapeForColour("blue"),
};
const setOnChildLinks = customiseInComboCheckbox.checked;
const linkShapesToSet = [];
chart.each({ type: "link", items: "all" }, (l) => {
if (!l.parentId || setOnChildLinks) {
linkShapesToSet.push({ id: l.id, linkShape: { name: shape[l.d.colour] } });
} else {
linkShapesToSet.push({ id: l.id, linkShape: null });
}
});
chart.setProperties(linkShapesToSet);
}
async function layout() {
await chart.layout("sequential", {
orientation: "right",
stretchType: "auto",
});
}
// Modify the default combo double click behavior so that
// it still toggles the open state of the combo, but disables
// push-pull and runs a new layout instead
async function onDoubleClick({ id, preventDefault, button }) {
if (!id || button !== 0) {
return;
}
const comboApi = chart.combo();
const combo = comboApi.isCombo(id) ? id : chart.getItem(id).parentId;
if (combo) {
const opts = { adapt: "none" };
preventDefault();
if (comboApi.isOpen(combo)) {
await comboApi.close(combo, opts);
} else {
await comboApi.open(combo, opts);
}
await layout();
}
}
function initialiseInteractions() {
const shapeByColourButtons = document.querySelectorAll(".link-shape-btn-group button");
shapeByColourButtons.forEach((button) => {
button.addEventListener("click", () => {
const colour = button.dataset.colour;
const currentSelection = document.querySelector(`#${colour}-link-shape-btn-group .active`);
const currentShape = currentSelection.value;
if (button.value !== currentShape) {
currentSelection.classList.remove("active");
button.classList.add("active");
setLinkShapes();
}
});
});
customiseInComboCheckbox.addEventListener("change", setLinkShapes);
// perform layout on combo open/close
chart.on("double-click", onDoubleClick);
}
async function startKeyLines() {
const options = {
handMode: true,
combos: { shape: "rectangle" },
defaultStyles: { openCombos: { borderRadius: 10 } },
};
chart = await KeyLines.create({ container: "klchart", options });
await chart.load(data);
setLinkShapes();
await chart.combo().open(comboIds);
await chart
.combo()
.arrange(comboIds, { name: "sequential", orientation: "right", linkShape: "curved" });
await layout();
initialiseInteractions();
}
window.addEventListener("DOMContentLoaded", startKeyLines); const colours = {
green: "#048170",
red: "#DD3C3C",
blue: "#3377FF",
grey: "#A9A9A9",
};
export const comboIds = ["n2", "n3"];
const nodes = [
{
id: "n1",
type: "node",
},
{
id: "n2",
type: "node",
},
{
id: "n2-a",
type: "node",
parentId: "n2",
},
{
id: "n2-b",
type: "node",
parentId: "n2",
},
{
id: "n2-c",
type: "node",
parentId: "n2",
},
{
id: "n2-d",
type: "node",
parentId: "n2",
},
{
id: "n2-e",
type: "node",
parentId: "n2",
},
{
id: "n2-f",
type: "node",
parentId: "n2",
},
{
id: "n3",
type: "node",
},
{
id: "n3-a",
type: "node",
parentId: "n3",
},
{
id: "n3-b",
type: "node",
parentId: "n3",
},
{
id: "n3-c",
type: "node",
parentId: "n3",
},
{
id: "n3-d",
type: "node",
parentId: "n3",
},
{
id: "n3-e",
type: "node",
parentId: "n3",
},
{
id: "n4",
type: "node",
},
{
id: "n5",
type: "node",
},
{
id: "n6",
type: "node",
},
{
id: "n7",
type: "node",
},
{
id: "n8",
type: "node",
},
{
id: "n9",
type: "node",
},
];
nodes.forEach((n) => {
n.e = 0.4;
n.sh = "box";
n.borderRadius = 5;
n.c = colours.grey;
});
const links = [
{
id: "n1-n2-red",
type: "link",
w: 4,
id1: "n1",
id2: "n2",
c: colours.red,
ls: "dashed",
a2: true,
d: { colour: "red" },
},
{
id: "n2-a-n2-b-green",
type: "link",
w: 4,
id1: "n2-a",
id2: "n2-b",
c: colours.green,
ls: "dotted",
a2: true,
d: { colour: "green" },
},
{
id: "n2-a-n2-c-red",
type: "link",
w: 4,
id1: "n2-a",
id2: "n2-c",
c: colours.red,
ls: "dashed",
a2: true,
d: { colour: "red" },
},
{
id: "n2-a-n2-d-red",
type: "link",
w: 4,
id1: "n2-a",
id2: "n2-d",
c: colours.red,
ls: "dashed",
a2: true,
d: { colour: "red" },
},
{
id: "n2-b-n2-e-blue",
type: "link",
w: 4,
id1: "n2-b",
id2: "n2-e",
c: colours.blue,
a2: true,
d: { colour: "blue" },
},
{
id: "n2-b-n2-f-blue",
type: "link",
w: 4,
id1: "n2-b",
id2: "n2-f",
c: colours.blue,
a2: true,
d: { colour: "blue" },
},
{
id: "n1-n3-green",
type: "link",
w: 4,
id1: "n1",
id2: "n3",
c: colours.green,
ls: "dotted",
a2: true,
d: { colour: "green" },
},
{
id: "n3-a-n3-b-green",
type: "link",
w: 4,
id1: "n3-a",
id2: "n3-b",
c: colours.green,
ls: "dotted",
a2: true,
d: { colour: "green" },
},
{
id: "n3-b-n3-c-green",
type: "link",
w: 4,
id1: "n3-b",
id2: "n3-c",
c: colours.green,
ls: "dotted",
a2: true,
d: { colour: "green" },
},
{
id: "n3-c-n3-e-green",
type: "link",
w: 4,
id1: "n3-c",
id2: "n3-e",
c: colours.green,
ls: "dotted",
a2: true,
d: { colour: "green" },
},
{
id: "n3-a-n3-d-red",
type: "link",
w: 4,
id1: "n3-a",
id2: "n3-d",
c: colours.red,
ls: "dashed",
a2: true,
d: { colour: "red" },
},
{
id: "n3-n4-blue",
type: "link",
w: 4,
id1: "n3",
id2: "n4",
c: colours.blue,
a2: true,
d: { colour: "blue" },
},
{
id: "n2-n4-green",
type: "link",
w: 4,
id1: "n2",
id2: "n4",
c: colours.green,
ls: "dotted",
a2: true,
d: { colour: "green" },
},
{
id: "n2-n5-green",
type: "link",
w: 4,
id1: "n2",
id2: "n5",
c: colours.green,
ls: "dotted",
a2: true,
d: { colour: "green" },
},
{
id: "n4-n6-green",
type: "link",
w: 4,
id1: "n4",
id2: "n6",
c: colours.green,
ls: "dotted",
a2: true,
d: { colour: "green" },
},
{
id: "n6-n7-blue",
type: "link",
w: 4,
id1: "n6",
id2: "n7",
c: colours.blue,
a2: true,
d: { colour: "blue" },
},
{
id: "n6-n8-blue",
type: "link",
w: 4,
id1: "n6",
id2: "n8",
c: colours.blue,
a2: true,
d: { colour: "blue" },
},
{
id: "n6-n9-blue",
type: "link",
w: 4,
id1: "n6",
id2: "n9",
c: colours.blue,
a2: true,
d: { colour: "blue" },
},
];
export const data = {
type: "LinkChart",
items: [...nodes, ...links],
}; <!doctype html>
<html lang="en" style="background-color: #2d383f">
<head>
<meta charset="utf-8" />
<title>Mixed Link Shapes</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="mixedlinkshapes.css" />
<link rel="stylesheet" href="./style.css" />
</head>
<body>
<div id="klchart" class="klchart"></div>
<script type="module" src="./code.js"></script>
</body>
</html> .btn-row {
display: flex;
margin: 8px 0px;
}
.btn-row p {
text-align: right;
padding: 8px;
width: 64px;
}
.btn-group .btn:hover {
color: white;
}
#red-link-shape-btn-group .btn.active {
background-color: #dd3c3c;
border-color: #dd3c3c;
}
#red-link-shape-btn-group .btn:hover {
background-color: #f15d5b;
border-color: #f15d5b;
}
#green-link-shape-btn-group .btn.active {
background-color: #048170;
border-color: #048170;
}
#green-link-shape-btn-group .btn:hover {
background-color: #2dcda8;
border-color: #2dcda8;
}
#blue-link-shape-btn-group .btn.active {
background-color: #3377ff;
border-color: #3377ff;
}
#blue-link-shape-btn-group .btn:hover {
background-color: #6699ff;
border-color: #6699ff;
}