neba 1.3.0 → 1.5.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 +7 -7
- package/dist/components/alert/Alert.d.ts +9 -1
- package/dist/components/alert/Alert.js +1 -1
- package/dist/components/area-chart/AreaChart.d.ts +54 -0
- package/dist/components/area-chart/AreaChart.js +1 -0
- package/dist/components/area-chart/index.d.ts +2 -0
- package/dist/components/area-chart/index.js +1 -0
- package/dist/components/bar-chart/BarChart.d.ts +63 -0
- package/dist/components/bar-chart/BarChart.js +1 -0
- package/dist/components/bar-chart/index.d.ts +2 -0
- package/dist/components/bar-chart/index.js +1 -0
- package/dist/components/bottom-navigation/BottomNavigation.d.ts +111 -0
- package/dist/components/bottom-navigation/BottomNavigation.js +1 -0
- package/dist/components/bottom-navigation/index.d.ts +1 -0
- package/dist/components/bottom-navigation/index.js +1 -0
- package/dist/components/breadcrumb/Breadcrumb.d.ts +35 -2
- package/dist/components/breadcrumb/Breadcrumb.js +1 -1
- package/dist/components/button/Button.d.ts +16 -0
- package/dist/components/button/Button.js +1 -1
- package/dist/components/carousel/Carousel.d.ts +10 -4
- package/dist/components/carousel/Carousel.js +1 -1
- package/dist/components/chip/Chip.d.ts +9 -1
- package/dist/components/chip/Chip.js +1 -1
- package/dist/components/collapsible/Collapsible.d.ts +88 -0
- package/dist/components/collapsible/Collapsible.js +1 -0
- package/dist/components/collapsible/index.d.ts +1 -0
- package/dist/components/collapsible/index.js +1 -0
- package/dist/components/combobox/Combobox.d.ts +19 -7
- package/dist/components/combobox/Combobox.js +1 -1
- package/dist/components/data-table/DataTable.d.ts +323 -0
- package/dist/components/data-table/DataTable.js +1 -0
- package/dist/components/data-table/index.d.ts +2 -0
- package/dist/components/data-table/index.js +1 -0
- package/dist/components/dialog/Dialog.d.ts +11 -5
- package/dist/components/dialog/Dialog.js +1 -1
- package/dist/components/drawer/Drawer.d.ts +11 -5
- package/dist/components/drawer/Drawer.js +1 -1
- package/dist/components/empty/Empty.d.ts +69 -0
- package/dist/components/empty/Empty.js +1 -0
- package/dist/components/empty/index.d.ts +2 -0
- package/dist/components/empty/index.js +1 -0
- package/dist/components/file-picker/FilePicker.d.ts +1 -3
- package/dist/components/file-picker/FilePicker.js +1 -1
- package/dist/components/floating-action-button/FloatingActionButton.d.ts +146 -0
- package/dist/components/floating-action-button/FloatingActionButton.js +1 -0
- package/dist/components/floating-action-button/index.d.ts +1 -0
- package/dist/components/floating-action-button/index.js +1 -0
- package/dist/components/heatmap-chart/HeatmapChart.d.ts +69 -0
- package/dist/components/heatmap-chart/HeatmapChart.js +1 -0
- package/dist/components/heatmap-chart/index.d.ts +2 -0
- package/dist/components/heatmap-chart/index.js +1 -0
- package/dist/components/highlight/Highlight.js +1 -1
- package/dist/components/line-chart/LineChart.d.ts +74 -0
- package/dist/components/line-chart/LineChart.js +1 -0
- package/dist/components/line-chart/index.d.ts +2 -0
- package/dist/components/line-chart/index.js +1 -0
- package/dist/components/mockup/Mockup.d.ts +151 -0
- package/dist/components/mockup/Mockup.js +1 -0
- package/dist/components/mockup/index.d.ts +2 -0
- package/dist/components/mockup/index.js +1 -0
- package/dist/components/number-field/NumberField.d.ts +10 -6
- package/dist/components/number-field/NumberField.js +1 -1
- package/dist/components/overlay/Overlay.d.ts +11 -6
- package/dist/components/overlay/Overlay.js +1 -1
- package/dist/components/pagination/Pagination.d.ts +28 -6
- package/dist/components/pagination/Pagination.js +1 -1
- package/dist/components/panes/Panes.js +1 -1
- package/dist/components/pie-chart/PieChart.d.ts +58 -0
- package/dist/components/pie-chart/PieChart.js +1 -0
- package/dist/components/pie-chart/index.d.ts +2 -0
- package/dist/components/pie-chart/index.js +1 -0
- package/dist/components/popover/Popover.d.ts +11 -5
- package/dist/components/popover/Popover.js +1 -1
- package/dist/components/progress-box/ProgressBox.js +1 -1
- package/dist/components/progress-circular/ProgressCircular.js +1 -1
- package/dist/components/progress-linear/ProgressLinear.js +1 -1
- package/dist/components/rating/Rating.d.ts +100 -0
- package/dist/components/rating/Rating.js +1 -0
- package/dist/components/rating/index.d.ts +1 -0
- package/dist/components/rating/index.js +1 -0
- package/dist/components/scatter-chart/ScatterChart.d.ts +45 -0
- package/dist/components/scatter-chart/ScatterChart.js +1 -0
- package/dist/components/scatter-chart/index.d.ts +2 -0
- package/dist/components/scatter-chart/index.js +1 -0
- package/dist/components/select/Select.d.ts +1 -3
- package/dist/components/select/Select.js +1 -1
- package/dist/components/sparkline/Sparkline.d.ts +67 -0
- package/dist/components/sparkline/Sparkline.js +1 -0
- package/dist/components/sparkline/index.d.ts +2 -0
- package/dist/components/sparkline/index.js +1 -0
- package/dist/components/statistic/Statistic.d.ts +7 -0
- package/dist/components/statistic/Statistic.js +1 -1
- package/dist/components/table/Table.d.ts +13 -2
- package/dist/components/table/Table.js +1 -1
- package/dist/components/text-link/TextLink.js +1 -1
- package/dist/components/time-picker/TimePicker.js +1 -1
- package/dist/components/timeline-chart/TimelineChart.d.ts +44 -0
- package/dist/components/timeline-chart/TimelineChart.js +1 -0
- package/dist/components/timeline-chart/index.d.ts +2 -0
- package/dist/components/timeline-chart/index.js +1 -0
- package/dist/components/toast/Toast.d.ts +10 -2
- package/dist/components/toast/Toast.js +1 -1
- package/dist/components/tooltip/Tooltip.d.ts +2 -4
- package/dist/components/tooltip/Tooltip.js +1 -1
- package/dist/components/tree-view/TreeView.js +1 -1
- package/dist/index.d.ts +15 -0
- package/dist/index.js +1 -1
- package/dist/internal/animate.d.ts +0 -9
- package/dist/internal/animate.js +1 -1
- package/dist/internal/calendar.js +1 -1
- package/dist/internal/chart-frame.d.ts +409 -0
- package/dist/internal/chart-frame.js +1 -0
- package/dist/internal/chart-line.d.ts +44 -0
- package/dist/internal/chart-line.js +1 -0
- package/dist/internal/chart.d.ts +456 -0
- package/dist/internal/chart.js +1 -0
- package/dist/internal/data-table.d.ts +170 -0
- package/dist/internal/data-table.js +1 -0
- package/dist/internal/date.d.ts +0 -2
- package/dist/internal/date.js +1 -1
- package/dist/internal/format.d.ts +29 -0
- package/dist/internal/format.js +1 -0
- package/dist/internal/i18n.d.ts +166 -0
- package/dist/internal/i18n.js +1 -1
- package/dist/internal/icons.d.ts +2 -0
- package/dist/internal/icons.js +1 -1
- package/dist/internal/mockup.d.ts +193 -0
- package/dist/internal/mockup.js +1 -0
- package/dist/internal/picker.d.ts +2 -4
- package/dist/internal/picker.js +1 -1
- package/dist/internal/progress.d.ts +1 -3
- package/dist/styles.css +1 -1
- package/dist/tailwind.css +231 -11
- package/dist/types.d.ts +214 -0
- package/package.json +19 -15
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The arithmetic every chart is made of.
|
|
3
|
+
*
|
|
4
|
+
* Here rather than in a component for the reason `progress.ts` is: five
|
|
5
|
+
* components draw five different marks and ask exactly the same four questions
|
|
6
|
+
* first — what is the range, where does a value land in the plot, what are the
|
|
7
|
+
* clean numbers to tick at, and what colour is series four. A chart file that
|
|
8
|
+
* also has to answer those is a file where the drawing cannot be read.
|
|
9
|
+
*
|
|
10
|
+
* There is no React in here and nothing in it knows what an SVG is. What it
|
|
11
|
+
* knows is data and pixels; `chart-frame.tsx` is where those become elements.
|
|
12
|
+
*
|
|
13
|
+
* The scales are deliberately not a `d3-scale`. A linear scale is six lines, a
|
|
14
|
+
* band scale is four, and `nice numbers` is twelve — and the package still has
|
|
15
|
+
* one runtime dependency, which is the same trade `color.ts` makes.
|
|
16
|
+
*/
|
|
17
|
+
import type * as React from 'react';
|
|
18
|
+
import type { NebaChartCategory, NebaChartDatum, NebaChartSeries, NebaDensity, NebaSize } from '../types';
|
|
19
|
+
/**
|
|
20
|
+
* How tall a plot is when nobody said, in pixels.
|
|
21
|
+
*
|
|
22
|
+
* A chart is one of the few things in the library with no intrinsic height — it
|
|
23
|
+
* is as tall as it is given — so this ladder is what stops every chart on a
|
|
24
|
+
* dashboard being a different shape. The steps climb faster than the control
|
|
25
|
+
* ladder because the thing being scaled is a *picture*: at `xs` this is a strip
|
|
26
|
+
* beside a number, at `xl` it is what the screen is about.
|
|
27
|
+
*
|
|
28
|
+
* The axis band is drawn *inside* this, not added to it. A card sized to the
|
|
29
|
+
* plot and then handed axis labels is the card that grows a two-line scrollbar.
|
|
30
|
+
*/
|
|
31
|
+
export declare const plotHeights: Record<NebaSize, number>;
|
|
32
|
+
/**
|
|
33
|
+
* A Sparkline's own ladder, which is a different object: it has no axes, no
|
|
34
|
+
* legend and nothing to read off it but the shape, so it is sized against the
|
|
35
|
+
* line of text it sits next to rather than against the page.
|
|
36
|
+
*/
|
|
37
|
+
export declare const sparklineHeights: Record<NebaSize, number>;
|
|
38
|
+
/**
|
|
39
|
+
* The weight of a line, in pixels. `md` is 2, which is the width a data line
|
|
40
|
+
* wants everywhere — thin enough to stay a line where two of them cross, heavy
|
|
41
|
+
* enough to hold a hue at 3:1.
|
|
42
|
+
*/
|
|
43
|
+
export declare const lineWidths: Record<NebaSize, number>;
|
|
44
|
+
/**
|
|
45
|
+
* The radius of a marker. `md` is 4, so the dot is 8px across before its ring —
|
|
46
|
+
* the floor below which a marker stops being something a pointer can find.
|
|
47
|
+
*/
|
|
48
|
+
export declare const markerRadii: Record<NebaSize, number>;
|
|
49
|
+
/**
|
|
50
|
+
* Tick and label type, in pixels rather than as a class.
|
|
51
|
+
*
|
|
52
|
+
* SVG text does not inherit a Tailwind utility usefully — the `<text>` has to
|
|
53
|
+
* carry a `font-size` the layout arithmetic can also read, because the room the
|
|
54
|
+
* axis reserves is measured from it. These are `metaTextClasses` as numbers;
|
|
55
|
+
* keep the two in step.
|
|
56
|
+
*/
|
|
57
|
+
export declare const chartFontSizes: Record<NebaSize, number>;
|
|
58
|
+
/**
|
|
59
|
+
* How thick a bar is allowed to get, in pixels.
|
|
60
|
+
*
|
|
61
|
+
* A cap and not a width: the band a bar sits in is whatever the plot divided by
|
|
62
|
+
* the category count gives, and a bar that fills its band leaves the chart with
|
|
63
|
+
* no air in it at all. Past this the leftover stays as space.
|
|
64
|
+
*/
|
|
65
|
+
export declare const barMaxThickness: Record<NebaSize, number>;
|
|
66
|
+
/**
|
|
67
|
+
* How much of a band the bars in it take, before the cap above applies.
|
|
68
|
+
* `density` is the only thing that moves it — the same rule as everywhere else,
|
|
69
|
+
* spacing and nothing but spacing.
|
|
70
|
+
*/
|
|
71
|
+
export declare const barBandRatio: Record<NebaDensity, number>;
|
|
72
|
+
/** The gap the surface shows through between two touching marks, in pixels. */
|
|
73
|
+
export declare const markGap = 2;
|
|
74
|
+
/** The corner cut off the data end of a bar. Square at the baseline. */
|
|
75
|
+
export declare const barRadius = 4;
|
|
76
|
+
/** The eight slots, as the `var()`s that resolve them per theme. */
|
|
77
|
+
export declare const chartPalette: readonly string[];
|
|
78
|
+
/** A `NebaColor` resolves to its readable-on-surface accent; anything else is CSS. */
|
|
79
|
+
export declare function resolveColor(value: string): string;
|
|
80
|
+
/**
|
|
81
|
+
* What colour a mark is, in the fixed order the palette is handed out in.
|
|
82
|
+
*
|
|
83
|
+
* `index` is the series' place in the array it was passed in, not its place
|
|
84
|
+
* among the ones currently visible. That is the whole point: filtering a legend
|
|
85
|
+
* must not repaint the survivors, because a reader who learned that Europe is
|
|
86
|
+
* blue has learned something that a re-render is not allowed to take back.
|
|
87
|
+
*
|
|
88
|
+
* Past the eighth slot it wraps, and a chart that gets there should not have —
|
|
89
|
+
* a ninth hue is indistinguishable from one of the first eight under colour
|
|
90
|
+
* vision deficiency no matter which one is chosen. Fold the tail into an
|
|
91
|
+
* "Other" series, or draw a second chart.
|
|
92
|
+
*/
|
|
93
|
+
export declare function seriesColor(series: Pick<NebaChartSeries, 'color'> | undefined, index: number, palette?: readonly string[]): string;
|
|
94
|
+
/** A datum unpacked into the shape the drawing code reads. */
|
|
95
|
+
export interface ChartValue {
|
|
96
|
+
value: number | null;
|
|
97
|
+
x?: NebaChartCategory;
|
|
98
|
+
z?: number;
|
|
99
|
+
color?: string;
|
|
100
|
+
label?: React.ReactNode;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* One datum, whichever of the three ways it was written.
|
|
104
|
+
*
|
|
105
|
+
* `NaN` is folded into `null` here rather than at every call site: it arrives
|
|
106
|
+
* from a division somewhere upstream, it means the same thing a gap means, and
|
|
107
|
+
* a scale that is handed one produces a path with the letters `NaN` in it —
|
|
108
|
+
* which fails silently as a blank chart rather than loudly as an error.
|
|
109
|
+
*/
|
|
110
|
+
export declare function toValue(datum: NebaChartDatum): ChartValue;
|
|
111
|
+
/** Every series unpacked, in the order it was given. */
|
|
112
|
+
export declare function toValues(series: readonly NebaChartSeries[]): ChartValue[][];
|
|
113
|
+
/**
|
|
114
|
+
* A category as a number, for a category axis that is really a value axis.
|
|
115
|
+
*
|
|
116
|
+
* A `Date` is its epoch milliseconds, which is what makes a scatter of
|
|
117
|
+
* timestamps work at all. A string is not a position on a number line, so it
|
|
118
|
+
* comes back `null` rather than `NaN` — the same rule `toValue` follows, and
|
|
119
|
+
* for the same reason: a `NaN` reaches the scale and leaves the letters in the
|
|
120
|
+
* path.
|
|
121
|
+
*/
|
|
122
|
+
export declare function toNumber(value: NebaChartCategory | undefined): number | null;
|
|
123
|
+
/**
|
|
124
|
+
* Where one point sits along a category axis that runs on numbers.
|
|
125
|
+
*
|
|
126
|
+
* The same three sources `categoryAt` reads, in the same order — but per
|
|
127
|
+
* *point* rather than per column, because on a scatter each series has its own
|
|
128
|
+
* x at every index and there is no column for them to share.
|
|
129
|
+
*/
|
|
130
|
+
export declare function pointX(value: ChartValue, index: number, categories: readonly NebaChartCategory[] | undefined): number | null;
|
|
131
|
+
/**
|
|
132
|
+
* The extent of the category values, for a chart whose x is a number.
|
|
133
|
+
*
|
|
134
|
+
* Only points that have a `y` count. A point with no value is not on the plot,
|
|
135
|
+
* so letting its `x` stretch the axis would leave a margin of empty plot
|
|
136
|
+
* standing in for data that was never drawn.
|
|
137
|
+
*/
|
|
138
|
+
export declare function categoryExtent(values: readonly ChartValue[][], categories: readonly NebaChartCategory[] | undefined): {
|
|
139
|
+
min: number;
|
|
140
|
+
max: number;
|
|
141
|
+
} | null;
|
|
142
|
+
/**
|
|
143
|
+
* The radius a bubble gets for its `z`, in pixels.
|
|
144
|
+
*
|
|
145
|
+
* `z` is an **area** and not a radius, which is the single most common way a
|
|
146
|
+
* bubble chart lies: encode it as a radius and a value twice as large draws a
|
|
147
|
+
* mark four times the size. The square root is what makes the ink on the page
|
|
148
|
+
* proportional to the number behind it.
|
|
149
|
+
*
|
|
150
|
+
* `min` is a floor rather than a scale — a bubble for a small-but-real value
|
|
151
|
+
* has to stay something a pointer can find, and a zero is the only thing
|
|
152
|
+
* allowed to disappear.
|
|
153
|
+
*/
|
|
154
|
+
export declare function bubbleRadius(z: number, maxZ: number, max: number, min: number): number;
|
|
155
|
+
/** How many categories the widest series has. */
|
|
156
|
+
export declare function categoryCount(series: readonly NebaChartSeries[]): number;
|
|
157
|
+
/**
|
|
158
|
+
* What the category axis says at position `index`.
|
|
159
|
+
*
|
|
160
|
+
* `categories` wins, then whatever the first series that has one calls its own
|
|
161
|
+
* point, then the index. Three sources rather than one because a chart is
|
|
162
|
+
* written both ways in the wild — a column of labels beside a column of
|
|
163
|
+
* numbers, or points that carry their own `x` — and neither is wrong.
|
|
164
|
+
*/
|
|
165
|
+
export declare function categoryAt(index: number, categories: readonly NebaChartCategory[] | undefined, values: readonly ChartValue[][]): NebaChartCategory;
|
|
166
|
+
/**
|
|
167
|
+
* The extent of the values, with the stacking rule applied.
|
|
168
|
+
*
|
|
169
|
+
* Stacked charts measure the *totals* and not the parts, and the two arms are
|
|
170
|
+
* accumulated separately so a series that goes negative does not shorten the
|
|
171
|
+
* bar above it. An all-`null` chart has no extent at all, which is what the
|
|
172
|
+
* `null` return says — the caller draws its empty state rather than an axis
|
|
173
|
+
* from `Infinity` to `-Infinity`.
|
|
174
|
+
*/
|
|
175
|
+
export declare function extentOf(values: readonly ChartValue[][], stacked: boolean): {
|
|
176
|
+
min: number;
|
|
177
|
+
max: number;
|
|
178
|
+
} | null;
|
|
179
|
+
/** A value scale: where it starts, where it ends, and what it ticks at. */
|
|
180
|
+
export interface ValueScale {
|
|
181
|
+
min: number;
|
|
182
|
+
max: number;
|
|
183
|
+
ticks: number[];
|
|
184
|
+
/** A value → a fraction of the plot, `0` at `min` and `1` at `max`. */
|
|
185
|
+
fraction: (value: number) => number;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* The scale a value axis runs on, rounded out to clean numbers.
|
|
189
|
+
*
|
|
190
|
+
* Rounding *outward* is the part that matters: a maximum of 4,830 becomes 5,000
|
|
191
|
+
* and not 4,830, so the top tick is a number and the tallest bar stops short of
|
|
192
|
+
* the ceiling. A scale whose last bar touches the frame reads as clipped even
|
|
193
|
+
* when it is exactly right.
|
|
194
|
+
*
|
|
195
|
+
* Zero is included unless the caller says otherwise, because bar length is only
|
|
196
|
+
* proportional to value when the baseline is zero. A line chart of a quantity
|
|
197
|
+
* that never approaches zero is the case for passing `min` — and it is a case
|
|
198
|
+
* the caller has to make, not one the chart makes for them.
|
|
199
|
+
*/
|
|
200
|
+
export declare function valueScale(extent: {
|
|
201
|
+
min: number;
|
|
202
|
+
max: number;
|
|
203
|
+
} | null, options?: {
|
|
204
|
+
min?: number;
|
|
205
|
+
max?: number;
|
|
206
|
+
tickCount?: number;
|
|
207
|
+
/** Keeps zero in range. Off for a line chart told an explicit `min`. */
|
|
208
|
+
includeZero?: boolean;
|
|
209
|
+
}): ValueScale;
|
|
210
|
+
/** The units a time axis is allowed to step in. */
|
|
211
|
+
export type TimeUnit = 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year';
|
|
212
|
+
/** A value scale whose numbers are instants, and the unit its ticks step in. */
|
|
213
|
+
export interface TimeScale extends ValueScale {
|
|
214
|
+
unit: TimeUnit;
|
|
215
|
+
/** How many of that unit each step covers — 1, 5, 15 minutes and so on. */
|
|
216
|
+
step: number;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* The scale a time axis runs on, ticking where a calendar ticks.
|
|
220
|
+
*
|
|
221
|
+
* The ends round *outward* to the step for the reason `valueScale`'s do: a span
|
|
222
|
+
* that starts exactly on the left edge reads as clipped rather than as
|
|
223
|
+
* starting there. Past a year the 1-2-5 family comes back, because above the
|
|
224
|
+
* year time really is decimal — decades and centuries are the only units left.
|
|
225
|
+
*/
|
|
226
|
+
export declare function timeScale(extent: {
|
|
227
|
+
min: number;
|
|
228
|
+
max: number;
|
|
229
|
+
} | null, options?: {
|
|
230
|
+
min?: number;
|
|
231
|
+
max?: number;
|
|
232
|
+
tickCount?: number;
|
|
233
|
+
}): TimeScale;
|
|
234
|
+
/** One instant on a time axis, written unambiguously — for a tooltip or a table. */
|
|
235
|
+
export declare function formatTimeValue(value: number, unit: TimeUnit, locale?: string): string;
|
|
236
|
+
/**
|
|
237
|
+
* A whole axis of ticks, written the way an axis is read.
|
|
238
|
+
*
|
|
239
|
+
* The year is decided for the axis rather than for each tick, and that is the
|
|
240
|
+
* part worth explaining. Writing it only where it *changes* is what a reader
|
|
241
|
+
* wants and is not safe here: the labels are thinned again downstream, by a
|
|
242
|
+
* stride measured against the plot's real width, and the tick the year was
|
|
243
|
+
* riding on is exactly the one that gets dropped — leaving `Oct 2025 · Dec ·
|
|
244
|
+
* Feb` with nothing to say which year February is in.
|
|
245
|
+
*
|
|
246
|
+
* So: an axis inside one year names it once, on the first tick, which is the
|
|
247
|
+
* one tick a stride never removes. An axis that crosses a year names it on
|
|
248
|
+
* every tick, so whichever ones survive are each unambiguous. Wider labels mean
|
|
249
|
+
* a heavier stride, and a heavier stride is the better failure.
|
|
250
|
+
*/
|
|
251
|
+
export declare function formatTimeTicks(ticks: readonly number[], unit: TimeUnit, locale?: string): string[];
|
|
252
|
+
/**
|
|
253
|
+
* How many ticks a category axis can show before the labels collide, and which
|
|
254
|
+
* ones they are.
|
|
255
|
+
*
|
|
256
|
+
* Every nth label rather than rotating them: a rotated axis is unreadable at a
|
|
257
|
+
* glance and it steals a band of the plot to be unreadable in. `n` is chosen so
|
|
258
|
+
* the labels clear each other at the measured width, and it always keeps the
|
|
259
|
+
* first — a reader who cannot see where the axis starts cannot read any of it.
|
|
260
|
+
*/
|
|
261
|
+
export declare function tickStride(count: number, available: number, labelWidth: number): number;
|
|
262
|
+
/**
|
|
263
|
+
* Whether the label at `index` survives the stride.
|
|
264
|
+
*
|
|
265
|
+
* Every nth, and — when it fits — the last one, which is the part a plain
|
|
266
|
+
* modulo gets wrong: a fourteen-day axis at a stride of two ends at day
|
|
267
|
+
* thirteen, and a percentage axis ends at 80%. The end of a scale is the number
|
|
268
|
+
* a reader looks for first, and dropping it to keep the arithmetic tidy is the
|
|
269
|
+
* wrong trade.
|
|
270
|
+
*
|
|
271
|
+
* `roomForLast` is the other half of it and is measured rather than guessed —
|
|
272
|
+
* see `fitsLast`. Forcing a label that does not fit turns a missing "Jun" into
|
|
273
|
+
* an overlapping "MayJun", which is worse than what it fixed.
|
|
274
|
+
*/
|
|
275
|
+
export declare function showsTick(index: number, count: number, stride: number, roomForLast: boolean): boolean;
|
|
276
|
+
/**
|
|
277
|
+
* Whether the last label clears the last one the stride kept.
|
|
278
|
+
*
|
|
279
|
+
* The two are `(count - 1) % stride` steps apart, and they need half of each
|
|
280
|
+
* label plus a little air between them — labels are centred on their tick, so
|
|
281
|
+
* only the inner halves can collide.
|
|
282
|
+
*/
|
|
283
|
+
export declare function fitsLast(count: number, stride: number, step: number, labelWidth: number): boolean;
|
|
284
|
+
/**
|
|
285
|
+
* Roughly how wide a string renders at a given font size.
|
|
286
|
+
*
|
|
287
|
+
* An estimate on purpose. The alternative is a canvas measurement per label per
|
|
288
|
+
* render, which is a layout read on a path that runs on every resize — and what
|
|
289
|
+
* this number is used for is deciding how much room to reserve, where being a
|
|
290
|
+
* few pixels generous costs nothing and being exact costs a reflow.
|
|
291
|
+
*
|
|
292
|
+
* 0.6em is the average advance of a digit in the sans-serifs a UI runs in;
|
|
293
|
+
* anything CJK is close to a full em, so the widest character decides.
|
|
294
|
+
*/
|
|
295
|
+
export declare function textWidth(text: string, fontSize: number): number;
|
|
296
|
+
/**
|
|
297
|
+
* A label cut to the room it has, with an ellipsis.
|
|
298
|
+
*
|
|
299
|
+
* The alternative when a category name is wider than its slot is to drop
|
|
300
|
+
* labels until the survivors fit, and on five categories called things like
|
|
301
|
+
* "Onboarding flow" that leaves exactly one of them on the axis — an axis with
|
|
302
|
+
* one label is not a shorter axis, it is an unlabelled one. Cut instead: the
|
|
303
|
+
* first few characters distinguish five words, and the tooltip and the table
|
|
304
|
+
* both still have the whole thing.
|
|
305
|
+
*/
|
|
306
|
+
export declare function truncate(text: string, maxWidth: number, fontSize: number): string;
|
|
307
|
+
/** The plot's box inside the chart, once the axes have taken their bands. */
|
|
308
|
+
export interface PlotBox {
|
|
309
|
+
left: number;
|
|
310
|
+
top: number;
|
|
311
|
+
width: number;
|
|
312
|
+
height: number;
|
|
313
|
+
}
|
|
314
|
+
/** A band scale: one slot per category, with the marks centred in it. */
|
|
315
|
+
export interface BandScale {
|
|
316
|
+
/** The centre of category `index`, in pixels along the axis. */
|
|
317
|
+
centre: (index: number) => number;
|
|
318
|
+
/** How wide one slot is. */
|
|
319
|
+
step: number;
|
|
320
|
+
/** How wide the marks in a slot are allowed to be, together. */
|
|
321
|
+
band: number;
|
|
322
|
+
}
|
|
323
|
+
export declare function bandScale(count: number, length: number, ratio: number): BandScale;
|
|
324
|
+
/**
|
|
325
|
+
* A path through the points, in whichever of the three shapes was asked for.
|
|
326
|
+
*
|
|
327
|
+
* `null` breaks the path rather than interpolating across it — the `M` that
|
|
328
|
+
* starts a new subpath is the gap. A line that bridges a missing month is a
|
|
329
|
+
* line that invents a number.
|
|
330
|
+
*
|
|
331
|
+
* `smooth` is a monotone cubic and not a Catmull-Rom, which is not a detail: a
|
|
332
|
+
* plain spline overshoots between two close points, so a series that never goes
|
|
333
|
+
* below zero draws a curve that does. A chart is allowed to be curved and it is
|
|
334
|
+
* not allowed to show a value that is not in the data.
|
|
335
|
+
*/
|
|
336
|
+
export declare function linePath(points: readonly ({
|
|
337
|
+
x: number;
|
|
338
|
+
y: number;
|
|
339
|
+
} | null)[], curve: 'linear' | 'smooth' | 'step'): string;
|
|
340
|
+
/**
|
|
341
|
+
* The same path closed down to a baseline, for an area.
|
|
342
|
+
*
|
|
343
|
+
* Built from the runs rather than from the whole line so a gap is a gap in the
|
|
344
|
+
* fill too — an area that closes across a missing month fills in a value that
|
|
345
|
+
* was never measured, which is the same lie the bridged line tells, painted
|
|
346
|
+
* over a larger part of the chart.
|
|
347
|
+
*/
|
|
348
|
+
export declare function areaPath(points: readonly ({
|
|
349
|
+
x: number;
|
|
350
|
+
y: number;
|
|
351
|
+
} | null)[], baseline: readonly ({
|
|
352
|
+
x: number;
|
|
353
|
+
y: number;
|
|
354
|
+
} | null)[] | number, curve: 'linear' | 'smooth' | 'step'): string;
|
|
355
|
+
/**
|
|
356
|
+
* A rectangle with the two corners at its *data end* cut off.
|
|
357
|
+
*
|
|
358
|
+
* Rounded at the end and square at the baseline, which is not a stylistic
|
|
359
|
+
* split: a bar that is rounded where it meets the axis has lost the exact
|
|
360
|
+
* moment it starts, and a row of them turns the baseline into a scalloped edge.
|
|
361
|
+
* The end is where the value is, and that is the end worth softening.
|
|
362
|
+
*
|
|
363
|
+
* The radius shrinks to fit rather than clipping, so a bar two pixels tall is a
|
|
364
|
+
* bar and not a circle. `end` is which way the value grows.
|
|
365
|
+
*/
|
|
366
|
+
export declare function barPath(x: number, y: number, width: number, height: number, radius: number, end: 'up' | 'down' | 'left' | 'right'): string;
|
|
367
|
+
/**
|
|
368
|
+
* The shapes a point mark can take.
|
|
369
|
+
*
|
|
370
|
+
* The dependable second identity channel, and the only chart form with one
|
|
371
|
+
* going spare: a line cannot be a triangle and a bar cannot be a cross, but a
|
|
372
|
+
* dot can be anything. Five is enough for every palette a chart is allowed.
|
|
373
|
+
*/
|
|
374
|
+
export type MarkShape = 'circle' | 'square' | 'triangle' | 'diamond' | 'cross';
|
|
375
|
+
/** The order shapes are handed out in, matching the palette: fixed, never cycled. */
|
|
376
|
+
export declare const markShapes: readonly MarkShape[];
|
|
377
|
+
/**
|
|
378
|
+
* One point mark, as a path, centred on `cx`/`cy` and covering the same area a
|
|
379
|
+
* circle of radius `r` would.
|
|
380
|
+
*
|
|
381
|
+
* A circle comes back as a path too rather than as a `<circle>`, so whoever
|
|
382
|
+
* draws the marks writes one element and not a branch — the ring, the fill and
|
|
383
|
+
* the hover treatment are then unarguably the same on all five.
|
|
384
|
+
*/
|
|
385
|
+
export declare function markPath(shape: MarkShape, cx: number, cy: number, r: number): string;
|
|
386
|
+
/**
|
|
387
|
+
* A slice of a ring, as a path.
|
|
388
|
+
*
|
|
389
|
+
* `inner` of 0 is a pie and anything above it is a donut. Angles are degrees
|
|
390
|
+
* clockwise from twelve o'clock, which is where a reader starts reading a
|
|
391
|
+
* circle — SVG's own zero is at three o'clock, and the offset is applied here
|
|
392
|
+
* once rather than at four call sites.
|
|
393
|
+
*/
|
|
394
|
+
export declare function arcPath(cx: number, cy: number, outer: number, inner: number, from: number, to: number): string;
|
|
395
|
+
/** Which way a magnitude is coloured. */
|
|
396
|
+
export type ChartScaleKind = 'sequential' | 'diverging';
|
|
397
|
+
/** How many steps each ramp has. Five, and the reason is in `styles.css`. */
|
|
398
|
+
export declare const rampSteps = 5;
|
|
399
|
+
/**
|
|
400
|
+
* The step a value lands on, and the ink a label on it wears.
|
|
401
|
+
*
|
|
402
|
+
* A magnitude is not an identity, so it does not come off the eight-slot
|
|
403
|
+
* categorical ramp — see the note above `--neba-chart-seq-1`. It comes off a
|
|
404
|
+
* one-hue ladder, and which rung is arithmetic on the value.
|
|
405
|
+
*
|
|
406
|
+
* A diverging scale is read from its *middle* rather than from its bottom, so
|
|
407
|
+
* it is the distance either side of the neutral that is scaled — and by the
|
|
408
|
+
* larger of the two arms, so a set running from −2 to +40 does not paint every
|
|
409
|
+
* negative the deepest blue there is.
|
|
410
|
+
*/
|
|
411
|
+
export declare function rampStep(value: number, min: number, max: number, kind: ChartScaleKind, midpoint?: number): number;
|
|
412
|
+
/** The `var()` for a step of the ramp, and for the ink that reads on it. */
|
|
413
|
+
export declare function rampFill(step: number, kind: ChartScaleKind): string;
|
|
414
|
+
export declare function rampInk(step: number, kind: ChartScaleKind): string;
|
|
415
|
+
/** One tile, in pixels, and which value it came from. */
|
|
416
|
+
export interface TreemapTile {
|
|
417
|
+
index: number;
|
|
418
|
+
x: number;
|
|
419
|
+
y: number;
|
|
420
|
+
width: number;
|
|
421
|
+
height: number;
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* A squarified treemap: the values as boxes whose areas are proportional, laid
|
|
425
|
+
* out as close to square as they can be got.
|
|
426
|
+
*
|
|
427
|
+
* Squarified rather than sliced, and the difference is the whole reason the
|
|
428
|
+
* forty lines are worth it. A slice-and-dice treemap of twenty values ends in
|
|
429
|
+
* slivers a pixel wide, and a sliver's *area* is unreadable however exact it is
|
|
430
|
+
* — the reader compares its length instead, which is not the encoded quantity.
|
|
431
|
+
* Bruls, Huizing and van Wijk's answer is greedy and simple: fill a row along
|
|
432
|
+
* the box's shorter side, keep adding to it while the worst aspect ratio in it
|
|
433
|
+
* improves, and start a new row the moment it stops.
|
|
434
|
+
*
|
|
435
|
+
* The order is the caller's; the layout sorts descending internally because the
|
|
436
|
+
* algorithm needs it and hands the original index back on every tile, so a
|
|
437
|
+
* tile's colour and its name are still its own.
|
|
438
|
+
*/
|
|
439
|
+
export declare function squarify(values: readonly number[], width: number, height: number): TreemapTile[];
|
|
440
|
+
/**
|
|
441
|
+
* How a category is written when nobody said.
|
|
442
|
+
*
|
|
443
|
+
* A `Date` gets the reader's own short form, because the alternative is an ISO
|
|
444
|
+
* string across the bottom of every time series. Everything else is `String`,
|
|
445
|
+
* which is what the caller wrote it as.
|
|
446
|
+
*/
|
|
447
|
+
export declare function formatCategory(value: NebaChartCategory, locale?: string): string;
|
|
448
|
+
/**
|
|
449
|
+
* A number, compactly enough that a y-axis of thousands is not four labels of
|
|
450
|
+
* seven characters.
|
|
451
|
+
*
|
|
452
|
+
* Only when the caller passed no `format` of their own — the moment they do,
|
|
453
|
+
* they have said what the number means and the library's opinion about
|
|
454
|
+
* thousands separators stops being welcome.
|
|
455
|
+
*/
|
|
456
|
+
export declare function compactNumber(value: number, locale?: string): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{dateFormatter as t,numberFormatter as e}from"./format";export const plotHeights={xs:120,sm:160,md:220,lg:280,xl:360};export const sparklineHeights={xs:16,sm:20,md:28,lg:40,xl:56};export const lineWidths={xs:1.5,sm:1.75,md:2,lg:2.25,xl:2.5};export const markerRadii={xs:3,sm:3.5,md:4,lg:4.5,xl:5};export const chartFontSizes={xs:10,sm:11,md:12,lg:13,xl:14};export const barMaxThickness={xs:14,sm:18,md:24,lg:30,xl:36};export const barBandRatio={default:.62,compact:.82};export const markGap=2;export const barRadius=4;export const chartPalette=["var(--neba-chart-1)","var(--neba-chart-2)","var(--neba-chart-3)","var(--neba-chart-4)","var(--neba-chart-5)","var(--neba-chart-6)","var(--neba-chart-7)","var(--neba-chart-8)"];const n=new Set(["primary","secondary","success","warning","danger","info"]);export function resolveColor(t){return n.has(t)?`var(--neba-${t}-accent)`:t}export function seriesColor(t,e,n=chartPalette){return resolveColor(t?.color?t.color:n[e%n.length]??chartPalette[0])}export function toValue(t){return null==t?{value:null}:"number"==typeof t?{value:Number.isFinite(t)?t:null}:(t=>"object"==typeof t&&null!==t)(t)?{value:null!==t.y&&Number.isFinite(t.y)?t.y:null,x:t.x,z:t.z,color:t.color?resolveColor(t.color):void 0,label:t.label}:{value:null}}export function toValues(t){return t.map(t=>t.data.map(toValue))}export function toNumber(t){return t instanceof Date?Number.isFinite(t.getTime())?t.getTime():null:"number"==typeof t&&Number.isFinite(t)?t:null}export function pointX(t,e,n){return toNumber(t.x??n?.[e]??e)}export function categoryExtent(t,e){let n=1/0,r=-1/0,o=!1;return t.forEach(t=>{t.forEach((t,a)=>{if(null===t.value)return;const i=pointX(t,a,e);null!==i&&(o=!0,n=Math.min(n,i),r=Math.max(r,i))})}),o?{min:n,max:r}:null}export function bubbleRadius(t,e,n,r){return e>0&&Number.isFinite(t)&&!(t<=0)?Math.max(r,Math.sqrt(Math.min(t,e)/e)*n):0===t?0:r}export function categoryCount(t){return t.reduce((t,e)=>Math.max(t,e.data.length),0)}export function categoryAt(t,e,n){if(e&&t<e.length)return e[t];for(const e of n){const n=e[t]?.x;if(void 0!==n)return n}return t}export function extentOf(t,e){let n=1/0,r=-1/0,o=!1;if(e){const e=t.reduce((t,e)=>Math.max(t,e.length),0);for(let a=0;a<e;a++){let e=0,i=0;for(const n of t){const t=n[a]?.value;null!=t&&(o=!0,t>=0?e+=t:i+=t)}n=Math.min(n,i),r=Math.max(r,e)}}else for(const e of t)for(const{value:t}of e)null!==t&&(o=!0,n=Math.min(n,t),r=Math.max(r,t));return o?{min:n,max:r}:null}function r(t){const e=10**Math.floor(Math.log10(t)),n=t/e;return n<=1?e:n<=2?2*e:n<=5?5*e:10*e}export function valueScale(t,e={}){const{tickCount:n=5,includeZero:o=!0}=e;let a=e.min??(t?t.min:0),i=e.max??(t?t.max:1);if(o&&void 0===e.min&&(a=Math.min(a,0)),o&&void 0===e.max&&(i=Math.max(i,0)),i===a){const t=Math.abs(i)>0?.5*Math.abs(i):1;a-=t,i+=t}const u=void 0!==e.min&&void 0!==e.max?function(t,e){const n=10**Math.floor(Math.log10(t/Math.max(1,e)));let o=r(t/Math.max(1,e)),a=1/0;for(const r of[.1,1,10])for(const i of[1,2,2.5,5]){const u=i*r*n,s=t/u,c=Math.round(s);if(c<1||Math.abs(s-c)>1e-9)continue;const l=Math.abs(c-e);l<a&&(a=l,o=u)}return o}(i-a,n):r((i-a)/Math.max(1,n)),s=void 0!==e.min?a:Math.floor(a/u)*u,c=void 0!==e.max?i:Math.ceil(i/u)*u,l=c-s||1,h=[];for(let t=s;t<=c+1e-9*u;t+=u)h.push(Number(t.toFixed(12)));return{min:s,max:c,ticks:h,fraction:t=>(t-s)/l}}const o=1e3,a=6e4,i=36e5,u=24*i,s=[{unit:"second",count:1,size:o},{unit:"second",count:5,size:5e3},{unit:"second",count:15,size:15e3},{unit:"second",count:30,size:3e4},{unit:"minute",count:1,size:a},{unit:"minute",count:5,size:3e5},{unit:"minute",count:15,size:9e5},{unit:"minute",count:30,size:18e5},{unit:"hour",count:1,size:i},{unit:"hour",count:3,size:3*i},{unit:"hour",count:6,size:6*i},{unit:"hour",count:12,size:12*i},{unit:"day",count:1,size:u},{unit:"day",count:2,size:2*u},{unit:"week",count:1,size:7*u},{unit:"week",count:2,size:14*u},{unit:"month",count:1,size:30*u},{unit:"quarter",count:1,size:91*u},{unit:"month",count:6,size:182*u},{unit:"year",count:1,size:365*u}];function c(t,e,n){const r=new Date(t);if("year"===e)r.setFullYear(r.getFullYear()+n);else if("quarter"===e)r.setMonth(r.getMonth()+3*n);else if("month"===e)r.setMonth(r.getMonth()+n);else if("week"===e)r.setDate(r.getDate()+7*n);else if("day"===e)r.setDate(r.getDate()+n);else{if("hour"!==e)return t+n*("minute"===e?a:o);r.setHours(r.getHours()+n)}return r.getTime()}const l={second:"minute",minute:"hour",hour:"day",day:"month",week:"week",month:"year",quarter:"year",year:"year"};function h(t,e,n){let r=function(t,e){const n=new Date(t);if("year"===e)return new Date(n.getFullYear(),0,1).getTime();if("quarter"===e)return new Date(n.getFullYear(),3*Math.floor(n.getMonth()/3),1).getTime();if("month"===e)return new Date(n.getFullYear(),n.getMonth(),1).getTime();if("week"===e){const t=new Date(n.getFullYear(),n.getMonth(),n.getDate());return t.setDate(t.getDate()-t.getDay()),t.getTime()}return"day"===e?new Date(n.getFullYear(),n.getMonth(),n.getDate()).getTime():"hour"===e?new Date(n.getFullYear(),n.getMonth(),n.getDate(),n.getHours()).getTime():"minute"===e?Math.floor(t/a)*a:Math.floor(t/o)*o}(t,l[e]);if("year"===e&&n>1){const t=new Date(r).getFullYear();r=new Date(t-(t%n+n)%n,0,1).getTime()}for(let o=0;o<500;o++){const o=c(r,e,n);if(o>t)return r;r=o}return r}export function timeScale(t,e={}){const{tickCount:n=6}=e;let o=e.min??t?.min??Date.parse("2000-01-01T00:00:00"),a=e.max??t?.max??o+u;a<=o&&(o-=u/2,a+=u/2);const i=a-o;let l=s[0],m=1/0;for(const t of s){const e=Math.abs(i/t.size-n);e<m&&(m=e,l=t)}const f=l.unit,$="year"===f?Math.max(1,Math.round(r(i/n/(365*u)))):l.count,x=e.min??h(o,f,$),p=[];let M=x;for(let t=0;t<500&&(p.push(M),!(M>a));t++)M=c(M,f,$);const g=e.max??p[p.length-1],d=g-x||1;return{min:x,max:g,ticks:p.filter(t=>t>=x&&t<=g),unit:f,step:$,fraction:t=>(t-x)/d}}function m(t,e){if("second"===t)return{hour:"2-digit",minute:"2-digit",second:"2-digit",hourCycle:"h23"};if("minute"===t||"hour"===t)return{hour:"2-digit",minute:"2-digit",hourCycle:"h23"};if("year"===t)return{year:"numeric"};const n="month"===t||"quarter"===t?{month:"short"}:{month:"short",day:"numeric"};return e?{...n,year:"numeric"}:n}export function formatTimeValue(e,n,r){return t(r,m(n,!0)).format(new Date(e))}export function formatTimeTicks(e,n,r){const o=new Set(e.map(t=>new Date(t).getFullYear())).size>1;return e.map((e,a)=>t(r,m(n,o||0===a)).format(new Date(e)))}export function tickStride(t,e,n){if(t<=1||e<=0)return 1;const r=Math.max(1,Math.floor(e/Math.max(1,n)));return Math.max(1,Math.ceil(t/r))}export function showsTick(t,e,n,r){return t%n===0||r&&t===e-1}export function fitsLast(t,e,n,r){const o=(t-1)%e;return o>0&&o*n>=r+8}export function textWidth(t,e){let n=0;for(const e of t)n+=/[ᄀ-ᇿ⺀-가-豈-︰-﹏]/.test(e)?1:.6;return n*e}export function truncate(t,e,n){if(e<=0||textWidth(t,n)<=e)return t;const r=e-textWidth("…",n);let o="",a=0;for(const e of t){const t=a+textWidth(e,n);if(t>r)break;a=t,o+=e}return o.length>0?`${o.trimEnd()}…`:"…"}export function bandScale(t,e,n){const r=t>0?e/t:e;return{step:r,band:r*n,centre:t=>r*(t+.5)}}export function linePath(t,e){const n=[];let r=[];const o=()=>{if(0!==r.length){if(1===r.length)n.push(`M${r[0].x} ${r[0].y}h0`);else if("step"===e){n.push(`M${r[0].x} ${r[0].y}`);for(let t=1;t<r.length;t++){const e=(r[t-1].x+r[t].x)/2;n.push(`H${e}V${r[t].y}H${r[t].x}`)}}else if("smooth"===e)n.push(`M${r[0].x} ${r[0].y}`),n.push(function(t){const e=t.length,n=[];for(let r=0;r<e-1;r++){const e=t[r+1].x-t[r].x;n.push(0===e?0:(t[r+1].y-t[r].y)/e)}const r=[n[0]??0];for(let t=1;t<e-1;t++){const e=n[t-1],o=n[t];r.push(e*o<=0?0:2*e*o/(e+o))}r.push(n[e-2]??0);const o=[];for(let n=0;n<e-1;n++){const e=(t[n+1].x-t[n].x)/3;o.push(`C${t[n].x+e} ${t[n].y+r[n]*e} ${t[n+1].x-e} ${t[n+1].y-r[n+1]*e} ${t[n+1].x} ${t[n+1].y}`)}return o.join("")}(r));else{n.push(`M${r[0].x} ${r[0].y}`);for(let t=1;t<r.length;t++)n.push(`L${r[t].x} ${r[t].y}`)}r=[]}};for(const e of t)null===e?o():r.push(e);return o(),n.join("")}export function areaPath(t,e,n){const r=[];let o=0;const a=a=>{const i=t.slice(o,a).filter(Boolean);if(0===i.length)return;const u="number"==typeof e?i.map(t=>({x:t.x,y:e})).reverse():e.slice(o,a).filter(Boolean).reverse();if(0===u.length)return;const s=linePath(i,n),c=linePath(u,n).replace(/^M/,"L");r.push(`${s}${c}Z`)};for(let e=0;e<t.length;e++)null===t[e]&&(a(e),o=e+1);return a(t.length),r.join("")}export function barPath(t,e,n,r,o,a){const i=Math.max(0,Math.min(o,n/2,r/2));if(0===i||n<=0||r<=0)return`M${t} ${e}h${n}v${r}h${-n}Z`;const u=(t,e)=>`a${i} ${i} 0 0 1 ${t} ${e}`;return"up"===a?`M${t} ${e+r}V${e+i}${u(i,-i)}H${t+n-i}${u(i,i)}V${e+r}Z`:"down"===a?`M${t} ${e}H${t+n}V${e+r-i}${u(-i,i)}H${t+i}${u(-i,-i)}Z`:"right"===a?`M${t} ${e}H${t+n-i}${u(i,i)}V${e+r-i}${u(-i,i)}H${t}Z`:`M${t+n} ${e+r}H${t+i}${u(-i,-i)}V${e+i}${u(i,-i)}H${t+n}Z`}export const markShapes=["circle","square","triangle","diamond","cross"];const f={circle:1,square:.8862,triangle:1.5551,diamond:1.2533,cross:1.189};export function markPath(t,e,n,r){const o=Math.max(0,r)*f[t];if(0===o)return"";if("circle"===t)return`M${e-o} ${n}a${o} ${o} 0 1 0 ${2*o} 0a${o} ${o} 0 1 0 ${2*-o} 0Z`;if("square"===t)return`M${e-o} ${n-o}h${2*o}v${2*o}h${2*-o}Z`;if("diamond"===t)return`M${e} ${n-o}L${e+o} ${n}L${e} ${n+o}L${e-o} ${n}Z`;if("triangle"===t){return`M${[0,120,240].map(t=>{const r=(t-90)*Math.PI/180;return`${e+o*Math.cos(r)} ${n+o*Math.sin(r)}`}).join("L")}Z`}const a=o/3;return`M${e-a} ${n-o}h${2*a}v${o-a}h${o-a}v${2*a}h${-(o-a)}v${o-a}h${2*-a}v${-(o-a)}h${-(o-a)}v${2*-a}h${o-a}Z`}export function arcPath(t,e,n,r,o,a){const i=(n,r)=>{const o=(r-90)*Math.PI/180;return`${t+n*Math.cos(o)} ${e+n*Math.sin(o)}`};if(Math.abs(a-o)>=360){const t=o+180;return r>0?`M${i(n,o)}A${n} ${n} 0 1 1 ${i(n,t)}A${n} ${n} 0 1 1 ${i(n,o)}ZM${i(r,o)}A${r} ${r} 0 1 0 ${i(r,t)}A${r} ${r} 0 1 0 ${i(r,o)}Z`:`M${i(n,o)}A${n} ${n} 0 1 1 ${i(n,t)}A${n} ${n} 0 1 1 ${i(n,o)}Z`}const u=Math.abs(a-o)>180?1:0;return r<=0?`M${t} ${e}L${i(n,o)}A${n} ${n} 0 ${u} 1 ${i(n,a)}Z`:`M${i(n,o)}A${n} ${n} 0 ${u} 1 ${i(n,a)}L${i(r,a)}A${r} ${r} 0 ${u} 0 ${i(r,o)}Z`}export const rampSteps=5;export function rampStep(t,e,n,r,o=0){if("diverging"===r){const r=Math.max(Math.abs(n-o),Math.abs(o-e));if(!(r>0))return 2;const a=(t-o)/r;return Math.min(4,Math.max(0,2+Math.round(2*a)))}const a=n-e;return a>0?Math.min(4,Math.max(0,Math.floor((t-e)/a*5))):4}export function rampFill(t,e){return`var(--neba-chart-${"diverging"===e?"div":"seq"}-${t+1})`}export function rampInk(t,e){return`var(--neba-chart-${"diverging"===e?"div":"seq"}-on-${t+1})`}export function squarify(t,e,n){const r=t.reduce((t,e)=>t+Math.max(0,e),0);if(!(r>0)||e<=0||n<=0)return[];const o=e*n/r,a=t.map((t,e)=>({index:e,area:Math.max(0,t)*o})).filter(t=>t.area>0).sort((t,e)=>e.area-t.area),i=[];let u=0,s=0,c=e,l=n,h=[];const m=t=>{const e=Math.min(c,l),n=t.reduce((t,e)=>t+e.area,0);if(!(n>0&&e>0))return 1/0;const r=t[0].area,o=t[t.length-1].area;return Math.max(e*e*r/(n*n),n*n/(e*e*o))},f=()=>{const t=h.reduce((t,e)=>t+e.area,0),e=Math.min(c,l),n=e>0?t/e:0,r=c>=l;let o=0;for(const t of h){const e=n>0?t.area/n:0;i.push(r?{index:t.index,x:u,y:s+o,width:n,height:e}:{index:t.index,x:u+o,y:s,width:e,height:n}),o+=e}r?(u+=n,c-=n):(s+=n,l-=n),h=[]};for(const t of a)0===h.length||m([...h,t])<=m(h)||f(),h.push(t);return h.length>0&&f(),i}const $={month:"short",day:"numeric"},x={notation:"compact",maximumFractionDigits:1},p={maximumFractionDigits:2};export function formatCategory(e,n){return e instanceof Date?t(n,$).format(e):String(e)}export function compactNumber(t,n){return Math.abs(t)>=1e4?e(n,x).format(t):e(n,p).format(t)}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The arithmetic a DataTable is made of.
|
|
3
|
+
*
|
|
4
|
+
* Here rather than in the component for the reason `progress.ts` and `color.ts`
|
|
5
|
+
* are here: none of it is layout and none of it is a class name. Sorting rows,
|
|
6
|
+
* matching a query against them, cutting a page out of the result and working
|
|
7
|
+
* out which twenty of forty thousand rows are actually on screen are four
|
|
8
|
+
* questions with numeric answers, and a component file that also has to draw a
|
|
9
|
+
* resize handle is not where they can be read.
|
|
10
|
+
*
|
|
11
|
+
* `color.ts` is the precedent for a single-consumer file: the test is whether
|
|
12
|
+
* the thing is separable, not whether two components happen to want it. Every
|
|
13
|
+
* function below takes plain values and returns plain values — there is no
|
|
14
|
+
* React in this file, and nothing in it knows what a column is.
|
|
15
|
+
*/
|
|
16
|
+
import type { NebaDensity, NebaSize } from '../types';
|
|
17
|
+
/**
|
|
18
|
+
* Row height, in **pixels**, and the one number in the library that has to be a
|
|
19
|
+
* number rather than a class.
|
|
20
|
+
*
|
|
21
|
+
* Virtual scrolling is arithmetic on this value: how far down the list a scroll
|
|
22
|
+
* offset lands, how tall the spacer above the first rendered row is, how many
|
|
23
|
+
* rows fit in the viewport for PageDown. A Tailwind class cannot be added up.
|
|
24
|
+
*
|
|
25
|
+
* The ladder is deliberately below `controlHeightClasses` at every step — 28px
|
|
26
|
+
* at `md` against a Button's 32 — because a DataTable is a hundred rows on one
|
|
27
|
+
* screen and a Table is eight. A row here is the height of the text plus enough
|
|
28
|
+
* air to click, and nothing more; `density` is what takes the last of the air
|
|
29
|
+
* out. A caller who puts a Chip or an Avatar in a cell raises `rowHeight`.
|
|
30
|
+
*/
|
|
31
|
+
export declare const dataRowHeights: Record<NebaDensity, Record<NebaSize, number>>;
|
|
32
|
+
/**
|
|
33
|
+
* The header, one step taller than the rows under it.
|
|
34
|
+
*
|
|
35
|
+
* A fixed number rather than "whatever the content measures", because a sticky
|
|
36
|
+
* header over a group row needs an offset to stick the second row at, and a
|
|
37
|
+
* measured one would be a layout read on every scroll frame.
|
|
38
|
+
*/
|
|
39
|
+
export declare const dataHeaderHeights: Record<NebaDensity, Record<NebaSize, number>>;
|
|
40
|
+
/**
|
|
41
|
+
* The tick column, and the resize handle's grab area.
|
|
42
|
+
*
|
|
43
|
+
* The tick column is `tickSizeClasses` plus the two padding tracks, rounded to
|
|
44
|
+
* a whole pixel: a column narrower than the box in it clips the focus ring.
|
|
45
|
+
*/
|
|
46
|
+
export declare const dataTickWidths: Record<NebaSize, number>;
|
|
47
|
+
/** How wide a column is when neither the caller nor a drag has said. */
|
|
48
|
+
export declare const defaultColumnWidth = 160;
|
|
49
|
+
/** How narrow a drag may make one. Below this the heading is a single letter. */
|
|
50
|
+
export declare const minColumnWidth = 48;
|
|
51
|
+
export type SortDirection = 'asc' | 'desc';
|
|
52
|
+
/** One key of a sort, and which way it runs. */
|
|
53
|
+
export interface SortEntry {
|
|
54
|
+
key: string;
|
|
55
|
+
direction: SortDirection;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The default comparison, for the columns that do not bring one.
|
|
59
|
+
*
|
|
60
|
+
* Empty sorts last in both directions, which is the one asymmetry here and the
|
|
61
|
+
* one every spreadsheet has: a blank is not the smallest value, it is the
|
|
62
|
+
* absence of one, and a descending sort whose first screen is forty blanks has
|
|
63
|
+
* answered the wrong question.
|
|
64
|
+
*
|
|
65
|
+
* Numbers compare as numbers, dates as instants, booleans with `false` first,
|
|
66
|
+
* and everything else through the collator — which is `numeric`, so `item2`
|
|
67
|
+
* comes before `item10` rather than after it.
|
|
68
|
+
*/
|
|
69
|
+
export declare function compareValues(a: unknown, b: unknown, collator: Intl.Collator): number;
|
|
70
|
+
/**
|
|
71
|
+
* Sorts by every key at once, first key outermost.
|
|
72
|
+
*
|
|
73
|
+
* The sort is **stable** — `Array.prototype.sort` has been since ES2019 — which
|
|
74
|
+
* is what makes a multi-key sort composable: sorting by name and then adding
|
|
75
|
+
* date to the end of the list leaves rows with the same date in name order,
|
|
76
|
+
* because that is the order they were already in.
|
|
77
|
+
*
|
|
78
|
+
* `comparatorFor` returns `null` for a key no column claims, so a sort left
|
|
79
|
+
* over from a column that has since been removed is skipped rather than
|
|
80
|
+
* throwing.
|
|
81
|
+
*/
|
|
82
|
+
export declare function sortRows<T>(rows: readonly T[], sort: readonly SortEntry[], comparatorFor: (key: string) => ((a: T, b: T) => number) | null): T[];
|
|
83
|
+
/**
|
|
84
|
+
* What pressing a heading does, given what the sort already says.
|
|
85
|
+
*
|
|
86
|
+
* Three states rather than two — ascending, descending, unsorted — because the
|
|
87
|
+
* order the rows arrived in is a state a caller cannot get back to by pressing
|
|
88
|
+
* anything if the cycle only has two. This is the opposite of the choice
|
|
89
|
+
* TreeView's single select makes, and for the same reason: there, "nothing
|
|
90
|
+
* chosen" is reachable by pressing something else; here it is not.
|
|
91
|
+
*
|
|
92
|
+
* `additive` is what a Shift-click sets. It keeps the column where it already
|
|
93
|
+
* is in the list rather than moving it to the end, so flipping the second key
|
|
94
|
+
* of a two-key sort does not silently make it the first.
|
|
95
|
+
*/
|
|
96
|
+
export declare function nextSort(current: readonly SortEntry[], key: string, additive: boolean): SortEntry[];
|
|
97
|
+
/**
|
|
98
|
+
* A cell value as something a query can be matched against.
|
|
99
|
+
*
|
|
100
|
+
* Case-folded and stripped of combining marks, so `jose` finds `José` and
|
|
101
|
+
* `SEOUL` finds `Seoul`. `NFD` splits an accented letter into the letter and
|
|
102
|
+
* its accent and the range then deletes the accent; a search field is the one
|
|
103
|
+
* place where losing that distinction is the point.
|
|
104
|
+
*
|
|
105
|
+
* A `Date` is deliberately *not* formatted here. What a reader sees in the cell
|
|
106
|
+
* came out of the caller's `render`, and guessing a format the search would
|
|
107
|
+
* agree with is how a table ends up not finding a date that is on the screen —
|
|
108
|
+
* a column that wants its dates searchable gives them a `value`.
|
|
109
|
+
*/
|
|
110
|
+
export declare function searchText(value: unknown): string;
|
|
111
|
+
/** Where one page starts and stops, and how many there are. */
|
|
112
|
+
export interface PageBounds {
|
|
113
|
+
/** How many pages the rows come to. Never below `1`. */
|
|
114
|
+
pages: number;
|
|
115
|
+
/** The page actually shown, clamped into range. */
|
|
116
|
+
page: number;
|
|
117
|
+
/** Index of the first row on it. */
|
|
118
|
+
start: number;
|
|
119
|
+
/** One past the last. */
|
|
120
|
+
end: number;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* The page arithmetic, with the clamp in it.
|
|
124
|
+
*
|
|
125
|
+
* The clamp is the whole reason this is a function: a filter that cuts a
|
|
126
|
+
* twenty-page table to three leaves the caller's `page` at 14, and a table that
|
|
127
|
+
* answers with an empty screen has told the reader their search found nothing.
|
|
128
|
+
* The page moves to the last one that exists instead.
|
|
129
|
+
*/
|
|
130
|
+
export declare function pageBounds(total: number, page: number, pageSize: number): PageBounds;
|
|
131
|
+
/** Which rows are rendered, and how much empty space stands in for the rest. */
|
|
132
|
+
export interface VirtualWindow {
|
|
133
|
+
/** Index of the first row rendered. */
|
|
134
|
+
start: number;
|
|
135
|
+
/** One past the last. */
|
|
136
|
+
end: number;
|
|
137
|
+
/** The height of the spacer row above, in pixels. */
|
|
138
|
+
before: number;
|
|
139
|
+
/** And of the one below. */
|
|
140
|
+
after: number;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* The rows a scroll offset puts on screen, plus a margin either side.
|
|
144
|
+
*
|
|
145
|
+
* Two spacer rows rather than a translated `<tbody>`: a transform on the body
|
|
146
|
+
* of a table breaks `position: sticky` on the header above it, and the library
|
|
147
|
+
* has a rule about transforms anyway. Padding is also what keeps the scrollbar
|
|
148
|
+
* honest — the scroll height is the real one, so dragging the thumb to the
|
|
149
|
+
* middle lands in the middle of the data.
|
|
150
|
+
*
|
|
151
|
+
* `viewport` of `0` is the first render, before anything has been measured. It
|
|
152
|
+
* cannot mean "no rows" — the layout effect that measures runs after a render
|
|
153
|
+
* and there would be nothing to measure — so it renders a screen's worth on
|
|
154
|
+
* spec and corrects before the frame is painted.
|
|
155
|
+
*/
|
|
156
|
+
export declare function virtualWindow(scrollTop: number, viewport: number, rowHeight: number, count: number, overscan: number): VirtualWindow;
|
|
157
|
+
/**
|
|
158
|
+
* Every key between two, inclusive, in the order the rows are currently in.
|
|
159
|
+
*
|
|
160
|
+
* "Currently" is the point: a Shift-click selects what the reader can see
|
|
161
|
+
* between the two rows they clicked, so the range is taken from the sorted,
|
|
162
|
+
* filtered order rather than from the order `items` arrived in. Which of the
|
|
163
|
+
* two keys is the anchor does not matter — dragging up selects the same rows as
|
|
164
|
+
* dragging down.
|
|
165
|
+
*
|
|
166
|
+
* A key that is no longer in the list (a row the current filter hides) yields
|
|
167
|
+
* an empty range rather than a range from one end, which is what an index of
|
|
168
|
+
* `-1` would silently produce.
|
|
169
|
+
*/
|
|
170
|
+
export declare function keysBetween(order: readonly string[], from: string, to: string): string[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const dataRowHeights={default:{xs:24,sm:28,md:32,lg:38,xl:44},compact:{xs:20,sm:24,md:28,lg:32,xl:38}};export const dataHeaderHeights={default:{xs:28,sm:32,md:36,lg:42,xl:48},compact:{xs:24,sm:28,md:32,lg:36,xl:42}};export const dataTickWidths={xs:30,sm:34,md:38,lg:42,xl:50};export const defaultColumnWidth=160;export const minColumnWidth=48;export function compareValues(t,e,n){const o=null==t||""===t,r=null==e||""===e;return o||r?o&&r?0:o?1:-1:"number"==typeof t&&"number"==typeof e?Number.isNaN(t)||Number.isNaN(e)?Number.isNaN(t)&&Number.isNaN(e)?0:Number.isNaN(t)?1:-1:t-e:"boolean"==typeof t&&"boolean"==typeof e?Number(t)-Number(e):t instanceof Date&&e instanceof Date?t.getTime()-e.getTime():n.compare(String(t),String(e))}export function sortRows(t,e,n){if(0===e.length)return t;const o=e.map(t=>{const e=n(t.key);return e?{compare:e,sign:"desc"===t.direction?-1:1}:null}).filter(t=>null!==t);return 0===o.length?t:[...t].sort((t,e)=>{for(const n of o){const o=n.compare(t,e);if(0!==o)return o*n.sign}return 0})}export function nextSort(t,e,n){const o=t.find(t=>t.key===e),r=void 0===o?"asc":"asc"===o.direction?"desc":null;return n?null===r?t.filter(t=>t.key!==e):void 0===o?[...t,{key:e,direction:r}]:t.map(t=>t.key===e?{key:e,direction:r}:t):null===r?[]:[{key:e,direction:r}]}export function searchText(t){return null==t||"object"==typeof t?"":String(t).normalize("NFD").replace(/[\u0300-\u036f]/g,"").toLowerCase()}export function pageBounds(t,e,n){const o=Math.max(1,Math.floor(n)),r=Math.max(1,Math.ceil(t/o)),a=Math.min(Math.max(Math.floor(e),1),r),i=(a-1)*o;return{pages:r,page:a,start:i,end:Math.min(i+o,t)}}export function virtualWindow(t,e,n,o,r){const a=e>0?Math.ceil(e/n):3*r,i=Math.max(0,Math.floor(t/n)-r),s=Math.min(o,i+a+2*r);return{start:i,end:s,before:i*n,after:Math.max(0,(o-s)*n)}}export function keysBetween(t,e,n){const o=t.indexOf(e),r=t.indexOf(n);return-1===o||-1===r?[]:t.slice(Math.min(o,r),Math.max(o,r)+1)}
|
package/dist/internal/date.d.ts
CHANGED
|
@@ -109,8 +109,6 @@ export declare function mergeDateAndTime(date: Date, time: Date): Date;
|
|
|
109
109
|
export declare function calendarWeeks(month: Date, weekStartsOn: NebaWeekday): Date[][];
|
|
110
110
|
/** The first year on the page a given year falls on. 2026 → 2016 at 12 a page. */
|
|
111
111
|
export declare function yearPageStart(year: number): number;
|
|
112
|
-
/** A memoised `Intl.DateTimeFormat`. `undefined` locale means the runtime's own. */
|
|
113
|
-
export declare function dateFormatter(locale: string | undefined, options: Intl.DateTimeFormatOptions): Intl.DateTimeFormat;
|
|
114
112
|
/** Formats a date, tolerating the `null` a cleared picker holds. */
|
|
115
113
|
export declare function formatDate(date: Date | null | undefined, locale: string | undefined, options: Intl.DateTimeFormatOptions): string;
|
|
116
114
|
/**
|