@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,79 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { getContext } from 'svelte'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Dual range slider for a continuous crossfilter dimension.
|
|
6
|
+
* NOTE: Interim implementation using HTML range inputs.
|
|
7
|
+
* The spec calls for a Plot+Point+brush architecture, deferred until brush geom is implemented.
|
|
8
|
+
*/
|
|
9
|
+
let { field, min, max, step = 0.1, label = '' } = $props()
|
|
10
|
+
|
|
11
|
+
const cf = getContext('crossfilter')
|
|
12
|
+
|
|
13
|
+
// Initialize from props; $effect keeps in sync when min/max change
|
|
14
|
+
let low = $state(0)
|
|
15
|
+
let high = $state(100)
|
|
16
|
+
|
|
17
|
+
$effect(() => {
|
|
18
|
+
low = min ?? 0
|
|
19
|
+
high = max ?? 100
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
function handleLow(e) {
|
|
23
|
+
low = Math.min(Number(e.currentTarget.value), high)
|
|
24
|
+
cf?.setRange(field, [low, high])
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function handleHigh(e) {
|
|
28
|
+
high = Math.max(Number(e.currentTarget.value), low)
|
|
29
|
+
cf?.setRange(field, [low, high])
|
|
30
|
+
}
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<div data-filter-slider data-filter-field={field}>
|
|
34
|
+
{#if label}
|
|
35
|
+
<span data-filter-slider-label>{label}</span>
|
|
36
|
+
{/if}
|
|
37
|
+
<div data-filter-slider-inputs>
|
|
38
|
+
<input
|
|
39
|
+
type="range"
|
|
40
|
+
{min} {max} {step}
|
|
41
|
+
value={low}
|
|
42
|
+
oninput={handleLow}
|
|
43
|
+
aria-label="Minimum {label || field}"
|
|
44
|
+
data-filter-slider-low
|
|
45
|
+
/>
|
|
46
|
+
<input
|
|
47
|
+
type="range"
|
|
48
|
+
{min} {max} {step}
|
|
49
|
+
value={high}
|
|
50
|
+
oninput={handleHigh}
|
|
51
|
+
aria-label="Maximum {label || field}"
|
|
52
|
+
data-filter-slider-high
|
|
53
|
+
/>
|
|
54
|
+
</div>
|
|
55
|
+
<div data-filter-slider-display>
|
|
56
|
+
{low} – {high}
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<style>
|
|
61
|
+
[data-filter-slider] {
|
|
62
|
+
display: flex;
|
|
63
|
+
flex-direction: column;
|
|
64
|
+
gap: 4px;
|
|
65
|
+
font-size: 12px;
|
|
66
|
+
}
|
|
67
|
+
[data-filter-slider-inputs] {
|
|
68
|
+
display: flex;
|
|
69
|
+
flex-direction: column;
|
|
70
|
+
gap: 2px;
|
|
71
|
+
}
|
|
72
|
+
[data-filter-slider-label] {
|
|
73
|
+
font-weight: 600;
|
|
74
|
+
}
|
|
75
|
+
[data-filter-slider-display] {
|
|
76
|
+
color: currentColor;
|
|
77
|
+
opacity: 0.7;
|
|
78
|
+
}
|
|
79
|
+
</style>
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a reactive cross-filter state object.
|
|
3
|
+
*
|
|
4
|
+
* Filter values follow the spec type:
|
|
5
|
+
* FilterState = Map<string, Set<unknown> | [number, number]>
|
|
6
|
+
* - categorical: raw Set of selected values
|
|
7
|
+
* - continuous: [min, max] tuple
|
|
8
|
+
*
|
|
9
|
+
* Exposes a `filters` getter so CrossFilter.svelte can bind to current state.
|
|
10
|
+
* Exposes a `version` counter that increments on every mutation, giving
|
|
11
|
+
* components a simple reactive signal to watch for filter changes.
|
|
12
|
+
*
|
|
13
|
+
* @returns {CrossFilter}
|
|
14
|
+
*/
|
|
15
|
+
export function createCrossFilter() {
|
|
16
|
+
// Map<dimension, Set<unknown> | [number, number]>
|
|
17
|
+
const filters = $state(new Map())
|
|
18
|
+
|
|
19
|
+
// Simple counter incremented on every mutation. Components read cf.version
|
|
20
|
+
// inside $effect to reactively recompute when any filter changes.
|
|
21
|
+
let version = $state(0)
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Returns true if any filter is active on this dimension.
|
|
25
|
+
* @param {string} dimension
|
|
26
|
+
*/
|
|
27
|
+
function isFiltered(dimension) {
|
|
28
|
+
const f = filters.get(dimension)
|
|
29
|
+
if (!f) return false
|
|
30
|
+
if (f instanceof Set) return f.size > 0
|
|
31
|
+
return true // range: always active if present
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Returns true if a value on this dimension is NOT in the active filter.
|
|
36
|
+
* Returns false when no filter is active on this dimension.
|
|
37
|
+
*
|
|
38
|
+
* @param {string} dimension
|
|
39
|
+
* @param {unknown} value
|
|
40
|
+
*/
|
|
41
|
+
function isDimmed(dimension, value) {
|
|
42
|
+
const f = filters.get(dimension)
|
|
43
|
+
if (!f) return false
|
|
44
|
+
if (f instanceof Set) {
|
|
45
|
+
return !f.has(value)
|
|
46
|
+
}
|
|
47
|
+
// [min, max] range
|
|
48
|
+
const [lo, hi] = f
|
|
49
|
+
return Number(value) < lo || Number(value) > hi
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Toggles a categorical value for a dimension.
|
|
54
|
+
* Adds when absent, removes when present.
|
|
55
|
+
* Clears the dimension filter when the last value is removed.
|
|
56
|
+
*
|
|
57
|
+
* @param {string} dimension
|
|
58
|
+
* @param {unknown} value
|
|
59
|
+
*/
|
|
60
|
+
function toggleCategorical(dimension, value) {
|
|
61
|
+
const existing = filters.get(dimension)
|
|
62
|
+
const set = existing instanceof Set ? new Set(existing) : new Set()
|
|
63
|
+
if (set.has(value)) {
|
|
64
|
+
set.delete(value)
|
|
65
|
+
} else {
|
|
66
|
+
set.add(value)
|
|
67
|
+
}
|
|
68
|
+
if (set.size === 0) {
|
|
69
|
+
filters.delete(dimension)
|
|
70
|
+
} else {
|
|
71
|
+
filters.set(dimension, set)
|
|
72
|
+
}
|
|
73
|
+
version++
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Sets a continuous range filter for a dimension.
|
|
78
|
+
* @param {string} dimension
|
|
79
|
+
* @param {[number, number]} range
|
|
80
|
+
*/
|
|
81
|
+
function setRange(dimension, range) {
|
|
82
|
+
filters.set(dimension, [range[0], range[1]])
|
|
83
|
+
version++
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Clears the filter for a single dimension.
|
|
88
|
+
* @param {string} dimension
|
|
89
|
+
*/
|
|
90
|
+
function clearFilter(dimension) {
|
|
91
|
+
filters.delete(dimension)
|
|
92
|
+
version++
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Clears all active filters. */
|
|
96
|
+
function clearAll() {
|
|
97
|
+
filters.clear()
|
|
98
|
+
version++
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
/** @readonly — reactive Map of current filter state */
|
|
103
|
+
get filters() { return filters },
|
|
104
|
+
/** @readonly — increments on every mutation; read inside $effect to react to any filter change */
|
|
105
|
+
get version() { return version },
|
|
106
|
+
isFiltered,
|
|
107
|
+
isDimmed,
|
|
108
|
+
toggleCategorical,
|
|
109
|
+
setRange,
|
|
110
|
+
clearFilter,
|
|
111
|
+
clearAll
|
|
112
|
+
}
|
|
113
|
+
}
|
package/src/elements/Bar.svelte
CHANGED
|
@@ -1,35 +1,33 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import { format } from 'd3-format'
|
|
3
|
+
import { get } from 'svelte/store'
|
|
3
4
|
import Label from './Label.svelte'
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
let {
|
|
7
|
+
rank,
|
|
8
|
+
value,
|
|
9
|
+
name,
|
|
10
|
+
formatString = '.1%',
|
|
11
|
+
scales,
|
|
12
|
+
height = 60,
|
|
13
|
+
fill,
|
|
14
|
+
spaceBetween = 5
|
|
15
|
+
} = $props()
|
|
13
16
|
|
|
14
17
|
const textHeight = 16
|
|
15
18
|
const charWidth = 12
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
let y = $derived(rank * (height + spaceBetween))
|
|
20
|
+
let width = $derived(get(scales).x(value))
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
let textWidth = $derived(name.length * charWidth)
|
|
23
|
+
let textOffset = $derived(width <= textWidth ? width + charWidth : width)
|
|
24
|
+
let textAnchor = $derived(textOffset > width ? 'start' : 'end')
|
|
22
25
|
|
|
23
|
-
|
|
26
|
+
let formattedValue = $derived(format(formatString)(value))
|
|
27
|
+
let xOrigin = $derived(get(scales).x(0))
|
|
24
28
|
</script>
|
|
25
29
|
|
|
26
|
-
<rect x={
|
|
27
|
-
<rect x={
|
|
28
|
-
<Label x={width} y={y + textHeight + 8} anchor={textAnchor}
|
|
29
|
-
<Label
|
|
30
|
-
x={width}
|
|
31
|
-
y={y + height - 14}
|
|
32
|
-
anchor={textAnchor}
|
|
33
|
-
label={formattedValue}
|
|
34
|
-
small
|
|
35
|
-
/>
|
|
30
|
+
<rect x={xOrigin} {y} {width} {height} {fill} opacity={0.5} />
|
|
31
|
+
<rect x={xOrigin} {y} width={5} {height} {fill} />
|
|
32
|
+
<Label x={width} y={y + textHeight + 8} anchor={textAnchor} text={name} />
|
|
33
|
+
<Label x={width} y={y + height - 14} anchor={textAnchor} text={formattedValue} small />
|
|
@@ -1,39 +1,37 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import { scaleLinear } from 'd3-scale'
|
|
3
|
-
import { uniqueId } from '
|
|
3
|
+
import { id as uniqueId } from '@rokkit/core'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
export let y = 0
|
|
7
|
-
export let textSize = 5
|
|
8
|
-
export let height = 10
|
|
9
|
-
export let width = 100
|
|
10
|
-
export let tickCount = 5
|
|
11
|
-
export let scale
|
|
5
|
+
let { x = 0, y = 0, textSize = 5, height = 10, width = 100, tickCount = 5, scale } = $props()
|
|
12
6
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
7
|
+
let scaleTicks = $derived(
|
|
8
|
+
scaleLinear()
|
|
9
|
+
.range([x, x + width])
|
|
10
|
+
.domain(scale.domain())
|
|
11
|
+
)
|
|
12
|
+
let scalePercent = $derived(scaleLinear().range([0, 100]).domain(scale.domain()))
|
|
13
|
+
let ticks = $derived(
|
|
14
|
+
scale.ticks.apply(scale, [tickCount]).map((d) => ({ x: scaleTicks(d), value: d }))
|
|
15
|
+
)
|
|
20
16
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
17
|
+
let colors = $derived(
|
|
18
|
+
ticks.map(({ value }) => ({
|
|
19
|
+
color: scale(value),
|
|
20
|
+
offset: `${scalePercent(value)}%`
|
|
21
|
+
}))
|
|
22
|
+
)
|
|
23
|
+
let id = $state(uniqueId('legend-'))
|
|
26
24
|
</script>
|
|
27
25
|
|
|
28
26
|
<defs>
|
|
29
27
|
<linearGradient {id}>
|
|
30
|
-
{#each colors as { color, offset }}
|
|
28
|
+
{#each colors as { color, offset }, index (index)}
|
|
31
29
|
<stop stop-color={color} {offset} />
|
|
32
30
|
{/each}
|
|
33
31
|
</linearGradient>
|
|
34
32
|
</defs>
|
|
35
33
|
<rect {x} y={y + height} {width} {height} fill="url(#{id})" />
|
|
36
|
-
{#each ticks as { x, value }}
|
|
34
|
+
{#each ticks as { x, value }, index (index)}
|
|
37
35
|
<line x1={x} y1={y + (2 * height) / 3} x2={x} y2={y + height * 2} />
|
|
38
36
|
<text {x} y={y + height / 2} font-size={textSize}>{value}</text>
|
|
39
37
|
{/each}
|
|
@@ -1,24 +1,27 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import { scaleLinear } from 'd3-scale'
|
|
3
|
-
import { uniqueId } from '../components/lib/utils'
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
4
|
+
let {
|
|
5
|
+
x = 0,
|
|
6
|
+
y = 0,
|
|
7
|
+
textSize = 5,
|
|
8
|
+
height = 10,
|
|
9
|
+
width = 100,
|
|
10
|
+
tickCount = 5,
|
|
11
|
+
scale,
|
|
12
|
+
id = 'legend'
|
|
13
|
+
} = $props()
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
let scaleTicks = $derived(
|
|
16
|
+
scaleLinear()
|
|
17
|
+
.range([x, x + 100])
|
|
18
|
+
.domain(scale.domain())
|
|
19
|
+
)
|
|
20
|
+
let ticks = $derived(
|
|
21
|
+
scale.ticks.apply(scale, [tickCount]).map((d) => ({ x: scaleTicks(d), label: d }))
|
|
22
|
+
)
|
|
19
23
|
|
|
20
|
-
|
|
21
|
-
$: id = uniqueId('legend-')
|
|
24
|
+
let colors = $derived(scale.range())
|
|
22
25
|
</script>
|
|
23
26
|
|
|
24
27
|
<defs>
|
|
@@ -28,7 +31,7 @@
|
|
|
28
31
|
</linearGradient>
|
|
29
32
|
</defs>
|
|
30
33
|
<rect {x} y={y + height} {width} {height} fill="url(#{id})" />
|
|
31
|
-
{#each ticks as { x, label }}
|
|
34
|
+
{#each ticks as { x, label }, index (index)}
|
|
32
35
|
<line x1={x} y1={y + (2 * height) / 3} x2={x} y2={y + height * 2} />
|
|
33
36
|
<text {x} y={y + height / 2} font-size={textSize}>{label}</text>
|
|
34
37
|
{/each}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { uniq } from 'ramda'
|
|
3
|
+
|
|
4
|
+
let {
|
|
5
|
+
size = 10,
|
|
6
|
+
patternUnits = 'userSpaceOnUse',
|
|
7
|
+
/** @type {Array<import('./types').Pattern>} */
|
|
8
|
+
patterns = []
|
|
9
|
+
} = $props()
|
|
10
|
+
|
|
11
|
+
let names = $derived(uniq(patterns.map(({ id }) => id)))
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
{#if names.length < patterns.length}
|
|
15
|
+
<error> Patterns should be an array and should have unique names for each pattern </error>
|
|
16
|
+
{:else if patterns.length > 0}
|
|
17
|
+
<defs>
|
|
18
|
+
{#each patterns as { id, component: Component, fill, stroke }, index (index)}
|
|
19
|
+
<pattern {id} {patternUnits} width={size} height={size}>
|
|
20
|
+
<Component {size} {fill} {stroke} />
|
|
21
|
+
</pattern>
|
|
22
|
+
{/each}
|
|
23
|
+
</defs>
|
|
24
|
+
{/if}
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
<script>
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
let {
|
|
3
|
+
x = 0,
|
|
4
|
+
y = 0,
|
|
5
|
+
textSize = 5,
|
|
6
|
+
size = 10,
|
|
7
|
+
space = 2,
|
|
8
|
+
padding = 5,
|
|
9
|
+
scale,
|
|
10
|
+
tickCount = 10
|
|
11
|
+
} = $props()
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
let sizeWithSpace = $derived(size + space)
|
|
14
|
+
let ticks = $derived(scale.ticks.apply(scale, [tickCount]))
|
|
13
15
|
</script>
|
|
14
16
|
|
|
15
|
-
{#each ticks as tick, i}
|
|
16
|
-
<text
|
|
17
|
-
|
|
18
|
-
y={y + size / 2}
|
|
19
|
-
font-size={textSize}>{tick}</text
|
|
17
|
+
{#each ticks as tick, i (i)}
|
|
18
|
+
<text x={x + padding + i * sizeWithSpace + size / 2} y={y + size / 2} font-size={textSize}
|
|
19
|
+
>{tick}</text
|
|
20
20
|
>
|
|
21
21
|
<rect
|
|
22
22
|
x={x + padding + i * sizeWithSpace}
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
<script>
|
|
2
|
-
|
|
3
|
-
export let label
|
|
4
|
-
export let angle = 0
|
|
5
|
-
export let anchor = 'middle'
|
|
6
|
-
export let x
|
|
7
|
-
export let y
|
|
2
|
+
let { x, y, text, angle = 0, small = false, anchor = 'middle' } = $props()
|
|
8
3
|
|
|
9
|
-
|
|
4
|
+
let transform = $derived(`translate(${x},${y}) rotate(${angle})`)
|
|
5
|
+
let validAnchor = $derived(['start', 'middle', 'end'].includes(anchor) ? anchor : 'middle')
|
|
10
6
|
</script>
|
|
11
7
|
|
|
12
|
-
<text class="label" class:small text-anchor={
|
|
8
|
+
<text class="label" class:small text-anchor={validAnchor} {transform}>{text}</text>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { swatch } from '../lib/swatch'
|
|
3
|
+
import { swatchGrid } from '../lib/grid'
|
|
4
|
+
import Symbol from '../Symbol.svelte'
|
|
5
|
+
|
|
6
|
+
let { base = 'teal', size = 4, shade = 600 } = $props()
|
|
7
|
+
|
|
8
|
+
let grid = $derived(swatchGrid(swatch.keys.symbol.length, size, 10))
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<svg viewBox="0 0 {grid.width} {grid.height}">
|
|
12
|
+
{#each grid.data as { x, y, r }, index (index)}
|
|
13
|
+
<Symbol
|
|
14
|
+
{x}
|
|
15
|
+
{y}
|
|
16
|
+
size={r * 2}
|
|
17
|
+
name={swatch.keys.symbol[index]}
|
|
18
|
+
fill={swatch.palette[base][shade]}
|
|
19
|
+
stroke={swatch.palette[base][shade]}
|
|
20
|
+
/>
|
|
21
|
+
{/each}
|
|
22
|
+
</svg>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { default as Bar } from './Bar.svelte'
|
|
2
|
+
export { default as ColorRamp } from './ColorRamp.svelte'
|
|
3
|
+
export { default as ContinuousLegend } from './ContinuousLegend.svelte'
|
|
4
|
+
export { default as Label } from './Label.svelte'
|
|
5
|
+
export { default as DefinePatterns } from './DefinePatterns.svelte'
|
|
6
|
+
export { default as SymbolGrid } from './SymbolGrid.svelte'
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { Plot } from '../index.js'
|
|
3
|
+
import { dataset } from '@rokkit/data'
|
|
4
|
+
|
|
5
|
+
// Sample data
|
|
6
|
+
const sampleData = [
|
|
7
|
+
{ model: 'Model A', name: 'Product 1', category: 'Electronics', count: 45 },
|
|
8
|
+
{ model: 'Model B', name: 'Product 2', category: 'Clothing', count: 32 },
|
|
9
|
+
{ model: 'Model C', name: 'Product 3', category: 'Electronics', count: 62 },
|
|
10
|
+
{ model: 'Model D', name: 'Product 4', category: 'Home', count: 28 },
|
|
11
|
+
{ model: 'Model E', name: 'Product 5', category: 'Electronics', count: 53 },
|
|
12
|
+
{ model: 'Model F', name: 'Product 6', category: 'Clothing', count: 24 },
|
|
13
|
+
{ model: 'Model G', name: 'Product 7', category: 'Home', count: 35 }
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
// Use the dataset class to process the data
|
|
17
|
+
const data = dataset(sampleData)
|
|
18
|
+
.groupBy('category')
|
|
19
|
+
.summarize('name', { count: (values) => values.length })
|
|
20
|
+
.rollup()
|
|
21
|
+
|
|
22
|
+
// Chart dimensions
|
|
23
|
+
const width = 600
|
|
24
|
+
const height = 400
|
|
25
|
+
const margin = { top: 20, right: 100, bottom: 60, left: 60 }
|
|
26
|
+
|
|
27
|
+
// Click handler for bars
|
|
28
|
+
function handleBarClick(item) {
|
|
29
|
+
console.log('Bar clicked:', item)
|
|
30
|
+
alert(`Clicked on ${item.category} with count ${item.count}`)
|
|
31
|
+
}
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<div class="example">
|
|
35
|
+
<h2>Bar Chart Example</h2>
|
|
36
|
+
|
|
37
|
+
<div class="chart-wrapper">
|
|
38
|
+
<Plot.Root {data} {width} {height} {margin} fill="category">
|
|
39
|
+
<Plot.Grid direction="y" lineStyle="dashed" />
|
|
40
|
+
<Plot.Axis type="x" field="category" label="Product Category" />
|
|
41
|
+
<Plot.Axis type="y" field="count" label="Number of Products" />
|
|
42
|
+
<Plot.Bar x="category" y="count" fill="category" onClick={handleBarClick} />
|
|
43
|
+
<Plot.Legend title="Categories" />
|
|
44
|
+
</Plot.Root>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<div class="code-example">
|
|
48
|
+
<h3>Example Code:</h3>
|
|
49
|
+
<pre>
|
|
50
|
+
<!-- <script>
|
|
51
|
+
import { Plot } from '@rokkit/chart';
|
|
52
|
+
import { dataset } from '@rokkit/data';
|
|
53
|
+
|
|
54
|
+
// Sample data
|
|
55
|
+
const sampleData = [
|
|
56
|
+
{ model: 'Model A', name: 'Product 1', category: 'Electronics', count: 45 },
|
|
57
|
+
{ model: 'Model B', name: 'Product 2', category: 'Clothing', count: 32 },
|
|
58
|
+
{ model: 'Model C', name: 'Product 3', category: 'Electronics', count: 62 },
|
|
59
|
+
{ model: 'Model D', name: 'Product 4', category: 'Home', count: 28 },
|
|
60
|
+
{ model: 'Model E', name: 'Product 5', category: 'Electronics', count: 53 },
|
|
61
|
+
{ model: 'Model F', name: 'Product 6', category: 'Clothing', count: 24 },
|
|
62
|
+
{ model: 'Model G', name: 'Product 7', category: 'Home', count: 35 },
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
// Use the dataset class to process the data
|
|
66
|
+
const data = dataset(sampleData)
|
|
67
|
+
.groupBy('category')
|
|
68
|
+
.summarize('name', { count: values => values.length })
|
|
69
|
+
.rollup();
|
|
70
|
+
</script>
|
|
71
|
+
|
|
72
|
+
<Plot.Root {data} width={600} height={400} margin={{ top: 20, right: 100, bottom: 60, left: 60 }} fill="category">
|
|
73
|
+
<Plot.Grid direction="y" lineStyle="dashed" />
|
|
74
|
+
<Plot.Axis type="x" field="category" label="Product Category" />
|
|
75
|
+
<Plot.Axis type="y" field="count" label="Number of Products" />
|
|
76
|
+
<Plot.Bar x="category" y="count" fill="category" onClick={handleBarClick} />
|
|
77
|
+
<Plot.Legend title="Categories" />
|
|
78
|
+
</Plot.Root> -->
|
|
79
|
+
</pre>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { getContext, onMount, onDestroy } from 'svelte'
|
|
3
|
+
import { buildArcs } from '../lib/brewing/marks/arcs.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `fill` is the primary prop name; `color` is accepted as an alias for
|
|
7
|
+
* spec-driven usage (Plot.svelte passes `color` to all geoms generically).
|
|
8
|
+
* @type {{ theta?: string, fill?: string, color?: string, pattern?: string, stat?: string, labelFn?: (data: Record<string, unknown>) => string, options?: { innerRadius?: number } }}
|
|
9
|
+
*/
|
|
10
|
+
let { theta, fill, color, pattern, labelFn = undefined, stat = 'identity', options = {} } = $props()
|
|
11
|
+
|
|
12
|
+
const fillField = $derived(fill ?? color)
|
|
13
|
+
|
|
14
|
+
const plotState = getContext('plot-state')
|
|
15
|
+
let id = $state(null)
|
|
16
|
+
|
|
17
|
+
onMount(() => {
|
|
18
|
+
id = plotState.registerGeom({ type: 'arc', channels: { color: fillField, y: theta, pattern }, stat, options })
|
|
19
|
+
})
|
|
20
|
+
onDestroy(() => { if (id) plotState.unregisterGeom(id) })
|
|
21
|
+
|
|
22
|
+
$effect(() => {
|
|
23
|
+
if (id) plotState.updateGeom(id, { channels: { color: fillField, y: theta, pattern }, stat })
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
const data = $derived(id ? plotState.geomData(id) : [])
|
|
27
|
+
const colors = $derived(plotState.colors)
|
|
28
|
+
const patterns = $derived(plotState.patterns)
|
|
29
|
+
const w = $derived(plotState.innerWidth)
|
|
30
|
+
const h = $derived(plotState.innerHeight)
|
|
31
|
+
|
|
32
|
+
const arcs = $derived.by(() => {
|
|
33
|
+
if (!data?.length) return []
|
|
34
|
+
// Guard: skip until data catches up after a fill-field change.
|
|
35
|
+
// When fillField changes, the $effect updates the geom asynchronously, but
|
|
36
|
+
// this derived runs first with stale data whose rows don't have the new
|
|
37
|
+
// field — causing all keys to be undefined (duplicate key error).
|
|
38
|
+
if (fillField && !(fillField in data[0])) return []
|
|
39
|
+
const innerRadius = (options.innerRadius ?? 0) * Math.min(w, h) / 2
|
|
40
|
+
return buildArcs(data, { color: fillField, y: theta, pattern }, colors, w, h, { innerRadius }, patterns)
|
|
41
|
+
})
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
{#if arcs.length > 0}
|
|
45
|
+
<g
|
|
46
|
+
data-plot-geom="arc"
|
|
47
|
+
transform="translate({w / 2}, {h / 2})"
|
|
48
|
+
>
|
|
49
|
+
{#each arcs as arc (arc.key)}
|
|
50
|
+
<path
|
|
51
|
+
d={arc.d}
|
|
52
|
+
fill={arc.fill}
|
|
53
|
+
stroke={arc.stroke}
|
|
54
|
+
stroke-width="1"
|
|
55
|
+
role="presentation"
|
|
56
|
+
data-plot-element="arc"
|
|
57
|
+
onmouseenter={() => plotState.setHovered({ ...arc.data, '%': `${arc.pct}%` })}
|
|
58
|
+
onmouseleave={() => plotState.clearHovered()}
|
|
59
|
+
/>
|
|
60
|
+
{#if arc.patternId}
|
|
61
|
+
<path d={arc.d} fill="url(#{arc.patternId})" stroke={arc.stroke} stroke-width="1" pointer-events="none" data-plot-element="arc" />
|
|
62
|
+
{/if}
|
|
63
|
+
{#if arc.pct >= 5}
|
|
64
|
+
{@const labelText = labelFn ? String(labelFn(arc.data) ?? '') : `${arc.pct}%`}
|
|
65
|
+
{#if labelText}
|
|
66
|
+
{@const lw = Math.max(36, labelText.length * 7 + 12)}
|
|
67
|
+
<g transform="translate({arc.centroid[0]},{arc.centroid[1]})" pointer-events="none" data-plot-element="arc-label">
|
|
68
|
+
<rect x={-lw / 2} y="-9" width={lw} height="18" rx="4" fill="white" fill-opacity="0.82" />
|
|
69
|
+
<text
|
|
70
|
+
text-anchor="middle"
|
|
71
|
+
dominant-baseline="central"
|
|
72
|
+
font-size="11"
|
|
73
|
+
font-weight="600"
|
|
74
|
+
fill={arc.stroke}
|
|
75
|
+
>{labelText}</text>
|
|
76
|
+
</g>
|
|
77
|
+
{/if}
|
|
78
|
+
{/if}
|
|
79
|
+
{/each}
|
|
80
|
+
</g>
|
|
81
|
+
{/if}
|