@fundar/data-chart-telling 0.0.14 → 0.0.15
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.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 +1 -1
- package/dist/configuration/themes/index.d.ts +56 -40
- package/dist/index.d.ts +26 -12
- 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/DiscreteSection.svelte +1 -1
- 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 +66 -0
- package/dist/plots/bar/BarSegments.svelte.d.ts +36 -0
- package/dist/plots/bar/Plot.svelte +132 -76
- 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 +548 -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 +129 -47
- package/dist/plots/heatmap/Plot.svelte.d.ts +7 -3
- package/dist/plots/line/Plot.svelte +44 -16
- package/dist/plots/line/Plot.svelte.d.ts +7 -3
- package/dist/plots/pyramid/Plot.svelte +70 -25
- 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/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 +1 -1
- package/dist/types/charts/props.d.ts +9 -6
- package/dist/types/configuration/styling.d.ts +11 -5
- 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/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/charts/{Chart.svelte → BaseChart.svelte} +0 -0
- /package/dist/types/{plots → markers}/common.js +0 -0
- /package/dist/types/{plots/markers.js → markers/geo.js} +0 -0
- /package/dist/types/{plots/styles.js → markers/props.js} +0 -0
|
@@ -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 { LineSegmentStyle } 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, LineSegmentStyle, Marker, AxisBasedScalesConfig<TData>>;
|
|
5
9
|
exports: {};
|
|
6
10
|
bindings: "";
|
|
7
11
|
slots: {};
|
|
@@ -6,12 +6,20 @@
|
|
|
6
6
|
import { paletteColor } from '../../utils/color';
|
|
7
7
|
import { buildSeries } from '../../utils/grouping';
|
|
8
8
|
import { buildGroupedSeries, validateSegments, resolveSegmentsForSeries, matchesSegmentX, getDataForSegments } from '../utils/segments';
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import
|
|
14
|
-
import
|
|
9
|
+
import BasePlotLayout from '../../layout/plot/BasePlotLayout.svelte';
|
|
10
|
+
import AxisLayout from '../../layout/plot/AxisLayout.svelte';
|
|
11
|
+
import GridLayout from '../../layout/plot/GridLayout.svelte';
|
|
12
|
+
import RuleLayout from '../../layout/plot/RuleLayout.svelte';
|
|
13
|
+
import HoverMarker from '../../markers/HoverMarker.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 { Series, 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 { ValueAnchor } from '../../types/plots/constants';
|
|
21
|
+
import type { BarSegmentStyle } from '../../types/plots/segments/common';
|
|
22
|
+
import type { Marker } from '../../types/markers/common';
|
|
15
23
|
|
|
16
24
|
/**
|
|
17
25
|
* A population-pyramid-style diverging bar chart: each series is mirrored
|
|
@@ -29,6 +37,15 @@
|
|
|
29
37
|
type PyramidSide = 'left' | 'right';
|
|
30
38
|
type InternalSeries = Series<TData> & { side: PyramidSide };
|
|
31
39
|
|
|
40
|
+
type Props = BasePlotProps<
|
|
41
|
+
SeriesProps<TData> | DataProps<TData>,
|
|
42
|
+
TData,
|
|
43
|
+
BasePlotStyles,
|
|
44
|
+
BarSegmentStyle,
|
|
45
|
+
Marker,
|
|
46
|
+
AxisBasedScalesConfig<TData>
|
|
47
|
+
>;
|
|
48
|
+
|
|
32
49
|
let {
|
|
33
50
|
width,
|
|
34
51
|
height,
|
|
@@ -43,7 +60,7 @@
|
|
|
43
60
|
segments = {},
|
|
44
61
|
markers = [],
|
|
45
62
|
tooltip = undefined,
|
|
46
|
-
}:
|
|
63
|
+
}: Props = $props();
|
|
47
64
|
|
|
48
65
|
$effect(() => { validateSegments(segments); });
|
|
49
66
|
|
|
@@ -53,6 +70,10 @@
|
|
|
53
70
|
const showVals = $derived(styles.values?.show ?? false);
|
|
54
71
|
const fmtVal = $derived(styles.values?.format ?? ((v: number) => `${v}`));
|
|
55
72
|
const valAnchor = $derived<ValueAnchor>(styles.values?.anchor ?? 'outside');
|
|
73
|
+
// 'outside' is a per-point escape hatch resolved by HoverMarker; pyramid's
|
|
74
|
+
// own value labels already have their own `anchor: 'outside'` concept
|
|
75
|
+
// above, so treat it as unset here.
|
|
76
|
+
const resolvedTextAnchor = $derived(styles.values?.textAnchor === 'outside' ? undefined : styles.values?.textAnchor);
|
|
56
77
|
|
|
57
78
|
// Sort series names alphabetically; even index → left, odd → right.
|
|
58
79
|
// scales.z.reverse flips the assignment so the second name goes left.
|
|
@@ -68,7 +89,9 @@
|
|
|
68
89
|
});
|
|
69
90
|
|
|
70
91
|
const groupedSeries = $derived(
|
|
71
|
-
|
|
92
|
+
// connect: false — bars are discrete; there's no visual gap between
|
|
93
|
+
// adjacent categories to bridge (see buildGroupedSeries's own doc).
|
|
94
|
+
buildGroupedSeries<InternalSeries, BarSegmentStyle>(resolvedSeries, segments, false),
|
|
72
95
|
);
|
|
73
96
|
|
|
74
97
|
function signedValue(s: InternalSeries, d: TData): number {
|
|
@@ -149,31 +172,47 @@
|
|
|
149
172
|
}
|
|
150
173
|
return map;
|
|
151
174
|
});
|
|
175
|
+
|
|
176
|
+
// The single, fully-resolved scales object BasePlotLayout reads for
|
|
177
|
+
// everything (ticks, grid, sort, and the real svelteplot scale) — avoids
|
|
178
|
+
// keeping a second, separately-defaulted copy of `scales.x`/`scales.y`
|
|
179
|
+
// (our own diverging-domain + band-scale defaults) out of sync with the
|
|
180
|
+
// one actually forwarded.
|
|
181
|
+
const resolvedScales = $derived<AxisBasedScalesConfig<TData>>({
|
|
182
|
+
...scales,
|
|
183
|
+
x: {
|
|
184
|
+
domain: [-maxMagnitude, maxMagnitude],
|
|
185
|
+
tickFormat: (v: unknown) => fmtVal(Math.abs(Number(v)), ''),
|
|
186
|
+
...(scales.x ?? {}),
|
|
187
|
+
},
|
|
188
|
+
y: { type: 'band', padding: 0.2, domain: yDomain, ...(scales.y ?? {}) },
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const hoverActive = $derived(tooltip != null || hasHoverMarker(markers));
|
|
192
|
+
const hoverStrategy = $derived(resolveHoverStrategy(tooltip?.strategy, markers, 'y'));
|
|
193
|
+
const hoverSync = $derived(resolveHoverSync(tooltip?.sync, markers));
|
|
152
194
|
</script>
|
|
153
195
|
|
|
154
|
-
<
|
|
196
|
+
<BasePlotLayout
|
|
155
197
|
{width}
|
|
156
198
|
{height}
|
|
157
|
-
x={{
|
|
158
|
-
domain: [-maxMagnitude, maxMagnitude],
|
|
159
|
-
tickFormat: (v: unknown) => fmtVal(Math.abs(Number(v)), ''),
|
|
160
|
-
...(scales.x ?? {}),
|
|
161
|
-
}}
|
|
162
|
-
y={{ type: 'band', padding: 0.2, domain: yDomain, ...(scales.y ?? {}) }}
|
|
163
199
|
data={flat}
|
|
164
200
|
{getX}
|
|
165
201
|
{getY}
|
|
166
|
-
matchY={getY}
|
|
167
202
|
{hoverPoints}
|
|
168
203
|
label={labelFor}
|
|
169
204
|
{tooltip}
|
|
170
|
-
{
|
|
171
|
-
|
|
172
|
-
{
|
|
205
|
+
{hoverActive}
|
|
206
|
+
{hoverStrategy}
|
|
207
|
+
{hoverSync}
|
|
208
|
+
scales={resolvedScales}
|
|
173
209
|
{margins}
|
|
174
|
-
|
|
210
|
+
xTickRotate={resolvedScales?.x?.tickRotate}
|
|
175
211
|
>
|
|
176
|
-
{#snippet children({ matchedPoints, ruleX, ruleY, impliesRuleX, impliesRuleY })}
|
|
212
|
+
{#snippet children({ matchedPoints, ruleX, ruleY, impliesRuleX, impliesRuleY, numericMargins })}
|
|
213
|
+
<AxisLayout data={flat} {getX} {getY} {width} {height} {numericMargins} scales={resolvedScales} />
|
|
214
|
+
<GridLayout scales={resolvedScales} />
|
|
215
|
+
<RuleLayout {markers} seriesData={resolvedSeries} {matchedPoints} {ruleX} {ruleY} {impliesRuleX} {impliesRuleY}>
|
|
177
216
|
{#each groupedSeries as group (group.series.name)}
|
|
178
217
|
{@const defaultColor = group.series.side === 'left' ? leftColor : rightColor}
|
|
179
218
|
{@const getCategory = resolveAccessor(group.series.x)}
|
|
@@ -210,14 +249,19 @@
|
|
|
210
249
|
text={(d: TData) => fmtVal(Math.abs(Number(resolveAccessor(group.series.y)(d))), group.series.name)}
|
|
211
250
|
dx={styles.values?.dx ?? defaultDx}
|
|
212
251
|
dy={styles.values?.dy ?? 0}
|
|
213
|
-
textAnchor={
|
|
252
|
+
textAnchor={resolvedTextAnchor ?? defaultTextAnchor}
|
|
214
253
|
lineAnchor={styles.values?.lineAnchor ?? 'middle'}
|
|
215
254
|
lineHeight={styles.values?.lineHeight}
|
|
216
255
|
rotate={styles.values?.rotate}
|
|
217
256
|
class={styles.values?.class}
|
|
218
257
|
textClass={styles.values?.textClass}
|
|
219
|
-
fill={cfg.axis.color}
|
|
220
|
-
fontSize={12}
|
|
258
|
+
fill={styles.values?.fill ?? cfg.axis.color}
|
|
259
|
+
fontSize={styles.values?.fontSize ?? 12}
|
|
260
|
+
fontWeight={styles.values?.fontWeight}
|
|
261
|
+
fontStyle={styles.values?.fontStyle}
|
|
262
|
+
stroke={styles.values?.stroke}
|
|
263
|
+
strokeWidth={styles.values?.strokeWidth}
|
|
264
|
+
paintOrder={styles.values?.paintOrder}
|
|
221
265
|
/>
|
|
222
266
|
{/if}
|
|
223
267
|
|
|
@@ -282,5 +326,6 @@
|
|
|
282
326
|
{/if}
|
|
283
327
|
{/each}
|
|
284
328
|
{/each}
|
|
329
|
+
</RuleLayout>
|
|
285
330
|
{/snippet}
|
|
286
|
-
</
|
|
331
|
+
</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 { BarSegmentStyle } 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, BarSegmentStyle, Marker, AxisBasedScalesConfig<TData>>;
|
|
5
9
|
exports: {};
|
|
6
10
|
bindings: "";
|
|
7
11
|
slots: {};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { GeoFeature } from '../../types/plots/data/geo';
|
|
2
|
+
/**
|
|
3
|
+
* Bounding-box center of a geometry's flattened coordinates — a cheap
|
|
4
|
+
* approximation used for default label placement, not a true
|
|
5
|
+
* geographic/area centroid (no polygon-hole or geodesic-area weighting).
|
|
6
|
+
* For hover-matching specifically, see `geometryPartCentroids` instead —
|
|
7
|
+
* this single combined point can land far from every actual part of a
|
|
8
|
+
* multi-part geometry whose pieces are geographically spread out.
|
|
9
|
+
*/
|
|
10
|
+
export declare function geometryCentroid(geometry: GeoJSON.Geometry): [number, number];
|
|
11
|
+
/**
|
|
12
|
+
* Splits a geometry into its disjoint parts: one `Polygon` per element of a
|
|
13
|
+
* `MultiPolygon`'s `coordinates`, one `LineString` per element of a
|
|
14
|
+
* `MultiLineString`'s, `GeometryCollection` recursed and flattened, and any
|
|
15
|
+
* other geometry type returned unchanged as its own single-element array.
|
|
16
|
+
* The shared building block behind `geometryPartCentroids` (one centroid per
|
|
17
|
+
* part) and `filterGeometryParts` (keep/drop whole parts).
|
|
18
|
+
*/
|
|
19
|
+
export declare function splitGeometryParts(geometry: GeoJSON.Geometry): GeoJSON.Geometry[];
|
|
20
|
+
/**
|
|
21
|
+
* Like `geometryCentroid`, but returns one point per disjoint part instead
|
|
22
|
+
* of a single bounding-box center across all of them. A `MultiPolygon`
|
|
23
|
+
* whose parts are geographically far apart — e.g. Argentina's "Tierra del
|
|
24
|
+
* Fuego, Antártida e Islas del Atlántico Sur" province, whose `MultiPolygon`
|
|
25
|
+
* spans the mainland island, the Antarctic sector, and the South Atlantic
|
|
26
|
+
* islands in one feature — would otherwise get a single hover point
|
|
27
|
+
* stranded somewhere between all of them, matching hovers over *none* of
|
|
28
|
+
* the actually-rendered shapes. Multiple points attributed to the same
|
|
29
|
+
* feature is fine for hover-matching (nearest point wins, whichever part
|
|
30
|
+
* that is); it's only label placement (`geometryCentroid`) that needs a
|
|
31
|
+
* single point.
|
|
32
|
+
*/
|
|
33
|
+
export declare function geometryPartCentroids(geometry: GeoJSON.Geometry): [number, number][];
|
|
34
|
+
/**
|
|
35
|
+
* Keeps or drops each disjoint part of a geometry (see `splitGeometryParts`)
|
|
36
|
+
* based on a per-part predicate, reassembling a geometry of the same type
|
|
37
|
+
* from whatever survives — `null` if nothing does. Used to let a single
|
|
38
|
+
* feature's geometry be split between the main map and an `inset` marker
|
|
39
|
+
* (see `GeoDataProps.excludeParts`/`GeoInsetMarker.featurePartsFilter`)
|
|
40
|
+
* without preprocessing the source data: e.g. a province whose `MultiPolygon`
|
|
41
|
+
* spans both a mainland part and a far-flung part can have the main map
|
|
42
|
+
* exclude the latter while an inset shows only it.
|
|
43
|
+
*/
|
|
44
|
+
export declare function filterGeometryParts(geometry: GeoJSON.Geometry, keep: (part: {
|
|
45
|
+
centroid: [number, number];
|
|
46
|
+
geometry: GeoJSON.Geometry;
|
|
47
|
+
}) => boolean): GeoJSON.Geometry | null;
|
|
48
|
+
/** Default `featureId`: `feature.id`, else `properties.id`, else `properties.name`. */
|
|
49
|
+
export declare function defaultFeatureId<TProps extends Record<string, unknown>>(f: GeoFeature<TProps>): string;
|
|
50
|
+
/**
|
|
51
|
+
* Densifies a great-circle arc between two lon/lat points into `steps + 1`
|
|
52
|
+
* points via spherical linear interpolation (slerp) — used to draw a `link`
|
|
53
|
+
* marker's geodesic curve through the `Geo` mark (see `Plot.svelte`).
|
|
54
|
+
*
|
|
55
|
+
* Not delegated to svelteplot's own `Link` mark: its 0.14.2 projection
|
|
56
|
+
* integration (`Mark.svelte`'s paired-channel handling) reads a single
|
|
57
|
+
* `row.x`/`row.y` pair regardless of channel suffix, so a mark bound to
|
|
58
|
+
* `x1`/`y1`/`x2`/`y2` (never plain `x`/`y`) resolves every point as invalid
|
|
59
|
+
* whenever the plot has an active `projection` scale — which GeoPlot always
|
|
60
|
+
* does. `Geo` has no such limitation (geo marks are explicitly excluded from
|
|
61
|
+
* that code path), so a manually-interpolated `LineString` feature sidesteps
|
|
62
|
+
* the bug entirely while still producing the same geodesic visual.
|
|
63
|
+
*/
|
|
64
|
+
export declare function greatCircleInterpolate(lon1: number, lat1: number, lon2: number, lat2: number, steps?: number): [number, number][];
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
function flattenCoords(geom, out) {
|
|
2
|
+
switch (geom.type) {
|
|
3
|
+
case 'Point':
|
|
4
|
+
out.push(geom.coordinates);
|
|
5
|
+
break;
|
|
6
|
+
case 'MultiPoint':
|
|
7
|
+
case 'LineString':
|
|
8
|
+
for (const c of geom.coordinates)
|
|
9
|
+
out.push(c);
|
|
10
|
+
break;
|
|
11
|
+
case 'MultiLineString':
|
|
12
|
+
case 'Polygon':
|
|
13
|
+
for (const ring of geom.coordinates) {
|
|
14
|
+
for (const c of ring)
|
|
15
|
+
out.push(c);
|
|
16
|
+
}
|
|
17
|
+
break;
|
|
18
|
+
case 'MultiPolygon':
|
|
19
|
+
for (const poly of geom.coordinates) {
|
|
20
|
+
for (const ring of poly) {
|
|
21
|
+
for (const c of ring)
|
|
22
|
+
out.push(c);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
break;
|
|
26
|
+
case 'GeometryCollection':
|
|
27
|
+
for (const g of geom.geometries)
|
|
28
|
+
flattenCoords(g, out);
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function boundsCenter(coords) {
|
|
33
|
+
if (coords.length === 0)
|
|
34
|
+
return [0, 0];
|
|
35
|
+
let minX = Infinity;
|
|
36
|
+
let maxX = -Infinity;
|
|
37
|
+
let minY = Infinity;
|
|
38
|
+
let maxY = -Infinity;
|
|
39
|
+
for (const [x, y] of coords) {
|
|
40
|
+
if (x < minX)
|
|
41
|
+
minX = x;
|
|
42
|
+
if (x > maxX)
|
|
43
|
+
maxX = x;
|
|
44
|
+
if (y < minY)
|
|
45
|
+
minY = y;
|
|
46
|
+
if (y > maxY)
|
|
47
|
+
maxY = y;
|
|
48
|
+
}
|
|
49
|
+
return [(minX + maxX) / 2, (minY + maxY) / 2];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Bounding-box center of a geometry's flattened coordinates — a cheap
|
|
53
|
+
* approximation used for default label placement, not a true
|
|
54
|
+
* geographic/area centroid (no polygon-hole or geodesic-area weighting).
|
|
55
|
+
* For hover-matching specifically, see `geometryPartCentroids` instead —
|
|
56
|
+
* this single combined point can land far from every actual part of a
|
|
57
|
+
* multi-part geometry whose pieces are geographically spread out.
|
|
58
|
+
*/
|
|
59
|
+
export function geometryCentroid(geometry) {
|
|
60
|
+
const coords = [];
|
|
61
|
+
flattenCoords(geometry, coords);
|
|
62
|
+
return boundsCenter(coords);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Splits a geometry into its disjoint parts: one `Polygon` per element of a
|
|
66
|
+
* `MultiPolygon`'s `coordinates`, one `LineString` per element of a
|
|
67
|
+
* `MultiLineString`'s, `GeometryCollection` recursed and flattened, and any
|
|
68
|
+
* other geometry type returned unchanged as its own single-element array.
|
|
69
|
+
* The shared building block behind `geometryPartCentroids` (one centroid per
|
|
70
|
+
* part) and `filterGeometryParts` (keep/drop whole parts).
|
|
71
|
+
*/
|
|
72
|
+
export function splitGeometryParts(geometry) {
|
|
73
|
+
switch (geometry.type) {
|
|
74
|
+
case 'MultiPolygon':
|
|
75
|
+
return geometry.coordinates.map((polygon) => ({ type: 'Polygon', coordinates: polygon }));
|
|
76
|
+
case 'MultiLineString':
|
|
77
|
+
return geometry.coordinates.map((line) => ({ type: 'LineString', coordinates: line }));
|
|
78
|
+
case 'GeometryCollection':
|
|
79
|
+
return geometry.geometries.flatMap((g) => splitGeometryParts(g));
|
|
80
|
+
default:
|
|
81
|
+
return [geometry];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Like `geometryCentroid`, but returns one point per disjoint part instead
|
|
86
|
+
* of a single bounding-box center across all of them. A `MultiPolygon`
|
|
87
|
+
* whose parts are geographically far apart — e.g. Argentina's "Tierra del
|
|
88
|
+
* Fuego, Antártida e Islas del Atlántico Sur" province, whose `MultiPolygon`
|
|
89
|
+
* spans the mainland island, the Antarctic sector, and the South Atlantic
|
|
90
|
+
* islands in one feature — would otherwise get a single hover point
|
|
91
|
+
* stranded somewhere between all of them, matching hovers over *none* of
|
|
92
|
+
* the actually-rendered shapes. Multiple points attributed to the same
|
|
93
|
+
* feature is fine for hover-matching (nearest point wins, whichever part
|
|
94
|
+
* that is); it's only label placement (`geometryCentroid`) that needs a
|
|
95
|
+
* single point.
|
|
96
|
+
*/
|
|
97
|
+
export function geometryPartCentroids(geometry) {
|
|
98
|
+
return splitGeometryParts(geometry).map((part) => geometryCentroid(part));
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Keeps or drops each disjoint part of a geometry (see `splitGeometryParts`)
|
|
102
|
+
* based on a per-part predicate, reassembling a geometry of the same type
|
|
103
|
+
* from whatever survives — `null` if nothing does. Used to let a single
|
|
104
|
+
* feature's geometry be split between the main map and an `inset` marker
|
|
105
|
+
* (see `GeoDataProps.excludeParts`/`GeoInsetMarker.featurePartsFilter`)
|
|
106
|
+
* without preprocessing the source data: e.g. a province whose `MultiPolygon`
|
|
107
|
+
* spans both a mainland part and a far-flung part can have the main map
|
|
108
|
+
* exclude the latter while an inset shows only it.
|
|
109
|
+
*/
|
|
110
|
+
export function filterGeometryParts(geometry, keep) {
|
|
111
|
+
const parts = splitGeometryParts(geometry);
|
|
112
|
+
const keptParts = parts.filter((part) => keep({ centroid: geometryCentroid(part), geometry: part }));
|
|
113
|
+
if (keptParts.length === parts.length)
|
|
114
|
+
return geometry;
|
|
115
|
+
if (keptParts.length === 0)
|
|
116
|
+
return null;
|
|
117
|
+
if (parts.length === 1)
|
|
118
|
+
return keptParts[0];
|
|
119
|
+
switch (geometry.type) {
|
|
120
|
+
case 'MultiPolygon':
|
|
121
|
+
return { type: 'MultiPolygon', coordinates: keptParts.map((part) => part.coordinates) };
|
|
122
|
+
case 'MultiLineString':
|
|
123
|
+
return {
|
|
124
|
+
type: 'MultiLineString',
|
|
125
|
+
coordinates: keptParts.map((part) => part.coordinates),
|
|
126
|
+
};
|
|
127
|
+
case 'GeometryCollection':
|
|
128
|
+
return { type: 'GeometryCollection', geometries: keptParts };
|
|
129
|
+
default:
|
|
130
|
+
return geometry;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/** Default `featureId`: `feature.id`, else `properties.id`, else `properties.name`. */
|
|
134
|
+
export function defaultFeatureId(f) {
|
|
135
|
+
const props = f.properties;
|
|
136
|
+
return String(f.id ?? props?.id ?? props?.name ?? '');
|
|
137
|
+
}
|
|
138
|
+
function toRad(deg) {
|
|
139
|
+
return (deg * Math.PI) / 180;
|
|
140
|
+
}
|
|
141
|
+
function toDeg(rad) {
|
|
142
|
+
return (rad * 180) / Math.PI;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Densifies a great-circle arc between two lon/lat points into `steps + 1`
|
|
146
|
+
* points via spherical linear interpolation (slerp) — used to draw a `link`
|
|
147
|
+
* marker's geodesic curve through the `Geo` mark (see `Plot.svelte`).
|
|
148
|
+
*
|
|
149
|
+
* Not delegated to svelteplot's own `Link` mark: its 0.14.2 projection
|
|
150
|
+
* integration (`Mark.svelte`'s paired-channel handling) reads a single
|
|
151
|
+
* `row.x`/`row.y` pair regardless of channel suffix, so a mark bound to
|
|
152
|
+
* `x1`/`y1`/`x2`/`y2` (never plain `x`/`y`) resolves every point as invalid
|
|
153
|
+
* whenever the plot has an active `projection` scale — which GeoPlot always
|
|
154
|
+
* does. `Geo` has no such limitation (geo marks are explicitly excluded from
|
|
155
|
+
* that code path), so a manually-interpolated `LineString` feature sidesteps
|
|
156
|
+
* the bug entirely while still producing the same geodesic visual.
|
|
157
|
+
*/
|
|
158
|
+
export function greatCircleInterpolate(lon1, lat1, lon2, lat2, steps = 64) {
|
|
159
|
+
const phi1 = toRad(lat1);
|
|
160
|
+
const lambda1 = toRad(lon1);
|
|
161
|
+
const phi2 = toRad(lat2);
|
|
162
|
+
const lambda2 = toRad(lon2);
|
|
163
|
+
const sinPhi1 = Math.sin(phi1);
|
|
164
|
+
const cosPhi1 = Math.cos(phi1);
|
|
165
|
+
const sinPhi2 = Math.sin(phi2);
|
|
166
|
+
const cosPhi2 = Math.cos(phi2);
|
|
167
|
+
const d = 2 *
|
|
168
|
+
Math.asin(Math.sqrt(Math.sin((phi2 - phi1) / 2) ** 2 + cosPhi1 * cosPhi2 * Math.sin((lambda2 - lambda1) / 2) ** 2));
|
|
169
|
+
if (!Number.isFinite(d) || d === 0) {
|
|
170
|
+
return [
|
|
171
|
+
[lon1, lat1],
|
|
172
|
+
[lon2, lat2],
|
|
173
|
+
];
|
|
174
|
+
}
|
|
175
|
+
const points = [];
|
|
176
|
+
for (let i = 0; i <= steps; i++) {
|
|
177
|
+
const f = i / steps;
|
|
178
|
+
const a = Math.sin((1 - f) * d) / Math.sin(d);
|
|
179
|
+
const b = Math.sin(f * d) / Math.sin(d);
|
|
180
|
+
const x = a * cosPhi1 * Math.cos(lambda1) + b * cosPhi2 * Math.cos(lambda2);
|
|
181
|
+
const y = a * cosPhi1 * Math.sin(lambda1) + b * cosPhi2 * Math.sin(lambda2);
|
|
182
|
+
const z = a * sinPhi1 + b * sinPhi2;
|
|
183
|
+
const phi = Math.atan2(z, Math.sqrt(x * x + y * y));
|
|
184
|
+
const lambda = Math.atan2(y, x);
|
|
185
|
+
points.push([toDeg(lambda), toDeg(phi)]);
|
|
186
|
+
}
|
|
187
|
+
return points;
|
|
188
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Segment } from '../../types/plots/segments/config';
|
|
2
|
+
import type { SegmentsConfig } from '../../types/plots/props';
|
|
3
|
+
/** Feature-specific segments listed after 'default' so they override it on merge (see `resolveFeatureStyle`). */
|
|
4
|
+
export declare function resolveSegmentsForFeature<TStyle>(segments: SegmentsConfig<TStyle>, featureId: string): Segment<TStyle>[];
|
|
5
|
+
/**
|
|
6
|
+
* A geo feature is atomic — `Segment.areas` (x/y range restriction) doesn't
|
|
7
|
+
* apply, so every entry under a key is treated as a flat style patch applied
|
|
8
|
+
* in order: `'default'` entries apply first, feature-specific entries
|
|
9
|
+
* override. This intentionally does NOT reuse `segments.ts`'s
|
|
10
|
+
* `validateSegments` — its "only the first catch-all wins" warning assumes
|
|
11
|
+
* area-based semantics that don't hold here, since every geo segment entry
|
|
12
|
+
* merges rather than being dropped.
|
|
13
|
+
*/
|
|
14
|
+
export declare function resolveFeatureStyle<TStyle extends object>(segments: SegmentsConfig<TStyle>, featureId: string): TStyle;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const DEFAULT_KEY = 'default';
|
|
2
|
+
/** Feature-specific segments listed after 'default' so they override it on merge (see `resolveFeatureStyle`). */
|
|
3
|
+
export function resolveSegmentsForFeature(segments, featureId) {
|
|
4
|
+
return [...(segments[DEFAULT_KEY] ?? []), ...(segments[featureId] ?? [])];
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* A geo feature is atomic — `Segment.areas` (x/y range restriction) doesn't
|
|
8
|
+
* apply, so every entry under a key is treated as a flat style patch applied
|
|
9
|
+
* in order: `'default'` entries apply first, feature-specific entries
|
|
10
|
+
* override. This intentionally does NOT reuse `segments.ts`'s
|
|
11
|
+
* `validateSegments` — its "only the first catch-all wins" warning assumes
|
|
12
|
+
* area-based semantics that don't hold here, since every geo segment entry
|
|
13
|
+
* merges rather than being dropped.
|
|
14
|
+
*/
|
|
15
|
+
export function resolveFeatureStyle(segments, featureId) {
|
|
16
|
+
return resolveSegmentsForFeature(segments, featureId).reduce((acc, seg) => ({ ...acc, ...(seg.style ?? {}) }), {});
|
|
17
|
+
}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import type { AxisValue
|
|
2
|
-
import type {
|
|
1
|
+
import type { AxisValue } from '../../types/plots/axis';
|
|
2
|
+
import type { Series } from '../../types/plots/data/common';
|
|
3
|
+
import type { LineSegmentStyle } from '../../types/plots/segments/common';
|
|
4
|
+
import type { Segment, VisualGroup } from '../../types/plots/segments/config';
|
|
5
|
+
import type { SegmentsConfig } from '../../types/plots/props';
|
|
3
6
|
/**
|
|
4
7
|
* Returns the flat {@link Segment} list for one series.
|
|
5
8
|
* Series-specific segments are added before `'default'` segments so that
|
|
6
9
|
* series-specific catch-alls win over default catch-alls on tie-breaks.
|
|
7
10
|
*/
|
|
8
|
-
export declare function resolveSegmentsForSeries<TStyle>(segments:
|
|
11
|
+
export declare function resolveSegmentsForSeries<TStyle>(segments: SegmentsConfig<TStyle>, seriesName: string): Segment<TStyle>[];
|
|
9
12
|
/** Returns true when `xVal` falls inside any of a segment's x ranges. */
|
|
10
13
|
export declare function matchesSegmentX<TStyle>(xVal: AxisValue, seg: Segment<TStyle>): boolean;
|
|
11
14
|
/**
|
|
@@ -22,10 +25,16 @@ export declare function getDataForSegments<TData, TStyle>(data: TData[], xAccess
|
|
|
22
25
|
* (no `areas`) in the same series list. Since catch-alls are evaluated last
|
|
23
26
|
* and in order, only the first one can ever match; later ones are dead code.
|
|
24
27
|
*/
|
|
25
|
-
export declare function validateSegments<TStyle>(segments:
|
|
28
|
+
export declare function validateSegments<TStyle>(segments: SegmentsConfig<TStyle>): void;
|
|
26
29
|
/**
|
|
27
30
|
* Builds the full list of visual groups — one per series — ready to render.
|
|
28
31
|
* Each group's `visualSegments` is derived by resolving the flat segment list
|
|
29
32
|
* for that series from all groups in `segments`.
|
|
33
|
+
*
|
|
34
|
+
* `connect` bridges adjacent visual segments so a *continuous* mark (line)
|
|
35
|
+
* has no visual gap at their boundary — the default, since `buildGroupedSeries`
|
|
36
|
+
* itself defaults `TStyle` to {@link LineSegmentStyle}. Discrete marks (bar,
|
|
37
|
+
* pyramid) have no gap to bridge; pass `connect: false` so boundary rows
|
|
38
|
+
* aren't duplicated as an extra, separately-styled mark.
|
|
30
39
|
*/
|
|
31
|
-
export declare function buildGroupedSeries<S extends Series<any>, TStyle = LineSegmentStyle>(seriesList: S[], segments:
|
|
40
|
+
export declare function buildGroupedSeries<S extends Series<any>, TStyle = LineSegmentStyle>(seriesList: S[], segments: SegmentsConfig<TStyle>, connect?: boolean): VisualGroup<S, TStyle>[];
|
|
@@ -90,7 +90,7 @@ function mergeStyles(base, override) {
|
|
|
90
90
|
return result;
|
|
91
91
|
}
|
|
92
92
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
93
|
-
function resolveSeriesSegments(series, segs) {
|
|
93
|
+
function resolveSeriesSegments(series, segs, connect) {
|
|
94
94
|
if (!segs.length) {
|
|
95
95
|
return [{ data: series.data, style: {} }];
|
|
96
96
|
}
|
|
@@ -147,14 +147,18 @@ function resolveSeriesSegments(series, segs) {
|
|
|
147
147
|
for (let r = 0; r < runs.length; r++) {
|
|
148
148
|
const run = runs[r];
|
|
149
149
|
if (run.segIdx === -1) {
|
|
150
|
-
// Unmatched run:
|
|
151
|
-
// user segments without a gap.
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
bridged
|
|
156
|
-
if (
|
|
157
|
-
|
|
150
|
+
// Unmatched run: for a continuous mark (line), insert bridge points so
|
|
151
|
+
// it connects to adjacent user segments without a visual gap. Discrete
|
|
152
|
+
// marks (bar, pyramid) have no such gap to bridge — bridging would just
|
|
153
|
+
// duplicate the neighboring runs' boundary rows as an extra, wrongly
|
|
154
|
+
// styled mark at that same category.
|
|
155
|
+
const bridged = [...run.data];
|
|
156
|
+
if (connect) {
|
|
157
|
+
if (r > 0)
|
|
158
|
+
bridged.unshift(runs[r - 1].data[runs[r - 1].data.length - 1]);
|
|
159
|
+
if (r < runs.length - 1)
|
|
160
|
+
bridged.push(runs[r + 1].data[0]);
|
|
161
|
+
}
|
|
158
162
|
defaultVisual.push({ data: bridged, style: {} });
|
|
159
163
|
}
|
|
160
164
|
else {
|
|
@@ -170,9 +174,11 @@ function resolveSeriesSegments(series, segs) {
|
|
|
170
174
|
// When the next run is also a user segment (no unmatched data between them),
|
|
171
175
|
// insert a line-only connector so the lines join without a gap. Using a
|
|
172
176
|
// separate mark (rather than appending a bridge point to the current run)
|
|
173
|
-
// avoids rendering an extra dot at the shared boundary point.
|
|
177
|
+
// avoids rendering an extra dot at the shared boundary point. Skipped
|
|
178
|
+
// entirely for discrete marks (see above) — it would duplicate both
|
|
179
|
+
// runs' boundary rows as an extra mark.
|
|
174
180
|
const nextRun = runs[r + 1];
|
|
175
|
-
if (nextRun && nextRun.segIdx !== -1) {
|
|
181
|
+
if (connect && nextRun && nextRun.segIdx !== -1) {
|
|
176
182
|
connectors.push({
|
|
177
183
|
data: [run.data[run.data.length - 1], nextRun.data[0]],
|
|
178
184
|
style: connectorStyle,
|
|
@@ -190,10 +196,16 @@ function resolveSeriesSegments(series, segs) {
|
|
|
190
196
|
* Builds the full list of visual groups — one per series — ready to render.
|
|
191
197
|
* Each group's `visualSegments` is derived by resolving the flat segment list
|
|
192
198
|
* for that series from all groups in `segments`.
|
|
199
|
+
*
|
|
200
|
+
* `connect` bridges adjacent visual segments so a *continuous* mark (line)
|
|
201
|
+
* has no visual gap at their boundary — the default, since `buildGroupedSeries`
|
|
202
|
+
* itself defaults `TStyle` to {@link LineSegmentStyle}. Discrete marks (bar,
|
|
203
|
+
* pyramid) have no gap to bridge; pass `connect: false` so boundary rows
|
|
204
|
+
* aren't duplicated as an extra, separately-styled mark.
|
|
193
205
|
*/
|
|
194
|
-
export function buildGroupedSeries(seriesList, segments) {
|
|
206
|
+
export function buildGroupedSeries(seriesList, segments, connect = true) {
|
|
195
207
|
return seriesList.map((s) => ({
|
|
196
208
|
series: s,
|
|
197
|
-
visualSegments: resolveSeriesSegments(s, resolveSegmentsForSeries(segments, s.name)),
|
|
209
|
+
visualSegments: resolveSeriesSegments(s, resolveSegmentsForSeries(segments, s.name), connect),
|
|
198
210
|
}));
|
|
199
211
|
}
|