@cfasim-ui/docs 0.4.11 → 0.4.13

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.
@@ -14,7 +14,23 @@ import {
14
14
  makeTooltipValueFormatter,
15
15
  ChartAnnotations,
16
16
  INLINE_LEGEND_ROW_HEIGHT,
17
+ TITLE_LINE_HEIGHT,
18
+ TITLE_FONT_SIZE,
19
+ TITLE_FONT_WEIGHT,
20
+ AXIS_LABEL_FONT_SIZE,
21
+ TICK_LABEL_FONT_SIZE,
22
+ TICK_LABEL_OPACITY,
23
+ LEGEND_FONT_SIZE,
24
+ resolveLabelStyle,
25
+ parseDate,
26
+ isAllDates,
27
+ pickDateTicks,
28
+ formatDate,
17
29
  type ChartData,
30
+ type LabelStyle,
31
+ type DateFormat,
32
+ type DateTickUnit,
33
+ type DateTimezone,
18
34
  type ChartCommonProps,
19
35
  type ChartHoverPayload,
20
36
  type ChartTooltipBaseProps,
@@ -29,6 +45,13 @@ import {
29
45
  */
30
46
  export type LineChartData = ChartData;
31
47
 
48
+ /**
49
+ * Accepted shapes for an `x` array. Numeric arrays (typed or plain) plot
50
+ * as numbers; string-or-`Date` arrays trigger date-axis mode when every
51
+ * element parses. See the `timezone` prop and `dateAxis` module.
52
+ */
53
+ export type LineChartXInput = LineChartData | readonly (string | Date)[];
54
+
32
55
  export interface Series {
33
56
  /**
34
57
  * Y-values. One of `y` or `data` must be supplied; `y` wins if both
@@ -40,9 +63,10 @@ export interface Series {
40
63
  /**
41
64
  * Optional x-values, parallel to `y`/`data`. When set, the chart
42
65
  * plots points at the given x positions (irregular spacing supported).
43
- * When omitted, points are plotted at indices 0, 1, 2, ...
66
+ * When omitted, points are plotted at indices 0, 1, 2, ... Accepts
67
+ * date strings or `Date` objects to enable date-axis mode.
44
68
  */
45
- x?: LineChartData;
69
+ x?: LineChartXInput;
46
70
  color?: string;
47
71
  dashed?: boolean;
48
72
  strokeWidth?: number;
@@ -78,7 +102,7 @@ export interface Area {
78
102
  upper: LineChartData;
79
103
  lower: LineChartData;
80
104
  /** Optional x-values parallel to `upper`/`lower`. See `Series.x`. */
81
- x?: LineChartData;
105
+ x?: LineChartXInput;
82
106
  color?: string;
83
107
  opacity?: number;
84
108
  }
@@ -104,6 +128,17 @@ export interface AreaSection {
104
128
  dashed?: boolean;
105
129
  /** Label placement: "below" (default) renders below chart, "inline" renders in legend row, false hides label */
106
130
  legend?: "inline" | "below" | false;
131
+ /**
132
+ * Style for the area section's primary label text. Defaults: font-size
133
+ * 11, font-weight 600, color taken from the section's `color`.
134
+ */
135
+ inlineLabelStyle?: LabelStyle;
136
+ /**
137
+ * Style for the area section's secondary description text. Defaults:
138
+ * font-size 11, currentColor at 0.6 opacity. Providing `color` drops
139
+ * the default opacity.
140
+ */
141
+ inlineDescriptionStyle?: LabelStyle;
107
142
  }
108
143
 
109
144
  interface LineChartProps extends ChartCommonProps {
@@ -115,8 +150,9 @@ interface LineChartProps extends ChartCommonProps {
115
150
  * Optional x-values paired with `y`/`data`. When provided, points
116
151
  * are plotted at the given x positions instead of at their indices.
117
152
  * Ignored when `series` is used — set `x` on each `Series` instead.
153
+ * Accepts date strings or `Date` objects to enable date-axis mode.
118
154
  */
119
- x?: LineChartData;
155
+ x?: LineChartXInput;
120
156
  series?: Series[];
121
157
  areas?: Area[];
122
158
  areaSections?: AreaSection[];
@@ -149,12 +185,24 @@ interface LineChartProps extends ChartCommonProps {
149
185
  */
150
186
  yTicks?: number | number[];
151
187
  /**
152
- * Formatter for x-axis tick labels. Accepts a preset name, a printf-style
153
- * format string, or a function. The two-arg function form `(value, index)`
154
- * is also supported for index-based labels. See `formatNumber` in
155
- * `@cfasim-ui/shared`.
188
+ * Formatter for x-axis tick labels. On a numeric axis, accepts a
189
+ * preset name, a printf-style format string, or a function (the
190
+ * two-arg `(value, index)` form is also supported for index-based
191
+ * labels — see `formatNumber` in `@cfasim-ui/shared`). On a date
192
+ * axis (auto-detected when every x value parses as a date), accepts
193
+ * a `DateFormat` instead — see the `dateAxis` module.
194
+ */
195
+ xTickFormat?:
196
+ | NumberFormat
197
+ | ((value: number, index: number) => string)
198
+ | DateFormat;
199
+ /**
200
+ * Timezone used when parsing offset-less ISO strings and rendering
201
+ * date-axis tick labels. `"utc"` (default) keeps visuals consistent
202
+ * across viewers; `"local"` uses the browser timezone. Ignored on a
203
+ * numeric axis.
156
204
  */
157
- xTickFormat?: NumberFormat | ((value: number, index: number) => string);
205
+ timezone?: DateTimezone;
158
206
  /**
159
207
  * Formatter for y-axis tick labels. Accepts a preset name, a printf-style
160
208
  * format string, or a function. See `formatNumber` in `@cfasim-ui/shared`.
@@ -186,15 +234,49 @@ defineSlots<{
186
234
  }>();
187
235
 
188
236
  /**
189
- * Internal series shape where `data` (y-values) is always resolved.
190
- * `Series.y` takes precedence over `Series.data` when both are set.
237
+ * Internal series shape where `data` (y-values) is always resolved and
238
+ * `x` is narrowed to numeric (date inputs have already been parsed to
239
+ * epoch-ms). `Series.y` takes precedence over `Series.data`.
191
240
  */
192
- type ResolvedSeries = Omit<Series, "data" | "y"> & { data: LineChartData };
241
+ type ResolvedSeries = Omit<Series, "data" | "y" | "x"> & {
242
+ data: LineChartData;
243
+ x?: LineChartData;
244
+ };
245
+
246
+ /** Same shape as `Area` but with `x` narrowed to numeric post-parse. */
247
+ type ResolvedArea = Omit<Area, "x"> & { x?: LineChartData };
193
248
 
194
249
  const EMPTY_DATA: readonly number[] = [];
195
250
 
196
- function resolveSeries(s: Series): ResolvedSeries {
197
- return { ...s, data: s.y ?? s.data ?? EMPTY_DATA };
251
+ /**
252
+ * Yields every user-supplied `x` array on the component (top-level,
253
+ * per-series, per-area). Used both for date auto-detection and for the
254
+ * single-pass resolution that converts strings/Dates to numeric ms.
255
+ */
256
+ function* xInputs(p: LineChartProps): Iterable<LineChartXInput> {
257
+ if (p.x && p.x.length > 0) yield p.x;
258
+ if (p.series) {
259
+ for (const s of p.series) if (s.x && s.x.length > 0) yield s.x;
260
+ }
261
+ if (p.areas) {
262
+ for (const a of p.areas) if (a.x && a.x.length > 0) yield a.x;
263
+ }
264
+ }
265
+
266
+ /** Convert an x input to numeric. Pre-parsed when `isDate` is true. */
267
+ function toNumericX(
268
+ x: LineChartXInput | undefined,
269
+ isDate: boolean,
270
+ tz: DateTimezone,
271
+ ): LineChartData | undefined {
272
+ if (!x) return undefined;
273
+ if (!isDate) return x as LineChartData;
274
+ const out = new Float64Array(x.length);
275
+ for (let i = 0; i < x.length; i++) {
276
+ const v = parseDate(x[i], tz);
277
+ out[i] = v ?? NaN;
278
+ }
279
+ return out;
198
280
  }
199
281
 
200
282
  const formatTooltipValue = makeTooltipValueFormatter(
@@ -202,15 +284,58 @@ const formatTooltipValue = makeTooltipValueFormatter(
202
284
  () => props.yTickFormat,
203
285
  );
204
286
 
205
- const allSeries = computed<ResolvedSeries[]>(() => {
206
- if (props.series && props.series.length > 0)
207
- return props.series.map(resolveSeries);
208
- const topY = props.y ?? props.data;
209
- if (topY) return [{ data: topY, x: props.x }];
210
- return [];
287
+ const tz = computed<DateTimezone>(() => props.timezone ?? "utc");
288
+
289
+ /**
290
+ * Single-pass resolution of x-axis mode + resolved series and areas.
291
+ * Date auto-detection (`isAllDates` per input array) and date→ms
292
+ * conversion (`toNumericX`) both call `parseDate`; this computed
293
+ * makes sure each user-supplied x value is parsed exactly once per
294
+ * render even though we have multiple consumers.
295
+ */
296
+ const resolvedXAxis = computed<{
297
+ isDate: boolean;
298
+ series: ResolvedSeries[];
299
+ areas: ResolvedArea[];
300
+ }>(() => {
301
+ const z = tz.value;
302
+ let isDate = false;
303
+ let any = false;
304
+ for (const x of xInputs(props)) {
305
+ any = true;
306
+ if (!isAllDates(x, z)) {
307
+ isDate = false;
308
+ break;
309
+ }
310
+ isDate = true;
311
+ }
312
+ if (!any) isDate = false;
313
+
314
+ const series: ResolvedSeries[] =
315
+ props.series && props.series.length > 0
316
+ ? props.series.map((s) => ({
317
+ ...s,
318
+ data: s.y ?? s.data ?? EMPTY_DATA,
319
+ x: toNumericX(s.x, isDate, z),
320
+ }))
321
+ : (() => {
322
+ const topY = props.y ?? props.data;
323
+ return topY
324
+ ? [{ data: topY, x: toNumericX(props.x, isDate, z) }]
325
+ : [];
326
+ })();
327
+
328
+ const areas: ResolvedArea[] = (props.areas ?? []).map((a) => ({
329
+ ...a,
330
+ x: toNumericX(a.x, isDate, z),
331
+ }));
332
+
333
+ return { isDate, series, areas };
211
334
  });
212
335
 
213
- const allAreas = computed<Area[]>(() => props.areas ?? []);
336
+ const xIsDate = computed(() => resolvedXAxis.value.isDate);
337
+ const allSeries = computed<ResolvedSeries[]>(() => resolvedXAxis.value.series);
338
+ const allAreas = computed<ResolvedArea[]>(() => resolvedXAxis.value.areas);
214
339
 
215
340
  const maxLen = computed(() => {
216
341
  let m = 0;
@@ -237,7 +362,7 @@ function seriesXAt(s: { x?: LineChartData }, i: number): number {
237
362
  }
238
363
 
239
364
  /** Data-space x value for the i-th point of an area. */
240
- function areaXAt(a: Area, i: number): number {
365
+ function areaXAt(a: ResolvedArea, i: number): number {
241
366
  return a.x ? Number(a.x[i]) : i;
242
367
  }
243
368
 
@@ -346,7 +471,7 @@ function toPoints(s: ResolvedSeries): { x: number; y: number }[] {
346
471
  return pts;
347
472
  }
348
473
 
349
- function toAreaPath(a: Area): string {
474
+ function toAreaPath(a: ResolvedArea): string {
350
475
  const len = Math.min(a.upper.length, a.lower.length);
351
476
  if (len === 0) return "";
352
477
  // Collect contiguous segments where both upper/lower and x are finite
@@ -435,6 +560,8 @@ interface PositionedSectionLabel {
435
560
  row: number;
436
561
  color: string;
437
562
  fillOpacity: number;
563
+ labelStyle: ReturnType<typeof resolveLabelStyle>;
564
+ descStyle: ReturnType<typeof resolveLabelStyle>;
438
565
  }
439
566
 
440
567
  const sectionLabels = computed<{
@@ -467,6 +594,16 @@ const sectionLabels = computed<{
467
594
  (sec.seriesIndex != null
468
595
  ? (allSeries.value[sec.seriesIndex]?.color ?? "currentColor")
469
596
  : "#999");
597
+ // Section labels default to the section's data-driven color/weight;
598
+ // user-supplied styles override piece-by-piece.
599
+ const labelStyle = resolveLabelStyle(
600
+ { color, fontWeight: 600, ...sec.inlineLabelStyle },
601
+ { fontSize: 11 },
602
+ );
603
+ const descStyle = resolveLabelStyle(sec.inlineDescriptionStyle, {
604
+ fontSize: 11,
605
+ fillOpacity: 0.6,
606
+ });
470
607
  items.push({
471
608
  cx,
472
609
  labelText,
@@ -475,6 +612,8 @@ const sectionLabels = computed<{
475
612
  row: 0,
476
613
  color,
477
614
  fillOpacity: sec.opacity ?? 0.15,
615
+ labelStyle,
616
+ descStyle,
478
617
  });
479
618
  }
480
619
 
@@ -581,43 +720,68 @@ const yTickItems = computed(() => {
581
720
  return values.map((v) => ({ value: fmt(v), y: snap(yPixel(v)) }));
582
721
  });
583
722
 
723
+ /**
724
+ * Format a single x-axis value into its display label. Used by both
725
+ * tick rendering and the tooltip x-label so the two stay in sync. On
726
+ * a date axis, `unit` (when supplied) drives the default formatter's
727
+ * preset choice (year-tick → "year", month-tick → "month-year", etc.);
728
+ * the tooltip path leaves it undefined and gets the ISO default.
729
+ */
730
+ function formatXValue(v: number, i: number, unit?: DateTickUnit): string {
731
+ const xf = props.xTickFormat;
732
+ if (xIsDate.value) {
733
+ if (typeof xf === "function") {
734
+ return (xf as (v: number, i: number) => string)(v, i);
735
+ }
736
+ return formatDate(v, xf as DateFormat | undefined, tz.value, unit);
737
+ }
738
+ const display = v + xDisplayOffset.value;
739
+ if (xf !== undefined) {
740
+ return typeof xf === "function"
741
+ ? (xf as (v: number, i: number) => string)(display, i)
742
+ : formatNumber(display, xf as NumberFormat);
743
+ }
744
+ if (
745
+ !hasExplicitX.value &&
746
+ props.xLabels &&
747
+ Number.isInteger(v) &&
748
+ v >= 0 &&
749
+ v < props.xLabels.length
750
+ ) {
751
+ return props.xLabels[v];
752
+ }
753
+ return formatTick(display);
754
+ }
755
+
584
756
  const xTickItems = computed(() => {
585
757
  const { min: xMin, max: xMax } = xExtent.value;
586
758
  if (xMin === xMax) return [];
587
- const offset = xDisplayOffset.value;
759
+ const isDate = xIsDate.value;
760
+ // `xMin` (display offset) is meaningless on a date axis — every x
761
+ // value already lives in absolute ms.
762
+ const offset = isDate ? 0 : xDisplayOffset.value;
588
763
  const len = maxLen.value;
589
-
590
- // Tick values are in data-space; display labels add `xDisplayOffset`.
591
- const fmt = (v: number, i: number) => {
592
- const display = v + offset;
593
- const xf = props.xTickFormat;
594
- if (xf !== undefined) {
595
- return typeof xf === "function"
596
- ? xf(display, i)
597
- : formatNumber(display, xf);
598
- }
599
- if (
600
- !hasExplicitX.value &&
601
- props.xLabels &&
602
- Number.isInteger(v) &&
603
- v >= 0 &&
604
- v < props.xLabels.length
605
- ) {
606
- return props.xLabels[v];
607
- }
608
- return formatTick(display);
609
- };
764
+ const targetTickCount = innerW.value / 80;
610
765
 
611
766
  let values: number[];
612
- if (
767
+ let unit: DateTickUnit | undefined;
768
+ if (isDate) {
769
+ const picked = pickDateTicks(xMin, xMax, targetTickCount, tz.value);
770
+ unit = picked.unit;
771
+ // Run through computeTickValues so the clip-to-range semantics
772
+ // match the numeric path. xMin is already 0, so `ticks` is used
773
+ // directly.
774
+ values = computeTickValues({ min: xMin, max: xMax, ticks: picked.values });
775
+ } else if (
613
776
  props.xTicks == null &&
614
777
  !hasExplicitX.value &&
615
778
  props.xLabels &&
616
779
  props.xLabels.length === len
617
780
  ) {
618
781
  // xLabels fallback: pick evenly-spaced index ticks so every label
619
- // bucket gets at most one tick.
620
- const targetTicks = Math.max(3, Math.floor(innerW.value / 80));
782
+ // bucket gets at most one tick. Date mode supersedes xLabels — if
783
+ // both are supplied, only the date axis is used.
784
+ const targetTicks = Math.max(3, Math.floor(targetTickCount));
621
785
  const step = Math.max(1, Math.round((len - 1) / targetTicks));
622
786
  values = [];
623
787
  for (let i = 0; i < len; i += step) values.push(i);
@@ -626,7 +790,7 @@ const xTickItems = computed(() => {
626
790
  min: xMin,
627
791
  max: xMax,
628
792
  ticks: props.xTicks,
629
- targetTickCount: innerW.value / 80,
793
+ targetTickCount,
630
794
  displayOffset: offset,
631
795
  });
632
796
  }
@@ -639,7 +803,7 @@ const xTickItems = computed(() => {
639
803
  let anchor: "start" | "middle" | "end" = "middle";
640
804
  if (x - leftEdge <= edgeSnapPx) anchor = "start";
641
805
  else if (rightEdge - x <= edgeSnapPx) anchor = "end";
642
- return { value: fmt(v, i), x, anchor };
806
+ return { value: formatXValue(v, i, unit), x, anchor };
643
807
  });
644
808
  });
645
809
 
@@ -706,18 +870,8 @@ const hoverSlotProps = computed(() => {
706
870
  const idx = hoverIndex.value;
707
871
  const targetX = hoverDataX.value;
708
872
  if (idx === null || targetX === null) return null;
709
- const offset = xDisplayOffset.value;
710
- const displayX = targetX + offset;
711
- let xLabel: string | undefined;
712
- const xf = props.xTickFormat;
713
- if (xf !== undefined) {
714
- xLabel =
715
- typeof xf === "function" ? xf(displayX, idx) : formatNumber(displayX, xf);
716
- } else if (!hasExplicitX.value) {
717
- xLabel = props.xLabels?.[idx];
718
- } else {
719
- xLabel = formatTick(displayX);
720
- }
873
+ // Single source of truth for label dispatch — same as the x-axis ticks.
874
+ const xLabel = formatXValue(targetX, idx);
721
875
  const series = allSeries.value;
722
876
  const values: ChartTooltipValue[] = [];
723
877
  for (let i = 0; i < series.length; i++) {
@@ -783,6 +937,7 @@ const {
783
937
  width: () => props.width,
784
938
  height: () => props.height,
785
939
  title: () => props.title,
940
+ titleStyle: () => props.titleStyle,
786
941
  xLabel: () => props.xLabel,
787
942
  yLabel: () => props.yLabel,
788
943
  debounce: () => props.debounce,
@@ -800,6 +955,46 @@ const {
800
955
  extraBelowHeight: () => sectionLabels.value.extraHeight,
801
956
  });
802
957
 
958
+ /** Resolved style for the x/y axis labels. */
959
+ const axisLabelResolved = computed(() =>
960
+ resolveLabelStyle(props.axisLabelStyle, { fontSize: AXIS_LABEL_FONT_SIZE }),
961
+ );
962
+ /** Resolved style for the axis tick labels. */
963
+ const tickLabelResolved = computed(() =>
964
+ resolveLabelStyle(props.tickLabelStyle, {
965
+ fontSize: TICK_LABEL_FONT_SIZE,
966
+ fillOpacity: TICK_LABEL_OPACITY,
967
+ }),
968
+ );
969
+ /** Resolved style for inline legend item labels. */
970
+ const legendResolved = computed(() =>
971
+ resolveLabelStyle(props.legendStyle, { fontSize: LEGEND_FONT_SIZE }),
972
+ );
973
+
974
+ /** Resolved title style with defaults applied. */
975
+ const titleResolved = computed(() => {
976
+ const s = props.titleStyle;
977
+ const align = s?.align ?? "left";
978
+ const b = bounds.value;
979
+ const x =
980
+ align === "left"
981
+ ? b.left
982
+ : align === "right"
983
+ ? b.right
984
+ : b.left + b.width / 2;
985
+ const anchor =
986
+ align === "left" ? "start" : align === "right" ? "end" : "middle";
987
+ return {
988
+ lines: (props.title ?? "").split("\n"),
989
+ fontSize: s?.fontSize ?? TITLE_FONT_SIZE,
990
+ lineHeight: s?.lineHeight ?? TITLE_LINE_HEIGHT,
991
+ fontWeight: s?.fontWeight ?? TITLE_FONT_WEIGHT,
992
+ color: s?.color ?? "currentColor",
993
+ x,
994
+ anchor,
995
+ };
996
+ });
997
+
803
998
  /**
804
999
  * Legend items joined with their wrapped pixel positions. `x` is the
805
1000
  * left edge of the indicator; `y` is the center of the row.
@@ -833,14 +1028,21 @@ const positionedLegendItems = computed(() => {
833
1028
  <!-- title -->
834
1029
  <text
835
1030
  v-if="title"
836
- :x="width / 2"
837
- :y="18"
838
- text-anchor="middle"
839
- font-size="14"
840
- font-weight="600"
841
- fill="currentColor"
1031
+ :x="titleResolved.x"
1032
+ :y="titleResolved.lineHeight"
1033
+ :text-anchor="titleResolved.anchor"
1034
+ :font-size="titleResolved.fontSize"
1035
+ :font-weight="titleResolved.fontWeight"
1036
+ :fill="titleResolved.color"
842
1037
  >
843
- {{ title }}
1038
+ <tspan
1039
+ v-for="(line, i) in titleResolved.lines"
1040
+ :key="i"
1041
+ :x="titleResolved.x"
1042
+ :dy="i === 0 ? 0 : titleResolved.lineHeight"
1043
+ >
1044
+ {{ line }}
1045
+ </tspan>
844
1046
  </text>
845
1047
  <!-- inline legend -->
846
1048
  <g v-if="positionedLegendItems.length > 0">
@@ -870,8 +1072,9 @@ const positionedLegendItems = computed(() => {
870
1072
  <text
871
1073
  :x="item.x + 18"
872
1074
  :y="item.y + 4"
873
- font-size="11"
874
- fill="currentColor"
1075
+ :font-size="legendResolved.fontSize"
1076
+ :fill="legendResolved.fill"
1077
+ :font-weight="legendResolved.fontWeight"
875
1078
  >
876
1079
  {{ item.label }}
877
1080
  </text>
@@ -929,9 +1132,10 @@ const positionedLegendItems = computed(() => {
929
1132
  :y="tick.y"
930
1133
  text-anchor="end"
931
1134
  dominant-baseline="middle"
932
- font-size="10"
933
- fill="currentColor"
934
- fill-opacity="0.6"
1135
+ :font-size="tickLabelResolved.fontSize"
1136
+ :fill="tickLabelResolved.fill"
1137
+ :font-weight="tickLabelResolved.fontWeight"
1138
+ :fill-opacity="tickLabelResolved.fillOpacity"
935
1139
  >
936
1140
  {{ tick.value }}
937
1141
  </text>
@@ -942,8 +1146,9 @@ const positionedLegendItems = computed(() => {
942
1146
  :y="0"
943
1147
  :transform="`translate(14, ${padding.top + innerH / 2}) rotate(-90)`"
944
1148
  text-anchor="middle"
945
- font-size="13"
946
- fill="currentColor"
1149
+ :font-size="axisLabelResolved.fontSize"
1150
+ :fill="axisLabelResolved.fill"
1151
+ :font-weight="axisLabelResolved.fontWeight"
947
1152
  >
948
1153
  {{ yLabel }}
949
1154
  </text>
@@ -955,9 +1160,10 @@ const positionedLegendItems = computed(() => {
955
1160
  :x="tick.x"
956
1161
  :y="padding.top + innerH + 16"
957
1162
  :text-anchor="tick.anchor"
958
- font-size="10"
959
- fill="currentColor"
960
- fill-opacity="0.6"
1163
+ :font-size="tickLabelResolved.fontSize"
1164
+ :fill="tickLabelResolved.fill"
1165
+ :font-weight="tickLabelResolved.fontWeight"
1166
+ :fill-opacity="tickLabelResolved.fillOpacity"
961
1167
  >
962
1168
  {{ tick.value }}
963
1169
  </text>
@@ -967,8 +1173,9 @@ const positionedLegendItems = computed(() => {
967
1173
  :x="padding.left + innerW / 2"
968
1174
  :y="height - 4"
969
1175
  text-anchor="middle"
970
- font-size="13"
971
- fill="currentColor"
1176
+ :font-size="axisLabelResolved.fontSize"
1177
+ :fill="axisLabelResolved.fill"
1178
+ :font-weight="axisLabelResolved.fontWeight"
972
1179
  >
973
1180
  {{ xLabel }}
974
1181
  </text>
@@ -1134,9 +1341,9 @@ const positionedLegendItems = computed(() => {
1134
1341
  v-if="item.labelText"
1135
1342
  :x="item.cx - item.textWidth / 2 + 8"
1136
1343
  :y="sectionLabelBaseY + item.row * SECTION_LABEL_ROW_HEIGHT + 8"
1137
- font-size="11"
1138
- font-weight="600"
1139
- :fill="item.color"
1344
+ :font-size="item.labelStyle.fontSize"
1345
+ :font-weight="item.labelStyle.fontWeight"
1346
+ :fill="item.labelStyle.fill"
1140
1347
  >
1141
1348
  {{ item.labelText }}
1142
1349
  </text>
@@ -1144,9 +1351,10 @@ const positionedLegendItems = computed(() => {
1144
1351
  v-if="item.descText"
1145
1352
  :x="item.cx - item.textWidth / 2 + 8"
1146
1353
  :y="sectionLabelBaseY + item.row * SECTION_LABEL_ROW_HEIGHT + 22"
1147
- font-size="11"
1148
- fill="currentColor"
1149
- fill-opacity="0.6"
1354
+ :font-size="item.descStyle.fontSize"
1355
+ :font-weight="item.descStyle.fontWeight"
1356
+ :fill="item.descStyle.fill"
1357
+ :fill-opacity="item.descStyle.fillOpacity"
1150
1358
  >
1151
1359
  {{ item.descText }}
1152
1360
  </text>