//
// Copyright © 2011-2025 Cambridge Intelligence Limited.
// All rights reserved.
//
// Sample Code
//
//! Organise links into aggregate links to reduce clutter in the chart.
import KeyLines from "keylines";
import { data, combineNodesAndRevealLinks } from "./linkaggregation-data.js";
let chart;
async function layout() {
await chart.layout("sequential", {
orientation: "right",
stretch: 2,
level: "level",
});
}
function isCombo(ids, type = "node") {
return chart.combo().isCombo(ids, { type });
}
function openCombo(ids) {
const targets = Array.isArray(ids) ? ids : [ids];
chart.combo().open(targets, { adapt: "inCombo" });
}
function closeCombo(ids) {
const targets = Array.isArray(ids) ? ids : [ids];
chart.combo().close(targets, { adapt: "inCombo" });
}
function initialiseInteractions() {
document.querySelectorAll('input[name="aggregateLinks"]').forEach((radio) => {
radio.addEventListener("click", async (e) => {
const value = e.target.value;
switch (value) {
case "true":
chart.options({ aggregateLinks: true });
break;
case "false":
chart.options({ aggregateLinks: false });
break;
case "via":
chart.options({ aggregateLinks: { aggregateBy: "via" } });
break;
}
});
});
chart.on("link-aggregation", ({ change, links, aggregateByValue, id }) => {
if (change !== "deleted") {
// style the aggregate after it has been created or updated
let colour = "grey";
let typeGlyph; // glyph that shows the type of transport
if (aggregateByValue) {
// if aggregating by link type, get some styling from a child link
const childLink = chart.getItem(links[0]);
colour = childLink.c;
typeGlyph = childLink.g[0];
}
// create a glyph to show the number of child links
const glyphs = [{ c: "white", b: colour, t: links.length, fc: colour }];
if (typeGlyph) {
glyphs.push(typeGlyph);
}
chart.setProperties({ id, w: links.length, c: colour, g: glyphs });
}
});
chart.on("double-click", ({ id, preventDefault, button }) => {
if (id && button === 0) {
if (isCombo(id)) {
if (chart.combo().isOpen(id)) {
closeCombo(id);
} else {
openCombo(id);
}
}
preventDefault();
}
});
}
async function startKeyLines() {
const options = {
handMode: true,
selectionColour: "#f75252",
combos: { shape: "rectangle" },
iconFontFamily: "Font Awesome 5 Free",
linkEnds: { avoidLabels: false },
aggregateLinks: { aggregateBy: "via" },
};
chart = await KeyLines.create({ container: "klchart", options });
initialiseInteractions();
chart.load(data);
// combine and reveal links
await combineNodesAndRevealLinks(chart);
await layout();
}
function loadWebFonts() {
document.fonts.load('24px "Font Awesome 5 Free"').then(startKeyLines);
}
window.addEventListener("DOMContentLoaded", loadWebFonts);
<!DOCTYPE html>
<html lang="en" style="background-color: #2d383f;">
<head>
<meta charset="UTF-8">
<title>Aggregating Links</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="/css/fontawesome5/fontawesome.css">
<link rel="stylesheet" type="text/css" href="/css/fontawesome5/solid.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="/linkaggregation.js" crossorigin="use-credentials" 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%;">Aggregating Links</text>
</svg>
</div>
<div class="tab-content-panel" data-tab-group="rhs">
<div class="toggle-content is-visible tabcontent" id="controlsTab">
<form autocomplete="off" onsubmit="return false" id="rhsForm">
<div class="cicontent">
<fieldset></fieldset>
<div class="row-fluid" style="margin-top: 10px;">
<p>Switch between link aggregation options and open and close combos to show different levels of detail.</p>
<h5 style="margin-top:10px">Link aggregation</h5>
<div class="radio-container">
<label class="radio">
<input type="radio" name="aggregateLinks" value="false"> None
</label>
<label class="radio">
<input type="radio" name="aggregateLinks" value="true"> All links
</label>
<label class="radio">
<input type="radio" name="aggregateLinks" value="via" checked="checked"> Aggregate by link category
</label>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<div id="moreParent">
<div id="moreContainer">
</div>
</div>
<div id="lazyScripts">
</div>
</body>
</html>
Loading source
