@rokkit/chart 1.0.0-next.146 → 1.0.0-next.148

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.
Files changed (157) hide show
  1. package/dist/Plot/index.d.ts +4 -0
  2. package/dist/PlotState.svelte.d.ts +47 -0
  3. package/dist/crossfilter/createCrossFilter.svelte.d.ts +15 -0
  4. package/dist/geoms/lib/areas.d.ts +52 -0
  5. package/dist/geoms/lib/bars.d.ts +3 -0
  6. package/dist/index.d.ts +38 -1
  7. package/dist/lib/brewing/BoxBrewer.svelte.d.ts +10 -0
  8. package/dist/lib/brewing/CartesianBrewer.svelte.d.ts +8 -0
  9. package/dist/lib/brewing/PieBrewer.svelte.d.ts +8 -0
  10. package/dist/lib/brewing/ViolinBrewer.svelte.d.ts +9 -0
  11. package/dist/lib/brewing/brewer.svelte.d.ts +145 -0
  12. package/dist/lib/brewing/colors.d.ts +17 -0
  13. package/dist/lib/brewing/marks/arcs.d.ts +17 -0
  14. package/dist/lib/brewing/marks/areas.d.ts +31 -0
  15. package/dist/lib/brewing/marks/bars.d.ts +1 -0
  16. package/dist/lib/brewing/marks/boxes.d.ts +24 -0
  17. package/dist/lib/brewing/marks/lines.d.ts +24 -0
  18. package/dist/lib/brewing/marks/points.d.ts +40 -0
  19. package/dist/lib/brewing/marks/violins.d.ts +20 -0
  20. package/dist/lib/brewing/patterns.d.ts +14 -0
  21. package/dist/lib/brewing/scales.d.ts +28 -0
  22. package/dist/lib/brewing/stats.d.ts +31 -0
  23. package/dist/lib/brewing/symbols.d.ts +7 -0
  24. package/dist/lib/plot/chartProps.d.ts +177 -0
  25. package/dist/lib/plot/crossfilter.d.ts +13 -0
  26. package/dist/lib/plot/facet.d.ts +24 -0
  27. package/dist/lib/plot/frames.d.ts +45 -0
  28. package/dist/lib/plot/helpers.d.ts +3 -0
  29. package/dist/lib/plot/preset.d.ts +29 -0
  30. package/dist/lib/plot/scales.d.ts +5 -0
  31. package/dist/lib/plot/stat.d.ts +32 -0
  32. package/dist/lib/plot/types.d.ts +89 -0
  33. package/dist/lib/scales.svelte.d.ts +1 -1
  34. package/dist/lib/swatch.d.ts +12 -0
  35. package/dist/lib/utils.d.ts +1 -0
  36. package/dist/lib/xscale.d.ts +11 -0
  37. package/dist/patterns/index.d.ts +4 -9
  38. package/dist/patterns/patterns.d.ts +72 -0
  39. package/dist/patterns/scale.d.ts +30 -0
  40. package/package.json +9 -3
  41. package/src/AnimatedPlot.svelte +194 -0
  42. package/src/Chart.svelte +101 -0
  43. package/src/FacetPlot/Panel.svelte +23 -0
  44. package/src/FacetPlot.svelte +90 -0
  45. package/src/Plot/Arc.svelte +29 -0
  46. package/src/Plot/Area.svelte +25 -0
  47. package/src/Plot/Axis.svelte +62 -84
  48. package/src/Plot/Grid.svelte +20 -58
  49. package/src/Plot/Legend.svelte +160 -120
  50. package/src/Plot/Line.svelte +27 -0
  51. package/src/Plot/Point.svelte +27 -0
  52. package/src/Plot/Timeline.svelte +95 -0
  53. package/src/Plot/Tooltip.svelte +81 -0
  54. package/src/Plot/index.js +4 -0
  55. package/src/Plot.svelte +189 -0
  56. package/src/PlotState.svelte.js +278 -0
  57. package/src/Sparkline.svelte +69 -0
  58. package/src/charts/AreaChart.svelte +25 -0
  59. package/src/charts/BarChart.svelte +26 -0
  60. package/src/charts/BoxPlot.svelte +21 -0
  61. package/src/charts/BubbleChart.svelte +23 -0
  62. package/src/charts/LineChart.svelte +26 -0
  63. package/src/charts/PieChart.svelte +25 -0
  64. package/src/charts/ScatterPlot.svelte +25 -0
  65. package/src/charts/ViolinPlot.svelte +21 -0
  66. package/src/crossfilter/CrossFilter.svelte +38 -0
  67. package/src/crossfilter/FilterBar.svelte +32 -0
  68. package/src/crossfilter/FilterSlider.svelte +79 -0
  69. package/src/crossfilter/createCrossFilter.svelte.js +113 -0
  70. package/src/elements/SymbolGrid.svelte +6 -7
  71. package/src/geoms/Arc.svelte +81 -0
  72. package/src/geoms/Area.svelte +50 -0
  73. package/src/geoms/Bar.svelte +142 -0
  74. package/src/geoms/Box.svelte +101 -0
  75. package/src/geoms/LabelPill.svelte +17 -0
  76. package/src/geoms/Line.svelte +100 -0
  77. package/src/geoms/Point.svelte +100 -0
  78. package/src/geoms/Violin.svelte +44 -0
  79. package/src/geoms/lib/areas.js +131 -0
  80. package/src/geoms/lib/bars.js +172 -0
  81. package/src/index.js +52 -3
  82. package/src/lib/brewing/BoxBrewer.svelte.js +56 -0
  83. package/src/lib/brewing/CartesianBrewer.svelte.js +16 -0
  84. package/src/lib/brewing/PieBrewer.svelte.js +14 -0
  85. package/src/lib/brewing/ViolinBrewer.svelte.js +55 -0
  86. package/src/lib/brewing/brewer.svelte.js +229 -0
  87. package/src/lib/brewing/colors.js +22 -0
  88. package/src/lib/brewing/marks/arcs.js +43 -0
  89. package/src/lib/brewing/marks/areas.js +59 -0
  90. package/src/lib/brewing/marks/bars.js +49 -0
  91. package/src/lib/brewing/marks/boxes.js +75 -0
  92. package/src/lib/brewing/marks/lines.js +48 -0
  93. package/src/lib/brewing/marks/points.js +57 -0
  94. package/src/lib/brewing/marks/violins.js +90 -0
  95. package/src/lib/brewing/patterns.js +31 -0
  96. package/src/lib/brewing/scales.js +51 -0
  97. package/src/lib/brewing/scales.svelte.js +2 -26
  98. package/src/lib/brewing/stats.js +62 -0
  99. package/src/lib/brewing/symbols.js +10 -0
  100. package/src/lib/plot/chartProps.js +76 -0
  101. package/src/lib/plot/crossfilter.js +16 -0
  102. package/src/lib/plot/facet.js +58 -0
  103. package/src/lib/plot/frames.js +90 -0
  104. package/src/lib/plot/helpers.js +14 -0
  105. package/src/lib/plot/preset.js +53 -0
  106. package/src/lib/plot/scales.js +56 -0
  107. package/src/lib/plot/stat.js +92 -0
  108. package/src/lib/plot/types.js +65 -0
  109. package/src/lib/scales.svelte.js +2 -26
  110. package/src/lib/swatch.js +13 -0
  111. package/src/lib/utils.js +9 -0
  112. package/src/lib/xscale.js +31 -0
  113. package/src/patterns/DefinePatterns.svelte +32 -0
  114. package/src/patterns/PatternDef.svelte +27 -0
  115. package/src/patterns/index.js +4 -14
  116. package/src/patterns/patterns.js +208 -0
  117. package/src/patterns/scale.js +87 -0
  118. package/src/spec/chart-spec.js +29 -0
  119. package/src/symbols/Shape.svelte +1 -1
  120. package/src/symbols/constants/index.js +1 -1
  121. package/dist/old_lib/index.d.ts +0 -4
  122. package/dist/old_lib/plots.d.ts +0 -3
  123. package/dist/old_lib/swatch.d.ts +0 -285
  124. package/dist/old_lib/utils.d.ts +0 -1
  125. package/dist/patterns/paths/constants.d.ts +0 -1
  126. package/dist/template/constants.d.ts +0 -43
  127. package/dist/template/shapes/index.d.ts +0 -4
  128. package/src/old_lib/index.js +0 -4
  129. package/src/old_lib/plots.js +0 -27
  130. package/src/old_lib/swatch.js +0 -16
  131. package/src/old_lib/utils.js +0 -8
  132. package/src/patterns/Brick.svelte +0 -15
  133. package/src/patterns/Circles.svelte +0 -18
  134. package/src/patterns/CrossHatch.svelte +0 -12
  135. package/src/patterns/CurvedWave.svelte +0 -7
  136. package/src/patterns/Dots.svelte +0 -20
  137. package/src/patterns/OutlineCircles.svelte +0 -13
  138. package/src/patterns/Tile.svelte +0 -16
  139. package/src/patterns/Triangles.svelte +0 -13
  140. package/src/patterns/Waves.svelte +0 -9
  141. package/src/patterns/paths/NamedPattern.svelte +0 -9
  142. package/src/patterns/paths/constants.js +0 -4
  143. package/src/template/Texture.svelte +0 -13
  144. package/src/template/constants.js +0 -43
  145. package/src/template/shapes/Circles.svelte +0 -15
  146. package/src/template/shapes/Lines.svelte +0 -16
  147. package/src/template/shapes/Path.svelte +0 -9
  148. package/src/template/shapes/Polygons.svelte +0 -15
  149. package/src/template/shapes/index.js +0 -4
  150. /package/dist/{old_lib → lib}/brewer.d.ts +0 -0
  151. /package/dist/{old_lib → lib}/chart.d.ts +0 -0
  152. /package/dist/{old_lib → lib}/grid.d.ts +0 -0
  153. /package/dist/{old_lib → lib}/ticks.d.ts +0 -0
  154. /package/src/{old_lib → lib}/brewer.js +0 -0
  155. /package/src/{old_lib → lib}/chart.js +0 -0
  156. /package/src/{old_lib → lib}/grid.js +0 -0
  157. /package/src/{old_lib → lib}/ticks.js +0 -0
