@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.
Files changed (148) hide show
  1. package/README.md +121 -17
  2. package/dist/charts/{Chart.svelte → BaseChart.svelte} +2 -0
  3. package/dist/charts/{Chart.svelte.d.ts → BaseChart.svelte.d.ts} +3 -3
  4. package/dist/charts/bar/Chart.svelte +5 -5
  5. package/dist/charts/bar/Chart.svelte.d.ts +1 -2
  6. package/dist/charts/heatmap/Chart.svelte +8 -7
  7. package/dist/charts/heatmap/Chart.svelte.d.ts +2 -3
  8. package/dist/charts/line/Chart.svelte +5 -5
  9. package/dist/charts/line/Chart.svelte.d.ts +1 -2
  10. package/dist/charts/pyramid/Chart.svelte +7 -6
  11. package/dist/charts/pyramid/Chart.svelte.d.ts +2 -3
  12. package/dist/configuration/config.svelte.js +2 -1
  13. package/dist/configuration/themes/index.d.ts +60 -40
  14. package/dist/index.d.ts +27 -13
  15. package/dist/index.js +12 -7
  16. package/dist/layout/facet/FacetLayout.svelte +1 -1
  17. package/dist/layout/facet/FacetLayout.svelte.d.ts +1 -1
  18. package/dist/layout/geo/GeoLayout.svelte +47 -0
  19. package/dist/layout/geo/GeoLayout.svelte.d.ts +22 -0
  20. package/dist/layout/geo/resolveProjection.d.ts +10 -0
  21. package/dist/layout/geo/resolveProjection.js +48 -0
  22. package/dist/layout/legend/ContinuousSection.svelte +158 -17
  23. package/dist/layout/legend/DiscreteSection.svelte +56 -4
  24. package/dist/layout/legend/interaction.svelte.d.ts +7 -0
  25. package/dist/layout/legend/interaction.svelte.js +24 -0
  26. package/dist/layout/plot/AxisLayout.svelte +95 -0
  27. package/dist/layout/plot/AxisLayout.svelte.d.ts +39 -0
  28. package/dist/layout/plot/BasePlotLayout.svelte +302 -0
  29. package/dist/layout/plot/BasePlotLayout.svelte.d.ts +95 -0
  30. package/dist/layout/plot/GridLayout.svelte +30 -0
  31. package/dist/layout/plot/GridLayout.svelte.d.ts +27 -0
  32. package/dist/layout/plot/RuleLayout.svelte +111 -0
  33. package/dist/layout/plot/RuleLayout.svelte.d.ts +40 -0
  34. package/dist/layout/plot/margins.d.ts +34 -0
  35. package/dist/layout/plot/margins.js +26 -0
  36. package/dist/layout/timeline/TimelineLayout.svelte +1 -1
  37. package/dist/layout/timeline/TimelineLayout.svelte.d.ts +1 -1
  38. package/dist/layout/tooltip/Tooltip.svelte +15 -0
  39. package/dist/layout/tooltip/Tooltip.svelte.d.ts +7 -0
  40. package/dist/layout/tooltip/TooltipLayout.svelte +45 -0
  41. package/dist/layout/tooltip/TooltipLayout.svelte.d.ts +41 -0
  42. package/dist/layout/tooltip/controller.svelte.d.ts +4 -3
  43. package/dist/layout/tooltip/controller.svelte.js +5 -6
  44. package/dist/layout/tooltip/hover.svelte.d.ts +1 -1
  45. package/dist/layout/tooltip/hover.svelte.js +1 -1
  46. package/dist/layout/tooltip/utils.d.ts +2 -2
  47. package/dist/markers/ContourClipMarker.svelte +77 -0
  48. package/dist/markers/ContourClipMarker.svelte.d.ts +10 -0
  49. package/dist/{plots/markers → markers}/DeltaMarker.svelte +4 -4
  50. package/dist/{plots/markers → markers}/DeltaMarker.svelte.d.ts +3 -3
  51. package/dist/{plots/markers → markers}/DotMarker.svelte +3 -3
  52. package/dist/{plots/markers → markers}/DotMarker.svelte.d.ts +3 -3
  53. package/dist/markers/HoverMarker.svelte +136 -0
  54. package/dist/{plots/markers → markers}/HoverMarker.svelte.d.ts +4 -4
  55. package/dist/{plots/markers → markers}/PeakMarker.svelte +2 -2
  56. package/dist/{plots/markers → markers}/PeakMarker.svelte.d.ts +2 -2
  57. package/dist/{plots/markers → markers}/TextMarker.svelte +11 -4
  58. package/dist/{plots/markers → markers}/TextMarker.svelte.d.ts +3 -3
  59. package/dist/markers/inset/InsetMarker.svelte +170 -0
  60. package/dist/markers/inset/InsetMarker.svelte.d.ts +51 -0
  61. package/dist/markers/inset/insetLayout.d.ts +25 -0
  62. package/dist/markers/inset/insetLayout.js +39 -0
  63. package/dist/markers/inset/insetProjection.d.ts +8 -0
  64. package/dist/markers/inset/insetProjection.js +39 -0
  65. package/dist/plots/bar/BarSegments.svelte +69 -0
  66. package/dist/plots/bar/BarSegments.svelte.d.ts +40 -0
  67. package/dist/plots/bar/Plot.svelte +140 -74
  68. package/dist/plots/bar/Plot.svelte.d.ts +7 -3
  69. package/dist/plots/bar/ValueLabels.svelte +89 -0
  70. package/dist/plots/bar/ValueLabels.svelte.d.ts +39 -0
  71. package/dist/plots/bar/hoverPoints.d.ts +15 -0
  72. package/dist/plots/bar/hoverPoints.js +35 -0
  73. package/dist/plots/bar/layout.svelte.d.ts +37 -0
  74. package/dist/plots/bar/layout.svelte.js +132 -0
  75. package/dist/plots/geo/Plot.svelte +577 -0
  76. package/dist/plots/geo/Plot.svelte.d.ts +29 -0
  77. package/dist/plots/geo/TileLayer.svelte +77 -0
  78. package/dist/plots/geo/TileLayer.svelte.d.ts +11 -0
  79. package/dist/plots/geo/projections/argentina.d.ts +8 -0
  80. package/dist/plots/geo/projections/argentina.js +35 -0
  81. package/dist/plots/geo/projections/fit.d.ts +8 -0
  82. package/dist/plots/geo/projections/fit.js +14 -0
  83. package/dist/plots/geo/rotate.svelte.d.ts +26 -0
  84. package/dist/plots/geo/rotate.svelte.js +79 -0
  85. package/dist/plots/geo/zoom.svelte.d.ts +32 -0
  86. package/dist/plots/geo/zoom.svelte.js +50 -0
  87. package/dist/plots/heatmap/Plot.svelte +171 -58
  88. package/dist/plots/heatmap/Plot.svelte.d.ts +7 -3
  89. package/dist/plots/line/Plot.svelte +67 -30
  90. package/dist/plots/line/Plot.svelte.d.ts +7 -3
  91. package/dist/plots/pyramid/Plot.svelte +88 -35
  92. package/dist/plots/pyramid/Plot.svelte.d.ts +7 -3
  93. package/dist/plots/utils/delta.d.ts +1 -1
  94. package/dist/plots/utils/geoAccessors.d.ts +64 -0
  95. package/dist/plots/utils/geoAccessors.js +188 -0
  96. package/dist/plots/utils/geoSegments.d.ts +14 -0
  97. package/dist/plots/utils/geoSegments.js +17 -0
  98. package/dist/plots/utils/legendDisabled.d.ts +15 -0
  99. package/dist/plots/utils/legendDisabled.js +41 -0
  100. package/dist/plots/utils/segments.d.ts +14 -5
  101. package/dist/plots/utils/segments.js +25 -13
  102. package/dist/plots/utils/tiles.d.ts +89 -0
  103. package/dist/plots/utils/tiles.js +159 -0
  104. package/dist/plots/utils/topojson.d.ts +9 -0
  105. package/dist/plots/utils/topojson.js +30 -0
  106. package/dist/types/charts/common.d.ts +2 -1
  107. package/dist/types/charts/legend.d.ts +32 -1
  108. package/dist/types/charts/props.d.ts +9 -6
  109. package/dist/types/configuration/styling.d.ts +13 -5
  110. package/dist/types/layout/legend.d.ts +26 -0
  111. package/dist/types/layout/tooltip.d.ts +14 -21
  112. package/dist/types/{plots/markers.d.ts → markers/common.d.ts} +2 -18
  113. package/dist/types/markers/geo.d.ts +237 -0
  114. package/dist/types/markers/props.d.ts +11 -0
  115. package/dist/types/markers/props.js +1 -0
  116. package/dist/types/plots/axis.d.ts +55 -0
  117. package/dist/types/plots/axis.js +1 -0
  118. package/dist/types/plots/constants.d.ts +30 -0
  119. package/dist/types/plots/constants.js +1 -0
  120. package/dist/types/plots/data/common.d.ts +40 -0
  121. package/dist/types/plots/data/common.js +1 -0
  122. package/dist/types/plots/data/geo.d.ts +74 -0
  123. package/dist/types/plots/data/geo.js +1 -0
  124. package/dist/types/plots/delta.d.ts +2 -2
  125. package/dist/types/plots/props.d.ts +105 -16
  126. package/dist/types/plots/scales/geo.d.ts +44 -0
  127. package/dist/types/plots/scales/geo.js +1 -0
  128. package/dist/types/plots/segments/common.d.ts +25 -0
  129. package/dist/types/plots/segments/common.js +1 -0
  130. package/dist/types/plots/segments/config.d.ts +51 -0
  131. package/dist/types/plots/segments/config.js +1 -0
  132. package/dist/types/plots/styles/common.d.ts +37 -0
  133. package/dist/types/plots/styles/common.js +1 -0
  134. package/dist/types/plots/styles/geo.d.ts +42 -0
  135. package/dist/types/plots/styles/geo.js +1 -0
  136. package/dist/types/plots/styling.d.ts +45 -0
  137. package/dist/types/plots/styling.js +1 -0
  138. package/dist/utils/grouping.d.ts +2 -1
  139. package/dist/utils/interpolate.d.ts +2 -1
  140. package/package.json +27 -13
  141. package/dist/layout/plot/PlotLayout.svelte +0 -294
  142. package/dist/layout/plot/PlotLayout.svelte.d.ts +0 -70
  143. package/dist/plots/markers/HoverMarker.svelte +0 -98
  144. package/dist/types/plots/common.d.ts +0 -97
  145. package/dist/types/plots/styles.d.ts +0 -187
  146. /package/dist/types/{plots/common.js → layout/legend.js} +0 -0
  147. /package/dist/types/{plots/markers.js → markers/common.js} +0 -0
  148. /package/dist/types/{plots/styles.js → markers/geo.js} +0 -0
