shap-svg 0.2.2 → 0.2.4

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/react.cjs CHANGED
@@ -1532,6 +1532,22 @@ function beeswarmLayout(valueRows, opts) {
1532
1532
  };
1533
1533
  }
1534
1534
 
1535
+ // src/core/tooltip.ts
1536
+ var CHAR_PX = 6.5;
1537
+ var PADDING_X = 7;
1538
+ var OFFSET = 8;
1539
+ var RAISE = 24;
1540
+ function placeTooltip(opts) {
1541
+ const { anchorX, anchorY, lines, lineHeight, minWidth, chartWidth, chartHeight } = opts;
1542
+ const longest = lines.reduce((max, line) => Math.max(max, line.length), 0);
1543
+ const width = Math.max(minWidth, longest * CHAR_PX + 2 * PADDING_X);
1544
+ const height = lines.length * lineHeight + lineHeight * 0.6;
1545
+ const right = anchorX + OFFSET;
1546
+ const x = right + width <= chartWidth ? right : Math.max(0, anchorX - OFFSET - width);
1547
+ const y = Math.max(0, Math.min(anchorY - RAISE, chartHeight - height));
1548
+ return { x, y, width, height };
1549
+ }
1550
+
1535
1551
  // src/react/ColorBar.tsx
1536
1552
  var import_jsx_runtime3 = require("react/jsx-runtime");
