@bicharts/chart-host 0.6.60 → 0.6.62
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.
|
@@ -2895,14 +2895,22 @@ function svgInkReach(svg) {
|
|
|
2895
2895
|
const box = svg.getBoundingClientRect();
|
|
2896
2896
|
if (!(box.width > 0) || !(box.height > 0)) return null;
|
|
2897
2897
|
const els = svg.querySelectorAll(FIT_CONTENT_SELECTOR);
|
|
2898
|
+
const clipOf = clipWindowReader(svg);
|
|
2898
2899
|
let right = -Infinity, bottom = -Infinity, seen = 0;
|
|
2899
2900
|
for (let i = 0; i < els.length && seen < FIT_WALK_CAP; i++) {
|
|
2900
2901
|
const r2 = els[i].getBoundingClientRect();
|
|
2901
2902
|
if (r2.width <= 0 && r2.height <= 0) continue;
|
|
2902
2903
|
if (isPhantomBox(r2.width, r2.height, box.width, box.height)) continue;
|
|
2904
|
+
let rr = r2.right, rb = r2.bottom;
|
|
2905
|
+
const clip = clipOf(els[i]);
|
|
2906
|
+
if (clip) {
|
|
2907
|
+
if (r2.left >= clip.right || r2.top >= clip.bottom) continue;
|
|
2908
|
+
rr = Math.min(rr, clip.right);
|
|
2909
|
+
rb = Math.min(rb, clip.bottom);
|
|
2910
|
+
}
|
|
2903
2911
|
seen++;
|
|
2904
|
-
if (
|
|
2905
|
-
if (
|
|
2912
|
+
if (rr > right) right = rr;
|
|
2913
|
+
if (rb > bottom) bottom = rb;
|
|
2906
2914
|
}
|
|
2907
2915
|
if (seen === 0) return null;
|
|
2908
2916
|
return { right: right - box.left, bottom: bottom - box.top };
|
|
@@ -2910,6 +2918,56 @@ function svgInkReach(svg) {
|
|
|
2910
2918
|
return null;
|
|
2911
2919
|
}
|
|
2912
2920
|
}
|
|
2921
|
+
function clipWindowReader(svg) {
|
|
2922
|
+
const own = /* @__PURE__ */ new Map();
|
|
2923
|
+
const byId = (id) => {
|
|
2924
|
+
try {
|
|
2925
|
+
return svg.querySelector(`clipPath[id="${id.replace(/"/g, '\\"')}"]`);
|
|
2926
|
+
} catch {
|
|
2927
|
+
return null;
|
|
2928
|
+
}
|
|
2929
|
+
};
|
|
2930
|
+
const windowOf = (el) => {
|
|
2931
|
+
if (own.has(el)) return own.get(el);
|
|
2932
|
+
let win = null;
|
|
2933
|
+
try {
|
|
2934
|
+
const ref = el.getAttribute("clip-path") || "";
|
|
2935
|
+
const m4 = /url\(\s*['"]?#([^'")\s]+)['"]?\s*\)/.exec(ref);
|
|
2936
|
+
const cp = m4 ? byId(m4[1]) : null;
|
|
2937
|
+
const ctm = cp ? el.getScreenCTM?.() : null;
|
|
2938
|
+
if (cp && ctm && cp.getAttribute("clipPathUnits") !== "objectBoundingBox") {
|
|
2939
|
+
let r2 = -Infinity, b4 = -Infinity;
|
|
2940
|
+
for (let i = 0; i < cp.children.length; i++) {
|
|
2941
|
+
const bb = cp.children[i].getBBox?.();
|
|
2942
|
+
if (!bb || !(bb.width > 0) || !(bb.height > 0)) continue;
|
|
2943
|
+
for (const [x3, y2] of [
|
|
2944
|
+
[bb.x, bb.y],
|
|
2945
|
+
[bb.x + bb.width, bb.y],
|
|
2946
|
+
[bb.x, bb.y + bb.height],
|
|
2947
|
+
[bb.x + bb.width, bb.y + bb.height]
|
|
2948
|
+
]) {
|
|
2949
|
+
const sx = ctm.a * x3 + ctm.c * y2 + ctm.e, sy = ctm.b * x3 + ctm.d * y2 + ctm.f;
|
|
2950
|
+
if (sx > r2) r2 = sx;
|
|
2951
|
+
if (sy > b4) b4 = sy;
|
|
2952
|
+
}
|
|
2953
|
+
}
|
|
2954
|
+
if (isFinite(r2) && isFinite(b4)) win = { right: r2, bottom: b4 };
|
|
2955
|
+
}
|
|
2956
|
+
} catch {
|
|
2957
|
+
win = null;
|
|
2958
|
+
}
|
|
2959
|
+
own.set(el, win);
|
|
2960
|
+
return win;
|
|
2961
|
+
};
|
|
2962
|
+
return (el) => {
|
|
2963
|
+
let acc = null;
|
|
2964
|
+
for (let a3 = el; a3 && a3 !== svg; a3 = a3.parentElement) {
|
|
2965
|
+
const w3 = windowOf(a3);
|
|
2966
|
+
if (w3) acc = acc ? { right: Math.min(acc.right, w3.right), bottom: Math.min(acc.bottom, w3.bottom) } : w3;
|
|
2967
|
+
}
|
|
2968
|
+
return acc;
|
|
2969
|
+
};
|
|
2970
|
+
}
|
|
2913
2971
|
function measureContainerBoxes(c2, inkOf) {
|
|
2914
2972
|
const boxes = [];
|
|
2915
2973
|
if (!c2 || !c2.children) return boxes;
|
|
@@ -3772,6 +3830,47 @@ function applyLabelContrast(container, opts = {}) {
|
|
|
3772
3830
|
}
|
|
3773
3831
|
}
|
|
3774
3832
|
|
|
3833
|
+
// src/idScope.ts
|
|
3834
|
+
var seq = 0;
|
|
3835
|
+
function newIdScope() {
|
|
3836
|
+
seq += 1;
|
|
3837
|
+
return "h" + seq;
|
|
3838
|
+
}
|
|
3839
|
+
var URL_REF = /url\(\s*(['"]?)#([^'")\s]+)\1\s*\)/g;
|
|
3840
|
+
function scopeChartIds(container, scope) {
|
|
3841
|
+
const report = { renamed: 0, collisions: [] };
|
|
3842
|
+
try {
|
|
3843
|
+
const tag = "--" + scope, doc = container.ownerDocument, map = /* @__PURE__ */ new Map();
|
|
3844
|
+
const refs = [], used = /* @__PURE__ */ new Set();
|
|
3845
|
+
container.querySelectorAll("*").forEach((el) => {
|
|
3846
|
+
for (const a3 of el.getAttributeNames()) {
|
|
3847
|
+
const v = el.getAttribute(a3);
|
|
3848
|
+
const href = (a3 === "href" || a3 === "xlink:href") && v[0] === "#";
|
|
3849
|
+
if (!href && v.indexOf("url(") < 0) continue;
|
|
3850
|
+
refs.push([el, a3, v]);
|
|
3851
|
+
if (href) used.add(v.slice(1));
|
|
3852
|
+
for (const m4 of v.matchAll(URL_REF)) used.add(m4[2]);
|
|
3853
|
+
}
|
|
3854
|
+
});
|
|
3855
|
+
container.querySelectorAll("[id]").forEach((el) => {
|
|
3856
|
+
const id = el.id;
|
|
3857
|
+
if (!used.has(id) || id.endsWith(tag) || id.startsWith("bic-")) return;
|
|
3858
|
+
const q2 = id.replace(/["\\]/g, "\\$&");
|
|
3859
|
+
if (doc && Array.from(doc.querySelectorAll(`[id="${q2}"],[id^="${q2}--h"]`)).some((o4) => !container.contains(o4))) {
|
|
3860
|
+
report.collisions.push(id);
|
|
3861
|
+
}
|
|
3862
|
+
map.set(id, el.id = id + tag);
|
|
3863
|
+
report.renamed++;
|
|
3864
|
+
});
|
|
3865
|
+
for (const [el, a3, v] of refs) {
|
|
3866
|
+
const nv = v[0] === "#" && map.has(v.slice(1)) ? "#" + map.get(v.slice(1)) : v.replace(URL_REF, (m4, q2, id) => map.has(id) ? `url(${q2}#${map.get(id)}${q2})` : m4);
|
|
3867
|
+
if (nv !== v) el.setAttribute(a3, nv);
|
|
3868
|
+
}
|
|
3869
|
+
} catch {
|
|
3870
|
+
}
|
|
3871
|
+
return report;
|
|
3872
|
+
}
|
|
3873
|
+
|
|
3775
3874
|
// src/invalidSentinel.ts
|
|
3776
3875
|
function isInvalidSentinelError(text2) {
|
|
3777
3876
|
if (!text2) return false;
|
|
@@ -4078,6 +4177,8 @@ function createChartHost(container, config) {
|
|
|
4078
4177
|
let destroyed = false;
|
|
4079
4178
|
let warnedGeo = false;
|
|
4080
4179
|
let warnedBlank = false;
|
|
4180
|
+
let warnedIds = false;
|
|
4181
|
+
const idScope = newIdScope();
|
|
4081
4182
|
let renderSeq = 0;
|
|
4082
4183
|
const annotationLayer = createAnnotationLayer(container, (a3) => config.onAnnotationClick?.(a3));
|
|
4083
4184
|
const drawAnnotations = () => annotationLayer.draw(
|
|
@@ -4249,6 +4350,20 @@ function createChartHost(container, config) {
|
|
|
4249
4350
|
}
|
|
4250
4351
|
};
|
|
4251
4352
|
const runPostRenderPasses = () => {
|
|
4353
|
+
try {
|
|
4354
|
+
const r2 = scopeChartIds(container, idScope);
|
|
4355
|
+
if (r2.collisions.length && !warnedIds) {
|
|
4356
|
+
warnedIds = true;
|
|
4357
|
+
try {
|
|
4358
|
+
(win?.console ?? console)?.warn(
|
|
4359
|
+
`[@bicharts/chart-host] this chart named ${r2.collisions.length} id(s) another element on the page already uses (${r2.collisions.slice(0, 5).join(", ")}). They were scoped to this chart, so its clip paths and gradients resolve to its own definitions.`
|
|
4360
|
+
);
|
|
4361
|
+
} catch {
|
|
4362
|
+
}
|
|
4363
|
+
}
|
|
4364
|
+
config.onIdScope?.(r2);
|
|
4365
|
+
} catch {
|
|
4366
|
+
}
|
|
4252
4367
|
if (labelContrastOpts !== false) {
|
|
4253
4368
|
try {
|
|
4254
4369
|
const lc = labelContrastOpts === true ? {} : { ...labelContrastOpts };
|
|
@@ -4328,7 +4443,7 @@ function createChartHost(container, config) {
|
|
|
4328
4443
|
const host = {
|
|
4329
4444
|
render() {
|
|
4330
4445
|
if (destroyed) return;
|
|
4331
|
-
const
|
|
4446
|
+
const seq2 = ++renderSeq;
|
|
4332
4447
|
stopAnim();
|
|
4333
4448
|
container.innerHTML = "";
|
|
4334
4449
|
if (!renderFn) {
|
|
@@ -4381,7 +4496,7 @@ function createChartHost(container, config) {
|
|
|
4381
4496
|
};
|
|
4382
4497
|
Promise.resolve(outcome).then(
|
|
4383
4498
|
() => done(() => {
|
|
4384
|
-
if (destroyed ||
|
|
4499
|
+
if (destroyed || seq2 !== renderSeq) {
|
|
4385
4500
|
resolve();
|
|
4386
4501
|
return;
|
|
4387
4502
|
}
|
|
@@ -4389,7 +4504,7 @@ function createChartHost(container, config) {
|
|
|
4389
4504
|
resolve();
|
|
4390
4505
|
}),
|
|
4391
4506
|
(err) => done(() => {
|
|
4392
|
-
if (destroyed ||
|
|
4507
|
+
if (destroyed || seq2 !== renderSeq) {
|
|
4393
4508
|
resolve();
|
|
4394
4509
|
return;
|
|
4395
4510
|
}
|
|
@@ -4715,6 +4830,8 @@ export {
|
|
|
4715
4830
|
LABEL_CONTRAST_CAP,
|
|
4716
4831
|
LABEL_PILL_CLASS,
|
|
4717
4832
|
applyLabelContrast,
|
|
4833
|
+
newIdScope,
|
|
4834
|
+
scopeChartIds,
|
|
4718
4835
|
isInvalidSentinelError,
|
|
4719
4836
|
invalidSentinelReason,
|
|
4720
4837
|
ANNOTATION_LAYER_CLASS,
|
package/dist/index.mjs
CHANGED
|
@@ -91,6 +91,7 @@ import {
|
|
|
91
91
|
measureContainerBoxes,
|
|
92
92
|
minContrastOver,
|
|
93
93
|
needsScroll,
|
|
94
|
+
newIdScope,
|
|
94
95
|
nextSelection,
|
|
95
96
|
noopViewStateProvider,
|
|
96
97
|
o,
|
|
@@ -108,6 +109,7 @@ import {
|
|
|
108
109
|
resolveAutoColorScale,
|
|
109
110
|
resolveOptions,
|
|
110
111
|
rowIdxsFromMark,
|
|
112
|
+
scopeChartIds,
|
|
111
113
|
scrollFitFor,
|
|
112
114
|
sessionViewStateProvider,
|
|
113
115
|
straddleSampleGrid,
|
|
@@ -120,7 +122,7 @@ import {
|
|
|
120
122
|
toSourceRows,
|
|
121
123
|
unpinScrolledAxis,
|
|
122
124
|
valuePlacementFlag
|
|
123
|
-
} from "./chunk-
|
|
125
|
+
} from "./chunk-ZLJM3EIP.mjs";
|
|
124
126
|
import "./chunk-A2GMXZP7.mjs";
|
|
125
127
|
import {
|
|
126
128
|
ACTIVE_TICK_CLASS,
|
|
@@ -3008,6 +3010,7 @@ export {
|
|
|
3008
3010
|
minContrastOver,
|
|
3009
3011
|
mountProductFeedbackBox,
|
|
3010
3012
|
needsScroll,
|
|
3013
|
+
newIdScope,
|
|
3011
3014
|
newQualifyGroupState,
|
|
3012
3015
|
newQualifyRefusalGroupState,
|
|
3013
3016
|
nextSelection,
|
|
@@ -3058,6 +3061,7 @@ export {
|
|
|
3058
3061
|
resolveOptions,
|
|
3059
3062
|
rowIdxsFromMark,
|
|
3060
3063
|
sameMarker,
|
|
3064
|
+
scopeChartIds,
|
|
3061
3065
|
scrollFitFor,
|
|
3062
3066
|
servedCorrelationProves,
|
|
3063
3067
|
sessionViewStateProvider,
|
package/dist/react.mjs
CHANGED
package/dist/types/host.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { type ColourSpreadCensus } from "./colourSpread";
|
|
|
6
6
|
import { type ValuePlacementCensus } from "./valuePlacement";
|
|
7
7
|
import { type FitRenderedChartOptions, type FitRenderedChartResult } from "./fitDom";
|
|
8
8
|
import { type LabelContrastOptions, type LabelContrastReport } from "./labelContrastDom";
|
|
9
|
+
import { type IdScopeReport } from "./idScope";
|
|
9
10
|
import { type MarkAnnotation, type AnnotationReport } from "./annotations";
|
|
10
11
|
export type RenderFn = (container: HTMLElement, data: any, options: RenderOptions) => void;
|
|
11
12
|
/**
|
|
@@ -166,6 +167,11 @@ export interface ChartHostConfig {
|
|
|
166
167
|
labelContrast?: boolean | LabelContrastOptions;
|
|
167
168
|
/** What the label-contrast pass did after each render - counts, so a host can log them. */
|
|
168
169
|
onLabelContrast?: (report: LabelContrastReport) => void;
|
|
170
|
+
/**
|
|
171
|
+
* What the id-scoping pass did after each render: how many of the chart's ids got this host's suffix, and which
|
|
172
|
+
* of them also existed elsewhere in the page (a clash it prevented - two same-size charts naming one clip path).
|
|
173
|
+
*/
|
|
174
|
+
onIdScope?: (report: IdScopeReport) => void;
|
|
169
175
|
/**
|
|
170
176
|
* THE CHART SAID THE DATA LACKS SOMETHING IT IS BUILT ON. Generated code has one sanctioned
|
|
171
177
|
* hard stop at runtime: a column the chart needs is gone from `data.columns`, and it throws
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** A fresh suffix for one chart host. Unique within the page for the page's lifetime. */
|
|
2
|
+
export declare function newIdScope(): string;
|
|
3
|
+
export interface IdScopeReport {
|
|
4
|
+
/** How many ids were given the suffix. */
|
|
5
|
+
renamed: number;
|
|
6
|
+
/** Ids that also existed outside this chart when the pass ran - each one a clash this pass prevented. */
|
|
7
|
+
collisions: string[];
|
|
8
|
+
}
|
|
9
|
+
export declare function scopeChartIds(container: Element, scope: string): IdScopeReport;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -27,6 +27,7 @@ export { formatSourceDate, formatSourceDateFor, isExcelDateFormat, type SourceFo
|
|
|
27
27
|
export { composeSummaryText } from "./summaryText";
|
|
28
28
|
export { ensureCrossfilterHitTargets, type HitTargetReport } from "./hitTargets";
|
|
29
29
|
export { applyLabelContrast, LABEL_CONTRAST_DONE_ATTR, LABEL_CONTRAST_CAP, LABEL_PILL_CLASS, type LabelContrastOptions, type LabelContrastReport, } from "./labelContrastDom";
|
|
30
|
+
export { scopeChartIds, newIdScope, type IdScopeReport } from "./idScope";
|
|
30
31
|
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, straddles, straddleSampleGrid, minContrastOver, pickColorOver, STRADDLE_COLS, STRADDLE_ROWS, STRADDLE_MIN_SHARE, STRADDLE_TARGET_CONTRAST, STRADDLE_PILL_ALPHA, type LabelDecision, } from "./labelContrast";
|
|
31
32
|
export { censusMarks, isBlankRender, blankRenderFlag, type MarkCensus, type BlankVerdictInput } from "./blankRender";
|
|
32
33
|
export { isInvalidSentinelError, invalidSentinelReason } from "./invalidSentinel";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bicharts/chart-host",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.62",
|
|
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",
|