//
// Copyright © 2011-2025 Cambridge Intelligence Limited.
// All rights reserved.
//
// Sample Code
//
//! Expand existing nodes to reveal new connections.
import KeyLines from "keylines";
import { data } from "./incrementallayout-data.js";
let chart;
function getRootNodes() {
const rootNodes = [];
chart.each({ type: "node" }, (node) => {
if (node.d.level === 0) {
rootNodes.push(node.id);
}
});
return rootNodes;
}
async function runExpand(itemsToExpand) {
const layoutName = document.querySelector(".btn-group button.btn.active")
.value;
const fixOption = document.querySelector('input[name="fix"]:checked').value;
const options = {
name: layoutName,
fit: true,
fix: fixOption,
consistent: fixOption !== "none",
};
if (layoutName === "radial" || layoutName === "sequential") {
options.level = "level";
options.top = getRootNodes();
}
chart.lock(true);
await chart.expand(itemsToExpand, { layout: options });
chart.lock(false);
}
async function runLayout() {
const layoutName = document.querySelector(".btn-group button.btn.active")
.value;
const options = { packing: "adaptive" };
if (layoutName === "radial" || layoutName === "sequential") {
options.level = "level";
options.top = getRootNodes();
}
if (layoutName === "sequential") {
options.packing = "aligned";
}
await chart.layout(layoutName, options);
}
function setUIAvailability(enabled) {
document.getElementById("reset").disabled = !enabled;
const buttons = ["organic", "sequential", "radial"];
buttons.forEach((button) => {
document.getElementById(button).disabled = !enabled;
});
const inputs = ['input[name="fix"]'];
inputs.forEach((input) => {
document.querySelectorAll(input).forEach((radio) => {
radio.disabled = !enabled;
});
});
}
function setFixOptionAvailability(layoutName) {
const fixInputs = document.querySelectorAll('input[name="fix"]');
fixInputs.forEach((radio) => {
if (layoutName === "radial") {
radio.disabled = true;
} else if (layoutName === "sequential") {
radio.disabled = radio.value === "all";
} else {
radio.disabled = false;
}
});
}
// If double-click is on the background, then create a new component
// otherwise expand node connections
async function doubleClickHandler({ id, preventDefault }) {
preventDefault();
setUIAvailability(false);
if (id) {
const item = chart.getItem(id);
if (item && item.type === "node") {
// remove glyph from selected node
chart.setProperties({ id: item.id, g: [] });
await runExpand(data.expandTree(item));
}
} else {
await runExpand(data.createTree());
}
setUIAvailability(true);
setFixOptionAvailability(
document.querySelector(".btn-group button.btn.active").value
);
}
async function resetChart() {
const items = data.reset();
chart.load(items);
await runLayout();
}
function addInteractions() {
chart.on("double-click", doubleClickHandler);
document.getElementById("reset").addEventListener("click", resetChart);
const layoutButtons = ["organic", "sequential", "radial"];
layoutButtons.forEach((button) => {
const buttonElement = document.getElementById(button);
buttonElement.addEventListener("click", handleLayoutButtonClick);
});
}
async function handleLayoutButtonClick(event) {
updateButton(event.target.id);
setFixOptionAvailability(event.target.value);
await runLayout();
}
function updateButton(clickedButtonId) {
const layoutButtons = ["organic", "sequential", "radial"];
layoutButtons.forEach((buttonId) => {
const buttonElement = document.getElementById(buttonId);
if (buttonId === clickedButtonId) {
buttonElement.classList.add("active");
} else {
buttonElement.classList.remove("active");
}
});
}
async function loadKeyLines() {
const options = {
logo: "/images/Logo.png",
handMode: true,
iconFontFamily: "Font Awesome 5 Free",
selectedNode: {
ha0: {
c: "rgb(114, 179, 0)",
w: 10,
r: 30,
},
},
};
chart = await KeyLines.create({
container: "klchart",
options,
});
resetChart();
addInteractions();
}
function loadFonts() {
document.fonts.load('24px "Font Awesome 5 Free"').then(loadKeyLines);
}
window.addEventListener("DOMContentLoaded", loadFonts);
<!DOCTYPE html>
<html lang="en" style="background-color: #2d383f;">
<head>
<meta charset="UTF-8">
<title>Adaptive Layouts</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="/incrementallayout.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%;">Adaptive Layouts</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">
<div class="row-fluid">
<p>Double click anywhere on the background to add a new graph component or on a node with a <i class="fas fa-plus"></i> to expand its connections.</p>
</div>
<div class="row-fluid" style="margin-top: 20px;">
<button class="btn" value="reset" id="reset">Reset</button>
</div>
<div class="row-fluid" style="margin-top: 20px;"><b>Select a layout:</b>
<div class="btn-container"></div>
<div class="btn-group" style="display: flex;">
<button class="btn active" value="organic" id="organic">Organic</button>
<button class="btn" value="sequential" id="sequential">Sequential</button>
<button class="btn" value="radial" id="radial">Radial</button>
</div>
</div>
<div class="row-fluid" style="margin-top: 20px;"><b>Select a behaviour for expanding nodes:</b>
<div class="radio-container">
<label class="radio">
<input type="radio" name="fix" value="adaptive" checked="checked"> Adaptive
</label>
<label class="radio">
<input type="radio" name="fix" value="none"> Full layout
</label>
<label class="radio">
<input type="radio" name="fix" value="all"> Fix everything
</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
