@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,278 @@
|
|
|
1
|
+
import { untrack } from 'svelte'
|
|
2
|
+
import { applyGeomStat } from './lib/plot/stat.js'
|
|
3
|
+
import { inferFieldType, inferOrientation, buildUnifiedXScale, buildUnifiedYScale, inferColorScaleType } from './lib/plot/scales.js'
|
|
4
|
+
import { resolvePreset } from './lib/plot/preset.js'
|
|
5
|
+
import { resolveFormat, resolveTooltip, resolveGeom } from './lib/plot/helpers.js'
|
|
6
|
+
import { distinct, assignColors } from './lib/brewing/colors.js'
|
|
7
|
+
import { assignPatterns } from './lib/brewing/patterns.js'
|
|
8
|
+
import { assignSymbols } from './lib/brewing/marks/points.js'
|
|
9
|
+
|
|
10
|
+
let nextId = 0
|
|
11
|
+
|
|
12
|
+
export class PlotState {
|
|
13
|
+
#data = $state([])
|
|
14
|
+
#rawData = []
|
|
15
|
+
#channels = $state({})
|
|
16
|
+
#labels = $state({})
|
|
17
|
+
#helpers = $state({})
|
|
18
|
+
#presetName = $state(undefined)
|
|
19
|
+
#colorMidpoint = $state(undefined)
|
|
20
|
+
#colorSpec = $state(undefined)
|
|
21
|
+
#colorDomain = $state(undefined)
|
|
22
|
+
#xDomain = $state(undefined)
|
|
23
|
+
#yDomain = $state(undefined)
|
|
24
|
+
#width = $state(600)
|
|
25
|
+
#height = $state(400)
|
|
26
|
+
#margin = $state({ top: 20, right: 20, bottom: 40, left: 50 })
|
|
27
|
+
#marginOverride = $state(undefined)
|
|
28
|
+
|
|
29
|
+
#geoms = $state([])
|
|
30
|
+
#mode = $state('light')
|
|
31
|
+
#hovered = $state(null)
|
|
32
|
+
|
|
33
|
+
axisOrigin = $state([undefined, undefined])
|
|
34
|
+
|
|
35
|
+
#effectiveMargin = $derived(this.#marginOverride ?? this.#margin)
|
|
36
|
+
#innerWidth = $derived(this.#width - this.#effectiveMargin.left - this.#effectiveMargin.right)
|
|
37
|
+
#innerHeight = $derived(this.#height - this.#effectiveMargin.top - this.#effectiveMargin.bottom)
|
|
38
|
+
|
|
39
|
+
// Effective channels: prefer top-level channels; fall back to first geom's channels
|
|
40
|
+
// for the declarative API where no spec is provided.
|
|
41
|
+
#effectiveChannels = $derived.by(() => {
|
|
42
|
+
const tc = this.#channels
|
|
43
|
+
if (tc.x && tc.y) return tc
|
|
44
|
+
const firstGeom = this.#geoms[0]
|
|
45
|
+
if (!firstGeom) return tc
|
|
46
|
+
return {
|
|
47
|
+
x: tc.x ?? firstGeom.channels?.x,
|
|
48
|
+
y: tc.y ?? firstGeom.channels?.y,
|
|
49
|
+
color: tc.color ?? firstGeom.channels?.color,
|
|
50
|
+
pattern: tc.pattern ?? firstGeom.channels?.pattern,
|
|
51
|
+
symbol: tc.symbol ?? firstGeom.channels?.symbol,
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
orientation = $derived.by(() => {
|
|
56
|
+
const xField = this.#effectiveChannels.x
|
|
57
|
+
const yField = this.#effectiveChannels.y
|
|
58
|
+
if (!xField || !yField) return 'none'
|
|
59
|
+
const rawXType = inferFieldType(this.#data, xField)
|
|
60
|
+
const yType = inferFieldType(this.#data, yField)
|
|
61
|
+
// Bar geoms treat numeric X as categorical (e.g. year on X → vertical bars).
|
|
62
|
+
const hasBarGeom = this.#geoms.some((g) => g.type === 'bar')
|
|
63
|
+
const xType = (hasBarGeom && rawXType === 'continuous' && yType === 'continuous') ? 'band' : rawXType
|
|
64
|
+
return inferOrientation(xType, yType)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
colorScaleType = $derived.by(() => {
|
|
68
|
+
const field = this.#effectiveChannels.color
|
|
69
|
+
if (!field) return 'categorical'
|
|
70
|
+
return inferColorScaleType(this.#data, field, {
|
|
71
|
+
colorScale: this.#colorSpec,
|
|
72
|
+
colorMidpoint: this.#colorMidpoint
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
xScale = $derived.by(() => {
|
|
77
|
+
const field = this.#effectiveChannels.x
|
|
78
|
+
if (!field) return null
|
|
79
|
+
const datasets = this.#geoms.length > 0
|
|
80
|
+
? this.#geoms.map((g) => this.geomData(g.id))
|
|
81
|
+
: [this.#rawData]
|
|
82
|
+
const includeZero = this.orientation === 'horizontal'
|
|
83
|
+
// For vertical bar charts, force scaleBand even when X values are numeric (e.g. year).
|
|
84
|
+
// Horizontal bar charts keep X as a continuous value axis.
|
|
85
|
+
const hasBarGeom = this.#geoms.some((g) => g.type === 'bar')
|
|
86
|
+
const bandX = hasBarGeom && this.orientation !== 'horizontal'
|
|
87
|
+
return buildUnifiedXScale(datasets, field, this.#innerWidth, {
|
|
88
|
+
domain: this.#xDomain,
|
|
89
|
+
includeZero,
|
|
90
|
+
band: bandX
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
yScale = $derived.by(() => {
|
|
95
|
+
const field = this.#effectiveChannels.y
|
|
96
|
+
if (!field) return null
|
|
97
|
+
const datasets = this.#geoms.length > 0
|
|
98
|
+
? this.#geoms.map((g) => this.geomData(g.id))
|
|
99
|
+
: [this.#rawData]
|
|
100
|
+
const includeZero = this.orientation === 'vertical'
|
|
101
|
+
|
|
102
|
+
// For stacked bars, the y domain must cover the per-x column *total*, not the
|
|
103
|
+
// per-row max — otherwise bars overflow the plot area.
|
|
104
|
+
// For box/violin geoms, the processed data has iqr_min/iqr_max instead of raw y values.
|
|
105
|
+
let yDomain = this.#yDomain
|
|
106
|
+
if (!yDomain) {
|
|
107
|
+
const boxGeom = this.#geoms.find((g) => g.type === 'box' || g.type === 'violin')
|
|
108
|
+
if (boxGeom) {
|
|
109
|
+
const boxData = this.geomData(boxGeom.id)
|
|
110
|
+
const mins = boxData.map((d) => d.iqr_min).filter((v) => v !== null && v !== undefined && !isNaN(v))
|
|
111
|
+
const maxs = boxData.map((d) => d.iqr_max).filter((v) => v !== null && v !== undefined && !isNaN(v))
|
|
112
|
+
if (mins.length > 0 && maxs.length > 0) {
|
|
113
|
+
yDomain = [Math.min(...mins), Math.max(...maxs)]
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (!yDomain) {
|
|
118
|
+
const stackGeom = this.#geoms.find((g) => g.options?.stack)
|
|
119
|
+
if (stackGeom) {
|
|
120
|
+
const xField = this.#effectiveChannels.x
|
|
121
|
+
const stackData = this.geomData(stackGeom.id)
|
|
122
|
+
if (xField && stackData.length > 0) {
|
|
123
|
+
// Mirror buildStackedBars/subBandFields: stack dimension is the first
|
|
124
|
+
// non-x field among [color, pattern]. Summing all raw rows (stat=identity)
|
|
125
|
+
// would overcount when multiple rows share the same (x, stack) key.
|
|
126
|
+
const colorField = this.#effectiveChannels.color
|
|
127
|
+
const patternField = this.#effectiveChannels.pattern
|
|
128
|
+
const stackField = [colorField, patternField].find((f) => f && f !== xField) ?? colorField
|
|
129
|
+
const lookup = new Map()
|
|
130
|
+
for (const d of stackData) {
|
|
131
|
+
const xVal = d[xField]
|
|
132
|
+
const cKey = stackField ? String(d[stackField]) : '_'
|
|
133
|
+
if (!lookup.has(xVal)) lookup.set(xVal, new Map())
|
|
134
|
+
lookup.get(xVal).set(cKey, Number(d[field]) || 0)
|
|
135
|
+
}
|
|
136
|
+
const totals = new Map()
|
|
137
|
+
for (const [xVal, colorMap] of lookup) {
|
|
138
|
+
totals.set(xVal, [...colorMap.values()].reduce((s, v) => s + v, 0))
|
|
139
|
+
}
|
|
140
|
+
const maxStack = Math.max(0, ...totals.values())
|
|
141
|
+
yDomain = [0, maxStack]
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return buildUnifiedYScale(datasets, field, this.#innerHeight, {
|
|
147
|
+
domain: yDomain,
|
|
148
|
+
includeZero
|
|
149
|
+
})
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
// Colors: Map<colorKey, { fill, stroke }> for all distinct color field values.
|
|
153
|
+
// If a colorDomain is provided (e.g. from FacetPlot for cross-panel consistency),
|
|
154
|
+
// use it instead of deriving distinct values from the local panel data.
|
|
155
|
+
colors = $derived.by(() => {
|
|
156
|
+
const field = this.#effectiveChannels.color
|
|
157
|
+
const values = this.#colorDomain ?? distinct(this.#data, field)
|
|
158
|
+
return assignColors(values, this.#mode)
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
// Patterns: Map<patternKey, patternName> — only populated when a pattern channel is set
|
|
162
|
+
// and the pattern field is categorical (continuous fields can't be discretely patterned).
|
|
163
|
+
patterns = $derived.by(() => {
|
|
164
|
+
const pf = this.#effectiveChannels.pattern
|
|
165
|
+
if (!pf) return new Map()
|
|
166
|
+
if (inferFieldType(this.#data, pf) === 'continuous') return new Map()
|
|
167
|
+
return assignPatterns(distinct(this.#data, pf))
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
// Symbols: Map<symbolKey, shapeName> — only populated when a symbol channel is set.
|
|
171
|
+
symbols = $derived.by(() => {
|
|
172
|
+
const sf = this.#effectiveChannels.symbol
|
|
173
|
+
if (!sf) return new Map()
|
|
174
|
+
return assignSymbols(distinct(this.#data, sf))
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
// Expose effective channel fields for consumers (e.g. Legend)
|
|
178
|
+
colorField = $derived(this.#effectiveChannels.color)
|
|
179
|
+
patternField = $derived(this.#effectiveChannels.pattern)
|
|
180
|
+
symbolField = $derived(this.#effectiveChannels.symbol)
|
|
181
|
+
|
|
182
|
+
// Set of geom types currently registered (used by Legend to pick swatch style)
|
|
183
|
+
geomTypes = $derived(new Set(this.#geoms.map((g) => g.type)))
|
|
184
|
+
|
|
185
|
+
xAxisY = $derived.by(() => {
|
|
186
|
+
if (!this.yScale || typeof this.yScale !== 'function') return this.#innerHeight
|
|
187
|
+
const crossVal = this.axisOrigin[1]
|
|
188
|
+
if (crossVal !== undefined) return this.yScale(crossVal)
|
|
189
|
+
const domain = this.yScale.domain?.()
|
|
190
|
+
return domain ? this.yScale(domain[0]) : this.#innerHeight
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
yAxisX = $derived.by(() => {
|
|
194
|
+
if (!this.xScale || typeof this.xScale !== 'function') return 0
|
|
195
|
+
const crossVal = this.axisOrigin[0]
|
|
196
|
+
if (crossVal !== undefined) return this.xScale(crossVal)
|
|
197
|
+
const domain = this.xScale.domain?.()
|
|
198
|
+
return domain ? this.xScale(domain[0]) : 0
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
constructor(config = {}) {
|
|
202
|
+
this.#rawData = config.data ?? []
|
|
203
|
+
this.#data = config.data ?? []
|
|
204
|
+
this.#channels = config.channels ?? {}
|
|
205
|
+
this.#labels = config.labels ?? {}
|
|
206
|
+
this.#helpers = config.helpers ?? {}
|
|
207
|
+
this.#presetName = config.preset
|
|
208
|
+
this.#colorMidpoint = config.colorMidpoint
|
|
209
|
+
this.#colorSpec = config.colorScale
|
|
210
|
+
this.#colorDomain = config.colorDomain
|
|
211
|
+
this.#xDomain = config.xDomain
|
|
212
|
+
this.#yDomain = config.yDomain
|
|
213
|
+
this.#width = config.width ?? 600
|
|
214
|
+
this.#height = config.height ?? 400
|
|
215
|
+
this.#mode = config.mode ?? 'light'
|
|
216
|
+
this.#marginOverride = config.margin ?? undefined
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
update(config) {
|
|
220
|
+
if (config.data !== undefined) { this.#rawData = config.data; this.#data = config.data }
|
|
221
|
+
if (config.channels !== undefined) this.#channels = config.channels
|
|
222
|
+
if (config.labels !== undefined) this.#labels = config.labels
|
|
223
|
+
if (config.helpers !== undefined) this.#helpers = config.helpers
|
|
224
|
+
if (config.preset !== undefined) this.#presetName = config.preset
|
|
225
|
+
if (config.colorMidpoint !== undefined) this.#colorMidpoint = config.colorMidpoint
|
|
226
|
+
if (config.colorScale !== undefined) this.#colorSpec = config.colorScale
|
|
227
|
+
this.#colorDomain = config.colorDomain
|
|
228
|
+
this.#xDomain = config.xDomain
|
|
229
|
+
this.#yDomain = config.yDomain
|
|
230
|
+
if (config.width !== undefined) this.#width = config.width
|
|
231
|
+
if (config.height !== undefined) this.#height = config.height
|
|
232
|
+
if (config.mode !== undefined) this.#mode = config.mode
|
|
233
|
+
this.#marginOverride = config.margin ?? undefined
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
registerGeom(config) {
|
|
237
|
+
const id = `geom-${nextId++}`
|
|
238
|
+
this.#geoms = [...this.#geoms, { id, ...config }]
|
|
239
|
+
return id
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
updateGeom(id, config) {
|
|
243
|
+
// untrack the read of #geoms to avoid effect_update_depth_exceeded when
|
|
244
|
+
// called from a geom's $effect (which would otherwise track #geoms as a dependency)
|
|
245
|
+
this.#geoms = untrack(() => this.#geoms).map((g) => g.id === id ? { ...g, ...config } : g)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
unregisterGeom(id) {
|
|
249
|
+
this.#geoms = this.#geoms.filter((g) => g.id !== id)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
geomData(id) {
|
|
253
|
+
const geom = this.#geoms.find((g) => g.id === id)
|
|
254
|
+
if (!geom) return []
|
|
255
|
+
const stat = geom.stat ?? 'identity'
|
|
256
|
+
if (stat === 'identity') return this.#rawData
|
|
257
|
+
const mergedChannels = { ...this.#channels, ...geom.channels }
|
|
258
|
+
return applyGeomStat(this.#rawData, { stat, channels: mergedChannels }, this.#helpers)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
label(field) {
|
|
262
|
+
return this.#labels?.[field] ?? field
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
format(field) { return resolveFormat(field, this.#helpers) }
|
|
266
|
+
tooltip() { return resolveTooltip(this.#helpers) }
|
|
267
|
+
geomComponent(type) { return resolveGeom(type, this.#helpers) }
|
|
268
|
+
preset() { return resolvePreset(this.#presetName, this.#helpers) }
|
|
269
|
+
|
|
270
|
+
get margin() { return this.#effectiveMargin }
|
|
271
|
+
get innerWidth() { return this.#innerWidth }
|
|
272
|
+
get innerHeight() { return this.#innerHeight }
|
|
273
|
+
get mode() { return this.#mode }
|
|
274
|
+
get hovered() { return this.#hovered }
|
|
275
|
+
|
|
276
|
+
setHovered(data) { this.#hovered = data }
|
|
277
|
+
clearHovered() { this.#hovered = null }
|
|
278
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { scaleLinear } from 'd3-scale'
|
|
3
|
+
import { line as d3line, area as d3area, curveCatmullRom } from 'd3-shape'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @type {number[] | object[]}
|
|
7
|
+
*/
|
|
8
|
+
let {
|
|
9
|
+
data = [],
|
|
10
|
+
field = undefined,
|
|
11
|
+
type = 'line',
|
|
12
|
+
curve = 'linear',
|
|
13
|
+
color = 'primary',
|
|
14
|
+
width = 80,
|
|
15
|
+
height = 24,
|
|
16
|
+
min = undefined,
|
|
17
|
+
max = undefined
|
|
18
|
+
} = $props()
|
|
19
|
+
|
|
20
|
+
const values = $derived(
|
|
21
|
+
field ? data.map((d) => Number(d[field])) : data.map(Number)
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
const yMin = $derived(min ?? Math.min(...values))
|
|
25
|
+
const yMax = $derived(max ?? Math.max(...values))
|
|
26
|
+
|
|
27
|
+
const xScale = $derived(
|
|
28
|
+
scaleLinear().domain([0, values.length - 1]).range([0, width])
|
|
29
|
+
)
|
|
30
|
+
const yScale = $derived(
|
|
31
|
+
scaleLinear().domain([yMin, yMax]).range([height, 0])
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
const linePath = $derived.by(() => {
|
|
35
|
+
const gen = d3line().x((_, i) => xScale(i)).y((v) => yScale(v))
|
|
36
|
+
if (curve === 'smooth') gen.curve(curveCatmullRom)
|
|
37
|
+
return gen(values)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
const areaPath = $derived.by(() => {
|
|
41
|
+
const gen = d3area().x((_, i) => xScale(i)).y0(height).y1((v) => yScale(v))
|
|
42
|
+
if (curve === 'smooth') gen.curve(curveCatmullRom)
|
|
43
|
+
return gen(values)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
const barWidth = $derived(Math.max(1, width / values.length - 1))
|
|
47
|
+
|
|
48
|
+
const strokeColor = $derived(`rgb(var(--color-${color}-500, 100,116,139))`)
|
|
49
|
+
const fillColor = $derived(`rgba(var(--color-${color}-300), 0.25)`)
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<svg {width} {height} style="overflow: visible; display: block;">
|
|
53
|
+
{#if type === 'line'}
|
|
54
|
+
<path d={linePath} fill="none" stroke={strokeColor} stroke-width="1.5" stroke-linejoin="round" stroke-linecap="round" />
|
|
55
|
+
{:else if type === 'area'}
|
|
56
|
+
<path d={areaPath} fill={fillColor} stroke="none" />
|
|
57
|
+
<path d={linePath} fill="none" stroke={strokeColor} stroke-width="1.5" stroke-linejoin="round" stroke-linecap="round" />
|
|
58
|
+
{:else if type === 'bar'}
|
|
59
|
+
{#each values as v, i}
|
|
60
|
+
<rect
|
|
61
|
+
x={xScale(i) - barWidth / 2}
|
|
62
|
+
y={yScale(v)}
|
|
63
|
+
width={barWidth}
|
|
64
|
+
height={height - yScale(v)}
|
|
65
|
+
fill={strokeColor}
|
|
66
|
+
/>
|
|
67
|
+
{/each}
|
|
68
|
+
{/if}
|
|
69
|
+
</svg>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { components } from './symbols'
|
|
3
|
+
|
|
4
|
+
let {
|
|
5
|
+
x = 0,
|
|
6
|
+
y = 0,
|
|
7
|
+
size = 10,
|
|
8
|
+
fill = 'currentColor',
|
|
9
|
+
stroke = 'currentColor',
|
|
10
|
+
name = 'circle',
|
|
11
|
+
...restProps
|
|
12
|
+
} = $props()
|
|
13
|
+
|
|
14
|
+
let RenderShape = $derived(components[name] ?? components.default)
|
|
15
|
+
let props = $derived({ ...restProps, x, y, size, fill, stroke })
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<!-- {#snippet defaultSymbol(props)}
|
|
19
|
+
<circle cx={x} cy={y} r={size / 2} {fill} {stroke} />
|
|
20
|
+
{/snippet} -->
|
|
21
|
+
<RenderShape {...props} />
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
let {
|
|
3
|
+
id,
|
|
4
|
+
path,
|
|
5
|
+
fill = 'currentColor',
|
|
6
|
+
stroke = 'currentColor',
|
|
7
|
+
thickness = 0.5,
|
|
8
|
+
patternUnits = 'userSpaceOnUse',
|
|
9
|
+
size = 10
|
|
10
|
+
} = $props()
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<pattern {id} {patternUnits} width={size} height={size}>
|
|
14
|
+
<rect width={size} height={size} {fill} />
|
|
15
|
+
{#if path}
|
|
16
|
+
<path d={path} fill="none" {stroke} stroke-width={thickness} />
|
|
17
|
+
{/if}
|
|
18
|
+
</pattern>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import Plot from '../Plot.svelte'
|
|
3
|
+
import Area from '../geoms/Area.svelte'
|
|
4
|
+
|
|
5
|
+
/** @type {import('../lib/plot/chartProps.js').LineAreaChartProps} */
|
|
6
|
+
let {
|
|
7
|
+
data = [],
|
|
8
|
+
x = undefined,
|
|
9
|
+
y = undefined,
|
|
10
|
+
fill = undefined,
|
|
11
|
+
stat = 'identity',
|
|
12
|
+
curve = undefined,
|
|
13
|
+
pattern = undefined,
|
|
14
|
+
stack = false,
|
|
15
|
+
width = 600,
|
|
16
|
+
height = 400,
|
|
17
|
+
mode = 'light',
|
|
18
|
+
grid = true,
|
|
19
|
+
legend = false
|
|
20
|
+
} = $props()
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<Plot {data} {width} {height} {mode} {grid} {legend}>
|
|
24
|
+
<Area {x} {y} color={fill} {pattern} {stat} options={{ curve, stack }} />
|
|
25
|
+
</Plot>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import Plot from '../Plot.svelte'
|
|
3
|
+
import Bar from '../geoms/Bar.svelte'
|
|
4
|
+
|
|
5
|
+
/** @type {import('../lib/plot/chartProps.js').BarChartProps} */
|
|
6
|
+
let {
|
|
7
|
+
data = [],
|
|
8
|
+
x = undefined,
|
|
9
|
+
y = undefined,
|
|
10
|
+
fill = undefined, // mapped to color channel
|
|
11
|
+
pattern = undefined,
|
|
12
|
+
width = 600,
|
|
13
|
+
height = 400,
|
|
14
|
+
mode = 'light',
|
|
15
|
+
grid = true,
|
|
16
|
+
legend = false,
|
|
17
|
+
stat = 'identity',
|
|
18
|
+
stack = false,
|
|
19
|
+
label = false,
|
|
20
|
+
tooltip = false
|
|
21
|
+
} = $props()
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<Plot {data} {width} {height} {mode} {grid} {legend} {tooltip}>
|
|
25
|
+
<Bar {x} {y} color={fill} {pattern} {label} {stat} options={{ stack }} />
|
|
26
|
+
</Plot>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import Plot from '../Plot.svelte'
|
|
3
|
+
import Box from '../geoms/Box.svelte'
|
|
4
|
+
|
|
5
|
+
/** @type {import('../lib/plot/chartProps.js').BoxViolinChartProps} */
|
|
6
|
+
let {
|
|
7
|
+
data = [],
|
|
8
|
+
x = undefined,
|
|
9
|
+
y = undefined,
|
|
10
|
+
fill = undefined,
|
|
11
|
+
width = 600,
|
|
12
|
+
height = 400,
|
|
13
|
+
mode = 'light',
|
|
14
|
+
grid = true,
|
|
15
|
+
legend = false
|
|
16
|
+
} = $props()
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<Plot {data} {width} {height} {mode} {grid} {legend}>
|
|
20
|
+
<Box {x} {y} {fill} />
|
|
21
|
+
</Plot>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import Plot from '../Plot.svelte'
|
|
3
|
+
import Point from '../geoms/Point.svelte'
|
|
4
|
+
|
|
5
|
+
/** @type {import('../lib/plot/chartProps.js').ScatterBubbleChartProps} */
|
|
6
|
+
let {
|
|
7
|
+
data = [],
|
|
8
|
+
x = undefined,
|
|
9
|
+
y = undefined,
|
|
10
|
+
color = undefined,
|
|
11
|
+
symbol: _symbol = undefined,
|
|
12
|
+
size, // required: field name for bubble radius
|
|
13
|
+
width = 600,
|
|
14
|
+
height = 400,
|
|
15
|
+
mode = 'light',
|
|
16
|
+
grid = true,
|
|
17
|
+
legend = false
|
|
18
|
+
} = $props()
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<Plot {data} {width} {height} {mode} {grid} {legend}>
|
|
22
|
+
<Point {x} {y} {color} {size} />
|
|
23
|
+
</Plot>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import Plot from '../Plot.svelte'
|
|
3
|
+
import Line from '../geoms/Line.svelte'
|
|
4
|
+
|
|
5
|
+
/** @type {import('../lib/plot/chartProps.js').LineAreaChartProps} */
|
|
6
|
+
let {
|
|
7
|
+
data = [],
|
|
8
|
+
x = undefined,
|
|
9
|
+
y = undefined,
|
|
10
|
+
color = undefined,
|
|
11
|
+
stat = 'identity',
|
|
12
|
+
symbol = undefined,
|
|
13
|
+
curve = undefined, // forwarded to Line options
|
|
14
|
+
label = false,
|
|
15
|
+
tooltip = false,
|
|
16
|
+
width = 600,
|
|
17
|
+
height = 400,
|
|
18
|
+
mode = 'light',
|
|
19
|
+
grid = true,
|
|
20
|
+
legend = false
|
|
21
|
+
} = $props()
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<Plot {data} {width} {height} {mode} {grid} {legend} {tooltip}>
|
|
25
|
+
<Line {x} {y} {color} {symbol} {label} {stat} options={{ curve }} />
|
|
26
|
+
</Plot>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import Plot from '../Plot.svelte'
|
|
3
|
+
import Arc from '../geoms/Arc.svelte'
|
|
4
|
+
|
|
5
|
+
/** @type {import('../lib/plot/chartProps.js').PieChartProps} */
|
|
6
|
+
let {
|
|
7
|
+
data = [],
|
|
8
|
+
label = undefined,
|
|
9
|
+
y = undefined,
|
|
10
|
+
fill = undefined,
|
|
11
|
+
pattern = undefined,
|
|
12
|
+
innerRadius = 0,
|
|
13
|
+
labelFn = undefined,
|
|
14
|
+
tooltip = false,
|
|
15
|
+
width = 400,
|
|
16
|
+
height = 400,
|
|
17
|
+
mode = 'light',
|
|
18
|
+
legend = false,
|
|
19
|
+
stat = 'sum'
|
|
20
|
+
} = $props()
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<Plot {data} {width} {height} {mode} grid={false} axes={false} margin={{ top: 10, right: 10, bottom: 10, left: 10 }} {legend} {tooltip}>
|
|
24
|
+
<Arc theta={y} fill={label ?? fill} {pattern} {labelFn} {stat} options={{ innerRadius }} />
|
|
25
|
+
</Plot>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import Plot from '../Plot.svelte'
|
|
3
|
+
import Point from '../geoms/Point.svelte'
|
|
4
|
+
|
|
5
|
+
/** @type {import('../lib/plot/chartProps.js').ScatterBubbleChartProps} */
|
|
6
|
+
let {
|
|
7
|
+
data = [],
|
|
8
|
+
x = undefined,
|
|
9
|
+
y = undefined,
|
|
10
|
+
color = undefined,
|
|
11
|
+
symbol = undefined,
|
|
12
|
+
size = undefined,
|
|
13
|
+
label = false,
|
|
14
|
+
tooltip = false,
|
|
15
|
+
width = 600,
|
|
16
|
+
height = 400,
|
|
17
|
+
mode = 'light',
|
|
18
|
+
grid = true,
|
|
19
|
+
legend = false
|
|
20
|
+
} = $props()
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<Plot {data} {width} {height} {mode} {grid} {legend} {tooltip}>
|
|
24
|
+
<Point {x} {y} {color} {size} {symbol} {label} />
|
|
25
|
+
</Plot>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import Plot from '../Plot.svelte'
|
|
3
|
+
import Violin from '../geoms/Violin.svelte'
|
|
4
|
+
|
|
5
|
+
/** @type {import('../lib/plot/chartProps.js').BoxViolinChartProps} */
|
|
6
|
+
let {
|
|
7
|
+
data = [],
|
|
8
|
+
x = undefined,
|
|
9
|
+
y = undefined,
|
|
10
|
+
fill = undefined,
|
|
11
|
+
width = 600,
|
|
12
|
+
height = 400,
|
|
13
|
+
mode = 'light',
|
|
14
|
+
grid = true,
|
|
15
|
+
legend = false
|
|
16
|
+
} = $props()
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<Plot {data} {width} {height} {mode} {grid} {legend}>
|
|
20
|
+
<Violin {x} {y} {fill} />
|
|
21
|
+
</Plot>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { setContext, untrack } from 'svelte'
|
|
3
|
+
import { createCrossFilter } from './createCrossFilter.svelte.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @type {{
|
|
7
|
+
* crossfilter?: ReturnType<typeof createCrossFilter>,
|
|
8
|
+
* mode?: 'dim' | 'hide',
|
|
9
|
+
* filters?: import('./createCrossFilter.svelte.js').FilterState,
|
|
10
|
+
* children?: import('svelte').Snippet
|
|
11
|
+
* }}
|
|
12
|
+
*/
|
|
13
|
+
let {
|
|
14
|
+
crossfilter: externalCf = undefined,
|
|
15
|
+
mode = 'dim',
|
|
16
|
+
filters = $bindable(),
|
|
17
|
+
children
|
|
18
|
+
} = $props()
|
|
19
|
+
|
|
20
|
+
// Use an externally provided instance (spec/helpers API) or create one internally.
|
|
21
|
+
// untrack() suppresses "captures initial value" warning — intentional: the cf
|
|
22
|
+
// instance is locked in at construction time and must not recreate on prop changes.
|
|
23
|
+
const cf = untrack(() => externalCf ?? createCrossFilter())
|
|
24
|
+
|
|
25
|
+
// Expose the reactive filters Map to callers via bind:filters
|
|
26
|
+
$effect(() => {
|
|
27
|
+
filters = cf.filters
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
setContext('crossfilter', cf)
|
|
31
|
+
// Use a getter object so children can read .mode reactively
|
|
32
|
+
const modeRef = { get mode() { return mode } }
|
|
33
|
+
setContext('crossfilter-mode', modeRef)
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<div data-crossfilter data-crossfilter-mode={mode}>
|
|
37
|
+
{@render children?.()}
|
|
38
|
+
</div>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import PlotChart from '../Plot.svelte'
|
|
3
|
+
import Bar from '../geoms/Bar.svelte'
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
data = [],
|
|
7
|
+
field,
|
|
8
|
+
valueField,
|
|
9
|
+
stat = 'sum',
|
|
10
|
+
width = 300,
|
|
11
|
+
height = 120,
|
|
12
|
+
mode = 'light'
|
|
13
|
+
} = $props()
|
|
14
|
+
|
|
15
|
+
const spec = $derived({
|
|
16
|
+
x: field,
|
|
17
|
+
y: valueField
|
|
18
|
+
})
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<!-- FilterBar must be used inside a <CrossFilter> parent. Does not create its own context. -->
|
|
22
|
+
<PlotChart
|
|
23
|
+
{data}
|
|
24
|
+
{spec}
|
|
25
|
+
{width}
|
|
26
|
+
{height}
|
|
27
|
+
{mode}
|
|
28
|
+
grid={false}
|
|
29
|
+
legend={false}
|
|
30
|
+
>
|
|
31
|
+
<Bar x={field} y={valueField} {stat} filterable={true} />
|
|
32
|
+
</PlotChart>
|