panelui-native 0.60.0 → 0.62.0
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/README.md +3 -1
- package/lib/module/components/area-chart/index.js +86 -1
- package/lib/module/components/area-chart/index.js.map +1 -1
- package/lib/module/components/bar-chart/index.js +112 -2
- package/lib/module/components/bar-chart/index.js.map +1 -1
- package/lib/module/components/live-line-chart/index.js +1063 -0
- package/lib/module/components/live-line-chart/index.js.map +1 -0
- package/lib/module/components/polar-area-chart/index.js +719 -0
- package/lib/module/components/polar-area-chart/index.js.map +1 -0
- package/lib/module/components/treemap-chart/index.js +957 -0
- package/lib/module/components/treemap-chart/index.js.map +1 -0
- package/lib/module/index.js +3 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/utils/chart.js +171 -4
- package/lib/module/utils/chart.js.map +1 -1
- package/lib/typescript/src/components/area-chart/index.d.ts +28 -1
- package/lib/typescript/src/components/area-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/bar-chart/index.d.ts +33 -3
- package/lib/typescript/src/components/bar-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/live-line-chart/index.d.ts +293 -0
- package/lib/typescript/src/components/live-line-chart/index.d.ts.map +1 -0
- package/lib/typescript/src/components/polar-area-chart/index.d.ts +259 -0
- package/lib/typescript/src/components/polar-area-chart/index.d.ts.map +1 -0
- package/lib/typescript/src/components/treemap-chart/index.d.ts +317 -0
- package/lib/typescript/src/components/treemap-chart/index.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +3 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/utils/chart.d.ts +27 -1
- package/lib/typescript/src/utils/chart.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/area-chart/index.tsx +86 -1
- package/src/components/bar-chart/index.tsx +115 -4
- package/src/components/live-line-chart/index.tsx +1294 -0
- package/src/components/polar-area-chart/index.tsx +928 -0
- package/src/components/treemap-chart/index.tsx +1175 -0
- package/src/index.ts +52 -0
- package/src/utils/chart.ts +202 -4
package/src/index.ts
CHANGED
|
@@ -556,6 +556,41 @@ export {
|
|
|
556
556
|
type PieChartStatus,
|
|
557
557
|
type PieDatum,
|
|
558
558
|
} from './components/pie-chart';
|
|
559
|
+
export {
|
|
560
|
+
LiveLineChart,
|
|
561
|
+
useLiveLineChart,
|
|
562
|
+
type LiveLineChartProps,
|
|
563
|
+
type LiveLineChartHandle,
|
|
564
|
+
type LiveLineChartHeaderProps,
|
|
565
|
+
type LiveLineChartGridProps,
|
|
566
|
+
type LiveLineChartAreaProps,
|
|
567
|
+
type LiveLineChartLineProps,
|
|
568
|
+
type LiveLineChartTipProps,
|
|
569
|
+
type LiveLineChartXAxisProps,
|
|
570
|
+
type LiveLineChartYAxisProps,
|
|
571
|
+
type LiveLineChartTooltipProps,
|
|
572
|
+
type LiveLineChartSkeletonProps,
|
|
573
|
+
type LiveLineChartStatus,
|
|
574
|
+
type LiveLineMomentum,
|
|
575
|
+
type LiveLineMomentumColors,
|
|
576
|
+
type LiveLinePoint,
|
|
577
|
+
} from './components/live-line-chart';
|
|
578
|
+
export {
|
|
579
|
+
PolarAreaChart,
|
|
580
|
+
usePolarAreaChart,
|
|
581
|
+
type PolarAreaChartProps,
|
|
582
|
+
type PolarAreaChartHandle,
|
|
583
|
+
type PolarAreaChartHeaderProps,
|
|
584
|
+
type PolarAreaChartGridProps,
|
|
585
|
+
type PolarAreaChartWedgesProps,
|
|
586
|
+
type PolarAreaChartLabelsProps,
|
|
587
|
+
type PolarAreaChartTooltipProps,
|
|
588
|
+
type PolarAreaChartLegendProps,
|
|
589
|
+
type PolarAreaChartSkeletonProps,
|
|
590
|
+
type PolarAreaChartStatus,
|
|
591
|
+
type PolarAreaScale,
|
|
592
|
+
type PolarAreaDatum,
|
|
593
|
+
} from './components/polar-area-chart';
|
|
559
594
|
export { Textarea, type TextareaProps } from './components/textarea';
|
|
560
595
|
export {
|
|
561
596
|
TagInput,
|
|
@@ -956,6 +991,23 @@ export {
|
|
|
956
991
|
type ToastIndicatorProps,
|
|
957
992
|
type ToastCloseProps,
|
|
958
993
|
} from './components/toast';
|
|
994
|
+
export {
|
|
995
|
+
TreemapChart,
|
|
996
|
+
useTreemapChart,
|
|
997
|
+
squarifyLayout,
|
|
998
|
+
type TreemapChartProps,
|
|
999
|
+
type TreemapChartHandle,
|
|
1000
|
+
type TreemapChartStatus,
|
|
1001
|
+
type TreemapDatum,
|
|
1002
|
+
type TreemapTile,
|
|
1003
|
+
type TreemapRect,
|
|
1004
|
+
type TreemapChartTilesProps,
|
|
1005
|
+
type TreemapChartLabelsProps,
|
|
1006
|
+
type TreemapChartTooltipProps,
|
|
1007
|
+
type TreemapChartLegendProps,
|
|
1008
|
+
type TreemapChartHeaderProps,
|
|
1009
|
+
type TreemapChartSkeletonProps,
|
|
1010
|
+
} from './components/treemap-chart';
|
|
959
1011
|
export {
|
|
960
1012
|
Tree,
|
|
961
1013
|
type TreeProps,
|
package/src/utils/chart.ts
CHANGED
|
@@ -36,12 +36,210 @@ export function useSeriesColor(
|
|
|
36
36
|
return explicit ?? (typeof token === 'string' ? token : FALLBACK_SERIES[index - 1]!);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* The `r, g, b` of a colour string, or `null` for one that cannot be read.
|
|
41
|
+
*
|
|
42
|
+
* Hex and `rgb()` / `rgba()` only, which is every form a theme token and a
|
|
43
|
+
* series colour take. A `null` is the caller's signal to keep whatever it
|
|
44
|
+
* would have used anyway, rather than a guess that could be the wrong end of
|
|
45
|
+
* the scale.
|
|
46
|
+
*/
|
|
47
|
+
function channelsOf(color: string): [number, number, number] | null {
|
|
48
|
+
const value = color.trim().toLowerCase();
|
|
49
|
+
|
|
50
|
+
if (value.startsWith('#')) {
|
|
51
|
+
const body =
|
|
52
|
+
value.length === 4 || value.length === 5
|
|
53
|
+
? value
|
|
54
|
+
.slice(1, 4)
|
|
55
|
+
.split('')
|
|
56
|
+
.map((digit) => digit + digit)
|
|
57
|
+
.join('')
|
|
58
|
+
: value.slice(1, 7);
|
|
59
|
+
if (body.length !== 6 || /[^0-9a-f]/.test(body)) return null;
|
|
60
|
+
return [
|
|
61
|
+
parseInt(body.slice(0, 2), 16),
|
|
62
|
+
parseInt(body.slice(2, 4), 16),
|
|
63
|
+
parseInt(body.slice(4, 6), 16),
|
|
64
|
+
];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const fn = /^rgba?\(([^)]+)\)$/.exec(value);
|
|
68
|
+
if (!fn) return null;
|
|
69
|
+
|
|
70
|
+
const parts = fn[1]!.split(/[\s,/]+/).filter(Boolean);
|
|
71
|
+
if (parts.length < 3) return null;
|
|
72
|
+
|
|
73
|
+
const channels = parts.slice(0, 3).map((raw) => {
|
|
74
|
+
const n = parseFloat(raw);
|
|
75
|
+
if (Number.isNaN(n)) return null;
|
|
76
|
+
return raw.endsWith('%') ? (n / 100) * 255 : n;
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
return channels.some((c) => c === null)
|
|
80
|
+
? null
|
|
81
|
+
: (channels as [number, number, number]);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** One channel, undone from the sRGB transfer curve. */
|
|
85
|
+
function linear(value: number): number {
|
|
86
|
+
const c = Math.min(1, Math.max(0, value / 255));
|
|
87
|
+
return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Relative luminance, 0 for black and 1 for white. */
|
|
91
|
+
function luminanceOf(rgb: [number, number, number]): number {
|
|
92
|
+
return 0.2126 * linear(rgb[0]) + 0.7152 * linear(rgb[1]) + 0.0722 * linear(rgb[2]);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Text laid over a fill: the solid colour and the softer one under it. */
|
|
96
|
+
export interface ChartInk {
|
|
97
|
+
color: string;
|
|
98
|
+
muted: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const LIGHT_INK: ChartInk = { color: '#ffffff', muted: 'rgba(255,255,255,0.82)' };
|
|
102
|
+
const DARK_INK: ChartInk = { color: '#0a0a0a', muted: 'rgba(10,10,10,0.82)' };
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The contrast ratio white keeps on a fill before the label is turned over.
|
|
106
|
+
*
|
|
107
|
+
* 3:1, which is the bar for bold text at this size. Higher and the mid-tone
|
|
108
|
+
* hues every theme uses for its second series onwards flip to dark text as
|
|
109
|
+
* well, which is a different chart rather than a legible one; lower and the
|
|
110
|
+
* pale fills this exists for stay unreadable.
|
|
111
|
+
*/
|
|
112
|
+
const INK_FLOOR = 3;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* The text colour to lay over `fill`: white while white can be read on it,
|
|
116
|
+
* near-black once it cannot.
|
|
117
|
+
*
|
|
118
|
+
* Neither the fill nor the theme can decide this on its own. A series colour is
|
|
119
|
+
* a colour, not a mode — a theme is free to make it near-white, and a fixed
|
|
120
|
+
* white label on it disappears while the foreground token would be just as
|
|
121
|
+
* wrong on the next tile along.
|
|
122
|
+
*
|
|
123
|
+
* White is kept wherever it holds up, rather than always taking whichever ink
|
|
124
|
+
* scores higher. Charts are drawn white-on-hue, and turning over every tile
|
|
125
|
+
* that a darker label would technically suit changes what the chart looks like
|
|
126
|
+
* to fix the two that were unreadable.
|
|
127
|
+
*
|
|
128
|
+
* `behind` and `opacity` are for a fill drawn part-transparent: what the reader
|
|
129
|
+
* sees is the blend, and a half-strength tile on a pale ground is lighter than
|
|
130
|
+
* the colour it was given. Compositing happens in gamma space, the way the
|
|
131
|
+
* renderer does it, before the luminance is taken.
|
|
132
|
+
*/
|
|
133
|
+
/** A colour moved `amount` of the way towards white, or towards black when negative. */
|
|
134
|
+
function tone(color: string, amount: number): string {
|
|
135
|
+
const rgb = channelsOf(color);
|
|
136
|
+
if (!rgb) return color;
|
|
137
|
+
|
|
138
|
+
const target = amount >= 0 ? 255 : 0;
|
|
139
|
+
const t = Math.min(Math.abs(amount), 0.85);
|
|
140
|
+
const [r, g, b] = rgb.map((c) => Math.round(c + (target - c) * t));
|
|
141
|
+
return `rgb(${r}, ${g}, ${b})`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* The colour for the `index`th thing in a chart that draws an unbounded number
|
|
146
|
+
* of them.
|
|
147
|
+
*
|
|
148
|
+
* There are five series tokens, and a chart is routinely asked to draw six
|
|
149
|
+
* things. Taking the palette modulo its length hands two of them the identical
|
|
150
|
+
* colour, which in a chart whose whole job is telling parts apart is a bug the
|
|
151
|
+
* theme decides the severity of — on a theme whose first token is green, the
|
|
152
|
+
* sixth wedge is a second green one.
|
|
153
|
+
*
|
|
154
|
+
* So each further lap through the palette is drawn a step further along a tone
|
|
155
|
+
* run, keeping the theme's own hues rather than inventing new ones.
|
|
156
|
+
*
|
|
157
|
+
* The run goes *away* from whichever end the colour already sits near, and
|
|
158
|
+
* always in that one direction. Alternating lighter and darker would be the
|
|
159
|
+
* obvious thing and it reintroduces the bug on the themes that matter: a token
|
|
160
|
+
* of `#fafafa` lightened is `#fafafa` back again, so the second lap of a
|
|
161
|
+
* near-white palette entry is the same colour it was trying not to be.
|
|
162
|
+
*/
|
|
163
|
+
export function seriesColorAt(palette: string[], index: number): string {
|
|
164
|
+
if (!palette.length) return FALLBACK_SERIES[0]!;
|
|
165
|
+
|
|
166
|
+
const base = palette[index % palette.length]!;
|
|
167
|
+
const lap = Math.floor(index / palette.length);
|
|
168
|
+
if (lap <= 0) return base;
|
|
169
|
+
|
|
170
|
+
const rgb = channelsOf(base);
|
|
171
|
+
const away = rgb && luminanceOf(rgb) > 0.4 ? -1 : 1;
|
|
172
|
+
// Each lap covers two thirds of what is left, so every one lands somewhere
|
|
173
|
+
// the last did not.
|
|
174
|
+
return tone(base, away * (1 - 0.66 ** lap));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function inkOn(fill: string, behind?: string, opacity = 1): ChartInk {
|
|
178
|
+
const front = channelsOf(fill);
|
|
179
|
+
if (!front) return LIGHT_INK;
|
|
180
|
+
|
|
181
|
+
const alpha = Math.min(1, Math.max(0, opacity));
|
|
182
|
+
const ground = alpha < 1 && behind ? channelsOf(behind) : null;
|
|
183
|
+
const seen: [number, number, number] = ground
|
|
184
|
+
? [
|
|
185
|
+
alpha * front[0] + (1 - alpha) * ground[0],
|
|
186
|
+
alpha * front[1] + (1 - alpha) * ground[1],
|
|
187
|
+
alpha * front[2] + (1 - alpha) * ground[2],
|
|
188
|
+
]
|
|
189
|
+
: front;
|
|
190
|
+
|
|
191
|
+
return 1.05 / (luminanceOf(seen) + 0.05) >= INK_FLOOR ? LIGHT_INK : DARK_INK;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** The suffixes, smallest first. Each one is a thousand of the one before it. */
|
|
195
|
+
const MAGNITUDES = ['k', 'M', 'B', 'T'] as const;
|
|
196
|
+
|
|
197
|
+
/** Drop the zeros a fixed decimal count left behind: `12.0` is `12`. */
|
|
198
|
+
function trimZeros(text: string): string {
|
|
199
|
+
return text.includes('.') ? text.replace(/\.?0+$/, '') : text;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* One decimal while the mantissa is a single digit, none after that.
|
|
204
|
+
*
|
|
205
|
+
* `1.2k` and `12k` rather than `1.2k` and `12.4k`: the second decimal place is
|
|
206
|
+
* spurious precision on a tick, and the label has one line to fit in.
|
|
207
|
+
*/
|
|
208
|
+
function mantissa(value: number): string {
|
|
209
|
+
return trimZeros(value.toFixed(Math.abs(value) < 10 ? 1 : 0));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** `12k` rather than `12400` — a readout has one line to say it in. */
|
|
40
213
|
export function compactNumber(value: number): string {
|
|
214
|
+
if (!Number.isFinite(value)) return String(value);
|
|
215
|
+
|
|
41
216
|
const abs = Math.abs(value);
|
|
42
|
-
if (abs
|
|
43
|
-
|
|
44
|
-
|
|
217
|
+
if (abs < 1_000) {
|
|
218
|
+
if (Number.isInteger(value)) return String(value);
|
|
219
|
+
// Below one, a single decimal rounds most values to `0`, which reads as
|
|
220
|
+
// nothing rather than as a small number.
|
|
221
|
+
return trimZeros(value.toFixed(abs < 1 ? 2 : 1));
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
let index = 0;
|
|
225
|
+
let scaled = value / 1_000;
|
|
226
|
+
while (Math.abs(scaled) >= 1_000 && index < MAGNITUDES.length - 1) {
|
|
227
|
+
scaled /= 1_000;
|
|
228
|
+
index += 1;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/*
|
|
232
|
+
* Rounding can push a mantissa over a thousand — 999,999 is `1000.0k` on the
|
|
233
|
+
* way in, which is a thousand thousands written the long way. Formatting
|
|
234
|
+
* first and promoting after is what makes it `1M`.
|
|
235
|
+
*/
|
|
236
|
+
let text = mantissa(scaled);
|
|
237
|
+
if (Math.abs(Number(text)) >= 1_000 && index < MAGNITUDES.length - 1) {
|
|
238
|
+
index += 1;
|
|
239
|
+
text = mantissa(scaled / 1_000);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return `${text}${MAGNITUDES[index]}`;
|
|
45
243
|
}
|
|
46
244
|
|
|
47
245
|
/** The drawable box inside a chart, after its padding is taken off. */
|