//
//     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