@humanforest/nuxt-layer 0.3.3 → 0.3.4
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/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@humanforest/nuxt-layer",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.4",
|
|
5
5
|
"description": "Forest design system as a Nuxt layer — extend it to inherit the theme, colour roles, icons and brand fonts in one line.",
|
|
6
6
|
"main": "./nuxt.config.ts",
|
|
7
7
|
"files": [
|
|
@@ -18,6 +18,7 @@ import { VisXYContainer, VisLine, VisArea } from '@unovis/vue';
|
|
|
18
18
|
import { CurveType } from '@unovis/ts';
|
|
19
19
|
import { motionDuration } from './motionDuration';
|
|
20
20
|
import { useThemeVersion } from './useThemeVersion';
|
|
21
|
+
import { sparklineDomain } from './sparklineDomain';
|
|
21
22
|
|
|
22
23
|
const props = withDefaults(
|
|
23
24
|
defineProps<{
|
|
@@ -52,7 +53,7 @@ const props = withDefaults(
|
|
|
52
53
|
endpoint?: boolean;
|
|
53
54
|
/**
|
|
54
55
|
* Takes the height its container gives it, with `height` as the floor. For a graphic in a space
|
|
55
|
-
* its surroundings have made taller (a KPI
|
|
56
|
+
* its surroundings have made taller (a KPI's chart row that a stretched card or a group has given
|
|
56
57
|
* more height), where the chart is the part that should grow. Works for both marks.
|
|
57
58
|
*/
|
|
58
59
|
fill?: boolean;
|
|
@@ -100,23 +101,18 @@ type Point = { i: number; v: number | null; c: number | null };
|
|
|
100
101
|
const points = computed<Point[]>(() =>
|
|
101
102
|
props.data.map((v, i) => ({ i, v, c: props.compare?.[i] ?? null })),
|
|
102
103
|
);
|
|
104
|
+
// Readings are plotted as their height above the frame's floor, which is 0 except under `area`.
|
|
103
105
|
const x = (d: Point) => d.i;
|
|
104
|
-
const y = (d: Point) => d.v;
|
|
105
|
-
const yCompare = (d: Point) => d.c;
|
|
106
|
+
const y = (d: Point) => (d.v === null ? null : d.v - frame.value.floor);
|
|
107
|
+
const yCompare = (d: Point) => (d.c === null ? null : d.c - frame.value.floor);
|
|
106
108
|
|
|
107
109
|
const hasCompare = computed(() => !!props.compare?.some((v) => v !== null && v !== undefined));
|
|
108
110
|
|
|
109
111
|
// The line marks share one scale, framed by the floor and ceiling of everything drawn on it — a
|
|
110
|
-
// line reads as a shape, and a shape wants its own range.
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
const
|
|
114
|
-
const vals = [...props.data, ...(props.compare ?? [])].filter((v): v is number => typeof v === 'number');
|
|
115
|
-
return vals.length ? [Math.min(...vals), Math.max(...vals)] : null;
|
|
116
|
-
});
|
|
117
|
-
// An area is a band from its baseline to baseline + y, not a fill up to y, so reaching the line
|
|
118
|
-
// from the floor of the frame means passing the height above that floor.
|
|
119
|
-
const yFromFloor = (d: Point) => (d.v === null || !domain.value ? null : d.v - domain.value[0]);
|
|
112
|
+
// line reads as a shape, and a shape wants its own range. The frame, and why `area` shifts it to
|
|
113
|
+
// start at 0, live in sparklineDomain. An area is a band from its baseline to baseline + y, so with
|
|
114
|
+
// the floor at 0 it runs from 0 up to each reading's height above the floor.
|
|
115
|
+
const frame = computed(() => sparklineDomain(props.data, props.compare, props.area));
|
|
120
116
|
|
|
121
117
|
// Bars are the exception: they are measured from zero, not from the data's own floor. A bar reads
|
|
122
118
|
// as a LENGTH, and a bar that starts anywhere but zero has a length no longer proportional to its
|
|
@@ -154,9 +150,9 @@ const last = computed(() => {
|
|
|
154
150
|
});
|
|
155
151
|
// Fraction of the height the final point sits at, for the endpoint dot.
|
|
156
152
|
const endpointTop = computed(() => {
|
|
157
|
-
const [lo, hi] =
|
|
153
|
+
const [lo, hi] = frame.value.domain ?? [0, 0];
|
|
158
154
|
if (last.value === null || hi === lo) return 50;
|
|
159
|
-
return (1 - (last.value - lo) / (hi - lo)) * 100;
|
|
155
|
+
return (1 - (last.value - frame.value.floor - lo) / (hi - lo)) * 100;
|
|
160
156
|
});
|
|
161
157
|
</script>
|
|
162
158
|
|
|
@@ -198,7 +194,7 @@ const endpointTop = computed(() => {
|
|
|
198
194
|
:data="points"
|
|
199
195
|
:height="fill ? undefined : height"
|
|
200
196
|
:margin="{ top: 2, right: endpoint ? 4 : 0, bottom: 0, left: 0 }"
|
|
201
|
-
:y-domain="domain ?? undefined"
|
|
197
|
+
:y-domain="frame.domain ?? undefined"
|
|
202
198
|
:duration="motionDuration('slow')"
|
|
203
199
|
>
|
|
204
200
|
<!-- Comparison first, so the current series draws over it. Dashed and thinner: the dash says
|
|
@@ -214,7 +210,7 @@ const endpointTop = computed(() => {
|
|
|
214
210
|
:opacity="0.45"
|
|
215
211
|
:curve-type="curve ? CurveType.MonotoneX : CurveType.Linear"
|
|
216
212
|
/>
|
|
217
|
-
<VisArea v-if="area" :x="x" :y="
|
|
213
|
+
<VisArea v-if="area" :x="x" :y="y" :baseline="0" :color="color" :opacity="0.12" :curve-type="curve ? CurveType.MonotoneX : CurveType.Linear" />
|
|
218
214
|
<VisLine :x="x" :y="y" :color="color" :line-width="1.5" :curve-type="curve ? CurveType.MonotoneX : CurveType.Linear" />
|
|
219
215
|
</VisXYContainer>
|
|
220
216
|
<span
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The frame a sparkline's line marks are plotted in: the floor and ceiling of everything drawn,
|
|
3
|
+
* shared by the series and its comparison, or null when there is nothing to draw so the container
|
|
4
|
+
* keeps its own fallback. Set by hand because an Unovis area is measured from a baseline: left to
|
|
5
|
+
* itself, `area` pulls the floor to zero and flattens the line it is filling under.
|
|
6
|
+
*
|
|
7
|
+
* Under `area` the frame starts at 0 and every reading is plotted as its height above the floor.
|
|
8
|
+
* Unovis insets a line's scale by half its stroke unless the domain starts at 0, and the area shares
|
|
9
|
+
* that scale, so from a non-zero floor the fill stopped 0.75px above the bottom edge: a hairline of
|
|
10
|
+
* card under the chart. From 0 the fill meets the edge, and the line's lowest point gives the lower
|
|
11
|
+
* half of its stroke to that edge, where the graphic sits anyway. A plain line keeps its range and
|
|
12
|
+
* its whole stroke.
|
|
13
|
+
*/
|
|
14
|
+
export function sparklineDomain(
|
|
15
|
+
data: (number | null)[],
|
|
16
|
+
compare: (number | null)[] | undefined,
|
|
17
|
+
area: boolean,
|
|
18
|
+
): { floor: number; domain: [number, number] | null } {
|
|
19
|
+
const vals = [...data, ...(compare ?? [])].filter((v): v is number => typeof v === 'number');
|
|
20
|
+
if (!vals.length) return { floor: 0, domain: null };
|
|
21
|
+
const lo = Math.min(...vals);
|
|
22
|
+
const hi = Math.max(...vals);
|
|
23
|
+
const floor = area ? lo : 0;
|
|
24
|
+
return { floor, domain: [lo - floor, hi - floor] };
|
|
25
|
+
}
|
|
@@ -119,8 +119,8 @@ const props = withDefaults(
|
|
|
119
119
|
flush?: boolean;
|
|
120
120
|
/**
|
|
121
121
|
* The card fills the height its row gives it instead of hugging its content, so cards in a row
|
|
122
|
-
* end level. Only for content that can take the extra height: an FKpi whose
|
|
123
|
-
* `fill` sparkline grows into it. A
|
|
122
|
+
* end level. Only for content that can take the extra height: an FKpi whose chart is a
|
|
123
|
+
* `fill` sparkline grows into it. A fixed-height chart, a table or a line of text cannot, and the spare height
|
|
124
124
|
* lands as a gap under it.
|
|
125
125
|
*/
|
|
126
126
|
stretch?: boolean;
|
|
@@ -22,8 +22,8 @@ export const kpiTheme = tv({
|
|
|
22
22
|
// bleed pulls by the same expression, so the graphic meets the edge the padding leaves.
|
|
23
23
|
//
|
|
24
24
|
// No row template: the rows are implicit, one per part present, so a tile without a visual has
|
|
25
|
-
// no empty track under its caption, and no gap in front of one. A
|
|
26
|
-
//
|
|
25
|
+
// no empty track under its caption, and no gap in front of one. A visual adds a flexible last
|
|
26
|
+
// row (`visualFit`); a grouped tile takes its rows from the group instead (`aligned`).
|
|
27
27
|
//
|
|
28
28
|
// One explicit minmax(0,1fr) column: without it the implicit column sizes to max-content, and a
|
|
29
29
|
// chart with an intrinsic width (Unovis defaults to 300px) makes the tile's own grid wider than
|
|
@@ -78,22 +78,22 @@ export const kpiTheme = tv({
|
|
|
78
78
|
* carry only the first — which left every other visual undoing it with utilities at the call
|
|
79
79
|
* site.
|
|
80
80
|
*
|
|
81
|
-
* `bleed` (the default) is for a graphic that IS data and nothing else, a
|
|
82
|
-
* case: it runs through the tile's gutter to both side edges and sits on the bottom edge, which
|
|
81
|
+
* `bleed` (the default) is for a graphic that IS data and nothing else, a line or area sparkline
|
|
82
|
+
* being the case: it runs through the tile's gutter to both side edges and sits on the bottom edge, which
|
|
83
83
|
* the tile reaches by zeroing its own bottom padding rather than by a negative margin — a
|
|
84
84
|
* margin would shrink the grid row and pull the graphic up over the caption. Its row is the
|
|
85
85
|
* tile's last and flexible one, and the graphic spans it bottom-aligned: a tile given more
|
|
86
86
|
* height than it needs — a group's shared row, a stretched FInsetCard — grows a `fill`
|
|
87
87
|
* sparkline into the spare height and leaves any other graphic on the bottom edge.
|
|
88
88
|
*
|
|
89
|
-
* `inset` is for a graphic carrying text, FDistributionBar's legend being the
|
|
90
|
-
* against the card's edge reads as a mistake, so the padding stays. It
|
|
91
|
-
*
|
|
92
|
-
*
|
|
89
|
+
* `inset` is for bars, and for a graphic carrying text, FDistributionBar's legend being the
|
|
90
|
+
* case. Text set against the card's edge reads as a mistake, so the padding stays. It shares
|
|
91
|
+
* bleed's last, flexible row, with the graphic at the top of it: spare height lands below a
|
|
92
|
+
* graphic that cannot grow, and a `fill` sparkline grows into it.
|
|
93
93
|
*/
|
|
94
94
|
visualFit: {
|
|
95
95
|
bleed: { root: 'pb-0 grid-rows-[auto_auto_auto_1fr]', visual: 'self-stretch flex flex-col justify-end -mx-[var(--forest-kpi-pad,var(--forest-card-pad))]' },
|
|
96
|
-
inset: { root: '
|
|
96
|
+
inset: { root: 'grid-rows-[auto_auto_auto_1fr]', visual: 'self-stretch flex flex-col justify-start' },
|
|
97
97
|
},
|
|
98
98
|
// A grouped tile spans exactly the rows its group declares (kpiGroupRows), so a group with no
|
|
99
99
|
// visual has no empty last track and no group gap under its captions.
|