svelteplot 0.1.3-next.9 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/Mark.svelte +18 -2
- package/dist/Plot.svelte +45 -29
- package/dist/helpers/index.d.ts +2 -1
- package/dist/helpers/index.js +1 -0
- package/dist/helpers/resolve.js +7 -6
- package/dist/helpers/scales.d.ts +2 -2
- package/dist/helpers/scales.js +8 -5
- package/dist/helpers/typeChecks.js +14 -10
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/marks/BarX.svelte +15 -5
- package/dist/marks/BarY.svelte +20 -12
- package/dist/marks/BarY.svelte.d.ts +22 -1
- package/dist/marks/Brush.svelte +364 -0
- package/dist/marks/Brush.svelte.d.ts +32 -0
- package/dist/marks/BrushX.svelte +7 -0
- package/dist/marks/BrushX.svelte.d.ts +4 -0
- package/dist/marks/BrushY.svelte +7 -0
- package/dist/marks/BrushY.svelte.d.ts +4 -0
- package/dist/marks/Cell.svelte +0 -7
- package/dist/marks/ColorLegend.svelte +6 -10
- package/dist/marks/Dot.svelte +11 -20
- package/dist/marks/Dot.svelte.d.ts +8 -8
- package/dist/marks/Frame.svelte +10 -5
- package/dist/marks/Frame.svelte.d.ts +6 -1
- package/dist/marks/Geo.svelte +50 -41
- package/dist/marks/Geo.svelte.d.ts +3 -1
- package/dist/marks/GridX.svelte +17 -8
- package/dist/marks/GridY.svelte +17 -8
- package/dist/marks/Pointer.svelte +4 -3
- package/dist/marks/Pointer.svelte.d.ts +2 -2
- package/dist/marks/Rect.svelte +12 -19
- package/dist/marks/Sphere.svelte.d.ts +14 -4
- package/dist/marks/Text.svelte +2 -2
- package/dist/marks/Text.svelte.d.ts +2 -2
- package/dist/marks/helpers/CanvasLayer.svelte +10 -16
- package/dist/marks/helpers/CanvasLayer.svelte.d.ts +2 -6
- package/dist/marks/helpers/DotCanvas.svelte +82 -159
- package/dist/marks/helpers/DotCanvas.svelte.d.ts +2 -4
- package/dist/marks/helpers/GeoCanvas.svelte +95 -145
- package/dist/marks/helpers/GeoCanvas.svelte.d.ts +3 -5
- package/dist/marks/helpers/events.d.ts +13 -0
- package/dist/marks/helpers/events.js +32 -3
- package/dist/transforms/bin.d.ts +7 -7
- package/dist/transforms/recordize.d.ts +2 -0
- package/dist/transforms/recordize.js +20 -10
- package/dist/transforms/stack.js +10 -7
- package/dist/transforms/window.d.ts +2 -0
- package/dist/types.d.ts +34 -13
- package/package.json +23 -20
|
@@ -1,29 +1,30 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
PlotState,
|
|
4
|
+
Mark,
|
|
5
|
+
BaseMarkProps,
|
|
6
|
+
ScaledDataRecord,
|
|
7
|
+
PlotContext
|
|
8
|
+
} from '../../types.js';
|
|
3
9
|
import { CSS_VAR } from '../../constants.js';
|
|
4
|
-
import {
|
|
5
|
-
import { resolveChannel, resolveProp, resolveScaledStyleProps } from '../../helpers/resolve.js';
|
|
6
|
-
import { projectXY } from '../../helpers/scales.js';
|
|
10
|
+
import { resolveProp } from '../../helpers/resolve.js';
|
|
7
11
|
import { maybeSymbol } from '../../helpers/symbols.js';
|
|
8
12
|
import { symbol as d3Symbol } from 'd3-shape';
|
|
9
|
-
import {
|
|
10
|
-
import
|
|
13
|
+
import type { Attachment } from 'svelte/attachments';
|
|
14
|
+
import CanvasLayer from './CanvasLayer.svelte';
|
|
15
|
+
import { getContext } from 'svelte';
|
|
16
|
+
import { devicePixelRatio } from 'svelte/reactivity/window';
|
|
11
17
|
|
|
12
|
-
|
|
13
|
-
|
|
18
|
+
const { getPlotState } = getContext<PlotContext>('svelteplot');
|
|
19
|
+
const plot = $derived(getPlotState());
|
|
14
20
|
|
|
15
21
|
let {
|
|
16
22
|
mark,
|
|
17
|
-
|
|
18
|
-
data,
|
|
19
|
-
testFacet,
|
|
20
|
-
usedScales
|
|
23
|
+
data
|
|
21
24
|
}: {
|
|
22
25
|
mark: Mark<BaseMarkProps>;
|
|
23
26
|
plot: PlotState;
|
|
24
|
-
data:
|
|
25
|
-
testFacet: any;
|
|
26
|
-
usedScales: any;
|
|
27
|
+
data: ScaledDataRecord[];
|
|
27
28
|
} = $props();
|
|
28
29
|
|
|
29
30
|
function drawSymbolPath(symbolType: string, size: number, context) {
|
|
@@ -31,154 +32,76 @@
|
|
|
31
32
|
return d3Symbol(maybeSymbol(symbolType), size).context(context)();
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
function scaleHash(scale) {
|
|
35
|
-
return { domain: scale.domain, type: scale.type, range: scale.range };
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
let _plotSize = $state([plot.width, plot.height]);
|
|
39
|
-
let _usedScales = $state(usedScales);
|
|
40
35
|
let _markOptions = $state(mark.options);
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
const rScale = $derived(scaleHash(plot.scales.r));
|
|
44
|
-
let _xScale = $state(xScale);
|
|
45
|
-
let _yScale = $state(yScale);
|
|
46
|
-
let _rScale = $state(rScale);
|
|
47
|
-
|
|
48
|
-
const filteredData = $derived(
|
|
49
|
-
data.filter((datum) => testFilter(datum, _markOptions) && testFacet(datum, _markOptions))
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
let _filteredData: DataRecord[] = $state([]);
|
|
53
|
-
|
|
54
|
-
$effect(() => {
|
|
55
|
-
// update _usedScales only if changed
|
|
56
|
-
if (!isEqual(usedScales, _usedScales)) _usedScales = usedScales;
|
|
57
|
-
if (!isEqual(mark.options, _markOptions)) _markOptions = mark.options;
|
|
58
|
-
|
|
59
|
-
const plotSize = [plot.width, plot.height];
|
|
60
|
-
if (!isEqual(plotSize, _plotSize)) _plotSize = plotSize;
|
|
61
|
-
|
|
62
|
-
if (
|
|
63
|
-
_markOptions.filter
|
|
64
|
-
? !isEqual(filteredData, _filteredData)
|
|
65
|
-
: filteredData.length !== _filteredData.length
|
|
66
|
-
) {
|
|
67
|
-
_filteredData = filteredData;
|
|
68
|
-
}
|
|
69
|
-
if (!isEqual(xScale, _xScale)) _xScale = xScale;
|
|
70
|
-
if (!isEqual(yScale, _yScale)) _yScale = yScale;
|
|
71
|
-
if (!isEqual(rScale, _rScale)) _rScale = rScale;
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
$effect(() => {
|
|
75
|
-
// track plot size, since we're untracking the scales
|
|
76
|
-
_plotSize;
|
|
77
|
-
_markOptions;
|
|
78
|
-
_xScale;
|
|
79
|
-
_yScale;
|
|
80
|
-
_rScale;
|
|
81
|
-
const plotScales = untrack(() => plot.scales);
|
|
36
|
+
|
|
37
|
+
const renderDots: Attachment = (canvas: HTMLCanvasElement) => {
|
|
82
38
|
const context = canvas.getContext('2d');
|
|
83
|
-
if (context === null) return;
|
|
84
|
-
// this will re-run whenever `color` or `size` change
|
|
85
|
-
context.resetTransform();
|
|
86
|
-
context.scale(devicePixelRatio, devicePixelRatio);
|
|
87
|
-
|
|
88
|
-
for (const datum of _filteredData) {
|
|
89
|
-
// untrack the filter test to avoid redrawing when not necessary
|
|
90
|
-
const x = resolveChannel('x', datum, _markOptions);
|
|
91
|
-
const y = resolveChannel('y', datum, _markOptions);
|
|
92
|
-
const r = resolveChannel('r', datum, _markOptions) || 2;
|
|
93
|
-
const symbol_ = resolveChannel('symbol', datum, {
|
|
94
|
-
symbol: 'circle',
|
|
95
|
-
..._markOptions
|
|
96
|
-
});
|
|
97
|
-
const symbol = _usedScales.symbol ? plotScales.symbol.fn(symbol_) : symbol_;
|
|
98
|
-
|
|
99
|
-
if (isValid(x) && isValid(y) && isValid(r)) {
|
|
100
|
-
const [px, py] = projectXY(plotScales, x, y, true, true);
|
|
101
|
-
|
|
102
|
-
const r_ = _usedScales.r ? plotScales.r.fn(r) : r;
|
|
103
|
-
const size = r_ * r_ * Math.PI * devicePixelRatio;
|
|
104
|
-
let { stroke, strokeOpacity, fillOpacity, fill, opacity } = resolveScaledStyleProps(
|
|
105
|
-
datum,
|
|
106
|
-
_markOptions,
|
|
107
|
-
_usedScales,
|
|
108
|
-
untrack(() => plot),
|
|
109
|
-
'stroke'
|
|
110
|
-
);
|
|
111
39
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
40
|
+
$effect(() => {
|
|
41
|
+
if (context) {
|
|
42
|
+
context.resetTransform();
|
|
43
|
+
context.scale(devicePixelRatio.current ?? 1, devicePixelRatio.current ?? 1);
|
|
44
|
+
|
|
45
|
+
for (const datum of data) {
|
|
46
|
+
if (datum.valid) {
|
|
47
|
+
let { fill, stroke } = datum;
|
|
48
|
+
|
|
49
|
+
if (`${fill}`.toLowerCase() === 'currentcolor')
|
|
50
|
+
fill = getComputedStyle(
|
|
51
|
+
canvas?.parentElement?.parentElement
|
|
52
|
+
).getPropertyValue('color');
|
|
53
|
+
if (`${stroke}`.toLowerCase() === 'currentcolor')
|
|
54
|
+
stroke = getComputedStyle(
|
|
55
|
+
canvas?.parentElement?.parentElement
|
|
56
|
+
).getPropertyValue('color');
|
|
57
|
+
|
|
58
|
+
if (CSS_VAR.test(fill))
|
|
59
|
+
fill = getComputedStyle(canvas).getPropertyValue(fill.slice(4, -1));
|
|
60
|
+
if (CSS_VAR.test(stroke))
|
|
61
|
+
stroke = getComputedStyle(canvas).getPropertyValue(stroke.slice(4, -1));
|
|
62
|
+
|
|
63
|
+
if (stroke && stroke !== 'none') {
|
|
64
|
+
const strokeWidth = resolveProp(
|
|
65
|
+
_markOptions.strokeWidth,
|
|
66
|
+
datum.datum,
|
|
67
|
+
1.6
|
|
68
|
+
);
|
|
69
|
+
context.lineWidth = strokeWidth;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
context.fillStyle = fill ? fill : 'none';
|
|
73
|
+
context.strokeStyle = stroke ? stroke : 'none';
|
|
74
|
+
context.translate(datum.x, datum.y);
|
|
75
|
+
|
|
76
|
+
const size = datum.r * datum.r * Math.PI;
|
|
77
|
+
|
|
78
|
+
context.beginPath();
|
|
79
|
+
drawSymbolPath(datum.symbol, size, context);
|
|
80
|
+
context.closePath();
|
|
81
|
+
|
|
82
|
+
const { opacity = 1, fillOpacity = 1, strokeOpacity = 1 } = datum;
|
|
83
|
+
|
|
84
|
+
if (opacity != null) context.globalAlpha = opacity ?? 1;
|
|
85
|
+
if (fillOpacity != null) context.globalAlpha = (opacity ?? 1) * fillOpacity;
|
|
86
|
+
if (fill && fill !== 'none') context.fill();
|
|
87
|
+
if (strokeOpacity != null)
|
|
88
|
+
context.globalAlpha = (opacity ?? 1) * strokeOpacity;
|
|
89
|
+
if (stroke && stroke !== 'none') context.stroke();
|
|
90
|
+
context.translate(-datum.x, -datum.y);
|
|
91
|
+
}
|
|
128
92
|
}
|
|
129
|
-
context.fillStyle = fill ? fill : 'none';
|
|
130
|
-
context.strokeStyle = stroke ? stroke : 'none';
|
|
131
|
-
context.translate(px, py);
|
|
132
|
-
|
|
133
|
-
context.beginPath();
|
|
134
|
-
drawSymbolPath(symbol, size, context);
|
|
135
|
-
context.closePath();
|
|
136
|
-
|
|
137
|
-
if (opacity != null) context.globalAlpha = opacity ?? 1;
|
|
138
|
-
if (fillOpacity != null) context.globalAlpha = (opacity ?? 1) * fillOpacity;
|
|
139
|
-
if (fill && fill !== 'none') context.fill();
|
|
140
|
-
if (strokeOpacity != null) context.globalAlpha = (opacity ?? 1) * strokeOpacity;
|
|
141
|
-
if (stroke && stroke !== 'none') context.stroke();
|
|
142
|
-
context.translate(-px, -py);
|
|
143
93
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
remove();
|
|
156
|
-
}
|
|
157
|
-
const mqString = `(resolution: ${window.devicePixelRatio}dppx)`;
|
|
158
|
-
const media = matchMedia(mqString);
|
|
159
|
-
media.addEventListener('change', updatePixelRatio);
|
|
160
|
-
remove = () => {
|
|
161
|
-
media.removeEventListener('change', updatePixelRatio);
|
|
162
|
-
};
|
|
163
|
-
devicePixelRatio = window.devicePixelRatio;
|
|
164
|
-
}
|
|
165
|
-
$effect(() => {
|
|
166
|
-
updatePixelRatio();
|
|
167
|
-
});
|
|
94
|
+
|
|
95
|
+
return () => {
|
|
96
|
+
context?.clearRect(
|
|
97
|
+
0,
|
|
98
|
+
0,
|
|
99
|
+
plot.width * (devicePixelRatio.current ?? 1),
|
|
100
|
+
plot.height * (devicePixelRatio.current ?? 1)
|
|
101
|
+
);
|
|
102
|
+
};
|
|
103
|
+
});
|
|
104
|
+
};
|
|
168
105
|
</script>
|
|
169
106
|
|
|
170
|
-
<
|
|
171
|
-
<canvas
|
|
172
|
-
xmlns="http://www.w3.org/1999/xhtml"
|
|
173
|
-
bind:this={canvas}
|
|
174
|
-
width={plot.width * devicePixelRatio}
|
|
175
|
-
height={plot.height * devicePixelRatio}
|
|
176
|
-
style="width: {plot.width}px; height: {plot.height}px;"></canvas>
|
|
177
|
-
</foreignObject>
|
|
178
|
-
|
|
179
|
-
<style>
|
|
180
|
-
foreignObject,
|
|
181
|
-
canvas {
|
|
182
|
-
color: currentColor;
|
|
183
|
-
}
|
|
184
|
-
</style>
|
|
107
|
+
<CanvasLayer {@attach renderDots} />
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import type { PlotState, Mark,
|
|
1
|
+
import type { PlotState, Mark, BaseMarkProps, ScaledDataRecord } from '../../types.js';
|
|
2
2
|
type $$ComponentProps = {
|
|
3
3
|
mark: Mark<BaseMarkProps>;
|
|
4
4
|
plot: PlotState;
|
|
5
|
-
data:
|
|
6
|
-
testFacet: any;
|
|
7
|
-
usedScales: any;
|
|
5
|
+
data: ScaledDataRecord[];
|
|
8
6
|
};
|
|
9
7
|
declare const DotCanvas: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
10
8
|
type DotCanvas = ReturnType<typeof DotCanvas>;
|
|
@@ -1,165 +1,115 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
Mark,
|
|
4
|
+
BaseMarkProps,
|
|
5
|
+
PlotContext,
|
|
6
|
+
ScaledDataRecord,
|
|
7
|
+
UsedScales
|
|
8
|
+
} from '../../types.js';
|
|
3
9
|
import { CSS_VAR } from '../../constants.js';
|
|
4
|
-
import { testFilter } from '../../helpers/index.js';
|
|
5
10
|
import { resolveProp, resolveScaledStyleProps } from '../../helpers/resolve.js';
|
|
6
|
-
import { untrack } from 'svelte';
|
|
7
|
-
import { isEqual } from 'es-toolkit';
|
|
11
|
+
import { getContext, untrack } from 'svelte';
|
|
8
12
|
import { type GeoPath } from 'd3-geo';
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
13
|
+
import CanvasLayer from './CanvasLayer.svelte';
|
|
14
|
+
import type { Attachment } from 'svelte/attachments';
|
|
15
|
+
import { devicePixelRatio } from 'svelte/reactivity/window';
|
|
16
|
+
import { GEOJSON_PREFER_STROKE } from '../../helpers/index.js';
|
|
12
17
|
|
|
13
18
|
let {
|
|
14
19
|
mark,
|
|
15
|
-
plot,
|
|
16
20
|
data,
|
|
17
|
-
|
|
18
|
-
usedScales
|
|
19
|
-
path
|
|
21
|
+
path,
|
|
22
|
+
usedScales
|
|
20
23
|
}: {
|
|
21
24
|
mark: Mark<BaseMarkProps>;
|
|
22
|
-
|
|
23
|
-
data: DataRecord[];
|
|
24
|
-
testFacet: any;
|
|
25
|
-
usedScales: any;
|
|
25
|
+
data: ScaledDataRecord[];
|
|
26
26
|
path: GeoPath;
|
|
27
|
+
usedScales: UsedScales;
|
|
27
28
|
} = $props();
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
let _plotSize = $state([plot.width, plot.height]);
|
|
34
|
-
let _usedScales = $state(usedScales);
|
|
35
|
-
let _markOptions = $state(mark.options);
|
|
36
|
-
|
|
37
|
-
const filteredData = $derived(
|
|
38
|
-
data.filter((datum) => testFilter(datum, _markOptions) && testFacet(datum, _markOptions))
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
let _filteredData: DataRecord[] = $state([]);
|
|
42
|
-
|
|
43
|
-
$effect(() => {
|
|
44
|
-
// update _usedScales only if changed
|
|
45
|
-
if (!isEqual(usedScales, _usedScales)) _usedScales = usedScales;
|
|
46
|
-
if (!isEqual(mark.options, _markOptions)) _markOptions = mark.options;
|
|
30
|
+
const { getPlotState } = getContext<PlotContext>('svelteplot');
|
|
31
|
+
const plot = $derived(getPlotState());
|
|
47
32
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (
|
|
52
|
-
_markOptions.filter
|
|
53
|
-
? !isEqual(filteredData, _filteredData)
|
|
54
|
-
: filteredData.length !== _filteredData.length
|
|
55
|
-
) {
|
|
56
|
-
_filteredData = filteredData;
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
$effect(() => {
|
|
61
|
-
// track plot size, since we're untracking the scales
|
|
62
|
-
_plotSize;
|
|
63
|
-
_markOptions;
|
|
33
|
+
function maybeOpacity(value) {
|
|
34
|
+
return value == null ? 1 : +value;
|
|
35
|
+
}
|
|
64
36
|
|
|
65
|
-
|
|
37
|
+
const render: Attachment = (canvas: HTMLCanvasElement) => {
|
|
66
38
|
const context = canvas.getContext('2d');
|
|
67
|
-
if (context === null) return;
|
|
68
|
-
// this will re-run whenever `color` or `size` change
|
|
69
|
-
context.resetTransform();
|
|
70
|
-
context.scale(devicePixelRatio, devicePixelRatio);
|
|
71
|
-
|
|
72
|
-
let currentColor;
|
|
73
|
-
|
|
74
|
-
path.context(context);
|
|
75
|
-
|
|
76
|
-
const plot_ = untrack(() => plot);
|
|
77
39
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
40
|
+
$effect(() => {
|
|
41
|
+
path.context(context);
|
|
42
|
+
if (context) {
|
|
43
|
+
context.resetTransform();
|
|
44
|
+
context.scale(devicePixelRatio.current ?? 1, devicePixelRatio.current ?? 1);
|
|
45
|
+
let currentColor;
|
|
46
|
+
|
|
47
|
+
for (const d of data) {
|
|
48
|
+
if (!d.valid) continue;
|
|
49
|
+
const geometry = resolveProp(mark.options.geometry, d.datum, d.datum);
|
|
50
|
+
// untrack the filter test to avoid redrawing when not necessary
|
|
51
|
+
let { stroke, fill, ...restStyles } = resolveScaledStyleProps(
|
|
52
|
+
d.datum,
|
|
53
|
+
mark.options,
|
|
54
|
+
usedScales,
|
|
55
|
+
plot,
|
|
56
|
+
GEOJSON_PREFER_STROKE.has(geometry.type) ? 'stroke' : 'fill'
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const opacity = maybeOpacity(restStyles['opacity']);
|
|
60
|
+
const fillOpacity = maybeOpacity(restStyles['fill-opacity']);
|
|
61
|
+
const strokeOpacity = maybeOpacity(restStyles['stroke-opacity']);
|
|
62
|
+
|
|
63
|
+
if (`${fill}`.toLowerCase() === 'currentcolor')
|
|
64
|
+
fill =
|
|
65
|
+
currentColor ||
|
|
66
|
+
(currentColor = getComputedStyle(
|
|
67
|
+
canvas?.parentElement?.parentElement
|
|
68
|
+
).getPropertyValue('color'));
|
|
69
|
+
if (`${stroke}`.toLowerCase() === 'currentcolor')
|
|
70
|
+
stroke =
|
|
71
|
+
currentColor ||
|
|
72
|
+
(currentColor = getComputedStyle(
|
|
73
|
+
canvas?.parentElement?.parentElement
|
|
74
|
+
).getPropertyValue('color'));
|
|
75
|
+
if (CSS_VAR.test(fill))
|
|
76
|
+
fill = getComputedStyle(canvas).getPropertyValue(fill.slice(4, -1));
|
|
77
|
+
if (CSS_VAR.test(stroke))
|
|
78
|
+
stroke = getComputedStyle(canvas).getPropertyValue(stroke.slice(4, -1));
|
|
79
|
+
|
|
80
|
+
if (stroke && stroke !== 'none') {
|
|
81
|
+
const strokeWidth = resolveProp(mark.options.strokeWidth, d.datum, 1);
|
|
82
|
+
context.lineWidth = strokeWidth ?? 1;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
context.fillStyle = fill ? fill : 'none';
|
|
86
|
+
context.strokeStyle = stroke ? stroke : 'none';
|
|
87
|
+
context.lineJoin = 'round';
|
|
88
|
+
context.beginPath();
|
|
89
|
+
|
|
90
|
+
path(geometry);
|
|
91
|
+
context.closePath();
|
|
92
|
+
|
|
93
|
+
if (opacity != null) context.globalAlpha = opacity;
|
|
94
|
+
if (fillOpacity != null) context.globalAlpha = opacity * fillOpacity;
|
|
95
|
+
|
|
96
|
+
if (fill && fill !== 'none') context.fill();
|
|
97
|
+
if (strokeOpacity != null) context.globalAlpha = opacity * strokeOpacity;
|
|
98
|
+
if (stroke && stroke !== 'none') context.stroke();
|
|
99
|
+
}
|
|
111
100
|
}
|
|
112
|
-
context
|
|
113
|
-
context
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if (stroke && stroke !== 'none') context.stroke();
|
|
125
|
-
}
|
|
126
|
-
return () => {
|
|
127
|
-
canvas?.getContext('2d')?.clearRect(0, 0, canvas?.width, canvas?.height);
|
|
128
|
-
};
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
// code from https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio
|
|
132
|
-
let remove: null | (() => void) = null;
|
|
133
|
-
|
|
134
|
-
function updatePixelRatio() {
|
|
135
|
-
if (remove != null) {
|
|
136
|
-
remove();
|
|
137
|
-
}
|
|
138
|
-
const mqString = `(resolution: ${window.devicePixelRatio}dppx)`;
|
|
139
|
-
const media = matchMedia(mqString);
|
|
140
|
-
media.addEventListener('change', updatePixelRatio);
|
|
141
|
-
remove = () => {
|
|
142
|
-
media.removeEventListener('change', updatePixelRatio);
|
|
143
|
-
};
|
|
144
|
-
devicePixelRatio = window.devicePixelRatio;
|
|
145
|
-
}
|
|
146
|
-
$effect(() => {
|
|
147
|
-
updatePixelRatio();
|
|
148
|
-
});
|
|
101
|
+
// reset path context in case we switch back to SVG
|
|
102
|
+
path.context(null);
|
|
103
|
+
return () => {
|
|
104
|
+
context?.clearRect(
|
|
105
|
+
0,
|
|
106
|
+
0,
|
|
107
|
+
plot.width * (devicePixelRatio.current ?? 1),
|
|
108
|
+
plot.height * (devicePixelRatio.current ?? 1)
|
|
109
|
+
);
|
|
110
|
+
};
|
|
111
|
+
});
|
|
112
|
+
};
|
|
149
113
|
</script>
|
|
150
114
|
|
|
151
|
-
<
|
|
152
|
-
<canvas
|
|
153
|
-
xmlns="http://www.w3.org/1999/xhtml"
|
|
154
|
-
bind:this={canvas}
|
|
155
|
-
width={plot.width * devicePixelRatio}
|
|
156
|
-
height={plot.height * devicePixelRatio}
|
|
157
|
-
style="width: {plot.width}px; height: {plot.height}px;"></canvas>
|
|
158
|
-
</foreignObject>
|
|
159
|
-
|
|
160
|
-
<style>
|
|
161
|
-
foreignObject,
|
|
162
|
-
canvas {
|
|
163
|
-
color: currentColor;
|
|
164
|
-
}
|
|
165
|
-
</style>
|
|
115
|
+
<CanvasLayer {@attach render} />
|
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Mark, BaseMarkProps, ScaledDataRecord, UsedScales } from '../../types.js';
|
|
2
2
|
import { type GeoPath } from 'd3-geo';
|
|
3
3
|
type $$ComponentProps = {
|
|
4
4
|
mark: Mark<BaseMarkProps>;
|
|
5
|
-
|
|
6
|
-
data: DataRecord[];
|
|
7
|
-
testFacet: any;
|
|
8
|
-
usedScales: any;
|
|
5
|
+
data: ScaledDataRecord[];
|
|
9
6
|
path: GeoPath;
|
|
7
|
+
usedScales: UsedScales;
|
|
10
8
|
};
|
|
11
9
|
declare const GeoCanvas: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
12
10
|
type GeoCanvas = ReturnType<typeof GeoCanvas>;
|
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
import type { BaseMarkProps, DataRecord, PlotState } from '../../types.js';
|
|
2
|
+
declare global {
|
|
3
|
+
interface MouseEvent {
|
|
4
|
+
layerX?: number;
|
|
5
|
+
layerY?: number;
|
|
6
|
+
dataX?: number | string | Date;
|
|
7
|
+
dataY?: number | string | Date;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Translates client coordinates (clientX, clientY) to the layer coordinates
|
|
12
|
+
* of the plot frame, regardless of which element triggered the event
|
|
13
|
+
*/
|
|
14
|
+
export declare function clientToLayerCoordinates(event: MouseEvent, plotBody: HTMLElement | null | undefined): [number, number];
|
|
2
15
|
export declare function addEventHandlers(node: SVGElement, { options, datum, getPlotState }: {
|
|
3
16
|
options: BaseMarkProps;
|
|
4
17
|
datum: DataRecord;
|
|
@@ -1,6 +1,27 @@
|
|
|
1
1
|
import { invert, pick } from 'es-toolkit';
|
|
2
2
|
import { RAW_VALUE } from '../../transforms/recordize.js';
|
|
3
3
|
import { INDEX } from '../../constants.js';
|
|
4
|
+
/**
|
|
5
|
+
* Translates client coordinates (clientX, clientY) to the layer coordinates
|
|
6
|
+
* of the plot frame, regardless of which element triggered the event
|
|
7
|
+
*/
|
|
8
|
+
export function clientToLayerCoordinates(event, plotBody) {
|
|
9
|
+
// If layerX/Y already exist and the target is the plot frame (rect element),
|
|
10
|
+
// we can use them directly
|
|
11
|
+
// if (event.layerX !== undefined && (event.target as SVGElement).tagName === 'rect') {
|
|
12
|
+
// return [event.layerX, event.layerY];
|
|
13
|
+
// }
|
|
14
|
+
// Otherwise, transform from client coordinates to layer coordinates
|
|
15
|
+
// by getting the bounds of the plot body element and calculating the offset
|
|
16
|
+
if (!plotBody)
|
|
17
|
+
return [0, 0];
|
|
18
|
+
const plotBodyRect = plotBody.getBoundingClientRect();
|
|
19
|
+
// Calculate the coordinates relative to the plot body
|
|
20
|
+
return [
|
|
21
|
+
event.clientX - plotBodyRect.left,
|
|
22
|
+
event.clientY - plotBodyRect.top
|
|
23
|
+
];
|
|
24
|
+
}
|
|
4
25
|
export function addEventHandlers(node, { options, datum, getPlotState }) {
|
|
5
26
|
const events = pick(options, [
|
|
6
27
|
'onclick',
|
|
@@ -18,11 +39,20 @@ export function addEventHandlers(node, { options, datum, getPlotState }) {
|
|
|
18
39
|
'onmouseleave',
|
|
19
40
|
'onmousemove',
|
|
20
41
|
'onmouseout',
|
|
42
|
+
'onmouseover',
|
|
21
43
|
'onmouseup',
|
|
22
|
-
'
|
|
44
|
+
'onpointercancel',
|
|
45
|
+
'onpointerdown',
|
|
46
|
+
'onpointerenter',
|
|
47
|
+
'onpointerleave',
|
|
48
|
+
'onpointermove',
|
|
49
|
+
'onpointerout',
|
|
50
|
+
'onpointerover',
|
|
51
|
+
'onpointerup',
|
|
23
52
|
'ontouchcancel',
|
|
24
53
|
'ontouchend',
|
|
25
|
-
'ontouchmove'
|
|
54
|
+
'ontouchmove',
|
|
55
|
+
'onwheel',
|
|
26
56
|
]);
|
|
27
57
|
const listeners = new Map();
|
|
28
58
|
// attach event handlers
|
|
@@ -66,7 +96,6 @@ function invertScale(scale, position) {
|
|
|
66
96
|
if (scale.type === 'band') {
|
|
67
97
|
// invert band scale since scaleBand doesn't have an invert function
|
|
68
98
|
const eachBand = scale.fn.step();
|
|
69
|
-
console.log({ eachBand, position });
|
|
70
99
|
const index = Math.floor(position / eachBand);
|
|
71
100
|
return scale.fn.domain()[index];
|
|
72
101
|
}
|
package/dist/transforms/bin.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { DataRecord, RawValue } from '../types.js';
|
|
2
2
|
import type { TransformArg } from '../types.js';
|
|
3
3
|
import { type ThresholdCountGenerator } from 'd3-array';
|
|
4
|
-
import {
|
|
4
|
+
import { type ReducerName } from '../helpers/reduce.js';
|
|
5
5
|
type NamedThresholdsGenerator = 'auto' | 'scott' | 'sturges' | 'freedman-diaconis';
|
|
6
6
|
type BinBaseOptions = {
|
|
7
7
|
domain?: [number, number];
|
|
@@ -20,14 +20,14 @@ type AdditionalOutputChannels = Partial<{
|
|
|
20
20
|
strokeOpacity: ReducerOption;
|
|
21
21
|
}>;
|
|
22
22
|
export type BinXOptions = BinBaseOptions & AdditionalOutputChannels & Partial<{
|
|
23
|
-
y:
|
|
24
|
-
y1:
|
|
25
|
-
y2:
|
|
23
|
+
y: ReducerOption;
|
|
24
|
+
y1: ReducerOption;
|
|
25
|
+
y2: ReducerOption;
|
|
26
26
|
}>;
|
|
27
27
|
export type BinYOptions = BinBaseOptions & AdditionalOutputChannels & Partial<{
|
|
28
|
-
x:
|
|
29
|
-
x1:
|
|
30
|
-
x2:
|
|
28
|
+
x: ReducerOption;
|
|
29
|
+
x1: ReducerOption;
|
|
30
|
+
x2: ReducerOption;
|
|
31
31
|
}>;
|
|
32
32
|
type BinOptions = BinBaseOptions & AdditionalOutputChannels;
|
|
33
33
|
/**
|