@@ -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
+ }
@@ -0,0 +1,15 @@
1
+ import type { LegendDisabledStyle } from '../../types/charts/legend';
2
+ import type { StrokeStyle, DotStyle } from '../../types/plots/styling';
3
+ /**
4
+ * The three per-mark override layers a legend "disabled" state can apply,
5
+ * derived once from a {@link LegendDisabledStyle}. Each is `undefined` when
6
+ * there's no active override, so call sites can slot the result in as just
7
+ * one more `??` layer ahead of their existing segment-style/default-color
8
+ * chain — no branching needed at the call site.
9
+ */
10
+ export declare function disabledStrokeOverride(disabled: LegendDisabledStyle | undefined): StrokeStyle | undefined;
11
+ export declare function disabledDotsOverride(disabled: LegendDisabledStyle | undefined): DotStyle | undefined;
12
+ export declare function disabledFillOverride(disabled: LegendDisabledStyle | undefined): {
13
+ fill?: string;
14
+ fillOpacity?: number;
15
+ } | undefined;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * The three per-mark override layers a legend "disabled" state can apply,
3
+ * derived once from a {@link LegendDisabledStyle}. Each is `undefined` when
4
+ * there's no active override, so call sites can slot the result in as just
5
+ * one more `??` layer ahead of their existing segment-style/default-color
6
+ * chain — no branching needed at the call site.
7
+ */
8
+ export function disabledStrokeOverride(disabled) {
9
+ if (!disabled)
10
+ return undefined;
11
+ return {
12
+ stroke: disabled.stroke?.stroke,
13
+ strokeWidth: disabled.stroke?.strokeWidth,
14
+ strokeOpacity: disabled.stroke?.strokeOpacity ?? disabled.opacity,
15
+ strokeDasharray: disabled.stroke?.strokeDasharray,
16
+ strokeLinecap: disabled.stroke?.strokeLinecap,
17
+ strokeLinejoin: disabled.stroke?.strokeLinejoin,
18
+ };
19
+ }
20
+ export function disabledDotsOverride(disabled) {
21
+ if (!disabled)
22
+ return undefined;
23
+ return {
24
+ dotRadius: disabled.dots?.dotRadius,
25
+ dotFill: disabled.dots?.dotFill,
26
+ dotFillOpacity: disabled.dots?.dotFillOpacity ?? disabled.opacity,
27
+ dotSymbol: disabled.dots?.dotSymbol,
28
+ dotStroke: disabled.dots?.dotStroke,
29
+ dotStrokeWidth: disabled.dots?.dotStrokeWidth,
30
+ dotStrokeOpacity: disabled.dots?.dotStrokeOpacity ?? disabled.opacity,
31
+ dotStrokeDasharray: disabled.dots?.dotStrokeDasharray,
32
+ };
33
+ }
34
+ export function disabledFillOverride(disabled) {
35
+ if (!disabled)
36
+ return undefined;
37
+ return {
38
+ fill: disabled.fill,
39
+ fillOpacity: disabled.fillOpacity ?? disabled.opacity,
40
+ };
41
+ }
@@ -1,11 +1,14 @@
1
- import type { AxisValue, Series } from '../../types/plots/common';
2
- import type { Segment, Segments, LineSegmentStyle, VisualGroup } from '../../types/plots/styles';
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: Segments<TStyle>, seriesName: string): Segment<TStyle>[];
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: Segments<TStyle>): void;
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: Segments<TStyle>): VisualGroup<S, TStyle>[];
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: insert bridge points so the line connects to adjacent
151
- // user segments without a gap.
152
- const bridged = [];
153
- if (r > 0)
154
- bridged.push(runs[r - 1].data[runs[r - 1].data.length - 1]);
155
- bridged.push(...run.data);
156
- if (r < runs.length - 1)
157
- bridged.push(runs[r + 1].data[0]);
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
  }