1537
1553
  function ColorBar({ bar }) {
@@ -1642,91 +1658,124 @@ function ShapBeeswarm({
1642
1658
  const fallbackRow = layout.rows[0];
1643
1659
  const activeRow = hovered ? layout.rows[hovered.rowIndex] : fallbackRow;
1644
1660
  const activePoint = hovered ? activeRow == null ? void 0 : activeRow.points[hovered.pointIndex] : activeRow == null ? void 0 : activeRow.points[0];
1645
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("svg", { width, height: layout.height, role: "img", "aria-label": "Global SHAP beeswarm", children: [
1646
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1647
- "line",
1648
- {
1649
- x1: layout.xZero,
1650
- x2: layout.xZero,
1651
- y1: marginTop,
1652
- y2: layout.plotBottom,
1653
- stroke: "#777777",
1654
- strokeWidth: 1
1655
- }
1656
- ),
1657
- layout.rows.map((row, rowIndex) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1658
- "g",
1659
- {
1660
- onMouseOver: (event) => handlePointHover(rowIndex, event),
1661
- onMouseLeave: () => setHovered(null),
1662
- onClick: () => onFeatureClick == null ? void 0 : onFeatureClick(row.featureIndex),
1663
- style: { cursor: onFeatureClick ? "pointer" : "default" },
1664
- children: [
1665
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1666
- "rect",
1667
- {
1668
- x: 0,
1669
- y: row.centerY - rowHeight / 2,
1670
- width,
1671
- height: rowHeight,
1672
- fill: (hovered == null ? void 0 : hovered.rowIndex) === rowIndex ? "#00000008" : "transparent"
1673
- }
1674
- ),
1675
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1676
- "text",
1677
- {
1678
- x: 250,
1679
- y: row.centerY,
1680
- textAnchor: "end",
1681
- dominantBaseline: "middle",
1682
- fontSize: 13,
1683
- fill: "#333333",
1684
- fontStyle: row.isOtherRow ? "normal" : "italic",
1685
- children: row.label
1686
- }
1687
- ),
1688
- row.points.map((point, pointIndex) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1689
- "circle",
1690
- {
1691
- "data-point-index": pointIndex,
1692
- cx: point.x,
1693
- cy: point.y,
1694
- r: point.radius,
1695
- fill: point.color,
1696
- fillOpacity: 0.7
1697
- },
1698
- `point-${point.sampleIndex}`
1699
- ))
1700
- ]
1701
- },
1702
- `row-${rowIndex}`
1703
- )),
1704
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1705
- XAxis,
1706
- {
1707
- ticks: layout.xTicks,
1708
- spine: layout.xSpine,
1709
- title: layout.xTitle,
1710
- plotBottom: layout.plotBottom,
1711
- tickFontSize: 11
1712
- }
1713
- ),
1714
- layout.colorBar && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ColorBar, { bar: layout.colorBar }),
1715
- activeRow && activePoint && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1716
- "g",
1717
- {
1718
- opacity: hovered ? 1 : 0,
1719
- pointerEvents: "none",
1720
- transform: `translate(${activePoint.x + 8} ${activePoint.y - 8})`,
1721
- children: [
1722
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { x: 0, y: -16, width: 210, height: 54, rx: 3, fill: "#ffffff", stroke: "#cccccc" }),
1723
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("text", { x: 7, y: 0, fontSize: 11, fill: "#222222", children: activeRow.label }),
1724
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("text", { x: 7, y: 15, fontSize: 11, fill: "#222222", children: `${words.shapValue}: ${formatShapValue(activePoint.valueX)}` }),
1725
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("text", { x: 7, y: 30, fontSize: 11, fill: "#222222", children: `${words.featureValue}: ${Number.isFinite(activePoint.featureValue) ? formatLevel(activePoint.featureValue) : words.missingFeatureValue}` })
1726
- ]
1727
- }
1728
- )
1729
- ] });
1661
+ const tooltipLines = activeRow && activePoint ? [
1662
+ activeRow.label,
1663
+ `${words.shapValue}: ${formatShapValue(activePoint.valueX)}`,
1664
+ // Unsigned: a feature value is an input, not a push in either direction.
1665
+ `${words.featureValue}: ${Number.isFinite(activePoint.featureValue) ? formatLevel(activePoint.featureValue) : words.missingFeatureValue}`
1666
+ ] : [];
1667
+ const tooltip = activePoint ? placeTooltip({
1668
+ anchorX: activePoint.x,
1669
+ anchorY: activePoint.y,
1670
+ lines: tooltipLines,
1671
+ lineHeight: 15,
1672
+ minWidth: 160,
1673
+ chartWidth: width,
1674
+ chartHeight: layout.height
1675
+ }) : null;
1676
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1677
+ "svg",
1678
+ {
1679
+ width,
1680
+ height: layout.height,
1681
+ role: "img",
1682
+ "aria-label": `${words.shapValue} of each feature, for every sample`,
1683
+ children: [
1684
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1685
+ "line",
1686
+ {
1687
+ x1: layout.xZero,
1688
+ x2: layout.xZero,
1689
+ y1: marginTop,
1690
+ y2: layout.plotBottom,
1691
+ stroke: "#777777",
1692
+ strokeWidth: 1
1693
+ }
1694
+ ),
1695
+ layout.rows.map((row, rowIndex) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1696
+ "g",
1697
+ {
1698
+ onMouseOver: (event) => handlePointHover(rowIndex, event),
1699
+ onMouseLeave: () => setHovered(null),
1700
+ onClick: () => onFeatureClick == null ? void 0 : onFeatureClick(row.featureIndex),
1701
+ style: { cursor: onFeatureClick ? "pointer" : "default" },
1702
+ children: [
1703
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1704
+ "rect",
1705
+ {
1706
+ x: 0,
1707
+ y: row.centerY - rowHeight / 2,
1708
+ width,
1709
+ height: rowHeight,
1710
+ fill: (hovered == null ? void 0 : hovered.rowIndex) === rowIndex ? "#00000008" : "transparent"
1711
+ }
1712
+ ),
1713
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1714
+ "text",
1715
+ {
1716
+ x: 250,
1717
+ y: row.centerY,
1718
+ textAnchor: "end",
1719
+ dominantBaseline: "middle",
1720
+ fontSize: 13,
1721
+ fill: "#333333",
1722
+ fontStyle: row.isOtherRow ? "normal" : "italic",
1723
+ children: row.label
1724
+ }
1725
+ ),
1726
+ row.points.map((point, pointIndex) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1727
+ "circle",
1728
+ {
1729
+ "data-point-index": pointIndex,
1730
+ cx: point.x,
1731
+ cy: point.y,
1732
+ r: point.radius,
1733
+ fill: point.color,
1734
+ fillOpacity: 0.7
1735
+ },
1736
+ `point-${point.sampleIndex}`
1737
+ ))
1738
+ ]
1739
+ },
1740
+ `row-${rowIndex}`
1741
+ )),
1742
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1743
+ XAxis,
1744
+ {
1745
+ ticks: layout.xTicks,
1746
+ spine: layout.xSpine,
1747
+ title: layout.xTitle,
1748
+ plotBottom: layout.plotBottom,
1749
+ tickFontSize: 11
1750
+ }
1751
+ ),
1752
+ layout.colorBar && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ColorBar, { bar: layout.colorBar }),
1753
+ tooltip && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1754
+ "g",
1755
+ {
1756
+ opacity: hovered ? 1 : 0,
1757
+ pointerEvents: "none",
1758
+ transform: `translate(${tooltip.x} ${tooltip.y})`,
1759
+ children: [
1760
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1761
+ "rect",
1762
+ {
1763
+ x: 0,
1764
+ y: 0,
1765
+ width: tooltip.width,
1766
+ height: tooltip.height,
1767
+ rx: 3,
1768
+ fill: "#ffffff",
1769
+ stroke: "#cccccc"
1770
+ }
1771
+ ),
1772
+ tooltipLines.map((line, index) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("text", { x: 7, y: 16 + index * 15, fontSize: 11, fill: "#222222", children: line }, `tooltip-${index}`))
1773
+ ]
1774
+ }
1775
+ )
1776
+ ]
1777
+ }
1778
+ );
1730
1779
  }
