//
//     Copyright © 2011-2025 Cambridge Intelligence Limited.
//     All rights reserved.
//
//     Sample Code
//
//!    Explore ways to style nodes and make them stand out.
import KeyLines from "keylines";
import { data } from "./nodestyles-data.js";

let chart;

// Puts the selected item and its group into foreground, and everything else into the background
function highlightSection(id) {
  // if the user hasn't clicked an element resize back to fit
  if (!id) {
    chart.foreground(() => true);
    return;
  }
  // if there is a clicked item set it to be foreground
  const clickedItem = chart.getItem(id);
  if (clickedItem) {
    chart.foreground((item) => clickedItem.id === item.id);
  }
}

function formatPrintString(key, value) {
  if (key === "x" || key === "y" || key === "bg") {
    return undefined;
  }
  // prints the borderRadius array as a single value if all directions are set to the same value
  if (key === "borderRadius") {
    const set = new Set(value);
    if (set.size === 1) {
      return value[0];
    }
  }
  return value;
}

// Formats the code snippets neatly in the display box on the right-hand side
function prettyPrint(id) {
  const item = chart.getItem(id);
  if (item) {
    const json = JSON.stringify(item, formatPrintString, 1);
    document.getElementById("display").innerHTML = json.replace(
      /"(t)":\s?"(KeyLines\.getFontIcon[^"]+)"/g,
      '"$1": $2'
    );
  } else {
    document.getElementById("display").innerHTML = "No node selected";
  }
}

// Starts and stops the glyph animation
let animated = false;
function startStopAnimation() {
  animated = !animated;
  // get the node to be animated
  const animatedNode = chart.getItem("g3");
  // update the first glyph of the selected item by toggling the animation option on/off
  animatedNode.g[0].a = !animatedNode.g[0].a;

  chart.setProperties(animatedNode);

  // Ensure that the displayed source code reflects the current state of the animated element.
  if (chart.selection()[0] === "g3") {
    prettyPrint("g3");
  }
}

// Called when KeyLines is created
function initializeInteractions() {
  // Calls highlighting and pretty printing when a selection is made
  chart.on("selection-change", () => {
    const id = chart.selection()[0];
    highlightSection(id);
    prettyPrint(id);
  });

  // Zoom the view to its original position when double-clicking the background
  chart.on("double-click", ({ id, preventDefault }) => {
    if (!chart.getItem(id)) {
      chart.zoom("fit", { animate: true, time: 500 });
    }
    // Default behaviour is to zoom in - we preventDefault to override this
    preventDefault();
  });

  // prevent nodes from being dragged
  chart.on("drag-start", ({ preventDefault, type }) => {
    if (type === "node") {
      preventDefault();
    }
  });

  // Listen to the checkbox change event so that the startStopAnimation function
  // updates the animation property of the glyph.
  document
    .getElementById("animateGlyph")
    .addEventListener("change", startStopAnimation);
}

// Custom image alignments for fontawesome nodes
const imageAlignment = {
  "fas fa-user": { dy: -8 },
  "far fa-user": { dy: -8 },
  "fas fa-exclamation-triangle": { dy: -10 },
};

async function startKeyLines() {
  const options = {
    logo: { u: "/images/Logo.png" },
    handMode: true,
    selectedNode: { b: "#FF9933" },
    iconFontFamily: "Font Awesome 5 Free",
    imageAlignment,
    overview: { icon: false },
    backColour: "rgb(238,238,238)",
  };

  chart = await KeyLines.create({ container: "klchart", options });
  chart.load(data);
  chart.zoom("fit");
  initializeInteractions();
}

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

window.addEventListener("DOMContentLoaded", loadFontsAndStart);
<!DOCTYPE html>
<html lang="en" style="background-color: #2d383f;">
  <head>
    <meta charset="UTF-8">
    <title>Node Styles</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="/nodestyles.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%;">Node Styles</text>
          </svg>
        </div>
        <div class="tab-content-panel" data-tab-group="rhs">
          <div class="toggle-content is-visible tabcontent" id="controlsTab">
            <p>Select a node to view its source code.</p>
            <form autocomplete="off" onsubmit="return false" id="rhsForm">
              <div class="cicontent">
                <fieldset>
                  <pre id="display" style="-webkit-user-select: text; -khtml-user-select: text; -moz-user-select: text; -ms-user-select: text; word-break: keep-all;">No node selected</pre>
                  <label class="checkbox" style="margin-top:20px;">
                    <input id="animateGlyph" type="checkbox"><span>&nbsp;Show Animated Glyph</span>
                  </label>
                </fieldset>
              </div>
            </form>
          </div>
        </div>
      </div>
    </div>
    <div id="moreParent">
      <div id="moreContainer">
      </div>
    </div>
    <div id="lazyScripts">
    </div>
  </body>
</html>
Loading source