@rokkit/chart 1.0.0-next.16 → 1.0.0-next.161
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 +42 -45
- package/src/AnimatedPlot.svelte +383 -0
- package/src/Chart.svelte +95 -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 +95 -0
- package/src/Plot/Bar.svelte +54 -0
- package/src/Plot/Grid.svelte +34 -0
- package/src/Plot/Legend.svelte +233 -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 +297 -0
- package/src/PlotState.svelte.js +350 -0
- package/src/Sparkline.svelte +108 -0
- package/src/Symbol.svelte +21 -0
- package/src/Texture.svelte +18 -0
- package/src/charts/AreaChart.svelte +27 -0
- package/src/charts/BarChart.svelte +28 -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 +35 -0
- package/src/charts/ScatterPlot.svelte +26 -0
- package/src/charts/ViolinPlot.svelte +21 -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 +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 +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/LabelPill.svelte +17 -0
- package/src/geoms/Line.svelte +123 -0
- package/src/geoms/Point.svelte +145 -0
- package/src/geoms/Violin.svelte +56 -0
- package/src/geoms/lib/areas.js +154 -0
- package/src/geoms/lib/bars.js +223 -0
- package/src/index.js +74 -16
- package/src/lib/brewer.js +25 -0
- 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 +51 -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 +221 -0
- package/src/lib/context.js +131 -0
- package/src/lib/grid.js +85 -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 +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/index.js +4 -0
- 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 +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/LICENSE +0 -21
- 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,87 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { getContext } from 'svelte'
|
|
3
|
+
|
|
4
|
+
/** @type {{ tooltip?: boolean | ((data: Record<string, unknown>) => string) }} */
|
|
5
|
+
let { tooltip = true } = $props()
|
|
6
|
+
|
|
7
|
+
const plotState = getContext('plot-state')
|
|
8
|
+
|
|
9
|
+
let mouseX = $state(0)
|
|
10
|
+
let mouseY = $state(0)
|
|
11
|
+
|
|
12
|
+
$effect(() => {
|
|
13
|
+
function onMove(e) {
|
|
14
|
+
mouseX = e.clientX
|
|
15
|
+
mouseY = e.clientY
|
|
16
|
+
}
|
|
17
|
+
window.addEventListener('mousemove', onMove)
|
|
18
|
+
return () => window.removeEventListener('mousemove', onMove)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const hovered = $derived(plotState.hovered)
|
|
22
|
+
const mode = $derived(plotState.mode)
|
|
23
|
+
|
|
24
|
+
function formatDefault(data) {
|
|
25
|
+
return Object.entries(data)
|
|
26
|
+
.filter(([, v]) => v !== undefined && v !== null)
|
|
27
|
+
.map(
|
|
28
|
+
([k, v]) =>
|
|
29
|
+
`<div class="tt-row"><span class="tt-key">${k}</span><span class="tt-val">${v}</span></div>`
|
|
30
|
+
)
|
|
31
|
+
.join('')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const content = $derived.by(() => {
|
|
35
|
+
if (!hovered) return ''
|
|
36
|
+
if (typeof tooltip === 'function') return String(tooltip(hovered) ?? '')
|
|
37
|
+
return formatDefault(hovered)
|
|
38
|
+
})
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
{#if hovered && content}
|
|
42
|
+
<div
|
|
43
|
+
class="plot-tooltip"
|
|
44
|
+
data-mode={mode}
|
|
45
|
+
style:left="{mouseX + 14}px"
|
|
46
|
+
style:top="{mouseY - 8}px"
|
|
47
|
+
>
|
|
48
|
+
{@html content}
|
|
49
|
+
</div>
|
|
50
|
+
{/if}
|
|
51
|
+
|
|
52
|
+
<style>
|
|
53
|
+
.plot-tooltip {
|
|
54
|
+
position: fixed;
|
|
55
|
+
pointer-events: none;
|
|
56
|
+
z-index: 1000;
|
|
57
|
+
padding: 8px 10px;
|
|
58
|
+
border-radius: 6px;
|
|
59
|
+
font-size: 12px;
|
|
60
|
+
line-height: 1.5;
|
|
61
|
+
white-space: nowrap;
|
|
62
|
+
box-shadow: 0 4px 12px rgb(0 0 0 / 0.12);
|
|
63
|
+
background: #ffffff;
|
|
64
|
+
border: 1px solid #e2e8f0;
|
|
65
|
+
color: #1e293b;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.plot-tooltip[data-mode='dark'] {
|
|
69
|
+
background: #1e293b;
|
|
70
|
+
border-color: #334155;
|
|
71
|
+
color: #f1f5f9;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
:global(.tt-row) {
|
|
75
|
+
display: flex;
|
|
76
|
+
gap: 10px;
|
|
77
|
+
justify-content: space-between;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
:global(.tt-key) {
|
|
81
|
+
opacity: 0.65;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
:global(.tt-val) {
|
|
85
|
+
font-weight: 600;
|
|
86
|
+
}
|
|
87
|
+
</style>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { default as Root } from './Root.svelte'
|
|
2
|
+
export { default as Axis } from './Axis.svelte'
|
|
3
|
+
export { default as Bar } from './Bar.svelte'
|
|
4
|
+
export { default as Legend } from './Legend.svelte'
|
|
5
|
+
export { default as Grid } from './Grid.svelte'
|
|
6
|
+
export { default as Line } from './Line.svelte'
|
|
7
|
+
export { default as Area } from './Area.svelte'
|
|
8
|
+
export { default as Point } from './Point.svelte'
|
|
9
|
+
export { default as Arc } from './Arc.svelte'
|
package/src/Plot.svelte
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { setContext, getContext, untrack } from 'svelte'
|
|
3
|
+
import { zoom as d3Zoom } from 'd3-zoom'
|
|
4
|
+
import { select } from 'd3-selection'
|
|
5
|
+
import { PlotState } from './PlotState.svelte.js'
|
|
6
|
+
import { defaultPreset } from './lib/preset.js'
|
|
7
|
+
import Axis from './Plot/Axis.svelte'
|
|
8
|
+
import Grid from './Plot/Grid.svelte'
|
|
9
|
+
import Legend from './Plot/Legend.svelte'
|
|
10
|
+
import Tooltip from './Plot/Tooltip.svelte'
|
|
11
|
+
import DefinePatterns from './patterns/DefinePatterns.svelte'
|
|
12
|
+
import Bar from './geoms/Bar.svelte'
|
|
13
|
+
import Line from './geoms/Line.svelte'
|
|
14
|
+
import Area from './geoms/Area.svelte'
|
|
15
|
+
import Point from './geoms/Point.svelte'
|
|
16
|
+
import Arc from './geoms/Arc.svelte'
|
|
17
|
+
import Box from './geoms/Box.svelte'
|
|
18
|
+
import Violin from './geoms/Violin.svelte'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @type {{
|
|
22
|
+
* data?: Object[],
|
|
23
|
+
* spec?: import('./lib/plot/types.js').PlotSpec,
|
|
24
|
+
* helpers?: import('./lib/plot/types.js').PlotHelpers,
|
|
25
|
+
* width?: number,
|
|
26
|
+
* height?: number,
|
|
27
|
+
* mode?: 'light' | 'dark',
|
|
28
|
+
* grid?: boolean,
|
|
29
|
+
* axes?: boolean,
|
|
30
|
+
* margin?: { top: number, right: number, bottom: number, left: number },
|
|
31
|
+
* legend?: boolean,
|
|
32
|
+
* title?: string,
|
|
33
|
+
* summary?: string,
|
|
34
|
+
* tooltip?: boolean | ((data: Record<string, unknown>) => string),
|
|
35
|
+
* zoom?: boolean,
|
|
36
|
+
* children?: import('svelte').Snippet,
|
|
37
|
+
* }}
|
|
38
|
+
*/
|
|
39
|
+
let {
|
|
40
|
+
data = [],
|
|
41
|
+
spec = undefined,
|
|
42
|
+
helpers = {},
|
|
43
|
+
width = 600,
|
|
44
|
+
height = 400,
|
|
45
|
+
mode = 'light',
|
|
46
|
+
grid = true,
|
|
47
|
+
axes = true,
|
|
48
|
+
margin = undefined,
|
|
49
|
+
legend = false,
|
|
50
|
+
title = '',
|
|
51
|
+
summary = '',
|
|
52
|
+
tooltip = false,
|
|
53
|
+
zoom = false,
|
|
54
|
+
xFormat = undefined,
|
|
55
|
+
yFormat = undefined,
|
|
56
|
+
children
|
|
57
|
+
} = $props()
|
|
58
|
+
|
|
59
|
+
const chartPresetCtx = getContext('chart-preset')
|
|
60
|
+
const chartPreset = $derived(chartPresetCtx?.current ?? defaultPreset)
|
|
61
|
+
|
|
62
|
+
// Responsive width: observe container and use actual pixel width for scale calculations
|
|
63
|
+
let svgEl = $state(null)
|
|
64
|
+
let containerEl = $state(null)
|
|
65
|
+
let observedWidth = $state(0)
|
|
66
|
+
|
|
67
|
+
$effect(() => {
|
|
68
|
+
if (!containerEl) return
|
|
69
|
+
const ro = new ResizeObserver((entries) => {
|
|
70
|
+
const w = Math.floor(entries[0].contentRect.width)
|
|
71
|
+
if (w > 0) observedWidth = w
|
|
72
|
+
})
|
|
73
|
+
ro.observe(containerEl)
|
|
74
|
+
return () => ro.disconnect()
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
const effectiveWidth = $derived(observedWidth > 0 ? observedWidth : (spec?.width ?? width))
|
|
78
|
+
const svgHeight = $derived(spec?.height ?? height)
|
|
79
|
+
const showGrid = $derived(spec?.grid ?? grid)
|
|
80
|
+
const showLegend = $derived(spec?.legend ?? legend)
|
|
81
|
+
const chartTitle = $derived(spec?.title ?? title)
|
|
82
|
+
const chartSummary = $derived(spec?.summary ?? summary)
|
|
83
|
+
|
|
84
|
+
// Accessible data table — screen reader fallback
|
|
85
|
+
const tableData = $derived(spec?.data ?? data)
|
|
86
|
+
const tableColumns = $derived.by(() => {
|
|
87
|
+
const cols = [spec?.x, spec?.y, spec?.color ?? spec?.fill].filter(Boolean)
|
|
88
|
+
if (cols.length > 0) return cols
|
|
89
|
+
const first = tableData[0]
|
|
90
|
+
return first ? Object.keys(first) : []
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
function buildPlotConfig() {
|
|
94
|
+
return {
|
|
95
|
+
data: spec?.data ?? data,
|
|
96
|
+
width: effectiveWidth,
|
|
97
|
+
height: spec?.height ?? height,
|
|
98
|
+
mode,
|
|
99
|
+
margin,
|
|
100
|
+
channels: spec ? { x: spec.x, y: spec.y, color: spec.color ?? spec.fill } : {},
|
|
101
|
+
labels: spec?.labels ?? {},
|
|
102
|
+
helpers,
|
|
103
|
+
xDomain: spec?.xDomain,
|
|
104
|
+
yDomain: spec?.yDomain,
|
|
105
|
+
colorDomain: spec?.colorDomain,
|
|
106
|
+
orientation: spec?.orientation,
|
|
107
|
+
chartPreset
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Create PlotState with initial values and provide as context.
|
|
112
|
+
// untrack() suppresses "captures initial value" warnings — intentional:
|
|
113
|
+
// the $effect below handles all subsequent reactive updates.
|
|
114
|
+
const plotState = untrack(() => new PlotState(buildPlotConfig()))
|
|
115
|
+
setContext('plot-state', plotState)
|
|
116
|
+
|
|
117
|
+
// Keep state in sync when reactive config changes
|
|
118
|
+
$effect(() => {
|
|
119
|
+
plotState.update(buildPlotConfig())
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
$effect(() => {
|
|
123
|
+
if (!zoom || !svgEl) return
|
|
124
|
+
const zoomBehavior = d3Zoom()
|
|
125
|
+
.scaleExtent([1, 8])
|
|
126
|
+
.on('zoom', (event) => {
|
|
127
|
+
plotState.applyZoom(event.transform)
|
|
128
|
+
})
|
|
129
|
+
const sel = select(svgEl)
|
|
130
|
+
sel.call(zoomBehavior)
|
|
131
|
+
return () => {
|
|
132
|
+
sel.on('.zoom', null)
|
|
133
|
+
plotState.resetZoom()
|
|
134
|
+
}
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
// Geoms from spec (spec-driven API)
|
|
138
|
+
const specGeoms = $derived(spec?.geoms ?? [])
|
|
139
|
+
|
|
140
|
+
// Geom component resolver for spec-driven mode
|
|
141
|
+
const GEOM_COMPONENTS = {
|
|
142
|
+
bar: Bar,
|
|
143
|
+
line: Line,
|
|
144
|
+
area: Area,
|
|
145
|
+
point: Point,
|
|
146
|
+
arc: Arc,
|
|
147
|
+
box: Box,
|
|
148
|
+
violin: Violin
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* @param {string} type
|
|
153
|
+
*/
|
|
154
|
+
function resolveGeomComponent(type) {
|
|
155
|
+
return helpers?.geoms?.[type] ?? GEOM_COMPONENTS[type]
|
|
156
|
+
}
|
|
157
|
+
</script>
|
|
158
|
+
|
|
159
|
+
<div class="plot-root" data-plot-root data-mode={mode} bind:this={containerEl}>
|
|
160
|
+
{#if chartTitle}
|
|
161
|
+
<div class="plot-title" data-plot-title>{chartTitle}</div>
|
|
162
|
+
{/if}
|
|
163
|
+
|
|
164
|
+
<svg
|
|
165
|
+
bind:this={svgEl}
|
|
166
|
+
width={effectiveWidth}
|
|
167
|
+
height={svgHeight}
|
|
168
|
+
viewBox="0 0 {effectiveWidth} {svgHeight}"
|
|
169
|
+
role="img"
|
|
170
|
+
aria-label={chartTitle || 'Chart visualization'}
|
|
171
|
+
style:cursor={zoom ? 'grab' : undefined}
|
|
172
|
+
>
|
|
173
|
+
{#if chartSummary}
|
|
174
|
+
<desc>{chartSummary}</desc>
|
|
175
|
+
{/if}
|
|
176
|
+
<!-- SVG pattern defs -->
|
|
177
|
+
<DefinePatterns />
|
|
178
|
+
|
|
179
|
+
<g
|
|
180
|
+
class="plot-canvas"
|
|
181
|
+
transform="translate({plotState.margin.left}, {plotState.margin.top})"
|
|
182
|
+
data-plot-canvas
|
|
183
|
+
>
|
|
184
|
+
<!-- Grid (behind everything) -->
|
|
185
|
+
{#if showGrid}
|
|
186
|
+
<Grid />
|
|
187
|
+
{/if}
|
|
188
|
+
|
|
189
|
+
<!-- Declarative children (geom components) -->
|
|
190
|
+
{@render children?.()}
|
|
191
|
+
|
|
192
|
+
<!-- Spec-driven geoms -->
|
|
193
|
+
{#each specGeoms as geomSpec (geomSpec.type)}
|
|
194
|
+
{@const GeomComponent = resolveGeomComponent(geomSpec.type)}
|
|
195
|
+
{#if GeomComponent}
|
|
196
|
+
<GeomComponent
|
|
197
|
+
x={geomSpec.x ?? spec?.x}
|
|
198
|
+
y={geomSpec.y ?? spec?.y}
|
|
199
|
+
color={geomSpec.color ?? spec?.color}
|
|
200
|
+
fill={geomSpec.fill ?? spec?.fill}
|
|
201
|
+
pattern={geomSpec.pattern}
|
|
202
|
+
symbol={geomSpec.symbol}
|
|
203
|
+
stat={geomSpec.stat}
|
|
204
|
+
label={geomSpec.label}
|
|
205
|
+
options={geomSpec.options}
|
|
206
|
+
/>
|
|
207
|
+
{/if}
|
|
208
|
+
{/each}
|
|
209
|
+
|
|
210
|
+
<!-- Axes -->
|
|
211
|
+
{#if axes}
|
|
212
|
+
<Axis type="x" label={spec?.labels?.[spec?.x ?? ''] ?? ''} format={xFormat} />
|
|
213
|
+
<Axis type="y" label={spec?.labels?.[spec?.y ?? ''] ?? ''} format={yFormat} />
|
|
214
|
+
{/if}
|
|
215
|
+
</g>
|
|
216
|
+
</svg>
|
|
217
|
+
|
|
218
|
+
<!-- Legend (HTML, outside SVG) -->
|
|
219
|
+
{#if showLegend}
|
|
220
|
+
<Legend labels={spec?.labels ?? {}} />
|
|
221
|
+
{/if}
|
|
222
|
+
|
|
223
|
+
<!-- Tooltip (HTML, fixed-position overlay) -->
|
|
224
|
+
{#if tooltip}
|
|
225
|
+
<Tooltip {tooltip} />
|
|
226
|
+
{/if}
|
|
227
|
+
|
|
228
|
+
<!-- Accessible data table — visually hidden, announces data to screen readers -->
|
|
229
|
+
{#if tableData.length > 0 && tableColumns.length > 0}
|
|
230
|
+
<table class="plot-sr-table" aria-label={chartTitle || 'Chart data'}>
|
|
231
|
+
{#if chartTitle}
|
|
232
|
+
<caption>{chartTitle}</caption>
|
|
233
|
+
{/if}
|
|
234
|
+
<thead>
|
|
235
|
+
<tr>
|
|
236
|
+
{#each tableColumns as col (col)}
|
|
237
|
+
<th scope="col">{col}</th>
|
|
238
|
+
{/each}
|
|
239
|
+
</tr>
|
|
240
|
+
</thead>
|
|
241
|
+
<tbody>
|
|
242
|
+
{#each tableData as row, i (i)}
|
|
243
|
+
<tr>
|
|
244
|
+
{#each tableColumns as col (col)}
|
|
245
|
+
<td>{row[col] ?? ''}</td>
|
|
246
|
+
{/each}
|
|
247
|
+
</tr>
|
|
248
|
+
{/each}
|
|
249
|
+
</tbody>
|
|
250
|
+
</table>
|
|
251
|
+
{/if}
|
|
252
|
+
</div>
|
|
253
|
+
|
|
254
|
+
<style>
|
|
255
|
+
.plot-root {
|
|
256
|
+
position: relative;
|
|
257
|
+
width: 100%;
|
|
258
|
+
height: auto;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
[data-plot-root][data-mode='light'] {
|
|
262
|
+
color: rgba(var(--color-surface-700, 59, 65, 77), 1);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
[data-plot-root][data-mode='dark'] {
|
|
266
|
+
color: rgba(var(--color-surface-200, 224, 224, 224), 1);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
svg {
|
|
270
|
+
display: block;
|
|
271
|
+
overflow: visible;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.plot-canvas {
|
|
275
|
+
pointer-events: all;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.plot-title {
|
|
279
|
+
font-size: 14px;
|
|
280
|
+
font-weight: 600;
|
|
281
|
+
text-align: center;
|
|
282
|
+
margin-bottom: 4px;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/* Visually hidden — in DOM for screen readers, not visible */
|
|
286
|
+
.plot-sr-table {
|
|
287
|
+
position: absolute;
|
|
288
|
+
width: 1px;
|
|
289
|
+
height: 1px;
|
|
290
|
+
padding: 0;
|
|
291
|
+
margin: -1px;
|
|
292
|
+
overflow: hidden;
|
|
293
|
+
clip: rect(0, 0, 0, 0);
|
|
294
|
+
white-space: nowrap;
|
|
295
|
+
border: 0;
|
|
296
|
+
}
|
|
297
|
+
</style>
|