@bicharts/chart-host 0.6.103 → 0.6.106
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/card.mjs +2 -2
- package/dist/chunk-5WTQN5DL.mjs +96 -0
- package/dist/{chunk-4BR6VJQX.mjs → chunk-K6UXTDMR.mjs} +40 -100
- package/dist/{chunk-D6A37G2G.mjs → chunk-MU4BWWO6.mjs} +10 -10
- package/dist/{chunk-CNT3QA3R.mjs → chunk-SYUJ4TPX.mjs} +961 -771
- package/dist/index.mjs +22 -15
- package/dist/provinces.mjs +229 -0
- package/dist/react.mjs +6 -4
- package/dist/types/admin1LookupLoader.d.ts +26 -0
- package/dist/types/contract.d.ts +1 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/optionsVocabulary.d.ts +5 -0
- package/dist/types/payload.d.ts +63 -4
- package/dist/types/provinceGeo.d.ts +38 -0
- package/dist/types/provinceHost.d.ts +47 -0
- package/dist/types/provinces.d.ts +3 -0
- package/dist/zoom-memory.mjs +1 -1
- package/package.json +6 -1
package/dist/index.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
formatSourceDateFor,
|
|
6
6
|
isExcelDateFormat,
|
|
7
7
|
normaliseAggregation
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-MU4BWWO6.mjs";
|
|
9
9
|
import {
|
|
10
10
|
captureSvgSnapshot,
|
|
11
11
|
chartSvgOf,
|
|
@@ -63,7 +63,6 @@ import {
|
|
|
63
63
|
censusColourSpread,
|
|
64
64
|
censusMarks,
|
|
65
65
|
censusValuePlacement,
|
|
66
|
-
clearGeoCache,
|
|
67
66
|
clippedTextFlag,
|
|
68
67
|
colourSpreadFlag,
|
|
69
68
|
compileRenderFn,
|
|
@@ -79,21 +78,16 @@ import {
|
|
|
79
78
|
exportFileName,
|
|
80
79
|
exportFormatsFor,
|
|
81
80
|
exportTableService,
|
|
82
|
-
geoAssetFor,
|
|
83
|
-
geoFromCache,
|
|
84
81
|
glyphSampleGrid,
|
|
85
82
|
invalidSentinelReason,
|
|
86
83
|
isBlankRender,
|
|
87
84
|
isInvalidSentinelError,
|
|
88
85
|
isPillBackdropAlpha,
|
|
89
|
-
loadGeo,
|
|
90
86
|
minContrastOver,
|
|
91
87
|
newIdScope,
|
|
92
88
|
noopViewStateProvider,
|
|
93
89
|
pickColorOver,
|
|
94
90
|
pillBacksGlyph,
|
|
95
|
-
registerGeo,
|
|
96
|
-
registerGeoAsset,
|
|
97
91
|
relativeLuminance,
|
|
98
92
|
requiredD3Plugins,
|
|
99
93
|
resolveAutoColorScale,
|
|
@@ -111,7 +105,7 @@ import {
|
|
|
111
105
|
toRGBA,
|
|
112
106
|
toSourceRows,
|
|
113
107
|
valuePlacementFlag
|
|
114
|
-
} from "./chunk-
|
|
108
|
+
} from "./chunk-K6UXTDMR.mjs";
|
|
115
109
|
import {
|
|
116
110
|
LASSO_AXES_SLOT,
|
|
117
111
|
LASSO_COUNT_CLASS,
|
|
@@ -200,10 +194,18 @@ import {
|
|
|
200
194
|
unpinScrolledAxis
|
|
201
195
|
} from "./chunk-IE7TM6DX.mjs";
|
|
202
196
|
import {
|
|
203
|
-
|
|
197
|
+
clearGeoCache,
|
|
198
|
+
geoAssetFor,
|
|
199
|
+
geoFromCache,
|
|
200
|
+
loadGeo,
|
|
201
|
+
registerGeo,
|
|
202
|
+
registerGeoAsset
|
|
203
|
+
} from "./chunk-5WTQN5DL.mjs";
|
|
204
|
+
import {
|
|
205
|
+
Kt,
|
|
204
206
|
l,
|
|
205
207
|
o
|
|
206
|
-
} from "./chunk-
|
|
208
|
+
} from "./chunk-SYUJ4TPX.mjs";
|
|
207
209
|
import "./chunk-A2GMXZP7.mjs";
|
|
208
210
|
import {
|
|
209
211
|
ACTIVE_TICK_CLASS,
|
|
@@ -334,7 +336,12 @@ var OPTIONS_VOCABULARY = Object.freeze({
|
|
|
334
336
|
* that made the chart asked for them. "on" / "off" is a reader's choice and wins either way.
|
|
335
337
|
*/
|
|
336
338
|
hoverDropLines: entry({ timing: "live", default: "", values: ["", "on", "off"] }),
|
|
339
|
+
/** A province map draws the national borders over its provinces unless this is false (a reader's "Country borders"). */
|
|
340
|
+
geoBorders: entry({ timing: "live", default: true }),
|
|
337
341
|
// ---- the next generated chart ----
|
|
342
|
+
/** A reader's "Derive provinces": false measures no city or coordinate rollup, so only a real province column offers
|
|
343
|
+
* a province map. It changes what the profile measures, so it travels in the shape rather than as a field. */
|
|
344
|
+
deriveProvinces: entry({ timing: "next-generate", default: true }),
|
|
338
345
|
levelOfDetail: entry({ timing: "next-generate", default: "", values: ["", "High", "Medium", "Low"], wire: "clientHints" }),
|
|
339
346
|
favorLimitTo: entry({ timing: "next-generate", default: "", values: ["", "latest", "top / greatest", "bottom / smallest", "most common", "outliers"], wire: "clientHints" }),
|
|
340
347
|
/** 0 asks for no limit, and travels as absent (`wireLimitCount`): any value the server receives is a limit. */
|
|
@@ -821,10 +828,10 @@ function buildProductFeedbackPayload(input) {
|
|
|
821
828
|
};
|
|
822
829
|
}
|
|
823
830
|
var DEFAULT_TEXT2 = {
|
|
824
|
-
label: "
|
|
825
|
-
placeholder: "
|
|
826
|
-
hint: "This
|
|
827
|
-
open: "
|
|
831
|
+
label: "Suggestions for the team that makes this product",
|
|
832
|
+
placeholder: "Message to the makers of this product: a feature you'd like, or something that got in your way. This is not a chart request.",
|
|
833
|
+
hint: "This goes to the people who build this product, not to the chart. It won't draw a chart.",
|
|
834
|
+
open: "Send a suggestion to the product team",
|
|
828
835
|
send: "Send",
|
|
829
836
|
sending: "Sending...",
|
|
830
837
|
sent: "Thank you - your suggestion was sent.",
|
|
@@ -2290,7 +2297,7 @@ function fingerprintComponentDigest(components) {
|
|
|
2290
2297
|
|
|
2291
2298
|
// src/index.ts
|
|
2292
2299
|
function registerCityTable(packed) {
|
|
2293
|
-
|
|
2300
|
+
Kt(packed);
|
|
2294
2301
|
}
|
|
2295
2302
|
export {
|
|
2296
2303
|
ACTIVE_TICK_CLASS,
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/*! @bicharts/chart-host — Apache-2.0. Bundled reference data: GeoNames (https://www.geonames.org/) CC BY 4.0; Natural Earth and US Census/TIGER (public domain). Full text: NOTICE in this package. */
|
|
2
|
+
import {
|
|
3
|
+
geoFromCache,
|
|
4
|
+
registerGeo
|
|
5
|
+
} from "./chunk-5WTQN5DL.mjs";
|
|
6
|
+
import {
|
|
7
|
+
Si,
|
|
8
|
+
W,
|
|
9
|
+
bt
|
|
10
|
+
} from "./chunk-SYUJ4TPX.mjs";
|
|
11
|
+
import "./chunk-A2GMXZP7.mjs";
|
|
12
|
+
import "./chunk-GHODWOAL.mjs";
|
|
13
|
+
|
|
14
|
+
// src/provinceGeo.ts
|
|
15
|
+
var PROVINCE_REGIONS = ["europe", "asia", "africa", "south-america", "north-america", "oceania"];
|
|
16
|
+
function provinceGeoPath(region) {
|
|
17
|
+
return `/geo/admin1-${String(region).toLowerCase()}.v1.json`;
|
|
18
|
+
}
|
|
19
|
+
function provinceGeoKey(region) {
|
|
20
|
+
return `admin1:${String(region).toLowerCase()}`;
|
|
21
|
+
}
|
|
22
|
+
function geoKeyFor(res) {
|
|
23
|
+
if (res.geoGrain && res.geoGrain.toLowerCase() === "admin1" && res.geoRegion) return provinceGeoKey(res.geoRegion);
|
|
24
|
+
return res.geoKind ?? null;
|
|
25
|
+
}
|
|
26
|
+
var inflight = /* @__PURE__ */ new Map();
|
|
27
|
+
async function loadProvinceGeo(region, opts = {}) {
|
|
28
|
+
const key = provinceGeoKey(region);
|
|
29
|
+
const cached = geoFromCache(key);
|
|
30
|
+
if (cached) return cached;
|
|
31
|
+
if (!PROVINCE_REGIONS.includes(String(region).toLowerCase())) return void 0;
|
|
32
|
+
const url = opts.url ?? (opts.origin ? opts.origin.replace(/\/+$/, "") : "") + provinceGeoPath(region);
|
|
33
|
+
let p = inflight.get(key);
|
|
34
|
+
if (!p) {
|
|
35
|
+
const started = Date.now();
|
|
36
|
+
p = (async () => {
|
|
37
|
+
try {
|
|
38
|
+
const doc = opts.fetchJson ? await opts.fetchJson(url) : await (await fetch(url, { credentials: "omit" })).json();
|
|
39
|
+
if (!doc || doc.type !== "FeatureCollection" || !Array.isArray(doc.features) || doc.features.length === 0)
|
|
40
|
+
throw new Error("not a FeatureCollection");
|
|
41
|
+
registerGeo(key, doc);
|
|
42
|
+
opts.log?.("province-geo-loaded", { region, ms: Date.now() - started, features: doc.features.length });
|
|
43
|
+
return doc;
|
|
44
|
+
} catch (e) {
|
|
45
|
+
opts.log?.("province-geo-failed", { region, url, error: String(e).slice(0, 200) });
|
|
46
|
+
return void 0;
|
|
47
|
+
} finally {
|
|
48
|
+
inflight.delete(key);
|
|
49
|
+
}
|
|
50
|
+
})();
|
|
51
|
+
inflight.set(key, p);
|
|
52
|
+
}
|
|
53
|
+
return p;
|
|
54
|
+
}
|
|
55
|
+
var PROVINCE_SNAP_DEG = 0.05;
|
|
56
|
+
function shapesOf(geo) {
|
|
57
|
+
const out = [];
|
|
58
|
+
for (const f of geo?.features ?? []) {
|
|
59
|
+
const g = f?.geometry;
|
|
60
|
+
if (!g || f?.properties?.kind === "border" || f.id === null || f.id === void 0) continue;
|
|
61
|
+
const polys = g.type === "Polygon" ? [g.coordinates] : g.type === "MultiPolygon" ? g.coordinates : [];
|
|
62
|
+
if (!polys.length) continue;
|
|
63
|
+
let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
|
|
64
|
+
for (const poly of polys) for (const [x, y] of poly[0] ?? []) {
|
|
65
|
+
if (x < x0) x0 = x;
|
|
66
|
+
if (x > x1) x1 = x;
|
|
67
|
+
if (y < y0) y0 = y;
|
|
68
|
+
if (y > y1) y1 = y;
|
|
69
|
+
}
|
|
70
|
+
out.push({ id: String(f.id), polys, box: [x0, y0, x1, y1] });
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
function inRing(x, y, ring) {
|
|
75
|
+
let inside = false;
|
|
76
|
+
for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
|
|
77
|
+
const [xi, yi] = ring[i], [xj, yj] = ring[j];
|
|
78
|
+
if (yi > y !== yj > y && x < (xj - xi) * (y - yi) / (yj - yi) + xi) inside = !inside;
|
|
79
|
+
}
|
|
80
|
+
return inside;
|
|
81
|
+
}
|
|
82
|
+
function inShape(x, y, s) {
|
|
83
|
+
if (x < s.box[0] || x > s.box[2] || y < s.box[1] || y > s.box[3]) return false;
|
|
84
|
+
return s.polys.some((poly) => poly.length > 0 && inRing(x, y, poly[0]) && !poly.slice(1).some((h) => inRing(x, y, h)));
|
|
85
|
+
}
|
|
86
|
+
function segDist2(px, py, ax, ay, bx, by) {
|
|
87
|
+
const dx = bx - ax, dy = by - ay;
|
|
88
|
+
const t = dx === 0 && dy === 0 ? 0 : Math.max(0, Math.min(1, ((px - ax) * dx + (py - ay) * dy) / (dx * dx + dy * dy)));
|
|
89
|
+
const ex = ax + t * dx - px, ey = ay + t * dy - py;
|
|
90
|
+
return ex * ex + ey * ey;
|
|
91
|
+
}
|
|
92
|
+
var locators = /* @__PURE__ */ new WeakMap();
|
|
93
|
+
function provinceLocator(geo, snapDeg = PROVINCE_SNAP_DEG) {
|
|
94
|
+
if (geo && typeof geo === "object" && snapDeg === PROVINCE_SNAP_DEG) {
|
|
95
|
+
const hit = locators.get(geo);
|
|
96
|
+
if (hit) return hit;
|
|
97
|
+
}
|
|
98
|
+
const shapes = shapesOf(geo);
|
|
99
|
+
const locate = (lat, lon) => {
|
|
100
|
+
if (!isFinite(lat) || !isFinite(lon)) return null;
|
|
101
|
+
for (const x of [lon, lon + 360, lon - 360]) {
|
|
102
|
+
for (const s of shapes) if (inShape(x, lat, s)) return s.id;
|
|
103
|
+
}
|
|
104
|
+
const k = Math.cos(lat * Math.PI / 180) || 1e-6;
|
|
105
|
+
let best = null, bestD = snapDeg * snapDeg;
|
|
106
|
+
for (const s of shapes) {
|
|
107
|
+
if (lon < s.box[0] - snapDeg / k || lon > s.box[2] + snapDeg / k || lat < s.box[1] - snapDeg || lat > s.box[3] + snapDeg) continue;
|
|
108
|
+
for (const poly of s.polys) for (const ring of poly) for (let i = 1; i < ring.length; i++) {
|
|
109
|
+
const d = segDist2(lon * k, lat, ring[i - 1][0] * k, ring[i - 1][1], ring[i][0] * k, ring[i][1]);
|
|
110
|
+
if (d <= bestD) {
|
|
111
|
+
bestD = d;
|
|
112
|
+
best = s.id;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return best;
|
|
117
|
+
};
|
|
118
|
+
if (geo && typeof geo === "object" && snapDeg === PROVINCE_SNAP_DEG) locators.set(geo, locate);
|
|
119
|
+
return locate;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/admin1LookupLoader.ts
|
|
123
|
+
var ADMIN1_LOOKUP_PATH = "/geo/admin1-lookup.v1.json";
|
|
124
|
+
var inflight2 = null;
|
|
125
|
+
async function loadAdmin1Lookup(url, opts = {}) {
|
|
126
|
+
if (W()) return true;
|
|
127
|
+
const log = opts.log;
|
|
128
|
+
if (!inflight2) {
|
|
129
|
+
const started = Date.now();
|
|
130
|
+
inflight2 = (async () => {
|
|
131
|
+
try {
|
|
132
|
+
const doc = opts.fetchJson ? await opts.fetchJson(url) : await (await fetch(url, { credentials: "omit" })).json();
|
|
133
|
+
const counts = bt(doc);
|
|
134
|
+
log?.("admin1-lookup-loaded", { url, ms: Date.now() - started, ...counts });
|
|
135
|
+
return true;
|
|
136
|
+
} catch (e) {
|
|
137
|
+
log?.("admin1-lookup-failed", { url, ms: Date.now() - started, error: String(e).slice(0, 200) });
|
|
138
|
+
inflight2 = null;
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
})();
|
|
142
|
+
}
|
|
143
|
+
const timeoutMs = opts.timeoutMs ?? 3e3;
|
|
144
|
+
let timer;
|
|
145
|
+
const timeout = new Promise((resolve) => {
|
|
146
|
+
timer = setTimeout(() => {
|
|
147
|
+
log?.("admin1-lookup-timeout", { url, timeoutMs });
|
|
148
|
+
resolve(false);
|
|
149
|
+
}, timeoutMs);
|
|
150
|
+
});
|
|
151
|
+
try {
|
|
152
|
+
return await Promise.race([inflight2, timeout]);
|
|
153
|
+
} finally {
|
|
154
|
+
if (timer) clearTimeout(timer);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
var PLACE_TOKENS = /(^|[^a-z])(city|cities|town|ville|stadt|ciudad|citta|cidade|state|province|provincia|provinz|county|department|departement|departamento|prefecture|oblast|canton|kanton|bundesland|municipality)([^a-z]|$)/i;
|
|
158
|
+
function needsAdmin1Lookup(columns) {
|
|
159
|
+
if (!columns) return false;
|
|
160
|
+
const geographic = columns.some((c) => {
|
|
161
|
+
const kind = (c?.geoKind ?? "").toLowerCase();
|
|
162
|
+
return kind.startsWith("country") || kind.startsWith("city");
|
|
163
|
+
});
|
|
164
|
+
if (geographic) return true;
|
|
165
|
+
return columns.some((c) => !!c && !c.isMeasure && !!c.name && PLACE_TOKENS.test(c.name.replace(/[_-]+/g, " ")));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// src/provinceHost.ts
|
|
169
|
+
async function prepareProvinceProfile(profile, opts) {
|
|
170
|
+
if (!profile || typeof profile.getColumnsWithStats !== "function") return;
|
|
171
|
+
let invalidate = false;
|
|
172
|
+
if (profile.deriveProvinces !== opts.deriveProvinces) {
|
|
173
|
+
profile.deriveProvinces = opts.deriveProvinces;
|
|
174
|
+
invalidate = true;
|
|
175
|
+
}
|
|
176
|
+
const origin = typeof opts.origin === "string" ? opts.origin.trim() : "";
|
|
177
|
+
if (!W() && origin) {
|
|
178
|
+
let cols = [];
|
|
179
|
+
try {
|
|
180
|
+
cols = profile.getColumnsWithStats(opts.privacyLevel, opts.locale);
|
|
181
|
+
} catch {
|
|
182
|
+
}
|
|
183
|
+
if (needsAdmin1Lookup(cols)) {
|
|
184
|
+
const ok = await loadAdmin1Lookup(
|
|
185
|
+
origin.replace(/\/+$/, "") + ADMIN1_LOOKUP_PATH,
|
|
186
|
+
{ timeoutMs: opts.timeoutMs ?? 3e3, fetchJson: opts.fetchJson, log: opts.log }
|
|
187
|
+
);
|
|
188
|
+
invalidate = invalidate || ok;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (invalidate) profile.invalidateStats?.();
|
|
192
|
+
}
|
|
193
|
+
function provinceBinding(res, cols, opts) {
|
|
194
|
+
if (!res.geoColumn || !res.geoKind) return null;
|
|
195
|
+
if (String(res.geoGrain ?? "").toLowerCase() !== "admin1") return { column: res.geoColumn, kind: res.geoKind };
|
|
196
|
+
const dims = cols.filter((c) => !c.isMeasure && c.name !== res.geoColumn);
|
|
197
|
+
const kindOf = (c) => String(c.geoKind ?? "");
|
|
198
|
+
const words = (n) => n.replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase().split(/[^a-z]+/);
|
|
199
|
+
const named = (re) => cols.find((c) => words(c.name).some((w) => re.test(w)))?.name ?? null;
|
|
200
|
+
return {
|
|
201
|
+
column: res.geoColumn,
|
|
202
|
+
kind: res.geoKind,
|
|
203
|
+
grain: "admin1",
|
|
204
|
+
region: res.geoRegion || null,
|
|
205
|
+
fallback: {
|
|
206
|
+
city: dims.find((c) => kindOf(c).startsWith("city"))?.name ?? null,
|
|
207
|
+
country: dims.find((c) => kindOf(c).startsWith("country"))?.name ?? null,
|
|
208
|
+
lat: named(/^(lat|latitude)$/),
|
|
209
|
+
lon: named(/^(lon|lng|long|longitude)$/)
|
|
210
|
+
},
|
|
211
|
+
deriveProvinces: opts.deriveProvinces,
|
|
212
|
+
join: Si,
|
|
213
|
+
locate: opts.geo ? provinceLocator(opts.geo) : null
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function geoKeyOfFields(res) {
|
|
217
|
+
return geoKeyFor({ geoKind: res.geoKind, geoRegion: res.geoRegion, geoGrain: res.geoGrain });
|
|
218
|
+
}
|
|
219
|
+
export {
|
|
220
|
+
ADMIN1_LOOKUP_PATH,
|
|
221
|
+
geoKeyFor,
|
|
222
|
+
geoKeyOfFields,
|
|
223
|
+
loadAdmin1Lookup,
|
|
224
|
+
loadProvinceGeo,
|
|
225
|
+
needsAdmin1Lookup,
|
|
226
|
+
prepareProvinceProfile,
|
|
227
|
+
provinceBinding,
|
|
228
|
+
provinceLocator
|
|
229
|
+
};
|
package/dist/react.mjs
CHANGED
|
@@ -18,16 +18,18 @@ import {
|
|
|
18
18
|
assembleD3,
|
|
19
19
|
createChartGroup,
|
|
20
20
|
createChartHost,
|
|
21
|
-
geoFromCache,
|
|
22
|
-
loadGeo,
|
|
23
21
|
syncMemberSelection,
|
|
24
22
|
toSourceRows
|
|
25
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-K6UXTDMR.mjs";
|
|
26
24
|
import "./chunk-IE426WU6.mjs";
|
|
27
25
|
import "./chunk-CPLWAW3Z.mjs";
|
|
28
26
|
import "./chunk-P2DLK7WZ.mjs";
|
|
29
27
|
import "./chunk-IE7TM6DX.mjs";
|
|
30
|
-
import
|
|
28
|
+
import {
|
|
29
|
+
geoFromCache,
|
|
30
|
+
loadGeo
|
|
31
|
+
} from "./chunk-5WTQN5DL.mjs";
|
|
32
|
+
import "./chunk-SYUJ4TPX.mjs";
|
|
31
33
|
import "./chunk-A2GMXZP7.mjs";
|
|
32
34
|
import "./chunk-7XRQUFVH.mjs";
|
|
33
35
|
import "./chunk-GHODWOAL.mjs";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Where the lookup is served, relative to the site origin. Versioned in the file name, so a rebuilt lookup is a new
|
|
2
|
+
* URL rather than a stale cache entry. */
|
|
3
|
+
export declare const ADMIN1_LOOKUP_PATH = "/geo/admin1-lookup.v1.json";
|
|
4
|
+
/**
|
|
5
|
+
* Fetch and register the lookup. Resolves true once it is registered (immediately when it already is), false on a
|
|
6
|
+
* timeout, a failed fetch or a body that is not a lookup - and never throws. One fetch is in flight at a time, so N
|
|
7
|
+
* charts on a page cause one request. `timeoutMs` bounds how long a host waits before profiling without it (default
|
|
8
|
+
* 3000); a late arrival still registers, for the next profile.
|
|
9
|
+
*/
|
|
10
|
+
export declare function loadAdmin1Lookup(url: string, opts?: {
|
|
11
|
+
timeoutMs?: number;
|
|
12
|
+
fetchJson?: (url: string) => Promise<any>;
|
|
13
|
+
log?: (tag: string, data?: any) => void;
|
|
14
|
+
}): Promise<boolean>;
|
|
15
|
+
/**
|
|
16
|
+
* Is the lookup worth loading for a table, judged from its profile? True when a column was profiled as a country or a
|
|
17
|
+
* city, or when a dimension's name reads as a city or a first-level division. Takes plain column records, so a host
|
|
18
|
+
* passes its profile as is.
|
|
19
|
+
*/
|
|
20
|
+
export declare function needsAdmin1Lookup(columns: ReadonlyArray<{
|
|
21
|
+
name?: string | null;
|
|
22
|
+
geoKind?: string | null;
|
|
23
|
+
isMeasure?: boolean | null;
|
|
24
|
+
}> | null | undefined): boolean;
|
|
25
|
+
/** Forget an in-flight load. Tests only. */
|
|
26
|
+
export declare function resetAdmin1LookupLoaderForTests(): void;
|
package/dist/types/contract.d.ts
CHANGED
package/dist/types/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export { loadGeo, geoFromCache, registerGeo, registerGeoAsset, geoAssetFor, clea
|
|
|
7
7
|
/** REPLACE the bundled city gazetteer with another packed table. Rarely needed — see the
|
|
8
8
|
* note above registerCityTable in shape-core's geoPoint.ts before using it. */
|
|
9
9
|
export declare function registerCityTable(packed: string): void;
|
|
10
|
-
export { buildRenderPayload, type RenderPayload, type GeoPointBinding } from "./payload";
|
|
10
|
+
export { buildRenderPayload, type RenderPayload, type GeoPointBinding, type GeoBinding, type GeoProvinceReport, type ProvinceJoin } from "./payload";
|
|
11
11
|
export { codeReadsDatesInLocalTime, vegaSpecReadsDatesInLocalTime } from "./dateCells";
|
|
12
12
|
export { createChartHost, compileRenderFn, stripEsmExports, requiredD3Plugins, explainRenderFailure, sessionViewStateProvider, noopViewStateProvider, assembleD3, D3_PLUGIN_PACKAGES, type ChartHost, type ChartHostConfig, type RenderFn, type SelectionPaintReport } from "./host";
|
|
13
13
|
export { ANNOTATION_CLASS, ANNOTATION_LAYER_CLASS, type MarkAnnotation, type AnnotationReport } from "./annotations";
|
|
@@ -71,6 +71,11 @@ export declare const OPTIONS_VOCABULARY: Readonly<{
|
|
|
71
71
|
* that made the chart asked for them. "on" / "off" is a reader's choice and wins either way.
|
|
72
72
|
*/
|
|
73
73
|
hoverDropLines: OptionVocabularyEntry;
|
|
74
|
+
/** A province map draws the national borders over its provinces unless this is false (a reader's "Country borders"). */
|
|
75
|
+
geoBorders: OptionVocabularyEntry;
|
|
76
|
+
/** A reader's "Derive provinces": false measures no city or coordinate rollup, so only a real province column offers
|
|
77
|
+
* a province map. It changes what the profile measures, so it travels in the shape rather than as a field. */
|
|
78
|
+
deriveProvinces: OptionVocabularyEntry;
|
|
74
79
|
levelOfDetail: OptionVocabularyEntry;
|
|
75
80
|
favorLimitTo: OptionVocabularyEntry;
|
|
76
81
|
/** 0 asks for no limit, and travels as absent (`wireLimitCount`): any value the server receives is a limit. */
|
package/dist/types/payload.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export interface GeoPointBinding {
|
|
|
9
9
|
lon?: string;
|
|
10
10
|
country?: string;
|
|
11
11
|
mapKind?: GeoMapKind;
|
|
12
|
+
region?: string | null;
|
|
12
13
|
}
|
|
13
14
|
/** What a point binding's resolution ACHIEVED, so the chart can annotate honestly rather
|
|
14
15
|
* than implying a precision it does not have. Named (rather than inlined on RenderPayload)
|
|
@@ -25,9 +26,70 @@ export interface GeoPointReport {
|
|
|
25
26
|
rolesBackfilled?: string[];
|
|
26
27
|
rolesRefused?: string[];
|
|
27
28
|
}
|
|
29
|
+
/** How a province map's rows were placed (geo binding with grain "admin1"), so the chart can say what its provinces
|
|
30
|
+
* rest on: the reader's province values, their coordinates, or their cities rolled up. */
|
|
31
|
+
export interface GeoProvinceReport {
|
|
32
|
+
/** Rows placed by each source. */
|
|
33
|
+
sources: {
|
|
34
|
+
province: number;
|
|
35
|
+
coordinates: number;
|
|
36
|
+
city: number;
|
|
37
|
+
};
|
|
38
|
+
/** Rows whose city matched more than one province: not placed, never guessed. */
|
|
39
|
+
ambiguousRows: number;
|
|
40
|
+
ambiguousExamples: string[];
|
|
41
|
+
/** Rows whose coordinates landed in no province. */
|
|
42
|
+
unplacedPoints: number;
|
|
43
|
+
}
|
|
44
|
+
/** The province join a host hands a province-map binding: shape-core's buildProvinceIsoColumn. Typed here, not imported,
|
|
45
|
+
* because the province code lives off this package's main entry (its eager budget): a host already loads shape-core to
|
|
46
|
+
* profile, so it passes the function it has rather than this bundle carrying a second copy. */
|
|
47
|
+
export type ProvinceJoin = (rows: Array<{
|
|
48
|
+
province?: unknown;
|
|
49
|
+
city?: unknown;
|
|
50
|
+
country?: unknown;
|
|
51
|
+
lat?: unknown;
|
|
52
|
+
lon?: unknown;
|
|
53
|
+
}>, opts: {
|
|
54
|
+
provinceKind?: string | null;
|
|
55
|
+
deriveProvinces?: boolean;
|
|
56
|
+
locate?: (lat: number, lon: number) => string | null;
|
|
57
|
+
}) => {
|
|
58
|
+
iso: (string | null)[];
|
|
59
|
+
source: ("province" | "coordinates" | "city" | null)[];
|
|
60
|
+
unmatched: string[];
|
|
61
|
+
matchedRows: number;
|
|
62
|
+
totalRows: number;
|
|
63
|
+
ambiguousRows: number;
|
|
64
|
+
ambiguousExamples: string[];
|
|
65
|
+
unplacedPoints: number;
|
|
66
|
+
};
|
|
67
|
+
/** A geo binding. A province map adds `grain: "admin1"`, its continent and `join`; the province value, a blank province's
|
|
68
|
+
* coordinates (through `locate`, from "@bicharts/chart-host/provinces" provinceLocator) and its city are then read in that
|
|
69
|
+
* order (the city and coordinate columns may be named in `fallback` beside the bound column), unless the reader turned
|
|
70
|
+
* deriving provinces off. A province binding without `join` builds no join column. */
|
|
71
|
+
export interface GeoBinding {
|
|
72
|
+
column: string;
|
|
73
|
+
kind: string;
|
|
74
|
+
grain?: string | null;
|
|
75
|
+
region?: string | null;
|
|
76
|
+
fallback?: {
|
|
77
|
+
city?: string | null;
|
|
78
|
+
country?: string | null;
|
|
79
|
+
lat?: string | null;
|
|
80
|
+
lon?: string | null;
|
|
81
|
+
} | null;
|
|
82
|
+
deriveProvinces?: boolean;
|
|
83
|
+
/** Places a coordinate in its province (provinceLocator over the continent's geometry). Absent: coordinates do not place. */
|
|
84
|
+
locate?: ((lat: number, lon: number) => string | null) | null;
|
|
85
|
+
/** Builds the province join (shape-core buildProvinceIsoColumn). Required for grain "admin1". */
|
|
86
|
+
join?: ProvinceJoin | null;
|
|
87
|
+
}
|
|
28
88
|
export interface RenderPayload {
|
|
29
89
|
columns: any[];
|
|
30
90
|
rows: any[][];
|
|
91
|
+
/** Present only for a province map: what placed its rows. */
|
|
92
|
+
geoProvince?: GeoProvinceReport;
|
|
31
93
|
geoUnmatched?: {
|
|
32
94
|
count: number;
|
|
33
95
|
examples: string[];
|
|
@@ -77,10 +139,7 @@ export interface RenderPayload {
|
|
|
77
139
|
}
|
|
78
140
|
export declare function buildRenderPayload(cols: Array<{
|
|
79
141
|
name: string;
|
|
80
|
-
} & Record<string, any>>, rowObjs: Array<Record<string, any>>, geo?: {
|
|
81
|
-
column: string;
|
|
82
|
-
kind: string;
|
|
83
|
-
} | null, point?: GeoPointBinding | null, destination?: GeoPointBinding | null, opts?: {
|
|
142
|
+
} & Record<string, any>>, rowObjs: Array<Record<string, any>>, geo?: GeoBinding | null, point?: GeoPointBinding | null, destination?: GeoPointBinding | null, opts?: {
|
|
84
143
|
utcDays?: boolean;
|
|
85
144
|
localWallClock?: boolean;
|
|
86
145
|
aliases?: ReadonlyArray<{
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** The continents a province map is drawn for. */
|
|
2
|
+
export declare const PROVINCE_REGIONS: readonly ["europe", "asia", "africa", "south-america", "north-america", "oceania"];
|
|
3
|
+
export type ProvinceRegion = typeof PROVINCE_REGIONS[number];
|
|
4
|
+
/** Where a continent's provinces are served, relative to the site origin. Versioned in the name, like the lookup. */
|
|
5
|
+
export declare function provinceGeoPath(region: string): string;
|
|
6
|
+
/** The geometry key a province map renders against: pass it as the host's geoKind. */
|
|
7
|
+
export declare function provinceGeoKey(region: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* The geometry key for a generation's geo fields: a province map (grain "admin1") renders against its continent's
|
|
10
|
+
* provinces, every other geo chart against its geoKind as before.
|
|
11
|
+
*/
|
|
12
|
+
export declare function geoKeyFor(res: {
|
|
13
|
+
geoKind?: string | null;
|
|
14
|
+
geoRegion?: string | null;
|
|
15
|
+
geoGrain?: string | null;
|
|
16
|
+
}): string | null;
|
|
17
|
+
/**
|
|
18
|
+
* Fetch and register a continent's provinces. Resolves the FeatureCollection once registered (immediately when it
|
|
19
|
+
* already is), undefined on a failed fetch or a body that is not one - and never throws. One fetch per continent is in
|
|
20
|
+
* flight at a time. The file is fetched from `origin` (default: the page's own) at its served path, or from `url`.
|
|
21
|
+
*/
|
|
22
|
+
export declare function loadProvinceGeo(region: string, opts?: {
|
|
23
|
+
origin?: string;
|
|
24
|
+
url?: string;
|
|
25
|
+
fetchJson?: (url: string) => Promise<any>;
|
|
26
|
+
log?: (tag: string, data?: any) => void;
|
|
27
|
+
}): Promise<any | undefined>;
|
|
28
|
+
/** How far outside every province a point may sit and still be placed in the nearest one, in degrees (about 5 km at
|
|
29
|
+
* the equator, less toward the poles): simplification moves coastlines, and a harbour city must not fall in the sea. */
|
|
30
|
+
export declare const PROVINCE_SNAP_DEG = 0.05;
|
|
31
|
+
/**
|
|
32
|
+
* A function placing a coordinate in its province: the province that contains it, else the nearest one within
|
|
33
|
+
* PROVINCE_SNAP_DEG (east-west distance scaled to the latitude), else null. Border lines are never a province. Built once
|
|
34
|
+
* per geometry object and reused.
|
|
35
|
+
*/
|
|
36
|
+
export declare function provinceLocator(geo: any, snapDeg?: number): (lat: number, lon: number) => string | null;
|
|
37
|
+
/** Forget in-flight loads. Tests only. */
|
|
38
|
+
export declare function resetProvinceGeoForTests(): void;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { GeoBinding } from "./payload";
|
|
2
|
+
/** The profile a host measures with: shape-core's IndexedText, by the members this needs. */
|
|
3
|
+
export interface ProvinceProfile {
|
|
4
|
+
deriveProvinces?: boolean;
|
|
5
|
+
getColumnsWithStats(privacyLevel: string, locale?: string): ReadonlyArray<{
|
|
6
|
+
name?: string | null;
|
|
7
|
+
geoKind?: string | null;
|
|
8
|
+
isMeasure?: boolean | null;
|
|
9
|
+
}>;
|
|
10
|
+
invalidateStats?(): void;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Before a request is profiled: hand the profile the reader's Derive Provinces setting, and when the table could carry
|
|
14
|
+
* provinces, wait (up to `timeoutMs`, default 3000) for the hosted admin-1 lookup served at `origin`. Either change
|
|
15
|
+
* re-measures the profile, which keeps its rows. Never throws; a late or failed load costs a province map, never a chart.
|
|
16
|
+
*/
|
|
17
|
+
export declare function prepareProvinceProfile(profile: ProvinceProfile | null | undefined, opts: {
|
|
18
|
+
origin: string;
|
|
19
|
+
deriveProvinces: boolean;
|
|
20
|
+
privacyLevel: string;
|
|
21
|
+
locale?: string;
|
|
22
|
+
timeoutMs?: number;
|
|
23
|
+
fetchJson?: (url: string) => Promise<any>;
|
|
24
|
+
log?: (tag: string, data?: any) => void;
|
|
25
|
+
}): Promise<void>;
|
|
26
|
+
/** The geo fields a generation names, as every host keeps them. */
|
|
27
|
+
export interface GeoFields {
|
|
28
|
+
geoColumn?: string | null;
|
|
29
|
+
geoKind?: string | null;
|
|
30
|
+
geoRegion?: string | null;
|
|
31
|
+
geoGrain?: string | null;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The geo binding for a render. A province map (grain "admin1") gets the province join, a locator over `geo` (the
|
|
35
|
+
* continent's province geometry, when the host has it), the reader's Derive Provinces setting and the columns a blank
|
|
36
|
+
* province falls back to, named from the profile; every other geo chart its column + kind. Null when nothing is bound.
|
|
37
|
+
*/
|
|
38
|
+
export declare function provinceBinding(res: GeoFields, cols: ReadonlyArray<{
|
|
39
|
+
name: string;
|
|
40
|
+
geoKind?: string | null;
|
|
41
|
+
isMeasure?: boolean | null;
|
|
42
|
+
}>, opts: {
|
|
43
|
+
deriveProvinces: boolean;
|
|
44
|
+
geo?: any;
|
|
45
|
+
}): GeoBinding | null;
|
|
46
|
+
/** The geometry key a generation renders against (geoKeyFor over a host's kept fields). */
|
|
47
|
+
export declare function geoKeyOfFields(res: GeoFields): string | null;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { loadProvinceGeo, geoKeyFor, provinceLocator } from "./provinceGeo";
|
|
2
|
+
export { loadAdmin1Lookup, needsAdmin1Lookup, ADMIN1_LOOKUP_PATH } from "./admin1LookupLoader";
|
|
3
|
+
export { prepareProvinceProfile, provinceBinding, geoKeyOfFields, type ProvinceProfile, type GeoFields } from "./provinceHost";
|
package/dist/zoom-memory.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*! @bicharts/chart-host — Apache-2.0. Bundled reference data: GeoNames (https://www.geonames.org/) CC BY 4.0; Natural Earth and US Census/TIGER (public domain). Full text: NOTICE in this package. */
|
|
2
2
|
import {
|
|
3
3
|
K
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-SYUJ4TPX.mjs";
|
|
5
5
|
import "./chunk-GHODWOAL.mjs";
|
|
6
6
|
|
|
7
7
|
// src/zoomMemory/zoomMemory.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bicharts/chart-host",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.106",
|
|
4
4
|
"description": "Run a BIC-generated D3 chart in any web host: compiles the generated render() function, applies the shared option defaults, resolves mark clicks (through tooltip overlays), owns the selection affordance, and translates row indices between cross-filtered charts. The same contract the BIC Power BI visual implements, minus Power BI. React bindings at @bicharts/chart-host/react.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -64,6 +64,11 @@
|
|
|
64
64
|
"import": "./dist/legend-fit.mjs",
|
|
65
65
|
"default": "./dist/legend-fit.mjs"
|
|
66
66
|
},
|
|
67
|
+
"./provinces": {
|
|
68
|
+
"types": "./dist/types/provinces.d.ts",
|
|
69
|
+
"import": "./dist/provinces.mjs",
|
|
70
|
+
"default": "./dist/provinces.mjs"
|
|
71
|
+
},
|
|
67
72
|
"./zoom-memory": {
|
|
68
73
|
"types": "./dist/types/zoomMemory/index.d.ts",
|
|
69
74
|
"import": "./dist/zoom-memory.mjs",
|