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