@@ -1,68 +1,30 @@
1
1
  <script>
2
- import { getContext } from 'svelte'
3
- import { createGrid } from '../lib/brewing/axes.svelte.js'
2
+ import { getContext } from 'svelte'
4
3
 
5
- let {
6
- direction = 'both',
7
- xTicks = null,
8
- yTicks = null,
9
- color = 'currentColor',
10
- opacity = 0.1,
11
- lineStyle = 'solid'
12
- } = $props()
4
+ const state = getContext('plot-state')
13
5
 
14
- // Get brewer from context
15
- const brewer = getContext('chart-brewer')
6
+ const xGridLines = $derived.by(() => {
7
+ const s = state.xScale
8
+ if (!s || typeof s.bandwidth !== 'function') return []
9
+ return s.domain().map((val) => ({ pos: (s(val) ?? 0) + s.bandwidth() / 2 }))
10
+ })
16
11
 
17
- // Get grid data
18
- let gridData = $derived(
19
- brewer.createGrid({
20
- direction,
21
- xTickCount: xTicks,
22
- yTickCount: yTicks
23
- })
24
- )
25
-
26
- // Convert lineStyle to stroke-dasharray
27
- let strokeDasharray = $derived(
28
- lineStyle === 'dashed' ? '5,5' : lineStyle === 'dotted' ? '1,3' : 'none'
29
- )
12
+ const yGridLines = $derived.by(() => {
13
+ const s = state.yScale
14
+ if (!s || typeof s.ticks !== 'function') return []
15
+ return s.ticks(6).map((val) => ({ pos: s(val) }))
16
+ })
30
17
  </script>