@@ -0,0 +1,89 @@
1
+ export type TileCoord = {
2
+ x: number;
3
+ y: number;
4
+ z: number;
5
+ };
6
+ export type ScreenBounds = {
7
+ left: number;
8
+ top: number;
9
+ width: number;
10
+ height: number;
11
+ };
12
+ /** The subset of a `d3-geo`-style projection this module needs. */
13
+ export type ProjectionLike = {
14
+ (point: [number, number]): [number, number] | null;
15
+ invert?(point: [number, number]): [number, number] | null;
16
+ };
17
+ /** The minimal geometry-stream shape a projection's `.stream()` accepts — kept local instead of importing `d3-geo`'s own `GeoStream` type, so this module stays dependency-free. */
18
+ type MinimalStream = {
19
+ point: (x: number, y: number, z?: number) => void;
20
+ lineStart: () => void;
21
+ lineEnd: () => void;
22
+ polygonStart: () => void;
23
+ polygonEnd: () => void;
24
+ sphere?: () => void;
25
+ };
26
+ /** What `toProjectionLike` needs from a svelteplot-resolved projection. */
27
+ export type StreamingProjection = {
28
+ stream: (s: MinimalStream) => MinimalStream;
29
+ invert?: (point: [number, number]) => [number, number] | null;
30
+ };
31
+ /**
32
+ * Adapts a svelteplot-resolved projection (which exposes `.stream()`/
33
+ * `.invert()`, but — unlike a raw `d3-geo` projection instance — isn't
34
+ * itself callable as `(point) => [x, y]`) into a `ProjectionLike`, by
35
+ * feeding a single point through its stream pipeline and capturing the
36
+ * projected output.
37
+ */
38
+ export declare function toProjectionLike(projection: StreamingProjection): ProjectionLike;
39
+ /**
40
+ * Derives the visible XYZ tile grid from svelteplot's own already-fitted
41
+ * projection (rather than this package computing/duplicating a `d3-geo` fit
42
+ * itself) — see `TileLayer.svelte`. Only correct for Mercator-family
43
+ * projections, where a screen-space rectangle maps back to a lon/lat bbox
44
+ * whose tile coverage is well-defined; callers are responsible for the
45
+ * projection-compatibility check (see `Plot.svelte`'s validation effect).
46
+ */
47
+ export declare function visibleTiles(projection: ProjectionLike, bounds: ScreenBounds, tileSize?: number, minZoom?: number, maxZoom?: number): TileCoord[];
48
+ /** An affine tile-space → screen-space mapping (see `buildMercatorFit`), reused across every tile in a grid instead of forward-projecting each one individually. */
49
+ export type MercatorFit = {
50
+ z: number;
51
+ screenLeft: number;
52
+ screenTop: number;
53
+ tileXAtLeft: number;
54
+ tileYAtTop: number;
55
+ pxPerTileX: number;
56
+ pxPerTileY: number;
57
+ };
58
+ /**
59
+ * Builds an affine tile-space → screen-space mapping from two reliable
60
+ * anchor points — `bounds`' own top-left/bottom-right corners, inverted
61
+ * back to lon/lat — rather than forward-projecting each tile's own corners
62
+ * individually (what this replaced). That per-tile approach broke under a
63
+ * projection whose scale/translate were fit to a *custom* domain rather
64
+ * than set via its own `.scale()`/`.translate()`: `d3-geo`'s cylindrical
65
+ * projections (mercator included) auto-install an internal `clipExtent`
66
+ * sized to whatever scale/translate they were constructed or last
67
+ * `.scale()`/`.translate()`-called with (guarding against the y → ±Infinity
68
+ * blowup near the poles); svelteplot's own domain-fitting wraps the
69
+ * *unmodified* projection instance in an external transform instead of
70
+ * calling those setters, so that internal clip keeps silently rejecting
71
+ * forward-projected points outside its stale bounds — nulling out most of
72
+ * a tile grid even for tiles nowhere near a pole. `.invert()` isn't routed
73
+ * through that clip, so anchoring to two known-good inverted corners
74
+ * sidesteps it entirely — sound because Mercator's tile space (longitude,
75
+ * and the standard log-tan latitude transform used by `latToTileY`) is
76
+ * affine in screen space by construction, so one linear fit applies
77
+ * uniformly to every tile in the grid.
78
+ */
79
+ export declare function buildMercatorFit(projection: ProjectionLike, bounds: ScreenBounds, z: number): MercatorFit | null;
80
+ /** Places a tile on screen via a `MercatorFit` — `null` only if `tile` is from a different zoom level than the fit was built for. */
81
+ export declare function tileScreenBounds(fit: MercatorFit, tile: TileCoord): ScreenBounds | null;
82
+ /**
83
+ * Fills `{z}/{x}/{y}` placeholders in a tile URL template. Also supports
84
+ * `{-y}` for TMS-scheme sources (e.g. IGN's tile services), whose row index
85
+ * counts from the south instead of the north — the standard XYZ-to-TMS
86
+ * conversion `2^z - 1 - y`.
87
+ */
88
+ export declare function tileUrl(template: string, tile: TileCoord): string;
89
+ export {};
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Adapts a svelteplot-resolved projection (which exposes `.stream()`/
3
+ * `.invert()`, but — unlike a raw `d3-geo` projection instance — isn't
4
+ * itself callable as `(point) => [x, y]`) into a `ProjectionLike`, by
5
+ * feeding a single point through its stream pipeline and capturing the
6
+ * projected output.
7
+ */
8
+ export function toProjectionLike(projection) {
9
+ const fn = ((point) => {
10
+ let result = null;
11
+ const sink = {
12
+ point: (x, y) => {
13
+ result = [x, y];
14
+ },
15
+ lineStart() { },
16
+ lineEnd() { },
17
+ polygonStart() { },
18
+ polygonEnd() { },
19
+ };
20
+ projection.stream(sink).point(point[0], point[1]);
21
+ return result;
22
+ });
23
+ if (projection.invert)
24
+ fn.invert = (point) => projection.invert(point);
25
+ return fn;
26
+ }
27
+ function lonToTileX(lon, z) {
28
+ return ((lon + 180) / 360) * 2 ** z;
29
+ }
30
+ function latToTileY(lat, z) {
31
+ const rad = (lat * Math.PI) / 180;
32
+ return ((1 - Math.log(Math.tan(rad) + 1 / Math.cos(rad)) / Math.PI) / 2) * 2 ** z;
33
+ }
34
+ /**
35
+ * Picks an integer XYZ zoom level whose tile pixel density roughly matches
36
+ * the projection's *current* pixels-per-degree at the map center — so tiles
37
+ * neither look blurry (too coarse) nor waste bandwidth (too fine).
38
+ */
39
+ function estimateZoomLevel(projection, centerLon, centerLat, tileSize) {
40
+ const eps = 0.01;
41
+ const p0 = projection([centerLon, centerLat]);
42
+ const p1 = projection([centerLon + eps, centerLat]);
43
+ if (!p0 || !p1)
44
+ return null;
45
+ const pixelsPerDegree = Math.abs(p1[0] - p0[0]) / eps;
46
+ const worldPixelsAtZ0 = tileSize / 360;
47
+ const ratio = pixelsPerDegree / worldPixelsAtZ0;
48
+ if (!Number.isFinite(ratio) || ratio <= 0)
49
+ return null;
50
+ return Math.round(Math.log2(ratio));
51
+ }
52
+ /**
53
+ * Derives the visible XYZ tile grid from svelteplot's own already-fitted
54
+ * projection (rather than this package computing/duplicating a `d3-geo` fit
55
+ * itself) — see `TileLayer.svelte`. Only correct for Mercator-family
56
+ * projections, where a screen-space rectangle maps back to a lon/lat bbox
57
+ * whose tile coverage is well-defined; callers are responsible for the
58
+ * projection-compatibility check (see `Plot.svelte`'s validation effect).
59
+ */
60
+ export function visibleTiles(projection, bounds, tileSize = 256, minZoom = 0, maxZoom = 19) {
61
+ if (!projection.invert)
62
+ return [];
63
+ const corners = [
64
+ [bounds.left, bounds.top],
65
+ [bounds.left + bounds.width, bounds.top],
66
+ [bounds.left, bounds.top + bounds.height],
67
+ [bounds.left + bounds.width, bounds.top + bounds.height],
68
+ ];
69
+ const lonLats = corners.map((c) => projection.invert(c)).filter((p) => p != null);
70
+ if (lonLats.length === 0)
71
+ return [];
72
+ const centerScreen = [bounds.left + bounds.width / 2, bounds.top + bounds.height / 2];
73
+ const center = projection.invert(centerScreen);
74
+ const z = center
75
+ ? Math.max(minZoom, Math.min(maxZoom, estimateZoomLevel(projection, center[0], center[1], tileSize) ?? minZoom))
76
+ : minZoom;
77
+ const lons = lonLats.map((p) => p[0]);
78
+ const lats = lonLats.map((p) => p[1]);
79
+ const minTileX = Math.floor(lonToTileX(Math.min(...lons), z));
80
+ const maxTileX = Math.floor(lonToTileX(Math.max(...lons), z));
81
+ const minTileY = Math.floor(latToTileY(Math.max(...lats), z));
82
+ const maxTileY = Math.floor(latToTileY(Math.min(...lats), z));
83
+ const maxTile = 2 ** z - 1;
84
+ const tiles = [];
85
+ for (let x = Math.max(0, minTileX); x <= Math.min(maxTile, maxTileX); x++) {
86
+ for (let y = Math.max(0, minTileY); y <= Math.min(maxTile, maxTileY); y++) {
87
+ tiles.push({ x, y, z });
88
+ }
89
+ }
90
+ return tiles;
91
+ }
92
+ /**
93
+ * Builds an affine tile-space → screen-space mapping from two reliable
94
+ * anchor points — `bounds`' own top-left/bottom-right corners, inverted
95
+ * back to lon/lat — rather than forward-projecting each tile's own corners
96
+ * individually (what this replaced). That per-tile approach broke under a
97
+ * projection whose scale/translate were fit to a *custom* domain rather
98
+ * than set via its own `.scale()`/`.translate()`: `d3-geo`'s cylindrical
99
+ * projections (mercator included) auto-install an internal `clipExtent`
100
+ * sized to whatever scale/translate they were constructed or last
101
+ * `.scale()`/`.translate()`-called with (guarding against the y → ±Infinity
102
+ * blowup near the poles); svelteplot's own domain-fitting wraps the
103
+ * *unmodified* projection instance in an external transform instead of
104
+ * calling those setters, so that internal clip keeps silently rejecting
105
+ * forward-projected points outside its stale bounds — nulling out most of
106
+ * a tile grid even for tiles nowhere near a pole. `.invert()` isn't routed
107
+ * through that clip, so anchoring to two known-good inverted corners
108
+ * sidesteps it entirely — sound because Mercator's tile space (longitude,
109
+ * and the standard log-tan latitude transform used by `latToTileY`) is
110
+ * affine in screen space by construction, so one linear fit applies
111
+ * uniformly to every tile in the grid.
112
+ */
113
+ export function buildMercatorFit(projection, bounds, z) {
114
+ if (!projection.invert)
115
+ return null;
116
+ const topLeft = projection.invert([bounds.left, bounds.top]);
117
+ const bottomRight = projection.invert([bounds.left + bounds.width, bounds.top + bounds.height]);
118
+ if (!topLeft || !bottomRight)
119
+ return null;
120
+ const tileXAtLeft = lonToTileX(topLeft[0], z);
121
+ const tileXAtRight = lonToTileX(bottomRight[0], z);
122
+ const tileYAtTop = latToTileY(topLeft[1], z);
123
+ const tileYAtBottom = latToTileY(bottomRight[1], z);
124
+ if (tileXAtRight === tileXAtLeft || tileYAtBottom === tileYAtTop)
125
+ return null;
126
+ return {
127
+ z,
128
+ screenLeft: bounds.left,
129
+ screenTop: bounds.top,
130
+ tileXAtLeft,
131
+ tileYAtTop,
132
+ pxPerTileX: bounds.width / (tileXAtRight - tileXAtLeft),
133
+ pxPerTileY: bounds.height / (tileYAtBottom - tileYAtTop),
134
+ };
135
+ }
136
+ /** Places a tile on screen via a `MercatorFit` — `null` only if `tile` is from a different zoom level than the fit was built for. */
137
+ export function tileScreenBounds(fit, tile) {
138
+ if (tile.z !== fit.z)
139
+ return null;
140
+ return {
141
+ left: fit.screenLeft + (tile.x - fit.tileXAtLeft) * fit.pxPerTileX,
142
+ top: fit.screenTop + (tile.y - fit.tileYAtTop) * fit.pxPerTileY,
143
+ width: fit.pxPerTileX,
144
+ height: fit.pxPerTileY,
145
+ };
146
+ }
147
+ /**
148
+ * Fills `{z}/{x}/{y}` placeholders in a tile URL template. Also supports
149
+ * `{-y}` for TMS-scheme sources (e.g. IGN's tile services), whose row index
150
+ * counts from the south instead of the north — the standard XYZ-to-TMS
151
+ * conversion `2^z - 1 - y`.
152
+ */
153
+ export function tileUrl(template, tile) {
154
+ return template
155
+ .replace('{z}', String(tile.z))
156
+ .replace('{x}', String(tile.x))
157
+ .replace('{-y}', String(2 ** tile.z - 1 - tile.y))
158
+ .replace('{y}', String(tile.y));
159
+ }
@@ -0,0 +1,9 @@
1
+ import type { GeoFeature, Topology } from '../../types/plots/data/geo';
2
+ export declare function isTopology(data: unknown): data is Topology;
3
+ /**
4
+ * Converts a TopoJSON `Topology` to GeoJSON via `topojson-client`'s
5
+ * `feature()`, loaded with a dynamic import so the package works perfectly
6
+ * for consumers who never install `topojson-client` — it's an optional peer
7
+ * dependency (see package.json), never statically imported.
8
+ */
9
+ export declare function topologyToGeoJson<TProps extends Record<string, unknown>>(topology: Topology, object: string): Promise<GeoFeature<TProps>[]>;