@vitessce/heatmap 3.8.9 → 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-e8-Lh_l9.js";
2
+ import { B as BaseDecoder } from "./index-CIYB_Yf2.js";
3
3
  class DeflateDecoder extends BaseDecoder {
4
4
  decodeBlock(buffer) {
5
5
  return inflate_1(new Uint8Array(buffer)).buffer;
@@ -3,7 +3,7 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
4
  var _a2, _b;
5
5
  import * as React from "react";
6
- import React__default, { useContext, forwardRef, useRef, useMemo, createContext, createElement, isValidElement, cloneElement, Children, useEffect, useLayoutEffect, useState, useImperativeHandle, useReducer, useCallback } from "react";
6
+ import React__default, { useContext, forwardRef, useRef, useMemo, createContext, createElement, isValidElement, cloneElement, Children, useState, useEffect, useCallback, useLayoutEffect, useImperativeHandle, useReducer } from "react";
7
7
  import { useVitessceContainer, useComponentHover, useComponentViewInfo, usePlotOptionsStyles, OptionsContainer, OptionSelect, useLoaders, useCoordinationScopes, useSetComponentHover, useSetComponentViewInfo, useCoordination, useDeckCanvasSize, useMultiObsLabels, useFeatureLabelsData, useExpandedFeatureLabelsMap, useObsFeatureMatrixData, useObsSetsData, useReady, useUrls, useUint8ObsFeatureMatrix, useGetObsInfo, useGetObsMembership, TitleInfo } from "@vitessce/vit-s";
8
8
  import * as ReactDOM from "react-dom";
9
9
  function _mergeNamespaces(n2, m) {
@@ -1832,7 +1832,7 @@ function debounce$1(func, wait, options) {
1832
1832
  leading = !!options.leading;
1833
1833
  maxing = "maxWait" in options;
1834
1834
  maxWait = maxing ? nativeMax$1(toNumber(options.maxWait) || 0, wait) : maxWait;
1835
- trailing = "trailing" in options ? true : trailing;
1835
+ trailing = "trailing" in options ? !!options.trailing : trailing;
1836
1836
  }
1837
1837
  function invokeFunc(time) {
1838
1838
  var args = lastArgs, thisArg = lastThis;
@@ -32438,40 +32438,51 @@ function number$2(x) {
32438
32438
  const ascendingBisect = bisector$1(ascending$2);
32439
32439
  const bisectRight = ascendingBisect.right;
32440
32440
  bisector$1(number$2).center;
32441
- var e10 = Math.sqrt(50), e5 = Math.sqrt(10), e2 = Math.sqrt(2);
32441
+ const e10 = Math.sqrt(50), e5 = Math.sqrt(10), e2 = Math.sqrt(2);
32442
+ function tickSpec(start, stop, count2) {
32443
+ 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;
32444
+ let i1, i2, inc;
32445
+ if (power < 0) {
32446
+ inc = Math.pow(10, -power) / factor;
32447
+ i1 = Math.round(start * inc);
32448
+ i2 = Math.round(stop * inc);
32449
+ if (i1 / inc < start) ++i1;
32450
+ if (i2 / inc > stop) --i2;
32451
+ inc = -inc;
32452
+ } else {
32453
+ inc = Math.pow(10, power) * factor;
32454
+ i1 = Math.round(start / inc);
32455
+ i2 = Math.round(stop / inc);
32456
+ if (i1 * inc < start) ++i1;
32457
+ if (i2 * inc > stop) --i2;
32458
+ }
32459
+ if (i2 < i1 && 0.5 <= count2 && count2 < 2) return tickSpec(start, stop, count2 * 2);
32460
+ return [i1, i2, inc];
32461
+ }
32442
32462
  function ticks(start, stop, count2) {
32443
- var reverse, i2 = -1, n2, ticks2, step;
32444
32463
  stop = +stop, start = +start, count2 = +count2;
32445
- if (start === stop && count2 > 0) return [start];
32446
- if (reverse = stop < start) n2 = start, start = stop, stop = n2;
32447
- if ((step = tickIncrement(start, stop, count2)) === 0 || !isFinite(step)) return [];
32448
- if (step > 0) {
32449
- let r0 = Math.round(start / step), r1 = Math.round(stop / step);
32450
- if (r0 * step < start) ++r0;
32451
- if (r1 * step > stop) --r1;
32452
- ticks2 = new Array(n2 = r1 - r0 + 1);
32453
- while (++i2 < n2) ticks2[i2] = (r0 + i2) * step;
32464
+ if (!(count2 > 0)) return [];
32465
+ if (start === stop) return [start];
32466
+ const reverse = stop < start, [i1, i2, inc] = reverse ? tickSpec(stop, start, count2) : tickSpec(start, stop, count2);
32467
+ if (!(i2 >= i1)) return [];
32468
+ const n2 = i2 - i1 + 1, ticks2 = new Array(n2);
32469
+ if (reverse) {
32470
+ if (inc < 0) for (let i3 = 0; i3 < n2; ++i3) ticks2[i3] = (i2 - i3) / -inc;
32471
+ else for (let i3 = 0; i3 < n2; ++i3) ticks2[i3] = (i2 - i3) * inc;
32454
32472
  } else {
32455
- step = -step;
32456
- let r0 = Math.round(start * step), r1 = Math.round(stop * step);
32457
- if (r0 / step < start) ++r0;
32458
- if (r1 / step > stop) --r1;
32459
- ticks2 = new Array(n2 = r1 - r0 + 1);
32460
- while (++i2 < n2) ticks2[i2] = (r0 + i2) / step;
32461
- }
32462
- if (reverse) ticks2.reverse();
32473
+ if (inc < 0) for (let i3 = 0; i3 < n2; ++i3) ticks2[i3] = (i1 + i3) / -inc;
32474
+ else for (let i3 = 0; i3 < n2; ++i3) ticks2[i3] = (i1 + i3) * inc;
32475
+ }
32463
32476
  return ticks2;
32464
32477
  }
32465
32478
  function tickIncrement(start, stop, count2) {
32466
- var step = (stop - start) / Math.max(0, count2), power = Math.floor(Math.log(step) / Math.LN10), error2 = step / Math.pow(10, power);
32467
- 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);
32479
+ stop = +stop, start = +start, count2 = +count2;
32480
+ return tickSpec(start, stop, count2)[2];
32468
32481
  }
32469
32482
  function tickStep(start, stop, count2) {
32470
- var step0 = Math.abs(stop - start) / Math.max(0, count2), step1 = Math.pow(10, Math.floor(Math.log(step0) / Math.LN10)), error2 = step0 / step1;
32471
- if (error2 >= e10) step1 *= 10;
32472
- else if (error2 >= e5) step1 *= 5;
32473
- else if (error2 >= e2) step1 *= 2;
32474
- return stop < start ? -step1 : step1;
32483
+ stop = +stop, start = +start, count2 = +count2;
32484
+ const reverse = stop < start, inc = reverse ? tickIncrement(stop, start, count2) : tickIncrement(start, stop, count2);
32485
+ return (reverse ? -1 : 1) * (inc < 0 ? 1 / -inc : inc);
32475
32486
  }
32476
32487
  function initRange(domain, range2) {
32477
32488
  switch (arguments.length) {
@@ -33553,6 +33564,9 @@ function ramp(color2, n2 = 256) {
33553
33564
  canvas.width = n2;
33554
33565
  canvas.height = 1;
33555
33566
  const context = canvas.getContext("2d");
33567
+ if (!context) {
33568
+ throw new Error("Could not get 2d context from canvas");
33569
+ }
33556
33570
  for (let i2 = 0; i2 < n2; ++i2) {
33557
33571
  context.fillStyle = color2(i2 / (n2 - 1));
33558
33572
  context.fillRect(i2, 0, 1, 1);
@@ -33571,6 +33585,8 @@ function getXlinkHref(cmap) {
33571
33585
  }
33572
33586
  const useStyles$2 = makeStyles()(() => ({
33573
33587
  legend: {
33588
+ position: "relative",
33589
+ // Needed for absolute positioning of slider overlay
33574
33590
  top: "2px",
33575
33591
  right: "2px",
33576
33592
  fontSize: "10px !important",
@@ -33602,6 +33618,74 @@ const useStyles$2 = makeStyles()(() => ({
33602
33618
  },
33603
33619
  legendInvisible: {
33604
33620
  display: "none"
33621
+ },
33622
+ sliderContainer: {
33623
+ position: "absolute",
33624
+ // Position at the colormap location: top offset = titleHeight
33625
+ top: "10px",
33626
+ // titleHeight
33627
+ left: "2px",
33628
+ // Account for parent padding
33629
+ width: "calc(100% - 4px)",
33630
+ // Account for left and right padding
33631
+ height: "8px",
33632
+ // rectHeight
33633
+ "&:hover $sliderThumb": {
33634
+ opacity: 1
33635
+ }
33636
+ },
33637
+ sliderRoot: {
33638
+ position: "absolute",
33639
+ top: 0,
33640
+ left: 0,
33641
+ width: "100%",
33642
+ height: "8px",
33643
+ // rectHeight
33644
+ padding: 0,
33645
+ "& .MuiSlider-rail": {
33646
+ display: "none"
33647
+ },
33648
+ "& .MuiSlider-track": {
33649
+ display: "none"
33650
+ },
33651
+ "& .MuiSlider-valueLabel": {
33652
+ fontSize: "9px",
33653
+ padding: "2px 4px",
33654
+ backgroundColor: "rgb(0, 0, 0)",
33655
+ borderRadius: "2px"
33656
+ }
33657
+ },
33658
+ sliderThumb: {
33659
+ width: "4px",
33660
+ height: "12px",
33661
+ borderRadius: "2px",
33662
+ backgroundColor: "white",
33663
+ border: "1px solid black",
33664
+ opacity: 0,
33665
+ transition: "opacity 0.15s ease-in-out",
33666
+ "&:hover, &.Mui-focusVisible": {
33667
+ boxShadow: "0 0 0 4px rgba(0, 0, 0, 0.16)",
33668
+ opacity: 1
33669
+ },
33670
+ "&.Mui-active": {
33671
+ boxShadow: "0 0 0 6px rgba(0, 0, 0, 0.16)",
33672
+ opacity: 1
33673
+ }
33674
+ },
33675
+ colormapImage: {
33676
+ position: "absolute",
33677
+ top: "2px",
33678
+ height: "6px",
33679
+ // rectHeight
33680
+ pointerEvents: "none"
33681
+ },
33682
+ grayTrack: {
33683
+ position: "absolute",
33684
+ top: "2px",
33685
+ height: "6px",
33686
+ // rectHeight
33687
+ backgroundColor: "rgba(128, 128, 128, 0.5)",
33688
+ pointerEvents: "none"
33605
33689
  }
33606
33690
  }));
33607
33691
  const titleHeight = 10;
@@ -33611,21 +33695,23 @@ const rectMarginX = 2;
33611
33695
  function combineExtents(extents, featureAggregationStrategy) {
33612
33696
  if (Array.isArray(extents)) {
33613
33697
  if (Array.isArray(extents == null ? void 0 : extents[0])) {
33698
+ const extentsArray = extents;
33614
33699
  if (featureAggregationStrategy === "first") {
33615
- return extents[0];
33700
+ return extentsArray[0];
33616
33701
  }
33617
33702
  if (featureAggregationStrategy === "last") {
33618
- return extents.at(-1);
33703
+ return extentsArray.at(-1) || null;
33619
33704
  }
33620
33705
  if (typeof featureAggregationStrategy === "number") {
33621
33706
  const i2 = featureAggregationStrategy;
33622
- return extents[i2];
33707
+ return extentsArray[i2];
33623
33708
  }
33624
33709
  if (featureAggregationStrategy === "sum") {
33625
- return extents.reduce((a2, h) => [a2[0] + h[0], a2[1] + h[1]]);
33710
+ return extentsArray.reduce((a2, h) => [a2[0] + h[0], a2[1] + h[1]]);
33626
33711
  }
33627
33712
  if (featureAggregationStrategy === "mean") {
33628
- return extents.reduce((a2, h) => [a2[0] + h[0], a2[1] + h[1]]).map((v) => v / extents.length);
33713
+ const sum2 = extentsArray.reduce((a2, h) => [a2[0] + h[0], a2[1] + h[1]]);
33714
+ return [sum2[0] / extentsArray.length, sum2[1] / extentsArray.length];
33629
33715
  }
33630
33716
  } else {
33631
33717
  return extents;
@@ -33639,7 +33725,7 @@ function combineMissings(missings, featureAggregationStrategy) {
33639
33725
  return missings[0];
33640
33726
  }
33641
33727
  if (featureAggregationStrategy === "last") {
33642
- return missings.at(-1);
33728
+ return missings.at(-1) || null;
33643
33729
  }
33644
33730
  if (typeof featureAggregationStrategy === "number") {
33645
33731
  const i2 = featureAggregationStrategy;
@@ -33655,52 +33741,127 @@ function combineMissings(missings, featureAggregationStrategy) {
33655
33741
  return null;
33656
33742
  }
33657
33743
  function Legend(props) {
33658
- 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;
33659
- const svgRef = useRef();
33744
+ const {
33745
+ visible: visibleProp,
33746
+ positionRelative = false,
33747
+ highContrast = false,
33748
+ obsType,
33749
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
33750
+ featureType: _featureType = void 0,
33751
+ // Unused but accepted for API compatibility
33752
+ featureValueType,
33753
+ considerSelections = true,
33754
+ obsColorEncoding,
33755
+ featureSelection,
33756
+ featureLabelsMap,
33757
+ featureValueColormap,
33758
+ featureValueColormapRange,
33759
+ setFeatureValueColormapRange,
33760
+ spatialChannelColor,
33761
+ spatialLayerColor,
33762
+ obsSetSelection,
33763
+ obsSetColor,
33764
+ featureAggregationStrategy,
33765
+ extent: extent2,
33766
+ missing,
33767
+ width: width2 = 100,
33768
+ height: height2 = 36,
33769
+ maxHeight: maxHeight2 = null,
33770
+ theme,
33771
+ showObsLabel = false,
33772
+ pointsVisible = true,
33773
+ contoursVisible = false,
33774
+ contoursFilled,
33775
+ contourPercentiles,
33776
+ contourThresholds
33777
+ } = props;
33778
+ const svgRef = useRef(null);
33660
33779
  const { classes } = useStyles$2();
33780
+ const [localRange, setLocalRange] = useState(featureValueColormapRange);
33781
+ useEffect(() => {
33782
+ setLocalRange(featureValueColormapRange);
33783
+ }, [featureValueColormapRange]);
33784
+ const debouncedSetRange = useMemo(() => setFeatureValueColormapRange ? debounce$1((value) => {
33785
+ setFeatureValueColormapRange(value);
33786
+ }, 5, { leading: false, trailing: true }) : null, [setFeatureValueColormapRange]);
33787
+ useEffect(() => () => {
33788
+ if (debouncedSetRange) {
33789
+ debouncedSetRange.cancel();
33790
+ }
33791
+ }, [debouncedSetRange]);
33792
+ const handleSliderChange = useCallback((_event, newValue) => {
33793
+ const rangeValue = newValue;
33794
+ setLocalRange(rangeValue);
33795
+ if (debouncedSetRange) {
33796
+ debouncedSetRange(rangeValue);
33797
+ }
33798
+ }, [debouncedSetRange]);
33661
33799
  const isDarkTheme = theme === "dark";
33662
33800
  const isStaticColor = obsColorEncoding === "spatialChannelColor" || obsColorEncoding === "spatialLayerColor";
33663
33801
  const isSetColor = obsColorEncoding === "cellSetSelection";
33664
- const layerColor = Array.isArray(spatialLayerColor) && spatialLayerColor.length === 3 ? spatialLayerColor : getDefaultColor(theme);
33665
- const channelColor = Array.isArray(spatialChannelColor) && spatialChannelColor.length === 3 ? spatialChannelColor : getDefaultColor(theme);
33802
+ const layerColor = Array.isArray(spatialLayerColor) && spatialLayerColor.length === 3 ? spatialLayerColor : getDefaultColor(theme ?? "light");
33803
+ const channelColor = Array.isArray(spatialChannelColor) && spatialChannelColor.length === 3 ? spatialChannelColor : getDefaultColor(theme ?? "light");
33666
33804
  const staticColor = obsColorEncoding === "spatialChannelColor" ? channelColor : layerColor;
33667
- 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);
33805
+ 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);
33668
33806
  const levelZeroNames = useMemo(() => Array.from(new Set((obsSetSelection == null ? void 0 : obsSetSelection.map((setPath) => setPath[0])) || [])), [obsSetSelection]);
33669
- const dynamicHeight = isSetColor && obsSetSelection ? levelZeroNames.length * titleHeight + (obsSetSelection == null ? void 0 : obsSetSelection.length) * (rectHeight + rectMarginY) : height2 + (!pointsVisible && contoursVisible ? 25 : 0);
33807
+ const dynamicHeight = isSetColor && obsSetSelection ? levelZeroNames.length * titleHeight + ((obsSetSelection == null ? void 0 : obsSetSelection.length) ?? 0) * (rectHeight + rectMarginY) : height2 + (!pointsVisible && contoursVisible ? 25 : 0);
33670
33808
  const availHeight = maxHeight2 !== null ? Math.max(0, maxHeight2 - 4) : Infinity;
33671
33809
  const needsScroll = Number.isFinite(availHeight) && dynamicHeight > availHeight + 1;
33672
33810
  useEffect(() => {
33673
33811
  const domElement = svgRef.current;
33812
+ if (!domElement)
33813
+ return;
33674
33814
  const foregroundColor = highContrast ? "black" : isDarkTheme ? "white" : "black";
33675
33815
  const svg = select(domElement);
33676
33816
  svg.selectAll("g").remove();
33677
33817
  svg.attr("width", width2).attr("height", dynamicHeight);
33678
33818
  const g2 = svg.append("g").attr("width", width2).attr("height", dynamicHeight);
33679
- if (!considerSelections || obsColorEncoding === "geneSelection") {
33680
- const [xMin, xMax] = combineExtents(extent2, featureAggregationStrategy) || [0, 1];
33819
+ const showInteractiveSlider2 = setFeatureValueColormapRange && ["geneSelection", "geneExpression"].includes(obsColorEncoding ?? "") && pointsVisible && featureValueColormap;
33820
+ if (!considerSelections || ["geneSelection", "geneExpression"].includes(obsColorEncoding ?? "")) {
33821
+ const combinedExtent = combineExtents(extent2 ?? null, featureAggregationStrategy ?? null) || [0, 1];
33822
+ const [xMin, xMax] = combinedExtent;
33681
33823
  if (featureValueColormap && pointsVisible) {
33682
- const xlinkHref = getXlinkHref(featureValueColormap);
33683
- g2.append("image").attr("x", 0).attr("y", titleHeight).attr("width", width2).attr("height", rectHeight).attr("preserveAspectRatio", "none").attr("href", xlinkHref);
33684
- const [rMin, rMax] = featureValueColormapRange;
33824
+ const xlinkHref2 = getXlinkHref(featureValueColormap);
33825
+ const currentRange = showInteractiveSlider2 ? localRange : featureValueColormapRange;
33826
+ const [rMin, rMax] = currentRange || [0, 1];
33827
+ if (showInteractiveSlider2) ;
33828
+ else if (setFeatureValueColormapRange) {
33829
+ g2.append("image").attr("x", rMin * width2).attr("y", titleHeight).attr("width", (rMax - rMin) * width2).attr("height", rectHeight).attr("preserveAspectRatio", "none").attr("href", xlinkHref2);
33830
+ } else {
33831
+ g2.append("image").attr("x", 0).attr("y", titleHeight).attr("width", width2).attr("height", rectHeight).attr("preserveAspectRatio", "none").attr("href", xlinkHref2);
33832
+ }
33685
33833
  const scaledDataExtent = [
33686
33834
  xMin + (xMax - xMin) * rMin,
33687
33835
  xMax - (xMax - xMin) * (1 - rMax)
33688
33836
  ];
33689
- const x = linear().domain(scaledDataExtent).range([0.5, width2 - 0.5]);
33690
- const axisTicks = g2.append("g").attr("transform", `translate(0,${titleHeight + rectHeight})`).style("font-size", "10px").call(axisBottom(x).tickValues(scaledDataExtent));
33691
- axisTicks.selectAll("line,path").style("stroke", foregroundColor);
33692
- axisTicks.selectAll("text").style("fill", foregroundColor);
33693
- axisTicks.selectAll("text").attr("text-anchor", (d, i2) => i2 === 0 ? "start" : "end");
33694
- } else if (contoursVisible) {
33837
+ let x;
33838
+ if (setFeatureValueColormapRange || showInteractiveSlider2) {
33839
+ x = linear().domain(scaledDataExtent).range([rMin * width2, rMax * width2]);
33840
+ } else {
33841
+ x = linear().domain(scaledDataExtent).range([0, width2]);
33842
+ }
33843
+ if (showInteractiveSlider2) {
33844
+ const xGlobal = linear().domain([xMin, xMax]).range([0, width2 - 4]);
33845
+ const axisTicks = g2.append("g").attr("transform", `translate(0,${titleHeight + rectHeight})`).style("font-size", "10px").call(axisBottom(xGlobal).tickValues([xMin, xMax]));
33846
+ axisTicks.selectAll("line,path").style("stroke", foregroundColor);
33847
+ axisTicks.selectAll("text").style("fill", foregroundColor);
33848
+ axisTicks.selectAll("text").attr("text-anchor", (_d, i2) => i2 === 0 ? "start" : "end");
33849
+ } else {
33850
+ const axisTicks = g2.append("g").attr("transform", `translate(0,${titleHeight + rectHeight})`).style("font-size", "10px").call(axisBottom(x).tickValues(scaledDataExtent));
33851
+ axisTicks.selectAll("line,path").style("stroke", foregroundColor);
33852
+ axisTicks.selectAll("text").style("fill", foregroundColor);
33853
+ axisTicks.selectAll("text").attr("text-anchor", (_d, i2) => i2 === 0 ? "start" : "end");
33854
+ }
33855
+ } else if (contoursVisible && contourPercentiles) {
33695
33856
  const tSize = 12;
33696
33857
  const xPercentile = linear().domain([0, 1]).range([tSize / 2, width2 - tSize / 2 - 2]);
33697
- 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));
33858
+ 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));
33698
33859
  axisTicks.selectAll("line,path").style("stroke", foregroundColor);
33699
33860
  axisTicks.selectAll("text").style("fill", foregroundColor);
33700
33861
  const NEIGHBOR_THRESHOLD = 18;
33701
- const contourPercentages = contourPercentiles.map((x) => x * 100);
33862
+ const contourPercentages = contourPercentiles.map((p) => p * 100);
33702
33863
  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) {
33703
- axisTicks.selectAll("text").attr("transform", (d, i2) => `translate(0,${i2 === 0 || i2 === contourPercentiles.length - 1 ? 0 : 10})`);
33864
+ axisTicks.selectAll("text").attr("transform", (_d, i2) => `translate(0,${i2 === 0 || i2 === contourPercentiles.length - 1 ? 0 : 10})`);
33704
33865
  }
33705
33866
  const triangleGroupG = g2.append("g").attr("transform", `translate(0,${titleHeight + rectHeight + 4})`);
33706
33867
  contourPercentiles.forEach((p, i2) => {
@@ -33710,7 +33871,7 @@ function Legend(props) {
33710
33871
  const thresholdGroupG = g2.append("g").attr("transform", `translate(0,${titleHeight + rectHeight})`);
33711
33872
  const thresholdFormatter = format(".0f");
33712
33873
  contourPercentiles.forEach((p, i2) => {
33713
- const contourThreshold = xMin + (xMax - xMin) * ((contourThresholds == null ? void 0 : contourThresholds[i2]) / 255);
33874
+ const contourThreshold = xMin + (xMax - xMin) * (((contourThresholds == null ? void 0 : contourThresholds[i2]) ?? 0) / 255);
33714
33875
  const thresholdG = thresholdGroupG.append("g").attr("transform", `translate(${xPercentile(p)},0)`).style("font-size", "7px");
33715
33876
  thresholdG.append("text").text(thresholdFormatter(contourThreshold)).style("fill", foregroundColor).attr("text-anchor", "middle");
33716
33877
  });
@@ -33734,9 +33895,9 @@ function Legend(props) {
33734
33895
  y += titleHeight;
33735
33896
  setPaths.forEach((setPath) => {
33736
33897
  var _a3;
33737
- const setColor2 = ((_a3 = obsSetColor == null ? void 0 : obsSetColor.find((d) => isEqual$1(d.path, setPath))) == null ? void 0 : _a3.color) || getDefaultColor(theme);
33898
+ const setColor2 = ((_a3 = obsSetColor == null ? void 0 : obsSetColor.find((d) => isEqual$1(d.path, setPath))) == null ? void 0 : _a3.color) || getDefaultColor(theme ?? "light");
33738
33899
  g2.append("rect").attr("x", 0).attr("y", y).attr("width", rectHeight).attr("height", rectHeight).attr("fill", `rgb(${setColor2[0]},${setColor2[1]},${setColor2[2]})`);
33739
- 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);
33900
+ 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);
33740
33901
  y += rectHeight + rectMarginY;
33741
33902
  });
33742
33903
  });
@@ -33757,17 +33918,17 @@ function Legend(props) {
33757
33918
  } else {
33758
33919
  featureSelectionLabelRawStr = featureSelectionLabelRaw == null ? void 0 : featureSelectionLabelRaw[0];
33759
33920
  }
33760
- const combinedMissing = combineMissings(missing, featureAggregationStrategy);
33921
+ const combinedMissing = combineMissings(missing ?? null, featureAggregationStrategy ?? null);
33761
33922
  const featureSelectionLabel = combinedMissing ? `${featureSelectionLabelRawStr} (${Math.round(combinedMissing * 100)}% NaN)` : featureSelectionLabelRawStr;
33762
- const obsLabel = capitalize$2(obsType);
33763
- const featureLabel = considerSelections ? featureSelectionLabel || capitalize$2(featureValueType) : capitalize$2(featureValueType);
33923
+ const obsLabel = capitalize$2(obsType ?? null);
33924
+ const featureLabel = considerSelections ? featureSelectionLabel || capitalize$2(featureValueType ?? null) : capitalize$2(featureValueType ?? null);
33764
33925
  const mainLabel = showObsLabel ? obsLabel : featureLabel;
33765
33926
  const subLabel = showObsLabel ? featureLabel : null;
33766
33927
  const hasSubLabel = subLabel !== null;
33767
33928
  if (!isSetColor) {
33768
- 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);
33929
+ 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);
33769
33930
  if (hasSubLabel) {
33770
- 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);
33931
+ 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);
33771
33932
  }
33772
33933
  }
33773
33934
  }, [
@@ -33775,6 +33936,7 @@ function Legend(props) {
33775
33936
  height2,
33776
33937
  featureValueColormap,
33777
33938
  featureValueColormapRange,
33939
+ localRange,
33778
33940
  considerSelections,
33779
33941
  obsType,
33780
33942
  obsColorEncoding,
@@ -33793,20 +33955,63 @@ function Legend(props) {
33793
33955
  contoursFilled,
33794
33956
  contoursVisible,
33795
33957
  pointsVisible,
33796
- featureAggregationStrategy
33958
+ featureAggregationStrategy,
33959
+ setFeatureValueColormapRange,
33960
+ dynamicHeight,
33961
+ highContrast,
33962
+ isStaticColor,
33963
+ missing,
33964
+ showObsLabel,
33965
+ staticColor
33797
33966
  ]);
33798
- return jsxRuntimeExports.jsx("div", { className: clsx$1(classes.legend, {
33967
+ const showInteractiveSlider = setFeatureValueColormapRange && ["geneSelection", "geneExpression"].includes(obsColorEncoding ?? "") && pointsVisible && featureValueColormap;
33968
+ const globalExtent = useMemo(() => {
33969
+ const combined = combineExtents(extent2 ?? null, featureAggregationStrategy ?? null);
33970
+ return combined || [0, 1];
33971
+ }, [extent2, featureAggregationStrategy]);
33972
+ const formatSliderValue = useCallback((value) => {
33973
+ const [xMin, xMax] = globalExtent;
33974
+ const dataValue = xMin + (xMax - xMin) * value;
33975
+ const range2 = xMax - xMin;
33976
+ if (range2 < 0.01) {
33977
+ return dataValue.toExponential(2);
33978
+ }
33979
+ if (range2 < 1) {
33980
+ return dataValue.toFixed(3);
33981
+ }
33982
+ if (range2 < 100) {
33983
+ return dataValue.toFixed(1);
33984
+ }
33985
+ return Math.round(dataValue).toString();
33986
+ }, [globalExtent]);
33987
+ const xlinkHref = featureValueColormap ? getXlinkHref(featureValueColormap) : null;
33988
+ const currentLocalRange = localRange || [0, 1];
33989
+ return jsxRuntimeExports.jsxs("div", { className: clsx$1(classes.legend, {
33799
33990
  [classes.legendRelative]: positionRelative,
33800
33991
  [classes.legendAbsolute]: !positionRelative,
33801
33992
  [classes.legendHighContrast]: highContrast,
33802
33993
  [classes.legendLowContrast]: !highContrast,
33803
33994
  [classes.legendInvisible]: !visible
33804
33995
  }), style: {
33805
- ...needsScroll ? { maxHeight: `${Math.floor(availHeight)}px`, overflowY: "auto" } : { maxHeight: void 0, overflowY: "visible" }
33806
- }, children: jsxRuntimeExports.jsx("svg", { ref: svgRef, style: {
33996
+ ...needsScroll ? { maxHeight: `${Math.floor(availHeight)}px`, overflowY: "auto" } : { maxHeight: void 0, overflowY: "visible" },
33997
+ width: `${width2}px`
33998
+ }, children: [jsxRuntimeExports.jsx("svg", { ref: svgRef, style: {
33807
33999
  width: `${width2}px`,
33808
34000
  height: `${dynamicHeight}px`
33809
- } }) });
34001
+ } }), showInteractiveSlider && xlinkHref && jsxRuntimeExports.jsxs("div", { className: classes.sliderContainer, children: [currentLocalRange[0] > 0 && jsxRuntimeExports.jsx("div", { className: classes.grayTrack, style: {
34002
+ left: 0,
34003
+ width: `${currentLocalRange[0] * 100}%`
34004
+ } }), currentLocalRange[1] < 1 && jsxRuntimeExports.jsx("div", { className: classes.grayTrack, style: {
34005
+ left: `${currentLocalRange[1] * 100}%`,
34006
+ width: `${(1 - currentLocalRange[1]) * 100}%`
34007
+ } }), jsxRuntimeExports.jsx("img", { src: xlinkHref, alt: "Colormap gradient", className: classes.colormapImage, style: {
34008
+ left: `${currentLocalRange[0] * 100}%`,
34009
+ width: `${(currentLocalRange[1] - currentLocalRange[0]) * 100}%`
34010
+ } }), 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: {
34011
+ thumb: {
34012
+ className: classes.sliderThumb
34013
+ }
34014
+ } })] })] });
33810
34015
  }
33811
34016
  makeStyles()(() => ({
33812
34017
  multiLegend: {
@@ -137829,22 +138034,22 @@ function addDecoder(cases, importFn) {
137829
138034
  }
137830
138035
  cases.forEach((c) => registry$1.set(c, importFn));
137831
138036
  }
137832
- addDecoder([void 0, 1], () => import("./raw-CYkXbM-r.js").then((m) => m.default));
137833
- addDecoder(5, () => import("./lzw-DuQQG26m.js").then((m) => m.default));
138037
+ addDecoder([void 0, 1], () => import("./raw-CVpDtOvK.js").then((m) => m.default));
138038
+ addDecoder(5, () => import("./lzw-Bk7eILPv.js").then((m) => m.default));
137834
138039
  addDecoder(6, () => {
137835
138040
  throw new Error("old style JPEG compression is not supported.");
137836
138041
  });
137837
- addDecoder(7, () => import("./jpeg-C-aR7Yvm.js").then((m) => m.default));
137838
- addDecoder([8, 32946], () => import("./deflate-D1zCiRQz.js").then((m) => m.default));
137839
- addDecoder(32773, () => import("./packbits-CKkWkWM2.js").then((m) => m.default));
138042
+ addDecoder(7, () => import("./jpeg-BwtBYp_n.js").then((m) => m.default));
138043
+ addDecoder([8, 32946], () => import("./deflate-BCK9hcdb.js").then((m) => m.default));
138044
+ addDecoder(32773, () => import("./packbits-DwgrK_HN.js").then((m) => m.default));
137840
138045
  addDecoder(
137841
138046
  34887,
137842
- () => import("./lerc-BDETtaHZ.js").then(async (m) => {
138047
+ () => import("./lerc-C6yVuiGq.js").then(async (m) => {
137843
138048
  await m.zstd.init();
137844
138049
  return m;
137845
138050
  }).then((m) => m.default)
137846
138051
  );
137847
- addDecoder(50001, () => import("./webimage-CK0o5if1.js").then((m) => m.default));
138052
+ addDecoder(50001, () => import("./webimage-DpgOps3O.js").then((m) => m.default));
137848
138053
  function decodeRowAcc(row, stride) {
137849
138054
  let length2 = row.length - stride;
137850
138055
  let offset2 = 0;
@@ -152870,6 +153075,7 @@ function HeatmapSubscriber(props) {
152870
153075
  obsSetSelection: cellSetSelection,
152871
153076
  featureValueColormap: geneExpressionColormap,
152872
153077
  featureValueColormapRange: geneExpressionColormapRange,
153078
+ setFeatureValueColormapRange: setGeneExpressionColormapRange,
152873
153079
  extent: obsFeatureMatrixExtent
152874
153080
  }
152875
153081
  )
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a, H } from "./index-e8-Lh_l9.js";
1
+ import { a, H } from "./index-CIYB_Yf2.js";
2
2
  export {
3
3
  a as Heatmap,
4
4
  H as HeatmapSubscriber
@@ -1,4 +1,4 @@
1
- import { B as BaseDecoder } from "./index-e8-Lh_l9.js";
1
+ import { B as BaseDecoder } from "./index-CIYB_Yf2.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-e8-Lh_l9.js";
2
+ import { g as getDefaultExportFromCjs, B as BaseDecoder } from "./index-CIYB_Yf2.js";
3
3
  const LercParameters = {
4
4
  AddCompression: 1
5
5
  };
@@ -1,4 +1,4 @@
1
- import { B as BaseDecoder } from "./index-e8-Lh_l9.js";
1
+ import { B as BaseDecoder } from "./index-CIYB_Yf2.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-e8-Lh_l9.js";
1
+ import { B as BaseDecoder } from "./index-CIYB_Yf2.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-e8-Lh_l9.js";
1
+ import { B as BaseDecoder } from "./index-CIYB_Yf2.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-e8-Lh_l9.js";
1
+ import { B as BaseDecoder } from "./index-CIYB_Yf2.js";
2
2
  class WebImageDecoder extends BaseDecoder {
3
3
  constructor() {
4
4
  super();
@@ -1 +1 @@
1
- {"version":3,"file":"HeatmapSubscriber.d.ts","sourceRoot":"","sources":["../src/HeatmapSubscriber.js"],"names":[],"mappings":"AA6BA;;;;;;;;;;GAUG;AACH,yCATG;IAAsB,IAAI,EAAlB,MAAM;IACQ,kBAAkB,EAAhC,MAAM;IAEU,mBAAmB;IAErB,KAAK,EAAnB,MAAM;IACS,SAAS,EAAxB,OAAO;CAEjB,eA0RA"}
1
+ {"version":3,"file":"HeatmapSubscriber.d.ts","sourceRoot":"","sources":["../src/HeatmapSubscriber.js"],"names":[],"mappings":"AA6BA;;;;;;;;;;GAUG;AACH,yCATG;IAAsB,IAAI,EAAlB,MAAM;IACQ,kBAAkB,EAAhC,MAAM;IAEU,mBAAmB;IAErB,KAAK,EAAnB,MAAM;IACS,SAAS,EAAxB,OAAO;CAEjB,eA2RA"}
@@ -121,5 +121,5 @@ export function HeatmapSubscriber(props) {
121
121
  setTargetY(target[1]);
122
122
  }, colormapRange: geneExpressionColormapRange, setColormapRange: setGeneExpressionColormapRange, height: height, width: width, theme: theme, uuid: uuid, uint8ObsFeatureMatrix: uint8ObsFeatureMatrix?.data, cellColors: cellColors, colormap: geneExpressionColormap, setIsRendering: setIsRendering, setCellHighlight: setCellHighlight, setGeneHighlight: setGeneHighlight, featureLabelsMap: featureLabelsMap, obsIndex: obsIndex, featureIndex: featureIndex, setTrackHighlight: setTrackHighlight, setComponentHover: () => {
123
123
  setComponentHover(uuid);
124
- }, updateViewInfo: setComponentViewInfo, observationsTitle: observationsTitle, variablesTitle: variablesTitle, variablesDashes: false, observationsDashes: false, cellColorLabels: cellColorLabels, useDevicePixels: true, onHeatmapClick: onHeatmapClick, setColorEncoding: setHoveredColorEncoding }), tooltipsVisible && (_jsx(HeatmapTooltipSubscriber, { parentUuid: uuid, width: width, height: height, transpose: transpose, getObsInfo: getObsInfo, getFeatureInfo: getFeatureInfo, obsHighlight: cellHighlight, featureHighlight: geneHighlight, featureType: featureType, featureLabelsMap: featureLabelsMap })), _jsx(Legend, { visible: true, theme: theme, featureType: featureType, featureValueType: featureValueType, obsColorEncoding: "geneExpression", considerSelections: false, featureSelection: geneSelection, obsSetSelection: cellSetSelection, featureValueColormap: geneExpressionColormap, featureValueColormapRange: geneExpressionColormapRange, extent: obsFeatureMatrixExtent })] }));
124
+ }, updateViewInfo: setComponentViewInfo, observationsTitle: observationsTitle, variablesTitle: variablesTitle, variablesDashes: false, observationsDashes: false, cellColorLabels: cellColorLabels, useDevicePixels: true, onHeatmapClick: onHeatmapClick, setColorEncoding: setHoveredColorEncoding }), tooltipsVisible && (_jsx(HeatmapTooltipSubscriber, { parentUuid: uuid, width: width, height: height, transpose: transpose, getObsInfo: getObsInfo, getFeatureInfo: getFeatureInfo, obsHighlight: cellHighlight, featureHighlight: geneHighlight, featureType: featureType, featureLabelsMap: featureLabelsMap })), _jsx(Legend, { visible: true, theme: theme, featureType: featureType, featureValueType: featureValueType, obsColorEncoding: "geneExpression", considerSelections: false, featureSelection: geneSelection, obsSetSelection: cellSetSelection, featureValueColormap: geneExpressionColormap, featureValueColormapRange: geneExpressionColormapRange, setFeatureValueColormapRange: setGeneExpressionColormapRange, extent: obsFeatureMatrixExtent })] }));
125
125
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitessce/heatmap",
3
- "version": "3.8.9",
3
+ "version": "3.8.10",
4
4
  "author": "HIDIVE Lab at HMS",
5
5
  "homepage": "http://vitessce.io",
6
6
  "repository": {
@@ -19,15 +19,15 @@
19
19
  "lodash-es": "^4.17.21",
20
20
  "uuid": "^9.0.0",
21
21
  "react-aria": "^3.28.0",
22
- "@vitessce/styles": "3.8.9",
23
- "@vitessce/constants-internal": "3.8.9",
24
- "@vitessce/gl": "3.8.9",
25
- "@vitessce/legend": "3.8.9",
26
- "@vitessce/sets-utils": "3.8.9",
27
- "@vitessce/tooltip": "3.8.9",
28
- "@vitessce/utils": "3.8.9",
29
- "@vitessce/vit-s": "3.8.9",
30
- "@vitessce/workers": "3.8.9"
22
+ "@vitessce/styles": "3.8.10",
23
+ "@vitessce/constants-internal": "3.8.10",
24
+ "@vitessce/gl": "3.8.10",
25
+ "@vitessce/legend": "3.8.10",
26
+ "@vitessce/sets-utils": "3.8.10",
27
+ "@vitessce/tooltip": "3.8.10",
28
+ "@vitessce/utils": "3.8.10",
29
+ "@vitessce/vit-s": "3.8.10",
30
+ "@vitessce/workers": "3.8.10"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@testing-library/jest-dom": "^6.6.3",
@@ -315,6 +315,7 @@ export function HeatmapSubscriber(props) {
315
315
  obsSetSelection={cellSetSelection}
316
316
  featureValueColormap={geneExpressionColormap}
317
317
  featureValueColormapRange={geneExpressionColormapRange}
318
+ setFeatureValueColormapRange={setGeneExpressionColormapRange}
318
319
  extent={obsFeatureMatrixExtent}
319
320
  />
320
321
  </TitleInfo>