Search

Annotations Basics

Annotations

Understand the main concepts behind annotations.

Annotations Basics
View live example →

Click on an annotation to see its data in the overlay, and pan and zoom the chart and drag the nodes to see how annotations respond to changes.

Annotations do not scale during zooming and stay visible at each zoom level.

Annotations can be positioned using world coordinates and stay fixed in place even during chart changes or view changes, or they can be positioned relative to their subjects and move to keep the relative position whenever their subjects move.

Annotations have two main parts - a body with information, styling and user controls, and a connector that links the annotation to its subjects. The connector further divides into a line, a subject end and a container, all of which can be styled or hidden in the connectorStyle property.

See also

import React, { useState } from "react";
import { createRoot } from "react-dom/client";

import { Chart } from "regraph";

import { data, dataPositions } from "./data";

import "@ci/theme/rg/css/properties.css";

export const Demo = () => <AnnotationsBasics items={data} itemPositions={dataPositions} />;

function AnnotationsBasics(props) {
  const { items, itemPositions } = props;
  const [clickedAnnotationId, setClickedAnnotationId] = useState(null);
  const [updatedPositions, setUpdatedPositions] = useState(itemPositions);

  const handleClick = ({ id, itemType }) => {
    if (id && itemType === "annotation") {
      setClickedAnnotationId(id);
    } else if (clickedAnnotationId) {
      setClickedAnnotationId(null);
    }
  };

  const handleChange = async ({ annotationPositions }) => {
    if (annotationPositions) {
      // Track annotation position changes so we can display them to the user
      setUpdatedPositions((current) => ({
        ...current,
        annotations: annotationPositions,
      }));
    }
  };

  return (
    <div style={{ width: "100%", height: "100%" }}>
      <Chart
        items={items}
        positions={itemPositions.nodes}
        annotationPositions={updatedPositions.annotations}
        onClick={handleClick}
        onChange={handleChange}
        options={{
          navigation: false,
          overview: false,
          backgroundColor: "rgb(231, 234, 224)",
          selection: false,
          minZoom: 0.3,
        }}
      />
      {clickedAnnotationId === null ? (
        <pre className="properties annotations empty">
          Click an annotation to see its properties
        </pre>
      ) : (
        <pre className="properties annotations">
          {JSON.stringify(
            {
              items: {
                [clickedAnnotationId]: {
                  ...items[clickedAnnotationId],
                },
              },
              annotationPositions: updatedPositions.annotations[clickedAnnotationId],
            },
            null,
            2
          )}
        </pre>
      )}
    </div>
  );
}

const root = createRoot(document.getElementById("regraph"));
root.render(<Demo />);
const annotationDistance = 100;
const columnXMidPoints = {
  1: 0,
  2: 300,
};

export const dataPositions = {
  // Node positions are populated via the makeNode function
  nodes: {},
  annotations: {
    a2: {
      angle: "0",
      distance: 20,
    },
    a2: {
      angle: "w",
      distance: annotationDistance - 50,
    },
    a3: {
      angle: "w",
      distance: annotationDistance,
    },
    a4: {
      angle: "w",
      distance: annotationDistance,
    },
    a5: {
      angle: "w",
      distance: annotationDistance,
    },
    a6: {
      angle: "w",
      distance: annotationDistance,
    },
    a7: {
      angle: "w",
      distance: annotationDistance,
    },
    a8: {
      x: columnXMidPoints[2],
      y: 125,
    },
    a9: {
      angle: "e",
      distance: annotationDistance - 50,
    },
    a10: {
      angle: "e",
      distance: annotationDistance - 50,
    },
    a11: {
      angle: "e",
      distance: annotationDistance - 50,
    },
  },
};

export const data = {
  ...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"),
  a1: {
    subject: "n1",
    label: {
      text: "This is the default annotation style",
    },
  },
  a2: {
    subject: ["n1", "n2"],
    connectorStyle: {
      subjectEnd: "none",
    },
    label: {
      text: "Annotate multiple subjects",
    },
  },
  a3: {
    subject: ["n2-n3"],
    connectorStyle: {
      subjectEnd: "none",
    },
    label: {
      text: "Annotate links",
    },
  },
  a4: {
    subject: ["n4"],
    connectorStyle: {
      lineStyle: "dashed",
      subjectEnd: "none",
    },
    label: {
      text: "Styled connector line",
    },
  },
  a5: {
    subject: ["n5"],
    connectorStyle: {
      subjectEnd: "none",
    },
    label: {
      text: "No subject end",
    },
  },
  a6: {
    subject: ["n5-n6"],
    connectorStyle: {
      subjectEnd: "arrow",
    },
    label: {
      text: "Arrow subject end",
    },
  },
  a7: {
    subject: ["n6"],
    connectorStyle: {
      subjectEnd: "dot",
    },
    label: {
      text: "Dot subject end",
    },
  },
  a8: {
    subject: [],
    label: {
      text: "Non subject annotation",
    },
  },
  a9: {
    subject: ["n7", "n8"],
    connectorStyle: {
      container: "rectangle",
      subjectEnd: "none",
    },
    label: {
      text: "Rectangle container",
    },
  },
  a10: {
    subject: ["n9", "n10"],
    connectorStyle: {
      container: "none",
      subjectEnd: "none",
    },
    label: {
      text: "No container",
    },
  },
  a11: {
    subject: ["n11", "n12"],
    connectorStyle: {
      container: "circle",
      subjectEnd: "none",
    },
    label: {
      text: "Circle container",
    },
  },
};

function makeNode(id, x, y) {
  dataPositions.nodes[id] = {
    x,
    y,
  };
  return {
    [id]: {
      color: "rgb(50, 152, 138)",
    },
  };
}

function makeLink(id1, id2) {
  return {
    [`${id1}-${id2}`]: {
      type: "link",
      id1: id1,
      id2: id2,
      color: "rgb(166, 166, 166)",
    },
  };
}
<!doctype html>
<html>
  <body>
    <div id="regraph" style="height: 100vh"></div>
    <script type="module" src="./code.jsx"></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.