@cfasim-ui/docs 0.4.4 → 0.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -391,6 +391,199 @@ Highlight a range of a series line by filling the area between the line and the
391
391
  </template>
392
392
  </ComponentDemo>
393
393
 
394
+ ### Annotations
395
+
396
+ Pin callouts to data points with `annotations`. Each annotation anchors at
397
+ `(x, y)` in data coordinates (the same x-space as the chart axis, so it
398
+ respects `xMin` and explicit `x` values), with a pixel
399
+ `offset: { x, y }` for the label position. Text supports `\n` for line
400
+ breaks. A curved
401
+ pointer line connects the anchor to the label, and the label gets a halo
402
+ stroke matching the background so it stays legible over series lines.
403
+
404
+ <ComponentDemo>
405
+ <LineChart
406
+ :data="[0, 4, 8, 15, 22, 30, 28, 20, 12, 5, 2]"
407
+ :annotations="[
408
+ { x: 5, y: 30, offset: { x: 24, y: -28 }, text: 'Peak\nDay 5' },
409
+ { x: 0, y: 0, offset: { x: 28, y: -22 }, text: 'Onset' },
410
+ ]"
411
+ :chart-padding="{ top: 40, right: 24 }"
412
+ :height="240"
413
+ x-label="Days"
414
+ y-label="Cases"
415
+ />
416
+
417
+ <template #code>
418
+
419
+ ```vue
420
+ <LineChart
421
+ :data="[0, 4, 8, 15, 22, 30, 28, 20, 12, 5, 2]"
422
+ :annotations="[
423
+ { x: 5, y: 30, offset: { x: 24, y: -28 }, text: 'Peak\nDay 5' },
424
+ { x: 0, y: 0, offset: { x: 28, y: -22 }, text: 'Onset' },
425
+ ]"
426
+ :chart-padding="{ top: 40, right: 24 }"
427
+ :height="240"
428
+ x-label="Days"
429
+ y-label="Cases"
430
+ />
431
+ ```
432
+
433
+ </template>
434
+ </ComponentDemo>
435
+
436
+ Use `chart-padding` to reserve room outside the plot so annotations (or
437
+ any other overlay) don't clip the data area. It accepts a number (same on
438
+ all sides) or an object with `top`, `right`, `bottom`, `left`.
439
+
440
+ Annotation text supports a small set of inline markers:
441
+
442
+ - `**bold**` — bold
443
+ - `_italic_` — italic
444
+ - `\n` — line break
445
+
446
+ Markers compose (`**_bold italic_**`).
447
+
448
+ <ComponentDemo>
449
+ <LineChart
450
+ :data="[0, 4, 8, 15, 22, 30, 28, 20, 12, 5, 2]"
451
+ :annotations="[
452
+ { x: 5, y: 30, offset: { x: 24, y: -28 }, text: '**Peak**\n_Day 5_' },
453
+ ]"
454
+ :chart-padding="{ top: 40, right: 24 }"
455
+ :height="240"
456
+ x-label="Days"
457
+ y-label="Cases"
458
+ />
459
+
460
+ <template #code>
461
+
462
+ ```vue
463
+ <LineChart
464
+ :data="[0, 4, 8, 15, 22, 30, 28, 20, 12, 5, 2]"
465
+ :annotations="[
466
+ { x: 5, y: 30, offset: { x: 24, y: -28 }, text: '**Peak**\n_Day 5_' },
467
+ ]"
468
+ :chart-padding="{ top: 40, right: 24 }"
469
+ :height="240"
470
+ x-label="Days"
471
+ y-label="Cases"
472
+ />
473
+ ```
474
+
475
+ </template>
476
+ </ComponentDemo>
477
+
478
+ ```ts
479
+ interface ChartAnnotation {
480
+ x: number; // anchor in data coords (x-axis)
481
+ y: number; // anchor in data coords (y-axis)
482
+ text: string; // label text; \n produces line breaks
483
+ offset: { x: number; y: number }; // pixel offset from anchor to label
484
+ color?: string; // text / pointer color (default: currentColor)
485
+ fontSize?: number; // default: 13 (matches axis labels)
486
+ fontWeight?: string | number; // default: "normal"
487
+ haloColor?: string; // background-matched halo (default: var(--color-bg-0, #fff))
488
+ haloWidth?: number; // default: 3
489
+ textAnchor?: "start" | "middle" | "end"; // default: derived from offset[0] sign
490
+ lineColor?: string; // connector-line color override (default: color)
491
+ lineWidth?: number; // default: 1
492
+ lineDash?: string | number | readonly number[]; // SVG stroke-dasharray
493
+ // default: "curved"
494
+ // "ruleX" / "ruleY" span the full plot on the named axis;
495
+ // "ruleUp" / "ruleDown" / "ruleFromLeft" / "ruleFromRight" run from an edge to the anchor.
496
+ pointer?:
497
+ | "curved"
498
+ | "straight"
499
+ | "none"
500
+ | "ruleX"
501
+ | "ruleY"
502
+ | "ruleUp"
503
+ | "ruleDown"
504
+ | "ruleFromLeft"
505
+ | "ruleFromRight";
506
+ arrow?: boolean; // triangle marker at the anchor end (default: true)
507
+ }
508
+ ```
509
+
510
+ ### Rules
511
+
512
+ Set `pointer` to one of the rule values to replace the curved /
513
+ straight connector with a straight line through the anchor:
514
+
515
+ - `"ruleX"` — vertical line spanning the plot height at the annotation's `x`.
516
+ - `"ruleY"` — horizontal line spanning the plot width at the annotation's `y`.
517
+ - `"ruleUp"` — vertical from the bottom edge up to the anchor.
518
+ - `"ruleDown"` — vertical from the top edge down to the anchor.
519
+ - `"ruleFromLeft"` — horizontal from the left edge in to the anchor.
520
+ - `"ruleFromRight"` — horizontal from the right edge in to the anchor.
521
+
522
+ `lineColor`, `lineWidth`, and `lineDash` style the line. The label is
523
+ positioned from the anchor as usual via `offset`.
524
+
525
+ <ComponentDemo>
526
+ <LineChart
527
+ :data="[0, 4, 8, 15, 22, 30, 28, 20, 12, 5, 2]"
528
+ :annotations="[
529
+ {
530
+ x: 5,
531
+ y: 30,
532
+ offset: { x: 8, y: 14 },
533
+ text: 'Peak',
534
+ pointer: 'ruleX',
535
+ lineDash: '4 3',
536
+ },
537
+ {
538
+ x: 5,
539
+ y: 30,
540
+ offset: { x: -8, y: -6 },
541
+ text: 'Max',
542
+ textAnchor: 'end',
543
+ pointer: 'ruleFromLeft',
544
+ lineDash: '4 3',
545
+ },
546
+ ]"
547
+ :chart-padding="{ top: 24, right: 24 }"
548
+ :height="240"
549
+ x-label="Days"
550
+ y-label="Cases"
551
+ />
552
+
553
+ <template #code>
554
+
555
+ ```vue
556
+ <LineChart
557
+ :data="[0, 4, 8, 15, 22, 30, 28, 20, 12, 5, 2]"
558
+ :annotations="[
559
+ {
560
+ x: 5,
561
+ y: 30,
562
+ offset: { x: 8, y: 14 },
563
+ text: 'Peak',
564
+ pointer: 'ruleX',
565
+ lineDash: '4 3',
566
+ },
567
+ {
568
+ x: 5,
569
+ y: 30,
570
+ offset: { x: -8, y: -6 },
571
+ text: 'Max',
572
+ textAnchor: 'end',
573
+ pointer: 'ruleFromLeft',
574
+ lineDash: '4 3',
575
+ },
576
+ ]"
577
+ :chart-padding="{ top: 24, right: 24 }"
578
+ :height="240"
579
+ x-label="Days"
580
+ y-label="Cases"
581
+ />
582
+ ```
583
+
584
+ </template>
585
+ </ComponentDemo>
586
+
394
587
  ### Custom CSV download
