@rokkit/chart 1.0.0-next.15 → 1.0.0-next.150
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/LICENSE +1 -1
- package/README.md +150 -46
- package/dist/Plot/index.d.ts +9 -0
- package/dist/PlotState.svelte.d.ts +47 -0
- package/dist/crossfilter/createCrossFilter.svelte.d.ts +15 -0
- package/dist/elements/index.d.ts +6 -0
- package/dist/geoms/lib/areas.d.ts +52 -0
- package/dist/geoms/lib/bars.d.ts +3 -0
- package/dist/index.d.ts +51 -0
- package/dist/lib/brewer.d.ts +9 -0
- package/dist/lib/brewing/BoxBrewer.svelte.d.ts +10 -0
- package/dist/lib/brewing/CartesianBrewer.svelte.d.ts +8 -0
- package/dist/lib/brewing/PieBrewer.svelte.d.ts +8 -0
- package/dist/lib/brewing/ViolinBrewer.svelte.d.ts +9 -0
- package/dist/lib/brewing/axes.svelte.d.ts +66 -0
- package/dist/lib/brewing/bars.svelte.d.ts +56 -0
- package/dist/lib/brewing/brewer.svelte.d.ts +145 -0
- package/dist/lib/brewing/colors.d.ts +17 -0
- package/dist/lib/brewing/dimensions.svelte.d.ts +35 -0
- package/dist/lib/brewing/index.svelte.d.ts +118 -0
- package/dist/lib/brewing/legends.svelte.d.ts +48 -0
- package/dist/lib/brewing/marks/arcs.d.ts +17 -0
- package/dist/lib/brewing/marks/areas.d.ts +31 -0
- package/dist/lib/brewing/marks/bars.d.ts +1 -0
- package/dist/lib/brewing/marks/boxes.d.ts +24 -0
- package/dist/lib/brewing/marks/lines.d.ts +24 -0
- package/dist/lib/brewing/marks/points.d.ts +40 -0
- package/dist/lib/brewing/marks/violins.d.ts +20 -0
- package/dist/lib/brewing/patterns.d.ts +14 -0
- package/dist/lib/brewing/scales.d.ts +28 -0
- package/dist/lib/brewing/scales.svelte.d.ts +24 -0
- package/dist/lib/brewing/stats.d.ts +31 -0
- package/dist/lib/brewing/symbols.d.ts +7 -0
- package/dist/lib/brewing/types.d.ts +162 -0
- package/dist/lib/chart.d.ts +40 -0
- package/dist/lib/context.d.ts +13 -0
- package/dist/lib/grid.d.ts +72 -0
- package/dist/lib/plot/chartProps.d.ts +177 -0
- package/dist/lib/plot/crossfilter.d.ts +13 -0
- package/dist/lib/plot/facet.d.ts +24 -0
- package/dist/lib/plot/frames.d.ts +47 -0
- package/dist/lib/plot/helpers.d.ts +3 -0
- package/dist/lib/plot/preset.d.ts +29 -0
- package/dist/lib/plot/scales.d.ts +5 -0
- package/dist/lib/plot/stat.d.ts +32 -0
- package/dist/lib/plot/types.d.ts +89 -0
- package/dist/lib/scales.svelte.d.ts +35 -0
- package/dist/lib/swatch.d.ts +12 -0
- package/dist/lib/ticks.d.ts +36 -0
- package/dist/lib/utils.d.ts +61 -0
- package/dist/lib/xscale.d.ts +11 -0
- package/dist/patterns/index.d.ts +4 -0
- package/dist/patterns/patterns.d.ts +72 -0
- package/dist/patterns/scale.d.ts +30 -0
- package/dist/symbols/constants/index.d.ts +1 -0
- package/dist/symbols/index.d.ts +5 -0
- package/package.json +41 -45
- package/src/AnimatedPlot.svelte +214 -0
- package/src/Chart.svelte +101 -0
- package/src/FacetPlot/Panel.svelte +23 -0
- package/src/FacetPlot.svelte +90 -0
- package/src/Plot/Arc.svelte +29 -0
- package/src/Plot/Area.svelte +25 -0
- package/src/Plot/Axis.svelte +73 -0
- package/src/Plot/Bar.svelte +96 -0
- package/src/Plot/Grid.svelte +30 -0
- package/src/Plot/Legend.svelte +167 -0
- package/src/Plot/Line.svelte +27 -0
- package/src/Plot/Point.svelte +27 -0
- package/src/Plot/Root.svelte +107 -0
- package/src/Plot/Timeline.svelte +95 -0
- package/src/Plot/Tooltip.svelte +81 -0
- package/src/Plot/index.js +9 -0
- package/src/Plot.svelte +189 -0
- package/src/PlotState.svelte.js +278 -0
- package/src/Sparkline.svelte +69 -0
- package/src/Symbol.svelte +21 -0
- package/src/Texture.svelte +18 -0
- package/src/charts/AreaChart.svelte +25 -0
- package/src/charts/BarChart.svelte +26 -0
- package/src/charts/BoxPlot.svelte +21 -0
- package/src/charts/BubbleChart.svelte +23 -0
- package/src/charts/LineChart.svelte +26 -0
- package/src/charts/PieChart.svelte +25 -0
- package/src/charts/ScatterPlot.svelte +25 -0
- package/src/charts/ViolinPlot.svelte +21 -0
- package/src/crossfilter/CrossFilter.svelte +38 -0
- package/src/crossfilter/FilterBar.svelte +32 -0
- package/src/crossfilter/FilterSlider.svelte +79 -0
- package/src/crossfilter/createCrossFilter.svelte.js +113 -0
- package/src/elements/Bar.svelte +22 -24
- package/src/elements/ColorRamp.svelte +20 -22
- package/src/elements/ContinuousLegend.svelte +20 -17
- package/src/elements/DefinePatterns.svelte +24 -0
- package/src/elements/DiscreteLegend.svelte +15 -15
- package/src/elements/Label.svelte +4 -8
- package/src/elements/SymbolGrid.svelte +22 -0
- package/src/elements/index.js +6 -0
- package/src/examples/BarChartExample.svelte +81 -0
- package/src/geoms/Arc.svelte +81 -0
- package/src/geoms/Area.svelte +50 -0
- package/src/geoms/Bar.svelte +142 -0
- package/src/geoms/Box.svelte +101 -0
- package/src/geoms/LabelPill.svelte +17 -0
- package/src/geoms/Line.svelte +100 -0
- package/src/geoms/Point.svelte +100 -0
- package/src/geoms/Violin.svelte +44 -0
- package/src/geoms/lib/areas.js +131 -0
- package/src/geoms/lib/bars.js +172 -0
- package/src/index.js +67 -16
- package/src/lib/brewer.js +25 -0
- package/src/lib/brewing/BoxBrewer.svelte.js +56 -0
- package/src/lib/brewing/CartesianBrewer.svelte.js +16 -0
- package/src/lib/brewing/PieBrewer.svelte.js +14 -0
- package/src/lib/brewing/ViolinBrewer.svelte.js +55 -0
- package/src/lib/brewing/axes.svelte.js +270 -0
- package/src/lib/brewing/bars.svelte.js +201 -0
- package/src/lib/brewing/brewer.svelte.js +229 -0
- package/src/lib/brewing/colors.js +22 -0
- package/src/lib/brewing/dimensions.svelte.js +56 -0
- package/src/lib/brewing/index.svelte.js +205 -0
- package/src/lib/brewing/legends.svelte.js +137 -0
- package/src/lib/brewing/marks/arcs.js +43 -0
- package/src/lib/brewing/marks/areas.js +59 -0
- package/src/lib/brewing/marks/bars.js +49 -0
- package/src/lib/brewing/marks/boxes.js +75 -0
- package/src/lib/brewing/marks/lines.js +48 -0
- package/src/lib/brewing/marks/points.js +57 -0
- package/src/lib/brewing/marks/violins.js +90 -0
- package/src/lib/brewing/patterns.js +31 -0
- package/src/lib/brewing/scales.js +51 -0
- package/src/lib/brewing/scales.svelte.js +82 -0
- package/src/lib/brewing/stats.js +62 -0
- package/src/lib/brewing/symbols.js +10 -0
- package/src/lib/brewing/types.js +73 -0
- package/src/lib/chart.js +213 -0
- package/src/lib/context.js +131 -0
- package/src/lib/grid.js +85 -0
- package/src/lib/plot/chartProps.js +76 -0
- package/src/lib/plot/crossfilter.js +16 -0
- package/src/lib/plot/facet.js +58 -0
- package/src/lib/plot/frames.js +80 -0
- package/src/lib/plot/helpers.js +14 -0
- package/src/lib/plot/preset.js +53 -0
- package/src/lib/plot/scales.js +56 -0
- package/src/lib/plot/stat.js +92 -0
- package/src/lib/plot/types.js +65 -0
- package/src/lib/scales.svelte.js +151 -0
- package/src/lib/swatch.js +13 -0
- package/src/lib/ticks.js +46 -0
- package/src/lib/utils.js +111 -118
- package/src/lib/xscale.js +31 -0
- package/src/patterns/DefinePatterns.svelte +32 -0
- package/src/patterns/PatternDef.svelte +27 -0
- package/src/patterns/README.md +3 -0
- package/src/patterns/index.js +4 -0
- package/src/patterns/patterns.js +208 -0
- package/src/patterns/scale.js +87 -0
- package/src/spec/chart-spec.js +29 -0
- package/src/symbols/RoundedSquare.svelte +33 -0
- package/src/symbols/Shape.svelte +37 -0
- package/src/symbols/constants/index.js +4 -0
- package/src/symbols/index.js +9 -0
- package/src/symbols/outline.svelte +60 -0
- package/src/symbols/solid.svelte +60 -0
- package/src/chart/FacetGrid.svelte +0 -51
- package/src/chart/Grid.svelte +0 -34
- package/src/chart/Legend.svelte +0 -16
- package/src/chart/PatternDefs.svelte +0 -13
- package/src/chart/Swatch.svelte +0 -93
- package/src/chart/SwatchButton.svelte +0 -29
- package/src/chart/SwatchGrid.svelte +0 -55
- package/src/chart/Symbol.svelte +0 -37
- package/src/chart/Texture.svelte +0 -16
- package/src/chart/TexturedShape.svelte +0 -27
- package/src/chart/TimelapseChart.svelte +0 -97
- package/src/chart/Timer.svelte +0 -27
- package/src/chart.js +0 -9
- package/src/components/charts/Axis.svelte +0 -66
- package/src/components/charts/Chart.svelte +0 -35
- package/src/components/index.js +0 -23
- package/src/components/lib/axis.js +0 -0
- package/src/components/lib/chart.js +0 -187
- package/src/components/lib/color.js +0 -327
- package/src/components/lib/funnel.js +0 -204
- package/src/components/lib/index.js +0 -19
- package/src/components/lib/pattern.js +0 -190
- package/src/components/lib/rollup.js +0 -55
- package/src/components/lib/shape.js +0 -199
- package/src/components/lib/summary.js +0 -145
- package/src/components/lib/theme.js +0 -23
- package/src/components/lib/timer.js +0 -41
- package/src/components/lib/utils.js +0 -165
- package/src/components/plots/BarPlot.svelte +0 -36
- package/src/components/plots/BoxPlot.svelte +0 -54
- package/src/components/plots/ScatterPlot.svelte +0 -30
- package/src/components/store.js +0 -70
- package/src/constants.js +0 -66
- package/src/elements/PatternDefs.svelte +0 -13
- package/src/elements/PatternMask.svelte +0 -20
- package/src/elements/Symbol.svelte +0 -38
- package/src/elements/Tooltip.svelte +0 -23
- package/src/funnel.svelte +0 -35
- package/src/geom.js +0 -105
- package/src/lib/axis.js +0 -75
- package/src/lib/colors.js +0 -32
- package/src/lib/geom.js +0 -4
- package/src/lib/shapes.js +0 -144
- package/src/lib/timer.js +0 -44
- package/src/lookup.js +0 -29
- package/src/plots/BarPlot.svelte +0 -55
- package/src/plots/BoxPlot.svelte +0 -0
- package/src/plots/FunnelPlot.svelte +0 -33
- package/src/plots/HeatMap.svelte +0 -5
- package/src/plots/HeatMapCalendar.svelte +0 -129
- package/src/plots/LinePlot.svelte +0 -55
- package/src/plots/Plot.svelte +0 -25
- package/src/plots/RankBarPlot.svelte +0 -38
- package/src/plots/ScatterPlot.svelte +0 -20
- package/src/plots/ViolinPlot.svelte +0 -11
- package/src/plots/heatmap.js +0 -70
- package/src/plots/index.js +0 -10
- package/src/swatch.js +0 -11
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
// import { scaleBand } from 'd3-scale'
|
|
2
|
+
|
|
3
|
+
import {} from './types.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {import('./types').TickData} TickData
|
|
7
|
+
* @typedef {import('./types').AxisData} AxisData
|
|
8
|
+
* @typedef {import('./types').ChartScales} ChartScales
|
|
9
|
+
* @typedef {import('./types').ChartDimensions} ChartDimensions
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const IDENTITY = (v) => v
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {Object} xScale
|
|
16
|
+
* @param {number|null} tickCount
|
|
17
|
+
* @returns {Array}
|
|
18
|
+
*/
|
|
19
|
+
function xTicksFromContinuous(xScale, tickCount) {
|
|
20
|
+
return tickCount !== null ? xScale.ticks(tickCount) : xScale.ticks()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {Array} domain
|
|
25
|
+
* @param {number} tickCount
|
|
26
|
+
* @returns {Array}
|
|
27
|
+
*/
|
|
28
|
+
function downsampleDomain(domain, tickCount) {
|
|
29
|
+
const step = Math.max(1, Math.floor(domain.length / tickCount))
|
|
30
|
+
return domain.filter((_, i) => i % step === 0)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param {Object} xScale
|
|
35
|
+
* @param {number|null} tickCount
|
|
36
|
+
* @returns {Array}
|
|
37
|
+
*/
|
|
38
|
+
function xTicksFromBand(xScale, tickCount) {
|
|
39
|
+
const domain = xScale.domain()
|
|
40
|
+
if (tickCount === null || tickCount >= domain.length) return domain
|
|
41
|
+
return downsampleDomain(domain, tickCount)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @param {Object} xScale
|
|
46
|
+
* @param {number|null} tickCount
|
|
47
|
+
* @returns {Array}
|
|
48
|
+
*/
|
|
49
|
+
function resolveXTicks(xScale, tickCount) {
|
|
50
|
+
return xScale.ticks ? xTicksFromContinuous(xScale, tickCount) : xTicksFromBand(xScale, tickCount)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param {Object} xScale
|
|
55
|
+
* @param {unknown} value
|
|
56
|
+
* @returns {number}
|
|
57
|
+
*/
|
|
58
|
+
function xPosition(xScale, value) {
|
|
59
|
+
return xScale.bandwidth ? xScale(value) + xScale.bandwidth() / 2 : xScale(value)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @param {Object} scale
|
|
64
|
+
* @param {Array} values
|
|
65
|
+
* @param {Function} formatter
|
|
66
|
+
* @param {(scale: Object, v: unknown) => number} getPosition
|
|
67
|
+
* @returns {Array}
|
|
68
|
+
*/
|
|
69
|
+
function mapTicks(scale, values, formatter, getPosition) {
|
|
70
|
+
return values.map((value) => ({
|
|
71
|
+
value,
|
|
72
|
+
position: getPosition(scale, value),
|
|
73
|
+
formattedValue: formatter(value)
|
|
74
|
+
}))
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @param {Object} options
|
|
79
|
+
* @returns {{ tickCount: number|null, formatter: Function, label: string }}
|
|
80
|
+
*/
|
|
81
|
+
function parseAxisOptions(options) {
|
|
82
|
+
const opts = options || {}
|
|
83
|
+
return {
|
|
84
|
+
tickCount: opts.tickCount !== undefined ? opts.tickCount : null,
|
|
85
|
+
formatter: opts.tickFormat ? opts.tickFormat : IDENTITY,
|
|
86
|
+
label: opts.label ? opts.label : ''
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Creates x-axis tick data for rendering
|
|
92
|
+
*
|
|
93
|
+
* @param {Object} scales - Chart scales
|
|
94
|
+
* @param {Function} scales.x - X-axis scale
|
|
95
|
+
* @param {Object} dimensions - Chart dimensions
|
|
96
|
+
* @param {Object} options - Axis options
|
|
97
|
+
* @param {number} [options.tickCount] - Number of ticks to show
|
|
98
|
+
* @param {Function} [options.tickFormat] - Tick formatting function
|
|
99
|
+
* @param {string} [options.label] - Axis label
|
|
100
|
+
* @returns {AxisData} Axis rendering data
|
|
101
|
+
*/
|
|
102
|
+
export function createXAxis(scales, dimensions, options) {
|
|
103
|
+
if (!scales.x) return { ticks: [] }
|
|
104
|
+
const { tickCount, formatter, label } = parseAxisOptions(options)
|
|
105
|
+
const ticks = mapTicks(scales.x, resolveXTicks(scales.x, tickCount), formatter, xPosition)
|
|
106
|
+
return {
|
|
107
|
+
ticks,
|
|
108
|
+
label,
|
|
109
|
+
transform: `translate(0, ${dimensions.innerHeight})`,
|
|
110
|
+
labelTransform: `translate(${dimensions.innerWidth / 2}, 35)`
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @param {Object} yScale
|
|
116
|
+
* @param {number|null} tickCount
|
|
117
|
+
* @returns {Array}
|
|
118
|
+
*/
|
|
119
|
+
function yTicksFromDomain(yScale, tickCount) {
|
|
120
|
+
const domain = yScale.domain()
|
|
121
|
+
if (tickCount === null || domain.length <= tickCount) return domain
|
|
122
|
+
return downsampleDomain(domain, tickCount)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* @param {Object} yScale
|
|
127
|
+
* @param {number|null} tickCount
|
|
128
|
+
* @returns {Array}
|
|
129
|
+
*/
|
|
130
|
+
function resolveYTicks(yScale, tickCount) {
|
|
131
|
+
if (yScale.ticks) return tickCount !== null ? yScale.ticks(tickCount) : yScale.ticks()
|
|
132
|
+
if (yScale.domain) return yTicksFromDomain(yScale, tickCount)
|
|
133
|
+
return []
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* @param {Object} scale
|
|
138
|
+
* @param {unknown} value
|
|
139
|
+
* @returns {unknown}
|
|
140
|
+
*/
|
|
141
|
+
function yPositionPassthrough(scale, value) {
|
|
142
|
+
return scale(value)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Creates y-axis tick data for rendering
|
|
147
|
+
*
|
|
148
|
+
* @param {Object} scales - Chart scales
|
|
149
|
+
* @param {Function} scales.y - Y-axis scale
|
|
150
|
+
* @param {Object} dimensions - Chart dimensions
|
|
151
|
+
* @param {Object} options - Axis options
|
|
152
|
+
* @param {number} [options.tickCount] - Number of ticks to show
|
|
153
|
+
* @param {Function} [options.tickFormat] - Tick formatting function
|
|
154
|
+
* @param {string} [options.label] - Axis label
|
|
155
|
+
* @returns {AxisData} Axis rendering data
|
|
156
|
+
*/
|
|
157
|
+
export function createYAxis(scales, dimensions, options) {
|
|
158
|
+
if (!scales.y) return { ticks: [] }
|
|
159
|
+
const { tickCount, formatter, label } = parseAxisOptions(options)
|
|
160
|
+
const ticks = mapTicks(
|
|
161
|
+
scales.y,
|
|
162
|
+
resolveYTicks(scales.y, tickCount),
|
|
163
|
+
formatter,
|
|
164
|
+
yPositionPassthrough
|
|
165
|
+
)
|
|
166
|
+
return {
|
|
167
|
+
ticks,
|
|
168
|
+
label,
|
|
169
|
+
transform: 'translate(0, 0)',
|
|
170
|
+
labelTransform: `translate(-40, ${dimensions.innerHeight / 2}) rotate(-90)`
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* @param {Object} scales
|
|
176
|
+
* @param {Object} dimensions
|
|
177
|
+
* @param {number|null} xTickCount
|
|
178
|
+
* @returns {Array}
|
|
179
|
+
*/
|
|
180
|
+
function buildXLines(scales, dimensions, xTickCount) {
|
|
181
|
+
return createXAxis(scales, dimensions, { tickCount: xTickCount }).ticks.map((tick) => ({
|
|
182
|
+
x1: tick.position,
|
|
183
|
+
y1: 0,
|
|
184
|
+
x2: tick.position,
|
|
185
|
+
y2: dimensions.innerHeight
|
|
186
|
+
}))
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* @param {Object} scales
|
|
191
|
+
* @param {Object} dimensions
|
|
192
|
+
* @param {number|null} yTickCount
|
|
193
|
+
* @returns {Array}
|
|
194
|
+
*/
|
|
195
|
+
function buildYLines(scales, dimensions, yTickCount) {
|
|
196
|
+
return createYAxis(scales, dimensions, { tickCount: yTickCount }).ticks.map((tick) => ({
|
|
197
|
+
x1: 0,
|
|
198
|
+
y1: tick.position,
|
|
199
|
+
x2: dimensions.innerWidth,
|
|
200
|
+
y2: tick.position
|
|
201
|
+
}))
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* @param {Object} opts
|
|
206
|
+
* @returns {{ direction: string, xTickCount: number|null, yTickCount: number|null }}
|
|
207
|
+
*/
|
|
208
|
+
function parseGridOptions(opts) {
|
|
209
|
+
const o = opts || {}
|
|
210
|
+
return {
|
|
211
|
+
direction: o.direction !== undefined ? o.direction : 'both',
|
|
212
|
+
xTickCount: o.xTickCount !== undefined ? o.xTickCount : null,
|
|
213
|
+
yTickCount: o.yTickCount !== undefined ? o.yTickCount : null
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* @param {string} direction
|
|
219
|
+
* @returns {{ showX: boolean, showY: boolean }}
|
|
220
|
+
*/
|
|
221
|
+
function gridDirections(direction) {
|
|
222
|
+
return {
|
|
223
|
+
showX: direction === 'x' || direction === 'both',
|
|
224
|
+
showY: direction === 'y' || direction === 'both'
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Creates grid line data for rendering
|
|
230
|
+
*
|
|
231
|
+
* @param {Object} scales - Chart scales
|
|
232
|
+
* @param {Object} dimensions - Chart dimensions
|
|
233
|
+
* @param {Object} options - Grid options
|
|
234
|
+
* @param {string} [options.direction='both'] - Grid direction ('x', 'y', or 'both')
|
|
235
|
+
* @param {number} [options.xTickCount] - Number of x-axis ticks
|
|
236
|
+
* @param {number} [options.yTickCount] - Number of y-axis ticks
|
|
237
|
+
* @returns {Object} Grid rendering data
|
|
238
|
+
*/
|
|
239
|
+
export function createGrid(scales, dimensions, options) {
|
|
240
|
+
const { direction, xTickCount, yTickCount } = parseGridOptions(options)
|
|
241
|
+
const { showX, showY } = gridDirections(direction)
|
|
242
|
+
return {
|
|
243
|
+
xLines: showX && scales.x ? buildXLines(scales, dimensions, xTickCount) : [],
|
|
244
|
+
yLines: showY && scales.y ? buildYLines(scales, dimensions, yTickCount) : []
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Creates DOM attributes for a tick element
|
|
250
|
+
*
|
|
251
|
+
* @param {TickData} tick - Tick data
|
|
252
|
+
* @param {string} axis - Axis type ('x' or 'y')
|
|
253
|
+
* @returns {Object} Attributes for the tick
|
|
254
|
+
*/
|
|
255
|
+
export function createTickAttributes(tick, axis) {
|
|
256
|
+
if (axis === 'x') {
|
|
257
|
+
return {
|
|
258
|
+
'data-plot-tick': 'major',
|
|
259
|
+
transform: `translate(${tick.position}, 0)`,
|
|
260
|
+
'text-anchor': 'middle'
|
|
261
|
+
}
|
|
262
|
+
} else {
|
|
263
|
+
return {
|
|
264
|
+
'data-plot-tick': 'major',
|
|
265
|
+
transform: `translate(0, ${tick.position})`,
|
|
266
|
+
'text-anchor': 'end',
|
|
267
|
+
'dominant-baseline': 'middle'
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { SvelteSet } from 'svelte/reactivity'
|
|
2
|
+
import {} from './types.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {import('./types').BarData} BarData
|
|
6
|
+
* @typedef {import('./types').ScaleFields} ScaleFields
|
|
7
|
+
* @typedef {import('./types').ChartScales} ChartScales
|
|
8
|
+
* @typedef {import('./types').ChartDimensions} ChartDimensions
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const DEFAULT_COLOR = '#4682b4'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {Object} d - data item
|
|
15
|
+
* @param {{ fields: Object, scales: Object, dimensions: Object, defaultColor: string }} ctx
|
|
16
|
+
* @returns {BarData}
|
|
17
|
+
*/
|
|
18
|
+
function buildBar(d, ctx) {
|
|
19
|
+
const { fields, scales, dimensions, defaultColor } = ctx
|
|
20
|
+
const { x: xField, y: yField, color: colorField } = fields
|
|
21
|
+
const barWidth = scales.x.bandwidth ? scales.x.bandwidth() : 10
|
|
22
|
+
const barX = scales.x.bandwidth ? scales.x(d[xField]) : scales.x(d[xField]) - barWidth / 2
|
|
23
|
+
return {
|
|
24
|
+
data: d,
|
|
25
|
+
x: barX,
|
|
26
|
+
y: scales.y(d[yField]),
|
|
27
|
+
width: barWidth,
|
|
28
|
+
height: dimensions.innerHeight - scales.y(d[yField]),
|
|
29
|
+
color: colorField && scales.color ? scales.color(d[colorField]) : defaultColor
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param {Object} options
|
|
35
|
+
* @param {Object} fields
|
|
36
|
+
* @param {Object} scales
|
|
37
|
+
* @returns {{ ctx: Object }|null} null if input is invalid
|
|
38
|
+
*/
|
|
39
|
+
function parseBarsInput(options, fields, scales) {
|
|
40
|
+
if (!scales.x || !scales.y) return null
|
|
41
|
+
const opts = options || {}
|
|
42
|
+
return {
|
|
43
|
+
fields,
|
|
44
|
+
scales,
|
|
45
|
+
dimensions: opts.dimensions,
|
|
46
|
+
defaultColor: opts.defaultColor !== undefined ? opts.defaultColor : DEFAULT_COLOR
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Creates bar data for rendering
|
|
52
|
+
*
|
|
53
|
+
* @param {Array} data - Chart data
|
|
54
|
+
* @param {Object} fields - Field mappings
|
|
55
|
+
* @param {string} fields.x - X-axis field
|
|
56
|
+
* @param {string} fields.y - Y-axis field
|
|
57
|
+
* @param {string} fields.color - Color field (optional)
|
|
58
|
+
* @param {Object} scales - Chart scales
|
|
59
|
+
* @param {Function} scales.x - X-axis scale
|
|
60
|
+
* @param {Function} scales.y - Y-axis scale
|
|
61
|
+
* @param {Function} scales.color - Color scale
|
|
62
|
+
* @param {Object} options - Options including dimensions and defaultColor
|
|
63
|
+
* @param {Object} options.dimensions - Chart dimensions
|
|
64
|
+
* @param {string} [options.defaultColor='#4682b4'] - Default color if no color scale
|
|
65
|
+
* @returns {BarData[]} Bar data for rendering
|
|
66
|
+
*/
|
|
67
|
+
export function createBars(data, fields, scales, options) {
|
|
68
|
+
if (!data || !data.length) return []
|
|
69
|
+
const ctx = parseBarsInput(options, fields, scales)
|
|
70
|
+
if (!ctx) return []
|
|
71
|
+
return data.map((d) => buildBar(d, ctx))
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Filter bars based on a selection criteria
|
|
76
|
+
*
|
|
77
|
+
* @param {BarData[]} bars - Bar data array
|
|
78
|
+
* @param {Object} selection - Selection criteria
|
|
79
|
+
* @returns {BarData[]} Filtered bars
|
|
80
|
+
*/
|
|
81
|
+
export function filterBars(bars, selection) {
|
|
82
|
+
if (!selection) return bars
|
|
83
|
+
|
|
84
|
+
return bars.filter((bar) => {
|
|
85
|
+
for (const [key, value] of Object.entries(selection)) {
|
|
86
|
+
if (bar.data[key] !== value) return false
|
|
87
|
+
}
|
|
88
|
+
return true
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @param {Object} item - data item
|
|
94
|
+
* @param {Object} scales - chart scales
|
|
95
|
+
* @param {{ yField: string, colorField: string, group: unknown, barX: number, barWidth: number, dimensions: Object }} ctx
|
|
96
|
+
* @returns {BarData}
|
|
97
|
+
*/
|
|
98
|
+
function buildGroupBar(item, scales, ctx) {
|
|
99
|
+
const { yField, colorField, group, barX, barWidth, dimensions } = ctx
|
|
100
|
+
return {
|
|
101
|
+
data: item,
|
|
102
|
+
group,
|
|
103
|
+
x: barX,
|
|
104
|
+
y: scales.y(item[yField]),
|
|
105
|
+
width: barWidth,
|
|
106
|
+
height: dimensions.innerHeight - scales.y(item[yField]),
|
|
107
|
+
color: scales.color ? scales.color(item[colorField]) : DEFAULT_COLOR
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @param {Object} scales
|
|
113
|
+
* @param {{ groupItems: Array, groupField: string, group: unknown, i: number, yField: string, colorField: string, barWidth: number, padding: number, dimensions: Object, xPos: number }} ctx
|
|
114
|
+
* @returns {BarData|null}
|
|
115
|
+
*/
|
|
116
|
+
function buildOneGroupBar(scales, ctx) {
|
|
117
|
+
const {
|
|
118
|
+
groupItems,
|
|
119
|
+
groupField,
|
|
120
|
+
group,
|
|
121
|
+
i,
|
|
122
|
+
yField,
|
|
123
|
+
colorField,
|
|
124
|
+
barWidth,
|
|
125
|
+
padding,
|
|
126
|
+
dimensions,
|
|
127
|
+
xPos
|
|
128
|
+
} = ctx
|
|
129
|
+
const item = groupItems.find((d) => d[groupField] === group)
|
|
130
|
+
if (!item) return null
|
|
131
|
+
const barX = xPos + i * (barWidth + padding)
|
|
132
|
+
return buildGroupBar(item, scales, { yField, colorField, group, barX, barWidth, dimensions })
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* @param {Object} fields
|
|
137
|
+
* @param {Object} options
|
|
138
|
+
* @returns {{ xField: string, yField: string, groupField: string, colorField: string, dimensions: Object, padding: number }}
|
|
139
|
+
*/
|
|
140
|
+
function parseGroupedBarsConfig(fields, options) {
|
|
141
|
+
const opts = options || {}
|
|
142
|
+
return {
|
|
143
|
+
xField: fields.x,
|
|
144
|
+
yField: fields.y,
|
|
145
|
+
groupField: fields.group,
|
|
146
|
+
colorField: fields.color !== undefined ? fields.color : fields.group,
|
|
147
|
+
dimensions: opts.dimensions,
|
|
148
|
+
padding: opts.padding !== undefined ? opts.padding : 0.1
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Creates a grouped bars layout
|
|
154
|
+
*
|
|
155
|
+
* @param {Array} data - Chart data
|
|
156
|
+
* @param {Object} fields - Field mappings
|
|
157
|
+
* @param {Object} scales - Chart scales
|
|
158
|
+
* @param {Object} options - Options including dimensions and padding
|
|
159
|
+
* @param {Object} options.dimensions - Chart dimensions
|
|
160
|
+
* @param {number} [options.padding=0.1] - Padding between bars in a group
|
|
161
|
+
* @returns {Object} Grouped bar data
|
|
162
|
+
*/
|
|
163
|
+
export function createGroupedBars(data, fields, scales, options) {
|
|
164
|
+
if (!data || !data.length || !fields.group) return { groups: [], bars: [] }
|
|
165
|
+
|
|
166
|
+
const { xField, yField, groupField, colorField, dimensions, padding } = parseGroupedBarsConfig(
|
|
167
|
+
fields,
|
|
168
|
+
options
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
const groups = [...new SvelteSet(data.map((d) => d[groupField]))]
|
|
172
|
+
const xValues = [...new SvelteSet(data.map((d) => d[xField]))]
|
|
173
|
+
|
|
174
|
+
const xScale = scales.x
|
|
175
|
+
const groupWidth = xScale.bandwidth ? xScale.bandwidth() : 20
|
|
176
|
+
const barWidth = (groupWidth - padding * (groups.length - 1)) / groups.length
|
|
177
|
+
|
|
178
|
+
const bars = []
|
|
179
|
+
xValues.forEach((xValue) => {
|
|
180
|
+
const groupItems = data.filter((d) => d[xField] === xValue)
|
|
181
|
+
const xPos = xScale(xValue)
|
|
182
|
+
groups.forEach((group, i) => {
|
|
183
|
+
const ctx = {
|
|
184
|
+
groupItems,
|
|
185
|
+
groupField,
|
|
186
|
+
group,
|
|
187
|
+
i,
|
|
188
|
+
yField,
|
|
189
|
+
colorField,
|
|
190
|
+
barWidth,
|
|
191
|
+
padding,
|
|
192
|
+
dimensions,
|
|
193
|
+
xPos
|
|
194
|
+
}
|
|
195
|
+
const bar = buildOneGroupBar(scales, ctx)
|
|
196
|
+
if (bar) bars.push(bar)
|
|
197
|
+
})
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
return { groups, bars }
|
|
201
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { distinct, assignColors } from './colors.js'
|
|
2
|
+
import { assignPatterns, toPatternId, PATTERN_ORDER } from './patterns.js'
|
|
3
|
+
import { assignSymbols } from './symbols.js'
|
|
4
|
+
import { buildXScale, buildYScale, buildSizeScale } from './scales.js'
|
|
5
|
+
import { buildBars } from './marks/bars.js'
|
|
6
|
+
import { buildLines } from './marks/lines.js'
|
|
7
|
+
import { buildAreas } from './marks/areas.js'
|
|
8
|
+
import { buildArcs } from './marks/arcs.js'
|
|
9
|
+
import { buildPoints } from './marks/points.js'
|
|
10
|
+
|
|
11
|
+
const DEFAULT_MARGIN = { top: 20, right: 20, bottom: 40, left: 50 }
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Groups aesthetic channel mappings by field name, merging aesthetics that
|
|
15
|
+
* share the same field into one legend section.
|
|
16
|
+
*
|
|
17
|
+
* @param {{ fill?: string, color?: string, pattern?: string, symbol?: string }} channels
|
|
18
|
+
* `fill` takes precedence over `color` for polygon charts (bars, areas, pie slices).
|
|
19
|
+
* @param {Map<unknown, {fill:string, stroke:string}>} colorMap
|
|
20
|
+
* @param {Map<unknown, string>} patternMap
|
|
21
|
+
* @param {Map<unknown, string>} symbolMap
|
|
22
|
+
* @returns {{ field: string, items: { label: string, fill: string|null, stroke: string|null, patternId: string|null, shape: string|null }[] }[]}
|
|
23
|
+
*/
|
|
24
|
+
export function buildLegendGroups(channels, colorMap, patternMap, symbolMap) {
|
|
25
|
+
const cf = channels.fill ?? channels.color
|
|
26
|
+
const { pattern: pf, symbol: sf } = channels
|
|
27
|
+
const byField = new Map()
|
|
28
|
+
|
|
29
|
+
if (cf) {
|
|
30
|
+
byField.set(cf, { aesthetics: ['color'], keys: [...colorMap.keys()] })
|
|
31
|
+
}
|
|
32
|
+
if (pf) {
|
|
33
|
+
if (byField.has(pf)) {
|
|
34
|
+
byField.get(pf).aesthetics.push('pattern')
|
|
35
|
+
} else {
|
|
36
|
+
byField.set(pf, { aesthetics: ['pattern'], keys: [...patternMap.keys()] })
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (sf) {
|
|
40
|
+
if (byField.has(sf)) {
|
|
41
|
+
byField.get(sf).aesthetics.push('symbol')
|
|
42
|
+
} else {
|
|
43
|
+
byField.set(sf, { aesthetics: ['symbol'], keys: [...symbolMap.keys()] })
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return [...byField.entries()].map(([field, { aesthetics, keys }]) => ({
|
|
48
|
+
field,
|
|
49
|
+
items: keys.filter((k) => k !== null && k !== undefined).map((key) => ({
|
|
50
|
+
label: String(key),
|
|
51
|
+
fill: aesthetics.includes('color') ? (colorMap.get(key)?.fill ?? null) : null,
|
|
52
|
+
stroke: aesthetics.includes('color') ? (colorMap.get(key)?.stroke ?? null) : null,
|
|
53
|
+
patternId:
|
|
54
|
+
aesthetics.includes('pattern') && patternMap.has(key) ? toPatternId(key) : null,
|
|
55
|
+
shape: aesthetics.includes('symbol') ? (symbolMap.get(key) ?? 'circle') : null
|
|
56
|
+
}))
|
|
57
|
+
})).filter((group) => group.items.length > 0)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export class ChartBrewer {
|
|
61
|
+
#rawData = $state([])
|
|
62
|
+
#channels = $state({})
|
|
63
|
+
#width = $state(600)
|
|
64
|
+
#height = $state(400)
|
|
65
|
+
#mode = $state('light')
|
|
66
|
+
#margin = $state(DEFAULT_MARGIN)
|
|
67
|
+
#layers = $state([])
|
|
68
|
+
#curve = $state(/** @type {'linear'|'smooth'|'step'|undefined} */(undefined))
|
|
69
|
+
#stat = $state('identity')
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Override in subclasses to apply stat aggregation.
|
|
73
|
+
* @param {Object[]} data
|
|
74
|
+
* @param {Object} channels
|
|
75
|
+
* @param {string|Function} stat
|
|
76
|
+
* @returns {Object[]}
|
|
77
|
+
*/
|
|
78
|
+
transform(data, _channels, _stat) {
|
|
79
|
+
return data
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Aggregated data — all derived marks read this, not #rawData */
|
|
83
|
+
processedData = $derived(this.transform(this.#rawData, this.#channels, this.#stat))
|
|
84
|
+
|
|
85
|
+
/** Exposes channels to subclasses for use in their own $derived properties */
|
|
86
|
+
get channels() { return this.#channels }
|
|
87
|
+
|
|
88
|
+
// Maps are built from rawData so the legend always reflects the full set of
|
|
89
|
+
// original values — independent of whichever stat aggregation is applied.
|
|
90
|
+
// e.g. pattern=quarter with stat=sum still shows all 8 quarters in the legend.
|
|
91
|
+
|
|
92
|
+
/** @type {Map<unknown, {fill:string,stroke:string}>} */
|
|
93
|
+
colorMap = $derived(
|
|
94
|
+
(this.#channels.fill ?? this.#channels.color)
|
|
95
|
+
? assignColors(distinct(this.#rawData, this.#channels.fill ?? this.#channels.color), this.#mode)
|
|
96
|
+
: new Map()
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
/** @type {Map<unknown, string>} */
|
|
100
|
+
patternMap = $derived(
|
|
101
|
+
this.#channels.pattern
|
|
102
|
+
? assignPatterns(distinct(this.#rawData, this.#channels.pattern))
|
|
103
|
+
: new Map()
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Unified pattern defs for ChartPatternDefs.
|
|
108
|
+
* When fill and pattern map the same field, pattern key = color key (simple case).
|
|
109
|
+
* When they differ, each unique (fillKey, patternKey) pair gets its own pattern def
|
|
110
|
+
* so bars/areas can have distinct colors per region AND distinct textures per category.
|
|
111
|
+
* @type {Array<{ id: string, name: string, fill: string, stroke: string }>}
|
|
112
|
+
*/
|
|
113
|
+
patternDefs = $derived((() => {
|
|
114
|
+
const pf = this.#channels.pattern
|
|
115
|
+
const ff = this.#channels.fill ?? this.#channels.color
|
|
116
|
+
if (!pf || this.patternMap.size === 0) return []
|
|
117
|
+
if (!ff || pf === ff) {
|
|
118
|
+
// Same field: pattern key = fill key — simple 1:1 lookup
|
|
119
|
+
return Array.from(this.patternMap.entries()).map(([key, name]) => {
|
|
120
|
+
const color = this.colorMap.get(key) ?? { fill: '#ddd', stroke: '#666' }
|
|
121
|
+
return { id: toPatternId(key), name, fill: color.fill, stroke: color.stroke }
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
// Different fields: need two sets of defs in the SVG:
|
|
125
|
+
// 1. Simple defs (neutral background) — referenced by legend swatches via toPatternId(patternKey)
|
|
126
|
+
// 2. Composite defs (fill-colored background) — referenced by bars via toPatternId(fillKey::patternKey)
|
|
127
|
+
const defs = []
|
|
128
|
+
for (const [pk, name] of this.patternMap.entries()) {
|
|
129
|
+
defs.push({ id: toPatternId(pk), name, fill: '#ddd', stroke: '#666' })
|
|
130
|
+
}
|
|
131
|
+
const seenComposite = new Set()
|
|
132
|
+
for (const d of this.processedData) {
|
|
133
|
+
const fk = d[ff]
|
|
134
|
+
const pk = d[pf]
|
|
135
|
+
if (pk === null || pk === undefined) continue
|
|
136
|
+
const compositeKey = `${fk}::${pk}`
|
|
137
|
+
if (seenComposite.has(compositeKey)) continue
|
|
138
|
+
seenComposite.add(compositeKey)
|
|
139
|
+
const name = this.patternMap.get(pk) ?? PATTERN_ORDER[0]
|
|
140
|
+
const color = this.colorMap.get(fk) ?? { fill: '#ddd', stroke: '#666' }
|
|
141
|
+
defs.push({ id: toPatternId(compositeKey), name, fill: color.fill, stroke: color.stroke })
|
|
142
|
+
}
|
|
143
|
+
return defs
|
|
144
|
+
})())
|
|
145
|
+
|
|
146
|
+
/** @type {Map<unknown, string>} */
|
|
147
|
+
symbolMap = $derived(
|
|
148
|
+
this.#channels.symbol
|
|
149
|
+
? assignSymbols(distinct(this.#rawData, this.#channels.symbol))
|
|
150
|
+
: new Map()
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
get innerWidth() { return this.#width - this.#margin.left - this.#margin.right }
|
|
154
|
+
get innerHeight() { return this.#height - this.#margin.top - this.#margin.bottom }
|
|
155
|
+
|
|
156
|
+
xScale = $derived(
|
|
157
|
+
this.#channels.x
|
|
158
|
+
? buildXScale(this.processedData, this.#channels.x, this.innerWidth)
|
|
159
|
+
: null
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
yScale = $derived(
|
|
163
|
+
this.#channels.y
|
|
164
|
+
? buildYScale(this.processedData, this.#channels.y, this.innerHeight, this.#layers)
|
|
165
|
+
: null
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
sizeScale = $derived(
|
|
169
|
+
this.#channels.size
|
|
170
|
+
? buildSizeScale(this.processedData, this.#channels.size)
|
|
171
|
+
: null
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
bars = $derived(
|
|
175
|
+
this.xScale && this.yScale
|
|
176
|
+
? buildBars(this.processedData, this.#channels, this.xScale, this.yScale, this.colorMap, this.patternMap)
|
|
177
|
+
: []
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
lines = $derived(
|
|
181
|
+
this.xScale && this.yScale
|
|
182
|
+
? buildLines(this.processedData, this.#channels, this.xScale, this.yScale, this.colorMap, this.#curve)
|
|
183
|
+
: []
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
areas = $derived(
|
|
187
|
+
this.xScale && this.yScale
|
|
188
|
+
? buildAreas(this.processedData, this.#channels, this.xScale, this.yScale, this.colorMap, this.#curve, this.patternMap)
|
|
189
|
+
: []
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
arcs = $derived(
|
|
193
|
+
this.#channels.y
|
|
194
|
+
? buildArcs(this.processedData, this.#channels, this.colorMap, this.#width, this.#height)
|
|
195
|
+
: []
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
points = $derived(
|
|
199
|
+
this.xScale && this.yScale
|
|
200
|
+
? buildPoints(this.processedData, this.#channels, this.xScale, this.yScale, this.colorMap, this.sizeScale, this.symbolMap)
|
|
201
|
+
: []
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
legendGroups = $derived(
|
|
205
|
+
buildLegendGroups(this.#channels, this.colorMap, this.patternMap, this.symbolMap)
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
get margin() { return this.#margin }
|
|
209
|
+
get width() { return this.#width }
|
|
210
|
+
get height() { return this.#height }
|
|
211
|
+
get mode() { return this.#mode }
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* @param {{ data?: Object[], channels?: Object, width?: number, height?: number, mode?: string, margin?: Object, layers?: Object[], curve?: string, stat?: string|Function }} opts
|
|
215
|
+
* Supported channel keys: `x`, `y`, `fill`, `color`, `pattern`, `symbol`, `size`, `label`.
|
|
216
|
+
* `frame` is reserved for future animation use (no-op).
|
|
217
|
+
*/
|
|
218
|
+
update(opts = {}) {
|
|
219
|
+
if (opts.data !== undefined) this.#rawData = opts.data
|
|
220
|
+
if (opts.channels !== undefined) this.#channels = opts.channels
|
|
221
|
+
if (opts.width !== undefined) this.#width = opts.width
|
|
222
|
+
if (opts.height !== undefined) this.#height = opts.height
|
|
223
|
+
if (opts.mode !== undefined) this.#mode = opts.mode
|
|
224
|
+
if (opts.margin !== undefined) this.#margin = { ...DEFAULT_MARGIN, ...opts.margin }
|
|
225
|
+
if (opts.layers !== undefined) this.#layers = opts.layers
|
|
226
|
+
if (opts.curve !== undefined) this.#curve = opts.curve
|
|
227
|
+
if (opts.stat !== undefined) this.#stat = opts.stat
|
|
228
|
+
}
|
|
229
|
+
}
|