@rokkit/chart 1.0.0-next.121 → 1.0.0-next.123

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 (36) hide show
  1. package/README.md +43 -35
  2. package/package.json +3 -3
  3. package/src/Plot/Axis.svelte +87 -95
  4. package/src/Plot/Bar.svelte +86 -86
  5. package/src/Plot/Grid.svelte +57 -57
  6. package/src/Plot/Legend.svelte +120 -122
  7. package/src/Plot/Root.svelte +107 -105
  8. package/src/elements/ColorRamp.svelte +19 -19
  9. package/src/elements/ContinuousLegend.svelte +1 -1
  10. package/src/elements/DefinePatterns.svelte +1 -1
  11. package/src/elements/DiscreteLegend.svelte +1 -1
  12. package/src/elements/SymbolGrid.svelte +1 -1
  13. package/src/examples/BarChartExample.svelte +42 -42
  14. package/src/index.js +13 -13
  15. package/src/lib/brewing/axes.svelte.js +120 -122
  16. package/src/lib/brewing/bars.svelte.js +70 -70
  17. package/src/lib/brewing/dimensions.svelte.js +25 -23
  18. package/src/lib/brewing/index.svelte.js +192 -204
  19. package/src/lib/brewing/legends.svelte.js +51 -52
  20. package/src/lib/brewing/scales.svelte.js +59 -68
  21. package/src/lib/brewing/types.js +2 -2
  22. package/src/lib/context.js +117 -107
  23. package/src/lib/scales.svelte.js +80 -87
  24. package/src/lib/utils.js +70 -72
  25. package/src/old_lib/chart.js +4 -4
  26. package/src/patterns/Brick.svelte +1 -1
  27. package/src/patterns/Circles.svelte +1 -1
  28. package/src/patterns/CrossHatch.svelte +1 -1
  29. package/src/patterns/Dots.svelte +1 -1
  30. package/src/patterns/OutlineCircles.svelte +1 -1
  31. package/src/patterns/Tile.svelte +1 -1
  32. package/src/patterns/Triangles.svelte +1 -1
  33. package/src/symbols/RoundedSquare.svelte +0 -1
  34. package/src/template/shapes/Circles.svelte +1 -1
  35. package/src/template/shapes/Lines.svelte +1 -1
  36. package/src/template/shapes/Polygons.svelte +1 -1
@@ -1,129 +1,127 @@
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(brewer.createLegend({
19
- title,
20
- align,
21
- shape,
22
- markerSize
23
- }));
24
-
25
- // Track selected items (for filtering)
26
- let selectedItems = $state([]);
27
-
28
- function toggleItem(item) {
29
- if (!onClick) return;
30
-
31
- const isSelected = selectedItems.includes(item.value);
32
-
33
- if (isSelected) {
34
- selectedItems = selectedItems.filter(v => v !== item.value);
35
- } else {
36
- selectedItems = [...selectedItems, item.value];
37
- }
38
-
39
- onClick(selectedItems);
40
- }
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
+ }
41
43
  </script>
42
44
 
