@fundar/data-chart-telling 0.0.36 → 0.0.37
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/dist/index.d.ts +1 -0
- package/dist/layout/coordinates/CameraFit.svelte +111 -0
- package/dist/layout/coordinates/CameraFit.svelte.d.ts +15 -0
- package/dist/layout/coordinates/CoordinatesLayout.svelte +169 -95
- package/dist/layout/coordinates/CoordinatesLayout.svelte.d.ts +1 -1
- package/dist/layout/coordinates/camera.svelte.d.ts +17 -0
- package/dist/layout/coordinates/camera.svelte.js +106 -0
- package/dist/layout/coordinates/resolveCameraTarget.d.ts +14 -0
- package/dist/layout/coordinates/resolveCameraTarget.js +19 -0
- package/dist/layout/coordinates/zoom.svelte.d.ts +11 -15
- package/dist/layout/coordinates/zoom.svelte.js +15 -12
- package/dist/layout/plot/BasePlotLayout.svelte +312 -282
- package/dist/plots/line/Plot.svelte +6 -18
- package/dist/plots/line/ValueLabels.svelte +45 -82
- package/dist/plots/line/ValueLabels.svelte.d.ts +1 -1
- package/dist/types/layout/coordinates.d.ts +11 -5
- package/dist/types/plots/camera.d.ts +42 -0
- package/dist/types/plots/camera.js +1 -0
- package/package.json +1 -1
|
@@ -49,9 +49,8 @@
|
|
|
49
49
|
tooltip = undefined,
|
|
50
50
|
}: Props = $props();
|
|
51
51
|
|
|
52
|
-
// Fills
|
|
53
|
-
//
|
|
54
|
-
// `segments` — see `GapsAwarePlotStylesConfig` (`types/layout/styles.ts`).
|
|
52
|
+
// Fills missing-data gaps with an interpolated, distinctly-styled bridge,
|
|
53
|
+
// resolved per series.
|
|
55
54
|
const gaps = $derived(styles.gaps ?? {});
|
|
56
55
|
|
|
57
56
|
const resolvedSeries = $derived(series ?? buildSeries(data!, x!, y!, z));
|
|
@@ -67,10 +66,8 @@
|
|
|
67
66
|
const showVals = $derived(styles.values?.show ?? false);
|
|
68
67
|
const hoverIsolate = $derived(styles.values?.hoverIsolate ?? false);
|
|
69
68
|
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
// one `<Plot>` instance (not the shared HoverStore) so the gesture can't
|
|
73
|
-
// leak into unrelated tooltip/crosshair behavior.
|
|
69
|
+
// Dims every other series like a legend toggle when a value label is
|
|
70
|
+
// hovered, local to this `<Plot>` instance.
|
|
74
71
|
let hoverIsolatedSeries = $state<string | null>(null);
|
|
75
72
|
|
|
76
73
|
function effectiveDisabledFor(name: string): LegendDisabledStyle | undefined {
|
|
@@ -85,10 +82,7 @@
|
|
|
85
82
|
const xAcc = $derived(resolveAccessor(resolvedSeries[0]?.x ?? ((d: TData) => d)));
|
|
86
83
|
const yAcc = $derived(resolveAccessor(resolvedSeries[0]?.y ?? ((d: TData) => d)));
|
|
87
84
|
const flat = $derived(resolvedSeries.flatMap((s) => s.data));
|
|
88
|
-
//
|
|
89
|
-
// in hover/tooltip — its points are simply absent from the candidate list.
|
|
90
|
-
// A gap row (see `gaps`) is excluded the same way — it carries no real
|
|
91
|
-
// value to hover.
|
|
85
|
+
// Legend-disabled series and gap rows are excluded from hover candidates.
|
|
92
86
|
const hoverPoints = $derived(
|
|
93
87
|
resolvedSeries.flatMap((s, idx) => {
|
|
94
88
|
if (legendInteraction?.disabledSeries.has(s.name)) return [];
|
|
@@ -156,13 +150,7 @@
|
|
|
156
150
|
.filter((p): p is { series: Series<TData>; lastRow: TData; color: string } => p !== undefined),
|
|
157
151
|
);
|
|
158
152
|
|
|
159
|
-
//
|
|
160
|
-
// margin — reserving real space there (rather than padding the domain,
|
|
161
|
-
// which would falsely imply data exists past the last real point) is
|
|
162
|
-
// what ValueLabels's own overflow measurement feeds into here, via a
|
|
163
|
-
// tracker local to this one `<Plot>` instance — see ValueLabels.svelte's
|
|
164
|
-
// own doc comment for why that locality (vs. svelteplot's shared margin
|
|
165
|
-
// context) matters.
|
|
153
|
+
// Grows the plot's margin to fit value labels that overflow the content box.
|
|
166
154
|
const labelMargin = createLabelMarginTracker(() => margins);
|
|
167
155
|
</script>
|
|
168
156
|
|
|
@@ -13,12 +13,8 @@
|
|
|
13
13
|
import type { MarginOverflow } from '../utils/labelOverflow.svelte';
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
* End-
|
|
17
|
-
*
|
|
18
|
-
* end up with close last values. Rendered as a proper descendant of
|
|
19
|
-
* svelteplot's `<Plot>` (not just of `LinePlot` itself) so `usePlot()`
|
|
20
|
-
* below resolves the real, live y-scale needed to measure pixel-space
|
|
21
|
-
* overlap.
|
|
16
|
+
* End-of-line value labels for `LinePlot`, with automatic collision
|
|
17
|
+
* resolution and shared horizontal alignment across labels.
|
|
22
18
|
*/
|
|
23
19
|
let {
|
|
24
20
|
lastPoints,
|
|
@@ -33,7 +29,7 @@
|
|
|
33
29
|
}: {
|
|
34
30
|
lastPoints: { series: Series<TData>; lastRow: TData; color: string }[];
|
|
35
31
|
values: ValuesStyle | undefined;
|
|
36
|
-
/**
|
|
32
|
+
/** Plot pixel size, used with `numericMargins` to compute the content box. */
|
|
37
33
|
width: number;
|
|
38
34
|
height: number;
|
|
39
35
|
/** `BasePlotLayout`'s resolved margins, already numeric on every side. */
|
|
@@ -49,17 +45,10 @@
|
|
|
49
45
|
|
|
50
46
|
const cfg = $derived(getConfiguration());
|
|
51
47
|
|
|
52
|
-
// Elbow-routed leader line geometry (pixels), configurable
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
// the last; `elbowGap` is the shortest lane (rank 0, right off the data
|
|
57
|
-
// point); `labelGap` is the small clearance between the outermost lane and
|
|
58
|
-
// the label text it feeds into. `connectorColor`/`connectorWidth` are the
|
|
59
|
-
// leader line's own default look — deliberately *not* each series' own
|
|
60
|
-
// colour or stroke weight (matching the data line reads as more data, not
|
|
61
|
-
// as a leader, right where several lines are already converging) — both
|
|
62
|
-
// still overridable per-plot via `values.strokeStyle`.
|
|
48
|
+
// Elbow-routed leader line geometry (pixels), configurable via
|
|
49
|
+
// `setConfiguration({ line: { valueLabels: {...} } })`. `connectorColor`/
|
|
50
|
+
// `connectorWidth` default to a neutral look rather than each series' own
|
|
51
|
+
// colour, overridable per-plot via `values.strokeStyle`.
|
|
63
52
|
const elbowGap = $derived(cfg.line.valueLabels.elbowGap);
|
|
64
53
|
const laneStep = $derived(cfg.line.valueLabels.laneStep);
|
|
65
54
|
const labelGap = $derived(cfg.line.valueLabels.labelGap);
|
|
@@ -70,12 +59,12 @@
|
|
|
70
59
|
const connector = $derived(values?.strokeStyle);
|
|
71
60
|
const formatValue = $derived(values?.format ?? ((v: number, name: string) => `${name} - ${v}`));
|
|
72
61
|
const valAnchor = $derived<ValueAnchor>(values?.anchor ?? 'start');
|
|
73
|
-
// 'outside' is
|
|
74
|
-
// value labels already have their own `anchor: 'outside'` concept above,
|
|
75
|
-
// so treat it as unset here.
|
|
62
|
+
// 'outside' is handled separately by HoverMarker, so it's treated as unset here.
|
|
76
63
|
const resolvedTextAnchor = $derived(font?.textAnchor === 'outside' ? undefined : font?.textAnchor);
|
|
77
64
|
|
|
78
65
|
const defaultDx = $derived(valAnchor === 'start' ? 6 : valAnchor === 'end' ? -6 : 0);
|
|
66
|
+
const baseDx = $derived(font?.dx ?? defaultDx);
|
|
67
|
+
const dir = $derived(Math.sign(baseDx) || 1);
|
|
79
68
|
const defaultDy = $derived(valAnchor === 'outside' ? -8 : 0);
|
|
80
69
|
const defaultTA = $derived(
|
|
81
70
|
valAnchor === 'start' ? ('start' as const) : valAnchor === 'end' ? ('end' as const) : ('middle' as const),
|
|
@@ -85,38 +74,21 @@
|
|
|
85
74
|
|
|
86
75
|
const plot = usePlot();
|
|
87
76
|
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
// registering directly into svelteplot's *shared* margin-auto-sizing
|
|
94
|
-
// context keeps this entirely local to one `<Plot>` instance: nothing here
|
|
95
|
-
// is shared across instances, which is what made that old approach spike
|
|
96
|
-
// CPU/memory into unresponsiveness when many instances mounted/unmounted
|
|
97
|
-
// together (e.g. a multi-story docs page → single story).
|
|
77
|
+
// Measures how far the rendered labels extend past the plot's content box
|
|
78
|
+
// and reports it to the caller (`Plot.svelte`), which grows its own local
|
|
79
|
+
// margin state to fit. Reported via a plain callback rather than
|
|
80
|
+
// svelteplot's shared margin context, to keep it local per `<Plot>`
|
|
81
|
+
// instance.
|
|
98
82
|
let groupEl: SVGGElement | undefined = $state();
|
|
99
83
|
|
|
100
84
|
watchLabelOverflow({
|
|
101
85
|
el: () => groupEl,
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
// doc comment), not from `usePlot().scales.x/y.fn.range()`: svelteplot's
|
|
106
|
-
// `<Plot>` accepts an explicit `width`/`height` (our `fixedWidth`) for
|
|
107
|
-
// its own real layout, but the scale object it exposes through context
|
|
108
|
-
// is computed off its *internal* auto-detected size instead — normally
|
|
109
|
-
// the same number, but never reconciled with `fixedWidth` when one is
|
|
110
|
-
// given. That left `.range()` reporting a stale/placeholder boundary
|
|
111
|
-
// (svelteplot's own internal default, unrelated to this plot's actual
|
|
112
|
-
// pixel size) wherever a caller fixes `width`/`height` explicitly — which
|
|
113
|
-
// every plot kind here does — so this watcher never saw genuine
|
|
114
|
-
// right/bottom overflow past the *real* edge and never grew the margin
|
|
115
|
-
// to fit it, leaving end-point labels to render past the plot's true
|
|
116
|
-
// right edge with no space reserved for them.
|
|
86
|
+
// Built from `width`/`height`/`numericMargins` rather than the live
|
|
87
|
+
// scale's own `.range()`, which doesn't reconcile with an explicitly
|
|
88
|
+
// sized `<Plot>`.
|
|
117
89
|
bounds: () => {
|
|
118
|
-
// getBBox()
|
|
119
|
-
//
|
|
90
|
+
// getBBox() isn't tracked reactively — depend on whatever changes the
|
|
91
|
+
// label layout so this re-measures.
|
|
120
92
|
void lastPoints;
|
|
121
93
|
void values;
|
|
122
94
|
return {
|
|
@@ -130,27 +102,20 @@
|
|
|
130
102
|
});
|
|
131
103
|
|
|
132
104
|
/**
|
|
133
|
-
* Pixel-space collision fix-up for end-point labels: resolves each
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
* in this file — it never becomes a data-space Y fed back into a mark,
|
|
139
|
-
* which would extend the y-scale's own domain and provoke an infinite
|
|
140
|
-
* autoscale ↔ declutter feedback loop.
|
|
105
|
+
* Pixel-space collision fix-up for end-point labels: resolves each
|
|
106
|
+
* label's pixel Y through the live y-scale, then declutters those pixels
|
|
107
|
+
* to find how far each needs to move to clear its neighbours (`extraDy`).
|
|
108
|
+
* `rank`/`lanesUsed`/`runSize` come from `laneAssignment`, which turns the
|
|
109
|
+
* decluttered runs into lane assignments.
|
|
141
110
|
*
|
|
142
|
-
*
|
|
143
|
-
* `
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
* bridge two unrelated clusters that each happened to need decluttering
|
|
148
|
-
* on their own. The elbow connector below turns `rank` into a lane index.
|
|
149
|
-
* A series with nothing nearby is absent from the map, and its label
|
|
150
|
-
* renders with no extra offset.
|
|
111
|
+
* Every label reaches out to the same shared lane column (`reach`/
|
|
112
|
+
* `laneReach`), not just the ones in its own cluster, so the whole set of
|
|
113
|
+
* end-of-line labels lines up on one horizontal line. A label that didn't
|
|
114
|
+
* need to move for collision avoidance still gets a connector to cover
|
|
115
|
+
* the resulting gap — see the `aligned` gate in the markup below.
|
|
151
116
|
*/
|
|
152
117
|
const declutteredOffsets = $derived.by(() => {
|
|
153
|
-
const map = new SvelteMap<string, { extraDy: number;
|
|
118
|
+
const map = new SvelteMap<string, { extraDy: number; aligned: boolean; rank: number; reach: number; laneReach: number }>();
|
|
154
119
|
if (lastPoints.length < 2) return map;
|
|
155
120
|
const yScale = plot.scales.y?.fn;
|
|
156
121
|
if (!yScale) return map;
|
|
@@ -160,20 +125,25 @@
|
|
|
160
125
|
pos: Number(yScale(Number(resolveAccessor(s.y)(lastRow)))),
|
|
161
126
|
}));
|
|
162
127
|
const sorted = [...items].sort((a, b) => a.pos - b.pos);
|
|
163
|
-
// Same content-box source as `watchLabelOverflow`'s `bounds()` above
|
|
164
|
-
// not `yScale.range()` — see that call's doc comment for why.
|
|
128
|
+
// Same content-box source as `watchLabelOverflow`'s `bounds()` above.
|
|
165
129
|
const bounds: [number, number] = [numericMargins.top, height - numericMargins.bottom];
|
|
166
130
|
const { positions: spread, connected } = declutter1D(items, minGap, bounds);
|
|
167
131
|
const spreadByName = new Map(spread.map((d) => [d.name, d.pos]));
|
|
168
132
|
const originals = sorted.map((item) => item.pos);
|
|
169
133
|
const finals = sorted.map((item) => spreadByName.get(item.name) ?? item.pos);
|
|
170
134
|
const ranks = laneAssignment(originals, finals, connected);
|
|
135
|
+
|
|
136
|
+
const anyGrouped = ranks.some((r) => r.runSize > 1);
|
|
137
|
+
const globalLanes = anyGrouped ? Math.max(...ranks.map((r) => r.lanesUsed)) : 1;
|
|
138
|
+
const reach = elbowGap + (globalLanes - 1) * laneStep + labelGap;
|
|
139
|
+
|
|
171
140
|
sorted.forEach((item, i) => {
|
|
172
141
|
map.set(item.name, {
|
|
173
142
|
extraDy: finals[i] - originals[i],
|
|
174
|
-
|
|
143
|
+
aligned: anyGrouped,
|
|
175
144
|
rank: ranks[i].rank,
|
|
176
|
-
|
|
145
|
+
reach,
|
|
146
|
+
laneReach: elbowGap + ranks[i].rank * laneStep,
|
|
177
147
|
});
|
|
178
148
|
});
|
|
179
149
|
return map;
|
|
@@ -185,23 +155,18 @@
|
|
|
185
155
|
{@const xFn = resolveAccessor(s.x)}
|
|
186
156
|
{@const yFn = resolveAccessor(s.y)}
|
|
187
157
|
{@const offset = declutteredOffsets.get(s.name)}
|
|
188
|
-
{@const extraDy = offset?.
|
|
189
|
-
{@const
|
|
190
|
-
{@const dir = Math.sign(baseDx) || 1}
|
|
191
|
-
{@const numLanes = offset?.inGroup ? offset.lanesUsed : 0}
|
|
192
|
-
{@const labelDx = offset?.inGroup
|
|
193
|
-
? dir * (elbowGap + (numLanes - 1) * laneStep + labelGap)
|
|
194
|
-
: baseDx}
|
|
158
|
+
{@const extraDy = offset?.aligned ? offset.extraDy : 0}
|
|
159
|
+
{@const labelDx = offset?.aligned ? dir * offset.reach : baseDx}
|
|
195
160
|
{@const text = formatValue(Number(yFn(lastRow)), s.name)}
|
|
196
161
|
{@const preferredTA = resolvedTextAnchor ?? defaultTA}
|
|
197
162
|
{@const preferredLA = font?.lineAnchor ?? defaultLA}
|
|
198
163
|
{@const disabled = disabledFor?.(s.name)}
|
|
199
164
|
{@const disabledStroke = disabledStrokeOverride(disabled)}
|
|
200
165
|
{@const disabledFill = disabledFillOverride(disabled)}
|
|
201
|
-
{#if offset?.
|
|
166
|
+
{#if offset?.aligned && plot.scales.x?.fn && plot.scales.y?.fn}
|
|
202
167
|
{@const px = Number(plot.scales.x.fn(xFn(lastRow)))}
|
|
203
168
|
{@const py = Number(plot.scales.y.fn(Number(yFn(lastRow))))}
|
|
204
|
-
{@const laneX = px + dir *
|
|
169
|
+
{@const laneX = px + dir * offset.laneReach}
|
|
205
170
|
{@const targetY = py + extraDy}
|
|
206
171
|
<path
|
|
207
172
|
d={`M ${px} ${py} H ${laneX} V ${targetY} H ${px + labelDx}`}
|
|
@@ -214,9 +179,7 @@
|
|
|
214
179
|
stroke-linejoin={connector?.strokeLinejoin ?? 'miter'}
|
|
215
180
|
/>
|
|
216
181
|
{/if}
|
|
217
|
-
<!--
|
|
218
|
-
despite declaring them in its types — a wrapping <g> catches the
|
|
219
|
-
boundary crossing instead (pointerenter/leave fire on every ancestor). -->
|
|
182
|
+
<!-- Text marks don't wire pointer handlers; a wrapping <g> catches them instead. -->
|
|
220
183
|
<g
|
|
221
184
|
role="presentation"
|
|
222
185
|
onpointerenter={onHoverSeries && (() => onHoverSeries(s.name))}
|
|
@@ -10,7 +10,7 @@ declare function $$render<TData extends Record<string, unknown>>(): {
|
|
|
10
10
|
color: string;
|
|
11
11
|
}[];
|
|
12
12
|
values: ValuesStyle | undefined;
|
|
13
|
-
/**
|
|
13
|
+
/** Plot pixel size, used with `numericMargins` to compute the content box. */
|
|
14
14
|
width: number;
|
|
15
15
|
height: number;
|
|
16
16
|
/** `BasePlotLayout`'s resolved margins, already numeric on every side. */
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { GeoProjection } from 'd3-geo';
|
|
2
|
+
import type { GeoCameraConfig } from '../plots/camera';
|
|
2
3
|
/**
|
|
3
4
|
* The projection names svelteplot's `<Plot projection>` accepts as a string
|
|
4
5
|
* (svelteplot itself doesn't export this as a type — defined here so authors
|
|
@@ -53,6 +54,7 @@ export type GeoScalesConfig = {
|
|
|
53
54
|
domain?: [number, number];
|
|
54
55
|
};
|
|
55
56
|
};
|
|
57
|
+
/** Wheel/pinch scale gesture only — see `CoordinatesConfig.pan` for drag-to-translate, and `.rotate` for drag-to-spin-the-globe. */
|
|
56
58
|
export type GeoZoomConfig = {
|
|
57
59
|
min?: number;
|
|
58
60
|
max?: number;
|
|
@@ -63,14 +65,18 @@ export type GeoZoomConfig = {
|
|
|
63
65
|
};
|
|
64
66
|
};
|
|
65
67
|
/**
|
|
66
|
-
* `coordinates` prop for GeoPlot — the
|
|
67
|
-
*
|
|
68
|
-
* Coordinates concern) and pan/zoom (navigating within that coordinate
|
|
69
|
-
* system), resolved together by `CoordinatesLayout`.
|
|
68
|
+
* `coordinates` prop for GeoPlot — bundles the projection, interactive
|
|
69
|
+
* gestures, and the declarative `camera`, resolved by `CoordinatesLayout`.
|
|
70
70
|
*/
|
|
71
71
|
export type CoordinatesConfig = {
|
|
72
72
|
/** Which map projection to draw with. Defaults to `'equal-earth'`. */
|
|
73
73
|
projection?: GeoProjectionConfig;
|
|
74
|
-
/** `true`/config enables
|
|
74
|
+
/** `true`/config enables scroll/pinch-to-zoom. Independent of `pan`/`rotate`. */
|
|
75
75
|
zoom?: boolean | GeoZoomConfig;
|
|
76
|
+
/** `true` enables drag-to-pan on a 2D (non-`orthographic`) projection. */
|
|
77
|
+
pan?: boolean;
|
|
78
|
+
/** `true` enables drag-to-rotate on `orthographic` (claims the drag gesture from `pan`). */
|
|
79
|
+
rotate?: boolean;
|
|
80
|
+
/** `GeoPlot`'s own declarative "current viewpoint" — frame/zoom/anchor on a target, animated on change. See `GeoCameraConfig`. */
|
|
81
|
+
camera?: GeoCameraConfig;
|
|
76
82
|
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { EasingName, EasingFn } from '../charts/interpolate';
|
|
2
|
+
import type { GeoInsetLocation } from '../markers/geo';
|
|
3
|
+
/** What the camera frames. `features` shares its id space with `segments`/`featureId`. `center` has no extent of its own — see `GeoCameraConfig.zoom`. */
|
|
4
|
+
export type GeoCameraTarget = {
|
|
5
|
+
features: string[];
|
|
6
|
+
} | {
|
|
7
|
+
center: [number, number];
|
|
8
|
+
} | {
|
|
9
|
+
bounds: GeoJSON.GeoJsonObject;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* How the camera moves to a newly resolved target.
|
|
13
|
+
* - `progress` unset: auto-plays once over `duration` ms, eased by `easing`.
|
|
14
|
+
* - `progress` set (a live 0–1 value, e.g. from scroll): renders the move as
|
|
15
|
+
* a pure function of it instead — stalls/reverses with the input.
|
|
16
|
+
*/
|
|
17
|
+
export type GeoCameraTransition = {
|
|
18
|
+
/** Auto-play duration in ms, ignored once `progress` is set. Defaults to 750. */
|
|
19
|
+
duration?: number;
|
|
20
|
+
/** Same `EasingName | EasingFn` vocabulary as `styles.gaps`/`TimelineConfig.interpolate`. */
|
|
21
|
+
easing?: EasingName | EasingFn;
|
|
22
|
+
/** A live 0–1 value the host drives itself instead of an auto-played duration. */
|
|
23
|
+
progress?: number;
|
|
24
|
+
/** Which way to interpolate rotation when ambiguous — only relevant for `orthographic`. `'shortest'` (default) or `'longest'`. No-op for 2D projections. */
|
|
25
|
+
direction?: 'shortest' | 'longest';
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* `coordinates.camera` — `GeoPlot`'s declarative "current viewpoint": frames,
|
|
29
|
+
* zooms and anchors on `target`, animated on change. Realized as a
|
|
30
|
+
* pixel-space pan/zoom transform for 2D projections, plus a `rotate`
|
|
31
|
+
* override for `orthographic`. Composes with interactive `coordinates.pan`/`zoom`/`rotate`.
|
|
32
|
+
*/
|
|
33
|
+
export type GeoCameraConfig = {
|
|
34
|
+
target: GeoCameraTarget;
|
|
35
|
+
/** Multiplier on the auto-fit scale for a `features`/`bounds` target (`1` = tight fit). For a `center` target, used directly as the pixel scale instead. */
|
|
36
|
+
zoom?: number;
|
|
37
|
+
/** Where the framed target lands within the plot frame — the same 9-point vocabulary `inset` markers use. Defaults to `'middle-middle'`. */
|
|
38
|
+
anchor?: GeoInsetLocation;
|
|
39
|
+
/** Pixel gap kept between the framed target and whichever edge(s) `anchor` pushes it toward. Defaults to 24. */
|
|
40
|
+
padding?: number;
|
|
41
|
+
transition?: GeoCameraTransition;
|
|
42
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|