@bicharts/chart-host 0.5.62 → 0.5.64
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-WG4NTR5H.mjs → chunk-YDFJF2UG.mjs} +226 -0
- package/dist/index.mjs +29 -1
- package/dist/react.mjs +1 -1
- package/dist/types/fit.d.ts +36 -0
- package/dist/types/fitDom.d.ts +59 -0
- package/dist/types/host.d.ts +19 -0
- package/dist/types/index.d.ts +2 -0
- package/package.json +1 -1
|
@@ -1308,6 +1308,210 @@ function hitBandFlag(c) {
|
|
|
1308
1308
|
return c.uncovered > 0 ? "hitband:d3:thin" : "hitband:d3:ok";
|
|
1309
1309
|
}
|
|
1310
1310
|
|
|
1311
|
+
// src/fit.ts
|
|
1312
|
+
var SCROLL_SLACK_PX = 8;
|
|
1313
|
+
function needsScroll(contentPx, viewportPx, slackPx = SCROLL_SLACK_PX) {
|
|
1314
|
+
if (!isFinite(contentPx) || !isFinite(viewportPx)) return false;
|
|
1315
|
+
if (contentPx <= 0 || viewportPx <= 0) return false;
|
|
1316
|
+
return contentPx - viewportPx > slackPx;
|
|
1317
|
+
}
|
|
1318
|
+
function contentExtentOf(boxes) {
|
|
1319
|
+
if (!boxes || boxes.length === 0) return { w: 0, h: 0, source: "none" };
|
|
1320
|
+
const usable = boxes.filter((b2) => b2 && isFinite(b2.right) && isFinite(b2.bottom) && (b2.right > 0 || b2.bottom > 0));
|
|
1321
|
+
if (usable.length === 0) return { w: 0, h: 0, source: "none" };
|
|
1322
|
+
const inFlow = usable.filter((b2) => !b2.floating);
|
|
1323
|
+
const counted = inFlow.length > 0 ? inFlow : usable;
|
|
1324
|
+
let w = 0, h3 = 0;
|
|
1325
|
+
for (const b2 of counted) {
|
|
1326
|
+
if (b2.right > w) w = b2.right;
|
|
1327
|
+
if (b2.bottom > h3) h3 = b2.bottom;
|
|
1328
|
+
}
|
|
1329
|
+
return { w, h: h3, source: inFlow.length > 0 ? "in-flow" : "all-floating" };
|
|
1330
|
+
}
|
|
1331
|
+
function scrollFitFor(contentW, contentH, viewportW, viewportH, slackPx = SCROLL_SLACK_PX) {
|
|
1332
|
+
return {
|
|
1333
|
+
overflowX: needsScroll(contentW, viewportW, slackPx) ? "auto" : "hidden",
|
|
1334
|
+
overflowY: needsScroll(contentH, viewportH, slackPx) ? "auto" : "hidden"
|
|
1335
|
+
};
|
|
1336
|
+
}
|
|
1337
|
+
var FIT_CONTENT_SELECTOR = "text, .d3-mark, .d3-legend-mark";
|
|
1338
|
+
var PHANTOM_FRACTION = 0.95;
|
|
1339
|
+
function isPhantomBox(w, h3, refW, refH) {
|
|
1340
|
+
return w > refW * PHANTOM_FRACTION || h3 > refH * PHANTOM_FRACTION;
|
|
1341
|
+
}
|
|
1342
|
+
var MAX_FRAME_GROW_FACTOR = 20;
|
|
1343
|
+
function planFrameGrow(inkBottomPx, elHeightPx, viewBoxH, ctmScale, slackPx = SCROLL_SLACK_PX, maxFactor = MAX_FRAME_GROW_FACTOR) {
|
|
1344
|
+
const none = (reason) => ({ grow: false, heightPx: elHeightPx, viewBoxH, reason });
|
|
1345
|
+
if (!isFinite(inkBottomPx) || !isFinite(elHeightPx) || elHeightPx <= 0) return none("unmeasurable");
|
|
1346
|
+
if (!needsScroll(inkBottomPx, elHeightPx, slackPx)) return none("fits");
|
|
1347
|
+
if (inkBottomPx > elHeightPx * maxFactor) return none("beyond-ceiling");
|
|
1348
|
+
const heightPx = Math.ceil(inkBottomPx);
|
|
1349
|
+
const delta = heightPx - elHeightPx;
|
|
1350
|
+
if (!(viewBoxH > 0)) return { grow: true, heightPx, viewBoxH, reason: "grow-no-viewbox" };
|
|
1351
|
+
if (!(ctmScale > 0) || !isFinite(ctmScale)) return none("no-scale");
|
|
1352
|
+
return { grow: true, heightPx, viewBoxH: viewBoxH + delta / ctmScale, reason: "grow" };
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
// src/fitDom.ts
|
|
1356
|
+
var FIT_WALK_CAP = 8e3;
|
|
1357
|
+
function ctmScaleOf(m2) {
|
|
1358
|
+
if (!m2) return 0;
|
|
1359
|
+
const s = Math.hypot(m2.a, m2.b);
|
|
1360
|
+
return isFinite(s) && s > 0 ? s : 0;
|
|
1361
|
+
}
|
|
1362
|
+
function svgInkReach(svg) {
|
|
1363
|
+
try {
|
|
1364
|
+
const box = svg.getBoundingClientRect();
|
|
1365
|
+
if (!(box.width > 0) || !(box.height > 0)) return null;
|
|
1366
|
+
const els = svg.querySelectorAll(FIT_CONTENT_SELECTOR);
|
|
1367
|
+
let right = -Infinity, bottom = -Infinity, seen = 0;
|
|
1368
|
+
for (let i = 0; i < els.length && seen < FIT_WALK_CAP; i++) {
|
|
1369
|
+
const r = els[i].getBoundingClientRect();
|
|
1370
|
+
if (r.width <= 0 && r.height <= 0) continue;
|
|
1371
|
+
if (isPhantomBox(r.width, r.height, box.width, box.height)) continue;
|
|
1372
|
+
seen++;
|
|
1373
|
+
if (r.right > right) right = r.right;
|
|
1374
|
+
if (r.bottom > bottom) bottom = r.bottom;
|
|
1375
|
+
}
|
|
1376
|
+
if (seen === 0) return null;
|
|
1377
|
+
return { right: right - box.left, bottom: bottom - box.top };
|
|
1378
|
+
} catch {
|
|
1379
|
+
return null;
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
function measureContainerBoxes(c, inkOf) {
|
|
1383
|
+
const boxes = [];
|
|
1384
|
+
if (!c || !c.children) return boxes;
|
|
1385
|
+
const view = c.ownerDocument && c.ownerDocument.defaultView || globalThis;
|
|
1386
|
+
const cRect = c.getBoundingClientRect();
|
|
1387
|
+
for (let i = 0; i < c.children.length; i++) {
|
|
1388
|
+
const el = c.children[i];
|
|
1389
|
+
let floating = false;
|
|
1390
|
+
try {
|
|
1391
|
+
const cs = view.getComputedStyle(el);
|
|
1392
|
+
if (cs.display === "none") continue;
|
|
1393
|
+
floating = cs.position === "absolute" || cs.position === "fixed";
|
|
1394
|
+
} catch {
|
|
1395
|
+
}
|
|
1396
|
+
const r = el.getBoundingClientRect();
|
|
1397
|
+
let right = r.right - cRect.left - c.clientLeft + c.scrollLeft;
|
|
1398
|
+
let bottom = r.bottom - cRect.top - c.clientTop + c.scrollTop;
|
|
1399
|
+
if (inkOf) {
|
|
1400
|
+
try {
|
|
1401
|
+
const ink = inkOf(el);
|
|
1402
|
+
if (ink) {
|
|
1403
|
+
const originX = r.left - cRect.left - c.clientLeft + c.scrollLeft;
|
|
1404
|
+
const originY = r.top - cRect.top - c.clientTop + c.scrollTop;
|
|
1405
|
+
if (isFinite(ink.right)) right = Math.max(right, originX + ink.right);
|
|
1406
|
+
if (isFinite(ink.bottom)) bottom = Math.max(bottom, originY + ink.bottom);
|
|
1407
|
+
}
|
|
1408
|
+
} catch {
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
boxes.push({ right, bottom, floating });
|
|
1412
|
+
}
|
|
1413
|
+
return boxes;
|
|
1414
|
+
}
|
|
1415
|
+
function fitReadingFor(lane, c, slackPx = SCROLL_SLACK_PX, inkOf) {
|
|
1416
|
+
if (!c) return null;
|
|
1417
|
+
const viewW = c.clientWidth, viewH = c.clientHeight;
|
|
1418
|
+
if (!(viewW > 0) || !(viewH > 0)) return null;
|
|
1419
|
+
const boxes = measureContainerBoxes(c, inkOf);
|
|
1420
|
+
const extent = contentExtentOf(boxes);
|
|
1421
|
+
const contentW = extent.source === "none" ? c.scrollWidth : extent.w;
|
|
1422
|
+
const contentH = extent.source === "none" ? c.scrollHeight : extent.h;
|
|
1423
|
+
if (!isFinite(contentW) || !isFinite(contentH)) return null;
|
|
1424
|
+
return {
|
|
1425
|
+
lane,
|
|
1426
|
+
contentW: Math.round(contentW),
|
|
1427
|
+
contentH: Math.round(contentH),
|
|
1428
|
+
viewW,
|
|
1429
|
+
viewH,
|
|
1430
|
+
overflowsX: needsScroll(contentW, viewW, slackPx),
|
|
1431
|
+
overflowsY: needsScroll(contentH, viewH, slackPx),
|
|
1432
|
+
extentSource: extent.source,
|
|
1433
|
+
boxes: boxes.length,
|
|
1434
|
+
floating: boxes.filter((b2) => b2.floating).length,
|
|
1435
|
+
scrollW: c.scrollWidth,
|
|
1436
|
+
scrollH: c.scrollHeight
|
|
1437
|
+
};
|
|
1438
|
+
}
|
|
1439
|
+
function fitRenderedChart(container, opts = {}) {
|
|
1440
|
+
const slackPx = opts.slackPx ?? SCROLL_SLACK_PX;
|
|
1441
|
+
const result = {
|
|
1442
|
+
grew: false,
|
|
1443
|
+
growFrom: 0,
|
|
1444
|
+
growTo: 0,
|
|
1445
|
+
growReason: "no-container",
|
|
1446
|
+
overflowX: "hidden",
|
|
1447
|
+
overflowY: "hidden",
|
|
1448
|
+
reading: null
|
|
1449
|
+
};
|
|
1450
|
+
if (!container) return result;
|
|
1451
|
+
try {
|
|
1452
|
+
let svg = null;
|
|
1453
|
+
for (let i = 0; i < container.children.length && !svg; i++) {
|
|
1454
|
+
const el = container.children[i];
|
|
1455
|
+
if (el.tagName && el.tagName.toLowerCase() === "svg") svg = el;
|
|
1456
|
+
}
|
|
1457
|
+
if (!svg) svg = container.querySelector("svg");
|
|
1458
|
+
let inkCache;
|
|
1459
|
+
const svgInk = (el) => {
|
|
1460
|
+
if (inkCache === void 0) inkCache = svgInkReach(el);
|
|
1461
|
+
return inkCache;
|
|
1462
|
+
};
|
|
1463
|
+
const inkOf = (el) => el && el.tagName && el.tagName.toLowerCase() === "svg" ? svgInk(el) : null;
|
|
1464
|
+
if (svg && opts.grow !== false) {
|
|
1465
|
+
try {
|
|
1466
|
+
const ink = svgInk(svg);
|
|
1467
|
+
const elH = svg.getBoundingClientRect().height;
|
|
1468
|
+
const vb = svg.viewBox && svg.viewBox.baseVal ? svg.viewBox.baseVal.height : 0;
|
|
1469
|
+
const ctm = typeof svg.getScreenCTM === "function" ? svg.getScreenCTM() : null;
|
|
1470
|
+
const plan = planFrameGrow(
|
|
1471
|
+
ink ? ink.bottom : NaN,
|
|
1472
|
+
elH,
|
|
1473
|
+
vb,
|
|
1474
|
+
ctmScaleOf(ctm),
|
|
1475
|
+
slackPx
|
|
1476
|
+
);
|
|
1477
|
+
result.growFrom = Math.round(elH);
|
|
1478
|
+
result.growTo = plan.heightPx;
|
|
1479
|
+
result.growReason = plan.reason;
|
|
1480
|
+
if (plan.grow) {
|
|
1481
|
+
svg.setAttribute("height", String(plan.heightPx));
|
|
1482
|
+
if (vb > 0) {
|
|
1483
|
+
const b2 = svg.viewBox.baseVal;
|
|
1484
|
+
svg.setAttribute("viewBox", `${b2.x} ${b2.y} ${b2.width} ${plan.viewBoxH}`);
|
|
1485
|
+
}
|
|
1486
|
+
result.grew = true;
|
|
1487
|
+
}
|
|
1488
|
+
} catch {
|
|
1489
|
+
result.growReason = "grow-threw";
|
|
1490
|
+
}
|
|
1491
|
+
} else if (!svg) {
|
|
1492
|
+
result.growReason = "no-svg";
|
|
1493
|
+
} else {
|
|
1494
|
+
result.growReason = "grow-disabled";
|
|
1495
|
+
}
|
|
1496
|
+
const reading = fitReadingFor(opts.lane ?? "chart", container, slackPx, inkOf);
|
|
1497
|
+
result.reading = reading;
|
|
1498
|
+
const boxes = measureContainerBoxes(container, inkOf);
|
|
1499
|
+
const extent = contentExtentOf(boxes);
|
|
1500
|
+
const svgBox = svg ? svg.getBoundingClientRect() : null;
|
|
1501
|
+
const contentW = extent.source === "none" ? Math.max(container.scrollWidth, svgBox ? svgBox.width : 0) : extent.w;
|
|
1502
|
+
const contentH = extent.source === "none" ? Math.max(container.scrollHeight, svgBox ? svgBox.height : 0) : extent.h;
|
|
1503
|
+
const fit = scrollFitFor(contentW, contentH, container.clientWidth, container.clientHeight, slackPx);
|
|
1504
|
+
result.overflowX = fit.overflowX;
|
|
1505
|
+
result.overflowY = fit.overflowY;
|
|
1506
|
+
if (opts.applyOverflow !== false) {
|
|
1507
|
+
container.style.overflowX = fit.overflowX;
|
|
1508
|
+
container.style.overflowY = fit.overflowY;
|
|
1509
|
+
}
|
|
1510
|
+
} catch {
|
|
1511
|
+
}
|
|
1512
|
+
return result;
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1311
1515
|
// src/host.ts
|
|
1312
1516
|
function sessionViewStateProvider(container) {
|
|
1313
1517
|
const holder = container;
|
|
@@ -1569,6 +1773,7 @@ function createChartHost(container, config) {
|
|
|
1569
1773
|
container.addEventListener("click", onClick);
|
|
1570
1774
|
container.classList?.add(HOST_CONTAINER_CLASS);
|
|
1571
1775
|
ensureAffordanceStyles();
|
|
1776
|
+
const fitOpts = config.fit ?? true;
|
|
1572
1777
|
const stopAnim = () => {
|
|
1573
1778
|
const s = container[CONTAINER_SLOT_ANIM_STOP];
|
|
1574
1779
|
if (typeof s === "function") {
|
|
@@ -1637,6 +1842,13 @@ function createChartHost(container, config) {
|
|
|
1637
1842
|
} catch {
|
|
1638
1843
|
}
|
|
1639
1844
|
}
|
|
1845
|
+
if (fitOpts !== false) {
|
|
1846
|
+
try {
|
|
1847
|
+
const r = fitRenderedChart(container, fitOpts === true ? {} : fitOpts);
|
|
1848
|
+
config.onFit?.(r);
|
|
1849
|
+
} catch {
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1640
1852
|
},
|
|
1641
1853
|
setOptions(partial) {
|
|
1642
1854
|
raw = { ...raw, ...partial };
|
|
@@ -1745,6 +1957,20 @@ export {
|
|
|
1745
1957
|
MIN_HIT_BAND_PX,
|
|
1746
1958
|
censusHitBands,
|
|
1747
1959
|
hitBandFlag,
|
|
1960
|
+
SCROLL_SLACK_PX,
|
|
1961
|
+
needsScroll,
|
|
1962
|
+
contentExtentOf,
|
|
1963
|
+
scrollFitFor,
|
|
1964
|
+
FIT_CONTENT_SELECTOR,
|
|
1965
|
+
PHANTOM_FRACTION,
|
|
1966
|
+
isPhantomBox,
|
|
1967
|
+
MAX_FRAME_GROW_FACTOR,
|
|
1968
|
+
planFrameGrow,
|
|
1969
|
+
ctmScaleOf,
|
|
1970
|
+
svgInkReach,
|
|
1971
|
+
measureContainerBoxes,
|
|
1972
|
+
fitReadingFor,
|
|
1973
|
+
fitRenderedChart,
|
|
1748
1974
|
sessionViewStateProvider,
|
|
1749
1975
|
noopViewStateProvider,
|
|
1750
1976
|
requiredD3Plugins,
|
package/dist/index.mjs
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
D,
|
|
22
22
|
DIM_OPACITY_DEFAULT,
|
|
23
23
|
DIM_OPACITY_VAR,
|
|
24
|
+
FIT_CONTENT_SELECTOR,
|
|
24
25
|
FLIP_MODE_DEFAULT,
|
|
25
26
|
G,
|
|
26
27
|
GEO_POINT_PRECISIONS,
|
|
@@ -31,8 +32,11 @@ import {
|
|
|
31
32
|
LIFT_SELECTED_CLASS,
|
|
32
33
|
MARK_CLASS,
|
|
33
34
|
MARK_SELECTED_CLASS,
|
|
35
|
+
MAX_FRAME_GROW_FACTOR,
|
|
34
36
|
MIN_HIT_BAND_PX,
|
|
37
|
+
PHANTOM_FRACTION,
|
|
35
38
|
ROW_IDX_ATTR,
|
|
39
|
+
SCROLL_SLACK_PX,
|
|
36
40
|
SELECTION_ACTIVE_CLASS,
|
|
37
41
|
VALUE_AXIS_BASELINE_DEFAULT,
|
|
38
42
|
XFILTER_REFRESH_EVENT,
|
|
@@ -43,26 +47,36 @@ import {
|
|
|
43
47
|
chartOwnsTimeline,
|
|
44
48
|
clearGeoCache,
|
|
45
49
|
compileRenderFn,
|
|
50
|
+
contentExtentOf,
|
|
46
51
|
createChartHost,
|
|
47
52
|
createMarkResolver,
|
|
53
|
+
ctmScaleOf,
|
|
48
54
|
ensureCrossfilterHitTargets,
|
|
49
55
|
explainRenderFailure,
|
|
56
|
+
fitReadingFor,
|
|
57
|
+
fitRenderedChart,
|
|
50
58
|
geoAssetFor,
|
|
51
59
|
geoFromCache,
|
|
52
60
|
hitBandFlag,
|
|
53
61
|
isBlankRender,
|
|
62
|
+
isPhantomBox,
|
|
54
63
|
loadGeo,
|
|
64
|
+
measureContainerBoxes,
|
|
55
65
|
ne,
|
|
66
|
+
needsScroll,
|
|
56
67
|
noopViewStateProvider,
|
|
57
68
|
periodTickSuppressesFeedback,
|
|
69
|
+
planFrameGrow,
|
|
58
70
|
registerGeo,
|
|
59
71
|
registerGeoAsset,
|
|
60
72
|
requiredD3Plugins,
|
|
61
73
|
resolveOptions,
|
|
74
|
+
scrollFitFor,
|
|
62
75
|
sessionViewStateProvider,
|
|
63
76
|
stripEsmExports,
|
|
77
|
+
svgInkReach,
|
|
64
78
|
te
|
|
65
|
-
} from "./chunk-
|
|
79
|
+
} from "./chunk-YDFJF2UG.mjs";
|
|
66
80
|
import "./chunk-A2GMXZP7.mjs";
|
|
67
81
|
|
|
68
82
|
// src/trivial.ts
|
|
@@ -1011,6 +1025,7 @@ export {
|
|
|
1011
1025
|
FILTER_MIN_TERM_CHARS,
|
|
1012
1026
|
FILTER_MIN_WIDTH_PX,
|
|
1013
1027
|
FILTER_ROW_PX,
|
|
1028
|
+
FIT_CONTENT_SELECTOR,
|
|
1014
1029
|
FLIP_MODE_DEFAULT,
|
|
1015
1030
|
GEO_POINT_PRECISIONS,
|
|
1016
1031
|
HOST_CONTAINER_CLASS,
|
|
@@ -1020,8 +1035,11 @@ export {
|
|
|
1020
1035
|
LIFT_SELECTED_CLASS,
|
|
1021
1036
|
MARK_CLASS,
|
|
1022
1037
|
MARK_SELECTED_CLASS,
|
|
1038
|
+
MAX_FRAME_GROW_FACTOR,
|
|
1023
1039
|
MIN_HIT_BAND_PX,
|
|
1040
|
+
PHANTOM_FRACTION,
|
|
1024
1041
|
ROW_IDX_ATTR,
|
|
1042
|
+
SCROLL_SLACK_PX,
|
|
1025
1043
|
SELECTION_ACTIVE_CLASS,
|
|
1026
1044
|
VALUE_AXIS_BASELINE_DEFAULT,
|
|
1027
1045
|
XFILTER_REFRESH_EVENT,
|
|
@@ -1043,22 +1061,29 @@ export {
|
|
|
1043
1061
|
computeQualifyFilterView,
|
|
1044
1062
|
computeSelectionCard,
|
|
1045
1063
|
confirmLaunch,
|
|
1064
|
+
contentExtentOf,
|
|
1046
1065
|
createChartHost,
|
|
1047
1066
|
createMarkResolver,
|
|
1067
|
+
ctmScaleOf,
|
|
1048
1068
|
ensureCrossfilterHitTargets,
|
|
1049
1069
|
explainRenderFailure,
|
|
1050
1070
|
filterFitsChooser,
|
|
1051
1071
|
filterQualifyRows,
|
|
1072
|
+
fitReadingFor,
|
|
1073
|
+
fitRenderedChart,
|
|
1052
1074
|
geoAssetFor,
|
|
1053
1075
|
geoFromCache,
|
|
1054
1076
|
hasRefusalsToShow,
|
|
1055
1077
|
hitBandFlag,
|
|
1056
1078
|
inlineFilterGate,
|
|
1057
1079
|
isBlankRender,
|
|
1080
|
+
isPhantomBox,
|
|
1058
1081
|
launchFavorStyle,
|
|
1059
1082
|
launchGenerates,
|
|
1060
1083
|
listNeedsFilter,
|
|
1061
1084
|
loadGeo,
|
|
1085
|
+
measureContainerBoxes,
|
|
1086
|
+
needsScroll,
|
|
1062
1087
|
newQualifyGroupState,
|
|
1063
1088
|
newQualifyRefusalGroupState,
|
|
1064
1089
|
noopViewStateProvider,
|
|
@@ -1066,6 +1091,7 @@ export {
|
|
|
1066
1091
|
normalizeFilterTerm,
|
|
1067
1092
|
orderRefusalsForDisplay,
|
|
1068
1093
|
periodTickSuppressesFeedback,
|
|
1094
|
+
planFrameGrow,
|
|
1069
1095
|
planTrivialChart,
|
|
1070
1096
|
qualifyAuto,
|
|
1071
1097
|
qualifyCancel,
|
|
@@ -1084,11 +1110,13 @@ export {
|
|
|
1084
1110
|
registerGeoAsset,
|
|
1085
1111
|
requiredD3Plugins,
|
|
1086
1112
|
resolveOptions,
|
|
1113
|
+
scrollFitFor,
|
|
1087
1114
|
sessionViewStateProvider,
|
|
1088
1115
|
shouldOpenChooserOnGenerate,
|
|
1089
1116
|
shouldOpenInlineChooserOnGenerate,
|
|
1090
1117
|
shouldReview,
|
|
1091
1118
|
stripEsmExports,
|
|
1119
|
+
svgInkReach,
|
|
1092
1120
|
svgNaturalSize,
|
|
1093
1121
|
svgToDataUrl
|
|
1094
1122
|
};
|
package/dist/react.mjs
CHANGED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export declare const SCROLL_SLACK_PX = 8;
|
|
2
|
+
export declare function needsScroll(contentPx: number, viewportPx: number, slackPx?: number): boolean;
|
|
3
|
+
export interface MeasuredBox {
|
|
4
|
+
right: number;
|
|
5
|
+
bottom: number;
|
|
6
|
+
floating: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface ContentExtent {
|
|
9
|
+
w: number;
|
|
10
|
+
h: number;
|
|
11
|
+
source: "in-flow" | "all-floating" | "none";
|
|
12
|
+
}
|
|
13
|
+
export declare function contentExtentOf(boxes: MeasuredBox[]): ContentExtent;
|
|
14
|
+
export interface ScrollFit {
|
|
15
|
+
overflowX: "hidden" | "auto";
|
|
16
|
+
overflowY: "hidden" | "auto";
|
|
17
|
+
}
|
|
18
|
+
export declare function scrollFitFor(contentW: number, contentH: number, viewportW: number, viewportH: number, slackPx?: number): ScrollFit;
|
|
19
|
+
export declare const FIT_CONTENT_SELECTOR = "text, .d3-mark, .d3-legend-mark";
|
|
20
|
+
export declare const PHANTOM_FRACTION = 0.95;
|
|
21
|
+
export declare function isPhantomBox(w: number, h: number, refW: number, refH: number): boolean;
|
|
22
|
+
export declare const MAX_FRAME_GROW_FACTOR = 20;
|
|
23
|
+
export interface FrameGrowPlan {
|
|
24
|
+
grow: boolean;
|
|
25
|
+
heightPx: number;
|
|
26
|
+
viewBoxH: number;
|
|
27
|
+
reason: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* @param inkBottomPx how far the chart's ink reaches below the element's top, in px
|
|
31
|
+
* @param elHeightPx the element's current rendered height, in px
|
|
32
|
+
* @param viewBoxH the current viewBox height in user units, or 0 when there is no viewBox
|
|
33
|
+
* (then user units ARE px and only the element moves)
|
|
34
|
+
* @param ctmScale px per user unit, from the live screen CTM
|
|
35
|
+
*/
|
|
36
|
+
export declare function planFrameGrow(inkBottomPx: number, elHeightPx: number, viewBoxH: number, ctmScale: number, slackPx?: number, maxFactor?: number): FrameGrowPlan;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { ContentExtent, MeasuredBox } from "./fit";
|
|
2
|
+
export declare function ctmScaleOf(m: {
|
|
3
|
+
a: number;
|
|
4
|
+
b: number;
|
|
5
|
+
} | null | undefined): number;
|
|
6
|
+
export declare function svgInkReach(svg: SVGSVGElement): {
|
|
7
|
+
right: number;
|
|
8
|
+
bottom: number;
|
|
9
|
+
} | null;
|
|
10
|
+
export type InkReach = (el: Element) => {
|
|
11
|
+
right: number;
|
|
12
|
+
bottom: number;
|
|
13
|
+
} | null;
|
|
14
|
+
export declare function measureContainerBoxes(c: HTMLElement, inkOf?: InkReach): MeasuredBox[];
|
|
15
|
+
export interface FitReading {
|
|
16
|
+
lane: string;
|
|
17
|
+
contentW: number;
|
|
18
|
+
contentH: number;
|
|
19
|
+
viewW: number;
|
|
20
|
+
viewH: number;
|
|
21
|
+
overflowsX: boolean;
|
|
22
|
+
overflowsY: boolean;
|
|
23
|
+
extentSource: ContentExtent["source"];
|
|
24
|
+
boxes: number;
|
|
25
|
+
floating: number;
|
|
26
|
+
scrollW: number;
|
|
27
|
+
scrollH: number;
|
|
28
|
+
}
|
|
29
|
+
export declare function fitReadingFor(lane: string, c: HTMLElement | null | undefined, slackPx?: number, inkOf?: InkReach): FitReading | null;
|
|
30
|
+
export interface FitRenderedChartOptions {
|
|
31
|
+
/** Name for this container in the returned reading. Purely descriptive. */
|
|
32
|
+
lane?: string;
|
|
33
|
+
/** How far content must exceed the viewport before it counts. Defaults to SCROLL_SLACK_PX. */
|
|
34
|
+
slackPx?: number;
|
|
35
|
+
/**
|
|
36
|
+
* Grow the chart's <svg> to whatever it actually drew, so the container has something to
|
|
37
|
+
* scroll TO. Default true - it is the half of this pass that recovers content that is
|
|
38
|
+
* otherwise unreachable. Set false to take the READING without touching the chart.
|
|
39
|
+
*/
|
|
40
|
+
grow?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Set `overflowX` / `overflowY` on the container. Default true. Set false when the host owns
|
|
43
|
+
* its own scrolling and only wants to be told.
|
|
44
|
+
*/
|
|
45
|
+
applyOverflow?: boolean;
|
|
46
|
+
}
|
|
47
|
+
export interface FitRenderedChartResult {
|
|
48
|
+
/** The frame was grown, so content that was being clipped is now painted. */
|
|
49
|
+
grew: boolean;
|
|
50
|
+
growFrom: number;
|
|
51
|
+
growTo: number;
|
|
52
|
+
growReason: string;
|
|
53
|
+
/** What the container's overflow was set to (or would have been). */
|
|
54
|
+
overflowX: "hidden" | "auto";
|
|
55
|
+
overflowY: "hidden" | "auto";
|
|
56
|
+
/** The post-grow reading, or null when nothing could be measured. */
|
|
57
|
+
reading: FitReading | null;
|
|
58
|
+
}
|
|
59
|
+
export declare function fitRenderedChart(container: HTMLElement | null | undefined, opts?: FitRenderedChartOptions): FitRenderedChartResult;
|
package/dist/types/host.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { type RenderOptions, type ViewStateProvider } from "./contract";
|
|
|
2
2
|
import { type ResolveOptionsInput } from "./defaults";
|
|
3
3
|
import { type MarkCensus } from "./blankRender";
|
|
4
4
|
import { type HitBandCensus } from "./hitBands";
|
|
5
|
+
import { type FitRenderedChartOptions, type FitRenderedChartResult } from "./fitDom";
|
|
5
6
|
export type RenderFn = (container: HTMLElement, data: any, options: RenderOptions) => void;
|
|
6
7
|
/**
|
|
7
8
|
* THE SESSION PROVIDER — what every host got hard-coded before contract 1.6.0, now named.
|
|
@@ -96,6 +97,24 @@ export interface ChartHostConfig {
|
|
|
96
97
|
/** The chart's marks are not tagged with the shared contract (a Vega lane names its own
|
|
97
98
|
* marks), so a mark count proves nothing and the blank verdict is suppressed. */
|
|
98
99
|
contractUntagged?: boolean;
|
|
100
|
+
/**
|
|
101
|
+
* DOES THE RENDERED CHART FIT ITS FRAME (2026-09-03)? Runs after every render.
|
|
102
|
+
*
|
|
103
|
+
* The outermost <svg> clips at its own viewport in every browser, so a chart that sets
|
|
104
|
+
* `svg height = options.height` and draws a taller body loses the overflow OUTRIGHT - the
|
|
105
|
+
* rows past the fold are never painted, and the container's scrollHeight agrees that
|
|
106
|
+
* everything fits, so nothing anywhere offers a scrollbar. This grows the frame to the ink
|
|
107
|
+
* the chart actually drew and THEN sets the container's overflow, in that order, because a
|
|
108
|
+
* scrollbar on an un-grown frame scrolls to nothing.
|
|
109
|
+
*
|
|
110
|
+
* DEFAULT ON, because the alternative is silent data loss and the pass only acts when ink is
|
|
111
|
+
* measurably outside the frame (8px of slack, and a 20x ceiling against a mark parked in the
|
|
112
|
+
* weeds). Pass `false` for a host that owns its own layout, or an options object to take the
|
|
113
|
+
* reading without touching the chart (`grow: false`, `applyOverflow: false`).
|
|
114
|
+
*/
|
|
115
|
+
fit?: boolean | FitRenderedChartOptions;
|
|
116
|
+
/** The fit result after each render - what was grown, and how the chart measured. */
|
|
117
|
+
onFit?: (result: FitRenderedChartResult) => void;
|
|
99
118
|
}
|
|
100
119
|
export interface ChartHost {
|
|
101
120
|
render(): void;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -18,3 +18,5 @@ export { computeSelectionCard, normaliseAggregation, type SelectionCardModel, ty
|
|
|
18
18
|
export { ensureCrossfilterHitTargets, type HitTargetReport } from "./hitTargets";
|
|
19
19
|
export { censusMarks, isBlankRender, blankRenderFlag, type MarkCensus, type BlankVerdictInput } from "./blankRender";
|
|
20
20
|
export { censusHitBands, hitBandFlag, MIN_HIT_BAND_PX, type HitBandCensus } from "./hitBands";
|
|
21
|
+
export { SCROLL_SLACK_PX, MAX_FRAME_GROW_FACTOR, PHANTOM_FRACTION, FIT_CONTENT_SELECTOR, needsScroll, contentExtentOf, scrollFitFor, planFrameGrow, isPhantomBox, type MeasuredBox, type ContentExtent, type ScrollFit, type FrameGrowPlan, } from "./fit";
|
|
22
|
+
export { fitRenderedChart, fitReadingFor, measureContainerBoxes, svgInkReach, ctmScaleOf, type FitReading, type InkReach, type FitRenderedChartOptions, type FitRenderedChartResult, } from "./fitDom";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bicharts/chart-host",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.64",
|
|
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",
|