@bicharts/chart-host 0.6.85 → 0.6.86
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/README.md +21 -0
- package/dist/{chunk-CDWNZHIN.mjs → chunk-MXL5SK7X.mjs} +6 -0
- package/dist/index.mjs +1 -1
- package/dist/legend-fit.mjs +33 -1
- package/dist/react.mjs +5 -3
- package/dist/types/host.d.ts +9 -0
- package/dist/types/legendFit/index.d.ts +1 -0
- package/dist/types/legendFit/pass.d.ts +29 -0
- package/dist/types/react.d.ts +7 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -394,6 +394,27 @@ a throw must never fail a chart that already drew. The geometry behind each phas
|
|
|
394
394
|
`planAxisTickThin`, `planFrameFit`, ...) is exported as plain functions over boxes, for a host that
|
|
395
395
|
measures on its own terms.
|
|
396
396
|
|
|
397
|
+
**Running them after every render.** `createLegendFitPass()` is both passes in the order that works
|
|
398
|
+
(the legend first, then the frame fit, which measures what the legend pass left), with nothing to log
|
|
399
|
+
and no policy. Hand it to the host as `layoutPass`, and the host runs it after every render, re-render
|
|
400
|
+
and async settle, after the label colors are fixed and before anything measures the marks or the
|
|
401
|
+
frame:
|
|
402
|
+
|
|
403
|
+
```ts
|
|
404
|
+
import { createChartHost } from "@bicharts/chart-host";
|
|
405
|
+
import { createLegendFitPass } from "@bicharts/chart-host/legend-fit";
|
|
406
|
+
|
|
407
|
+
const layoutPass = createLegendFitPass({
|
|
408
|
+
legend: true, // or "narrow" to act only on large overlaps, or false
|
|
409
|
+
frameFit: true,
|
|
410
|
+
onReport: r => console.debug(r.legend, r.frame), // what each run did; log what you like
|
|
411
|
+
});
|
|
412
|
+
const host = createChartHost(el, { code, data, d3, layoutPass });
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
In React it is the `layoutPass` prop of `<BicChart>`. Build the pass once, at module scope or in a
|
|
416
|
+
`useMemo`: a new function on every render is a new host config, and the chart is rebuilt.
|
|
417
|
+
|
|
397
418
|
## Architecture — three layers, and why it matters if you contribute
|
|
398
419
|
|
|
399
420
|
Read this before adding code. The package has **three** concerns, and they are independent.
|
|
@@ -2312,6 +2312,12 @@ function createChartHost(container, config) {
|
|
|
2312
2312
|
} catch {
|
|
2313
2313
|
}
|
|
2314
2314
|
}
|
|
2315
|
+
if (config.layoutPass) {
|
|
2316
|
+
try {
|
|
2317
|
+
config.layoutPass(container);
|
|
2318
|
+
} catch {
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2315
2321
|
ensureCrossfilterHitTargets(container, doc);
|
|
2316
2322
|
paintSelection();
|
|
2317
2323
|
try {
|
package/dist/index.mjs
CHANGED
package/dist/legend-fit.mjs
CHANGED
|
@@ -1,6 +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
|
-
chartSvgOf
|
|
3
|
+
chartSvgOf,
|
|
4
|
+
tableHostOf
|
|
4
5
|
} from "./chunk-ZGQURBRJ.mjs";
|
|
5
6
|
import {
|
|
6
7
|
FIT_CONTENT_SELECTOR,
|
|
@@ -1554,6 +1555,36 @@ function reconcileBottomTextPhase(svg) {
|
|
|
1554
1555
|
}
|
|
1555
1556
|
return { applied: true, bt: `moved${moved} dy${Math.round(maxDy)}` };
|
|
1556
1557
|
}
|
|
1558
|
+
|
|
1559
|
+
// src/legendFit/pass.ts
|
|
1560
|
+
var NARROW = { minOverlap: 16, maxGutterFraction: 0.2 };
|
|
1561
|
+
var errorText = (e) => String(e?.message ?? e).slice(0, 120);
|
|
1562
|
+
function createLegendFitPass(opts = {}) {
|
|
1563
|
+
const legendOn = opts.legend ?? true;
|
|
1564
|
+
const frameOn = opts.frameFit ?? true;
|
|
1565
|
+
return (container) => {
|
|
1566
|
+
const report = { legend: { skipped: "off" }, frame: { skipped: "off" } };
|
|
1567
|
+
if (legendOn !== false) {
|
|
1568
|
+
try {
|
|
1569
|
+
const svg = chartSvgOf(container);
|
|
1570
|
+
report.legend = svg ? reconcileLegendInSvg(svg, legendOn === "narrow" ? NARROW : {}) : { skipped: "no-svg" };
|
|
1571
|
+
} catch (e) {
|
|
1572
|
+
report.legend = { error: errorText(e) };
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
if (frameOn) {
|
|
1576
|
+
try {
|
|
1577
|
+
report.frame = tableHostOf(container) ? { skipped: "table-lane" } : fitTextToFrame(container) ?? { skipped: "nothing-to-measure" };
|
|
1578
|
+
} catch (e) {
|
|
1579
|
+
report.frame = { error: errorText(e) };
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
try {
|
|
1583
|
+
opts.onReport?.(report);
|
|
1584
|
+
} catch {
|
|
1585
|
+
}
|
|
1586
|
+
};
|
|
1587
|
+
}
|
|
1557
1588
|
export {
|
|
1558
1589
|
AXIS_THIN_GAP,
|
|
1559
1590
|
AXIS_THIN_MIN_TICKS,
|
|
@@ -1583,6 +1614,7 @@ export {
|
|
|
1583
1614
|
colorbarProbePoints,
|
|
1584
1615
|
colorbarReconcileSide,
|
|
1585
1616
|
colorbarSitsOnMarks,
|
|
1617
|
+
createLegendFitPass,
|
|
1586
1618
|
cutToFit,
|
|
1587
1619
|
findColorbarFurniture,
|
|
1588
1620
|
findSignatureLegendGroups,
|
package/dist/react.mjs
CHANGED
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
loadGeo,
|
|
23
23
|
syncMemberSelection,
|
|
24
24
|
toSourceRows
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-MXL5SK7X.mjs";
|
|
26
26
|
import "./chunk-QIRLWJBI.mjs";
|
|
27
27
|
import "./chunk-2ZUUIZWL.mjs";
|
|
28
28
|
import "./chunk-7HKVV5T5.mjs";
|
|
@@ -69,6 +69,7 @@ function BicChart(props) {
|
|
|
69
69
|
viewState,
|
|
70
70
|
labelContrast,
|
|
71
71
|
onLabelContrast,
|
|
72
|
+
layoutPass,
|
|
72
73
|
onInvalidSentinel,
|
|
73
74
|
id,
|
|
74
75
|
filteredBy,
|
|
@@ -150,6 +151,7 @@ function BicChart(props) {
|
|
|
150
151
|
geoKind,
|
|
151
152
|
viewState,
|
|
152
153
|
labelContrast,
|
|
154
|
+
layoutPass,
|
|
153
155
|
onLabelContrast: (r) => onLabelContrastRef.current?.(r),
|
|
154
156
|
// A GETTER, not a wrapper: the host decides whether to
|
|
155
157
|
// swallow the sentinel throw by whether this is SET, so
|
|
@@ -181,7 +183,7 @@ function BicChart(props) {
|
|
|
181
183
|
host.destroy();
|
|
182
184
|
hostRef.current = null;
|
|
183
185
|
};
|
|
184
|
-
}, [code, renderFn, d3, geoKind, labelContrast, controls]);
|
|
186
|
+
}, [code, renderFn, d3, geoKind, labelContrast, layoutPass, controls]);
|
|
185
187
|
useLayoutEffect(() => {
|
|
186
188
|
const host = hostRef.current;
|
|
187
189
|
if (!host || !selects) return;
|
|
@@ -207,7 +209,7 @@ function BicChart(props) {
|
|
|
207
209
|
b.detach();
|
|
208
210
|
if (bindingRef.current === b) bindingRef.current = null;
|
|
209
211
|
};
|
|
210
|
-
}, [selects, code, renderFn, d3, geoKind, labelContrast]);
|
|
212
|
+
}, [selects, code, renderFn, d3, geoKind, labelContrast, layoutPass]);
|
|
211
213
|
useEffect(() => {
|
|
212
214
|
if (!hostRef.current || !options) return;
|
|
213
215
|
if (builtWithRef.current?.optKey === optKey) return;
|
package/dist/types/host.d.ts
CHANGED
|
@@ -190,6 +190,15 @@ export interface ChartHostConfig {
|
|
|
190
190
|
labelContrast?: boolean | LabelContrastOptions;
|
|
191
191
|
/** What the label-contrast pass did after each render - counts, so a host can log them. */
|
|
192
192
|
onLabelContrast?: (report: LabelContrastReport) => void;
|
|
193
|
+
/**
|
|
194
|
+
* A LAYOUT PASS the host brings, run after every render (both the synchronous and the promise lane, and every
|
|
195
|
+
* setOptions / setData re-render) AFTER the label-contrast pass and BEFORE the hit-target heal, the selection
|
|
196
|
+
* repaint, the censuses and the fit - the order a pass that moves the legend or grows the viewBox needs, because
|
|
197
|
+
* the fit must measure the final layout. Absent by default: the main entry carries no such pass. Build one with
|
|
198
|
+
* createLegendFitPass() from "@bicharts/chart-host/legend-fit". It must not throw (a throw is swallowed and the
|
|
199
|
+
* render stands); it reports through its own callback, never through this host.
|
|
200
|
+
*/
|
|
201
|
+
layoutPass?: (container: HTMLElement) => void;
|
|
193
202
|
/**
|
|
194
203
|
* What the id-scoping pass did after each render: how many of the chart's ids got this host's suffix, and which
|
|
195
204
|
* of them also existed elsewhere in the page (a clash it prevented - two same-size charts naming one clip path).
|
|
@@ -2,3 +2,4 @@ export { MIN_OVERLAP, MAX_GUTTER_FRACTION, COLORBAR_ON_PLOT_COVER, MAX_FIT_FRACT
|
|
|
2
2
|
export { FRAME_PAD, MAX_EXTEND_FRACTION, FIT_TYPE_FLOOR, FIT_SHRINK_FLOOR, planFrameFit, viewBoxAttr, labelRoom, labelFitDecision, cutToFit, type FrameFitPlan, type TextAnchor, type LabelFitDecision, } from "./frameFit";
|
|
3
3
|
export { reconcileLegendInSvg, findSignatureLegendGroups, findColorbarFurniture, smallestRenderedTextPx, styleDeclaresScroll, type LegendReconcileResult, type AxisThinReport, } from "./legendReconcileDom";
|
|
4
4
|
export { FRAME_FIT_TEXT_CAP, fitTextToFrame, fitDeferredLabels, fitLabelToRoom, isClippedOrScrolled, type FrameFitOutcome, type DeferredLabelFitReport, } from "./frameFitDom";
|
|
5
|
+
export { createLegendFitPass, type LegendFitPassOptions, type LegendFitReport, type LegendFitStrictness } from "./pass";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type LegendReconcileResult } from "./legendReconcileDom";
|
|
2
|
+
import { type FrameFitOutcome } from "./frameFitDom";
|
|
3
|
+
/** "smart" acts on smaller overlaps with larger moves; "narrow" only on large, unambiguous ones. */
|
|
4
|
+
export type LegendFitStrictness = "smart" | "narrow";
|
|
5
|
+
export interface LegendFitPassOptions {
|
|
6
|
+
/** Run the legend reconcile (legend slide, colorbar, axis and label-row thinning, bottom-text push, content fit). Default true. */
|
|
7
|
+
legend?: boolean | LegendFitStrictness;
|
|
8
|
+
/** Run the frame fit (viewBox growth for text just past the frame, then shrink-and-cut of the labels it deferred). Default true. */
|
|
9
|
+
frameFit?: boolean;
|
|
10
|
+
/** What each run did. Counts and reasons only; the host decides what to log. */
|
|
11
|
+
onReport?: (report: LegendFitReport) => void;
|
|
12
|
+
}
|
|
13
|
+
export interface LegendFitReport {
|
|
14
|
+
legend: LegendReconcileResult | {
|
|
15
|
+
skipped: "off" | "no-svg";
|
|
16
|
+
} | {
|
|
17
|
+
error: string;
|
|
18
|
+
};
|
|
19
|
+
frame: FrameFitOutcome | {
|
|
20
|
+
skipped: "off" | "table-lane" | "nothing-to-measure";
|
|
21
|
+
} | {
|
|
22
|
+
error: string;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Build the pass once (module scope, or a memo) and hand it to createChartHost as `layoutPass`: a new function
|
|
27
|
+
* on every render is a new host config. Each run never throws; a pass that fails reports `{ error }`.
|
|
28
|
+
*/
|
|
29
|
+
export declare function createLegendFitPass(opts?: LegendFitPassOptions): (container: HTMLElement) => void;
|
package/dist/types/react.d.ts
CHANGED
|
@@ -41,6 +41,13 @@ export interface BicChartProps {
|
|
|
41
41
|
labelContrast?: ChartHostConfig["labelContrast"];
|
|
42
42
|
/** What the label-contrast pass did after each render - counts, for a page that logs. */
|
|
43
43
|
onLabelContrast?: ChartHostConfig["onLabelContrast"];
|
|
44
|
+
/**
|
|
45
|
+
* A LAYOUT PASS run after every render, after label contrast and before the fit - e.g.
|
|
46
|
+
* `createLegendFitPass()` from "@bicharts/chart-host/legend-fit", which slides a legend clear of
|
|
47
|
+
* the plot and fits text to the frame. Absent by default. Build it ONCE (module scope or useMemo):
|
|
48
|
+
* a new function each render rebuilds the host. See createChartHost's `layoutPass`.
|
|
49
|
+
*/
|
|
50
|
+
layoutPass?: ChartHostConfig["layoutPass"];
|
|
44
51
|
/**
|
|
45
52
|
* THE CHART STOPPED BECAUSE THE DATA LACKS A COLUMN IT IS BUILT ON. Supplied: the chart's
|
|
46
53
|
* container is left empty and this is called with the chart's reason, instead of the throw
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bicharts/chart-host",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.86",
|
|
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",
|