Link copied to clipboard!

Sample Code

Highcharts

import Highcharts from "highcharts";
import HighchartsReact from "highcharts-react-official";
import {
  useCordAnnotationTargetRef,
  useCordAnnotationCaptureHandler,
  useCordAnnotationClickHandler,
  useCordAnnotationRenderer,
} from "@cord-sdk/react";

type HighchartsAnnotationLocation = {
  section: string;
  series: number;
  x: number;
  y: number;
};

// this must be rendered downstream of a CordProvider
function HighchartsComponent() {
  const highchartsRef = useRef<HighchartsReact.RefObject>(null);

  const location = { section: "chart" };

  const annotationTargetRef = useCordAnnotationTargetRef<
    HTMLDivElement,
    HighchartsAnnotationLocation
  >(location);

  useCordAnnotationCaptureHandler<HighchartsAnnotationLocation>(
    location,
    ({ x, y }) => {
      const chart = highchartsRef.current?.chart;
      if (chart) {
        const { plotLeft, plotTop } = chart;
        const plotRelativeX = x - plotLeft;
        const plotRelativeY = y - plotTop;

        let minDistance = Infinity;
        let closestPoint: Highcharts.Point | undefined;

        for (const series of chart.series) {
          for (const point of series.points) {
            const { plotX, plotY } = point;
            if (plotX && plotY) {
              const distance = planarDistance(
                plotX,
                plotY,
                plotRelativeX,
                plotRelativeY
              );
              if (distance < minDistance) {
                minDistance = distance;
                closestPoint = point;
              }
            }
          }
        }

        if (closestPoint) {
          return {
            extraLocation: {
              x: closestPoint.x,
              y: closestPoint.y,
              series: closestPoint.series.index,
            },
            label: `${closestPoint.series.name}: ${closestPoint.x}`,
          };
        }
      }
    }
  );

  useCordAnnotationRenderer<HighchartsAnnotationLocation>(
    location,
    (annotation) => {
      const chart = highchartsRef.current?.chart;
      const container = highchartsRef.current?.container?.current;

      if (chart && container) {
        if (!chart.series[annotation.location.series].visible) {
          return;
        }

        return {
          element: container,
          coordinates: {
            x: chart.xAxis[0].toPixels(annotation.location.x, false),
            y: chart.yAxis[0].toPixels(annotation.location.y, false),
          },
        };
      }
    }
  );

  useCordAnnotationClickHandler<HighchartsAnnotationLocation>(
    location,
    (annotation) => {
      highchartsRef.current?.chart.series[
        annotation.location.series
      ].setVisible(true);
    }
  );

  return (
    <div ref={annotationTargetRef}>
      <HighchartsReact
        ref={highchartsRef}
        highcharts={Highcharts}
        options={{
          title: {
            text: "Solar Employment Growth by Sector, 2010-2016",
          },

          subtitle: {
            text: "Source: thesolarfoundation.com",
          },

          yAxis: {
            title: {
              text: "Number of Employees",
            },
          },

          xAxis: {
            accessibility: {
              rangeDescription: "Range: 2010 to 2017",
            },
          },

          legend: {
            layout: "vertical",
            align: "right",
            verticalAlign: "middle",
          },

          plotOptions: {
            series: {
              label: {
                connectorAllowed: false,
              },
              pointStart: 2010 + startIndex,
            },
          },

          series: [
            {
              name: "Installation",
              data: [43934, 52503, 57177, 69658, 97031, 119931, 137133, 154175],
            },
            {
              name: "Manufacturing",
              data: [24916, 24064, 29742, 29851, 32490, 30282, 38121, 40434],
            },
            {
              name: "Sales & Distribution",
              data: [11744, 17722, 16005, 19771, 20185, 24377, 32147, 39387],
            },
            {
              name: "Project Development",
              data: [null, null, 7988, 12169, 15112, 22452, 34400, 34227],
            },
            {
              name: "Other",
              data: [12908, 5948, 8105, 11248, 8989, 11816, 18274, 18111],
            },
          ],
        }}
      />
    </div>
  );
}

Chart.js: Bar chart

