shap-svg 0.2.2 → 0.2.3

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.
@@ -1278,6 +1278,22 @@ function scalarFormatterLabels(locs) {
1278
1278
  return order === 0 ? { labels } : { labels, offsetText: `1e${String(order).replace("-", MINUS3)}` };
1279
1279
  }
1280
1280
 
1281
+ // src/core/tooltip.ts
1282
+ var CHAR_PX = 6.5;
1283
+ var PADDING_X = 7;
1284
+ var OFFSET = 8;
1285
+ var RAISE = 24;
1286
+ function placeTooltip(opts) {
1287
+ const { anchorX, anchorY, lines, lineHeight, minWidth, chartWidth, chartHeight } = opts;
1288
+ const longest = lines.reduce((max, line) => Math.max(max, line.length), 0);
1289
+ const width = Math.max(minWidth, longest * CHAR_PX + 2 * PADDING_X);
1290
+ const height = lines.length * lineHeight + lineHeight * 0.6;
1291
+ const right = anchorX + OFFSET;
1292
+ const x = right + width <= chartWidth ? right : Math.max(0, anchorX - OFFSET - width);
1293
+ const y = Math.max(0, Math.min(anchorY - RAISE, chartHeight - height));
1294
+ return { x, y, width, height };
1295
+ }
1296
+
1281
1297
  // src/core/beeswarmLayout.ts
1282
1298
  var BEESWARM_MISSING_COLOR = "#777777";
1283
1299
  var BEESWARM_ROW_HEIGHT = 0.4;
