@vitessce/statistical-plots 3.5.6 → 3.5.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 (60) hide show
  1. package/dist/{deflate-70ede287.js → deflate-1679ef33.js} +1 -1
  2. package/dist/{index-07a7a93a.js → index-0f4fe21d.js} +2926 -1090
  3. package/dist/index.js +10 -6
  4. package/dist/{jpeg-00a52550.js → jpeg-280f0ee1.js} +1 -1
  5. package/dist/{lerc-55c1ff7e.js → lerc-12264a36.js} +1 -1
  6. package/dist/{lzw-b906a3b9.js → lzw-70f852cc.js} +1 -1
  7. package/dist/{packbits-10b5f4aa.js → packbits-393c67b2.js} +1 -1
  8. package/dist/{raw-9317b0fd.js → raw-d8d7ab7f.js} +1 -1
  9. package/dist/{webimage-1c43145b.js → webimage-5d24a8e2.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/CellSetExpressionPlot.d.ts.map +1 -1
  17. package/dist-tsc/CellSetExpressionPlot.js +2 -1
  18. package/dist-tsc/FeatureSetEnrichmentBarPlot.d.ts +5 -0
  19. package/dist-tsc/FeatureSetEnrichmentBarPlot.d.ts.map +1 -0
  20. package/dist-tsc/FeatureSetEnrichmentBarPlot.js +164 -0
  21. package/dist-tsc/FeatureSetEnrichmentBarPlotSubscriber.d.ts +2 -0
  22. package/dist-tsc/FeatureSetEnrichmentBarPlotSubscriber.d.ts.map +1 -0
  23. package/dist-tsc/FeatureSetEnrichmentBarPlotSubscriber.js +51 -0
  24. package/dist-tsc/Treemap.d.ts +11 -0
  25. package/dist-tsc/Treemap.d.ts.map +1 -0
  26. package/dist-tsc/Treemap.js +154 -0
  27. package/dist-tsc/TreemapOptions.d.ts +2 -0
  28. package/dist-tsc/TreemapOptions.d.ts.map +1 -0
  29. package/dist-tsc/TreemapOptions.js +29 -0
  30. package/dist-tsc/TreemapSubscriber.d.ts +2 -0
  31. package/dist-tsc/TreemapSubscriber.d.ts.map +1 -0
  32. package/dist-tsc/TreemapSubscriber.js +103 -0
  33. package/dist-tsc/VolcanoPlot.d.ts +2 -0
  34. package/dist-tsc/VolcanoPlot.d.ts.map +1 -0
  35. package/dist-tsc/VolcanoPlot.js +230 -0
  36. package/dist-tsc/VolcanoPlotOptions.d.ts +2 -0
  37. package/dist-tsc/VolcanoPlotOptions.d.ts.map +1 -0
  38. package/dist-tsc/VolcanoPlotOptions.js +23 -0
  39. package/dist-tsc/VolcanoPlotSubscriber.d.ts +2 -0
  40. package/dist-tsc/VolcanoPlotSubscriber.d.ts.map +1 -0
  41. package/dist-tsc/VolcanoPlotSubscriber.js +33 -0
  42. package/dist-tsc/index.d.ts +4 -0
  43. package/dist-tsc/index.js +4 -0
  44. package/dist-tsc/utils.d.ts +9 -0
  45. package/dist-tsc/utils.d.ts.map +1 -0
  46. package/dist-tsc/utils.js +40 -0
  47. package/package.json +8 -7
  48. package/src/CellSetCompositionBarPlot.js +205 -0
  49. package/src/CellSetCompositionBarPlotSubscriber.js +151 -0
  50. package/src/CellSetExpressionPlot.js +4 -1
  51. package/src/FeatureSetEnrichmentBarPlot.js +203 -0
  52. package/src/FeatureSetEnrichmentBarPlotSubscriber.js +166 -0
  53. package/src/Treemap.js +202 -0
  54. package/src/TreemapOptions.js +90 -0
  55. package/src/TreemapSubscriber.js +262 -0
  56. package/src/VolcanoPlot.js +313 -0
  57. package/src/VolcanoPlotOptions.js +136 -0
  58. package/src/VolcanoPlotSubscriber.js +162 -0
  59. package/src/index.js +4 -0
  60. package/src/utils.js +47 -0
@@ -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 ADDED
@@ -0,0 +1,202 @@
1
+ /* eslint-disable indent */
2
+ /* eslint-disable camelcase */
3
+ import React, { useMemo, useEffect, useRef } from 'react';
4
+ import { select } from 'd3-selection';
5
+ import { treemap, treemapBinary, hierarchy as d3_hierarchy } from 'd3-hierarchy';
6
+ import { rollup as d3_rollup } from 'd3-array';
7
+ import { isEqual } from 'lodash-es';
8
+ import { pluralize as plur } from '@vitessce/utils';
9
+ import { getColorScale } from './utils.js';
10
+
11
+ // Based on Observable's built-in DOM.uid function.
12
+ // This is intended to be used with SVG clipPaths
13
+ // which require a unique href value to reference
14
+ // other elements contained in the DOM.
15
+ function uidGenerator(prefix) {
16
+ let i = 0;
17
+ return () => {
18
+ i += 1;
19
+ return { id: `${prefix}-${i}`, href: `#${prefix}-${i}` };
20
+ };
21
+ }
22
+
23
+ /**
24
+ * Renders a treemap plot using D3.
25
+ * References:
26
+ * - https://observablehq.com/@d3/treemap-component
27
+ * - https://observablehq.com/@d3/treemap-stratify
28
+ * - https://observablehq.com/@d3/json-treemap
29
+ * - https://observablehq.com/@d3/nested-treemap
30
+ * @returns
31
+ */
32
+ export default function Treemap(props) {
33
+ const {
34
+ obsCounts,
35
+ obsColorEncoding,
36
+ hierarchyLevels,
37
+ theme,
38
+ width,
39
+ height,
40
+ obsType,
41
+ sampleType,
42
+ obsSetColor,
43
+ sampleSetColor,
44
+ obsSetSelection,
45
+ sampleSetSelection,
46
+ marginTop = 5,
47
+ marginRight = 5,
48
+ marginLeft = 80,
49
+ marginBottom,
50
+ } = props;
51
+
52
+ const hierarchyData = useMemo(() => {
53
+ // Support both sampleSet->obsSet and
54
+ // obsSet->sampleSet hierarchy modes
55
+ if (!obsCounts) {
56
+ return null;
57
+ }
58
+ let map;
59
+ if (isEqual(hierarchyLevels, ['sampleSet', 'obsSet'])) {
60
+ map = d3_rollup(
61
+ obsCounts,
62
+ D => D[0].value,
63
+ d => d.sampleSetPath,
64
+ d => d.obsSetPath,
65
+ );
66
+ } else if (isEqual(hierarchyLevels, ['obsSet', 'sampleSet'])) {
67
+ map = d3_rollup(
68
+ obsCounts,
69
+ D => D[0].value,
70
+ d => d.obsSetPath,
71
+ d => d.sampleSetPath,
72
+ );
73
+ } else {
74
+ throw new Error('Unexpected levels value.');
75
+ }
76
+ return d3_hierarchy(map);
77
+ }, [obsCounts, hierarchyLevels]);
78
+
79
+ const [obsSetColorScale, sampleSetColorScale] = useMemo(() => [
80
+ getColorScale(obsSetSelection, obsSetColor, theme),
81
+ getColorScale(sampleSetSelection, sampleSetColor, theme),
82
+ ], [obsSetSelection, sampleSetSelection, sampleSetColor, obsSetColor, theme]);
83
+
84
+ const treemapLeaves = useMemo(() => {
85
+ const treemapFunc = treemap()
86
+ .tile(treemapBinary)
87
+ .size([width, height])
88
+ .padding(1)
89
+ .round(true);
90
+
91
+ // When d3.hierarchy is passed a Map object,
92
+ // the nodes are represented like [key, value] tuples.
93
+ // So in `.sum` and `.sort` below,
94
+ // `d[1]` accesses the value (i.e., cell count).
95
+ // Reference: https://d3js.org/d3-hierarchy/hierarchy#hierarchy
96
+ const treemapLayout = treemapFunc(hierarchyData
97
+ .sum(d => d[1])
98
+ .sort((a, b) => b[1] - a[1]));
99
+ return treemapLayout.leaves();
100
+ }, [hierarchyData, width, height]);
101
+
102
+ const svgRef = useRef();
103
+
104
+ useEffect(() => {
105
+ const domElement = svgRef.current;
106
+
107
+ const svg = select(domElement);
108
+ svg.selectAll('g').remove();
109
+ svg
110
+ .attr('width', width)
111
+ .attr('height', height)
112
+ .attr('viewBox', [0, 0, width, height])
113
+ .attr('style', 'font: 10px sans-serif');
114
+
115
+ if (!treemapLeaves || !obsSetSelection || !sampleSetSelection) {
116
+ return;
117
+ }
118
+
119
+ // Add a group for each leaf of the hierarchy.
120
+ const leaf = svg.selectAll('g')
121
+ .data(treemapLeaves)
122
+ .join('g')
123
+ .attr('transform', d => `translate(${d.x0},${d.y0})`);
124
+
125
+ // Append a tooltip.
126
+ leaf.append('title')
127
+ .text((d) => {
128
+ const cellCount = d.data?.[1];
129
+ const primaryPathString = JSON.stringify(d.data[0]);
130
+ const secondaryPathString = JSON.stringify(d.parent.data[0]);
131
+ return `${cellCount.toLocaleString()} ${plur(obsType, cellCount)} in ${primaryPathString} and ${secondaryPathString}`;
132
+ });
133
+
134
+ const getLeafUid = uidGenerator('leaf');
135
+ const getClipUid = uidGenerator('clip');
136
+
137
+ const colorScale = obsColorEncoding === 'sampleSetSelection'
138
+ ? sampleSetColorScale
139
+ : obsSetColorScale;
140
+ const getPathForColoring = d => (
141
+ // eslint-disable-next-line no-nested-ternary
142
+ obsColorEncoding === 'sampleSetSelection'
143
+ ? (hierarchyLevels[0] === 'obsSet' ? d.data?.[0] : d.parent?.data?.[0])
144
+ : (hierarchyLevels[0] === 'sampleSet' ? d.data?.[0] : d.parent?.data?.[0])
145
+ );
146
+
147
+ // Append a color rectangle for each leaf.
148
+ leaf.append('rect')
149
+ .attr('id', (d) => {
150
+ // eslint-disable-next-line no-param-reassign
151
+ d.leafUid = getLeafUid();
152
+ return d.leafUid.id;
153
+ })
154
+ .attr('fill', d => colorScale(getPathForColoring(d)))
155
+ .attr('fill-opacity', 0.8)
156
+ .attr('width', d => d.x1 - d.x0)
157
+ .attr('height', d => d.y1 - d.y0);
158
+
159
+ // Append a clipPath to ensure text does not overflow.
160
+ leaf.append('clipPath')
161
+ .attr('id', (d) => {
162
+ // eslint-disable-next-line no-param-reassign
163
+ d.clipUid = getClipUid();
164
+ return d.clipUid.id;
165
+ })
166
+ .append('use')
167
+ .attr('xlink:href', d => d.leafUid.href);
168
+
169
+ // Append multiline text.
170
+ leaf.append('text')
171
+ .attr('clip-path', d => `url(${d.clipUid.href})`)
172
+ .selectAll('tspan')
173
+ .data(d => ([
174
+ // Each element in this array corresponds to a line of text.
175
+ d.data?.[0]?.at(-1),
176
+ d.parent?.data?.[0]?.at(-1),
177
+ `${d.data?.[1].toLocaleString()} ${plur(obsType, d.data?.[1])}`,
178
+ ]))
179
+ .join('tspan')
180
+ .attr('x', 3)
181
+ // eslint-disable-next-line no-unused-vars
182
+ .attr('y', (d, i, nodes) => `${(i === nodes.length - 1) * 0.3 + 1.1 + i * 0.9}em`)
183
+ .text(d => d);
184
+ }, [width, height, marginLeft, marginBottom, theme, marginTop, marginRight,
185
+ obsType, sampleType, treemapLeaves, sampleSetColor, sampleSetSelection,
186
+ obsSetSelection, obsSetColor, obsSetColorScale, sampleSetColorScale,
187
+ obsColorEncoding, hierarchyLevels,
188
+ ]);
189
+
190
+ return (
191
+ <svg
192
+ ref={svgRef}
193
+ style={{
194
+ top: 0,
195
+ left: 0,
196
+ width: `${width}px`,
197
+ height: `${height}px`,
198
+ position: 'relative',
199
+ }}
200
+ />
201
+ );
202
+ }
@@ -0,0 +1,90 @@
1
+ import React from 'react';
2
+ import { useId } from 'react-aria';
3
+ import { isEqual } from 'lodash-es';
4
+ import { TableCell, TableRow } from '@material-ui/core';
5
+ import { capitalize } from '@vitessce/utils';
6
+ import {
7
+ usePlotOptionsStyles, OptionSelect, OptionsContainer,
8
+ } from '@vitessce/vit-s';
9
+
10
+ export default function TreemapOptions(props) {
11
+ const {
12
+ children,
13
+ obsType,
14
+ sampleType,
15
+
16
+ hierarchyLevels,
17
+ setHierarchyLevels,
18
+
19
+ obsColorEncoding,
20
+ setObsColorEncoding,
21
+
22
+ } = props;
23
+
24
+ const treemapOptionsId = useId();
25
+ const classes = usePlotOptionsStyles();
26
+
27
+ function handleColorEncodingChange(event) {
28
+ setObsColorEncoding(event.target.value);
29
+ }
30
+
31
+ function handleHierarchyLevelsOrderingChange(event) {
32
+ if (event.target.value === 'sampleSet') {
33
+ setHierarchyLevels(['sampleSet', 'obsSet']);
34
+ } else {
35
+ setHierarchyLevels(['obsSet', 'sampleSet']);
36
+ }
37
+ }
38
+
39
+ const primaryHierarchyLevel = isEqual(hierarchyLevels, ['sampleSet', 'obsSet']) ? 'sampleSet' : 'obsSet';
40
+
41
+ return (
42
+ <OptionsContainer>
43
+ {children}
44
+ <TableRow>
45
+ <TableCell className={classes.labelCell} variant="head" scope="row">
46
+ <label
47
+ htmlFor={`cell-color-encoding-select-${treemapOptionsId}`}
48
+ >
49
+ Color Encoding
50
+ </label>
51
+ </TableCell>
52
+ <TableCell className={classes.inputCell} variant="body">
53
+ <OptionSelect
54
+ className={classes.select}
55
+ value={obsColorEncoding}
56
+ onChange={handleColorEncodingChange}
57
+ inputProps={{
58
+ id: `cell-color-encoding-select-${treemapOptionsId}`,
59
+ }}
60
+ >
61
+ <option value="cellSetSelection">{capitalize(obsType)} Sets</option>
62
+ <option value="sampleSetSelection">{capitalize(sampleType)} Sets</option>
63
+ </OptionSelect>
64
+ </TableCell>
65
+ </TableRow>
66
+ <TableRow>
67
+ <TableCell className={classes.labelCell} variant="head" scope="row">
68
+ <label
69
+ htmlFor={`treemap-set-hierarchy-levels-${treemapOptionsId}`}
70
+ >
71
+ Primary Hierarchy Level
72
+ </label>
73
+ </TableCell>
74
+ <TableCell className={classes.inputCell} variant="body">
75
+ <OptionSelect
76
+ className={classes.select}
77
+ value={primaryHierarchyLevel}
78
+ onChange={handleHierarchyLevelsOrderingChange}
79
+ inputProps={{
80
+ id: `hierarchy-level-select-${treemapOptionsId}`,
81
+ }}
82
+ >
83
+ <option value="obsSet">{capitalize(obsType)} Sets</option>
84
+ <option value="sampleSet">{capitalize(sampleType)} Sets</option>
85
+ </OptionSelect>
86
+ </TableCell>
87
+ </TableRow>
88
+ </OptionsContainer>
89
+ );
90
+ }