@vitessce/statistical-plots 3.5.7 → 3.5.9

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.
Files changed (51) hide show
  1. package/dist/{deflate-287e693d.js → deflate-19841f78.js} +1 -1
  2. package/dist/{index-1f1b6355.js → index-dc733355.js} +2084 -1065
  3. package/dist/index.js +9 -6
  4. package/dist/{jpeg-1b2c1d25.js → jpeg-a83077be.js} +1 -1
  5. package/dist/{lerc-4f010cd7.js → lerc-1edd075a.js} +1 -1
  6. package/dist/{lzw-e60fb582.js → lzw-9572eac3.js} +1 -1
  7. package/dist/{packbits-a8bfe098.js → packbits-cce11fbc.js} +1 -1
  8. package/dist/{raw-01dff90e.js → raw-f7587aff.js} +1 -1
  9. package/dist/{webimage-6b926ce3.js → webimage-8d38cd8b.js} +1 -1
  10. package/dist-tsc/CellSetCompositionBarPlot.d.ts +5 -0
  11. package/dist-tsc/CellSetCompositionBarPlot.d.ts.map +1 -0
  12. package/dist-tsc/CellSetCompositionBarPlot.js +166 -0
  13. package/dist-tsc/CellSetCompositionBarPlotSubscriber.d.ts +2 -0
  14. package/dist-tsc/CellSetCompositionBarPlotSubscriber.d.ts.map +1 -0
  15. package/dist-tsc/CellSetCompositionBarPlotSubscriber.js +40 -0
  16. package/dist-tsc/FeatureSetEnrichmentBarPlot.d.ts +5 -0
  17. package/dist-tsc/FeatureSetEnrichmentBarPlot.d.ts.map +1 -0
  18. package/dist-tsc/FeatureSetEnrichmentBarPlot.js +164 -0
  19. package/dist-tsc/FeatureSetEnrichmentBarPlotSubscriber.d.ts +2 -0
  20. package/dist-tsc/FeatureSetEnrichmentBarPlotSubscriber.d.ts.map +1 -0
  21. package/dist-tsc/FeatureSetEnrichmentBarPlotSubscriber.js +51 -0
  22. package/dist-tsc/Treemap.d.ts.map +1 -1
  23. package/dist-tsc/Treemap.js +11 -15
  24. package/dist-tsc/TreemapSubscriber.d.ts.map +1 -1
  25. package/dist-tsc/TreemapSubscriber.js +6 -2
  26. package/dist-tsc/VolcanoPlot.d.ts +2 -0
  27. package/dist-tsc/VolcanoPlot.d.ts.map +1 -0
  28. package/dist-tsc/VolcanoPlot.js +231 -0
  29. package/dist-tsc/VolcanoPlotOptions.d.ts +2 -0
  30. package/dist-tsc/VolcanoPlotOptions.d.ts.map +1 -0
  31. package/dist-tsc/VolcanoPlotOptions.js +23 -0
  32. package/dist-tsc/VolcanoPlotSubscriber.d.ts +2 -0
  33. package/dist-tsc/VolcanoPlotSubscriber.d.ts.map +1 -0
  34. package/dist-tsc/VolcanoPlotSubscriber.js +33 -0
  35. package/dist-tsc/index.d.ts +3 -0
  36. package/dist-tsc/index.js +3 -0
  37. package/dist-tsc/utils.d.ts +9 -0
  38. package/dist-tsc/utils.d.ts.map +1 -0
  39. package/dist-tsc/utils.js +40 -0
  40. package/package.json +7 -7
  41. package/src/CellSetCompositionBarPlot.js +205 -0
  42. package/src/CellSetCompositionBarPlotSubscriber.js +151 -0
  43. package/src/FeatureSetEnrichmentBarPlot.js +203 -0
  44. package/src/FeatureSetEnrichmentBarPlotSubscriber.js +166 -0
  45. package/src/Treemap.js +12 -19
  46. package/src/TreemapSubscriber.js +7 -1
  47. package/src/VolcanoPlot.js +316 -0
  48. package/src/VolcanoPlotOptions.js +136 -0
  49. package/src/VolcanoPlotSubscriber.js +162 -0
  50. package/src/index.js +3 -0
  51. package/src/utils.js +47 -0
