@vitessce/heatmap 2.0.3 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{deflate.1b70f605.mjs → deflate-16770a05.js} +2 -2
- package/dist/{index.35c24bfa.mjs → index-516800f9.js} +132612 -120210
- package/dist/{index.mjs → index.js} +2 -3
- package/dist/{jpeg.1b7865ea.mjs → jpeg-aaa6dcb6.js} +1 -1
- package/dist/{lerc.ff3140f6.mjs → lerc-4c94e66f.js} +76 -5
- package/dist/{lzw.1036ab46.mjs → lzw-072df1d0.js} +1 -1
- package/dist/{packbits.088a4e84.mjs → packbits-3dcafae7.js} +1 -1
- package/dist/{pako.esm.4b234125.mjs → pako.esm-68f84e2a.js} +97 -15
- package/dist/{raw.b3ce459e.mjs → raw-282aecb8.js} +1 -1
- package/dist/{webimage.4cecc301.mjs → webimage-0d9c3bfd.js} +1 -1
- package/dist-tsc/Heatmap.d.ts +43 -0
- package/dist-tsc/Heatmap.d.ts.map +1 -0
- package/dist-tsc/Heatmap.js +698 -0
- package/dist-tsc/Heatmap.test.d.ts +2 -0
- package/dist-tsc/Heatmap.test.d.ts.map +1 -0
- package/dist-tsc/Heatmap.test.fixtures.d.ts +7 -0
- package/dist-tsc/Heatmap.test.fixtures.d.ts.map +1 -0
- package/dist-tsc/Heatmap.test.fixtures.js +20 -0
- package/dist-tsc/Heatmap.test.js +17 -0
- package/dist-tsc/HeatmapOptions.d.ts +2 -0
- package/dist-tsc/HeatmapOptions.d.ts.map +1 -0
- package/dist-tsc/HeatmapOptions.js +29 -0
- package/dist-tsc/HeatmapSubscriber.d.ts +19 -0
- package/dist-tsc/HeatmapSubscriber.d.ts.map +1 -0
- package/dist-tsc/HeatmapSubscriber.js +120 -0
- package/dist-tsc/HeatmapTooltipSubscriber.d.ts +2 -0
- package/dist-tsc/HeatmapTooltipSubscriber.d.ts.map +1 -0
- package/dist-tsc/HeatmapTooltipSubscriber.js +24 -0
- package/dist-tsc/HeatmapWorkerPool.d.ts +28 -0
- package/dist-tsc/HeatmapWorkerPool.d.ts.map +1 -0
- package/dist-tsc/HeatmapWorkerPool.js +49 -0
- package/dist-tsc/index.d.ts +3 -0
- package/dist-tsc/index.d.ts.map +1 -0
- package/dist-tsc/index.js +2 -2
- package/dist-tsc/utils.d.ts +49 -0
- package/dist-tsc/utils.d.ts.map +1 -0
- package/dist-tsc/utils.js +181 -0
- package/dist-tsc/utils.test.d.ts +2 -0
- package/dist-tsc/utils.test.d.ts.map +1 -0
- package/dist-tsc/utils.test.js +71 -0
- package/package.json +26 -14
- package/src/Heatmap.js +45 -30
- package/src/Heatmap.test.jsx +7 -6
- package/src/HeatmapOptions.js +27 -4
- package/src/HeatmapSubscriber.js +62 -26
- package/src/index.js +2 -2
- package/src/utils.js +1 -1
- package/src/utils.test.js +1 -1
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { mouseToHeatmapPosition, heatmapToMousePosition } from './utils.js';
|
|
2
|
+
describe('heatmap tooltip utils', () => {
|
|
3
|
+
it('transforms mouse coordinates to row and column indices when zoomed out', () => {
|
|
4
|
+
const mouseX = 35;
|
|
5
|
+
const mouseY = 78;
|
|
6
|
+
const [colI, rowI] = mouseToHeatmapPosition(mouseX, mouseY, {
|
|
7
|
+
offsetLeft: 10,
|
|
8
|
+
offsetTop: 10,
|
|
9
|
+
targetX: 0,
|
|
10
|
+
targetY: 0,
|
|
11
|
+
scaleFactor: 1,
|
|
12
|
+
matrixWidth: 100,
|
|
13
|
+
matrixHeight: 100,
|
|
14
|
+
numRows: 5,
|
|
15
|
+
numCols: 4,
|
|
16
|
+
});
|
|
17
|
+
expect(colI).toEqual(1);
|
|
18
|
+
expect(rowI).toEqual(3);
|
|
19
|
+
});
|
|
20
|
+
it('transforms mouse coordinates to row and column indices when zoomed in', () => {
|
|
21
|
+
const mouseX = 35;
|
|
22
|
+
const mouseY = 78;
|
|
23
|
+
const [colI, rowI] = mouseToHeatmapPosition(mouseX, mouseY, {
|
|
24
|
+
offsetLeft: 10,
|
|
25
|
+
offsetTop: 10,
|
|
26
|
+
targetX: 21,
|
|
27
|
+
targetY: -11,
|
|
28
|
+
scaleFactor: 4,
|
|
29
|
+
matrixWidth: 100,
|
|
30
|
+
matrixHeight: 100,
|
|
31
|
+
numRows: 5,
|
|
32
|
+
numCols: 4,
|
|
33
|
+
});
|
|
34
|
+
expect(colI).toEqual(2);
|
|
35
|
+
expect(rowI).toEqual(2);
|
|
36
|
+
});
|
|
37
|
+
it('transforms row and column indices when zoomed out', () => {
|
|
38
|
+
const colI = 1;
|
|
39
|
+
const rowI = 3;
|
|
40
|
+
const [mouseX, mouseY] = heatmapToMousePosition(colI, rowI, {
|
|
41
|
+
offsetLeft: 10,
|
|
42
|
+
offsetTop: 10,
|
|
43
|
+
targetX: 0,
|
|
44
|
+
targetY: 0,
|
|
45
|
+
scaleFactor: 1,
|
|
46
|
+
matrixWidth: 100,
|
|
47
|
+
matrixHeight: 100,
|
|
48
|
+
numRows: 5,
|
|
49
|
+
numCols: 4,
|
|
50
|
+
});
|
|
51
|
+
expect(mouseX).toEqual(47.5);
|
|
52
|
+
expect(mouseY).toEqual(80);
|
|
53
|
+
});
|
|
54
|
+
it('transforms row and column indices when zoomed in', () => {
|
|
55
|
+
const colI = 2;
|
|
56
|
+
const rowI = 2;
|
|
57
|
+
const [mouseX, mouseY] = heatmapToMousePosition(colI, rowI, {
|
|
58
|
+
offsetLeft: 10,
|
|
59
|
+
offsetTop: 10,
|
|
60
|
+
targetX: 21,
|
|
61
|
+
targetY: -11,
|
|
62
|
+
scaleFactor: 4,
|
|
63
|
+
matrixWidth: 100,
|
|
64
|
+
matrixHeight: 100,
|
|
65
|
+
numRows: 5,
|
|
66
|
+
numCols: 4,
|
|
67
|
+
});
|
|
68
|
+
expect(mouseX).toEqual(26);
|
|
69
|
+
expect(mouseY).toEqual(104);
|
|
70
|
+
});
|
|
71
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vitessce/heatmap",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"author": "Gehlenborg Lab",
|
|
5
5
|
"homepage": "http://vitessce.io",
|
|
6
6
|
"repository": {
|
|
@@ -8,28 +8,33 @@
|
|
|
8
8
|
"url": "git+https://github.com/vitessce/vitessce.git"
|
|
9
9
|
},
|
|
10
10
|
"license": "MIT",
|
|
11
|
-
"
|
|
11
|
+
"type": "module",
|
|
12
|
+
"main": "dist/index.js",
|
|
12
13
|
"files": [
|
|
14
|
+
"src",
|
|
13
15
|
"dist",
|
|
14
|
-
"
|
|
16
|
+
"dist-tsc"
|
|
15
17
|
],
|
|
16
18
|
"dependencies": {
|
|
17
19
|
"@material-ui/core": "~4.12.3",
|
|
18
|
-
"lodash": "^4.17.21",
|
|
20
|
+
"lodash-es": "^4.17.21",
|
|
19
21
|
"plur": "^5.1.0",
|
|
20
|
-
"uuid": "^
|
|
21
|
-
"@vitessce/constants-internal": "
|
|
22
|
-
"@vitessce/gl": "
|
|
23
|
-
"@vitessce/
|
|
24
|
-
"@vitessce/
|
|
25
|
-
"@vitessce/
|
|
26
|
-
"@vitessce/
|
|
27
|
-
"@vitessce/
|
|
22
|
+
"uuid": "^9.0.0",
|
|
23
|
+
"@vitessce/constants-internal": "3.0.0",
|
|
24
|
+
"@vitessce/gl": "3.0.0",
|
|
25
|
+
"@vitessce/legend": "3.0.0",
|
|
26
|
+
"@vitessce/sets-utils": "3.0.0",
|
|
27
|
+
"@vitessce/tooltip": "3.0.0",
|
|
28
|
+
"@vitessce/utils": "3.0.0",
|
|
29
|
+
"@vitessce/vit-s": "3.0.0",
|
|
30
|
+
"@vitessce/workers": "3.0.0"
|
|
28
31
|
},
|
|
29
32
|
"devDependencies": {
|
|
30
33
|
"react": "^18.0.0",
|
|
31
|
-
"vite": "^3.0
|
|
32
|
-
"vitest": "^0.23.4"
|
|
34
|
+
"vite": "^4.3.0",
|
|
35
|
+
"vitest": "^0.23.4",
|
|
36
|
+
"@testing-library/jest-dom": "^5.16.4",
|
|
37
|
+
"@testing-library/react": "^13.3.0"
|
|
33
38
|
},
|
|
34
39
|
"peerDependencies": {
|
|
35
40
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
|
@@ -37,5 +42,12 @@
|
|
|
37
42
|
"scripts": {
|
|
38
43
|
"bundle": "pnpm exec vite build -c ../../../scripts/vite.config.js",
|
|
39
44
|
"test": "pnpm exec vitest --run -r ../../../ --dir ."
|
|
45
|
+
},
|
|
46
|
+
"module": "dist/index.js",
|
|
47
|
+
"exports": {
|
|
48
|
+
".": {
|
|
49
|
+
"types": "./dist-tsc/index.d.ts",
|
|
50
|
+
"import": "./dist/index.js"
|
|
51
|
+
}
|
|
40
52
|
}
|
|
41
53
|
}
|
package/src/Heatmap.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import React, {
|
|
3
3
|
useRef, useState, useCallback, useMemo, useEffect, useReducer, forwardRef,
|
|
4
4
|
} from 'react';
|
|
5
|
-
import uuidv4 from 'uuid
|
|
5
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
6
6
|
import {
|
|
7
7
|
deck, luma,
|
|
8
8
|
HeatmapCompositeTextLayer,
|
|
@@ -17,9 +17,7 @@ import {
|
|
|
17
17
|
DATA_TEXTURE_SIZE,
|
|
18
18
|
PIXELATED_TEXTURE_PARAMETERS,
|
|
19
19
|
} from '@vitessce/gl';
|
|
20
|
-
import range from 'lodash
|
|
21
|
-
import clamp from 'lodash/clamp';
|
|
22
|
-
import isEqual from 'lodash/isEqual';
|
|
20
|
+
import { range, clamp, isEqual } from 'lodash-es';
|
|
23
21
|
import {
|
|
24
22
|
getLongestString,
|
|
25
23
|
DEFAULT_GL_OPTIONS,
|
|
@@ -38,8 +36,8 @@ import {
|
|
|
38
36
|
mouseToHeatmapPosition,
|
|
39
37
|
heatmapToMousePosition,
|
|
40
38
|
mouseToCellColorPosition,
|
|
41
|
-
} from './utils';
|
|
42
|
-
import HeatmapWorkerPool from './HeatmapWorkerPool';
|
|
39
|
+
} from './utils.js';
|
|
40
|
+
import HeatmapWorkerPool from './HeatmapWorkerPool.js';
|
|
43
41
|
// Only allocate the memory once for the container
|
|
44
42
|
const paddedExpressionContainer = new Uint8Array(DATA_TEXTURE_SIZE * DATA_TEXTURE_SIZE);
|
|
45
43
|
|
|
@@ -122,6 +120,9 @@ const Heatmap = forwardRef((props, deckRef) => {
|
|
|
122
120
|
useDevicePixels = 1,
|
|
123
121
|
hideObservationLabels = false,
|
|
124
122
|
hideVariableLabels = false,
|
|
123
|
+
onHeatmapClick,
|
|
124
|
+
setColorEncoding,
|
|
125
|
+
featureIndex,
|
|
125
126
|
} = props;
|
|
126
127
|
|
|
127
128
|
const viewState = {
|
|
@@ -146,6 +147,7 @@ const Heatmap = forwardRef((props, deckRef) => {
|
|
|
146
147
|
const [axisLeftLabels, setAxisLeftLabels] = useState([]);
|
|
147
148
|
const [axisTopLabels, setAxisTopLabels] = useState([]);
|
|
148
149
|
const [numCellColorTracks, setNumCellColorTracks] = useState([]);
|
|
150
|
+
const [cursorType, setCursorType] = useState('default');
|
|
149
151
|
|
|
150
152
|
|
|
151
153
|
// Since we are storing the tile data in a ref,
|
|
@@ -688,8 +690,9 @@ const Heatmap = forwardRef((props, deckRef) => {
|
|
|
688
690
|
matrixWidth, tileWidth, tileHeight, transpose]);
|
|
689
691
|
|
|
690
692
|
|
|
693
|
+
const showText = width > 0 && height > 0;
|
|
691
694
|
const layers = heatmapLayers
|
|
692
|
-
.concat(textLayers)
|
|
695
|
+
.concat(showText ? textLayers : [])
|
|
693
696
|
.concat(...cellColorsLayersList);
|
|
694
697
|
|
|
695
698
|
// Set up the onHover function.
|
|
@@ -698,6 +701,9 @@ const Heatmap = forwardRef((props, deckRef) => {
|
|
|
698
701
|
return;
|
|
699
702
|
}
|
|
700
703
|
|
|
704
|
+
let highlightedCell = null;
|
|
705
|
+
let highlightedGene = null;
|
|
706
|
+
|
|
701
707
|
const { x: mouseX, y: mouseY } = event.offsetCenter;
|
|
702
708
|
|
|
703
709
|
const [trackColI, trackI] = mouseToCellColorPosition(mouseX, mouseY, {
|
|
@@ -717,13 +723,18 @@ const Heatmap = forwardRef((props, deckRef) => {
|
|
|
717
723
|
numCols: width,
|
|
718
724
|
});
|
|
719
725
|
|
|
726
|
+
// we are hovering over a gene colored track
|
|
720
727
|
if (trackI === null || trackColI === null) {
|
|
721
728
|
setTrackHighlight(null);
|
|
722
|
-
|
|
723
|
-
|
|
729
|
+
setColorEncoding('geneSelection');
|
|
730
|
+
} else { // we are hovering over a cell colored track
|
|
731
|
+
const obsI = expression.rows.indexOf(transpose
|
|
732
|
+
? axisTopLabels[trackColI]
|
|
733
|
+
: axisLeftLabels[trackColI]);
|
|
724
734
|
const cellIndex = expression.rows[obsI];
|
|
725
|
-
|
|
726
735
|
setTrackHighlight([cellIndex, trackI, mouseX, mouseY]);
|
|
736
|
+
highlightedCell = cellIndex;
|
|
737
|
+
setColorEncoding('cellSelection');
|
|
727
738
|
}
|
|
728
739
|
|
|
729
740
|
const [colI, rowI] = mouseToHeatmapPosition(mouseX, mouseY, {
|
|
@@ -738,22 +749,6 @@ const Heatmap = forwardRef((props, deckRef) => {
|
|
|
738
749
|
numCols: width,
|
|
739
750
|
});
|
|
740
751
|
|
|
741
|
-
if (colI === null) {
|
|
742
|
-
if (transpose) {
|
|
743
|
-
setCellHighlight(null);
|
|
744
|
-
} else {
|
|
745
|
-
setGeneHighlight(null);
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
if (rowI === null) {
|
|
750
|
-
if (transpose) {
|
|
751
|
-
setGeneHighlight(null);
|
|
752
|
-
} else {
|
|
753
|
-
setCellHighlight(null);
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
|
|
757
752
|
const obsI = expression.rows.indexOf(transpose
|
|
758
753
|
? axisTopLabels[colI]
|
|
759
754
|
: axisLeftLabels[rowI]);
|
|
@@ -762,13 +757,32 @@ const Heatmap = forwardRef((props, deckRef) => {
|
|
|
762
757
|
: axisTopLabels[colI]);
|
|
763
758
|
|
|
764
759
|
const obsId = expression.rows[obsI];
|
|
765
|
-
|
|
760
|
+
|
|
761
|
+
// We need to use featureIndex here,
|
|
762
|
+
// because expression.cols may be mapped to
|
|
763
|
+
// use featureLabels (if those were available in the dataset).
|
|
764
|
+
// Highlights and selections are assumed to be in terms of
|
|
765
|
+
// obsIndex/featureIndex (as opposed to obsLabels/featureLabels).
|
|
766
|
+
const varId = featureIndex[varI];
|
|
766
767
|
|
|
767
768
|
if (setComponentHover) {
|
|
768
769
|
setComponentHover();
|
|
769
770
|
}
|
|
770
|
-
|
|
771
|
-
|
|
771
|
+
|
|
772
|
+
if (obsId) {
|
|
773
|
+
highlightedCell = obsId;
|
|
774
|
+
}
|
|
775
|
+
if (varId) {
|
|
776
|
+
highlightedGene = varId;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
setCellHighlight(highlightedCell);
|
|
780
|
+
setGeneHighlight(highlightedGene);
|
|
781
|
+
if (highlightedCell !== null || highlightedGene !== null) {
|
|
782
|
+
setCursorType('pointer');
|
|
783
|
+
} else {
|
|
784
|
+
setCursorType('default');
|
|
785
|
+
}
|
|
772
786
|
}
|
|
773
787
|
|
|
774
788
|
const cellColorsViews = useMemo(() => {
|
|
@@ -844,12 +858,13 @@ const Heatmap = forwardRef((props, deckRef) => {
|
|
|
844
858
|
]}
|
|
845
859
|
layers={layers}
|
|
846
860
|
layerFilter={layerFilter}
|
|
847
|
-
getCursor={interactionState => (interactionState.isDragging ? 'grabbing' :
|
|
861
|
+
getCursor={interactionState => (interactionState.isDragging ? 'grabbing' : cursorType)}
|
|
848
862
|
glOptions={DEFAULT_GL_OPTIONS}
|
|
849
863
|
onViewStateChange={onViewStateChange}
|
|
850
864
|
viewState={viewState}
|
|
851
865
|
onHover={onHover}
|
|
852
866
|
useDevicePixels={useDevicePixels}
|
|
867
|
+
onClick={onHeatmapClick}
|
|
853
868
|
/>
|
|
854
869
|
);
|
|
855
870
|
});
|
package/src/Heatmap.test.jsx
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
/* eslint-disable func-names */
|
|
2
2
|
import '@testing-library/jest-dom';
|
|
3
|
-
import { cleanup, render
|
|
4
|
-
import { afterEach } from 'vitest'
|
|
3
|
+
import { cleanup, render } from '@testing-library/react';
|
|
4
|
+
import { afterEach } from 'vitest';
|
|
5
|
+
import React from 'react';
|
|
5
6
|
|
|
6
|
-
import Heatmap from './Heatmap';
|
|
7
|
-
import { expressionMatrix, cellColors } from './Heatmap.test.fixtures';
|
|
7
|
+
import Heatmap from './Heatmap.js';
|
|
8
|
+
import { expressionMatrix, cellColors } from './Heatmap.test.fixtures.js';
|
|
8
9
|
|
|
9
10
|
afterEach(() => {
|
|
10
|
-
cleanup()
|
|
11
|
+
cleanup();
|
|
11
12
|
});
|
|
12
13
|
|
|
13
14
|
describe('<Heatmap/>', () => {
|
|
14
|
-
it('renders a DeckGL element',
|
|
15
|
+
it('renders a DeckGL element', () => {
|
|
15
16
|
const { container } = render(
|
|
16
17
|
<Heatmap
|
|
17
18
|
uuid="heatmap-0"
|
package/src/HeatmapOptions.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import React, { useCallback } from 'react';
|
|
2
|
-
import debounce from 'lodash
|
|
3
|
-
import Slider from '@material-ui/core
|
|
4
|
-
import TableCell from '@material-ui/core/TableCell';
|
|
5
|
-
import TableRow from '@material-ui/core/TableRow';
|
|
2
|
+
import { debounce } from 'lodash-es';
|
|
3
|
+
import { Checkbox, Slider, TableCell, TableRow } from '@material-ui/core';
|
|
6
4
|
import { usePlotOptionsStyles, OptionsContainer, OptionSelect } from '@vitessce/vit-s';
|
|
7
5
|
import { GLSL_COLORMAPS } from '@vitessce/gl';
|
|
8
6
|
|
|
@@ -12,6 +10,8 @@ export default function HeatmapOptions(props) {
|
|
|
12
10
|
setGeneExpressionColormap,
|
|
13
11
|
geneExpressionColormapRange,
|
|
14
12
|
setGeneExpressionColormapRange,
|
|
13
|
+
tooltipsVisible,
|
|
14
|
+
setTooltipsVisible,
|
|
15
15
|
} = props;
|
|
16
16
|
|
|
17
17
|
const classes = usePlotOptionsStyles();
|
|
@@ -20,6 +20,10 @@ export default function HeatmapOptions(props) {
|
|
|
20
20
|
setGeneExpressionColormap(event.target.value);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
function handleTooltipsVisibilityChange(event) {
|
|
24
|
+
setTooltipsVisible(event.target.checked);
|
|
25
|
+
}
|
|
26
|
+
|
|
23
27
|
function handleColormapRangeChange(event, value) {
|
|
24
28
|
setGeneExpressionColormapRange(value);
|
|
25
29
|
}
|
|
@@ -49,6 +53,25 @@ export default function HeatmapOptions(props) {
|
|
|
49
53
|
</OptionSelect>
|
|
50
54
|
</TableCell>
|
|
51
55
|
</TableRow>
|
|
56
|
+
<TableRow>
|
|
57
|
+
<TableCell className={classes.labelCell}>
|
|
58
|
+
Tooltips Visible
|
|
59
|
+
</TableCell>
|
|
60
|
+
<TableCell className={classes.inputCell}>
|
|
61
|
+
<Checkbox
|
|
62
|
+
className={classes.checkbox}
|
|
63
|
+
/**
|
|
64
|
+
* We have to use "checked" here, not "value".
|
|
65
|
+
* The checkbox state is not persisting with value.
|
|
66
|
+
* For reference, https://v4.mui.com/api/checkbox/
|
|
67
|
+
*/
|
|
68
|
+
checked={tooltipsVisible}
|
|
69
|
+
onChange={handleTooltipsVisibilityChange}
|
|
70
|
+
name="gene-expression-colormap-option-toltip-visibility"
|
|
71
|
+
color="default"
|
|
72
|
+
/>
|
|
73
|
+
</TableCell>
|
|
74
|
+
</TableRow>
|
|
52
75
|
<TableRow>
|
|
53
76
|
<TableCell className={classes.labelCell}>
|
|
54
77
|
Gene Expression Colormap Range
|
package/src/HeatmapSubscriber.js
CHANGED
|
@@ -5,23 +5,25 @@ import plur from 'plur';
|
|
|
5
5
|
import {
|
|
6
6
|
TitleInfo,
|
|
7
7
|
useDeckCanvasSize,
|
|
8
|
+
useGetObsMembership,
|
|
8
9
|
useGetObsInfo,
|
|
9
10
|
useReady,
|
|
10
11
|
useUrls,
|
|
11
12
|
useObsSetsData,
|
|
12
13
|
useObsFeatureMatrixData,
|
|
14
|
+
useUint8ObsFeatureMatrix,
|
|
13
15
|
useMultiObsLabels,
|
|
14
16
|
useFeatureLabelsData,
|
|
15
17
|
useCoordination, useLoaders,
|
|
16
18
|
useSetComponentHover, useSetComponentViewInfo,
|
|
17
|
-
registerPluginViewType,
|
|
18
19
|
} from '@vitessce/vit-s';
|
|
19
20
|
import { capitalize, commaNumber, getCellColors } from '@vitessce/utils';
|
|
20
|
-
import { mergeObsSets } from '@vitessce/sets-utils';
|
|
21
|
+
import { mergeObsSets, findLongestCommonPath } from '@vitessce/sets-utils';
|
|
21
22
|
import { COMPONENT_COORDINATION_TYPES, ViewType } from '@vitessce/constants-internal';
|
|
22
|
-
import
|
|
23
|
-
import
|
|
24
|
-
import
|
|
23
|
+
import { Legend } from '@vitessce/legend';
|
|
24
|
+
import Heatmap from './Heatmap.js';
|
|
25
|
+
import HeatmapTooltipSubscriber from './HeatmapTooltipSubscriber.js';
|
|
26
|
+
import HeatmapOptions from './HeatmapOptions.js';
|
|
25
27
|
|
|
26
28
|
|
|
27
29
|
/**
|
|
@@ -34,8 +36,6 @@ import HeatmapOptions from './HeatmapOptions';
|
|
|
34
36
|
* @param {string} props.title The component title.
|
|
35
37
|
* @param {boolean} props.transpose Whether to
|
|
36
38
|
* render as cell-by-gene or gene-by-cell.
|
|
37
|
-
* @param {boolean} props.disableTooltip Whether to disable the
|
|
38
|
-
* tooltip on mouse hover.
|
|
39
39
|
*/
|
|
40
40
|
export function HeatmapSubscriber(props) {
|
|
41
41
|
const {
|
|
@@ -46,7 +46,6 @@ export function HeatmapSubscriber(props) {
|
|
|
46
46
|
transpose,
|
|
47
47
|
observationsLabelOverride,
|
|
48
48
|
variablesLabelOverride,
|
|
49
|
-
disableTooltip = false,
|
|
50
49
|
title = 'Heatmap',
|
|
51
50
|
} = props;
|
|
52
51
|
|
|
@@ -71,17 +70,21 @@ export function HeatmapSubscriber(props) {
|
|
|
71
70
|
additionalObsSets: additionalCellSets,
|
|
72
71
|
featureValueColormap: geneExpressionColormap,
|
|
73
72
|
featureValueColormapRange: geneExpressionColormapRange,
|
|
73
|
+
tooltipsVisible,
|
|
74
74
|
}, {
|
|
75
75
|
setHeatmapZoomX: setZoomX,
|
|
76
76
|
setHeatmapZoomY: setZoomY,
|
|
77
77
|
setHeatmapTargetX: setTargetX,
|
|
78
78
|
setHeatmapTargetY: setTargetY,
|
|
79
79
|
setObsHighlight: setCellHighlight,
|
|
80
|
+
setFeatureSelection: setGeneSelection,
|
|
81
|
+
setObsColorEncoding: setCellColorEncoding,
|
|
80
82
|
setFeatureHighlight: setGeneHighlight,
|
|
81
83
|
setObsSetSelection: setCellSetSelection,
|
|
82
84
|
setObsSetColor: setCellSetColor,
|
|
83
85
|
setFeatureValueColormapRange: setGeneExpressionColormapRange,
|
|
84
86
|
setFeatureValueColormap: setGeneExpressionColormap,
|
|
87
|
+
setTooltipsVisible,
|
|
85
88
|
}] = useCoordination(COMPONENT_COORDINATION_TYPES[ViewType.HEATMAP], coordinationScopes);
|
|
86
89
|
|
|
87
90
|
const observationsLabel = observationsLabelOverride || obsType;
|
|
@@ -93,6 +96,10 @@ export function HeatmapSubscriber(props) {
|
|
|
93
96
|
const variablesTitle = capitalize(variablesPluralLabel);
|
|
94
97
|
|
|
95
98
|
const [isRendering, setIsRendering] = useState(false);
|
|
99
|
+
// We need to know whether the user is currently hovering over the expression part
|
|
100
|
+
// of the heatmap vs. the color bar part, which will affect whether we call
|
|
101
|
+
// setObsColorEncoding with 'geneSelection' or 'cellSetSelection' upon a click.
|
|
102
|
+
const [hoveredColorEncoding, setHoveredColorEncoding] = useState('geneSelection');
|
|
96
103
|
|
|
97
104
|
const [urls, addUrl] = useUrls(loaders, dataset);
|
|
98
105
|
const [width, height, deckRef] = useDeckCanvasSize();
|
|
@@ -122,46 +129,50 @@ export function HeatmapSubscriber(props) {
|
|
|
122
129
|
obsSetsStatus,
|
|
123
130
|
]);
|
|
124
131
|
|
|
132
|
+
const [uint8ObsFeatureMatrix, obsFeatureMatrixExtent] = useUint8ObsFeatureMatrix(
|
|
133
|
+
{ obsFeatureMatrix },
|
|
134
|
+
);
|
|
135
|
+
|
|
125
136
|
const mergedCellSets = useMemo(() => mergeObsSets(
|
|
126
137
|
cellSets, additionalCellSets,
|
|
127
138
|
), [cellSets, additionalCellSets]);
|
|
128
139
|
|
|
129
140
|
const cellColors = useMemo(() => getCellColors({
|
|
130
|
-
// Only show cell set selection on heatmap labels.
|
|
131
|
-
cellColorEncoding: 'cellSetSelection',
|
|
132
|
-
geneSelection,
|
|
133
141
|
cellSets: mergedCellSets,
|
|
134
142
|
cellSetSelection,
|
|
135
143
|
cellSetColor,
|
|
136
144
|
obsIndex,
|
|
137
145
|
theme,
|
|
138
|
-
}), [mergedCellSets,
|
|
146
|
+
}), [mergedCellSets, theme,
|
|
139
147
|
cellSetColor, cellSetSelection, obsIndex]);
|
|
140
148
|
|
|
141
149
|
const getObsInfo = useGetObsInfo(
|
|
142
150
|
observationsLabel, obsLabelsTypes, obsLabelsData, obsSetsMembership,
|
|
143
151
|
);
|
|
144
152
|
|
|
153
|
+
const getObsMembership = useGetObsMembership(obsSetsMembership);
|
|
154
|
+
|
|
145
155
|
const getFeatureInfo = useCallback((featureId) => {
|
|
146
156
|
if (featureId) {
|
|
147
|
-
|
|
157
|
+
const featureLabel = featureLabelsMap?.get(featureId) || featureId;
|
|
158
|
+
return { [`${capitalize(variablesLabel)} ID`]: featureLabel };
|
|
148
159
|
}
|
|
149
160
|
return null;
|
|
150
|
-
}, [variablesLabel]);
|
|
161
|
+
}, [variablesLabel, featureLabelsMap]);
|
|
151
162
|
|
|
152
163
|
const expressionMatrix = useMemo(() => {
|
|
153
|
-
if (obsIndex && featureIndex &&
|
|
164
|
+
if (obsIndex && featureIndex && uint8ObsFeatureMatrix) {
|
|
154
165
|
return {
|
|
155
166
|
rows: obsIndex,
|
|
156
167
|
cols: (featureLabelsMap
|
|
157
168
|
? featureIndex.map(key => featureLabelsMap.get(key) || key)
|
|
158
169
|
: featureIndex
|
|
159
170
|
),
|
|
160
|
-
matrix:
|
|
171
|
+
matrix: uint8ObsFeatureMatrix.data,
|
|
161
172
|
};
|
|
162
173
|
}
|
|
163
174
|
return null;
|
|
164
|
-
}, [obsIndex, featureIndex,
|
|
175
|
+
}, [obsIndex, featureIndex, uint8ObsFeatureMatrix, featureLabelsMap]);
|
|
165
176
|
|
|
166
177
|
const cellsCount = obsIndex ? obsIndex.length : 0;
|
|
167
178
|
const genesCount = featureIndex ? featureIndex.length : 0;
|
|
@@ -171,6 +182,22 @@ export function HeatmapSubscriber(props) {
|
|
|
171
182
|
// logs in the console on every hover event.
|
|
172
183
|
}, []);
|
|
173
184
|
|
|
185
|
+
const onHeatmapClick = () => {
|
|
186
|
+
if (hoveredColorEncoding === 'geneSelection' && geneHighlight) {
|
|
187
|
+
setGeneSelection([geneHighlight]);
|
|
188
|
+
setCellColorEncoding('geneSelection');
|
|
189
|
+
} else if (hoveredColorEncoding === 'cellSelection' && cellSetSelection) {
|
|
190
|
+
const selectionFullPath = getObsMembership(cellHighlight);
|
|
191
|
+
if (selectionFullPath?.length > 0) {
|
|
192
|
+
const selectionToHighlight = findLongestCommonPath(selectionFullPath, cellSetSelection);
|
|
193
|
+
if (selectionToHighlight) {
|
|
194
|
+
setCellSetSelection([selectionToHighlight]);
|
|
195
|
+
setCellColorEncoding('cellSelection');
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
|
|
174
201
|
const cellColorLabels = useMemo(() => ([
|
|
175
202
|
`${capitalize(observationsLabel)} Set`,
|
|
176
203
|
]), [observationsLabel]);
|
|
@@ -191,6 +218,8 @@ export function HeatmapSubscriber(props) {
|
|
|
191
218
|
setGeneExpressionColormap={setGeneExpressionColormap}
|
|
192
219
|
geneExpressionColormapRange={geneExpressionColormapRange}
|
|
193
220
|
setGeneExpressionColormapRange={setGeneExpressionColormapRange}
|
|
221
|
+
tooltipsVisible={tooltipsVisible}
|
|
222
|
+
setTooltipsVisible={setTooltipsVisible}
|
|
194
223
|
/>
|
|
195
224
|
)}
|
|
196
225
|
>
|
|
@@ -216,6 +245,7 @@ export function HeatmapSubscriber(props) {
|
|
|
216
245
|
setIsRendering={setIsRendering}
|
|
217
246
|
setCellHighlight={setCellHighlight}
|
|
218
247
|
setGeneHighlight={setGeneHighlight}
|
|
248
|
+
featureIndex={featureIndex}
|
|
219
249
|
setTrackHighlight={setTrackHighlight}
|
|
220
250
|
setComponentHover={() => {
|
|
221
251
|
setComponentHover(uuid);
|
|
@@ -227,8 +257,10 @@ export function HeatmapSubscriber(props) {
|
|
|
227
257
|
observationsDashes={false}
|
|
228
258
|
cellColorLabels={cellColorLabels}
|
|
229
259
|
useDevicePixels
|
|
260
|
+
onHeatmapClick={onHeatmapClick}
|
|
261
|
+
setColorEncoding={setHoveredColorEncoding}
|
|
230
262
|
/>
|
|
231
|
-
{
|
|
263
|
+
{tooltipsVisible && (
|
|
232
264
|
<HeatmapTooltipSubscriber
|
|
233
265
|
parentUuid={uuid}
|
|
234
266
|
width={width}
|
|
@@ -240,14 +272,18 @@ export function HeatmapSubscriber(props) {
|
|
|
240
272
|
featureHighlight={geneHighlight}
|
|
241
273
|
/>
|
|
242
274
|
)}
|
|
275
|
+
<Legend
|
|
276
|
+
visible
|
|
277
|
+
theme={theme}
|
|
278
|
+
featureType={featureType}
|
|
279
|
+
featureValueType={featureValueType}
|
|
280
|
+
obsColorEncoding="geneExpression"
|
|
281
|
+
considerSelections={false}
|
|
282
|
+
featureSelection={geneSelection}
|
|
283
|
+
featureValueColormap={geneExpressionColormap}
|
|
284
|
+
featureValueColormapRange={geneExpressionColormapRange}
|
|
285
|
+
extent={obsFeatureMatrixExtent}
|
|
286
|
+
/>
|
|
243
287
|
</TitleInfo>
|
|
244
288
|
);
|
|
245
289
|
}
|
|
246
|
-
|
|
247
|
-
export function register() {
|
|
248
|
-
registerPluginViewType(
|
|
249
|
-
ViewType.HEATMAP,
|
|
250
|
-
HeatmapSubscriber,
|
|
251
|
-
COMPONENT_COORDINATION_TYPES[ViewType.HEATMAP],
|
|
252
|
-
);
|
|
253
|
-
}
|
package/src/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { HeatmapSubscriber
|
|
2
|
-
export { default as Heatmap } from './Heatmap';
|
|
1
|
+
export { HeatmapSubscriber } from './HeatmapSubscriber.js';
|
|
2
|
+
export { default as Heatmap } from './Heatmap.js';
|
package/src/utils.js
CHANGED
package/src/utils.test.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mouseToHeatmapPosition, heatmapToMousePosition } from './utils';
|
|
1
|
+
import { mouseToHeatmapPosition, heatmapToMousePosition } from './utils.js';
|
|
2
2
|
|
|
3
3
|
describe('heatmap tooltip utils', () => {
|
|
4
4
|
it('transforms mouse coordinates to row and column indices when zoomed out', () => {
|