@vitessce/statistical-plots 3.5.9 → 3.5.10

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 (53) hide show
  1. package/dist/{deflate-19841f78.js → deflate-ad0dcbe4.js} +1 -1
  2. package/dist/{index-dc733355.js → index-b8398176.js} +33528 -15439
  3. package/dist/index.js +6 -5
  4. package/dist/{jpeg-a83077be.js → jpeg-81bd1053.js} +1 -1
  5. package/dist/{lerc-1edd075a.js → lerc-b15c3a4c.js} +1 -1
  6. package/dist/{lzw-9572eac3.js → lzw-503cb795.js} +1 -1
  7. package/dist/{packbits-cce11fbc.js → packbits-40cbad40.js} +1 -1
  8. package/dist/{raw-f7587aff.js → raw-9b8d9daf.js} +1 -1
  9. package/dist/{webimage-8d38cd8b.js → webimage-bbc59b4a.js} +1 -1
  10. package/dist-tsc/CellSetCompositionBarPlot.js +3 -3
  11. package/dist-tsc/CellSetCompositionBarPlotSubscriber.js +1 -1
  12. package/dist-tsc/CellSetExpressionPlot.d.ts.map +1 -1
  13. package/dist-tsc/CellSetExpressionPlot.js +26 -10
  14. package/dist-tsc/CellSetExpressionPlotSubscriber.d.ts.map +1 -1
  15. package/dist-tsc/CellSetExpressionPlotSubscriber.js +5 -2
  16. package/dist-tsc/DotPlot.d.ts.map +1 -1
  17. package/dist-tsc/DotPlot.js +54 -5
  18. package/dist-tsc/DotPlotSubscriber.d.ts.map +1 -1
  19. package/dist-tsc/DotPlotSubscriber.js +1 -1
  20. package/dist-tsc/FeatureStatsTable.d.ts +2 -0
  21. package/dist-tsc/FeatureStatsTable.d.ts.map +1 -0
  22. package/dist-tsc/FeatureStatsTable.js +81 -0
  23. package/dist-tsc/FeatureStatsTableSubscriber.d.ts +2 -0
  24. package/dist-tsc/FeatureStatsTableSubscriber.d.ts.map +1 -0
  25. package/dist-tsc/FeatureStatsTableSubscriber.js +28 -0
  26. package/dist-tsc/Treemap.d.ts.map +1 -1
  27. package/dist-tsc/Treemap.js +17 -3
  28. package/dist-tsc/TreemapSubscriber.d.ts.map +1 -1
  29. package/dist-tsc/TreemapSubscriber.js +5 -3
  30. package/dist-tsc/VolcanoPlot.d.ts.map +1 -1
  31. package/dist-tsc/VolcanoPlot.js +15 -46
  32. package/dist-tsc/VolcanoPlotSubscriber.d.ts.map +1 -1
  33. package/dist-tsc/VolcanoPlotSubscriber.js +4 -2
  34. package/dist-tsc/index.d.ts +1 -0
  35. package/dist-tsc/index.js +1 -0
  36. package/dist-tsc/utils.d.ts +1 -0
  37. package/dist-tsc/utils.d.ts.map +1 -1
  38. package/dist-tsc/utils.js +56 -0
  39. package/package.json +8 -7
  40. package/src/CellSetCompositionBarPlot.js +3 -3
  41. package/src/CellSetCompositionBarPlotSubscriber.js +1 -1
  42. package/src/CellSetExpressionPlot.js +33 -10
  43. package/src/CellSetExpressionPlotSubscriber.js +10 -3
  44. package/src/DotPlot.js +77 -9
  45. package/src/DotPlotSubscriber.js +3 -1
  46. package/src/FeatureStatsTable.js +116 -0
  47. package/src/FeatureStatsTableSubscriber.js +133 -0
  48. package/src/Treemap.js +21 -3
  49. package/src/TreemapSubscriber.js +6 -4
  50. package/src/VolcanoPlot.js +16 -64
  51. package/src/VolcanoPlotSubscriber.js +6 -1
  52. package/src/index.js +1 -0
  53. package/src/utils.js +82 -1
@@ -6,21 +6,20 @@ import { scaleLinear } from 'd3-scale';
6
6
  import { axisBottom, axisLeft } from 'd3-axis';
7
7
  import { extent as d3_extent } from 'd3-array';
8
8
  import { select } from 'd3-selection';
9
- import { isEqual } from 'lodash-es';
10
9
  import { capitalize, getDefaultForegroundColor } from '@vitessce/utils';
11
10
  import { colorArrayToString } from '@vitessce/sets-utils';
