//
// Copyright © 2011-2025 Cambridge Intelligence Limited.
// All rights reserved.
//
// Sample Code
//
//! See how to use multiple link shapes in your chart.
import KeyLines from "keylines";
import { comboIds, data } from "./mixedlinkshapes-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);
<!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="/css/keylines.css">
<link rel="stylesheet" type="text/css" href="/css/minimalsdk.css">
<link rel="stylesheet" type="text/css" href="/css/sdk-layout.css">
<link rel="stylesheet" type="text/css" href="/css/demo.css">
<link rel="stylesheet" type="text/css" href="mixedlinkshapes.css">
<script src="/vendor/jquery.js" defer type="text/javascript"></script>
<script id="keylines" src="/public/keylines.umd.cjs" defer type="text/javascript"></script>
<script src="/vendor/webfontloader.js" defer type="text/javascript"></script>
<script src="/mixedlinkshapes.js" crossorigin="use-credentials" defer type="module"></script>
</head>
<body>
<div class="chart-wrapper demo-cols">
<div class="tab-content-panel flex1" id="lhs" data-tab-group="rhs">
<div class="toggle-content is-visible" id="lhsVisual" style="width:100%; height: 100%;">
<div class="klchart" id="klchart">
</div>
</div>
</div>
<div class="rhs citext closed" id="demorhscontent">
<div class="title-bar">
<svg viewBox="0 0 360 60" style="max-width: 360px; max-height: 60px; font-size: 24px; font-family: Raleway; flex: 1;">
<text x="0" y="38" style="width: 100%;">Mixed Link Shapes</text>
</svg>
</div>
<div class="tab-content-panel" data-tab-group="rhs">
<div class="toggle-content is-visible tabcontent" id="controlsTab">
<p>Select a link shape for each set of coloured links. Choose whether they override the combo link shape, which is set to 'curved'.</p>
<form autocomplete="off" onsubmit="return false" id="rhsForm">
<div class="cicontent">
<fieldset>
<legend>Link Shape Customisation</legend>
<div class="btn-row">
<p>Red</p>
<div class="link-shape-btn-group btn-group" id="red-link-shape-btn-group">
<button class="btn btn-kl" type="button" data-colour="red" value="direct">Direct</button>
<button class="btn btn-kl active" type="button" data-colour="red" value="curved">Curved</button>
<button class="btn btn-kl" type="button" data-colour="red" value="angled">Angled</button>
</div>
</div>
<div class="btn-row">
<p>Green</p>
<div class="link-shape-btn-group btn-group" id="green-link-shape-btn-group">
<button class="btn btn-kl active" type="button" data-colour="green" value="direct">Direct</button>
<button class="btn btn-kl" type="button" data-colour="green" value="curved">Curved</button>
<button class="btn btn-kl" type="button" data-colour="green" value="angled">Angled</button>
</div>
</div>
<div class="btn-row">
<p>Blue</p>
<div class="link-shape-btn-group btn-group" id="blue-link-shape-btn-group">
<button class="btn btn-kl" type="button" data-colour="blue" value="direct">Direct</button>
<button class="btn btn-kl" type="button" data-colour="blue" value="curved">Curved</button>
<button class="btn btn-kl active" type="button" data-colour="blue" value="angled">Angled</button>
</div>
</div>
<label class="checkbox inline">
<input id="customise-in-combos" type="checkbox">Apply customised link shapes within combos
</label>
</fieldset>
</div>
</form>
</div>
</div>
</div>
</div>
<div id="moreParent">
<div id="moreContainer">
</div>
</div>
<div id="lazyScripts">
</div>
</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;
}
Loading source
