@bicharts/chart-host 0.5.76 → 0.5.77
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-N6XJDJRN.mjs → chunk-WI2R6E6U.mjs} +312 -1
- package/dist/index.mjs +23 -3
- package/dist/react.mjs +1 -1
- package/dist/types/fit.d.ts +56 -0
- package/dist/types/fitDom.d.ts +31 -1
- package/dist/types/index.d.ts +2 -0
- package/package.json +1 -1
|
@@ -1813,6 +1813,64 @@ function planFrameGrow(inkBottomPx, elHeightPx, viewBoxH, ctmScale, slackPx = SC
|
|
|
1813
1813
|
if (!(ctmScale > 0) || !isFinite(ctmScale)) return none("no-scale");
|
|
1814
1814
|
return { grow: true, heightPx, viewBoxH: viewBoxH + delta / ctmScale, reason: "grow" };
|
|
1815
1815
|
}
|
|
1816
|
+
var AXIS_PIN_MIN_LABELS = 2;
|
|
1817
|
+
var AXIS_PIN_BAND_PAD_PX = 2;
|
|
1818
|
+
var AXIS_PIN_MAX_BAND_FRACTION = 0.4;
|
|
1819
|
+
var AXIS_PIN_TRACK_REACH_PX = 12;
|
|
1820
|
+
function isHorizontalLabelRow(boxes) {
|
|
1821
|
+
if (!boxes || boxes.length < 2) return false;
|
|
1822
|
+
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
|
|
1823
|
+
for (const b2 of boxes) {
|
|
1824
|
+
const cx = (b2.left + b2.right) / 2, cy = (b2.top + b2.bottom) / 2;
|
|
1825
|
+
if (!isFinite(cx) || !isFinite(cy)) return false;
|
|
1826
|
+
if (cx < minX) minX = cx;
|
|
1827
|
+
if (cx > maxX) maxX = cx;
|
|
1828
|
+
if (cy < minY) minY = cy;
|
|
1829
|
+
if (cy > maxY) maxY = cy;
|
|
1830
|
+
}
|
|
1831
|
+
return maxX - minX > maxY - minY;
|
|
1832
|
+
}
|
|
1833
|
+
function labelBand(boxes, tracks = [], pad = AXIS_PIN_BAND_PAD_PX, trackReach = AXIS_PIN_TRACK_REACH_PX) {
|
|
1834
|
+
if (!boxes || boxes.length === 0) return null;
|
|
1835
|
+
let top = Infinity, bottom = -Infinity;
|
|
1836
|
+
for (const b2 of boxes) {
|
|
1837
|
+
if (!isFinite(b2.top) || !isFinite(b2.bottom)) continue;
|
|
1838
|
+
if (b2.top < top) top = b2.top;
|
|
1839
|
+
if (b2.bottom > bottom) bottom = b2.bottom;
|
|
1840
|
+
}
|
|
1841
|
+
if (!isFinite(top) || !isFinite(bottom) || bottom <= top) return null;
|
|
1842
|
+
for (const t of tracks || []) {
|
|
1843
|
+
if (!isFinite(t.top) || !isFinite(t.bottom)) continue;
|
|
1844
|
+
const gap = t.bottom < top ? top - t.bottom : t.top > bottom ? t.top - bottom : 0;
|
|
1845
|
+
if (gap > trackReach) continue;
|
|
1846
|
+
if (t.top < top) top = t.top;
|
|
1847
|
+
if (t.bottom > bottom) bottom = t.bottom;
|
|
1848
|
+
}
|
|
1849
|
+
return { top: top - pad, bottom: bottom + pad };
|
|
1850
|
+
}
|
|
1851
|
+
function planAxisPin(candidates, viewportH, minLabels = AXIS_PIN_MIN_LABELS, maxFraction = AXIS_PIN_MAX_BAND_FRACTION) {
|
|
1852
|
+
const none = (reason) => ({ pin: false, index: -1, band: null, reason });
|
|
1853
|
+
if (!isFinite(viewportH) || viewportH <= 0) return none("unmeasurable");
|
|
1854
|
+
let sawAxis = false, best = null;
|
|
1855
|
+
for (const c of candidates || []) {
|
|
1856
|
+
if (!c || !c.labels || c.labels.length < minLabels) continue;
|
|
1857
|
+
sawAxis = true;
|
|
1858
|
+
if (!isHorizontalLabelRow(c.labels)) continue;
|
|
1859
|
+
if (!best || c.labels.length > best.labels.length) best = c;
|
|
1860
|
+
}
|
|
1861
|
+
if (!best) return none(sawAxis ? "vertical-only" : "no-axis");
|
|
1862
|
+
const band = labelBand(best.labels, best.tracks || []);
|
|
1863
|
+
if (!band) return none("unmeasurable");
|
|
1864
|
+
if (band.bottom - band.top > viewportH * maxFraction) return none("band-too-tall");
|
|
1865
|
+
return { pin: true, index: best.index, band, reason: "pinned" };
|
|
1866
|
+
}
|
|
1867
|
+
function axisPinPlacement(band, scrollTop, viewportH) {
|
|
1868
|
+
if (!band || !isFinite(band.top) || !isFinite(band.bottom)) return null;
|
|
1869
|
+
if (!isFinite(scrollTop) || !isFinite(viewportH) || viewportH <= 0) return null;
|
|
1870
|
+
if (band.top < scrollTop) return "top";
|
|
1871
|
+
if (band.bottom > scrollTop + viewportH) return "bottom";
|
|
1872
|
+
return null;
|
|
1873
|
+
}
|
|
1816
1874
|
|
|
1817
1875
|
// src/fitDom.ts
|
|
1818
1876
|
var FIT_WALK_CAP = 8e3;
|
|
@@ -1898,6 +1956,237 @@ function fitReadingFor(lane, c, slackPx = SCROLL_SLACK_PX, inkOf) {
|
|
|
1898
1956
|
scrollH: c.scrollHeight
|
|
1899
1957
|
};
|
|
1900
1958
|
}
|
|
1959
|
+
var noPin = (reason) => ({ pinned: false, reason, bandPx: 0, axisAt: "", labels: 0, carried: 0, edge: null });
|
|
1960
|
+
var CONTAINER_SLOT_AXIS_PIN = "__bicAxisPin";
|
|
1961
|
+
var SVG_NS2 = "http://www.w3.org/2000/svg";
|
|
1962
|
+
var AXIS_PIN_MAX_CARRIED = 12;
|
|
1963
|
+
function unpinScrolledAxis(container) {
|
|
1964
|
+
if (!container) return;
|
|
1965
|
+
const st = container[CONTAINER_SLOT_AXIS_PIN];
|
|
1966
|
+
if (!st) return;
|
|
1967
|
+
try {
|
|
1968
|
+
container.removeEventListener("scroll", st.onScroll);
|
|
1969
|
+
} catch {
|
|
1970
|
+
}
|
|
1971
|
+
try {
|
|
1972
|
+
if (st.overlay.parentNode) st.overlay.parentNode.removeChild(st.overlay);
|
|
1973
|
+
} catch {
|
|
1974
|
+
}
|
|
1975
|
+
try {
|
|
1976
|
+
delete container[CONTAINER_SLOT_AXIS_PIN];
|
|
1977
|
+
} catch {
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
function textHidden(t, view) {
|
|
1981
|
+
if (t.getAttribute("display") === "none" || t.getAttribute("visibility") === "hidden") return true;
|
|
1982
|
+
try {
|
|
1983
|
+
const cs = view.getComputedStyle(t);
|
|
1984
|
+
if (cs && (cs.display === "none" || cs.visibility === "hidden")) return true;
|
|
1985
|
+
} catch {
|
|
1986
|
+
}
|
|
1987
|
+
return false;
|
|
1988
|
+
}
|
|
1989
|
+
function backdropFill(svg, container, view, svgBox) {
|
|
1990
|
+
try {
|
|
1991
|
+
const rects = svg.querySelectorAll("rect");
|
|
1992
|
+
for (let i2 = 0; i2 < rects.length && i2 < 6; i2++) {
|
|
1993
|
+
const r = rects[i2];
|
|
1994
|
+
if (r.closest(".d3-mark")) continue;
|
|
1995
|
+
const b2 = r.getBoundingClientRect();
|
|
1996
|
+
if (b2.width >= svgBox.width * 0.9 && b2.height >= svgBox.height * 0.9) {
|
|
1997
|
+
let fill = "";
|
|
1998
|
+
try {
|
|
1999
|
+
fill = view.getComputedStyle(r).fill || "";
|
|
2000
|
+
} catch {
|
|
2001
|
+
}
|
|
2002
|
+
if (!fill || fill === "none") fill = r.getAttribute("fill") || "";
|
|
2003
|
+
if (fill && fill !== "none" && fill !== "transparent") return fill;
|
|
2004
|
+
break;
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
} catch {
|
|
2008
|
+
}
|
|
2009
|
+
try {
|
|
2010
|
+
let el = container;
|
|
2011
|
+
for (let hops = 0; el && hops < 12; hops++, el = el.parentElement) {
|
|
2012
|
+
const bg = view.getComputedStyle(el).backgroundColor;
|
|
2013
|
+
if (bg && bg !== "transparent" && bg !== "rgba(0, 0, 0, 0)") return bg;
|
|
2014
|
+
}
|
|
2015
|
+
} catch {
|
|
2016
|
+
}
|
|
2017
|
+
return "#ffffff";
|
|
2018
|
+
}
|
|
2019
|
+
function cloneWithChain(el, svg, root) {
|
|
2020
|
+
const chain = [];
|
|
2021
|
+
let n = el.parentElement;
|
|
2022
|
+
while (n && n !== svg) {
|
|
2023
|
+
chain.unshift(n);
|
|
2024
|
+
n = n.parentElement;
|
|
2025
|
+
}
|
|
2026
|
+
let parent = root;
|
|
2027
|
+
for (const anc of chain) {
|
|
2028
|
+
const c = anc.cloneNode(false);
|
|
2029
|
+
parent.appendChild(c);
|
|
2030
|
+
parent = c;
|
|
2031
|
+
}
|
|
2032
|
+
parent.appendChild(el.cloneNode(true));
|
|
2033
|
+
const ids = root.querySelectorAll("[id]");
|
|
2034
|
+
for (let i2 = 0; i2 < ids.length; i2++) ids[i2].removeAttribute("id");
|
|
2035
|
+
}
|
|
2036
|
+
function pinScrolledAxis(container, svg) {
|
|
2037
|
+
unpinScrolledAxis(container);
|
|
2038
|
+
if (!container) return noPin("no-container");
|
|
2039
|
+
if (!svg) return noPin("no-svg");
|
|
2040
|
+
try {
|
|
2041
|
+
const doc = svg.ownerDocument;
|
|
2042
|
+
const view = doc && doc.defaultView || globalThis;
|
|
2043
|
+
const svgBox = svg.getBoundingClientRect();
|
|
2044
|
+
if (!(svgBox.width > 0) || !(svgBox.height > 0)) return noPin("unmeasurable");
|
|
2045
|
+
const m2 = typeof svg.getScreenCTM === "function" ? svg.getScreenCTM() : null;
|
|
2046
|
+
if (!m2 || !(m2.a > 0) || !(m2.d > 0) || !isFinite(m2.e) || !isFinite(m2.f)) return noPin("no-ctm");
|
|
2047
|
+
const viewportH = container.clientHeight;
|
|
2048
|
+
if (!(viewportH > 0)) return noPin("unmeasurable");
|
|
2049
|
+
const rel = (r) => ({ left: r.left - svgBox.left, top: r.top - svgBox.top, right: r.right - svgBox.left, bottom: r.bottom - svgBox.top });
|
|
2050
|
+
const ticks = svg.querySelectorAll(".tick");
|
|
2051
|
+
const byAxis = /* @__PURE__ */ new Map();
|
|
2052
|
+
for (let i2 = 0; i2 < ticks.length; i2++) {
|
|
2053
|
+
const t = ticks[i2];
|
|
2054
|
+
if (t.closest("svg") !== svg) continue;
|
|
2055
|
+
const p2 = t.parentElement;
|
|
2056
|
+
if (!p2) continue;
|
|
2057
|
+
const list = byAxis.get(p2);
|
|
2058
|
+
if (list) list.push(t);
|
|
2059
|
+
else byAxis.set(p2, [t]);
|
|
2060
|
+
}
|
|
2061
|
+
const axes = [];
|
|
2062
|
+
const cands = [];
|
|
2063
|
+
byAxis.forEach((group, axis2) => {
|
|
2064
|
+
const labels = [];
|
|
2065
|
+
for (const tick of group) {
|
|
2066
|
+
const txt = tick.querySelector("text");
|
|
2067
|
+
if (!txt || textHidden(txt, view)) continue;
|
|
2068
|
+
const r = txt.getBoundingClientRect();
|
|
2069
|
+
if (!(r.width > 0) || !(r.height > 0)) continue;
|
|
2070
|
+
labels.push(rel(r));
|
|
2071
|
+
}
|
|
2072
|
+
const tracks = [];
|
|
2073
|
+
for (let i2 = 0; i2 < axis2.children.length; i2++) {
|
|
2074
|
+
const ch = axis2.children[i2];
|
|
2075
|
+
if (ch.tagName.toLowerCase() !== "path" || !(ch.getAttribute("class") || "").split(/\s+/).includes("domain")) continue;
|
|
2076
|
+
const r = ch.getBoundingClientRect();
|
|
2077
|
+
if (r.width > 0 && r.height <= 4) tracks.push(rel(r));
|
|
2078
|
+
}
|
|
2079
|
+
cands.push({ index: axes.length, labels, tracks });
|
|
2080
|
+
axes.push(axis2);
|
|
2081
|
+
});
|
|
2082
|
+
const plan = planAxisPin(cands, viewportH);
|
|
2083
|
+
if (!plan.pin || !plan.band) return noPin(plan.reason);
|
|
2084
|
+
const axis = axes[plan.index];
|
|
2085
|
+
const band = { top: Math.max(0, plan.band.top), bottom: Math.min(svgBox.height, plan.band.bottom) };
|
|
2086
|
+
const bandPx = band.bottom - band.top;
|
|
2087
|
+
if (!(bandPx > 0)) return noPin("unmeasurable");
|
|
2088
|
+
const carried = [];
|
|
2089
|
+
const texts = svg.querySelectorAll("text");
|
|
2090
|
+
for (let i2 = 0; i2 < texts.length && carried.length < AXIS_PIN_MAX_CARRIED; i2++) {
|
|
2091
|
+
const t = texts[i2];
|
|
2092
|
+
if (t.closest("svg") !== svg) continue;
|
|
2093
|
+
if (t.closest(".tick") || t.closest(".d3-mark") || t.closest(".d3-legend-mark")) continue;
|
|
2094
|
+
if (axis.contains(t) || textHidden(t, view)) continue;
|
|
2095
|
+
const r = rel(t.getBoundingClientRect());
|
|
2096
|
+
if (!(r.right > r.left) || !(r.bottom > r.top)) continue;
|
|
2097
|
+
if (r.top >= band.top && r.bottom <= band.bottom) carried.push(t);
|
|
2098
|
+
}
|
|
2099
|
+
const pinSvg = doc.createElementNS(SVG_NS2, "svg");
|
|
2100
|
+
for (const a2 of ["font-family", "font-size", "font-weight", "font", "fill", "color", "text-rendering"]) {
|
|
2101
|
+
const v2 = svg.getAttribute(a2);
|
|
2102
|
+
if (v2) pinSvg.setAttribute(a2, v2);
|
|
2103
|
+
}
|
|
2104
|
+
const styles = svg.querySelectorAll("style");
|
|
2105
|
+
for (let i2 = 0; i2 < styles.length; i2++) pinSvg.appendChild(styles[i2].cloneNode(true));
|
|
2106
|
+
const vbX = (svgBox.left - m2.e) / m2.a, vbY = (svgBox.top + band.top - m2.f) / m2.d;
|
|
2107
|
+
const vbW = svgBox.width / m2.a, vbH = bandPx / m2.d;
|
|
2108
|
+
pinSvg.setAttribute("viewBox", `${vbX} ${vbY} ${vbW} ${vbH}`);
|
|
2109
|
+
pinSvg.setAttribute("preserveAspectRatio", "none");
|
|
2110
|
+
pinSvg.setAttribute("width", String(svgBox.width));
|
|
2111
|
+
pinSvg.setAttribute("height", String(bandPx));
|
|
2112
|
+
pinSvg.setAttribute("aria-hidden", "true");
|
|
2113
|
+
pinSvg.style.display = "block";
|
|
2114
|
+
pinSvg.style.pointerEvents = "none";
|
|
2115
|
+
const back = doc.createElementNS(SVG_NS2, "rect");
|
|
2116
|
+
back.setAttribute("x", String(vbX));
|
|
2117
|
+
back.setAttribute("y", String(vbY));
|
|
2118
|
+
back.setAttribute("width", String(vbW));
|
|
2119
|
+
back.setAttribute("height", String(vbH));
|
|
2120
|
+
back.setAttribute("fill", backdropFill(svg, container, view, svgBox));
|
|
2121
|
+
pinSvg.appendChild(back);
|
|
2122
|
+
cloneWithChain(axis, svg, pinSvg);
|
|
2123
|
+
for (const t of carried) cloneWithChain(t, svg, pinSvg);
|
|
2124
|
+
const overlay = doc.createElement("div");
|
|
2125
|
+
overlay.setAttribute("data-bic-axis-pin", "1");
|
|
2126
|
+
const o2 = overlay.style;
|
|
2127
|
+
o2.position = "absolute";
|
|
2128
|
+
o2.margin = "0";
|
|
2129
|
+
o2.padding = "0";
|
|
2130
|
+
o2.border = "0";
|
|
2131
|
+
o2.width = `${svgBox.width}px`;
|
|
2132
|
+
o2.height = `${bandPx}px`;
|
|
2133
|
+
o2.overflow = "hidden";
|
|
2134
|
+
o2.pointerEvents = "none";
|
|
2135
|
+
o2.zIndex = "3";
|
|
2136
|
+
o2.display = "none";
|
|
2137
|
+
overlay.appendChild(pinSvg);
|
|
2138
|
+
try {
|
|
2139
|
+
const cs = view.getComputedStyle(container);
|
|
2140
|
+
const pos = cs && cs.position || container.style.position;
|
|
2141
|
+
if (!pos || pos === "static") container.style.position = "relative";
|
|
2142
|
+
} catch {
|
|
2143
|
+
container.style.position = container.style.position || "relative";
|
|
2144
|
+
}
|
|
2145
|
+
const cRect = container.getBoundingClientRect();
|
|
2146
|
+
const svgLeft = svgBox.left - cRect.left - container.clientLeft + container.scrollLeft;
|
|
2147
|
+
const svgTop = svgBox.top - cRect.top - container.clientTop + container.scrollTop;
|
|
2148
|
+
const contentBand = { top: svgTop + band.top, bottom: svgTop + band.bottom };
|
|
2149
|
+
const place = () => {
|
|
2150
|
+
const st = container.scrollTop, vh = container.clientHeight;
|
|
2151
|
+
const edge2 = axisPinPlacement(contentBand, st, vh);
|
|
2152
|
+
if (!edge2) {
|
|
2153
|
+
o2.display = "none";
|
|
2154
|
+
return null;
|
|
2155
|
+
}
|
|
2156
|
+
o2.display = "block";
|
|
2157
|
+
o2.left = `${svgLeft}px`;
|
|
2158
|
+
o2.top = `${edge2 === "top" ? st : st + vh - bandPx}px`;
|
|
2159
|
+
return edge2;
|
|
2160
|
+
};
|
|
2161
|
+
const onScroll = () => {
|
|
2162
|
+
try {
|
|
2163
|
+
if (!overlay.isConnected) {
|
|
2164
|
+
unpinScrolledAxis(container);
|
|
2165
|
+
return;
|
|
2166
|
+
}
|
|
2167
|
+
place();
|
|
2168
|
+
} catch {
|
|
2169
|
+
}
|
|
2170
|
+
};
|
|
2171
|
+
container.appendChild(overlay);
|
|
2172
|
+
container.addEventListener("scroll", onScroll, { passive: true });
|
|
2173
|
+
container[CONTAINER_SLOT_AXIS_PIN] = { overlay, onScroll };
|
|
2174
|
+
const edge = place();
|
|
2175
|
+
const mid = (band.top + band.bottom) / 2 / svgBox.height;
|
|
2176
|
+
return {
|
|
2177
|
+
pinned: true,
|
|
2178
|
+
reason: "pinned",
|
|
2179
|
+
bandPx: Math.round(bandPx),
|
|
2180
|
+
axisAt: mid < 1 / 3 ? "top" : mid > 2 / 3 ? "bottom" : "middle",
|
|
2181
|
+
labels: cands[plan.index].labels.length,
|
|
2182
|
+
carried: carried.length,
|
|
2183
|
+
edge
|
|
2184
|
+
};
|
|
2185
|
+
} catch {
|
|
2186
|
+
unpinScrolledAxis(container);
|
|
2187
|
+
return noPin("threw");
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
1901
2190
|
function fitRenderedChart(container, opts = {}) {
|
|
1902
2191
|
const slackPx = opts.slackPx ?? SCROLL_SLACK_PX;
|
|
1903
2192
|
const result = {
|
|
@@ -1907,7 +2196,8 @@ function fitRenderedChart(container, opts = {}) {
|
|
|
1907
2196
|
growReason: "no-container",
|
|
1908
2197
|
overflowX: "hidden",
|
|
1909
2198
|
overflowY: "hidden",
|
|
1910
|
-
reading: null
|
|
2199
|
+
reading: null,
|
|
2200
|
+
axisPin: noPin("no-container")
|
|
1911
2201
|
};
|
|
1912
2202
|
if (!container) return result;
|
|
1913
2203
|
try {
|
|
@@ -1968,6 +2258,16 @@ function fitRenderedChart(container, opts = {}) {
|
|
|
1968
2258
|
if (opts.applyOverflow !== false) {
|
|
1969
2259
|
container.style.overflowX = fit.overflowX;
|
|
1970
2260
|
container.style.overflowY = fit.overflowY;
|
|
2261
|
+
if (fit.overflowY !== "auto") {
|
|
2262
|
+
unpinScrolledAxis(container);
|
|
2263
|
+
result.axisPin = noPin("not-scrolling");
|
|
2264
|
+
} else if (opts.pinAxis === false) {
|
|
2265
|
+
unpinScrolledAxis(container);
|
|
2266
|
+
result.axisPin = noPin("disabled");
|
|
2267
|
+
} else result.axisPin = pinScrolledAxis(container, svg);
|
|
2268
|
+
} else {
|
|
2269
|
+
unpinScrolledAxis(container);
|
|
2270
|
+
result.axisPin = noPin("overflow-not-applied");
|
|
1971
2271
|
}
|
|
1972
2272
|
} catch {
|
|
1973
2273
|
}
|
|
@@ -2631,6 +2931,7 @@ function createChartHost(container, config) {
|
|
|
2631
2931
|
container.removeEventListener(XFILTER_REFRESH_EVENT, onXf);
|
|
2632
2932
|
container.removeEventListener("click", onClick);
|
|
2633
2933
|
subs.clear();
|
|
2934
|
+
unpinScrolledAxis(container);
|
|
2634
2935
|
container.innerHTML = "";
|
|
2635
2936
|
}
|
|
2636
2937
|
};
|
|
@@ -2703,10 +3004,20 @@ export {
|
|
|
2703
3004
|
isPhantomBox,
|
|
2704
3005
|
MAX_FRAME_GROW_FACTOR,
|
|
2705
3006
|
planFrameGrow,
|
|
3007
|
+
AXIS_PIN_MIN_LABELS,
|
|
3008
|
+
AXIS_PIN_BAND_PAD_PX,
|
|
3009
|
+
AXIS_PIN_MAX_BAND_FRACTION,
|
|
3010
|
+
AXIS_PIN_TRACK_REACH_PX,
|
|
3011
|
+
isHorizontalLabelRow,
|
|
3012
|
+
labelBand,
|
|
3013
|
+
planAxisPin,
|
|
3014
|
+
axisPinPlacement,
|
|
2706
3015
|
ctmScaleOf,
|
|
2707
3016
|
svgInkReach,
|
|
2708
3017
|
measureContainerBoxes,
|
|
2709
3018
|
fitReadingFor,
|
|
3019
|
+
unpinScrolledAxis,
|
|
3020
|
+
pinScrolledAxis,
|
|
2710
3021
|
fitRenderedChart,
|
|
2711
3022
|
DARK_TEXT,
|
|
2712
3023
|
LIGHT_TEXT,
|
package/dist/index.mjs
CHANGED
|
@@ -11,6 +11,10 @@ import {
|
|
|
11
11
|
ANIM_PLAY_SPEED_MIN,
|
|
12
12
|
APPROXIMATE_POSITIONS_DEFAULT,
|
|
13
13
|
AXIS_FILTER_CLASS,
|
|
14
|
+
AXIS_PIN_BAND_PAD_PX,
|
|
15
|
+
AXIS_PIN_MAX_BAND_FRACTION,
|
|
16
|
+
AXIS_PIN_MIN_LABELS,
|
|
17
|
+
AXIS_PIN_TRACK_REACH_PX,
|
|
14
18
|
BACKING_MAJORITY,
|
|
15
19
|
COLOR_SCALE_SELF_CLAMP_PCT_DEFAULT,
|
|
16
20
|
COLOR_SCALE_SELF_CLAMP_PCT_MAX,
|
|
@@ -56,6 +60,7 @@ import {
|
|
|
56
60
|
XFILTER_REFRESH_EVENT,
|
|
57
61
|
_e,
|
|
58
62
|
applyLabelContrast,
|
|
63
|
+
axisPinPlacement,
|
|
59
64
|
backingHoldsGlyph,
|
|
60
65
|
blankRenderFlag,
|
|
61
66
|
buildRenderPayload,
|
|
@@ -81,14 +86,18 @@ import {
|
|
|
81
86
|
glyphSampleGrid,
|
|
82
87
|
hitBandFlag,
|
|
83
88
|
isBlankRender,
|
|
89
|
+
isHorizontalLabelRow,
|
|
84
90
|
isPhantomBox,
|
|
85
91
|
isPillBackdropAlpha,
|
|
92
|
+
labelBand,
|
|
86
93
|
loadGeo,
|
|
87
94
|
measureContainerBoxes,
|
|
88
95
|
needsScroll,
|
|
89
96
|
noopViewStateProvider,
|
|
90
97
|
periodTickSuppressesFeedback,
|
|
91
98
|
pillBacksGlyph,
|
|
99
|
+
pinScrolledAxis,
|
|
100
|
+
planAxisPin,
|
|
92
101
|
planFrameGrow,
|
|
93
102
|
registerGeo,
|
|
94
103
|
registerGeoAsset,
|
|
@@ -99,8 +108,9 @@ import {
|
|
|
99
108
|
sessionViewStateProvider,
|
|
100
109
|
stripEsmExports,
|
|
101
110
|
svgInkReach,
|
|
102
|
-
toRGBA
|
|
103
|
-
|
|
111
|
+
toRGBA,
|
|
112
|
+
unpinScrolledAxis
|
|
113
|
+
} from "./chunk-WI2R6E6U.mjs";
|
|
104
114
|
import "./chunk-A2GMXZP7.mjs";
|
|
105
115
|
import "./chunk-GHODWOAL.mjs";
|
|
106
116
|
|
|
@@ -1033,6 +1043,10 @@ export {
|
|
|
1033
1043
|
ANIM_PLAY_SPEED_MIN,
|
|
1034
1044
|
APPROXIMATE_POSITIONS_DEFAULT,
|
|
1035
1045
|
AXIS_FILTER_CLASS,
|
|
1046
|
+
AXIS_PIN_BAND_PAD_PX,
|
|
1047
|
+
AXIS_PIN_MAX_BAND_FRACTION,
|
|
1048
|
+
AXIS_PIN_MIN_LABELS,
|
|
1049
|
+
AXIS_PIN_TRACK_REACH_PX,
|
|
1036
1050
|
BACKING_MAJORITY,
|
|
1037
1051
|
CHOOSER_MIN_HEIGHT_PX,
|
|
1038
1052
|
CHOOSER_MIN_WIDTH_PX,
|
|
@@ -1082,6 +1096,7 @@ export {
|
|
|
1082
1096
|
actionFor,
|
|
1083
1097
|
applyLabelContrast,
|
|
1084
1098
|
askApplyImprovements,
|
|
1099
|
+
axisPinPlacement,
|
|
1085
1100
|
backingHoldsGlyph,
|
|
1086
1101
|
bareBase64,
|
|
1087
1102
|
blankRenderFlag,
|
|
@@ -1120,8 +1135,10 @@ export {
|
|
|
1120
1135
|
hitBandFlag,
|
|
1121
1136
|
inlineFilterGate,
|
|
1122
1137
|
isBlankRender,
|
|
1138
|
+
isHorizontalLabelRow,
|
|
1123
1139
|
isPhantomBox,
|
|
1124
1140
|
isPillBackdropAlpha,
|
|
1141
|
+
labelBand,
|
|
1125
1142
|
launchFavorStyle,
|
|
1126
1143
|
launchGenerates,
|
|
1127
1144
|
listNeedsFilter,
|
|
@@ -1136,6 +1153,8 @@ export {
|
|
|
1136
1153
|
orderRefusalsForDisplay,
|
|
1137
1154
|
periodTickSuppressesFeedback,
|
|
1138
1155
|
pillBacksGlyph,
|
|
1156
|
+
pinScrolledAxis,
|
|
1157
|
+
planAxisPin,
|
|
1139
1158
|
planFrameGrow,
|
|
1140
1159
|
planTrivialChart,
|
|
1141
1160
|
qualifyAuto,
|
|
@@ -1165,5 +1184,6 @@ export {
|
|
|
1165
1184
|
svgInkReach,
|
|
1166
1185
|
svgNaturalSize,
|
|
1167
1186
|
svgToDataUrl,
|
|
1168
|
-
toRGBA
|
|
1187
|
+
toRGBA,
|
|
1188
|
+
unpinScrolledAxis
|
|
1169
1189
|
};
|
package/dist/react.mjs
CHANGED
package/dist/types/fit.d.ts
CHANGED
|
@@ -34,3 +34,59 @@ export interface FrameGrowPlan {
|
|
|
34
34
|
* @param ctmScale px per user unit, from the live screen CTM
|
|
35
35
|
*/
|
|
36
36
|
export declare function planFrameGrow(inkBottomPx: number, elHeightPx: number, viewBoxH: number, ctmScale: number, slackPx?: number, maxFactor?: number): FrameGrowPlan;
|
|
37
|
+
export declare const AXIS_PIN_MIN_LABELS = 2;
|
|
38
|
+
export declare const AXIS_PIN_BAND_PAD_PX = 2;
|
|
39
|
+
export declare const AXIS_PIN_MAX_BAND_FRACTION = 0.4;
|
|
40
|
+
export declare const AXIS_PIN_TRACK_REACH_PX = 12;
|
|
41
|
+
export interface LabelRowBox {
|
|
42
|
+
left: number;
|
|
43
|
+
top: number;
|
|
44
|
+
right: number;
|
|
45
|
+
bottom: number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Does a row of label boxes run ACROSS the chart rather than DOWN it? A horizontal axis spreads
|
|
49
|
+
* its label centres over x; a y-axis spreads them over y, and pinning a y-axis would pin the
|
|
50
|
+
* ROWS - the exact thing the scroll exists to reveal. Ties are vertical: a single label has no
|
|
51
|
+
* direction and is not an axis.
|
|
52
|
+
*/
|
|
53
|
+
export declare function isHorizontalLabelRow(boxes: LabelRowBox[]): boolean;
|
|
54
|
+
export interface AxisBand {
|
|
55
|
+
top: number;
|
|
56
|
+
bottom: number;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The vertical band a label row occupies, padded, widened to take in a thin track (the domain
|
|
60
|
+
* line) that sits within reach of the labels. Tracks further away are ignored on purpose: a
|
|
61
|
+
* gridline drawn as a full-height tick line is part of the plot, not of the header.
|
|
62
|
+
*/
|
|
63
|
+
export declare function labelBand(boxes: LabelRowBox[], tracks?: LabelRowBox[], pad?: number, trackReach?: number): AxisBand | null;
|
|
64
|
+
export interface AxisPinCandidate {
|
|
65
|
+
/** The caller's handle for this axis - returned in the plan. */
|
|
66
|
+
index: number;
|
|
67
|
+
/** One box per VISIBLE tick label, in any one consistent coordinate space. */
|
|
68
|
+
labels: LabelRowBox[];
|
|
69
|
+
/** Thin tracks that may belong to the band (a horizontal domain line). Optional. */
|
|
70
|
+
tracks?: LabelRowBox[];
|
|
71
|
+
}
|
|
72
|
+
export interface AxisPinPlan {
|
|
73
|
+
pin: boolean;
|
|
74
|
+
index: number;
|
|
75
|
+
band: AxisBand | null;
|
|
76
|
+
reason: string;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Which axis to pin, and the band it occupies. Coordinates are whatever the caller measured in;
|
|
80
|
+
* the plan only compares them, and `viewportH` (same units) bounds the band. With two horizontal
|
|
81
|
+
* axes the one with more labels wins - it is the one carrying the scale.
|
|
82
|
+
*/
|
|
83
|
+
export declare function planAxisPin(candidates: AxisPinCandidate[], viewportH: number, minLabels?: number, maxFraction?: number): AxisPinPlan;
|
|
84
|
+
export type AxisPinEdge = "top" | "bottom";
|
|
85
|
+
/**
|
|
86
|
+
* Where the pinned copy sits for this scroll position, or null when the original axis is in view
|
|
87
|
+
* and the copy must get out of the way. `band` is in CONTENT coordinates (the scroll origin), the
|
|
88
|
+
* viewport is [scrollTop, scrollTop + viewportH]. The copy appears as soon as the original is CUT
|
|
89
|
+
* by an edge, not only once it has fully left: a half-visible label row reads worse than a whole
|
|
90
|
+
* one drawn over it.
|
|
91
|
+
*/
|
|
92
|
+
export declare function axisPinPlacement(band: AxisBand, scrollTop: number, viewportH: number): AxisPinEdge | null;
|
package/dist/types/fitDom.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ContentExtent, MeasuredBox } from "./fit";
|
|
1
|
+
import type { ContentExtent, MeasuredBox, AxisPinEdge } from "./fit";
|
|
2
2
|
export declare function ctmScaleOf(m: {
|
|
3
3
|
a: number;
|
|
4
4
|
b: number;
|
|
@@ -43,6 +43,13 @@ export interface FitRenderedChartOptions {
|
|
|
43
43
|
* its own scrolling and only wants to be told.
|
|
44
44
|
*/
|
|
45
45
|
applyOverflow?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* When the container scrolls vertically, keep the chart's horizontal axis in view by pinning
|
|
48
|
+
* a copy of it at the viewport edge while the original is scrolled out. Default true. The
|
|
49
|
+
* copy hides itself whenever the original is on screen, so there is never a second axis.
|
|
50
|
+
* Set false for a host that draws its own sticky header.
|
|
51
|
+
*/
|
|
52
|
+
pinAxis?: boolean;
|
|
46
53
|
}
|
|
47
54
|
export interface FitRenderedChartResult {
|
|
48
55
|
/** The frame was grown, so content that was being clipped is now painted. */
|
|
@@ -55,5 +62,28 @@ export interface FitRenderedChartResult {
|
|
|
55
62
|
overflowY: "hidden" | "auto";
|
|
56
63
|
/** The post-grow reading, or null when nothing could be measured. */
|
|
57
64
|
reading: FitReading | null;
|
|
65
|
+
/** The axis pin: what was pinned when the container scrolls, or why nothing was. */
|
|
66
|
+
axisPin: AxisPinReport;
|
|
67
|
+
}
|
|
68
|
+
export interface AxisPinReport {
|
|
69
|
+
pinned: boolean;
|
|
70
|
+
/** "pinned", or the fail-open reason: not-scrolling, disabled, no-svg, no-ctm, no-axis,
|
|
71
|
+
* vertical-only, band-too-tall, unmeasurable, overflow-not-applied, threw. */
|
|
72
|
+
reason: string;
|
|
73
|
+
/** The pinned band's height on screen, px. */
|
|
74
|
+
bandPx: number;
|
|
75
|
+
/** Where the ORIGINAL axis sits in the chart body: top third, bottom third, or between. */
|
|
76
|
+
axisAt: "top" | "bottom" | "middle" | "";
|
|
77
|
+
/** Visible tick labels in the copy. */
|
|
78
|
+
labels: number;
|
|
79
|
+
/** Furniture texts that shared the band and came along (a "Today" caption, an axis title). */
|
|
80
|
+
carried: number;
|
|
81
|
+
/** Where the copy sat at the moment of the pass - null when the original was in view. */
|
|
82
|
+
edge: AxisPinEdge | null;
|
|
58
83
|
}
|
|
84
|
+
/** Remove a previous render's pinned axis - its overlay and its scroll listener. Safe to call
|
|
85
|
+
* when there is none. Hosts that clear the container themselves still need this for the
|
|
86
|
+
* listener, which outlives the overlay. */
|
|
87
|
+
export declare function unpinScrolledAxis(container: HTMLElement | null | undefined): void;
|
|
88
|
+
export declare function pinScrolledAxis(container: HTMLElement | null | undefined, svg: SVGSVGElement | null | undefined): AxisPinReport;
|
|
59
89
|
export declare function fitRenderedChart(container: HTMLElement | null | undefined, opts?: FitRenderedChartOptions): FitRenderedChartResult;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -22,3 +22,5 @@ export { censusMarks, isBlankRender, blankRenderFlag, type MarkCensus, type Blan
|
|
|
22
22
|
export { censusHitBands, hitBandFlag, MIN_HIT_BAND_PX, type HitBandCensus } from "./hitBands";
|
|
23
23
|
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";
|
|
24
24
|
export { fitRenderedChart, fitReadingFor, measureContainerBoxes, svgInkReach, ctmScaleOf, type FitReading, type InkReach, type FitRenderedChartOptions, type FitRenderedChartResult, } from "./fitDom";
|
|
25
|
+
export { AXIS_PIN_MIN_LABELS, AXIS_PIN_BAND_PAD_PX, AXIS_PIN_MAX_BAND_FRACTION, AXIS_PIN_TRACK_REACH_PX, isHorizontalLabelRow, labelBand, planAxisPin, axisPinPlacement, type LabelRowBox, type AxisBand, type AxisPinCandidate, type AxisPinPlan, type AxisPinEdge, } from "./fit";
|
|
26
|
+
export { pinScrolledAxis, unpinScrolledAxis, type AxisPinReport } 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.77",
|
|
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",
|