395
588
 
396
589
  By default, the Download CSV menu item exports the chart series as CSV. Use
@@ -444,36 +637,38 @@ until the user clicks Download:
444
637
 
445
638
  | Prop | Type | Required | Default |
446
639
  |------|------|----------|---------|
640
+ | `width` | `number` | No | — |
641
+ | `height` | `number` | No | — |
642
+ | `title` | `string` | No | — |
643
+ | `xLabel` | `string` | No | — |
644
+ | `yLabel` | `string` | No | — |
645
+ | `debounce` | `number` | No | — |
646
+ | `menu` | `boolean \| string` | No | `true` |
647
+ | `tooltipData` | `ArrayLike&lt;unknown&gt;` | No | — |
648
+ | `tooltipTrigger` | `"hover" \| "click"` | No | — |
649
+ | `tooltipClamp` | `"none" \| "chart" \| "window"` | No | `"chart"` |
650
+ | `tooltipValueFormat` | `(value: number) =&gt; string` | No | — |
651
+ | `csv` | `string \| (() =&gt; string)` | No | — |
652
+ | `filename` | `string` | No | — |
653
+ | `downloadLink` | `boolean \| string` | No | — |
654
+ | `annotations` | `readonly ChartAnnotation[]` | No | — |
655
+ | `chartPadding` | `ChartPadding` | No | — |
447
656
  | `y` | `LineChartData` | No | — |
