@rokkit/chart 1.0.0-next.98 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +150 -46
- package/package.json +40 -51
- package/src/AnimatedPlot.svelte +383 -0
- package/src/Chart.svelte +87 -59
- package/src/ChartExporter.svelte +125 -0
- package/src/ChartLegend.svelte +148 -0
- package/src/ChartProvider.svelte +10 -0
- package/src/FacetPlot/Panel.svelte +37 -0
- package/src/FacetPlot.svelte +114 -0
- package/src/Plot/Arc.svelte +29 -0
- package/src/Plot/Area.svelte +32 -0
- package/src/Plot/Axis.svelte +164 -0
- package/src/Plot/Bar.svelte +54 -0
- package/src/Plot/Grid.svelte +34 -0
- package/src/Plot/Legend.svelte +268 -0
- package/src/Plot/Line.svelte +37 -0
- package/src/Plot/Point.svelte +40 -0
- package/src/Plot/Root.svelte +62 -0
- package/src/Plot/Timeline.svelte +95 -0
- package/src/Plot/Tooltip.svelte +87 -0
- package/src/Plot/index.js +9 -0
- package/src/Plot.svelte +313 -0
- package/src/PlotState.svelte.js +375 -0
- package/src/Sparkline.svelte +108 -0
- package/src/Symbol.svelte +15 -11
- package/src/Texture.svelte +9 -7
- package/src/charts/AreaChart.svelte +30 -0
- package/src/charts/BarChart.svelte +31 -0
- package/src/charts/BoxPlot.svelte +24 -0
- package/src/charts/BubbleChart.svelte +28 -0
- package/src/charts/LineChart.svelte +31 -0
- package/src/charts/PieChart.svelte +35 -0
- package/src/charts/ScatterPlot.svelte +31 -0
- package/src/charts/ViolinPlot.svelte +24 -0
- package/src/crossfilter/CrossFilter.svelte +42 -0
- package/src/crossfilter/FilterBar.svelte +24 -0
- package/src/crossfilter/FilterHistogram.svelte +290 -0
- package/src/crossfilter/FilterSlider.svelte +83 -0
- package/src/crossfilter/createCrossFilter.svelte.js +124 -0
- package/src/elements/Bar.svelte +20 -16
- package/src/elements/ColorRamp.svelte +19 -19
- package/src/elements/ContinuousLegend.svelte +41 -19
- package/src/elements/DefinePatterns.svelte +9 -7
- package/src/elements/DiscreteLegend.svelte +13 -11
- package/src/elements/Label.svelte +4 -9
- package/src/elements/SymbolGrid.svelte +8 -9
- package/src/examples/BarChartExample.svelte +81 -0
- package/src/geoms/Arc.svelte +126 -0
- package/src/geoms/Area.svelte +78 -0
- package/src/geoms/Bar.svelte +200 -0
- package/src/geoms/Box.svelte +113 -0
- package/src/geoms/Candlestick.svelte +110 -0
- package/src/geoms/Heatmap.svelte +92 -0
- package/src/geoms/Hexbin.svelte +114 -0
- package/src/geoms/LabelPill.svelte +17 -0
- package/src/geoms/Line.svelte +123 -0
- package/src/geoms/Point.svelte +145 -0
- package/src/geoms/Ribbon.svelte +161 -0
- package/src/geoms/Violin.svelte +56 -0
- package/src/geoms/Waterfall.svelte +114 -0
- package/src/geoms/lib/areas.js +154 -0
- package/src/geoms/lib/bars.js +223 -0
- package/src/index.js +81 -2
- package/src/lib/brewing/BoxBrewer.svelte.js +14 -0
- package/src/lib/brewing/CartesianBrewer.svelte.js +21 -0
- package/src/lib/brewing/PieBrewer.svelte.js +14 -0
- package/src/lib/brewing/QuartileBrewer.svelte.js +51 -0
- package/src/lib/brewing/ViolinBrewer.svelte.js +14 -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 +277 -0
- package/src/lib/brewing/colors.js +157 -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 +72 -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 +55 -0
- package/src/lib/brewing/marks/points.js +105 -0
- package/src/lib/brewing/marks/violins.js +90 -0
- package/src/lib/brewing/patterns.js +45 -0
- package/src/lib/brewing/scales.js +51 -0
- package/src/lib/brewing/scales.svelte.js +82 -0
- package/src/lib/brewing/stats.js +74 -0
- package/src/lib/brewing/symbols.js +10 -0
- package/src/lib/brewing/types.js +73 -0
- package/src/lib/chart.js +16 -8
- package/src/lib/context.js +131 -0
- package/src/lib/keyboard-nav.js +37 -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 +81 -0
- package/src/lib/plot/helpers.js +14 -0
- package/src/lib/plot/preset.js +67 -0
- package/src/lib/plot/scales.js +81 -0
- package/src/lib/plot/stat.js +92 -0
- package/src/lib/plot/types.js +65 -0
- package/src/lib/preset.js +41 -0
- package/src/lib/scales.svelte.js +151 -0
- package/src/lib/swatch.js +4 -7
- package/src/lib/utils.js +142 -0
- package/src/lib/xscale.js +31 -0
- package/src/patterns/DefinePatterns.svelte +32 -0
- package/src/patterns/PatternDef.svelte +27 -0
- package/src/patterns/index.js +4 -14
- package/src/patterns/patterns.js +360 -0
- package/src/patterns/scale.js +116 -0
- package/src/spec/chart-spec.js +72 -0
- package/src/symbols/RoundedSquare.svelte +18 -12
- package/src/symbols/Shape.svelte +22 -16
- package/src/symbols/constants/index.js +1 -1
- package/LICENSE +0 -21
- package/src/lib/index.js +0 -4
- package/src/lib/plots.js +0 -27
- package/src/patterns/Brick.svelte +0 -17
- package/src/patterns/Circles.svelte +0 -18
- package/src/patterns/CrossHatch.svelte +0 -14
- package/src/patterns/CurvedWave.svelte +0 -9
- package/src/patterns/Dots.svelte +0 -19
- package/src/patterns/OutlineCircles.svelte +0 -15
- package/src/patterns/Tile.svelte +0 -17
- package/src/patterns/Triangles.svelte +0 -15
- package/src/patterns/Waves.svelte +0 -13
- package/src/patterns/paths/NamedPattern.svelte +0 -12
- package/src/patterns/paths/constants.js +0 -4
- package/src/plots/Plot.svelte +0 -40
- package/src/plots/index.js +0 -1
- package/src/template/Texture.svelte +0 -16
- package/src/template/constants.js +0 -43
- package/src/template/shapes/Circles.svelte +0 -16
- package/src/template/shapes/Lines.svelte +0 -17
- package/src/template/shapes/Path.svelte +0 -12
- package/src/template/shapes/Polygons.svelte +0 -18
- package/src/template/shapes/index.js +0 -4
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// packages/chart/src/lib/plot/types.js
|
|
2
|
+
// JSDoc typedefs for the Plot system. No runtime code.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {Object} GeomSpec
|
|
6
|
+
* @property {string} type - Geom type: 'bar'|'line'|'area'|'point'|'box'|'violin'|'arc' or custom
|
|
7
|
+
* @property {string} [x]
|
|
8
|
+
* @property {string} [y]
|
|
9
|
+
* @property {string} [color]
|
|
10
|
+
* @property {string} [fill]
|
|
11
|
+
* @property {string} [size]
|
|
12
|
+
* @property {string} [symbol]
|
|
13
|
+
* @property {string} [pattern]
|
|
14
|
+
* @property {string} [stat] - Built-in or helpers.stats key
|
|
15
|
+
* @property {Record<string, unknown>} [options]
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {Object} PlotSpec
|
|
20
|
+
* @property {Record<string, unknown>[]} data
|
|
21
|
+
* @property {string} [x]
|
|
22
|
+
* @property {string} [y]
|
|
23
|
+
* @property {string} [color]
|
|
24
|
+
* @property {string} [fill]
|
|
25
|
+
* @property {string} [size]
|
|
26
|
+
* @property {string} [symbol]
|
|
27
|
+
* @property {string} [pattern]
|
|
28
|
+
* @property {string} [theta]
|
|
29
|
+
* @property {Record<string, string>} [labels]
|
|
30
|
+
* @property {unknown[]} [xDomain]
|
|
31
|
+
* @property {number[]} [yDomain]
|
|
32
|
+
* @property {string} [xLabel]
|
|
33
|
+
* @property {string} [yLabel]
|
|
34
|
+
* @property {[number, number]} [axisOrigin]
|
|
35
|
+
* @property {'categorical'|'sequential'|'diverging'} [colorScale]
|
|
36
|
+
* @property {string} [colorScheme]
|
|
37
|
+
* @property {number} [colorMidpoint]
|
|
38
|
+
* @property {unknown[]} [colorDomain]
|
|
39
|
+
* @property {GeomSpec[]} geoms
|
|
40
|
+
* @property {{ by: string, cols?: number, scales?: 'fixed'|'free'|'free_x'|'free_y' }} [facet]
|
|
41
|
+
* @property {{ by: string, duration?: number, loop?: boolean }} [animate]
|
|
42
|
+
* @property {boolean} [grid]
|
|
43
|
+
* @property {boolean} [legend]
|
|
44
|
+
* @property {boolean} [tooltip]
|
|
45
|
+
* @property {string} [title]
|
|
46
|
+
* @property {string} [preset]
|
|
47
|
+
* @property {number} [width]
|
|
48
|
+
* @property {number} [height]
|
|
49
|
+
* @property {'light'|'dark'} [mode]
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @typedef {Object} PlotHelpers
|
|
54
|
+
* @property {Record<string, (values: unknown[]) => unknown>} [stats]
|
|
55
|
+
* @property {Record<string, (v: unknown) => string>} [format]
|
|
56
|
+
* @property {(d: Record<string, unknown>) => string} [tooltip]
|
|
57
|
+
* @property {Record<string, unknown>} [geoms] Svelte components keyed by type name
|
|
58
|
+
* @property {unknown} [colorScale] d3 scale override
|
|
59
|
+
* @property {{ colors: string[], patterns: string[], symbols: string[] }} [preset]
|
|
60
|
+
* @property {Record<string, { colors: string[], patterns: string[], symbols: string[] }>} [presets]
|
|
61
|
+
* @property {Record<string, unknown>} [patterns] custom SVG pattern components keyed by name
|
|
62
|
+
* @property {Record<string, unknown>} [symbols] custom symbol shape components keyed by name
|
|
63
|
+
*/
|
|
64
|
+
|
|
65
|
+
export {}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// packages/chart/src/lib/preset.js
|
|
2
|
+
|
|
3
|
+
export const defaultPreset = {
|
|
4
|
+
colors: ['blue', 'emerald', 'rose', 'amber', 'violet', 'sky', 'pink', 'teal',
|
|
5
|
+
'orange', 'indigo', 'lime', 'cyan', 'gold', 'lavender'],
|
|
6
|
+
shades: {
|
|
7
|
+
light: { fill: '300', stroke: '700' },
|
|
8
|
+
dark: { fill: '500', stroke: '200' }
|
|
9
|
+
},
|
|
10
|
+
opacity: {
|
|
11
|
+
area: 0.6,
|
|
12
|
+
box: 0.5,
|
|
13
|
+
violin: 0.5,
|
|
14
|
+
point: 0.8
|
|
15
|
+
},
|
|
16
|
+
patterns: ['diagonal', 'dots', 'triangles', 'hatch', 'lattice', 'swell',
|
|
17
|
+
'checkerboard', 'waves', 'petals', 'brick', 'diamonds', 'tile',
|
|
18
|
+
'scales', 'circles', 'pip', 'rings', 'chevrons', 'shards',
|
|
19
|
+
'wedge', 'argyle', 'shell'],
|
|
20
|
+
symbols: ['circle', 'square', 'triangle', 'diamond', 'cross', 'star']
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Creates a chart preset by deep-merging overrides with the default preset.
|
|
25
|
+
* All fields are optional. `opacity` is merged key-by-key so partial overrides work.
|
|
26
|
+
* @param {Partial<typeof defaultPreset>} [overrides]
|
|
27
|
+
* @returns {typeof defaultPreset}
|
|
28
|
+
*/
|
|
29
|
+
export function createChartPreset(overrides = {}) {
|
|
30
|
+
return {
|
|
31
|
+
...defaultPreset,
|
|
32
|
+
...overrides,
|
|
33
|
+
shades: overrides.shades
|
|
34
|
+
? {
|
|
35
|
+
light: { ...defaultPreset.shades.light, ...overrides.shades.light },
|
|
36
|
+
dark: { ...defaultPreset.shades.dark, ...overrides.shades.dark }
|
|
37
|
+
}
|
|
38
|
+
: defaultPreset.shades,
|
|
39
|
+
opacity: { ...defaultPreset.opacity, ...overrides.opacity }
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { SvelteSet } from 'svelte/reactivity'
|
|
2
|
+
import { scaleLinear, scaleOrdinal } from 'd3-scale'
|
|
3
|
+
import { schemeCategory10 } from 'd3-scale-chromatic'
|
|
4
|
+
import { min, max } from 'd3-array'
|
|
5
|
+
import { buildXScale } from './xscale.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Creates appropriate scales based on data and dimensions
|
|
9
|
+
*
|
|
10
|
+
* @param {Array} data The dataset
|
|
11
|
+
* @param {Object} dimensions Chart dimensions
|
|
12
|
+
* @param {Object} options Additional options
|
|
13
|
+
* @param {string} options.xKey Field to use for x-axis
|
|
14
|
+
* @param {string} options.yKey Field to use for y-axis
|
|
15
|
+
* @param {string} [options.colorKey] Field to use for color mapping
|
|
16
|
+
* @param {number} [options.padding=0.2] Padding for band scales
|
|
17
|
+
* @returns {Object} Object containing xScale, yScale, and colorScale
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* @param {Array} data
|
|
21
|
+
* @param {string} colorKey
|
|
22
|
+
* @returns {Object}
|
|
23
|
+
*/
|
|
24
|
+
function buildColorScale(data, colorKey) {
|
|
25
|
+
const uniqueCategories = [...new SvelteSet(data.map((d) => d[colorKey]))]
|
|
26
|
+
return scaleOrdinal().domain(uniqueCategories).range(schemeCategory10)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {Object} options
|
|
31
|
+
* @returns {{ xKey: string, yKey: string, colorKey: string|undefined, padding: number }}
|
|
32
|
+
*/
|
|
33
|
+
function parseScaleOptions(options) {
|
|
34
|
+
const opts = options || {}
|
|
35
|
+
return {
|
|
36
|
+
xKey: opts.xKey,
|
|
37
|
+
yKey: opts.yKey,
|
|
38
|
+
colorKey: opts.colorKey,
|
|
39
|
+
padding: opts.padding !== undefined ? opts.padding : 0.2
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function createScales(data, dimensions, options) {
|
|
44
|
+
if (!data || !data.length) return {}
|
|
45
|
+
|
|
46
|
+
const { xKey, yKey, colorKey, padding } = parseScaleOptions(options)
|
|
47
|
+
const xValues = data.map((d) => d[xKey])
|
|
48
|
+
const yValues = data.map((d) => d[yKey])
|
|
49
|
+
const colorScale = colorKey ? buildColorScale(data, colorKey) : null
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
xScale: buildXScale(xValues, dimensions, padding),
|
|
53
|
+
yScale: scaleLinear()
|
|
54
|
+
.domain([0, max(yValues) * 1.1])
|
|
55
|
+
.nice()
|
|
56
|
+
.range([dimensions.innerHeight, 0]),
|
|
57
|
+
colorScale
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Calculates the actual chart dimensions after applying margins
|
|
63
|
+
*
|
|
64
|
+
* @param {number} width
|
|
65
|
+
* @param {number} height
|
|
66
|
+
* @param {Object} margin
|
|
67
|
+
* @returns {Object} Dimensions with calculated inner width and height
|
|
68
|
+
*/
|
|
69
|
+
export function calculateChartDimensions(width, height, margin) {
|
|
70
|
+
return {
|
|
71
|
+
width,
|
|
72
|
+
height,
|
|
73
|
+
margin,
|
|
74
|
+
innerWidth: width - margin.left - margin.right,
|
|
75
|
+
innerHeight: height - margin.top - margin.bottom
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Gets the axis origin value
|
|
81
|
+
*
|
|
82
|
+
* @param {Object} scale D3 scale
|
|
83
|
+
* @returns {number} Origin value
|
|
84
|
+
*/
|
|
85
|
+
export function getOriginValue(scale) {
|
|
86
|
+
return scale.ticks ? scale(Math.max(0, min(scale.domain()))) : scale.range()[0]
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Resolve tick values for a band scale, applying downsampling when needed
|
|
91
|
+
* @param {Object} scale
|
|
92
|
+
* @param {number} count
|
|
93
|
+
* @returns {{ ticks: Array, offset: number }}
|
|
94
|
+
*/
|
|
95
|
+
function bandTicks(scale, count) {
|
|
96
|
+
const offset = scale.bandwidth() / 2
|
|
97
|
+
const domain = scale.domain()
|
|
98
|
+
const cappedCount = Math.min(Math.round(count), domain.length)
|
|
99
|
+
const step = Math.ceil(domain.length / cappedCount)
|
|
100
|
+
const ticks = cappedCount < domain.length ? domain.filter((_, i) => i % step === 0) : domain
|
|
101
|
+
return { ticks, offset }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* @param {number} rangeSize
|
|
106
|
+
* @param {string} axis
|
|
107
|
+
* @param {number} fontSize
|
|
108
|
+
* @returns {number}
|
|
109
|
+
*/
|
|
110
|
+
function defaultTickCount(rangeSize, axis, fontSize) {
|
|
111
|
+
const divisor = fontSize * (axis === 'y' ? 3 : 6)
|
|
112
|
+
return Math.abs(rangeSize / divisor)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @param {Array} ticks
|
|
117
|
+
* @param {Object} scale
|
|
118
|
+
* @param {number} offset
|
|
119
|
+
* @param {boolean} isXAxis
|
|
120
|
+
* @returns {Array}
|
|
121
|
+
*/
|
|
122
|
+
function formatTicks(ticks, scale, offset, isXAxis) {
|
|
123
|
+
const pos = isXAxis ? offset : 0
|
|
124
|
+
return ticks.map((t) => ({ value: t, position: scale(t) + pos }))
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Creates axis ticks
|
|
129
|
+
*
|
|
130
|
+
* @param {Object} scale D3 scale
|
|
131
|
+
* @param {string} axis Axis type ('x' or 'y')
|
|
132
|
+
* @param {number} count Number of ticks
|
|
133
|
+
* @param {number} fontSize Font size for determining tick density
|
|
134
|
+
* @returns {Array} Array of tick objects
|
|
135
|
+
*/
|
|
136
|
+
export function createTicks(scale, axis, count = null, fontSize = 12) {
|
|
137
|
+
const [minRange, maxRange] = scale.range()
|
|
138
|
+
const tickCount = count ?? defaultTickCount(maxRange - minRange, axis, fontSize)
|
|
139
|
+
|
|
140
|
+
let ticks, offset
|
|
141
|
+
if (scale.ticks) {
|
|
142
|
+
ticks = scale.ticks(Math.round(tickCount))
|
|
143
|
+
offset = 0
|
|
144
|
+
} else {
|
|
145
|
+
const band = bandTicks(scale, tickCount)
|
|
146
|
+
ticks = band.ticks
|
|
147
|
+
offset = band.offset
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return formatTicks(ticks, scale, offset, axis === 'x')
|
|
151
|
+
}
|
package/src/lib/swatch.js
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
|
-
import { writable } from 'svelte/store'
|
|
2
1
|
import palette from './palette.json'
|
|
3
|
-
|
|
4
|
-
import * as patterns from '../patterns'
|
|
2
|
+
import { PATTERN_ORDER } from './brewing/patterns.js'
|
|
5
3
|
import { shapes } from '../symbols'
|
|
6
4
|
|
|
7
|
-
export const swatch =
|
|
5
|
+
export const swatch = {
|
|
8
6
|
palette,
|
|
9
|
-
patterns,
|
|
10
7
|
keys: {
|
|
11
8
|
gray: ['gray'],
|
|
12
9
|
color: Object.keys(palette).filter((name) => name !== 'gray'),
|
|
13
10
|
symbol: shapes,
|
|
14
|
-
pattern:
|
|
11
|
+
pattern: PATTERN_ORDER
|
|
15
12
|
}
|
|
16
|
-
}
|
|
13
|
+
}
|
package/src/lib/utils.js
CHANGED
|
@@ -1,3 +1,145 @@
|
|
|
1
|
+
import { scaleBand, scaleLinear, scaleOrdinal } from 'd3-scale'
|
|
2
|
+
import { schemeCategory10 } from 'd3-scale-chromatic'
|
|
3
|
+
import { max } from 'd3-array'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Creates appropriate scales based on data and dimensions
|
|
7
|
+
*
|
|
8
|
+
* @param {Array} data The dataset
|
|
9
|
+
* @param {Object} dimensions Chart dimensions
|
|
10
|
+
* @param {Object} [options] Additional options
|
|
11
|
+
* @param {string} options.xKey Field to use for x-axis
|
|
12
|
+
* @param {string} options.yKey Field to use for y-axis
|
|
13
|
+
* @param {string} [options.colorKey] Field to use for color mapping
|
|
14
|
+
* @returns {Object} Object containing xScale, yScale, and colorScale
|
|
15
|
+
*/
|
|
16
|
+
export function createScales(data, dimensions, options = {}) {
|
|
17
|
+
if (!data || data.length === 0) return {}
|
|
18
|
+
|
|
19
|
+
const { xKey, yKey, colorKey } = options
|
|
20
|
+
|
|
21
|
+
const xScale = scaleBand()
|
|
22
|
+
.domain(data.map((d) => d[xKey]))
|
|
23
|
+
.range([0, dimensions.width])
|
|
24
|
+
.padding(0.2)
|
|
25
|
+
|
|
26
|
+
const yScale = scaleLinear()
|
|
27
|
+
.domain([0, max(data, (d) => d[yKey]) * 1.1]) // Add 10% padding on top
|
|
28
|
+
.nice()
|
|
29
|
+
.range([dimensions.height, 0])
|
|
30
|
+
|
|
31
|
+
let colorScale = null
|
|
32
|
+
|
|
33
|
+
if (colorKey) {
|
|
34
|
+
const uniqueCategories = [...new Set(data.map((d) => d[colorKey]))]
|
|
35
|
+
colorScale = scaleOrdinal().domain(uniqueCategories).range(schemeCategory10)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return { xScale, yScale, colorScale }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Calculates the actual chart dimensions after applying margins
|
|
43
|
+
*
|
|
44
|
+
* @param {Object} dimensions Original dimensions
|
|
45
|
+
* @returns {Object} Dimensions with calculated inner width and height
|
|
46
|
+
*/
|
|
47
|
+
export function calculateChartDimensions(dimensions) {
|
|
48
|
+
const { width, height, margin } = dimensions
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
...dimensions,
|
|
52
|
+
innerWidth: width - margin.left - margin.right,
|
|
53
|
+
innerHeight: height - margin.top - margin.bottom
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Normalizes data for use with D3 charts
|
|
59
|
+
*
|
|
60
|
+
* @param {Array|Object} inputData Raw data or dataset object
|
|
61
|
+
* @returns {Array} Normalized data array
|
|
62
|
+
*/
|
|
63
|
+
export function normalizeData(inputData) {
|
|
64
|
+
if (!inputData) return []
|
|
65
|
+
|
|
66
|
+
// If it's a dataset class instance, call select() to get the data
|
|
67
|
+
if (inputData.select && typeof inputData.select === 'function') {
|
|
68
|
+
return inputData.select()
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// If it's already an array, return as is
|
|
72
|
+
if (Array.isArray(inputData)) {
|
|
73
|
+
return inputData
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return []
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Generates a unique ID for SVG elements
|
|
81
|
+
*
|
|
82
|
+
* @param {string} prefix Prefix for the ID
|
|
83
|
+
* @returns {string} A unique ID
|
|
84
|
+
*/
|
|
85
|
+
export function uniqueId(prefix = 'chart') {
|
|
86
|
+
return `${prefix}-${Math.random().toString(36).substring(2, 10)}`
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Format a single key-value pair for tooltip
|
|
91
|
+
* @param {string} key
|
|
92
|
+
* @param {unknown} value
|
|
93
|
+
* @param {Function|undefined} formatter
|
|
94
|
+
* @returns {string}
|
|
95
|
+
*/
|
|
96
|
+
function formatField(key, value, formatter) {
|
|
97
|
+
const formatted = formatter ? formatter(value) : value
|
|
98
|
+
return `${key}: ${formatted}`
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Formats tooltip content for a data point
|
|
103
|
+
*
|
|
104
|
+
* @param {Object} d Data point
|
|
105
|
+
* @param {Object} options Tooltip format options
|
|
106
|
+
* @returns {string} Formatted tooltip HTML content
|
|
107
|
+
*/
|
|
108
|
+
export function formatTooltipContent(d, options = {}) {
|
|
109
|
+
if (!d) return ''
|
|
110
|
+
|
|
111
|
+
const { xKey, yKey, xFormat, yFormat } = options
|
|
112
|
+
|
|
113
|
+
if (xKey && yKey) {
|
|
114
|
+
return [formatField(xKey, d[xKey], xFormat), formatField(yKey, d[yKey], yFormat)].join('<br>')
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return Object.entries(d)
|
|
118
|
+
.map(([key, value]) => `${key}: ${value}`)
|
|
119
|
+
.join('<br>')
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Generates a tooltip formatter function
|
|
124
|
+
*
|
|
125
|
+
* @param {Object} options Tooltip format options
|
|
126
|
+
* @returns {Function} A function that formats tooltip content
|
|
127
|
+
*/
|
|
128
|
+
export function createTooltipFormatter(options = {}) {
|
|
129
|
+
return (d) => formatTooltipContent(d, options)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Calculates the transform attribute for SVG elements
|
|
134
|
+
*
|
|
135
|
+
* @param {number} x X position
|
|
136
|
+
* @param {number} y Y position
|
|
137
|
+
* @returns {string} Transform attribute value
|
|
138
|
+
*/
|
|
139
|
+
export function transform(x, y) {
|
|
140
|
+
return `translate(${x}, ${y})`
|
|
141
|
+
}
|
|
142
|
+
|
|
1
143
|
import { scaledPath } from '@rokkit/core'
|
|
2
144
|
|
|
3
145
|
export function scaledPathCollection(paths) {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { scaleBand, scaleLinear, scaleTime } from 'd3-scale'
|
|
2
|
+
import { min, max } from 'd3-array'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Builds an appropriate D3 x-axis scale based on value types.
|
|
6
|
+
* Returns scaleTime for Date values, scaleLinear for numeric values,
|
|
7
|
+
* or scaleBand for categorical values.
|
|
8
|
+
*
|
|
9
|
+
* @param {Array} xValues
|
|
10
|
+
* @param {Object} dimensions
|
|
11
|
+
* @param {number} padding
|
|
12
|
+
* @returns {import('d3-scale').ScaleContinuousNumeric|import('d3-scale').ScaleBand}
|
|
13
|
+
*/
|
|
14
|
+
export function buildXScale(xValues, dimensions, padding) {
|
|
15
|
+
const xIsDate = xValues.some((v) => v instanceof Date)
|
|
16
|
+
const xIsNumeric = !xIsDate && xValues.every((v) => !isNaN(parseFloat(v)))
|
|
17
|
+
|
|
18
|
+
if (xIsDate) {
|
|
19
|
+
return scaleTime()
|
|
20
|
+
.domain([min(xValues), max(xValues)])
|
|
21
|
+
.range([0, dimensions.innerWidth])
|
|
22
|
+
.nice()
|
|
23
|
+
}
|
|
24
|
+
if (xIsNumeric) {
|
|
25
|
+
return scaleLinear()
|
|
26
|
+
.domain([min([0, ...xValues]), max(xValues)])
|
|
27
|
+
.range([0, dimensions.innerWidth])
|
|
28
|
+
.nice()
|
|
29
|
+
}
|
|
30
|
+
return scaleBand().domain(xValues).range([0, dimensions.innerWidth]).padding(padding)
|
|
31
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { getContext } from 'svelte'
|
|
3
|
+
import { toPatternId } from '../lib/brewing/patterns.js'
|
|
4
|
+
import { PATTERNS } from './patterns.js'
|
|
5
|
+
import PatternDef from './PatternDef.svelte'
|
|
6
|
+
|
|
7
|
+
/** @type {{ patterns?: Record<string, import('./patterns.js').PatternMark[]> }} */
|
|
8
|
+
let { patterns = PATTERNS } = $props()
|
|
9
|
+
|
|
10
|
+
const state = getContext('plot-state')
|
|
11
|
+
|
|
12
|
+
const patternDefs = $derived.by(() => {
|
|
13
|
+
const defs = []
|
|
14
|
+
for (const [key, patternName] of (state.patterns ?? new Map()).entries()) {
|
|
15
|
+
const colorEntry = state.colors?.get(key) ?? { stroke: '#444' }
|
|
16
|
+
defs.push({
|
|
17
|
+
id: toPatternId(String(key)),
|
|
18
|
+
marks: patterns[patternName] ?? [],
|
|
19
|
+
stroke: colorEntry.stroke ?? '#444'
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
return defs
|
|
23
|
+
})
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
{#if patternDefs.length > 0}
|
|
27
|
+
<defs data-plot-pattern-defs>
|
|
28
|
+
{#each patternDefs as def (def.id)}
|
|
29
|
+
<PatternDef id={def.id} marks={def.marks} stroke={def.stroke} />
|
|
30
|
+
{/each}
|
|
31
|
+
</defs>
|
|
32
|
+
{/if}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { scaleMark, resolveMarkAttrs } from './scale.js'
|
|
3
|
+
|
|
4
|
+
/** @type {{ id: string, marks?: import('./patterns.js').PatternMark[], size?: number, stroke?: string, thickness?: number }} */
|
|
5
|
+
let { id, marks = [], size = 10, stroke = '#444', thickness = 0.5 } = $props()
|
|
6
|
+
|
|
7
|
+
const resolvedMarks = $derived(
|
|
8
|
+
marks.map((m) => resolveMarkAttrs(scaleMark(m, size), { fill: stroke, stroke, thickness }))
|
|
9
|
+
)
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<pattern {id} patternUnits="userSpaceOnUse" width={size} height={size}>
|
|
13
|
+
<rect width={size} height={size} fill="none" />
|
|
14
|
+
{#each resolvedMarks as { type, attrs }, i (i)}
|
|
15
|
+
{#if type === 'line'}
|
|
16
|
+
<line {...attrs} />
|
|
17
|
+
{:else if type === 'circle'}
|
|
18
|
+
<circle {...attrs} />
|
|
19
|
+
{:else if type === 'rect'}
|
|
20
|
+
<rect {...attrs} />
|
|
21
|
+
{:else if type === 'polygon'}
|
|
22
|
+
<polygon {...attrs} />
|
|
23
|
+
{:else if type === 'path'}
|
|
24
|
+
<path {...attrs} />
|
|
25
|
+
{/if}
|
|
26
|
+
{/each}
|
|
27
|
+
</pattern>
|
package/src/patterns/index.js
CHANGED
|
@@ -1,14 +1,4 @@
|
|
|
1
|
-
export { default as
|
|
2
|
-
export { default as
|
|
3
|
-
export {
|
|
4
|
-
export {
|
|
5
|
-
export { default as Waves } from './Waves.svelte'
|
|
6
|
-
// export { default as Squares } from './Squares.svelte'
|
|
7
|
-
export { default as Tile } from './Tile.svelte'
|
|
8
|
-
export { default as Triangles } from './Triangles.svelte'
|
|
9
|
-
// export { default as Waves } from './Waves.svelte'
|
|
10
|
-
// export { default as ZigZag } from './ZigZag.svelte'
|
|
11
|
-
// export { default as Flower } from './Flower.svelte'
|
|
12
|
-
// export { default as NamedPath } from './NamedPath.svelte'
|
|
13
|
-
export { default as CurvedWave } from './CurvedWave.svelte'
|
|
14
|
-
export { default as OutlineCircles } from './OutlineCircles.svelte'
|
|
1
|
+
export { default as PatternDef } from './PatternDef.svelte'
|
|
2
|
+
export { default as DefinePatterns } from './DefinePatterns.svelte'
|
|
3
|
+
export { PATTERNS } from './patterns.js'
|
|
4
|
+
export { scaleMark, resolveMarkAttrs } from './scale.js'
|