31
18
 
32
- <g class="chart-grid" data-plot-grid={direction}>
33
- {#if direction === 'x' || direction === 'both'}
34
- {#each gridData.xLines as line, index (index)}
35
- <line
36
- data-plot-grid-line="x"
37
- x1={line.x1}
38
- y1={line.y1}
39
- x2={line.x2}
40
- y2={line.y2}
41
- stroke={color}
42
- stroke-opacity={opacity}
43
- stroke-dasharray={strokeDasharray}
44
- />
45
- {/each}
46
- {/if}
47
-
48
- {#if direction === 'y' || direction === 'both'}
49
- {#each gridData.yLines as line, index (index)}
50
- <line
51
- data-plot-grid-line="y"
52
- x1={line.x1}
53
- y1={line.y1}
54
- x2={line.x2}
55
- y2={line.y2}
56
- stroke={color}
57
- stroke-opacity={opacity}
58
- stroke-dasharray={strokeDasharray}
59
- />
60
- {/each}
61
- {/if}
19
+ <g class="grid" data-plot-grid>
20
+ {#each yGridLines as line (line.pos)}
21
+ <line x1="0" y1={line.pos} x2={state.innerWidth} y2={line.pos} data-plot-grid-line />
22
+ {/each}
23
+ {#each xGridLines as line (line.pos)}
24
+ <line x1={line.pos} y1="0" x2={line.pos} y2={state.innerHeight} data-plot-grid-line="x" />
25
+ {/each}
62
26
  </g>
63
27
 
64
28
  <style>
65
- .chart-grid {
66
- pointer-events: none;
67
- }
29
+ [data-plot-grid-line] { stroke: var(--chart-grid-color, currentColor); opacity: 0.15; stroke-dasharray: 2 4; }
68
30
  </style>
@@ -1,127 +1,167 @@
1
1
  <script>
2
- import { getContext } from 'svelte'
3
- import { createLegend, createLegendItemAttributes } from '../lib/brewing/legends.svelte.js'
4
-
5
- let {
6
- title = '',
7
- align = 'right',
8
- verticalAlign = 'top',
9
- shape = 'rect',
10
- markerSize = 10,
11
- onClick = null
12
- } = $props()
13
-
14
- // Get brewer from context
15
- const brewer = getContext('chart-brewer')
16
-
17
- // Get legend data
18
- let legendData = $derived(
19
- brewer.createLegend({
20
- title,
21
- align,
22
- shape,
23
- markerSize
24
- })
25
- )
26
-
27
- // Track selected items (for filtering)
28
- let selectedItems = $state([])
29
-
30
- function toggleItem(item) {
31
- if (!onClick) return
32
-
33
- const isSelected = selectedItems.includes(item.value)
34
-
35
- if (isSelected) {
36
- selectedItems = selectedItems.filter((v) => v !== item.value)
37
- } else {
38
- selectedItems = [...selectedItems, item.value]
39
- }
40
-
41
- onClick(selectedItems)
42
- }
2
+ import { getContext } from 'svelte'
3
+ import { toPatternId } from '../lib/brewing/patterns.js'
4
+ import { buildSymbolPath } from '../lib/brewing/marks/points.js'
5
+
6
+ /** @type {Record<string, string>} */
7
+ let { labels = {} } = $props()
8
+
9
+ const state = getContext('plot-state')
10
+
11
+ const isCategorical = $derived(state.colorScaleType === 'categorical')
12
+ const isLineGeom = $derived(state.geomTypes?.has('line') ?? false)
13
+ const isPointGeom = $derived(state.geomTypes?.has('point') ?? false)
14
+ const hasSymbols = $derived((state.symbols?.size ?? 0) > 0)
15
+
16
+ // Split conditions
17
+ const splitPattern = $derived(
18
+ !!state.colorField && !!state.patternField && state.colorField !== state.patternField
19
+ )
20
+ const splitSymbol = $derived(
21
+ hasSymbols && !!state.colorField && !!state.symbolField && state.colorField !== state.symbolField
22
+ )
23
+ const symbolOnly = $derived(hasSymbols && !state.colorField)
24
+
25
+ // Color section items — combined with same-field pattern/symbol overlays
26
+ const colorItems = $derived(
27
+ [...(state.colors?.entries() ?? [])].map(([key, entry]) => ({
28
+ key,
29
+ label: labels[String(key)] ?? String(key),
30
+ fill: entry.fill,
31
+ stroke: entry.stroke,
32
+ patternId: !splitPattern && state.patterns?.has(key) ? toPatternId(String(key)) : null,
33
+ symbolShape: !splitSymbol && state.symbols?.get(key) ? state.symbols.get(key) : null
34
+ }))
35
+ )
36
+
37
+ // Pattern section — only when pattern encodes a different field than color
38
+ const patternItems = $derived(
39
+ splitPattern
40
+ ? [...(state.patterns?.entries() ?? [])].map(([key]) => ({
41
+ key,
42
+ label: labels[String(key)] ?? String(key),
43
+ patternId: toPatternId(String(key))
44
+ }))
45
+ : []
46
+ )
47
+
48
+ // Symbol section — only when symbol encodes a different field than color, or symbol-only
49
+ const symbolItems = $derived(
50
+ splitSymbol || symbolOnly
51
+ ? [...(state.symbols?.entries() ?? [])].map(([key, shape]) => ({
52
+ key,
53
+ label: labels[String(key)] ?? String(key),
54
+ shape,
55
+ fill: state.colors?.get(key)?.fill ?? '#888',
56
+ stroke: state.colors?.get(key)?.stroke ?? '#888'
57
+ }))
58
+ : []
59
+ )
60
+
61
+ const gradientStyle = $derived.by(() => {
62
+ if (isCategorical) return ''
63
+ return `background: linear-gradient(to right, #cfe2f3, #084594)`
64
+ })
43
65
  </script>
44
66
 
45
- {#if legendData.items.length > 0}
46
- <g class="chart-legend" transform={legendData.transform} data-plot-legend>
47
- <!-- Legend title -->
48
- {#if legendData.title}
49
- <text
50
- class="legend-title"
51
- x="0"
52
- y="-6"
53
- text-anchor={align === 'left' ? 'start' : align === 'right' ? 'end' : 'middle'}
54
- data-plot-legend-title
55
- >
56
- {legendData.title}
57
- </text>
58
- {/if}
59
-
60
- <!-- Legend items -->
61
- {#each legendData.items as item, index (index)}
62
- {@const attrs = createLegendItemAttributes(item)}
63
- {@const isSelected = selectedItems.includes(item.value)}
64
- <g
65
- {...attrs}
66
- class="legend-item"
67
- class:selected={isSelected}
68
- onclick={() => toggleItem(item)}
69
- >
70
- <!-- Shape: circle or rect -->
71
- {#if item.shape === 'circle'}
72
- <circle
73
- cx={item.markerSize / 2}
74
- cy={item.markerSize / 2}
75
- r={item.markerSize / 2}
76
- fill={item.color}
77
- stroke={isSelected ? 'currentColor' : 'none'}
78
- stroke-width={isSelected ? 1 : 0}
79
- data-plot-legend-marker="circle"
80
- />
81
- {:else}
82
- <rect
83
- width={item.markerSize}
84
- height={item.markerSize}
85
- fill={item.color}
86
- stroke={isSelected ? 'currentColor' : 'none'}
87
- stroke-width={isSelected ? 1 : 0}
88
- data-plot-legend-marker="rect"
89
- />
90
- {/if}
91
-
92
- <!-- Text label -->
93
- <text
94
- x={item.markerSize + 5}
95
- y={item.markerSize - 2}
96
- text-anchor="start"
97
- data-plot-legend-label
98
- >
99
- {item.value}
100
- </text>
101
- </g>
102
- {/each}
103
- </g>
67
+ {#if isCategorical}
68
+ <div class="legend-root" data-plot-legend>
69
+
70
+ <!-- Symbol-only: no color field, just symbol shapes -->
71
+ {#if symbolOnly}
72
+ <div class="legend categorical">
73
+ {#each symbolItems as item (item.key)}
74
+ <div class="legend-item" data-plot-legend-item>
75
+ <svg width="14" height="14" data-plot-legend-swatch>
76
+ <path transform="translate(7,7)" d={buildSymbolPath(item.shape, 5)} fill={item.fill} />
77
+ </svg>
78
+ <span class="label" data-plot-legend-label>{item.label}</span>
79
+ </div>
80
+ {/each}
81
+ </div>
82
+
83
+ <!-- Color section (line, point, or fill swatches) -->
84
+ {:else if colorItems.length > 0}
85
+ <div class="legend categorical">
86
+ {#each colorItems as item (item.key)}
87
+ <div class="legend-item" data-plot-legend-item>
88
+ {#if isLineGeom}
89
+ <!-- Line swatch with optional combined symbol -->
90
+ <svg width="24" height="14" data-plot-legend-swatch>
91
+ <line x1="2" y1="7" x2="22" y2="7" stroke={item.stroke} stroke-width="2" stroke-linecap="round" />
92
+ {#if item.symbolShape}
93
+ <path transform="translate(12,7)" d={buildSymbolPath(item.symbolShape, 4)} fill={item.stroke} />
94
+ {/if}
95
+ </svg>
96
+ {:else if isPointGeom && item.symbolShape}
97
+ <!-- Symbol shape swatch for scatter -->
98
+ <svg width="14" height="14" data-plot-legend-swatch>
99
+ <path transform="translate(7,7)" d={buildSymbolPath(item.symbolShape, 5)} fill={item.fill} />
100
+ </svg>
101
+ {:else if item.patternId}
102
+ <!-- Fill + pattern overlay -->
103
+ <svg width="14" height="14" data-plot-legend-swatch>
104
+ <rect width="14" height="14" fill={item.fill} />
105
+ <rect width="14" height="14" fill="url(#{item.patternId})" />
106
+ </svg>
107
+ {:else}
108
+ <!-- Plain fill swatch -->
109
+ <span class="swatch" style:background-color={item.fill} data-plot-legend-swatch></span>
110
+ {/if}
111
+ <span class="label" data-plot-legend-label>{item.label}</span>
112
+ </div>
113
+ {/each}
114
+ </div>
115
+ {/if}
116
+
117
+ <!-- Pattern section (different field from color) -->
118
+ {#if patternItems.length > 0}
119
+ <div class="legend categorical legend-section">
120
+ {#each patternItems as item (item.key)}
121
+ <div class="legend-item" data-plot-legend-item>
122
+ <svg width="14" height="14" data-plot-legend-swatch>
123
+ <rect width="14" height="14" fill="var(--color-surface-z2, #ccc)" />
124
+ <rect width="14" height="14" fill="url(#{item.patternId})" />
125
+ </svg>
126
+ <span class="label" data-plot-legend-label>{item.label}</span>
127
+ </div>
128
+ {/each}
129
+ </div>
130
+ {/if}
131
+
132
+ <!-- Symbol section (different field from color) -->
133
+ {#if symbolItems.length > 0 && !symbolOnly}
134
+ <div class="legend categorical legend-section">
135
+ {#each symbolItems as item (item.key)}
136
+ <div class="legend-item" data-plot-legend-item>
137
+ {#if isLineGeom}
138
+ <svg width="24" height="14" data-plot-legend-swatch>
139
+ <line x1="2" y1="7" x2="22" y2="7" stroke={item.stroke ?? item.fill} stroke-width="2" stroke-linecap="round" stroke-dasharray="4 2" />
140
+ <path transform="translate(12,7)" d={buildSymbolPath(item.shape, 4)} fill={item.stroke ?? item.fill} />
141
+ </svg>
142
+ {:else}
143
+ <svg width="14" height="14" data-plot-legend-swatch>
144
+ <path transform="translate(7,7)" d={buildSymbolPath(item.shape, 5)} fill={item.fill} />
145
+ </svg>
146
+ {/if}
147
+ <span class="label" data-plot-legend-label>{item.label}</span>
148
+ </div>
149
+ {/each}
150
+ </div>
151
+ {/if}
152
+
153
+ </div>
154
+ {:else}
155
+ <div class="legend gradient" data-plot-legend>
156
+ <div class="gradient-bar" style={gradientStyle} data-plot-legend-gradient></div>
157
+ </div>
104
158
  {/if}
105
159
 
106
160
  <style>
107
- .chart-legend {
108
- font-size: 12px;
109
- }
110
-
111
- .legend-title {
112
- font-weight: bold;
113
- font-size: 14px;
114
- }
115
-
116
- .legend-item {
117
- cursor: pointer;
118
- }
119
-
120
- .legend-item:hover [data-plot-legend-label] {
121
- font-weight: 500;
122
- }
123
-
124
- .legend-item.selected [data-plot-legend-label] {
125
- font-weight: 700;
126
- }
161
+ .legend-root { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; }
162
+ .legend { display: flex; flex-wrap: wrap; gap: 8px; font-size: 12px; }
163
+ .legend-section { border-top: 1px solid var(--color-surface-z3, #e0e0e0); padding-top: 6px; }
164
+ .legend-item { display: flex; align-items: center; gap: 4px; }
165
+ .swatch { display: inline-block; width: 14px; height: 14px; border-radius: 2px; flex-shrink: 0; }
166
+ .gradient-bar { width: 180px; height: 14px; border-radius: 2px; }
127
167
  </style>
@@ -0,0 +1,27 @@
1
+ <script>
2
+ import { getContext } from 'svelte'
3
+
4
+ const brewer = getContext('chart-brewer')
5
+ </script>
6
+
7
+ {#if brewer && brewer.lines && brewer.lines.length > 0}
8
+ <g class="chart-lines" data-plot-type="line">
9
+ {#each brewer.lines as seg (seg.key ?? seg.d)}
10
+ <path
11
+ d={seg.d}
12
+ fill={seg.fill}
13
+ stroke={seg.stroke}
14
+ stroke-width="2"
15
+ stroke-linejoin="round"
16
+ stroke-linecap="round"
17
+ data-plot-element="line"
18
+ />
19
+ {/each}
20
+ </g>
21
+ {/if}
22
+
23
+ <style>
24
+ .chart-lines {
25
+ pointer-events: none;
26
+ }
27
+ </style>
@@ -0,0 +1,27 @@
1
+ <script>
2
+ import { getContext } from 'svelte'
3
+
4
+ const brewer = getContext('chart-brewer')
5
+ </script>
6
+
7
+ {#if brewer && brewer.points && brewer.points.length > 0}
8
+ <g class="chart-points" data-plot-type="point">
9
+ {#each brewer.points as pt, i (i)}
10
+ <circle
11
+ cx={pt.cx}
12
+ cy={pt.cy}
13
+ r={pt.r}
14
+ fill={pt.fill}
15
+ stroke={pt.stroke}
16
+ stroke-width="1"
17
+ data-plot-element="point"
18
+ />
19
+ {/each}
20
+ </g>
21
+ {/if}
22
+
23
+ <style>
24
+ .chart-points {
25
+ pointer-events: none;
26
+ }
27
+ </style>
@@ -0,0 +1,95 @@
1
+ <script>
2
+ /**
3
+ * @type {{
4
+ * frameKeys: unknown[],
5
+ * currentIndex: number,
6
+ * playing: boolean,
7
+ * speed: number,
8
+ * onplay: () => void,
9
+ * onpause: () => void,
10
+ * onscrub: (index: number) => void,
11
+ * onspeed: (speed: number) => void
12
+ * }}
13
+ */
14
+ let {
15
+ frameKeys = [],
16
+ currentIndex = 0,
17
+ playing = false,
18
+ speed = 1,
19
+ onplay,
20
+ onpause,
21
+ onscrub,
22
+ onspeed
23
+ } = $props()
24
+
25
+ const safeIndex = $derived(
26
+ frameKeys.length === 0 ? 0 : Math.min(currentIndex, frameKeys.length - 1)
27
+ )
28
+
29
+ const SPEEDS = [0.5, 1, 1.5, 2, 4]
30
+ </script>
31
+
32
+ <div class="timeline" data-plot-timeline>
33
+ <!-- Play / Pause -->
34
+ <button
35
+ class="play-pause"
36
+ aria-label={playing ? 'Pause' : 'Play'}
37
+ onclick={() => playing ? onpause?.() : onplay?.()}
38
+ disabled={frameKeys.length === 0}
39
+ data-plot-timeline-playpause
40
+ >
41
+ {playing ? '⏸' : '▶'}
42
+ </button>
43
+
44
+ <!-- Frame label -->
45
+ <span class="frame-label" data-plot-timeline-label>{frameKeys[safeIndex] ?? ''}</span>
46
+
47
+ <!-- Scrub slider -->
48
+ <input
49
+ type="range"
50
+ min="0"
51
+ max={Math.max(0, frameKeys.length - 1)}
52
+ value={safeIndex}
53
+ disabled={frameKeys.length === 0}
54
+ class="scrub"
55
+ aria-label="Animation timeline"
56
+ oninput={(e) => onscrub?.(Number(e.currentTarget.value))}
57
+ data-plot-timeline-scrub
58
+ />
59
+
60
+ <!-- Speed selector -->
61
+ <select
62
+ aria-label="Playback speed"
63
+ value={speed}
64
+ onchange={(e) => onspeed?.(Number(e.currentTarget.value))}
65
+ data-plot-timeline-speed
66
+ >
67
+ {#each SPEEDS as s (s)}
68
+ <option value={s}>{s}×</option>
69
+ {/each}
70
+ </select>
71
+ </div>
72
+
73
+ <style>
74
+ .timeline {
75
+ display: flex;
76
+ align-items: center;
77
+ gap: 8px;
78
+ padding: 8px 0;
79
+ font-size: 12px;
80
+ }
81
+ .play-pause {
82
+ font-size: 16px;
83
+ cursor: pointer;
84
+ background: none;
85
+ border: none;
86
+ padding: 0;
87
+ }
88
+ .scrub {
89
+ flex: 1;
90
+ }
91
+ .frame-label {
92
+ min-width: 4ch;
93
+ text-align: right;
94
+ }
95
+ </style>
@@ -0,0 +1,81 @@
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) { mouseX = e.clientX; mouseY = e.clientY }
14
+ window.addEventListener('mousemove', onMove)
15
+ return () => window.removeEventListener('mousemove', onMove)
16
+ })
17
+
18
+ const hovered = $derived(plotState.hovered)
19
+ const mode = $derived(plotState.mode)
20
+
21
+ function formatDefault(data) {
22
+ return Object.entries(data)
23
+ .filter(([, v]) => v !== undefined && v !== null)
24
+ .map(([k, v]) => `<div class="tt-row"><span class="tt-key">${k}</span><span class="tt-val">${v}</span></div>`)
25
+ .join('')
26
+ }
27
+
28
+ const content = $derived.by(() => {
29
+ if (!hovered) return ''
30
+ if (typeof tooltip === 'function') return String(tooltip(hovered) ?? '')
31
+ return formatDefault(hovered)
32
+ })
33
+ </script>
34
+
35
+ {#if hovered && content}
36
+ <div
37
+ class="plot-tooltip"
38
+ data-mode={mode}
39
+ style:left="{mouseX + 14}px"
40
+ style:top="{mouseY - 8}px"
41
+ >
42
+ {@html content}
43
+ </div>
44
+ {/if}
45
+
46
+ <style>
47
+ .plot-tooltip {
48
+ position: fixed;
49
+ pointer-events: none;
50
+ z-index: 1000;
51
+ padding: 8px 10px;
52
+ border-radius: 6px;
53
+ font-size: 12px;
54
+ line-height: 1.5;
55
+ white-space: nowrap;
56
+ box-shadow: 0 4px 12px rgb(0 0 0 / 0.12);
57
+ background: #ffffff;
58
+ border: 1px solid #e2e8f0;
59
+ color: #1e293b;
60
+ }
61
+
62
+ .plot-tooltip[data-mode="dark"] {
63
+ background: #1e293b;
64
+ border-color: #334155;
65
+ color: #f1f5f9;
66
+ }
67
+
68
+ :global(.tt-row) {
69
+ display: flex;
70
+ gap: 10px;
71
+ justify-content: space-between;
72
+ }
73
+
74
+ :global(.tt-key) {
75
+ opacity: 0.65;
76
+ }
77
+
78
+ :global(.tt-val) {
79
+ font-weight: 600;
80
+ }
81
+ </style>
package/src/Plot/index.js CHANGED
@@ -3,3 +3,7 @@ export { default as Axis } from './Axis.svelte'
3
3
  export { default as Bar } from './Bar.svelte'
4
4
  export { default as Legend } from './Legend.svelte'
5
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'