@cfasim-ui/docs 0.4.10 → 0.4.12

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.
@@ -160,6 +160,54 @@ Use `value-tick-format` to format the value-axis labels. `tooltip-value-format`
160
160
  </template>
161
161
  </ComponentDemo>
162
162
 
163
+ ### Date categories
164
+
165
+ When every entry of `categories` parses as a date (`YYYY-MM-DD` string
166
+ or `Date` instance), labels switch to date-aware formatting and the
167
+ chart thins them to clean date boundaries (week / month / year
168
+ depending on range). Bar positions stay ordinal — every category gets
169
+ a bar — only the labels are thinned.
170
+
171
+ <ComponentDemo>
172
+ <BarChart
173
+ :data="[12, 18, 24, 35, 41, 38, 29, 21, 14, 9, 7, 4]"
174
+ :categories="['2026-01-05','2026-01-12','2026-01-19','2026-01-26','2026-02-02','2026-02-09','2026-02-16','2026-02-23','2026-03-02','2026-03-09','2026-03-16','2026-03-23']"
175
+ :height="220"
176
+ y-label="% ED visits"
177
+ />
178
+
179
+ <template #code>
180
+
181
+ ```vue
182
+ <BarChart
183
+ :data="[12, 18, 24, 35, 41, 38, 29, 21, 14, 9, 7, 4]"
184
+ :categories="[
185
+ '2026-01-05',
186
+ '2026-01-12',
187
+ '2026-01-19',
188
+ '2026-01-26',
189
+ '2026-02-02',
190
+ '2026-02-09',
191
+ '2026-02-16',
192
+ '2026-02-23',
193
+ '2026-03-02',
194
+ '2026-03-09',
195
+ '2026-03-16',
196
+ '2026-03-23',
197
+ ]"
198
+ :height="220"
199
+ y-label="% ED visits"
200
+ />
201
+ ```
202
+
203
+ </template>
204
+ </ComponentDemo>
205
+
206
+ Use `date-format` to override the auto-picked preset. Same values as
207
+ LineChart's `x-tick-format` (e.g. `"iso"`, `"month-year"`, an
208
+ `Intl.DateTimeFormatOptions` object, or a `(ms, unit?) => string`
209
+ function).
210
+
163
211
  ### Logarithmic value axis
164
212
 
165
213
  Set `value-scale-type="log"` to switch the value axis to base-10 log
@@ -313,6 +361,10 @@ anchor. `lineColor`, `lineWidth`, and `lineDash` style the line.
313
361
  | `width` | `number` | No | — |
314
362
  | `height` | `number` | No | — |
315
363
  | `title` | `string` | No | — |
364
+ | `titleStyle` | `TitleStyle` | No | — |
365
+ | `axisLabelStyle` | `LabelStyle` | No | — |
366
+ | `tickLabelStyle` | `LabelStyle` | No | — |
367
+ | `legendStyle` | `LabelStyle` | No | — |
316
368
  | `xLabel` | `string` | No | — |
317
369
  | `yLabel` | `string` | No | — |
318
370
  | `debounce` | `number` | No | — |
@@ -329,7 +381,7 @@ anchor. `lineColor`, `lineWidth`, and `lineDash` style the line.
329
381
  | `data` | `BarChartData` | No | — |
330
382
  | `y` | `BarChartData` | No | — |
331
383
  | `series` | `BarSeries[]` | No | — |
332
- | `categories` | `readonly string[]` | No | — |
384
+ | `categories` | `readonly (string \| Date)[]` | No | — |
333
385
  | `orientation` | `"vertical" \| "horizontal"` | No | `"vertical"` |
334
386
  | `layout` | `"grouped" \| "stacked"` | No | `"grouped"` |
335
387
  | `valueMin` | `number` | No | `0` |
@@ -337,6 +389,7 @@ anchor. `lineColor`, `lineWidth`, and `lineDash` style the line.
337
389
  | `valueTicks` | `number \| number[]` | No | — |
338
390
  | `valueTickFormat` | `NumberFormat` | No | — |
