@vitessce/scatterplot-embedding 3.8.8 → 3.8.10

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.
@@ -1,5 +1,5 @@
1
1
  import { i as inflate_1 } from "./pako.esm-SxljTded.js";
2
- import { B as BaseDecoder } from "./index-DXCCF6LM.js";
2
+ import { B as BaseDecoder } from "./index-BgrJUaQb.js";
3
3
  class DeflateDecoder extends BaseDecoder {
4
4
  decodeBlock(buffer) {
5
5
  return inflate_1(new Uint8Array(buffer)).buffer;
@@ -3098,6 +3098,59 @@ const PALETTE = [
3098
3098
  [136, 34, 85],
3099
3099
  [170, 68, 153]
3100
3100
  ];
3101
+ function aggregateFeatureArrays(arrays, strategy) {
3102
+ if (!arrays || arrays.length === 0)
3103
+ return null;
3104
+ let targetArray;
3105
+ if (strategy === "first" || typeof strategy === "number" && strategy === 0) {
3106
+ targetArray = arrays[0];
3107
+ } else if (strategy === "last") {
3108
+ targetArray = arrays[arrays.length - 1];
3109
+ } else if (typeof strategy === "number") {
3110
+ if (strategy >= 0 && strategy < arrays.length) {
3111
+ targetArray = arrays[strategy];
3112
+ } else {
3113
+ throw new Error(`Array index out of bounds: ${strategy}`);
3114
+ }
3115
+ }
3116
+ if (targetArray) {
3117
+ return targetArray;
3118
+ }
3119
+ const numArrays = arrays.length;
3120
+ const firstArrayLength = arrays[0].length;
3121
+ if (arrays.some((arr) => arr.length !== firstArrayLength)) {
3122
+ throw new Error("All arrays must have the same length for aggregation.");
3123
+ }
3124
+ if (strategy === "sum" || strategy === "mean") {
3125
+ const resultArray = new Float64Array(firstArrayLength);
3126
+ for (let i2 = 0; i2 < numArrays; i2++) {
3127
+ const arr = arrays[i2];
3128
+ for (let j = 0; j < firstArrayLength; j++) {
3129
+ resultArray[j] += arr[j];
3130
+ }
3131
+ }
3132
+ if (strategy === "mean") {
3133
+ for (let i2 = 0; i2 < firstArrayLength; i2++) {
3134
+ resultArray[i2] /= numArrays;
3135
+ }
3136
+ }
3137
+ return resultArray;
3138
+ }
3139
+ if (strategy === "difference") {
3140
+ if (numArrays !== 2) {
3141
+ console.warn("Difference strategy requires exactly 2 arrays.");
3142
+ return arrays[0];
3143
+ }
3144
+ const arr0 = arrays[0];
3145
+ const arr1 = arrays[1];
3146
+ const resultArray = new Float64Array(firstArrayLength);
3147
+ for (let i2 = 0; i2 < firstArrayLength; i2++) {
3148
+ resultArray[i2] = arr0[i2] - arr1[i2];
3149
+ }
3150
+ return resultArray;
3151
+ }
3152
+ throw new Error(`Unknown aggregation strategy: ${strategy}`);
3153
+ }
3101
3154
  var util;
3102
3155
  (function(util2) {
3103
3156
  util2.assertEqual = (val) => val;
@@ -123348,22 +123401,22 @@ function addDecoder(cases, importFn) {
123348
123401
  }
123349
123402
  cases.forEach((c2) => registry$1.set(c2, importFn));
123350
123403
  }
123351
- addDecoder([void 0, 1], () => import("./raw-CBdLq0ga.js").then((m2) => m2.default));
123352
- addDecoder(5, () => import("./lzw-Dbo7OpaS.js").then((m2) => m2.default));
123404
+ addDecoder([void 0, 1], () => import("./raw-BJDJq5QK.js").then((m2) => m2.default));
123405
+ addDecoder(5, () => import("./lzw-BwAlpbiv.js").then((m2) => m2.default));
123353
123406
  addDecoder(6, () => {
123354
123407
  throw new Error("old style JPEG compression is not supported.");
123355
123408
  });
123356
- addDecoder(7, () => import("./jpeg-UKzKGs1t.js").then((m2) => m2.default));
123357
- addDecoder([8, 32946], () => import("./deflate-Dr6bxD0w.js").then((m2) => m2.default));
123358
- addDecoder(32773, () => import("./packbits-0X-GrQId.js").then((m2) => m2.default));
123409
+ addDecoder(7, () => import("./jpeg-V5Wg0DTH.js").then((m2) => m2.default));
123410
+ addDecoder([8, 32946], () => import("./deflate-DV84laLg.js").then((m2) => m2.default));
123411
+ addDecoder(32773, () => import("./packbits-BzaxDQbT.js").then((m2) => m2.default));
123359
123412
  addDecoder(
123360
123413
  34887,
123361
- () => import("./lerc-D62tGpqi.js").then(async (m2) => {
123414
+ () => import("./lerc-LFwYjack.js").then(async (m2) => {
123362
123415
  await m2.zstd.init();
123363
123416
  return m2;
123364
123417
  }).then((m2) => m2.default)
123365
123418
  );
123366
- addDecoder(50001, () => import("./webimage-C2vUudNG.js").then((m2) => m2.default));
123419
+ addDecoder(50001, () => import("./webimage-6o_3zIHO.js").then((m2) => m2.default));
123367
123420
  function decodeRowAcc(row, stride) {
123368
123421
  let length2 = row.length - stride;
123369
123422
  let offset2 = 0;
@@ -150440,8 +150493,14 @@ function forceCollideRects() {
150440
150493
  };
150441
150494
  return force;
150442
150495
  }
150443
- function getPointSizeDevicePixels(devicePixelRatio, zoom, xRange, yRange, width2, height2) {
150444
- const pointSize = 5e-4;
150496
+ const BASE_POINT_SIZE = 5;
150497
+ const LARGE_DATASET_CELL_COUNT = 1e4;
150498
+ const SMALL_DATASET_CELL_COUNT = 100;
150499
+ function getInitialPointSize(numCells = LARGE_DATASET_CELL_COUNT) {
150500
+ return BASE_POINT_SIZE / clamp$4(numCells, SMALL_DATASET_CELL_COUNT, LARGE_DATASET_CELL_COUNT);
150501
+ }
150502
+ function getPointSizeDevicePixels(devicePixelRatio, zoom, xRange, yRange, width2, height2, numCells) {
150503
+ const pointSize = getInitialPointSize(numCells);
150445
150504
  const pointScreenSizeMax = 10;
150446
150505
  const pointScreenSizeMin = 1 / devicePixelRatio;
150447
150506
  const scaleFactor = 2 ** zoom;
@@ -151999,40 +152058,51 @@ function number$2(x) {
151999
152058
  const ascendingBisect = bisector(ascending);
152000
152059
  const bisectRight = ascendingBisect.right;
152001
152060
  bisector(number$2).center;
152002
- var e10 = Math.sqrt(50), e5 = Math.sqrt(10), e2 = Math.sqrt(2);
152061
+ const e10 = Math.sqrt(50), e5 = Math.sqrt(10), e2 = Math.sqrt(2);
152062
+ function tickSpec(start, stop, count2) {
152063
+ const step = (stop - start) / Math.max(0, count2), power = Math.floor(Math.log10(step)), error2 = step / Math.pow(10, power), factor = error2 >= e10 ? 10 : error2 >= e5 ? 5 : error2 >= e2 ? 2 : 1;
152064
+ let i1, i2, inc;
152065
+ if (power < 0) {
152066
+ inc = Math.pow(10, -power) / factor;
152067
+ i1 = Math.round(start * inc);
152068
+ i2 = Math.round(stop * inc);
152069
+ if (i1 / inc < start) ++i1;
152070
+ if (i2 / inc > stop) --i2;
152071
+ inc = -inc;
152072
+ } else {
152073
+ inc = Math.pow(10, power) * factor;
152074
+ i1 = Math.round(start / inc);
152075
+ i2 = Math.round(stop / inc);
152076
+ if (i1 * inc < start) ++i1;
152077
+ if (i2 * inc > stop) --i2;
152078
+ }
152079
+ if (i2 < i1 && 0.5 <= count2 && count2 < 2) return tickSpec(start, stop, count2 * 2);
152080
+ return [i1, i2, inc];
152081
+ }
152003
152082
  function ticks(start, stop, count2) {
152004
- var reverse, i2 = -1, n2, ticks2, step;
152005
152083
  stop = +stop, start = +start, count2 = +count2;
152006
- if (start === stop && count2 > 0) return [start];
152007
- if (reverse = stop < start) n2 = start, start = stop, stop = n2;
152008
- if ((step = tickIncrement(start, stop, count2)) === 0 || !isFinite(step)) return [];
152009
- if (step > 0) {
152010
- let r0 = Math.round(start / step), r1 = Math.round(stop / step);
152011
- if (r0 * step < start) ++r0;
152012
- if (r1 * step > stop) --r1;
152013
- ticks2 = new Array(n2 = r1 - r0 + 1);
152014
- while (++i2 < n2) ticks2[i2] = (r0 + i2) * step;
152084
+ if (!(count2 > 0)) return [];
152085
+ if (start === stop) return [start];
152086
+ const reverse = stop < start, [i1, i2, inc] = reverse ? tickSpec(stop, start, count2) : tickSpec(start, stop, count2);
152087
+ if (!(i2 >= i1)) return [];
152088
+ const n2 = i2 - i1 + 1, ticks2 = new Array(n2);
152089
+ if (reverse) {
152090
+ if (inc < 0) for (let i3 = 0; i3 < n2; ++i3) ticks2[i3] = (i2 - i3) / -inc;
152091
+ else for (let i3 = 0; i3 < n2; ++i3) ticks2[i3] = (i2 - i3) * inc;
152015
152092
  } else {
152016
- step = -step;
152017
- let r0 = Math.round(start * step), r1 = Math.round(stop * step);
152018
- if (r0 / step < start) ++r0;
152019
- if (r1 / step > stop) --r1;
152020
- ticks2 = new Array(n2 = r1 - r0 + 1);
152021
- while (++i2 < n2) ticks2[i2] = (r0 + i2) / step;
152022
- }
152023
- if (reverse) ticks2.reverse();
152093
+ if (inc < 0) for (let i3 = 0; i3 < n2; ++i3) ticks2[i3] = (i1 + i3) / -inc;
152094
+ else for (let i3 = 0; i3 < n2; ++i3) ticks2[i3] = (i1 + i3) * inc;
152095
+ }
152024
152096
  return ticks2;
152025
152097
  }
152026
152098
  function tickIncrement(start, stop, count2) {
152027
- var step = (stop - start) / Math.max(0, count2), power = Math.floor(Math.log(step) / Math.LN10), error2 = step / Math.pow(10, power);
152028
- return power >= 0 ? (error2 >= e10 ? 10 : error2 >= e5 ? 5 : error2 >= e2 ? 2 : 1) * Math.pow(10, power) : -Math.pow(10, -power) / (error2 >= e10 ? 10 : error2 >= e5 ? 5 : error2 >= e2 ? 2 : 1);
152099
+ stop = +stop, start = +start, count2 = +count2;
152100
+ return tickSpec(start, stop, count2)[2];
152029
152101
  }
152030
152102
  function tickStep(start, stop, count2) {
152031
- var step0 = Math.abs(stop - start) / Math.max(0, count2), step1 = Math.pow(10, Math.floor(Math.log(step0) / Math.LN10)), error2 = step0 / step1;
152032
- if (error2 >= e10) step1 *= 10;
152033
- else if (error2 >= e5) step1 *= 5;
152034
- else if (error2 >= e2) step1 *= 2;
152035
- return stop < start ? -step1 : step1;
152103
+ stop = +stop, start = +start, count2 = +count2;
152104
+ const reverse = stop < start, inc = reverse ? tickIncrement(stop, start, count2) : tickIncrement(start, stop, count2);
152105
+ return (reverse ? -1 : 1) * (inc < 0 ? 1 / -inc : inc);
152036
152106
  }
152037
152107
  function initRange(domain, range2) {
152038
152108
  switch (arguments.length) {
@@ -153114,6 +153184,9 @@ function ramp(color2, n2 = 256) {
153114
153184
  canvas.width = n2;
153115
153185
  canvas.height = 1;
153116
153186
  const context = canvas.getContext("2d");
153187
+ if (!context) {
153188
+ throw new Error("Could not get 2d context from canvas");
153189
+ }
153117
153190
  for (let i2 = 0; i2 < n2; ++i2) {
153118
153191
  context.fillStyle = color2(i2 / (n2 - 1));
153119
153192
  context.fillRect(i2, 0, 1, 1);
@@ -153132,6 +153205,8 @@ function getXlinkHref(cmap) {
153132
153205
  }
153133
153206
  const useStyles = makeStyles()(() => ({
153134
153207
  legend: {
153208
+ position: "relative",
153209
+ // Needed for absolute positioning of slider overlay
153135
153210
  top: "2px",
153136
153211
  right: "2px",
153137
153212
  fontSize: "10px !important",
@@ -153163,6 +153238,74 @@ const useStyles = makeStyles()(() => ({
153163
153238
  },
153164
153239
  legendInvisible: {
153165
153240
  display: "none"
153241
+ },
153242
+ sliderContainer: {
153243
+ position: "absolute",
153244
+ // Position at the colormap location: top offset = titleHeight
153245
+ top: "10px",
153246
+ // titleHeight
153247
+ left: "2px",
153248
+ // Account for parent padding
153249
+ width: "calc(100% - 4px)",
153250
+ // Account for left and right padding
153251
+ height: "8px",
153252
+ // rectHeight
153253
+ "&:hover $sliderThumb": {
153254
+ opacity: 1
153255
+ }
153256
+ },
153257
+ sliderRoot: {
153258
+ position: "absolute",
153259
+ top: 0,
153260
+ left: 0,
153261
+ width: "100%",
153262
+ height: "8px",
153263
+ // rectHeight
153264
+ padding: 0,
153265
+ "& .MuiSlider-rail": {
153266
+ display: "none"
153267
+ },
153268
+ "& .MuiSlider-track": {
153269
+ display: "none"
153270
+ },
153271
+ "& .MuiSlider-valueLabel": {
153272
+ fontSize: "9px",
153273
+ padding: "2px 4px",
153274
+ backgroundColor: "rgb(0, 0, 0)",
153275
+ borderRadius: "2px"
153276
+ }
153277
+ },
153278
+ sliderThumb: {
153279
+ width: "4px",
153280
+ height: "12px",
153281
+ borderRadius: "2px",
153282
+ backgroundColor: "white",
153283
+ border: "1px solid black",
153284
+ opacity: 0,
153285
+ transition: "opacity 0.15s ease-in-out",
153286
+ "&:hover, &.Mui-focusVisible": {
153287
+ boxShadow: "0 0 0 4px rgba(0, 0, 0, 0.16)",
153288
+ opacity: 1
153289
+ },
153290
+ "&.Mui-active": {
153291
+ boxShadow: "0 0 0 6px rgba(0, 0, 0, 0.16)",
153292
+ opacity: 1
153293
+ }
153294
+ },
153295
+ colormapImage: {
153296
+ position: "absolute",
153297
+ top: "2px",
153298
+ height: "6px",
153299
+ // rectHeight
153300
+ pointerEvents: "none"
153301
+ },
153302
+ grayTrack: {
153303
+ position: "absolute",
153304
+ top: "2px",
153305
+ height: "6px",
153306
+ // rectHeight
153307
+ backgroundColor: "rgba(128, 128, 128, 0.5)",
153308
+ pointerEvents: "none"
153166
153309
  }
153167
153310
  }));
153168
153311
  const titleHeight = 10;
@@ -153172,21 +153315,23 @@ const rectMarginX = 2;
153172
153315
  function combineExtents(extents, featureAggregationStrategy) {
153173
153316
  if (Array.isArray(extents)) {
153174
153317
  if (Array.isArray(extents == null ? void 0 : extents[0])) {
153318
+ const extentsArray = extents;
153175
153319
  if (featureAggregationStrategy === "first") {
153176
- return extents[0];
153320
+ return extentsArray[0];
153177
153321
  }
153178
153322
  if (featureAggregationStrategy === "last") {
153179
- return extents.at(-1);
153323
+ return extentsArray.at(-1) || null;
153180
153324
  }
153181
153325
  if (typeof featureAggregationStrategy === "number") {
153182
153326
  const i2 = featureAggregationStrategy;
153183
- return extents[i2];
153327
+ return extentsArray[i2];
153184
153328
  }
153185
153329
  if (featureAggregationStrategy === "sum") {
153186
- return extents.reduce((a2, h) => [a2[0] + h[0], a2[1] + h[1]]);
153330
+ return extentsArray.reduce((a2, h) => [a2[0] + h[0], a2[1] + h[1]]);
153187
153331
  }
153188
153332
  if (featureAggregationStrategy === "mean") {
153189
- return extents.reduce((a2, h) => [a2[0] + h[0], a2[1] + h[1]]).map((v) => v / extents.length);
153333
+ const sum2 = extentsArray.reduce((a2, h) => [a2[0] + h[0], a2[1] + h[1]]);
153334
+ return [sum2[0] / extentsArray.length, sum2[1] / extentsArray.length];
153190
153335
  }
153191
153336
  } else {
153192
153337
  return extents;
@@ -153200,7 +153345,7 @@ function combineMissings(missings, featureAggregationStrategy) {
153200
153345
  return missings[0];
153201
153346
  }
153202
153347
  if (featureAggregationStrategy === "last") {
153203
- return missings.at(-1);
153348
+ return missings.at(-1) || null;
153204
153349
  }
153205
153350
  if (typeof featureAggregationStrategy === "number") {
153206
153351
  const i2 = featureAggregationStrategy;
@@ -153216,52 +153361,127 @@ function combineMissings(missings, featureAggregationStrategy) {
153216
153361
  return null;
153217
153362
  }
153218
153363
  function Legend(props) {
153219
- const { visible: visibleProp, positionRelative = false, highContrast = false, obsType, featureValueType, considerSelections = true, obsColorEncoding, featureSelection, featureLabelsMap, featureValueColormap, featureValueColormapRange, spatialChannelColor, spatialLayerColor, obsSetSelection, obsSetColor, featureAggregationStrategy, extent: extent2, missing, width: width2 = 100, height: height2 = 36, maxHeight: maxHeight2 = null, theme, showObsLabel = false, pointsVisible = true, contoursVisible = false, contoursFilled, contourPercentiles, contourThresholds } = props;
153220
- const svgRef = useRef();
153364
+ const {
153365
+ visible: visibleProp,
153366
+ positionRelative = false,
153367
+ highContrast = false,
153368
+ obsType,
153369
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
153370
+ featureType: _featureType = void 0,
153371
+ // Unused but accepted for API compatibility
153372
+ featureValueType,
153373
+ considerSelections = true,
153374
+ obsColorEncoding,
153375
+ featureSelection,
153376
+ featureLabelsMap,
153377
+ featureValueColormap,
153378
+ featureValueColormapRange,
153379
+ setFeatureValueColormapRange,
153380
+ spatialChannelColor,
153381
+ spatialLayerColor,
153382
+ obsSetSelection,
153383
+ obsSetColor,
153384
+ featureAggregationStrategy,
153385
+ extent: extent2,
153386
+ missing,
153387
+ width: width2 = 100,
153388
+ height: height2 = 36,
153389
+ maxHeight: maxHeight2 = null,
153390
+ theme,
153391
+ showObsLabel = false,
153392
+ pointsVisible = true,
153393
+ contoursVisible = false,
153394
+ contoursFilled,
153395
+ contourPercentiles,
153396
+ contourThresholds
153397
+ } = props;
153398
+ const svgRef = useRef(null);
153221
153399
  const { classes } = useStyles();
153400
+ const [localRange, setLocalRange] = useState(featureValueColormapRange);
153401
+ useEffect(() => {
153402
+ setLocalRange(featureValueColormapRange);
153403
+ }, [featureValueColormapRange]);
153404
+ const debouncedSetRange = useMemo(() => setFeatureValueColormapRange ? debounce$1((value) => {
153405
+ setFeatureValueColormapRange(value);
153406
+ }, 5, { leading: false, trailing: true }) : null, [setFeatureValueColormapRange]);
153407
+ useEffect(() => () => {
153408
+ if (debouncedSetRange) {
153409
+ debouncedSetRange.cancel();
153410
+ }
153411
+ }, [debouncedSetRange]);
153412
+ const handleSliderChange = useCallback((_event, newValue) => {
153413
+ const rangeValue = newValue;
153414
+ setLocalRange(rangeValue);
153415
+ if (debouncedSetRange) {
153416
+ debouncedSetRange(rangeValue);
153417
+ }
153418
+ }, [debouncedSetRange]);
153222
153419
  const isDarkTheme = theme === "dark";
153223
153420
  const isStaticColor = obsColorEncoding === "spatialChannelColor" || obsColorEncoding === "spatialLayerColor";
153224
153421
  const isSetColor = obsColorEncoding === "cellSetSelection";
153225
- const layerColor = Array.isArray(spatialLayerColor) && spatialLayerColor.length === 3 ? spatialLayerColor : getDefaultColor(theme);
153226
- const channelColor = Array.isArray(spatialChannelColor) && spatialChannelColor.length === 3 ? spatialChannelColor : getDefaultColor(theme);
153422
+ const layerColor = Array.isArray(spatialLayerColor) && spatialLayerColor.length === 3 ? spatialLayerColor : getDefaultColor(theme ?? "light");
153423
+ const channelColor = Array.isArray(spatialChannelColor) && spatialChannelColor.length === 3 ? spatialChannelColor : getDefaultColor(theme ?? "light");
153227
153424
  const staticColor = obsColorEncoding === "spatialChannelColor" ? channelColor : layerColor;
153228
- const visible = visibleProp && (!considerSelections || obsColorEncoding === "geneSelection" && featureSelection && Array.isArray(featureSelection) && featureSelection.length >= 1 || isSetColor && (obsSetSelection == null ? void 0 : obsSetSelection.length) > 0 && (obsSetColor == null ? void 0 : obsSetColor.length) > 0 || isStaticColor);
153425
+ const visible = visibleProp && (!considerSelections || ["geneSelection", "geneExpression"].includes(obsColorEncoding ?? "") && featureSelection && Array.isArray(featureSelection) && featureSelection.length >= 1 || isSetColor && ((obsSetSelection == null ? void 0 : obsSetSelection.length) ?? 0) > 0 && ((obsSetColor == null ? void 0 : obsSetColor.length) ?? 0) > 0 || isStaticColor);
153229
153426
  const levelZeroNames = useMemo(() => Array.from(new Set((obsSetSelection == null ? void 0 : obsSetSelection.map((setPath) => setPath[0])) || [])), [obsSetSelection]);
153230
- const dynamicHeight = isSetColor && obsSetSelection ? levelZeroNames.length * titleHeight + (obsSetSelection == null ? void 0 : obsSetSelection.length) * (rectHeight + rectMarginY) : height2 + (!pointsVisible && contoursVisible ? 25 : 0);
153427
+ const dynamicHeight = isSetColor && obsSetSelection ? levelZeroNames.length * titleHeight + ((obsSetSelection == null ? void 0 : obsSetSelection.length) ?? 0) * (rectHeight + rectMarginY) : height2 + (!pointsVisible && contoursVisible ? 25 : 0);
153231
153428
  const availHeight = maxHeight2 !== null ? Math.max(0, maxHeight2 - 4) : Infinity;
153232
153429
  const needsScroll = Number.isFinite(availHeight) && dynamicHeight > availHeight + 1;
153233
153430
  useEffect(() => {
153234
153431
  const domElement = svgRef.current;
153432
+ if (!domElement)
153433
+ return;
153235
153434
  const foregroundColor = highContrast ? "black" : isDarkTheme ? "white" : "black";
153236
153435
  const svg = select(domElement);
153237
153436
  svg.selectAll("g").remove();
153238
153437
  svg.attr("width", width2).attr("height", dynamicHeight);
153239
153438
  const g2 = svg.append("g").attr("width", width2).attr("height", dynamicHeight);
153240
- if (!considerSelections || obsColorEncoding === "geneSelection") {
153241
- const [xMin, xMax] = combineExtents(extent2, featureAggregationStrategy) || [0, 1];
153439
+ const showInteractiveSlider2 = setFeatureValueColormapRange && ["geneSelection", "geneExpression"].includes(obsColorEncoding ?? "") && pointsVisible && featureValueColormap;
153440
+ if (!considerSelections || ["geneSelection", "geneExpression"].includes(obsColorEncoding ?? "")) {
153441
+ const combinedExtent = combineExtents(extent2 ?? null, featureAggregationStrategy ?? null) || [0, 1];
153442
+ const [xMin, xMax] = combinedExtent;
153242
153443
  if (featureValueColormap && pointsVisible) {
153243
- const xlinkHref = getXlinkHref(featureValueColormap);
153244
- g2.append("image").attr("x", 0).attr("y", titleHeight).attr("width", width2).attr("height", rectHeight).attr("preserveAspectRatio", "none").attr("href", xlinkHref);
153245
- const [rMin, rMax] = featureValueColormapRange;
153444
+ const xlinkHref2 = getXlinkHref(featureValueColormap);
153445
+ const currentRange = showInteractiveSlider2 ? localRange : featureValueColormapRange;
153446
+ const [rMin, rMax] = currentRange || [0, 1];
153447
+ if (showInteractiveSlider2) ;
153448
+ else if (setFeatureValueColormapRange) {
153449
+ g2.append("image").attr("x", rMin * width2).attr("y", titleHeight).attr("width", (rMax - rMin) * width2).attr("height", rectHeight).attr("preserveAspectRatio", "none").attr("href", xlinkHref2);
153450
+ } else {
153451
+ g2.append("image").attr("x", 0).attr("y", titleHeight).attr("width", width2).attr("height", rectHeight).attr("preserveAspectRatio", "none").attr("href", xlinkHref2);
153452
+ }
153246
153453
  const scaledDataExtent = [
153247
153454
  xMin + (xMax - xMin) * rMin,
153248
153455
  xMax - (xMax - xMin) * (1 - rMax)
153249
153456
  ];
153250
- const x = linear().domain(scaledDataExtent).range([0.5, width2 - 0.5]);
153251
- const axisTicks = g2.append("g").attr("transform", `translate(0,${titleHeight + rectHeight})`).style("font-size", "10px").call(axisBottom(x).tickValues(scaledDataExtent));
153252
- axisTicks.selectAll("line,path").style("stroke", foregroundColor);
153253
- axisTicks.selectAll("text").style("fill", foregroundColor);
153254
- axisTicks.selectAll("text").attr("text-anchor", (d, i2) => i2 === 0 ? "start" : "end");
153255
- } else if (contoursVisible) {
153457
+ let x;
153458
+ if (setFeatureValueColormapRange || showInteractiveSlider2) {
153459
+ x = linear().domain(scaledDataExtent).range([rMin * width2, rMax * width2]);
153460
+ } else {
153461
+ x = linear().domain(scaledDataExtent).range([0, width2]);
153462
+ }
153463
+ if (showInteractiveSlider2) {
153464
+ const xGlobal = linear().domain([xMin, xMax]).range([0, width2 - 4]);
153465
+ const axisTicks = g2.append("g").attr("transform", `translate(0,${titleHeight + rectHeight})`).style("font-size", "10px").call(axisBottom(xGlobal).tickValues([xMin, xMax]));
153466
+ axisTicks.selectAll("line,path").style("stroke", foregroundColor);
153467
+ axisTicks.selectAll("text").style("fill", foregroundColor);
153468
+ axisTicks.selectAll("text").attr("text-anchor", (_d, i2) => i2 === 0 ? "start" : "end");
153469
+ } else {
153470
+ const axisTicks = g2.append("g").attr("transform", `translate(0,${titleHeight + rectHeight})`).style("font-size", "10px").call(axisBottom(x).tickValues(scaledDataExtent));
153471
+ axisTicks.selectAll("line,path").style("stroke", foregroundColor);
153472
+ axisTicks.selectAll("text").style("fill", foregroundColor);
153473
+ axisTicks.selectAll("text").attr("text-anchor", (_d, i2) => i2 === 0 ? "start" : "end");
153474
+ }
153475
+ } else if (contoursVisible && contourPercentiles) {
153256
153476
  const tSize = 12;
153257
153477
  const xPercentile = linear().domain([0, 1]).range([tSize / 2, width2 - tSize / 2 - 2]);
153258
- const axisTicks = g2.append("g").attr("transform", `translate(0,${titleHeight + rectHeight + 15})`).style("font-size", "9px").call(axisBottom(xPercentile).tickValues(contourPercentiles).tickFormat(format(".0%")).tickSizeOuter(0));
153478
+ const axisTicks = g2.append("g").attr("transform", `translate(0,${titleHeight + rectHeight + 15})`).style("font-size", "9px").call(axisBottom(xPercentile).tickValues(contourPercentiles).tickFormat((d) => format(".0%")(d)).tickSizeOuter(0));
153259
153479
  axisTicks.selectAll("line,path").style("stroke", foregroundColor);
153260
153480
  axisTicks.selectAll("text").style("fill", foregroundColor);
153261
153481
  const NEIGHBOR_THRESHOLD = 18;
153262
- const contourPercentages = contourPercentiles.map((x) => x * 100);
153482
+ const contourPercentages = contourPercentiles.map((p) => p * 100);
153263
153483
  if ((contourPercentages == null ? void 0 : contourPercentages[1]) - (contourPercentages == null ? void 0 : contourPercentages[0]) <= NEIGHBOR_THRESHOLD || (contourPercentages == null ? void 0 : contourPercentages[2]) - (contourPercentages == null ? void 0 : contourPercentages[1]) <= NEIGHBOR_THRESHOLD) {
153264
- axisTicks.selectAll("text").attr("transform", (d, i2) => `translate(0,${i2 === 0 || i2 === contourPercentiles.length - 1 ? 0 : 10})`);
153484
+ axisTicks.selectAll("text").attr("transform", (_d, i2) => `translate(0,${i2 === 0 || i2 === contourPercentiles.length - 1 ? 0 : 10})`);
153265
153485
  }
153266
153486
  const triangleGroupG = g2.append("g").attr("transform", `translate(0,${titleHeight + rectHeight + 4})`);
153267
153487
  contourPercentiles.forEach((p, i2) => {
@@ -153271,7 +153491,7 @@ function Legend(props) {
153271
153491
  const thresholdGroupG = g2.append("g").attr("transform", `translate(0,${titleHeight + rectHeight})`);
153272
153492
  const thresholdFormatter = format(".0f");
153273
153493
  contourPercentiles.forEach((p, i2) => {
153274
- const contourThreshold = xMin + (xMax - xMin) * ((contourThresholds == null ? void 0 : contourThresholds[i2]) / 255);
153494
+ const contourThreshold = xMin + (xMax - xMin) * (((contourThresholds == null ? void 0 : contourThresholds[i2]) ?? 0) / 255);
153275
153495
  const thresholdG = thresholdGroupG.append("g").attr("transform", `translate(${xPercentile(p)},0)`).style("font-size", "7px");
153276
153496
  thresholdG.append("text").text(thresholdFormatter(contourThreshold)).style("fill", foregroundColor).attr("text-anchor", "middle");
153277
153497
  });
@@ -153295,9 +153515,9 @@ function Legend(props) {
153295
153515
  y += titleHeight;
153296
153516
  setPaths.forEach((setPath) => {
153297
153517
  var _a3;
153298
- const setColor2 = ((_a3 = obsSetColor == null ? void 0 : obsSetColor.find((d) => isEqual$1(d.path, setPath))) == null ? void 0 : _a3.color) || getDefaultColor(theme);
153518
+ const setColor2 = ((_a3 = obsSetColor == null ? void 0 : obsSetColor.find((d) => isEqual$1(d.path, setPath))) == null ? void 0 : _a3.color) || getDefaultColor(theme ?? "light");
153299
153519
  g2.append("rect").attr("x", 0).attr("y", y).attr("width", rectHeight).attr("height", rectHeight).attr("fill", `rgb(${setColor2[0]},${setColor2[1]},${setColor2[2]})`);
153300
- g2.append("text").attr("text-anchor", "start").attr("dominant-baseline", "hanging").attr("x", rectHeight + rectMarginX).attr("y", y).text(setPath.at(-1)).style("font-size", "9px").style("fill", foregroundColor);
153520
+ g2.append("text").attr("text-anchor", "start").attr("dominant-baseline", "hanging").attr("x", rectHeight + rectMarginX).attr("y", y).text(setPath.at(-1) ?? "").style("font-size", "9px").style("fill", foregroundColor);
153301
153521
  y += rectHeight + rectMarginY;
153302
153522
  });
153303
153523
  });
@@ -153318,17 +153538,17 @@ function Legend(props) {
153318
153538
  } else {
153319
153539
  featureSelectionLabelRawStr = featureSelectionLabelRaw == null ? void 0 : featureSelectionLabelRaw[0];
153320
153540
  }
153321
- const combinedMissing = combineMissings(missing, featureAggregationStrategy);
153541
+ const combinedMissing = combineMissings(missing ?? null, featureAggregationStrategy ?? null);
153322
153542
  const featureSelectionLabel = combinedMissing ? `${featureSelectionLabelRawStr} (${Math.round(combinedMissing * 100)}% NaN)` : featureSelectionLabelRawStr;
153323
- const obsLabel = capitalize$2(obsType);
153324
- const featureLabel = considerSelections ? featureSelectionLabel || capitalize$2(featureValueType) : capitalize$2(featureValueType);
153543
+ const obsLabel = capitalize$2(obsType ?? null);
153544
+ const featureLabel = considerSelections ? featureSelectionLabel || capitalize$2(featureValueType ?? null) : capitalize$2(featureValueType ?? null);
153325
153545
  const mainLabel = showObsLabel ? obsLabel : featureLabel;
153326
153546
  const subLabel = showObsLabel ? featureLabel : null;
153327
153547
  const hasSubLabel = subLabel !== null;
153328
153548
  if (!isSetColor) {
153329
- g2.append("text").attr("text-anchor", hasSubLabel ? "start" : "end").attr("dominant-baseline", "hanging").attr("x", hasSubLabel ? 0 : width2).attr("y", 0).text(mainLabel).style("font-size", "10px").style("fill", foregroundColor);
153549
+ g2.append("text").attr("text-anchor", hasSubLabel ? "start" : "end").attr("dominant-baseline", "hanging").attr("x", hasSubLabel ? 0 : width2 - 4).attr("y", 0).text(mainLabel ?? "").style("font-size", "10px").style("fill", foregroundColor);
153330
153550
  if (hasSubLabel) {
153331
- g2.append("text").attr("text-anchor", "end").attr("dominant-baseline", "hanging").attr("x", width2).attr("y", titleHeight).text(subLabel).style("font-size", "9px").style("fill", foregroundColor);
153551
+ g2.append("text").attr("text-anchor", "end").attr("dominant-baseline", "hanging").attr("x", width2).attr("y", titleHeight + rectHeight).text(subLabel ?? "").style("font-size", "9px").style("fill", foregroundColor);
153332
153552
  }
153333
153553
  }
153334
153554
  }, [
@@ -153336,6 +153556,7 @@ function Legend(props) {
153336
153556
  height2,
153337
153557
  featureValueColormap,
153338
153558
  featureValueColormapRange,
153559
+ localRange,
153339
153560
  considerSelections,
153340
153561
  obsType,
153341
153562
  obsColorEncoding,
@@ -153354,20 +153575,63 @@ function Legend(props) {
153354
153575
  contoursFilled,
153355
153576
  contoursVisible,
153356
153577
  pointsVisible,
153357
- featureAggregationStrategy
153578
+ featureAggregationStrategy,
153579
+ setFeatureValueColormapRange,
153580
+ dynamicHeight,
153581
+ highContrast,
153582
+ isStaticColor,
153583
+ missing,
153584
+ showObsLabel,
153585
+ staticColor
153358
153586
  ]);
153359
- return jsxRuntimeExports.jsx("div", { className: clsx$1(classes.legend, {
153587
+ const showInteractiveSlider = setFeatureValueColormapRange && ["geneSelection", "geneExpression"].includes(obsColorEncoding ?? "") && pointsVisible && featureValueColormap;
153588
+ const globalExtent = useMemo(() => {
153589
+ const combined = combineExtents(extent2 ?? null, featureAggregationStrategy ?? null);
153590
+ return combined || [0, 1];
153591
+ }, [extent2, featureAggregationStrategy]);
153592
+ const formatSliderValue = useCallback((value) => {
153593
+ const [xMin, xMax] = globalExtent;
153594
+ const dataValue = xMin + (xMax - xMin) * value;
153595
+ const range2 = xMax - xMin;
153596
+ if (range2 < 0.01) {
153597
+ return dataValue.toExponential(2);
153598
+ }
153599
+ if (range2 < 1) {
153600
+ return dataValue.toFixed(3);
153601
+ }
153602
+ if (range2 < 100) {
153603
+ return dataValue.toFixed(1);
153604
+ }
153605
+ return Math.round(dataValue).toString();
153606
+ }, [globalExtent]);
153607
+ const xlinkHref = featureValueColormap ? getXlinkHref(featureValueColormap) : null;
153608
+ const currentLocalRange = localRange || [0, 1];
153609
+ return jsxRuntimeExports.jsxs("div", { className: clsx$1(classes.legend, {
153360
153610
  [classes.legendRelative]: positionRelative,
153361
153611
  [classes.legendAbsolute]: !positionRelative,
153362
153612
  [classes.legendHighContrast]: highContrast,
153363
153613
  [classes.legendLowContrast]: !highContrast,
153364
153614
  [classes.legendInvisible]: !visible
153365
153615
  }), style: {
153366
- ...needsScroll ? { maxHeight: `${Math.floor(availHeight)}px`, overflowY: "auto" } : { maxHeight: void 0, overflowY: "visible" }
153367
- }, children: jsxRuntimeExports.jsx("svg", { ref: svgRef, style: {
153616
+ ...needsScroll ? { maxHeight: `${Math.floor(availHeight)}px`, overflowY: "auto" } : { maxHeight: void 0, overflowY: "visible" },
153617
+ width: `${width2}px`
153618
+ }, children: [jsxRuntimeExports.jsx("svg", { ref: svgRef, style: {
153368
153619
  width: `${width2}px`,
153369
153620
  height: `${dynamicHeight}px`
153370
- } }) });
153621
+ } }), showInteractiveSlider && xlinkHref && jsxRuntimeExports.jsxs("div", { className: classes.sliderContainer, children: [currentLocalRange[0] > 0 && jsxRuntimeExports.jsx("div", { className: classes.grayTrack, style: {
153622
+ left: 0,
153623
+ width: `${currentLocalRange[0] * 100}%`
153624
+ } }), currentLocalRange[1] < 1 && jsxRuntimeExports.jsx("div", { className: classes.grayTrack, style: {
153625
+ left: `${currentLocalRange[1] * 100}%`,
153626
+ width: `${(1 - currentLocalRange[1]) * 100}%`
153627
+ } }), jsxRuntimeExports.jsx("img", { src: xlinkHref, alt: "Colormap gradient", className: classes.colormapImage, style: {
153628
+ left: `${currentLocalRange[0] * 100}%`,
153629
+ width: `${(currentLocalRange[1] - currentLocalRange[0]) * 100}%`
153630
+ } }), jsxRuntimeExports.jsx(Slider, { className: classes.sliderRoot, value: currentLocalRange, onChange: handleSliderChange, min: 0, max: 1, step: 0.01, disableSwap: true, valueLabelDisplay: "auto", valueLabelFormat: formatSliderValue, "aria-label": "Colormap range", getAriaLabel: (index2) => index2 === 0 ? "Colormap minimum" : "Colormap maximum", getAriaValueText: (value) => formatSliderValue(value), slotProps: {
153631
+ thumb: {
153632
+ className: classes.sliderThumb
153633
+ }
153634
+ } })] })] });
153371
153635
  }
153372
153636
  makeStyles()(() => ({
153373
153637
  multiLegend: {
@@ -153715,7 +153979,8 @@ function EmbeddingScatterplotSubscriber(props) {
153715
153979
  xRange,
153716
153980
  yRange,
153717
153981
  width2,
153718
- height2
153982
+ height2,
153983
+ numCells
153719
153984
  );
153720
153985
  setDynamicCellRadius(pointSizeDevicePixels);
153721
153986
  const nextCellOpacityScale = getPointOpacity(
@@ -153768,11 +154033,18 @@ function EmbeddingScatterplotSubscriber(props) {
153768
154033
  const getCellIsSelected = useCallback((object2, { index: index2 }) => (cellSelectionSet || /* @__PURE__ */ new Set([])).has(obsEmbeddingIndex[index2]) ? 1 : 0, [cellSelectionSet, obsEmbeddingIndex]);
153769
154034
  const cellRadius = cellRadiusMode === "manual" ? cellRadiusFixed : dynamicCellRadius;
153770
154035
  const cellOpacity = cellOpacityMode === "manual" ? cellOpacityFixed : dynamicCellOpacity;
154036
+ const aggregatedExpressionData = useMemo(() => {
154037
+ if (featureAggregationStrategyToUse != null && expressionData && expressionData.length > 1) {
154038
+ const aggregated = aggregateFeatureArrays(expressionData, featureAggregationStrategyToUse);
154039
+ return [aggregated];
154040
+ }
154041
+ return expressionData;
154042
+ }, [expressionData, featureAggregationStrategyToUse]);
153771
154043
  const {
153772
154044
  normData: uint8ExpressionData,
153773
154045
  extents: expressionExtents,
153774
154046
  missing: expressionMissing
153775
- } = useUint8FeatureSelection(expressionData);
154047
+ } = useUint8FeatureSelection(aggregatedExpressionData);
153776
154048
  const getExpressionValue = useExpressionValueGetter({
153777
154049
  instanceObsIndex: obsEmbeddingIndex,
153778
154050
  matrixObsIndex,
@@ -154015,6 +154287,7 @@ function EmbeddingScatterplotSubscriber(props) {
154015
154287
  featureLabelsMap,
154016
154288
  featureValueColormap: geneExpressionColormap,
154017
154289
  featureValueColormapRange: geneExpressionColormapRange,
154290
+ setFeatureValueColormapRange: setGeneExpressionColormapRange,
154018
154291
  obsSetSelection: cellSetSelection,
154019
154292
  extent: expressionExtents,
154020
154293
  missing: expressionMissing,
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { D, E } from "./index-DXCCF6LM.js";
1
+ import { D, E } from "./index-BgrJUaQb.js";
2
2
  export {
3
3
  D as DualEmbeddingScatterplotSubscriber,
4
4
  E as EmbeddingScatterplotSubscriber
@@ -1,4 +1,4 @@
1
- import { B as BaseDecoder } from "./index-DXCCF6LM.js";
1
+ import { B as BaseDecoder } from "./index-BgrJUaQb.js";
2
2
  const dctZigZag = new Int32Array([
3
3
  0,
4
4
  1,
@@ -1,5 +1,5 @@
1
1
  import { i as inflate_1 } from "./pako.esm-SxljTded.js";
2
- import { g as getDefaultExportFromCjs, B as BaseDecoder } from "./index-DXCCF6LM.js";
2
+ import { g as getDefaultExportFromCjs, B as BaseDecoder } from "./index-BgrJUaQb.js";
3
3
  const LercParameters = {
4
4
  AddCompression: 1
5
5
  };
@@ -1,4 +1,4 @@
1
- import { B as BaseDecoder } from "./index-DXCCF6LM.js";
1
+ import { B as BaseDecoder } from "./index-BgrJUaQb.js";
2
2
  const MIN_BITS = 9;
3
3
  const CLEAR_CODE = 256;
4
4
  const EOI_CODE = 257;
@@ -1,4 +1,4 @@
1
- import { B as BaseDecoder } from "./index-DXCCF6LM.js";
1
+ import { B as BaseDecoder } from "./index-BgrJUaQb.js";
2
2
  class PackbitsDecoder extends BaseDecoder {
3
3
  decodeBlock(buffer) {
4
4
  const dataView = new DataView(buffer);
@@ -1,4 +1,4 @@
1
- import { B as BaseDecoder } from "./index-DXCCF6LM.js";
1
+ import { B as BaseDecoder } from "./index-BgrJUaQb.js";
2
2
  class RawDecoder extends BaseDecoder {
3
3
  decodeBlock(buffer) {
4
4
  return buffer;
@@ -1,4 +1,4 @@
1
- import { B as BaseDecoder } from "./index-DXCCF6LM.js";
1
+ import { B as BaseDecoder } from "./index-BgrJUaQb.js";
2
2
  class WebImageDecoder extends BaseDecoder {
3
3
  constructor() {
4
4
  super();
@@ -1 +1 @@
1
- {"version":3,"file":"EmbeddingScatterplotSubscriber.d.ts","sourceRoot":"","sources":["../src/EmbeddingScatterplotSubscriber.js"],"names":[],"mappings":"AA6CA;;;;;;;;;;;;GAYG;AACH,sDAVG;IAAsB,IAAI,EAAlB,MAAM;IACQ,KAAK,EAAnB,MAAM;IACQ,kBAAkB,EAAhC,MAAM;IAEU,mBAAmB;IAErB,KAAK,EAAnB,MAAM;IACQ,kBAAkB,EAAhC,MAAM;CAEhB,eAimBA"}
1
+ {"version":3,"file":"EmbeddingScatterplotSubscriber.d.ts","sourceRoot":"","sources":["../src/EmbeddingScatterplotSubscriber.js"],"names":[],"mappings":"AA6CA;;;;;;;;;;;;GAYG;AACH,sDAVG;IAAsB,IAAI,EAAlB,MAAM;IACQ,KAAK,EAAnB,MAAM;IACQ,kBAAkB,EAAhC,MAAM;IAEU,mBAAmB;IAErB,KAAK,EAAnB,MAAM;IACQ,kBAAkB,EAAhC,MAAM;CAEhB,eA6mBA"}
@@ -5,7 +5,7 @@ import { isEqual } from 'lodash-es';
5
5
  import { circle } from '@turf/circle';
6
6
  import { TitleInfo, useReady, useUrls, useDeckCanvasSize, useUint8FeatureSelection, useExpressionValueGetter, useGetObsInfo, useObsEmbeddingData, useObsSetsData, useFeatureSelection, useObsFeatureMatrixIndices, useFeatureLabelsData, useMultiObsLabels, useSampleSetsData, useSampleEdgesData, useCoordination, useLoaders, useSetComponentHover, useSetComponentViewInfo, useInitialCoordination, useExpandedFeatureLabelsMap, useCoordinationScopes, } from '@vitessce/vit-s';
7
7
  import { setObsSelection, mergeObsSets, getCellSetPolygons, getCellColors, stratifyArrays, } from '@vitessce/sets-utils';
8
- import { pluralize as plur, commaNumber } from '@vitessce/utils';
8
+ import { pluralize as plur, commaNumber, aggregateFeatureArrays } from '@vitessce/utils';
9
9
  import { Scatterplot, ScatterplotTooltipSubscriber, ScatterplotOptions, getPointSizeDevicePixels, getPointOpacity, } from '@vitessce/scatterplot';
10
10
  import { Legend } from '@vitessce/legend';
11
11
  import { ViewType, COMPONENT_COORDINATION_TYPES, ViewHelpMapping } from '@vitessce/constants-internal';
@@ -148,7 +148,7 @@ export function EmbeddingScatterplotSubscriber(props) {
148
148
  // extents of the cell coordinates on the x/y axes.
149
149
  useEffect(() => {
150
150
  if (xRange && yRange && width && height) {
151
- const pointSizeDevicePixels = getPointSizeDevicePixels(window.devicePixelRatio, zoom, xRange, yRange, width, height);
151
+ const pointSizeDevicePixels = getPointSizeDevicePixels(window.devicePixelRatio, zoom, xRange, yRange, width, height, numCells);
152
152
  setDynamicCellRadius(pointSizeDevicePixels);
153
153
  const nextCellOpacityScale = getPointOpacity(zoom, xRange, yRange, width, height, numCells, averageFillDensity);
154
154
  setDynamicCellOpacity(nextCellOpacityScale);
@@ -182,7 +182,17 @@ export function EmbeddingScatterplotSubscriber(props) {
182
182
  const getCellIsSelected = useCallback((object, { index }) => ((cellSelectionSet || new Set([])).has(obsEmbeddingIndex[index]) ? 1.0 : 0.0), [cellSelectionSet, obsEmbeddingIndex]);
183
183
  const cellRadius = (cellRadiusMode === 'manual' ? cellRadiusFixed : dynamicCellRadius);
184
184
  const cellOpacity = (cellOpacityMode === 'manual' ? cellOpacityFixed : dynamicCellOpacity);
185
- const { normData: uint8ExpressionData, extents: expressionExtents, missing: expressionMissing, } = useUint8FeatureSelection(expressionData);
185
+ // Compute aggregated expression data if featureAggregationStrategyToUse is not null
186
+ // and we have multiple features to aggregate.
187
+ const aggregatedExpressionData = useMemo(() => {
188
+ if (featureAggregationStrategyToUse != null && expressionData && expressionData.length > 1) {
189
+ const aggregated = aggregateFeatureArrays(expressionData, featureAggregationStrategyToUse);
190
+ // Return as array with single element to match expressionData structure
191
+ return [aggregated];
192
+ }
193
+ return expressionData;
194
+ }, [expressionData, featureAggregationStrategyToUse]);
195
+ const { normData: uint8ExpressionData, extents: expressionExtents, missing: expressionMissing, } = useUint8FeatureSelection(aggregatedExpressionData);
186
196
  // Set up a getter function for gene expression values, to be used
187
197
  // by the DeckGL layer to obtain values for instanced attributes.
188
198
  const getExpressionValue = useExpressionValueGetter({
@@ -316,7 +326,7 @@ export function EmbeddingScatterplotSubscriber(props) {
316
326
  }, updateViewInfo: setComponentViewInfo, getExpressionValue: getExpressionValue, getCellIsSelected: getCellIsSelected, obsSetSelection: cellSetSelection, sampleSetSelection: sampleSetSelection,
317
327
  // InternMap data structures where keys are
318
328
  // obsSet -> sampleSet -> arrayKey -> [].
319
- stratifiedData: stratifiedData, obsSetColor: cellSetColor, sampleSetColor: sampleSetColor, contourThresholds: contourThresholds, contourColorEncoding: contourColorEncoding, contourColor: contourColor, contoursFilled: embeddingContoursFilled, embeddingPointsVisible: embeddingPointsVisible, embeddingContoursVisible: embeddingContoursVisible, circleInfo: circleInfo, featureSelection: geneSelection }), tooltipsVisible && width && height ? (_jsx(ScatterplotTooltipSubscriber, { parentUuid: uuid, obsHighlight: cellHighlight, width: width, height: height, getObsInfo: getObsInfo, featureType: featureType, featureLabelsMap: featureLabelsMap })) : null, _jsx(Legend, { visible: true, theme: theme, featureType: featureType, featureValueType: featureValueType, obsColorEncoding: cellColorEncoding, featureSelection: geneSelection, featureLabelsMap: featureLabelsMap, featureValueColormap: geneExpressionColormap, featureValueColormapRange: geneExpressionColormapRange, obsSetSelection: cellSetSelection, extent: expressionExtents, missing: expressionMissing,
329
+ stratifiedData: stratifiedData, obsSetColor: cellSetColor, sampleSetColor: sampleSetColor, contourThresholds: contourThresholds, contourColorEncoding: contourColorEncoding, contourColor: contourColor, contoursFilled: embeddingContoursFilled, embeddingPointsVisible: embeddingPointsVisible, embeddingContoursVisible: embeddingContoursVisible, circleInfo: circleInfo, featureSelection: geneSelection }), tooltipsVisible && width && height ? (_jsx(ScatterplotTooltipSubscriber, { parentUuid: uuid, obsHighlight: cellHighlight, width: width, height: height, getObsInfo: getObsInfo, featureType: featureType, featureLabelsMap: featureLabelsMap })) : null, _jsx(Legend, { visible: true, theme: theme, featureType: featureType, featureValueType: featureValueType, obsColorEncoding: cellColorEncoding, featureSelection: geneSelection, featureLabelsMap: featureLabelsMap, featureValueColormap: geneExpressionColormap, featureValueColormapRange: geneExpressionColormapRange, setFeatureValueColormapRange: setGeneExpressionColormapRange, obsSetSelection: cellSetSelection, extent: expressionExtents, missing: expressionMissing,
320
330
  // Contour percentile legend
321
331
  pointsVisible: embeddingPointsVisible, contoursVisible: embeddingContoursVisible, contoursFilled: embeddingContoursFilled, contourPercentiles: contourPercentiles || DEFAULT_CONTOUR_PERCENTILES, contourThresholds: contourThresholds, featureAggregationStrategy: featureAggregationStrategyToUse })] }));
322
332
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitessce/scatterplot-embedding",
3
- "version": "3.8.8",
3
+ "version": "3.8.10",
4
4
  "author": "HIDIVE Lab at HMS",
5
5
  "homepage": "http://vitessce.io",
6
6
  "repository": {
@@ -20,13 +20,13 @@
20
20
  "lodash-es": "^4.17.21",
21
21
  "react-aria": "^3.28.0",
22
22
  "@turf/circle": "^7.2.0",
23
- "@vitessce/styles": "3.8.8",
24
- "@vitessce/constants-internal": "3.8.8",
25
- "@vitessce/legend": "3.8.8",
26
- "@vitessce/sets-utils": "3.8.8",
27
- "@vitessce/utils": "3.8.8",
28
- "@vitessce/vit-s": "3.8.8",
29
- "@vitessce/scatterplot": "3.8.8"
23
+ "@vitessce/styles": "3.8.10",
24
+ "@vitessce/constants-internal": "3.8.10",
25
+ "@vitessce/legend": "3.8.10",
26
+ "@vitessce/scatterplot": "3.8.10",
27
+ "@vitessce/sets-utils": "3.8.10",
28
+ "@vitessce/utils": "3.8.10",
29
+ "@vitessce/vit-s": "3.8.10"
30
30
  },
31
31
  "devDependencies": {
32
32
  "react": "18.3.1",
@@ -31,7 +31,7 @@ import {
31
31
  setObsSelection, mergeObsSets, getCellSetPolygons, getCellColors,
32
32
  stratifyArrays,
33
33
  } from '@vitessce/sets-utils';
34
- import { pluralize as plur, commaNumber } from '@vitessce/utils';
34
+ import { pluralize as plur, commaNumber, aggregateFeatureArrays } from '@vitessce/utils';
35
35
  import {
36
36
  Scatterplot, ScatterplotTooltipSubscriber, ScatterplotOptions,
37
37
  getPointSizeDevicePixels,
@@ -335,7 +335,7 @@ export function EmbeddingScatterplotSubscriber(props) {
335
335
  useEffect(() => {
336
336
  if (xRange && yRange && width && height) {
337
337
  const pointSizeDevicePixels = getPointSizeDevicePixels(
338
- window.devicePixelRatio, zoom, xRange, yRange, width, height,
338
+ window.devicePixelRatio, zoom, xRange, yRange, width, height, numCells,
339
339
  );
340
340
  setDynamicCellRadius(pointSizeDevicePixels);
341
341
 
@@ -365,7 +365,7 @@ export function EmbeddingScatterplotSubscriber(props) {
365
365
  setOriginalViewState({ target: [initialTargetX, initialTargetY, 0], zoom: initialZoom });
366
366
  }
367
367
  }
368
- // eslint-disable-next-line react-hooks/exhaustive-deps
368
+ // eslint-disable-next-line react-hooks/exhaustive-deps
369
369
  }, [xRange, yRange, xExtent, yExtent, numCells,
370
370
  width, height, initialZoom, zoom, initialTargetX, initialTargetY, averageFillDensity]);
371
371
 
@@ -381,11 +381,22 @@ export function EmbeddingScatterplotSubscriber(props) {
381
381
  const cellRadius = (cellRadiusMode === 'manual' ? cellRadiusFixed : dynamicCellRadius);
382
382
  const cellOpacity = (cellOpacityMode === 'manual' ? cellOpacityFixed : dynamicCellOpacity);
383
383
 
384
+ // Compute aggregated expression data if featureAggregationStrategyToUse is not null
385
+ // and we have multiple features to aggregate.
386
+ const aggregatedExpressionData = useMemo(() => {
387
+ if (featureAggregationStrategyToUse != null && expressionData && expressionData.length > 1) {
388
+ const aggregated = aggregateFeatureArrays(expressionData, featureAggregationStrategyToUse);
389
+ // Return as array with single element to match expressionData structure
390
+ return [aggregated];
391
+ }
392
+ return expressionData;
393
+ }, [expressionData, featureAggregationStrategyToUse]);
394
+
384
395
  const {
385
396
  normData: uint8ExpressionData,
386
397
  extents: expressionExtents,
387
398
  missing: expressionMissing,
388
- } = useUint8FeatureSelection(expressionData);
399
+ } = useUint8FeatureSelection(aggregatedExpressionData);
389
400
 
390
401
  // Set up a getter function for gene expression values, to be used
391
402
  // by the DeckGL layer to obtain values for instanced attributes.
@@ -651,6 +662,7 @@ export function EmbeddingScatterplotSubscriber(props) {
651
662
  featureLabelsMap={featureLabelsMap}
652
663
  featureValueColormap={geneExpressionColormap}
653
664
  featureValueColormapRange={geneExpressionColormapRange}
665
+ setFeatureValueColormapRange={setGeneExpressionColormapRange}
654
666
  obsSetSelection={cellSetSelection}
655
667
  extent={expressionExtents}
656
668
  missing={expressionMissing}