//
//     Copyright © 2011-2025 Cambridge Intelligence Limited.
//     All rights reserved.
//
//!    Discover new ways to visualise large networks.
import KeyLines from "keylines";
let chart;
let dataSizeOverlay;

function getById(id) {
  return document.getElementById(id);
}

const progressBarContainer = getById("progressBarContainer");
const progressBar = getById("progressBar");
const progressFrame = getById("progressFrame");
const layoutButton = getById("layoutButton");
const fpsLabel = getById("fps");

function updateProgressBar(progress) {
  const percentage = (progress * 100).toFixed(2);
  progressBar.style.width = `${percentage}%`;
}

async function doLayout() {
  layoutButton.disabled = true;
  progressBarContainer.style.display = "block";
  await chart.layout("organic", { time: 1500 });
  progressBarContainer.style.display = "none";
  layoutButton.disabled = false;
}

function setupLayoutUX() {
  // Add progress event listener
  chart.on("progress", ({ progress, task }) => {
    if (task === "layout") {
      updateProgressBar(progress);
    }
  });
  layoutButton.addEventListener("click", () => doLayout());
}

function fpsCounter(blend = 0.33) {
  let tick = Date.now();
  let mean;

  (function next() {
    requestAnimationFrame((tock) => {
      const dt = tock - tick;
      mean = mean ? mean * blend + dt * (1 - blend) : dt;
      tick = tock;
      next();
    });
  })();

  return { get: () => Math.round(1000 / mean) };
}

function toggleSwitch(name, onOn, onOff) {
  const onElement = getById(`${name}On`);
  const offElement = getById(`${name}Off`);
  onElement.addEventListener("click", (evt) => {
    onElement.classList.add("active", "btn-kl");
    offElement.classList.remove("active", "btn-kl");
    offElement.disabled = false;
    onElement.disabled = true;
    onOn();
    evt.preventDefault();
  });
  offElement.addEventListener("click", (evt) => {
    onElement.classList.remove("active", "btn-kl");
    onElement.disabled = false;
    offElement.disabled = true;
    offElement.classList.add("active", "btn-kl");
    onOff();
    evt.preventDefault();
  });
}

async function loadAndStart() {
  dataSizeOverlay = document.getElementsByClassName("datasize")[0];
  const chartDefinition = {
    container: "klchart",
    options: {
      backColour: "#2d383f",
      controlTheme: "dark",
      drag: {
        links: false,
      },
      minZoom: 0.01,
      overview: {
        backColour: "#2d383f",
        borderColour: "grey",
      },
      handMode: true,
    },
  };
  chart = await KeyLines.create(chartDefinition);

  // display frame rate
  setInterval(
    (fps) => {
      fpsLabel.innerText = fps.get() + " Frames per Second";
    },
    400,
    fpsCounter()
  );

  function adaptiveStylingOn() {
    chart.options({ zoom: { adaptiveStyling: true } });
  }

  function adaptiveStylingOff() {
    chart.options({ zoom: { adaptiveStyling: false } });
  }

  function darkModeOn() {
    chart.options({
      backColour: "#2d383f",
      controlTheme: "dark",
      overview: {
        backColour: "#2d383f",
        borderColour: "grey",
      },
    });
    progressFrame.style["border-color"] = "white";
    dataSizeOverlay.classList.add("dark");
  }

  function darkModeOff() {
    chart.options({
      backColour: "white",
      controlTheme: "light",
      overview: {
        backColour: "white",
        borderColour: "rgb(243, 246, 247)",
      },
    });
    progressFrame.style["border-color"] = "black";
    dataSizeOverlay.classList.remove("dark");
  }

  function gradientLinksOn() {
    const links = [];
    chart.each({ type: "link" }, (link) => {
      links.push({ id: link.id, c: link.d.c, c2: link.d.c2 });
    });
    chart.setProperties(links);
  }

  function gradientLinksOff() {
    const links = [];
    chart.each({ type: "link" }, (link) => {
      links.push({
        id: link.id,
        c: null,
        c2: null,
        d: { c: link.c, c2: link.c2 },
      });
    });
    chart.setProperties(links);
  }

  toggleSwitch("adaptiveStyling", adaptiveStylingOn, adaptiveStylingOff);
  toggleSwitch("darkMode", darkModeOn, darkModeOff);
  toggleSwitch("gradientLinks", gradientLinksOn, gradientLinksOff);

  const data = await (await fetch("/worldflights-data.json")).json();
  chart.load(data);

  setupLayoutUX();
  await doLayout();
}

