@vitessce/statistical-plots 4.0.6 → 4.0.8

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 (39) hide show
  1. package/dist/{deflate-C-0XOaix.js → deflate--aCxt3Uk.js} +1 -1
  2. package/dist/{index-CFje_HC1.js → index-BSDNU38l.js} +935 -127
  3. package/dist/index.js +2 -1
  4. package/dist/{jpeg-HJZeqbJ9.js → jpeg-KhuOxw4Z.js} +1 -1
  5. package/dist/{lerc-BtoOWLPL.js → lerc-CElkXFuI.js} +1 -1
  6. package/dist/{lzw-BS2ckxRa.js → lzw-CKBozwxj.js} +1 -1
  7. package/dist/{packbits-8sTofHBF.js → packbits-DBXx1JcY.js} +1 -1
  8. package/dist/{raw-DA0bjume.js → raw-Vb44MxD9.js} +1 -1
  9. package/dist/{webimage-C3yiEsf-.js → webimage-Df6sOKlo.js} +1 -1
  10. package/dist-tsc/CellSetCompositionBarPlot.d.ts.map +1 -1
  11. package/dist-tsc/CellSetCompositionBarPlot.js +87 -53
  12. package/dist-tsc/CellSetCompositionBarPlot.js.map +1 -1
  13. package/dist-tsc/SingleCellSetSelector.d.ts +2 -0
  14. package/dist-tsc/SingleCellSetSelector.d.ts.map +1 -0
  15. package/dist-tsc/SingleCellSetSelector.js +27 -0
  16. package/dist-tsc/SingleCellSetSelector.js.map +1 -0
  17. package/dist-tsc/SingleCellSetSelectorSubscriber.d.ts +2 -0
  18. package/dist-tsc/SingleCellSetSelectorSubscriber.d.ts.map +1 -0
  19. package/dist-tsc/SingleCellSetSelectorSubscriber.js +40 -0
  20. package/dist-tsc/SingleCellSetSelectorSubscriber.js.map +1 -0
  21. package/dist-tsc/VolcanoPlot.d.ts.map +1 -1
  22. package/dist-tsc/VolcanoPlot.js +6 -2
  23. package/dist-tsc/VolcanoPlot.js.map +1 -1
  24. package/dist-tsc/VolcanoPlotOptions.js +2 -2
  25. package/dist-tsc/VolcanoPlotOptions.js.map +1 -1
  26. package/dist-tsc/VolcanoPlotSubscriber.d.ts.map +1 -1
  27. package/dist-tsc/VolcanoPlotSubscriber.js +3 -1
  28. package/dist-tsc/VolcanoPlotSubscriber.js.map +1 -1
  29. package/dist-tsc/index.d.ts +1 -0
  30. package/dist-tsc/index.js +1 -0
  31. package/dist-tsc/index.js.map +1 -1
  32. package/package.json +8 -8
  33. package/src/CellSetCompositionBarPlot.js +102 -57
  34. package/src/SingleCellSetSelector.js +54 -0
  35. package/src/SingleCellSetSelectorSubscriber.js +124 -0
  36. package/src/VolcanoPlot.js +8 -2
  37. package/src/VolcanoPlotOptions.js +2 -2
  38. package/src/VolcanoPlotSubscriber.js +33 -21
  39. package/src/index.js +1 -0
@@ -88,6 +88,7 @@ export default function CellSetCompositionBarPlot(props) {
88
88
  interceptExpectedSample: df.interceptExpectedSample[i],
89
89
  effectExpectedSample: df.effectExpectedSample[i],
90
90
  isCredibleEffect: df.isCredibleEffect[i],
91
+ significanceText: df.isCredibleEffect[i] ? ' ' : 'NS',
91
92
  // Boolean flag for wasReferenceObsSet (check metadata)
92
93
  isReferenceSet: (obsSetId === referenceCellType),
93
94
  };
