@bicharts/chart-host 0.5.85 → 0.5.87
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-RD3DRWLL.mjs → chunk-NWMMNXH2.mjs} +538 -522
- package/dist/index.mjs +411 -42
- package/dist/react.mjs +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/selectionCard.d.ts +11 -0
- package/dist/types/sourceDateFormat.d.ts +30 -0
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
GLYPH_SAMPLE_N,
|
|
33
33
|
HOST_CONTAINER_CLASS,
|
|
34
34
|
HOST_CONTRACT_VERSION,
|
|
35
|
+
It,
|
|
35
36
|
L3,
|
|
36
37
|
LABEL_CONTRAST_CAP,
|
|
37
38
|
LABEL_CONTRAST_DONE_ATTR,
|
|
@@ -44,18 +45,16 @@ import {
|
|
|
44
45
|
MIN_CONTRAST,
|
|
45
46
|
MIN_HIT_BAND_PX,
|
|
46
47
|
Me,
|
|
47
|
-
Nt,
|
|
48
48
|
PAGE_MATCH_TOLERANCE,
|
|
49
49
|
PHANTOM_FRACTION,
|
|
50
50
|
PILL_MIN_ALPHA,
|
|
51
51
|
PILL_MIN_COVERAGE,
|
|
52
52
|
PILL_OPAQUE_ALPHA,
|
|
53
53
|
ROW_IDX_ATTR,
|
|
54
|
-
Rt,
|
|
55
54
|
SCROLL_SLACK_PX,
|
|
56
55
|
SEASONAL_MARKERS_DEFAULT,
|
|
57
56
|
SELECTION_ACTIVE_CLASS,
|
|
58
|
-
|
|
57
|
+
Te,
|
|
59
58
|
VALUE_AXIS_BASELINE_DEFAULT,
|
|
60
59
|
WHITE_TEXT_BG_LUM,
|
|
61
60
|
XFILTER_REFRESH_EVENT,
|
|
@@ -94,6 +93,7 @@ import {
|
|
|
94
93
|
measureContainerBoxes,
|
|
95
94
|
needsScroll,
|
|
96
95
|
noopViewStateProvider,
|
|
96
|
+
p,
|
|
97
97
|
periodTickSuppressesFeedback,
|
|
98
98
|
pillBacksGlyph,
|
|
99
99
|
pinScrolledAxis,
|
|
@@ -109,8 +109,10 @@ import {
|
|
|
109
109
|
stripEsmExports,
|
|
110
110
|
svgInkReach,
|
|
111
111
|
toRGBA,
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
u,
|
|
113
|
+
unpinScrolledAxis,
|
|
114
|
+
vt
|
|
115
|
+
} from "./chunk-NWMMNXH2.mjs";
|
|
114
116
|
import "./chunk-A2GMXZP7.mjs";
|
|
115
117
|
import "./chunk-GHODWOAL.mjs";
|
|
116
118
|
|
|
@@ -148,7 +150,7 @@ function planTrivialChart(data) {
|
|
|
148
150
|
}
|
|
149
151
|
if (dataCols.length !== 1) return null;
|
|
150
152
|
const idx = dataCols[0];
|
|
151
|
-
const
|
|
153
|
+
const name2 = columnName(allCols[idx]) || "Value";
|
|
152
154
|
const finish = (kind, reason, source) => ({
|
|
153
155
|
kind,
|
|
154
156
|
reason,
|
|
@@ -160,7 +162,7 @@ function planTrivialChart(data) {
|
|
|
160
162
|
return finish(
|
|
161
163
|
"card",
|
|
162
164
|
"One row of one column holds a single value, so this is a card. No generation was needed and none was used.",
|
|
163
|
-
cardSource(idx,
|
|
165
|
+
cardSource(idx, name2)
|
|
164
166
|
);
|
|
165
167
|
}
|
|
166
168
|
if (isNumericColumn(rows, idx)) {
|
|
@@ -168,7 +170,7 @@ function planTrivialChart(data) {
|
|
|
168
170
|
return finish(
|
|
169
171
|
"histogram",
|
|
170
172
|
"A single numeric column has one defensible chart: the distribution of its values. No generation was needed and none was used.",
|
|
171
|
-
histogramSource(idx,
|
|
173
|
+
histogramSource(idx, name2)
|
|
172
174
|
);
|
|
173
175
|
}
|
|
174
176
|
const distinct = /* @__PURE__ */ new Set();
|
|
@@ -181,7 +183,7 @@ function planTrivialChart(data) {
|
|
|
181
183
|
return finish(
|
|
182
184
|
"frequency-bar",
|
|
183
185
|
"A single categorical column has one defensible chart: how often each value occurs. No generation was needed and none was used.",
|
|
184
|
-
frequencySource(idx,
|
|
186
|
+
frequencySource(idx, name2)
|
|
185
187
|
);
|
|
186
188
|
}
|
|
187
189
|
function compileTrivialSource(source) {
|
|
@@ -222,7 +224,7 @@ var PRELUDE = `
|
|
|
222
224
|
}
|
|
223
225
|
container.replaceChildren();
|
|
224
226
|
`;
|
|
225
|
-
function cardSource(idx,
|
|
227
|
+
function cardSource(idx, name2) {
|
|
226
228
|
return `function render(container, data, options) {${PRELUDE}
|
|
227
229
|
var raw = rows[0] ? rows[0][${idx}] : null;
|
|
228
230
|
var blank = (raw === null || raw === undefined || raw === '');
|
|
@@ -254,13 +256,13 @@ function cardSource(idx, name) {
|
|
|
254
256
|
label.style.marginTop = '6px'; label.style.textAlign = 'center';
|
|
255
257
|
label.style.maxWidth = '100%'; label.style.overflow = 'hidden';
|
|
256
258
|
label.style.textOverflow = 'ellipsis'; label.style.whiteSpace = 'nowrap';
|
|
257
|
-
label.textContent = ${lit(
|
|
259
|
+
label.textContent = ${lit(name2)};
|
|
258
260
|
|
|
259
261
|
root.appendChild(value); root.appendChild(label);
|
|
260
262
|
container.appendChild(root);
|
|
261
263
|
}`;
|
|
262
264
|
}
|
|
263
|
-
function frequencySource(idx,
|
|
265
|
+
function frequencySource(idx, name2) {
|
|
264
266
|
return `function render(container, data, options) {${PRELUDE}
|
|
265
267
|
var counts = new Map();
|
|
266
268
|
for (var r = 0; r < rows.length; r++) {
|
|
@@ -280,7 +282,7 @@ function frequencySource(idx, name) {
|
|
|
280
282
|
var barH = Math.min(bandH - 3, 26);
|
|
281
283
|
|
|
282
284
|
var title = svgEl('text', { x: 8, y: CF + 2, 'font-size': CF, 'font-weight': '700', fill: FG });
|
|
283
|
-
title.textContent = ${lit(
|
|
285
|
+
title.textContent = ${lit(name2)} + ' \\u2014 count of ' + fmt(rows.length) + ' rows';
|
|
284
286
|
svg.appendChild(title);
|
|
285
287
|
|
|
286
288
|
entries.forEach(function (e, i) {
|
|
@@ -319,7 +321,7 @@ function frequencySource(idx, name) {
|
|
|
319
321
|
container.appendChild(svg);
|
|
320
322
|
}`;
|
|
321
323
|
}
|
|
322
|
-
function histogramSource(idx,
|
|
324
|
+
function histogramSource(idx, name2) {
|
|
323
325
|
return `function render(container, data, options) {${PRELUDE}
|
|
324
326
|
var vals = [];
|
|
325
327
|
for (var r = 0; r < rows.length; r++) {
|
|
@@ -355,7 +357,7 @@ function histogramSource(idx, name) {
|
|
|
355
357
|
var bw = plotW / binCount;
|
|
356
358
|
|
|
357
359
|
var title = svgEl('text', { x: padL, y: CF + 2, 'font-size': CF, 'font-weight': '700', fill: FG });
|
|
358
|
-
title.textContent = ${lit(
|
|
360
|
+
title.textContent = ${lit(name2)} + ' \\u2014 distribution of ' + fmt(vals.length) + ' values';
|
|
359
361
|
svg.appendChild(title);
|
|
360
362
|
|
|
361
363
|
bins.forEach(function (idxs, i) {
|
|
@@ -709,10 +711,10 @@ function normalizeFilterTerm(raw) {
|
|
|
709
711
|
return raw.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/\s+/g, " ").trim();
|
|
710
712
|
}
|
|
711
713
|
var FILTER_MIN_TERM_CHARS = 2;
|
|
712
|
-
function startsAWord(
|
|
713
|
-
for (let i =
|
|
714
|
+
function startsAWord(name2, t) {
|
|
715
|
+
for (let i = name2.indexOf(t); i >= 0; i = name2.indexOf(t, i + 1)) {
|
|
714
716
|
if (i === 0) return true;
|
|
715
|
-
const prev =
|
|
717
|
+
const prev = name2.charCodeAt(i - 1);
|
|
716
718
|
const alnum = prev >= 97 && prev <= 122 || prev >= 48 && prev <= 57;
|
|
717
719
|
if (!alnum) return true;
|
|
718
720
|
}
|
|
@@ -727,10 +729,10 @@ function filterQualifyRows(rows, term, read) {
|
|
|
727
729
|
const byDesc = [];
|
|
728
730
|
for (const row of all) {
|
|
729
731
|
const f = read(row);
|
|
730
|
-
const
|
|
731
|
-
if (
|
|
732
|
-
if (
|
|
733
|
-
(startsAWord(
|
|
732
|
+
const name2 = normalizeFilterTerm(f.name);
|
|
733
|
+
if (name2 === "") continue;
|
|
734
|
+
if (name2.indexOf(t) >= 0) {
|
|
735
|
+
(startsAWord(name2, t) ? atWordStart : midWord).push(row);
|
|
734
736
|
continue;
|
|
735
737
|
}
|
|
736
738
|
if (normalizeFilterTerm(f.description).indexOf(t) >= 0) byDesc.push(row);
|
|
@@ -820,6 +822,357 @@ function inlineFilterGate(rowCount) {
|
|
|
820
822
|
}
|
|
821
823
|
var INLINE_FILTER_MIN_ROWS = 8;
|
|
822
824
|
|
|
825
|
+
// src/sourceDateFormat.ts
|
|
826
|
+
function formatSourceDate(raw, opts = {}) {
|
|
827
|
+
const d = toDate(raw);
|
|
828
|
+
if (!d) return null;
|
|
829
|
+
const dayFrame = u(d);
|
|
830
|
+
const frame = dayFrame ?? (opts.dialect === "dotnet" ? "local" : "utc");
|
|
831
|
+
const p3 = partsIn(d, frame);
|
|
832
|
+
const format = cleanFormat(opts.format, opts.dialect);
|
|
833
|
+
if (format && opts.dialect) {
|
|
834
|
+
const rendered = renderFormat(format, p3, opts.dialect, opts.culture);
|
|
835
|
+
if (rendered) return rendered;
|
|
836
|
+
}
|
|
837
|
+
return localeDate(p3, !!dayFrame, opts.culture);
|
|
838
|
+
}
|
|
839
|
+
function formatSourceDateFor(raw, columnName2, formats, culture) {
|
|
840
|
+
return formatSourceDate(raw, {
|
|
841
|
+
format: formats?.byColumn?.[columnName2] ?? null,
|
|
842
|
+
dialect: formats?.dialect,
|
|
843
|
+
culture
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
var ISO_LIKE_RE = /^\s*\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})?)?\s*$/;
|
|
847
|
+
function toDate(raw) {
|
|
848
|
+
if (raw instanceof Date) return isNaN(raw.getTime()) ? null : raw;
|
|
849
|
+
if (typeof raw !== "string" || !ISO_LIKE_RE.test(raw)) return null;
|
|
850
|
+
const d = p(raw.trim());
|
|
851
|
+
return d && !isNaN(d.getTime()) ? d : null;
|
|
852
|
+
}
|
|
853
|
+
function partsIn(d, frame) {
|
|
854
|
+
return frame === "utc" ? {
|
|
855
|
+
year: d.getUTCFullYear(),
|
|
856
|
+
month: d.getUTCMonth() + 1,
|
|
857
|
+
day: d.getUTCDate(),
|
|
858
|
+
hours: d.getUTCHours(),
|
|
859
|
+
minutes: d.getUTCMinutes(),
|
|
860
|
+
seconds: d.getUTCSeconds(),
|
|
861
|
+
ms: d.getUTCMilliseconds(),
|
|
862
|
+
weekday: d.getUTCDay()
|
|
863
|
+
} : {
|
|
864
|
+
year: d.getFullYear(),
|
|
865
|
+
month: d.getMonth() + 1,
|
|
866
|
+
day: d.getDate(),
|
|
867
|
+
hours: d.getHours(),
|
|
868
|
+
minutes: d.getMinutes(),
|
|
869
|
+
seconds: d.getSeconds(),
|
|
870
|
+
ms: d.getMilliseconds(),
|
|
871
|
+
weekday: d.getDay()
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
function utcAnchor(p3) {
|
|
875
|
+
const d = new Date(Date.UTC(p3.year, p3.month - 1, p3.day, p3.hours, p3.minutes, p3.seconds, p3.ms));
|
|
876
|
+
if (p3.year >= 0 && p3.year < 100) d.setUTCFullYear(p3.year);
|
|
877
|
+
return d;
|
|
878
|
+
}
|
|
879
|
+
function localeDate(p3, wholeDay, culture) {
|
|
880
|
+
const anchor = utcAnchor(p3);
|
|
881
|
+
try {
|
|
882
|
+
const o = { dateStyle: "medium", timeZone: "UTC" };
|
|
883
|
+
if (!wholeDay) o.timeStyle = p3.seconds || p3.ms ? "medium" : "short";
|
|
884
|
+
return new Intl.DateTimeFormat(culture || void 0, o).format(anchor);
|
|
885
|
+
} catch {
|
|
886
|
+
const iso = anchor.toISOString();
|
|
887
|
+
return wholeDay ? iso.slice(0, 10) : `${iso.slice(0, 10)} ${iso.slice(11, 19)}`;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
function cleanFormat(raw, dialect) {
|
|
891
|
+
if (!raw || !dialect) return null;
|
|
892
|
+
let s = firstSection(String(raw), dialect);
|
|
893
|
+
if (dialect === "excel") {
|
|
894
|
+
s = s.replace(/\[(h{1,2}|m{1,2}|s{1,2})\]/gi, "$1").replace(/\[[^\]]*\]/g, "");
|
|
895
|
+
}
|
|
896
|
+
if (!s.trim() || /^general$/i.test(s.trim())) return null;
|
|
897
|
+
return hasDateToken(s, dialect) ? s : null;
|
|
898
|
+
}
|
|
899
|
+
function firstSection(s, dialect) {
|
|
900
|
+
let out = "";
|
|
901
|
+
let quote = "";
|
|
902
|
+
for (let i = 0; i < s.length; i++) {
|
|
903
|
+
const ch = s[i];
|
|
904
|
+
if (quote) {
|
|
905
|
+
out += ch;
|
|
906
|
+
if (ch === quote) quote = "";
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
909
|
+
if (ch === '"' || dialect === "dotnet" && ch === "'") {
|
|
910
|
+
quote = ch;
|
|
911
|
+
out += ch;
|
|
912
|
+
continue;
|
|
913
|
+
}
|
|
914
|
+
if (ch === "\\") {
|
|
915
|
+
out += ch + (s[i + 1] ?? "");
|
|
916
|
+
i++;
|
|
917
|
+
continue;
|
|
918
|
+
}
|
|
919
|
+
if (ch === ";") break;
|
|
920
|
+
out += ch;
|
|
921
|
+
}
|
|
922
|
+
return out;
|
|
923
|
+
}
|
|
924
|
+
function hasDateToken(s, dialect) {
|
|
925
|
+
const stripped = stripLiterals(s, dialect);
|
|
926
|
+
return dialect === "excel" ? /[ymdhs]/i.test(stripped) : /[yMdHhmsft]/.test(stripped);
|
|
927
|
+
}
|
|
928
|
+
function stripLiterals(s, dialect) {
|
|
929
|
+
let out = "";
|
|
930
|
+
for (let i = 0; i < s.length; i++) {
|
|
931
|
+
const ch = s[i];
|
|
932
|
+
if (ch === "\\" || dialect === "dotnet" && ch === "%") {
|
|
933
|
+
i++;
|
|
934
|
+
continue;
|
|
935
|
+
}
|
|
936
|
+
if (ch === '"' || dialect === "dotnet" && ch === "'") {
|
|
937
|
+
const end = s.indexOf(ch, i + 1);
|
|
938
|
+
i = end < 0 ? s.length : end;
|
|
939
|
+
continue;
|
|
940
|
+
}
|
|
941
|
+
out += ch;
|
|
942
|
+
}
|
|
943
|
+
return out;
|
|
944
|
+
}
|
|
945
|
+
var EXCEL_TOKENS = [
|
|
946
|
+
[/^am\/pm/i, "meridiemLong"],
|
|
947
|
+
[/^a\/p/i, "meridiemShort"],
|
|
948
|
+
[/^y{3,}/i, "year4"],
|
|
949
|
+
[/^y{1,2}/i, "year2"],
|
|
950
|
+
[/^m{5,}/i, "monthNarrow"],
|
|
951
|
+
[/^m{4}/i, "monthLong"],
|
|
952
|
+
[/^m{3}/i, "monthShort"],
|
|
953
|
+
[/^m{2}/i, "mAmbiguous2"],
|
|
954
|
+
[/^m/i, "mAmbiguous1"],
|
|
955
|
+
[/^d{4,}/i, "weekdayLong"],
|
|
956
|
+
[/^d{3}/i, "weekdayShort"],
|
|
957
|
+
[/^d{2}/i, "day2"],
|
|
958
|
+
[/^d/i, "day1"],
|
|
959
|
+
[/^h{2,}/i, "hour2"],
|
|
960
|
+
[/^h/i, "hour1"],
|
|
961
|
+
[/^s{2,}/i, "second2"],
|
|
962
|
+
[/^s/i, "second1"]
|
|
963
|
+
];
|
|
964
|
+
var DOTNET_TOKENS = [
|
|
965
|
+
[/^y{3,}/, "year4"],
|
|
966
|
+
[/^y{1,2}/, "year2"],
|
|
967
|
+
[/^M{4,}/, "monthLong"],
|
|
968
|
+
[/^M{3}/, "monthShort"],
|
|
969
|
+
[/^M{2}/, "month2"],
|
|
970
|
+
[/^M/, "month1"],
|
|
971
|
+
[/^d{4,}/, "weekdayLong"],
|
|
972
|
+
[/^d{3}/, "weekdayShort"],
|
|
973
|
+
[/^d{2}/, "day2"],
|
|
974
|
+
[/^d/, "day1"],
|
|
975
|
+
[/^H{2,}/, "hour2"],
|
|
976
|
+
[/^H/, "hour1"],
|
|
977
|
+
[/^h{2,}/, "hour2"],
|
|
978
|
+
[/^h/, "hour1"],
|
|
979
|
+
[/^m{2,}/, "minute2"],
|
|
980
|
+
[/^m/, "minute1"],
|
|
981
|
+
[/^s{2,}/, "second2"],
|
|
982
|
+
[/^s/, "second1"],
|
|
983
|
+
[/^f+/, "fraction"],
|
|
984
|
+
[/^tt/, "meridiemLong"],
|
|
985
|
+
[/^t/, "meridiemShort"]
|
|
986
|
+
];
|
|
987
|
+
function tokenize(s, dialect) {
|
|
988
|
+
const table = dialect === "excel" ? EXCEL_TOKENS : DOTNET_TOKENS;
|
|
989
|
+
const out = [];
|
|
990
|
+
let i = 0;
|
|
991
|
+
while (i < s.length) {
|
|
992
|
+
const ch = s[i];
|
|
993
|
+
if (ch === "\\" || dialect === "dotnet" && ch === "%") {
|
|
994
|
+
if (i + 1 < s.length) out.push({ kind: "literal", text: s[i + 1] });
|
|
995
|
+
i += 2;
|
|
996
|
+
continue;
|
|
997
|
+
}
|
|
998
|
+
if (ch === '"' || dialect === "dotnet" && ch === "'") {
|
|
999
|
+
const end = s.indexOf(ch, i + 1);
|
|
1000
|
+
out.push({ kind: "literal", text: end < 0 ? s.slice(i + 1) : s.slice(i + 1, end) });
|
|
1001
|
+
i = end < 0 ? s.length : end + 1;
|
|
1002
|
+
continue;
|
|
1003
|
+
}
|
|
1004
|
+
const rest = s.slice(i);
|
|
1005
|
+
let matched = false;
|
|
1006
|
+
for (const [re, kind] of table) {
|
|
1007
|
+
const m = re.exec(rest);
|
|
1008
|
+
if (m) {
|
|
1009
|
+
out.push({ kind });
|
|
1010
|
+
i += m[0].length;
|
|
1011
|
+
matched = true;
|
|
1012
|
+
break;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
if (!matched) {
|
|
1016
|
+
out.push({ kind: "literal", text: ch });
|
|
1017
|
+
i++;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
return out;
|
|
1021
|
+
}
|
|
1022
|
+
function resolveAmbiguousM(tokens) {
|
|
1023
|
+
const isHour = (t) => t.kind === "hour1" || t.kind === "hour2";
|
|
1024
|
+
const isSecond = (t) => t.kind === "second1" || t.kind === "second2";
|
|
1025
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
1026
|
+
const t = tokens[i];
|
|
1027
|
+
if (t.kind !== "mAmbiguous1" && t.kind !== "mAmbiguous2") continue;
|
|
1028
|
+
const two = t.kind === "mAmbiguous2";
|
|
1029
|
+
let minute = false;
|
|
1030
|
+
for (let j = i - 1; j >= 0; j--) {
|
|
1031
|
+
if (tokens[j].kind === "literal") continue;
|
|
1032
|
+
minute = isHour(tokens[j]);
|
|
1033
|
+
break;
|
|
1034
|
+
}
|
|
1035
|
+
if (!minute) {
|
|
1036
|
+
for (let j = i + 1; j < tokens.length; j++) {
|
|
1037
|
+
if (tokens[j].kind === "literal") continue;
|
|
1038
|
+
minute = isSecond(tokens[j]);
|
|
1039
|
+
break;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
t.kind = minute ? two ? "minute2" : "minute1" : two ? "month2" : "month1";
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
var EN_MONTHS_LONG = [
|
|
1046
|
+
"January",
|
|
1047
|
+
"February",
|
|
1048
|
+
"March",
|
|
1049
|
+
"April",
|
|
1050
|
+
"May",
|
|
1051
|
+
"June",
|
|
1052
|
+
"July",
|
|
1053
|
+
"August",
|
|
1054
|
+
"September",
|
|
1055
|
+
"October",
|
|
1056
|
+
"November",
|
|
1057
|
+
"December"
|
|
1058
|
+
];
|
|
1059
|
+
var EN_MONTHS_SHORT = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
1060
|
+
var EN_DAYS_LONG = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
|
1061
|
+
var EN_DAYS_SHORT = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
1062
|
+
function name(p3, kind, width, culture) {
|
|
1063
|
+
try {
|
|
1064
|
+
const opts = kind === "month" ? { month: width, timeZone: "UTC" } : { weekday: width === "narrow" ? "short" : width, timeZone: "UTC" };
|
|
1065
|
+
return new Intl.DateTimeFormat(culture || void 0, opts).format(utcAnchor(p3));
|
|
1066
|
+
} catch {
|
|
1067
|
+
if (kind === "month") {
|
|
1068
|
+
const long = EN_MONTHS_LONG[p3.month - 1] ?? "";
|
|
1069
|
+
if (width === "long") return long;
|
|
1070
|
+
return width === "narrow" ? long.slice(0, 1) : EN_MONTHS_SHORT[p3.month - 1] ?? "";
|
|
1071
|
+
}
|
|
1072
|
+
return (width === "long" ? EN_DAYS_LONG[p3.weekday] : EN_DAYS_SHORT[p3.weekday]) ?? "";
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
var p2 = (n) => (n < 10 ? "0" : "") + n;
|
|
1076
|
+
function renderFormat(format, p3, dialect, culture) {
|
|
1077
|
+
const tokens = tokenize(format, dialect);
|
|
1078
|
+
if (dialect === "excel") resolveAmbiguousM(tokens);
|
|
1079
|
+
const twelve = tokens.some((t) => t.kind === "meridiemLong" || t.kind === "meridiemShort");
|
|
1080
|
+
const hour12 = () => {
|
|
1081
|
+
const h = p3.hours % 12;
|
|
1082
|
+
return h === 0 ? 12 : h;
|
|
1083
|
+
};
|
|
1084
|
+
let out = "";
|
|
1085
|
+
let sawField = false;
|
|
1086
|
+
for (const t of tokens) {
|
|
1087
|
+
switch (t.kind) {
|
|
1088
|
+
case "literal":
|
|
1089
|
+
out += t.text ?? "";
|
|
1090
|
+
break;
|
|
1091
|
+
case "year4":
|
|
1092
|
+
out += String(p3.year);
|
|
1093
|
+
sawField = true;
|
|
1094
|
+
break;
|
|
1095
|
+
case "year2":
|
|
1096
|
+
out += p2((p3.year % 100 + 100) % 100);
|
|
1097
|
+
sawField = true;
|
|
1098
|
+
break;
|
|
1099
|
+
case "monthLong":
|
|
1100
|
+
out += name(p3, "month", "long", culture);
|
|
1101
|
+
sawField = true;
|
|
1102
|
+
break;
|
|
1103
|
+
case "monthShort":
|
|
1104
|
+
out += name(p3, "month", "short", culture);
|
|
1105
|
+
sawField = true;
|
|
1106
|
+
break;
|
|
1107
|
+
case "monthNarrow":
|
|
1108
|
+
out += name(p3, "month", "long", culture).slice(0, 1);
|
|
1109
|
+
sawField = true;
|
|
1110
|
+
break;
|
|
1111
|
+
case "month2":
|
|
1112
|
+
out += p2(p3.month);
|
|
1113
|
+
sawField = true;
|
|
1114
|
+
break;
|
|
1115
|
+
case "month1":
|
|
1116
|
+
out += String(p3.month);
|
|
1117
|
+
sawField = true;
|
|
1118
|
+
break;
|
|
1119
|
+
case "weekdayLong":
|
|
1120
|
+
out += name(p3, "weekday", "long", culture);
|
|
1121
|
+
sawField = true;
|
|
1122
|
+
break;
|
|
1123
|
+
case "weekdayShort":
|
|
1124
|
+
out += name(p3, "weekday", "short", culture);
|
|
1125
|
+
sawField = true;
|
|
1126
|
+
break;
|
|
1127
|
+
case "day2":
|
|
1128
|
+
out += p2(p3.day);
|
|
1129
|
+
sawField = true;
|
|
1130
|
+
break;
|
|
1131
|
+
case "day1":
|
|
1132
|
+
out += String(p3.day);
|
|
1133
|
+
sawField = true;
|
|
1134
|
+
break;
|
|
1135
|
+
case "hour2":
|
|
1136
|
+
out += p2(twelve ? hour12() : p3.hours);
|
|
1137
|
+
sawField = true;
|
|
1138
|
+
break;
|
|
1139
|
+
case "hour1":
|
|
1140
|
+
out += String(twelve ? hour12() : p3.hours);
|
|
1141
|
+
sawField = true;
|
|
1142
|
+
break;
|
|
1143
|
+
case "minute2":
|
|
1144
|
+
out += p2(p3.minutes);
|
|
1145
|
+
sawField = true;
|
|
1146
|
+
break;
|
|
1147
|
+
case "minute1":
|
|
1148
|
+
out += String(p3.minutes);
|
|
1149
|
+
sawField = true;
|
|
1150
|
+
break;
|
|
1151
|
+
case "second2":
|
|
1152
|
+
out += p2(p3.seconds);
|
|
1153
|
+
sawField = true;
|
|
1154
|
+
break;
|
|
1155
|
+
case "second1":
|
|
1156
|
+
out += String(p3.seconds);
|
|
1157
|
+
sawField = true;
|
|
1158
|
+
break;
|
|
1159
|
+
case "fraction":
|
|
1160
|
+
out += String(p3.ms).padStart(3, "0");
|
|
1161
|
+
sawField = true;
|
|
1162
|
+
break;
|
|
1163
|
+
case "meridiemLong":
|
|
1164
|
+
out += p3.hours < 12 ? "AM" : "PM";
|
|
1165
|
+
sawField = true;
|
|
1166
|
+
break;
|
|
1167
|
+
case "meridiemShort":
|
|
1168
|
+
out += p3.hours < 12 ? "A" : "P";
|
|
1169
|
+
sawField = true;
|
|
1170
|
+
break;
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
return sawField && out.trim() ? out : null;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
823
1176
|
// src/selectionCard.ts
|
|
824
1177
|
var SYNTHETIC_PREFIX = "__";
|
|
825
1178
|
var NUMERIC_DATATYPE = /int|double|decimal|single|float|number|currency|money/i;
|
|
@@ -852,8 +1205,8 @@ function aggLabel(agg) {
|
|
|
852
1205
|
return "Sum";
|
|
853
1206
|
}
|
|
854
1207
|
}
|
|
855
|
-
function isSynthetic(
|
|
856
|
-
return String(
|
|
1208
|
+
function isSynthetic(name2) {
|
|
1209
|
+
return String(name2 ?? "").startsWith(SYNTHETIC_PREFIX);
|
|
857
1210
|
}
|
|
858
1211
|
function isMeasureColumn(col) {
|
|
859
1212
|
if (!col || isSynthetic(col.name)) return false;
|
|
@@ -863,16 +1216,31 @@ function isMeasureColumn(col) {
|
|
|
863
1216
|
function isRealColumn(col) {
|
|
864
1217
|
return !!col && !isSynthetic(col.name);
|
|
865
1218
|
}
|
|
1219
|
+
function isSerialisedDateColumn(col) {
|
|
1220
|
+
return /^datetime/i.test(String(col?.dataType ?? ""));
|
|
1221
|
+
}
|
|
1222
|
+
function displayText(col, raw, opts) {
|
|
1223
|
+
if (raw == null || raw === "") return "(blank)";
|
|
1224
|
+
if (isSerialisedDateColumn(col)) {
|
|
1225
|
+
const shown = formatSourceDate(raw, {
|
|
1226
|
+
format: opts.sourceFormats?.byColumn?.[String(col?.name ?? "")] ?? null,
|
|
1227
|
+
dialect: opts.sourceFormats?.dialect,
|
|
1228
|
+
culture: opts.cultureCode
|
|
1229
|
+
});
|
|
1230
|
+
if (shown) return shown;
|
|
1231
|
+
}
|
|
1232
|
+
return String(raw);
|
|
1233
|
+
}
|
|
866
1234
|
function isSuppressed(col) {
|
|
867
|
-
return
|
|
1235
|
+
return vt(col).suppressed;
|
|
868
1236
|
}
|
|
869
1237
|
function resolveAgg(col, requested, auto) {
|
|
870
1238
|
const fallback = () => {
|
|
871
|
-
const d =
|
|
1239
|
+
const d = Me(col);
|
|
872
1240
|
return d === "first" ? "count" : d;
|
|
873
1241
|
};
|
|
874
1242
|
if (auto) return fallback();
|
|
875
|
-
return
|
|
1243
|
+
return It(col).indexOf(requested) >= 0 ? requested : fallback();
|
|
876
1244
|
}
|
|
877
1245
|
function toNumber(v) {
|
|
878
1246
|
if (v == null || v === "") return null;
|
|
@@ -939,9 +1307,8 @@ function computeSelectionCard(payload, selectedRowIdxs, opts = {}) {
|
|
|
939
1307
|
if (dimIdx >= 0) {
|
|
940
1308
|
const seen = [];
|
|
941
1309
|
const set = /* @__PURE__ */ new Set();
|
|
942
|
-
for (const
|
|
943
|
-
const
|
|
944
|
-
const s = raw == null || raw === "" ? "(blank)" : String(raw);
|
|
1310
|
+
for (const p3 of positions) {
|
|
1311
|
+
const s = displayText(columns[dimIdx], rows[p3]?.[dimIdx], opts);
|
|
945
1312
|
if (!set.has(s)) {
|
|
946
1313
|
set.add(s);
|
|
947
1314
|
seen.push(s);
|
|
@@ -969,14 +1336,14 @@ function computeSelectionCard(payload, selectedRowIdxs, opts = {}) {
|
|
|
969
1336
|
const colAgg = resolveAgg(col, agg, auto);
|
|
970
1337
|
const selVals = [];
|
|
971
1338
|
const selRaw = [];
|
|
972
|
-
for (const
|
|
973
|
-
const n = toNumber(rows[
|
|
974
|
-
selRaw.push(rows[
|
|
1339
|
+
for (const p3 of positions) {
|
|
1340
|
+
const n = toNumber(rows[p3]?.[c]);
|
|
1341
|
+
selRaw.push(rows[p3]?.[c]);
|
|
975
1342
|
if (n != null) selVals.push(n);
|
|
976
1343
|
}
|
|
977
1344
|
const value = reduce(selVals, colAgg, selRaw);
|
|
978
1345
|
let sharePct = null;
|
|
979
|
-
if (
|
|
1346
|
+
if (Te(col, colAgg) && value != null) {
|
|
980
1347
|
const allVals = [];
|
|
981
1348
|
const allRaw = [];
|
|
982
1349
|
for (let r = 0; r < rows.length; r++) {
|
|
@@ -1001,20 +1368,20 @@ function computeSelectionCard(payload, selectedRowIdxs, opts = {}) {
|
|
|
1001
1368
|
for (let c = 0; c < columns.length && dimensionLines.length < maxDimensions; c++) {
|
|
1002
1369
|
if (c === dimIdx) continue;
|
|
1003
1370
|
if (!isRealColumn(columns[c]) || isMeasureColumn(columns[c])) continue;
|
|
1004
|
-
const
|
|
1371
|
+
const name2 = String(columns[c]?.name ?? "");
|
|
1005
1372
|
const distinct = /* @__PURE__ */ new Set();
|
|
1006
1373
|
let firstText = "";
|
|
1007
|
-
for (const
|
|
1008
|
-
const raw = rows[
|
|
1009
|
-
const
|
|
1010
|
-
if (!distinct.size) firstText =
|
|
1011
|
-
distinct.add(
|
|
1374
|
+
for (const p3 of positions) {
|
|
1375
|
+
const raw = rows[p3]?.[c];
|
|
1376
|
+
const key = raw == null || raw === "" ? "(blank)" : String(raw);
|
|
1377
|
+
if (!distinct.size) firstText = displayText(columns[c], raw, opts);
|
|
1378
|
+
distinct.add(key);
|
|
1012
1379
|
}
|
|
1013
1380
|
if (!distinct.size) continue;
|
|
1014
1381
|
const single = distinct.size === 1;
|
|
1015
1382
|
dimensionLines.push({
|
|
1016
|
-
column:
|
|
1017
|
-
label: single ?
|
|
1383
|
+
column: name2,
|
|
1384
|
+
label: single ? name2 : `Distinct ${name2}`,
|
|
1018
1385
|
value: single ? null : distinct.size,
|
|
1019
1386
|
valueText: single ? firstText : fmtNumber(distinct.size, opts.cultureCode),
|
|
1020
1387
|
sharePct: null,
|
|
@@ -1137,6 +1504,8 @@ export {
|
|
|
1137
1504
|
filterQualifyRows,
|
|
1138
1505
|
fitReadingFor,
|
|
1139
1506
|
fitRenderedChart,
|
|
1507
|
+
formatSourceDate,
|
|
1508
|
+
formatSourceDateFor,
|
|
1140
1509
|
geoAssetFor,
|
|
1141
1510
|
geoFromCache,
|
|
1142
1511
|
glyphSampleGrid,
|
package/dist/react.mjs
CHANGED
package/dist/types/index.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export { qualifyGroupHeadingFor, newQualifyGroupState, type QualifyGroupRow, typ
|
|
|
15
15
|
export { qualifyPick, qualifyAuto, qualifyCancel, launchGenerates, launchFavorStyle, chooserFitsViewport, shouldOpenChooserOnGenerate, shouldOpenInlineChooserOnGenerate, canConfirmLaunch, confirmLaunch, qualifyFailureFallsOpen, CHOOSER_MIN_WIDTH_PX, CHOOSER_MIN_HEIGHT_PX, type QualifyLaunchOutcome, type ChooserGateInput, } from "./qualifyLaunch";
|
|
16
16
|
export { normalizeFilterTerm, filterQualifyRows, readQualifyChartRow, readQualifyRefusalRow, filterFitsChooser, listNeedsFilter, qualifyFilterGate, inlineFilterGate, FILTER_MIN_TERM_CHARS, FILTER_ROW_PX, FILTER_MIN_WIDTH_PX, FILTER_MIN_HEIGHT_PX, INLINE_FILTER_MIN_ROWS, computeQualifyFilterView, qualifyFilterCountText, type QualifyFilterTier, type QualifyFilterResult, type QualifyFilterRead, type QualifyFilterGateReason, type QualifyFilterGateInput, type QualifyFilterRow, type QualifyFilterGroup, type QualifyFilterView, type QualifyFilterNoteKind, } from "./qualifyFilter";
|
|
17
17
|
export { computeSelectionCard, normaliseAggregation, type SelectionCardModel, type SelectionCardLine, type SelectionCardOptions, } from "./selectionCard";
|
|
18
|
+
export { formatSourceDate, formatSourceDateFor, type SourceFormats, type SourceFormatDialect, type SourceDateOptions, } from "./sourceDateFormat";
|
|
18
19
|
export { ensureCrossfilterHitTargets, type HitTargetReport } from "./hitTargets";
|
|
19
20
|
export { applyLabelContrast, LABEL_CONTRAST_DONE_ATTR, LABEL_CONTRAST_CAP, type LabelContrastOptions, type LabelContrastReport, } from "./labelContrastDom";
|
|
20
21
|
export { decideLabelColor, toRGBA, compositeOver, relativeLuminance, contrastRatio, isPillBackdropAlpha, pillBacksGlyph, backingHoldsGlyph, cellSuppressesNormalize, glyphSampleGrid, DARK_TEXT, LIGHT_TEXT, MIN_CONTRAST, WHITE_TEXT_BG_LUM, PILL_MIN_ALPHA, PILL_OPAQUE_ALPHA, PILL_MIN_COVERAGE, PAGE_MATCH_TOLERANCE, BACKING_MAJORITY, GLYPH_SAMPLE_N, type LabelDecision, } from "./labelContrast";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { RenderPayload } from "./payload";
|
|
2
|
+
import { type SourceFormats } from "./sourceDateFormat";
|
|
2
3
|
export interface SelectionCardOptions {
|
|
3
4
|
/** The chart's own aggregation, so the card AGREES with the picture it sits on. A card
|
|
4
5
|
* reading "Sum" beside a chart drawn from averages is two answers to one question.
|
|
@@ -24,6 +25,16 @@ export interface SelectionCardOptions {
|
|
|
24
25
|
* cap already had (on the sample table the two coordinate columns took two of the four
|
|
25
26
|
* measure slots and pushed the real measures under "+2 more"). Default 2. */
|
|
26
27
|
maxDimensions?: number;
|
|
28
|
+
/** THE SOURCE'S OWN DATE FORMATS, keyed by column name, plus the dialect they are written in
|
|
29
|
+
* — Excel's `range.numberFormat` for the add-in, a `.NET` format string for the visual.
|
|
30
|
+
*
|
|
31
|
+
* Optional, and the fallback is deliberately good rather than minimal: a host that supplies
|
|
32
|
+
* nothing still gets a locale date instead of the ISO instant this replaced, so the MCP and
|
|
33
|
+
* React hosts (a CSV has no cell formatting to read) and every chart cached before today
|
|
34
|
+
* improve without anyone plumbing anything. What the map buys is AGREEMENT with the cells
|
|
35
|
+
* the card is floating over — a sheet that shows `31-Aug-25` gets a card that says
|
|
36
|
+
* `31-Aug-25`. */
|
|
37
|
+
sourceFormats?: SourceFormats | null;
|
|
27
38
|
}
|
|
28
39
|
export interface SelectionCardLine {
|
|
29
40
|
column: string;
|