Graph NG: annotations display (#27972)

* Annotations support POC

* Fix markers memoization

* dev dashboard update

* Update public/app/plugins/panel/graph3/plugins/AnnotationsPlugin.tsx
This commit is contained in:
Dominik Prokop 2020-10-06 11:39:06 +02:00 committed by GitHub
parent a2816ee64a
commit aa6c98f7ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 609 additions and 83 deletions

View File

@ -22,6 +22,9 @@
"datasource": null,
"fieldConfig": {
"defaults": {
"color": {
"mode": "fixed"
},
"custom": {
"axis": {
"grid": true,
@ -36,11 +39,11 @@
"alpha": 0.1
},
"line": {
"show": true,
"width": 1,
"color": {
"mode": "fixed"
}
},
"show": true,
"width": 1
},
"nullValues": "null",
"points": {
@ -355,6 +358,9 @@
"datasource": "gdev-testdata",
"fieldConfig": {
"defaults": {
"color": {
"mode": "fixed"
},
"custom": {
"align": null,
"axis": {
@ -371,10 +377,7 @@
},
"line": {
"show": true,
"width": 1,
"color": {
"mode": "fixed"
}
"width": 1
},
"nullValues": "null",
"points": {
@ -560,6 +563,9 @@
"datasource": "gdev-testdata",
"fieldConfig": {
"defaults": {
"color": {
"mode": "fixed"
},
"custom": {
"align": null,
"axis": {
@ -576,10 +582,7 @@
},
"line": {
"show": true,
"width": 1,
"color": {
"mode": "fixed"
}
"width": 1
},
"nullValues": "null",
"points": {
@ -678,6 +681,9 @@
"datasource": "gdev-testdata",
"fieldConfig": {
"defaults": {
"color": {
"mode": "fixed"
},
"custom": {
"align": null,
"axis": {
@ -694,10 +700,7 @@
},
"line": {
"show": true,
"width": 1,
"color": {
"mode": "fixed"
}
"width": 1
},
"nullValues": "null",
"points": {
@ -811,6 +814,9 @@
"datasource": "gdev-testdata",
"fieldConfig": {
"defaults": {
"color": {
"mode": "fixed"
},
"custom": {
"align": null,
"axis": {
@ -827,10 +833,7 @@
},
"line": {
"show": false,
"width": 1,
"color": {
"mode": "fixed"
}
"width": 1
},
"nullValues": "null",
"points": {
@ -899,7 +902,7 @@
"value": false
},
{
"id": "custom.line.color",
"id": "color",
"value": {
"fixedColor": "purple",
"mode": "fixed"
@ -960,6 +963,9 @@
"datasource": "gdev-testdata",
"fieldConfig": {
"defaults": {
"color": {
"mode": "fixed"
},
"custom": {
"align": null,
"axis": {
@ -976,10 +982,7 @@
},
"line": {
"show": false,
"width": 1,
"color": {
"mode": "fixed"
}
"width": 1
},
"nullValues": "null",
"points": {
@ -1048,7 +1051,7 @@
"value": false
},
{
"id": "custom.line.color",
"id": "color",
"value": {
"fixedColor": "purple",
"mode": "fixed"
@ -1109,6 +1112,9 @@
"datasource": "gdev-testdata",
"fieldConfig": {
"defaults": {
"color": {
"mode": "fixed"
},
"custom": {
"align": null,
"axis": {
@ -1125,10 +1131,7 @@
},
"line": {
"show": false,
"width": 1,
"color": {
"mode": "fixed"
}
"width": 1
},
"nullValues": "null",
"points": {
@ -1197,7 +1200,7 @@
"value": false
},
{
"id": "custome.line.color",
"id": "color",
"value": {
"fixedColor": "purple",
"mode": "fixed"
@ -1258,6 +1261,9 @@
"datasource": "gdev-testdata",
"fieldConfig": {
"defaults": {
"color": {
"mode": "fixed"
},
"custom": {
"align": null,
"axis": {
@ -1274,10 +1280,7 @@
},
"line": {
"show": false,
"width": 1,
"color": {
"mode": "fixed"
}
"width": 1
},
"nullValues": "null",
"points": {
@ -1346,7 +1349,7 @@
"value": false
},
{
"id": "custom.line.color",
"id": "color",
"value": {
"fixedColor": "purple",
"mode": "fixed"
@ -1422,6 +1425,9 @@
"datasource": null,
"fieldConfig": {
"defaults": {
"color": {
"mode": "fixed"
},
"custom": {
"align": null,
"axis": {
@ -1437,11 +1443,11 @@
"alpha": 0
},
"line": {
"show": true,
"width": 1,
"color": {
"mode": "fixed"
}
},
"show": true,
"width": 1
},
"nullValues": "null",
"points": {
@ -1506,6 +1512,9 @@
"datasource": null,
"fieldConfig": {
"defaults": {
"color": {
"mode": "fixed"
},
"custom": {
"align": null,
"axis": {
@ -1521,11 +1530,11 @@
"alpha": 0
},
"line": {
"show": true,
"width": 1,
"color": {
"mode": "fixed"
}
},
"show": true,
"width": 1
},
"nullValues": "null",
"points": {
@ -1590,6 +1599,9 @@
"datasource": null,
"fieldConfig": {
"defaults": {
"color": {
"mode": "fixed"
},
"custom": {
"axis": {
"grid": true,
@ -1604,11 +1616,11 @@
"alpha": 0
},
"line": {
"show": true,
"width": 1,
"color": {
"mode": "fixed"
}
},
"show": true,
"width": 1
},
"nullValues": "null",
"points": {
@ -1668,6 +1680,96 @@
"timeShift": null,
"title": "No tooltip",
"type": "graph3"
},
{
"datasource": "gdev-testdata",
"fieldConfig": {
"defaults": {
"color": {
"mode": "fixed"
},
"custom": {
"axis": {
"grid": true,
"label": "Temperature",
"side": 3,
"width": 60
},
"bars": {
"show": false
},
"fill": {
"alpha": 0
},
"line": {
"color": {
"mode": "fixed"
},
"show": true,
"width": 1
},
"nullValues": "null",
"points": {
"radius": 8,
"show": true
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "celsius"
},
"overrides": []
},
"gridPos": {
"h": 9,
"w": 12,
"x": 0,
"y": 22
},
"id": 9,
"options": {
"graph": {
"realTimeUpdates": false
},
"legend": {
"asTable": false,
"isVisible": true,
"placement": "under"
},
"tooltipOptions": {
"mode": "multi"
}
},
"pluginVersion": "7.2.0-pre",
"targets": [
{
"refId": "C",
"scenarioId": "csv_metric_values",
"stringInput": "1,20,90,30,5,0"
},
{
"refId": "A",
"scenarioId": "csv_metric_values",
"stringInput": "10,25,40"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Diff number of datapoints milt multi series tooltip",
"transformations": [],
"type": "graph3"
}
],
"title": "Tooltip",
@ -1688,6 +1790,9 @@
"datasource": "gdev-testdata",
"fieldConfig": {
"defaults": {
"color": {
"mode": "fixed"
},
"custom": {
"axis": {
"grid": true,
@ -1702,11 +1807,11 @@
"alpha": 0
},
"line": {
"show": true,
"width": 1,
"color": {
"mode": "fixed"
}
},
"show": true,
"width": 1
},
"nullValues": "null",
"points": {
@ -1775,6 +1880,9 @@
"datasource": "gdev-testdata",
"fieldConfig": {
"defaults": {
"color": {
"mode": "fixed"
},
"custom": {
"axis": {
"grid": true,
@ -1795,10 +1903,7 @@
"show": false
},
"line": {
"show": true,
"color": {
"mode": "fixed"
}
"show": true
},
"points": {
"radius": 8,
@ -1809,6 +1914,9 @@
"alpha": 0
},
"line": {
"color": {
"mode": "fixed"
},
"show": true,
"width": 1
},
@ -1879,6 +1987,9 @@
"datasource": "gdev-testdata",
"fieldConfig": {
"defaults": {
"color": {
"mode": "fixed"
},
"custom": {
"axis": {
"grid": true,
@ -1893,11 +2004,11 @@
"alpha": 0
},
"line": {
"show": true,
"width": 1,
"color": {
"mode": "fixed"
}
},
"show": true,
"width": 1
},
"nullValues": "connected",
"points": {
@ -1981,6 +2092,9 @@
"datasource": "gdev-testdata",
"fieldConfig": {
"defaults": {
"color": {
"mode": "fixed"
},
"custom": {
"axis": {
"grid": true,
@ -1995,11 +2109,11 @@
"alpha": 0
},
"line": {
"show": true,
"width": 1,
"color": {
"mode": "fixed"
}
},
"show": true,
"width": 1
},
"nullValues": "null",
"points": {
@ -2031,7 +2145,7 @@
"x": 0,
"y": 16
},
"id": 9,
"id": 43,
"options": {
"graph": {
"realTimeUpdates": false
@ -2083,6 +2197,9 @@
"datasource": "gdev-testdata",
"fieldConfig": {
"defaults": {
"color": {
"mode": "fixed"
},
"custom": {
"align": null,
"axis": {
@ -2098,11 +2215,11 @@
"alpha": 0.2
},
"line": {
"show": false,
"width": 1,
"color": {
"mode": "fixed"
}
},
"show": false,
"width": 1
},
"nullValues": "null",
"points": {
@ -2175,7 +2292,7 @@
"value": false
},
{
"id": "custom.line.color",
"id": "color",
"value": {
"fixedColor": "purple",
"mode": "fixed"
@ -2229,6 +2346,124 @@
],
"title": "Streaming",
"type": "row"
},
{
"collapsed": true,
"datasource": null,
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 17
},
"id": 45,
"panels": [
{
"datasource": null,
"fieldConfig": {
"defaults": {
"color": {
"mode": "fixed"
},
"custom": {
"axis": {
"grid": true,
"label": "",
"side": 3,
"width": 60
},
"bars": {
"show": false
},
"fill": {
"alpha": 0.1
},
"line": {
"color": {
"mode": "fixed"
},
"show": true,
"width": 1
},
"nullValues": "null",
"points": {
"radius": 4,
"show": false
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 11,
"w": 12,
"x": 0,
"y": 18
},
"id": 46,
"options": {
"graph": {
"realTimeUpdates": false
},
"legend": {
"asTable": false,
"isVisible": true,
"placement": "bottom"
},
"tooltipOptions": {
"mode": "single"
}
},
"pluginVersion": "7.3.0-pre",
"targets": [
{
"refId": "A",
"scenarioId": "random_walk"
},
{
"refId": "B",
"scenarioId": "random_walk"
},
{
"refId": "C",
"scenarioId": "random_walk"
},
{
"refId": "D",
"scenarioId": "random_walk"
},
{
"refId": "E",
"scenarioId": "random_walk"
},
{
"refId": "F",
"scenarioId": "annotations",
"stringInput": ""
}
],
"timeFrom": null,
"timeShift": null,
"title": "React NG graph",
"type": "graph3"
}
],
"title": "Annotations",
"type": "row"
}
],
"refresh": false,
@ -2239,15 +2474,14 @@
"list": []
},
"time": {
"from": "now-5m",
"from": "now-2d",
"to": "now"
},
"timepicker": {
"refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"]
},
"timezone": "Africa/Accra",
"title": "Panel Tests - Graph NG",
"title": "Graph NG - tests",
"uid": "TkZXxlNG3",
"version": 61,
"tags": ["gdev", "panel-tests", "graph"]
"version": 65
}

View File

@ -1,12 +1,12 @@
import React, { useState, useLayoutEffect, useRef } from 'react';
import React, { useState, useLayoutEffect, useRef, HTMLAttributes } from 'react';
import { stylesFactory } from '../../themes/stylesFactory';
import { selectThemeVariant } from '../../themes/selectThemeVariant';
import { css } from 'emotion';
import { css, cx } from 'emotion';
import { useTheme } from '../../themes/ThemeContext';
import useWindowSize from 'react-use/lib/useWindowSize';
import { GrafanaTheme } from '@grafana/data';
interface TooltipContainerProps {
interface TooltipContainerProps extends HTMLAttributes<HTMLDivElement> {
position: { x: number; y: number };
offset: { x: number; y: number };
children?: JSX.Element;
@ -27,7 +27,13 @@ const getTooltipContainerStyles = stylesFactory((theme: GrafanaTheme) => {
};
});
export const TooltipContainer: React.FC<TooltipContainerProps> = ({ position, offset, children }) => {
export const TooltipContainer: React.FC<TooltipContainerProps> = ({
position,
offset,
children,
className,
...otherProps
}) => {
const theme = useTheme();
const tooltipRef = useRef<HTMLDivElement>(null);
const { width, height } = useWindowSize();
@ -70,7 +76,8 @@ export const TooltipContainer: React.FC<TooltipContainerProps> = ({ position, of
top: 0,
transform: `translate3d(${placement.x}px, ${placement.y}px, 0)`,
}}
className={styles.wrapper}
{...otherProps}
className={cx(styles.wrapper, className)}
>
{children}
</div>

View File

@ -25,7 +25,13 @@ export const Tag = forwardRef<HTMLElement, Props>(({ name, onClick, className, c
};
return (
<span key={name} ref={ref} onClick={onTagClick} className={cx(styles.wrapper, className)} {...rest}>
<span
key={name}
ref={ref}
onClick={onTagClick}
className={cx(styles.wrapper, className, onClick && styles.hover)}
{...rest}
>
{name}
</span>
);
@ -50,8 +56,9 @@ const getTagStyles = (theme: GrafanaTheme, name: string, colorIndex?: number) =>
text-shadow: none;
padding: 3px 6px;
border-radius: ${theme.border.radius.md};
:hover {
`,
hover: css`
&:hover {
opacity: 0.85;
cursor: pointer;
}

View File

@ -75,6 +75,7 @@ export * from './uPlot/geometries';
export { usePlotConfigContext } from './uPlot/context';
export { Canvas } from './uPlot/Canvas';
export * from './uPlot/plugins';
export { usePlotContext, usePlotData, usePlotPluginContext } from './uPlot/context';
export { Gauge } from './Gauge/Gauge';
export { Graph } from './Graph/Graph';
@ -129,6 +130,7 @@ export { FadeTransition } from './transitions/FadeTransition';
export { SlideOutTransition } from './transitions/SlideOutTransition';
export { Segment, SegmentAsync, SegmentInput, SegmentSelect } from './Segment/';
export { default as Chart } from './Chart';
export { TooltipContainer } from './Chart/TooltipContainer';
export { Drawer } from './Drawer/Drawer';
export { Slider } from './Slider/Slider';

View File

@ -82,7 +82,7 @@ export const UPlotChart: React.FC<PlotProps> = props => {
// Memoize plot context
const plotCtx = useMemo(() => {
return buildPlotContext(registerPlugin, addSeries, addAxis, addScale, canvasRef, props.data, plotInstance);
}, [registerPlugin, canvasRef, props.data, plotInstance, addSeries, addAxis, addScale]);
}, [registerPlugin, addSeries, addAxis, addScale, canvasRef, props.data, plotInstance]);
return (
<PlotContext.Provider value={plotCtx}>

View File

@ -36,11 +36,11 @@ export const usePlotPlugins = () => {
(plugin: PlotPlugin) => {
pluginLog(plugin.id, false, 'register');
if (plugins.hasOwnProperty(plugin.id)) {
throw new Error(`${plugin.id} that is already registered`);
}
setPlugins(plugs => {
if (plugs.hasOwnProperty(plugin.id)) {
throw new Error(`${plugin.id} that is already registered`);
}
return {
...plugs,
[plugin.id]: plugin,
@ -58,7 +58,7 @@ export const usePlotPlugins = () => {
});
};
},
[setPlugins, plugins]
[setPlugins]
);
// When uPlot mounts let's check if there are any plugins pending registration
@ -74,7 +74,7 @@ export const usePlotPlugins = () => {
return {
arePluginsReady,
plugins,
plugins: plugins || {},
registerPlugin,
};
};

View File

@ -10,7 +10,7 @@ interface ContextMenuPluginProps {
}
export const ContextMenuPlugin: React.FC<ContextMenuPluginProps> = ({ onClose }) => {
const [isOpen, setIsOpen] = useState<boolean>(false);
const [isOpen, setIsOpen] = useState(false);
const onClick = useCallback(() => {
setIsOpen(!isOpen);

View File

@ -32,7 +32,7 @@ export const LegendPlugin: React.FC<LegendPluginProps> = ({ placement, displayMo
label: getFieldDisplayName(field, data),
isVisible: true,
//flot vs uPlot differences
yAxis: (field.config.custom as GraphCustomFieldConfig).axis?.side === 1 ? 3 : 1,
yAxis: (field.config.custom as GraphCustomFieldConfig)?.axis?.side === 1 ? 3 : 1,
});
seriesIdx++;
}

View File

@ -99,6 +99,9 @@ export const shouldReinitialisePlot = (prevConfig?: uPlot.Options, config?: uPlo
}
if (!prevConfig && config) {
if (config.width === 0 || config.height === 0) {
return false;
}
return true;
}

View File

@ -33,6 +33,7 @@ import { VizLayout } from './VizLayout';
import { Axis } from '@grafana/ui/src/components/uPlot/geometries/Axis';
import { timeFormatToTemplate } from '@grafana/ui/src/components/uPlot/utils';
import { AnnotationsPlugin } from './plugins/AnnotationsPlugin';
interface GraphPanelProps extends PanelProps<Options> {}
@ -232,6 +233,7 @@ export const GraphPanel: React.FC<GraphPanelProps> = ({
<ZoomPlugin onZoom={onChangeTimeRange} />
<ContextMenuPlugin />
{data.annotations && <AnnotationsPlugin annotations={data.annotations} timeZone={timeZone} />}
{/* TODO: */}
{/*<AnnotationsEditorPlugin />*/}
</UPlotChart>

View File

@ -0,0 +1,145 @@
import React, { useCallback, useRef, useState } from 'react';
import { AnnotationEvent, GrafanaTheme } from '@grafana/data';
import { HorizontalGroup, Portal, Tag, TooltipContainer, useStyles } from '@grafana/ui';
import { css, cx } from 'emotion';
interface AnnotationMarkerProps {
formatTime: (value: number) => string;
annotationEvent: AnnotationEvent;
x: number;
}
export const AnnotationMarker: React.FC<AnnotationMarkerProps> = ({ annotationEvent, x, formatTime }) => {
const styles = useStyles(getAnnotationMarkerStyles);
const [isOpen, setIsOpen] = useState(false);
const markerRef = useRef<HTMLDivElement>(null);
const annotationPopoverRef = useRef<HTMLDivElement>(null);
const popoverRenderTimeout = useRef<NodeJS.Timer>();
const onMouseEnter = useCallback(() => {
if (popoverRenderTimeout.current) {
clearTimeout(popoverRenderTimeout.current);
}
setIsOpen(true);
}, [setIsOpen]);
const onMouseLeave = useCallback(() => {
popoverRenderTimeout.current = setTimeout(() => {
setIsOpen(false);
}, 100);
}, [setIsOpen]);
const renderMarker = useCallback(() => {
if (!markerRef?.current) {
return null;
}
const el = markerRef.current;
const elBBox = el.getBoundingClientRect();
return (
<TooltipContainer
position={{ x: elBBox.left, y: elBBox.top + elBBox.height }}
offset={{ x: 0, y: 0 }}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
className={styles.tooltip}
>
<div ref={annotationPopoverRef} className={styles.wrapper}>
<div className={styles.header}>
<span className={styles.title}>{annotationEvent.title}</span>
{annotationEvent.time && <span className={styles.time}>{formatTime(annotationEvent.time)}</span>}
</div>
<div className={styles.body}>
{annotationEvent.text && <div dangerouslySetInnerHTML={{ __html: annotationEvent.text }} />}
<>
<HorizontalGroup spacing="xs" wrap>
{annotationEvent.tags?.map((t, i) => (
<Tag name={t} key={`${t}-${i}`} />
))}
</HorizontalGroup>
</>
</div>
</div>
</TooltipContainer>
);
}, [annotationEvent]);
return (
<>
<div
ref={markerRef}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
className={cx(
styles.markerWrapper,
css`
left: ${x - 8}px;
`
)}
>
<div className={styles.marker} />
</div>
{isOpen && <Portal>{renderMarker()}</Portal>}
</>
);
};
const getAnnotationMarkerStyles = (theme: GrafanaTheme) => {
const bg = theme.isDark ? theme.palette.dark2 : theme.palette.white;
const headerBg = theme.isDark ? theme.palette.dark9 : theme.palette.gray5;
const shadowColor = theme.isDark ? theme.palette.black : theme.palette.white;
return {
markerWrapper: css`
padding: 0 4px 4px 4px;
position: absolute;
top: 0;
`,
marker: css`
width: 0;
height: 0;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-bottom: 4px solid ${theme.palette.red};
pointer-events: none;
`,
wrapper: css`
background: ${bg};
border: 1px solid ${headerBg};
border-radius: ${theme.border.radius.md};
max-width: 400px;
box-shadow: 0 0 20px ${shadowColor};
`,
tooltip: css`
background: none;
padding: 0;
`,
header: css`
background: ${headerBg};
padding: 6px 10px;
display: flex;
`,
title: css`
font-weight: ${theme.typography.weight.semibold};
padding-right: ${theme.spacing.md};
overflow: hidden;
display: inline-block;
white-space: nowrap;
text-overflow: ellipsis;
flex-grow: 1;
`,
time: css`
color: ${theme.colors.textWeak};
font-style: italic;
font-weight: normal;
display: inline-block;
position: relative;
top: 1px;
`,
body: css`
padding: ${theme.spacing.sm};
font-weight: ${theme.typography.weight.semibold};
`,
};
};

View File

@ -0,0 +1,126 @@
import { AnnotationEvent, DataFrame, dateTimeFormat, systemDateFormats, TimeZone } from '@grafana/data';
import { usePlotContext, usePlotPluginContext, useTheme } from '@grafana/ui';
import { getAnnotationsFromData } from 'app/features/annotations/standardAnnotationSupport';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { css } from 'emotion';
import { AnnotationMarker } from './AnnotationMarker';
interface AnnotationsPluginProps {
annotations: DataFrame[];
timeZone: TimeZone;
}
export const AnnotationsPlugin: React.FC<AnnotationsPluginProps> = ({ annotations, timeZone }) => {
const pluginId = 'AnnotationsPlugin';
const pluginsApi = usePlotPluginContext();
const plotContext = usePlotContext();
const annotationsRef = useRef<AnnotationEvent[] | null>(null);
const [renderCounter, setRenderCounter] = useState(0);
const theme = useTheme();
const timeFormatter = useCallback(
(value: number) => {
return dateTimeFormat(value, {
format: systemDateFormats.fullDate,
timeZone,
});
},
[timeZone]
);
const annotationsData = useMemo(() => {
return getAnnotationsFromData(annotations);
}, [annotations]);
const annotationMarkers = useMemo(() => {
if (!plotContext || !plotContext?.u) {
return null;
}
const markers = [];
for (let i = 0; i < annotationsData.length; i++) {
const annotation = annotationsData[i];
if (!annotation.time) {
continue;
}
const xpos = plotContext.u.valToPos(annotation.time / 1000, 'x');
markers.push(
<AnnotationMarker
x={xpos}
key={`${annotation.time}-${i}`}
formatTime={timeFormatter}
annotationEvent={annotation}
/>
);
}
return (
<div
className={css`
position: absolute;
left: ${plotContext.u.bbox.left / window.devicePixelRatio}px;
top: ${plotContext.u.bbox.top / window.devicePixelRatio +
plotContext.u.bbox.height / window.devicePixelRatio}px;
width: ${plotContext.u.bbox.width / window.devicePixelRatio}px;
height: 14px;
`}
>
{markers}
</div>
);
}, [annotationsData, timeFormatter, plotContext, renderCounter]);
// For uPlot plugin to have access to lates annotation data we need to update the data ref
useEffect(() => {
annotationsRef.current = annotationsData;
}, [annotationsData]);
useEffect(() => {
const unregister = pluginsApi.registerPlugin({
id: pluginId,
hooks: {
// Render annotation lines on the canvas
draw: u => {
/**
* We cannot rely on state value here, as it would require this effect to be dependent on the state value.
* This would make the plugin re-register making the entire plot to reinitialise. ref is the way to go :)
*/
if (!annotationsRef.current) {
return null;
}
const ctx = u.ctx;
if (!ctx) {
return;
}
for (let i = 0; i < annotationsRef.current.length; i++) {
const annotation = annotationsRef.current[i];
if (!annotation.time) {
continue;
}
const xpos = u.valToPos(annotation.time / 1000, 'x', true);
ctx.beginPath();
ctx.lineWidth = 2;
ctx.strokeStyle = theme.palette.red;
ctx.setLineDash([5, 5]);
ctx.moveTo(xpos, u.bbox.top);
ctx.lineTo(xpos, u.bbox.top + u.bbox.height);
ctx.stroke();
ctx.closePath();
}
setRenderCounter(c => c + 1);
return;
},
},
});
return () => {
unregister();
};
}, []);
if (!plotContext || !plotContext.u || !plotContext.canvas) {
return null;
}
return <div>{annotationMarkers}</div>;
};