import React, { useRef } from "react";
import {
  Chart as ChartJS,
  CategoryScale,
  LinearScale,
  BarElement,
  Title,
  Tooltip,
  Legend,
} from "chart.js";
import type { ChartMeta } from "chart.js";
import { Bar } from "react-chartjs-2";
import {
  useCordAnnotationCaptureHandler,
  useCordAnnotationClickHandler,
  useCordAnnotationRenderer,
  useCordAnnotationTargetRef,
} from "opensource/cord-sdk/packages/react";
import type { AnnotationCapturePosition } from "opensource/cord-sdk/packages/types";

ChartJS.register(
  CategoryScale,
  LinearScale,
  BarElement,
  Title,
  Tooltip,
  Legend
);

type DataPoint = {
  x: number;
  y: number;
};

type BarChartAnnotation = {
  page: string;
  element: string;
  x: number;
  y: number;
  datasetIndex: number;
  barElementIndex: number;
};

export const BarChart = () => {
  const barChartRef = useRef<ChartJS<"bar">>();
  const location = { element: "barchart" };

  const barChartContainerRef =
    useCordAnnotationTargetRef<HTMLDivElement>(location);

  useCordAnnotationCaptureHandler<BarChartAnnotation>(
    location,
    (annotation) => {
      // The pin will attach to the top of any bar element clicked by the user.
      // Annotations created outside of a bar element will appear at the clicked location.
      const chart = barChartRef.current;

      if (!chart) {
        return;
      }

      const datasets = chart.getSortedVisibleDatasetMetas();
      const clickedElementData = findClickedElement(datasets, annotation);

      if (!clickedElementData) {
        return;
      }

      const {
        datasetLabel,
        datasetIndex,
        clickedElement,
        clickedElementIndex,
      } = clickedElementData;

      chart.setActiveElements([{ datasetIndex, index: clickedElementIndex }]);
      chart.update();

      return {
        extraLocation: {
          x: clickedElement.x,
          y: clickedElement.y,
          datasetIndex,
          barElementIndex: clickedElementIndex,
        },
        label: `x: ${chart.scales.x.getLabelForValue(
          clickedElementIndex
        )}, y: ${clickedElement.y}, ${datasetLabel}`,
      };
    }
  );

  useCordAnnotationClickHandler<BarChartAnnotation>(location, (annotation) => {
    const chart = barChartRef.current;

    if (chart) {
      const {
        location: { barElementIndex, datasetIndex },
      } = annotation;

      const dataset = chart.getDatasetMeta(datasetIndex);

      if (dataset.hidden) {
        chart.show(datasetIndex);
      }
      chart.setActiveElements([{ datasetIndex, index: barElementIndex }]);

      chart.update();
    }
  });

  useCordAnnotationRenderer<BarChartAnnotation>(location, (annotation) => {
    const chart = barChartRef.current;

    if (!chart || !barChartContainerRef.current) {
      return;
    }

    const {
      location: { barElementIndex, datasetIndex },
    } = annotation;

    const dataset = chart.getDatasetMeta(datasetIndex);

    if (dataset.hidden) {
      return;
    }
    const datasetData = dataset.data[barElementIndex];

    if (!datasetData) {
      return;
    }

    return {
      coordinates: { x: datasetData.x, y: datasetData.y },
      element: barChartContainerRef.current,
    };
  });

  return (
    <div ref={barChartContainerRef}>
      <Bar options={OPTIONS} data={DATA} ref={barChartRef} />
    </div>
  );
};

const OPTIONS = {
  responsive: true,
  plugins: {
    title: {
      display: true,
      text: "Cord Annotations - Chart.js Bar Chart",
    },
  },
};

const LABELS = ["January", "February", "March", "April", "May", "June", "July"];
const DATASET1 = [123, 543, 634, 975, 234, 84, 99];
const DATASET2 = [973, 125, 385, 442, 487, 192, 827];

const DATA = {
  labels: LABELS,
  datasets: [
    {
      label: "Dataset 1",
      data: DATASET1,
      backgroundColor: "rgba(255, 99, 132, 0.5)",
      hoverBorderColor: "black",
    },
    {
      label: "Dataset 2",
      data: DATASET2,
      backgroundColor: "rgba(53, 162, 235, 0.5)",
      hoverBorderColor: "black",
    },
  ],
};

