//
//     Copyright © 2011-2025 Cambridge Intelligence Limited.
//     All rights reserved.
//
//     Sample Code
//
//!    Create annotations with labels, buttons and interactive controls.

import {
  data,
  itemsMap,
  colours,
  themeAnnotations,
  threadAnnotations,
  icons,
  showAnnotationGlyph,
} from "./advancedannotationstyles-data.js";

import KeyLines from "keylines";

let chart;
const subItemTypePropMap = {
  glyph: "g",
  label: "t",
};
/**
 * Contains the data and click handlers needed to manage and process user interactions with chart items
 * Sub items (labels / glyphs) act as buttons
 * - an onClick handler is specified for each button and called via the chart 'click' event
 * - a hover style is specified for each hoverable button and applied using chart.setProperties() via the chart 'hover' event
 * The lastHoveredButton property stores the item id and subItem id of the most recently hovered button
 * so that the hover effect can be removed when it is unhovered
 */
const interactionInfo = {
  // store the last hovered button's parent id and subItemIndex for style updates
  lastHoveredButton: null,
  items: {
    a4: {
      subItemType: "glyph",
      buttons: {
        0: {
          onClick: async () => await toggleShowHideAnnotation("hide"),
          styles: {
            default: {
              c: colours.a4.defaultButton,
              b: colours.a4.defaultButton,
            },
            hovered: {
              c: colours.a4.hoveredButton,
              b: colours.a4.hoveredButton,
            },
          },
        },
      },
    },
    n4: {
      subItemType: "glyph",
      buttons: {
        0: {
          onClick: async () => await toggleShowHideAnnotation("show"),
          styles: {
            default: {
              fi: {
                t: icons.comment,
                c: colours.a4.defaultButton,
              },
            },
            hovered: {
              fi: {
                t: icons.comment,
                c: colours.a4.hoveredButton,
              },
            },
          },
        },
      },
    },
    a5: {
      subItemType: "label",
      buttons: {
        1: {
          onClick: async () => await updateAnnotation(themeAnnotations.light),
          styles: {
            default: {
              fbc: colours.a5.defaultButton,
              fc: "rgb(0, 0, 0)",
            },
            hovered: {
              fbc: colours.a5.hoveredButton,
              fc: "rgb(255, 255, 255)",
            },
          },
        },
        2: {
          onClick: async () => await updateAnnotation(themeAnnotations.dark),
          styles: {
            default: {
              fbc: colours.a5.defaultButton,
              fc: "rgb(0, 0, 0)",
            },
            hovered: {
              fbc: colours.a5.hoveredButton,
              fc: "rgb(255, 255, 255)",
            },
          },
        },
      },
    },
    a6: {
      subItemType: "label",
      buttons: {
        5: {
          onClick: async () => await updateAnnotation(threadAnnotations.open),
        },
        10: {
          onClick: async () => await updateAnnotation(threadAnnotations.closed),
        },
      },
    },
  },
};

/* Formats the code snippets neatly in the display box on the right-hand side
   if no annotation is provided, prompt the user to select an annotation */
function prettyPrint(item) {
  const codeDisplayEl = document.getElementById("display");
  if (item?.type === "annotation") {
    const json = JSON.stringify(item, undefined, 1);
    codeDisplayEl.textContent = json;
  } else {
    codeDisplayEl.textContent = "Click an annotation";
  }
}

// Update the itemsMap so we can prettyPrint only the required properties of the annotation
async function updateAnnotation(updatedAnnotation) {
  itemsMap[updatedAnnotation.id] = updatedAnnotation;
  await chart.setProperties(updatedAnnotation);
}

async function toggleShowHideAnnotation(action) {
  const toggleShowHideAnnotationId = "a4";
  // Show / hide the annotation
  chart[action](toggleShowHideAnnotationId, { animate: true, time: 280 });

  // Add / remove the show glyph from the node subject
  const nodeSubjectUpdate = {
    id: "n4",
    g: action === "hide" ? [showAnnotationGlyph] : undefined,
  };
  await setHoveredState(toggleShowHideAnnotationId, "0", false);
  await chart.setProperties(nodeSubjectUpdate);
}

function isHoveringButton(id, subItem) {
  const itemInteractionInfo = interactionInfo.items[id];
  return (
    itemInteractionInfo?.subItemType === subItem.type &&
    itemInteractionInfo?.buttons[subItem.index]
  );
}

// Enable cursor styling override while interacting with the chart
function setCursor(hovering) {
  const type = hovering ? "pointer" : null;
  const chartContainer = document.getElementById("klchart");
  // get all non-cursor classes
  const classes = Array.from(chartContainer.classList).filter(
    (cn) => !cn.match(/^cursor-/)
  );
  if (type) {
    classes.push(`cursor-${type}`);
  }
  chartContainer.className = classes.join(" ");
}