@@ -1757,6 +1773,7 @@ export {
1757
1773
  fitColorBar,
1758
1774
  colorBarLayout,
1759
1775
  scalarFormatterLabels,
1776
+ placeTooltip,
1760
1777
  BEESWARM_MISSING_COLOR,
1761
1778
  BEESWARM_ROW_HEIGHT,
1762
1779
  beeswarmRows,
@@ -1764,4 +1781,4 @@ export {
1764
1781
  heatmapRows,
1765
1782
  heatmapLayout
1766
1783
  };
1767
- //# sourceMappingURL=chunk-4X6PMJYT.js.map
1784
+ //# sourceMappingURL=chunk-UVIVTJK3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/types.ts","../src/core/taxonomy.ts","../src/core/rowSort.ts","../src/core/parse.ts","../src/core/order.ts","../src/core/format.ts","../src/core/labels.ts","../src/core/collapse.ts","../src/core/ticks.ts","../src/core/barLayout.ts","../src/core/waterfallLayout.ts","../src/core/colormaps.json","../src/core/colormap.ts","../src/core/colorBar.ts","../src/core/tooltip.ts","../src/core/beeswarmLayout.ts","../src/core/heatmapLayout.ts"],"sourcesContent":["/** The wire payload. Core fields are named exactly as shap.Explanation names them. */\nexport type Explanation = {\n contract_version: number;\n values: number[][] | number[][][];\n base_values: number | number[] | number[][];\n data: number[][];\n feature_names: string[];\n sample_ids?: string[];\n /** n — what a person reads for each Sample; never a join key. */\n sample_labels?: string[];\n /** Header of the uploaded column the labels came from, when it had one. */\n sample_label_column?: string;\n output_names?: string[];\n model_name?: string;\n model_version?: string;\n};\n\n/** A validated Explanation with the class axis resolved away. */\nexport type ParsedExplanation = {\n /** n x p, class already selected */\n values: number[][];\n /** n x p */\n data: number[][];\n /** length n — always per Sample, even when the wire form was a scalar */\n baseValues: number[];\n /** length p */\n featureNames: string[];\n /** length n, undefined when the payload omitted it */\n sampleIds?: string[];\n /** length n — display names. sampleIds stays the key for joins and click-through. */\n sampleLabels?: string[];\n sampleLabelColumn?: string;\n outputName?: string;\n nSamples: number;\n nFeatures: number;\n};\n\n/** One row of a chart: either a real Feature or the collapsed Other features row. */\nexport type DisplayRow = {\n label: string;\n /** index into ParsedExplanation.featureNames, or null for the Other features row */\n featureIndex: number | null;\n /** the value this row draws — for the bar chart, mean(|phi|) */\n value: number;\n isOtherRow: boolean;\n};\n\nexport type DisplayRows = {\n rows: DisplayRow[];\n /** how many Features were folded into the Other features row; 0 when none */\n collapsedCount: number;\n};\n\nexport class UnsupportedContractVersionError extends Error {\n constructor(public readonly received: unknown) {\n super(`shap-svg supports contract_version 1, received ${JSON.stringify(received)}`);\n this.name = \"UnsupportedContractVersionError\";\n }\n}\n\nexport class InvalidExplanationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"InvalidExplanationError\";\n }\n}\n","import { ParsedExplanation } from \"./types\";\n\n/**\n * Taxonomy-aware views of an Explanation.\n *\n * Feature names in this platform are `Genus_species`, which is a real structure\n * the generic charts ignore. At the species level a genus the model genuinely\n * uses can be spread across a dozen columns, each with a SHAP value small\n * enough to read as noise, while the genus as a whole is one of the strongest\n * signals present. The runtime already offers the same collapse behind\n * `?aggregate_by=genus`; doing it here instead costs no request, because the\n * per-species values are already in the browser.\n */\n\n/** The genus part of a `Genus_species` name. */\nexport function genusOf(featureName: string): string {\n const underscore = featureName.indexOf(\"_\");\n return underscore === -1 ? featureName : featureName.slice(0, underscore);\n}\n\nexport type GenusGrouping = {\n /** Genera in first-seen order. */\n genera: string[];\n /** For each genus, the Feature indices belonging to it. */\n memberIndices: number[][];\n};\n\n/**\n * Group Feature indices by genus, preserving first-seen order.\n *\n * Order matters: it is what keeps the grouping stable across requests, and it\n * is the same rule `_aggregate_shap_by_genus` uses in the runtime, so the two\n * paths cannot disagree about which column belongs where.\n */\nexport function groupByGenus(featureNames: string[]): GenusGrouping {\n const genera: string[] = [];\n const memberIndices: number[][] = [];\n const seen = new Map<string, number>();\n\n featureNames.forEach((name, index) => {\n const genus = genusOf(name);\n let slot = seen.get(genus);\n if (slot === undefined) {\n slot = genera.length;\n seen.set(genus, slot);\n genera.push(genus);\n memberIndices.push([]);\n }\n memberIndices[slot].push(index);\n });\n\n return { genera, memberIndices };\n}\n\nexport type AggregatedExplanation = {\n values: number[][];\n data: number[][];\n featureNames: string[];\n};\n\n/**\n * Collapse `Genus_species` columns into one column per genus.\n *\n * Summing is the correct operator, not averaging: SHAP is additive, so the\n * per-Feature values satisfy `base + sum(phi) = f(x)`. A sum within groups is a\n * re-partition of that same total, which leaves the identity intact — every\n * waterfall still adds up, and there is a test saying so. Abundance is summed\n * for the same reason, giving the genus's total relative abundance, which is\n * what the colour should mean once the rows are genera.\n */\nexport function aggregateByGenus(\n values: number[][],\n data: number[][],\n featureNames: string[],\n): AggregatedExplanation {\n const { genera, memberIndices } = groupByGenus(featureNames);\n const sumInto = (rows: number[][]) =>\n rows.map((row) =>\n memberIndices.map((members) =>\n members.reduce((total, index) => total + (row[index] ?? 0), 0),\n ),\n );\n\n return {\n values: sumInto(values),\n data: sumInto(data),\n featureNames: genera,\n };\n}\n\n/**\n * A parsed Explanation with its Feature axis collapsed to genera.\n *\n * Only the Feature axis changes. Base values, Sample ids and the Sample count\n * carry through untouched, so everything downstream — ordering, the Other row,\n * the waterfall walk — runs unchanged on genera, and `base + sum(phi) = f(x)`\n * still holds for every Sample. The input is not mutated.\n */\nexport function groupExplanationByGenus(explanation: ParsedExplanation): ParsedExplanation {\n const grouped = aggregateByGenus(\n explanation.values,\n explanation.data,\n explanation.featureNames,\n );\n return {\n ...explanation,\n values: grouped.values,\n data: grouped.data,\n featureNames: grouped.featureNames,\n nFeatures: grouped.featureNames.length,\n };\n}\n","import { DisplayRows } from \"./types\";\n\n/**\n * The order the displayed Feature rows are drawn in.\n *\n * `importance` is SHAP's own order and the default. The other two only\n * *reorder* the rows importance already chose — they never change which\n * Features are shown. Sorting every Feature by name and taking the first N\n * would show whatever happens to begin with A, not what the model relies on.\n */\nexport type RowSort = \"importance\" | \"name\" | \"featureValue\";\n\n/**\n * Reorder a collapsed set of rows.\n *\n * * `name` — alphabetical, ignoring case.\n * * `featureValue` — mean feature value across Samples, highest first. For\n * this platform that is mean relative abundance, so the most abundant taxa\n * sit on top.\n *\n * Ties keep importance order, so a sort is stable and repeatable. The Other\n * row is not a Feature and always stays last.\n */\nexport function sortDisplayRows(\n display: DisplayRows,\n sort: RowSort,\n data: number[][],\n): DisplayRows {\n if (sort === \"importance\") return display;\n\n const meanOf = (featureIndex: number) =>\n data.length === 0\n ? 0\n : data.reduce((sum, sample) => sum + sample[featureIndex], 0) / data.length;\n\n const keyed = display.rows\n .filter((row) => !row.isOtherRow)\n .map((row, rank) => ({\n row,\n rank,\n mean: row.featureIndex === null ? 0 : meanOf(row.featureIndex),\n }));\n\n keyed.sort((a, b) => {\n const primary = sort === \"name\"\n ? a.row.label.localeCompare(b.row.label, undefined, { sensitivity: \"base\" })\n : b.mean - a.mean;\n return primary || a.rank - b.rank;\n });\n\n return {\n ...display,\n rows: [...keyed.map((k) => k.row), ...display.rows.filter((row) => row.isOtherRow)],\n };\n}\n","import {\n Explanation,\n InvalidExplanationError,\n ParsedExplanation,\n UnsupportedContractVersionError,\n} from \"./types\";\n\nconst SUPPORTED_CONTRACT_VERSION = 1;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\nfunction assertFiniteRows(rows: unknown, field: string): asserts rows is number[][] {\n if (!Array.isArray(rows)) {\n throw new InvalidExplanationError(`${field} must be an array of Sample rows`);\n }\n for (let i = 0; i < rows.length; i++) {\n const row = rows[i];\n if (!Array.isArray(row)) {\n throw new InvalidExplanationError(`${field}[${i}] must be an array of Feature values`);\n }\n for (let j = 0; j < row.length; j++) {\n if (typeof row[j] !== \"number\" || !Number.isFinite(row[j])) {\n throw new InvalidExplanationError(\n `${field}[${i}][${j}] is ${String(row[j])}; the payload must contain only finite numbers`,\n );\n }\n }\n }\n}\n\nfunction assertStringArray(value: unknown, field: string): asserts value is string[] {\n if (!Array.isArray(value) || value.some((entry) => typeof entry !== \"string\")) {\n throw new InvalidExplanationError(`${field} must be an array of strings`);\n }\n}\n\nfunction selectClassValues(values: unknown, classIndex: number): number[][] {\n if (!Array.isArray(values)) {\n throw new InvalidExplanationError(\"values must be an array\");\n }\n if (!Number.isInteger(classIndex) || classIndex < 0) {\n throw new InvalidExplanationError(`classIndex must be a non-negative integer, received ${classIndex}`);\n }\n const firstRow = values[0];\n const firstCell = Array.isArray(firstRow) ? firstRow[0] : undefined;\n if (Array.isArray(firstCell)) {\n return values.map((row, sampleIndex) => {\n if (!Array.isArray(row)) {\n throw new InvalidExplanationError(`values[${sampleIndex}] must be an array of Feature values`);\n }\n return row.map((cell, featureIndex) => {\n if (!Array.isArray(cell) || classIndex >= cell.length) {\n throw new InvalidExplanationError(\n `values[${sampleIndex}][${featureIndex}] has no class ${classIndex}`,\n );\n }\n return cell[classIndex] as number;\n });\n });\n }\n return values as number[][];\n}\n\nfunction selectBaseValues(value: unknown, nSamples: number, classIndex: number): number[] {\n if (typeof value === \"number\") {\n return new Array(nSamples).fill(value);\n }\n if (!Array.isArray(value)) {\n throw new InvalidExplanationError(\"base_values must be a number, an array, or an array of class arrays\");\n }\n if (Array.isArray(value[0])) {\n return value.map((row, sampleIndex) => {\n if (!Array.isArray(row) || classIndex >= row.length) {\n throw new InvalidExplanationError(`base_values[${sampleIndex}] has no class ${classIndex}`);\n }\n return row[classIndex] as number;\n });\n }\n return value as number[];\n}\n\nexport function parseExplanation(\n input: unknown,\n opts: { classIndex?: number } = {},\n): ParsedExplanation {\n const classIndex = opts.classIndex ?? 1;\n if (!isRecord(input) || input.contract_version !== SUPPORTED_CONTRACT_VERSION) {\n throw new UnsupportedContractVersionError(isRecord(input) ? input.contract_version : undefined);\n }\n\n const e = input as Explanation;\n assertStringArray(e.feature_names, \"feature_names\");\n assertFiniteRows(e.data, \"data\");\n const values = selectClassValues(e.values, classIndex);\n assertFiniteRows(values, \"values\");\n\n const nSamples = values.length;\n const nFeatures = e.feature_names.length;\n if (e.data.length !== nSamples) {\n throw new InvalidExplanationError(`data has ${e.data.length} Samples but values has ${nSamples}`);\n }\n for (const [name, rows] of [[\"values\", values], [\"data\", e.data]] as const) {\n for (let i = 0; i < rows.length; i++) {\n if (rows[i].length !== nFeatures) {\n throw new InvalidExplanationError(\n `${name}[${i}] has ${rows[i].length} Features but feature_names has ${nFeatures}`,\n );\n }\n }\n }\n\n const baseValues = selectBaseValues(e.base_values, nSamples, classIndex);\n if (baseValues.length !== nSamples) {\n throw new InvalidExplanationError(\n `base_values has ${baseValues.length} entries but there are ${nSamples} Samples`,\n );\n }\n assertFiniteRows([baseValues], \"base_values\");\n\n if (e.sample_ids !== undefined) {\n assertStringArray(e.sample_ids, \"sample_ids\");\n if (e.sample_ids.length !== nSamples) {\n throw new InvalidExplanationError(\n `sample_ids has ${e.sample_ids.length} entries but there are ${nSamples} Samples`,\n );\n }\n }\n if (e.sample_labels !== undefined) {\n assertStringArray(e.sample_labels, \"sample_labels\");\n if (e.sample_labels.length !== nSamples) {\n throw new InvalidExplanationError(\n `sample_labels has ${e.sample_labels.length} entries but there are ${nSamples} Samples`,\n );\n }\n }\n if (e.sample_label_column !== undefined && typeof e.sample_label_column !== \"string\") {\n throw new InvalidExplanationError(\"sample_label_column must be a string\");\n }\n if (e.output_names !== undefined) assertStringArray(e.output_names, \"output_names\");\n\n return {\n values,\n data: e.data,\n baseValues,\n featureNames: e.feature_names,\n sampleIds: e.sample_ids,\n sampleLabels: e.sample_labels,\n sampleLabelColumn: e.sample_label_column,\n outputName: e.output_names?.[0],\n nSamples,\n nFeatures,\n };\n}\n","import { ParsedExplanation } from \"./types\";\n\n/** mean(|phi|) over Samples, per Feature — what shap.plots.bar collapses a 2-D Explanation to. */\nexport function globalImportance(e: ParsedExplanation): number[] {\n const out = new Array<number>(e.nFeatures).fill(0);\n for (const row of e.values) {\n for (let j = 0; j < e.nFeatures; j++) out[j] += Math.abs(row[j]);\n }\n return out.map((sum) => sum / e.nSamples);\n}\n\n/** Descending by importance; ties resolved by ascending index so renders are reproducible. */\nexport function orderFeatures(importance: number[]): number[] {\n return importance\n .map((value, index) => ({ value, index }))\n .sort((a, b) => (b.value - a.value) || (a.index - b.index))\n .map((entry) => entry.index);\n}\n","const MINUS = \"−\";\n/** Below this magnitude, fixed notation would round away to zero at our precision. */\nconst EXPONENT_THRESHOLD = 1e-3;\n\n/** How the value labels are written. Display only. */\nexport type ValuePrecision = 2 | 3 | 4 | \"percent\";\n\n/** Decimal places a percentage is shown to. */\nconst PERCENT_DECIMALS = 2;\n\n/**\n * Spec 3.5 V1. SHAP's \"%0.03f\" renders most relative-abundance-scale values as \"0\" or \"-0\";\n * this keeps them readable and always signs the value so a bar's direction is unambiguous.\n *\n * `decimals` fixes how the value is written for display. It does not change any\n * value that is computed from — or compared against — the payload; it only\n * changes the glyphs. Values too small to survive at that precision still fall\n * back to an exponent rather than collapsing to a signed zero, which is the\n * whole point of V1: \"+0.00\" hides both the magnitude and the direction.\n *\n * \"percent\" moves the decimal point two places and adds a sign. It reaches a\n * range the fixed-decimal settings cannot: a contribution of 0.0003 is an\n * exponent at two decimals but an ordinary 0.03%, which is most of this data.\n */\nexport function formatShapValue(v: number, decimals?: ValuePrecision): string {\n if (v === 0) return \"0\";\n return (v < 0 ? MINUS : \"+\") + magnitudeOf(v, decimals);\n}\n\n/**\n * A value on the model's output scale: `E[f(X)]` and `f(x)`.\n *\n * Unsigned, unlike a contribution. SHAP draws the same distinction —\n * `_waterfall.py:327,339` format these with `\"%0.03f\"` and only the bar\n * contributions with `\"%+0.02f\"` — and it matters: a leading \"+\" on a model\n * output reads as \"went up by\", when the number is where the prediction landed,\n * not how far it moved. A minus is still kept, because nothing guarantees a\n * model output is a probability.\n */\nexport function formatLevel(v: number, decimals?: ValuePrecision): string {\n if (v === 0) return \"0\";\n return (v < 0 ? MINUS : \"\") + magnitudeOf(v, decimals);\n}\n\n/** The digits both formatters share; the caller owns the sign. */\nfunction magnitudeOf(v: number, decimals?: ValuePrecision): string {\n const magnitude = Math.abs(v);\n\n if (decimals === undefined) {\n return magnitude < EXPONENT_THRESHOLD\n ? magnitude.toExponential(0)\n : String(Number(magnitude.toPrecision(3)));\n }\n\n const places = decimals === \"percent\" ? PERCENT_DECIMALS : decimals;\n const scaled = decimals === \"percent\" ? magnitude * 100 : magnitude;\n const unit = decimals === \"percent\" ? \"%\" : \"\";\n\n // Half of the last retained place: anything under it rounds to all zeros.\n // The unit is dropped along with the fixed notation, because \"1e-7%\" reads\n // as a percentage of a percentage.\n if (scaled < 0.5 * 10 ** -places) return magnitude.toExponential(0);\n return scaled.toFixed(places) + unit;\n}\n\n/** Spec 3.5 V3. The italic styling is applied by the renderer, not here. */\nexport function formatFeatureLabel(name: string): string {\n return name.replace(/_/g, \" \");\n}\n","/**\n * Every piece of text the charts draw, keyed by what it means rather than where\n * it appears, so a word used in two places is changed once.\n *\n * The defaults are SHAP 0.49.1's own wording: a chart given no labels reads the\n * way SHAP's figures do.\n */\nexport type PlotLabels = {\n /** The value itself, as the beeswarm tooltip names it. */\n shapValue: string;\n /** The axis that measures it: beeswarm x axis and heatmap colour bar (_labels.py VALUE). */\n shapValueAxis: string;\n /** The bar chart's x axis. */\n meanAbsShapValue: string;\n /** A Feature's input value: beeswarm tooltip and colour bar title. */\n featureValue: string;\n /** The low end of the beeswarm colour bar. */\n featureValueLow: string;\n /** The high end of the beeswarm colour bar. */\n featureValueHigh: string;\n /** Written in place of a Feature value the payload does not have. */\n missingFeatureValue: string;\n /** The heatmap's Sample axis. */\n samples: string;\n /** A Sample's summed SHAP values, in the heatmap tooltip. */\n sampleTotal: string;\n /** A Sample the payload gives no label, numbered from 1. */\n sampleFallback: (sampleNumber: number) => string;\n /** The waterfall's starting point, E[f(X)]. */\n baseValue: string;\n /** The waterfall's end point, f(x). */\n modelOutput: string;\n /**\n * The row standing in for every Feature not shown. `style` is \"sum\" for the\n * bar, beeswarm and heatmap row in faithfulOtherRow mode, which SHAP titles as\n * a sum, and \"count\" everywhere else.\n */\n otherFeatures: (count: number, style: \"sum\" | \"count\") => string;\n};\n\nexport const shapLabels: PlotLabels = {\n shapValue: \"SHAP value\",\n shapValueAxis: \"SHAP value (impact on model output)\",\n meanAbsShapValue: \"mean(|SHAP value|)\",\n featureValue: \"Feature value\",\n featureValueLow: \"Low\",\n featureValueHigh: \"High\",\n missingFeatureValue: \"missing\",\n samples: \"Instances\",\n sampleTotal: \"Σφ\",\n sampleFallback: (sampleNumber) => `Sample ${sampleNumber}`,\n baseValue: \"E[f(X)]\",\n modelOutput: \"f(x)\",\n otherFeatures: (count, style) =>\n style === \"sum\" ? `Sum of ${count} other features` : `${count} other features`,\n};\n\n/** The given wording over SHAP's. A key given as undefined keeps the default. */\nexport function resolveLabels(labels?: Partial<PlotLabels>): PlotLabels {\n if (!labels) return shapLabels;\n const resolved: PlotLabels = { ...shapLabels };\n for (const key of Object.keys(labels) as (keyof PlotLabels)[]) {\n const value = labels[key];\n if (value !== undefined) (resolved as Record<keyof PlotLabels, unknown>)[key] = value;\n }\n return resolved;\n}\n","import { DisplayRow, DisplayRows } from \"./types\";\nimport { formatFeatureLabel } from \"./format\";\nimport { PlotLabels, shapLabels } from \"./labels\";\n\n/**\n * Spec 3.4. `faithfulOtherRow` reproduces shap/plots/_bar.py:228-241, where the last displayed row\n * absorbs the Feature ranked `maxDisplay` — so maxDisplay=15 shows 14 real Features. The default\n * corrected mode shows `maxDisplay` real Features and adds the Other features row alongside.\n */\nexport function collapseToDisplay(\n featureNames: string[],\n importance: number[],\n order: number[],\n maxDisplay: number,\n faithfulOtherRow: boolean,\n labels: PlotLabels = shapLabels,\n): DisplayRows {\n const p = order.length;\n\n if (maxDisplay >= p) {\n return {\n rows: order.map((index) => ({\n label: formatFeatureLabel(featureNames[index]),\n featureIndex: index,\n value: importance[index],\n isOtherRow: false,\n })),\n collapsedCount: 0,\n };\n }\n\n const realCount = faithfulOtherRow ? maxDisplay - 1 : maxDisplay;\n const rows: DisplayRow[] = order.slice(0, realCount).map((index) => ({\n label: formatFeatureLabel(featureNames[index]),\n featureIndex: index,\n value: importance[index],\n isOtherRow: false,\n }));\n\n const collapsed = order.slice(realCount);\n const collapsedValue = collapsed.reduce((sum, index) => sum + importance[index], 0);\n rows.push({\n label: labels.otherFeatures(collapsed.length, faithfulOtherRow ? \"sum\" : \"count\"),\n featureIndex: null,\n value: collapsedValue,\n isOtherRow: true,\n });\n\n return { rows, collapsedCount: collapsed.length };\n}\n","/**\n * Axis ticks, chosen the way matplotlib chooses them for SHAP's figures.\n *\n * Two rules, both read from the installed matplotlib rather than approximated:\n *\n * * How many ticks fit. `XAxis.get_tick_space` (axis.py) is\n * `floor(axis_length_pt / (tick_label_size_pt * 3))` — the 3 is its estimate\n * of a tick label's aspect ratio — and `MaxNLocator` (ticker.py) clips that to\n * at most 9. Our axis length is in CSS pixels, which are 0.75 pt; the label\n * size is SHAP's own, in points.\n * * Which step. The smallest of 1, 2, 2.5, 5 or 10 times a power of ten that\n * covers the span in that many intervals.\n *\n * Evaluated at the size each chart is drawn inline, this reproduces the ticks on\n * SHAP's own reference figures for all three of this platform's charts — see\n * tests/ticks.test.ts. Tick density still follows axis width, as it does in\n * matplotlib, so the expanded view shows finer ticks than the inline one.\n */\n\nconst MINUS = \"−\";\n/** CSS defines 1px as 0.75pt. */\nconst PX_TO_PT = 0.75;\n/** MaxNLocator's upper bound on intervals when nbins is \"auto\". */\nconst MAX_TICKS = 9;\nconst LADDER = [1, 2, 2.5, 5, 10];\n\n/** matplotlib's default major tick, 3.5 pt, rounded to whole pixels. */\nexport const TICK_LENGTH = 5;\n/** Baseline of the tick labels below the axis. */\nexport const TICK_LABEL_DY = 18;\n/** Baseline of the axis title below the axis. */\nexport const AXIS_TITLE_DY = 38;\n\nexport type AxisTick = { value: number; x: number; label: string };\nexport type AxisSpine = { x1: number; x2: number; y: number };\nexport type AxisTitle = { text: string; x: number; y: number; fontSize: number };\n\n/** How many tick intervals fit along an axis, as matplotlib estimates it. */\nexport function tickSpace(axisPx: number, labelPt: number): number {\n const space = Math.floor((axisPx * PX_TO_PT) / (labelPt * 3));\n return Math.min(MAX_TICKS, Math.max(1, space));\n}\n\n/**\n * Round tick values across `[min, max]`, in at most `maxTicks` intervals.\n *\n * `integer` keeps every step whole. A heatmap's x axis counts Samples, and for a\n * handful of them matplotlib would happily tick at 0.5; there is no Sample 0.5.\n *\n * Ticks are `first + i * step`, not an accumulation, so a step of 0.1 does not\n * drift into 0.30000000000000004; values within a hair of zero are snapped to a\n * positive zero so no label reads \"−0.0\".\n */\nexport function niceTicks(\n min: number,\n max: number,\n maxTicks: number,\n { integer = false }: { integer?: boolean } = {},\n): { ticks: number[]; step: number } {\n const span = max - min;\n if (!(span > 0) || !(maxTicks > 0)) return { ticks: [], step: 0 };\n\n const rough = span / maxTicks;\n let magnitude = 10 ** Math.floor(Math.log10(rough));\n let step = 0;\n // A decade may have no admissible step once integer steps are required, so\n // walk up until one fits.\n for (let decade = 0; decade < 4 && step === 0; decade++, magnitude *= 10) {\n for (const multiple of LADDER) {\n const candidate = multiple * magnitude;\n if (candidate < rough * (1 - 1e-9)) continue;\n if (integer && (candidate < 1 || Math.abs(candidate - Math.round(candidate)) > 1e-9)) {\n continue;\n }\n step = candidate;\n break;\n }\n }\n if (step === 0) return { ticks: [], step: 0 };\n\n const first = Math.ceil(min / step - 1e-9) * step;\n const count = Math.floor((max - first) / step + 1e-9) + 1;\n const ticks: number[] = [];\n for (let i = 0; i < count; i++) {\n const value = first + i * step;\n ticks.push(Math.abs(value) < step * 1e-9 ? 0 : value);\n }\n return { ticks, step };\n}\n\n/**\n * A tick label, with as many decimals as the step needs and no more.\n *\n * A tick is a round number by construction, so \"0.1\" carries everything\n * \"0.1000\" does, and several share one axis. Negative values take the unicode\n * minus, as matplotlib's ScalarFormatter and the rest of this package do.\n */\nexport function tickLabel(value: number, step: number, percent = false): string {\n const scaled = percent ? value * 100 : value;\n const scaledStep = percent ? step * 100 : step;\n let decimals = 0;\n while (decimals < 6) {\n const factor = 10 ** decimals;\n if (Math.abs(scaledStep * factor - Math.round(scaledStep * factor)) < 1e-9) break;\n decimals++;\n }\n const digits = Math.abs(scaled).toFixed(decimals);\n const sign = scaled < 0 && Number(digits) !== 0 ? MINUS : \"\";\n return sign + digits + (percent ? \"%\" : \"\");\n}\n","import { DisplayRows } from \"./types\";\nimport { PlotLabels, shapLabels } from \"./labels\";\nimport {\n AXIS_TITLE_DY,\n AxisSpine,\n AxisTick,\n AxisTitle,\n niceTicks,\n tickLabel,\n tickSpace,\n} from \"./ticks\";\n\nexport const POSITIVE_COLOR = \"#ff0051\";\nexport const NEGATIVE_COLOR = \"#008bfb\";\n\n/** shap/plots/_bar.py:259 — total_width 0.7 of the row pitch. */\nconst BAR_THICKNESS_RATIO = 0.7;\n/** Room below the last row for ticks, their labels and the axis title. */\nconst AXIS_HEIGHT = 52;\n/** _bar.py:331 tick_params(\"x\", labelsize=11). */\nconst TICK_LABEL_PT = 11;\n/** _bar.py:345 set_xlabel(xlabel, fontsize=13). */\nconst TITLE_PT = 13;\n/** matplotlib's default axes.xmargin, and _bar.py:334's own x_buffer ratio. */\nconst X_MARGIN = 0.05;\n\nexport type BarLayoutOptions = {\n width: number;\n rowHeight: number;\n marginLeft: number;\n marginRight: number;\n marginTop: number;\n /** Resolved wording; SHAP's when omitted. */\n labels?: PlotLabels;\n};\n\nexport type BarGeometry = {\n label: string;\n featureIndex: number | null;\n isOtherRow: boolean;\n value: number;\n x: number;\n y: number;\n width: number;\n height: number;\n color: string;\n /** vertical centre of the row, for label baselines */\n centerY: number;\n};\n\nexport type BarLayout = {\n bars: BarGeometry[];\n xDomain: [number, number];\n xZero: number;\n plotWidth: number;\n /** Bottom edge of the last row, where the axis area begins. */\n plotBottom: number;\n xTicks: AxisTick[];\n /** The bottom spine, which _bar.py:327-330 never hides. */\n xSpine: AxisSpine | null;\n xTitle: AxisTitle;\n /**\n * The solid vertical at zero. Always present, because SHAP's two code paths\n * converge on it: _bar.py:252-254 draws axvline(0) when a value is negative,\n * and _bar.py:329-330 hides the left spine only in that same case. With no\n * negatives the spine stays and barh pins the axes' left edge to 0 — so\n * either way there is one line at zero. Mean |SHAP| is never negative, so the\n * summary chart always takes the spine path.\n */\n zeroLine: { x: number; y1: number; y2: number };\n height: number;\n};\n\nfunction barXAxis(\n min: number,\n max: number,\n toX: (value: number) => number,\n marginLeft: number,\n plotWidth: number,\n plotBottom: number,\n title: string,\n): Pick<BarLayout, \"xTicks\" | \"xSpine\" | \"xTitle\"> {\n const { ticks, step } = niceTicks(min, max, tickSpace(plotWidth, TICK_LABEL_PT));\n return {\n xTicks: ticks.map((value) => ({ value, x: toX(value), label: tickLabel(value, step) })),\n xSpine: { x1: marginLeft, x2: marginLeft + plotWidth, y: plotBottom },\n xTitle: {\n // _bar.py:143-150 builds the default from the Explanation's transform\n // history: \"SHAP value\" -> \"|SHAP value|\" -> \"mean(|SHAP value|)\".\n text: title,\n x: marginLeft + plotWidth / 2,\n y: plotBottom + AXIS_TITLE_DY,\n fontSize: TITLE_PT,\n },\n };\n}\n\nexport function barLayout(rows: DisplayRows, opts: BarLayoutOptions): BarLayout {\n const { width, rowHeight, marginLeft, marginRight, marginTop } = opts;\n const plotWidth = width - marginLeft - marginRight;\n\n const values = rows.rows.map((r) => r.value);\n // Zero must always be in the domain, otherwise a bar would not start at the axis.\n const dataMin = Math.min(0, ...values);\n const dataMax = Math.max(0, ...values);\n const dataSpan = dataMax - dataMin;\n const negative = values.some((v) => v < 0);\n\n // Two 5% pads, measured by running shap.plots.bar: xlim came back as\n // max * 1.05 * 1.05. matplotlib's autoscale pads first — barh pins the edge\n // that sits at zero, so with nothing negative only the right grows — and\n // _bar.py:334-340 then reads that padded xlim and adds a 5% buffer of it:\n // to the right only, unless something is negative, then to both sides.\n const autoMin = negative ? dataMin - dataSpan * X_MARGIN : dataMin;\n const autoMax = dataMax + dataSpan * X_MARGIN;\n const buffer = (autoMax - autoMin) * X_MARGIN;\n const min = negative ? autoMin - buffer : autoMin;\n const max = autoMax + buffer;\n const span = max - min || 1;\n const toX = (v: number) => marginLeft + ((v - min) / span) * plotWidth;\n const xZero = toX(0);\n\n const barHeight = rowHeight * BAR_THICKNESS_RATIO;\n const inset = (rowHeight - barHeight) / 2;\n\n const bars: BarGeometry[] = rows.rows.map((row, i) => {\n const rowTop = marginTop + i * rowHeight;\n const end = toX(row.value);\n // shap/plots/_bar.py:267-271 colours a value of exactly zero as negative.\n const positive = row.value > 0;\n return {\n label: row.label,\n featureIndex: row.featureIndex,\n isOtherRow: row.isOtherRow,\n value: row.value,\n x: positive ? xZero : end,\n y: rowTop + inset,\n width: Math.abs(end - xZero),\n height: barHeight,\n color: positive ? POSITIVE_COLOR : NEGATIVE_COLOR,\n centerY: rowTop + rowHeight / 2,\n };\n });\n\n return {\n bars,\n xDomain: [min, max],\n xZero,\n plotWidth,\n plotBottom: marginTop + rows.rows.length * rowHeight,\n ...barXAxis(\n min,\n max,\n toX,\n marginLeft,\n plotWidth,\n marginTop + rows.rows.length * rowHeight,\n (opts.labels ?? shapLabels).meanAbsShapValue,\n ),\n zeroLine: { x: xZero, y1: marginTop, y2: marginTop + rows.rows.length * rowHeight },\n height: marginTop + rows.rows.length * rowHeight + AXIS_HEIGHT,\n };\n}\n","import { NEGATIVE_COLOR, POSITIVE_COLOR } from \"./barLayout\";\nimport { formatFeatureLabel, formatLevel, formatShapValue, ValuePrecision } from \"./format\";\nimport { niceTicks, tickLabel, tickSpace } from \"./ticks\";\nimport { orderFeatures } from \"./order\";\nimport { ParsedExplanation } from \"./types\";\nimport { PlotLabels, shapLabels } from \"./labels\";\n\n/** Fixed on-screen arrowhead length; matplotlib's equivalent is 0.08 inches. */\nexport const WATERFALL_HEAD_LENGTH_PX = 8;\nconst BAR_THICKNESS_RATIO = 0.8;\n/** Tick marks, their labels, and the E[f(X)] caption below them. */\nconst AXIS_HEIGHT = 52;\nconst TICK_LENGTH = 5;\nexport const WATERFALL_TICK_LABEL_DY = 18;\nexport const WATERFALL_BASE_LABEL_DY = 36;\n/** _waterfall.py:316 ax.tick_params(labelsize=13). */\nconst TICK_LABEL_PT = 13;\n\nexport type WaterfallRow = {\n label: string;\n featureIndex: number | null;\n isOtherRow: boolean;\n /** The full, unrounded SHAP contribution represented by this row. */\n value: number;\n /** Start of the arrow in value space while walking backward from f(x). */\n left: number;\n /** Signed full arrow width in value space. */\n width: number;\n /** matplotlib-compatible row number, counted upward from the bottom. */\n row: number;\n color: string;\n};\n\nexport type WaterfallRows = {\n /** Rows are ordered top-to-bottom. */\n rows: WaterfallRow[];\n baseValue: number;\n modelOutput: number;\n collapsedCount: number;\n};\n\nexport type WaterfallLayoutOptions = {\n width: number;\n rowHeight: number;\n marginLeft: number;\n marginRight: number;\n marginTop: number;\n /** Decimal places for the bar labels. Display only; omit for 3 significant figures. */\n decimals?: ValuePrecision;\n /** Resolved wording; SHAP's when omitted. */\n labels?: PlotLabels;\n};\n\nexport type Point = { x: number; y: number };\n\n/** Where a bar's numeric label goes, and roughly how wide it is. */\nexport type WaterfallValueLabel = {\n x: number;\n anchor: \"start\" | \"middle\" | \"end\";\n /** Estimated from the glyph count — enough to decide whether it fits. */\n estimatedWidth: number;\n /** Drawn over the bar, so the renderer must paint it in a contrasting colour. */\n inside: boolean;\n text: string;\n};\n\nexport type WaterfallArrowGeometry = WaterfallRow & {\n points: Point[];\n startX: number;\n endX: number;\n y: number;\n centerY: number;\n height: number;\n headLength: number;\n valueLabel: WaterfallValueLabel;\n};\n\nexport type WaterfallAxisMark = {\n kind: \"base\" | \"output\";\n value: number;\n x: number;\n /** Vertical extent of the dashed rule. The two do not match — see below. */\n y1: number;\n y2: number;\n label: string;\n};\n\n/**\n * The dashed vertical joining one bar's start to the next bar's end.\n *\n * shap/plots/_waterfall.py:130-137 draws these inside the loop that walks the\n * contributions, at `loc` immediately after `loc -= sval` — so the line sits\n * exactly where the two bars meet, which is what shows the reader that the\n * steps really do join up.\n */\nexport type WaterfallConnector = {\n x: number;\n y1: number;\n y2: number;\n};\n\nexport type WaterfallTick = {\n value: number;\n x: number;\n label: string;\n};\n\nexport type WaterfallSeparator = {\n y: number;\n x1: number;\n x2: number;\n};\n\nexport type WaterfallLayout = {\n arrows: WaterfallArrowGeometry[];\n axisMarks: WaterfallAxisMark[];\n connectors: WaterfallConnector[];\n xTicks: WaterfallTick[];\n separators: WaterfallSeparator[];\n xDomain: [number, number];\n plotWidth: number;\n /** Left and right edge of the plot area, where the axis is drawn. */\n plotLeft: number;\n plotRight: number;\n plotBottom: number;\n height: number;\n};\n\nconst colorFor = (value: number) => value < 0 ? NEGATIVE_COLOR : POSITIVE_COLOR;\n\n/** Gap between a bar's tip and its number. */\nconst VALUE_LABEL_GAP = 6;\nconst VALUE_LABEL_FONT_SIZE = 12;\n/** Mean glyph width as a fraction of font size, for this digit-heavy text. */\nconst GLYPH_WIDTH_RATIO = 0.6;\n/** Clearance the text keeps from the bar's own edges when it sits inside. */\nconst INSIDE_PADDING = 4;\n\n/**\n * Place a bar's numeric label, following shap/plots/_waterfall.py lines 230-245.\n *\n * SHAP draws the number inside the arrow, measures it, and moves it out only\n * when the text is wider than the arrow. Inside is the better default: it reads\n * as part of the bar and it cannot collide with anything.\n *\n * When the text does not fit, it goes just past the tip on the side the bar\n * points to. For a negative bar that is leftward, and a short negative bar sits\n * close to the feature names, so the label would land on top of them. There it\n * flips to the inner side instead, where the plot area always has room.\n * matplotlib hits the same conflict and resolves it with a wide left margin;\n * flipping is the better answer when the margin is a fixed gutter.\n *\n * Widths are estimated from the glyph count rather than measured. Measuring\n * would mean a DOM round-trip per row and a layout that cannot be computed or\n * tested outside a browser; over digits, which vary little in width, the\n * estimate is close enough to decide \"does this fit\".\n */\nfunction placeValueLabel(\n value: number,\n startX: number,\n endX: number,\n gutterX: number,\n decimals: ValuePrecision | undefined,\n): WaterfallValueLabel {\n const text = formatShapValue(value, decimals);\n const estimatedWidth = text.length * VALUE_LABEL_FONT_SIZE * GLYPH_WIDTH_RATIO;\n const base = { estimatedWidth, text };\n\n if (estimatedWidth + 2 * INSIDE_PADDING <= Math.abs(endX - startX)) {\n return { ...base, x: (startX + endX) / 2, anchor: \"middle\", inside: true };\n }\n\n if (value >= 0) {\n return { ...base, x: endX + VALUE_LABEL_GAP, anchor: \"start\", inside: false };\n }\n\n // A negative bar points left, so its label belongs off the left tip.\n const outsideLeftEdge = endX - VALUE_LABEL_GAP - estimatedWidth;\n if (outsideLeftEdge >= gutterX) {\n return { ...base, x: endX - VALUE_LABEL_GAP, anchor: \"end\", inside: false };\n }\n // No room there, so flip to the far side of the bar and read rightward from\n // its tail. That is startX, not endX: endX is the arrowhead, and starting\n // there would lay the text across the bar in the bar's own colour.\n return { ...base, x: startX + VALUE_LABEL_GAP, anchor: \"start\", inside: false };\n}\n\n/**\n * The value-space part of shap/plots/_waterfall.py::waterfall_legacy.\n * It walks backward from f(x), leaving pixel scaling and arrowheads to waterfallLayout.\n */\nexport function waterfallRows(\n explanation: ParsedExplanation,\n sampleIndex: number,\n maxDisplay: number,\n faithfulOtherRow: boolean,\n labels: PlotLabels = shapLabels,\n): WaterfallRows {\n if (!Number.isInteger(sampleIndex) || sampleIndex < 0 || sampleIndex >= explanation.nSamples) {\n throw new RangeError(\n `sampleIndex must identify a Sample from 0 to ${explanation.nSamples - 1}, received ${sampleIndex}`,\n );\n }\n if (!Number.isInteger(maxDisplay) || maxDisplay <= 0) {\n throw new RangeError(`maxDisplay must be a positive integer, received ${maxDisplay}`);\n }\n\n const values = explanation.values[sampleIndex];\n const baseValue = explanation.baseValues[sampleIndex];\n const modelOutput = baseValue + values.reduce((sum, value) => sum + value, 0);\n const order = orderFeatures(values.map(Math.abs));\n const visibleLimit = Math.min(maxDisplay, explanation.nFeatures);\n const hasOtherRow = visibleLimit < explanation.nFeatures;\n const realCount = hasOtherRow && faithfulOtherRow ? visibleLimit - 1 : visibleLimit;\n const rowCount = realCount + (hasOtherRow ? 1 : 0);\n\n let location = modelOutput;\n const rows: WaterfallRow[] = [];\n for (let rank = 0; rank < realCount; rank++) {\n const featureIndex = order[rank];\n const value = values[featureIndex];\n location -= value;\n rows.push({\n label: formatFeatureLabel(explanation.featureNames[featureIndex]),\n featureIndex,\n isOtherRow: false,\n value,\n left: location,\n width: value,\n row: rowCount - 1 - rank,\n color: colorFor(value),\n });\n }\n\n const collapsed = order.slice(realCount);\n if (hasOtherRow) {\n const value = collapsed.reduce((sum, featureIndex) => sum + values[featureIndex], 0);\n rows.push({\n // SHAP's waterfall counts the hidden Features whatever the collapse mode.\n label: labels.otherFeatures(collapsed.length, \"count\"),\n featureIndex: null,\n isOtherRow: true,\n value,\n left: baseValue,\n width: value,\n row: 0,\n color: colorFor(value),\n });\n }\n\n return {\n rows,\n baseValue,\n modelOutput,\n collapsedCount: hasOtherRow ? collapsed.length : 0,\n };\n}\n\nexport function waterfallLayout(\n valueRows: WaterfallRows,\n opts: WaterfallLayoutOptions,\n): WaterfallLayout {\n const { width, rowHeight, marginLeft, marginRight, marginTop } = opts;\n const labels = opts.labels ?? shapLabels;\n const plotWidth = width - marginLeft - marginRight;\n const coordinates = [valueRows.baseValue, valueRows.modelOutput];\n for (const row of valueRows.rows) coordinates.push(row.left, row.left + row.width);\n const min = Math.min(...coordinates);\n const max = Math.max(...coordinates);\n const span = max - min || 1;\n const toX = (value: number) => marginLeft + ((value - min) / span) * plotWidth;\n\n const barHeight = rowHeight * BAR_THICKNESS_RATIO;\n const inset = (rowHeight - barHeight) / 2;\n const arrows = valueRows.rows.map((row, index): WaterfallArrowGeometry => {\n const y = marginTop + index * rowHeight + inset;\n const centerY = marginTop + index * rowHeight + rowHeight / 2;\n const startX = toX(row.left);\n const endX = toX(row.left + row.width);\n const headLength = Math.min(Math.abs(endX - startX), WATERFALL_HEAD_LENGTH_PX);\n const neckX = row.width < 0 ? endX + headLength : endX - headLength;\n const points = [\n { x: startX, y },\n { x: neckX, y },\n { x: endX, y: centerY },\n { x: neckX, y: y + barHeight },\n { x: startX, y: y + barHeight },\n ];\n return {\n ...row,\n points,\n startX,\n endX,\n y,\n centerY,\n height: barHeight,\n headLength,\n valueLabel: placeValueLabel(row.value, startX, endX, marginLeft, opts.decimals),\n };\n });\n\n const plotBottom = marginTop + valueRows.rows.length * rowHeight;\n const { ticks, step } = niceTicks(min, max, tickSpace(plotWidth, TICK_LABEL_PT));\n\n // SHAP draws a connector per *individually* plotted Feature, so the Other row\n // never gets one below it. Without an Other row the condition becomes\n // `i + 4 < num_individual`, dropping the last four: one would be enough to\n // keep the line off the axis, and the 4 is unexplained in SHAP. Reproduced\n // as found rather than corrected, because this is a faithful detail.\n const individualCount = valueRows.rows.filter((row) => !row.isOtherRow).length;\n const hasOtherRow = individualCount < valueRows.rows.length;\n const connectors: WaterfallConnector[] = [];\n for (let index = 0; index < individualCount; index++) {\n if (!hasOtherRow && index + 4 >= individualCount) continue;\n connectors.push({\n x: arrows[index].startX,\n y1: marginTop + index * rowHeight + inset,\n y2: marginTop + (index + 1) * rowHeight + inset + barHeight,\n });\n }\n return {\n arrows,\n connectors,\n xTicks: ticks.map((value) => ({\n value,\n x: toX(value),\n label: tickLabel(value, step, opts.decimals === \"percent\"),\n })),\n axisMarks: [\n {\n kind: \"base\",\n value: valueRows.baseValue,\n x: toX(valueRows.baseValue),\n // axvline(base_values, 0, 1 / num_features) — one row tall, not full\n // height. Drawn the full height it reads as a y axis the chart has not\n // got, which is the whole reason SHAP hides the left spine.\n y1: plotBottom - rowHeight,\n y2: plotBottom,\n label: `${labels.baseValue} = ${formatLevel(valueRows.baseValue, opts.decimals)}`,\n },\n {\n kind: \"output\",\n value: valueRows.modelOutput,\n x: toX(valueRows.modelOutput),\n // axvline(fx, 0, 1) — the full height.\n y1: marginTop,\n y2: plotBottom,\n label: `${labels.modelOutput} = ${formatLevel(valueRows.modelOutput, opts.decimals)}`,\n },\n ],\n separators: valueRows.rows.map((_, index) => ({\n y: marginTop + index * rowHeight + rowHeight / 2,\n x1: marginLeft,\n x2: marginLeft + plotWidth,\n })),\n xDomain: [min, max],\n plotWidth,\n plotLeft: marginLeft,\n plotRight: marginLeft + plotWidth,\n plotBottom,\n height: plotBottom + AXIS_HEIGHT,\n };\n}\n","{\n \"note\": \"256-entry sRGB lookup tables dumped from shap.plots.colors. Interpolate linearly between entries; do not re-derive from Lch.\",\n \"red_blue\": [\n \"#008bfb\",\n \"#008afb\",\n \"#0089fa\",\n \"#0089fa\",\n \"#0088fa\",\n \"#0088fa\",\n \"#0087fa\",\n \"#0087fa\",\n \"#0086fa\",\n \"#0086f9\",\n \"#0085f9\",\n \"#0085f9\",\n \"#0084f9\",\n \"#0083f8\",\n \"#0083f8\",\n \"#0082f8\",\n \"#0082f8\",\n \"#0081f8\",\n \"#0081f7\",\n \"#0080f7\",\n \"#0080f7\",\n \"#007ff6\",\n \"#007ef6\",\n \"#007ef6\",\n \"#007df6\",\n \"#007df5\",\n \"#007cf5\",\n \"#007bf5\",\n \"#007bf4\",\n \"#007af4\",\n \"#007af4\",\n \"#0079f3\",\n \"#0078f3\",\n \"#0078f2\",\n \"#0077f2\",\n \"#0076f2\",\n \"#0076f1\",\n \"#0075f1\",\n \"#0075f0\",\n \"#0074f0\",\n \"#0073f0\",\n \"#0073ef\",\n \"#0072ef\",\n \"#0071ee\",\n \"#0071ee\",\n \"#0070ed\",\n \"#006fed\",\n \"#066fec\",\n \"#0f6eec\",\n \"#196deb\",\n \"#1e6deb\",\n \"#236cea\",\n \"#286bea\",\n \"#2c6be9\",\n \"#306ae9\",\n \"#3369e8\",\n \"#3769e8\",\n \"#3a68e7\",\n \"#3c67e6\",\n \"#3f66e6\",\n \"#4266e5\",\n \"#4465e5\",\n \"#4764e4\",\n \"#4964e3\",\n \"#4b63e3\",\n \"#4d62e2\",\n \"#5061e2\",\n \"#5261e1\",\n \"#5460e0\",\n \"#565fe0\",\n \"#575edf\",\n \"#595ede\",\n \"#5b5dde\",\n \"#5d5cdd\",\n \"#5f5bdc\",\n \"#605bdb\",\n \"#625adb\",\n \"#6359da\",\n \"#6558d9\",\n \"#6757d9\",\n \"#6856d8\",\n \"#6a56d7\",\n \"#6b55d6\",\n \"#6d54d6\",\n \"#6e53d5\",\n \"#6f52d4\",\n \"#7151d3\",\n \"#7251d2\",\n \"#7350d2\",\n \"#754fd1\",\n \"#764ed0\",\n \"#774dcf\",\n \"#794cce\",\n \"#7a4bce\",\n \"#7b4acd\",\n \"#7c49cc\",\n \"#7d48cb\",\n \"#7e48ca\",\n \"#8047c9\",\n \"#8146c8\",\n \"#8245c7\",\n \"#8344c7\",\n \"#8443c6\",\n \"#8542c5\",\n \"#8641c4\",\n \"#8740c3\",\n \"#883fc2\",\n \"#893ec1\",\n \"#8a3cc0\",\n \"#8b3bbf\",\n \"#8c3abe\",\n \"#8d39bd\",\n \"#8e38bc\",\n \"#8f37bb\",\n \"#9036ba\",\n \"#9134b9\",\n \"#9233b8\",\n \"#9232b8\",\n \"#9331b7\",\n \"#942fb6\",\n \"#952eb4\",\n \"#962db3\",\n \"#972bb2\",\n \"#972ab1\",\n \"#9828b0\",\n \"#9927af\",\n \"#9a25ae\",\n \"#9b24ae\",\n \"#9c23ad\",\n \"#9d22ac\",\n \"#9e21ac\",\n \"#a020ab\",\n \"#a11fab\",\n \"#a21eaa\",\n \"#a41daa\",\n \"#a51ca9\",\n \"#a61ba9\",\n \"#a719a8\",\n \"#a918a8\",\n \"#aa17a7\",\n \"#ab16a7\",\n \"#ac14a6\",\n \"#ae13a6\",\n \"#af11a5\",\n \"#b010a5\",\n \"#b10ea4\",\n \"#b20ca4\",\n \"#b40aa3\",\n \"#b507a2\",\n \"#b605a2\",\n \"#b703a1\",\n \"#b802a1\",\n \"#b900a0\",\n \"#bb00a0\",\n \"#bc009f\",\n \"#bd009e\",\n \"#be009e\",\n \"#bf009d\",\n \"#c0009d\",\n \"#c1009c\",\n \"#c2009b\",\n \"#c3009b\",\n \"#c4009a\",\n \"#c50099\",\n \"#c60099\",\n \"#c70098\",\n \"#c90097\",\n \"#ca0097\",\n \"#cb0096\",\n \"#cc0096\",\n \"#cd0095\",\n \"#cd0094\",\n \"#ce0093\",\n \"#cf0093\",\n \"#d00092\",\n \"#d10091\",\n \"#d20091\",\n \"#d30090\",\n \"#d4008f\",\n \"#d5008f\",\n \"#d6008e\",\n \"#d7008d\",\n \"#d8008d\",\n \"#d9008c\",\n \"#da008b\",\n \"#da008a\",\n \"#db008a\",\n \"#dc0089\",\n \"#dd0088\",\n \"#de0087\",\n \"#df0087\",\n \"#df0086\",\n \"#e00085\",\n \"#e10084\",\n \"#e20084\",\n \"#e30083\",\n \"#e30082\",\n \"#e40081\",\n \"#e50081\",\n \"#e60080\",\n \"#e6007f\",\n \"#e7007e\",\n \"#e8007d\",\n \"#e9007d\",\n \"#e9007c\",\n \"#ea007b\",\n \"#eb007a\",\n \"#eb007a\",\n \"#ec0079\",\n \"#ed0078\",\n \"#ed0077\",\n \"#ee0076\",\n \"#ef0076\",\n \"#ef0075\",\n \"#f00074\",\n \"#f10073\",\n \"#f10072\",\n \"#f20071\",\n \"#f20071\",\n \"#f30070\",\n \"#f4006f\",\n \"#f4006e\",\n \"#f5006d\",\n \"#f5006d\",\n \"#f6006c\",\n \"#f6006b\",\n \"#f7006a\",\n \"#f70069\",\n \"#f80068\",\n \"#f80068\",\n \"#f90067\",\n \"#f90066\",\n \"#fa0065\",\n \"#fa0064\",\n \"#fb0063\",\n \"#fb0062\",\n \"#fc0062\",\n \"#fc0061\",\n \"#fd0060\",\n \"#fd005f\",\n \"#fd005e\",\n \"#fe005d\",\n \"#fe005c\",\n \"#fe005c\",\n \"#ff005b\",\n \"#ff005a\",\n \"#ff0059\",\n \"#ff0058\",\n \"#ff0057\",\n \"#ff0056\",\n \"#ff0055\",\n \"#ff0055\",\n \"#ff0054\",\n \"#ff0053\",\n \"#ff0052\",\n \"#ff0051\"\n ],\n \"red_white_blue\": [\n \"#008bfb\",\n \"#028bfb\",\n \"#048cfb\",\n \"#068dfb\",\n \"#088efb\",\n \"#0a8ffb\",\n \"#0c90fb\",\n \"#0e91fb\",\n \"#1092fb\",\n \"#1293fb\",\n \"#1494fb\",\n \"#1695fb\",\n \"#1896fb\",\n \"#1a96fb\",\n \"#1c97fb\",\n \"#1e98fb\",\n \"#2099fb\",\n \"#229afb\",\n \"#249bfb\",\n \"#269cfb\",\n \"#289dfb\",\n \"#2a9efb\",\n \"#2c9ffb\",\n \"#2ea0fc\",\n \"#30a1fc\",\n \"#32a2fc\",\n \"#34a2fc\",\n \"#36a3fc\",\n \"#38a4fc\",\n \"#3aa5fc\",\n \"#3ca6fc\",\n \"#3ea7fc\",\n \"#40a8fc\",\n \"#42a9fc\",\n \"#44aafc\",\n \"#46abfc\",\n \"#48acfc\",\n \"#4aadfc\",\n \"#4cadfc\",\n \"#4eaefc\",\n \"#50affc\",\n \"#52b0fc\",\n \"#54b1fc\",\n \"#56b2fc\",\n \"#58b3fc\",\n \"#5ab4fc\",\n \"#5cb5fc\",\n \"#5eb6fc\",\n \"#60b7fc\",\n \"#62b8fc\",\n \"#65b8fc\",\n \"#67b9fc\",\n \"#69bafc\",\n \"#6bbbfd\",\n \"#6dbcfd\",\n \"#6fbdfd\",\n \"#71befd\",\n \"#73bffd\",\n \"#75c0fd\",\n \"#77c1fd\",\n \"#79c2fd\",\n \"#7bc3fd\",\n \"#7dc3fd\",\n \"#7fc4fd\",\n \"#81c5fd\",\n \"#83c6fd\",\n \"#85c7fd\",\n \"#87c8fd\",\n \"#89c9fd\",\n \"#8bcafd\",\n \"#8dcbfd\",\n \"#8fccfd\",\n \"#91cdfd\",\n \"#93cefd\",\n \"#95cefd\",\n \"#97cffd\",\n \"#99d0fd\",\n \"#9bd1fd\",\n \"#9dd2fd\",\n \"#9fd3fd\",\n \"#a1d4fd\",\n \"#a3d5fd\",\n \"#a5d6fe\",\n \"#a7d7fe\",\n \"#a9d8fe\",\n \"#abd9fe\",\n \"#add9fe\",\n \"#afdafe\",\n \"#b1dbfe\",\n \"#b3dcfe\",\n \"#b5ddfe\",\n \"#b7defe\",\n \"#b9dffe\",\n \"#bbe0fe\",\n \"#bde1fe\",\n \"#bfe2fe\",\n \"#c1e3fe\",\n \"#c3e4fe\",\n \"#c5e5fe\",\n \"#c7e5fe\",\n \"#c9e6fe\",\n \"#cbe7fe\",\n \"#cde8fe\",\n \"#cfe9fe\",\n \"#d1eafe\",\n \"#d3ebfe\",\n \"#d5ecfe\",\n \"#d7edfe\",\n \"#d9eefe\",\n \"#dbeffe\",\n \"#ddf0fe\",\n \"#dff0fe\",\n \"#e1f1ff\",\n \"#e3f2ff\",\n \"#e5f3ff\",\n \"#e7f4ff\",\n \"#e9f5ff\",\n \"#ebf6ff\",\n \"#edf7ff\",\n \"#eff8ff\",\n \"#f1f9ff\",\n \"#f3faff\",\n \"#f5fbff\",\n \"#f7fbff\",\n \"#f9fcff\",\n \"#fbfdff\",\n \"#fdfeff\",\n \"#ffffff\",\n \"#ffffff\",\n \"#fffdfe\",\n \"#fffbfc\",\n \"#fff9fb\",\n \"#fff7fa\",\n \"#fff5f8\",\n \"#fff3f7\",\n \"#fff1f6\",\n \"#ffeff4\",\n \"#ffedf3\",\n \"#ffebf1\",\n \"#ffe9f0\",\n \"#ffe7ef\",\n \"#ffe5ed\",\n \"#ffe3ec\",\n \"#ffe1eb\",\n \"#ffdfe9\",\n \"#ffdde8\",\n \"#ffdbe7\",\n \"#ffd9e5\",\n \"#ffd7e4\",\n \"#ffd5e2\",\n \"#ffd3e1\",\n \"#ffd1e0\",\n \"#ffcfde\",\n \"#ffcddd\",\n \"#ffcbdc\",\n \"#ffc9da\",\n \"#ffc7d9\",\n \"#ffc5d7\",\n \"#ffc3d6\",\n \"#ffc1d5\",\n \"#ffbfd3\",\n \"#ffbdd2\",\n \"#ffbbd1\",\n \"#ffb9cf\",\n \"#ffb7ce\",\n \"#ffb5cc\",\n \"#ffb3cb\",\n \"#ffb1ca\",\n \"#ffafc8\",\n \"#ffadc7\",\n \"#ffabc6\",\n \"#ffa9c4\",\n \"#ffa7c3\",\n \"#ffa5c1\",\n \"#ffa3c0\",\n \"#ffa1bf\",\n \"#ff9fbd\",\n \"#ff9dbc\",\n \"#ff9bbb\",\n \"#ff99b9\",\n \"#ff97b8\",\n \"#ff95b7\",\n \"#ff93b5\",\n \"#ff91b4\",\n \"#ff8fb2\",\n \"#ff8db1\",\n \"#ff8bb0\",\n \"#ff89ae\",\n \"#ff87ad\",\n \"#ff85ac\",\n \"#ff83aa\",\n \"#ff81a9\",\n \"#ff7fa7\",\n \"#ff7da6\",\n \"#ff7ba5\",\n \"#ff79a3\",\n \"#ff77a2\",\n \"#ff75a1\",\n \"#ff739f\",\n \"#ff719e\",\n \"#ff6f9c\",\n \"#ff6d9b\",\n \"#ff6b9a\",\n \"#ff6998\",\n \"#ff6797\",\n \"#ff6596\",\n \"#ff6294\",\n \"#ff6093\",\n \"#ff5e92\",\n \"#ff5c90\",\n \"#ff5a8f\",\n \"#ff588d\",\n \"#ff568c\",\n \"#ff548b\",\n \"#ff5289\",\n \"#ff5088\",\n \"#ff4e87\",\n \"#ff4c85\",\n \"#ff4a84\",\n \"#ff4882\",\n \"#ff4681\",\n \"#ff4480\",\n \"#ff427e\",\n \"#ff407d\",\n \"#ff3e7c\",\n \"#ff3c7a\",\n \"#ff3a79\",\n \"#ff3877\",\n \"#ff3676\",\n \"#ff3475\",\n \"#ff3273\",\n \"#ff3072\",\n \"#ff2e71\",\n \"#ff2c6f\",\n \"#ff2a6e\",\n \"#ff286d\",\n \"#ff266b\",\n \"#ff246a\",\n \"#ff2268\",\n \"#ff2067\",\n \"#ff1e66\",\n \"#ff1c64\",\n \"#ff1a63\",\n \"#ff1862\",\n \"#ff1660\",\n \"#ff145f\",\n \"#ff125d\",\n \"#ff105c\",\n \"#ff0e5b\",\n \"#ff0c59\",\n \"#ff0a58\",\n \"#ff0857\",\n \"#ff0655\",\n \"#ff0454\",\n \"#ff0252\",\n \"#ff0051\"\n ],\n \"nan_grey\": \"#848484\"\n}","// Runtime data, not a test fixture: the lookup tables SHAP's own colour maps\n// resolve to, captured by running shap 0.49.1. Kept beside the code so the\n// published package does not depend on the fixtures directory.\nimport colormaps from \"./colormaps.json\";\n\nexport type ColormapName = \"red_blue\" | \"red_white_blue\";\n\nconst tables = colormaps as Record<ColormapName, string[]>;\n\nfunction channel(hex: string, offset: number): number {\n return Number.parseInt(hex.slice(offset, offset + 2), 16);\n}\n\nfunction hexByte(value: number): string {\n return Math.round(value).toString(16).padStart(2, \"0\");\n}\n\n/** Samples a captured SHAP colour map, interpolating adjacent LUT entries in sRGB. */\nexport function sampleColormap(name: ColormapName, t: number): string {\n if (!Number.isFinite(t)) {\n throw new RangeError(`colormap position must be finite, received ${String(t)}`);\n }\n\n const table = tables[name];\n if (!table) throw new RangeError(`unknown colormap ${String(name)}`);\n\n const position = Math.max(0, Math.min(1, t)) * (table.length - 1);\n const lowerIndex = Math.floor(position);\n const upperIndex = Math.ceil(position);\n const fraction = position - lowerIndex;\n const lower = table[lowerIndex];\n const upper = table[upperIndex];\n\n const red = channel(lower, 1) + (channel(upper, 1) - channel(lower, 1)) * fraction;\n const green = channel(lower, 3) + (channel(upper, 3) - channel(lower, 3)) * fraction;\n const blue = channel(lower, 5) + (channel(upper, 5) - channel(lower, 5)) * fraction;\n return `#${hexByte(red)}${hexByte(green)}${hexByte(blue)}`;\n}\n","import { ColormapName, sampleColormap } from \"./colormap\";\n\n/** fig.colorbar(..., aspect=80) in both _beeswarm.py:481 and _heatmap.py:184. */\nconst ASPECT = 80;\n/** matplotlib's ytick.major.pad: the gap between a bar and its tick labels, in pt. */\nconst TICK_PAD = 3.5;\n/** cb.ax.tick_params(labelsize=11), _beeswarm.py:484 and _heatmap.py:189. */\nconst TICK_LABEL_PT = 11;\n/** cb.set_label(..., size=12), _beeswarm.py:483 and _heatmap.py:188. */\nconst LABEL_PT = 12;\n/**\n * The rotated label's thickness and where its baseline sits inside it, in ems.\n * Measured from shap 0.49.1's own figures: an 18 px band for 12 pt text at 100 dpi.\n */\nconst LABEL_THICKNESS_EM = 1.08;\nconst LABEL_BASELINE_EM = 0.84;\n/** Average advance of a character, in ems; no text is measured in the layout. */\nconst CHAR_EM = 0.6;\nconst STEPS = 64;\n\nexport type ColorBarSpec = {\n colormap: ColormapName;\n /** The labels at the bottom and top ends of the bar. */\n tickLabels: [string, string];\n /** matplotlib's offset text (\"1e−5\"), when the tick labels are scaled. */\n offsetText?: string;\n label: string;\n /** set_label's labelpad, in pt: 0 for the beeswarm, -10 for the heatmap. */\n labelPad: number;\n};\n\nexport type ColorBarStep = { y: number; height: number; color: string };\nexport type ColorBarTick = { y: number; label: string };\n\nexport type ColorBarGeometry = {\n x: number;\n y1: number;\n y2: number;\n width: number;\n /** The colour map from bottom to top, as stacked bands. */\n steps: ColorBarStep[];\n tickX: number;\n tickFontSize: number;\n ticks: ColorBarTick[];\n offsetText?: { x: number; y: number; text: string };\n /** Drawn rotated -90° about (x, y), reading bottom to top. */\n label: { x: number; y: number; text: string; fontSize: number };\n /** Right edge of everything the bar draws. */\n right: number;\n};\n\nfunction textWidth(text: string, fontSize: number): number {\n return text.length * fontSize * CHAR_EM;\n}\n\nfunction tickColumnWidth(spec: ColorBarSpec): number {\n return Math.max(...spec.tickLabels.map((label) => textWidth(label, TICK_LABEL_PT)));\n}\n\n/** How far right of its own left edge a bar of this height draws. */\nexport function colorBarExtent(height: number, spec: ColorBarSpec): number {\n return (\n height / ASPECT +\n TICK_PAD +\n Math.max(0, tickColumnWidth(spec) + spec.labelPad) +\n LABEL_PT * LABEL_THICKNESS_EM\n );\n}\n\n/**\n * Width of the plot once a colour bar beside it has room.\n *\n * matplotlib's colorbar pad is a fraction of the axes it steals from, so the gap\n * grows with the plot; `minGap` keeps it clear of anything drawn between the two,\n * such as the heatmap's side bars. When the right margin cannot hold the bar, the\n * plot gives up the difference rather than the bar being clipped.\n */\nexport function fitColorBar(opts: {\n plotWidth: number;\n available: number;\n gapRatio: number;\n minGap: number;\n extent: number;\n}): { plotWidth: number; gap: number } {\n const { plotWidth, available, gapRatio, minGap, extent } = opts;\n const gapFor = (width: number) => Math.max(gapRatio * width, minGap);\n if (gapFor(plotWidth) + extent <= available) {\n return { plotWidth, gap: gapFor(plotWidth) };\n }\n // Solve gap(w) + extent = available + (plotWidth - w) on whichever branch of\n // gap(w) holds at the answer.\n const byRatio = (available + plotWidth - extent) / (1 + gapRatio);\n const fitted = Math.max(\n 0,\n gapRatio * byRatio >= minGap ? byRatio : plotWidth - (minGap + extent - available),\n );\n return { plotWidth: fitted, gap: gapFor(fitted) };\n}\n\n/** Places a vertical colour bar with its left edge at `x`, spanning y1 (top) to y2. */\nexport function colorBarLayout(\n spec: ColorBarSpec,\n position: { x: number; y1: number; y2: number },\n): ColorBarGeometry {\n const { x, y1, y2 } = position;\n const width = (y2 - y1) / ASPECT;\n const step = (y2 - y1) / STEPS;\n const steps = Array.from({ length: STEPS }, (_, index): ColorBarStep => ({\n y: y2 - (index + 1) * step,\n // Every band but the lowest reaches half a pixel into the one below, so\n // anti-aliasing cannot open a hairline seam between them.\n height: index === 0 ? step : step + 0.5,\n color: sampleColormap(spec.colormap, index / (STEPS - 1)),\n }));\n\n const tickX = x + width + TICK_PAD;\n const labelLeft = tickX + tickColumnWidth(spec) + spec.labelPad;\n return {\n x,\n y1,\n y2,\n width,\n steps,\n tickX,\n tickFontSize: TICK_LABEL_PT,\n ticks: [\n { y: y2, label: spec.tickLabels[0] },\n { y: y1, label: spec.tickLabels[1] },\n ],\n ...(spec.offsetText\n ? { offsetText: { x: tickX, y: y1 - TICK_LABEL_PT, text: spec.offsetText } }\n : {}),\n label: {\n x: labelLeft + LABEL_PT * LABEL_BASELINE_EM,\n y: (y1 + y2) / 2,\n text: spec.label,\n fontSize: LABEL_PT,\n },\n right: Math.max(\n tickX + tickColumnWidth(spec),\n labelLeft + LABEL_PT * LABEL_THICKNESS_EM,\n ),\n };\n}\n\nconst MINUS = \"−\";\n/** axes.formatter.limits: scientific notation outside 1e-5 .. 1e6. */\nconst POWER_LIMITS: [number, number] = [-5, 6];\n\nfunction roundTo(value: number, decimals: number): number {\n const factor = 10 ** decimals;\n return Math.round(value * factor) / factor;\n}\n\n/**\n * Tick labels as matplotlib's ScalarFormatter writes them for a fixed set of\n * ticks: the decimals are the fewest that keep every tick exact to a thousandth\n * of the tick range, and the minus sign is U+2212. Ticks outside the power\n * limits are scaled, with the scale returned as matplotlib's offset text.\n */\nexport function scalarFormatterLabels(locs: number[]): { labels: string[]; offsetText?: string } {\n const largest = Math.max(0, ...locs.map(Math.abs));\n const magnitude = largest === 0 ? 0 : Math.floor(Math.log10(largest));\n const order = magnitude <= POWER_LIMITS[0] || magnitude >= POWER_LIMITS[1] ? magnitude : 0;\n const scaled = locs.map((value) => value / 10 ** order);\n\n let range = Math.max(...scaled) - Math.min(...scaled);\n if (range === 0) range = Math.max(...scaled.map(Math.abs));\n if (range === 0) range = 1;\n const rangeMagnitude = Math.floor(Math.log10(range));\n const threshold = 1e-3 * 10 ** rangeMagnitude;\n let decimals = Math.max(0, 3 - rangeMagnitude);\n while (decimals >= 0) {\n const error = Math.max(...scaled.map((value) => Math.abs(value - roundTo(value, decimals))));\n if (error < threshold) decimals -= 1;\n else break;\n }\n decimals += 1;\n\n const labels = scaled.map((value) => {\n const text = value.toFixed(decimals);\n return Number(text) === 0 ? text.replace(\"-\", \"\") : text.replace(\"-\", MINUS);\n });\n return order === 0\n ? { labels }\n : { labels, offsetText: `1e${String(order).replace(\"-\", MINUS)}` };\n}\n","/** Average advance of one glyph of the 11 px tooltip text. Widths are estimated, never measured. */\nconst CHAR_PX = 6.5;\nconst PADDING_X = 7;\n/** Gap between the pointer and the box. */\nconst OFFSET = 8;\n/** How far above the pointer the box starts, so its first line sits level with it. */\nconst RAISE = 24;\n\nexport type TooltipBox = { x: number; y: number; width: number; height: number };\n\n/**\n * Where a hover box goes: right of the point, flipped to its left when the\n * chart's right edge would cut it off, and kept inside the chart top to bottom.\n * Its width follows the longest line, so a long taxon name or a long label\n * stays inside the box.\n */\nexport function placeTooltip(opts: {\n anchorX: number;\n anchorY: number;\n lines: string[];\n lineHeight: number;\n minWidth: number;\n chartWidth: number;\n chartHeight: number;\n}): TooltipBox {\n const { anchorX, anchorY, lines, lineHeight, minWidth, chartWidth, chartHeight } = opts;\n const longest = lines.reduce((max, line) => Math.max(max, line.length), 0);\n const width = Math.max(minWidth, longest * CHAR_PX + 2 * PADDING_X);\n const height = lines.length * lineHeight + lineHeight * 0.6;\n\n const right = anchorX + OFFSET;\n const x = right + width <= chartWidth ? right : Math.max(0, anchorX - OFFSET - width);\n const y = Math.max(0, Math.min(anchorY - RAISE, chartHeight - height));\n return { x, y, width, height };\n}\n","import { collapseToDisplay } from \"./collapse\";\nimport { PlotLabels, shapLabels } from \"./labels\";\nimport {\n ColorBarGeometry,\n ColorBarSpec,\n colorBarExtent,\n colorBarLayout,\n fitColorBar,\n} from \"./colorBar\";\nimport { sampleColormap } from \"./colormap\";\nimport { RowSort, sortDisplayRows } from \"./rowSort\";\nimport {\n AXIS_TITLE_DY,\n AxisSpine,\n AxisTick,\n AxisTitle,\n niceTicks,\n tickLabel,\n tickSpace,\n} from \"./ticks\";\nimport { globalImportance, orderFeatures } from \"./order\";\nimport { ParsedExplanation } from \"./types\";\n\nexport const BEESWARM_MISSING_COLOR = \"#777777\";\nexport const BEESWARM_ROW_HEIGHT = 0.4;\nconst NBINS = 100;\n/** Room below the rows for ticks, their labels and the axis title. */\nconst AXIS_HEIGHT = 52;\n/** fig.colorbar(m, ax=ax, ticks=[0, 1], aspect=80) with Low/High labels, _beeswarm.py:479-483. */\nfunction colorBarSpec(labels: PlotLabels): ColorBarSpec {\n return {\n colormap: \"red_blue\",\n tickLabels: [labels.featureValueLow, labels.featureValueHigh],\n label: labels.featureValue,\n labelPad: 0,\n };\n}\n/**\n * fig.colorbar's defaults, pad=0.05 and fraction=0.15, are fractions of the axes\n * before the bar is taken out of it: the gap is 0.05 of that and the plot keeps 0.80.\n */\nconst COLOR_BAR_GAP_RATIO = 0.05 / 0.8;\n/** _beeswarm.py:499 tick_params(\"x\", labelsize=11). */\nconst TICK_LABEL_PT = 11;\n/** _beeswarm.py:501 set_xlabel(..., fontsize=13). */\nconst TITLE_PT = 13;\n/** matplotlib's default axes.xmargin; _beeswarm.py never sets xlim itself. */\nconst X_MARGIN = 0.05;\n\nexport type BeeswarmPoint = {\n sampleIndex: number;\n /** SHAP value in value space. */\n x: number;\n /** SHAP-compatible row coordinate, including signed vertical jitter. */\n y: number;\n /** Original Feature value before percentile clipping. */\n featureValue: number;\n /** Value sent through the colour map, or null when the Feature value is missing. */\n colorValue: number | null;\n color: string;\n};\n\nexport type BeeswarmRow = {\n label: string;\n featureIndex: number | null;\n isOtherRow: boolean;\n /** SHAP row number, counted upward from the bottom. */\n rowIndex: number;\n vmin: number;\n vmax: number;\n points: BeeswarmPoint[];\n};\n\nexport type BeeswarmRows = {\n /** Rows are ordered top-to-bottom. */\n rows: BeeswarmRow[];\n collapsedCount: number;\n};\n\nexport type BeeswarmLayoutOptions = {\n width: number;\n rowHeight: number;\n marginLeft: number;\n marginRight: number;\n marginTop: number;\n dotRadius: number;\n /** Draw SHAP's feature value colour bar right of the plot. */\n colorBar?: boolean;\n /** Resolved wording; SHAP's when omitted. */\n labels?: PlotLabels;\n};\n\nexport type BeeswarmPointGeometry = Omit<BeeswarmPoint, \"x\" | \"y\"> & {\n x: number;\n y: number;\n valueX: number;\n valueY: number;\n radius: number;\n};\n\nexport type BeeswarmRowGeometry = Omit<BeeswarmRow, \"points\"> & {\n centerY: number;\n points: BeeswarmPointGeometry[];\n};\n\nexport type BeeswarmLayout = {\n rows: BeeswarmRowGeometry[];\n xDomain: [number, number];\n xZero: number;\n plotWidth: number;\n plotBottom: number;\n xTicks: AxisTick[];\n /** The bottom spine, which _beeswarm.py:493-495 leaves visible. */\n xSpine: AxisSpine | null;\n xTitle: AxisTitle;\n /** Null unless the options asked for one. */\n colorBar: ColorBarGeometry | null;\n height: number;\n};\n\nfunction percentile(values: number[], percent: number): number {\n const finite = values.filter(Number.isFinite).sort((a, b) => a - b);\n if (finite.length === 0) return 0;\n const position = (finite.length - 1) * percent / 100;\n const lower = Math.floor(position);\n const upper = Math.ceil(position);\n const fraction = position - lower;\n return finite[lower] + (finite[upper] - finite[lower]) * fraction;\n}\n\nfunction colorDomain(featureValues: number[]): [number, number] {\n let vmin = percentile(featureValues, 5);\n let vmax = percentile(featureValues, 95);\n if (vmin === vmax) {\n vmin = percentile(featureValues, 1);\n vmax = percentile(featureValues, 99);\n }\n if (vmin === vmax) {\n const finite = featureValues.filter(Number.isFinite);\n if (finite.length > 0) {\n vmin = Math.min(...finite);\n vmax = Math.max(...finite);\n }\n }\n if (vmin > vmax) vmin = vmax;\n return [vmin, vmax];\n}\n\n/** Small deterministic PRNG used only to resolve points tied in the same x bin. */\nfunction seededRandom(seed: number): () => number {\n let state = Math.trunc(seed) >>> 0;\n return () => {\n state = (state + 0x6d2b79f5) >>> 0;\n let value = state;\n value = Math.imul(value ^ (value >>> 15), value | 1);\n value ^= value + Math.imul(value ^ (value >>> 7), value | 61);\n return ((value ^ (value >>> 14)) >>> 0) / 4294967296;\n };\n}\n\n/** JavaScript rounds .5 upward; NumPy rounds an exact .5 to the nearest even integer. */\nfunction numpyRound(value: number): number {\n const lower = Math.floor(value);\n if (value - lower === 0.5) return lower % 2 === 0 ? lower : lower + 1;\n return Math.round(value);\n}\n\nfunction spreadPoints(xs: number[], rowIndex: number, seed: number): number[] {\n const min = Math.min(...xs);\n const max = Math.max(...xs);\n const quantized = xs.map((x) => numpyRound(NBINS * (x - min) / (max - min + 1e-8)));\n const random = seededRandom(seed);\n const order = quantized\n .map((bin, index) => ({ bin, index, tieBreak: random() }))\n .sort((a, b) => (a.bin - b.bin) || (a.tieBreak - b.tieBreak))\n .map((entry) => entry.index);\n\n const offsets = new Array<number>(xs.length).fill(0);\n let layer = 0;\n let lastBin = -1;\n for (const index of order) {\n const bin = quantized[index];\n if (bin !== lastBin) layer = 0;\n offsets[index] = Math.ceil(layer / 2) * ((layer % 2) * 2 - 1);\n layer += 1;\n lastBin = bin;\n }\n\n const maxPositiveOffset = Math.max(0, ...offsets);\n const scale = 0.9 * (BEESWARM_ROW_HEIGHT / (maxPositiveOffset + 1));\n return offsets.map((offset) => rowIndex + offset * scale);\n}\n\n/**\n * Computes SHAP-compatible beeswarm rows entirely in value space. Pixel scaling belongs in\n * beeswarmLayout so golden values can be compared without a viewport.\n */\nexport function beeswarmRows(\n explanation: ParsedExplanation,\n maxDisplay: number,\n faithfulOtherRow: boolean,\n seed = 0,\n rowSort: RowSort = \"importance\",\n labels: PlotLabels = shapLabels,\n): BeeswarmRows {\n if (!Number.isInteger(maxDisplay) || maxDisplay <= 0) {\n throw new RangeError(`maxDisplay must be a positive integer, received ${maxDisplay}`);\n }\n if (!Number.isFinite(seed)) {\n throw new RangeError(`seed must be finite, received ${String(seed)}`);\n }\n\n const importance = globalImportance(explanation);\n const order = orderFeatures(importance);\n // Which rows are shown is decided by importance; rowSort only reorders them.\n const display = sortDisplayRows(\n collapseToDisplay(\n explanation.featureNames,\n importance,\n order,\n maxDisplay,\n faithfulOtherRow,\n labels,\n ),\n rowSort,\n explanation.data,\n );\n const visibleFeatures = new Set(\n display.rows.flatMap((row) => row.featureIndex === null ? [] : [row.featureIndex]),\n );\n const collapsedFeatures = order.filter((featureIndex) => !visibleFeatures.has(featureIndex));\n\n const rows = display.rows.map((displayRow, displayIndex): BeeswarmRow => {\n const featureIndices = displayRow.featureIndex === null\n ? collapsedFeatures\n : [displayRow.featureIndex];\n // SHAP's faithful collapse mutates only the SHAP-value matrix. Its colour values remain those\n // of the Feature at the absorbed rank. Corrected mode has a genuinely separate Other row and\n // therefore sums the hidden Feature values as specified for that mode.\n const colorFeatureIndices = displayRow.featureIndex === null && faithfulOtherRow\n ? collapsedFeatures.slice(0, 1)\n : featureIndices;\n const xs = explanation.values.map((sample) =>\n featureIndices.reduce((sum, featureIndex) => sum + sample[featureIndex], 0));\n const featureValues = explanation.data.map((sample) =>\n colorFeatureIndices.reduce((sum, featureIndex) => sum + sample[featureIndex], 0));\n const rowIndex = display.rows.length - 1 - displayIndex;\n const ys = spreadPoints(xs, rowIndex, seed + Math.imul(displayIndex + 1, 0x9e3779b1));\n const [vmin, vmax] = colorDomain(featureValues);\n const colorSpan = vmax - vmin;\n\n return {\n label: displayRow.label,\n featureIndex: displayRow.featureIndex,\n isOtherRow: displayRow.isOtherRow,\n rowIndex,\n vmin,\n vmax,\n points: xs.map((x, sampleIndex): BeeswarmPoint => {\n const featureValue = featureValues[sampleIndex];\n if (!Number.isFinite(featureValue)) {\n return {\n sampleIndex,\n x,\n y: ys[sampleIndex],\n featureValue,\n colorValue: null,\n color: BEESWARM_MISSING_COLOR,\n };\n }\n const colorValue = Math.max(vmin, Math.min(vmax, featureValue));\n const normalized = colorSpan === 0 ? 0 : (colorValue - vmin) / colorSpan;\n return {\n sampleIndex,\n x,\n y: ys[sampleIndex],\n featureValue,\n colorValue,\n color: sampleColormap(\"red_blue\", normalized),\n };\n }),\n };\n });\n\n return { rows, collapsedCount: display.collapsedCount };\n}\n\nfunction beeswarmXAxis(\n min: number,\n max: number,\n toX: (value: number) => number,\n marginLeft: number,\n plotWidth: number,\n plotBottom: number,\n title: string,\n): Pick<BeeswarmLayout, \"xTicks\" | \"xSpine\" | \"xTitle\"> {\n const { ticks, step } = niceTicks(min, max, tickSpace(plotWidth, TICK_LABEL_PT));\n return {\n xTicks: ticks.map((value) => ({ value, x: toX(value), label: tickLabel(value, step) })),\n xSpine: { x1: marginLeft, x2: marginLeft + plotWidth, y: plotBottom },\n xTitle: {\n // The default is _labels.py:5, labels[\"VALUE\"].\n text: title,\n x: marginLeft + plotWidth / 2,\n y: plotBottom + AXIS_TITLE_DY,\n fontSize: TITLE_PT,\n },\n };\n}\n\n/** Projects beeswarm value-space rows into SVG coordinates. */\nexport function beeswarmLayout(\n valueRows: BeeswarmRows,\n opts: BeeswarmLayoutOptions,\n): BeeswarmLayout {\n const { width, rowHeight, marginLeft, marginRight, marginTop, dotRadius } = opts;\n const labels = opts.labels ?? shapLabels;\n const colorBar = colorBarSpec(labels);\n const plotBottom = marginTop + valueRows.rows.length * rowHeight;\n const fit = opts.colorBar\n ? fitColorBar({\n plotWidth: width - marginLeft - marginRight,\n available: marginRight,\n gapRatio: COLOR_BAR_GAP_RATIO,\n minGap: 0,\n extent: colorBarExtent(plotBottom - marginTop, colorBar),\n })\n : null;\n const plotWidth = fit ? fit.plotWidth : width - marginLeft - marginRight;\n const values = valueRows.rows.flatMap((row) => row.points.map((point) => point.x));\n const dataMin = Math.min(0, ...values);\n const dataMax = Math.max(0, ...values);\n // SHAP never sets the beeswarm's xlim, so matplotlib's default 5% margin\n // applies on both sides. That is what lets the axis reach a round tick just\n // past the outermost dot, as SHAP's own figures do.\n const margin = (dataMax - dataMin) * X_MARGIN;\n const min = dataMin - margin;\n const max = dataMax + margin;\n const span = max - min || 1;\n const toX = (value: number) => marginLeft + (value - min) / span * plotWidth;\n const highestRowIndex = Math.max(0, valueRows.rows.length - 1);\n\n const rows = valueRows.rows.map((row): BeeswarmRowGeometry => {\n const centerY = marginTop + (highestRowIndex - row.rowIndex + 0.5) * rowHeight;\n return {\n ...row,\n centerY,\n points: row.points.map((point): BeeswarmPointGeometry => ({\n ...point,\n valueX: point.x,\n valueY: point.y,\n x: toX(point.x),\n y: centerY - (point.y - row.rowIndex) * rowHeight,\n radius: dotRadius,\n })),\n };\n });\n\n return {\n rows,\n xDomain: [min, max],\n xZero: toX(0),\n ...beeswarmXAxis(min, max, toX, marginLeft, plotWidth, plotBottom, labels.shapValueAxis),\n plotWidth,\n plotBottom,\n colorBar: fit\n ? colorBarLayout(colorBar, {\n x: marginLeft + plotWidth + fit.gap,\n y1: marginTop,\n y2: plotBottom,\n })\n : null,\n height: plotBottom + AXIS_HEIGHT,\n };\n}\n","import { collapseToDisplay } from \"./collapse\";\nimport { PlotLabels, shapLabels } from \"./labels\";\nimport {\n ColorBarGeometry,\n ColorBarSpec,\n colorBarExtent,\n colorBarLayout,\n fitColorBar,\n scalarFormatterLabels,\n} from \"./colorBar\";\nimport { sampleColormap } from \"./colormap\";\nimport { formatShapValue } from \"./format\";\nimport { RowSort, sortDisplayRows } from \"./rowSort\";\nimport {\n AXIS_TITLE_DY,\n AxisSpine,\n AxisTick,\n AxisTitle,\n niceTicks,\n tickLabel,\n tickSpace,\n} from \"./ticks\";\nimport { globalImportance, orderFeatures } from \"./order\";\nimport { ParsedExplanation } from \"./types\";\n\nconst FX_TOP = 8;\nconst FX_BOTTOM_GAP = 12;\nconst SEPARATOR_GAP = 4;\nconst SIDE_BAR_GAP = 10;\nconst SIDE_BAR_RIGHT_INSET = 40;\nconst SIDE_BAR_HEIGHT_RATIO = 0.6;\n/** Room below the grid for ticks, their labels and the Instances title. */\nconst AXIS_HEIGHT = 52;\n/** _heatmap.py leaves x tick labels at matplotlib's default \"medium\", 10 pt. */\nconst TICK_LABEL_PT = 10;\n/**\n * plt.colorbar(..., fraction=0.01, pad=0.10), _heatmap.py:180-186, as fractions\n * of the axes before the bar is taken out of it: the gap is 0.10 of that and the\n * plot keeps 0.89.\n */\nconst COLOR_BAR_GAP_RATIO = 0.1 / 0.89;\n/** Space kept between the longest side bar and the colour bar. */\nconst COLOR_BAR_CLEARANCE = 8;\n\nexport type HeatmapCell = {\n /** Original index in the Explanation, before Sample ordering. */\n sampleIndex: number;\n /** Full-precision SHAP value represented by this cell. */\n value: number;\n /** Value after clipping to the shared symmetric colour domain. */\n colorValue: number;\n color: string;\n};\n\nexport type HeatmapColumn = {\n sampleIndex: number;\n /** The record UUID: the key for click-through, never shown to a person. */\n sampleId?: string;\n /** What a person reads for this Sample, when the payload carries labels. */\n sampleLabel?: string;\n /** Sum of all SHAP values for this Sample. */\n total: number;\n};\n\nexport type HeatmapRow = {\n label: string;\n featureIndex: number | null;\n isOtherRow: boolean;\n importance: number;\n /** Importance divided by the largest displayed-row importance. */\n sideBarValue: number;\n cells: HeatmapCell[];\n};\n\nexport type HeatmapRows = {\n /** Header of the uploaded column the labels came from, when it had one. */\n sampleLabelColumn?: string;\n /** Feature rows in top-to-bottom display order. */\n rows: HeatmapRow[];\n /** Sample columns in descending total-attribution order. */\n columns: HeatmapColumn[];\n /** Sample totals in the same order as columns. */\n fxLine: number[];\n vmin: number;\n vmax: number;\n collapsedCount: number;\n};\n\nexport type HeatmapLayoutOptions = {\n width: number;\n rowHeight: number;\n marginLeft: number;\n marginRight: number;\n /** Top of the matrix; the f(x) line occupies the space above it. */\n marginTop: number;\n /** Draw SHAP's SHAP value colour bar right of the side bars. */\n colorBar?: boolean;\n /** Resolved wording; SHAP's when omitted. */\n labels?: PlotLabels;\n};\n\nexport type HeatmapCellGeometry = HeatmapCell & {\n x: number;\n y: number;\n width: number;\n height: number;\n};\n\nexport type HeatmapSideBarGeometry = {\n value: number;\n x: number;\n y: number;\n width: number;\n height: number;\n};\n\nexport type HeatmapRowGeometry = Omit<HeatmapRow, \"cells\"> & {\n centerY: number;\n cells: HeatmapCellGeometry[];\n sideBar: HeatmapSideBarGeometry;\n};\n\nexport type HeatmapColumnGeometry = HeatmapColumn & {\n x: number;\n centerX: number;\n width: number;\n};\n\nexport type HeatmapLinePoint = {\n sampleIndex: number;\n sampleId?: string;\n value: number;\n x: number;\n y: number;\n};\n\nexport type HeatmapAxisMark = {\n value: number;\n y: number;\n label: string;\n};\n\nexport type HeatmapSpine = { x: number; y1: number; y2: number };\n\n/** An outward tick on the left edge, one per Feature row. */\nexport type HeatmapYTick = { y: number; x1: number; x2: number };\n\n/** matplotlib's default major tick, 3.5 pt, at the 100 dpi SHAP renders at. */\nconst Y_TICK_LENGTH = 5;\n\nexport type HeatmapLayout = {\n sampleLabelColumn?: string;\n rows: HeatmapRowGeometry[];\n columns: HeatmapColumnGeometry[];\n fxLine: HeatmapLinePoint[];\n fxAxisMarks: HeatmapAxisMark[];\n fxDomain: [number, number];\n separatorY: number;\n gridLeft: number;\n gridRight: number;\n gridTop: number;\n plotBottom: number;\n /**\n * _heatmap.py:135 shows the left and right spines (and hides top and bottom),\n * and :136 bounds them with set_bounds(n - row_height, -row_height). With\n * row_height = 0.5 (:116) that is exactly the outer edge of the first and last\n * row — so they frame the grid and stop short of the f(x) chart above it. The\n * side bars are drawn with clip_on=False (:173), outside the right spine.\n */\n spines: { left: HeatmapSpine; right: HeatmapSpine };\n /** yaxis.set_ticks_position(\"left\") with tick_params(direction=\"out\"), :134,:138. */\n yTicks: HeatmapYTick[];\n /** Ticks along the Sample axis, at the centre of each ticked column. */\n xTicks: AxisTick[];\n /** Always null: _heatmap.py:137 hides the bottom spine. */\n xSpine: AxisSpine | null;\n xTitle: AxisTitle;\n plotWidth: number;\n cellWidth: number;\n /** Null unless the options asked for one. */\n colorBar: ColorBarGeometry | null;\n height: number;\n};\n\nfunction percentile(values: number[], fraction: number): number {\n if (values.length === 0) return 0;\n const sorted = [...values].sort((a, b) => a - b);\n const position = (sorted.length - 1) * fraction;\n const lower = Math.floor(position);\n const upper = Math.ceil(position);\n const weight = position - lower;\n return sorted[lower] + (sorted[upper] - sorted[lower]) * weight;\n}\n\n/** Computes the SHAP-compatible orderings, collapse, colours, line, and side bars in value space. */\nexport function heatmapRows(\n explanation: ParsedExplanation,\n maxDisplay: number,\n faithfulOtherRow: boolean,\n rowSort: RowSort = \"importance\",\n labels: PlotLabels = shapLabels,\n): HeatmapRows {\n if (!Number.isInteger(maxDisplay) || maxDisplay <= 0) {\n throw new RangeError(`maxDisplay must be a positive integer, received ${maxDisplay}`);\n }\n\n const importance = globalImportance(explanation);\n const featureOrder = orderFeatures(importance);\n // Which rows are shown is decided by importance; rowSort only reorders them.\n // Sample columns keep SHAP's order either way.\n const display = sortDisplayRows(\n collapseToDisplay(\n explanation.featureNames,\n importance,\n featureOrder,\n maxDisplay,\n faithfulOtherRow,\n labels,\n ),\n rowSort,\n explanation.data,\n );\n const displayedFeatures = new Set(\n display.rows.flatMap((row) => row.featureIndex === null ? [] : [row.featureIndex]),\n );\n const collapsedFeatures = featureOrder.filter((index) => !displayedFeatures.has(index));\n\n const columns = explanation.values\n .map((sample, sampleIndex): HeatmapColumn => ({\n sampleIndex,\n sampleId: explanation.sampleIds?.[sampleIndex],\n sampleLabel: explanation.sampleLabels?.[sampleIndex],\n total: sample.reduce((sum, value) => sum + value, 0),\n }))\n .sort((a, b) => (b.total - a.total) || (a.sampleIndex - b.sampleIndex));\n\n const uncolouredRows = display.rows.map((displayRow) => {\n const featureIndices = displayRow.featureIndex === null\n ? collapsedFeatures\n : [displayRow.featureIndex];\n return {\n displayRow,\n cells: columns.map((column) => ({\n sampleIndex: column.sampleIndex,\n value: featureIndices.reduce(\n (sum, featureIndex) => sum + explanation.values[column.sampleIndex][featureIndex],\n 0,\n ),\n })),\n };\n });\n\n const allCells = uncolouredRows.flatMap((row) => row.cells.map((cell) => cell.value));\n const lower = percentile(allCells, 0.01);\n const upper = percentile(allCells, 0.99);\n const limit = Math.max(-lower, upper, 0);\n const vmin = limit === 0 ? 0 : -limit;\n const vmax = limit;\n const largestImportance = Math.max(0, ...display.rows.map((row) => row.value));\n\n const rows = uncolouredRows.map(({ displayRow, cells }): HeatmapRow => ({\n label: displayRow.label,\n featureIndex: displayRow.featureIndex,\n isOtherRow: displayRow.isOtherRow,\n importance: displayRow.value,\n sideBarValue: largestImportance === 0 ? 0 : displayRow.value / largestImportance,\n cells: cells.map((cell): HeatmapCell => {\n const colorValue = limit === 0\n ? 0\n : Math.max(-limit, Math.min(limit, cell.value));\n const normalized = limit === 0 ? 0.5 : (colorValue + limit) / (2 * limit);\n return {\n ...cell,\n colorValue,\n color: sampleColormap(\"red_white_blue\", normalized),\n };\n }),\n }));\n\n return {\n rows,\n columns,\n fxLine: columns.map((column) => column.total),\n vmin,\n vmax,\n collapsedCount: display.collapsedCount,\n sampleLabelColumn: explanation.sampleLabelColumn,\n };\n}\n\n/**\n * The Sample axis. xlim(-0.5, n - 0.5) at _heatmap.py:151 puts integer i at the\n * centre of column i, so a tick's position is its column's centre. Integer steps\n * only: the axis counts Samples, and for a handful of them there is no Sample 0.5.\n */\nfunction heatmapXAxis(\n sampleCount: number,\n marginLeft: number,\n cellWidth: number,\n plotWidth: number,\n plotBottom: number,\n title: string,\n): Pick<HeatmapLayout, \"xTicks\" | \"xSpine\" | \"xTitle\"> {\n const { ticks, step } = niceTicks(\n -0.5,\n sampleCount - 0.5,\n tickSpace(plotWidth, TICK_LABEL_PT),\n { integer: true },\n );\n return {\n xTicks: ticks.map((value) => ({\n value,\n x: marginLeft + (value + 0.5) * cellWidth,\n label: tickLabel(value, step),\n })),\n xSpine: null,\n xTitle: {\n text: title,\n x: marginLeft + plotWidth / 2,\n y: plotBottom + AXIS_TITLE_DY,\n fontSize: TICK_LABEL_PT,\n },\n };\n}\n\n/** Projects heatmap value-space rows into SVG geometry. */\nexport function heatmapLayout(\n valueRows: HeatmapRows,\n opts: HeatmapLayoutOptions,\n): HeatmapLayout {\n const { width, rowHeight, marginLeft, marginRight, marginTop } = opts;\n const labels = opts.labels ?? shapLabels;\n const plotBottom = marginTop + valueRows.rows.length * rowHeight;\n const sideBarWidth = Math.max(0, marginRight - SIDE_BAR_RIGHT_INSET - SIDE_BAR_GAP);\n\n // _heatmap.py:180-188: ticks at the two ends of the symmetric colour range,\n // labelled by matplotlib's default formatter.\n const ticks = scalarFormatterLabels([valueRows.vmin, valueRows.vmax]);\n const colorBarSpec: ColorBarSpec = {\n colormap: \"red_white_blue\",\n tickLabels: [ticks.labels[0], ticks.labels[1]],\n ...(ticks.offsetText ? { offsetText: ticks.offsetText } : {}),\n label: labels.shapValueAxis,\n labelPad: -10,\n };\n // The bar is sized against the whole axes, which includes the f(x) chart.\n const colorBarTop = FX_TOP;\n const fit = opts.colorBar\n ? fitColorBar({\n plotWidth: width - marginLeft - marginRight,\n available: marginRight,\n gapRatio: COLOR_BAR_GAP_RATIO,\n minGap: SIDE_BAR_GAP + sideBarWidth + COLOR_BAR_CLEARANCE,\n extent: colorBarExtent(plotBottom - colorBarTop, colorBarSpec),\n })\n : null;\n const plotWidth = fit ? fit.plotWidth : width - marginLeft - marginRight;\n const cellWidth = valueRows.columns.length === 0 ? 0 : plotWidth / valueRows.columns.length;\n const gridRight = marginLeft + plotWidth;\n\n const columns = valueRows.columns.map((column, index): HeatmapColumnGeometry => ({\n ...column,\n x: marginLeft + index * cellWidth,\n centerX: marginLeft + (index + 0.5) * cellWidth,\n width: cellWidth,\n }));\n\n const rows = valueRows.rows.map((row, rowIndex): HeatmapRowGeometry => {\n const y = marginTop + rowIndex * rowHeight;\n const centerY = y + rowHeight / 2;\n const barHeight = rowHeight * SIDE_BAR_HEIGHT_RATIO;\n return {\n ...row,\n centerY,\n cells: row.cells.map((cell, columnIndex): HeatmapCellGeometry => ({\n ...cell,\n x: marginLeft + columnIndex * cellWidth,\n y,\n width: cellWidth,\n height: rowHeight,\n })),\n sideBar: {\n value: row.sideBarValue,\n x: gridRight + SIDE_BAR_GAP,\n y: centerY - barHeight / 2,\n width: row.sideBarValue * sideBarWidth,\n height: barHeight,\n },\n };\n });\n\n const fxMin = Math.min(0, ...valueRows.fxLine);\n const fxMax = Math.max(0, ...valueRows.fxLine);\n const fxSpan = fxMax - fxMin || 1;\n const fxBottom = Math.max(FX_TOP, marginTop - FX_BOTTOM_GAP);\n const toY = (value: number) => FX_TOP + (fxMax - value) / fxSpan * (fxBottom - FX_TOP);\n const fxLine = columns.map((column, index): HeatmapLinePoint => ({\n sampleIndex: column.sampleIndex,\n sampleId: column.sampleId,\n value: valueRows.fxLine[index],\n x: column.centerX,\n y: toY(valueRows.fxLine[index]),\n }));\n const axisValues = [fxMax, 0, fxMin].filter(\n (value, index, values) => values.indexOf(value) === index,\n );\n\n return {\n rows,\n columns,\n fxLine,\n fxAxisMarks: axisValues.map((value) => ({\n value,\n y: toY(value),\n label: formatShapValue(value),\n })),\n fxDomain: [fxMin, fxMax],\n separatorY: marginTop - SEPARATOR_GAP,\n gridLeft: marginLeft,\n gridRight,\n gridTop: marginTop,\n plotBottom,\n spines: {\n left: { x: marginLeft, y1: marginTop, y2: plotBottom },\n right: { x: gridRight, y1: marginTop, y2: plotBottom },\n },\n yTicks: rows.map((row) => ({\n y: row.centerY,\n x1: marginLeft - Y_TICK_LENGTH,\n x2: marginLeft,\n })),\n ...heatmapXAxis(\n valueRows.columns.length,\n marginLeft,\n cellWidth,\n plotWidth,\n plotBottom,\n labels.samples,\n ),\n sampleLabelColumn: valueRows.sampleLabelColumn,\n plotWidth,\n cellWidth,\n colorBar: fit\n ? colorBarLayout(colorBarSpec, {\n x: gridRight + fit.gap,\n y1: colorBarTop,\n y2: plotBottom,\n })\n : null,\n height: plotBottom + AXIS_HEIGHT,\n };\n}\n"],"mappings":"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AAAA,IACF,SAAS,CAAC,KAAK,GAAG;AAAA,IAClB;AAAA,IACA,UAAU;AAAA,IACV,WAAW,aAAa;AAAA,IACxB;AAAA,IACA,QAAQ,aAAaE;AAAA,EACvB;AACF;;;AC1WA;AAAA,EACC,MAAQ;AAAA,EACR,UAAY;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD;AAAA,EACA,gBAAkB;AAAA,IACjB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;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","names":["MINUS","BAR_THICKNESS_RATIO","AXIS_HEIGHT","TICK_LABEL_PT","BAR_THICKNESS_RATIO","AXIS_HEIGHT","TICK_LABEL_PT","MINUS","AXIS_HEIGHT","TICK_LABEL_PT","TITLE_PT","X_MARGIN","AXIS_HEIGHT","TICK_LABEL_PT","COLOR_BAR_GAP_RATIO","percentile","colorBarSpec"]}
package/dist/index.cjs CHANGED
@@ -48,6 +48,7 @@ __export(index_exports, {
48
48
  heatmapRows: () => heatmapRows,
49
49
  orderFeatures: () => orderFeatures,
50
50
  parseExplanation: () => parseExplanation,
51
+ placeTooltip: () => placeTooltip,
51
52
  resolveLabels: () => resolveLabels,
52
53
  sampleColormap: () => sampleColormap,
53
54
  scalarFormatterLabels: () => scalarFormatterLabels,
@@ -1336,6 +1337,22 @@ function scalarFormatterLabels(locs) {
1336
1337
  return order === 0 ? { labels } : { labels, offsetText: `1e${String(order).replace("-", MINUS3)}` };
1337
1338
  }
1338
1339
 
1340
+ // src/core/tooltip.ts
1341
+ var CHAR_PX = 6.5;
1342
+ var PADDING_X = 7;
1343
+ var OFFSET = 8;
1344
+ var RAISE = 24;
1345
+ function placeTooltip(opts) {
1346
+ const { anchorX, anchorY, lines, lineHeight, minWidth, chartWidth, chartHeight } = opts;
1347
+ const longest = lines.reduce((max, line) => Math.max(max, line.length), 0);
1348
+ const width = Math.max(minWidth, longest * CHAR_PX + 2 * PADDING_X);
1349
+ const height = lines.length * lineHeight + lineHeight * 0.6;
1350
+ const right = anchorX + OFFSET;
1351
+ const x = right + width <= chartWidth ? right : Math.max(0, anchorX - OFFSET - width);
1352
+ const y = Math.max(0, Math.min(anchorY - RAISE, chartHeight - height));
1353
+ return { x, y, width, height };
1354
+ }
1355
+
1339
1356
  // src/core/beeswarmLayout.ts
1340
1357
  var BEESWARM_MISSING_COLOR = "#777777";
1341
1358
  var BEESWARM_ROW_HEIGHT = 0.4;
@@ -1812,6 +1829,7 @@ function heatmapLayout(valueRows, opts) {
1812
1829
  heatmapRows,
1813
1830
  orderFeatures,
1814
1831
  parseExplanation,
1832
+ placeTooltip,
1815
1833
  resolveLabels,
1816
1834
  sampleColormap,
1817
1835
  scalarFormatterLabels,