@bicharts/chart-host 0.6.63 → 0.6.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.
|
@@ -567,15 +567,15 @@ function decideLabelColor(textColor, fill, fillOpacity, pageBg, minContrast = MI
|
|
|
567
567
|
return useLight ? { fix: true, color: LIGHT_TEXT, reason: "fixed-light" } : { fix: true, color: DARK_TEXT, reason: "fixed-dark" };
|
|
568
568
|
}
|
|
569
569
|
var GLYPH_SAMPLE_N = 3;
|
|
570
|
-
function glyphSampleGrid(
|
|
570
|
+
function glyphSampleGrid(box2, n = GLYPH_SAMPLE_N) {
|
|
571
571
|
const out = [];
|
|
572
|
-
if (!
|
|
572
|
+
if (!box2 || !(box2.width > 0) || !(box2.height > 0) || !(n >= 1)) return out;
|
|
573
573
|
const k3 = Math.floor(n);
|
|
574
574
|
for (let i = 0; i < k3; i++) {
|
|
575
575
|
for (let j4 = 0; j4 < k3; j4++) {
|
|
576
576
|
out.push({
|
|
577
|
-
x:
|
|
578
|
-
y:
|
|
577
|
+
x: box2.left + box2.width * (i + 0.5) / k3,
|
|
578
|
+
y: box2.top + box2.height * (j4 + 0.5) / k3
|
|
579
579
|
});
|
|
580
580
|
}
|
|
581
581
|
}
|
|
@@ -586,14 +586,14 @@ var STRADDLE_ROWS = 2;
|
|
|
586
586
|
var STRADDLE_MIN_SHARE = 0.125;
|
|
587
587
|
var STRADDLE_TARGET_CONTRAST = 4.5;
|
|
588
588
|
var STRADDLE_PILL_ALPHA = 0.9;
|
|
589
|
-
function straddleSampleGrid(
|
|
589
|
+
function straddleSampleGrid(box2) {
|
|
590
590
|
const out = [];
|
|
591
|
-
if (!
|
|
591
|
+
if (!box2 || !(box2.width > 0) || !(box2.height > 0)) return out;
|
|
592
592
|
for (let i = 0; i < STRADDLE_COLS; i++) {
|
|
593
593
|
for (let j4 = 0; j4 < STRADDLE_ROWS; j4++) {
|
|
594
594
|
out.push({
|
|
595
|
-
x:
|
|
596
|
-
y:
|
|
595
|
+
x: box2.left + box2.width * (i + 0.5) / STRADDLE_COLS,
|
|
596
|
+
y: box2.top + box2.height * (j4 + 0.5) / STRADDLE_ROWS
|
|
597
597
|
});
|
|
598
598
|
}
|
|
599
599
|
}
|
|
@@ -2451,9 +2451,9 @@ function createMarkResolver(env) {
|
|
|
2451
2451
|
const r2 = el.getBoundingClientRect ? el.getBoundingClientRect() : null;
|
|
2452
2452
|
if (!r2 || r2.width <= 0 || r2.height <= 0) continue;
|
|
2453
2453
|
if (x3 < r2.left || x3 > r2.right || y2 < r2.top || y2 > r2.bottom) continue;
|
|
2454
|
-
const
|
|
2455
|
-
if (
|
|
2456
|
-
bestArea =
|
|
2454
|
+
const area2 = r2.width * r2.height;
|
|
2455
|
+
if (area2 < bestArea) {
|
|
2456
|
+
bestArea = area2;
|
|
2457
2457
|
best = el;
|
|
2458
2458
|
}
|
|
2459
2459
|
}
|
|
@@ -3062,15 +3062,15 @@ function ctmScaleOf(m4) {
|
|
|
3062
3062
|
}
|
|
3063
3063
|
function svgInkReach(svg) {
|
|
3064
3064
|
try {
|
|
3065
|
-
const
|
|
3066
|
-
if (!(
|
|
3065
|
+
const box2 = svg.getBoundingClientRect();
|
|
3066
|
+
if (!(box2.width > 0) || !(box2.height > 0)) return null;
|
|
3067
3067
|
const els = svg.querySelectorAll(FIT_CONTENT_SELECTOR);
|
|
3068
3068
|
const clipOf = clipWindowReader(svg);
|
|
3069
3069
|
let right = -Infinity, bottom = -Infinity, seen = 0;
|
|
3070
3070
|
for (let i = 0; i < els.length && seen < FIT_WALK_CAP; i++) {
|
|
3071
3071
|
const r2 = els[i].getBoundingClientRect();
|
|
3072
3072
|
if (r2.width <= 0 && r2.height <= 0) continue;
|
|
3073
|
-
if (isPhantomBox(r2.width, r2.height,
|
|
3073
|
+
if (isPhantomBox(r2.width, r2.height, box2.width, box2.height)) continue;
|
|
3074
3074
|
let rr = r2.right, rb = r2.bottom;
|
|
3075
3075
|
const clip = clipOf(els[i]);
|
|
3076
3076
|
if (clip) {
|
|
@@ -3083,51 +3083,39 @@ function svgInkReach(svg) {
|
|
|
3083
3083
|
if (rb > bottom) bottom = rb;
|
|
3084
3084
|
}
|
|
3085
3085
|
if (seen === 0) return null;
|
|
3086
|
-
return { right: right -
|
|
3086
|
+
return { right: right - box2.left, bottom: bottom - box2.top };
|
|
3087
|
+
} catch {
|
|
3088
|
+
return null;
|
|
3089
|
+
}
|
|
3090
|
+
}
|
|
3091
|
+
function clipWindowOf(el) {
|
|
3092
|
+
try {
|
|
3093
|
+
const m4 = /url\(\s*['"]?#([^'")\s]+)['"]?\s*\)/.exec(el.getAttribute("clip-path") || "");
|
|
3094
|
+
const cp = m4 ? (el.closest("svg") ?? el.ownerDocument).querySelector(`clipPath[id="${m4[1].replace(/"/g, '\\"')}"]`) : null;
|
|
3095
|
+
const ctm = cp ? el.getScreenCTM?.() : null;
|
|
3096
|
+
if (!cp || !ctm || cp.getAttribute("clipPathUnits") === "objectBoundingBox") return null;
|
|
3097
|
+
let l2 = Infinity, t = Infinity, r2 = -Infinity, b4 = -Infinity;
|
|
3098
|
+
for (let i = 0; i < cp.children.length; i++) {
|
|
3099
|
+
const bb = cp.children[i].getBBox?.();
|
|
3100
|
+
if (!bb || !(bb.width > 0) || !(bb.height > 0)) continue;
|
|
3101
|
+
for (const [x3, y2] of [[bb.x, bb.y], [bb.x + bb.width, bb.y], [bb.x, bb.y + bb.height], [bb.x + bb.width, bb.y + bb.height]]) {
|
|
3102
|
+
const sx = ctm.a * x3 + ctm.c * y2 + ctm.e, sy = ctm.b * x3 + ctm.d * y2 + ctm.f;
|
|
3103
|
+
l2 = Math.min(l2, sx);
|
|
3104
|
+
t = Math.min(t, sy);
|
|
3105
|
+
r2 = Math.max(r2, sx);
|
|
3106
|
+
b4 = Math.max(b4, sy);
|
|
3107
|
+
}
|
|
3108
|
+
}
|
|
3109
|
+
return isFinite(r2) && isFinite(b4) ? { left: l2, top: t, right: r2, bottom: b4 } : null;
|
|
3087
3110
|
} catch {
|
|
3088
3111
|
return null;
|
|
3089
3112
|
}
|
|
3090
3113
|
}
|
|
3091
3114
|
function clipWindowReader(svg) {
|
|
3092
3115
|
const own = /* @__PURE__ */ new Map();
|
|
3093
|
-
const byId = (id) => {
|
|
3094
|
-
try {
|
|
3095
|
-
return svg.querySelector(`clipPath[id="${id.replace(/"/g, '\\"')}"]`);
|
|
3096
|
-
} catch {
|
|
3097
|
-
return null;
|
|
3098
|
-
}
|
|
3099
|
-
};
|
|
3100
3116
|
const windowOf = (el) => {
|
|
3101
|
-
if (own.has(el))
|
|
3102
|
-
|
|
3103
|
-
try {
|
|
3104
|
-
const ref = el.getAttribute("clip-path") || "";
|
|
3105
|
-
const m4 = /url\(\s*['"]?#([^'")\s]+)['"]?\s*\)/.exec(ref);
|
|
3106
|
-
const cp = m4 ? byId(m4[1]) : null;
|
|
3107
|
-
const ctm = cp ? el.getScreenCTM?.() : null;
|
|
3108
|
-
if (cp && ctm && cp.getAttribute("clipPathUnits") !== "objectBoundingBox") {
|
|
3109
|
-
let r2 = -Infinity, b4 = -Infinity;
|
|
3110
|
-
for (let i = 0; i < cp.children.length; i++) {
|
|
3111
|
-
const bb = cp.children[i].getBBox?.();
|
|
3112
|
-
if (!bb || !(bb.width > 0) || !(bb.height > 0)) continue;
|
|
3113
|
-
for (const [x3, y2] of [
|
|
3114
|
-
[bb.x, bb.y],
|
|
3115
|
-
[bb.x + bb.width, bb.y],
|
|
3116
|
-
[bb.x, bb.y + bb.height],
|
|
3117
|
-
[bb.x + bb.width, bb.y + bb.height]
|
|
3118
|
-
]) {
|
|
3119
|
-
const sx = ctm.a * x3 + ctm.c * y2 + ctm.e, sy = ctm.b * x3 + ctm.d * y2 + ctm.f;
|
|
3120
|
-
if (sx > r2) r2 = sx;
|
|
3121
|
-
if (sy > b4) b4 = sy;
|
|
3122
|
-
}
|
|
3123
|
-
}
|
|
3124
|
-
if (isFinite(r2) && isFinite(b4)) win = { right: r2, bottom: b4 };
|
|
3125
|
-
}
|
|
3126
|
-
} catch {
|
|
3127
|
-
win = null;
|
|
3128
|
-
}
|
|
3129
|
-
own.set(el, win);
|
|
3130
|
-
return win;
|
|
3117
|
+
if (!own.has(el)) own.set(el, clipWindowOf(el));
|
|
3118
|
+
return own.get(el);
|
|
3131
3119
|
};
|
|
3132
3120
|
return (el) => {
|
|
3133
3121
|
let acc = null;
|
|
@@ -3895,7 +3883,34 @@ function invalidSentinelReason(text2, max = 240) {
|
|
|
3895
3883
|
// src/annotations.ts
|
|
3896
3884
|
var ANNOTATION_LAYER_CLASS = "bic-annotations";
|
|
3897
3885
|
var ANNOTATION_CLASS = "bic-annotation";
|
|
3886
|
+
var ZOOM_PAD_CLASS = "llm-zoom-pad";
|
|
3898
3887
|
var text = (v) => v == null ? "" : String(v).trim();
|
|
3888
|
+
var FREE = { l: -Infinity, t: -Infinity, r: Infinity, b: Infinity };
|
|
3889
|
+
var box = (r2) => ({ l: r2.left, t: r2.top, r: r2.right, b: r2.bottom });
|
|
3890
|
+
var area = (b4) => (b4.r - b4.l) * (b4.b - b4.t);
|
|
3891
|
+
var cut = (a3, b4) => {
|
|
3892
|
+
if (!a3) return null;
|
|
3893
|
+
const c2 = { l: Math.max(a3.l, b4.l), t: Math.max(a3.t, b4.t), r: Math.min(a3.r, b4.r), b: Math.min(a3.b, b4.b) };
|
|
3894
|
+
return c2.r > c2.l && c2.b > c2.t ? c2 : null;
|
|
3895
|
+
};
|
|
3896
|
+
function shapeTest(mark) {
|
|
3897
|
+
const g5 = mark;
|
|
3898
|
+
let inv = null;
|
|
3899
|
+
try {
|
|
3900
|
+
inv = g5?.isPointInFill ? g5.getScreenCTM?.()?.inverse() ?? null : null;
|
|
3901
|
+
} catch {
|
|
3902
|
+
inv = null;
|
|
3903
|
+
}
|
|
3904
|
+
if (!g5 || !inv) return () => true;
|
|
3905
|
+
const m4 = inv;
|
|
3906
|
+
return (x3, y2) => {
|
|
3907
|
+
try {
|
|
3908
|
+
return g5.isPointInFill({ x: m4.a * x3 + m4.c * y2 + m4.e, y: m4.b * x3 + m4.d * y2 + m4.f });
|
|
3909
|
+
} catch {
|
|
3910
|
+
return true;
|
|
3911
|
+
}
|
|
3912
|
+
};
|
|
3913
|
+
}
|
|
3899
3914
|
var parseIdxs = (s2) => (s2 || "").split(",").map((x3) => parseInt(x3, 10)).filter((n) => Number.isFinite(n));
|
|
3900
3915
|
function createAnnotationLayer(container, onClick) {
|
|
3901
3916
|
let list = [];
|
|
@@ -3952,6 +3967,37 @@ function createAnnotationLayer(container, onClick) {
|
|
|
3952
3967
|
else marksByRow.set(r2, [m4]);
|
|
3953
3968
|
}
|
|
3954
3969
|
const origin = container.getBoundingClientRect();
|
|
3970
|
+
const open = origin.width > 0 && origin.height > 0 ? box(origin) : FREE;
|
|
3971
|
+
const clipOf = /* @__PURE__ */ new Map();
|
|
3972
|
+
const viewOf = (m4) => {
|
|
3973
|
+
let v = open;
|
|
3974
|
+
const svg = m4.closest("svg");
|
|
3975
|
+
if (svg) {
|
|
3976
|
+
const s2 = svg.getBoundingClientRect();
|
|
3977
|
+
if (s2.width > 0 && s2.height > 0) v = cut(v, box(s2));
|
|
3978
|
+
}
|
|
3979
|
+
for (let el = m4.parentElement; v && el && el !== svg && el !== container; el = el.parentElement) {
|
|
3980
|
+
if (!clipOf.has(el)) {
|
|
3981
|
+
const w4 = clipWindowOf(el);
|
|
3982
|
+
clipOf.set(el, w4 && box(w4));
|
|
3983
|
+
}
|
|
3984
|
+
const w3 = clipOf.get(el);
|
|
3985
|
+
if (w3) v = cut(v, w3);
|
|
3986
|
+
}
|
|
3987
|
+
return v;
|
|
3988
|
+
};
|
|
3989
|
+
const controls = Array.from(container.querySelectorAll(`.${CONTROL_CLASS}, .${ZOOM_PAD_CLASS}`)).map((c2) => c2.getBoundingClientRect()).filter((r2) => r2.width > 0 && r2.height > 0).map(box);
|
|
3990
|
+
const clear = (v, len, mark) => {
|
|
3991
|
+
const hw = Math.max(16, len * 6 + 8) / 2, hh = 8, g5 = 2;
|
|
3992
|
+
const cx = (v.l + v.r) / 2, cy = (v.t + v.b) / 2;
|
|
3993
|
+
const free = (x3, y2) => x3 >= v.l && x3 <= v.r && y2 >= v.t && y2 <= v.b && controls.every((c2) => x3 + hw <= c2.l || x3 - hw >= c2.r || y2 + hh <= c2.t || y2 - hh >= c2.b);
|
|
3994
|
+
const onShape = shapeTest(mark);
|
|
3995
|
+
const tries = [[cx, cy]];
|
|
3996
|
+
for (let i = 0; i <= 8; i++) for (let j4 = 0; j4 <= 6; j4++) tries.push([v.l + (v.r - v.l) * i / 8, v.t + (v.b - v.t) * j4 / 6]);
|
|
3997
|
+
tries.sort((p4, q2) => Math.hypot(p4[0] - cx, p4[1] - cy) - Math.hypot(q2[0] - cx, q2[1] - cy));
|
|
3998
|
+
for (const c2 of controls) tries.push([cx, c2.b + hh + g5], [cx, c2.t - hh - g5], [c2.l - hw - g5, cy], [c2.r + hw + g5, cy]);
|
|
3999
|
+
return tries.find(([x3, y2]) => free(x3, y2) && onShape(x3, y2)) ?? tries.find(([x3, y2]) => free(x3, y2)) ?? null;
|
|
4000
|
+
};
|
|
3955
4001
|
let shown = 0;
|
|
3956
4002
|
for (const a3 of list) {
|
|
3957
4003
|
const pairs = (a3.where ? Object.entries(a3.where) : a3.column != null ? [[a3.column, a3.value]] : []).map(([c2, v]) => [names.indexOf(c2), text(v)]);
|
|
@@ -3961,23 +4007,30 @@ function createAnnotationLayer(container, onClick) {
|
|
|
3961
4007
|
if (pairs.every(([ci, v]) => text(row?.[ci]) === v)) for (const m4 of marksByRow.get(r2) ?? []) hits.add(m4);
|
|
3962
4008
|
});
|
|
3963
4009
|
if (!hits.size) continue;
|
|
3964
|
-
|
|
3965
|
-
let best = null;
|
|
4010
|
+
let best = null, bestMark = null, laidOut = false;
|
|
3966
4011
|
for (const m4 of hits) {
|
|
3967
4012
|
const b4 = m4.getBoundingClientRect();
|
|
3968
|
-
if (!
|
|
4013
|
+
if (!(b4.width > 0 && b4.height > 0)) continue;
|
|
4014
|
+
laidOut = true;
|
|
4015
|
+
const v = cut(box(b4), viewOf(m4));
|
|
4016
|
+
if (v && (!best || area(v) > area(best))) {
|
|
4017
|
+
best = v;
|
|
4018
|
+
bestMark = m4;
|
|
4019
|
+
}
|
|
3969
4020
|
}
|
|
4021
|
+
const pt2 = best ? clear(best, (a3.label ?? "\u2022").length, bestMark) : null;
|
|
4022
|
+
if (laidOut && !pt2) continue;
|
|
4023
|
+
shown++;
|
|
3970
4024
|
const badge = doc.createElement("div");
|
|
3971
4025
|
badge.className = ANNOTATION_CLASS;
|
|
3972
4026
|
badge.textContent = a3.label ?? "\u2022";
|
|
3973
4027
|
if (a3.title) badge.setAttribute("title", a3.title);
|
|
3974
4028
|
badge.setAttribute("role", "button");
|
|
3975
4029
|
badge.setAttribute("aria-label", a3.title ? `Note: ${a3.title}` : `Note on ${want}`);
|
|
3976
|
-
const laidOut = !!best && best.width > 0 && best.height > 0;
|
|
3977
4030
|
Object.assign(badge.style, {
|
|
3978
4031
|
position: "absolute",
|
|
3979
|
-
left: `${Math.round(
|
|
3980
|
-
top: `${Math.round(
|
|
4032
|
+
left: `${Math.round(pt2 ? pt2[0] - origin.left : 0)}px`,
|
|
4033
|
+
top: `${Math.round(pt2 ? pt2[1] - origin.top : 0)}px`,
|
|
3981
4034
|
transform: "translate(-50%, -50%)",
|
|
3982
4035
|
minWidth: "16px",
|
|
3983
4036
|
height: "16px",
|
package/dist/index.mjs
CHANGED
package/dist/react.mjs
CHANGED
|
@@ -23,6 +23,9 @@ export interface AnnotationReport {
|
|
|
23
23
|
}
|
|
24
24
|
export declare const ANNOTATION_LAYER_CLASS = "bic-annotations";
|
|
25
25
|
export declare const ANNOTATION_CLASS = "bic-annotation";
|
|
26
|
+
/** The class the d3.llmGeoZoom helper gives its button pad - a control drawn by a generated chart without the
|
|
27
|
+
* contract's CONTROL_CLASS, so the badge layer names it too. */
|
|
28
|
+
export declare const ZOOM_PAD_CLASS = "llm-zoom-pad";
|
|
26
29
|
export interface AnnotationLayer {
|
|
27
30
|
set(list: MarkAnnotation[]): void;
|
|
28
31
|
/** Redraw against the container's current marks and the given data. */
|
package/dist/types/fitDom.d.ts
CHANGED
|
@@ -7,6 +7,15 @@ export declare function svgInkReach(svg: SVGSVGElement): {
|
|
|
7
7
|
right: number;
|
|
8
8
|
bottom: number;
|
|
9
9
|
} | null;
|
|
10
|
+
/** The window ONE element's own clip-path attribute opens, in screen coordinates - or null: no clip, a clip in
|
|
11
|
+
* objectBoundingBox units, or one that won't measure. Shared by the frame fit (right/bottom) and the note badges
|
|
12
|
+
* (the whole window). The clipPath is looked up within the element's own svg. */
|
|
13
|
+
export declare function clipWindowOf(el: Element): {
|
|
14
|
+
left: number;
|
|
15
|
+
top: number;
|
|
16
|
+
right: number;
|
|
17
|
+
bottom: number;
|
|
18
|
+
} | null;
|
|
10
19
|
export type InkReach = (el: Element) => {
|
|
11
20
|
right: number;
|
|
12
21
|
bottom: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bicharts/chart-host",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.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",
|