shap-svg 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -0
- package/dist/{chunk-OXFKP5I3.js → chunk-4X6PMJYT.js} +235 -34
- package/dist/chunk-4X6PMJYT.js.map +1 -0
- package/dist/index.cjs +240 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +104 -7
- package/dist/index.d.ts +104 -7
- package/dist/index.js +13 -1
- package/dist/{format-wEav_cPe.d.cts → labels-7wofev8M.d.cts} +44 -1
- package/dist/{format-wEav_cPe.d.ts → labels-7wofev8M.d.ts} +44 -1
- package/dist/react.cjs +369 -126
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +33 -5
- package/dist/react.d.ts +33 -5
- package/dist/react.js +143 -92
- package/dist/react.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-OXFKP5I3.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -34,6 +34,9 @@ __export(index_exports, {
|
|
|
34
34
|
beeswarmLayout: () => beeswarmLayout,
|
|
35
35
|
beeswarmRows: () => beeswarmRows,
|
|
36
36
|
collapseToDisplay: () => collapseToDisplay,
|
|
37
|
+
colorBarExtent: () => colorBarExtent,
|
|
38
|
+
colorBarLayout: () => colorBarLayout,
|
|
39
|
+
fitColorBar: () => fitColorBar,
|
|
37
40
|
formatFeatureLabel: () => formatFeatureLabel,
|
|
38
41
|
formatLevel: () => formatLevel,
|
|
39
42
|
formatShapValue: () => formatShapValue,
|
|
@@ -45,7 +48,10 @@ __export(index_exports, {
|
|
|
45
48
|
heatmapRows: () => heatmapRows,
|
|
46
49
|
orderFeatures: () => orderFeatures,
|
|
47
50
|
parseExplanation: () => parseExplanation,
|
|
51
|
+
resolveLabels: () => resolveLabels,
|
|
48
52
|
sampleColormap: () => sampleColormap,
|
|
53
|
+
scalarFormatterLabels: () => scalarFormatterLabels,
|
|
54
|
+
shapLabels: () => shapLabels,
|
|
49
55
|
sortDisplayRows: () => sortDisplayRows,
|
|
50
56
|
waterfallLayout: () => waterfallLayout,
|
|
51
57
|
waterfallRows: () => waterfallRows
|
|
@@ -315,8 +321,34 @@ function formatFeatureLabel(name) {
|
|
|
315
321
|
return name.replace(/_/g, " ");
|
|
316
322
|
}
|
|
317
323
|
|
|
324
|
+
// src/core/labels.ts
|
|
325
|
+
var shapLabels = {
|
|
326
|
+
shapValue: "SHAP value",
|
|
327
|
+
shapValueAxis: "SHAP value (impact on model output)",
|
|
328
|
+
meanAbsShapValue: "mean(|SHAP value|)",
|
|
329
|
+
featureValue: "Feature value",
|
|
330
|
+
featureValueLow: "Low",
|
|
331
|
+
featureValueHigh: "High",
|
|
332
|
+
missingFeatureValue: "missing",
|
|
333
|
+
samples: "Instances",
|
|
334
|
+
sampleTotal: "\u03A3\u03C6",
|
|
335
|
+
sampleFallback: (sampleNumber) => `Sample ${sampleNumber}`,
|
|
336
|
+
baseValue: "E[f(X)]",
|
|
337
|
+
modelOutput: "f(x)",
|
|
338
|
+
otherFeatures: (count, style) => style === "sum" ? `Sum of ${count} other features` : `${count} other features`
|
|
339
|
+
};
|
|
340
|
+
function resolveLabels(labels) {
|
|
341
|
+
if (!labels) return shapLabels;
|
|
342
|
+
const resolved = { ...shapLabels };
|
|
343
|
+
for (const key of Object.keys(labels)) {
|
|
344
|
+
const value = labels[key];
|
|
345
|
+
if (value !== void 0) resolved[key] = value;
|
|
346
|
+
}
|
|
347
|
+
return resolved;
|
|
348
|
+
}
|
|
349
|
+
|
|
318
350
|
// src/core/collapse.ts
|
|
319
|
-
function collapseToDisplay(featureNames, importance, order, maxDisplay, faithfulOtherRow) {
|
|
351
|
+
function collapseToDisplay(featureNames, importance, order, maxDisplay, faithfulOtherRow, labels = shapLabels) {
|
|
320
352
|
const p = order.length;
|
|
321
353
|
if (maxDisplay >= p) {
|
|
322
354
|
return {
|
|
@@ -339,7 +371,7 @@ function collapseToDisplay(featureNames, importance, order, maxDisplay, faithful
|
|
|
339
371
|
const collapsed = order.slice(realCount);
|
|
340
372
|
const collapsedValue = collapsed.reduce((sum, index) => sum + importance[index], 0);
|
|
341
373
|
rows.push({
|
|
342
|
-
label:
|
|
374
|
+
label: labels.otherFeatures(collapsed.length, faithfulOtherRow ? "sum" : "count"),
|
|
343
375
|
featureIndex: null,
|
|
344
376
|
value: collapsedValue,
|
|
345
377
|
isOtherRow: true
|
|
@@ -406,15 +438,15 @@ var AXIS_HEIGHT = 52;
|
|
|
406
438
|
var TICK_LABEL_PT = 11;
|
|
407
439
|
var TITLE_PT = 13;
|
|
408
440
|
var X_MARGIN = 0.05;
|
|
409
|
-
function barXAxis(min, max, toX, marginLeft, plotWidth, plotBottom) {
|
|
441
|
+
function barXAxis(min, max, toX, marginLeft, plotWidth, plotBottom, title) {
|
|
410
442
|
const { ticks, step } = niceTicks(min, max, tickSpace(plotWidth, TICK_LABEL_PT));
|
|
411
443
|
return {
|
|
412
444
|
xTicks: ticks.map((value) => ({ value, x: toX(value), label: tickLabel(value, step) })),
|
|
413
445
|
xSpine: { x1: marginLeft, x2: marginLeft + plotWidth, y: plotBottom },
|
|
414
446
|
xTitle: {
|
|
415
|
-
// _bar.py:143-150 builds
|
|
416
|
-
// "SHAP value" -> "|SHAP value|" -> "mean(|SHAP value|)".
|
|
417
|
-
text:
|
|
447
|
+
// _bar.py:143-150 builds the default from the Explanation's transform
|
|
448
|
+
// history: "SHAP value" -> "|SHAP value|" -> "mean(|SHAP value|)".
|
|
449
|
+
text: title,
|
|
418
450
|
x: marginLeft + plotWidth / 2,
|
|
419
451
|
y: plotBottom + AXIS_TITLE_DY,
|
|
420
452
|
fontSize: TITLE_PT
|
|
@@ -422,6 +454,7 @@ function barXAxis(min, max, toX, marginLeft, plotWidth, plotBottom) {
|
|
|
422
454
|
};
|
|
423
455
|
}
|
|
424
456
|
function barLayout(rows, opts) {
|
|
457
|
+
var _a;
|
|
425
458
|
const { width, rowHeight, marginLeft, marginRight, marginTop } = opts;
|
|
426
459
|
const plotWidth = width - marginLeft - marginRight;
|
|
427
460
|
const values = rows.rows.map((r) => r.value);
|
|
@@ -462,7 +495,15 @@ function barLayout(rows, opts) {
|
|
|
462
495
|
xZero,
|
|
463
496
|
plotWidth,
|
|
464
497
|
plotBottom: marginTop + rows.rows.length * rowHeight,
|
|
465
|
-
...barXAxis(
|
|
498
|
+
...barXAxis(
|
|
499
|
+
min,
|
|
500
|
+
max,
|
|
501
|
+
toX,
|
|
502
|
+
marginLeft,
|
|
503
|
+
plotWidth,
|
|
504
|
+
marginTop + rows.rows.length * rowHeight,
|
|
505
|
+
((_a = opts.labels) != null ? _a : shapLabels).meanAbsShapValue
|
|
506
|
+
),
|
|
466
507
|
zeroLine: { x: xZero, y1: marginTop, y2: marginTop + rows.rows.length * rowHeight },
|
|
467
508
|
height: marginTop + rows.rows.length * rowHeight + AXIS_HEIGHT
|
|
468
509
|
};
|
|
@@ -496,7 +537,7 @@ function placeValueLabel(value, startX, endX, gutterX, decimals) {
|
|
|
496
537
|
}
|
|
497
538
|
return { ...base, x: startX + VALUE_LABEL_GAP, anchor: "start", inside: false };
|
|
498
539
|
}
|
|
499
|
-
function waterfallRows(explanation, sampleIndex, maxDisplay, faithfulOtherRow) {
|
|
540
|
+
function waterfallRows(explanation, sampleIndex, maxDisplay, faithfulOtherRow, labels = shapLabels) {
|
|
500
541
|
if (!Number.isInteger(sampleIndex) || sampleIndex < 0 || sampleIndex >= explanation.nSamples) {
|
|
501
542
|
throw new RangeError(
|
|
502
543
|
`sampleIndex must identify a Sample from 0 to ${explanation.nSamples - 1}, received ${sampleIndex}`
|
|
@@ -534,7 +575,8 @@ function waterfallRows(explanation, sampleIndex, maxDisplay, faithfulOtherRow) {
|
|
|
534
575
|
if (hasOtherRow) {
|
|
535
576
|
const value = collapsed.reduce((sum, featureIndex) => sum + values[featureIndex], 0);
|
|
536
577
|
rows.push({
|
|
537
|
-
|
|
578
|
+
// SHAP's waterfall counts the hidden Features whatever the collapse mode.
|
|
579
|
+
label: labels.otherFeatures(collapsed.length, "count"),
|
|
538
580
|
featureIndex: null,
|
|
539
581
|
isOtherRow: true,
|
|
540
582
|
value,
|
|
@@ -552,7 +594,9 @@ function waterfallRows(explanation, sampleIndex, maxDisplay, faithfulOtherRow) {
|
|
|
552
594
|
};
|
|
553
595
|
}
|
|
554
596
|
function waterfallLayout(valueRows, opts) {
|
|
597
|
+
var _a;
|
|
555
598
|
const { width, rowHeight, marginLeft, marginRight, marginTop } = opts;
|
|
599
|
+
const labels = (_a = opts.labels) != null ? _a : shapLabels;
|
|
556
600
|
const plotWidth = width - marginLeft - marginRight;
|
|
557
601
|
const coordinates = [valueRows.baseValue, valueRows.modelOutput];
|
|
558
602
|
for (const row of valueRows.rows) coordinates.push(row.left, row.left + row.width);
|
|
@@ -619,7 +663,7 @@ function waterfallLayout(valueRows, opts) {
|
|
|
619
663
|
// got, which is the whole reason SHAP hides the left spine.
|
|
620
664
|
y1: plotBottom - rowHeight,
|
|
621
665
|
y2: plotBottom,
|
|
622
|
-
label:
|
|
666
|
+
label: `${labels.baseValue} = ${formatLevel(valueRows.baseValue, opts.decimals)}`
|
|
623
667
|
},
|
|
624
668
|
{
|
|
625
669
|
kind: "output",
|
|
@@ -628,7 +672,7 @@ function waterfallLayout(valueRows, opts) {
|
|
|
628
672
|
// axvline(fx, 0, 1) — the full height.
|
|
629
673
|
y1: marginTop,
|
|
630
674
|
y2: plotBottom,
|
|
631
|
-
label:
|
|
675
|
+
label: `${labels.modelOutput} = ${formatLevel(valueRows.modelOutput, opts.decimals)}`
|
|
632
676
|
}
|
|
633
677
|
],
|
|
634
678
|
separators: valueRows.rows.map((_, index) => ({
|
|
@@ -1193,12 +1237,120 @@ function sampleColormap(name, t) {
|
|
|
1193
1237
|
return `#${hexByte(red)}${hexByte(green)}${hexByte(blue)}`;
|
|
1194
1238
|
}
|
|
1195
1239
|
|
|
1240
|
+
// src/core/colorBar.ts
|
|
1241
|
+
var ASPECT = 80;
|
|
1242
|
+
var TICK_PAD = 3.5;
|
|
1243
|
+
var TICK_LABEL_PT3 = 11;
|
|
1244
|
+
var LABEL_PT = 12;
|
|
1245
|
+
var LABEL_THICKNESS_EM = 1.08;
|
|
1246
|
+
var LABEL_BASELINE_EM = 0.84;
|
|
1247
|
+
var CHAR_EM = 0.6;
|
|
1248
|
+
var STEPS = 64;
|
|
1249
|
+
function textWidth(text, fontSize) {
|
|
1250
|
+
return text.length * fontSize * CHAR_EM;
|
|
1251
|
+
}
|
|
1252
|
+
function tickColumnWidth(spec) {
|
|
1253
|
+
return Math.max(...spec.tickLabels.map((label) => textWidth(label, TICK_LABEL_PT3)));
|
|
1254
|
+
}
|
|
1255
|
+
function colorBarExtent(height, spec) {
|
|
1256
|
+
return height / ASPECT + TICK_PAD + Math.max(0, tickColumnWidth(spec) + spec.labelPad) + LABEL_PT * LABEL_THICKNESS_EM;
|
|
1257
|
+
}
|
|
1258
|
+
function fitColorBar(opts) {
|
|
1259
|
+
const { plotWidth, available, gapRatio, minGap, extent } = opts;
|
|
1260
|
+
const gapFor = (width) => Math.max(gapRatio * width, minGap);
|
|
1261
|
+
if (gapFor(plotWidth) + extent <= available) {
|
|
1262
|
+
return { plotWidth, gap: gapFor(plotWidth) };
|
|
1263
|
+
}
|
|
1264
|
+
const byRatio = (available + plotWidth - extent) / (1 + gapRatio);
|
|
1265
|
+
const fitted = Math.max(
|
|
1266
|
+
0,
|
|
1267
|
+
gapRatio * byRatio >= minGap ? byRatio : plotWidth - (minGap + extent - available)
|
|
1268
|
+
);
|
|
1269
|
+
return { plotWidth: fitted, gap: gapFor(fitted) };
|
|
1270
|
+
}
|
|
1271
|
+
function colorBarLayout(spec, position) {
|
|
1272
|
+
const { x, y1, y2 } = position;
|
|
1273
|
+
const width = (y2 - y1) / ASPECT;
|
|
1274
|
+
const step = (y2 - y1) / STEPS;
|
|
1275
|
+
const steps = Array.from({ length: STEPS }, (_, index) => ({
|
|
1276
|
+
y: y2 - (index + 1) * step,
|
|
1277
|
+
// Every band but the lowest reaches half a pixel into the one below, so
|
|
1278
|
+
// anti-aliasing cannot open a hairline seam between them.
|
|
1279
|
+
height: index === 0 ? step : step + 0.5,
|
|
1280
|
+
color: sampleColormap(spec.colormap, index / (STEPS - 1))
|
|
1281
|
+
}));
|
|
1282
|
+
const tickX = x + width + TICK_PAD;
|
|
1283
|
+
const labelLeft = tickX + tickColumnWidth(spec) + spec.labelPad;
|
|
1284
|
+
return {
|
|
1285
|
+
x,
|
|
1286
|
+
y1,
|
|
1287
|
+
y2,
|
|
1288
|
+
width,
|
|
1289
|
+
steps,
|
|
1290
|
+
tickX,
|
|
1291
|
+
tickFontSize: TICK_LABEL_PT3,
|
|
1292
|
+
ticks: [
|
|
1293
|
+
{ y: y2, label: spec.tickLabels[0] },
|
|
1294
|
+
{ y: y1, label: spec.tickLabels[1] }
|
|
1295
|
+
],
|
|
1296
|
+
...spec.offsetText ? { offsetText: { x: tickX, y: y1 - TICK_LABEL_PT3, text: spec.offsetText } } : {},
|
|
1297
|
+
label: {
|
|
1298
|
+
x: labelLeft + LABEL_PT * LABEL_BASELINE_EM,
|
|
1299
|
+
y: (y1 + y2) / 2,
|
|
1300
|
+
text: spec.label,
|
|
1301
|
+
fontSize: LABEL_PT
|
|
1302
|
+
},
|
|
1303
|
+
right: Math.max(
|
|
1304
|
+
tickX + tickColumnWidth(spec),
|
|
1305
|
+
labelLeft + LABEL_PT * LABEL_THICKNESS_EM
|
|
1306
|
+
)
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1309
|
+
var MINUS3 = "\u2212";
|
|
1310
|
+
var POWER_LIMITS = [-5, 6];
|
|
1311
|
+
function roundTo(value, decimals) {
|
|
1312
|
+
const factor = 10 ** decimals;
|
|
1313
|
+
return Math.round(value * factor) / factor;
|
|
1314
|
+
}
|
|
1315
|
+
function scalarFormatterLabels(locs) {
|
|
1316
|
+
const largest = Math.max(0, ...locs.map(Math.abs));
|
|
1317
|
+
const magnitude = largest === 0 ? 0 : Math.floor(Math.log10(largest));
|
|
1318
|
+
const order = magnitude <= POWER_LIMITS[0] || magnitude >= POWER_LIMITS[1] ? magnitude : 0;
|
|
1319
|
+
const scaled = locs.map((value) => value / 10 ** order);
|
|
1320
|
+
let range = Math.max(...scaled) - Math.min(...scaled);
|
|
1321
|
+
if (range === 0) range = Math.max(...scaled.map(Math.abs));
|
|
1322
|
+
if (range === 0) range = 1;
|
|
1323
|
+
const rangeMagnitude = Math.floor(Math.log10(range));
|
|
1324
|
+
const threshold = 1e-3 * 10 ** rangeMagnitude;
|
|
1325
|
+
let decimals = Math.max(0, 3 - rangeMagnitude);
|
|
1326
|
+
while (decimals >= 0) {
|
|
1327
|
+
const error = Math.max(...scaled.map((value) => Math.abs(value - roundTo(value, decimals))));
|
|
1328
|
+
if (error < threshold) decimals -= 1;
|
|
1329
|
+
else break;
|
|
1330
|
+
}
|
|
1331
|
+
decimals += 1;
|
|
1332
|
+
const labels = scaled.map((value) => {
|
|
1333
|
+
const text = value.toFixed(decimals);
|
|
1334
|
+
return Number(text) === 0 ? text.replace("-", "") : text.replace("-", MINUS3);
|
|
1335
|
+
});
|
|
1336
|
+
return order === 0 ? { labels } : { labels, offsetText: `1e${String(order).replace("-", MINUS3)}` };
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1196
1339
|
// src/core/beeswarmLayout.ts
|
|
1197
1340
|
var BEESWARM_MISSING_COLOR = "#777777";
|
|
1198
1341
|
var BEESWARM_ROW_HEIGHT = 0.4;
|
|
1199
1342
|
var NBINS = 100;
|
|
1200
|
-
var AXIS_HEIGHT3 =
|
|
1201
|
-
|
|
1343
|
+
var AXIS_HEIGHT3 = 52;
|
|
1344
|
+
function colorBarSpec(labels) {
|
|
1345
|
+
return {
|
|
1346
|
+
colormap: "red_blue",
|
|
1347
|
+
tickLabels: [labels.featureValueLow, labels.featureValueHigh],
|
|
1348
|
+
label: labels.featureValue,
|
|
1349
|
+
labelPad: 0
|
|
1350
|
+
};
|
|
1351
|
+
}
|
|
1352
|
+
var COLOR_BAR_GAP_RATIO = 0.05 / 0.8;
|
|
1353
|
+
var TICK_LABEL_PT4 = 11;
|
|
1202
1354
|
var TITLE_PT2 = 13;
|
|
1203
1355
|
var X_MARGIN2 = 0.05;
|
|
1204
1356
|
function percentile(values, percent) {
|
|
@@ -1262,7 +1414,7 @@ function spreadPoints(xs, rowIndex, seed) {
|
|
|
1262
1414
|
const scale = 0.9 * (BEESWARM_ROW_HEIGHT / (maxPositiveOffset + 1));
|
|
1263
1415
|
return offsets.map((offset) => rowIndex + offset * scale);
|
|
1264
1416
|
}
|
|
1265
|
-
function beeswarmRows(explanation, maxDisplay, faithfulOtherRow, seed = 0, rowSort = "importance") {
|
|
1417
|
+
function beeswarmRows(explanation, maxDisplay, faithfulOtherRow, seed = 0, rowSort = "importance", labels = shapLabels) {
|
|
1266
1418
|
if (!Number.isInteger(maxDisplay) || maxDisplay <= 0) {
|
|
1267
1419
|
throw new RangeError(`maxDisplay must be a positive integer, received ${maxDisplay}`);
|
|
1268
1420
|
}
|
|
@@ -1277,7 +1429,8 @@ function beeswarmRows(explanation, maxDisplay, faithfulOtherRow, seed = 0, rowSo
|
|
|
1277
1429
|
importance,
|
|
1278
1430
|
order,
|
|
1279
1431
|
maxDisplay,
|
|
1280
|
-
faithfulOtherRow
|
|
1432
|
+
faithfulOtherRow,
|
|
1433
|
+
labels
|
|
1281
1434
|
),
|
|
1282
1435
|
rowSort,
|
|
1283
1436
|
explanation.data
|
|
@@ -1329,14 +1482,14 @@ function beeswarmRows(explanation, maxDisplay, faithfulOtherRow, seed = 0, rowSo
|
|
|
1329
1482
|
});
|
|
1330
1483
|
return { rows, collapsedCount: display.collapsedCount };
|
|
1331
1484
|
}
|
|
1332
|
-
function beeswarmXAxis(min, max, toX, marginLeft, plotWidth, plotBottom) {
|
|
1333
|
-
const { ticks, step } = niceTicks(min, max, tickSpace(plotWidth,
|
|
1485
|
+
function beeswarmXAxis(min, max, toX, marginLeft, plotWidth, plotBottom, title) {
|
|
1486
|
+
const { ticks, step } = niceTicks(min, max, tickSpace(plotWidth, TICK_LABEL_PT4));
|
|
1334
1487
|
return {
|
|
1335
1488
|
xTicks: ticks.map((value) => ({ value, x: toX(value), label: tickLabel(value, step) })),
|
|
1336
1489
|
xSpine: { x1: marginLeft, x2: marginLeft + plotWidth, y: plotBottom },
|
|
1337
1490
|
xTitle: {
|
|
1338
|
-
// _labels.py:5, labels["VALUE"].
|
|
1339
|
-
text:
|
|
1491
|
+
// The default is _labels.py:5, labels["VALUE"].
|
|
1492
|
+
text: title,
|
|
1340
1493
|
x: marginLeft + plotWidth / 2,
|
|
1341
1494
|
y: plotBottom + AXIS_TITLE_DY,
|
|
1342
1495
|
fontSize: TITLE_PT2
|
|
@@ -1344,8 +1497,19 @@ function beeswarmXAxis(min, max, toX, marginLeft, plotWidth, plotBottom) {
|
|
|
1344
1497
|
};
|
|
1345
1498
|
}
|
|
1346
1499
|
function beeswarmLayout(valueRows, opts) {
|
|
1500
|
+
var _a;
|
|
1347
1501
|
const { width, rowHeight, marginLeft, marginRight, marginTop, dotRadius } = opts;
|
|
1348
|
-
const
|
|
1502
|
+
const labels = (_a = opts.labels) != null ? _a : shapLabels;
|
|
1503
|
+
const colorBar = colorBarSpec(labels);
|
|
1504
|
+
const plotBottom = marginTop + valueRows.rows.length * rowHeight;
|
|
1505
|
+
const fit = opts.colorBar ? fitColorBar({
|
|
1506
|
+
plotWidth: width - marginLeft - marginRight,
|
|
1507
|
+
available: marginRight,
|
|
1508
|
+
gapRatio: COLOR_BAR_GAP_RATIO,
|
|
1509
|
+
minGap: 0,
|
|
1510
|
+
extent: colorBarExtent(plotBottom - marginTop, colorBar)
|
|
1511
|
+
}) : null;
|
|
1512
|
+
const plotWidth = fit ? fit.plotWidth : width - marginLeft - marginRight;
|
|
1349
1513
|
const values = valueRows.rows.flatMap((row) => row.points.map((point) => point.x));
|
|
1350
1514
|
const dataMin = Math.min(0, ...values);
|
|
1351
1515
|
const dataMax = Math.max(0, ...values);
|
|
@@ -1370,14 +1534,18 @@ function beeswarmLayout(valueRows, opts) {
|
|
|
1370
1534
|
}))
|
|
1371
1535
|
};
|
|
1372
1536
|
});
|
|
1373
|
-
const plotBottom = marginTop + valueRows.rows.length * rowHeight;
|
|
1374
1537
|
return {
|
|
1375
1538
|
rows,
|
|
1376
1539
|
xDomain: [min, max],
|
|
1377
1540
|
xZero: toX(0),
|
|
1378
|
-
...beeswarmXAxis(min, max, toX, marginLeft, plotWidth, plotBottom),
|
|
1541
|
+
...beeswarmXAxis(min, max, toX, marginLeft, plotWidth, plotBottom, labels.shapValueAxis),
|
|
1379
1542
|
plotWidth,
|
|
1380
1543
|
plotBottom,
|
|
1544
|
+
colorBar: fit ? colorBarLayout(colorBar, {
|
|
1545
|
+
x: marginLeft + plotWidth + fit.gap,
|
|
1546
|
+
y1: marginTop,
|
|
1547
|
+
y2: plotBottom
|
|
1548
|
+
}) : null,
|
|
1381
1549
|
height: plotBottom + AXIS_HEIGHT3
|
|
1382
1550
|
};
|
|
1383
1551
|
}
|
|
@@ -1390,7 +1558,9 @@ var SIDE_BAR_GAP = 10;
|
|
|
1390
1558
|
var SIDE_BAR_RIGHT_INSET = 40;
|
|
1391
1559
|
var SIDE_BAR_HEIGHT_RATIO = 0.6;
|
|
1392
1560
|
var AXIS_HEIGHT4 = 52;
|
|
1393
|
-
var
|
|
1561
|
+
var TICK_LABEL_PT5 = 10;
|
|
1562
|
+
var COLOR_BAR_GAP_RATIO2 = 0.1 / 0.89;
|
|
1563
|
+
var COLOR_BAR_CLEARANCE = 8;
|
|
1394
1564
|
var Y_TICK_LENGTH = 5;
|
|
1395
1565
|
function percentile2(values, fraction) {
|
|
1396
1566
|
if (values.length === 0) return 0;
|
|
@@ -1401,7 +1571,7 @@ function percentile2(values, fraction) {
|
|
|
1401
1571
|
const weight = position - lower;
|
|
1402
1572
|
return sorted[lower] + (sorted[upper] - sorted[lower]) * weight;
|
|
1403
1573
|
}
|
|
1404
|
-
function heatmapRows(explanation, maxDisplay, faithfulOtherRow, rowSort = "importance") {
|
|
1574
|
+
function heatmapRows(explanation, maxDisplay, faithfulOtherRow, rowSort = "importance", labels = shapLabels) {
|
|
1405
1575
|
if (!Number.isInteger(maxDisplay) || maxDisplay <= 0) {
|
|
1406
1576
|
throw new RangeError(`maxDisplay must be a positive integer, received ${maxDisplay}`);
|
|
1407
1577
|
}
|
|
@@ -1413,7 +1583,8 @@ function heatmapRows(explanation, maxDisplay, faithfulOtherRow, rowSort = "impor
|
|
|
1413
1583
|
importance,
|
|
1414
1584
|
featureOrder,
|
|
1415
1585
|
maxDisplay,
|
|
1416
|
-
faithfulOtherRow
|
|
1586
|
+
faithfulOtherRow,
|
|
1587
|
+
labels
|
|
1417
1588
|
),
|
|
1418
1589
|
rowSort,
|
|
1419
1590
|
explanation.data
|
|
@@ -1477,11 +1648,11 @@ function heatmapRows(explanation, maxDisplay, faithfulOtherRow, rowSort = "impor
|
|
|
1477
1648
|
sampleLabelColumn: explanation.sampleLabelColumn
|
|
1478
1649
|
};
|
|
1479
1650
|
}
|
|
1480
|
-
function heatmapXAxis(sampleCount, marginLeft, cellWidth, plotWidth, plotBottom) {
|
|
1651
|
+
function heatmapXAxis(sampleCount, marginLeft, cellWidth, plotWidth, plotBottom, title) {
|
|
1481
1652
|
const { ticks, step } = niceTicks(
|
|
1482
1653
|
-0.5,
|
|
1483
1654
|
sampleCount - 0.5,
|
|
1484
|
-
tickSpace(plotWidth,
|
|
1655
|
+
tickSpace(plotWidth, TICK_LABEL_PT5),
|
|
1485
1656
|
{ integer: true }
|
|
1486
1657
|
);
|
|
1487
1658
|
return {
|
|
@@ -1492,20 +1663,38 @@ function heatmapXAxis(sampleCount, marginLeft, cellWidth, plotWidth, plotBottom)
|
|
|
1492
1663
|
})),
|
|
1493
1664
|
xSpine: null,
|
|
1494
1665
|
xTitle: {
|
|
1495
|
-
text:
|
|
1666
|
+
text: title,
|
|
1496
1667
|
x: marginLeft + plotWidth / 2,
|
|
1497
1668
|
y: plotBottom + AXIS_TITLE_DY,
|
|
1498
|
-
fontSize:
|
|
1669
|
+
fontSize: TICK_LABEL_PT5
|
|
1499
1670
|
}
|
|
1500
1671
|
};
|
|
1501
1672
|
}
|
|
1502
1673
|
function heatmapLayout(valueRows, opts) {
|
|
1674
|
+
var _a;
|
|
1503
1675
|
const { width, rowHeight, marginLeft, marginRight, marginTop } = opts;
|
|
1504
|
-
const
|
|
1505
|
-
const cellWidth = valueRows.columns.length === 0 ? 0 : plotWidth / valueRows.columns.length;
|
|
1506
|
-
const gridRight = marginLeft + plotWidth;
|
|
1676
|
+
const labels = (_a = opts.labels) != null ? _a : shapLabels;
|
|
1507
1677
|
const plotBottom = marginTop + valueRows.rows.length * rowHeight;
|
|
1508
1678
|
const sideBarWidth = Math.max(0, marginRight - SIDE_BAR_RIGHT_INSET - SIDE_BAR_GAP);
|
|
1679
|
+
const ticks = scalarFormatterLabels([valueRows.vmin, valueRows.vmax]);
|
|
1680
|
+
const colorBarSpec2 = {
|
|
1681
|
+
colormap: "red_white_blue",
|
|
1682
|
+
tickLabels: [ticks.labels[0], ticks.labels[1]],
|
|
1683
|
+
...ticks.offsetText ? { offsetText: ticks.offsetText } : {},
|
|
1684
|
+
label: labels.shapValueAxis,
|
|
1685
|
+
labelPad: -10
|
|
1686
|
+
};
|
|
1687
|
+
const colorBarTop = FX_TOP;
|
|
1688
|
+
const fit = opts.colorBar ? fitColorBar({
|
|
1689
|
+
plotWidth: width - marginLeft - marginRight,
|
|
1690
|
+
available: marginRight,
|
|
1691
|
+
gapRatio: COLOR_BAR_GAP_RATIO2,
|
|
1692
|
+
minGap: SIDE_BAR_GAP + sideBarWidth + COLOR_BAR_CLEARANCE,
|
|
1693
|
+
extent: colorBarExtent(plotBottom - colorBarTop, colorBarSpec2)
|
|
1694
|
+
}) : null;
|
|
1695
|
+
const plotWidth = fit ? fit.plotWidth : width - marginLeft - marginRight;
|
|
1696
|
+
const cellWidth = valueRows.columns.length === 0 ? 0 : plotWidth / valueRows.columns.length;
|
|
1697
|
+
const gridRight = marginLeft + plotWidth;
|
|
1509
1698
|
const columns = valueRows.columns.map((column, index) => ({
|
|
1510
1699
|
...column,
|
|
1511
1700
|
x: marginLeft + index * cellWidth,
|
|
@@ -1574,10 +1763,22 @@ function heatmapLayout(valueRows, opts) {
|
|
|
1574
1763
|
x1: marginLeft - Y_TICK_LENGTH,
|
|
1575
1764
|
x2: marginLeft
|
|
1576
1765
|
})),
|
|
1577
|
-
...heatmapXAxis(
|
|
1766
|
+
...heatmapXAxis(
|
|
1767
|
+
valueRows.columns.length,
|
|
1768
|
+
marginLeft,
|
|
1769
|
+
cellWidth,
|
|
1770
|
+
plotWidth,
|
|
1771
|
+
plotBottom,
|
|
1772
|
+
labels.samples
|
|
1773
|
+
),
|
|
1578
1774
|
sampleLabelColumn: valueRows.sampleLabelColumn,
|
|
1579
1775
|
plotWidth,
|
|
1580
1776
|
cellWidth,
|
|
1777
|
+
colorBar: fit ? colorBarLayout(colorBarSpec2, {
|
|
1778
|
+
x: gridRight + fit.gap,
|
|
1779
|
+
y1: colorBarTop,
|
|
1780
|
+
y2: plotBottom
|
|
1781
|
+
}) : null,
|
|
1581
1782
|
height: plotBottom + AXIS_HEIGHT4
|
|
1582
1783
|
};
|
|
1583
1784
|
}
|
|
@@ -1597,6 +1798,9 @@ function heatmapLayout(valueRows, opts) {
|
|
|
1597
1798
|
beeswarmLayout,
|
|
1598
1799
|
beeswarmRows,
|
|
1599
1800
|
collapseToDisplay,
|
|
1801
|
+
colorBarExtent,
|
|
1802
|
+
colorBarLayout,
|
|
1803
|
+
fitColorBar,
|
|
1600
1804
|
formatFeatureLabel,
|
|
1601
1805
|
formatLevel,
|
|
1602
1806
|
formatShapValue,
|
|
@@ -1608,7 +1812,10 @@ function heatmapLayout(valueRows, opts) {
|
|
|
1608
1812
|
heatmapRows,
|
|
1609
1813
|
orderFeatures,
|
|
1610
1814
|
parseExplanation,
|
|
1815
|
+
resolveLabels,
|
|
1611
1816
|
sampleColormap,
|
|
1817
|
+
scalarFormatterLabels,
|
|
1818
|
+
shapLabels,
|
|
1612
1819
|
sortDisplayRows,
|
|
1613
1820
|
waterfallLayout,
|
|
1614
1821
|
waterfallRows
|