43
45
  {#if legendData.items.length > 0}
44
- <g
45
- class="chart-legend"
46
- transform={legendData.transform}
47
- data-plot-legend
48
- >
49
- <!-- Legend title -->
50
- {#if legendData.title}
51
- <text
52
- class="legend-title"
53
- x="0"
54
- y="-6"
55
- text-anchor={align === 'left' ? 'start' : align === 'right' ? 'end' : 'middle'}
56
- data-plot-legend-title
57
- >
58
- {legendData.title}
59
- </text>
60
- {/if}
61
-
62
- <!-- Legend items -->
63
- {#each legendData.items as item}
64
- {@const attrs = createLegendItemAttributes(item)}
65
- {@const isSelected = selectedItems.includes(item.value)}
66
- <g
67
- {...attrs}
68
- class="legend-item"
69
- class:selected={isSelected}
70
- on:click={() => toggleItem(item)}
71
- >
72
- <!-- Shape: circle or rect -->
73
- {#if item.shape === 'circle'}
74
- <circle
75
- cx={item.markerSize / 2}
76
- cy={item.markerSize / 2}
77
- r={item.markerSize / 2}
78
- fill={item.color}
79
- stroke={isSelected ? 'currentColor' : 'none'}
80
- stroke-width={isSelected ? 1 : 0}
81
- data-plot-legend-marker="circle"
82
- />
83
- {:else}
84
- <rect
85
- width={item.markerSize}
86
- height={item.markerSize}
87
- fill={item.color}
88
- stroke={isSelected ? 'currentColor' : 'none'}
89
- stroke-width={isSelected ? 1 : 0}
90
- data-plot-legend-marker="rect"
91
- />
92
- {/if}
93
-
94
- <!-- Text label -->
95
- <text
96
- x={item.markerSize + 5}
97
- y={item.markerSize - 2}
98
- text-anchor="start"
99
- data-plot-legend-label
100
- >
101
- {item.value}
102
- </text>
103
- </g>
104
- {/each}
105
- </g>
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>
106
104
  {/if}
107
105
 
108
106
  <style>
109
- .chart-legend {
110
- font-size: 12px;
111
- }
112
-
113
- .legend-title {
114
- font-weight: bold;
115
- font-size: 14px;
116
- }
117
-
118
- .legend-item {
119
- cursor: pointer;
120
- }
121
-
122
- .legend-item:hover [data-plot-legend-label] {
123
- font-weight: 500;
124
- }
125
-
126
- .legend-item.selected [data-plot-legend-label] {
127
- font-weight: 700;
128
- }
129
- </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
+ }
127
+ </style>
@@ -1,112 +1,114 @@
1
1
  <script>
2
- import { setContext } from 'svelte';
3
- import { ChartBrewer } from '../lib/brewing/index.svelte.js';
4
-
5
- let {
6
- data = [],
7
- width = 600,
8
- height = 400,
9
- margin = { top: 20, right: 30, bottom: 40, left: 50 },
10
- fill = null,
11
- responsive = true,
12
- animationDuration = 300
13
- } = $props();
14
-
15
- // Create chart brewer instance
16
- let brewer = $state(new ChartBrewer({
17
- width,
18
- height,
19
- margin,
20
- animationDuration
21
- }));
22
-
23
- // Chart dimensions derived from brewer
24
- let dimensions = $derived(brewer.getDimensions());
25
-
26
- // Process data
27
- $effect(() => {
28
- // If data has a select method (dataset object), call it to get actual data
29
- const chartData = data.select && typeof data.select === 'function' ? data.select() : data;
30
-
31
- // Update brewer with data and fields
32
- brewer.setData(chartData);
33
- brewer.setFields({ color: fill });
34
-
35
- // Create scales after setting data
36
- brewer.createScales();
37
- });
38
-
39
- // Update chart dimensions when props change
40
- $effect(() => {
41
- brewer.setDimensions({ width, height, margin });
42
- });
43
-
44
- // Provide chart context to child components
45
- setContext('chart-brewer', brewer);
46
-
47
- // Handle responsive behavior
48
- let container;
49
-
50
- $effect(() => {
51
- if (!responsive || !container || !document) return;
52
-
53
- const resizeObserver = new ResizeObserver(entries => {
54
- const entry = entries[0];
55
- if (!entry) return;
56
-
57
- const containerWidth = entry.contentRect.width;
58
- const aspectRatio = height / width;
59
-
60
- // Update chart dimensions while maintaining aspect ratio
61
- brewer.setDimensions({
62
- width: containerWidth,
63
- height: containerWidth * aspectRatio
64
- });
65
-
66
- // Update scales after dimensions change
67
- brewer.createScales();
68
- });
69
-
70
- // Start observing container size
71
- resizeObserver.observe(container);
72
-
73
- return () => {
74
- resizeObserver.disconnect();
75
- };
76
- });
2
+ import { setContext } from 'svelte'
3
+ import { ChartBrewer } from '../lib/brewing/index.svelte.js'
4
+
5
+ let {
6
+ data = [],
7
+ width = 600,
8
+ height = 400,
9
+ margin = { top: 20, right: 30, bottom: 40, left: 50 },
10
+ fill = null,
11
+ responsive = true,
12
+ animationDuration = 300
13
+ } = $props()
14
+
15
+ // Create chart brewer instance
16
+ let brewer = $state(
17
+ new ChartBrewer({
18
+ width,
19
+ height,
20
+ margin,
21
+ animationDuration
22
+ })
23
+ )
24
+
25
+ // Chart dimensions derived from brewer
26
+ let dimensions = $derived(brewer.getDimensions())
27
+
28
+ // Process data
29
+ $effect(() => {
30
+ // If data has a select method (dataset object), call it to get actual data
31
+ const chartData = data.select && typeof data.select === 'function' ? data.select() : data
32
+
33
+ // Update brewer with data and fields
34
+ brewer.setData(chartData)
35
+ brewer.setFields({ color: fill })
36
+
37
+ // Create scales after setting data
38
+ brewer.createScales()
39
+ })
40
+
41
+ // Update chart dimensions when props change
42
+ $effect(() => {
43
+ brewer.setDimensions({ width, height, margin })
44
+ })
45
+
46
+ // Provide chart context to child components
47
+ setContext('chart-brewer', brewer)
48
+
49
+ // Handle responsive behavior
50
+ let container
51
+
52
+ $effect(() => {
53
+ if (!responsive || !container || !document) return
54
+
55
+ const resizeObserver = new ResizeObserver((entries) => {
56
+ const entry = entries[0]
57
+ if (!entry) return
58
+
59
+ const containerWidth = entry.contentRect.width
60
+ const aspectRatio = height / width
61
+
62
+ // Update chart dimensions while maintaining aspect ratio
63
+ brewer.setDimensions({
64
+ width: containerWidth,
65
+ height: containerWidth * aspectRatio
66
+ })
67
+
68
+ // Update scales after dimensions change
69
+ brewer.createScales()
70
+ })
71
+
72
+ // Start observing container size
73
+ resizeObserver.observe(container)
74
+
75
+ return () => {
76
+ resizeObserver.disconnect()
77
+ }
78
+ })
77
79
  </script>
78
80
 
79
81
  <div class="chart-container" bind:this={container} data-plot-root>
80
- <svg
81
- width={dimensions.width}
82
- height={dimensions.height}
83
- viewBox="0 0 {dimensions.width} {dimensions.height}"
84
- role="img"
85
- aria-label="Chart visualization"
86
- >
87
- <g
88
- class="chart-area"
89
- transform="translate({dimensions.margin.left}, {dimensions.margin.top})"
90
- data-plot-canvas
91
- >
92
- <slot />
93
- </g>
94
- </svg>
82
+ <svg
83
+ width={dimensions.width}
84
+ height={dimensions.height}
85
+ viewBox="0 0 {dimensions.width} {dimensions.height}"
86
+ role="img"
87
+ aria-label="Chart visualization"
88
+ >
89
+ <g
90
+ class="chart-area"
91
+ transform="translate({dimensions.margin.left}, {dimensions.margin.top})"
92
+ data-plot-canvas
93
+ >
94
+ <slot />
95
+ </g>
96
+ </svg>
95
97
  </div>
96
98
 
97
99
  <style>
98
- .chart-container {
99
- position: relative;
100
- width: 100%;
101
- height: auto;
102
- }
103
-
104
- svg {
105
- display: block;
106
- overflow: visible;
107
- }
108
-
109
- .chart-area {
110
- pointer-events: all;
111
- }
112
- </style>
100
+ .chart-container {
101
+ position: relative;
102
+ width: 100%;
103
+ height: auto;
104
+ }
105
+
106
+ svg {
107
+ display: block;
108
+ overflow: visible;
109
+ }
110
+
111
+ .chart-area {
112
+ pointer-events: all;
113
+ }
114
+ </style>
@@ -2,36 +2,36 @@
2
2
  import { scaleLinear } from 'd3-scale'
3
3
  import { id as uniqueId } from '@rokkit/core'
4
4
 
5
- export let x = 0
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
- $: scaleTicks = scaleLinear()
14
- .range([x, x + width])
15
- .domain(scale.domain())
16
- $: scalePercent = scaleLinear().range([0, 100]).domain(scale.domain())
17
- $: ticks = scale.ticks.apply(scale, [tickCount]).map((d) => ({ x: scaleTicks(d), value: d }))
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
+ )
18
16
 
19
- $: colors = ticks.map(({ value }) => ({
20
- color: scale(value),
21
- offset: `${scalePercent(value)}%`
22
- }))
23
- $: id = uniqueId('legend-')
17
+ let colors = $derived(
18
+ ticks.map(({ value }) => ({
19
+ color: scale(value),
20
+ offset: `${scalePercent(value)}%`
21
+ }))
22
+ )
23
+ let id = $state(uniqueId('legend-'))
24
24
  </script>
25
25
 
26
26
  <defs>
27
27
  <linearGradient {id}>
28
- {#each colors as { color, offset }}
28
+ {#each colors as { color, offset }, index (index)}
29
29
  <stop stop-color={color} {offset} />
30
30
  {/each}
31
31
  </linearGradient>
32
32
  </defs>
33
33
  <rect {x} y={y + height} {width} {height} fill="url(#{id})" />
34
- {#each ticks as { x, value }}
34
+ {#each ticks as { x, value }, index (index)}
35
35
  <line x1={x} y1={y + (2 * height) / 3} x2={x} y2={y + height * 2} />
36
36
  <text {x} y={y + height / 2} font-size={textSize}>{value}</text>
37
37
  {/each}
@@ -27,7 +27,7 @@
27
27
  </linearGradient>
28
28
  </defs>
29
29
  <rect {x} y={y + height} {width} {height} fill="url(#{id})" />
30
- {#each ticks as { x, label }}
30
+ {#each ticks as { x, label }, index (index)}
31
31
  <line x1={x} y1={y + (2 * height) / 3} x2={x} y2={y + height * 2} />
32
32
  <text {x} y={y + height / 2} font-size={textSize}>{label}</text>
33
33
  {/each}
@@ -13,7 +13,7 @@
13
13
  <error> Patterns should be an array and should have unique names for each pattern </error>
14
14
  {:else if patterns.length > 0}
15
15
  <defs>
16
- {#each patterns as { id, component, fill, stroke }}
16
+ {#each patterns as { id, component, fill, stroke }, index (index)}
17
17
  <pattern {id} {patternUnits} width={size} height={size}>
18
18
  <svelte:component this={component} {size} {fill} {stroke} />
19
19
  </pattern>
@@ -12,7 +12,7 @@
12
12
  $: ticks = scale.ticks.apply(scale, [tickCount])
13
13
  </script>
14
14
 
15
- {#each ticks as tick, i}
15
+ {#each ticks as tick, i (i)}
16
16
  <text x={x + padding + i * sizeWithSpace + size / 2} y={y + size / 2} font-size={textSize}
17
17
  >{tick}</text
18
18
  >
@@ -10,7 +10,7 @@
10
10
  </script>
11
11
 
12
12
  <svg viewBox="0 0 {grid.width} {grid.height}">
13
- {#each grid.data as { x, y, r }, index}
13
+ {#each grid.data as { x, y, r }, index (index)}
14
14
  <Symbol
15
15
  {x}
16
16
  {y}