@vitessce/statistical-plots 3.5.8 → 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 (55) hide show
  1. package/dist/{deflate-1679ef33.js → deflate-ad0dcbe4.js} +1 -1
  2. package/dist/{index-0f4fe21d.js → index-b8398176.js} +33547 -15443
  3. package/dist/index.js +6 -5
  4. package/dist/{jpeg-280f0ee1.js → jpeg-81bd1053.js} +1 -1
  5. package/dist/{lerc-12264a36.js → lerc-b15c3a4c.js} +1 -1
  6. package/dist/{lzw-70f852cc.js → lzw-503cb795.js} +1 -1
  7. package/dist/{packbits-393c67b2.js → packbits-40cbad40.js} +1 -1
  8. package/dist/{raw-d8d7ab7f.js → raw-9b8d9daf.js} +1 -1
  9. package/dist/{webimage-5d24a8e2.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/FeatureSetEnrichmentBarPlot.js +1 -1
  21. package/dist-tsc/FeatureStatsTable.d.ts +2 -0
  22. package/dist-tsc/FeatureStatsTable.d.ts.map +1 -0
  23. package/dist-tsc/FeatureStatsTable.js +81 -0
  24. package/dist-tsc/FeatureStatsTableSubscriber.d.ts +2 -0
  25. package/dist-tsc/FeatureStatsTableSubscriber.d.ts.map +1 -0
  26. package/dist-tsc/FeatureStatsTableSubscriber.js +28 -0
  27. package/dist-tsc/Treemap.d.ts.map +1 -1
  28. package/dist-tsc/Treemap.js +26 -6
  29. package/dist-tsc/TreemapSubscriber.d.ts.map +1 -1
  30. package/dist-tsc/TreemapSubscriber.js +10 -5
  31. package/dist-tsc/VolcanoPlot.d.ts.map +1 -1
  32. package/dist-tsc/VolcanoPlot.js +18 -48
  33. package/dist-tsc/VolcanoPlotSubscriber.d.ts.map +1 -1
  34. package/dist-tsc/VolcanoPlotSubscriber.js +4 -2
  35. package/dist-tsc/index.d.ts +1 -0
  36. package/dist-tsc/index.js +1 -0
  37. package/dist-tsc/utils.d.ts +1 -0
  38. package/dist-tsc/utils.d.ts.map +1 -1
  39. package/dist-tsc/utils.js +56 -0
  40. package/package.json +8 -7
  41. package/src/CellSetCompositionBarPlot.js +3 -3
  42. package/src/CellSetCompositionBarPlotSubscriber.js +1 -1
  43. package/src/CellSetExpressionPlot.js +33 -10
  44. package/src/CellSetExpressionPlotSubscriber.js +10 -3
  45. package/src/DotPlot.js +77 -9
  46. package/src/DotPlotSubscriber.js +3 -1
  47. package/src/FeatureSetEnrichmentBarPlot.js +1 -1
  48. package/src/FeatureStatsTable.js +116 -0
  49. package/src/FeatureStatsTableSubscriber.js +133 -0
  50. package/src/Treemap.js +31 -5
  51. package/src/TreemapSubscriber.js +12 -5
  52. package/src/VolcanoPlot.js +21 -66
  53. package/src/VolcanoPlotSubscriber.js +6 -1
  54. package/src/index.js +1 -0
  55. package/src/utils.js +82 -1
@@ -6,20 +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
- import { capitalize } from '@vitessce/utils';
11
- import { getColorScale } from './utils.js';
9
+ import { capitalize, getDefaultForegroundColor } from '@vitessce/utils';
10
+ import { colorArrayToString } from '@vitessce/sets-utils';
11
+ import { getColorScale, useFilteredVolcanoData } from './utils.js';
12
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;
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;
14
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]);
15
+ const [computedData, filteredData] = useFilteredVolcanoData({
16
+ data,
17
+ obsSetsColumnNameMappingReversed,
18
+ sampleSetsColumnNameMappingReversed,
19
+ featurePointFoldChangeThreshold,
20
+ featurePointSignificanceThreshold,
21
+ sampleSetSelection,
22
+ });
23
23
  const [xExtent, yExtent] = useMemo(() => {
24
24
  if (!computedData) {
25
25
  return [null, null];
@@ -43,7 +43,7 @@ export default function VolcanoPlot(props) {
43
43
  .attr('height', height)
44
44
  .attr('viewBox', [0, 0, width, height])
45
45
  .attr('style', 'font: 10px sans-serif');
46
- if (!computedData || !xExtent || !yExtent) {
46
+ if (!filteredData || !xExtent || !yExtent) {
47
47
  return;
48
48
  }
49
49
  // Render scatterplot
@@ -68,7 +68,7 @@ export default function VolcanoPlot(props) {
68
68
  .call(axisLeft(yScale));
69
69
  // Axis titles
70
70
  const titleG = svg.append('g');
71
- const fgColor = 'black'; // TODO: use theme to determine this
71
+ const fgColor = colorArrayToString(getDefaultForegroundColor(theme));
72
72
  // Y-axis title
73
73
  titleG
74
74
  .append('text')
@@ -88,13 +88,6 @@ export default function VolcanoPlot(props) {
88
88
  .text('log2 fold-change')
89
89
  .style('font-size', '12px')
90
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
91
  // Horizontal and vertical rules to indicate currently-selected thresholds
99
92
  // Vertical lines
100
93
  const ruleColor = 'silver';
@@ -149,39 +142,15 @@ export default function VolcanoPlot(props) {
149
142
  .style('fill', fgColor);
150
143
  const g = svg.append('g');
151
144
  // Append a circle for each data point.
152
- computedData.forEach((comparisonObject) => {
145
+ filteredData.forEach((comparisonObject) => {
153
146
  const obsSetG = g.append('g');
154
- const { df, metadata } = comparisonObject;
147
+ const { df: filteredDf, metadata } = comparisonObject;
155
148
  const coordinationValues = metadata.coordination_values;
156
149
  const rawObsSetPath = coordinationValues.obsSetFilter
157
150
  ? coordinationValues.obsSetFilter[0]
158
151
  : coordinationValues.obsSetSelection[0];
159
152
  const obsSetPath = [...rawObsSetPath];
160
153
  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
154
  const color = obsSetColorScale(obsSetPath);
186
155
  obsSetG.append('g')
187
156
  .selectAll('circle')
@@ -213,12 +182,13 @@ export default function VolcanoPlot(props) {
213
182
  .text(d => `${featureType}: ${d.featureId}\nin ${obsSetPath?.at(-1)}\nlog2 fold-change: ${d.logFoldChange}\np-value: ${d.featureSignificance}`);
214
183
  });
215
184
  }, [width, height, theme, sampleSetColor, sampleSetSelection,
216
- obsSetSelection, obsSetColor, featureType, computedData,
185
+ obsSetSelection, obsSetColor, featureType, filteredData,
217
186
  xExtent, yExtent, obsType,
218
187
  marginLeft, marginBottom, marginTop, marginRight,
219
188
  obsSetColorScale, sampleSetColorScale, onFeatureClick,
220
189
  featurePointSignificanceThreshold, featurePointFoldChangeThreshold,
221
190
  featureLabelSignificanceThreshold, featureLabelFoldChangeThreshold,
191
+ obsSetsColumnNameMappingReversed,
222
192
  ]);
223
193
  return (_jsx("svg", { ref: svgRef, style: {
224
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.8",
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.8",
33
- "@vitessce/sets-utils": "3.5.8",
34
- "@vitessce/utils": "3.5.8",
35
- "@vitessce/vega": "3.5.8",
36
- "@vitessce/vit-s": "3.5.8",
37
- "@vitessce/gl": "3.5.8"
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>
@@ -140,7 +140,7 @@ export default function FeatureSetEnrichmentBarPlot(props) {
140
140
  x: {
141
141
  field: 'minusLog10p',
142
142
  type: 'quantitative',
143
- title: '- log10 p-value',
143
+ title: '-log10 p-value',
144
144
  },
145
145
  color: {
146
146
  field: 'key',