@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,154 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /* eslint-disable indent */
3
+ /* eslint-disable camelcase */
4
+ import React, { useMemo, useEffect, useRef } from 'react';
5
+ import { select } from 'd3-selection';
6
+ import { treemap, treemapBinary, hierarchy as d3_hierarchy } from 'd3-hierarchy';
7
+ import { rollup as d3_rollup } from 'd3-array';
8
+ import { isEqual } from 'lodash-es';
9
+ import { pluralize as plur } from '@vitessce/utils';
10
+ import { getColorScale } from './utils.js';
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
+ * Renders a treemap plot using D3.
24
+ * References:
25
+ * - https://observablehq.com/@d3/treemap-component
26
+ * - https://observablehq.com/@d3/treemap-stratify
27
+ * - https://observablehq.com/@d3/json-treemap
28
+ * - https://observablehq.com/@d3/nested-treemap
29
+ * @returns
30
+ */
31
+ export default function Treemap(props) {
32
+ const { obsCounts, obsColorEncoding, hierarchyLevels, theme, width, height, obsType, sampleType, obsSetColor, sampleSetColor, obsSetSelection, sampleSetSelection, marginTop = 5, marginRight = 5, marginLeft = 80, marginBottom, } = props;
33
+ const hierarchyData = useMemo(() => {
34
+ // Support both sampleSet->obsSet and
35
+ // obsSet->sampleSet hierarchy modes
36
+ if (!obsCounts) {
37
+ return null;
38
+ }
39
+ let map;
40
+ if (isEqual(hierarchyLevels, ['sampleSet', 'obsSet'])) {
41
+ map = d3_rollup(obsCounts, D => D[0].value, d => d.sampleSetPath, d => d.obsSetPath);
42
+ }
43
+ else if (isEqual(hierarchyLevels, ['obsSet', 'sampleSet'])) {
44
+ map = d3_rollup(obsCounts, D => D[0].value, d => d.obsSetPath, d => d.sampleSetPath);
45
+ }
46
+ else {
47
+ throw new Error('Unexpected levels value.');
48
+ }
49
+ return d3_hierarchy(map);
50
+ }, [obsCounts, hierarchyLevels]);
51
+ const [obsSetColorScale, sampleSetColorScale] = useMemo(() => [
52
+ getColorScale(obsSetSelection, obsSetColor, theme),
53
+ getColorScale(sampleSetSelection, sampleSetColor, theme),
54
+ ], [obsSetSelection, sampleSetSelection, sampleSetColor, obsSetColor, theme]);
55
+ const treemapLeaves = useMemo(() => {
56
+ const treemapFunc = treemap()
57
+ .tile(treemapBinary)
58
+ .size([width, height])
59
+ .padding(1)
60
+ .round(true);
61
+ // When d3.hierarchy is passed a Map object,
62
+ // the nodes are represented like [key, value] tuples.
63
+ // So in `.sum` and `.sort` below,
64
+ // `d[1]` accesses the value (i.e., cell count).
65
+ // Reference: https://d3js.org/d3-hierarchy/hierarchy#hierarchy
66
+ const treemapLayout = treemapFunc(hierarchyData
67
+ .sum(d => d[1])
68
+ .sort((a, b) => b[1] - a[1]));
69
+ return treemapLayout.leaves();
70
+ }, [hierarchyData, width, height]);
71
+ const svgRef = useRef();
72
+ useEffect(() => {
73
+ const domElement = svgRef.current;
74
+ const svg = select(domElement);
75
+ svg.selectAll('g').remove();
76
+ svg
77
+ .attr('width', width)
78
+ .attr('height', height)
79
+ .attr('viewBox', [0, 0, width, height])
80
+ .attr('style', 'font: 10px sans-serif');
81
+ if (!treemapLeaves || !obsSetSelection || !sampleSetSelection) {
82
+ return;
83
+ }
84
+ // Add a group for each leaf of the hierarchy.
85
+ const leaf = svg.selectAll('g')
86
+ .data(treemapLeaves)
87
+ .join('g')
88
+ .attr('transform', d => `translate(${d.x0},${d.y0})`);
89
+ // Append a tooltip.
90
+ leaf.append('title')
91
+ .text((d) => {
92
+ const cellCount = d.data?.[1];
93
+ const primaryPathString = JSON.stringify(d.data[0]);
94
+ const secondaryPathString = JSON.stringify(d.parent.data[0]);
95
+ return `${cellCount.toLocaleString()} ${plur(obsType, cellCount)} in ${primaryPathString} and ${secondaryPathString}`;
96
+ });
97
+ const getLeafUid = uidGenerator('leaf');
98
+ const getClipUid = uidGenerator('clip');
99
+ const colorScale = obsColorEncoding === 'sampleSetSelection'
100
+ ? sampleSetColorScale
101
+ : obsSetColorScale;
102
+ const getPathForColoring = d => (
103
+ // eslint-disable-next-line no-nested-ternary
104
+ obsColorEncoding === 'sampleSetSelection'
105
+ ? (hierarchyLevels[0] === 'obsSet' ? d.data?.[0] : d.parent?.data?.[0])
106
+ : (hierarchyLevels[0] === 'sampleSet' ? d.data?.[0] : d.parent?.data?.[0]));
107
+ // Append a color rectangle for each leaf.
108
+ leaf.append('rect')
109
+ .attr('id', (d) => {
110
+ // eslint-disable-next-line no-param-reassign
111
+ d.leafUid = getLeafUid();
112
+ return d.leafUid.id;
113
+ })
114
+ .attr('fill', d => colorScale(getPathForColoring(d)))
115
+ .attr('fill-opacity', 0.8)
116
+ .attr('width', d => d.x1 - d.x0)
117
+ .attr('height', d => d.y1 - d.y0);
118
+ // Append a clipPath to ensure text does not overflow.
119
+ leaf.append('clipPath')
120
+ .attr('id', (d) => {
121
+ // eslint-disable-next-line no-param-reassign
122
+ d.clipUid = getClipUid();
123
+ return d.clipUid.id;
124
+ })
125
+ .append('use')
126
+ .attr('xlink:href', d => d.leafUid.href);
127
+ // Append multiline text.
128
+ leaf.append('text')
129
+ .attr('clip-path', d => `url(${d.clipUid.href})`)
130
+ .selectAll('tspan')
131
+ .data(d => ([
132
+ // Each element in this array corresponds to a line of text.
133
+ d.data?.[0]?.at(-1),
134
+ d.parent?.data?.[0]?.at(-1),
135
+ `${d.data?.[1].toLocaleString()} ${plur(obsType, d.data?.[1])}`,
136
+ ]))
137
+ .join('tspan')
138
+ .attr('x', 3)
139
+ // eslint-disable-next-line no-unused-vars
140
+ .attr('y', (d, i, nodes) => `${(i === nodes.length - 1) * 0.3 + 1.1 + i * 0.9}em`)
141
+ .text(d => d);
142
+ }, [width, height, marginLeft, marginBottom, theme, marginTop, marginRight,
143
+ obsType, sampleType, treemapLeaves, sampleSetColor, sampleSetSelection,
144
+ obsSetSelection, obsSetColor, obsSetColorScale, sampleSetColorScale,
145
+ obsColorEncoding, hierarchyLevels,
146
+ ]);
147
+ return (_jsx("svg", { ref: svgRef, style: {
148
+ top: 0,
149
+ left: 0,
150
+ width: `${width}px`,
151
+ height: `${height}px`,
152
+ position: 'relative',
153
+ } }));
154
+ }
@@ -0,0 +1,2 @@
1
+ export default function TreemapOptions(props: any): JSX.Element;
2
+ //# sourceMappingURL=TreemapOptions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TreemapOptions.d.ts","sourceRoot":"","sources":["../src/TreemapOptions.js"],"names":[],"mappings":"AASA,gEAgFC"}
@@ -0,0 +1,29 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import { useId } from 'react-aria';
4
+ import { isEqual } from 'lodash-es';
5
+ import { TableCell, TableRow } from '@material-ui/core';
6
+ import { capitalize } from '@vitessce/utils';
7
+ import { usePlotOptionsStyles, OptionSelect, OptionsContainer, } from '@vitessce/vit-s';
8
+ export default function TreemapOptions(props) {
9
+ const { children, obsType, sampleType, hierarchyLevels, setHierarchyLevels, obsColorEncoding, setObsColorEncoding, } = props;
10
+ const treemapOptionsId = useId();
11
+ const classes = usePlotOptionsStyles();
12
+ function handleColorEncodingChange(event) {
13
+ setObsColorEncoding(event.target.value);
14
+ }
15
+ function handleHierarchyLevelsOrderingChange(event) {
16
+ if (event.target.value === 'sampleSet') {
17
+ setHierarchyLevels(['sampleSet', 'obsSet']);
18
+ }
19
+ else {
20
+ setHierarchyLevels(['obsSet', 'sampleSet']);
21
+ }
22
+ }
23
+ const primaryHierarchyLevel = isEqual(hierarchyLevels, ['sampleSet', 'obsSet']) ? 'sampleSet' : 'obsSet';
24
+ return (_jsxs(OptionsContainer, { children: [children, _jsxs(TableRow, { children: [_jsx(TableCell, { className: classes.labelCell, variant: "head", scope: "row", children: _jsx("label", { htmlFor: `cell-color-encoding-select-${treemapOptionsId}`, children: "Color Encoding" }) }), _jsx(TableCell, { className: classes.inputCell, variant: "body", children: _jsxs(OptionSelect, { className: classes.select, value: obsColorEncoding, onChange: handleColorEncodingChange, inputProps: {
25
+ id: `cell-color-encoding-select-${treemapOptionsId}`,
26
+ }, children: [_jsxs("option", { value: "cellSetSelection", children: [capitalize(obsType), " Sets"] }), _jsxs("option", { value: "sampleSetSelection", children: [capitalize(sampleType), " Sets"] })] }) })] }), _jsxs(TableRow, { children: [_jsx(TableCell, { className: classes.labelCell, variant: "head", scope: "row", children: _jsx("label", { htmlFor: `treemap-set-hierarchy-levels-${treemapOptionsId}`, children: "Primary Hierarchy Level" }) }), _jsx(TableCell, { className: classes.inputCell, variant: "body", children: _jsxs(OptionSelect, { className: classes.select, value: primaryHierarchyLevel, onChange: handleHierarchyLevelsOrderingChange, inputProps: {
27
+ id: `hierarchy-level-select-${treemapOptionsId}`,
28
+ }, children: [_jsxs("option", { value: "obsSet", children: [capitalize(obsType), " Sets"] }), _jsxs("option", { value: "sampleSet", children: [capitalize(sampleType), " Sets"] })] }) })] })] }));
29
+ }
@@ -0,0 +1,2 @@
1
+ export function TreemapSubscriber(props: any): JSX.Element;
2
+ //# sourceMappingURL=TreemapSubscriber.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TreemapSubscriber.d.ts","sourceRoot":"","sources":["../src/TreemapSubscriber.js"],"names":[],"mappings":"AAyBA,2DA4OC"}
@@ -0,0 +1,103 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /* eslint-disable no-unused-vars */
3
+ import React, { useMemo } from 'react';
4
+ import { TitleInfo, useCoordination, useLoaders, useUrls, useReady, useGridItemSize, useObsFeatureMatrixIndices, useObsSetsData, useSampleEdgesData, useSampleSetsData, } from '@vitessce/vit-s';
5
+ import { ViewType, COMPONENT_COORDINATION_TYPES, ViewHelpMapping } from '@vitessce/constants-internal';
6
+ import { treeToSelectedSetMap, treeToSetSizesBySetNames, mergeObsSets } from '@vitessce/sets-utils';
7
+ import { pluralize as plur, commaNumber, unnestMap, capitalize } from '@vitessce/utils';
8
+ import { InternMap } from 'internmap';
9
+ import { isEqual } from 'lodash-es';
10
+ import Treemap from './Treemap.js';
11
+ import { useStyles } from './styles.js';
12
+ import TreemapOptions from './TreemapOptions.js';
13
+ const DEFAULT_HIERARCHY_LEVELS = ['obsSet', 'sampleSet'];
14
+ export function TreemapSubscriber(props) {
15
+ const { coordinationScopes, removeGridComponent, theme, helpText = ViewHelpMapping.TREEMAP, } = props;
16
+ const classes = useStyles();
17
+ const loaders = useLoaders();
18
+ // Get "props" from the coordination space.
19
+ const [{ dataset, obsType, featureType, featureValueType, obsFilter, obsHighlight, obsSetSelection, obsSetFilter, obsSelection, obsSelectionMode, obsSetHighlight, obsSetColor, obsColorEncoding, additionalObsSets, sampleType, sampleSetSelection, sampleSetFilter, sampleSetColor, sampleSelection, sampleSelectionMode, sampleFilter, sampleFilterMode, sampleHighlight, hierarchyLevels, }, { setObsFilter, setObsSelection, setObsSetFilter, setObsSetSelection, setObsSelectionMode, setObsFilterMode, setObsHighlight, setObsSetColor, setObsColorEncoding, setAdditionalObsSets, setSampleFilter, setSampleSetFilter, setSampleFilterMode, setSampleSelection, setSampleSetSelection, setSampleSelectionMode, setSampleHighlight, setSampleSetColor, setHierarchyLevels, }] = useCoordination(COMPONENT_COORDINATION_TYPES[ViewType.TREEMAP], coordinationScopes);
20
+ const [width, height, containerRef] = useGridItemSize();
21
+ // TODO: how to deal with multimodal cases (multiple obsIndex, one per modality)?
22
+ const [{ obsIndex }, matrixIndicesStatus, matrixIndicesUrls] = useObsFeatureMatrixIndices(loaders, dataset, false, { obsType, featureType, featureValueType });
23
+ const [{ obsSets }, obsSetsStatus, obsSetsUrls] = useObsSetsData(loaders, dataset, true, {}, {}, { obsType });
24
+ const [{ sampleIndex, sampleSets }, sampleSetsStatus, sampleSetsUrls] = useSampleSetsData(loaders, dataset,
25
+ // TODO: support `false`, i.e., configurations in which
26
+ // there are no sampleSets
27
+ true, { setSampleSetColor }, { sampleSetColor }, { sampleType });
28
+ const [{ sampleEdges }, sampleEdgesStatus, sampleEdgesUrls] = useSampleEdgesData(loaders, dataset,
29
+ // TODO: support `false`, i.e., configurations in which
30
+ // there are no sampleEdges
31
+ true, {}, {}, { obsType, sampleType });
32
+ const isReady = useReady([
33
+ matrixIndicesStatus,
34
+ obsSetsStatus,
35
+ sampleSetsStatus,
36
+ sampleEdgesStatus,
37
+ ]);
38
+ const urls = useUrls([
39
+ matrixIndicesUrls,
40
+ obsSetsUrls,
41
+ sampleSetsUrls,
42
+ sampleEdgesUrls,
43
+ ]);
44
+ const mergedObsSets = useMemo(() => mergeObsSets(obsSets, additionalObsSets), [obsSets, additionalObsSets]);
45
+ const mergedSampleSets = useMemo(() => mergeObsSets(sampleSets, null), [sampleSets]);
46
+ const obsCount = obsIndex?.length || 0;
47
+ const sampleCount = sampleIndex?.length || 0;
48
+ // TODO: use obsFilter / sampleFilter to display
49
+ // _all_ cells/samples in gray / transparent in background,
50
+ // and use obsSetSelection/sampleSetSelection to display
51
+ // the _selected_ samples in color in the foreground.
52
+ const [obsCounts, sampleCounts] = useMemo(() => {
53
+ const obsResult = new InternMap([], JSON.stringify);
54
+ const sampleResult = new InternMap([], JSON.stringify);
55
+ const hasSampleSetSelection = (Array.isArray(sampleSetSelection)
56
+ && sampleSetSelection.length > 0);
57
+ const hasCellSetSelection = (Array.isArray(obsSetSelection)
58
+ && obsSetSelection.length > 0);
59
+ const sampleSetKeys = hasSampleSetSelection ? sampleSetSelection : [null];
60
+ const cellSetKeys = hasCellSetSelection ? obsSetSelection : [null];
61
+ // First level: cell set
62
+ cellSetKeys.forEach((cellSetKey) => {
63
+ obsResult.set(cellSetKey, new InternMap([], JSON.stringify));
64
+ // Second level: sample set
65
+ sampleSetKeys.forEach((sampleSetKey) => {
66
+ obsResult.get(cellSetKey).set(sampleSetKey, 0);
67
+ });
68
+ });
69
+ const sampleSetSizes = treeToSetSizesBySetNames(mergedSampleSets, sampleSetSelection, sampleSetSelection, sampleSetColor, theme);
70
+ sampleSetKeys.forEach((sampleSetKey) => {
71
+ const sampleSetSize = sampleSetSizes.find(d => isEqual(d.setNamePath, sampleSetKey))?.size;
72
+ sampleResult.set(sampleSetKey, sampleSetSize || 0);
73
+ });
74
+ if (mergedObsSets && obsSetSelection) {
75
+ const sampleIdToSetMap = sampleSets && sampleSetSelection
76
+ ? treeToSelectedSetMap(sampleSets, sampleSetSelection)
77
+ : null;
78
+ const cellIdToSetMap = treeToSelectedSetMap(mergedObsSets, obsSetSelection);
79
+ for (let i = 0; i < obsIndex.length; i += 1) {
80
+ const obsId = obsIndex[i];
81
+ const cellSet = cellIdToSetMap?.get(obsId);
82
+ const sampleId = sampleEdges?.get(obsId);
83
+ const sampleSet = sampleId ? sampleIdToSetMap?.get(sampleId) : null;
84
+ if (hasSampleSetSelection && !sampleSet) {
85
+ // Skip this sample if it is not in the selected sample set.
86
+ // eslint-disable-next-line no-continue
87
+ continue;
88
+ }
89
+ const prevObsCount = obsResult.get(cellSet)?.get(sampleSet);
90
+ obsResult.get(cellSet)?.set(sampleSet, prevObsCount + 1);
91
+ }
92
+ }
93
+ return [
94
+ unnestMap(obsResult, ['obsSetPath', 'sampleSetPath', 'value']),
95
+ unnestMap(sampleResult, ['sampleSetPath', 'value']),
96
+ ];
97
+ }, [obsIndex, sampleEdges, sampleSets, obsSetColor,
98
+ sampleSetColor, mergedObsSets, obsSetSelection, mergedSampleSets,
99
+ sampleSetSelection,
100
+ // TODO: consider filtering-related coordination values
101
+ ]);
102
+ return (_jsx(TitleInfo, { title: `Treemap of ${capitalize(plur(obsType, 2))}`, info: `${commaNumber(obsCount)} ${plur(obsType, obsCount)} from ${commaNumber(sampleCount)} ${plur(sampleType, sampleCount)}`, removeGridComponent: removeGridComponent, urls: urls, theme: theme, isReady: isReady, helpText: helpText, options: (_jsx(TreemapOptions, { obsType: obsType, sampleType: sampleType, obsColorEncoding: obsColorEncoding, setObsColorEncoding: setObsColorEncoding, hierarchyLevels: hierarchyLevels || DEFAULT_HIERARCHY_LEVELS, setHierarchyLevels: setHierarchyLevels })), children: _jsx("div", { ref: containerRef, className: classes.vegaContainer, children: _jsx(Treemap, { obsCounts: obsCounts, sampleCounts: sampleCounts, obsColorEncoding: obsColorEncoding, hierarchyLevels: hierarchyLevels || DEFAULT_HIERARCHY_LEVELS, theme: theme, width: width, height: height, obsType: obsType, sampleType: sampleType, obsSetColor: obsSetColor, sampleSetColor: sampleSetColor, obsSetSelection: obsSetSelection, sampleSetSelection: sampleSetSelection }) }) }));
103
+ }
@@ -0,0 +1,2 @@
1
+ export default function VolcanoPlot(props: any): JSX.Element;
2
+ //# sourceMappingURL=VolcanoPlot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VolcanoPlot.d.ts","sourceRoot":"","sources":["../src/VolcanoPlot.js"],"names":[],"mappings":"AAWA,6DA6SC"}
@@ -0,0 +1,230 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /* eslint-disable indent */
3
+ /* eslint-disable camelcase */
4
+ import React, { useMemo, useEffect, useRef } from 'react';
5
+ import { scaleLinear } from 'd3-scale';
6
+ import { axisBottom, axisLeft } from 'd3-axis';
7
+ import { extent as d3_extent } from 'd3-array';
8
+ import { select } from 'd3-selection';
9
+ import { isEqual } from 'lodash-es';
10
+ import { capitalize } from '@vitessce/utils';
11
+ import { getColorScale } from './utils.js';
12
+ export default function VolcanoPlot(props) {
13
+ const { theme, width, height, obsType, featureType, obsSetsColumnNameMapping, sampleSetsColumnNameMapping, sampleSetSelection, obsSetSelection, obsSetColor, sampleSetColor, data, marginTop = 5, marginRight = 5, marginLeft = 50, marginBottom = 50, onFeatureClick, featurePointSignificanceThreshold, featurePointFoldChangeThreshold, featureLabelSignificanceThreshold, featureLabelFoldChangeThreshold, } = props;
14
+ const svgRef = useRef();
15
+ const computedData = useMemo(() => data.map(d => ({
16
+ ...d,
17
+ df: {
18
+ ...d.df,
19
+ minusLog10p: d.df.featureSignificance.map(v => -Math.log10(v)),
20
+ logFoldChange: d.df.featureFoldChange.map(v => Math.log2(v)),
21
+ },
22
+ })), [data]);
23
+ const [xExtent, yExtent] = useMemo(() => {
24
+ if (!computedData) {
25
+ return [null, null];
26
+ }
27
+ let xExtentResult = d3_extent(computedData.flatMap(d => d3_extent(d.df.logFoldChange)));
28
+ const xAbsMax = Math.max(Math.abs(xExtentResult[0]), Math.abs(xExtentResult[1]));
29
+ xExtentResult = [-xAbsMax, xAbsMax];
30
+ const yExtentResult = d3_extent(computedData.flatMap(d => d3_extent(d.df.minusLog10p.filter(v => Number.isFinite(v)))));
31
+ return [xExtentResult, yExtentResult];
32
+ }, [computedData]);
33
+ const [obsSetColorScale, sampleSetColorScale] = useMemo(() => [
34
+ getColorScale(obsSetSelection, obsSetColor, theme),
35
+ getColorScale(sampleSetSelection, sampleSetColor, theme),
36
+ ], [obsSetSelection, sampleSetSelection, sampleSetColor, obsSetColor, theme]);
37
+ useEffect(() => {
38
+ const domElement = svgRef.current;
39
+ const svg = select(domElement);
40
+ svg.selectAll('g').remove();
41
+ svg
42
+ .attr('width', width)
43
+ .attr('height', height)
44
+ .attr('viewBox', [0, 0, width, height])
45
+ .attr('style', 'font: 10px sans-serif');
46
+ if (!computedData || !xExtent || !yExtent) {
47
+ return;
48
+ }
49
+ // Render scatterplot
50
+ const innerWidth = width - marginLeft;
51
+ const innerHeight = height - marginBottom;
52
+ const xScale = scaleLinear()
53
+ .range([marginLeft, width - marginRight])
54
+ .domain(xExtent);
55
+ // For the y domain, use the yMin prop
56
+ // to support a use case such as 'Aspect Ratio',
57
+ // where the domain minimum should be 1 rather than 0.
58
+ const yScale = scaleLinear()
59
+ .domain(yExtent)
60
+ .range([innerHeight, marginTop])
61
+ .clamp(true);
62
+ // Add the axes.
63
+ svg.append('g')
64
+ .attr('transform', `translate(0,${height - marginBottom})`)
65
+ .call(axisBottom(xScale));
66
+ svg.append('g')
67
+ .attr('transform', `translate(${marginLeft},0)`)
68
+ .call(axisLeft(yScale));
69
+ // Axis titles
70
+ const titleG = svg.append('g');
71
+ const fgColor = 'black'; // TODO: use theme to determine this
72
+ // Y-axis title
73
+ titleG
74
+ .append('text')
75
+ .attr('text-anchor', 'middle')
76
+ .attr('x', -innerHeight / 2)
77
+ .attr('y', 15)
78
+ .attr('transform', 'rotate(-90)')
79
+ .text('-log10 p-value')
80
+ .style('font-size', '12px')
81
+ .style('fill', fgColor);
82
+ // X-axis title
83
+ titleG
84
+ .append('text')
85
+ .attr('text-anchor', 'middle')
86
+ .attr('x', marginLeft + innerWidth / 2)
87
+ .attr('y', height - 10)
88
+ .text('log2 fold-change')
89
+ .style('font-size', '12px')
90
+ .style('fill', fgColor);
91
+ // Get a mapping from column name to group name.
92
+ const obsSetsColumnNameMappingReversed = Object.fromEntries(Object
93
+ .entries(obsSetsColumnNameMapping)
94
+ .map(([key, value]) => ([value, key])));
95
+ const sampleSetsColumnNameMappingReversed = Object.fromEntries(Object
96
+ .entries(sampleSetsColumnNameMapping)
97
+ .map(([key, value]) => ([value, key])));
98
+ // Horizontal and vertical rules to indicate currently-selected thresholds
99
+ // Vertical lines
100
+ const ruleColor = 'silver';
101
+ const ruleDash = '2,2';
102
+ titleG.append('line')
103
+ .attr('x1', xScale(featurePointFoldChangeThreshold))
104
+ .attr('x2', xScale(featurePointFoldChangeThreshold))
105
+ .attr('y1', yScale.range()[0])
106
+ .attr('y2', yScale.range()[1])
107
+ .style('stroke', ruleColor)
108
+ .style('stroke-dasharray', ruleDash);
109
+ titleG.append('line')
110
+ .attr('x1', xScale(-featurePointFoldChangeThreshold))
111
+ .attr('x2', xScale(-featurePointFoldChangeThreshold))
112
+ .attr('y1', yScale.range()[0])
113
+ .attr('y2', yScale.range()[1])
114
+ .style('stroke', ruleColor)
115
+ .style('stroke-dasharray', ruleDash);
116
+ // Horizontal lines
117
+ titleG.append('line')
118
+ .attr('x1', xScale.range()[0])
119
+ .attr('x2', xScale.range()[1])
120
+ .attr('y1', yScale(-Math.log10(featurePointSignificanceThreshold)))
121
+ .attr('y2', yScale(-Math.log10(featurePointSignificanceThreshold)))
122
+ .style('stroke', ruleColor)
123
+ .style('stroke-dasharray', ruleDash);
124
+ // Upregulated/downregulated and sampleSet directional indicators.
125
+ const lhsText = sampleSetSelection && sampleSetSelection.length === 2
126
+ ? sampleSetSelection[0].at(-1)
127
+ : '__rest__';
128
+ // eslint-disable-next-line no-nested-ternary
129
+ const rhsText = sampleSetSelection && sampleSetSelection.length === 2
130
+ ? sampleSetSelection[1].at(-1)
131
+ : (obsSetSelection && obsSetSelection.length === 1
132
+ ? obsSetSelection?.[0]?.at(-1)
133
+ : `${capitalize(obsType)} Set`);
134
+ titleG
135
+ .append('text')
136
+ .attr('text-anchor', 'start')
137
+ .attr('x', marginLeft)
138
+ .attr('y', height - 10)
139
+ .text(`\u2190 ${lhsText}`)
140
+ .style('font-size', '12px')
141
+ .style('fill', fgColor);
142
+ titleG
143
+ .append('text')
144
+ .attr('text-anchor', 'end')
145
+ .attr('x', marginLeft + innerWidth)
146
+ .attr('y', height - 10)
147
+ .text(`${rhsText} \u2192`)
148
+ .style('font-size', '12px')
149
+ .style('fill', fgColor);
150
+ const g = svg.append('g');
151
+ // Append a circle for each data point.
152
+ computedData.forEach((comparisonObject) => {
153
+ const obsSetG = g.append('g');
154
+ const { df, metadata } = comparisonObject;
155
+ const coordinationValues = metadata.coordination_values;
156
+ const rawObsSetPath = coordinationValues.obsSetFilter
157
+ ? coordinationValues.obsSetFilter[0]
158
+ : coordinationValues.obsSetSelection[0];
159
+ const obsSetPath = [...rawObsSetPath];
160
+ obsSetPath[0] = obsSetsColumnNameMappingReversed[rawObsSetPath[0]];
161
+ // Swap the foldchange direction if backwards with
162
+ // respect to the current sampleSetSelection pair.
163
+ // TODO: move this swapping into the computedData useMemo?
164
+ let shouldSwapFoldChangeDirection = false;
165
+ if (coordinationValues.sampleSetFilter
166
+ && coordinationValues.sampleSetFilter.length === 2) {
167
+ const rawSampleSetPathA = coordinationValues.sampleSetFilter[0];
168
+ const sampleSetPathA = [...rawSampleSetPathA];
169
+ sampleSetPathA[0] = sampleSetsColumnNameMappingReversed[rawSampleSetPathA[0]];
170
+ const rawSampleSetPathB = coordinationValues.sampleSetFilter[1];
171
+ const sampleSetPathB = [...rawSampleSetPathB];
172
+ sampleSetPathB[0] = sampleSetsColumnNameMappingReversed[rawSampleSetPathB[0]];
173
+ if (isEqual(sampleSetPathA, sampleSetSelection[1])
174
+ && isEqual(sampleSetPathB, sampleSetSelection[0])) {
175
+ shouldSwapFoldChangeDirection = true;
176
+ }
177
+ }
178
+ const filteredDf = df.featureId.map((featureId, i) => ({
179
+ featureId,
180
+ logFoldChange: df.logFoldChange[i] * (shouldSwapFoldChangeDirection ? -1 : 1),
181
+ featureSignificance: df.featureSignificance[i],
182
+ minusLog10p: df.minusLog10p[i],
183
+ })).filter(d => ((Math.abs(d.logFoldChange) >= (featurePointFoldChangeThreshold ?? 1.0))
184
+ && (d.featureSignificance <= (featurePointSignificanceThreshold ?? 0.05))));
185
+ const color = obsSetColorScale(obsSetPath);
186
+ obsSetG.append('g')
187
+ .selectAll('circle')
188
+ .data(filteredDf)
189
+ .join('circle')
190
+ .attr('cx', d => xScale(d.logFoldChange))
191
+ .attr('cy', d => yScale(d.minusLog10p))
192
+ .attr('r', 3)
193
+ .attr('opacity', 0.5)
194
+ .attr('fill', color)
195
+ .on('click', (event, d) => {
196
+ onFeatureClick(d.featureId);
197
+ });
198
+ const textElements = obsSetG.append('g')
199
+ .selectAll('text')
200
+ .data(filteredDf)
201
+ .join('text')
202
+ .text(d => d.featureId)
203
+ .attr('text-anchor', d => (d.logFoldChange < 0 ? 'end' : 'start'))
204
+ .attr('x', d => xScale(d.logFoldChange))
205
+ .attr('y', d => yScale(d.minusLog10p))
206
+ .style('display', d => ((Math.abs(d.logFoldChange) < (featureLabelFoldChangeThreshold ?? 5.0)
207
+ || (d.featureSignificance >= (featureLabelSignificanceThreshold ?? 0.01))) ? 'none' : undefined))
208
+ .attr('fill', color)
209
+ .on('click', (event, d) => {
210
+ onFeatureClick(d.featureId);
211
+ });
212
+ textElements.append('title')
213
+ .text(d => `${featureType}: ${d.featureId}\nin ${obsSetPath?.at(-1)}\nlog2 fold-change: ${d.logFoldChange}\np-value: ${d.featureSignificance}`);
214
+ });
215
+ }, [width, height, theme, sampleSetColor, sampleSetSelection,
216
+ obsSetSelection, obsSetColor, featureType, computedData,
217
+ xExtent, yExtent, obsType,
218
+ marginLeft, marginBottom, marginTop, marginRight,
219
+ obsSetColorScale, sampleSetColorScale, onFeatureClick,
220
+ featurePointSignificanceThreshold, featurePointFoldChangeThreshold,
221
+ featureLabelSignificanceThreshold, featureLabelFoldChangeThreshold,
222
+ ]);
223
+ return (_jsx("svg", { ref: svgRef, style: {
224
+ top: 0,
225
+ left: 0,
226
+ width: `${width}px`,
227
+ height: `${height}px`,
228
+ position: 'relative',
229
+ } }));
230
+ }
@@ -0,0 +1,2 @@
1
+ export default function VolcanoPlotOptions(props: any): JSX.Element;
2
+ //# sourceMappingURL=VolcanoPlotOptions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VolcanoPlotOptions.d.ts","sourceRoot":"","sources":["../src/VolcanoPlotOptions.js"],"names":[],"mappings":"AAOA,oEAgIC"}
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import { useId } from 'react-aria';
4
+ import { TableCell, TableRow, Slider } from '@material-ui/core';
5
+ import { usePlotOptionsStyles, OptionsContainer, } from '@vitessce/vit-s';
6
+ export default function VolcanoPlotOptions(props) {
7
+ const { children, featurePointSignificanceThreshold, featurePointFoldChangeThreshold, featureLabelSignificanceThreshold, featureLabelFoldChangeThreshold, setFeaturePointSignificanceThreshold, setFeaturePointFoldChangeThreshold, setFeatureLabelSignificanceThreshold, setFeatureLabelFoldChangeThreshold, } = props;
8
+ const volcanoOptionsId = useId();
9
+ const classes = usePlotOptionsStyles();
10
+ function handlePointSignificanceChange(event, value) {
11
+ setFeaturePointSignificanceThreshold(10 ** -value);
12
+ }
13
+ function handlePointFoldChangeChange(event, value) {
14
+ setFeaturePointFoldChangeThreshold(value);
15
+ }
16
+ function handleLabelSignificanceChange(event, value) {
17
+ setFeatureLabelSignificanceThreshold(10 ** -value);
18
+ }
19
+ function handleLabelFoldChangeChange(event, value) {
20
+ setFeatureLabelFoldChangeThreshold(value);
21
+ }
22
+ return (_jsxs(OptionsContainer, { children: [children, _jsxs(TableRow, { children: [_jsx(TableCell, { className: classes.labelCell, variant: "head", scope: "row", children: _jsx("label", { htmlFor: `volcano-label-significance-${volcanoOptionsId}`, children: "Label Significance Threshold" }) }), _jsx(TableCell, { className: classes.inputCell, variant: "body", children: _jsx(Slider, { classes: { root: classes.slider, valueLabel: classes.sliderValueLabel }, value: -Math.log10(featureLabelSignificanceThreshold), onChange: handleLabelSignificanceChange, "aria-label": "Volcano plot label significance threshold slider", id: `volcano-label-significance-${volcanoOptionsId}`, valueLabelDisplay: "auto", step: 1, min: 0, max: 100 }) })] }), _jsxs(TableRow, { children: [_jsx(TableCell, { className: classes.labelCell, variant: "head", scope: "row", children: _jsx("label", { htmlFor: `volcano-label-fc-${volcanoOptionsId}`, children: "Label Fold-Change Threshold" }) }), _jsx(TableCell, { className: classes.inputCell, variant: "body", children: _jsx(Slider, { classes: { root: classes.slider, valueLabel: classes.sliderValueLabel }, value: featureLabelFoldChangeThreshold, onChange: handleLabelFoldChangeChange, "aria-label": "Volcano plot label fold-change threshold slider", id: `volcano-label-fc-${volcanoOptionsId}`, valueLabelDisplay: "auto", step: 0.5, min: 0, max: 50 }) })] }), _jsxs(TableRow, { children: [_jsx(TableCell, { className: classes.labelCell, variant: "head", scope: "row", children: _jsx("label", { htmlFor: `volcano-point-significance-${volcanoOptionsId}`, children: "Point Significance Threshold" }) }), _jsx(TableCell, { className: classes.inputCell, variant: "body", children: _jsx(Slider, { classes: { root: classes.slider, valueLabel: classes.sliderValueLabel }, value: -Math.log10(featurePointSignificanceThreshold), onChange: handlePointSignificanceChange, "aria-label": "Volcano plot point significance threshold slider", id: `volcano-point-significance-${volcanoOptionsId}`, valueLabelDisplay: "auto", step: 1, min: 0, max: 100 }) })] }), _jsxs(TableRow, { children: [_jsx(TableCell, { className: classes.labelCell, variant: "head", scope: "row", children: _jsx("label", { htmlFor: `volcano-point-fc-${volcanoOptionsId}`, children: "Point Fold-Change Threshold" }) }), _jsx(TableCell, { className: classes.inputCell, variant: "body", children: _jsx(Slider, { classes: { root: classes.slider, valueLabel: classes.sliderValueLabel }, value: featurePointFoldChangeThreshold, onChange: handlePointFoldChangeChange, "aria-label": "Volcano plot point fold-change threshold slider", id: `volcano-point-fc-${volcanoOptionsId}`, valueLabelDisplay: "auto", step: 0.5, min: 0, max: 50 }) })] })] }));
23
+ }
@@ -0,0 +1,2 @@
1
+ export function VolcanoPlotSubscriber(props: any): JSX.Element;
2
+ //# sourceMappingURL=VolcanoPlotSubscriber.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VolcanoPlotSubscriber.d.ts","sourceRoot":"","sources":["../src/VolcanoPlotSubscriber.js"],"names":[],"mappings":"AAuBA,+DA0IC"}
@@ -0,0 +1,33 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /* eslint-disable no-unused-vars */
3
+ import React, { useMemo, useCallback } from 'react';
4
+ import { TitleInfo, useCoordination, useLoaders, useReady, useGridItemSize, useFeatureStatsData, useMatchingLoader, useColumnNameMapping, } from '@vitessce/vit-s';
5
+ import { ViewType, COMPONENT_COORDINATION_TYPES, ViewHelpMapping, DataType, } from '@vitessce/constants-internal';
6
+ import VolcanoPlot from './VolcanoPlot.js';
7
+ import { useStyles } from './styles.js';
8
+ import VolcanoPlotOptions from './VolcanoPlotOptions.js';
9
+ import { useRawSetPaths } from './utils.js';
10
+ export function VolcanoPlotSubscriber(props) {
11
+ const { coordinationScopes, removeGridComponent, theme, helpText = ViewHelpMapping.VOLCANO_PLOT, } = props;
12
+ const classes = useStyles();
13
+ const loaders = useLoaders();
14
+ // Get "props" from the coordination space.
15
+ const [{ dataset, obsType, sampleType, featureType, featureValueType, obsFilter: cellFilter, obsHighlight: cellHighlight, obsSetSelection, obsSetColor, obsColorEncoding: cellColorEncoding, additionalObsSets: additionalCellSets, featurePointSignificanceThreshold, featurePointFoldChangeThreshold, featureLabelSignificanceThreshold, featureLabelFoldChangeThreshold, featureValueTransform, featureValueTransformCoefficient, gatingFeatureSelectionX, gatingFeatureSelectionY, featureSelection, sampleSetSelection, sampleSetColor, }, { setObsFilter: setCellFilter, setObsSetSelection, setObsHighlight: setCellHighlight, setObsSetColor: setCellSetColor, setObsColorEncoding: setCellColorEncoding, setAdditionalObsSets: setAdditionalCellSets, setFeaturePointSignificanceThreshold, setFeaturePointFoldChangeThreshold, setFeatureLabelSignificanceThreshold, setFeatureLabelFoldChangeThreshold, setFeatureValueTransform, setFeatureValueTransformCoefficient, setGatingFeatureSelectionX, setGatingFeatureSelectionY, setFeatureSelection, setSampleSetSelection, setSampleSetColor, }] = useCoordination(COMPONENT_COORDINATION_TYPES[ViewType.VOLCANO_PLOT], coordinationScopes);
16
+ const [width, height, containerRef] = useGridItemSize();
17
+ const obsSetsLoader = useMatchingLoader(loaders, dataset, DataType.OBS_SETS, { obsType });
18
+ const sampleSetsLoader = useMatchingLoader(loaders, dataset, DataType.SAMPLE_SETS, { sampleType });
19
+ const obsSetsColumnNameMapping = useColumnNameMapping(obsSetsLoader);
20
+ const sampleSetsColumnNameMapping = useColumnNameMapping(sampleSetsLoader);
21
+ const rawSampleSetSelection = useRawSetPaths(sampleSetsColumnNameMapping, sampleSetSelection);
22
+ const rawObsSetSelection = useRawSetPaths(obsSetsColumnNameMapping, obsSetSelection);
23
+ const [{ featureStats }, featureStatsStatus] = useFeatureStatsData(loaders, dataset, false, { obsType, featureType, sampleType },
24
+ // These volcanoOptions are passed to FeatureStatsAnndataLoader.loadMulti():
25
+ { sampleSetSelection: rawSampleSetSelection, obsSetSelection: rawObsSetSelection });
26
+ const isReady = useReady([
27
+ featureStatsStatus,
28
+ ]);
29
+ const onFeatureClick = useCallback((featureId) => {
30
+ setFeatureSelection([featureId]);
31
+ }, [setFeatureSelection]);
32
+ return (_jsx(TitleInfo, { title: "Volcano Plot", removeGridComponent: removeGridComponent, theme: theme, isReady: isReady, helpText: helpText, options: (_jsx(VolcanoPlotOptions, { obsType: obsType, featureType: featureType, featurePointSignificanceThreshold: featurePointSignificanceThreshold, featurePointFoldChangeThreshold: featurePointFoldChangeThreshold, featureLabelSignificanceThreshold: featureLabelSignificanceThreshold, featureLabelFoldChangeThreshold: featureLabelFoldChangeThreshold, setFeaturePointSignificanceThreshold: setFeaturePointSignificanceThreshold, setFeaturePointFoldChangeThreshold: setFeaturePointFoldChangeThreshold, setFeatureLabelSignificanceThreshold: setFeatureLabelSignificanceThreshold, setFeatureLabelFoldChangeThreshold: setFeatureLabelFoldChangeThreshold })), children: _jsx("div", { ref: containerRef, className: classes.vegaContainer, children: featureStats ? (_jsx(VolcanoPlot, { theme: theme, width: width, height: height, obsType: obsType, featureType: featureType, obsSetsColumnNameMapping: obsSetsColumnNameMapping, sampleSetsColumnNameMapping: sampleSetsColumnNameMapping, sampleSetSelection: sampleSetSelection, obsSetSelection: obsSetSelection, obsSetColor: obsSetColor, sampleSetColor: sampleSetColor, data: featureStats, onFeatureClick: onFeatureClick, featurePointSignificanceThreshold: featurePointSignificanceThreshold, featurePointFoldChangeThreshold: featurePointFoldChangeThreshold, featureLabelSignificanceThreshold: featureLabelSignificanceThreshold, featureLabelFoldChangeThreshold: featureLabelFoldChangeThreshold })) : (_jsxs("span", { children: ["Select at least one ", obsType, " set."] })) }) }));
33
+ }
@@ -3,6 +3,10 @@ export { CellSetSizesPlotSubscriber } from "./CellSetSizesPlotSubscriber.js";
3
3
  export { ExpressionHistogramSubscriber } from "./ExpressionHistogramSubscriber.js";
4
4
  export { DotPlotSubscriber } from "./DotPlotSubscriber.js";
5
5
  export { FeatureBarPlotSubscriber } from "./FeatureBarPlotSubscriber.js";
6
+ export { TreemapSubscriber } from "./TreemapSubscriber.js";
7
+ export { VolcanoPlotSubscriber } from "./VolcanoPlotSubscriber.js";
8
+ export { CellSetCompositionBarPlotSubscriber } from "./CellSetCompositionBarPlotSubscriber.js";
9
+ export { FeatureSetEnrichmentBarPlotSubscriber } from "./FeatureSetEnrichmentBarPlotSubscriber.js";
6
10
  export { default as CellSetSizesPlot } from "./CellSetSizesPlot.js";
7
11
  export { default as CellSetExpressionPlot } from "./CellSetExpressionPlot.js";
8
12
  export { default as ExpressionHistogram } from "./ExpressionHistogram.js";