@cfasim-ui/charts 0.7.8 → 0.8.1
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.
- package/dist/BarChart/BarChart.d.ts +9 -26
- package/dist/ChartMenu/ChartMenu.d.ts +3 -2
- package/dist/ChartTooltip/ChartTooltip.d.ts +9 -12
- package/dist/ChoroplethMap/ChoroplethMap.d.ts +42 -190
- package/dist/ChoroplethMap/ChoroplethTooltip.d.ts +20 -27
- package/dist/ChoroplethMap/canvasLayer.d.ts +23 -6
- package/dist/ChoroplethMap/cityLayout.d.ts +3 -2
- package/dist/ChoroplethMap/mixedGeo.d.ts +74 -0
- package/dist/ChoroplethMap/mixedGeo.test.d.ts +1 -0
- package/dist/DataTable/DataTable.d.ts +3 -2
- package/dist/LineChart/LineChart.d.ts +9 -26
- package/dist/_shared/ChartAnnotations.d.ts +3 -2
- package/dist/_shared/ChartZoomControls.d.ts +3 -2
- package/dist/_shared/index.d.ts +2 -1
- package/dist/_shared/mapTheme.d.ts +159 -0
- package/dist/_shared/mapTheme.test.d.ts +1 -0
- package/dist/index.css +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1821 -1462
- package/docs/BarChart.md +776 -0
- package/docs/ChoroplethMap.md +1394 -0
- package/docs/DataTable.md +386 -0
- package/docs/LineChart.md +1267 -0
- package/docs/index.json +56 -0
- package/package.json +25 -21
- package/src/BarChart/BarChart.md +743 -0
- package/src/BarChart/BarChart.vue +1901 -0
- package/src/ChartMenu/ChartMenu.vue +220 -0
- package/src/ChartMenu/download.ts +120 -0
- package/src/ChartTooltip/ChartTooltip.vue +97 -0
- package/src/ChoroplethMap/ChoroplethMap.md +1354 -0
- package/src/ChoroplethMap/ChoroplethMap.vue +3778 -0
- package/src/ChoroplethMap/ChoroplethTooltip.vue +103 -0
- package/src/ChoroplethMap/canvasLayer.ts +373 -0
- package/src/ChoroplethMap/cityLayout.ts +262 -0
- package/src/ChoroplethMap/hsaMapping.ts +4116 -0
- package/src/ChoroplethMap/mixedGeo.ts +201 -0
- package/src/DataTable/DataTable.md +372 -0
- package/src/DataTable/DataTable.vue +406 -0
- package/src/LineChart/LineChart.md +1225 -0
- package/src/LineChart/LineChart.vue +1555 -0
- package/src/_shared/ChartAnnotations.vue +420 -0
- package/src/_shared/ChartZoomControls.vue +138 -0
- package/src/_shared/annotations.ts +106 -0
- package/src/_shared/axes.ts +69 -0
- package/src/_shared/chartProps.ts +201 -0
- package/src/_shared/computeTicks.ts +42 -0
- package/src/_shared/contrast.ts +100 -0
- package/src/_shared/dateAxis.ts +501 -0
- package/src/_shared/index.ts +78 -0
- package/src/_shared/mapTheme.ts +551 -0
- package/src/_shared/scale.ts +86 -0
- package/src/_shared/seriesCsv.ts +68 -0
- package/src/_shared/touch.ts +8 -0
- package/src/_shared/useChartFoundation.ts +175 -0
- package/src/_shared/useChartFullscreen.ts +254 -0
- package/src/_shared/useChartMenu.ts +111 -0
- package/src/_shared/useChartPadding.ts +235 -0
- package/src/_shared/useChartSize.ts +58 -0
- package/src/_shared/useChartTooltip.ts +205 -0
- package/src/env.d.ts +4 -0
- package/src/hsa-mapping.ts +1 -0
- package/src/index.ts +41 -0
- package/src/tooltip-position.ts +55 -0
- package/src/us-cities/data.ts +1371 -0
- package/src/us-cities/index.ts +122 -0
- package/src/us-cities.ts +7 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// Mixed-level geography for ChoroplethMap: render most of the map at the base
|
|
2
|
+
// `geoType` while a few states render at a different level — a county map where
|
|
3
|
+
// California is one merged shape (a single state-level estimate), or a state map
|
|
4
|
+
// where New York is broken out into its counties.
|
|
5
|
+
//
|
|
6
|
+
// The unit of substitution is always a whole state: a data row that declares a
|
|
7
|
+
// `geoType` other than the base marks its state, and that state's entire area is
|
|
8
|
+
// re-tiled at the row's level. That keeps the map a partition of the same
|
|
9
|
+
// territory (no gaps, no double-painted areas) whichever direction you mix in.
|
|
10
|
+
//
|
|
11
|
+
// Kept free of Vue/DOM/topojson so it can be unit tested in isolation.
|
|
12
|
+
|
|
13
|
+
import type { GeoType } from "./ChoroplethMap.vue";
|
|
14
|
+
|
|
15
|
+
/** Minimal shape of the GeoJSON features this module shuffles around. */
|
|
16
|
+
export interface MixFeature {
|
|
17
|
+
id?: string | number | null;
|
|
18
|
+
properties?: { name?: string } | null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** id → feature and name → id indexes for one geographic level. */
|
|
22
|
+
export interface LevelLookup<F extends MixFeature = MixFeature> {
|
|
23
|
+
byId: Map<string, F>;
|
|
24
|
+
byName: Map<string, string>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Lazily resolves the lookup for a level, so a map that never mixes never pays
|
|
29
|
+
* to index the levels it doesn't render. Returns null when the topology can't
|
|
30
|
+
* supply that level (e.g. "counties" from a states-only topology, or "hsas"
|
|
31
|
+
* before the lazy HSA chunk loads).
|
|
32
|
+
*/
|
|
33
|
+
export type LevelResolver<F extends MixFeature = MixFeature> = (
|
|
34
|
+
level: GeoType,
|
|
35
|
+
) => LevelLookup<F> | null;
|
|
36
|
+
|
|
37
|
+
/** Digits in a fully-qualified id at each level ("06" / "06075"). */
|
|
38
|
+
const ID_WIDTH: Record<GeoType, number> = { states: 2, counties: 5, hsas: 6 };
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 2-digit state FIPS a feature id belongs to, or null when it can't be
|
|
42
|
+
* determined. States and counties carry it as their first two digits; HSA
|
|
43
|
+
* codes need the FIPS→HSA table (inverted into `hsaToState`), because an HSA
|
|
44
|
+
* code isn't state-prefixed. No HSA spans two states, so the mapping is total.
|
|
45
|
+
*/
|
|
46
|
+
export function stateOfId(
|
|
47
|
+
level: GeoType,
|
|
48
|
+
id: string,
|
|
49
|
+
hsaToState?: ReadonlyMap<string, string> | null,
|
|
50
|
+
): string | null {
|
|
51
|
+
if (level === "hsas") return hsaToState?.get(id) ?? null;
|
|
52
|
+
const padded = id.padStart(ID_WIDTH[level], "0");
|
|
53
|
+
return /^\d{2}/.test(padded) ? padded.slice(0, 2) : null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Resolves a user-supplied id (code or feature name) to a canonical feature id. */
|
|
57
|
+
function canonicalId(
|
|
58
|
+
rawId: string,
|
|
59
|
+
level: GeoType,
|
|
60
|
+
lookup: LevelLookup,
|
|
61
|
+
): string | null {
|
|
62
|
+
if (lookup.byId.has(rawId)) return rawId;
|
|
63
|
+
const padded = rawId.padStart(ID_WIDTH[level], "0");
|
|
64
|
+
if (lookup.byId.has(padded)) return padded;
|
|
65
|
+
return lookup.byName.get(rawId) ?? null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** A `data` row as far as mixing is concerned. */
|
|
69
|
+
export interface MixRow {
|
|
70
|
+
id: string;
|
|
71
|
+
geoType?: GeoType;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface OverrideResolution {
|
|
75
|
+
/** state FIPS → the level that state renders at. */
|
|
76
|
+
overrides: Map<string, GeoType>;
|
|
77
|
+
/** Row ids whose state couldn't be resolved (bad id, or level unavailable). */
|
|
78
|
+
unresolved: string[];
|
|
79
|
+
/**
|
|
80
|
+
* Row ids that named an already-claimed state at a different level. The first
|
|
81
|
+
* row wins; these are reported so the caller can warn.
|
|
82
|
+
*/
|
|
83
|
+
conflicts: string[];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Collects the per-state level overrides implied by `rows`. Rows without a
|
|
88
|
+
* `geoType`, or whose `geoType` matches the base map, are plain data and are
|
|
89
|
+
* ignored here.
|
|
90
|
+
*/
|
|
91
|
+
export function resolveGeoOverrides(
|
|
92
|
+
rows: readonly MixRow[] | undefined,
|
|
93
|
+
baseGeoType: GeoType,
|
|
94
|
+
getLevel: LevelResolver,
|
|
95
|
+
hsaToState?: ReadonlyMap<string, string> | null,
|
|
96
|
+
): OverrideResolution {
|
|
97
|
+
const overrides = new Map<string, GeoType>();
|
|
98
|
+
const unresolved: string[] = [];
|
|
99
|
+
const conflicts: string[] = [];
|
|
100
|
+
if (!rows) return { overrides, unresolved, conflicts };
|
|
101
|
+
|
|
102
|
+
for (const row of rows) {
|
|
103
|
+
const level = row.geoType;
|
|
104
|
+
if (!level || level === baseGeoType) continue;
|
|
105
|
+
const lookup = getLevel(level);
|
|
106
|
+
if (!lookup) {
|
|
107
|
+
unresolved.push(row.id);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const id = canonicalId(row.id, level, lookup);
|
|
111
|
+
const fips = id == null ? null : stateOfId(level, id, hsaToState);
|
|
112
|
+
if (!fips) {
|
|
113
|
+
unresolved.push(row.id);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const claimed = overrides.get(fips);
|
|
117
|
+
if (claimed == null) overrides.set(fips, level);
|
|
118
|
+
else if (claimed !== level) conflicts.push(row.id);
|
|
119
|
+
}
|
|
120
|
+
return { overrides, unresolved, conflicts };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Serializes overrides into a comparable key. The caller derives the override
|
|
125
|
+
* map from this string so its identity only changes when the *set* of overrides
|
|
126
|
+
* does — otherwise every `data` update would rebuild the whole feature tree.
|
|
127
|
+
*/
|
|
128
|
+
export function serializeOverrides(overrides: Map<string, GeoType>): string {
|
|
129
|
+
return [...overrides]
|
|
130
|
+
.map(([fips, level]) => `${fips}:${level}`)
|
|
131
|
+
.sort()
|
|
132
|
+
.join(",");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Inverse of `serializeOverrides`. */
|
|
136
|
+
export function parseOverrides(key: string): Map<string, GeoType> {
|
|
137
|
+
const map = new Map<string, GeoType>();
|
|
138
|
+
if (!key) return map;
|
|
139
|
+
for (const part of key.split(",")) {
|
|
140
|
+
const [fips, level] = part.split(":");
|
|
141
|
+
map.set(fips, level as GeoType);
|
|
142
|
+
}
|
|
143
|
+
return map;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export interface MixedFeatures<F extends MixFeature> {
|
|
147
|
+
features: F[];
|
|
148
|
+
/**
|
|
149
|
+
* Feature id → level, for substituted features only. Ids absent from the map
|
|
150
|
+
* render at the base `geoType`.
|
|
151
|
+
*/
|
|
152
|
+
levelById: Map<string, GeoType>;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Replaces every overridden state's base features with that state's features at
|
|
157
|
+
* the override level. Returns `base` untouched when there's nothing to mix, so
|
|
158
|
+
* the common case costs one `size` check.
|
|
159
|
+
*
|
|
160
|
+
* `scopeFips` mirrors the map's single-state mode: substituted features outside
|
|
161
|
+
* the scoped state are dropped, exactly as the base set already is.
|
|
162
|
+
*/
|
|
163
|
+
export function mixFeatures<F extends MixFeature>(
|
|
164
|
+
base: readonly F[],
|
|
165
|
+
baseGeoType: GeoType,
|
|
166
|
+
overrides: Map<string, GeoType>,
|
|
167
|
+
getLevel: LevelResolver<F>,
|
|
168
|
+
hsaToState?: ReadonlyMap<string, string> | null,
|
|
169
|
+
scopeFips?: string | null,
|
|
170
|
+
): MixedFeatures<F> {
|
|
171
|
+
const levelById = new Map<string, GeoType>();
|
|
172
|
+
if (overrides.size === 0) return { features: base as F[], levelById };
|
|
173
|
+
|
|
174
|
+
// Collect the replacements first: a state is only cut out of the base once
|
|
175
|
+
// something is standing by to cover it, so a level that isn't ready yet (the
|
|
176
|
+
// lazy HSA chunk) leaves the base map intact instead of punching a hole.
|
|
177
|
+
const substitutes: F[] = [];
|
|
178
|
+
const replaced = new Set<string>();
|
|
179
|
+
for (const [fips, level] of overrides) {
|
|
180
|
+
if (scopeFips && fips !== scopeFips) continue;
|
|
181
|
+
const lookup = getLevel(level);
|
|
182
|
+
if (!lookup) continue;
|
|
183
|
+
const forState: F[] = [];
|
|
184
|
+
for (const [id, f] of lookup.byId) {
|
|
185
|
+
if (stateOfId(level, id, hsaToState) !== fips) continue;
|
|
186
|
+
forState.push(f);
|
|
187
|
+
levelById.set(id, level);
|
|
188
|
+
}
|
|
189
|
+
if (!forState.length) continue;
|
|
190
|
+
replaced.add(fips);
|
|
191
|
+
substitutes.push(...forState);
|
|
192
|
+
}
|
|
193
|
+
if (!replaced.size) return { features: base as F[], levelById };
|
|
194
|
+
|
|
195
|
+
const features = base.filter((f) => {
|
|
196
|
+
const fips = stateOfId(baseGeoType, String(f.id), hsaToState);
|
|
197
|
+
return !(fips && replaced.has(fips));
|
|
198
|
+
});
|
|
199
|
+
features.push(...substitutes);
|
|
200
|
+
return { features, levelById };
|
|
201
|
+
}
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
# DataTable
|
|
2
|
+
|
|
3
|
+
A table for displaying columnar data. Accepts a plain record of arrays or a `ModelOutput` from a simulation.
|
|
4
|
+
|
|
5
|
+
## Examples
|
|
6
|
+
|
|
7
|
+
### Basic usage
|
|
8
|
+
|
|
9
|
+
<ComponentDemo>
|
|
10
|
+
<DataTable :data="{ day: [0, 1, 2, 3, 4], susceptible: [1000, 980, 945, 900, 860], infected: [1, 21, 56, 101, 141] }" />
|
|
11
|
+
|
|
12
|
+
<template #code>
|
|
13
|
+
|
|
14
|
+
```vue
|
|
15
|
+
<DataTable
|
|
16
|
+
:data="{
|
|
17
|
+
day: [0, 1, 2, 3, 4],
|
|
18
|
+
susceptible: [1000, 980, 945, 900, 860],
|
|
19
|
+
infected: [1, 21, 56, 101, 141],
|
|
20
|
+
}"
|
|
21
|
+
/>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
</template>
|
|
25
|
+
</ComponentDemo>
|
|
26
|
+
|
|
27
|
+
### Column labels and width
|
|
28
|
+
|
|
29
|
+
<ComponentDemo>
|
|
30
|
+
<DataTable
|
|
31
|
+
:data="{ day: [0, 1, 2, 3, 4], susceptible: [1000, 980, 945, 900, 860], infected: [1, 21, 56, 101, 141] }"
|
|
32
|
+
:column-config="{
|
|
33
|
+
day: { label: 'Day', width: 'small' },
|
|
34
|
+
susceptible: { label: 'Susceptible' },
|
|
35
|
+
infected: { label: 'Infected' },
|
|
36
|
+
}"
|
|
37
|
+
/>
|
|
38
|
+
|
|
39
|
+
<template #code>
|
|
40
|
+
|
|
41
|
+
```vue
|
|
42
|
+
<DataTable
|
|
43
|
+
:data="{
|
|
44
|
+
day: [0, 1, 2, 3, 4],
|
|
45
|
+
susceptible: [1000, 980, 945, 900, 860],
|
|
46
|
+
infected: [1, 21, 56, 101, 141],
|
|
47
|
+
}"
|
|
48
|
+
:column-config="{
|
|
49
|
+
day: { label: 'Day', width: 'small' },
|
|
50
|
+
susceptible: { label: 'Susceptible' },
|
|
51
|
+
infected: { label: 'Infected' },
|
|
52
|
+
}"
|
|
53
|
+
/>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
</template>
|
|
57
|
+
</ComponentDemo>
|
|
58
|
+
|
|
59
|
+
### Formatting cell values
|
|
60
|
+
|
|
61
|
+
Use `format` on a column to override the default rendering. Accepts a
|
|
62
|
+
{@link NumberFormat} value — a preset name (optionally with `:N` digits,
|
|
63
|
+
e.g. `"percent:1"`), a printf-style format string, or a function
|
|
64
|
+
`(value, row) => string`. Formatted values are also used in CSV exports.
|
|
65
|
+
|
|
66
|
+
<ComponentDemo>
|
|
67
|
+
<DataTable
|
|
68
|
+
:data="{ day: [0, 1, 2, 3, 4], rate: [0.012, 0.234, 0.467, 0.512, 0.601], cases: [1, 21, 56, 101, 141] }"
|
|
69
|
+
:column-config="{
|
|
70
|
+
day: { label: 'Day', width: 'small' },
|
|
71
|
+
rate: { label: 'Attack rate', format: 'percent:1' },
|
|
72
|
+
cases: { label: 'Cases', format: '%05d' },
|
|
73
|
+
}"
|
|
74
|
+
/>
|
|
75
|
+
|
|
76
|
+
<template #code>
|
|
77
|
+
|
|
78
|
+
```vue
|
|
79
|
+
<DataTable
|
|
80
|
+
:data="{
|
|
81
|
+
day: [0, 1, 2, 3, 4],
|
|
82
|
+
rate: [0.012, 0.234, 0.467, 0.512, 0.601],
|
|
83
|
+
cases: [1, 21, 56, 101, 141],
|
|
84
|
+
}"
|
|
85
|
+
:column-config="{
|
|
86
|
+
day: { label: 'Day', width: 'small' },
|
|
87
|
+
rate: { label: 'Attack rate', format: 'percent:1' },
|
|
88
|
+
cases: { label: 'Cases', format: '%05d' },
|
|
89
|
+
}"
|
|
90
|
+
/>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
</template>
|
|
94
|
+
</ComponentDemo>
|
|
95
|
+
|
|
96
|
+
### Cell class and max rows
|
|
97
|
+
|
|
98
|
+
<ComponentDemo>
|
|
99
|
+
<DataTable
|
|
100
|
+
:data="{ generation: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], cases: [1, 3, 8, 15, 28, 45, 62, 71, 55, 30] }"
|
|
101
|
+
:max-rows="5"
|
|
102
|
+
:column-config="{
|
|
103
|
+
generation: { label: 'Gen', cellClass: 'text-secondary', width: 50 },
|
|
104
|
+
cases: { label: 'Cases' },
|
|
105
|
+
}"
|
|
106
|
+
/>
|
|
107
|
+
|
|
108
|
+
<template #code>
|
|
109
|
+
|
|
110
|
+
```vue
|
|
111
|
+
<DataTable
|
|
112
|
+
:data="{
|
|
113
|
+
generation: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
|
|
114
|
+
cases: [1, 3, 8, 15, 28, 45, 62, 71, 55, 30],
|
|
115
|
+
}"
|
|
116
|
+
:max-rows="5"
|
|
117
|
+
:column-config="{
|
|
118
|
+
generation: { label: 'Gen', cellClass: 'text-secondary', width: 50 },
|
|
119
|
+
cases: { label: 'Cases' },
|
|
120
|
+
}"
|
|
121
|
+
/>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
</template>
|
|
125
|
+
</ComponentDemo>
|
|
126
|
+
|
|
127
|
+
### Wrapping long content
|
|
128
|
+
|
|
129
|
+
By default, cell content stays on one line and truncates with an ellipsis
|
|
130
|
+
when it's wider than the column — so a long string in one column can't
|
|
131
|
+
spill into the next. Set `wrap: true` on a column to let it grow
|
|
132
|
+
vertically instead.
|
|
133
|
+
|
|
134
|
+
<ComponentDemo>
|
|
135
|
+
<DataTable
|
|
136
|
+
:data="{
|
|
137
|
+
drug: ['Compound A', 'Compound B', 'Compound C'],
|
|
138
|
+
note: [
|
|
139
|
+
'Well tolerated at all tested doses; no adverse events reported.',
|
|
140
|
+
'Mild GI symptoms in 4% of participants; resolved without intervention.',
|
|
141
|
+
'Discontinued at week 6 after liver-enzyme elevation in two participants.',
|
|
142
|
+
],
|
|
143
|
+
}"
|
|
144
|
+
:column-config="{
|
|
145
|
+
drug: { width: 'small' },
|
|
146
|
+
note: { wrap: true, width: 'large' },
|
|
147
|
+
}"
|
|
148
|
+
/>
|
|
149
|
+
|
|
150
|
+
<template #code>
|
|
151
|
+
|
|
152
|
+
```vue
|
|
153
|
+
<DataTable
|
|
154
|
+
:data="{
|
|
155
|
+
drug: ['Compound A', 'Compound B', 'Compound C'],
|
|
156
|
+
note: [
|
|
157
|
+
'Well tolerated at all tested doses; no adverse events reported.',
|
|
158
|
+
'Mild GI symptoms in 4% of participants; resolved without intervention.',
|
|
159
|
+
'Discontinued at week 6 after liver-enzyme elevation in two participants.',
|
|
160
|
+
],
|
|
161
|
+
}"
|
|
162
|
+
:column-config="{
|
|
163
|
+
drug: { width: 'small' },
|
|
164
|
+
note: { wrap: true, width: 'large' },
|
|
165
|
+
}"
|
|
166
|
+
/>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
</template>
|
|
170
|
+
</ComponentDemo>
|
|
171
|
+
|
|
172
|
+
Use `columnClass` to attach a class to both the header and every body
|
|
173
|
+
cell in a column — handy when whole-column styling (background, border,
|
|
174
|
+
font weight) should match across the header and data. `cellClass` keeps
|
|
175
|
+
its meaning (body cells only).
|
|
176
|
+
|
|
177
|
+
### Full width
|
|
178
|
+
|
|
179
|
+
By default the table sizes to its content (columns default to a fixed
|
|
180
|
+
medium width, so they're evenly spaced). Pass `full-width` to stretch the
|
|
181
|
+
table to fill its container; columns without an explicit width will share
|
|
182
|
+
the available space equally.
|
|
183
|
+
|
|
184
|
+
<ComponentDemo>
|
|
185
|
+
<DataTable
|
|
186
|
+
:data="{ day: [0, 1, 2, 3, 4], susceptible: [1000, 980, 945, 900, 860], infected: [1, 21, 56, 101, 141] }"
|
|
187
|
+
full-width
|
|
188
|
+
/>
|
|
189
|
+
|
|
190
|
+
<template #code>
|
|
191
|
+
|
|
192
|
+
```vue
|
|
193
|
+
<DataTable
|
|
194
|
+
:data="{
|
|
195
|
+
day: [0, 1, 2, 3, 4],
|
|
196
|
+
susceptible: [1000, 980, 945, 900, 860],
|
|
197
|
+
infected: [1, 21, 56, 101, 141],
|
|
198
|
+
}"
|
|
199
|
+
full-width
|
|
200
|
+
/>
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
</template>
|
|
204
|
+
</ComponentDemo>
|
|
205
|
+
|
|
206
|
+
### Download menu
|
|
207
|
+
|
|
208
|
+
A `⋯` menu appears in the top-right corner of every table with a
|
|
209
|
+
**Download** item that exports the data as CSV. Use `download-menu-link`
|
|
210
|
+
to customize the menu item label and `filename` to control the
|
|
211
|
+
downloaded filename. Pass `:menu="false"` to hide the menu entirely.
|
|
212
|
+
|
|
213
|
+
<ComponentDemo>
|
|
214
|
+
<DataTable
|
|
215
|
+
:data="{ day: [0, 1, 2, 3, 4], cases: [1, 21, 56, 101, 141] }"
|
|
216
|
+
filename="sir-cases"
|
|
217
|
+
download-menu-link="Download cases (CSV)"
|
|
218
|
+
/>
|
|
219
|
+
|
|
220
|
+
<template #code>
|
|
221
|
+
|
|
222
|
+
```vue
|
|
223
|
+
<DataTable
|
|
224
|
+
:data="{
|
|
225
|
+
day: [0, 1, 2, 3, 4],
|
|
226
|
+
cases: [1, 21, 56, 101, 141],
|
|
227
|
+
}"
|
|
228
|
+
filename="sir-cases"
|
|
229
|
+
download-menu-link="Download cases (CSV)"
|
|
230
|
+
/>
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
</template>
|
|
234
|
+
</ComponentDemo>
|
|
235
|
+
|
|
236
|
+
### Custom CSV download
|
|
237
|
+
|
|
238
|
+
By default, the Download menu item exports the displayed table as CSV.
|
|
239
|
+
Use the `csv` prop to supply your own content — for example, to include
|
|
240
|
+
ISO dates, extra columns that aren't in the table, or values formatted
|
|
241
|
+
differently from the on-screen rendering. Accepts a raw string or a
|
|
242
|
+
function returning one (called lazily on click).
|
|
243
|
+
|
|
244
|
+
<ComponentDemo>
|
|
245
|
+
<DataTable
|
|
246
|
+
:data="{ day: [0, 1, 2, 3, 4], cases: [1, 21, 56, 101, 141] }"
|
|
247
|
+
filename="sir-cases"
|
|
248
|
+
:csv="'date,day,cases\n2024-01-01,0,1\n2024-01-02,1,21\n2024-01-03,2,56\n2024-01-04,3,101\n2024-01-05,4,141'"
|
|
249
|
+
/>
|
|
250
|
+
|
|
251
|
+
<template #code>
|
|
252
|
+
|
|
253
|
+
```vue
|
|
254
|
+
<DataTable
|
|
255
|
+
:data="{
|
|
256
|
+
day: [0, 1, 2, 3, 4],
|
|
257
|
+
cases: [1, 21, 56, 101, 141],
|
|
258
|
+
}"
|
|
259
|
+
filename="sir-cases"
|
|
260
|
+
:csv="`date,day,cases
|
|
261
|
+
2024-01-01,0,1
|
|
262
|
+
2024-01-02,1,21
|
|
263
|
+
2024-01-03,2,56
|
|
264
|
+
2024-01-04,3,101
|
|
265
|
+
2024-01-05,4,141`"
|
|
266
|
+
/>
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
</template>
|
|
270
|
+
</ComponentDemo>
|
|
271
|
+
|
|
272
|
+
### Download button
|
|
273
|
+
|
|
274
|
+
Pass `download-button` to render a visible, labeled button beneath the
|
|
275
|
+
table instead of exposing the download only via the top-right menu. The
|
|
276
|
+
button uses `download-menu-link` as its label, and the menu's Download
|
|
277
|
+
item is suppressed so the action isn't duplicated. The button has the
|
|
278
|
+
class `data-table-download-button` and its styles are unscoped, so it
|
|
279
|
+
can be targeted directly from custom CSS without specificity battles.
|
|
280
|
+
|
|
281
|
+
<ComponentDemo>
|
|
282
|
+
<DataTable
|
|
283
|
+
:data="{ day: [0, 1, 2, 3, 4], cases: [1, 21, 56, 101, 141] }"
|
|
284
|
+
filename="sir-cases"
|
|
285
|
+
download-menu-link="Download CSV"
|
|
286
|
+
download-button
|
|
287
|
+
:csv="'date,day,cases\n2024-01-01,0,1\n2024-01-02,1,21\n2024-01-03,2,56\n2024-01-04,3,101\n2024-01-05,4,141'"
|
|
288
|
+
/>
|
|
289
|
+
|
|
290
|
+
<template #code>
|
|
291
|
+
|
|
292
|
+
```vue
|
|
293
|
+
<DataTable
|
|
294
|
+
:data="{
|
|
295
|
+
day: [0, 1, 2, 3, 4],
|
|
296
|
+
cases: [1, 21, 56, 101, 141],
|
|
297
|
+
}"
|
|
298
|
+
filename="sir-cases"
|
|
299
|
+
download-menu-link="Download CSV"
|
|
300
|
+
download-button
|
|
301
|
+
:csv="`date,day,cases
|
|
302
|
+
2024-01-01,0,1
|
|
303
|
+
2024-01-02,1,21
|
|
304
|
+
2024-01-03,2,56
|
|
305
|
+
2024-01-04,3,101
|
|
306
|
+
2024-01-05,4,141`"
|
|
307
|
+
/>
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
</template>
|
|
311
|
+
</ComponentDemo>
|
|
312
|
+
|
|
313
|
+
### Download link
|
|
314
|
+
|
|
315
|
+
Pass `download-link` to render a plain text link beneath the table
|
|
316
|
+
instead of (or alongside) the menu. It's a real `<a href download>` —
|
|
317
|
+
right-click → Save As works. Pass `true` for the default "Download data
|
|
318
|
+
(CSV)" label, or a string to customize. The menu's Download item is
|
|
319
|
+
suppressed when this is set. If both `download-link` and
|
|
320
|
+
`download-button` are set, the button wins.
|
|
321
|
+
|
|
322
|
+
<ComponentDemo>
|
|
323
|
+
<DataTable
|
|
324
|
+
:data="{ day: [0, 1, 2, 3, 4], cases: [1, 21, 56, 101, 141] }"
|
|
325
|
+
filename="sir-cases"
|
|
326
|
+
download-link
|
|
327
|
+
/>
|
|
328
|
+
|
|
329
|
+
<template #code>
|
|
330
|
+
|
|
331
|
+
```vue
|
|
332
|
+
<DataTable
|
|
333
|
+
:data="{
|
|
334
|
+
day: [0, 1, 2, 3, 4],
|
|
335
|
+
cases: [1, 21, 56, 101, 141],
|
|
336
|
+
}"
|
|
337
|
+
filename="sir-cases"
|
|
338
|
+
download-link
|
|
339
|
+
/>
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
</template>
|
|
343
|
+
</ComponentDemo>
|
|
344
|
+
|
|
345
|
+
<!--@include: ./_api/data-table.md-->
|
|
346
|
+
|
|
347
|
+
### ColumnConfig
|
|
348
|
+
|
|
349
|
+
```ts
|
|
350
|
+
interface ColumnConfig {
|
|
351
|
+
label?: string;
|
|
352
|
+
width?: "small" | "medium" | "large" | number;
|
|
353
|
+
align?: "left" | "center" | "right";
|
|
354
|
+
/** Class applied to body `<td>` cells only. */
|
|
355
|
+
cellClass?: string;
|
|
356
|
+
/** Class applied to both the header `<th>` and body `<td>` cells. */
|
|
357
|
+
columnClass?: string;
|
|
358
|
+
/** Allow cell contents to wrap. Default `false` (nowrap + ellipsis). */
|
|
359
|
+
wrap?: boolean;
|
|
360
|
+
format?: NumberFormat | ((value: CellValue, row: number) => string);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
type CellValue = number | string | boolean;
|
|
364
|
+
|
|
365
|
+
type NumberFormat =
|
|
366
|
+
| NumberFormatPreset // "plain" | "localized" | "percent" | "compact" | "scientific" | "engineering" (optionally with ":N" digits, e.g. "percent:1")
|
|
367
|
+
| string // printf-style format, e.g. "%.2f", "%05d"
|
|
368
|
+
| ((value: number) => string);
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
See `formatNumber` in `@cfasim-ui/shared` for the underlying utility — you
|
|
372
|
+
can also call it directly in your own code.
|