1731
1780
 
1732
1781
  // src/react/ShapHeatmap.tsx
@@ -2005,170 +2054,179 @@ function ShapHeatmap({
2005
2054
  };
2006
2055
  const tooltipWidth = activeColumn ? Math.max(190, nameOf(activeColumn).length * 6.5 + 16) : 190;
2007
2056
  const tooltipX = activeColumn ? Math.max(0, Math.min(activeColumn.centerX + 8, width - tooltipWidth - 8)) : 0;
2008
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("svg", { width, height: layout.height, role: "img", "aria-label": "Global SHAP heatmap", children: [
2009
- layout.fxAxisMarks.map((mark) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("g", { children: [
2010
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2011
- "line",
2012
- {
2013
- x1: layout.gridLeft - 4,
2014
- x2: layout.gridRight,
2015
- y1: mark.y,
2016
- y2: mark.y,
2017
- stroke: "#dddddd",
2018
- strokeWidth: 1
2019
- }
2020
- ),
2021
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2022
- "text",
2023
- {
2024
- x: layout.gridLeft - 8,
2025
- y: mark.y,
2026
- textAnchor: "end",
2027
- dominantBaseline: "middle",
2028
- fontSize: 10,
2029
- fill: "#666666",
2030
- children: mark.label
2031
- }
2032
- )
2033
- ] }, `fx-axis-${mark.value}`)),
2034
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2035
- "polyline",
2036
- {
2037
- points: layout.fxLine.map((point) => `${point.x},${point.y}`).join(" "),
2038
- fill: "none",
2039
- stroke: "#333333",
2040
- strokeWidth: 1.5
2041
- }
2042
- ),
2043
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2044
- "line",
2045
- {
2046
- x1: layout.gridLeft,
2047
- x2: layout.gridRight,
2048
- y1: layout.separatorY,
2049
- y2: layout.separatorY,
2050
- stroke: "#888888",
2051
- strokeWidth: 1,
2052
- strokeDasharray: "4 4"
2053
- }
2054
- ),
2055
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2056
- XAxis,
2057
- {
2058
- ticks: layout.xTicks,
2059
- spine: layout.xSpine,
2060
- title: layout.xTitle,
2061
- plotBottom: layout.plotBottom,
2062
- tickFontSize: 10
2063
- }
2064
- ),
2065
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("g", { "aria-hidden": "true", children: [
2066
- [layout.spines.left, layout.spines.right].map((spine, index) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2067
- "line",
2068
- {
2069
- x1: spine.x,
2070
- x2: spine.x,
2071
- y1: spine.y1,
2072
- y2: spine.y2,
2073
- stroke: "#333333",
2074
- strokeWidth: 1
2075
- },
2076
- `spine-${index}`
2077
- )),
2078
- layout.yTicks.map((tick, index) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2079
- "line",
2080
- {
2081
- x1: tick.x1,
2082
- x2: tick.x2,
2083
- y1: tick.y,
2084
- y2: tick.y,
2085
- stroke: "#333333",
2086
- strokeWidth: 1
2087
- },
2088
- `ytick-${index}`
2089
- ))
2090
- ] }),
2091
- layout.colorBar && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ColorBar, { bar: layout.colorBar }),
2092
- layout.rows.map((row, rowIndex) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2093
- "g",
2094
- {
2095
- onClick: () => onFeatureClick == null ? void 0 : onFeatureClick(row.featureIndex),
2096
- style: { cursor: onFeatureClick ? "pointer" : "default" },
2097
- children: [
2057
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2058
+ "svg",
2059
+ {
2060
+ width,
2061
+ height: layout.height,
2062
+ role: "img",
2063
+ "aria-label": `${words.shapValue} by feature and sample`,
2064
+ children: [
2065
+ layout.fxAxisMarks.map((mark) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("g", { children: [
2066
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2067
+ "line",
2068
+ {
2069
+ x1: layout.gridLeft - 4,
2070
+ x2: layout.gridRight,
2071
+ y1: mark.y,
2072
+ y2: mark.y,
2073
+ stroke: "#dddddd",
2074
+ strokeWidth: 1
2075
+ }
2076
+ ),
2098
2077
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2099
2078
  "text",
2100
2079
  {
2101
- x: 250,
2102
- y: row.centerY,
2080
+ x: layout.gridLeft - 8,
2081
+ y: mark.y,
2103
2082
  textAnchor: "end",
2104
2083
  dominantBaseline: "middle",
2105
- fontSize: 13,
2106
- fill: "#333333",
2107
- fontStyle: row.isOtherRow ? "normal" : "italic",
2108
- children: row.label
2084
+ fontSize: 10,
2085
+ fill: "#666666",
2086
+ children: mark.label
2109
2087
  }
2110
- ),
2111
- row.cells.map((cell, columnIndex) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2112
- "rect",
2088
+ )
2089
+ ] }, `fx-axis-${mark.value}`)),
2090
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2091
+ "polyline",
2092
+ {
2093
+ points: layout.fxLine.map((point) => `${point.x},${point.y}`).join(" "),
2094
+ fill: "none",
2095
+ stroke: "#333333",
2096
+ strokeWidth: 1.5
2097
+ }
2098
+ ),
2099
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2100
+ "line",
2101
+ {
2102
+ x1: layout.gridLeft,
2103
+ x2: layout.gridRight,
2104
+ y1: layout.separatorY,
2105
+ y2: layout.separatorY,
2106
+ stroke: "#888888",
2107
+ strokeWidth: 1,
2108
+ strokeDasharray: "4 4"
2109
+ }
2110
+ ),
2111
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2112
+ XAxis,
2113
+ {
2114
+ ticks: layout.xTicks,
2115
+ spine: layout.xSpine,
2116
+ title: layout.xTitle,
2117
+ plotBottom: layout.plotBottom,
2118
+ tickFontSize: 10
2119
+ }
2120
+ ),
2121
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("g", { "aria-hidden": "true", children: [
2122
+ [layout.spines.left, layout.spines.right].map((spine, index) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2123
+ "line",
2113
2124
  {
2114
- x: cell.x,
2115
- y: cell.y,
2116
- width: cell.width,
2117
- height: cell.height,
2118
- fill: cell.color
2125
+ x1: spine.x,
2126
+ x2: spine.x,
2127
+ y1: spine.y1,
2128
+ y2: spine.y2,
2129
+ stroke: "#333333",
2130
+ strokeWidth: 1
2119
2131
  },
2120
- `cell-${columnIndex}`
2132
+ `spine-${index}`
2121
2133
  )),
2122
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2123
- "rect",
2134
+ layout.yTicks.map((tick, index) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2135
+ "line",
2124
2136
  {
2125
- x: row.sideBar.x,
2126
- y: row.sideBar.y,
2127
- width: row.sideBar.width,
2128
- height: row.sideBar.height,
2129
- fill: "#777777"
2130
- }
2131
- )
2132
- ]
2133
- },
2134
- `row-${rowIndex}`
2135
- )),
2136
- activeColumn && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2137
- "rect",
2138
- {
2139
- x: activeColumn.x,
2140
- y: 8,
2141
- width: activeColumn.width,
2142
- height: layout.plotBottom - 8,
2143
- fill: "#000000",
2144
- fillOpacity: 0.1,
2145
- pointerEvents: "none"
2146
- }
2147
- ),
2148
- layout.columns.map((column, columnIndex) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2149
- "rect",
2150
- {
2151
- x: column.x,
2152
- y: 8,
2153
- width: column.width,
2154
- height: layout.plotBottom - 8,
2155
- fill: "transparent",
2156
- "aria-label": `${nameOf(column)}, total ${words.shapValue} ${formatShapValue(column.total)}`,
2157
- onMouseEnter: () => setHoveredColumn(columnIndex),
2158
- onMouseLeave: () => setHoveredColumn(null),
2159
- onClick: () => {
2160
- if (column.sampleId !== void 0) onSampleClick == null ? void 0 : onSampleClick(column.sampleId);
2161
- },
2162
- style: { cursor: column.sampleId && onSampleClick ? "pointer" : "default" }
2163
- },
2164
- `column-hit-${column.sampleIndex}`
2165
- )),
2166
- activeColumn && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("g", { pointerEvents: "none", transform: `translate(${tooltipX} 10)`, children: [
2167
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("rect", { x: 0, y: 0, width: tooltipWidth, height: 42, rx: 3, fill: "#ffffff", stroke: "#cccccc" }),
2168
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("text", { x: 7, y: 15, fontSize: 11, fill: "#222222", children: nameOf(activeColumn) }),
2169
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("text", { x: 7, y: 31, fontSize: 11, fill: "#222222", children: `${words.sampleTotal}: ${formatShapValue(activeColumn.total)}` })
2170
- ] })
2171
- ] });
2137
+ x1: tick.x1,
2138
+ x2: tick.x2,
2139
+ y1: tick.y,
2140
+ y2: tick.y,
2141
+ stroke: "#333333",
2142
+ strokeWidth: 1
2143
+ },
2144
+ `ytick-${index}`
2145
+ ))
2146
+ ] }),
2147
+ layout.colorBar && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ColorBar, { bar: layout.colorBar }),
2148
+ layout.rows.map((row, rowIndex) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2149
+ "g",
2150
+ {
2151
+ onClick: () => onFeatureClick == null ? void 0 : onFeatureClick(row.featureIndex),
2152
+ style: { cursor: onFeatureClick ? "pointer" : "default" },
2153
+ children: [
2154
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2155
+ "text",
2156
+ {
2157
+ x: 250,
2158
+ y: row.centerY,
2159
+ textAnchor: "end",
2160
+ dominantBaseline: "middle",
2161
+ fontSize: 13,
2162
+ fill: "#333333",
2163
+ fontStyle: row.isOtherRow ? "normal" : "italic",
2164
+ children: row.label
2165
+ }
2166
+ ),
2167
+ row.cells.map((cell, columnIndex) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2168
+ "rect",
2169
+ {
2170
+ x: cell.x,
2171
+ y: cell.y,
2172
+ width: cell.width,
2173
+ height: cell.height,
2174
+ fill: cell.color
2175
+ },
2176
+ `cell-${columnIndex}`
2177
+ )),
2178
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2179
+ "rect",
2180
+ {
2181
+ x: row.sideBar.x,
2182
+ y: row.sideBar.y,
2183
+ width: row.sideBar.width,
2184
+ height: row.sideBar.height,
2185
+ fill: "#777777"
2186
+ }
2187
+ )
2188
+ ]
2189
+ },
2190
+ `row-${rowIndex}`
2191
+ )),
2192
+ activeColumn && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2193
+ "rect",
2194
+ {
2195
+ x: activeColumn.x,
2196
+ y: 8,
2197
+ width: activeColumn.width,
2198
+ height: layout.plotBottom - 8,
2199
+ fill: "#000000",
2200
+ fillOpacity: 0.1,
2201
+ pointerEvents: "none"
2202
+ }
2203
+ ),
2204
+ layout.columns.map((column, columnIndex) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2205
+ "rect",
2206
+ {
2207
+ x: column.x,
2208
+ y: 8,
2209
+ width: column.width,
2210
+ height: layout.plotBottom - 8,
2211
+ fill: "transparent",
2212
+ "aria-label": `${nameOf(column)}, total ${words.shapValue} ${formatShapValue(column.total)}`,
2213
+ onMouseEnter: () => setHoveredColumn(columnIndex),
2214
+ onMouseLeave: () => setHoveredColumn(null),
2215
+ onClick: () => {
2216
+ if (column.sampleId !== void 0) onSampleClick == null ? void 0 : onSampleClick(column.sampleId);
2217
+ },
2218
+ style: { cursor: column.sampleId && onSampleClick ? "pointer" : "default" }
2219
+ },
2220
+ `column-hit-${column.sampleIndex}`
2221
+ )),
2222
+ activeColumn && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("g", { pointerEvents: "none", transform: `translate(${tooltipX} 10)`, children: [
2223
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("rect", { x: 0, y: 0, width: tooltipWidth, height: 42, rx: 3, fill: "#ffffff", stroke: "#cccccc" }),
2224
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("text", { x: 7, y: 15, fontSize: 11, fill: "#222222", children: nameOf(activeColumn) }),
2225
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("text", { x: 7, y: 31, fontSize: 11, fill: "#222222", children: `${words.sampleTotal}: ${formatShapValue(activeColumn.total)}` })
2226
+ ] })
2227
+ ]
2228
+ }
2229
+ );
2172
2230
  }