function findClickedElement(
  datasets: ChartMeta[],
  annotation: AnnotationCapturePosition
) {
  const { x, y } = annotation;

  for (const dataset of datasets) {
    for (const [barElementIndex, data] of dataset.data.entries()) {
      //@ts-ignore
      // Checks if the user click is within any bar elements
      if (data.inRange(x, y, true)) {
        const clickedElement = dataset._parsed[barElementIndex];

        if (!isDataPoint(clickedElement)) {
          return null;
        }

        return {
          datasetLabel: dataset.label,
          datasetIndex: dataset.index,
          clickedElement,
          clickedElementIndex: barElementIndex,
        };
      }
    }
  }

  return null;
}

function isDataPoint(obj: any): obj is DataPoint {
  return typeof obj === "object" && obj !== null && "x" in obj && "y" in obj;
}

Chart.js: Scatter chart

import React, { useRef } from "react";
import {
  Chart as ChartJS,
  CategoryScale,
  LinearScale,
  Title,
  Tooltip,
  Legend,
  PointElement,
  LineElement,
} from "chart.js";
import { Scatter } from "react-chartjs-2";
import {
  useCordAnnotationCaptureHandler,
  useCordAnnotationClickHandler,
  useCordAnnotationRenderer,
  useCordAnnotationTargetRef,
} from "opensource/cord-sdk/packages/react";
import type { AnnotationCapturePosition } from "opensource/cord-sdk/packages/types";

ChartJS.register(
  CategoryScale,
  LinearScale,
  Title,
  Tooltip,
  Legend,
  PointElement,
  LineElement
);

type DataPoint = {
  x: number;
  y: number;
};

type ScatterPointAnnotation = {
  page: string;
  element: string;
  x: number;
  y: number;
  datasetIndex: number;
  pointIndex: number;
};

export const ScatterChart = () => {
  const location = { element: "scatterchart" };
  const scatterChartRef = useRef<ChartJS<"scatter">>();
  const scatterChartContainerRef =
    useCordAnnotationTargetRef<HTMLDivElement>(location);

  useCordAnnotationCaptureHandler<ScatterPointAnnotation>(
    location,
    (annotation) => {
      // The pin will be attached to the closest data point relative to the location of the user click.
      const chart = scatterChartRef.current;

      if (!chart) {
        return;
      }

      const { x, y } = annotation;

      let shortestDistance: number = Infinity;
      let datasetIndex: number | undefined;
      let closestDataPointIndex: number | undefined;

      const datasets = chart.getSortedVisibleDatasetMetas();

      for (const dataset of datasets) {
        for (const [j, data] of dataset.data.entries()) {
          const distanceFromPin = calcDistanceBetweenCoordinates(
            { x, y },
            { x: data.x, y: data.y }
          );

          if (distanceFromPin < shortestDistance) {
            shortestDistance = distanceFromPin;
            closestDataPointIndex = j;
            datasetIndex = dataset.index;
          }
        }
      }

      if (closestDataPointIndex === undefined || datasetIndex === undefined) {
        return;
      }

      const correctDataset = datasets.find(
        (dataset) => dataset.index === datasetIndex
      );

      const parsed = correctDataset?._parsed[closestDataPointIndex];

      if (!isDataPoint(parsed)) {
        return;
      }

      const { x: scatterPointX, y: scatterPointY } = parsed;

      chart.setActiveElements([{ datasetIndex, index: closestDataPointIndex }]);
      chart.update();

      return {
        extraLocation: {
          x: scatterPointX,
          y: scatterPointY,
          datasetIndex,
          pointIndex: closestDataPointIndex,
        },
        label: `x: ${scatterPointX}, y: ${scatterPointY}, ${correctDataset?.label}`,
      };
    }
  );

  useCordAnnotationClickHandler<ScatterPointAnnotation>(
    location,
    (annotation) => {
      const chart = scatterChartRef.current;

      if (chart) {
        const {
          location: { pointIndex, datasetIndex },
        } = annotation;

        const dataset = chart.getDatasetMeta(datasetIndex);

        if (dataset.hidden) {
          chart.show(datasetIndex);
        }

        chart.setActiveElements([{ datasetIndex, index: pointIndex }]);

        chart.update();
      }
    }
  );

  useCordAnnotationRenderer<ScatterPointAnnotation>(location, (annotation) => {
    const chart = scatterChartRef.current;

    if (!chart || !scatterChartContainerRef.current) {
      return;
    }

    const {
      location: { x, y, datasetIndex },
    } = annotation;

    const dataset = chart.getDatasetMeta(datasetIndex);

    if (dataset.hidden) {
      return;
    }

    return {
      coordinates: {
        x: chart.scales.x.getPixelForValue(x),
        y: chart.scales.y.getPixelForValue(y),
      },
      element: scatterChartContainerRef.current,
    };
  });

  return (
    <div ref={scatterChartContainerRef}>
      <Scatter options={OPTIONS} data={DATA} ref={scatterChartRef} />
    </div>
  );
};