window.addEventListener("DOMContentLoaded", loadAndStart);
<!DOCTYPE html>
<html lang="en" style="background-color: #2d383f;">
  <head>
    <meta charset="UTF-8">
    <title>Visualising Big Data</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="/worldflights.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="/worldflights.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 id="progressBarContainer">
              <div class="progress" id="progressFrame">
                <div id="progressBar"></div>
              </div>
            </div>
            <div class="datasize dark" id="dataSize">
              <h5 id="fps">0 Frames per Second</h5>
              <p>Nodes: 2875</p>
              <p>Links: 13139</p>
            </div>
          </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%;">Visualising Big Data</text>
          </svg>
        </div>
        <div class="tab-content-panel" data-tab-group="rhs">
          <div class="toggle-content is-visible tabcontent" id="controlsTab">
            <p>Discover new ways to visualise large amounts of data.</p>
            <form autocomplete="off" onsubmit="return false" id="rhsForm">
              <div class="cicontent">
                <p>Zoom in and out to trigger adaptive styling and watch the chart maintain a clean view with the right amount of detail.</p>
                <input class="btn" type="button" value="Layout" style="text-align: center; width: 200px; margin-top:12px;" disabled id="layoutButton">
                <fieldset>
                  <legend>Options</legend>
                  <h4>Adaptive Styling</h4>
                  <div style="margin-left:14px; margin-bottom: 16px;">
                    <div class="btn-group">
                      <input class="btn btn-kl active" type="button" value="On" disabled id="adaptiveStylingOn">
                      <input class="btn" type="button" value="Off" id="adaptiveStylingOff">
                    </div>
                  </div>
                  <h4>Dark Mode</h4>
                  <div style="margin-left:14px; margin-bottom: 16px;">
                    <div class="btn-group">
                      <input class="btn btn-kl active" type="button" value="On" disabled id="darkModeOn">
                      <input class="btn" type="button" value="Off" id="darkModeOff">
                    </div>
                  </div>
                  <h4>Gradient Links</h4>
                  <div style="margin-left:14px; margin-bottom: 16px;">
                    <div class="btn-group">
                      <input class="btn btn-kl active" type="button" value="On" disabled id="gradientLinksOn">
                      <input class="btn" type="button" value="Off" id="gradientLinksOff">
                    </div>
                  </div>
                </fieldset>
              </div>
            </form>
          </div>
        </div>
      </div>
    </div>
    <div id="moreParent">
      <div id="moreContainer">
      </div>
    </div>
    <div id="lazyScripts">
    </div>
  </body>
</html>
.klchart {
  background-color: #2d383f;
}

.datasize {
  position: absolute;
  bottom: 0px;
  height: 100px;
  line-height:16px;
  left: 16px;
  color: #2d383f;
  background-color: transparent;
}

.datasize > h5 {
  font-size: 20px;
}

.datasize.dark {
  color: #eee;
}

.progress {
  position: absolute;
  top: calc(50% - 22.5px);
  left: 30%;
  width: 40%;
  height: 45px;
  padding: 4px;
  opacity: 0.9;
  border: white 3px solid;
}

.progress > div {
  background-color: #009968;
  width: 0%;
  height: 100%;
  transition: width 1s ease-in;
}
Loading source