448
657
  | `data` | `LineChartData` | No | — |
449
658
  | `x` | `LineChartData` | No | — |
450
659
  | `series` | `Series[]` | No | — |
451
660
  | `areas` | `Area[]` | No | — |
452
661
  | `areaSections` | `AreaSection[]` | No | — |
453
- | `width` | `number` | No | — |
454
- | `height` | `number` | No | — |
455
662
  | `lineOpacity` | `number` | No | `1` |
456
- | `title` | `string` | No | — |
457
- | `xLabel` | `string` | No | — |
458
- | `yLabel` | `string` | No | — |
459
663
  | `yMin` | `number` | No | — |
460
664
  | `xMin` | `number` | No | — |
461
665
  | `xTicks` | `number \| number[]` | No | — |
462
666
  | `yTicks` | `number \| number[]` | No | — |
463
667
  | `xTickFormat` | `(value: number, index: number) =&gt; string` | No | — |
464
668
  | `yTickFormat` | `(value: number) =&gt; string` | No | — |
465
- | `tooltipValueFormat` | `(value: number) =&gt; string` | No | — |
466
669
  | `xLabels` | `string[]` | No | — |
467
- | `debounce` | `number` | No | — |
468
- | `menu` | `boolean \| string` | No | `true` |
469
670
  | `xGrid` | `boolean` | No | — |
470
671
  | `yGrid` | `boolean` | No | — |
471
- | `tooltipData` | `ArrayLike&lt;unknown&gt;` | No | — |
472
- | `tooltipTrigger` | `"hover" \| "click"` | No | — |
473
- | `tooltipClamp` | `"none" \| "chart" \| "window"` | No | `"chart"` |
474
- | `csv` | `string \| (() =&gt; string)` | No | — |
475
- | `filename` | `string` | No | — |
476
- | `downloadLink` | `boolean \| string` | No | — |
477
672
 
478
673
 
479
674
  ### Data
@@ -1,17 +1,18 @@
1
1
  <script setup lang="ts">
2
- import { computed, ref } from "vue";
2
+ import { computed } from "vue";
3
3
  import ChartMenu from "../ChartMenu/ChartMenu.vue";
4
4
  import {
5
5
  snap,
6
6
  formatTick,
7
7
  computeTickValues,
8
8
  seriesToCsv,
9
- useChartSize,
10
- useChartTooltip,
11
- useChartMenu,
12
- useChartPadding,
13
- INLINE_LEGEND_HEIGHT,
9
+ useChartFoundation,
10
+ makeTooltipValueFormatter,
11
+ ChartAnnotations,
14
12
  type ChartData,
13
+ type ChartCommonProps,
14
+ type ChartHoverPayload,
15
+ type ChartTooltipBaseProps,
15
16
  } from "../_shared/index.js";
16
17
 