const OPTIONS = {
  scales: {
    y: {
      beginAtZero: true,
    },
  },
  plugins: {
    title: {
      display: true,
      text: "Cord Annotations - Chart.js Scatter Chart",
    },
  },
};

const DATA = {
  datasets: [
    {
      label: "Dataset A",
      // Generate or import dummy data
      data: scatterChartDatasetA,
      backgroundColor: "rgba(255, 99, 132, 1)",
      pointRadius: 5,
      pointHitRadius: 5,
      pointHoverRadius: 7,
    },
    {
      label: "Dataset B",
      // Generate or import dummy data
      data: scatterChartDatasetB,
      backgroundColor: "rgb(75, 192, 192)",
      pointRadius: 5,
      pointHitRadius: 5,
      pointHoverRadius: 7,
    },
  ],
};

function calcDistanceBetweenCoordinates(a: DataPoint, b: DataPoint) {
  return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2));
}

function isDataPoint(obj: any): obj is DataPoint {
  return typeof obj === "object" && obj !== null && "x" in obj && "y" in obj;
}

Monaco Editor

import React, { useRef } from "react";

import type * as monaco from "monaco-editor";
import Editor from "@monaco-editor/react";

type MonacoLocation = {
  target: string;
  lineNumber?: number;
};

const LOCATION: MonacoLocation = {
  target: "monaco-editor",
};

function MonacoEditorExample() {
  const editorWrapperRef = useCordAnnotationTargetRef<HTMLDivElement>(LOCATION);
  const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null);

  // Retrieving the editor instance, to be able to e.g. scroll to a certain line.
  function handleEditorDidMount(editor: monaco.editor.IStandaloneCodeEditor) {
    editorRef.current = editor;

    editor.onDidScrollChange(() => {
      redrawAnnotations();
    });
  }

  // Runs when users click on the screen to annotate.
  useCordAnnotationCaptureHandler(LOCATION, (position) => {
    const { current: editor } = editorRef;
    if (!editor) {
      return;
    }

    const { x, y } = position.element.getBoundingClientRect();
    try {
      const target = editor.getTargetAtClientPoint(
        position.x + x,
        position.y + y
      )?.position;

      if (!target) {
        return;
      }

      const { lineNumber, column } = target;
      const lineContent = editor.getModel()?.getLineContent(lineNumber);
      return {
        // Storing the line number so later we can scroll back to it,
        // and the column to position the pin in the right place horizontally.
        extraLocation: { lineNumber, column },
        // Showing the entire line content as the annotation label.
        label: lineContent,
        // label could be any string though, e.g.
        // label: `line: ${lineNumber}`;
      };
    } catch (e) {
      console.error(e);
      return;
    }
  });

  const { redrawAnnotations } = useCordAnnotationRenderer(
    LOCATION,
    (annotation) => {
      const { current: editor } = editorRef;
      if (!editor) {
        return;
      }

      const { lineNumber, column } = annotation.location;
      const lineCount = editor.getModel()?.getLineCount() ?? 0;
      if (lineNumber > lineCount) {
        return;
      }

      const top =
        editor.getTopForLineNumber(lineNumber + 1) - editor.getScrollTop();
      return {
        coordinates: {
          x:
            // `contentLeft` tells us how many px from the left the actual line content starts
            editor.getLayoutInfo().contentLeft +
            // How many px from the beginning of the actual line
            editor.getOffsetForColumn(lineNumber, column) -
            editor.getScrollLeft(),
          y: top,
        },
        element: editorWrapperRef.current ?? undefined,
      };
    }
  );

  // Runs when a user clicks on the annotation pill within the sidebar.
  useCordAnnotationClickHandler(LOCATION, (annotation) => {
    const { current: editor } = editorRef;
    if (!editor) {
      return;
    }

    editor.revealLineInCenter(annotation.location.lineNumber);
  });

  return (
    <div ref={editorWrapperRef}>
      <Editor
        height="90vh"
        defaultLanguage="javascript"
        // Filling the editor with some dummy lines
        defaultValue={new Array(150)
          .fill(`function foo() { \tconsole.log("bar"); }`)
          .join("\n")}
        onMount={handleEditorDidMount}
      />
    </div>
  );
}

