@rokkit/chart 1.6.0 → 1.8.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 +13 -0
- package/dist/Plot.svelte +38 -10
- package/dist/Plot.svelte.d.ts +7 -2
- package/dist/PlotState.svelte.d.ts +115 -71
- package/dist/PlotState.svelte.js +174 -547
- package/dist/SparkState.svelte.d.ts +3 -1
- package/dist/SparkState.svelte.js +11 -41
- package/dist/geoms/Contour.svelte +88 -0
- package/dist/geoms/Contour.svelte.d.ts +19 -0
- package/dist/geoms/Hull.svelte +89 -0
- package/dist/geoms/Hull.svelte.d.ts +17 -0
- package/dist/geoms/Region.svelte +107 -0
- package/dist/geoms/Region.svelte.d.ts +27 -0
- package/dist/geoms/Rule.svelte +88 -26
- package/dist/geoms/Rule.svelte.d.ts +8 -0
- package/dist/geoms/lib/PlotAreaClip.svelte +18 -0
- package/dist/geoms/lib/PlotAreaClip.svelte.d.ts +6 -0
- package/dist/geoms/lib/bars.js +13 -20
- package/dist/geoms/lib/grouping.d.ts +35 -0
- package/dist/geoms/lib/grouping.js +59 -0
- package/dist/geoms/lib/marks/contour.d.ts +26 -0
- package/dist/geoms/lib/marks/contour.js +67 -0
- package/dist/geoms/lib/marks/hull.d.ts +26 -0
- package/dist/geoms/lib/marks/hull.js +36 -0
- package/dist/geoms/lib/marks/waterfall.js +15 -20
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/lib/abline.d.ts +41 -0
- package/dist/lib/abline.js +51 -0
- package/dist/lib/brewing/colors.js +1 -1
- package/dist/lib/hull.d.ts +18 -0
- package/dist/lib/hull.js +64 -0
- package/dist/lib/plot/domains.d.ts +61 -0
- package/dist/lib/plot/domains.js +130 -0
- package/dist/lib/plot/preset.d.ts +1 -1
- package/dist/lib/plot/preset.js +1 -1
- package/dist/lib/plot/running.d.ts +23 -0
- package/dist/lib/plot/running.js +32 -0
- package/dist/lib/plot/stacking.d.ts +32 -0
- package/dist/lib/plot/stacking.js +34 -0
- package/dist/lib/plot/types.d.ts +8 -1
- package/dist/lib/plot/types.js +5 -1
- package/dist/lib/preset.d.ts +2 -0
- package/dist/lib/preset.js +76 -41
- package/dist/lib/region.d.ts +63 -0
- package/dist/lib/region.js +127 -0
- package/dist/lib/swatch.d.ts +1 -1
- package/dist/lib/swatch.js +1 -1
- package/dist/state/AestheticState.svelte.d.ts +43 -0
- package/dist/state/AestheticState.svelte.js +84 -0
- package/dist/state/AxisState.svelte.d.ts +26 -0
- package/dist/state/AxisState.svelte.js +56 -0
- package/dist/state/ChannelState.svelte.d.ts +34 -0
- package/dist/state/ChannelState.svelte.js +67 -0
- package/dist/state/GeomRegistry.svelte.d.ts +44 -0
- package/dist/state/GeomRegistry.svelte.js +86 -0
- package/dist/state/InteractionState.svelte.d.ts +30 -0
- package/dist/state/InteractionState.svelte.js +69 -0
- package/dist/state/OrientationState.svelte.d.ts +34 -0
- package/dist/state/OrientationState.svelte.js +64 -0
- package/dist/state/PlotConfig.svelte.d.ts +179 -0
- package/dist/state/PlotConfig.svelte.js +195 -0
- package/dist/state/PlotFrame.svelte.d.ts +17 -0
- package/dist/state/PlotFrame.svelte.js +18 -0
- package/dist/state/ScaleState.svelte.d.ts +36 -0
- package/dist/state/ScaleState.svelte.js +121 -0
- package/package.json +5 -3
- package/dist/lib/palette.json +0 -277
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Band categories ordered by their summed value — "sort bars by size". Null when not sorting.
|
|
3
|
+
*
|
|
4
|
+
* @param {Record<string, unknown>[][]} datasets - every geom's rows
|
|
5
|
+
* @param {string | undefined} xf
|
|
6
|
+
* @param {string | undefined} yf
|
|
7
|
+
* @param {'asc' | 'desc' | undefined} sort
|
|
8
|
+
*/
|
|
9
|
+
export function sortedBandDomain(datasets: Record<string, unknown>[][], xf: string | undefined, yf: string | undefined, sort: "asc" | "desc" | undefined): any[] | null;
|
|
10
|
+
/**
|
|
11
|
+
* A box/violin spans its whiskers (`iqr_min` / `iqr_max`) and every outlier.
|
|
12
|
+
*
|
|
13
|
+
* @param {Record<string, any>[]} rows - box-stat rows
|
|
14
|
+
*/
|
|
15
|
+
export function boxDomain(rows: Record<string, any>[]): number[] | null;
|
|
16
|
+
/**
|
|
17
|
+
* A stacked bar reaches its column total; a 100%-filled one reaches 1.
|
|
18
|
+
*
|
|
19
|
+
* Summed per (x, stack key), keeping the LAST value per key — the builder's own lookup does the
|
|
20
|
+
* same, so rows sharing a key under an identity stat do not double-count. The stack key is the
|
|
21
|
+
* builder's (`stackFieldOf`); with none, the builder draws plain bars, so there is no total to
|
|
22
|
+
* size to and the normal extent applies (null).
|
|
23
|
+
*
|
|
24
|
+
* @param {Record<string, unknown>[]} rows
|
|
25
|
+
* @param {{ x?: string, y?: string, group?: string, fill?: string, color?: string, pattern?: string }} channels
|
|
26
|
+
* @param {'stack' | 'fill' | undefined} position
|
|
27
|
+
*/
|
|
28
|
+
export function stackDomain(rows: Record<string, unknown>[], channels: {
|
|
29
|
+
x?: string;
|
|
30
|
+
y?: string;
|
|
31
|
+
group?: string;
|
|
32
|
+
fill?: string;
|
|
33
|
+
color?: string;
|
|
34
|
+
pattern?: string;
|
|
35
|
+
}, position: "stack" | "fill" | undefined): number[] | null;
|
|
36
|
+
/**
|
|
37
|
+
* A waterfall spans its running total: each step from where the last ended, a total row from 0.
|
|
38
|
+
*
|
|
39
|
+
* @param {Record<string, unknown>[]} rows
|
|
40
|
+
* @param {string} field
|
|
41
|
+
* @param {string} [totalField] - rows flagged here are totals, drawn from zero
|
|
42
|
+
*/
|
|
43
|
+
export function waterfallDomain(rows: Record<string, unknown>[], field: string, totalField?: string): number[] | null;
|
|
44
|
+
/**
|
|
45
|
+
* The value-axis domain the mounted geoms' geometry demands, or null for the plain extent.
|
|
46
|
+
*
|
|
47
|
+
* @param {Array<{ id: string, type: string, options?: Record<string, any> }>} geoms
|
|
48
|
+
* @param {(id: string) => Record<string, unknown>[]} rowsOf - a geom's rows (post-stat)
|
|
49
|
+
* @param {{ x?: string, fill?: string, color?: string, pattern?: string }} channels - effective
|
|
50
|
+
* @param {string} field - the value channel
|
|
51
|
+
*/
|
|
52
|
+
export function resolveValueDomain(geoms: Array<{
|
|
53
|
+
id: string;
|
|
54
|
+
type: string;
|
|
55
|
+
options?: Record<string, any>;
|
|
56
|
+
}>, rowsOf: (id: string) => Record<string, unknown>[], channels: {
|
|
57
|
+
x?: string;
|
|
58
|
+
fill?: string;
|
|
59
|
+
color?: string;
|
|
60
|
+
pattern?: string;
|
|
61
|
+
}, field: string): number[] | null;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Axis domains that a geom's own geometry decides, rather than the raw extent of its rows.
|
|
3
|
+
*
|
|
4
|
+
* A box spans its whiskers and outliers, not its rows' y; a stacked bar reaches its column
|
|
5
|
+
* TOTAL; a waterfall bar sits on a running total. Pure functions over the rows a geom draws,
|
|
6
|
+
* so they are testable without Svelte, and `resolveValueDomain` asks them in a fixed order —
|
|
7
|
+
* a new geom with a geometry-driven domain adds a row to that table instead of editing the
|
|
8
|
+
* scale code.
|
|
9
|
+
*/
|
|
10
|
+
import { stackFieldOf } from './stacking.js'
|
|
11
|
+
import { runningSpans } from './running.js'
|
|
12
|
+
|
|
13
|
+
const valid = (v) => v !== null && v !== undefined && !isNaN(v)
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Band categories ordered by their summed value — "sort bars by size". Null when not sorting.
|
|
17
|
+
*
|
|
18
|
+
* @param {Record<string, unknown>[][]} datasets - every geom's rows
|
|
19
|
+
* @param {string | undefined} xf
|
|
20
|
+
* @param {string | undefined} yf
|
|
21
|
+
* @param {'asc' | 'desc' | undefined} sort
|
|
22
|
+
*/
|
|
23
|
+
export function sortedBandDomain(datasets, xf, yf, sort) {
|
|
24
|
+
if (!sort || !xf || !yf) return null
|
|
25
|
+
// A Map, not an object: keys are raw values (numbers, Dates) an object would stringify.
|
|
26
|
+
const totals = new Map()
|
|
27
|
+
for (const rows of datasets) {
|
|
28
|
+
for (const d of rows) totals.set(d[xf], (totals.get(d[xf]) ?? 0) + (Number(d[yf]) || 0))
|
|
29
|
+
}
|
|
30
|
+
const dir = sort === 'asc' ? 1 : -1
|
|
31
|
+
return [...totals.keys()].sort((a, b) => dir * (totals.get(a) - totals.get(b)))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* A box/violin spans its whiskers (`iqr_min` / `iqr_max`) and every outlier.
|
|
36
|
+
*
|
|
37
|
+
* @param {Record<string, any>[]} rows - box-stat rows
|
|
38
|
+
*/
|
|
39
|
+
export function boxDomain(rows) {
|
|
40
|
+
const outliers = rows.flatMap((d) => d.outliers ?? []).filter(valid)
|
|
41
|
+
const mins = [...rows.map((d) => d.iqr_min).filter(valid), ...outliers]
|
|
42
|
+
const maxs = [...rows.map((d) => d.iqr_max).filter(valid), ...outliers]
|
|
43
|
+
return mins.length > 0 && maxs.length > 0 ? [Math.min(...mins), Math.max(...maxs)] : null
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* A stacked bar reaches its column total; a 100%-filled one reaches 1.
|
|
48
|
+
*
|
|
49
|
+
* Summed per (x, stack key), keeping the LAST value per key — the builder's own lookup does the
|
|
50
|
+
* same, so rows sharing a key under an identity stat do not double-count. The stack key is the
|
|
51
|
+
* builder's (`stackFieldOf`); with none, the builder draws plain bars, so there is no total to
|
|
52
|
+
* size to and the normal extent applies (null).
|
|
53
|
+
*
|
|
54
|
+
* @param {Record<string, unknown>[]} rows
|
|
55
|
+
* @param {{ x?: string, y?: string, group?: string, fill?: string, color?: string, pattern?: string }} channels
|
|
56
|
+
* @param {'stack' | 'fill' | undefined} position
|
|
57
|
+
*/
|
|
58
|
+
export function stackDomain(rows, channels, position) {
|
|
59
|
+
if (position === 'fill') return [0, 1]
|
|
60
|
+
const stackField = stackFieldOf(channels)
|
|
61
|
+
if (!channels.x || rows.length === 0 || !stackField) return null
|
|
62
|
+
const columns = new Map()
|
|
63
|
+
for (const d of rows) {
|
|
64
|
+
const column = columns.get(d[channels.x]) ?? new Map()
|
|
65
|
+
column.set(String(d[stackField]), Number(d[channels.y]) || 0)
|
|
66
|
+
columns.set(d[channels.x], column)
|
|
67
|
+
}
|
|
68
|
+
const totals = [...columns.values()].map((c) => [...c.values()].reduce((s, v) => s + v, 0))
|
|
69
|
+
return [0, Math.max(0, ...totals)]
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* A waterfall spans its running total: each step from where the last ended, a total row from 0.
|
|
74
|
+
*
|
|
75
|
+
* @param {Record<string, unknown>[]} rows
|
|
76
|
+
* @param {string} field
|
|
77
|
+
* @param {string} [totalField] - rows flagged here are totals, drawn from zero
|
|
78
|
+
*/
|
|
79
|
+
export function waterfallDomain(rows, field, totalField) {
|
|
80
|
+
if (rows.length === 0) return null
|
|
81
|
+
const spans = runningSpans(rows, field, totalField)
|
|
82
|
+
return [Math.min(0, ...spans.map((s) => s.lo)), Math.max(0, ...spans.map((s) => s.hi))]
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** A geom's channels without the keys it left undefined (to inherit). */
|
|
86
|
+
const definedOf = (channels = {}) =>
|
|
87
|
+
Object.fromEntries(Object.entries(channels).filter(([, v]) => v !== undefined))
|
|
88
|
+
|
|
89
|
+
const isStacked = (g) =>
|
|
90
|
+
g.options?.stack || g.options?.position === 'stack' || g.options?.position === 'fill'
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Geom types whose geometry decides the VALUE axis, asked in order; the first to return a
|
|
94
|
+
* domain wins. Each resolver reads the first mounted geom it matches.
|
|
95
|
+
*/
|
|
96
|
+
const VALUE_DOMAINS = [
|
|
97
|
+
{ matches: (g) => g.type === 'box' || g.type === 'violin', domain: (rows) => boxDomain(rows) },
|
|
98
|
+
{
|
|
99
|
+
matches: isStacked,
|
|
100
|
+
// The geom's OWN channels win — `group` exists only there — over the effective ones.
|
|
101
|
+
domain: (rows, geom, channels, field) =>
|
|
102
|
+
stackDomain(
|
|
103
|
+
rows,
|
|
104
|
+
{ ...channels, ...definedOf(geom.channels), y: field },
|
|
105
|
+
geom.options?.position === 'fill' ? 'fill' : 'stack'
|
|
106
|
+
)
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
matches: (g) => g.type === 'waterfall',
|
|
110
|
+
domain: (rows, geom, _channels, field) => waterfallDomain(rows, field, geom.options?.totalField)
|
|
111
|
+
}
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* The value-axis domain the mounted geoms' geometry demands, or null for the plain extent.
|
|
116
|
+
*
|
|
117
|
+
* @param {Array<{ id: string, type: string, options?: Record<string, any> }>} geoms
|
|
118
|
+
* @param {(id: string) => Record<string, unknown>[]} rowsOf - a geom's rows (post-stat)
|
|
119
|
+
* @param {{ x?: string, fill?: string, color?: string, pattern?: string }} channels - effective
|
|
120
|
+
* @param {string} field - the value channel
|
|
121
|
+
*/
|
|
122
|
+
export function resolveValueDomain(geoms, rowsOf, channels, field) {
|
|
123
|
+
for (const resolver of VALUE_DOMAINS) {
|
|
124
|
+
const geom = geoms.find(resolver.matches)
|
|
125
|
+
if (!geom) continue
|
|
126
|
+
const domain = resolver.domain(rowsOf(geom.id), geom, channels, field)
|
|
127
|
+
if (domain) return domain
|
|
128
|
+
}
|
|
129
|
+
return null
|
|
130
|
+
}
|
package/dist/lib/plot/preset.js
CHANGED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A waterfall's geometry: where each bar starts and ends on the value axis.
|
|
3
|
+
*
|
|
4
|
+
* One function for the builder that draws the bars and the domain that sizes the axis to them —
|
|
5
|
+
* two copies disagreed: the builder drew a negative total as a 1px sliver at zero, and let a
|
|
6
|
+
* missing value turn every later bar into NaN, while the axis was sized as if neither happened.
|
|
7
|
+
*
|
|
8
|
+
* A step runs from the running total before it to the running total after; a total row (flagged
|
|
9
|
+
* by `totalField`) runs from 0 to the running total, in whichever direction that is. A missing
|
|
10
|
+
* or non-numeric value is a step of 0.
|
|
11
|
+
*
|
|
12
|
+
* @param {Record<string, unknown>[]} rows
|
|
13
|
+
* @param {string} field
|
|
14
|
+
* @param {string} [totalField]
|
|
15
|
+
* @returns {Array<{ lo: number, hi: number, total: number, isTotal: boolean, delta: number }>}
|
|
16
|
+
*/
|
|
17
|
+
export function runningSpans(rows: Record<string, unknown>[], field: string, totalField?: string): Array<{
|
|
18
|
+
lo: number;
|
|
19
|
+
hi: number;
|
|
20
|
+
total: number;
|
|
21
|
+
isTotal: boolean;
|
|
22
|
+
delta: number;
|
|
23
|
+
}>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A waterfall's geometry: where each bar starts and ends on the value axis.
|
|
3
|
+
*
|
|
4
|
+
* One function for the builder that draws the bars and the domain that sizes the axis to them —
|
|
5
|
+
* two copies disagreed: the builder drew a negative total as a 1px sliver at zero, and let a
|
|
6
|
+
* missing value turn every later bar into NaN, while the axis was sized as if neither happened.
|
|
7
|
+
*
|
|
8
|
+
* A step runs from the running total before it to the running total after; a total row (flagged
|
|
9
|
+
* by `totalField`) runs from 0 to the running total, in whichever direction that is. A missing
|
|
10
|
+
* or non-numeric value is a step of 0.
|
|
11
|
+
*
|
|
12
|
+
* @param {Record<string, unknown>[]} rows
|
|
13
|
+
* @param {string} field
|
|
14
|
+
* @param {string} [totalField]
|
|
15
|
+
* @returns {Array<{ lo: number, hi: number, total: number, isTotal: boolean, delta: number }>}
|
|
16
|
+
*/
|
|
17
|
+
export function runningSpans(rows, field, totalField) {
|
|
18
|
+
let running = 0
|
|
19
|
+
return rows.map((d) => {
|
|
20
|
+
const isTotal = Boolean(totalField && d[totalField])
|
|
21
|
+
const delta = isTotal ? 0 : Number(d[field]) || 0
|
|
22
|
+
const start = isTotal ? 0 : running
|
|
23
|
+
running += delta
|
|
24
|
+
return {
|
|
25
|
+
lo: Math.min(start, running),
|
|
26
|
+
hi: Math.max(start, running),
|
|
27
|
+
total: running,
|
|
28
|
+
isTotal,
|
|
29
|
+
delta
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The distinct non-x fields among [group, color, pattern] — the fields that put more than one
|
|
3
|
+
* bar in a single x band (dodge) or more than one segment in a column (stack). `group`, the
|
|
4
|
+
* explicit grouping channel, comes first. Literal colours are paint, not fields.
|
|
5
|
+
*
|
|
6
|
+
* @param {{ x?: string, group?: string, color?: string, pattern?: string }} channels
|
|
7
|
+
* @returns {string[]}
|
|
8
|
+
*/
|
|
9
|
+
export function subBandFields(channels: {
|
|
10
|
+
x?: string;
|
|
11
|
+
group?: string;
|
|
12
|
+
color?: string;
|
|
13
|
+
pattern?: string;
|
|
14
|
+
}): string[];
|
|
15
|
+
/**
|
|
16
|
+
* The field a stacked bar stacks by, from a bar's own channels — or null when there is none,
|
|
17
|
+
* in which case the builder draws plain bars. One rule for the builder that draws the stack
|
|
18
|
+
* and the domain that sizes the axis to it: two copies disagreed, and the axis ended below
|
|
19
|
+
* the tallest bar.
|
|
20
|
+
*
|
|
21
|
+
* `group` defaults to the interior (`fill ?? color`), exactly as the bar marks resolve it.
|
|
22
|
+
*
|
|
23
|
+
* @param {{ x?: string, group?: string, fill?: string, color?: string, pattern?: string }} channels
|
|
24
|
+
* @returns {string | null}
|
|
25
|
+
*/
|
|
26
|
+
export function stackFieldOf({ x, group, fill, color, pattern }: {
|
|
27
|
+
x?: string;
|
|
28
|
+
group?: string;
|
|
29
|
+
fill?: string;
|
|
30
|
+
color?: string;
|
|
31
|
+
pattern?: string;
|
|
32
|
+
}): string | null;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { isLiteralColor } from '../brewing/colors.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The distinct non-x fields among [group, color, pattern] — the fields that put more than one
|
|
5
|
+
* bar in a single x band (dodge) or more than one segment in a column (stack). `group`, the
|
|
6
|
+
* explicit grouping channel, comes first. Literal colours are paint, not fields.
|
|
7
|
+
*
|
|
8
|
+
* @param {{ x?: string, group?: string, color?: string, pattern?: string }} channels
|
|
9
|
+
* @returns {string[]}
|
|
10
|
+
*/
|
|
11
|
+
export function subBandFields(channels) {
|
|
12
|
+
const { x: xf, group: gf, color: cf, pattern: pf } = channels
|
|
13
|
+
const out = []
|
|
14
|
+
for (const f of [gf, cf, pf]) {
|
|
15
|
+
if (f && f !== xf && !out.includes(f) && !isLiteralColor(f)) out.push(f)
|
|
16
|
+
}
|
|
17
|
+
return out
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The field a stacked bar stacks by, from a bar's own channels — or null when there is none,
|
|
22
|
+
* in which case the builder draws plain bars. One rule for the builder that draws the stack
|
|
23
|
+
* and the domain that sizes the axis to it: two copies disagreed, and the axis ended below
|
|
24
|
+
* the tallest bar.
|
|
25
|
+
*
|
|
26
|
+
* `group` defaults to the interior (`fill ?? color`), exactly as the bar marks resolve it.
|
|
27
|
+
*
|
|
28
|
+
* @param {{ x?: string, group?: string, fill?: string, color?: string, pattern?: string }} channels
|
|
29
|
+
* @returns {string | null}
|
|
30
|
+
*/
|
|
31
|
+
export function stackFieldOf({ x, group, fill, color, pattern }) {
|
|
32
|
+
const interior = fill ?? color
|
|
33
|
+
return subBandFields({ x, group: group ?? interior, color: interior, pattern })[0] ?? null
|
|
34
|
+
}
|
package/dist/lib/plot/types.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type GeomSpec = {
|
|
2
2
|
/**
|
|
3
|
-
* - Geom type: 'bar'|'line'|'area'|'point'|'box'|'violin'|'arc' or custom
|
|
3
|
+
* - Geom type: 'bar'|'line'|'area'|'point'|'box'|'violin'|'arc'|'hull'|'contour'|'rule'|'region' or custom
|
|
4
4
|
*/
|
|
5
5
|
type: string;
|
|
6
6
|
x?: string | undefined;
|
|
@@ -16,6 +16,13 @@ export type GeomSpec = {
|
|
|
16
16
|
stat?: string | undefined;
|
|
17
17
|
label?: string | boolean | undefined;
|
|
18
18
|
options?: Record<string, unknown> | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* - Geom-specific props spread onto the component
|
|
21
|
+
* (`Region`'s `points`/`label`, `Rule`'s `slope`, `Hull`'s `padding`). For the annotation
|
|
22
|
+
* geoms (`rule`, `region`) these are the ONLY props: they read no data, and a Region's `x`/`y`
|
|
23
|
+
* are ranges, so inheriting the spec's field names would be wrong.
|
|
24
|
+
*/
|
|
25
|
+
props?: Record<string, unknown> | undefined;
|
|
19
26
|
};
|
|
20
27
|
export type PlotSpec = {
|
|
21
28
|
data?: Record<string, unknown>[] | undefined;
|
package/dist/lib/plot/types.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* @typedef {Object} GeomSpec
|
|
6
|
-
* @property {string} type - Geom type: 'bar'|'line'|'area'|'point'|'box'|'violin'|'arc' or custom
|
|
6
|
+
* @property {string} type - Geom type: 'bar'|'line'|'area'|'point'|'box'|'violin'|'arc'|'hull'|'contour'|'rule'|'region' or custom
|
|
7
7
|
* @property {string} [x]
|
|
8
8
|
* @property {string} [y]
|
|
9
9
|
* @property {string} [color]
|
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
* @property {string} [stat] - Built-in or helpers.stats key
|
|
15
15
|
* @property {boolean | string} [label]
|
|
16
16
|
* @property {Record<string, unknown>} [options]
|
|
17
|
+
* @property {Record<string, unknown>} [props] - Geom-specific props spread onto the component
|
|
18
|
+
* (`Region`'s `points`/`label`, `Rule`'s `slope`, `Hull`'s `padding`). For the annotation
|
|
19
|
+
* geoms (`rule`, `region`) these are the ONLY props: they read no data, and a Region's `x`/`y`
|
|
20
|
+
* are ranges, so inheriting the spec's field names would be wrong.
|
|
17
21
|
*/
|
|
18
22
|
|
|
19
23
|
/**
|
package/dist/lib/preset.d.ts
CHANGED
package/dist/lib/preset.js
CHANGED
|
@@ -1,36 +1,71 @@
|
|
|
1
1
|
// packages/chart/src/lib/preset.js
|
|
2
2
|
|
|
3
3
|
export const defaultPreset = {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
4
|
+
colors: [
|
|
5
|
+
'blue',
|
|
6
|
+
'emerald',
|
|
7
|
+
'rose',
|
|
8
|
+
'amber',
|
|
9
|
+
'violet',
|
|
10
|
+
'sky',
|
|
11
|
+
'pink',
|
|
12
|
+
'teal',
|
|
13
|
+
'orange',
|
|
14
|
+
'indigo',
|
|
15
|
+
'lime',
|
|
16
|
+
'cyan',
|
|
17
|
+
'gold',
|
|
18
|
+
'lavender'
|
|
19
|
+
],
|
|
20
|
+
shades: {
|
|
21
|
+
light: { fill: '300', stroke: '700' },
|
|
22
|
+
dark: { fill: '500', stroke: '200' }
|
|
23
|
+
},
|
|
24
|
+
// Per-geom default mark opacity. `alpha` (a fixed 0–1 prop on each geom / Plot.Root)
|
|
25
|
+
// overrides these; solid marks default to 1 so `alpha` is the single opacity knob.
|
|
26
|
+
opacity: {
|
|
27
|
+
area: 0.6,
|
|
28
|
+
bar: 1,
|
|
29
|
+
line: 1,
|
|
30
|
+
point: 0.8,
|
|
31
|
+
arc: 1,
|
|
32
|
+
box: 0.5,
|
|
33
|
+
violin: 0.5,
|
|
34
|
+
jitter: 0.8,
|
|
35
|
+
heatmap: 1,
|
|
36
|
+
candlestick: 1,
|
|
37
|
+
hexbin: 1,
|
|
38
|
+
ribbon: 0.5,
|
|
39
|
+
waterfall: 1,
|
|
40
|
+
rule: 1,
|
|
41
|
+
hull: 0.15,
|
|
42
|
+
contour: 0.5,
|
|
43
|
+
radar: 0.25
|
|
44
|
+
},
|
|
45
|
+
patterns: [
|
|
46
|
+
'diagonal',
|
|
47
|
+
'dots',
|
|
48
|
+
'triangles',
|
|
49
|
+
'hatch',
|
|
50
|
+
'lattice',
|
|
51
|
+
'swell',
|
|
52
|
+
'checkerboard',
|
|
53
|
+
'waves',
|
|
54
|
+
'petals',
|
|
55
|
+
'brick',
|
|
56
|
+
'diamonds',
|
|
57
|
+
'tile',
|
|
58
|
+
'scales',
|
|
59
|
+
'circles',
|
|
60
|
+
'pip',
|
|
61
|
+
'rings',
|
|
62
|
+
'chevrons',
|
|
63
|
+
'shards',
|
|
64
|
+
'wedge',
|
|
65
|
+
'argyle',
|
|
66
|
+
'shell'
|
|
67
|
+
],
|
|
68
|
+
symbols: ['circle', 'square', 'triangle', 'diamond', 'cross', 'star']
|
|
34
69
|
}
|
|
35
70
|
|
|
36
71
|
/**
|
|
@@ -40,15 +75,15 @@ export const defaultPreset = {
|
|
|
40
75
|
* @returns {typeof defaultPreset}
|
|
41
76
|
*/
|
|
42
77
|
export function createChartPreset(overrides = {}) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
78
|
+
return {
|
|
79
|
+
...defaultPreset,
|
|
80
|
+
...overrides,
|
|
81
|
+
shades: overrides.shades
|
|
82
|
+
? {
|
|
83
|
+
light: { ...defaultPreset.shades.light, ...overrides.shades.light },
|
|
84
|
+
dark: { ...defaultPreset.shades.dark, ...overrides.shades.dark }
|
|
85
|
+
}
|
|
86
|
+
: defaultPreset.shades,
|
|
87
|
+
opacity: { ...defaultPreset.opacity, ...overrides.opacity }
|
|
88
|
+
}
|
|
54
89
|
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Screen span of a [lo, hi] range on one axis. A band category contributes its whole band,
|
|
3
|
+
* so `x={['a', 'b']}` shades both columns edge to edge. Omitted → the full range; a `null`
|
|
4
|
+
* end runs to that edge of the axis, so `[p75, null]` is "the top quartile and beyond".
|
|
5
|
+
*
|
|
6
|
+
* @param {AnyScale} scale
|
|
7
|
+
* @param {unknown[] | undefined} range
|
|
8
|
+
* @returns {[number, number]}
|
|
9
|
+
*/
|
|
10
|
+
export function spanOf(scale: AnyScale, range: unknown[] | undefined): [number, number];
|
|
11
|
+
/**
|
|
12
|
+
* Position of a single data value — band categories resolve to the band centre, which is
|
|
13
|
+
* where a point in that category is drawn.
|
|
14
|
+
*
|
|
15
|
+
* @param {AnyScale} scale
|
|
16
|
+
* @param {unknown} v
|
|
17
|
+
*/
|
|
18
|
+
export function positionOf(scale: AnyScale, v: unknown): number;
|
|
19
|
+
/**
|
|
20
|
+
* The region's vertices in (u, v) plot coordinates — before orientation `place`.
|
|
21
|
+
*
|
|
22
|
+
* `points` (a polygon, three or more vertices) wins over `x`/`y` ranges. Fewer than three
|
|
23
|
+
* points is not an area, so it resolves to nothing rather than a degenerate sliver.
|
|
24
|
+
*
|
|
25
|
+
* @param {{ x?: unknown[], y?: unknown[], points?: unknown[][] }} shape
|
|
26
|
+
* @param {AnyScale} xScale
|
|
27
|
+
* @param {AnyScale} yScale
|
|
28
|
+
* @returns {Array<[number, number]>}
|
|
29
|
+
*/
|
|
30
|
+
export function regionVertices(shape: {
|
|
31
|
+
x?: unknown[];
|
|
32
|
+
y?: unknown[];
|
|
33
|
+
points?: unknown[][];
|
|
34
|
+
}, xScale: AnyScale, yScale: AnyScale): Array<[number, number]>;
|
|
35
|
+
/**
|
|
36
|
+
* Area centroid of a simple polygon — where its label goes. Falls back to the vertex mean for
|
|
37
|
+
* a zero-area (collinear) polygon, which has no area centroid.
|
|
38
|
+
*
|
|
39
|
+
* @param {Array<{ x: number, y: number }>} pts
|
|
40
|
+
* @returns {{ x: number, y: number }}
|
|
41
|
+
*/
|
|
42
|
+
export function centroid(pts: Array<{
|
|
43
|
+
x: number;
|
|
44
|
+
y: number;
|
|
45
|
+
}>): {
|
|
46
|
+
x: number;
|
|
47
|
+
y: number;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* SVG path data for a closed polygon.
|
|
51
|
+
*
|
|
52
|
+
* @param {Array<{ x: number, y: number }>} pts
|
|
53
|
+
*/
|
|
54
|
+
export function polygonPath(pts: Array<{
|
|
55
|
+
x: number;
|
|
56
|
+
y: number;
|
|
57
|
+
}>): string;
|
|
58
|
+
export type AnyScale = {
|
|
59
|
+
(v: unknown): number | undefined;
|
|
60
|
+
bandwidth?: () => number;
|
|
61
|
+
range: () => number[];
|
|
62
|
+
domain: () => unknown[];
|
|
63
|
+
};
|