17
18
  /**
@@ -83,118 +84,69 @@ export interface AreaSection {
83
84
  legend?: "inline" | "below" | false;
84
85
  }
85
86
 
86
- const props = withDefaults(
87
- defineProps<{
88
- /** Y-values. Equivalent to `data`. If both are set, `y` wins. */
89
- y?: LineChartData;
90
- /** Y-values (alternative name for `y`). */
91
- data?: LineChartData;
92
- /**
93
- * Optional x-values paired with `y`/`data`. When provided, points
94
- * are plotted at the given x positions instead of at their indices.
95
- * Ignored when `series` is used — set `x` on each `Series` instead.
96
- */
97
- x?: LineChartData;
98
- series?: Series[];
99
- areas?: Area[];
100
- areaSections?: AreaSection[];
101
- width?: number;
102
- height?: number;
103
- lineOpacity?: number;
104
- title?: string;
105
- xLabel?: string;
106
- yLabel?: string;
107
- yMin?: number;
108
- /**
109
- * Offset applied to index-based x values (e.g. `xMin: 10` starts the
110
- * x axis at 10 instead of 0). Ignored when any series or area has
111
- * explicit `x` values.
112
- */
113
- xMin?: number;
114
- /**
115
- * Tick placement on the x-axis. Number = interval in data units
116
- * (respecting `xMin`, e.g. `7` ticks every 7 days). Array = explicit tick
117
- * values in data space; values outside the data range are dropped.
118
- * When omitted, ticks are chosen automatically.
119
- */
120
- xTicks?: number | number[];
121
- /**
122
- * Tick placement on the y-axis. Number = interval in data units. Array =
123
- * explicit tick values; values outside the data range are dropped. When
124
- * omitted, ticks are chosen automatically.
125
- */
126
- yTicks?: number | number[];
127
- /** Formatter for x-axis tick labels. Receives the raw numeric value. */
128
- xTickFormat?: (value: number, index: number) => string;
129
- /** Formatter for y-axis tick labels. Receives the raw numeric value. */
130
- yTickFormat?: (value: number) => string;
131
- /**
132
- * Formatter for numeric values shown in the default tooltip. Receives
133
- * the raw value. Defaults to the same tick formatter used for axes.
134
- */
135
- tooltipValueFormat?: (value: number) => string;
136
- /**
137
- * @deprecated Use `xTickFormat` (e.g. `(_, i) => labels[i]`) together
138
- * with `xTicks` for explicit control. Still honored for tooltip x-labels
139
- * and as a default x-tick formatter when `xTickFormat` is not provided.
140
- */
141
- xLabels?: string[];
142
- debounce?: number;
143
- menu?: boolean | string;
144
- xGrid?: boolean;
145
- yGrid?: boolean;
146
- /**
147
- * Custom per-index data passed to the tooltip slot. Accepts a plain
148
- * array or any `ArrayLike` (e.g. a typed array column from a
149
- * `ModelOutput`).
150
- */
151
- tooltipData?: ArrayLike<unknown>;
152
- /** Tooltip activation mode. Default: 'hover' */
153
- tooltipTrigger?: "hover" | "click";
154
- /**
155
- * Boundary for tooltip flip/clamp. `"none"` always places to the right of
156
- * the pointer with no clamping. `"chart"` (default) uses the chart
157
- * container's bounding box. `"window"` uses the viewport.
158
- */
159
- tooltipClamp?: "none" | "chart" | "window";
160
- /**
161
- * Custom CSV content for the Download CSV menu item. Can be a raw CSV
162
- * string or a function returning one. When omitted, CSV is generated
163
- * from the chart series.
164
- */
165
- csv?: string | (() => string);
166
- /** Filename (without extension) for downloaded SVG, PNG and CSV files. */
167
- filename?: string;
168
- /**
169
- * Show a plain text link below the chart to download the CSV data.
170
- * Pass `true` for the default label ("Download data (CSV)") or a string
171
- * to customize the link text.
172
- */
173
- downloadLink?: boolean | string;
174
- }>(),
175
- { lineOpacity: 1, menu: true, tooltipClamp: "chart" },
176
- );
87
+ interface LineChartProps extends ChartCommonProps {
88
+ /** Y-values. Equivalent to `data`. If both are set, `y` wins. */
89
+ y?: LineChartData;
90
+ /** Y-values (alternative name for `y`). */
91
+ data?: LineChartData;
92
+ /**
93
+ * Optional x-values paired with `y`/`data`. When provided, points
94
+ * are plotted at the given x positions instead of at their indices.
95
+ * Ignored when `series` is used set `x` on each `Series` instead.
96
+ */
97
+ x?: LineChartData;
98
+ series?: Series[];
99
+ areas?: Area[];
100
+ areaSections?: AreaSection[];
101
+ lineOpacity?: number;
102
+ yMin?: number;
103
+ /**
104
+ * Offset applied to index-based x values (e.g. `xMin: 10` starts the
105
+ * x axis at 10 instead of 0). Ignored when any series or area has
106
+ * explicit `x` values.
107
+ */
108
+ xMin?: number;
109
+ /**
110
+ * Tick placement on the x-axis. Number = interval in data units
111
+ * (respecting `xMin`, e.g. `7` ticks every 7 days). Array = explicit tick
112
+ * values in data space; values outside the data range are dropped.
113
+ * When omitted, ticks are chosen automatically.
114
+ */
115
+ xTicks?: number | number[];
116
+ /**
117
+ * Tick placement on the y-axis. Number = interval in data units. Array =
118
+ * explicit tick values; values outside the data range are dropped. When
119
+ * omitted, ticks are chosen automatically.
120
+ */
121
+ yTicks?: number | number[];
122
+ /** Formatter for x-axis tick labels. Receives the raw numeric value. */
123
+ xTickFormat?: (value: number, index: number) => string;
124
+ /** Formatter for y-axis tick labels. Receives the raw numeric value. */
125
+ yTickFormat?: (value: number) => string;
126
+ /**
127
+ * @deprecated Use `xTickFormat` (e.g. `(_, i) => labels[i]`) together
128
+ * with `xTicks` for explicit control. Still honored for tooltip x-labels
129
+ * and as a default x-tick formatter when `xTickFormat` is not provided.
130
+ */
131
+ xLabels?: string[];
132
+ xGrid?: boolean;
133
+ yGrid?: boolean;
134
+ }
135
+
136
+ const props = withDefaults(defineProps<LineChartProps>(), {
137
+ lineOpacity: 1,
138
+ menu: true,
139
+ tooltipClamp: "chart",
140
+ });
177
141
 
