Search

Annotations Basics

Annotations

Understand the main concepts behind KeyLines annotations.

Annotations Basics
View live example →

KeyLines annotations are a special type of a chart item that can annotate nodes, links, combos, or can stand as a free-standing chart annotation without any subjects.

Any annotations (with or without subjects) can be positioned using world coordinates, and stay fixed in place even during chart changes or view changes.

Annotations with subjects can also be placed relative to their subjects, and move to keep in their relative position even when a layout is run or when the subjects move.

Annotations have two main parts - a body to show information, styling elements or user controls and a connector to link the annotation to its subjects. You can read more in Annotations documentation and see more advanced examples of body styling in the Advanced Annotation Styles demo.

The connector further splits into a line, a subject end decoration and a container around subjects, all of which can be styled or disabled in the connectorStyle property.

Key functions used:

import KeyLines from "keylines";
import { data, itemsMap } from "./data.js";

let chart;

/* 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";
  }
}

function initialiseInteractions() {
  chart.on("click", ({ id, preventDefault }) => {
    const item = itemsMap[id];
    if (item) {
      // Prevent item selection
      preventDefault();
    }
    prettyPrint(item);
  });
}

async function startKeyLines() {
  const options = {
    handMode: true,
    backColour: "rgb(230, 234, 224)",
    minZoom: 0.42,
    navigation: false,
  };

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

  initialiseInteractions();
}

window.addEventListener("DOMContentLoaded", startKeyLines);
function makeNode(id, x, y) {
  return {
    id,
    type: "node",
    c: "rgb(124, 205, 197)",
    x,
    y,
  };
}

function makeLink(id1, id2) {
  return {
    id: `${id1}-${id2}`,
    type: "link",
    id1: id1,
    id2: id2,
    c: "rgb(166, 166, 166)",
  };
}

const columnXMidPoints = {
  1: 0,
  2: 300,
};
const annotationDistance = 100;

const items = [
  makeNode("n1", columnXMidPoints[1], 50),
  makeNode("n2", columnXMidPoints[1], 200),
  makeNode("n3", columnXMidPoints[1], 350),
  makeLink("n2", "n3"),
  makeNode("n4", columnXMidPoints[1], 650),
  makeNode("n5", columnXMidPoints[1], 800),
  makeLink("n4", "n5"),
  makeNode("n6", columnXMidPoints[1], 1050),
  makeLink("n5", "n6"),
  makeNode("n7", columnXMidPoints[2], 275),
  makeNode("n8", columnXMidPoints[2], 425),
  makeLink("n7", "n8"),
  makeNode("n9", columnXMidPoints[2], 575),
  makeNode("n10", columnXMidPoints[2], 725),
  makeLink("n9", "n10"),
  makeNode("n11", columnXMidPoints[2], 875),
  makeNode("n12", columnXMidPoints[2], 1025),
  makeLink("n11", "n12"),
  {
    id: "a1",
    type: "annotation",
    subject: ["n1"],
    t: {
      t: "This is the default annotation style",
    },
  },
  {
    id: "a2",
    type: "annotation",
    subject: ["n1", "n2"],
    position: {
      angle: "w",
      distance: annotationDistance - 50,
    },
    connectorStyle: {
      subjectEnd: "none",
    },
    t: {
      t: "Annotate multiple subjects",
    },
  },
  {
    id: "a3",
    type: "annotation",
    subject: ["n2-n3"],
    position: {
      angle: "w",
      distance: annotationDistance,
    },
    connectorStyle: {
      subjectEnd: "none",
    },
    t: {
      t: "Annotate links",
    },
  },
  {
    id: "a4",
    type: "annotation",
    subject: ["n4"],
    position: {
      angle: "w",
      distance: annotationDistance,
    },
    connectorStyle: {
      ls: "dashed",
      subjectEnd: "none",
    },
    t: {
      t: "Styled connector line",
    },
  },

  {
    id: "a5",
    type: "annotation",
    subject: ["n5"],
    position: {
      angle: "w",
      distance: annotationDistance,
    },
    connectorStyle: {
      subjectEnd: "none",
    },
    t: {
      t: "No subject end",
    },
  },
  {
    id: "a6",
    type: "annotation",
    subject: ["n5-n6"],
    position: {
      angle: "w",
      distance: annotationDistance,
    },
    connectorStyle: {
      subjectEnd: "arrow",
    },
    t: {
      t: "Arrow subject end",
    },
  },
  {
    id: "a7",
    type: "annotation",
    subject: ["n6"],
    position: {
      angle: "w",
      distance: annotationDistance,
    },
    connectorStyle: {
      subjectEnd: "dot",
    },
    t: {
      t: "Dot subject end",
    },
  },
  {
    id: "a8",
    type: "annotation",
    subject: [],
    position: {
      x: columnXMidPoints[2],
      y: 125,
    },
    t: {
      t: "Non subject annotation",
    },
  },
  {
    id: "a9",
    type: "annotation",
    subject: ["n7", "n8"],
    position: {
      angle: "e",
      distance: annotationDistance,
    },
    connectorStyle: {
      container: "rectangle",
      subjectEnd: "none",
    },
    t: {
      t: "Rectangle container",
    },
  },
  {
    id: "a10",
    type: "annotation",
    subject: ["n9", "n10"],
    position: {
      angle: "e",
      distance: annotationDistance,
    },
    connectorStyle: {
      container: "none",
      subjectEnd: "none",
    },
    t: {
      t: "No container",
    },
  },
  {
    id: "a11",
    type: "annotation",
    subject: ["n11", "n12"],
    position: {
      angle: "e",
      distance: annotationDistance,
    },
    connectorStyle: {
      container: "circle",
      subjectEnd: "none",
    },
    t: {
      t: "Circle container",
    },
  },
];

export const itemsMap = {};
items.forEach((item) => (itemsMap[item.id] = item));

export const data = {
  type: "LinkChart",
  items,
};
<!doctype html>
<html lang="en" style="background-color: #2d383f">
  <head>
    <meta charset="utf-8" />
    <title>Annotations Basics</title>
    <link rel="stylesheet" type="text/css" href="@ci/theme/kl/css/keylines.css" />
    <link rel="stylesheet" type="text/css" href="@ci/theme/kl/css/minimalsdk.css" />
    <link rel="stylesheet" type="text/css" href="@ci/theme/kl/css/sdk-layout.css" />
    <link rel="stylesheet" type="text/css" href="@ci/theme/kl/css/demo.css" />
  </head>
  <body>
    <div id="klchart" class="klchart"></div>
    <script type="module" src="./code.js"></script>
  </body>
</html>

Terms of use

These terms do not alter or supersede any existing agreements between you (or your employer) and us.

By accessing or using any Content you agree to be bound by these Terms of Use. Please review these terms carefully before using the website.

The contents of this website, including but not limited to any text, code samples, API references, schemas, interactive tools, and other materials (collectively, the 'Content'), are made available for informational and internal evaluation purposes only. All intellectual property rights in the Content are reserved. No licence is granted to use the Content for any commercial purpose, or to copy, distribute, modify, reverse-engineer, or incorporate any part of the Content into any product or service, without our prior written consent.

This Content is provided “as is” and “as available,” without any representations, warranties, or guarantees of any kind, whether express or implied, including but not limited to implied warranties of merchantability, fitness for a particular purpose, non-infringement, or accuracy. To the fullest extent permitted by applicable law, we expressly exclude and disclaim all implied warranties, conditions, and other terms that might otherwise be implied.

We disclaim all liability for any loss or damage, whether direct, indirect, incidental, consequential, or otherwise, arising from any reliance placed on the Content or from your use of it, to the fullest extent permitted by applicable law. By continuing to access or use the Content, you acknowledge and agree to these terms.