339
391
  | `categoryFormat` | `(label: string, index: number) =&gt; string` | No | — |
392
+ | `dateFormat` | `DateFormat` | No | — |
340
393
  | `barPadding` | `number` | No | `0.2` |
341
394
  | `groupGap` | `number` | No | `1` |
342
395
  | `valueGrid` | `boolean` | No | `true` |
@@ -13,10 +13,25 @@ import {
13
13
  useChartFoundation,
14
14
  makeTooltipValueFormatter,
15
15
  ChartAnnotations,
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,
16
29
  type ChartData,
30
+ type DateFormat,
17
31
  type ChartCommonProps,
18
32
  type ChartHoverPayload,
19
33
  type ChartTooltipBaseProps,
34
+ type ChartTooltipValue,
20
35
  } from "../_shared/index.js";
21
36
 
22
37
  export type BarChartData = ChartData;
@@ -29,6 +44,16 @@ export interface BarSeries {
29
44
  opacity?: number;
30
45
  /** Label shown in the inline legend. */
31
46
  legend?: string;
47
+ /**
48
+ * Whether this series appears in the inline legend. Defaults to true.
49
+ * Has no effect when `legend` is unset (no legend entry to begin with).
50
+ */
51
+ showInLegend?: boolean;
52
+ /**
53
+ * Whether this series contributes a value to the tooltip. Defaults to
54
+ * true. The bars are still drawn.
55
+ */
56
+ showInTooltip?: boolean;
32
57
  }
33
58
 