async function setHoveredState(id, subItem, hovering) {
  if (hovering === undefined) {
    hovering = isHoveringButton(id, subItem);
  }
  const itemIdToUpdate = hovering ? id : interactionInfo.lastHoveredButton?.id;
  if (itemIdToUpdate) {
    setCursor(hovering);
    const itemInteractionInfo = interactionInfo.items[itemIdToUpdate];
    let subItemIndexToUpdate = hovering
      ? subItem.index
      : interactionInfo.lastHoveredButton.subItemIndex;
    const itemToUpdate = chart.getItem(itemIdToUpdate);
    const subItemPropName =
      subItemTypePropMap[interactionInfo.items[itemIdToUpdate].subItemType];
    const subItemArray = itemToUpdate[subItemPropName];
    let styleName = null;
    if (
      subItemArray[subItemIndexToUpdate] &&
      subItemArray[subItemIndexToUpdate].fbc !== colours.a5.selectedButton
    ) {
      styleName = hovering ? "hovered" : "default";
    }
    // Remove the hover effect from the previously hovered theme button if we are travelling
    // straight from the other theme button
    else if (
      interactionInfo.lastHoveredButton &&
      subItemArray[interactionInfo.lastHoveredButton.subItemIndex]?.fbc !==
        colours.a5.selectedButton
    ) {
      subItemIndexToUpdate = interactionInfo.lastHoveredButton.subItemIndex;
      styleName = "default";
    }
    if (styleName) {
      const styleUpdate =
        itemInteractionInfo.buttons[subItemIndexToUpdate]?.styles?.[styleName];
      if (styleUpdate) {
        subItemArray[subItemIndexToUpdate] = {
          ...subItemArray[subItemIndexToUpdate],
          ...styleUpdate,
        };
        await chart.setProperties({
          id: itemIdToUpdate,
          [subItemPropName]: subItemArray,
        });
      }
      interactionInfo.lastHoveredButton = hovering
        ? {
            id,
            subItemIndex: subItem.index,
          }
        : null;
    }
  }
}

function initialiseInteractions() {
  chart.on("click", async ({ id, subItem, preventDefault }) => {
    if (itemsMap[id]) {
      // Prevent item selection
      preventDefault();
      if (subItem.type === "label" || subItem.type === "glyph") {
        // if the subItem is clickable trigger the appropriate onClick action
        if (
          interactionInfo.items[id] &&
          interactionInfo.items[id].buttons[subItem.index]
        ) {
          interactionInfo.items[id].buttons[subItem.index].onClick();
        }
      }
    }
    prettyPrint(itemsMap[id]);
  });
  chart.on(
    "hover",
    async ({ id, subItem }) => await setHoveredState(id, subItem)
  );
}

async function startKeyLines() {
  const options = {
    handMode: true,
    backColour: "rgb(230, 234, 224)",
    navigation: false,
    minZoom: 0.3,
    hover: 10,
    zoom: { adaptiveStyling: false },
    iconFontFamily: "CI-Icons",
    imageAlignment: {
      [icons.x]: { e: 0.8 },
      [icons.comment]: { e: 2 },
    },
    legacyGlyphAndLabelOrdering: false,
  };

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

  initialiseInteractions();
}

// if you need font icons, keep this.
// If not, call startKeyLines() from the listener on the last line and delete this function
function loadFonts() {
  document.fonts.load('24px "CI-Icons"').then(startKeyLines);
}

window.addEventListener("DOMContentLoaded", loadFonts);
<!DOCTYPE html>
<html lang="en" style="background-color: #2d383f;">
  <head>
    <meta charset="UTF-8">
    <title>Advanced Annotation 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/ci-icons.css">
    <link rel="stylesheet" type="text/css" href="/advancedannotationstyles.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="/advancedannotationstyles.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%;">Advanced Annotation Styles</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">
                <p>Click any annotation to view its source code.</p>
                <p>
                  Click the buttons on interactive annotations
                  to explore various user actions.
                  
                </p>
                <pre id="display" style="-webkit-user-select: text; -khtml-user-select: text; -moz-user-select: text; -ms-user-select: text; word-break: keep-all;">Click an annotation</pre>
              </div>
            </form>
          </div>
        </div>
      </div>
    </div>
    <div id="moreParent">
      <div id="moreContainer">
      </div>
    </div>
    <div id="lazyScripts">
    </div>
  </body>
</html>
/* Enable cursor overriding while hovering the chart */
.cursor-pointer canvas {
  cursor: pointer !important;
}
Loading source