2173
2231
 
2174
2232
  // src/react/ShapWaterfall.tsx
@@ -2372,11 +2430,14 @@ function ShapWaterfall({
2372
2430
  const [hovered, setHovered] = (0, import_react4.useState)(null);
2373
2431
  const words = (0, import_react4.useMemo)(() => resolveLabels(labels), [labels]);
2374
2432
  const marginTop = 34;
2375
- const layout = (0, import_react4.useMemo)(() => {
2433
+ const { layout, sampleName } = (0, import_react4.useMemo)(() => {
2434
+ var _a;
2376
2435
  const raw = parseExplanation(explanation, { classIndex });
2377
2436
  const parsed = groupByGenus2 ? groupExplanationByGenus(raw) : raw;
2378
2437
  const rows = waterfallRows(parsed, sampleIndex, maxDisplay, faithfulOtherRow, words);
2379
- return waterfallLayout(rows, {
2438
+ const sampleLabel = (_a = raw.sampleLabels) == null ? void 0 : _a[sampleIndex];
2439
+ const sampleName2 = sampleLabel ? raw.sampleLabelColumn ? `${raw.sampleLabelColumn}: ${sampleLabel}` : sampleLabel : words.sampleFallback(sampleIndex + 1);
2440
+ const layout2 = waterfallLayout(rows, {
2380
2441
  width,
2381
2442
  rowHeight,
2382
2443
  marginLeft: 260,
@@ -2385,6 +2446,7 @@ function ShapWaterfall({
2385
2446
  decimals,
2386
2447
  labels: words
2387
2448
  });
2449
+ return { layout: layout2, sampleName: sampleName2 };
2388
2450
  }, [
2389
2451
  groupByGenus2,
2390
2452
  explanation,
@@ -2403,7 +2465,7 @@ function ShapWaterfall({
2403
2465
  width,
2404
2466
  height: layout.height,
2405
2467
  role: "img",
2406
- "aria-label": `Local SHAP waterfall for Sample ${sampleIndex}`,
2468
+ "aria-label": `${words.shapValue} of each feature for ${sampleName}`,
2407
2469
  children: [
2408
2470
  layout.separators.map((separator, index) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2409
2471
  "line",