34
59
  interface BarChartProps extends ChartCommonProps {
@@ -41,8 +66,11 @@ interface BarChartProps extends ChartCommonProps {
41
66
  /**
42
67
  * Category labels for the categorical axis. Length should match the
43
68
  * longest series. When omitted, indices (0, 1, 2, ...) are used.
69
+ * Accepts date strings or `Date` objects: when every category parses
70
+ * as a date, label formatting switches to date mode (bar positions
71
+ * stay ordinal — they are not time-proportional).
44
72
  */
45
- categories?: readonly string[];
73
+ categories?: readonly (string | Date)[];
46
74
  /** "vertical" (default, aka column) draws upright bars; "horizontal" draws sideways. */
47
75
  orientation?: "vertical" | "horizontal";
48
76
  /** "grouped" (default) places series side-by-side; "stacked" stacks them. */
@@ -71,6 +99,16 @@ interface BarChartProps extends ChartCommonProps {
71
99
  valueTickFormat?: NumberFormat;
72
100
  /** Formatter for category-axis labels. Receives the resolved category string. */
73
101
  categoryFormat?: (label: string, index: number) => string;
102
+ /**
103
+ * Date-tick formatter for date-mode category labels (auto-detected
104
+ * when every entry of `categories` parses as a date). Ignored unless
105
+ * the axis is in date mode. Accepts a `DateFormat` — preset name,
106
+ * `Intl.DateTimeFormatOptions`, or `(ms, unit?) => string`. When
107
+ * omitted, the formatter is picked from the chosen tick unit (e.g.
108
+ * monthly ticks → `"month-year"`). Has no effect when
109
+ * `categoryFormat` is also set.
110
+ */
111
+ dateFormat?: DateFormat;
74
112
  /**
75
113
  * Fraction of each category slot reserved as gap between groups (0..1).
76
114
  * Default 0.2 — i.e. bars/groups fill 80% of their slot.
@@ -104,8 +142,6 @@ defineSlots<{
104
142
  tooltip?(props: ChartTooltipBaseProps & { category: string }): unknown;
105
143
  }>();
106
144
 
107
- const hasInlineLegend = computed(() => allSeries.value.some((s) => s.legend));
108
-
109
145
  const EMPTY_DATA: readonly number[] = [];
110
146
 
111
147
  type ResolvedSeries = {
@@ -113,6 +149,8 @@ type ResolvedSeries = {
113
149
  color?: string;
114
150
  opacity?: number;
115
151
  legend?: string;
152
+ showInLegend?: boolean;
153
+ showInTooltip?: boolean;
116
154
  };
117
155
 
118
156
  function resolveSeries(s: BarSeries): ResolvedSeries {
@@ -121,6 +159,8 @@ function resolveSeries(s: BarSeries): ResolvedSeries {
121
159
  color: s.color,
122
160
  opacity: s.opacity,
123
161
  legend: s.legend,
162
+ showInLegend: s.showInLegend,
163
+ showInTooltip: s.showInTooltip,
124
164
  };
125
165
  }
126
166
 
@@ -144,11 +184,34 @@ const categoryLabels = computed<string[]>(() => {
144
184
  const n = categoryCount.value;
145
185
  const labels = new Array<string>(n);
146
186
  for (let i = 0; i < n; i++) {
147
- labels[i] = props.categories?.[i] ?? String(i);
187
+ const c = props.categories?.[i];
188
+ if (c instanceof Date) {
189
+ // Stable string form for CSV / tooltip. Date axis formatting
190
+ // (visible labels) goes through `categoryTickItems` separately.
191
+ labels[i] = c.toISOString().slice(0, 10);
192
+ } else {
193
+ labels[i] = c ?? String(i);
194
+ }
148
195
  }
149
196
  return labels;
150
197
  });
151
198
 
199
+ /**
200
+ * Parallel array of epoch-ms timestamps when every category parses as
201
+ * a date, otherwise `null`. Drives both date-aware tick thinning and
202
+ * the default tick label formatter.
203
+ */
204
+ const categoryDatesMs = computed<number[] | null>(() => {
205
+ const cats = props.categories;
206
+ if (!cats || cats.length === 0) return null;
207
+ if (!isAllDates(cats, "utc")) return null;
208
+ const out = new Array<number>(cats.length);
209
+ for (let i = 0; i < cats.length; i++) {
210
+ out[i] = parseDate(cats[i], "utc") ?? NaN;
211
+ }
212
+ return out;
213
+ });
214
+
152
215
  const isVertical = computed(() => props.orientation === "vertical");
153
216
 
154
217
  /** Extent of the value axis (across all series, accounting for stacking). */
@@ -439,8 +502,49 @@ interface CategoryTickItem {
439
502
  }
440
503
 
441
504
  const categoryTickItems = computed<CategoryTickItem[]>(() => {
442
- const out: CategoryTickItem[] = [];
443
505
  const n = categoryCount.value;
506
+ const dateMs = categoryDatesMs.value;
507
+
508
+ // Date mode: thin labels to the closest category index for each
509
+ // tick boundary picked by `pickDateTicks`. Bar positions stay
510
+ // ordinal — only the labels become date-aware.
511
+ if (dateMs && dateMs.length > 0) {
512
+ let min = Infinity;
513
+ let max = -Infinity;
514
+ for (const m of dateMs) {
515
+ if (!Number.isFinite(m)) continue;
516
+ if (m < min) min = m;
517
+ if (m > max) max = m;
518
+ }
519
+ if (!Number.isFinite(min) || min === max) return [];
520
+ const span = isVertical.value ? innerW.value : innerH.value;
521
+ const target = Math.max(2, Math.floor(span / 80));
522
+ const picked = pickDateTicks(min, max, target, "utc");
523
+ const unit = picked.unit;
524
+ const items: CategoryTickItem[] = [];
525
+ const seen = new Set<number>();
526
+ for (const tickMs of picked.values) {
527
+ let nearest = -1;
528
+ let best = Infinity;
529
+ for (let i = 0; i < dateMs.length; i++) {
530
+ const d = Math.abs(dateMs[i] - tickMs);
531
+ if (d < best) {
532
+ best = d;
533
+ nearest = i;
534
+ }
535
+ }
536
+ if (nearest < 0 || seen.has(nearest)) continue;
537
+ seen.add(nearest);
538
+ const center = slotStart(nearest) + slotSize.value / 2;
539
+ const label = props.categoryFormat
540
+ ? props.categoryFormat(categoryLabels.value[nearest], nearest)
541
+ : formatDate(dateMs[nearest], props.dateFormat, "utc", unit);
542
+ items.push({ label, pos: center, anchor: "middle" });
543
+ }
544
+ return items;
545
+ }
546
+
547
+ const out: CategoryTickItem[] = [];
444
548
  const fmt = (label: string, i: number) =>
445
549
  props.categoryFormat ? props.categoryFormat(label, i) : label;
446
550
  for (let i = 0; i < n; i++) {
@@ -462,12 +566,16 @@ interface InlineLegendItem {
462
566
  const inlineLegendItems = computed<InlineLegendItem[]>(() => {
463
567
  const items: InlineLegendItem[] = [];
464
568
  allSeries.value.forEach((s, i) => {
465
- if (!s.legend) return;
569
+ if (!s.legend || s.showInLegend === false) return;
466
570
  items.push({ label: s.legend, color: s.color ?? defaultColor(i) });
467
571
  });
468
572
  return items;
469
573
  });
470
574
 
575
+ const inlineLegendLabels = computed(() =>
576
+ inlineLegendItems.value.map((item) => item.label),
577
+ );
578
+
471
579
  function toCsv(): string {
472
580
  if (typeof props.csv === "function") return props.csv();
473
581
  if (typeof props.csv === "string") return props.csv;
@@ -514,6 +622,7 @@ const {
514
622
  height,
515
623
  padding,
516
624
  legendY,
625
+ inlineLegendLayout,
517
626
  innerW,
518
627
  innerH,
519
628
  bounds,
@@ -525,10 +634,12 @@ const {
525
634
  downloadLinkText,
526
635
  csvHref,
527
636
  menuFilename,
637
+ isFullscreen,
528
638
  } = useChartFoundation({
529
639
  width: () => props.width,
530
640
  height: () => props.height,
531
641
  title: () => props.title,
642
+ titleStyle: () => props.titleStyle,
532
643
  xLabel: () => props.xLabel,
533
644
  yLabel: () => props.yLabel,
534
645
  debounce: () => props.debounce,
@@ -538,30 +649,84 @@ const {
538
649
  filename: () => props.filename,
539
650
  downloadLink: () => props.downloadLink,
540
651
  chartPadding: () => props.chartPadding,
541
- hasInlineLegend: () => hasInlineLegend.value,
652
+ inlineLegendLabels: () => inlineLegendLabels.value,
542
653
  hasTooltipSlot: () => hasTooltipSlot.value,
543
654
  getCsv: toCsv,
544
655
  pointerToIndex,
545
656
  onHover: (payload) => emit("hover", payload),
546
657
  });
547
658
 
659
+ /** Resolved style for the x/y axis labels. */
660
+ const axisLabelResolved = computed(() =>
661
+ resolveLabelStyle(props.axisLabelStyle, { fontSize: AXIS_LABEL_FONT_SIZE }),
662
+ );
663
+ /** Resolved style for the axis tick labels. */
664
+ const tickLabelResolved = computed(() =>
665
+ resolveLabelStyle(props.tickLabelStyle, {
666
+ fontSize: TICK_LABEL_FONT_SIZE,
667
+ fillOpacity: TICK_LABEL_OPACITY,
668
+ }),
669
+ );
670
+ /** Resolved style for inline legend item labels. */
671
+ const legendResolved = computed(() =>
672
+ resolveLabelStyle(props.legendStyle, { fontSize: LEGEND_FONT_SIZE }),
673
+ );
674
+
675
+ /** Resolved title style with defaults applied. */
676
+ const titleResolved = computed(() => {
677
+ const s = props.titleStyle;
678
+ const align = s?.align ?? "left";
679
+ const b = bounds.value;
680
+ const x =
681
+ align === "left"
682
+ ? b.left
683
+ : align === "right"
684
+ ? b.right
685
+ : b.left + b.width / 2;
686
+ const anchor =
687
+ align === "left" ? "start" : align === "right" ? "end" : "middle";
688
+ return {
689
+ lines: (props.title ?? "").split("\n"),
690
+ fontSize: s?.fontSize ?? TITLE_FONT_SIZE,
691
+ lineHeight: s?.lineHeight ?? TITLE_LINE_HEIGHT,
692
+ fontWeight: s?.fontWeight ?? TITLE_FONT_WEIGHT,
693
+ color: s?.color ?? "currentColor",
694
+ x,
695
+ anchor,
696
+ };
697
+ });
698
+
548
699
  const hoveredCategoryLabel = computed(() => {
549
700
  const i = hoverIndex.value;
550
701
  if (i === null) return undefined;
702
+ // In date mode, format the tooltip label so it honors `dateFormat`
703
+ // (otherwise the user would see the raw ISO string while the ticks
704
+ // render with a nicer preset).
705
+ const dateMs = categoryDatesMs.value;
706
+ if (dateMs && Number.isFinite(dateMs[i])) {
707
+ return formatDate(dateMs[i], props.dateFormat, "utc");
708
+ }
551
709
  return categoryLabels.value[i];
552
710
  });
553
711
 
554
712
  const hoverSlotProps = computed(() => {
555
713
  const idx = hoverIndex.value;
556
714
  if (idx === null) return null;
557
- return {
558
- index: idx,
559
- category: categoryLabels.value[idx] ?? String(idx),
560
- values: allSeries.value.map((s, i) => ({
715
+ const series = allSeries.value;
716
+ const values: ChartTooltipValue[] = [];
717
+ for (let i = 0; i < series.length; i++) {
718
+ const s = series[i];
719
+ if (s.showInTooltip === false) continue;
720
+ values.push({
561
721
  value: Number(s.data[idx] ?? NaN),
562
722
  color: s.color ?? defaultColor(i),
563
723
  seriesIndex: i,
564
- })),
724
+ });
725
+ }
726
+ return {
727
+ index: idx,
728
+ category: categoryLabels.value[idx] ?? String(idx),
729
+ values,
565
730
  data: props.tooltipData?.[idx] ?? null,
566
731
  };
567
732
  });
@@ -586,39 +751,72 @@ const hoverBand = computed(() => {
586
751
  h: slotSize.value,
587
752
  };
588
753
  });
754
+
755
+ /**
756
+ * Legend items joined with their wrapped pixel positions. `x` is the
757
+ * left edge of the indicator; `y` is the center of the row.
758
+ */
759
+ const positionedLegendItems = computed(() => {
760
+ const positions = inlineLegendLayout.value.positions;
761
+ const pad = padding.value.left;
762
+ const baseY = legendY.value;
763
+ return inlineLegendItems.value.map((item, i) => {
764
+ const pos = positions[i];
765
+ return {
766
+ ...item,
767
+ x: pad + pos.x,
768
+ y: baseY + pos.row * INLINE_LEGEND_ROW_HEIGHT,
769
+ };
770
+ });
771
+ });
589
772
  </script>
590
773
 
591
774
  <template>
592
- <div ref="containerRef" class="bar-chart-wrapper">
775
+ <div
776
+ ref="containerRef"
777
+ class="bar-chart-wrapper"
778
+ :class="{ 'is-fullscreen': isFullscreen }"
779
+ >
593
780
  <ChartMenu v-if="menu" :items="menuItems" />
781
+ <div class="chart-sr-only" aria-live="polite">
782
+ {{ isFullscreen ? "Chart expanded to fill window" : "" }}
783
+ </div>
594
784
  <svg ref="svgRef" :width="width" :height="height">
595
785
  <!-- title -->
596
786
  <text
597
787
  v-if="title"
598
- :x="width / 2"
599
- :y="18"
600
- text-anchor="middle"
601
- font-size="14"
602
- font-weight="600"
603
- fill="currentColor"
788
+ :x="titleResolved.x"
789
+ :y="titleResolved.lineHeight"
790
+ :text-anchor="titleResolved.anchor"
791
+ :font-size="titleResolved.fontSize"
792
+ :font-weight="titleResolved.fontWeight"
793
+ :fill="titleResolved.color"
604
794
  >
605
- {{ title }}
795
+ <tspan
796
+ v-for="(line, i) in titleResolved.lines"
797
+ :key="i"
798
+ :x="titleResolved.x"
799
+ :dy="i === 0 ? 0 : titleResolved.lineHeight"
800
+ >
801
+ {{ line }}
802
+ </tspan>
606
803
  </text>
607
804
  <!-- inline legend -->
608
- <g v-if="inlineLegendItems.length > 0">
609
- <template v-for="(item, i) in inlineLegendItems" :key="'ileg' + i">
805
+ <g v-if="positionedLegendItems.length > 0">
806
+ <template v-for="(item, i) in positionedLegendItems" :key="'ileg' + i">
610
807
  <rect
611
- :x="padding.left + i * 120"
612
- :y="legendY - 5"
808
+ :x="item.x"
809
+ :y="item.y - 5"
613
810
  width="12"
614
811
  height="10"
615
812
  :fill="item.color"
616
813
  />
617
814
  <text
618
- :x="padding.left + i * 120 + 18"
619
- :y="legendY + 4"
620
- font-size="11"
621
- fill="currentColor"
815
+ :x="item.x + 18"
816
+ :y="item.y + 4"
817
+ :font-size="legendResolved.fontSize"
818
+ :fill="legendResolved.fill"
819
+ :font-weight="legendResolved.fontWeight"
622
820
  >
623
821
  {{ item.label }}
624
822
  </text>
@@ -675,9 +873,10 @@ const hoverBand = computed(() => {
675
873
  :y="tick.pos"
676
874
  text-anchor="end"
677
875
  dominant-baseline="middle"
678
- font-size="10"
679
- fill="currentColor"
680
- fill-opacity="0.6"
876
+ :font-size="tickLabelResolved.fontSize"
877
+ :fill="tickLabelResolved.fill"
878
+ :font-weight="tickLabelResolved.fontWeight"
879
+ :fill-opacity="tickLabelResolved.fillOpacity"
681
880
  >
682
881
  {{ tick.value }}
683
882
  </text>
@@ -690,9 +889,10 @@ const hoverBand = computed(() => {
690
889
  :x="tick.pos"
691
890
  :y="padding.top + innerH + 16"
692
891
  text-anchor="middle"
693
- font-size="10"
694
- fill="currentColor"
695
- fill-opacity="0.6"
892
+ :font-size="tickLabelResolved.fontSize"
893
+ :fill="tickLabelResolved.fill"
894
+ :font-weight="tickLabelResolved.fontWeight"
895
+ :fill-opacity="tickLabelResolved.fillOpacity"
696
896
  >
697
897
  {{ tick.value }}
698
898
  </text>
@@ -704,8 +904,9 @@ const hoverBand = computed(() => {
704
904
  :y="0"
705
905
  :transform="`translate(14, ${padding.top + innerH / 2}) rotate(-90)`"
706
906
  text-anchor="middle"
707
- font-size="13"
708
- fill="currentColor"
907
+ :font-size="axisLabelResolved.fontSize"
908
+ :fill="axisLabelResolved.fill"
909
+ :font-weight="axisLabelResolved.fontWeight"
709
910
  >
710
911
  {{ yLabel }}
711
912
  </text>
@@ -718,9 +919,10 @@ const hoverBand = computed(() => {
718
919
  :x="tick.pos"
719
920
  :y="padding.top + innerH + 16"
720
921
  :text-anchor="tick.anchor"
721
- font-size="10"
722
- fill="currentColor"
723
- fill-opacity="0.6"
922
+ :font-size="tickLabelResolved.fontSize"
923
+ :fill="tickLabelResolved.fill"
924
+ :font-weight="tickLabelResolved.fontWeight"
925
+ :fill-opacity="tickLabelResolved.fillOpacity"
724
926
  >
725
927
  {{ tick.label }}
726
928
  </text>
@@ -734,9 +936,10 @@ const hoverBand = computed(() => {
734
936
  :y="tick.pos"
735
937
  text-anchor="end"
736
938
  dominant-baseline="middle"
737
- font-size="10"
738
- fill="currentColor"
739
- fill-opacity="0.6"
939
+ :font-size="tickLabelResolved.fontSize"
940
+ :fill="tickLabelResolved.fill"
941
+ :font-weight="tickLabelResolved.fontWeight"
942
+ :fill-opacity="tickLabelResolved.fillOpacity"
740
943
  >
741
944
  {{ tick.label }}
742
945
  </text>
@@ -747,8 +950,9 @@ const hoverBand = computed(() => {
747
950
  :x="padding.left + innerW / 2"
748
951
  :y="height - 4"
749
952
  text-anchor="middle"
750
- font-size="13"
751
- fill="currentColor"
953
+ :font-size="axisLabelResolved.fontSize"
954
+ :fill="axisLabelResolved.fill"
955
+ :font-weight="axisLabelResolved.fontWeight"
752
956
  >
753
957
  {{ xLabel }}
754
958
  </text>
@@ -837,10 +1041,6 @@ const hoverBand = computed(() => {
837
1041
  width: 100%;
838
1042
  }
839
1043
 
840
- .bar-chart-wrapper:hover :deep(.chart-menu-button) {
841
- opacity: 1;
842
- }
843
-
844
1044
  .bar-chart-tooltip-label {
845
1045
  font-weight: 600;
846
1046
  margin-bottom: 0.25em;
@@ -10,6 +10,8 @@ import {
10
10
  export interface ChartMenuItem {
11
11
  label: string;
12
12
  action: () => void;
13
+ /** Sets aria-pressed on the menu item — use for toggle-style items. */
14
+ ariaPressed?: boolean;
13
15
  }
14
16
 
15
17
  const props = withDefaults(
@@ -72,6 +74,7 @@ const useDropdown = () => props.forceDropdown || props.items.length > 1;
72
74
  v-for="item in items"
73
75
  :key="item.label"
74
76
  class="chart-menu-item"
77
+ :aria-pressed="item.ariaPressed"
75
78
  @select="item.action"
76
79
  >
77
80
  {{ item.label }}
@@ -1,7 +1,7 @@
1
1
  <script setup>
2
2
  import { computed, ref } from "vue";
3
3
  import countiesTopoForPerf from "us-atlas/counties-10m.json";
4
- import { fipsToHsa } from "@cfasim-ui/charts";
4
+ import { fipsToHsa } from "@cfasim-ui/charts/hsa-mapping";
5
5
 
6
6
  // Build one row per county (~3,143) with a deterministic-ish value so the
7
7
  // perf example can render every region with a custom tooltip.
@@ -478,6 +478,10 @@ feature (e.g. an HSA from a county via `fipsToHsa`). Pass both as a
478
478
  parent HSA renders on top as a dashed overlay (`stroke: "#666"` here —
479
479
  default is white).
480
480
 
481
+ `fipsToHsa` and `hsaNames` ship from the `@cfasim-ui/charts/hsa-mapping`
482
+ subpath so consumers that don't need HSA lookups don't pay for the
483
+ ~25 KB of mapping data.
484
+
481
485
  <ComponentDemo>
482
486
  <ChoroplethMap
483
487
  :topology="countiesTopo"
@@ -512,7 +516,7 @@ default is white).
512
516
  ```vue
513
517
  <script setup>
514
518
  import { ref, computed } from "vue";
515
- import { fipsToHsa } from "@cfasim-ui/charts";
519
+ import { fipsToHsa } from "@cfasim-ui/charts/hsa-mapping";
516
520
 
517
521
  const focusedCounty = ref(null);
518
522
  const focus = computed(() => {
@@ -691,6 +695,8 @@ set `tooltip-trigger`.
691
695
  | `height` | `number` | No | — |
692
696
  | `colorScale` | `ChoroplethColorScale \| ThresholdStop[] \| CategoricalStop[]` | No | — |
693
697
  | `title` | `string` | No | — |
698
+ | `titleStyle` | `TitleStyle` | No | — |
699
+ | `legendStyle` | `LabelStyle` | No | — |
694
700
  | `noDataColor` | `string` | No | `"#ddd"` |
695
701
  | `strokeColor` | `string` | No | `"#fff"` |
696
702
  | `strokeWidth` | `number` | No | `0.5` |