Visx: Geo Mercator

...
const Country = ({ feature, path }: { feature: any; path: any }) => {
  const classes = useStyles();

  const location = {
    element: 'worldmap',
    country: feature.properties.name,
  };

  const [highlighted, setHighlighted] = useState(false);

  const countryRef = useCordAnnotationTargetRef<HTMLElement & SVGPathElement>(
    location,
  );

  const isMountedRef = useRef(false);

  useEffect(() => {
    isMountedRef.current = true;

    return () => {
      isMountedRef.current = false;
    };
  }, []);

  const highlightCountry = useCallback(() => {
    setHighlighted(true);
    setTimeout(() => {
      if (isMountedRef.current === true) {
        setHighlighted(false);
      }
    }, 1500);
  }, []);

  useCordAnnotationCaptureHandler(location, () => {
    if (!countryRef.current) {
      return;
    }

    highlightCountry();
    return {
      label: feature.properties.name,
    };
  });

  useCordAnnotationClickHandler(location, () => {
    if (countryRef.current) {
      highlightCountry();
    }
  });

  useCordAnnotationRenderer(location, () => {
    if (!countryRef.current) {
      return;
    }
    // Adjusting the default pin location
    if (feature.properties.name === 'Russia') {
      return {
        coordinates: { x: '77%', y: '65%' },
        element: countryRef.current,
      };
    }
    if (feature.properties.name === 'United States') {
      return {
        coordinates: { x: '22%', y: '72%' },
        element: countryRef.current,
      };
    }
    return { element: countryRef.current };
  });

  return (
    <path
      className={cx(classes.default, {
        [classes.highlightedCountry]: highlighted,
      })}
      ref={countryRef}
      d={path || ''}
      fill={color(feature.geometry.coordinates.length)}
      stroke={BACKGROUND_COLOR}
      strokeWidth={0.5}
    />
  );
};

export const WorldMap = ({ width, height }: GeoMercatorProps) => {
  const classes = useStyles();
  const centerX = width / 2;
  const centerY = height / 2;
  const scale = (width / 630) * 100;

  return (
    <svg width={'100%'} className={classes.svgContainer}>
      <rect
        x={0}
        y={0}
        width={'100%'}
        height={'100%'}
        fill={BACKGROUND_COLOR}
        rx={14}
      />
      <Mercator
        data={world.features}
        scale={scale}
        translate={[centerX, centerY + 50]}
      >
        {(mercator) => (
          <g>
            <Graticule
              graticule={(g) => mercator.path(g) || ''}
              stroke="rgba(33,33,33,0.05)"
            />
            {mercator.features.map(({ feature, path }, i) => {
              return <Country key={i} feature={feature} path={path} />;
            })}
          </g>
        )}
      </Mercator>
    </svg>
  );
};

AG Grid

import React, { useRef } from "react";
import { AgGridReact } from "ag-grid-react";

type GridLocation = {
  section: string;
  row: string;
  column: string;
};

export const GridApp = () => {
  const location = { section: "grid" };

  const containerRef = useCordAnnotationTargetRef(location);

  useCordAnnotationCaptureHandler(location, (_position, element) => {
    const colId = element.closest("[role=gridcell]")?.getAttribute("col-id");
    const rowId = element.closest("[role=row]")?.getAttribute("row-id");

    if (rowId && colId) {
      return {
        extraLocation: {
          row: rowId,
          column: colId,
        },
      };
    }
  });

  useCordAnnotationRenderer<GridLocation>(location, (annotation) => {
    const { row, column } = annotation.location;
    return {
      element: containerRef.current?.querySelector(
        `*[role='row'][row-id='${row}'] *[role='gridcell'][col-id='${column}']`
      )
    };
  });

  const gridRef = useRef<AgGridReact>(null);

  return (
    <div ref={containerRef}>
      <AgGridReact ref={gridRef} rowData={ROW_DATA} columnDefs={COLUMNS} />
    </div>
  );
};

In this section