12
- import { getColorScale } from './utils.js';
11
+ import { getColorScale, useFilteredVolcanoData } from './utils.js';
13
12
  export default function VolcanoPlot(props) {
14
- 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;
13
+ const { theme, width, height, obsType, featureType, obsSetsColumnNameMappingReversed, sampleSetsColumnNameMappingReversed, sampleSetSelection, obsSetSelection, obsSetColor, sampleSetColor, data, marginTop = 5, marginRight = 5, marginLeft = 50, marginBottom = 50, onFeatureClick, featurePointSignificanceThreshold, featurePointFoldChangeThreshold, featureLabelSignificanceThreshold, featureLabelFoldChangeThreshold, } = props;
15
14
  const svgRef = useRef();
16
- const computedData = useMemo(() => data.map(d => ({
17
- ...d,
18
- df: {
19
- ...d.df,
20
- minusLog10p: d.df.featureSignificance.map(v => -Math.log10(v)),
21
- logFoldChange: d.df.featureFoldChange.map(v => Math.log2(v)),
22
- },
23
- })), [data]);
15
+ const [computedData, filteredData] = useFilteredVolcanoData({
16
+ data,
17
+ obsSetsColumnNameMappingReversed,
18
+ sampleSetsColumnNameMappingReversed,
19
+ featurePointFoldChangeThreshold,
20
+ featurePointSignificanceThreshold,
21
+ sampleSetSelection,
22
+ });
24
23
  const [xExtent, yExtent] = useMemo(() => {
25
24
  if (!computedData) {
26
25
  return [null, null];
@@ -44,7 +43,7 @@ export default function VolcanoPlot(props) {
44
43
  .attr('height', height)
45
44
  .attr('viewBox', [0, 0, width, height])
46
45
  .attr('style', 'font: 10px sans-serif');
47
- if (!computedData || !xExtent || !yExtent) {
46
+ if (!filteredData || !xExtent || !yExtent) {
48
47
  return;
49
48
  }
50
49
  // Render scatterplot
@@ -89,13 +88,6 @@ export default function VolcanoPlot(props) {
89
88
  .text('log2 fold-change')
90
89
  .style('font-size', '12px')
91
90
  .style('fill', fgColor);
92
- // Get a mapping from column name to group name.
93
- const obsSetsColumnNameMappingReversed = Object.fromEntries(Object
94
- .entries(obsSetsColumnNameMapping)
95
- .map(([key, value]) => ([value, key])));
96
- const sampleSetsColumnNameMappingReversed = Object.fromEntries(Object
97
- .entries(sampleSetsColumnNameMapping)
98
- .map(([key, value]) => ([value, key])));
99
91
  // Horizontal and vertical rules to indicate currently-selected thresholds
100
92
  // Vertical lines
101
93
  const ruleColor = 'silver';
@@ -150,39 +142,15 @@ export default function VolcanoPlot(props) {
150
142
  .style('fill', fgColor);
151
143
  const g = svg.append('g');
152
144
  // Append a circle for each data point.
153
- computedData.forEach((comparisonObject) => {
145
+ filteredData.forEach((comparisonObject) => {
154
146
  const obsSetG = g.append('g');
155
- const { df, metadata } = comparisonObject;
147
+ const { df: filteredDf, metadata } = comparisonObject;
156
148
  const coordinationValues = metadata.coordination_values;
157
149
  const rawObsSetPath = coordinationValues.obsSetFilter
158
150
  ? coordinationValues.obsSetFilter[0]
159
151
  : coordinationValues.obsSetSelection[0];
160
152
  const obsSetPath = [...rawObsSetPath];
161
153
  obsSetPath[0] = obsSetsColumnNameMappingReversed[rawObsSetPath[0]];
162
- // Swap the foldchange direction if backwards with
163
- // respect to the current sampleSetSelection pair.
164
- // TODO: move this swapping into the computedData useMemo?
165
- let shouldSwapFoldChangeDirection = false;
166
- if (coordinationValues.sampleSetFilter
167
- && coordinationValues.sampleSetFilter.length === 2) {
168
- const rawSampleSetPathA = coordinationValues.sampleSetFilter[0];
169
- const sampleSetPathA = [...rawSampleSetPathA];
170
- sampleSetPathA[0] = sampleSetsColumnNameMappingReversed[rawSampleSetPathA[0]];
171
- const rawSampleSetPathB = coordinationValues.sampleSetFilter[1];
172
- const sampleSetPathB = [...rawSampleSetPathB];
173
- sampleSetPathB[0] = sampleSetsColumnNameMappingReversed[rawSampleSetPathB[0]];
174
- if (isEqual(sampleSetPathA, sampleSetSelection[1])
175
- && isEqual(sampleSetPathB, sampleSetSelection[0])) {
176
- shouldSwapFoldChangeDirection = true;
177
- }
178
- }
179
- const filteredDf = df.featureId.map((featureId, i) => ({
180
- featureId,
181
- logFoldChange: df.logFoldChange[i] * (shouldSwapFoldChangeDirection ? -1 : 1),
182
- featureSignificance: df.featureSignificance[i],
183
- minusLog10p: df.minusLog10p[i],
184
- })).filter(d => ((Math.abs(d.logFoldChange) >= (featurePointFoldChangeThreshold ?? 1.0))
185
- && (d.featureSignificance <= (featurePointSignificanceThreshold ?? 0.05))));
186
154
  const color = obsSetColorScale(obsSetPath);
187
155
  obsSetG.append('g')
188
156
  .selectAll('circle')
@@ -214,12 +182,13 @@ export default function VolcanoPlot(props) {
214
182
  .text(d => `${featureType}: ${d.featureId}\nin ${obsSetPath?.at(-1)}\nlog2 fold-change: ${d.logFoldChange}\np-value: ${d.featureSignificance}`);
215
183
  });
216
184
  }, [width, height, theme, sampleSetColor, sampleSetSelection,
217
- obsSetSelection, obsSetColor, featureType, computedData,
185
+ obsSetSelection, obsSetColor, featureType, filteredData,
218
186
  xExtent, yExtent, obsType,
219
187
  marginLeft, marginBottom, marginTop, marginRight,
220
188
  obsSetColorScale, sampleSetColorScale, onFeatureClick,
221
189
  featurePointSignificanceThreshold, featurePointFoldChangeThreshold,
222
190
  featureLabelSignificanceThreshold, featureLabelFoldChangeThreshold,
191
+ obsSetsColumnNameMappingReversed,
223
192
  ]);
224
193
  return (_jsx("svg", { ref: svgRef, style: {
225
194
  top: 0,
@@ -1 +1 @@
1
- {"version":3,"file":"VolcanoPlotSubscriber.d.ts","sourceRoot":"","sources":["../src/VolcanoPlotSubscriber.js"],"names":[],"mappings":"AAuBA,+DA0IC"}
1
+ {"version":3,"file":"VolcanoPlotSubscriber.d.ts","sourceRoot":"","sources":["../src/VolcanoPlotSubscriber.js"],"names":[],"mappings":"AAuBA,+DA+IC"}
@@ -8,7 +8,7 @@ import { useStyles } from './styles.js';
8
8
  import VolcanoPlotOptions from './VolcanoPlotOptions.js';
9
9
  import { useRawSetPaths } from './utils.js';
10
10
  export function VolcanoPlotSubscriber(props) {
11
- const { coordinationScopes, removeGridComponent, theme, helpText = ViewHelpMapping.VOLCANO_PLOT, } = props;
11
+ const { title = 'Volcano Plot', coordinationScopes, removeGridComponent, theme, helpText = ViewHelpMapping.VOLCANO_PLOT, } = props;
12
12
  const classes = useStyles();
13
13
  const loaders = useLoaders();
14
14
  // Get "props" from the coordination space.
@@ -17,7 +17,9 @@ export function VolcanoPlotSubscriber(props) {
17
17
  const obsSetsLoader = useMatchingLoader(loaders, dataset, DataType.OBS_SETS, { obsType });
18
18
  const sampleSetsLoader = useMatchingLoader(loaders, dataset, DataType.SAMPLE_SETS, { sampleType });
19
19
  const obsSetsColumnNameMapping = useColumnNameMapping(obsSetsLoader);
20
+ const obsSetsColumnNameMappingReversed = useColumnNameMapping(obsSetsLoader, true);
20
21
  const sampleSetsColumnNameMapping = useColumnNameMapping(sampleSetsLoader);
22
+ const sampleSetsColumnNameMappingReversed = useColumnNameMapping(sampleSetsLoader, true);
21
23
  const rawSampleSetSelection = useRawSetPaths(sampleSetsColumnNameMapping, sampleSetSelection);
22
24
  const rawObsSetSelection = useRawSetPaths(obsSetsColumnNameMapping, obsSetSelection);
23
25
  const [{ featureStats }, featureStatsStatus] = useFeatureStatsData(loaders, dataset, false, { obsType, featureType, sampleType },
@@ -29,5 +31,5 @@ export function VolcanoPlotSubscriber(props) {
29
31
  const onFeatureClick = useCallback((featureId) => {
30
32
  setFeatureSelection([featureId]);
31
33
  }, [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."] })) }) }));
34
+ return (_jsx(TitleInfo, { title: title, 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, obsSetsColumnNameMappingReversed: obsSetsColumnNameMappingReversed, sampleSetsColumnNameMapping: sampleSetsColumnNameMapping, sampleSetsColumnNameMappingReversed: sampleSetsColumnNameMappingReversed, 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
35
  }
@@ -7,6 +7,7 @@ export { TreemapSubscriber } from "./TreemapSubscriber.js";
7
7
  export { VolcanoPlotSubscriber } from "./VolcanoPlotSubscriber.js";
8
8
  export { CellSetCompositionBarPlotSubscriber } from "./CellSetCompositionBarPlotSubscriber.js";
9
9
  export { FeatureSetEnrichmentBarPlotSubscriber } from "./FeatureSetEnrichmentBarPlotSubscriber.js";
10
+ export { FeatureStatsTableSubscriber } from "./FeatureStatsTableSubscriber.js";
10
11
  export { default as CellSetSizesPlot } from "./CellSetSizesPlot.js";
11
12
  export { default as CellSetExpressionPlot } from "./CellSetExpressionPlot.js";
12
13
  export { default as ExpressionHistogram } from "./ExpressionHistogram.js";
package/dist-tsc/index.js CHANGED
@@ -7,6 +7,7 @@ export { TreemapSubscriber } from './TreemapSubscriber.js';
7
7
  export { VolcanoPlotSubscriber } from './VolcanoPlotSubscriber.js';
8
8
  export { CellSetCompositionBarPlotSubscriber } from './CellSetCompositionBarPlotSubscriber.js';
9
9
  export { FeatureSetEnrichmentBarPlotSubscriber } from './FeatureSetEnrichmentBarPlotSubscriber.js';
10
+ export { FeatureStatsTableSubscriber } from './FeatureStatsTableSubscriber.js';
10
11
  export { default as CellSetSizesPlot } from './CellSetSizesPlot.js';
11
12
  export { default as CellSetExpressionPlot } from './CellSetExpressionPlot.js';
12
13
  export { default as ExpressionHistogram } from './ExpressionHistogram.js';
@@ -6,4 +6,5 @@ export function getColorScale(setSelectionArr: any, setColorArr: any, theme: any
6
6
  * @returns {string[][]} Transformed set paths.
7
7
  */
8
8
  export function useRawSetPaths(columnNameMapping: Record<string, string>, setPaths: string[][]): string[][];
9
+ export function useFilteredVolcanoData(props: any): any[];
9
10
  //# sourceMappingURL=utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.js"],"names":[],"mappings":"AAcA,0GAeC;AAGD;;;;;GAKG;AACH,kDAJW,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,YACtB,MAAM,EAAE,EAAE,GACR,MAAM,EAAE,EAAE,CAUtB"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.js"],"names":[],"mappings":"AAcA,0GAeC;AAGD;;;;;GAKG;AACH,kDAJW,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,YACtB,MAAM,EAAE,EAAE,GACR,MAAM,EAAE,EAAE,CAUtB;AAID,0DA6EC"}
package/dist-tsc/utils.js CHANGED
@@ -1,3 +1,4 @@
1
+ /* eslint-disable camelcase */
1
2
  import { useMemo } from 'react';
2
3
  import { isEqual } from 'lodash-es';
3
4
  import { colorArrayToString } from '@vitessce/sets-utils';
@@ -38,3 +39,58 @@ export function useRawSetPaths(columnNameMapping, setPaths) {
38
39
  return newSetPath;
39
40
  }), [columnNameMapping, setPaths]);
40
41
  }
42
+ // Data transformation hook function that is used both here
43
+ // and in the FeatureStatsTable view.
44
+ export function useFilteredVolcanoData(props) {
45
+ const { data, obsSetsColumnNameMappingReversed, sampleSetsColumnNameMappingReversed, featurePointFoldChangeThreshold, featurePointSignificanceThreshold, sampleSetSelection, } = props;
46
+ const computedData = useMemo(() => data.map((d) => {
47
+ const { metadata } = d;
48
+ const coordinationValues = metadata.coordination_values;
49
+ const rawObsSetPath = coordinationValues.obsSetFilter
50
+ ? coordinationValues.obsSetFilter[0]
51
+ : coordinationValues.obsSetSelection[0];
52
+ const obsSetPath = [...rawObsSetPath];
53
+ obsSetPath[0] = obsSetsColumnNameMappingReversed[rawObsSetPath[0]];
54
+ // Swap the foldchange direction if backwards with
55
+ // respect to the current sampleSetSelection pair.
56
+ // TODO: move this swapping into the computedData useMemo?
57
+ let shouldSwapFoldChangeDirection = false;
58
+ if (coordinationValues.sampleSetFilter
59
+ && coordinationValues.sampleSetFilter.length === 2) {
60
+ const rawSampleSetPathA = coordinationValues.sampleSetFilter[0];
61
+ const sampleSetPathA = [...rawSampleSetPathA];
62
+ sampleSetPathA[0] = sampleSetsColumnNameMappingReversed[rawSampleSetPathA[0]];
63
+ const rawSampleSetPathB = coordinationValues.sampleSetFilter[1];
64
+ const sampleSetPathB = [...rawSampleSetPathB];
65
+ sampleSetPathB[0] = sampleSetsColumnNameMappingReversed[rawSampleSetPathB[0]];
66
+ if (isEqual(sampleSetPathA, sampleSetSelection[1])
67
+ && isEqual(sampleSetPathB, sampleSetSelection[0])) {
68
+ shouldSwapFoldChangeDirection = true;
69
+ }
70
+ }
71
+ return ({
72
+ ...d,
73
+ df: {
74
+ ...d.df,
75
+ minusLog10p: d.df.featureSignificance.map(v => -Math.log10(v)),
76
+ logFoldChange: d.df.featureFoldChange.map(v => (Math.log2(v) * (shouldSwapFoldChangeDirection ? -1 : 1))),
77
+ },
78
+ });
79
+ }), [
80
+ data, obsSetsColumnNameMappingReversed, sampleSetsColumnNameMappingReversed,
81
+ sampleSetSelection,
82
+ ]);
83
+ const filteredData = useMemo(() => computedData.map(obj => ({
84
+ ...obj,
85
+ // Instead of an object of one array per column,
86
+ // this is now an array of one object per row.
87
+ df: obj.df.featureId.map((featureId, i) => ({
88
+ featureId,
89
+ logFoldChange: obj.df.logFoldChange[i],
90
+ featureSignificance: obj.df.featureSignificance[i],
91
+ minusLog10p: obj.df.minusLog10p[i],
92
+ })).filter(d => ((Math.abs(d.logFoldChange) >= (featurePointFoldChangeThreshold ?? 1.0))
93
+ && (d.featureSignificance <= (featurePointSignificanceThreshold ?? 0.05)))),
94
+ })), [computedData, featurePointFoldChangeThreshold, featurePointSignificanceThreshold]);
95
+ return [computedData, filteredData];
96
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitessce/statistical-plots",
3
- "version": "3.5.9",
3
+ "version": "3.5.10",
4
4
  "author": "HIDIVE Lab at HMS",
5
5
  "homepage": "http://vitessce.io",
6
6
  "repository": {
@@ -17,6 +17,7 @@
17
17
  ],
18
18
  "dependencies": {
19
19
  "@material-ui/core": "~4.12.3",
20
+ "@mui/x-data-grid": "^4.0.2",
20
21
  "d3-array": "^2.4.0",
21
22
  "d3-scale": "^4.0.0",
22
23
  "d3-shape": "^3.2.0",
@@ -29,12 +30,12 @@
29
30
  "react-aria": "^3.28.0",
30
31
  "internmap": "^2.0.3",
31
32
  "uuid": "^9.0.0",
32
- "@vitessce/constants-internal": "3.5.9",
33
- "@vitessce/sets-utils": "3.5.9",
34
- "@vitessce/utils": "3.5.9",
35
- "@vitessce/vega": "3.5.9",
36
- "@vitessce/vit-s": "3.5.9",
37
- "@vitessce/gl": "3.5.9"
33
+ "@vitessce/constants-internal": "3.5.10",
34
+ "@vitessce/sets-utils": "3.5.10",
35
+ "@vitessce/utils": "3.5.10",
36
+ "@vitessce/vega": "3.5.10",
37
+ "@vitessce/vit-s": "3.5.10",
38
+ "@vitessce/gl": "3.5.10"
38
39
  },
39
40
  "devDependencies": {
40
41
  "react": "^18.0.0",
@@ -95,13 +95,13 @@ export default function CellSetCompositionBarPlot(props) {
95
95
  ]);
96
96
 
97
97
  // Get an array of keys for sorting purposes.
98
- const keys = computedData.map(d => d.keyName);
98
+ const keys = computedData?.map(d => d.keyName);
99
99
 
100
100
  const colorScale = {
101
101
  // Manually set the color scale so that Vega-Lite does
102
102
  // not choose the colors automatically.
103
- domain: computedData.map(d => d.key),
104
- range: computedData.map(d => d.color),
103
+ domain: computedData?.map(d => d.key),
104
+ range: computedData?.map(d => d.color),
105
105
  };
106
106
  const captializedObsType = capitalize(obsType);
107
107
 
@@ -143,7 +143,7 @@ export function CellSetCompositionBarPlotSubscriber(props) {
143
143
  onBarSelect={onBarSelect}
144
144
  />
145
145
  ) : (
146
- <span>Select at least one {obsType} set.</span>
146
+ <span>Select at least one {obsType} set and a pair of {sampleType} sets.</span>
147
147
  )}
148
148
  </div>
149
149
  </TitleInfo>
@@ -8,6 +8,7 @@ import { area as d3_area, curveBasis } from 'd3-shape';
8
8
  import { select } from 'd3-selection';
9
9
  import { colorArrayToString } from '@vitessce/sets-utils';
10
10
  import { capitalize } from '@vitessce/utils';
11
+ import { getColorScale } from './utils.js';
11
12
 
12
13
  const scaleBand = vega_scale('band');
13
14
 
@@ -35,9 +36,11 @@ const scaleBand = vega_scale('band');
35
36
  export default function CellSetExpressionPlot(props) {
36
37
  const {
37
38
  yMin: yMinProp,
39
+ xAxisTitle = null,
38
40
  yUnits,
39
41
  jitter,
40
- cellSetSelection,
42
+ obsSetSelection,
43
+ obsSetColor,
41
44
  sampleSetSelection,
42
45
  sampleSetColor,
43
46
  colors,
@@ -57,18 +60,22 @@ export default function CellSetExpressionPlot(props) {
57
60
 
58
61
  const svgRef = useRef();
59
62
 
63
+ const obsSetColorScale = useMemo(() => getColorScale(
64
+ obsSetSelection, obsSetColor, theme,
65
+ ), [obsSetSelection, obsSetColor, theme]);
66
+
60
67
  // Get the max characters in an axis label for autsizing the bottom margin.
61
68
  const maxCharactersForLabel = useMemo(() => {
62
- if (!cellSetSelection) {
69
+ if (!obsSetSelection) {
63
70
  return 0;
64
71
  }
65
- const cellSetNames = cellSetSelection.map(d => d.at(-1));
72
+ const cellSetNames = obsSetSelection.map(d => d.at(-1));
66
73
  return cellSetNames.reduce((acc, name) => {
67
74
  // eslint-disable-next-line no-param-reassign
68
75
  acc = acc === undefined || name.length > acc ? name.length : acc;
69
76
  return acc;
70
77
  }, 0);
71
- }, [cellSetSelection]);
78
+ }, [obsSetSelection]);
72
79
 
73
80
  const isStratified = (Array.isArray(sampleSetSelection) && sampleSetSelection.length === 2);
74
81
 
@@ -81,7 +88,7 @@ export default function CellSetExpressionPlot(props) {
81
88
  const unitSuffix = yUnits ? ` (${yUnits})` : '';
82
89
  const yTitle = `${transformPrefix}${capitalize(featureValueType)}${unitSuffix}`;
83
90
 
84
- const xTitle = `${capitalize(obsType)} Set`;
91
+ const xTitle = xAxisTitle ?? `${capitalize(obsType)} Set`;
85
92
 
86
93
  // Use a square-root term because the angle of the labels is 45 degrees (see below)
87
94
  // so the perpendicular distance to the bottom of the labels is proportional to the
@@ -146,7 +153,7 @@ export default function CellSetExpressionPlot(props) {
146
153
 
147
154
  const xGroup = scaleBand()
148
155
  .range([marginLeft, width - marginRight])
149
- .domain(cellSetSelection)
156
+ .domain(obsSetSelection)
150
157
  .padding(0.1);
151
158
 
152
159
 
@@ -314,11 +321,12 @@ export default function CellSetExpressionPlot(props) {
314
321
  .style('font-size', '11px');
315
322
 
316
323
  // X-axis ticks
317
- g
324
+ const xTickG = g
318
325
  .append('g')
319
326
  .attr('transform', `translate(0,${innerHeight})`)
320
- .style('font-size', '14px')
321
- .call(axisBottom(xGroup).tickFormat(d => d.at(-1)))
327
+ .style('font-size', '14px');
328
+
329
+ xTickG.call(axisBottom(xGroup).tickFormat(d => d.at(-1)))
322
330
  .selectAll('text')
323
331
  .style('font-size', '11px')
324
332
  .attr('dx', '-6px')
@@ -326,6 +334,20 @@ export default function CellSetExpressionPlot(props) {
326
334
  .attr('transform', 'rotate(-45)')
327
335
  .style('text-anchor', 'end');
328
336
 
337
+ if (isStratified) {
338
+ // Associate each X tick with a cell type color,
339
+ // since in the stratified case the violins are colored
340
+ // by sample set.
341
+ const tickWidth = xGroup.bandwidth();
342
+ xTickG.selectAll('.tick')
343
+ .append('rect')
344
+ .attr('x', -tickWidth / 2)
345
+ // .attr("y", -innerHeight)
346
+ .attr('width', tickWidth)
347
+ .attr('height', 4)
348
+ .style('fill', d => obsSetColorScale(d));
349
+ }
350
+
329
351
  // Y-axis title
330
352
  g
331
353
  .append('text')
@@ -394,7 +416,8 @@ export default function CellSetExpressionPlot(props) {
394
416
  }, [width, height, data, marginLeft, marginBottom, colors,
395
417
  jitter, theme, yMinProp, marginTop, marginRight, featureType,
396
418
  featureValueType, featureValueTransformName, yUnits, obsType,
397
- maxCharactersForLabel, sampleSetSelection,
419
+ maxCharactersForLabel, sampleSetSelection, isStratified,
420
+ obsSetColorScale,
398
421
  ]);
399
422
 
400
423
  return (
@@ -114,6 +114,8 @@ export function CellSetExpressionPlotSubscriber(props) {
114
114
  downloadButtonVisible,
115
115
  removeGridComponent,
116
116
  theme,
117
+ title,
118
+ xAxisTitle,
117
119
  jitter = false,
118
120
  yMin = null,
119
121
  yUnits = null,
@@ -222,11 +224,14 @@ export function CellSetExpressionPlotSubscriber(props) {
222
224
  const selectedTransformName = transformOptions.find(
223
225
  o => o.value === featureValueTransform,
224
226
  )?.name;
225
-
227
+ // Use empty string when firstGeneSelected is null
228
+ const titleSuffix = firstGeneSelected ? ` (${firstGeneSelected})` : '';
226
229
 
227
230
  return (
228
231
  <TitleInfo
229
- title={`Expression by ${capitalize(obsType)} Set${(firstGeneSelected ? ` (${firstGeneSelected})` : '')}`}
232
+ title={title ? `${title}${titleSuffix}`
233
+ : `Expression by ${capitalize(obsType)} Set${titleSuffix}`
234
+ }
230
235
  closeButtonVisible={closeButtonVisible}
231
236
  downloadButtonVisible={downloadButtonVisible}
232
237
  removeGridComponent={removeGridComponent}
@@ -250,7 +255,8 @@ export function CellSetExpressionPlotSubscriber(props) {
250
255
  yMin={yMin}
251
256
  yUnits={yUnits}
252
257
  jitter={jitter}
253
- cellSetSelection={cellSetSelection}
258
+ obsSetSelection={cellSetSelection}
259
+ obsSetColor={cellSetColor}
254
260
  sampleSetSelection={sampleSetSelection}
255
261
  sampleSetColor={sampleSetColor}
256
262
  colors={setArr}
@@ -263,6 +269,7 @@ export function CellSetExpressionPlotSubscriber(props) {
263
269
  featureType={featureType}
264
270
  featureValueType={featureValueType}
265
271
  featureValueTransformName={selectedTransformName}
272
+ xAxisTitle={xAxisTitle}
266
273
  />
267
274
  ) : (
268
275
  <span>Select a {featureType}.</span>
package/src/DotPlot.js CHANGED
@@ -1,7 +1,9 @@
1
- import React from 'react';
1
+ import React, { useRef, useEffect, useCallback, useState, useMemo } from 'react';
2
2
  import { clamp } from 'lodash-es';
3
3
  import { VegaPlot, VEGA_THEMES } from '@vitessce/vega';
4
4
  import { capitalize, pluralize as plur } from '@vitessce/utils';
5
+ import { select } from 'd3-selection';
6
+ import { getColorScale } from './utils.js';
5
7
 
6
8
  /**
7
9
  * Gene expression dot plot,
@@ -32,14 +34,18 @@ export default function DotPlot(props) {
32
34
  marginRight,
33
35
  marginBottom,
34
36
  obsType,
37
+ sampleType,
35
38
  keyLength = 36,
36
39
  featureType,
37
40
  featureValueType,
38
41
  featureValueTransformName,
39
42
  featureValueColormap,
40
- cellSetSelection,
43
+ obsSetSelection,
44
+ obsSetColor,
41
45
  } = props;
42
46
 
47
+ const vegaContainerRef = useRef();
48
+
43
49
  // Add a property `keyGroup` and `keyFeature` which concatenates the key and the name,
44
50
  // which is both unique and can easily be converted
45
51
  // back to the name by taking a substring.
@@ -80,18 +86,17 @@ export default function DotPlot(props) {
80
86
  || 30 + Math.sqrt(maxCharactersForSampleSet / 2) * 30;
81
87
 
82
88
  const plotWidth = transpose
83
- ? clamp(width - autoMarginForFeature - 180, 10, Infinity) / (cellSetSelection?.length || 1)
89
+ ? clamp(width - autoMarginForFeature - 180, 10, Infinity) / (obsSetSelection?.length || 1)
84
90
  : clamp(width - autoMarginForGroup - autoMarginForSampleSet - 200, 10, Infinity);
85
91
  const plotHeight = transpose
86
92
  ? clamp((height - autoMarginForGroup - autoMarginForSampleSet - 50), 10, Infinity)
87
- : clamp((height - autoMarginForFeature - 80), 10, Infinity) / (cellSetSelection?.length || 1);
93
+ : clamp((height - autoMarginForFeature - 80), 10, Infinity) / (obsSetSelection?.length || 1);
88
94
 
89
95
  // Get an array of keys for sorting purposes.
90
96
  const groupKeys = data.map(d => d.keyGroup);
91
97
  const featureKeys = data.map(d => d.keyFeature);
92
98
  const groupSecondaryKeys = data.map(d => d.keyGroupSecondary);
93
99
 
94
-
95
100
  const meanTransform = (featureValueTransformName && featureValueTransformName !== 'None')
96
101
  // Mean Log-Transformed Normalized Expression
97
102
  ? [`Mean ${featureValueTransformName}-transformed`, `normalized ${featureValueType}`, 'in set']
@@ -150,6 +155,11 @@ export default function DotPlot(props) {
150
155
  legend: {
151
156
  symbolFillColor: 'white',
152
157
  },
158
+ scale: { domain: [0, 100] },
159
+ },
160
+ tooltip: {
161
+ field: 'pctPosInGroup',
162
+ type: 'quantitative',
153
163
  },
154
164
  },
155
165
  width: plotWidth,
@@ -166,10 +176,68 @@ export default function DotPlot(props) {
166
176
  },
167
177
  };
168
178
 
179
+ const getTooltipText = useCallback(item => ({
180
+ [`${capitalize(featureType)}`]: item.datum.feature,
181
+ [`${capitalize(obsType)} Set`]: item.datum.group,
182
+ ...(isStratified
183
+ ? ({ [`${capitalize(sampleType)} Set`]: item.datum.secondaryGroup })
184
+ : {}
185
+ ),
186
+ [`Percentage of ${plur(obsType, 2)} in set`]: item.datum.pctPosInGroup,
187
+ [meanTransform.join(' ')]: item.datum.meanExpInGroup,
188
+ }), [featureType, obsType, featureValueType, featureValueTransformName]);
189
+
190
+ const obsSetColorScale = useMemo(() => getColorScale(
191
+ obsSetSelection, obsSetColor, theme,
192
+ ), [obsSetSelection, obsSetColor, theme]);
193
+
194
+ const [vegaRenderIncrement, setVegaRenderIncrement] = useState(0);
195
+
196
+ useEffect(() => {
197
+ // If the dot plot is stratified by both obsSet and sampleSet,
198
+ // then we want to add cell set colors.
199
+ // TODO: do we also want to add these color bars
200
+ // when only stratified by obsSet?
201
+ const domElement = vegaContainerRef.current;
202
+
203
+ // Here, we assume that the Vega SVG renderer is being used.
204
+ const svg = select(domElement)
205
+ .select('svg');
206
+ // We use the following CSS selector to identify all of
207
+ // the <line> elements that we are interested to modify.
208
+ const tickEls = svg.selectAll('g.root g.column_footer g.role-axis g.role-axis-domain line');
209
+
210
+ tickEls
211
+ .attr('stroke-width', 5)
212
+ .attr('dy', 2.5)
213
+ .attr('stroke', (d, i) => {
214
+ const obsSetPath = obsSetSelection?.[i];
215
+ return obsSetColorScale(obsSetPath);
216
+ });
217
+ }, [vegaContainerRef, vegaRenderIncrement, obsSetSelection, obsSetColorScale]);
218
+
219
+
220
+ // We want to increment the counter whenever we detect that VegaPlot
221
+ // has re-rendered.
222
+ const onNewView = useCallback(() => {
223
+ setVegaRenderIncrement(prev => prev + 1);
224
+ }, []);
225
+
226
+ // This is kind of hacky, since it is possible that the useEffect runs prior
227
+ // to the Vega rendering, but in practice it seems to work.
228
+ useEffect(() => {
229
+ setVegaRenderIncrement(prev => prev + 1);
230
+ }, [rawData]);
231
+
169
232
  return (
170
- <VegaPlot
171
- data={data}
172
- spec={spec}
173
- />
233
+ <div ref={vegaContainerRef}>
234
+ <VegaPlot
235
+ data={data}
236
+ spec={spec}
237
+ onNewView={onNewView}
238
+ getTooltipText={getTooltipText}
239
+ renderer="svg"
240
+ />
241
+ </div>
174
242
  );
175
243
  }
@@ -160,11 +160,13 @@ export function DotPlotSubscriber(props) {
160
160
  width={width}
161
161
  height={height}
162
162
  obsType={obsType}
163
+ sampleType={sampleType}
163
164
  featureType={featureType}
164
165
  featureValueType={featureValueType}
165
166
  featureValueTransformName={selectedTransformName}
166
167
  featureValueColormap={featureValueColormap}
167
- cellSetSelection={cellSetSelection}
168
+ obsSetSelection={cellSetSelection}
169
+ obsSetColor={cellSetColor}
168
170
  />
169
171
  ) : (
170
172
  <span>Select at least one {featureType}.</span>