@vitessce/heatmap 2.0.2 → 2.0.3
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 +13 -0
- package/dist/index.35c24bfa.mjs +132140 -0
- package/dist/index.mjs +9 -0
- package/dist/jpeg.1b7865ea.mjs +840 -0
- package/dist/lerc.ff3140f6.mjs +1943 -0
- package/dist/lzw.1036ab46.mjs +128 -0
- package/dist/packbits.088a4e84.mjs +30 -0
- package/dist/pako.esm.4b234125.mjs +3940 -0
- package/dist/raw.b3ce459e.mjs +12 -0
- package/dist/webimage.4cecc301.mjs +32 -0
- package/{dist → dist-tsc}/index.js +0 -0
- package/package.json +12 -12
- package/src/Heatmap.js +857 -0
- package/src/Heatmap.test.fixtures.js +21 -0
- package/src/Heatmap.test.jsx +31 -0
- package/src/HeatmapOptions.js +71 -0
- package/src/HeatmapSubscriber.js +253 -0
- package/src/HeatmapTooltipSubscriber.js +51 -0
- package/src/HeatmapWorkerPool.js +52 -0
- package/src/heatmap-indexing.pdf +0 -0
- package/src/heatmap-indexing.tex +65 -0
- package/src/index.js +2 -0
- package/src/utils.js +247 -0
- package/src/utils.test.js +75 -0
- package/dist/Heatmap.js +0 -689
- package/dist/Heatmap.test.fixtures.js +0 -20
- package/dist/Heatmap.test.js +0 -16
- package/dist/HeatmapOptions.js +0 -22
- package/dist/HeatmapSubscriber.js +0 -104
- package/dist/HeatmapTooltipSubscriber.js +0 -24
- package/dist/HeatmapWorkerPool.js +0 -49
- package/dist/utils.js +0 -181
- package/dist/utils.test.js +0 -71
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const expressionMatrix = {
|
|
2
|
+
rows: ['cell-0', 'cell-1', 'cell-2', 'cell-3', 'cell-4'],
|
|
3
|
+
cols: ['gene-0', 'gene-1', 'gene-2', 'gene-3'],
|
|
4
|
+
// "An image with an 'F' on it has a clear direction so it's easy to tell
|
|
5
|
+
// if it's turned or flipped etc when we use it as a texture."
|
|
6
|
+
// - https://webglfundamentals.org/webgl/lessons/webgl-3d-textures.html
|
|
7
|
+
matrix: Uint8Array.from([
|
|
8
|
+
0, 255, 255, 0,
|
|
9
|
+
0, 255, 0, 0,
|
|
10
|
+
0, 255, 255, 0,
|
|
11
|
+
0, 255, 0, 0,
|
|
12
|
+
0, 255, 0, 0,
|
|
13
|
+
]),
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const cellColors = new Map([
|
|
17
|
+
['cell-1', [0, 0, 255]],
|
|
18
|
+
['cell-0', [0, 0, 255]],
|
|
19
|
+
['cell-3', [255, 0, 0]],
|
|
20
|
+
['cell-2', [255, 0, 0]],
|
|
21
|
+
]);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/* eslint-disable func-names */
|
|
2
|
+
import '@testing-library/jest-dom';
|
|
3
|
+
import { cleanup, render, screen } from '@testing-library/react'
|
|
4
|
+
import { afterEach } from 'vitest'
|
|
5
|
+
|
|
6
|
+
import Heatmap from './Heatmap';
|
|
7
|
+
import { expressionMatrix, cellColors } from './Heatmap.test.fixtures';
|
|
8
|
+
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
cleanup()
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
describe('<Heatmap/>', () => {
|
|
14
|
+
it('renders a DeckGL element', function () {
|
|
15
|
+
const { container } = render(
|
|
16
|
+
<Heatmap
|
|
17
|
+
uuid="heatmap-0"
|
|
18
|
+
theme="dark"
|
|
19
|
+
width={100}
|
|
20
|
+
height={100}
|
|
21
|
+
colormap="plasma"
|
|
22
|
+
colormapRange={[0.0, 1.0]}
|
|
23
|
+
expressionMatrix={expressionMatrix}
|
|
24
|
+
cellColors={cellColors}
|
|
25
|
+
transpose
|
|
26
|
+
viewState={{ zoom: 0, target: [0, 0] }}
|
|
27
|
+
/>,
|
|
28
|
+
);
|
|
29
|
+
expect(container.querySelectorAll('#deckgl-overlay-heatmap-0-wrapper').length).toEqual(1);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import React, { useCallback } from 'react';
|
|
2
|
+
import debounce from 'lodash/debounce';
|
|
3
|
+
import Slider from '@material-ui/core/Slider';
|
|
4
|
+
import TableCell from '@material-ui/core/TableCell';
|
|
5
|
+
import TableRow from '@material-ui/core/TableRow';
|
|
6
|
+
import { usePlotOptionsStyles, OptionsContainer, OptionSelect } from '@vitessce/vit-s';
|
|
7
|
+
import { GLSL_COLORMAPS } from '@vitessce/gl';
|
|
8
|
+
|
|
9
|
+
export default function HeatmapOptions(props) {
|
|
10
|
+
const {
|
|
11
|
+
geneExpressionColormap,
|
|
12
|
+
setGeneExpressionColormap,
|
|
13
|
+
geneExpressionColormapRange,
|
|
14
|
+
setGeneExpressionColormapRange,
|
|
15
|
+
} = props;
|
|
16
|
+
|
|
17
|
+
const classes = usePlotOptionsStyles();
|
|
18
|
+
|
|
19
|
+
function handleGeneExpressionColormapChange(event) {
|
|
20
|
+
setGeneExpressionColormap(event.target.value);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function handleColormapRangeChange(event, value) {
|
|
24
|
+
setGeneExpressionColormapRange(value);
|
|
25
|
+
}
|
|
26
|
+
const handleColormapRangeChangeDebounced = useCallback(
|
|
27
|
+
debounce(handleColormapRangeChange, 5, { trailing: true }),
|
|
28
|
+
[handleColormapRangeChange],
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<OptionsContainer>
|
|
33
|
+
<TableRow>
|
|
34
|
+
<TableCell className={classes.labelCell} htmlFor="gene-expression-colormap-select">
|
|
35
|
+
Gene Expression Colormap
|
|
36
|
+
</TableCell>
|
|
37
|
+
<TableCell className={classes.inputCell}>
|
|
38
|
+
<OptionSelect
|
|
39
|
+
className={classes.select}
|
|
40
|
+
value={geneExpressionColormap}
|
|
41
|
+
onChange={handleGeneExpressionColormapChange}
|
|
42
|
+
inputProps={{
|
|
43
|
+
id: 'gene-expression-colormap-select',
|
|
44
|
+
}}
|
|
45
|
+
>
|
|
46
|
+
{GLSL_COLORMAPS.map(cmap => (
|
|
47
|
+
<option key={cmap} value={cmap}>{cmap}</option>
|
|
48
|
+
))}
|
|
49
|
+
</OptionSelect>
|
|
50
|
+
</TableCell>
|
|
51
|
+
</TableRow>
|
|
52
|
+
<TableRow>
|
|
53
|
+
<TableCell className={classes.labelCell}>
|
|
54
|
+
Gene Expression Colormap Range
|
|
55
|
+
</TableCell>
|
|
56
|
+
<TableCell className={classes.inputCell}>
|
|
57
|
+
<Slider
|
|
58
|
+
classes={{ root: classes.slider, valueLabel: classes.sliderValueLabel }}
|
|
59
|
+
value={geneExpressionColormapRange}
|
|
60
|
+
onChange={handleColormapRangeChangeDebounced}
|
|
61
|
+
aria-labelledby="gene-expression-colormap-range-slider"
|
|
62
|
+
valueLabelDisplay="auto"
|
|
63
|
+
step={0.005}
|
|
64
|
+
min={0.0}
|
|
65
|
+
max={1.0}
|
|
66
|
+
/>
|
|
67
|
+
</TableCell>
|
|
68
|
+
</TableRow>
|
|
69
|
+
</OptionsContainer>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
useState, useCallback, useMemo,
|
|
3
|
+
} from 'react';
|
|
4
|
+
import plur from 'plur';
|
|
5
|
+
import {
|
|
6
|
+
TitleInfo,
|
|
7
|
+
useDeckCanvasSize,
|
|
8
|
+
useGetObsInfo,
|
|
9
|
+
useReady,
|
|
10
|
+
useUrls,
|
|
11
|
+
useObsSetsData,
|
|
12
|
+
useObsFeatureMatrixData,
|
|
13
|
+
useMultiObsLabels,
|
|
14
|
+
useFeatureLabelsData,
|
|
15
|
+
useCoordination, useLoaders,
|
|
16
|
+
useSetComponentHover, useSetComponentViewInfo,
|
|
17
|
+
registerPluginViewType,
|
|
18
|
+
} from '@vitessce/vit-s';
|
|
19
|
+
import { capitalize, commaNumber, getCellColors } from '@vitessce/utils';
|
|
20
|
+
import { mergeObsSets } from '@vitessce/sets-utils';
|
|
21
|
+
import { COMPONENT_COORDINATION_TYPES, ViewType } from '@vitessce/constants-internal';
|
|
22
|
+
import Heatmap from './Heatmap';
|
|
23
|
+
import HeatmapTooltipSubscriber from './HeatmapTooltipSubscriber';
|
|
24
|
+
import HeatmapOptions from './HeatmapOptions';
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {object} props
|
|
29
|
+
* @param {number} props.uuid The unique identifier for this component.
|
|
30
|
+
* @param {object} props.coordinationScopes The mapping from coordination types to coordination
|
|
31
|
+
* scopes.
|
|
32
|
+
* @param {function} props.removeGridComponent The callback function to pass to TitleInfo,
|
|
33
|
+
* to call when the component has been removed from the grid.
|
|
34
|
+
* @param {string} props.title The component title.
|
|
35
|
+
* @param {boolean} props.transpose Whether to
|
|
36
|
+
* render as cell-by-gene or gene-by-cell.
|
|
37
|
+
* @param {boolean} props.disableTooltip Whether to disable the
|
|
38
|
+
* tooltip on mouse hover.
|
|
39
|
+
*/
|
|
40
|
+
export function HeatmapSubscriber(props) {
|
|
41
|
+
const {
|
|
42
|
+
uuid,
|
|
43
|
+
coordinationScopes,
|
|
44
|
+
removeGridComponent,
|
|
45
|
+
theme,
|
|
46
|
+
transpose,
|
|
47
|
+
observationsLabelOverride,
|
|
48
|
+
variablesLabelOverride,
|
|
49
|
+
disableTooltip = false,
|
|
50
|
+
title = 'Heatmap',
|
|
51
|
+
} = props;
|
|
52
|
+
|
|
53
|
+
const loaders = useLoaders();
|
|
54
|
+
const setComponentHover = useSetComponentHover();
|
|
55
|
+
const setComponentViewInfo = useSetComponentViewInfo(uuid);
|
|
56
|
+
|
|
57
|
+
// Get "props" from the coordination space.
|
|
58
|
+
const [{
|
|
59
|
+
dataset,
|
|
60
|
+
obsType,
|
|
61
|
+
featureType,
|
|
62
|
+
featureValueType,
|
|
63
|
+
heatmapZoomX: zoomX,
|
|
64
|
+
heatmapTargetX: targetX,
|
|
65
|
+
heatmapTargetY: targetY,
|
|
66
|
+
featureSelection: geneSelection,
|
|
67
|
+
obsHighlight: cellHighlight,
|
|
68
|
+
featureHighlight: geneHighlight,
|
|
69
|
+
obsSetSelection: cellSetSelection,
|
|
70
|
+
obsSetColor: cellSetColor,
|
|
71
|
+
additionalObsSets: additionalCellSets,
|
|
72
|
+
featureValueColormap: geneExpressionColormap,
|
|
73
|
+
featureValueColormapRange: geneExpressionColormapRange,
|
|
74
|
+
}, {
|
|
75
|
+
setHeatmapZoomX: setZoomX,
|
|
76
|
+
setHeatmapZoomY: setZoomY,
|
|
77
|
+
setHeatmapTargetX: setTargetX,
|
|
78
|
+
setHeatmapTargetY: setTargetY,
|
|
79
|
+
setObsHighlight: setCellHighlight,
|
|
80
|
+
setFeatureHighlight: setGeneHighlight,
|
|
81
|
+
setObsSetSelection: setCellSetSelection,
|
|
82
|
+
setObsSetColor: setCellSetColor,
|
|
83
|
+
setFeatureValueColormapRange: setGeneExpressionColormapRange,
|
|
84
|
+
setFeatureValueColormap: setGeneExpressionColormap,
|
|
85
|
+
}] = useCoordination(COMPONENT_COORDINATION_TYPES[ViewType.HEATMAP], coordinationScopes);
|
|
86
|
+
|
|
87
|
+
const observationsLabel = observationsLabelOverride || obsType;
|
|
88
|
+
const observationsPluralLabel = plur(observationsLabel);
|
|
89
|
+
const variablesLabel = variablesLabelOverride || featureType;
|
|
90
|
+
const variablesPluralLabel = plur(variablesLabel);
|
|
91
|
+
|
|
92
|
+
const observationsTitle = capitalize(observationsPluralLabel);
|
|
93
|
+
const variablesTitle = capitalize(variablesPluralLabel);
|
|
94
|
+
|
|
95
|
+
const [isRendering, setIsRendering] = useState(false);
|
|
96
|
+
|
|
97
|
+
const [urls, addUrl] = useUrls(loaders, dataset);
|
|
98
|
+
const [width, height, deckRef] = useDeckCanvasSize();
|
|
99
|
+
|
|
100
|
+
// Get data from loaders using the data hooks.
|
|
101
|
+
const [obsLabelsTypes, obsLabelsData] = useMultiObsLabels(
|
|
102
|
+
coordinationScopes, obsType, loaders, dataset, addUrl,
|
|
103
|
+
);
|
|
104
|
+
// TODO: support multiple feature labels using featureLabelsType coordination values.
|
|
105
|
+
const [{ featureLabelsMap }, featureLabelsStatus] = useFeatureLabelsData(
|
|
106
|
+
loaders, dataset, addUrl, false, {}, {},
|
|
107
|
+
{ featureType },
|
|
108
|
+
);
|
|
109
|
+
const [{ obsIndex, featureIndex, obsFeatureMatrix }, matrixStatus] = useObsFeatureMatrixData(
|
|
110
|
+
loaders, dataset, addUrl, true, {}, {},
|
|
111
|
+
{ obsType, featureType, featureValueType },
|
|
112
|
+
);
|
|
113
|
+
const [{ obsSets: cellSets, obsSetsMembership }, obsSetsStatus] = useObsSetsData(
|
|
114
|
+
loaders, dataset, addUrl, false,
|
|
115
|
+
{ setObsSetSelection: setCellSetSelection, setObsSetColor: setCellSetColor },
|
|
116
|
+
{ obsSetSelection: cellSetSelection, obsSetColor: cellSetColor },
|
|
117
|
+
{ obsType },
|
|
118
|
+
);
|
|
119
|
+
const isReady = useReady([
|
|
120
|
+
featureLabelsStatus,
|
|
121
|
+
matrixStatus,
|
|
122
|
+
obsSetsStatus,
|
|
123
|
+
]);
|
|
124
|
+
|
|
125
|
+
const mergedCellSets = useMemo(() => mergeObsSets(
|
|
126
|
+
cellSets, additionalCellSets,
|
|
127
|
+
), [cellSets, additionalCellSets]);
|
|
128
|
+
|
|
129
|
+
const cellColors = useMemo(() => getCellColors({
|
|
130
|
+
// Only show cell set selection on heatmap labels.
|
|
131
|
+
cellColorEncoding: 'cellSetSelection',
|
|
132
|
+
geneSelection,
|
|
133
|
+
cellSets: mergedCellSets,
|
|
134
|
+
cellSetSelection,
|
|
135
|
+
cellSetColor,
|
|
136
|
+
obsIndex,
|
|
137
|
+
theme,
|
|
138
|
+
}), [mergedCellSets, geneSelection, theme,
|
|
139
|
+
cellSetColor, cellSetSelection, obsIndex]);
|
|
140
|
+
|
|
141
|
+
const getObsInfo = useGetObsInfo(
|
|
142
|
+
observationsLabel, obsLabelsTypes, obsLabelsData, obsSetsMembership,
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
const getFeatureInfo = useCallback((featureId) => {
|
|
146
|
+
if (featureId) {
|
|
147
|
+
return { [`${capitalize(variablesLabel)} ID`]: featureId };
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
}, [variablesLabel]);
|
|
151
|
+
|
|
152
|
+
const expressionMatrix = useMemo(() => {
|
|
153
|
+
if (obsIndex && featureIndex && obsFeatureMatrix) {
|
|
154
|
+
return {
|
|
155
|
+
rows: obsIndex,
|
|
156
|
+
cols: (featureLabelsMap
|
|
157
|
+
? featureIndex.map(key => featureLabelsMap.get(key) || key)
|
|
158
|
+
: featureIndex
|
|
159
|
+
),
|
|
160
|
+
matrix: obsFeatureMatrix.data,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
return null;
|
|
164
|
+
}, [obsIndex, featureIndex, obsFeatureMatrix, featureLabelsMap]);
|
|
165
|
+
|
|
166
|
+
const cellsCount = obsIndex ? obsIndex.length : 0;
|
|
167
|
+
const genesCount = featureIndex ? featureIndex.length : 0;
|
|
168
|
+
|
|
169
|
+
const setTrackHighlight = useCallback(() => {
|
|
170
|
+
// No-op, since the default handler
|
|
171
|
+
// logs in the console on every hover event.
|
|
172
|
+
}, []);
|
|
173
|
+
|
|
174
|
+
const cellColorLabels = useMemo(() => ([
|
|
175
|
+
`${capitalize(observationsLabel)} Set`,
|
|
176
|
+
]), [observationsLabel]);
|
|
177
|
+
|
|
178
|
+
const selectedCount = cellColors.size;
|
|
179
|
+
return (
|
|
180
|
+
<TitleInfo
|
|
181
|
+
title={title}
|
|
182
|
+
info={`${commaNumber(cellsCount)} ${plur(observationsLabel, cellsCount)} × ${commaNumber(genesCount)} ${plur(variablesLabel, genesCount)},
|
|
183
|
+
with ${commaNumber(selectedCount)} ${plur(observationsLabel, selectedCount)} selected`}
|
|
184
|
+
urls={urls}
|
|
185
|
+
theme={theme}
|
|
186
|
+
removeGridComponent={removeGridComponent}
|
|
187
|
+
isReady={isReady && !isRendering}
|
|
188
|
+
options={(
|
|
189
|
+
<HeatmapOptions
|
|
190
|
+
geneExpressionColormap={geneExpressionColormap}
|
|
191
|
+
setGeneExpressionColormap={setGeneExpressionColormap}
|
|
192
|
+
geneExpressionColormapRange={geneExpressionColormapRange}
|
|
193
|
+
setGeneExpressionColormapRange={setGeneExpressionColormapRange}
|
|
194
|
+
/>
|
|
195
|
+
)}
|
|
196
|
+
>
|
|
197
|
+
<Heatmap
|
|
198
|
+
ref={deckRef}
|
|
199
|
+
transpose={transpose}
|
|
200
|
+
viewState={{ zoom: zoomX, target: [targetX, targetY] }}
|
|
201
|
+
setViewState={({ zoom, target }) => {
|
|
202
|
+
setZoomX(zoom);
|
|
203
|
+
setZoomY(zoom);
|
|
204
|
+
setTargetX(target[0]);
|
|
205
|
+
setTargetY(target[1]);
|
|
206
|
+
}}
|
|
207
|
+
colormapRange={geneExpressionColormapRange}
|
|
208
|
+
setColormapRange={setGeneExpressionColormapRange}
|
|
209
|
+
height={height}
|
|
210
|
+
width={width}
|
|
211
|
+
theme={theme}
|
|
212
|
+
uuid={uuid}
|
|
213
|
+
expressionMatrix={expressionMatrix}
|
|
214
|
+
cellColors={cellColors}
|
|
215
|
+
colormap={geneExpressionColormap}
|
|
216
|
+
setIsRendering={setIsRendering}
|
|
217
|
+
setCellHighlight={setCellHighlight}
|
|
218
|
+
setGeneHighlight={setGeneHighlight}
|
|
219
|
+
setTrackHighlight={setTrackHighlight}
|
|
220
|
+
setComponentHover={() => {
|
|
221
|
+
setComponentHover(uuid);
|
|
222
|
+
}}
|
|
223
|
+
updateViewInfo={setComponentViewInfo}
|
|
224
|
+
observationsTitle={observationsTitle}
|
|
225
|
+
variablesTitle={variablesTitle}
|
|
226
|
+
variablesDashes={false}
|
|
227
|
+
observationsDashes={false}
|
|
228
|
+
cellColorLabels={cellColorLabels}
|
|
229
|
+
useDevicePixels
|
|
230
|
+
/>
|
|
231
|
+
{!disableTooltip && (
|
|
232
|
+
<HeatmapTooltipSubscriber
|
|
233
|
+
parentUuid={uuid}
|
|
234
|
+
width={width}
|
|
235
|
+
height={height}
|
|
236
|
+
transpose={transpose}
|
|
237
|
+
getObsInfo={getObsInfo}
|
|
238
|
+
getFeatureInfo={getFeatureInfo}
|
|
239
|
+
obsHighlight={cellHighlight}
|
|
240
|
+
featureHighlight={geneHighlight}
|
|
241
|
+
/>
|
|
242
|
+
)}
|
|
243
|
+
</TitleInfo>
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function register() {
|
|
248
|
+
registerPluginViewType(
|
|
249
|
+
ViewType.HEATMAP,
|
|
250
|
+
HeatmapSubscriber,
|
|
251
|
+
COMPONENT_COORDINATION_TYPES[ViewType.HEATMAP],
|
|
252
|
+
);
|
|
253
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Tooltip2D, TooltipContent } from '@vitessce/tooltip';
|
|
3
|
+
import { useComponentHover, useComponentViewInfo } from '@vitessce/vit-s';
|
|
4
|
+
|
|
5
|
+
export default function HeatmapTooltipSubscriber(props) {
|
|
6
|
+
const {
|
|
7
|
+
parentUuid,
|
|
8
|
+
width, height, transpose,
|
|
9
|
+
getObsInfo, getFeatureInfo,
|
|
10
|
+
obsHighlight, featureHighlight,
|
|
11
|
+
} = props;
|
|
12
|
+
|
|
13
|
+
const sourceUuid = useComponentHover();
|
|
14
|
+
const viewInfo = useComponentViewInfo(parentUuid);
|
|
15
|
+
|
|
16
|
+
const [cellInfo, cellCoord] = (obsHighlight && getObsInfo ? (
|
|
17
|
+
[
|
|
18
|
+
getObsInfo(obsHighlight),
|
|
19
|
+
(viewInfo && viewInfo.project
|
|
20
|
+
? viewInfo.project(obsHighlight, null)[(transpose ? 0 : 1)]
|
|
21
|
+
: null),
|
|
22
|
+
]
|
|
23
|
+
) : ([null, null]));
|
|
24
|
+
|
|
25
|
+
const [geneInfo, geneCoord] = (featureHighlight && getFeatureInfo ? (
|
|
26
|
+
[
|
|
27
|
+
getFeatureInfo(featureHighlight),
|
|
28
|
+
(viewInfo && viewInfo.project
|
|
29
|
+
? viewInfo.project(null, featureHighlight)[(transpose ? 1 : 0)]
|
|
30
|
+
: null),
|
|
31
|
+
]
|
|
32
|
+
) : ([null, null]));
|
|
33
|
+
|
|
34
|
+
const x = (transpose ? cellCoord : geneCoord);
|
|
35
|
+
const y = (transpose ? geneCoord : cellCoord);
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
(cellInfo || geneInfo ? (
|
|
39
|
+
<Tooltip2D
|
|
40
|
+
x={x}
|
|
41
|
+
y={y}
|
|
42
|
+
parentUuid={parentUuid}
|
|
43
|
+
parentWidth={width}
|
|
44
|
+
parentHeight={height}
|
|
45
|
+
sourceUuid={sourceUuid}
|
|
46
|
+
>
|
|
47
|
+
<TooltipContent info={{ ...geneInfo, ...cellInfo }} />
|
|
48
|
+
</Tooltip2D>
|
|
49
|
+
) : null)
|
|
50
|
+
);
|
|
51
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { HeatmapWorker } from '@vitessce/workers';
|
|
2
|
+
import { Pool } from '@vitessce/utils';
|
|
3
|
+
|
|
4
|
+
// Reference: https://github.com/developit/jsdom-worker/issues/14#issuecomment-1268070123
|
|
5
|
+
function createWorker() {
|
|
6
|
+
return new HeatmapWorker();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Pool for workers to decode chunks of the images.
|
|
11
|
+
* This is a line-for-line copy of GeoTIFFs old implementation: https://github.com/geotiffjs/geotiff.js/blob/v1.0.0-beta.6/src/pool.js
|
|
12
|
+
*/
|
|
13
|
+
export default class HeatmapPool extends Pool {
|
|
14
|
+
constructor() {
|
|
15
|
+
super(createWorker);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Process each heatmap tile
|
|
20
|
+
* @param {object} params The arguments passed to the heatmap worker.
|
|
21
|
+
* @param {string} params.curr The current task uuid.
|
|
22
|
+
* @param {number} params.xTiles How many tiles required in the x direction?
|
|
23
|
+
* @param {number} params.yTiles How many tiles required in the y direction?
|
|
24
|
+
* @param {number} params.tileSize How many entries along each tile axis?
|
|
25
|
+
* @param {string[]} params.cellOrdering The current ordering of cells.
|
|
26
|
+
* @param {string[]} params.rows The name of each row (cell ID).
|
|
27
|
+
* Does not take transpose into account (always cells).
|
|
28
|
+
* @param {string[]} params.cols The name of each column (gene ID).
|
|
29
|
+
* Does not take transpose into account (always genes).
|
|
30
|
+
* @param {ArrayBuffer} params.data The array buffer.
|
|
31
|
+
* Need to transfer back to main thread when done.
|
|
32
|
+
* @param {boolean} params.transpose Is the heatmap transposed?
|
|
33
|
+
* @returns {array} [message, transfers]
|
|
34
|
+
* @returns {Promise.<ArrayBuffer>} the decoded result as a `Promise`
|
|
35
|
+
*/
|
|
36
|
+
async process(args) {
|
|
37
|
+
const currentWorker = await this.waitForWorker();
|
|
38
|
+
return new Promise((resolve, reject) => {
|
|
39
|
+
currentWorker.onmessage = (event) => {
|
|
40
|
+
// this.workers.push(currentWorker);
|
|
41
|
+
this.finishTask(currentWorker);
|
|
42
|
+
resolve(event.data);
|
|
43
|
+
};
|
|
44
|
+
currentWorker.onerror = (error) => {
|
|
45
|
+
// this.workers.push(currentWorker);
|
|
46
|
+
this.finishTask(currentWorker);
|
|
47
|
+
reject(error);
|
|
48
|
+
};
|
|
49
|
+
currentWorker.postMessage(['getTile', args], [args.data]);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
\documentclass[a4paper]{article}
|
|
2
|
+
|
|
3
|
+
\usepackage[utf8]{inputenc}
|
|
4
|
+
\usepackage[T1]{fontenc}
|
|
5
|
+
\usepackage{textcomp}
|
|
6
|
+
\usepackage[english]{babel}
|
|
7
|
+
\usepackage{amsmath, amsthm, amssymb, fancyhdr, enumitem, bbm}
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
% figure support
|
|
11
|
+
\usepackage{import}
|
|
12
|
+
\usepackage{xifthen}
|
|
13
|
+
\pdfminorversion=7
|
|
14
|
+
\usepackage{pdfpages}
|
|
15
|
+
\usepackage{transparent}
|
|
16
|
+
\newcommand{\incfig}[1]{%
|
|
17
|
+
\def\svgwidth{\columnwidth}
|
|
18
|
+
\import{./figures/}{#1.pdf_tex}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
\pdfsuppresswarningpagegroup=1
|
|
22
|
+
|
|
23
|
+
\pagestyle{fancy}
|
|
24
|
+
\fancyhf{}
|
|
25
|
+
\lhead{\textsc{Ilan Gold, vitessce-heatmap}}
|
|
26
|
+
|
|
27
|
+
\newcommand{\Rr}{\mathbb{R}}
|
|
28
|
+
\newcommand{\Qq}{\mathbb{Q}}
|
|
29
|
+
\newcommand{\Zz}{\mathbb{Z}}
|
|
30
|
+
\newcommand{\Cc}{\mathbb{C}}
|
|
31
|
+
|
|
32
|
+
\newtheorem*{theorem*}{Theorem}
|
|
33
|
+
\newtheorem*{lemma*}{Lemma}
|
|
34
|
+
\newtheorem*{corollary*}{Corollary}
|
|
35
|
+
\newtheorem*{definition*}{Definition}
|
|
36
|
+
\newtheorem*{remark*}{Remark}
|
|
37
|
+
\newtheorem*{example*}{Example}
|
|
38
|
+
\newtheorem{theorem}{Theorem}
|
|
39
|
+
\newtheorem{lemma}{Lemma} \newtheorem{corollary}{Corollary}
|
|
40
|
+
\newtheorem{definition}{Definition}
|
|
41
|
+
\newtheorem{remark}{Remark}
|
|
42
|
+
\newtheorem{example}{Example}
|
|
43
|
+
|
|
44
|
+
\begin{document}
|
|
45
|
+
|
|
46
|
+
\begin{center}
|
|
47
|
+
\Large{\textsc{Heatmap Indexing}}
|
|
48
|
+
\end{center}
|
|
49
|
+
|
|
50
|
+
We first define a \textbf{data matrix} $M \in \Rr^{n\times m}$. We then have a \textbf{tile size} $\tau$ so that we have $\left\lceil \frac{n}{\tau} \right\rceil = R$ row tiles and $\left\lceil \frac{m}{\tau} \right\rceil = C $ column tiles. Because this data matrix cannot fit on the GPU it must be reshaped into a $M^{*} \in \Rr^{p \times p}$ \textbf{reshaped matrix} where $p$ is the max texture size on the computer (we assume that $mn < p^{2}$ otherwise this doesn't work).
|
|
51
|
+
|
|
52
|
+
The data is thus reshaped into this new matrix in row major order under the mapping $\phi: \{0, \ldots, n - 1\} \times \{0, \ldots, m-1\} \to \{0, \ldots p-1\}^{2}$ \[
|
|
53
|
+
\phi: (i, j) \to \left( \left\lfloor \frac{\left( i * m \right) + j}{p}\right\rfloor, (\left( i * m \right) + j) - p \left\lfloor \frac{\left( i * m \right) + j}{p}\right\rfloor \right)
|
|
54
|
+
.\]
|
|
55
|
+
Assume we have some fragment within a tile \[
|
|
56
|
+
F = \left( \left( a, b \right), \left( x, y \right) \right) \in [0,1]^{2} \times \left( \{0, \ldots, C - 1\} \times \{0, \ldots, R-1\} \right) = \mathcal{F}
|
|
57
|
+
\] defined by \textbf{fragment coordinates} $\left( a, b \right) \in [0,1]^{2}$, in normalized fragment space, and $\left( x,y \right) \in \{0, \ldots, C - 1\} \times \{0, \ldots, R-1\} $ the \textbf{tile coordinates}. Note that we are following the graphics convention, not the matrix convention - for matrices, the first coordinate represents row number (i.e going "up and down") but in graphics, the first coordinate represents the lateral coordinate, in this case for both the coordiante and the fragment coordinate.
|
|
58
|
+
|
|
59
|
+
In summary, every fragment in the total fragment space $\mathcal{F}$ (over all tiles) is described by what tile it is in, $\left( x, y \right) $, and the coordinates within that tile in normalized fragment space $(a,b)$. We are then able to define the coordinates of the tile in terms of the original data matrix coordinates by the mapping $\psi: \mathcal{F} \to \{0, \ldots, n - 1\} \times \{0, \ldots, m-1\} $ \[
|
|
60
|
+
\psi: (\left( a, b \right), \left( x, y \right) = T) \to \left( y * \tau + \left\lfloor b * \tau \right\rfloor, x * \tau + \left\lfloor a * \tau \right\rfloor \right)
|
|
61
|
+
.\]
|
|
62
|
+
This mapping takes the $[0,1]$ fragment, finds the coordinate within the tile that is correct independent of the underlying data (this is the floor term), and then offsets this coordinate by which tile of the data matrix we are in.
|
|
63
|
+
|
|
64
|
+
Thus the mapping $\phi \circ \psi: \mathcal{F} \to \{0, \ldots p-1\}^{2} $ is a mapping from a fragment within a given tile to a spot on the reshaped data matrix, and this mapping only relies on knowing $m, n, p, \tau$ and $F$, all of which are accessible for every fragment by binding these parameters as uniforms. By then normalizing the image of $\phi \circ \psi$ i.e dividing both coordinates by $p$, we would get a final coordinate in $[0, 1]^{2}$ which can be used to sample the reshaped data texture.
|
|
65
|
+
\end{document}
|
package/src/index.js
ADDED