@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,57 @@
|
|
|
1
|
+
import { symbol, symbolCircle, symbolSquare, symbolTriangle, symbolDiamond, symbolCross, symbolStar } from 'd3-shape'
|
|
2
|
+
|
|
3
|
+
const SYMBOL_TYPES = [symbolCircle, symbolSquare, symbolTriangle, symbolDiamond, symbolCross, symbolStar]
|
|
4
|
+
const SYMBOL_NAMES = ['circle', 'square', 'triangle', 'diamond', 'cross', 'star']
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Returns a Map assigning shape names to distinct values, cycling through available shapes.
|
|
8
|
+
* @param {unknown[]} values
|
|
9
|
+
* @returns {Map<unknown, string>}
|
|
10
|
+
*/
|
|
11
|
+
export function assignSymbols(values) {
|
|
12
|
+
return new Map(values.map((v, i) => [v, SYMBOL_NAMES[i % SYMBOL_NAMES.length]]))
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Builds an SVG path string for a given shape name and radius.
|
|
17
|
+
* @param {string} shapeName
|
|
18
|
+
* @param {number} r
|
|
19
|
+
* @returns {string}
|
|
20
|
+
*/
|
|
21
|
+
export function buildSymbolPath(shapeName, r) {
|
|
22
|
+
const idx = SYMBOL_NAMES.indexOf(shapeName)
|
|
23
|
+
const type = idx >= 0 ? SYMBOL_TYPES[idx] : symbolCircle
|
|
24
|
+
return symbol().type(type).size(Math.PI * r * r)() ?? ''
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Builds point geometry for scatter/bubble charts.
|
|
29
|
+
* @param {Object[]} data
|
|
30
|
+
* @param {{ x: string, y: string, color?: string, size?: string, symbol?: string }} channels
|
|
31
|
+
* @param {Function} xScale
|
|
32
|
+
* @param {Function} yScale
|
|
33
|
+
* @param {Map} colors
|
|
34
|
+
* @param {Function|null} sizeScale
|
|
35
|
+
* @param {Map<unknown, string>|null} symbolMap — maps symbol field value → shape name
|
|
36
|
+
* @param {number} defaultRadius
|
|
37
|
+
*/
|
|
38
|
+
export function buildPoints(data, channels, xScale, yScale, colors, sizeScale, symbolMap, defaultRadius = 5) {
|
|
39
|
+
const { x: xf, y: yf, color: cf, size: sf, symbol: symf } = channels
|
|
40
|
+
return data.map((d) => {
|
|
41
|
+
const colorKey = cf ? d[cf] : null
|
|
42
|
+
const colorEntry = colors?.get(colorKey) ?? { fill: '#888', stroke: '#444' }
|
|
43
|
+
const r = sf && sizeScale ? sizeScale(d[sf]) : defaultRadius
|
|
44
|
+
const shapeName = symf && symbolMap ? (symbolMap.get(d[symf]) ?? 'circle') : null
|
|
45
|
+
const symbolPath = shapeName ? buildSymbolPath(shapeName, r) : null
|
|
46
|
+
return {
|
|
47
|
+
data: d,
|
|
48
|
+
cx: xScale(d[xf]),
|
|
49
|
+
cy: yScale(d[yf]),
|
|
50
|
+
r,
|
|
51
|
+
fill: colorEntry.fill,
|
|
52
|
+
stroke: colorEntry.stroke,
|
|
53
|
+
symbolPath,
|
|
54
|
+
key: colorKey
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { line, curveCatmullRom } from 'd3-shape'
|
|
2
|
+
|
|
3
|
+
// Relative widths at each stat anchor (fraction of max half-width)
|
|
4
|
+
const DENSITY_AT = { iqr_min: 0.08, q1: 0.55, median: 1.0, q3: 0.55, iqr_max: 0.08 }
|
|
5
|
+
const ANCHOR_ORDER = ['iqr_max', 'q3', 'median', 'q1', 'iqr_min']
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Builds a closed violin shape path for each group.
|
|
9
|
+
* Input rows must have { q1, median, q3, iqr_min, iqr_max } from applyBoxStat.
|
|
10
|
+
*
|
|
11
|
+
* When `fill` differs from `x`, violins are sub-grouped within each x-band
|
|
12
|
+
* (one narrower violin per fill value per x category, like grouped bars).
|
|
13
|
+
* Violin body uses the lighter fill shade; outline uses the darker stroke shade.
|
|
14
|
+
*
|
|
15
|
+
* @param {Object[]} data
|
|
16
|
+
* @param {{ x: string, fill?: string }} channels
|
|
17
|
+
* `fill` drives violin interior and outline (defaults to x-field).
|
|
18
|
+
* @param {import('d3-scale').ScaleBand} xScale
|
|
19
|
+
* @param {import('d3-scale').ScaleLinear} yScale
|
|
20
|
+
* @param {Map} colors
|
|
21
|
+
* @returns {Array}
|
|
22
|
+
*/
|
|
23
|
+
export function buildViolins(data, channels, xScale, yScale, colors) {
|
|
24
|
+
const { x: xf, fill: ff } = channels
|
|
25
|
+
const bw = typeof xScale.bandwidth === 'function' ? xScale.bandwidth() : 40
|
|
26
|
+
const grouped = ff && ff !== xf
|
|
27
|
+
|
|
28
|
+
const pathGen = line()
|
|
29
|
+
.x((pt) => pt.x)
|
|
30
|
+
.y((pt) => pt.y)
|
|
31
|
+
.curve(curveCatmullRom.alpha(0.5))
|
|
32
|
+
|
|
33
|
+
if (grouped) {
|
|
34
|
+
const fillValues = [...new Set(data.map((d) => d[ff]))]
|
|
35
|
+
const n = fillValues.length
|
|
36
|
+
const subBandWidth = bw / n
|
|
37
|
+
const halfMax = subBandWidth * 0.45
|
|
38
|
+
|
|
39
|
+
return data.map((d) => {
|
|
40
|
+
const fillVal = d[ff]
|
|
41
|
+
const subIndex = fillValues.indexOf(fillVal)
|
|
42
|
+
const bandStart = xScale(d[xf]) ?? 0
|
|
43
|
+
const cx = bandStart + subIndex * subBandWidth + subBandWidth / 2
|
|
44
|
+
const colorEntry = colors?.get(fillVal) ?? { fill: '#aaa', stroke: '#666' }
|
|
45
|
+
|
|
46
|
+
const rightPts = ANCHOR_ORDER.map((key) => ({
|
|
47
|
+
x: cx + halfMax * DENSITY_AT[key],
|
|
48
|
+
y: yScale(d[key])
|
|
49
|
+
}))
|
|
50
|
+
const leftPts = [...ANCHOR_ORDER].reverse().map((key) => ({
|
|
51
|
+
x: cx - halfMax * DENSITY_AT[key],
|
|
52
|
+
y: yScale(d[key])
|
|
53
|
+
}))
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
data: d,
|
|
57
|
+
cx,
|
|
58
|
+
d: pathGen([...rightPts, ...leftPts, rightPts[0]]),
|
|
59
|
+
fill: colorEntry.fill,
|
|
60
|
+
stroke: colorEntry.stroke
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Non-grouped: one violin per x category
|
|
66
|
+
const halfMax = bw * 0.45
|
|
67
|
+
|
|
68
|
+
return data.map((d) => {
|
|
69
|
+
const fillKey = ff ? d[ff] : d[xf]
|
|
70
|
+
const colorEntry = colors?.get(fillKey) ?? { fill: '#aaa', stroke: '#666' }
|
|
71
|
+
const cx = (xScale(d[xf]) ?? 0) + (typeof xScale.bandwidth === 'function' ? bw / 2 : 0)
|
|
72
|
+
|
|
73
|
+
const rightPts = ANCHOR_ORDER.map((key) => ({
|
|
74
|
+
x: cx + halfMax * DENSITY_AT[key],
|
|
75
|
+
y: yScale(d[key])
|
|
76
|
+
}))
|
|
77
|
+
const leftPts = [...ANCHOR_ORDER].reverse().map((key) => ({
|
|
78
|
+
x: cx - halfMax * DENSITY_AT[key],
|
|
79
|
+
y: yScale(d[key])
|
|
80
|
+
}))
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
data: d,
|
|
84
|
+
cx,
|
|
85
|
+
d: pathGen([...rightPts, ...leftPts, rightPts[0]]),
|
|
86
|
+
fill: colorEntry.fill,
|
|
87
|
+
stroke: colorEntry.stroke
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a data key to a safe SVG element ID for pattern references.
|
|
3
|
+
* Spaces and non-word characters are replaced to avoid broken url(#...) refs.
|
|
4
|
+
* @param {unknown} key
|
|
5
|
+
* @returns {string}
|
|
6
|
+
*/
|
|
7
|
+
export function toPatternId(key) {
|
|
8
|
+
return `chart-pat-${String(key).replace(/\s+/g, '-').replace(/[^\w-]/g, '_')}`
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Keys must match the keys in packages/chart/src/patterns/patterns.js
|
|
12
|
+
export const PATTERN_ORDER = [
|
|
13
|
+
'diagonal',
|
|
14
|
+
'dots',
|
|
15
|
+
'triangles',
|
|
16
|
+
'hatch',
|
|
17
|
+
'lattice',
|
|
18
|
+
'swell',
|
|
19
|
+
'checkerboard',
|
|
20
|
+
'waves',
|
|
21
|
+
'petals'
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Assigns patterns from PATTERN_ORDER to an array of distinct values.
|
|
26
|
+
* @param {unknown[]} values
|
|
27
|
+
* @returns {Map<unknown, string>}
|
|
28
|
+
*/
|
|
29
|
+
export function assignPatterns(values) {
|
|
30
|
+
return new Map(values.map((v, i) => [v, PATTERN_ORDER[i % PATTERN_ORDER.length]]))
|
|
31
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { scaleBand, scaleLinear, scaleSqrt } from 'd3-scale'
|
|
2
|
+
import { max, extent } from 'd3-array'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Builds an x scale (band for categorical, linear for numeric).
|
|
6
|
+
* @param {Object[]} data
|
|
7
|
+
* @param {string} field
|
|
8
|
+
* @param {number} width - inner width (pixels)
|
|
9
|
+
* @param {{ padding?: number }} opts
|
|
10
|
+
*/
|
|
11
|
+
export function buildXScale(data, field, width, opts = {}) {
|
|
12
|
+
const values = [...new Set(data.map((d) => d[field]))]
|
|
13
|
+
const isNumeric = values.every((v) => typeof v === 'number' || (!isNaN(Number(v)) && v !== ''))
|
|
14
|
+
if (isNumeric) {
|
|
15
|
+
const [minVal, maxVal] = extent(data, (d) => Number(d[field]))
|
|
16
|
+
return scaleLinear().domain([minVal, maxVal]).range([0, width]).nice()
|
|
17
|
+
}
|
|
18
|
+
return scaleBand()
|
|
19
|
+
.domain(values)
|
|
20
|
+
.range([0, width])
|
|
21
|
+
.padding(opts.padding ?? 0.2)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Builds a y linear scale from 0 to max, extended by any layer overrides.
|
|
26
|
+
* @param {Object[]} data
|
|
27
|
+
* @param {string} field
|
|
28
|
+
* @param {number} height - inner height (pixels)
|
|
29
|
+
* @param {Array<{data?: Object[], y?: string}>} layers
|
|
30
|
+
*/
|
|
31
|
+
export function buildYScale(data, field, height, layers = []) {
|
|
32
|
+
let maxVal = max(data, (d) => Number(d[field])) ?? 0
|
|
33
|
+
for (const layer of layers) {
|
|
34
|
+
if (layer.data && layer.y) {
|
|
35
|
+
const layerMax = max(layer.data, (d) => Number(d[layer.y])) ?? 0
|
|
36
|
+
if (layerMax > maxVal) maxVal = layerMax
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return scaleLinear().domain([0, maxVal]).range([height, 0]).nice()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Builds a sqrt scale for bubble/point size.
|
|
44
|
+
* @param {Object[]} data
|
|
45
|
+
* @param {string} field
|
|
46
|
+
* @param {number} maxRadius
|
|
47
|
+
*/
|
|
48
|
+
export function buildSizeScale(data, field, maxRadius = 20) {
|
|
49
|
+
const maxVal = max(data, (d) => Number(d[field])) ?? 1
|
|
50
|
+
return scaleSqrt().domain([0, maxVal]).range([0, maxRadius])
|
|
51
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { SvelteSet } from 'svelte/reactivity'
|
|
2
|
+
import { min, max } from 'd3-array'
|
|
3
|
+
import { scaleLinear, scaleOrdinal } from 'd3-scale'
|
|
4
|
+
import { schemeCategory10 } from 'd3-scale-chromatic'
|
|
5
|
+
import {} from './types.js'
|
|
6
|
+
import { buildXScale } from '../xscale.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {import('./types').ChartScales} ChartScales
|
|
10
|
+
* @typedef {import('./types').ScaleFields} ScaleFields
|
|
11
|
+
* @typedef {import('./types').ChartDimensions} ChartDimensions
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {Array} data
|
|
16
|
+
* @param {string} colorField
|
|
17
|
+
* @returns {import('d3-scale').ScaleOrdinal}
|
|
18
|
+
*/
|
|
19
|
+
function buildColorScale(data, colorField) {
|
|
20
|
+
const colorValues = [...new SvelteSet(data.map((d) => d[colorField]))]
|
|
21
|
+
return scaleOrdinal().domain(colorValues).range(schemeCategory10)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param {Array} data
|
|
26
|
+
* @param {string} yField
|
|
27
|
+
* @param {Object} dimensions
|
|
28
|
+
* @returns {import('d3-scale').ScaleContinuousNumeric}
|
|
29
|
+
*/
|
|
30
|
+
function buildYScale(data, yField, dimensions) {
|
|
31
|
+
const yValues = data.map((d) => d[yField])
|
|
32
|
+
return scaleLinear()
|
|
33
|
+
.domain([0, max(yValues) * 1.1])
|
|
34
|
+
.nice()
|
|
35
|
+
.range([dimensions.innerHeight, 0])
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {Array} data
|
|
40
|
+
* @param {ScaleFields} fields
|
|
41
|
+
* @returns {boolean}
|
|
42
|
+
*/
|
|
43
|
+
function hasRequiredFields(data, fields) {
|
|
44
|
+
return Boolean(data && data.length && fields.x && fields.y)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Creates scales based on data, fields, and dimensions
|
|
49
|
+
*
|
|
50
|
+
* @param {Array} data - Chart data
|
|
51
|
+
* @param {ScaleFields} fields - Field mappings
|
|
52
|
+
* @param {Object} dimensions - Chart dimensions
|
|
53
|
+
* @param {Object} options - Scale options
|
|
54
|
+
* @param {number} [options.padding=0.2] - Padding for band scales
|
|
55
|
+
* @returns {ChartScales} Chart scales
|
|
56
|
+
*/
|
|
57
|
+
export function createScales(data, fields, dimensions, options = {}) {
|
|
58
|
+
if (!hasRequiredFields(data, fields)) return { x: null, y: null, color: null }
|
|
59
|
+
|
|
60
|
+
const padding = options.padding ?? 0.2
|
|
61
|
+
const xValues = data.map((d) => d[fields.x])
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
x: buildXScale(xValues, dimensions, padding),
|
|
65
|
+
y: buildYScale(data, fields.y, dimensions),
|
|
66
|
+
color: fields.color ? buildColorScale(data, fields.color) : null
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Gets the origin coordinates for the axes
|
|
72
|
+
*
|
|
73
|
+
* @param {ChartScales} scales - Chart scales
|
|
74
|
+
* @param {Object} dimensions - Chart dimensions
|
|
75
|
+
* @returns {Object} Origin coordinates
|
|
76
|
+
*/
|
|
77
|
+
export function getOrigin(scales, dimensions) {
|
|
78
|
+
return {
|
|
79
|
+
x: scales.y ? scales.y(0) : dimensions.innerHeight,
|
|
80
|
+
y: scales.x ? (scales.x.ticks ? scales.x(Math.max(0, min(scales.x.domain()))) : 0) : 0
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { sum, mean, min, max, quantile, ascending } from 'd3-array'
|
|
2
|
+
import { dataset } from '@rokkit/data'
|
|
3
|
+
|
|
4
|
+
function sortedQuantile(values, p) {
|
|
5
|
+
return quantile([...values].sort(ascending), p)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Built-in reduction functions. Each receives an array of numeric values.
|
|
10
|
+
* @type {Record<string, (values: number[]) => number>}
|
|
11
|
+
*/
|
|
12
|
+
export const STAT_FNS = {
|
|
13
|
+
sum,
|
|
14
|
+
mean,
|
|
15
|
+
min,
|
|
16
|
+
max,
|
|
17
|
+
count: (values) => values.length
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Computes box plot quartile statistics grouped by x (and optionally color).
|
|
22
|
+
* Output rows have { q1, median, q3, iqr_min, iqr_max } replacing the raw y values.
|
|
23
|
+
*
|
|
24
|
+
* @param {Object[]} data
|
|
25
|
+
* @param {{ x?: string, y?: string, color?: string }} channels
|
|
26
|
+
* @returns {Object[]}
|
|
27
|
+
*/
|
|
28
|
+
export function applyBoxStat(data, channels) {
|
|
29
|
+
const { x: xf, y: yf, color: cf } = channels
|
|
30
|
+
if (!xf || !yf) return data
|
|
31
|
+
const by = [xf, cf].filter(Boolean)
|
|
32
|
+
return dataset(data)
|
|
33
|
+
.groupBy(...by)
|
|
34
|
+
.summarize((row) => row[yf], {
|
|
35
|
+
q1: (v) => sortedQuantile(v, 0.25),
|
|
36
|
+
median: (v) => sortedQuantile(v, 0.5),
|
|
37
|
+
q3: (v) => sortedQuantile(v, 0.75),
|
|
38
|
+
iqr_min: (v) => { const q1 = sortedQuantile(v, 0.25); const q3 = sortedQuantile(v, 0.75); return q1 - 1.5 * (q3 - q1) },
|
|
39
|
+
iqr_max: (v) => { const q1 = sortedQuantile(v, 0.25); const q3 = sortedQuantile(v, 0.75); return q3 + 1.5 * (q3 - q1) }
|
|
40
|
+
})
|
|
41
|
+
.rollup()
|
|
42
|
+
.select()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Aggregates data by one or more grouping fields, reducing the value field
|
|
47
|
+
* using the given stat. Accepts a built-in name or a custom function.
|
|
48
|
+
*
|
|
49
|
+
* @param {Object[]} data
|
|
50
|
+
* @param {{ by: string[], value: string, stat: string|Function }} opts
|
|
51
|
+
* @returns {Object[]}
|
|
52
|
+
*/
|
|
53
|
+
export function applyAggregate(data, { by, value, stat }) {
|
|
54
|
+
if (stat === 'identity' || by.length === 0 || value === null || value === undefined) return data
|
|
55
|
+
const fn = typeof stat === 'function' ? stat : STAT_FNS[stat]
|
|
56
|
+
if (fn === null || fn === undefined) return data
|
|
57
|
+
return dataset(data)
|
|
58
|
+
.groupBy(...by)
|
|
59
|
+
.summarize((row) => row[value], { [value]: fn })
|
|
60
|
+
.rollup()
|
|
61
|
+
.select()
|
|
62
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const SYMBOL_ORDER = ['circle', 'square', 'triangle', 'diamond', 'plus', 'cross', 'star']
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Assigns shapes from SYMBOL_ORDER to an array of distinct values.
|
|
5
|
+
* @param {unknown[]} values
|
|
6
|
+
* @returns {Map<unknown, string>}
|
|
7
|
+
*/
|
|
8
|
+
export function assignSymbols(values) {
|
|
9
|
+
return new Map(values.map((v, i) => [v, SYMBOL_ORDER[i % SYMBOL_ORDER.length]]))
|
|
10
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} ChartMargin
|
|
3
|
+
* @property {number} top - Top margin
|
|
4
|
+
* @property {number} right - Right margin
|
|
5
|
+
* @property {number} bottom - Bottom margin
|
|
6
|
+
* @property {number} left - Left margin
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {Object} ChartDimensions
|
|
11
|
+
* @property {number} width - Total chart width
|
|
12
|
+
* @property {number} height - Total chart height
|
|
13
|
+
* @property {ChartMargin} margin - Chart margins
|
|
14
|
+
* @property {number} innerWidth - Chart width without margins
|
|
15
|
+
* @property {number} innerHeight - Chart height without margins
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {Object} ChartScales
|
|
20
|
+
* @property {Function} x - X-axis scale function
|
|
21
|
+
* @property {Function} y - Y-axis scale function
|
|
22
|
+
* @property {Function} color - Color scale function
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @typedef {Object} ScaleFields
|
|
27
|
+
* @property {string} x - X-axis field
|
|
28
|
+
* @property {string} y - Y-axis field
|
|
29
|
+
* @property {string} color - Color field
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @typedef {Object} TickData
|
|
34
|
+
* @property {*} value - Tick value
|
|
35
|
+
* @property {number} position - Tick position in pixels
|
|
36
|
+
* @property {string} formattedValue - Formatted tick label
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @typedef {Object} AxisData
|
|
41
|
+
* @property {TickData[]} ticks - Tick data
|
|
42
|
+
* @property {string} label - Axis label
|
|
43
|
+
* @property {string} transform - SVG transform attribute value
|
|
44
|
+
* @property {string} labelTransform - SVG transform for the label
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @typedef {Object} BarData
|
|
49
|
+
* @property {Object} data - Original data point
|
|
50
|
+
* @property {number} x - X position
|
|
51
|
+
* @property {number} y - Y position
|
|
52
|
+
* @property {number} width - Width of the bar
|
|
53
|
+
* @property {number} height - Height of the bar
|
|
54
|
+
* @property {string} color - Color of the bar
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @typedef {Object} LegendItem
|
|
59
|
+
* @property {*} value - Legend item value
|
|
60
|
+
* @property {string} color - Item color
|
|
61
|
+
* @property {number} y - Y position
|
|
62
|
+
* @property {string} shape - Shape type ('rect' or 'circle')
|
|
63
|
+
* @property {number} markerSize - Size of the marker
|
|
64
|
+
*/
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @typedef {Object} LegendData
|
|
68
|
+
* @property {LegendItem[]} items - Legend items
|
|
69
|
+
* @property {string} title - Legend title
|
|
70
|
+
* @property {string} transform - SVG transform attribute value
|
|
71
|
+
*/
|
|
72
|
+
|
|
73
|
+
export {}
|
package/src/lib/chart.js
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { min, max } from 'd3-array'
|
|
2
|
+
import { scaleBand, scaleLinear, scaleTime } from 'd3-scale'
|
|
3
|
+
|
|
4
|
+
function getOriginValue(scale) {
|
|
5
|
+
return scale.ticks ? scale(Math.max(0, Math.min(...scale.domain()))) : scale.range()[0]
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function getScale(domain, range, padding = 0) {
|
|
9
|
+
if (domain.some(isNaN)) {
|
|
10
|
+
return scaleBand().domain(domain).range(range).padding(padding)
|
|
11
|
+
} else if (domain[0] instanceof Date) {
|
|
12
|
+
return scaleTime()
|
|
13
|
+
.domain([min(domain), max(domain)])
|
|
14
|
+
.range(range)
|
|
15
|
+
.nice()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return scaleLinear()
|
|
19
|
+
.domain([min([0, ...domain]), max([0, ...domain])])
|
|
20
|
+
.range(range)
|
|
21
|
+
.nice()
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
class Chart {
|
|
25
|
+
// data = []
|
|
26
|
+
// width = 512
|
|
27
|
+
// height = 512
|
|
28
|
+
// origin = { x: 0, y: 0 }
|
|
29
|
+
// range = {
|
|
30
|
+
// x: [0, this.width],
|
|
31
|
+
// y: [this.height, 0]
|
|
32
|
+
// }
|
|
33
|
+
// x
|
|
34
|
+
// y
|
|
35
|
+
// stat = 'identity'
|
|
36
|
+
// scale
|
|
37
|
+
// fill
|
|
38
|
+
// color
|
|
39
|
+
// value
|
|
40
|
+
// shape
|
|
41
|
+
// valueFormat
|
|
42
|
+
// valueLabel
|
|
43
|
+
// domain
|
|
44
|
+
// margin
|
|
45
|
+
// spacing
|
|
46
|
+
// padding
|
|
47
|
+
// flipCoords = false
|
|
48
|
+
|
|
49
|
+
constructor(data, opts) {
|
|
50
|
+
this.width = Number(opts.width) || 2048
|
|
51
|
+
this.height = Number(opts.height) || 2048
|
|
52
|
+
this.flipCoords = opts.flipCoords || false
|
|
53
|
+
this.x = opts.x
|
|
54
|
+
this.y = opts.y
|
|
55
|
+
this.value = opts.value || opts.y
|
|
56
|
+
this.valueLabel = opts.valueLabel || this.value
|
|
57
|
+
this.valueFormat = opts.valueFormat || ((d) => d)
|
|
58
|
+
this.fill = opts.fill || opts.x
|
|
59
|
+
this.color = opts.color || opts.fill
|
|
60
|
+
this.shape = opts.shape || opts.fill
|
|
61
|
+
|
|
62
|
+
this.padding = opts.padding !== undefined ? Number(opts.padding) : 32
|
|
63
|
+
|
|
64
|
+
this.spacing =
|
|
65
|
+
Number(opts.spacing) >= 0 && Number(opts.spacing) <= 0.5 ? Number(opts.spacing) : 0
|
|
66
|
+
this.margin = {
|
|
67
|
+
top: Number(opts.margin?.top) || 0,
|
|
68
|
+
left: Number(opts.margin?.left) || 0,
|
|
69
|
+
right: Number(opts.margin?.right) || 0,
|
|
70
|
+
bottom: Number(opts.margin?.bottom) || 0
|
|
71
|
+
}
|
|
72
|
+
this.domain = {
|
|
73
|
+
x: [...new Set(data.map((d) => d[this.x]))],
|
|
74
|
+
y: [...new Set(data.map((d) => d[this.y]))]
|
|
75
|
+
}
|
|
76
|
+
if (this.flipCoords) {
|
|
77
|
+
this.domain = { y: this.domain.x, x: this.domain.y }
|
|
78
|
+
}
|
|
79
|
+
this.stat = opts.stat || 'identity'
|
|
80
|
+
|
|
81
|
+
this.data = data.map((d) => ({
|
|
82
|
+
x: this.flipCoords ? d[this.y] : d[this.x],
|
|
83
|
+
y: this.flipCoords ? d[this.x] : d[this.y],
|
|
84
|
+
// fill: d[this.fill],
|
|
85
|
+
color: d[this.color]
|
|
86
|
+
// shape: d[this.shape]
|
|
87
|
+
}))
|
|
88
|
+
|
|
89
|
+
this.refresh()
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
padding(value) {
|
|
93
|
+
this.padding = value
|
|
94
|
+
return this.refresh()
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
margin(value) {
|
|
98
|
+
this.margin = value
|
|
99
|
+
return this.refresh()
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
refresh() {
|
|
103
|
+
this.range = {
|
|
104
|
+
x: [this.margin.left + this.padding, this.width - this.margin.right - this.padding],
|
|
105
|
+
y: [this.height - this.padding - this.margin.bottom, this.margin.top + this.padding]
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const scale = {
|
|
109
|
+
x: getScale(this.domain.x, this.range.x, this.spacing),
|
|
110
|
+
y: getScale(this.domain.y, this.range.y, this.spacing)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// scale['value'] = this.value === this.x ? scale.x : scale.y
|
|
114
|
+
|
|
115
|
+
this.origin = {
|
|
116
|
+
x: getOriginValue(scale.x),
|
|
117
|
+
y: getOriginValue(scale.y)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
this.scale = scale
|
|
121
|
+
|
|
122
|
+
return this
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// get scale() {
|
|
126
|
+
// return this.scale
|
|
127
|
+
// }
|
|
128
|
+
// get origin() {
|
|
129
|
+
// return this.origin
|
|
130
|
+
// }
|
|
131
|
+
// get margin() {
|
|
132
|
+
// return this.margin
|
|
133
|
+
// }
|
|
134
|
+
// get range() {
|
|
135
|
+
// const [x1, x2] = this.scale.x.range()
|
|
136
|
+
// const [y1, y2] = this.scale.y.range()
|
|
137
|
+
|
|
138
|
+
// return { x1, y1, x2, y2 }
|
|
139
|
+
// }
|
|
140
|
+
// get data() {
|
|
141
|
+
// // aggregate data group by x,y,fill,shape, color
|
|
142
|
+
// // stat = [min, max, avg, std, q1, q3, median, sum, count, box, all]
|
|
143
|
+
|
|
144
|
+
// return this.data
|
|
145
|
+
// }
|
|
146
|
+
// get width() {
|
|
147
|
+
// return this.width
|
|
148
|
+
// }
|
|
149
|
+
// get height() {
|
|
150
|
+
// return this.height
|
|
151
|
+
// }
|
|
152
|
+
// set width(value) {
|
|
153
|
+
// this.width = value
|
|
154
|
+
// }
|
|
155
|
+
// set height(value) {
|
|
156
|
+
// this.height = value
|
|
157
|
+
// }
|
|
158
|
+
// get domain() {
|
|
159
|
+
// return this.domain
|
|
160
|
+
// }
|
|
161
|
+
// get flipCoords() {
|
|
162
|
+
// return this.flipCoords
|
|
163
|
+
// }
|
|
164
|
+
aggregate(value, stat) {
|
|
165
|
+
this.value = value
|
|
166
|
+
this.stat = stat
|
|
167
|
+
|
|
168
|
+
// this.data = nest(this.data)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
ticks(axis, count, fontSize = 8) {
|
|
172
|
+
const scale = this.scale[axis]
|
|
173
|
+
const [minRange, maxRange] = scale.range()
|
|
174
|
+
let ticks = []
|
|
175
|
+
let offset = 0
|
|
176
|
+
|
|
177
|
+
count = count || Math.abs((maxRange - minRange) / (fontSize * (axis === 'y' ? 8 : 8)))
|
|
178
|
+
|
|
179
|
+
if (scale.ticks) {
|
|
180
|
+
ticks = scale.ticks(Math.round(count))
|
|
181
|
+
} else {
|
|
182
|
+
offset = scale.bandwidth() / 2
|
|
183
|
+
count = Math.min(Math.round(count), scale.domain().length)
|
|
184
|
+
|
|
185
|
+
ticks = scale.domain()
|
|
186
|
+
if (count < scale.domain().length) {
|
|
187
|
+
const diff = scale.domain().length - count
|
|
188
|
+
ticks = ticks.filter((d, i) => i % diff === 0)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
ticks = ticks
|
|
193
|
+
.map((t) => ({
|
|
194
|
+
label: t,
|
|
195
|
+
pos: scale(t)
|
|
196
|
+
}))
|
|
197
|
+
.map(({ label, pos }) => ({
|
|
198
|
+
label,
|
|
199
|
+
offset: {
|
|
200
|
+
x: axis === 'x' ? offset : 0,
|
|
201
|
+
y: axis === 'y' ? offset : 0
|
|
202
|
+
},
|
|
203
|
+
x: axis === 'x' ? pos : this.origin.x,
|
|
204
|
+
y: axis === 'y' ? pos : this.origin.y
|
|
205
|
+
}))
|
|
206
|
+
|
|
207
|
+
return ticks
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function chart(data, aes) {
|
|
212
|
+
return new Chart(data, aes)
|
|
213
|
+
}
|