178
142
  const emit = defineEmits<{
179
- (e: "hover", payload: { index: number } | null): void;
143
+ (e: "hover", payload: ChartHoverPayload): void;
180
144
  }>();
181
145
 
182
146
  defineSlots<{
183
- tooltip?(props: {
184
- index: number;
185
- xLabel?: string;
186
- values: { value: number; color: string; seriesIndex: number }[];
187
- data: unknown;
188
- }): unknown;
147
+ tooltip?(props: ChartTooltipBaseProps & { xLabel?: string }): unknown;
189
148
  }>();
190
149
 
191
- const { containerRef, measuredWidth } = useChartSize({
192
- debounce: () => props.debounce,
193
- });
194
-
195
- const width = computed(() => props.width ?? (measuredWidth.value || 400));
196
- const height = computed(() => props.height ?? 200);
197
-
198
150
  const hasInlineLegend = computed(
199
151
  () =>
200
152
  allSeries.value.some((s) => s.legend) ||
@@ -204,15 +156,6 @@ const hasInlineLegend = computed(
204
156
  false,
205
157
  );
206
158
 
207
- const { padding, innerW, innerH } = useChartPadding({
208
- title: () => props.title,
209
- xLabel: () => props.xLabel,
210
- yLabel: () => props.yLabel,
211
- hasInlineLegend: () => hasInlineLegend.value,
212
- width: () => width.value,
213
- height: () => height.value,
214
- });
215
-
216
159
  /**
217
160
  * Internal series shape where `data` (y-values) is always resolved.
218
161
  * `Series.y` takes precedence over `Series.data` when both are set.
@@ -225,11 +168,10 @@ function resolveSeries(s: Series): ResolvedSeries {
225
168
  return { ...s, data: s.y ?? s.data ?? EMPTY_DATA };
226
169
  }
227
170
 
228
- function formatTooltipValue(v: number): string {
229
- if (props.tooltipValueFormat) return props.tooltipValueFormat(v);
230
- if (props.yTickFormat) return props.yTickFormat(v);
231
- return formatTick(v);
232
- }
171
+ const formatTooltipValue = makeTooltipValueFormatter(
172
+ () => props.tooltipValueFormat,
173
+ () => props.yTickFormat,
174
+ );
233
175
 
234
176
  const allSeries = computed<ResolvedSeries[]>(() => {
235
177
  if (props.series && props.series.length > 0)
@@ -758,6 +700,18 @@ const hoverSlotProps = computed(() => {
758
700
  };
759
701
  });
760
702
 
703
+ function projectAnnotation(
704
+ x: number,
705
+ y: number,
706
+ ): { x: number; y: number } | null {
707
+ if (!isFinite(x) || !isFinite(y)) return null;
708
+ const internalX = x - xDisplayOffset.value;
709
+ const { min, range } = extent.value;
710
+ const py =
711
+ padding.value.top + innerH.value - (y - min) * (innerH.value / range);
712
+ return { x: xPixel(internalX), y: py };
713
+ }
714
+
761
715
  function indexFromPointer(clientX: number): number | null {
762
716
  const rect = containerRef.value?.getBoundingClientRect();
763
717
  if (!rect) return null;
@@ -771,30 +725,41 @@ function indexFromPointer(clientX: number): number | null {
771
725
  }
772
726
 
773
727
  const {
728
+ containerRef,
729
+ svgRef,
730
+ width,
731
+ height,
732
+ padding,
733
+ legendY,
734
+ innerW,
735
+ innerH,
736
+ bounds,
774
737
  hoverIndex,
775
738
  tooltipRef,
776
739
  tooltipPos,
777
- handlers: tooltipHandlers,
778
- } = useChartTooltip({
779
- enabled: () => hasTooltipSlot.value,
780
- trigger: () => props.tooltipTrigger,
781
- clamp: () => props.tooltipClamp,
782
- pointerToIndex: indexFromPointer,
783
- containerRef,
784
- onHover: (payload) => emit("hover", payload),
785
- });
786
-
787
- const {
788
- svgRef,
789
- items: menuItems,
740
+ tooltipHandlers,
741
+ menuItems,
790
742
  downloadLinkText,
791
743
  csvHref,
792
- resolvedFilename: menuFilename,
793
- } = useChartMenu({
744
+ menuFilename,
745
+ } = useChartFoundation({
746
+ width: () => props.width,
747
+ height: () => props.height,
748
+ title: () => props.title,
749
+ xLabel: () => props.xLabel,
750
+ yLabel: () => props.yLabel,
751
+ debounce: () => props.debounce,
752
+ menu: () => props.menu,
753
+ tooltipTrigger: () => props.tooltipTrigger,
754
+ tooltipClamp: () => props.tooltipClamp,
794
755
  filename: () => props.filename,
795
- legacyMenuLabel: () => props.menu,
796
- getCsv: toCsv,
797
756
  downloadLink: () => props.downloadLink,
757
+ chartPadding: () => props.chartPadding,
758
+ hasInlineLegend: () => hasInlineLegend.value,
759
+ hasTooltipSlot: () => hasTooltipSlot.value,
760
+ getCsv: toCsv,
761
+ pointerToIndex: indexFromPointer,
762
+ onHover: (payload) => emit("hover", payload),
798
763
  });
799
764
  </script>
800
765
 
@@ -821,9 +786,9 @@ const {
821
786
  <line
822
787
  v-if="item.type === 'series'"
823
788
  :x1="padding.left + i * 120"
824
- :y1="padding.top - INLINE_LEGEND_HEIGHT / 2"
789
+ :y1="legendY"
825
790
  :x2="padding.left + i * 120 + 12"
826
- :y2="padding.top - INLINE_LEGEND_HEIGHT / 2"
791
+ :y2="legendY"
827
792
  :stroke="item.color"
828
793
  stroke-width="2"
829
794
  :stroke-dasharray="item.dashed ? '4 2' : undefined"
@@ -832,7 +797,7 @@ const {
832
797
  <circle
833
798
  v-else
834
799
  :cx="padding.left + i * 120 + 4"
835
- :cy="padding.top - INLINE_LEGEND_HEIGHT / 2"
800
+ :cy="legendY"
836
801
  r="4"
837
802
  :fill="item.color"
838
803
  :fill-opacity="item.fillOpacity"
@@ -841,7 +806,7 @@ const {
841
806
  />
842
807
  <text
843
808
  :x="padding.left + i * 120 + 18"
844
- :y="padding.top - INLINE_LEGEND_HEIGHT / 2 + 4"
809
+ :y="legendY + 4"
845
810
  font-size="11"
846
811
  fill="currentColor"
847
812
  >
@@ -1074,6 +1039,13 @@ const {
1074
1039
  style="cursor: crosshair; touch-action: none"
1075
1040
  v-on="tooltipHandlers"
1076
1041
  />
1042
+ <!-- annotations (top layer) -->
1043
+ <ChartAnnotations
1044
+ v-if="annotations && annotations.length > 0"
1045
+ :annotations="annotations"
1046
+ :project="projectAnnotation"
1047
+ :bounds="bounds"
1048
+ />
1077
1049
  <!-- area section labels -->
1078
1050
  <g v-for="(item, i) in sectionLabels.labels" :key="'seclab' + i">
1079
1051
  <circle