//
//     Copyright © 2011-2025 Cambridge Intelligence Limited.
//     All rights reserved.
//
//     Sample Code
//
//!    Animate properties to highlight interesting items.

import KeyLines from "keylines";

import data from "./animation-data.js";

let chart;

const palette = [
  "rgb(166, 86, 40)",
  "rgb(247, 129, 191)",
  "rgb(55, 126, 184)",
  "rgb(255, 127, 0)",
  "rgb(77, 175, 74)",
  "rgb(228, 26, 28)",
  "rgb(152, 78, 163)",
  "rgb(255, 255, 51)",
  "rgb(153, 153, 153)",
  "rgb(75, 75, 75)",
  "rgb(200, 200, 200)",
];

const numberOfNodes = 11;
const interval = 1000;
const delta = 50;
let nextPing = 0;

// Return a random integer from 0 to n-1
function randInt(n) {
  return Math.floor(Math.random() * n);
}

// Animate properties depending on which checkboxes are checked
async function doAnimation() {
  const items = [];

  // Defining Buttons
  const animPos = document.getElementById("animpos").checked;
  const animSize = document.getElementById("animsize").checked;
  const nodeColour = document.getElementById("nodecolour").checked;
  const animWidth = document.getElementById("animwidth").checked;

  // Animate nodes
  if (animPos || animSize || nodeColour) {
    chart.each({ type: "node" }, (node) => {
      const item = { id: node.id };

      if (animPos) {
        item.x = node.x + randInt(2 * delta) - delta;
        item.y = node.y + randInt(2 * delta) - delta;
      }

      if (animSize) {
        item.e = (randInt(20) + 5) / 10;
      }

      if (nodeColour) {
        const newColour = palette[randInt(palette.length)];
        if (node.fi) {
          // set a random colour from the palette to the font icon node
          item.fi = { c: newColour };
        } else {
          item.c = newColour;
        }
      }

      items.push(item);
    });
  }

  // Animate links
  if (animWidth) {
    chart.each({ type: "link" }, (link) => {
      // We don't want the width of a link to be larger than the
      // Smallest width of the nodes it's linking
      const end1 = chart.getItem(link.id1);
      const end2 = chart.getItem(link.id2);
      const maxEnlargement = Math.min(end1.e, end2.e);
      items.push({
        id: link.id,
        w: randInt(maxEnlargement * 20) + 1,
      });
    });
  }

  await chart.animateProperties(items, { time: interval });
  // we call this function here so that it starts again once
  // the animation is complete
  doAnimation();
}

// Ping the next node in sequence
function doPing() {
  const id = `n${nextPing}`;

  const node = chart.getItem(id);

  // We make the halo colour match the node colour
  chart.ping(id, { c: node.fi ? node.fi.c : node.c });

  // Increment nextPing, wrapping to zero when we reach numberOfNodes
  nextPing = (nextPing + 1) % numberOfNodes;
}

function enableInteraction() {
  doAnimation();

  // Hook up the Ping button handler
  const pingButton = document.getElementById("ping");
  pingButton.addEventListener("click", () => {
    doPing();
  });
}

async function startKeyLines() {
  const options = {
    drag: {
      links: false,
    },
    logo: "/images/Logo.png",
    handMode: true,
    iconFontFamily: "Font Awesome 5 Free",
  };

  chart = await KeyLines.create({
    container: "klchart",
    options,
  });
  chart.load(data);
  chart.layout("organic", { animate: false, tightness: 2 });
  enableInteraction();
}

function loadKeyLines() {
  document.fonts.load('24px "Font Awesome 5 Free"').then(startKeyLines);
}

window.addEventListener("DOMContentLoaded", loadKeyLines);
<!DOCTYPE html>
<html lang="en" style="background-color: #2d383f;">
  <head>
    <meta charset="UTF-8">
    <title>Animate Items</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="/animation.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%;">Animate Items</text>
          </svg>
        </div>
        <div class="tab-content-panel" data-tab-group="rhs">
          <div class="toggle-content is-visible tabcontent" id="controlsTab">
            <p>Choose the properties to animate.</p>
            <form autocomplete="off" onsubmit="return false" id="rhsForm">
              <div class="cicontent">
                <fieldset>
                  <label class="checkbox">
                    <input id="animsize" type="checkbox" checked="true">Node enlargement
                  </label>
                  <label class="checkbox">
                    <input id="animpos" type="checkbox">Node position
                  </label>
                  <label class="checkbox">
                    <input id="animwidth" type="checkbox">Link width
                  </label>
                  <label class="checkbox">
                    <input id="nodecolour" type="checkbox">Node colour
                  </label>
                </fieldset>
                <fieldset>
                  <div class="form-inline">
                    <input class="btn btn-spaced" type="button" value="Ping" id="ping">
                  </div>
                </fieldset>
              </div>
            </form>
          </div>
        </div>
      </div>
    </div>
    <div id="moreParent">
      <div id="moreContainer">
      </div>
    </div>
    <div id="lazyScripts">
    </div>
  </body>
</html>
Loading source