@@ -0,0 +1,151 @@
1
+ /* eslint-disable no-unused-vars */
2
+ import React, { useMemo, useCallback } from 'react';
3
+ import {
4
+ TitleInfo,
5
+ useCoordination,
6
+ useLoaders,
7
+ useReady,
8
+ useGridItemSize,
9
+ useObsSetStatsData,
10
+ useMatchingLoader,
11
+ useColumnNameMapping,
12
+ } from '@vitessce/vit-s';
13
+ import {
14
+ ViewType,
15
+ COMPONENT_COORDINATION_TYPES,
16
+ ViewHelpMapping,
17
+ DataType,
18
+ } from '@vitessce/constants-internal';
19
+ import { capitalize } from '@vitessce/utils';
20
+ import CellSetCompositionBarPlot from './CellSetCompositionBarPlot.js';
21
+ import { useStyles } from './styles.js';
22
+ import { useRawSetPaths } from './utils.js';
23
+
24
+
25
+ export function CellSetCompositionBarPlotSubscriber(props) {
26
+ const {
27
+ coordinationScopes,
28
+ removeGridComponent,
29
+ theme,
30
+ helpText = ViewHelpMapping.OBS_SET_COMPOSITION_BAR_PLOT,
31
+ } = props;
32
+
33
+ const classes = useStyles();
34
+ const loaders = useLoaders();
35
+
36
+ // Get "props" from the coordination space.
37
+ const [{
38
+ dataset,
39
+ obsType,
40
+ sampleType,
41
+ featureType,
42
+ featureValueType,
43
+ obsFilter: cellFilter,
44
+ obsHighlight: cellHighlight,
45
+ obsSetSelection,
46
+ obsSetColor,
47
+ obsColorEncoding: cellColorEncoding,
48
+ additionalObsSets: additionalCellSets,
49
+ featurePointSignificanceThreshold,
50
+ featurePointFoldChangeThreshold,
51
+ featureLabelSignificanceThreshold,
52
+ featureLabelFoldChangeThreshold,
53
+ featureValueTransform,
54
+ featureValueTransformCoefficient,
55
+ gatingFeatureSelectionX,
56
+ gatingFeatureSelectionY,
57
+ featureSelection,
58
+ sampleSetSelection,
59
+ sampleSetColor,
60
+ }, {
61
+ setObsFilter: setCellFilter,
62
+ setObsSetSelection,
63
+ setObsHighlight: setCellHighlight,
64
+ setObsSetColor: setCellSetColor,
65
+ setObsColorEncoding: setCellColorEncoding,
66
+ setAdditionalObsSets: setAdditionalCellSets,
67
+ setFeaturePointSignificanceThreshold,
68
+ setFeaturePointFoldChangeThreshold,
69
+ setFeatureLabelSignificanceThreshold,
70
+ setFeatureLabelFoldChangeThreshold,
71
+ setFeatureValueTransform,
72
+ setFeatureValueTransformCoefficient,
73
+ setGatingFeatureSelectionX,
74
+ setGatingFeatureSelectionY,
75
+ setFeatureSelection,
76
+ setSampleSetSelection,
77
+ setSampleSetColor,
78
+ }] = useCoordination(
79
+ COMPONENT_COORDINATION_TYPES[ViewType.OBS_SET_COMPOSITION_BAR_PLOT],
80
+ coordinationScopes,
81
+ );
82
+ const [width, height, containerRef] = useGridItemSize();
83
+
84
+ const obsSetsLoader = useMatchingLoader(
85
+ loaders, dataset, DataType.OBS_SETS, { obsType },
86
+ );
87
+ const sampleSetsLoader = useMatchingLoader(
88
+ loaders, dataset, DataType.SAMPLE_SETS, { sampleType },
89
+ );
90
+ const obsSetsColumnNameMapping = useColumnNameMapping(obsSetsLoader);
91
+ const obsSetsColumnNameMappingReversed = useColumnNameMapping(obsSetsLoader, true);
92
+ const sampleSetsColumnNameMapping = useColumnNameMapping(sampleSetsLoader);
93
+ const sampleSetsColumnNameMappingReversed = useColumnNameMapping(sampleSetsLoader, true);
94
+
95
+ const rawSampleSetSelection = useRawSetPaths(sampleSetsColumnNameMapping, sampleSetSelection);
96
+ const rawObsSetSelection = useRawSetPaths(obsSetsColumnNameMapping, obsSetSelection);
97
+
98
+ const [{ obsSetStats }, obsSetStatsStatus] = useObsSetStatsData(
99
+ loaders, dataset, false,
100
+ { obsType, sampleType },
101
+ // These volcanoOptions are passed to ObsSetStatsAnndataLoader.loadMulti():
102
+ { sampleSetSelection: rawSampleSetSelection, obsSetSelection: rawObsSetSelection },
103
+ );
104
+
105
+ const isReady = useReady([
106
+ obsSetStatsStatus,
107
+ ]);
108
+
109
+ // Support a click handler which selects individual cell set bars.
110
+ const onBarSelect = useCallback((setNamePath, isShiftDown = false) => {
111
+ // TODO: Implement different behavior when isShiftDown
112
+ setObsSetSelection([setNamePath]);
113
+ }, [setObsSetSelection]);
114
+
115
+ // TODO: support the following options
116
+ // - Use logFoldChange vs. intercept+effect for the bar y-value.
117
+ // - Boolean flag to allow hiding non-significant bars.
118
+
119
+ return (
120
+ <TitleInfo
121
+ title={`${capitalize(obsType)} Set Composition Analysis Plot`}
122
+ removeGridComponent={removeGridComponent}
123
+ theme={theme}
124
+ isReady={isReady}
125
+ helpText={helpText}
126
+ >
127
+ <div ref={containerRef} className={classes.vegaContainer}>
128
+ {obsSetStats ? (
129
+ <CellSetCompositionBarPlot
130
+ theme={theme}
131
+ width={width}
132
+ height={height}
133
+ obsType={obsType}
134
+ obsSetsColumnNameMapping={obsSetsColumnNameMapping}
135
+ obsSetsColumnNameMappingReversed={obsSetsColumnNameMappingReversed}
136
+ sampleSetsColumnNameMapping={sampleSetsColumnNameMapping}
137
+ sampleSetsColumnNameMappingReversed={sampleSetsColumnNameMappingReversed}
138
+ sampleSetSelection={sampleSetSelection}
139
+ obsSetSelection={obsSetSelection}
140
+ obsSetColor={obsSetColor}
141
+ sampleSetColor={sampleSetColor}
142
+ data={obsSetStats}
143
+ onBarSelect={onBarSelect}
144
+ />
145
+ ) : (
146
+ <span>Select at least one {obsType} set.</span>
147
+ )}
148
+ </div>
149
+ </TitleInfo>
150
+ );
151
+ }
@@ -0,0 +1,203 @@
1
+ import React, { useCallback, useMemo } from 'react';
2
+ import { clamp } from 'lodash-es';
3
+ import { v4 as uuidv4 } from 'uuid';
4
+ import { VegaPlot, VEGA_THEMES } from '@vitessce/vega';
5
+ import { capitalize } from '@vitessce/utils';
6
+ import { getColorScale } from './utils.js';
7
+
8
+ /**
9
+ * Feature set enrichment test results displayed using a bar chart.
10
+ */
11
+ export default function FeatureSetEnrichmentBarPlot(props) {
12
+ const {
13
+ data,
14
+ theme,
15
+ width,
16
+ height,
17
+ marginRight = 200,
18
+ marginBottom = 120,
19
+ keyLength = 36,
20
+ featureType,
21
+ onBarSelect,
22
+ obsSetsColumnNameMappingReversed,
23
+ sampleSetsColumnNameMappingReversed,
24
+ sampleSetSelection,
25
+ obsSetSelection,
26
+ obsSetColor,
27
+ sampleSetColor,
28
+ pValueThreshold,
29
+ } = props;
30
+
31
+ const [obsSetColorScale, sampleSetColorScale] = useMemo(() => [
32
+ getColorScale(obsSetSelection, obsSetColor, theme),
33
+ getColorScale(sampleSetSelection, sampleSetColor, theme),
34
+ ], [obsSetSelection, sampleSetSelection, sampleSetColor, obsSetColor, theme]);
35
+
36
+ const computedData = useMemo(() => {
37
+ if (Array.isArray(data)) {
38
+ let result = [];
39
+ data.forEach((comparisonObject) => {
40
+ const { df, metadata } = comparisonObject;
41
+ const coordinationValues = metadata?.coordination_values;
42
+
43
+ const rawObsSetPath = coordinationValues.obsSetFilter
44
+ ? coordinationValues.obsSetFilter[0]
45
+ : coordinationValues.obsSetSelection[0];
46
+ const obsSetPath = [...rawObsSetPath];
47
+ obsSetPath[0] = obsSetsColumnNameMappingReversed[rawObsSetPath[0]];
48
+
49
+ const color = obsSetColorScale(obsSetPath);
50
+
51
+ df.featureSetName.forEach((featureSetName, i) => {
52
+ const key = uuidv4();
53
+ result.push({
54
+ key,
55
+ name: featureSetName,
56
+ term: df.featureSetTerm[i],
57
+ color,
58
+ obsSetPath,
59
+ obsSetPaths: [obsSetPath],
60
+ obsSetNameToPval: { [obsSetPath.at(-1)]: df.featureSetSignificance[i] },
61
+ keyName: `${key}${featureSetName}`,
62
+ featureSetSignificance: df.featureSetSignificance[i],
63
+ minusLog10p: -Math.log10(df.featureSetSignificance[i]),
64
+ // Color based on obsSet
65
+ });
66
+ });
67
+ });
68
+
69
+ // TODO: instead of filtering, perhaps use virtual scrolling
70
+ // (would require custom renderer / not using Vega-Lite).
71
+ result = result
72
+ .map(d => ({
73
+ ...d,
74
+ minusLog10p: Math.min(50, d.minusLog10p), // Clamp infinite values at 50
75
+ }))
76
+ .filter(d => d.featureSetSignificance <= pValueThreshold)
77
+ .toSorted((a, b) => a.featureSetSignificance - b.featureSetSignificance)
78
+ .reduce((a, h) => {
79
+ // Only add the pathway once if it appears for multiple cell types?
80
+ const match = a.find(d => d.name === h.name);
81
+ if (match) {
82
+ match.obsSetPaths.push(h.obsSetPath);
83
+ match.obsSetNameToPval[h.obsSetPath.at(-1)] = h.featureSetSignificance;
84
+ return a;
85
+ }
86
+ return [...a, h];
87
+ }, []);
88
+
89
+ const MAX_ROWS = 25;
90
+ result = result.slice(0, MAX_ROWS);
91
+ return result;
92
+ }
93
+ return null;
94
+ }, [data, sampleSetSelection, obsSetsColumnNameMappingReversed,
95
+ sampleSetsColumnNameMappingReversed, obsSetSelection,
96
+ obsSetColorScale, sampleSetColorScale, pValueThreshold,
97
+ ]);
98
+
99
+ // Get an array of keys for sorting purposes.
100
+ const keys = computedData.map(d => d.keyName);
101
+
102
+ const colorScale = {
103
+ // Manually set the color scale so that Vega-Lite does
104
+ // not choose the colors automatically.
105
+ domain: computedData.map(d => d.key),
106
+ range: computedData.map(d => d.color),
107
+ };
108
+ const captializedFeatureType = capitalize(featureType);
109
+
110
+ const spec = {
111
+ mark: { type: 'bar', stroke: 'black', cursor: 'pointer' },
112
+ params: [
113
+ {
114
+ name: 'bar_select',
115
+ select: {
116
+ type: 'point',
117
+ on: 'click[event.shiftKey === false]',
118
+ fields: ['name'],
119
+ empty: 'none',
120
+ },
121
+ },
122
+ {
123
+ name: 'shift_bar_select',
124
+ select: {
125
+ type: 'point',
126
+ on: 'click[event.shiftKey]',
127
+ fields: ['name'],
128
+ empty: 'none',
129
+ },
130
+ },
131
+ ],
132
+ encoding: {
133
+ y: {
134
+ field: 'keyName',
135
+ type: 'nominal',
136
+ axis: { labelExpr: `substring(datum.label, ${keyLength})` },
137
+ title: `${captializedFeatureType} Set`,
138
+ sort: keys,
139
+ },
140
+ x: {
141
+ field: 'minusLog10p',
142
+ type: 'quantitative',
143
+ title: '-log10 p-value',
144
+ },
145
+ color: {
146
+ field: 'key',
147
+ type: 'nominal',
148
+ scale: colorScale,
149
+ legend: null,
150
+ },
151
+ /*
152
+ fillOpacity: {
153
+ field: 'isCredibleEffect',
154
+ type: 'nominal',
155
+ scale: opacityScale,
156
+ },
157
+ strokeWidth: {
158
+ field: 'isReferenceSet',
159
+ type: 'nominal',
160
+ scale: strokeWidthScale,
161
+ },
162
+ */
163
+ tooltip: {
164
+ field: 'featureSetSignificance',
165
+ type: 'quantitative',
166
+ },
167
+ },
168
+ // TODO: for width, also subtract length of longest y-axis set name label.
169
+ width: clamp(width - marginRight, 10, Infinity),
170
+ height: clamp(height - marginBottom, 10, Infinity),
171
+ config: VEGA_THEMES[theme],
172
+ };
173
+
174
+ const handleSignal = (name, value) => {
175
+ if (name === 'bar_select') {
176
+ onBarSelect(value.obsSetPath);
177
+ } else if (name === 'shift_bar_select') {
178
+ onBarSelect(value.obsSetPath, true);
179
+ }
180
+ };
181
+
182
+ const signalListeners = { bar_select: handleSignal, shift_bar_select: handleSignal };
183
+ const getTooltipText = useCallback(item => ({
184
+ [`${captializedFeatureType} Set`]: item.datum.name,
185
+ 'Ontology Term': item.datum.term,
186
+ ...Object.fromEntries(
187
+ Object.entries(item.datum.obsSetNameToPval).map(([cellSetName, pVal]) => ([
188
+ `p-value for ${cellSetName}`,
189
+ pVal,
190
+ ])),
191
+ ),
192
+ }
193
+ ), [captializedFeatureType]);
194
+
195
+ return (
196
+ <VegaPlot
197
+ data={computedData}
198
+ spec={spec}
199
+ signalListeners={signalListeners}
200
+ getTooltipText={getTooltipText}
201
+ />
202
+ );
203
+ }
@@ -0,0 +1,166 @@
1
+ /* eslint-disable no-unused-vars */
2
+ import React, { useCallback } from 'react';
3
+ import {
4
+ TitleInfo,
5
+ useCoordination,
6
+ useLoaders,
7
+ useReady,
8
+ useGridItemSize,
9
+ useFeatureSetStatsData,
10
+ useMatchingLoader,
11
+ useColumnNameMapping,
12
+ useAsyncFunction,
13
+ } from '@vitessce/vit-s';
14
+ import {
15
+ ViewType,
16
+ COMPONENT_COORDINATION_TYPES,
17
+ ViewHelpMapping,
18
+ DataType,
19
+ AsyncFunctionType,
20
+ } from '@vitessce/constants-internal';
21
+ import { capitalize } from '@vitessce/utils';
22
+ import FeatureSetEnrichmentBarPlot from './FeatureSetEnrichmentBarPlot.js';
23
+ import { useStyles } from './styles.js';
24
+ import { useRawSetPaths } from './utils.js';
25
+
26
+
27
+ export function FeatureSetEnrichmentBarPlotSubscriber(props) {
28
+ const {
29
+ coordinationScopes,
30
+ removeGridComponent,
31
+ theme,
32
+ helpText = ViewHelpMapping.FEATURE_SET_ENRICHMENT_BAR_PLOT,
33
+ } = props;
34
+
35
+ const classes = useStyles();
36
+ const loaders = useLoaders();
37
+ const transformFeature = useAsyncFunction(AsyncFunctionType.TRANSFORM_FEATURE);
38
+
39
+ // Get "props" from the coordination space.
40
+ const [{
41
+ dataset,
42
+ obsType,
43
+ sampleType,
44
+ featureType,
45
+ featureValueType,
46
+ obsFilter: cellFilter,
47
+ obsHighlight: cellHighlight,
48
+ obsSetSelection,
49
+ obsSetColor,
50
+ obsColorEncoding: cellColorEncoding,
51
+ additionalObsSets: additionalCellSets,
52
+ featurePointSignificanceThreshold,
53
+ featurePointFoldChangeThreshold,
54
+ featureLabelSignificanceThreshold,
55
+ featureLabelFoldChangeThreshold,
56
+ featureValueTransform,
57
+ featureValueTransformCoefficient,
58
+ gatingFeatureSelectionX,
59
+ gatingFeatureSelectionY,
60
+ featureSelection,
61
+ sampleSetSelection,
62
+ sampleSetColor,
63
+ }, {
64
+ setObsFilter: setCellFilter,
65
+ setObsSetSelection,
66
+ setObsHighlight: setCellHighlight,
67
+ setObsSetColor: setCellSetColor,
68
+ setObsColorEncoding: setCellColorEncoding,
69
+ setAdditionalObsSets: setAdditionalCellSets,
70
+ setFeaturePointSignificanceThreshold,
71
+ setFeaturePointFoldChangeThreshold,
72
+ setFeatureLabelSignificanceThreshold,
73
+ setFeatureLabelFoldChangeThreshold,
74
+ setFeatureValueTransform,
75
+ setFeatureValueTransformCoefficient,
76
+ setGatingFeatureSelectionX,
77
+ setGatingFeatureSelectionY,
78
+ setFeatureSelection,
79
+ setSampleSetSelection,
80
+ setSampleSetColor,
81
+ }] = useCoordination(
82
+ COMPONENT_COORDINATION_TYPES[ViewType.FEATURE_SET_ENRICHMENT_BAR_PLOT],
83
+ coordinationScopes,
84
+ );
85
+ const [width, height, containerRef] = useGridItemSize();
86
+
87
+ const obsSetsLoader = useMatchingLoader(
88
+ loaders, dataset, DataType.OBS_SETS, { obsType },
89
+ );
90
+ const sampleSetsLoader = useMatchingLoader(
91
+ loaders, dataset, DataType.SAMPLE_SETS, { sampleType },
92
+ );
93
+ const obsSetsColumnNameMapping = useColumnNameMapping(obsSetsLoader);
94
+ const obsSetsColumnNameMappingReversed = useColumnNameMapping(obsSetsLoader, true);
95
+ const sampleSetsColumnNameMapping = useColumnNameMapping(sampleSetsLoader);
96
+ const sampleSetsColumnNameMappingReversed = useColumnNameMapping(sampleSetsLoader, true);
97
+
98
+ const rawSampleSetSelection = useRawSetPaths(sampleSetsColumnNameMapping, sampleSetSelection);
99
+ const rawObsSetSelection = useRawSetPaths(obsSetsColumnNameMapping, obsSetSelection);
100
+
101
+ const [{ featureSetStats }, featureSetStatsStatus] = useFeatureSetStatsData(
102
+ loaders, dataset, false,
103
+ { obsType, featureType, sampleType },
104
+ // These volcanoOptions are passed to ObsSetStatsAnndataLoader.loadMulti():
105
+ { sampleSetSelection: rawSampleSetSelection, obsSetSelection: rawObsSetSelection },
106
+ );
107
+
108
+ const isReady = useReady([
109
+ featureSetStatsStatus,
110
+ ]);
111
+
112
+ // Support a click handler which selects individual cell set bars.
113
+ const onBarSelect = useCallback(async (featureSetName, featureSetTerm, isShiftDown = false) => {
114
+ // TODO: Implement different behavior when isShiftDown
115
+ // TODO: get feature IDs using AsyncFunction transformFeature
116
+ // (pathway term in, gene names out).
117
+ const kgNode = { nodeType: 'pathway', term: featureSetTerm };
118
+ const targetFeatureType = featureType;
119
+ // Will not work since transformFeature currently:
120
+ // - matches based on kgId (rather than term)
121
+ // - only knows about Reactome pathways (not GO terms).
122
+ // console.log(await transformFeature(kgNode, targetFeatureType))
123
+ // setFeatureSelection(featureIds);
124
+ }, [setFeatureSelection]);
125
+
126
+ // TODO: support the following options
127
+ // - p-value threshold for which bars to show
128
+ // - max number of bars to show
129
+ // - Boolean flag: should same pathway which appears multiple times
130
+ // be de-duplicated (one bar per pathway, using most-significant result)?
131
+
132
+ return (
133
+ <TitleInfo
134
+ title={`${capitalize(featureType)} Set Enrichment Plot`}
135
+ removeGridComponent={removeGridComponent}
136
+ theme={theme}
137
+ isReady={isReady}
138
+ helpText={helpText}
139
+ >
140
+ <div ref={containerRef} className={classes.vegaContainer}>
141
+ {featureSetStats ? (
142
+ <FeatureSetEnrichmentBarPlot
143
+ theme={theme}
144
+ width={width}
145
+ height={height}
146
+ obsType={obsType}
147
+ featureType={featureType}
148
+ obsSetsColumnNameMapping={obsSetsColumnNameMapping}
149
+ obsSetsColumnNameMappingReversed={obsSetsColumnNameMappingReversed}
150
+ sampleSetsColumnNameMapping={sampleSetsColumnNameMapping}
151
+ sampleSetsColumnNameMappingReversed={sampleSetsColumnNameMappingReversed}
152
+ sampleSetSelection={sampleSetSelection}
153
+ obsSetSelection={obsSetSelection}
154
+ obsSetColor={obsSetColor}
155
+ sampleSetColor={sampleSetColor}
156
+ data={featureSetStats}
157
+ onBarSelect={onBarSelect}
158
+ pValueThreshold={0.01}
159
+ />
160
+ ) : (
161
+ <span>Select at least one {obsType} set.</span>
162
+ )}
163
+ </div>
164
+ </TitleInfo>
165
+ );
166
+ }
package/src/Treemap.js CHANGED
@@ -1,13 +1,12 @@
1
1
  /* eslint-disable indent */
