shap-svg 0.2.3 → 0.3.0

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.
@@ -221,6 +221,10 @@ function parseExplanation(input, opts = {}) {
221
221
  nFeatures
222
222
  };
223
223
  }
224
+ function sampleDisplayName(parsed, sampleIndex, words) {
225
+ var _a, _b;
226
+ return (_b = (_a = parsed.sampleLabels) == null ? void 0 : _a[sampleIndex]) != null ? _b : words.sampleFallback(sampleIndex + 1);
227
+ }
224
228
 
225
229
  // src/core/order.ts
226
230
  function globalImportance(e) {
@@ -238,6 +242,9 @@ function orderFeatures(importance) {
238
242
  var MINUS = "\u2212";
239
243
  var EXPONENT_THRESHOLD = 1e-3;
240
244
  var PERCENT_DECIMALS = 2;
245
+ function formatFixed(v, decimals) {
246
+ return Math.abs(v).toFixed(decimals);
247
+ }
241
248
  function formatShapValue(v, decimals) {
242
249
  if (v === 0) return "0";
243
250
  return (v < 0 ? MINUS : "+") + magnitudeOf(v, decimals);
@@ -275,7 +282,21 @@ var shapLabels = {
275
282
  sampleFallback: (sampleNumber) => `Sample ${sampleNumber}`,
276
283
  baseValue: "E[f(X)]",
277
284
  modelOutput: "f(x)",
278
- otherFeatures: (count, style) => style === "sum" ? `Sum of ${count} other features` : `${count} other features`
285
+ higher: "higher",
286
+ lower: "lower",
287
+ otherFeatures: (count, style) => style === "sum" ? `Sum of ${count} other features` : `${count} other features`,
288
+ absent: "Absent",
289
+ absentWithCount: (count) => `Absent (n = ${count})`,
290
+ principalComponent: (index, varianceRatio) => `SHAP PC${index} (${formatFixed(varianceRatio * 100, 0)}% of SHAP variance)`,
291
+ cumulativeShapValue: "Model output",
292
+ clusterDistance: "Clustering cutoff",
293
+ interactionScore: (score) => `interaction ${formatFixed(score, 2)}`,
294
+ weakInteraction: "no strong interaction found",
295
+ trend: "Median trend",
296
+ tableCaption: "Chart data",
297
+ componentTracksTotal: (r) => `tracks \u03A3\u03C6, r = ${formatFixed(r, 2)}`,
298
+ colorScale: (what) => `Colour: ${what}`,
299
+ suppliedComponent: (index) => `Dimension ${index}`
279
300
  };
280
301
  function resolveLabels(labels) {
281
302
  if (!labels) return shapLabels;
@@ -319,6 +340,140 @@ function collapseToDisplay(featureNames, importance, order, maxDisplay, faithful
319
340
  return { rows, collapsedCount: collapsed.length };
320
341
  }
321
342
 
343
+ // src/core/hclust.ts
344
+ var MAX_DISTANCE = 1;
345
+ function absCorrelation(a, b) {
346
+ const n = a.length;
347
+ let meanA = 0;
348
+ let meanB = 0;
349
+ for (let i = 0; i < n; i++) {
350
+ meanA += a[i];
351
+ meanB += b[i];
352
+ }
353
+ meanA /= n;
354
+ meanB /= n;
355
+ let cov = 0;
356
+ let varA = 0;
357
+ let varB = 0;
358
+ for (let i = 0; i < n; i++) {
359
+ const da = a[i] - meanA;
360
+ const db = b[i] - meanB;
361
+ cov += da * db;
362
+ varA += da * da;
363
+ varB += db * db;
364
+ }
365
+ if (!(varA > 0) || !(varB > 0)) return 0;
366
+ return Math.min(1, Math.abs(cov / Math.sqrt(varA * varB)));
367
+ }
368
+ function correlationDistances(columns) {
369
+ const k = columns.length;
370
+ const out = Array.from({ length: k }, () => new Array(k).fill(0));
371
+ for (let i = 0; i < k; i++) {
372
+ for (let j = i + 1; j < k; j++) {
373
+ const distance = MAX_DISTANCE - absCorrelation(columns[i], columns[j]);
374
+ out[i][j] = distance;
375
+ out[j][i] = distance;
376
+ }
377
+ }
378
+ return out;
379
+ }
380
+ function averageLinkage(distances) {
381
+ const k = distances.length;
382
+ if (k < 2) return [];
383
+ const members = /* @__PURE__ */ new Map();
384
+ for (let i = 0; i < k; i++) members.set(i, [i]);
385
+ const between = (a, b) => {
386
+ let sum = 0;
387
+ for (const i of a) for (const j of b) sum += distances[i][j];
388
+ return sum / (a.length * b.length);
389
+ };
390
+ const linkage = [];
391
+ for (let step = 0; step < k - 1; step++) {
392
+ const ids = [...members.keys()].sort((a, b) => a - b);
393
+ let bestLeft = ids[0];
394
+ let bestRight = ids[1];
395
+ let bestDistance = Infinity;
396
+ for (let a = 0; a < ids.length; a++) {
397
+ for (let b = a + 1; b < ids.length; b++) {
398
+ const d = between(members.get(ids[a]), members.get(ids[b]));
399
+ if (d < bestDistance - 1e-12) {
400
+ bestDistance = d;
401
+ bestLeft = ids[a];
402
+ bestRight = ids[b];
403
+ }
404
+ }
405
+ }
406
+ const merged = [...members.get(bestLeft), ...members.get(bestRight)];
407
+ members.delete(bestLeft);
408
+ members.delete(bestRight);
409
+ members.set(k + step, merged);
410
+ linkage.push([bestLeft, bestRight, bestDistance, merged.length]);
411
+ }
412
+ return linkage;
413
+ }
414
+ function copheneticDistances(linkage) {
415
+ const k = linkage.length + 1;
416
+ const out = Array.from({ length: k }, () => new Array(k).fill(0));
417
+ const leaves = /* @__PURE__ */ new Map();
418
+ for (let i = 0; i < k; i++) leaves.set(i, [i]);
419
+ linkage.forEach((row, step) => {
420
+ const left = leaves.get(row[0]);
421
+ const right = leaves.get(row[1]);
422
+ for (const i of left) for (const j of right) {
423
+ out[i][j] = row[2];
424
+ out[j][i] = row[2];
425
+ }
426
+ leaves.set(k + step, [...left, ...right]);
427
+ });
428
+ return out;
429
+ }
430
+ function leafOrder(linkage, leafValues) {
431
+ var _a;
432
+ const k = linkage.length + 1;
433
+ if (k === 1) return [0];
434
+ const peak = /* @__PURE__ */ new Map();
435
+ const children = /* @__PURE__ */ new Map();
436
+ for (let i = 0; i < k; i++) peak.set(i, (_a = leafValues[i]) != null ? _a : 0);
437
+ linkage.forEach((row, step) => {
438
+ var _a2, _b;
439
+ const id = k + step;
440
+ children.set(id, [row[0], row[1]]);
441
+ peak.set(id, Math.max((_a2 = peak.get(row[0])) != null ? _a2 : 0, (_b = peak.get(row[1])) != null ? _b : 0));
442
+ });
443
+ const out = [];
444
+ const walk = (id) => {
445
+ var _a2, _b;
446
+ const pair = children.get(id);
447
+ if (!pair) {
448
+ out.push(id);
449
+ return;
450
+ }
451
+ const [left, right] = pair;
452
+ const heavierFirst = ((_a2 = peak.get(left)) != null ? _a2 : 0) >= ((_b = peak.get(right)) != null ? _b : 0) ? [left, right] : [right, left];
453
+ walk(heavierFirst[0]);
454
+ walk(heavierFirst[1]);
455
+ };
456
+ walk(k + linkage.length - 1);
457
+ return out;
458
+ }
459
+ function relaxSortOrder(cophenetic, clusterOrder, cutoff, importanceOrder) {
460
+ const taken = /* @__PURE__ */ new Set();
461
+ const out = [];
462
+ for (const index of importanceOrder) {
463
+ if (taken.has(index)) continue;
464
+ taken.add(index);
465
+ out.push(index);
466
+ for (const neighbour of clusterOrder) {
467
+ if (taken.has(neighbour)) continue;
468
+ if (cophenetic[index][neighbour] <= cutoff) {
469
+ taken.add(neighbour);
470
+ out.push(neighbour);
471
+ }
472
+ }
473
+ }
474
+ return out;
475
+ }
476
+
322
477
  // src/core/ticks.ts
323
478
  var MINUS2 = "\u2212";
324
479
  var PX_TO_PT = 0.75;
@@ -375,6 +530,41 @@ function tickLabel(value, step, percent = false) {
375
530
  // src/core/barLayout.ts
376
531
  var POSITIVE_COLOR = "#ff0051";
377
532
  var NEGATIVE_COLOR = "#008bfb";
533
+ var CLUSTERING_POOL = 50;
534
+ function clusteredOrder(input) {
535
+ const { parsed, importanceOrder, mode, cutoff } = input;
536
+ const poolSize = Array.isArray(mode) ? mode.length + 1 : CLUSTERING_POOL;
537
+ const pool = importanceOrder.slice(0, Math.min(poolSize, importanceOrder.length));
538
+ let linkage;
539
+ if (Array.isArray(mode)) {
540
+ for (const row of mode) {
541
+ if (row.length !== 4) {
542
+ throw new RangeError("a clustering matrix must have rows of width 4");
543
+ }
544
+ }
545
+ linkage = mode;
546
+ } else {
547
+ const source = mode === "shap" ? parsed.values : parsed.data;
548
+ const columns = pool.map((j) => source.map((row) => row[j]));
549
+ linkage = averageLinkage(correlationDistances(columns));
550
+ }
551
+ if (linkage.length === 0) return { order: importanceOrder, linkage };
552
+ const importanceWithinPool = pool.map((_, position) => position);
553
+ const meanAbs = pool.map((j) => {
554
+ let sum = 0;
555
+ for (const row of parsed.values) sum += Math.abs(row[j]);
556
+ return sum / parsed.nSamples;
557
+ });
558
+ const clusterPositions = leafOrder(linkage, meanAbs);
559
+ const relaxed = relaxSortOrder(
560
+ copheneticDistances(linkage),
561
+ clusterPositions,
562
+ cutoff,
563
+ importanceWithinPool
564
+ );
565
+ const order = [...relaxed.map((position) => pool[position]), ...importanceOrder.slice(pool.length)];
566
+ return { order, linkage };
567
+ }
378
568
  var BAR_THICKNESS_RATIO = 0.7;
379
569
  var AXIS_HEIGHT = 52;
380
570
  var TICK_LABEL_PT = 11;
@@ -1178,6 +1368,7 @@ function sampleColormap(name, t) {
1178
1368
  const blue = channel(lower, 5) + (channel(upper, 5) - channel(lower, 5)) * fraction;
1179
1369
  return `#${hexByte(red)}${hexByte(green)}${hexByte(blue)}`;
1180
1370
  }
1371
+ var UNCOLOURED = "#1f77b4";
1181
1372
 
1182
1373
  // src/core/colorBar.ts
1183
1374
  var ASPECT = 80;
@@ -1240,6 +1431,7 @@ function colorBarLayout(spec, position) {
1240
1431
  x: labelLeft + LABEL_PT * LABEL_BASELINE_EM,
1241
1432
  y: (y1 + y2) / 2,
1242
1433
  text: spec.label,
1434
+ ...spec.labelParts ? { parts: spec.labelParts } : {},
1243
1435
  fontSize: LABEL_PT
1244
1436
  },
1245
1437
  right: Math.max(
@@ -1277,22 +1469,37 @@ function scalarFormatterLabels(locs) {
1277
1469
  });
1278
1470
  return order === 0 ? { labels } : { labels, offsetText: `1e${String(order).replace("-", MINUS3)}` };
1279
1471
  }
1472
+ function colorScaleTitleParts(title, taxon2) {
1473
+ const at = title.indexOf(taxon2);
1474
+ if (at < 0) return void 0;
1475
+ return [
1476
+ { text: title.slice(0, at) },
1477
+ { text: taxon2, italic: true },
1478
+ { text: title.slice(at + taxon2.length) }
1479
+ ];
1480
+ }
1280
1481
 
1281
1482
  // src/core/tooltip.ts
1282
- var CHAR_PX = 6.5;
1483
+ var GLYPH_PX = { body: 6.5, axis: 5.6 };
1283
1484
  var PADDING_X = 7;
1284
1485
  var OFFSET = 8;
1285
1486
  var RAISE = 24;
1286
1487
  function placeTooltip(opts) {
1287
1488
  const { anchorX, anchorY, lines, lineHeight, minWidth, chartWidth, chartHeight } = opts;
1288
1489
  const longest = lines.reduce((max, line) => Math.max(max, line.length), 0);
1289
- const width = Math.max(minWidth, longest * CHAR_PX + 2 * PADDING_X);
1490
+ const width = Math.max(minWidth, longest * GLYPH_PX.body + 2 * PADDING_X);
1290
1491
  const height = lines.length * lineHeight + lineHeight * 0.6;
1291
1492
  const right = anchorX + OFFSET;
1292
1493
  const x = right + width <= chartWidth ? right : Math.max(0, anchorX - OFFSET - width);
1293
1494
  const y = Math.max(0, Math.min(anchorY - RAISE, chartHeight - height));
1294
1495
  return { x, y, width, height };
1295
1496
  }
1497
+ var runsToText = (runs) => runs.map((run) => run.text).join("");
1498
+ var namedValue = (name, value) => [
1499
+ { text: name, italic: true },
1500
+ { text: `: ${value}` }
1501
+ ];
1502
+ var TOOLTIP_LINE_HEIGHT = 15;
1296
1503
 
1297
1504
  // src/core/beeswarmLayout.ts
1298
1505
  var BEESWARM_MISSING_COLOR = "#777777";
@@ -1741,6 +1948,686 @@ function heatmapLayout(valueRows, opts) {
1741
1948
  };
1742
1949
  }
1743
1950
 
1951
+ // src/core/stats.ts
1952
+ function pearson(a, b) {
1953
+ const n = a.length;
1954
+ if (n < 2) return 0;
1955
+ let meanA = 0;
1956
+ let meanB = 0;
1957
+ for (let i = 0; i < n; i++) {
1958
+ meanA += a[i];
1959
+ meanB += b[i];
1960
+ }
1961
+ meanA /= n;
1962
+ meanB /= n;
1963
+ let cov = 0;
1964
+ let varA = 0;
1965
+ let varB = 0;
1966
+ for (let i = 0; i < n; i++) {
1967
+ const da = a[i] - meanA;
1968
+ const db = b[i] - meanB;
1969
+ cov += da * db;
1970
+ varA += da * da;
1971
+ varB += db * db;
1972
+ }
1973
+ if (!(varA > 0) || !(varB > 0)) return 0;
1974
+ return cov / Math.sqrt(varA * varB);
1975
+ }
1976
+
1977
+ // src/core/interactions.ts
1978
+ var MAX_WINDOW = 50;
1979
+ function interactionWindowSize(sampleCount) {
1980
+ return Math.max(Math.min(Math.floor(sampleCount / 10), MAX_WINDOW), 1);
1981
+ }
1982
+ function eligibleInteractionFeature(column) {
1983
+ return column.some((value) => Math.abs(value) >= 1e-8) && column.some((value) => value !== column[0]);
1984
+ }
1985
+ function interactionScores(featureIndex, values, data) {
1986
+ var _a, _b;
1987
+ const sampleCount = data.length;
1988
+ const featureCount = (_b = (_a = data[0]) == null ? void 0 : _a.length) != null ? _b : 0;
1989
+ const scores = new Array(featureCount).fill(0);
1990
+ if (sampleCount < 2) return scores;
1991
+ const order = Array.from({ length: sampleCount }, (_, i) => i).sort(
1992
+ (a, b) => data[a][featureIndex] - data[b][featureIndex] || a - b
1993
+ );
1994
+ const sortedShap = order.map((i) => values[i][featureIndex]);
1995
+ const window = interactionWindowSize(sampleCount);
1996
+ const windowCount = Math.ceil(sampleCount / window);
1997
+ for (let j = 0; j < featureCount; j++) {
1998
+ if (j === featureIndex) continue;
1999
+ const sortedOther = order.map((i) => data[i][j]);
2000
+ if (!eligibleInteractionFeature(sortedOther)) continue;
2001
+ let sum = 0;
2002
+ for (let start = 0; start < sampleCount; start += window) {
2003
+ const shapSlice = sortedShap.slice(start, start + window);
2004
+ const otherSlice = sortedOther.slice(start, start + window);
2005
+ sum += Math.abs(pearson(shapSlice, otherSlice));
2006
+ }
2007
+ scores[j] = sum / windowCount;
2008
+ }
2009
+ return scores;
2010
+ }
2011
+ function strongestInteraction(featureIndex, values, data) {
2012
+ const scores = interactionScores(featureIndex, values, data);
2013
+ let best = -1;
2014
+ let bestScore = -1;
2015
+ scores.forEach((score, index) => {
2016
+ if (index === featureIndex) return;
2017
+ const column = data.map((row) => row[index]);
2018
+ if (eligibleInteractionFeature(column) && score > bestScore) {
2019
+ bestScore = score;
2020
+ best = index;
2021
+ }
2022
+ });
2023
+ return best === -1 ? null : { index: best, score: bestScore };
2024
+ }
2025
+
2026
+ // src/core/scatterLayout.ts
2027
+ var MIN_TREND_SAMPLES = 4;
2028
+ var FLAT_DOMAIN_FACTOR = 10;
2029
+ function scatterPoints(parsed, featureIndex) {
2030
+ const absent = [];
2031
+ const detected = [];
2032
+ for (let i = 0; i < parsed.nSamples; i++) {
2033
+ const point = {
2034
+ sampleIndex: i,
2035
+ value: parsed.data[i][featureIndex],
2036
+ shap: parsed.values[i][featureIndex]
2037
+ };
2038
+ (point.value > 0 ? detected : absent).push(point);
2039
+ }
2040
+ detected.sort((a, b) => a.value - b.value || a.sampleIndex - b.sampleIndex);
2041
+ return { absent, detected };
2042
+ }
2043
+ function logDomain(detected) {
2044
+ if (detected.length === 0) return [1, 10];
2045
+ const low = detected[0].value;
2046
+ const high = detected[detected.length - 1].value;
2047
+ if (low === high) return [low / FLAT_DOMAIN_FACTOR, high * FLAT_DOMAIN_FACTOR];
2048
+ return [low, high];
2049
+ }
2050
+ function median(sorted) {
2051
+ const middle = Math.floor(sorted.length / 2);
2052
+ return sorted.length % 2 === 1 ? sorted[middle] : (sorted[middle - 1] + sorted[middle]) / 2;
2053
+ }
2054
+ function binnedMedianTrend(detected) {
2055
+ if (detected.length < MIN_TREND_SAMPLES) return [];
2056
+ const window = interactionWindowSize(detected.length);
2057
+ const out = [];
2058
+ for (let start = 0; start < detected.length; start += window) {
2059
+ const slice = detected.slice(start, start + window);
2060
+ if (slice.length === 0) continue;
2061
+ out.push({
2062
+ value: median(slice.map((p) => p.value)),
2063
+ shap: median([...slice.map((p) => p.shap)].sort((a, b) => a - b))
2064
+ });
2065
+ }
2066
+ return out;
2067
+ }
2068
+ var MARGIN = { right: 24, top: 16, bottom: 56 };
2069
+ var ABSENT_BAND = 56;
2070
+ var ABSENT_GAP = 40;
2071
+ var DOT_RADIUS = 4;
2072
+ var TICK_LABEL_PT6 = 11;
2073
+ var Y_TITLE_X = 14;
2074
+ var sampleName = (parsed, sampleIndex, words) => sampleDisplayName(parsed, sampleIndex, words);
2075
+ function scatterTooltipLines(parsed, sampleIndex, featureIndex, colorFeatureIndex, words) {
2076
+ const abundance = (index) => {
2077
+ const value = parsed.data[sampleIndex][index];
2078
+ return namedValue(
2079
+ formatFeatureLabel(parsed.featureNames[index]),
2080
+ value > 0 ? formatLevel(value) : words.absent
2081
+ );
2082
+ };
2083
+ const lines = [
2084
+ [{ text: sampleName(parsed, sampleIndex, words) }],
2085
+ abundance(featureIndex)
2086
+ ];
2087
+ if (colorFeatureIndex !== null) lines.push(abundance(colorFeatureIndex));
2088
+ lines.push([
2089
+ {
2090
+ text: `${words.shapValue}: ${formatShapValue(parsed.values[sampleIndex][featureIndex])}`
2091
+ }
2092
+ ]);
2093
+ return lines;
2094
+ }
2095
+ function wrapToWidth(text, maxWidth) {
2096
+ const words = text.split(" ");
2097
+ const lines = [];
2098
+ let line = "";
2099
+ for (const word of words) {
2100
+ const candidate = line ? `${line} ${word}` : word;
2101
+ if (line && candidate.length * GLYPH_PX.axis > maxWidth) {
2102
+ lines.push(line);
2103
+ line = word;
2104
+ } else {
2105
+ line = candidate;
2106
+ }
2107
+ }
2108
+ if (line) lines.push(line);
2109
+ return lines;
2110
+ }
2111
+ function scatterGeometry(input) {
2112
+ var _a;
2113
+ const {
2114
+ parsed,
2115
+ featureIndex,
2116
+ width,
2117
+ height,
2118
+ colorFeature,
2119
+ colorFeatureMinScore,
2120
+ colorBar = true,
2121
+ xScale,
2122
+ trend,
2123
+ colormap = "red_blue"
2124
+ } = input;
2125
+ const words = (_a = input.labels) != null ? _a : resolveLabels();
2126
+ const { absent, detected } = scatterPoints(parsed, featureIndex);
2127
+ const shapValues = parsed.values.map((row) => row[featureIndex]);
2128
+ const shapMin = Math.min(0, ...shapValues);
2129
+ const shapMax = Math.max(0, ...shapValues);
2130
+ const domainMin = shapMin === shapMax ? shapMin - 1 : shapMin;
2131
+ const domainMax = shapMin === shapMax ? shapMax + 1 : shapMax;
2132
+ const plotTop = MARGIN.top;
2133
+ const plotBottom = height - MARGIN.bottom;
2134
+ const initialYTicks = niceTicks(
2135
+ domainMin,
2136
+ domainMax,
2137
+ tickSpace(plotBottom - plotTop, TICK_LABEL_PT6)
2138
+ );
2139
+ const yStep = initialYTicks.step || 1;
2140
+ const yMin = Math.floor(domainMin / yStep) * yStep;
2141
+ const yMax = Math.ceil(domainMax / yStep) * yStep;
2142
+ const yTickValues = niceTicks(
2143
+ yMin,
2144
+ yMax,
2145
+ tickSpace(plotBottom - plotTop, TICK_LABEL_PT6)
2146
+ ).ticks;
2147
+ const yTickLabel = (value) => {
2148
+ const normalized = Number(tickLabel(value, yStep).replace("\u2212", "-"));
2149
+ return formatShapValue(value !== 0 && normalized === 0 ? value : normalized);
2150
+ };
2151
+ const widestYTick = Math.max(...yTickValues.map((value) => yTickLabel(value).length), 1) * GLYPH_PX.body;
2152
+ const plotLeft = Math.max(70, Y_TITLE_X + 13 + 8 + widestYTick + 8);
2153
+ const plotRight = width - (colorBar ? 90 : MARGIN.right);
2154
+ const detectedLeft = absent.length > 0 ? plotLeft + ABSENT_BAND + ABSENT_GAP : plotLeft;
2155
+ const shapSpan = yMax - yMin || 1;
2156
+ const toY = (shap) => plotBottom - (shap - yMin) / shapSpan * (plotBottom - plotTop);
2157
+ const yTicks = yTickValues.map((value) => ({ value, y: toY(value), label: yTickLabel(value) }));
2158
+ const [lowValue, highValue] = logDomain(detected);
2159
+ const useLog = xScale === "log" && lowValue > 0;
2160
+ const scaleOf = (v) => useLog ? Math.log10(v) : v;
2161
+ const lowScaled = scaleOf(lowValue);
2162
+ const highScaled = scaleOf(highValue);
2163
+ const scaledSpan = highScaled - lowScaled || 1;
2164
+ const toX = (v) => detectedLeft + (scaleOf(v) - lowScaled) / scaledSpan * (plotRight - detectedLeft);
2165
+ let colorFeatureIndex = null;
2166
+ let colorNote = "";
2167
+ let byOutput = false;
2168
+ if (colorFeature === "auto") {
2169
+ const best = strongestInteraction(featureIndex, parsed.values, parsed.data);
2170
+ if (best && best.score >= colorFeatureMinScore) {
2171
+ colorFeatureIndex = best.index;
2172
+ colorNote = words.interactionScore(best.score);
2173
+ } else {
2174
+ colorNote = words.weakInteraction;
2175
+ }
2176
+ } else if (colorFeature === "output") {
2177
+ byOutput = true;
2178
+ } else if (typeof colorFeature === "number" && colorFeature !== featureIndex) {
2179
+ colorFeatureIndex = colorFeature;
2180
+ }
2181
+ const outputs = parsed.values.map(
2182
+ (row, i) => parsed.baseValues[i] + row.reduce((sum, value) => sum + value, 0)
2183
+ );
2184
+ let colorLow = 0;
2185
+ let colorHigh = 1;
2186
+ const clipTo = (column) => {
2187
+ const sorted = [...column].sort((a, b) => a - b);
2188
+ const at = (q) => sorted[Math.min(sorted.length - 1, Math.floor(q * sorted.length))];
2189
+ colorLow = at(0.05);
2190
+ colorHigh = at(0.95);
2191
+ if (colorLow === colorHigh) {
2192
+ colorLow = sorted[0];
2193
+ colorHigh = sorted[sorted.length - 1];
2194
+ }
2195
+ };
2196
+ if (colorFeatureIndex !== null) {
2197
+ clipTo(parsed.data.map((row) => row[colorFeatureIndex]));
2198
+ } else if (byOutput) {
2199
+ clipTo(outputs);
2200
+ }
2201
+ const colorOf = (sampleIndex) => {
2202
+ if (colorFeatureIndex === null && !byOutput) return UNCOLOURED;
2203
+ const value = byOutput ? outputs[sampleIndex] : parsed.data[sampleIndex][colorFeatureIndex];
2204
+ const t = colorHigh === colorLow ? 0.5 : (value - colorLow) / (colorHigh - colorLow);
2205
+ return sampleColormap(colormap, t);
2206
+ };
2207
+ const colorFeatureLabel = colorFeatureIndex === null ? null : formatFeatureLabel(parsed.featureNames[colorFeatureIndex]);
2208
+ const scaleSubject = colorFeatureLabel ? `${colorFeatureLabel} \xB7 ${words.featureValue}${colorNote ? ` (${colorNote})` : ""}` : words.modelOutput;
2209
+ const scaleParts = colorFeatureLabel ? colorScaleTitleParts(words.colorScale(scaleSubject), colorFeatureLabel) : void 0;
2210
+ const colorBarGeometry = colorFeatureIndex === null && !byOutput || !colorBar ? null : colorBarLayout({
2211
+ colormap,
2212
+ tickLabels: byOutput ? [formatLevel(colorLow), formatLevel(colorHigh)] : [words.featureValueLow, words.featureValueHigh],
2213
+ label: words.colorScale(scaleSubject),
2214
+ ...scaleParts ? { labelParts: scaleParts } : {},
2215
+ labelPad: 0
2216
+ }, { x: plotRight + 18, y1: plotTop, y2: plotBottom });
2217
+ const absentCenter = plotLeft + ABSENT_BAND / 2;
2218
+ const absentPoints = absent.map((p, i) => ({
2219
+ // Spread the band's points so identical SHAP values do not stack invisibly.
2220
+ cx: absentCenter + (i % 5 - 2) * (DOT_RADIUS + 1),
2221
+ cy: toY(p.shap),
2222
+ color: colorOf(p.sampleIndex),
2223
+ sampleIndex: p.sampleIndex
2224
+ }));
2225
+ const detectedPoints = detected.map((p) => ({
2226
+ cx: toX(p.value),
2227
+ cy: toY(p.shap),
2228
+ color: colorOf(p.sampleIndex),
2229
+ sampleIndex: p.sampleIndex
2230
+ }));
2231
+ const trendPoints = trend ? binnedMedianTrend(detected) : [];
2232
+ const trendPath = trendPoints.length >= 2 ? trendPoints.map((p, i) => `${i === 0 ? "M" : "L"}${toX(p.value)},${toY(p.shap)}`).join(" ") : null;
2233
+ const absentMeanY = absent.length > 0 ? toY(absent.reduce((sum, p) => sum + p.shap, 0) / absent.length) : null;
2234
+ const { ticks, step } = niceTicks(
2235
+ lowScaled,
2236
+ highScaled,
2237
+ tickSpace(plotRight - detectedLeft, TICK_LABEL_PT6)
2238
+ );
2239
+ const xTicks = ticks.map((t) => ({
2240
+ x: detectedLeft + (t - lowScaled) / scaledSpan * (plotRight - detectedLeft),
2241
+ label: useLog ? formatLevel(10 ** t) : tickLabel(t, step)
2242
+ }));
2243
+ return {
2244
+ absentPoints,
2245
+ detectedPoints,
2246
+ absentLabelLines: absent.length > 0 ? wrapToWidth(words.absentWithCount(absent.length), ABSENT_BAND + ABSENT_GAP - 4) : [],
2247
+ absentMeanY,
2248
+ zeroRuleY: toY(0),
2249
+ trendPath,
2250
+ colorFeatureIndex,
2251
+ colorFeatureLabel,
2252
+ colorNote,
2253
+ colorBar: colorBarGeometry,
2254
+ xTicks,
2255
+ yTicks,
2256
+ plotLeft,
2257
+ plotRight,
2258
+ plotTop,
2259
+ plotBottom,
2260
+ xTitle: {
2261
+ unit: words.featureValue,
2262
+ text: formatFeatureLabel(parsed.featureNames[featureIndex]),
2263
+ x: (plotLeft + plotRight) / 2,
2264
+ y: plotBottom + AXIS_TITLE_DY
2265
+ },
2266
+ yTitle: words.shapValue,
2267
+ yTitleX: Y_TITLE_X
2268
+ };
2269
+ }
2270
+
2271
+ // src/core/pca.ts
2272
+ var DEFAULT_ITERATIONS = 200;
2273
+ var EPSILON = 1e-12;
2274
+ function centerColumns(values) {
2275
+ var _a, _b;
2276
+ const rows = values.length;
2277
+ const cols = (_b = (_a = values[0]) == null ? void 0 : _a.length) != null ? _b : 0;
2278
+ const means = new Array(cols).fill(0);
2279
+ for (const row of values) for (let j = 0; j < cols; j++) means[j] += row[j];
2280
+ for (let j = 0; j < cols; j++) means[j] /= rows;
2281
+ return values.map((row) => row.map((v, j) => v - means[j]));
2282
+ }
2283
+ function leadingComponent(m, iterations) {
2284
+ var _a, _b;
2285
+ const cols = (_b = (_a = m[0]) == null ? void 0 : _a.length) != null ? _b : 0;
2286
+ let v = Array.from({ length: cols }, (_, j) => 1 / (j + 1));
2287
+ normalise(v);
2288
+ for (let step = 0; step < iterations; step++) {
2289
+ const w = m.map((row) => {
2290
+ let sum = 0;
2291
+ for (let j = 0; j < cols; j++) sum += row[j] * v[j];
2292
+ return sum;
2293
+ });
2294
+ const next = new Array(cols).fill(0);
2295
+ m.forEach((row, i) => {
2296
+ const weight = w[i];
2297
+ for (let j = 0; j < cols; j++) next[j] += row[j] * weight;
2298
+ });
2299
+ if (!normalise(next)) return new Array(cols).fill(0);
2300
+ v = next;
2301
+ }
2302
+ return v;
2303
+ }
2304
+ function normalise(v) {
2305
+ let norm = 0;
2306
+ for (const x of v) norm += x * x;
2307
+ norm = Math.sqrt(norm);
2308
+ if (!(norm > EPSILON)) return false;
2309
+ for (let j = 0; j < v.length; j++) v[j] /= norm;
2310
+ return true;
2311
+ }
2312
+ function fixSign(component) {
2313
+ let peak = 0;
2314
+ for (const x of component) if (Math.abs(x) > Math.abs(peak)) peak = x;
2315
+ if (peak < 0) for (let j = 0; j < component.length; j++) component[j] = -component[j];
2316
+ }
2317
+ function project(m, component) {
2318
+ return m.map((row) => {
2319
+ let sum = 0;
2320
+ for (let j = 0; j < row.length; j++) sum += row[j] * component[j];
2321
+ return sum;
2322
+ });
2323
+ }
2324
+ function shapPca(values, iterations = DEFAULT_ITERATIONS) {
2325
+ if (values.length === 0) return { coords: [], varianceRatios: [0, 0] };
2326
+ const centered = centerColumns(values);
2327
+ let total = 0;
2328
+ for (const row of centered) for (const v of row) total += v * v;
2329
+ const first = leadingComponent(centered, iterations);
2330
+ fixSign(first);
2331
+ const xs = project(centered, first);
2332
+ const residual = centered.map(
2333
+ (row, i) => row.map((v, j) => v - xs[i] * first[j])
2334
+ );
2335
+ const second = leadingComponent(residual, iterations);
2336
+ fixSign(second);
2337
+ const ys = project(residual, second);
2338
+ const energy = (projected) => projected.reduce((sum, v) => sum + v * v, 0);
2339
+ const ratio = (projected) => total > EPSILON ? energy(projected) / total : 0;
2340
+ return {
2341
+ coords: xs.map((x, i) => [x, ys[i]]),
2342
+ varianceRatios: [ratio(xs), ratio(ys)]
2343
+ };
2344
+ }
2345
+
2346
+ // src/core/embeddingLayout.ts
2347
+ var MARGIN2 = { left: 64, right: 24, top: 20, bottom: 52 };
2348
+ var COLOR_BAR_MARGIN = 96;
2349
+ var MEANING_MIN_R = 0.7;
2350
+ function embeddingLayout(input) {
2351
+ var _a;
2352
+ const { parsed, width, height, colorBy, coords, colormap = "red_blue", colorBar = true } = input;
2353
+ const words = (_a = input.labels) != null ? _a : resolveLabels();
2354
+ if (coords && coords.length !== parsed.nSamples) {
2355
+ throw new RangeError(
2356
+ `coords must hold one position per Sample: expected ${parsed.nSamples}, received ${coords.length}`
2357
+ );
2358
+ }
2359
+ const projection = coords ? null : shapPca(parsed.values);
2360
+ const positions = coords != null ? coords : projection.coords;
2361
+ const plotLeft = MARGIN2.left;
2362
+ const showColorBar = colorBar && colorBy !== "none";
2363
+ const plotRight = width - (showColorBar ? COLOR_BAR_MARGIN : MARGIN2.right);
2364
+ const plotTop = MARGIN2.top;
2365
+ const plotBottom = height - MARGIN2.bottom;
2366
+ const axisRange = (axis) => {
2367
+ const all = positions.map((p) => p[axis]);
2368
+ const low = Math.min(...all);
2369
+ const high = Math.max(...all);
2370
+ return low === high ? [low - 1, high + 1] : [low, high];
2371
+ };
2372
+ const [xLow, xHigh] = axisRange(0);
2373
+ const [yLow, yHigh] = axisRange(1);
2374
+ const toX = (v) => plotLeft + (v - xLow) / (xHigh - xLow) * (plotRight - plotLeft);
2375
+ const toY = (v) => plotBottom - (v - yLow) / (yHigh - yLow) * (plotBottom - plotTop);
2376
+ const colourValues = colorBy === "none" ? null : parsed.values.map(
2377
+ (row) => colorBy === "sum" ? row.reduce((sum, v) => sum + v, 0) : row[colorBy]
2378
+ );
2379
+ const colourLow = colourValues ? Math.min(...colourValues) : 0;
2380
+ const colourHigh = colourValues ? Math.max(...colourValues) : 1;
2381
+ const points = positions.map((position, i) => ({
2382
+ cx: toX(position[0]),
2383
+ cy: toY(position[1]),
2384
+ coordinates: position,
2385
+ sampleIndex: i,
2386
+ color: colourValues ? sampleColormap(
2387
+ colormap,
2388
+ colourHigh === colourLow ? 0.5 : (colourValues[i] - colourLow) / (colourHigh - colourLow)
2389
+ ) : UNCOLOURED
2390
+ }));
2391
+ const title = (index) => projection ? words.principalComponent(index, projection.varianceRatios[index - 1]) : words.suppliedComponent(index);
2392
+ const colouredTaxon = typeof colorBy === "number" ? formatFeatureLabel(parsed.featureNames[colorBy]) : null;
2393
+ const colourLabel = colorBy === "sum" ? words.sampleTotal : colouredTaxon ? `${colouredTaxon} \xB7 ${words.shapValue}` : "";
2394
+ const colourParts = colouredTaxon ? colorScaleTitleParts(words.colorScale(colourLabel), colouredTaxon) : void 0;
2395
+ const inside = (value, low, high) => value > low && value < high;
2396
+ const totals = parsed.values.map((row) => row.reduce((sum, value) => sum + value, 0));
2397
+ const meaningOf = (axis) => {
2398
+ const r = pearson(positions.map((position) => position[axis]), totals);
2399
+ return Math.abs(r) >= MEANING_MIN_R ? words.componentTracksTotal(r) : null;
2400
+ };
2401
+ return {
2402
+ points,
2403
+ xMeaning: meaningOf(0),
2404
+ yMeaning: meaningOf(1),
2405
+ zeroX: inside(0, xLow, xHigh) ? toX(0) : null,
2406
+ zeroY: inside(0, yLow, yHigh) ? toY(0) : null,
2407
+ xTitle: title(1),
2408
+ yTitle: title(2),
2409
+ plotLeft,
2410
+ plotRight,
2411
+ plotTop,
2412
+ plotBottom,
2413
+ colorBar: showColorBar && colourValues ? colorBarLayout(
2414
+ {
2415
+ colormap,
2416
+ // The actual range, not "Low" and "High": these are signed
2417
+ // SHAP values and their sign is the thing worth reading.
2418
+ tickLabels: [formatShapValue(colourLow), formatShapValue(colourHigh)],
2419
+ label: words.colorScale(colourLabel),
2420
+ ...colourParts ? { labelParts: colourParts } : {},
2421
+ labelPad: 0
2422
+ },
2423
+ { x: plotRight + 18, y1: plotTop, y2: plotBottom }
2424
+ ) : null
2425
+ };
2426
+ }
2427
+
2428
+ // src/core/decisionLayout.ts
2429
+ var AXIS_HEIGHT5 = 52;
2430
+ var MARGIN3 = { left: 220, right: 40, top: 16 };
2431
+ var X_MARGIN3 = 0.02;
2432
+ var MAX_OPACITY = 0.9;
2433
+ var MIN_OPACITY = 0.08;
2434
+ var OPACITY_FLOOR_AT = 400;
2435
+ function decisionLayout(input) {
2436
+ var _a;
2437
+ const { parsed, width, rowHeight, maxDisplay, colormap = "red_blue" } = input;
2438
+ const indices = (_a = input.sampleIndices) != null ? _a : parsed.values.map((_, i) => i);
2439
+ const descending = orderFeatures(globalImportance(parsed));
2440
+ const shown = descending.slice(0, Math.min(maxDisplay, descending.length));
2441
+ const hidden = descending.slice(shown.length);
2442
+ const bottomUp = [...shown].reverse();
2443
+ const paths = indices.map((sampleIndex) => {
2444
+ const row = parsed.values[sampleIndex];
2445
+ const hiddenSum = hidden.reduce((sum, j) => sum + row[j], 0);
2446
+ const values = [parsed.baseValues[sampleIndex] + hiddenSum];
2447
+ for (const j of bottomUp) values.push(values[values.length - 1] + row[j]);
2448
+ return { sampleIndex, values };
2449
+ });
2450
+ const base = indices.length === 0 ? 0 : indices.reduce((sum, sampleIndex) => sum + parsed.baseValues[sampleIndex], 0) / indices.length;
2451
+ let reach = 0;
2452
+ for (const path of paths) {
2453
+ for (const value of path.values) reach = Math.max(reach, Math.abs(value - base));
2454
+ }
2455
+ reach = reach || 1;
2456
+ const pad = reach * 2 * X_MARGIN3;
2457
+ const xDomain = [base - reach - pad, base + reach + pad];
2458
+ const plotLeft = MARGIN3.left;
2459
+ const plotRight = width - MARGIN3.right;
2460
+ const plotTop = MARGIN3.top;
2461
+ const plotBottom = plotTop + bottomUp.length * rowHeight;
2462
+ const toX = (v) => plotLeft + (v - xDomain[0]) / (xDomain[1] - xDomain[0]) * (plotRight - plotLeft);
2463
+ const toY = (step) => plotBottom - step * rowHeight;
2464
+ const pathOpacity = MIN_OPACITY + (MAX_OPACITY - MIN_OPACITY) * Math.max(0, 1 - Math.max(0, indices.length - 1) / OPACITY_FLOOR_AT);
2465
+ return {
2466
+ rowLabels: bottomUp.map((j) => parsed.featureNames[j]),
2467
+ rowY: bottomUp.map((_, i) => plotBottom - i * rowHeight - rowHeight / 2),
2468
+ paths: paths.map(({ sampleIndex, values }) => {
2469
+ const end = values[values.length - 1];
2470
+ const t = (end - xDomain[0]) / (xDomain[1] - xDomain[0]);
2471
+ return {
2472
+ sampleIndex,
2473
+ values,
2474
+ points: values.map((value, step) => ({ x: toX(value), y: toY(step) })),
2475
+ color: sampleColormap(colormap, t)
2476
+ };
2477
+ }),
2478
+ pathOpacity,
2479
+ xDomain,
2480
+ baseValue: base,
2481
+ baseValueX: toX(base),
2482
+ plotLeft,
2483
+ plotRight,
2484
+ plotTop,
2485
+ plotBottom,
2486
+ height: plotBottom + AXIS_HEIGHT5
2487
+ };
2488
+ }
2489
+
2490
+ // src/core/forceLayout.ts
2491
+ var MIN_LABEL_SHARE = 0.05;
2492
+ var MARGIN4 = { left: 16, right: 16 };
2493
+ function forceLayout(input) {
2494
+ var _a;
2495
+ const { parsed, sampleIndex, width, height, maxDisplay, faithfulOtherRow = false } = input;
2496
+ const words = (_a = input.labels) != null ? _a : resolveLabels();
2497
+ const row = parsed.values[sampleIndex];
2498
+ const baseValue = parsed.baseValues[sampleIndex];
2499
+ const modelOutput = row.reduce((sum, v) => sum + v, 0) + baseValue;
2500
+ const magnitudes = row.map((v) => Math.abs(v));
2501
+ const order = magnitudes.map((value, index) => ({ value, index })).sort((a, b) => b.value - a.value || a.index - b.index).map((entry) => entry.index);
2502
+ const display = collapseToDisplay(
2503
+ parsed.featureNames,
2504
+ row,
2505
+ order,
2506
+ maxDisplay,
2507
+ faithfulOtherRow,
2508
+ words
2509
+ );
2510
+ const totalEffect = display.rows.reduce((sum, r) => sum + Math.abs(r.value), 0) || 1;
2511
+ const usable = Math.max(0, width - MARGIN4.left - MARGIN4.right);
2512
+ const scale = usable / totalEffect;
2513
+ const positives = display.rows.filter((r) => r.value > 0).sort((a, b) => b.value - a.value);
2514
+ const negatives = display.rows.filter((r) => r.value <= 0).sort((a, b) => a.value - b.value);
2515
+ const positiveTotal = positives.reduce((sum, r) => sum + Math.abs(r.value), 0);
2516
+ const meetingX = MARGIN4.left + positiveTotal * scale;
2517
+ const barHeight = Math.max(12, height - 48);
2518
+ const barY = 24;
2519
+ const segments = [];
2520
+ let cursor = meetingX;
2521
+ for (const r of positives) {
2522
+ const segmentWidth = Math.abs(r.value) * scale;
2523
+ cursor -= segmentWidth;
2524
+ segments.push({
2525
+ label: r.label,
2526
+ featureIndex: r.featureIndex,
2527
+ isOtherRow: r.isOtherRow,
2528
+ value: r.value,
2529
+ x: cursor,
2530
+ width: segmentWidth,
2531
+ color: POSITIVE_COLOR,
2532
+ labelled: Math.abs(r.value) / totalEffect >= MIN_LABEL_SHARE
2533
+ });
2534
+ }
2535
+ cursor = meetingX;
2536
+ for (const r of negatives) {
2537
+ const segmentWidth = Math.abs(r.value) * scale;
2538
+ segments.push({
2539
+ label: r.label,
2540
+ featureIndex: r.featureIndex,
2541
+ isOtherRow: r.isOtherRow,
2542
+ value: r.value,
2543
+ x: cursor,
2544
+ width: segmentWidth,
2545
+ color: NEGATIVE_COLOR,
2546
+ labelled: Math.abs(r.value) / totalEffect >= MIN_LABEL_SHARE
2547
+ });
2548
+ cursor += segmentWidth;
2549
+ }
2550
+ const outputSpan = modelOutput - baseValue;
2551
+ return {
2552
+ segments,
2553
+ baseValue,
2554
+ modelOutput,
2555
+ meetingX,
2556
+ baseValueX: meetingX - outputSpan * scale,
2557
+ barY,
2558
+ barHeight
2559
+ };
2560
+ }
2561
+
2562
+ // src/core/tableRows.ts
2563
+ var taxon = (name) => ({ text: formatFeatureLabel(name), italic: true });
2564
+ function scatterTableRows(parsed, featureIndex, split, words) {
2565
+ const featureName = taxon(parsed.featureNames[featureIndex]);
2566
+ const rows = [...split.absent, ...split.detected].sort((a, b) => a.sampleIndex - b.sampleIndex).map((point) => [
2567
+ sampleDisplayName(parsed, point.sampleIndex, words),
2568
+ point.value > 0 ? formatLevel(point.value) : words.absent,
2569
+ formatShapValue(point.shap)
2570
+ ]);
2571
+ return {
2572
+ caption: words.tableCaption,
2573
+ columns: ["Sample", featureName, words.shapValue],
2574
+ rows
2575
+ };
2576
+ }
2577
+ function embeddingTableRows(layout, parsed, words) {
2578
+ return {
2579
+ caption: words.tableCaption,
2580
+ columns: ["Sample", layout.xTitle, layout.yTitle],
2581
+ rows: layout.points.map((point) => [
2582
+ sampleDisplayName(parsed, point.sampleIndex, words),
2583
+ formatLevel(point.coordinates[0]),
2584
+ formatLevel(point.coordinates[1])
2585
+ ])
2586
+ };
2587
+ }
2588
+ function decisionTableRows(layout, parsed, words) {
2589
+ return {
2590
+ caption: words.tableCaption,
2591
+ columns: ["Sample", words.baseValue, ...layout.rowLabels.map(taxon)],
2592
+ rows: layout.paths.map((path) => [
2593
+ sampleDisplayName(parsed, path.sampleIndex, words),
2594
+ ...path.values.map((value) => formatLevel(value))
2595
+ ])
2596
+ };
2597
+ }
2598
+ function forceTableRows(layout, words) {
2599
+ return {
2600
+ caption: words.tableCaption,
2601
+ columns: ["Feature", words.shapValue],
2602
+ rows: layout.segments.map((segment) => [
2603
+ // The Other features row is a count, not a taxon, so it stays upright.
2604
+ segment.isOtherRow ? segment.label : taxon(segment.label),
2605
+ formatShapValue(segment.value)
2606
+ ])
2607
+ };
2608
+ }
2609
+
2610
+ // src/core/dendrogram.ts
2611
+ function dendrogramCoords(leafPositions, linkage) {
2612
+ if (linkage.length === 0) return [];
2613
+ const leafCount = leafPositions.length;
2614
+ const out = [];
2615
+ const walk = (node) => {
2616
+ if (node < leafCount) return [leafPositions[node], 0];
2617
+ const row = linkage[node - leafCount];
2618
+ const [leftX, leftY] = walk(row[0]);
2619
+ const [rightX, rightY] = walk(row[1]);
2620
+ const height = row[2];
2621
+ out.push({
2622
+ xs: [leftX, leftX, rightX, rightX],
2623
+ ys: [leftY, height, height, rightY]
2624
+ });
2625
+ return [(leftX + rightX) / 2, height];
2626
+ };
2627
+ walk(leafCount + linkage.length - 1);
2628
+ return out;
2629
+ }
2630
+
1744
2631
  export {
1745
2632
  UnsupportedContractVersionError,
1746
2633
  InvalidExplanationError,
@@ -1750,18 +2637,27 @@ export {
1750
2637
  groupExplanationByGenus,
1751
2638
  sortDisplayRows,
1752
2639
  parseExplanation,
2640
+ sampleDisplayName,
1753
2641
  globalImportance,
1754
2642
  orderFeatures,
2643
+ formatFixed,
1755
2644
  formatShapValue,
1756
2645
  formatLevel,
1757
2646
  formatFeatureLabel,
1758
2647
  shapLabels,
1759
2648
  resolveLabels,
1760
2649
  collapseToDisplay,
2650
+ correlationDistances,
2651
+ averageLinkage,
2652
+ copheneticDistances,
2653
+ leafOrder,
2654
+ relaxSortOrder,
1761
2655
  TICK_LENGTH,
1762
2656
  TICK_LABEL_DY,
1763
2657
  POSITIVE_COLOR,
1764
2658
  NEGATIVE_COLOR,
2659
+ CLUSTERING_POOL,
2660
+ clusteredOrder,
1765
2661
  barLayout,
1766
2662
  WATERFALL_HEAD_LENGTH_PX,
1767
2663
  WATERFALL_TICK_LABEL_DY,
@@ -1769,16 +2665,42 @@ export {
1769
2665
  waterfallRows,
1770
2666
  waterfallLayout,
1771
2667
  sampleColormap,
2668
+ UNCOLOURED,
1772
2669
  colorBarExtent,
1773
2670
  fitColorBar,
1774
2671
  colorBarLayout,
1775
2672
  scalarFormatterLabels,
2673
+ colorScaleTitleParts,
2674
+ GLYPH_PX,
1776
2675
  placeTooltip,
2676
+ runsToText,
2677
+ namedValue,
2678
+ TOOLTIP_LINE_HEIGHT,
1777
2679
  BEESWARM_MISSING_COLOR,
1778
2680
  BEESWARM_ROW_HEIGHT,
1779
2681
  beeswarmRows,
1780
2682
  beeswarmLayout,
1781
2683
  heatmapRows,
1782
- heatmapLayout
2684
+ heatmapLayout,
2685
+ interactionWindowSize,
2686
+ interactionScores,
2687
+ strongestInteraction,
2688
+ scatterPoints,
2689
+ logDomain,
2690
+ binnedMedianTrend,
2691
+ ABSENT_BAND,
2692
+ DOT_RADIUS,
2693
+ scatterTooltipLines,
2694
+ wrapToWidth,
2695
+ scatterGeometry,
2696
+ shapPca,
2697
+ embeddingLayout,
2698
+ decisionLayout,
2699
+ forceLayout,
2700
+ scatterTableRows,
2701
+ embeddingTableRows,
2702
+ decisionTableRows,
2703
+ forceTableRows,
2704
+ dendrogramCoords
1783
2705
  };
1784
- //# sourceMappingURL=chunk-UVIVTJK3.js.map
2706
+ //# sourceMappingURL=chunk-JQBQSWIU.js.map