@@ -134,69 +135,107 @@ export default function CellSetCompositionBarPlot(props) {
134
135
  return undefined;
135
136
  }, [computedData]);
136
137
 
138
+ const nsTextAlign = useMemo(() => {
139
+ if (computedData) {
140
+ const firstNotSig = computedData.find(d => !d.isCredibleEffect);
141
+ return firstNotSig?.logFoldChange < 0 ? 'right' : 'left';
142
+ }
143
+ return 'center';
144
+ }, [computedData]);
145
+
146
+ const areAnySignificant = useMemo(() => computedData?.some(
147
+ d => d.isCredibleEffect,
148
+ ), [computedData]);
149
+
137
150
  const xScale = {
138
151
  domain: xExtent,
139
152
  };
140
153
 
141
154
  const spec = {
142
- mark: { type: 'bar', stroke: 'black', cursor: 'pointer' },
143
- params: [
155
+ layer: [
144
156
  {
145
- name: 'bar_select',
146
- select: {
147
- type: 'point',
148
- on: 'click[event.shiftKey === false]',
149
- fields: ['obsSetPath'],
150
- empty: 'none',
157
+ params: [
158
+ {
159
+ name: 'bar_select',
160
+ select: {
161
+ type: 'point',
162
+ on: 'click[event.shiftKey === false]',
163
+ fields: ['obsSetPath'],
164
+ empty: 'none',
165
+ },
166
+ },
167
+ {
168
+ name: 'shift_bar_select',
169
+ select: {
170
+ type: 'point',
171
+ on: 'click[event.shiftKey]',
172
+ fields: ['obsSetPath'],
173
+ empty: 'none',
174
+ },
175
+ },
176
+ ],
177
+ mark: { type: 'bar', stroke: 'black', cursor: 'pointer' },
178
+ encoding: {
179
+ y: {
180
+ field: 'keyName',
181
+ type: 'nominal',
182
+ axis: { labelExpr: `substring(datum.label, ${keyLength})` },
183
+ title: `${captializedObsType} Set`,
184
+ sort: keys,
185
+ },
186
+ x: {
187
+ // TODO: support using intercept+effect here based on user-selected options?
188
+ field: 'logFoldChange',
189
+ type: 'quantitative',
190
+ title: 'Log fold-change',
191
+ scale: xScale,
192
+ },
193
+ color: {
194
+ field: 'key',
195
+ type: 'nominal',
196
+ scale: colorScale,
197
+ legend: null,
198
+ },
199
+ fillOpacity: {
200
+ field: 'isCredibleEffect',
201
+ type: 'nominal',
202
+ scale: opacityScale,
203
+ },
204
+ strokeWidth: {
205
+ field: 'isReferenceSet',
206
+ type: 'nominal',
207
+ scale: strokeWidthScale,
208
+ legend: null,
209
+ },
210
+ tooltip: {
211
+ field: 'effectExpectedSample',
212
+ type: 'quantitative',
213
+ },
151
214
  },
152
215
  },
153
216
  {
154
- name: 'shift_bar_select',
155
- select: {
156
- type: 'point',
157
- on: 'click[event.shiftKey]',
158
- fields: ['obsSetPath'],
159
- empty: 'none',
217
+ mark: { type: 'text', align: nsTextAlign, dx: nsTextAlign === 'right' ? -5 : 5 },
218
+ encoding: {
219
+ y: {
220
+ field: 'keyName',
221
+ type: 'nominal',
222
+ axis: { labelExpr: `substring(datum.label, ${keyLength})` },
223
+ title: `${captializedObsType} Set`,
224
+ sort: keys,
225
+ },
226
+ x: {
227
+ field: 'logFoldChange',
228
+ type: 'quantitative',
229
+ title: 'Log fold-change',
230
+ scale: xScale,
231
+ },
232
+ text: {
233
+ field: 'significanceText',
234
+ },
160
235
  },
161
236
  },
162
237
  ],
163
- encoding: {
164
- y: {
165
- field: 'keyName',
166
- type: 'nominal',
167
- axis: { labelExpr: `substring(datum.label, ${keyLength})` },
168
- title: `${captializedObsType} Set`,
169
- sort: keys,
170
- },
171
- x: {
172
- // TODO: support using intercept+effect here based on user-selected options?
173
- field: 'logFoldChange',
174
- type: 'quantitative',
175
- title: 'Log fold-change',
176
- scale: xScale,
177
- },
178
- color: {
179
- field: 'key',
180
- type: 'nominal',
181
- scale: colorScale,
182
- legend: null,
183
- },
184
- fillOpacity: {
185
- field: 'isCredibleEffect',
186
- type: 'nominal',
187
- scale: opacityScale,
188
- },
189
- strokeWidth: {
190
- field: 'isReferenceSet',
191
- type: 'nominal',
192
- scale: strokeWidthScale,
193
- legend: null,
194
- },
195
- tooltip: {
196
- field: 'effectExpectedSample',
197
- type: 'quantitative',
198
- },
199
- },
238
+
200
239
  // TODO: for width, also subtract length of longest y-axis set name label.
201
240
  width: clamp(width - marginRight, 10, Infinity),
202
241
  height: clamp(height - marginBottom, 10, Infinity),
@@ -221,11 +260,17 @@ export default function CellSetCompositionBarPlot(props) {
221
260
  ), [captializedObsType]);
222
261
 
223
262
  return (
224
- <VegaPlot
225
- data={computedData}
226
- spec={spec}
227
- signalListeners={signalListeners}
228
- getTooltipText={getTooltipText}
229
- />
263
+ <>
264
+ {areAnySignificant ? (
265
+ <VegaPlot
266
+ data={computedData}
267
+ spec={spec}
268
+ signalListeners={signalListeners}
269
+ getTooltipText={getTooltipText}
270
+ />
271
+ ) : (
272
+ <p>No results were significant for the selected conditions.</p>
273
+ )}
274
+ </>
230
275
  );
231
276
  }
@@ -0,0 +1,54 @@
1
+ import React, { useCallback } from 'react';
2
+ import { Grid, NativeSelect } from '@vitessce/styles';
3
+
4
+ function pathToKey(pathArr) {
5
+ return pathArr.join('___');
6
+ }
7
+
8
+ function keyToPath(keyStr) {
9
+ return keyStr.split('___');
10
+ }
11
+
12
+ export default function SingleCellSetSelector(props) {
13
+ const {
14
+ multiObsSetSelection,
15
+ singleObsSetSelection,
16
+ setSingleObsSetSelection,
17
+ } = props;
18
+
19
+ const handleChange = useCallback((event) => {
20
+ if (event.target.value === '__none__') {
21
+ setSingleObsSetSelection(null);
22
+ } else {
23
+ const nextPath = keyToPath(event.target.value);
24
+ setSingleObsSetSelection([
25
+ nextPath,
26
+ ]);
27
+ }
28
+ }, []);
29
+
30
+ const hasMultiOptions = (
31
+ Array.isArray(multiObsSetSelection)
32
+ && multiObsSetSelection.length > 0
33
+ );
34
+
35
+ return (
36
+ <Grid container size={12}>
37
+ <NativeSelect
38
+ onChange={handleChange}
39
+ value={singleObsSetSelection ? pathToKey(singleObsSetSelection) : '__none__'}
40
+ variant="standard"
41
+ sx={{ width: '100%' }}
42
+ >
43
+ <option value="__none__">None</option>
44
+ {hasMultiOptions ? (
45
+ <>
46
+ {multiObsSetSelection.map(o => (
47
+ <option value={pathToKey(o)}>{o.at(-1)}</option>
48
+ ))}
49
+ </>
50
+ ) : null}
51
+ </NativeSelect>
52
+ </Grid>
53
+ );
54
+ }
@@ -0,0 +1,124 @@
1
+ /* eslint-disable no-unused-vars */
2
+ import React, { useEffect } from 'react';
3
+ import {
4
+ TitleInfo,
5
+ useCoordination,
6
+ useLoaders,
7
+ useReady,
8
+ useFeatureStatsData,
9
+ useMatchingLoader,
10
+ useColumnNameMapping,
11
+ useCoordinationScopes,
12
+ useMultiCoordinationScopes,
13
+ } from '@vitessce/vit-s';
14
+ import {
15
+ ViewType,
16
+ COMPONENT_COORDINATION_TYPES,
17
+ ViewHelpMapping,
18
+ DataType,
19
+ CoordinationType,
20
+ } from '@vitessce/constants-internal';
21
+ import { isEqual } from 'lodash-es';
22
+ import SingleCellSetSelector from './SingleCellSetSelector.js';
23
+
24
+ export function SingleCellSetSelectorSubscriber(props) {
25
+ const {
26
+ title = 'Cell Type of Interest',
27
+ coordinationScopes: coordinationScopesRaw,
28
+ removeGridComponent,
29
+ theme,
30
+ helpText = ViewHelpMapping.SINGLE_OBS_SET_SELECTOR,
31
+ } = props;
32
+
33
+ const loaders = useLoaders();
34
+ const coordinationScopes = useCoordinationScopes(coordinationScopesRaw);
35
+
36
+ // Get "props" from the coordination space.
37
+ const [{
38
+ obsType,
39
+ obsSetColor,
40
+ }, {
41
+ setObsType,
42
+ setObsSetColor,
43
+ }] = useCoordination(
44
+ COMPONENT_COORDINATION_TYPES[ViewType.SINGLE_OBS_SET_SELECTOR],
45
+ coordinationScopes,
46
+ );
47
+
48
+
49
+ const obsSetSelectionScopes = useMultiCoordinationScopes(
50
+ CoordinationType.OBS_SET_SELECTION,
51
+ coordinationScopes,
52
+ );
53
+
54
+ const [{
55
+ obsSetSelection: multiObsSetSelection,
56
+ }, {
57
+ setObsSetSelection: setMultiObsSetSelection,
58
+ }] = useCoordination(
59
+ COMPONENT_COORDINATION_TYPES[ViewType.SINGLE_OBS_SET_SELECTOR],
60
+ { obsSetSelection: obsSetSelectionScopes?.[0] },
61
+ );
62
+
63
+ const [{
64
+ obsSetSelection: singleObsSetSelectionArr,
65
+ }, {
66
+ setObsSetSelection: setSingleObsSetSelection,
67
+ }] = useCoordination(
68
+ COMPONENT_COORDINATION_TYPES[ViewType.SINGLE_OBS_SET_SELECTOR],
69
+ { obsSetSelection: obsSetSelectionScopes?.[1] },
70
+ );
71
+
72
+ const singleObsSetSelection = singleObsSetSelectionArr?.[0];
73
+
74
+
75
+ useEffect(() => {
76
+ // If multiObsSetSelection changes, there are multiple things that can occur:
77
+ // - length is one and does not match the current singleObsSetSelection
78
+ // -> we update singleObsSetSelection.
79
+ // - length is zero or does not contain the current singleObsSetSelection
80
+ // -> we set singleObsSetSelection to null or the first entry of the
81
+ // multiple selection array.
82
+ // - otherwise, do nothing.
83
+
84
+ if (!Array.isArray(multiObsSetSelection) || multiObsSetSelection.length === 0) {
85
+ setSingleObsSetSelection(null);
86
+ } else {
87
+ const multiContainsSingle = multiObsSetSelection.some(
88
+ setPath => isEqual(setPath, singleObsSetSelection),
89
+ );
90
+ if (!multiContainsSingle) {
91
+ setSingleObsSetSelection([multiObsSetSelection?.[0]]);
92
+ }
93
+ }
94
+ }, [multiObsSetSelection]);
95
+
96
+ const errors = [];
97
+
98
+ const isReady = useReady([]);
99
+
100
+ return (
101
+ <TitleInfo
102
+ title={title}
103
+ removeGridComponent={removeGridComponent}
104
+ theme={theme}
105
+ isReady={isReady}
106
+ helpText={helpText}
107
+ errors={errors}
108
+ withPadding={false}
109
+ >
110
+ {Array.isArray(multiObsSetSelection) && multiObsSetSelection.length > 0 ? (
111
+ <SingleCellSetSelector
112
+ theme={theme}
113
+ obsType={obsType}
114
+ obsSetColor={obsSetColor}
115
+ multiObsSetSelection={multiObsSetSelection}
116
+ singleObsSetSelection={singleObsSetSelection}
117
+ setSingleObsSetSelection={setSingleObsSetSelection}
118
+ />
119
+ ) : (
120
+ <p style={{ padding: '12px' }}>Select at least one {obsType} set.</p>
121
+ )}
122
+ </TitleInfo>
123
+ );
124
+ }
@@ -58,7 +58,7 @@ export default function VolcanoPlot(props) {
58
58
  return [null, null];
59
59
  }
60
60
  let xExtentResult = d3_extent(
61
- computedData.flatMap(d => d3_extent(d.df.logFoldChange)),
61
+ computedData.flatMap(d => d3_extent(d.df.logFoldChange.filter(v => Number.isFinite(v)))),
62
62
  );
63
63
  const xAbsMax = Math.max(Math.abs(xExtentResult[0]), Math.abs(xExtentResult[1]));
64
64
  xExtentResult = [-xAbsMax, xAbsMax];
@@ -207,7 +207,13 @@ export default function VolcanoPlot(props) {
207
207
  filteredData.forEach((comparisonObject) => {
208
208
  const obsSetG = g.append('g');
209
209
 
210
- const { df: filteredDf, metadata } = comparisonObject;
210
+ const { df: rawFilteredDf, metadata } = comparisonObject;
211
+ // Points with a NaN logFoldChange or minusLog10p (e.g. missing/invalid p-value) cannot be
212
+ // positioned on the y-axis, even with clamping/jitter, so drop them here.
213
+ // Note that we still try to position points with -inf or inf values below, just not NaN.
214
+ const filteredDf = rawFilteredDf.filter(d => (
215
+ !Number.isNaN(d.minusLog10p) && !Number.isNaN(d.logFoldChange)
216
+ ));
211
217
  const coordinationValues = metadata.coordination_values;
212
218
 
213
219
  const rawObsSetPath = coordinationValues.obsSetFilter
@@ -64,7 +64,7 @@ export default function VolcanoPlotOptions(props) {
64
64
  valueLabelDisplay="auto"
65
65
  step={1}
66
66
  min={0}
67
- max={100}
67
+ max={50}
68
68
  />
69
69
  </TableCell>
70
70
  </TableRow>
@@ -89,7 +89,7 @@ export default function VolcanoPlotOptions(props) {
89
89
  valueLabelDisplay="auto"
90
90
  step={0.5}
91
91
  min={0}
92
- max={50}
92
+ max={10}
93
93
  />
94
94
  </TableCell>
95
95
  </TableRow>
@@ -1,4 +1,5 @@
1
1
  /* eslint-disable no-unused-vars */
2
+ /* eslint-disable no-nested-ternary */
2
3
  import React, { useMemo, useCallback } from 'react';
3
4
  import {
4
5
  TitleInfo,
@@ -122,6 +123,10 @@ export function VolcanoPlotSubscriber(props) {
122
123
  setFeatureSelection([featureId]);
123
124
  }, [setFeatureSelection]);
124
125
 
126
+ const anyNonEmpty = useMemo(() => featureStats?.some(
127
+ d => !d.metadata?.analysis_params?.is_empty,
128
+ ), [featureStats]);
129
+
125
130
  return (
126
131
  <TitleInfo
127
132
  title={title}
@@ -149,28 +154,35 @@ export function VolcanoPlotSubscriber(props) {
149
154
  >
150
155
  <div ref={containerRef} className={classes.vegaContainer}>
151
156
  {featureStats ? (
152
- <VolcanoPlot
153
- theme={theme}
154
- width={width}
155
- height={height}
156
- obsType={obsType}
157
- featureType={featureType}
158
- obsSetsColumnNameMapping={obsSetsColumnNameMapping}
159
- obsSetsColumnNameMappingReversed={obsSetsColumnNameMappingReversed}
160
- sampleSetsColumnNameMapping={sampleSetsColumnNameMapping}
161
- sampleSetsColumnNameMappingReversed={sampleSetsColumnNameMappingReversed}
162
- sampleSetSelection={sampleSetSelection}
163
- obsSetSelection={obsSetSelection}
164
- obsSetColor={obsSetColor}
165
- sampleSetColor={sampleSetColor}
166
- data={featureStats}
167
- onFeatureClick={onFeatureClick}
157
+ anyNonEmpty ? (
158
+ <VolcanoPlot
159
+ theme={theme}
160
+ width={width}
161
+ height={height}
162
+ obsType={obsType}
163
+ featureType={featureType}
164
+ obsSetsColumnNameMapping={obsSetsColumnNameMapping}
165
+ obsSetsColumnNameMappingReversed={obsSetsColumnNameMappingReversed}
166
+ sampleSetsColumnNameMapping={sampleSetsColumnNameMapping}
167
+ sampleSetsColumnNameMappingReversed={sampleSetsColumnNameMappingReversed}
168
+ sampleSetSelection={sampleSetSelection}
169
+ obsSetSelection={obsSetSelection}
170
+ obsSetColor={obsSetColor}
171
+ sampleSetColor={sampleSetColor}
172
+ data={featureStats}
173
+ onFeatureClick={onFeatureClick}
168
174
 
169
- featurePointSignificanceThreshold={featurePointSignificanceThreshold}
170
- featurePointFoldChangeThreshold={featurePointFoldChangeThreshold}
171
- featureLabelSignificanceThreshold={featureLabelSignificanceThreshold}
172
- featureLabelFoldChangeThreshold={featureLabelFoldChangeThreshold}
173
- />
175
+ featurePointSignificanceThreshold={featurePointSignificanceThreshold}
176
+ featurePointFoldChangeThreshold={featurePointFoldChangeThreshold}
177
+ featureLabelSignificanceThreshold={featureLabelSignificanceThreshold}
178
+ featureLabelFoldChangeThreshold={featureLabelFoldChangeThreshold}
179
+ />
180
+ ) : (
181
+ <span>
182
+ Differential expression tests were not performed
183
+ for the currently selected groups due to insufficient data.
184
+ </span>
185
+ )
174
186
  ) : (
175
187
  <span>Select at least one {obsType} set.</span>
176
188
  )}
package/src/index.js CHANGED
@@ -8,6 +8,7 @@ export { VolcanoPlotSubscriber } from './VolcanoPlotSubscriber.js';
8
8
  export { CellSetCompositionBarPlotSubscriber } from './CellSetCompositionBarPlotSubscriber.js';
9
9
  export { FeatureSetEnrichmentBarPlotSubscriber } from './FeatureSetEnrichmentBarPlotSubscriber.js';
10
10
  export { FeatureStatsTableSubscriber } from './FeatureStatsTableSubscriber.js';
11
+ export { SingleCellSetSelectorSubscriber } from './SingleCellSetSelectorSubscriber.js';
11
12
  export { default as CellSetSizesPlot } from './CellSetSizesPlot.js';
12
13
  export { default as CellSetExpressionPlot } from './CellSetExpressionPlot.js';
13
14
  export { default as ExpressionHistogram } from './ExpressionHistogram.js';