@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.
@@ -17,10 +17,17 @@ import "d3-transition";
17
17
  import { feature, mesh, merge } from "topojson-client";
18
18
  import type { Topology, GeometryCollection } from "topojson-specification";
19
19
  import { formatNumber, type NumberFormat } from "@cfasim-ui/shared";
20
- import { fipsToHsa, hsaNames } from "./hsaMapping.js";
21
20
  import ChartMenu from "../ChartMenu/ChartMenu.vue";
22
21
  import type { ChartMenuItem } from "../ChartMenu/ChartMenu.vue";
23
22
  import { saveSvg, savePng } from "../ChartMenu/download.js";
23
+ import {
24
+ useChartFullscreen,
25
+ TITLE_FONT_SIZE,
26
+ TITLE_LINE_HEIGHT,
27
+ TITLE_FONT_WEIGHT,
28
+ type TitleStyle,
29
+ type LabelStyle,
30
+ } from "../_shared/index.js";
24
31
  import { placeTooltip } from "../tooltip-position.js";
25
32
  import ChoroplethTooltip from "./ChoroplethTooltip.vue";
26
33
 
@@ -103,7 +110,15 @@ const props = withDefaults(
103
110
  width?: number;
104
111
  height?: number;
105
112
  colorScale?: ChoroplethColorScale | ThresholdStop[] | CategoricalStop[];
113
+ /**
114
+ * Map title. `\n` in the string creates additional lines, each
115
+ * adding `titleStyle.lineHeight` (default 18px) of vertical space.
116
+ */
106
117
  title?: string;
118
+ /** Styling for the map title. See `TitleStyle`. */
119
+ titleStyle?: TitleStyle;
120
+ /** Styling for the legend (title, swatch labels, and continuous-scale ticks). */
121
+ legendStyle?: LabelStyle;
107
122
  noDataColor?: string;
108
123
  strokeColor?: string;
109
124
  strokeWidth?: number;
@@ -230,6 +245,33 @@ const slots = useSlots();
230
245
  const hasInteractiveTooltip = computed(
231
246
  () => !!props.tooltipTrigger || !!props.tooltipFormat || !!slots.tooltip,
232
247
  );
248
+
249
+ /**
250
+ * Inline style for the legend container. Font properties cascade to
251
+ * children (legend title, swatch labels, continuous-scale ticks).
252
+ */
253
+ const legendInlineStyle = computed(() => {
254
+ const s = props.legendStyle;
255
+ const style: Record<string, string> = {};
256
+ if (s?.fontSize != null) style["font-size"] = `${s.fontSize}px`;
257
+ if (s?.fontWeight != null) style["font-weight"] = String(s.fontWeight);
258
+ if (s?.color != null) style.color = s.color;
259
+ return style;
260
+ });
261
+
262
+ /** Inline style for the title element, applying TitleStyle overrides. */
263
+ const titleInlineStyle = computed(() => {
264
+ const s = props.titleStyle;
265
+ const style: Record<string, string> = {
266
+ "font-size": `${s?.fontSize ?? TITLE_FONT_SIZE}px`,
267
+ "line-height": `${s?.lineHeight ?? TITLE_LINE_HEIGHT}px`,
268
+ "font-weight": String(s?.fontWeight ?? TITLE_FONT_WEIGHT),
269
+ "text-align": s?.align ?? "left",
270
+ width: "100%",
271
+ };
272
+ if (s?.color) style.color = s.color;
273
+ return style;
274
+ });
233
275
  // Imperative path bookkeeping. Plain Maps rather than refs — Vue never reads
234
276
  // these from a render scope, so mutating them does not trigger re-renders.
235
277
  const pathsByFeatureId = new Map<string, SVGPathElement>();
@@ -611,7 +653,39 @@ type CountiesTopo = Topology<{
611
653
  states: NamedGeometry;
612
654
  }>;
613
655
 
656
+ // HSA mapping is loaded lazily — it's ~25KB gzipped and only needed when
657
+ // geoType or dataGeoType is "hsas". Keeps the main bundle small for users
658
+ // who only need states/counties maps.
659
+ type HsaModule = typeof import("./hsaMapping.js");
660
+ const hsaModule = ref<HsaModule | null>(null);
661
+ let hsaModulePromise: Promise<HsaModule> | null = null;
662
+ function loadHsaModule() {
663
+ if (!hsaModulePromise) {
664
+ hsaModulePromise = import("./hsaMapping.js").then((m) => {
665
+ hsaModule.value = m;
666
+ return m;
667
+ });
668
+ }
669
+ return hsaModulePromise;
670
+ }
671
+ watch(
672
+ () => {
673
+ if (props.geoType === "hsas" || props.dataGeoType === "hsas") return true;
674
+ const focus = props.focus;
675
+ if (!focus) return false;
676
+ const items = Array.isArray(focus) ? focus : [focus];
677
+ return items.some((f) => typeof f !== "string" && f.geoType === "hsas");
678
+ },
679
+ (needsHsa) => {
680
+ if (needsHsa) loadHsaModule();
681
+ },
682
+ { immediate: true },
683
+ );
684
+
614
685
  const hsaFeaturesGeo = computed(() => {
686
+ const mod = hsaModule.value;
687
+ if (!mod) return { type: "FeatureCollection" as const, features: [] };
688
+ const { fipsToHsa, hsaNames } = mod;
615
689
  const topo = toRaw(props.topology) as unknown as CountiesTopo;
616
690
  const countyGeometries = topo.objects.counties.geometries;
617
691
  const groups = new Map<string, typeof countyGeometries>();
@@ -695,7 +769,7 @@ function baseToDataId(baseId: string): string | undefined {
695
769
  const dataGt = props.dataGeoType;
696
770
  if (!dataGt || dataGt === props.geoType) return baseId;
697
771
  if (props.geoType === "counties" && dataGt === "hsas") {
698
- return fipsToHsa[baseId];
772
+ return hsaModule.value?.fipsToHsa[baseId];
699
773
  }
700
774
  if (props.geoType === "counties" && dataGt === "states") {
701
775
  return baseId.slice(0, 2);
@@ -1298,9 +1372,12 @@ const gradientCss = computed(() => {
1298
1372
  return `linear-gradient(to right, ${stops})`;
1299
1373
  });
1300
1374
 
1375
+ const fullscreen = useChartFullscreen();
1376
+
1301
1377
  const menuItems = computed<ChartMenuItem[]>(() => {
1302
1378
  const fname = menuFilename();
1303
1379
  return [
1380
+ fullscreen.menuItem.value,
1304
1381
  {
1305
1382
  label: "Save as SVG",
1306
1383
  action: () => {
@@ -1351,25 +1428,42 @@ watch(
1351
1428
  // `flush: "post"` so any pending path rebuild from the watcher above has
1352
1429
  // already run; we still use the GeoJSON pathGenerator directly so the SVG
1353
1430
  // path tree isn't actually required, but keeping the order avoids stacking
1354
- // two zoom transforms in the same tick.
1431
+ // two zoom transforms in the same tick. `hsaModule` is included so a
1432
+ // cross-geoType focus on an hsa item re-applies once the lazy module
1433
+ // resolves (the base pathGenerator doesn't depend on hsa data).
1355
1434
  watch(
1356
- () => [normalizedFocus.value, pathGenerator.value],
1435
+ () => [normalizedFocus.value, pathGenerator.value, hsaModule.value],
1357
1436
  () => applyFocus(),
1358
1437
  { flush: "post" },
1359
1438
  );
1360
1439
  </script>
1361
1440
 
1362
1441
  <template>
1363
- <div ref="containerRef" :class="['choropleth-wrapper', { pannable: pan }]">
1442
+ <div
1443
+ ref="containerRef"
1444
+ :class="[
1445
+ 'choropleth-wrapper',
1446
+ { pannable: pan, 'is-fullscreen': fullscreen.isFullscreen.value },
1447
+ ]"
1448
+ >
1364
1449
  <ChartMenu v-if="menu" :items="menuItems" />
1450
+ <div class="chart-sr-only" aria-live="polite">
1451
+ {{ fullscreen.isFullscreen.value ? "Map expanded to fill window" : "" }}
1452
+ </div>
1365
1453
  <!--
1366
1454
  Title + legend live as an HTML overlay on top of the SVG so they keep
1367
1455
  their intrinsic px sizes regardless of how the browser scales the
1368
1456
  viewBox to fit the container.
1369
1457
  -->
1370
1458
  <div v-if="title || showLegend" class="choropleth-header">
1371
- <div v-if="title" class="choropleth-title">{{ title }}</div>
1372
- <div v-if="showLegend" class="choropleth-legend">
1459
+ <div v-if="title" class="choropleth-title" :style="titleInlineStyle">
1460
+ {{ title }}
1461
+ </div>
1462
+ <div
1463
+ v-if="showLegend"
1464
+ class="choropleth-legend"
1465
+ :style="legendInlineStyle"
1466
+ >
1373
1467
  <span v-if="legendTitle" class="choropleth-legend-title">
1374
1468
  {{ legendTitle }}
1375
1469
  </span>
@@ -1476,10 +1570,6 @@ watch(
1476
1570
  cursor: grabbing;
1477
1571
  }
1478
1572
 
1479
- .choropleth-wrapper:hover :deep(.chart-menu-button) {
1480
- opacity: 1;
1481
- }
1482
-
1483
1573
  .state-path {
1484
1574
  cursor: pointer;
1485
1575
  }
@@ -1530,6 +1620,7 @@ watch(
1530
1620
  font-size: 14px;
1531
1621
  font-weight: 600;
1532
1622
  line-height: 1.2;
1623
+ white-space: pre-line;
1533
1624
  }
1534
1625
 
1535
1626
  .choropleth-legend {
@@ -59,6 +59,127 @@ charts, set `x` and `y` (or `data`) on each `Series`.
59
59
 
60
60
  When `x` is omitted, `y`/`data` values are plotted at indices 0, 1, 2, etc.
61
61
 
62
+ ### Dates
63
+
64
+ Pass ISO date strings (`YYYY-MM-DD`) or `Date` objects as `x` and the
65
+ chart auto-detects a date axis: ticks anchor to year / month / week /
66
+ day / hour boundaries depending on the visible range, and labels
67
+ format themselves accordingly. Numeric `x` arrays keep their existing
68
+ behavior — auto-detection is content-driven and never promotes plain
69
+ numbers.
70
+
71
+ <ComponentDemo>
72
+ <LineChart
73
+ :x="['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']"
74
+ :y="[4, 9, 18, 32, 41, 38, 27, 18, 11, 6]"
75
+ :height="220"
76
+ y-label="% ED visits"
77
+ />
78
+
79
+ <template #code>
80
+
81
+ ```vue
82
+ <LineChart
83
+ :x="[
84
+ '2026-01-05',
85
+ '2026-01-12',
86
+ '2026-01-19',
87
+ '2026-01-26',
88
+ '2026-02-02',
89
+ '2026-02-09',
90
+ '2026-02-16',
91
+ '2026-02-23',
92
+ '2026-03-02',
93
+ '2026-03-09',
94
+ ]"
95
+ :y="[4, 9, 18, 32, 41, 38, 27, 18, 11, 6]"
96
+ :height="220"
97
+ y-label="% ED visits"
98
+ />
99
+ ```
100
+
101
+ </template>
102
+ </ComponentDemo>
103
+
104
+ Override the format via `x-tick-format`. Presets: `"iso"`,
105
+ `"iso-datetime"`, `"year"`, `"month-year"`, `"month-day"`, `"day"`,
106
+ `"time"`, `"datetime"`, `"medium"`. You can also pass an
107
+ `Intl.DateTimeFormatOptions` literal, or a function `(ms, unit?) =>
108
+ string` for full control.
109
+
110
+ <ComponentDemo>
111
+ <LineChart
112
+ :x="['2026-01-05', '2026-01-12', '2026-01-19', '2026-01-26', '2026-02-02']"
113
+ :y="[4, 9, 18, 32, 41]"
114
+ x-tick-format="iso"
115
+ :height="220"
116
+ y-label="% ED visits"
117
+ />
118
+
119
+ <template #code>
120
+
121
+ ```vue
122
+ <LineChart
123
+ :x="['2026-01-05', '2026-01-12', '2026-01-19', '2026-01-26', '2026-02-02']"
124
+ :y="[4, 9, 18, 32, 41]"
125
+ x-tick-format="iso"
126
+ :height="220"
127
+ y-label="% ED visits"
128
+ />
129
+ ```
130
+
131
+ </template>
132
+ </ComponentDemo>
133
+
134
+ By default the chart parses bare `YYYY-MM-DD` (and offset-less
135
+ `YYYY-MM-DDTHH:mm:ss`) as UTC and renders tick labels in UTC. Set
136
+ `timezone="local"` to interpret offset-less strings in the browser's
137
+ timezone instead. Strings that include an explicit offset (`Z` or
138
+ `+05:00`) are always parsed exactly as written.
139
+
140
+ > **Annotations on a date axis.** `ChartAnnotation.x` is always in the
141
+ > chart's resolved coordinate space — that's **epoch-ms** when the axis
142
+ > is in date mode. Pass `Date.UTC(2026, 0, 15)`, `new Date("2026-01-15").getTime()`,
143
+ > or `Date.parse("2026-01-15T00:00:00Z")`. Passing a small integer like
144
+ > `0` will place the annotation at 1970-01-01 (off-screen).
145
+ >
146
+ > **`xLabels` on a date axis.** When both `x: [...date strings]` and
147
+ > `xLabels` are supplied, the date axis wins — `xLabels` is ignored.
148
+ > Date strings carry their own labeling.
149
+
150
+ ### Title
151
+
152
+ The `title` prop accepts `\n` to break across lines. Use `titleStyle`
153
+ to override `fontSize`, `lineHeight`, `color`, `fontWeight`, and
154
+ `align` (`"left" | "center" | "right"`, default `"left"`). Top padding
155
+ grows automatically for additional lines.
156
+
157
+ <ComponentDemo>
158
+ <LineChart
159
+ :data="[0, 4, 8, 15, 22, 30, 28, 20, 12, 5, 2]"
160
+ :height="220"
161
+ :title="'Daily ED visits\nInfluenza-like illness, last 11 days'"
162
+ :title-style="{ fontSize: 16, lineHeight: 20, color: '#0057b7' }"
163
+ x-label="Days"
164
+ y-label="Cases"
165
+ />
166
+
167
+ <template #code>
168
+
169
+ ```vue
170
+ <LineChart
171
+ :data="[0, 4, 8, 15, 22, 30, 28, 20, 12, 5, 2]"
172
+ :height="220"
173
+ :title="'Daily ED visits\nInfluenza-like illness, last 11 days'"
174
+ :title-style="{ fontSize: 16, lineHeight: 20, color: '#0057b7' }"
175
+ x-label="Days"
176
+ y-label="Cases"
177
+ />
178
+ ```
179
+
180
+ </template>
181
+ </ComponentDemo>
182
+
62
183
  ### Multiple series
63
184
 
64
185
  <ComponentDemo>
@@ -98,6 +219,46 @@ When `x` is omitted, `y`/`data` values are plotted at indices 0, 1, 2, etc.
98
219
  </template>
99
220
  </ComponentDemo>
100
221
 
222
+ ### Legend
223
+
224
+ Set `legend` on a series to add an entry to the inline legend strip above the plot. When the items don't all fit on one row at the chart's current width, the legend wraps to additional rows and the plot area shrinks to keep the legend clear of the data. Use `showInLegend: false` to keep a series off the legend (e.g. when its `legend` string is reused as a CSV column header) and `showInTooltip: false` to omit it from the hover tooltip.
225
+
226
+ <ComponentDemo>
227
+ <LineChart
228
+ :series="[
229
+ { data: [0, 12, 28, 45, 60, 55, 40, 25, 12], color: '#0057b7', legend: 'No interventions' },
230
+ { data: [0, 10, 22, 38, 50, 45, 32, 18, 8], color: '#fb7e38', legend: 'School closures' },
231
+ { data: [0, 8, 18, 30, 40, 35, 25, 14, 6], color: '#ef4444', legend: 'Masking + distancing' },
232
+ { data: [0, 6, 14, 24, 32, 28, 20, 10, 4], color: '#10b981', legend: 'Vaccination only' },
233
+ { data: [0, 4, 10, 16, 22, 19, 13, 7, 3], color: '#6366f1', legend: 'Vaccination + masking' },
234
+ { data: [0, 3, 7, 11, 15, 13, 9, 5, 2], color: '#a855f7', legend: 'All interventions combined' },
235
+ ]"
236
+ :height="240"
237
+ x-label="Weeks"
238
+ y-label="Incidence"
239
+ />
240
+
241
+ <template #code>
242
+
243
+ ```vue
244
+ <LineChart
245
+ :series="[
246
+ { data: scenarioA, color: '#0057b7', legend: 'No interventions' },
247
+ { data: scenarioB, color: '#fb7e38', legend: 'School closures' },
248
+ { data: scenarioC, color: '#ef4444', legend: 'Masking + distancing' },
249
+ { data: scenarioD, color: '#10b981', legend: 'Vaccination only' },
250
+ { data: scenarioE, color: '#6366f1', legend: 'Vaccination + masking' },
251
+ { data: scenarioF, color: '#a855f7', legend: 'All interventions combined' },
252
+ ]"
253
+ :height="240"
254
+ x-label="Weeks"
255
+ y-label="Incidence"
256
+ />
257
+ ```
258
+
259
+ </template>
260
+ </ComponentDemo>
261
+
101
262
  ### Tooltip
102
263
 
103
264
  Hover over the chart to see a tooltip with values at each data point. Set `tooltip-trigger="hover"` to enable the built-in tooltip with crosshair and highlight dots. Use the `#tooltip` slot for custom content. Pass `tooltip-value-format` to control how numeric values render (e.g. percentages, currency); it falls back to `y-tick-format` when omitted.
@@ -248,6 +409,46 @@ floor, and `yMin <= 0` is ignored.
248
409
  </template>
249
410
  </ComponentDemo>
250
411
 
412
+ ### Outline
413
+
414
+ Set `outline: true` on a series to draw a page-colored stroke behind the
415
+ line. This is useful when lines overlap, cross dense areas, or sit on a
416
+ busy background — the outline keeps each series visually distinct.
417
+
418
+ <ComponentDemo>
419
+ <LineChart
420
+ :series="[
421
+ { data: [0, 10, 25, 45, 60, 55, 40, 20, 8], color: '#fb7e38', strokeWidth: 3, outline: true },
422
+ { data: [0, 12, 22, 40, 58, 58, 42, 22, 10], color: '#0057b7', strokeWidth: 3, outline: true },
423
+ ]"
424
+ :height="200"
425
+ />
426
+
427
+ <template #code>
428
+
429
+ ```vue
430
+ <LineChart
431
+ :series="[
432
+ {
433
+ data: [0, 10, 25, 45, 60, 55, 40, 20, 8],
434
+ color: '#fb7e38',
435
+ strokeWidth: 3,
436
+ outline: true,
437
+ },
438
+ {
439
+ data: [0, 12, 22, 40, 58, 58, 42, 22, 10],
440
+ color: '#0057b7',
441
+ strokeWidth: 3,
442
+ outline: true,
443
+ },
444
+ ]"
445
+ :height="200"
446
+ />
447
+ ```
448
+
449
+ </template>
450
+ </ComponentDemo>
451
+
251
452
  ### Many trajectories with low opacity
252
453
 
253
454
  <ComponentDemo>
@@ -478,8 +679,9 @@ Pin callouts to data points with `annotations`. Each annotation anchors at
478
679
  respects `xMin` and explicit `x` values), with a pixel
479
680
  `offset: { x, y }` for the label position. Text supports `\n` for line
480
681
  breaks. A curved
481
- pointer line connects the anchor to the label, and the label gets a halo
482
- stroke matching the background so it stays legible over series lines.
682
+ pointer line connects the anchor to the label, and the label gets an
683
+ outline stroke matching the background so it stays legible over series
684
+ lines.
483
685
 
484
686
  <ComponentDemo>
485
687
  <LineChart
@@ -564,9 +766,9 @@ interface ChartAnnotation {
564
766
  color?: string; // text / pointer color (default: currentColor)
565
767
  fontSize?: number; // default: 13 (matches axis labels)
566
768
  fontWeight?: string | number; // default: "normal"
567
- haloColor?: string; // background-matched halo (default: var(--color-bg-0, #fff))
568
- haloWidth?: number; // default: 3
569
- textAnchor?: "start" | "middle" | "end"; // default: derived from offset[0] sign
769
+ outlineColor?: string; // background-matched outline (default: var(--color-bg-0, #fff))
770
+ outlineWidth?: number; // default: 3
771
+ align?: "left" | "center" | "right"; // default: derived from offset[0] sign
570
772
  lineColor?: string; // connector-line color override (default: color)
571
773
  lineWidth?: number; // default: 1
572
774
  lineDash?: string | number | readonly number[]; // SVG stroke-dasharray
@@ -619,7 +821,7 @@ positioned from the anchor as usual via `offset`.
619
821
  y: 30,
620
822
  offset: { x: -8, y: -6 },
621
823
  text: 'Max',
622
- textAnchor: 'end',
824
+ align: 'right',
623
825
  pointer: 'ruleFromLeft',
624
826
  lineDash: '4 3',
625
827
  },
@@ -649,7 +851,7 @@ positioned from the anchor as usual via `offset`.
649
851
  y: 30,
650
852
  offset: { x: -8, y: -6 },
651
853
  text: 'Max',
652
- textAnchor: 'end',
854
+ align: 'right',
653
855
  pointer: 'ruleFromLeft',
654
856
  lineDash: '4 3',
655
857
  },
@@ -664,6 +866,13 @@ positioned from the anchor as usual via `offset`.
664
866
  </template>
665
867
  </ComponentDemo>
666
868
 
869
+ ### Chart menu
870
+
871
+ The chart menu (top-right) leads with Expand / Collapse and then Save as SVG,
872
+ Save as PNG, and Download CSV. Expand grows the chart to fill the browser
873
+ window so the plot has more room; press <kbd>Esc</kbd> or pick "Collapse" to
874
+ return to the inline size. Set `menu="false"` to hide the trigger entirely.
875
+
667
876
  ### Custom CSV download
668
877
 
669
878
  By default, the Download CSV menu item exports the chart series as CSV. Use
@@ -720,6 +929,10 @@ until the user clicks Download:
720
929
  | `width` | `number` | No | — |
721
930
  | `height` | `number` | No | — |
722
931
  | `title` | `string` | No | — |
932
+ | `titleStyle` | `TitleStyle` | No | — |
933
+ | `axisLabelStyle` | `LabelStyle` | No | — |
934
+ | `tickLabelStyle` | `LabelStyle` | No | — |
935
+ | `legendStyle` | `LabelStyle` | No | — |
723
936
  | `xLabel` | `string` | No | — |
724
937
  | `yLabel` | `string` | No | — |
725
938
  | `debounce` | `number` | No | — |
@@ -735,7 +948,7 @@ until the user clicks Download:
735
948
  | `chartPadding` | `ChartPadding` | No | — |
736
949
  | `y` | `LineChartData` | No | — |
737
950
  | `data` | `LineChartData` | No | — |
738
- | `x` | `LineChartData` | No | — |
951
+ | `x` | `LineChartXInput` | No | — |
739
952
  | `series` | `Series[]` | No | — |
740
953
  | `areas` | `Area[]` | No | — |
741
954
  | `areaSections` | `AreaSection[]` | No | — |
@@ -745,7 +958,10 @@ until the user clicks Download:
745
958
  | `xMin` | `number` | No | — |
746
959
  | `xTicks` | `number \| number[]` | No | — |
747
960
  | `yTicks` | `number \| number[]` | No | — |
748
- | `xTickFormat` | `NumberFormat \| ((value: number, index: number) =&gt; string)` | No | — |
961
+ | `xTickFormat` | `\| NumberFormat
962
+ \| ((value: number, index: number) =&gt; string)
963
+ \| DateFormat` | No | — |
964
+ | `timezone` | `DateTimezone` | No | — |
749
965
  | `yTickFormat` | `NumberFormat` | No | — |
750
966
  | `xLabels` | `string[]` | No | — |
751
967
  | `xGrid` | `boolean` | No | — |
@@ -789,6 +1005,7 @@ interface Series {
789
1005
  strokeWidth?: number;
790
1006
  opacity?: number;
791
1007
  line?: boolean;
1008
+ outline?: boolean; // page-colored stroke drawn behind the line
792
1009
  dots?: boolean;
793
1010
  dotRadius?: number;
794
1011
  dotFill?: string;