2
2
  /* eslint-disable camelcase */
3
3
  import React, { useMemo, useEffect, useRef } from 'react';
4
- import { scaleOrdinal } from 'd3-scale';
5
4
  import { select } from 'd3-selection';
6
5
  import { treemap, treemapBinary, hierarchy as d3_hierarchy } from 'd3-hierarchy';
7
6
  import { rollup as d3_rollup } from 'd3-array';
8
7
  import { isEqual } from 'lodash-es';
9
- import { colorArrayToString } from '@vitessce/sets-utils';
10
- import { getDefaultColor, pluralize as plur } from '@vitessce/utils';
8
+ import { pluralize as plur } from '@vitessce/utils';
9
+ import { getColorScale } from './utils.js';
11
10
 
12
11
  // Based on Observable's built-in DOM.uid function.
13
12
  // This is intended to be used with SVG clipPaths
@@ -21,20 +20,6 @@ function uidGenerator(prefix) {
21
20
  };
22
21
  }
23
22
 
24
- // Create a d3-scale ordinal scale mapping set paths to color strings.
25
- function getColorScale(setSelectionArr, setColorArr, theme) {
26
- return scaleOrdinal()
27
- .domain(setSelectionArr || [])
28
- .range(
29
- setSelectionArr
30
- ?.map(setNamePath => (
31
- setColorArr?.find(d => isEqual(d.path, setNamePath))?.color
32
- || getDefaultColor(theme)
33
- ))
34
- ?.map(colorArrayToString) || [],
35
- );
36
- }
37
-
38
23
  /**
39
24
  * Renders a treemap plot using D3.
40
25
  * References:
@@ -62,6 +47,7 @@ export default function Treemap(props) {
62
47
  marginRight = 5,
63
48
  marginLeft = 80,
64
49
  marginBottom,
50
+ onNodeClick,
65
51
  } = props;
66
52
 
67
53
  const hierarchyData = useMemo(() => {
@@ -169,7 +155,14 @@ export default function Treemap(props) {
169
155
  .attr('fill', d => colorScale(getPathForColoring(d)))
170
156
  .attr('fill-opacity', 0.8)
171
157
  .attr('width', d => d.x1 - d.x0)
172
- .attr('height', d => d.y1 - d.y0);
158
+ .attr('height', d => d.y1 - d.y0)
159
+ .on('click', (e, d) => {
160
+ const obsSetPath = (hierarchyLevels[0] === 'obsSet'
161
+ ? d.parent?.data?.[0]
162
+ : d.data?.[0]
163
+ );
164
+ onNodeClick(obsSetPath);
165
+ });
173
166
 
174
167
  // Append a clipPath to ensure text does not overflow.
175
168
  leaf.append('clipPath')
@@ -199,7 +192,7 @@ export default function Treemap(props) {
199
192
  }, [width, height, marginLeft, marginBottom, theme, marginTop, marginRight,
200
193
  obsType, sampleType, treemapLeaves, sampleSetColor, sampleSetSelection,
201
194
  obsSetSelection, obsSetColor, obsSetColorScale, sampleSetColorScale,
202
- obsColorEncoding, hierarchyLevels,
195
+ obsColorEncoding, hierarchyLevels, onNodeClick,
203
196
  ]);
204
197
 
205
198
  return (
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable no-unused-vars */
2
- import React, { useMemo } from 'react';
2
+ import React, { useMemo, useCallback } from 'react';
3
3
  import {
4
4
  TitleInfo,
5
5
  useCoordination,
@@ -211,9 +211,14 @@ export function TreemapSubscriber(props) {
211
211
  ];
212
212
  }, [obsIndex, sampleEdges, sampleSets, obsSetColor,
213
213
  sampleSetColor, mergedObsSets, obsSetSelection, mergedSampleSets,
214
+ sampleSetSelection,
214
215
  // TODO: consider filtering-related coordination values
215
216
  ]);
216
217
 
218
+ const onNodeClick = useCallback((obsSetPath) => {
219
+ setObsSetSelection([obsSetPath]);
220
+ }, [setObsSetSelection]);
221
+
217
222
  return (
218
223
  <TitleInfo
219
224
  title={`Treemap of ${capitalize(plur(obsType, 2))}`}
@@ -254,6 +259,7 @@ export function TreemapSubscriber(props) {
254
259
  sampleSetColor={sampleSetColor}
255
260
  obsSetSelection={obsSetSelection}
256
261
  sampleSetSelection={sampleSetSelection}
262
+ onNodeClick={onNodeClick}
257
263
  />
258
264
  </div>
259
265
  </TitleInfo>