@fundar/data-chart-telling 0.0.14 → 0.0.16
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 +121 -17
- package/dist/charts/{Chart.svelte → BaseChart.svelte} +2 -0
- package/dist/charts/{Chart.svelte.d.ts → BaseChart.svelte.d.ts} +3 -3
- package/dist/charts/bar/Chart.svelte +5 -5
- package/dist/charts/bar/Chart.svelte.d.ts +1 -2
- package/dist/charts/heatmap/Chart.svelte +8 -7
- package/dist/charts/heatmap/Chart.svelte.d.ts +2 -3
- package/dist/charts/line/Chart.svelte +5 -5
- package/dist/charts/line/Chart.svelte.d.ts +1 -2
- package/dist/charts/pyramid/Chart.svelte +7 -6
- package/dist/charts/pyramid/Chart.svelte.d.ts +2 -3
- package/dist/configuration/config.svelte.js +2 -1
- package/dist/configuration/themes/index.d.ts +60 -40
- package/dist/index.d.ts +27 -13
- package/dist/index.js +12 -7
- package/dist/layout/facet/FacetLayout.svelte +1 -1
- package/dist/layout/facet/FacetLayout.svelte.d.ts +1 -1
- package/dist/layout/geo/GeoLayout.svelte +47 -0
- package/dist/layout/geo/GeoLayout.svelte.d.ts +22 -0
- package/dist/layout/geo/resolveProjection.d.ts +10 -0
- package/dist/layout/geo/resolveProjection.js +48 -0
- package/dist/layout/legend/ContinuousSection.svelte +158 -17
- package/dist/layout/legend/DiscreteSection.svelte +56 -4
- package/dist/layout/legend/interaction.svelte.d.ts +7 -0
- package/dist/layout/legend/interaction.svelte.js +24 -0
- package/dist/layout/plot/AxisLayout.svelte +95 -0
- package/dist/layout/plot/AxisLayout.svelte.d.ts +39 -0
- package/dist/layout/plot/BasePlotLayout.svelte +302 -0
- package/dist/layout/plot/BasePlotLayout.svelte.d.ts +95 -0
- package/dist/layout/plot/GridLayout.svelte +30 -0
- package/dist/layout/plot/GridLayout.svelte.d.ts +27 -0
- package/dist/layout/plot/RuleLayout.svelte +111 -0
- package/dist/layout/plot/RuleLayout.svelte.d.ts +40 -0
- package/dist/layout/plot/margins.d.ts +34 -0
- package/dist/layout/plot/margins.js +26 -0
- package/dist/layout/timeline/TimelineLayout.svelte +1 -1
- package/dist/layout/timeline/TimelineLayout.svelte.d.ts +1 -1
- package/dist/layout/tooltip/Tooltip.svelte +15 -0
- package/dist/layout/tooltip/Tooltip.svelte.d.ts +7 -0
- package/dist/layout/tooltip/TooltipLayout.svelte +45 -0
- package/dist/layout/tooltip/TooltipLayout.svelte.d.ts +41 -0
- package/dist/layout/tooltip/controller.svelte.d.ts +4 -3
- package/dist/layout/tooltip/controller.svelte.js +5 -6
- package/dist/layout/tooltip/hover.svelte.d.ts +1 -1
- package/dist/layout/tooltip/hover.svelte.js +1 -1
- package/dist/layout/tooltip/utils.d.ts +2 -2
- package/dist/markers/ContourClipMarker.svelte +77 -0
- package/dist/markers/ContourClipMarker.svelte.d.ts +10 -0
- package/dist/{plots/markers → markers}/DeltaMarker.svelte +4 -4
- package/dist/{plots/markers → markers}/DeltaMarker.svelte.d.ts +3 -3
- package/dist/{plots/markers → markers}/DotMarker.svelte +3 -3
- package/dist/{plots/markers → markers}/DotMarker.svelte.d.ts +3 -3
- package/dist/markers/HoverMarker.svelte +136 -0
- package/dist/{plots/markers → markers}/HoverMarker.svelte.d.ts +4 -4
- package/dist/{plots/markers → markers}/PeakMarker.svelte +2 -2
- package/dist/{plots/markers → markers}/PeakMarker.svelte.d.ts +2 -2
- package/dist/{plots/markers → markers}/TextMarker.svelte +11 -4
- package/dist/{plots/markers → markers}/TextMarker.svelte.d.ts +3 -3
- package/dist/markers/inset/InsetMarker.svelte +170 -0
- package/dist/markers/inset/InsetMarker.svelte.d.ts +51 -0
- package/dist/markers/inset/insetLayout.d.ts +25 -0
- package/dist/markers/inset/insetLayout.js +39 -0
- package/dist/markers/inset/insetProjection.d.ts +8 -0
- package/dist/markers/inset/insetProjection.js +39 -0
- package/dist/plots/bar/BarSegments.svelte +69 -0
- package/dist/plots/bar/BarSegments.svelte.d.ts +40 -0
- package/dist/plots/bar/Plot.svelte +140 -74
- package/dist/plots/bar/Plot.svelte.d.ts +7 -3
- package/dist/plots/bar/ValueLabels.svelte +89 -0
- package/dist/plots/bar/ValueLabels.svelte.d.ts +39 -0
- package/dist/plots/bar/hoverPoints.d.ts +15 -0
- package/dist/plots/bar/hoverPoints.js +35 -0
- package/dist/plots/bar/layout.svelte.d.ts +37 -0
- package/dist/plots/bar/layout.svelte.js +132 -0
- package/dist/plots/geo/Plot.svelte +577 -0
- package/dist/plots/geo/Plot.svelte.d.ts +29 -0
- package/dist/plots/geo/TileLayer.svelte +77 -0
- package/dist/plots/geo/TileLayer.svelte.d.ts +11 -0
- package/dist/plots/geo/projections/argentina.d.ts +8 -0
- package/dist/plots/geo/projections/argentina.js +35 -0
- package/dist/plots/geo/projections/fit.d.ts +8 -0
- package/dist/plots/geo/projections/fit.js +14 -0
- package/dist/plots/geo/rotate.svelte.d.ts +26 -0
- package/dist/plots/geo/rotate.svelte.js +79 -0
- package/dist/plots/geo/zoom.svelte.d.ts +32 -0
- package/dist/plots/geo/zoom.svelte.js +50 -0
- package/dist/plots/heatmap/Plot.svelte +171 -58
- package/dist/plots/heatmap/Plot.svelte.d.ts +7 -3
- package/dist/plots/line/Plot.svelte +67 -30
- package/dist/plots/line/Plot.svelte.d.ts +7 -3
- package/dist/plots/pyramid/Plot.svelte +88 -35
- package/dist/plots/pyramid/Plot.svelte.d.ts +7 -3
- package/dist/plots/utils/delta.d.ts +1 -1
- package/dist/plots/utils/geoAccessors.d.ts +64 -0
- package/dist/plots/utils/geoAccessors.js +188 -0
- package/dist/plots/utils/geoSegments.d.ts +14 -0
- package/dist/plots/utils/geoSegments.js +17 -0
- package/dist/plots/utils/legendDisabled.d.ts +15 -0
- package/dist/plots/utils/legendDisabled.js +41 -0
- package/dist/plots/utils/segments.d.ts +14 -5
- package/dist/plots/utils/segments.js +25 -13
- package/dist/plots/utils/tiles.d.ts +89 -0
- package/dist/plots/utils/tiles.js +159 -0
- package/dist/plots/utils/topojson.d.ts +9 -0
- package/dist/plots/utils/topojson.js +30 -0
- package/dist/types/charts/common.d.ts +2 -1
- package/dist/types/charts/legend.d.ts +32 -1
- package/dist/types/charts/props.d.ts +9 -6
- package/dist/types/configuration/styling.d.ts +13 -5
- package/dist/types/layout/legend.d.ts +26 -0
- package/dist/types/layout/tooltip.d.ts +14 -21
- package/dist/types/{plots/markers.d.ts → markers/common.d.ts} +2 -18
- package/dist/types/markers/geo.d.ts +237 -0
- package/dist/types/markers/props.d.ts +11 -0
- package/dist/types/markers/props.js +1 -0
- package/dist/types/plots/axis.d.ts +55 -0
- package/dist/types/plots/axis.js +1 -0
- package/dist/types/plots/constants.d.ts +30 -0
- package/dist/types/plots/constants.js +1 -0
- package/dist/types/plots/data/common.d.ts +40 -0
- package/dist/types/plots/data/common.js +1 -0
- package/dist/types/plots/data/geo.d.ts +74 -0
- package/dist/types/plots/data/geo.js +1 -0
- package/dist/types/plots/delta.d.ts +2 -2
- package/dist/types/plots/props.d.ts +105 -16
- package/dist/types/plots/scales/geo.d.ts +44 -0
- package/dist/types/plots/scales/geo.js +1 -0
- package/dist/types/plots/segments/common.d.ts +25 -0
- package/dist/types/plots/segments/common.js +1 -0
- package/dist/types/plots/segments/config.d.ts +51 -0
- package/dist/types/plots/segments/config.js +1 -0
- package/dist/types/plots/styles/common.d.ts +37 -0
- package/dist/types/plots/styles/common.js +1 -0
- package/dist/types/plots/styles/geo.d.ts +42 -0
- package/dist/types/plots/styles/geo.js +1 -0
- package/dist/types/plots/styling.d.ts +45 -0
- package/dist/types/plots/styling.js +1 -0
- package/dist/utils/grouping.d.ts +2 -1
- package/dist/utils/interpolate.d.ts +2 -1
- package/package.json +27 -13
- package/dist/layout/plot/PlotLayout.svelte +0 -294
- package/dist/layout/plot/PlotLayout.svelte.d.ts +0 -70
- package/dist/plots/markers/HoverMarker.svelte +0 -98
- package/dist/types/plots/common.d.ts +0 -97
- package/dist/types/plots/styles.d.ts +0 -187
- /package/dist/types/{plots/common.js → layout/legend.js} +0 -0
- /package/dist/types/{plots/markers.js → markers/common.js} +0 -0
- /package/dist/types/{plots/styles.js → markers/geo.js} +0 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { GeoTileLayerConfig } from '../../types/plots/styles/geo';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
config: GeoTileLayerConfig;
|
|
4
|
+
left: number;
|
|
5
|
+
top: number;
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
};
|
|
9
|
+
declare const TileLayer: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
10
|
+
type TileLayer = ReturnType<typeof TileLayer>;
|
|
11
|
+
export default TileLayer;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { GeoCustomProjectionFactory } from '../../../types/plots/scales/geo';
|
|
2
|
+
/**
|
|
3
|
+
* Argentina's territory in one unbroken frame: the mainland and the
|
|
4
|
+
* Antarctic/South Atlantic sector both visible, related to each other at
|
|
5
|
+
* true scale (azimuthal-equidistant, rotated so the split falls to the
|
|
6
|
+
* sides rather than through the middle of either half).
|
|
7
|
+
*/
|
|
8
|
+
export declare const argentinaBicontinental: GeoCustomProjectionFactory;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { geoAzimuthalEquidistant } from 'd3-geo';
|
|
2
|
+
import { pointsToFitFeature } from './fit';
|
|
3
|
+
// Representative extremity points — not real border geometry (which stays
|
|
4
|
+
// out of `src/lib`, see stories/plots/geo/data.ts), just enough for
|
|
5
|
+
// `fitSize` to frame Argentina's full territorial claim sensibly. These are
|
|
6
|
+
// plain real-world coordinates, not derived from any particular dataset, so
|
|
7
|
+
// they work the same way regardless of which Argentina GeoJSON actually
|
|
8
|
+
// gets drawn through this projection.
|
|
9
|
+
const MAINLAND_POINTS = [
|
|
10
|
+
[-65.3, -21.8], // La Quiaca (north)
|
|
11
|
+
[-68.3, -54.8], // Ushuaia / Tierra del Fuego (south)
|
|
12
|
+
[-53.7, -25.6], // Puerto Iguazú (east)
|
|
13
|
+
[-73.4, -50.3], // Los Glaciares, Santa Cruz (west)
|
|
14
|
+
[-71.8, -41.1], // Bariloche (west, mid-latitude bulge)
|
|
15
|
+
];
|
|
16
|
+
const ANTARCTIC_POINTS = [
|
|
17
|
+
[-61.2, -51.0], // Malvinas (north tip)
|
|
18
|
+
[-57.8, -52.3], // Malvinas (east tip)
|
|
19
|
+
[-38.0, -53.9], // Georgias del Sur
|
|
20
|
+
[-26.5, -59.5], // Sandwich del Sur
|
|
21
|
+
[-74, -60], // Antarctic sector, western meridian (60°S treaty boundary)
|
|
22
|
+
[-25, -60], // Antarctic sector, eastern meridian (60°S treaty boundary)
|
|
23
|
+
[-49.5, -89.9], // South Pole, on the sector's central meridian
|
|
24
|
+
];
|
|
25
|
+
/**
|
|
26
|
+
* Argentina's territory in one unbroken frame: the mainland and the
|
|
27
|
+
* Antarctic/South Atlantic sector both visible, related to each other at
|
|
28
|
+
* true scale (azimuthal-equidistant, rotated so the split falls to the
|
|
29
|
+
* sides rather than through the middle of either half).
|
|
30
|
+
*/
|
|
31
|
+
export const argentinaBicontinental = ({ width, height }) => {
|
|
32
|
+
const projection = geoAzimuthalEquidistant().rotate([60, 70]);
|
|
33
|
+
projection.fitSize([width, height], pointsToFitFeature([...MAINLAND_POINTS, ...ANTARCTIC_POINTS]));
|
|
34
|
+
return projection;
|
|
35
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wraps a handful of representative `[lon, lat]` points as a `MultiPoint`
|
|
3
|
+
* Feature, just so `projection.fitExtent`/`fitSize` has something to
|
|
4
|
+
* measure. Custom projections built on these live in `src/lib` (see
|
|
5
|
+
* `stories/plots/geo/data.ts` for why real border geometry doesn't), so a
|
|
6
|
+
* few hand-picked extremity points stand in for a region's true extent.
|
|
7
|
+
*/
|
|
8
|
+
export declare function pointsToFitFeature(points: [number, number][]): GeoJSON.Feature;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wraps a handful of representative `[lon, lat]` points as a `MultiPoint`
|
|
3
|
+
* Feature, just so `projection.fitExtent`/`fitSize` has something to
|
|
4
|
+
* measure. Custom projections built on these live in `src/lib` (see
|
|
5
|
+
* `stories/plots/geo/data.ts` for why real border geometry doesn't), so a
|
|
6
|
+
* few hand-picked extremity points stand in for a region's true extent.
|
|
7
|
+
*/
|
|
8
|
+
export function pointsToFitFeature(points) {
|
|
9
|
+
return {
|
|
10
|
+
type: 'Feature',
|
|
11
|
+
properties: null,
|
|
12
|
+
geometry: { type: 'MultiPoint', coordinates: points },
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type GeoRotate = [number, number, number];
|
|
2
|
+
/**
|
|
3
|
+
* Wires drag-to-rotate onto a DOM node for orthographic-style projections,
|
|
4
|
+
* exposing the resulting `[lambda, phi, gamma]` as reactive Svelte state —
|
|
5
|
+
* the caller feeds it back into the projection's own `rotate` config (see
|
|
6
|
+
* `GeoPlot`'s own `Plot.svelte`). Plain pointer events rather than d3-drag: a
|
|
7
|
+
* single-pointer drag-delta-to-rotation-delta conversion doesn't need
|
|
8
|
+
* d3-drag's multi-touch gesture machinery, and this package doesn't
|
|
9
|
+
* otherwise depend on it.
|
|
10
|
+
*
|
|
11
|
+
* Sensitivity is a fixed degrees-per-pixel constant scaled by the current
|
|
12
|
+
* pan/zoom `scale` (so dragging feels consistent whether zoomed in or out)
|
|
13
|
+
* — not derived from the projection's own fitted scale, which isn't
|
|
14
|
+
* exposed back up to this layer. Known v1 simplification, in the same
|
|
15
|
+
* spirit as `createGeoZoom`'s own "v1 limitation" — good enough to spin
|
|
16
|
+
* the globe, not tuned to feel identical across wildly different
|
|
17
|
+
* width/height/domain combinations.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createGeoRotate(args: {
|
|
20
|
+
enabled: () => boolean;
|
|
21
|
+
baseRotate: () => GeoRotate;
|
|
22
|
+
scale: () => number;
|
|
23
|
+
node: () => HTMLElement | null;
|
|
24
|
+
}): {
|
|
25
|
+
readonly rotate: GeoRotate;
|
|
26
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wires drag-to-rotate onto a DOM node for orthographic-style projections,
|
|
3
|
+
* exposing the resulting `[lambda, phi, gamma]` as reactive Svelte state —
|
|
4
|
+
* the caller feeds it back into the projection's own `rotate` config (see
|
|
5
|
+
* `GeoPlot`'s own `Plot.svelte`). Plain pointer events rather than d3-drag: a
|
|
6
|
+
* single-pointer drag-delta-to-rotation-delta conversion doesn't need
|
|
7
|
+
* d3-drag's multi-touch gesture machinery, and this package doesn't
|
|
8
|
+
* otherwise depend on it.
|
|
9
|
+
*
|
|
10
|
+
* Sensitivity is a fixed degrees-per-pixel constant scaled by the current
|
|
11
|
+
* pan/zoom `scale` (so dragging feels consistent whether zoomed in or out)
|
|
12
|
+
* — not derived from the projection's own fitted scale, which isn't
|
|
13
|
+
* exposed back up to this layer. Known v1 simplification, in the same
|
|
14
|
+
* spirit as `createGeoZoom`'s own "v1 limitation" — good enough to spin
|
|
15
|
+
* the globe, not tuned to feel identical across wildly different
|
|
16
|
+
* width/height/domain combinations.
|
|
17
|
+
*/
|
|
18
|
+
export function createGeoRotate(args) {
|
|
19
|
+
let rotate = $state(args.baseRotate());
|
|
20
|
+
// Reset to the configured base rotation whenever it changes (e.g. a
|
|
21
|
+
// Storybook control edit) — but only while not actively enabled, so this
|
|
22
|
+
// doesn't fight the user's own in-progress drag.
|
|
23
|
+
$effect(() => {
|
|
24
|
+
if (!args.enabled())
|
|
25
|
+
rotate = args.baseRotate();
|
|
26
|
+
});
|
|
27
|
+
$effect(() => {
|
|
28
|
+
const el = args.node();
|
|
29
|
+
if (!args.enabled() || !el)
|
|
30
|
+
return;
|
|
31
|
+
// `dragging`/`start`/`startRotate` are deliberately plain closure
|
|
32
|
+
// variables, not `$state` — this effect must NOT read the reactive
|
|
33
|
+
// `rotate` value anywhere in its own synchronous body (only inside the
|
|
34
|
+
// event-handler closures below, which aren't part of dependency
|
|
35
|
+
// tracking). Reading it here would make every `rotate` write from
|
|
36
|
+
// `onPointerMove` re-trigger this very effect mid-drag, tearing down
|
|
37
|
+
// and rebuilding the listeners — and losing `dragging`'s `true` state
|
|
38
|
+
// — on every pixel of movement.
|
|
39
|
+
let dragging = false;
|
|
40
|
+
let start = { x: 0, y: 0 };
|
|
41
|
+
let startRotate = [0, 0, 0];
|
|
42
|
+
function onPointerDown(event) {
|
|
43
|
+
if (event.button !== 0)
|
|
44
|
+
return;
|
|
45
|
+
dragging = true;
|
|
46
|
+
start = { x: event.clientX, y: event.clientY };
|
|
47
|
+
startRotate = rotate;
|
|
48
|
+
el.setPointerCapture(event.pointerId);
|
|
49
|
+
}
|
|
50
|
+
function onPointerMove(event) {
|
|
51
|
+
if (!dragging)
|
|
52
|
+
return;
|
|
53
|
+
const degPerPixel = 0.3 / Math.max(args.scale(), 0.1);
|
|
54
|
+
const dLambda = (event.clientX - start.x) * degPerPixel;
|
|
55
|
+
const dPhi = (event.clientY - start.y) * degPerPixel;
|
|
56
|
+
const [lambda0, phi0, gamma0] = startRotate;
|
|
57
|
+
rotate = [lambda0 + dLambda, Math.max(-90, Math.min(90, phi0 - dPhi)), gamma0];
|
|
58
|
+
}
|
|
59
|
+
function onPointerUp(event) {
|
|
60
|
+
dragging = false;
|
|
61
|
+
el.releasePointerCapture(event.pointerId);
|
|
62
|
+
}
|
|
63
|
+
el.addEventListener('pointerdown', onPointerDown);
|
|
64
|
+
el.addEventListener('pointermove', onPointerMove);
|
|
65
|
+
el.addEventListener('pointerup', onPointerUp);
|
|
66
|
+
el.addEventListener('pointercancel', onPointerUp);
|
|
67
|
+
return () => {
|
|
68
|
+
el.removeEventListener('pointerdown', onPointerDown);
|
|
69
|
+
el.removeEventListener('pointermove', onPointerMove);
|
|
70
|
+
el.removeEventListener('pointerup', onPointerUp);
|
|
71
|
+
el.removeEventListener('pointercancel', onPointerUp);
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
return {
|
|
75
|
+
get rotate() {
|
|
76
|
+
return rotate;
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { GeoZoomConfig } from '../../types/plots/styles/geo';
|
|
2
|
+
export type GeoZoomTransform = {
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
k: number;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Wires drag-pan + scroll-zoom onto a DOM node via d3-zoom, exposing the
|
|
9
|
+
* resulting transform as reactive Svelte state. The caller applies it as an
|
|
10
|
+
* SVG `<g transform="translate(x,y) scale(k)">` wrapper — cheap, since the
|
|
11
|
+
* underlying `d3-geo` projection is computed once by svelteplot and never
|
|
12
|
+
* recomputed per zoom tick.
|
|
13
|
+
*
|
|
14
|
+
* Known v1 limitation: svelteplot's own `<Pointer>` hit-tests in the plot's
|
|
15
|
+
* pre-zoom projected coordinate space, so hover/tooltip matching does not
|
|
16
|
+
* compensate for an active pan/zoom transform — it still works, but targets
|
|
17
|
+
* a shape's un-zoomed position rather than its currently-visible one.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createGeoZoom(args: {
|
|
20
|
+
enabled: () => boolean;
|
|
21
|
+
config: () => GeoZoomConfig | undefined;
|
|
22
|
+
node: () => HTMLElement | null;
|
|
23
|
+
/**
|
|
24
|
+
* When true, drag/touch gestures are left for something else to handle
|
|
25
|
+
* (see `createGeoRotate`) — only wheel/pinch scaling stays wired to this
|
|
26
|
+
* behavior. Checked per-event, so toggling it doesn't need to tear down
|
|
27
|
+
* and recreate the underlying d3-zoom behavior.
|
|
28
|
+
*/
|
|
29
|
+
disableDrag?: () => boolean;
|
|
30
|
+
}): {
|
|
31
|
+
readonly transform: GeoZoomTransform;
|
|
32
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { select } from 'd3-selection';
|
|
2
|
+
import { zoom as d3zoom, zoomIdentity } from 'd3-zoom';
|
|
3
|
+
/**
|
|
4
|
+
* Wires drag-pan + scroll-zoom onto a DOM node via d3-zoom, exposing the
|
|
5
|
+
* resulting transform as reactive Svelte state. The caller applies it as an
|
|
6
|
+
* SVG `<g transform="translate(x,y) scale(k)">` wrapper — cheap, since the
|
|
7
|
+
* underlying `d3-geo` projection is computed once by svelteplot and never
|
|
8
|
+
* recomputed per zoom tick.
|
|
9
|
+
*
|
|
10
|
+
* Known v1 limitation: svelteplot's own `<Pointer>` hit-tests in the plot's
|
|
11
|
+
* pre-zoom projected coordinate space, so hover/tooltip matching does not
|
|
12
|
+
* compensate for an active pan/zoom transform — it still works, but targets
|
|
13
|
+
* a shape's un-zoomed position rather than its currently-visible one.
|
|
14
|
+
*/
|
|
15
|
+
export function createGeoZoom(args) {
|
|
16
|
+
let transform = $state({ x: 0, y: 0, k: 1 });
|
|
17
|
+
$effect(() => {
|
|
18
|
+
const el = args.node();
|
|
19
|
+
if (!args.enabled() || !el)
|
|
20
|
+
return;
|
|
21
|
+
const cfg = args.config();
|
|
22
|
+
const behavior = d3zoom()
|
|
23
|
+
.scaleExtent([cfg?.min ?? 1, cfg?.max ?? 8])
|
|
24
|
+
.filter((event) => {
|
|
25
|
+
if (args.disableDrag?.() && event.type !== 'wheel')
|
|
26
|
+
return false;
|
|
27
|
+
// d3-zoom's own default filter, minus the part this replaces.
|
|
28
|
+
return (!event.ctrlKey || event.type === 'wheel') && !event.button;
|
|
29
|
+
})
|
|
30
|
+
.on('zoom', (event) => {
|
|
31
|
+
transform = { x: event.transform.x, y: event.transform.y, k: event.transform.k };
|
|
32
|
+
});
|
|
33
|
+
const selection = select(el).call(behavior);
|
|
34
|
+
const initial = cfg?.initial;
|
|
35
|
+
if (initial) {
|
|
36
|
+
behavior.transform(selection, zoomIdentity.translate(initial.x ?? 0, initial.y ?? 0).scale(initial.k ?? 1));
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
transform = { x: 0, y: 0, k: 1 };
|
|
40
|
+
}
|
|
41
|
+
return () => {
|
|
42
|
+
selection.on('.zoom', null);
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
return {
|
|
46
|
+
get transform() {
|
|
47
|
+
return transform;
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
<script lang="ts" generics="TData extends Record<string, unknown>">
|
|
2
2
|
import { Cell, Text } from 'svelteplot';
|
|
3
3
|
import { resolveAccessor } from '../utils/accessors';
|
|
4
|
+
import { validateSegments } from '../utils/segments';
|
|
4
5
|
import { buildSeries } from '../../utils/grouping';
|
|
5
6
|
import { makeColorScale } from '../../utils/color';
|
|
6
7
|
import { getConfiguration } from '../../configuration/config.svelte';
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
8
|
+
import { getLegendInteraction } from '../../layout/legend/interaction.svelte';
|
|
9
|
+
import { disabledFillOverride } from '../utils/legendDisabled';
|
|
10
|
+
import BasePlotLayout from '../../layout/plot/BasePlotLayout.svelte';
|
|
11
|
+
import AxisLayout from '../../layout/plot/AxisLayout.svelte';
|
|
12
|
+
import GridLayout from '../../layout/plot/GridLayout.svelte';
|
|
13
|
+
import RuleLayout from '../../layout/plot/RuleLayout.svelte';
|
|
14
|
+
import { hasHoverMarker, resolveHoverStrategy, resolveHoverSync } from '../../layout/tooltip/utils';
|
|
15
|
+
import type { BasePlotProps } from '../../types/plots/props';
|
|
16
|
+
import type { AxisValue } from '../../types/plots/axis';
|
|
17
|
+
import type { SeriesProps, DataProps } from '../../types/plots/data/common';
|
|
18
|
+
import type { AxisBasedScalesConfig } from '../../types/plots/props';
|
|
19
|
+
import type { BasePlotStyles } from '../../types/plots/styles/common';
|
|
20
|
+
import type { CellSegmentStyle } from '../../types/plots/segments/common';
|
|
21
|
+
import type { Marker } from '../../types/markers/common';
|
|
11
22
|
|
|
12
23
|
/**
|
|
13
24
|
* A categorical grid of value-coloured cells. Accepts either a pre-built
|
|
@@ -18,6 +29,15 @@
|
|
|
18
29
|
* The colour domain is read from `scales.z.domain`; when absent it is derived
|
|
19
30
|
* from the visible slice of data.
|
|
20
31
|
*/
|
|
32
|
+
type Props = BasePlotProps<
|
|
33
|
+
SeriesProps<TData> | DataProps<TData>,
|
|
34
|
+
TData,
|
|
35
|
+
BasePlotStyles,
|
|
36
|
+
CellSegmentStyle,
|
|
37
|
+
Marker,
|
|
38
|
+
AxisBasedScalesConfig<TData>
|
|
39
|
+
>;
|
|
40
|
+
|
|
21
41
|
let {
|
|
22
42
|
width,
|
|
23
43
|
height,
|
|
@@ -32,9 +52,10 @@
|
|
|
32
52
|
margins,
|
|
33
53
|
markers = [],
|
|
34
54
|
tooltip = undefined,
|
|
35
|
-
}:
|
|
55
|
+
}: Props = $props();
|
|
36
56
|
|
|
37
57
|
$effect(() => {
|
|
58
|
+
validateSegments(segments);
|
|
38
59
|
const scoped = markers.filter(
|
|
39
60
|
(m) => (m.type === 'hover' || m.type === 'component') && m.series,
|
|
40
61
|
);
|
|
@@ -43,12 +64,10 @@
|
|
|
43
64
|
'HeatmapPlot: markers scoped to a `series` are ignored — heatmaps have no series concept.',
|
|
44
65
|
);
|
|
45
66
|
}
|
|
46
|
-
const seriesKeys = segments.
|
|
47
|
-
Object.keys(g as Record<string, unknown>).filter((k) => k !== 'default'),
|
|
48
|
-
);
|
|
67
|
+
const seriesKeys = Object.keys(segments).filter((k) => k !== 'default');
|
|
49
68
|
if (seriesKeys.length > 0) {
|
|
50
69
|
console.warn(
|
|
51
|
-
`HeatmapPlot: segment groups have series-specific keys (${
|
|
70
|
+
`HeatmapPlot: segment groups have series-specific keys (${seriesKeys.join(', ')}) — heatmaps have no named series. Use the 'default' key instead.`,
|
|
52
71
|
);
|
|
53
72
|
}
|
|
54
73
|
});
|
|
@@ -58,6 +77,9 @@
|
|
|
58
77
|
const maxColor = $derived(styles.colors?.max ?? cfg.continuous.max);
|
|
59
78
|
const showVals = $derived(styles.values?.show ?? true);
|
|
60
79
|
const fmtVal = $derived(styles.values?.format ?? ((v: number) => `${v}`));
|
|
80
|
+
// 'outside' is a per-point escape hatch resolved by HoverMarker; heatmaps
|
|
81
|
+
// have no signed axis to resolve it against, so treat it as unset.
|
|
82
|
+
const resolvedTextAnchor = $derived(styles.values?.textAnchor === 'outside' ? undefined : styles.values?.textAnchor);
|
|
61
83
|
|
|
62
84
|
const resolvedSeries = $derived(series ?? buildSeries(data!, x!, y!, undefined));
|
|
63
85
|
const flat = $derived(resolvedSeries.flatMap((s) => s.data));
|
|
@@ -66,15 +88,6 @@
|
|
|
66
88
|
const getY = $derived(resolveAccessor<TData>(resolvedSeries[0]?.y ?? y!));
|
|
67
89
|
const getZ = $derived(resolveAccessor<TData>(resolvedSeries[0]?.z ?? z!));
|
|
68
90
|
|
|
69
|
-
const hoverPoints = $derived(
|
|
70
|
-
flat.map((d) => ({
|
|
71
|
-
x: getX(d) as AxisValue,
|
|
72
|
-
y: getY(d) as AxisValue,
|
|
73
|
-
row: d,
|
|
74
|
-
label: fmtVal(Number(getZ(d)), ''),
|
|
75
|
-
})),
|
|
76
|
-
);
|
|
77
|
-
|
|
78
91
|
const xDomain = $derived([...new Set(flat.map((d) => getX(d) as AxisValue))]);
|
|
79
92
|
const yDomain = $derived([...new Set(flat.map((d) => getY(d) as AxisValue))]);
|
|
80
93
|
|
|
@@ -86,33 +99,77 @@
|
|
|
86
99
|
return vals.length ? [Math.min(...vals, 0), Math.max(...vals)] : [0, 1];
|
|
87
100
|
});
|
|
88
101
|
|
|
102
|
+
// ── Legend-driven range filter ────────────────────────────────────────────
|
|
103
|
+
const legendInteraction = $derived(getLegendInteraction());
|
|
104
|
+
const range = $derived(legendInteraction?.continuousRange);
|
|
105
|
+
// 'rescale' reprojects the ramp onto the narrowed range; 'filter' (and no
|
|
106
|
+
// active range) keeps the original domain — only which cells are hidden
|
|
107
|
+
// changes.
|
|
108
|
+
const effectiveDomain = $derived.by((): [number, number] =>
|
|
109
|
+
range && range.mode === 'rescale' ? [range.min, range.max] : valueDomain,
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
function rangeFilteredStyle(base: CellSegmentStyle, value: number): CellSegmentStyle {
|
|
113
|
+
const r = range;
|
|
114
|
+
if (r == null || (value >= r.min && value <= r.max)) return base;
|
|
115
|
+
return { ...base, ...disabledFillOverride(r.disabledStyle) };
|
|
116
|
+
}
|
|
117
|
+
|
|
89
118
|
const colorScale = $derived(
|
|
90
|
-
makeColorScale(
|
|
119
|
+
makeColorScale(effectiveDomain[0], effectiveDomain[1], minColor, maxColor),
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
// A cell outside the active range is dimmed in place, but never surfaces
|
|
123
|
+
// in hover/tooltip — it's simply absent from the candidate list.
|
|
124
|
+
const hoverPoints = $derived(
|
|
125
|
+
flat
|
|
126
|
+
.filter((d) => {
|
|
127
|
+
const r = range;
|
|
128
|
+
if (r == null) return true;
|
|
129
|
+
const v = Number(getZ(d));
|
|
130
|
+
return v >= r.min && v <= r.max;
|
|
131
|
+
})
|
|
132
|
+
.map((d) => ({
|
|
133
|
+
x: getX(d) as AxisValue,
|
|
134
|
+
y: getY(d) as AxisValue,
|
|
135
|
+
row: d,
|
|
136
|
+
label: fmtVal(Number(getZ(d)), ''),
|
|
137
|
+
})),
|
|
91
138
|
);
|
|
92
139
|
|
|
93
|
-
const segList = $derived(segments.
|
|
140
|
+
const segList = $derived(segments.default ?? []);
|
|
94
141
|
|
|
142
|
+
/**
|
|
143
|
+
* A catch-all (no `areas`) always matches, so evaluating segments in plain
|
|
144
|
+
* array order and merging every match — as this used to do — makes a
|
|
145
|
+
* catch-all listed *after* an area-specific one (the documented, and only
|
|
146
|
+
* demonstrated, convention: baseline first, highlight second) overwrite it
|
|
147
|
+
* on every cell, since it's applied last unconditionally. Area-specific
|
|
148
|
+
* segments must always win over the catch-all regardless of list order —
|
|
149
|
+
* so the catch-all is applied once as the baseline, and only
|
|
150
|
+
* area-specific segments can override it on top, mirroring the same
|
|
151
|
+
* catch-all/area-specific priority every other plot kind's segments
|
|
152
|
+
* already follow (see `resolveSeriesSegments` in `utils/segments.ts`).
|
|
153
|
+
*/
|
|
95
154
|
const segStyleMap = $derived.by(() => {
|
|
96
155
|
const map = new Map<string, CellSegmentStyle>();
|
|
97
156
|
if (!segList.length) return map;
|
|
157
|
+
const catchAll = segList.find((s) => !s.areas || s.areas.length === 0);
|
|
158
|
+
const areaSegs = segList.filter((s) => s.areas && s.areas.length > 0);
|
|
98
159
|
for (const d of flat) {
|
|
99
160
|
const cx = getX(d) as AxisValue;
|
|
100
161
|
const cy = getY(d) as AxisValue;
|
|
101
162
|
const key = `${String(cx)}::${String(cy)}`;
|
|
102
163
|
if (!map.has(key)) {
|
|
103
|
-
let style: CellSegmentStyle = {};
|
|
104
|
-
for (const seg of
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
!
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
113
|
-
const yOk = !area.y || ((area.y.from === undefined || (cy as any) >= area.y.from) && (area.y.to === undefined || (cy as any) <= area.y.to));
|
|
114
|
-
return xOk && yOk;
|
|
115
|
-
});
|
|
164
|
+
let style: CellSegmentStyle = catchAll?.style ? { ...catchAll.style } : {};
|
|
165
|
+
for (const seg of areaSegs) {
|
|
166
|
+
const matches = seg.areas!.some((area) => {
|
|
167
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
168
|
+
const xOk = !area.x || ((area.x.from === undefined || (cx as any) >= area.x.from) && (area.x.to === undefined || (cx as any) <= area.x.to));
|
|
169
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
170
|
+
const yOk = !area.y || ((area.y.from === undefined || (cy as any) >= area.y.from) && (area.y.to === undefined || (cy as any) <= area.y.to));
|
|
171
|
+
return xOk && yOk;
|
|
172
|
+
});
|
|
116
173
|
if (matches) style = { ...style, ...seg.style };
|
|
117
174
|
}
|
|
118
175
|
map.set(key, style);
|
|
@@ -122,39 +179,89 @@
|
|
|
122
179
|
});
|
|
123
180
|
|
|
124
181
|
const getCellStyle = $derived(
|
|
125
|
-
(d: TData) =>
|
|
182
|
+
(d: TData) =>
|
|
183
|
+
rangeFilteredStyle(
|
|
184
|
+
segStyleMap.get(`${String(getX(d))}::${String(getY(d))}`) ?? {},
|
|
185
|
+
Number(getZ(d)),
|
|
186
|
+
),
|
|
126
187
|
);
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* One `<Cell>` call per *distinct resolved style*, not one call for the
|
|
191
|
+
* whole dataset. svelteplot registers `fillOpacity`/`strokeWidth` under
|
|
192
|
+
* its shared "opacity"/numeric scale machinery — passed as a per-row
|
|
193
|
+
* accessor that genuinely varies within a single mark instance, that
|
|
194
|
+
* scale collapses every row to the same (wrong) value instead of the
|
|
195
|
+
* literal numbers set on each segment. A literal, per-call constant (one
|
|
196
|
+
* call per style bucket) bypasses that entirely, which is exactly how
|
|
197
|
+
* bar/line avoid the same trap (one mark per visual segment). `fill`
|
|
198
|
+
* stays a per-cell accessor within each group — unlike fillOpacity, it
|
|
199
|
+
* must keep varying continuously with the underlying value wherever a
|
|
200
|
+
* segment doesn't explicitly override it.
|
|
201
|
+
*/
|
|
202
|
+
type StyleGroup = { style: CellSegmentStyle; data: TData[] };
|
|
203
|
+
const styleGroups = $derived.by((): StyleGroup[] => {
|
|
204
|
+
const groups = new Map<string, StyleGroup>();
|
|
205
|
+
for (const d of flat) {
|
|
206
|
+
const style = getCellStyle(d);
|
|
207
|
+
const key = JSON.stringify(style);
|
|
208
|
+
let group = groups.get(key);
|
|
209
|
+
if (!group) {
|
|
210
|
+
group = { style, data: [] };
|
|
211
|
+
groups.set(key, group);
|
|
212
|
+
}
|
|
213
|
+
group.data.push(d);
|
|
214
|
+
}
|
|
215
|
+
return [...groups.values()];
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// The single, fully-resolved scales object BasePlotLayout reads for
|
|
219
|
+
// everything (ticks, grid, sort, and the real svelteplot scale) — avoids
|
|
220
|
+
// keeping a second, separately-defaulted copy of `scales.x`/`scales.y`
|
|
221
|
+
// (our own band-scale + domain defaults) out of sync with the one
|
|
222
|
+
// actually forwarded.
|
|
223
|
+
const resolvedScales = $derived<AxisBasedScalesConfig<TData>>({
|
|
224
|
+
...scales,
|
|
225
|
+
x: { type: 'band', labelAnchor: 'right', domain: xDomain, ...(scales.x ?? {}) },
|
|
226
|
+
y: { type: 'band', domain: yDomain, ...(scales.y ?? {}) },
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
const hoverActive = $derived(tooltip != null || hasHoverMarker(markers));
|
|
230
|
+
const hoverStrategy = $derived(resolveHoverStrategy(tooltip?.strategy, markers, 'punctual'));
|
|
231
|
+
const hoverSync = $derived(resolveHoverSync(tooltip?.sync, markers));
|
|
127
232
|
</script>
|
|
128
233
|
|
|
129
|
-
<
|
|
234
|
+
<BasePlotLayout
|
|
130
235
|
{width}
|
|
131
236
|
{height}
|
|
132
|
-
x={{ type: 'band', labelAnchor: 'right', domain: xDomain, ...(scales.x ?? {}) }}
|
|
133
|
-
y={{ type: 'band', domain: yDomain, ...(scales.y ?? {}) }}
|
|
134
237
|
data={flat}
|
|
135
238
|
{getX}
|
|
136
239
|
{getY}
|
|
137
|
-
matchY={getY}
|
|
138
240
|
{hoverPoints}
|
|
139
241
|
label={(d) => fmtVal(Number(getZ(d)), '')}
|
|
140
242
|
{tooltip}
|
|
141
|
-
{
|
|
142
|
-
|
|
143
|
-
|
|
243
|
+
{hoverActive}
|
|
244
|
+
{hoverStrategy}
|
|
245
|
+
{hoverSync}
|
|
246
|
+
scales={resolvedScales}
|
|
144
247
|
{margins}
|
|
145
|
-
showHoverLabels={false}
|
|
146
|
-
defaultStrategy="punctual"
|
|
147
248
|
>
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
249
|
+
{#snippet children({ matchedPoints, ruleX, ruleY, impliesRuleX, impliesRuleY, numericMargins })}
|
|
250
|
+
<AxisLayout data={flat} {getX} {getY} {width} {height} {numericMargins} scales={resolvedScales} />
|
|
251
|
+
<GridLayout scales={resolvedScales} />
|
|
252
|
+
<RuleLayout {markers} seriesData={resolvedSeries} {matchedPoints} {ruleX} {ruleY} {impliesRuleX} {impliesRuleY} showHoverLabels={false}>
|
|
253
|
+
{#each styleGroups as group, i (i)}
|
|
254
|
+
<Cell
|
|
255
|
+
data={group.data as unknown as Record<string | symbol, never>[]}
|
|
256
|
+
x={getX}
|
|
257
|
+
y={getY}
|
|
258
|
+
fill={(d) => group.style.fill ?? colorScale(Number(getZ(d as TData)))}
|
|
259
|
+
fillOpacity={group.style.fillOpacity ?? 1}
|
|
260
|
+
stroke={group.style.stroke}
|
|
261
|
+
strokeWidth={group.style.strokeWidth ?? 0}
|
|
262
|
+
inset={0.5}
|
|
263
|
+
/>
|
|
264
|
+
{/each}
|
|
158
265
|
|
|
159
266
|
{#if showVals}
|
|
160
267
|
<Text
|
|
@@ -162,17 +269,23 @@
|
|
|
162
269
|
x={getX}
|
|
163
270
|
y={getY}
|
|
164
271
|
text={(d) => fmtVal(Number(getZ(d)), '')}
|
|
165
|
-
fill={cfg.continuous.labelColor}
|
|
166
|
-
fontSize={12}
|
|
167
|
-
fontWeight={600}
|
|
168
|
-
|
|
272
|
+
fill={styles.values?.fill ?? cfg.continuous.labelColor}
|
|
273
|
+
fontSize={styles.values?.fontSize ?? 12}
|
|
274
|
+
fontWeight={styles.values?.fontWeight ?? 600}
|
|
275
|
+
fontStyle={styles.values?.fontStyle}
|
|
276
|
+
textAnchor={resolvedTextAnchor ?? 'middle'}
|
|
169
277
|
lineAnchor={styles.values?.lineAnchor ?? 'middle'}
|
|
170
278
|
dx={styles.values?.dx ?? 0}
|
|
171
279
|
dy={styles.values?.dy ?? 0}
|
|
172
280
|
lineHeight={styles.values?.lineHeight}
|
|
173
281
|
rotate={styles.values?.rotate}
|
|
282
|
+
stroke={styles.values?.stroke}
|
|
283
|
+
strokeWidth={styles.values?.strokeWidth}
|
|
284
|
+
paintOrder={styles.values?.paintOrder}
|
|
174
285
|
class={styles.values?.class}
|
|
175
286
|
textClass={styles.values?.textClass}
|
|
176
287
|
/>
|
|
177
288
|
{/if}
|
|
178
|
-
</
|
|
289
|
+
</RuleLayout>
|
|
290
|
+
{/snippet}
|
|
291
|
+
</BasePlotLayout>
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
1
|
+
import type { BasePlotProps } from '../../types/plots/props';
|
|
2
|
+
import type { SeriesProps, DataProps } from '../../types/plots/data/common';
|
|
3
|
+
import type { AxisBasedScalesConfig } from '../../types/plots/props';
|
|
4
|
+
import type { BasePlotStyles } from '../../types/plots/styles/common';
|
|
5
|
+
import type { CellSegmentStyle } from '../../types/plots/segments/common';
|
|
6
|
+
import type { Marker } from '../../types/markers/common';
|
|
3
7
|
declare function $$render<TData extends Record<string, unknown>>(): {
|
|
4
|
-
props:
|
|
8
|
+
props: BasePlotProps<SeriesProps<TData> | DataProps<TData>, TData, BasePlotStyles, CellSegmentStyle, Marker, AxisBasedScalesConfig<TData>>;
|
|
5
9
|
exports: {};
|
|
6
10
|
bindings: "";
|
|
7
11
|
slots: {};
|