@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.
@@ -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
- const hasPointBind = !!point && !!(point.city || point.state || point.zip || point.lat && point.lon);
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 = point;
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-UNH36TGD.mjs.map
1355
+ //# sourceMappingURL=chunk-2WOMFNMG.mjs.map