@bicharts/chart-host 0.1.5 → 0.1.6
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/{chunk-UNH36TGD.mjs → chunk-2WOMFNMG.mjs} +174 -4
- package/dist/chunk-2WOMFNMG.mjs.map +7 -0
- package/dist/index.mjs +1 -1
- package/dist/react.mjs +1 -1
- package/dist/types/contract.d.ts +2 -0
- package/dist/types/payload.d.ts +2 -0
- package/package.json +67 -67
- package/dist/chunk-UNH36TGD.mjs.map +0 -7
|
@@ -260,6 +260,9 @@ function buildGeoPointColumns(rows) {
|
|
|
260
260
|
}
|
|
261
261
|
return { lat, lon, precision: coarsest, unmatched, ambiguousRows, matchedRows, totalRows: rows.length };
|
|
262
262
|
}
|
|
263
|
+
function isKnownCity(normalizedName) {
|
|
264
|
+
return !!normalizedName && cityIndex().has(normalizedName);
|
|
265
|
+
}
|
|
263
266
|
|
|
264
267
|
// ../shape-core/dist/chunk-2NJREYQG.mjs
|
|
265
268
|
/*! @bicharts/shape-core — Apache-2.0. Bundled reference data: GeoNames (https://www.geonames.org/) CC BY 4.0; US Census/TIGER (public domain). Full text: NOTICE in this package. */
|
|
@@ -682,6 +685,158 @@ function buildGeoIsoColumn(values, geoKind) {
|
|
|
682
685
|
|
|
683
686
|
// ../shape-core/dist/index.mjs
|
|
684
687
|
/*! @bicharts/shape-core — Apache-2.0. Bundled reference data: GeoNames (https://www.geonames.org/) CC BY 4.0; US Census/TIGER (public domain). Full text: NOTICE in this package. */
|
|
688
|
+
var THRESHOLD_PCT = 85;
|
|
689
|
+
var MIN_DISTINCT_BACKFILL = 2;
|
|
690
|
+
var ZIP_THRESHOLD_PCT = 100;
|
|
691
|
+
var MAX_DISTINCT_SCAN = 400;
|
|
692
|
+
var COUNTRY_IDS = /* @__PURE__ */ new Set([
|
|
693
|
+
"us",
|
|
694
|
+
"usa",
|
|
695
|
+
"u s",
|
|
696
|
+
"u s a",
|
|
697
|
+
"united states",
|
|
698
|
+
"united states of america",
|
|
699
|
+
"america",
|
|
700
|
+
"ca",
|
|
701
|
+
"can",
|
|
702
|
+
"canada",
|
|
703
|
+
"mx",
|
|
704
|
+
"mex",
|
|
705
|
+
"mexico",
|
|
706
|
+
"estados unidos mexicanos"
|
|
707
|
+
]);
|
|
708
|
+
function distinctNormalized(values) {
|
|
709
|
+
const seen = /* @__PURE__ */ new Set();
|
|
710
|
+
for (const v of values) {
|
|
711
|
+
if (v === null || v === void 0) continue;
|
|
712
|
+
const k = normalizePlaceName(String(v));
|
|
713
|
+
if (!k) continue;
|
|
714
|
+
if (!seen.has(k)) {
|
|
715
|
+
seen.add(k);
|
|
716
|
+
if (seen.size >= MAX_DISTINCT_SCAN) break;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
return Array.from(seen);
|
|
720
|
+
}
|
|
721
|
+
function looksLikeCountryColumn(values) {
|
|
722
|
+
const d = distinctNormalized(values);
|
|
723
|
+
if (d.length === 0) return false;
|
|
724
|
+
return d.every((v) => COUNTRY_IDS.has(v));
|
|
725
|
+
}
|
|
726
|
+
function admin1MatchPct(values) {
|
|
727
|
+
const d = distinctNormalized(values);
|
|
728
|
+
if (d.length === 0) return 0;
|
|
729
|
+
let n = 0;
|
|
730
|
+
for (const v of d) if (resolveAdmin1(v)) n++;
|
|
731
|
+
return Math.round(n / d.length * 1e3) / 10;
|
|
732
|
+
}
|
|
733
|
+
function distinctRaw(values) {
|
|
734
|
+
const seen = /* @__PURE__ */ new Set();
|
|
735
|
+
for (const v of values) {
|
|
736
|
+
if (v === null || v === void 0) continue;
|
|
737
|
+
const s = String(v).trim();
|
|
738
|
+
if (!s) continue;
|
|
739
|
+
if (!seen.has(s)) {
|
|
740
|
+
seen.add(s);
|
|
741
|
+
if (seen.size >= MAX_DISTINCT_SCAN) break;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
return Array.from(seen);
|
|
745
|
+
}
|
|
746
|
+
function zipMatchPct(values) {
|
|
747
|
+
const d = distinctRaw(values);
|
|
748
|
+
if (d.length === 0) return 0;
|
|
749
|
+
let n = 0;
|
|
750
|
+
for (const v of d) if (zipPrefixCandidates(v).length > 0) n++;
|
|
751
|
+
return Math.round(n / d.length * 1e3) / 10;
|
|
752
|
+
}
|
|
753
|
+
function cityPct(values) {
|
|
754
|
+
const d = distinctNormalized(values);
|
|
755
|
+
if (d.length === 0) return 0;
|
|
756
|
+
let n = 0;
|
|
757
|
+
for (const v of d) if (isKnownCity(v)) n++;
|
|
758
|
+
return Math.round(n / d.length * 1e3) / 10;
|
|
759
|
+
}
|
|
760
|
+
function distinctMatches(values, pred) {
|
|
761
|
+
let n = 0;
|
|
762
|
+
for (const v of distinctNormalized(values)) if (pred(v)) n++;
|
|
763
|
+
return n;
|
|
764
|
+
}
|
|
765
|
+
function distinctRawMatches(values, pred) {
|
|
766
|
+
let n = 0;
|
|
767
|
+
for (const v of distinctRaw(values)) if (pred(v)) n++;
|
|
768
|
+
return n;
|
|
769
|
+
}
|
|
770
|
+
function resolvePointRoles(columns, rows, hint) {
|
|
771
|
+
const backfilled = [];
|
|
772
|
+
const refused = [];
|
|
773
|
+
const out = {};
|
|
774
|
+
const colSet = new Set(columns);
|
|
775
|
+
const valuesOf = (name) => rows.map((r) => r[name]);
|
|
776
|
+
for (const role of ["city", "zip", "lat", "lon"]) {
|
|
777
|
+
const name = hint?.[role];
|
|
778
|
+
if (name && colSet.has(name)) out[role] = name;
|
|
779
|
+
}
|
|
780
|
+
const hintedState = hint?.state;
|
|
781
|
+
if (hintedState && colSet.has(hintedState)) {
|
|
782
|
+
const vals = valuesOf(hintedState);
|
|
783
|
+
if (looksLikeCountryColumn(vals)) {
|
|
784
|
+
refused.push(`state=${hintedState} (every value is a country, not a state)`);
|
|
785
|
+
} else if (admin1MatchPct(vals) < THRESHOLD_PCT) {
|
|
786
|
+
refused.push(`state=${hintedState} (only ${admin1MatchPct(vals)}% of values are states/provinces)`);
|
|
787
|
+
} else {
|
|
788
|
+
out.state = hintedState;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
const taken = new Set(Object.values(out).filter(Boolean));
|
|
792
|
+
const candidates = columns.filter((c) => !taken.has(c) && !c.startsWith("__"));
|
|
793
|
+
if (!out.state) {
|
|
794
|
+
let best = null;
|
|
795
|
+
for (const c of candidates) {
|
|
796
|
+
const vals = valuesOf(c);
|
|
797
|
+
if (looksLikeCountryColumn(vals)) continue;
|
|
798
|
+
const pct = admin1MatchPct(vals);
|
|
799
|
+
if (pct < THRESHOLD_PCT) continue;
|
|
800
|
+
if (distinctMatches(vals, (v) => !!resolveAdmin1(v)) < MIN_DISTINCT_BACKFILL) continue;
|
|
801
|
+
if (!best || pct > best.pct) best = { name: c, pct };
|
|
802
|
+
}
|
|
803
|
+
if (best) {
|
|
804
|
+
out.state = best.name;
|
|
805
|
+
taken.add(best.name);
|
|
806
|
+
backfilled.push(`state=${best.name} (${best.pct}% states/provinces)`);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
if (!out.zip) {
|
|
810
|
+
for (const c of candidates) {
|
|
811
|
+
if (taken.has(c)) continue;
|
|
812
|
+
const vals = valuesOf(c);
|
|
813
|
+
const pct = zipMatchPct(vals);
|
|
814
|
+
if (pct < ZIP_THRESHOLD_PCT) continue;
|
|
815
|
+
if (distinctRawMatches(vals, (v) => zipPrefixCandidates(v).length > 0) < MIN_DISTINCT_BACKFILL) continue;
|
|
816
|
+
out.zip = c;
|
|
817
|
+
taken.add(c);
|
|
818
|
+
backfilled.push(`zip=${c} (${pct}% ZIP codes)`);
|
|
819
|
+
break;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
if (!out.city) {
|
|
823
|
+
let best = null;
|
|
824
|
+
for (const c of candidates) {
|
|
825
|
+
if (taken.has(c)) continue;
|
|
826
|
+
const vals = valuesOf(c);
|
|
827
|
+
const pct = cityPct(vals);
|
|
828
|
+
if (pct < THRESHOLD_PCT) continue;
|
|
829
|
+
if (distinctMatches(vals, (v) => isKnownCity(v)) < MIN_DISTINCT_BACKFILL) continue;
|
|
830
|
+
if (!best || pct > best.pct) best = { name: c, pct };
|
|
831
|
+
}
|
|
832
|
+
if (best) {
|
|
833
|
+
out.city = best.name;
|
|
834
|
+
backfilled.push(`city=${best.name} (${best.pct}% known cities)`);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
const any = !!(out.city || out.state || out.zip || out.lat && out.lon);
|
|
838
|
+
return { bind: any ? out : null, backfilled, refused };
|
|
839
|
+
}
|
|
685
840
|
|
|
686
841
|
// src/payload.ts
|
|
687
842
|
function buildRenderPayload(cols, rowObjs, geo, point) {
|
|
@@ -689,9 +844,19 @@ function buildRenderPayload(cols, rowObjs, geo, point) {
|
|
|
689
844
|
let pLat = null;
|
|
690
845
|
let pLon = null;
|
|
691
846
|
let geoPoint;
|
|
692
|
-
|
|
847
|
+
let bind = point ?? null;
|
|
848
|
+
let rolesBackfilled = [];
|
|
849
|
+
let rolesRefused = [];
|
|
850
|
+
if (bind) {
|
|
851
|
+
const dims = cols.filter((c) => !c.isMeasure).map((c) => c.name);
|
|
852
|
+
const res = resolvePointRoles(dims, rowObjs, bind);
|
|
853
|
+
bind = res.bind;
|
|
854
|
+
rolesBackfilled = res.backfilled;
|
|
855
|
+
rolesRefused = res.refused;
|
|
856
|
+
}
|
|
857
|
+
const hasPointBind = !!bind && !!(bind.city || bind.state || bind.zip || bind.lat && bind.lon);
|
|
693
858
|
if (hasPointBind) {
|
|
694
|
-
const p =
|
|
859
|
+
const p = bind;
|
|
695
860
|
const built = buildGeoPointColumns(rowObjs.map((r) => ({
|
|
696
861
|
lat: p.lat ? r[p.lat] : null,
|
|
697
862
|
lon: p.lon ? r[p.lon] : null,
|
|
@@ -705,7 +870,12 @@ function buildRenderPayload(cols, rowObjs, geo, point) {
|
|
|
705
870
|
precision: built.precision,
|
|
706
871
|
unplaced: built.totalRows - built.matchedRows,
|
|
707
872
|
unplacedExamples: built.unmatched.slice(0, 8),
|
|
708
|
-
ambiguousRows: built.ambiguousRows
|
|
873
|
+
ambiguousRows: built.ambiguousRows,
|
|
874
|
+
// Omitted entirely when nothing was adjusted, so the common case adds no bytes
|
|
875
|
+
// and a chart can treat presence as "something about this binding is worth
|
|
876
|
+
// saying".
|
|
877
|
+
...rolesBackfilled.length ? { rolesBackfilled } : {},
|
|
878
|
+
...rolesRefused.length ? { rolesRefused } : {}
|
|
709
879
|
};
|
|
710
880
|
}
|
|
711
881
|
let geoIso = null;
|
|
@@ -1182,4 +1352,4 @@ export {
|
|
|
1182
1352
|
compileRenderFn,
|
|
1183
1353
|
createChartHost
|
|
1184
1354
|
};
|
|
1185
|
-
//# sourceMappingURL=chunk-
|
|
1355
|
+
//# sourceMappingURL=chunk-2WOMFNMG.mjs.map
|