@vitessce/statistical-plots 3.4.6 → 3.4.7

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 (47) hide show
  1. package/dist/deflate-c8c2f459.js +13 -0
  2. package/dist/index-a1925e78.js +206649 -0
  3. package/dist/index.js +13 -91680
  4. package/dist/jpeg-ffd14ffe.js +840 -0
  5. package/dist/lerc-9d1dd17e.js +2014 -0
  6. package/dist/lzw-3705b408.js +128 -0
  7. package/dist/packbits-6f657116.js +30 -0
  8. package/dist/pako.esm-68f84e2a.js +4022 -0
  9. package/dist/raw-0a76dec9.js +12 -0
  10. package/dist/webimage-fbdf3bdf.js +32 -0
  11. package/dist-tsc/CellSetExpressionPlot.d.ts.map +1 -1
  12. package/dist-tsc/CellSetExpressionPlot.js +207 -106
  13. package/dist-tsc/CellSetExpressionPlotOptions.d.ts.map +1 -1
  14. package/dist-tsc/CellSetExpressionPlotOptions.js +14 -4
  15. package/dist-tsc/CellSetExpressionPlotSubscriber.d.ts.map +1 -1
  16. package/dist-tsc/CellSetExpressionPlotSubscriber.js +15 -31
  17. package/dist-tsc/CellSetSizesPlot.d.ts.map +1 -1
  18. package/dist-tsc/CellSetSizesPlotSubscriber.d.ts.map +1 -1
  19. package/dist-tsc/DotPlot.d.ts +28 -0
  20. package/dist-tsc/DotPlot.d.ts.map +1 -0
  21. package/dist-tsc/DotPlot.js +144 -0
  22. package/dist-tsc/DotPlotSubscriber.d.ts +14 -0
  23. package/dist-tsc/DotPlotSubscriber.d.ts.map +1 -0
  24. package/dist-tsc/DotPlotSubscriber.js +54 -0
  25. package/dist-tsc/ExpressionHistogram.d.ts.map +1 -1
  26. package/dist-tsc/ExpressionHistogramSubscriber.d.ts.map +1 -1
  27. package/dist-tsc/dot-plot-hook.d.ts +23 -0
  28. package/dist-tsc/dot-plot-hook.d.ts.map +1 -0
  29. package/dist-tsc/dot-plot-hook.js +69 -0
  30. package/dist-tsc/expr-hooks.d.ts +51 -0
  31. package/dist-tsc/expr-hooks.d.ts.map +1 -0
  32. package/dist-tsc/expr-hooks.js +135 -0
  33. package/dist-tsc/expr-hooks.test.d.ts +2 -0
  34. package/dist-tsc/expr-hooks.test.d.ts.map +1 -0
  35. package/dist-tsc/expr-hooks.test.js +97 -0
  36. package/dist-tsc/index.d.ts +2 -0
  37. package/dist-tsc/index.js +2 -0
  38. package/package.json +10 -7
  39. package/src/CellSetExpressionPlot.js +223 -124
  40. package/src/CellSetExpressionPlotOptions.js +57 -1
  41. package/src/CellSetExpressionPlotSubscriber.js +45 -38
  42. package/src/DotPlot.js +175 -0
  43. package/src/DotPlotSubscriber.js +173 -0
  44. package/src/dot-plot-hook.js +107 -0
  45. package/src/expr-hooks.js +170 -0
  46. package/src/expr-hooks.test.js +116 -0
  47. package/src/index.js +2 -0
@@ -0,0 +1,12 @@
1
+ import { B as BaseDecoder } from "./index-a1925e78.js";
2
+ import "react";
3
+ import "@vitessce/vit-s";
4
+ import "react-dom";
5
+ class RawDecoder extends BaseDecoder {
6
+ decodeBlock(buffer) {
7
+ return buffer;
8
+ }
9
+ }
10
+ export {
11
+ RawDecoder as default
12
+ };
@@ -0,0 +1,32 @@
1
+ import { B as BaseDecoder } from "./index-a1925e78.js";
2
+ import "react";
3
+ import "@vitessce/vit-s";
4
+ import "react-dom";
5
+ class WebImageDecoder extends BaseDecoder {
6
+ constructor() {
7
+ super();
8
+ if (typeof createImageBitmap === "undefined") {
9
+ throw new Error("Cannot decode WebImage as `createImageBitmap` is not available");
10
+ } else if (typeof document === "undefined" && typeof OffscreenCanvas === "undefined") {
11
+ throw new Error("Cannot decode WebImage as neither `document` nor `OffscreenCanvas` is not available");
12
+ }
13
+ }
14
+ async decode(fileDirectory, buffer) {
15
+ const blob = new Blob([buffer]);
16
+ const imageBitmap = await createImageBitmap(blob);
17
+ let canvas;
18
+ if (typeof document !== "undefined") {
19
+ canvas = document.createElement("canvas");
20
+ canvas.width = imageBitmap.width;
21
+ canvas.height = imageBitmap.height;
22
+ } else {
23
+ canvas = new OffscreenCanvas(imageBitmap.width, imageBitmap.height);
24
+ }
25
+ const ctx = canvas.getContext("2d");
26
+ ctx.drawImage(imageBitmap, 0, 0);
27
+ return ctx.getImageData(0, 0, imageBitmap.width, imageBitmap.height).data.buffer;
28
+ }
29
+ }
30
+ export {
31
+ WebImageDecoder as default
32
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"CellSetExpressionPlot.d.ts","sourceRoot":"","sources":["../src/CellSetExpressionPlot.js"],"names":[],"mappings":"AA8DA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH;IAjB2B,IAAI,EAApB,MAAM,EAAE;IAEM,SAAS,EAAvB,MAAM;IACU,MAAM,EAAtB,MAAM,EAAE;IAEM,KAAK,EAAnB,MAAM;IACQ,KAAK,EAAnB,MAAM;IACQ,MAAM,EAApB,MAAM;IACQ,WAAW,EAAzB,MAAM;IAGQ,YAAY,EAA1B,MAAM;IAGa,yBAAyB,EAA5C,MAAM,GAAC,IAAI;gBAqOrB"}
1
+ {"version":3,"file":"CellSetExpressionPlot.d.ts","sourceRoot":"","sources":["../src/CellSetExpressionPlot.js"],"names":[],"mappings":"AAaA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qDAjBG;IAAwB,IAAI,EAApB,MAAM,EAAE;IAEM,SAAS,EAAvB,MAAM;IACU,MAAM,EAAtB,MAAM,EAAE;IAEM,KAAK,EAAnB,MAAM;IACQ,KAAK,EAAnB,MAAM;IACQ,MAAM,EAApB,MAAM;IACQ,WAAW,EAAzB,MAAM;IAGQ,YAAY,EAA1B,MAAM;IAGa,yBAAyB,EAA5C,MAAM,GAAC,IAAI;CAErB,eAuXA"}
@@ -2,51 +2,14 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  /* eslint-disable indent */
3
3
  /* eslint-disable camelcase */
4
4
  import React, { useMemo, useEffect, useRef } from 'react';
5
- import { scaleLinear } from 'd3-scale';
5
+ import { scaleLinear, scaleOrdinal } from 'd3-scale';
6
6
  import { scale as vega_scale } from 'vega-scale';
7
7
  import { axisBottom, axisLeft } from 'd3-axis';
8
- import { bin, min, max, rollup as d3_rollup, mean as d3_mean, deviation as d3_deviation, ascending as d3_ascending, map as d3_map, quantileSorted, } from 'd3-array';
9
8
  import { area as d3_area, curveBasis } from 'd3-shape';
10
9
  import { select } from 'd3-selection';
11
10
  import { colorArrayToString } from '@vitessce/sets-utils';
12
11
  import { capitalize } from '@vitessce/utils';
13
12
  const scaleBand = vega_scale('band');
14
- const GROUP_KEY = 'set';
15
- const VALUE_KEY = 'value';
16
- // Reference: https://github.com/d3/d3-array/issues/180#issuecomment-851378012
17
- function summarize(iterable, keepZeros) {
18
- const values = d3_map(iterable, d => d[VALUE_KEY])
19
- .filter(d => keepZeros || d !== 0.0)
20
- .sort(d3_ascending);
21
- const minVal = values[0];
22
- const maxVal = values[values.length - 1];
23
- const q1 = quantileSorted(values, 0.25);
24
- const q2 = quantileSorted(values, 0.5);
25
- const q3 = quantileSorted(values, 0.75);
26
- const iqr = q3 - q1; // interquartile range
27
- const r0 = Math.max(minVal, q1 - iqr * 1.5);
28
- const r1 = Math.min(maxVal, q3 + iqr * 1.5);
29
- let i = -1;
30
- while (values[++i] < r0)
31
- ;
32
- const w0 = values[i];
33
- while (values[++i] <= r1)
34
- ;
35
- const w1 = values[i - 1];
36
- // Chauvenet
37
- // Reference: https://en.wikipedia.org/wiki/Chauvenet%27s_criterion
38
- const mean = d3_mean(values);
39
- const stdv = d3_deviation(values);
40
- const c0 = mean - 3 * stdv;
41
- const c1 = mean + 3 * stdv;
42
- return {
43
- quartiles: [q1, q2, q3],
44
- range: [r0, r1],
45
- whiskers: [w0, w1],
46
- chauvenetRange: [c0, c1],
47
- nonOutliers: values.filter(v => c0 <= v && v <= c1),
48
- };
49
- }
50
13
  /**
51
14
  * Gene expression histogram displayed as a bar chart,
52
15
  * implemented with the VegaPlot component.
@@ -69,14 +32,21 @@ function summarize(iterable, keepZeros) {
69
32
  * for the feature value transformation function.
70
33
  */
71
34
  export default function CellSetExpressionPlot(props) {
72
- const { yMin: yMinProp, yUnits, jitter, colors, data, theme, width, height, marginTop = 5, marginRight = 5, marginLeft = 50, marginBottom, obsType, featureType, featureValueType, featureValueTransformName, } = props;
35
+ const { yMin: yMinProp, yUnits, jitter, cellSetSelection, sampleSetSelection, sampleSetColor, colors, data, theme, width, height, marginTop = 5, marginRight = 5, marginLeft = 50, marginBottom, obsType, featureType, featureValueType, featureValueTransformName, } = props;
73
36
  const svgRef = useRef();
74
37
  // Get the max characters in an axis label for autsizing the bottom margin.
75
- const maxCharactersForLabel = useMemo(() => data.reduce((acc, val) => {
76
- // eslint-disable-next-line no-param-reassign
77
- acc = acc === undefined || val[GROUP_KEY].length > acc ? val[GROUP_KEY].length : acc;
78
- return acc;
79
- }, 0), [data]);
38
+ const maxCharactersForLabel = useMemo(() => {
39
+ if (!cellSetSelection) {
40
+ return 0;
41
+ }
42
+ const cellSetNames = cellSetSelection.map(d => d.at(-1));
43
+ return cellSetNames.reduce((acc, name) => {
44
+ // eslint-disable-next-line no-param-reassign
45
+ acc = acc === undefined || name.length > acc ? name.length : acc;
46
+ return acc;
47
+ }, 0);
48
+ }, [cellSetSelection]);
49
+ const isStratified = (Array.isArray(sampleSetSelection) && sampleSetSelection.length === 2);
80
50
  useEffect(() => {
81
51
  const domElement = svgRef.current;
82
52
  const transformPrefix = (featureValueTransformName && featureValueTransformName !== 'None')
@@ -92,6 +62,7 @@ export default function CellSetExpressionPlot(props) {
92
62
  const autoMarginBottom = marginBottom
93
63
  || 30 + Math.sqrt(maxCharactersForLabel / 2) * 30;
94
64
  const rectColor = (theme === 'dark' ? 'white' : 'black');
65
+ const fgColor = (theme === 'dark' ? 'white' : 'black');
95
66
  const svg = select(domElement);
96
67
  svg.selectAll('g').remove();
97
68
  svg
@@ -101,36 +72,41 @@ export default function CellSetExpressionPlot(props) {
101
72
  .append('g')
102
73
  .attr('width', width)
103
74
  .attr('height', height);
104
- const groupNames = colors.map(d => d.name);
105
75
  // Manually set the color scale so that Vega-Lite does
106
76
  // not choose the colors automatically.
107
- const colorScale = {
108
- domain: colors.map(d => d.name),
109
- range: colors.map(d => colorArrayToString(d.color)),
110
- };
77
+ const colorScale = scaleOrdinal()
78
+ .domain(colors.map(d => d.setNamePath))
79
+ .range(colors.map(d => colorArrayToString(d.color)));
80
+ const sampleSetNames = sampleSetSelection?.map(path => path.at(-1));
81
+ let stratificationSide;
82
+ let stratificationColor;
83
+ if (isStratified) {
84
+ stratificationSide = scaleOrdinal()
85
+ .domain(sampleSetNames)
86
+ .range(['left', 'right']);
87
+ stratificationColor = scaleOrdinal()
88
+ .domain(sampleSetNames)
89
+ .range(
90
+ // TODO: check for full path equality here.
91
+ sampleSetNames
92
+ .map(name => sampleSetColor?.find(d => d.path.at(-1) === name).color)
93
+ .map(colorArrayToString));
94
+ }
111
95
  // Remove outliers on a per-group basis.
112
- const groupedSummaries = Array.from(d3_rollup(data, groupData => summarize(groupData, true), d => d[GROUP_KEY]), ([key, value]) => ({ key, value }));
113
- const groupedData = groupedSummaries
114
- .map(({ key, value }) => ({ key, value: value.nonOutliers }));
115
- const trimmedData = groupedData.map(kv => kv.value).flat();
96
+ const { groupSummaries: groupedSummaries, groupData: groupedData, groupBins, // Array of [{ key, value: [{ key, value: histogram(nonOutliers) }] }]
97
+ groupBinsMax, // Number
98
+ } = data;
99
+ let { y } = data;
116
100
  const innerWidth = width - marginLeft;
117
101
  const innerHeight = height - autoMarginBottom;
118
102
  const xGroup = scaleBand()
119
103
  .range([marginLeft, width - marginRight])
120
- .domain(groupNames)
104
+ .domain(cellSetSelection)
121
105
  .padding(0.1);
122
- const yMin = (yMinProp === null ? Math.min(0, min(trimmedData)) : yMinProp);
123
106
  // For the y domain, use the yMin prop
124
107
  // to support a use case such as 'Aspect Ratio',
125
108
  // where the domain minimum should be 1 rather than 0.
126
- const y = scaleLinear()
127
- .domain([yMin, max(trimmedData)])
128
- .range([innerHeight, marginTop]);
129
- const histogram = bin()
130
- .thresholds(y.ticks(16))
131
- .domain(y.domain());
132
- const groupBins = groupedData.map(kv => ({ key: kv.key, value: histogram(kv.value) }));
133
- const groupBinsMax = max(groupBins.flatMap(d => d.value.map(v => v.length)));
109
+ y = y.range([innerHeight, marginTop]);
134
110
  const x = scaleLinear()
135
111
  .domain([-groupBinsMax, groupBinsMax])
136
112
  .range([0, xGroup.bandwidth()]);
@@ -139,55 +115,136 @@ export default function CellSetExpressionPlot(props) {
139
115
  .x1(d => x(d.length))
140
116
  .y(d => y(d.x0))
141
117
  .curve(curveBasis);
118
+ const leftArea = d3_area()
119
+ .x0(d => x(-d.length))
120
+ .x1(() => x(0))
121
+ .y(d => y(d.x0))
122
+ .curve(curveBasis);
123
+ const rightArea = d3_area()
124
+ .x0(() => x(0))
125
+ .x1(d => x(d.length))
126
+ .y(d => y(d.x0))
127
+ .curve(curveBasis);
128
+ const sideToAreaFunc = {
129
+ left: leftArea,
130
+ right: rightArea,
131
+ };
142
132
  // Violin areas
143
- g
144
- .selectAll('violin')
145
- .data(groupBins)
146
- .enter()
147
- .append('g')
148
- .attr('transform', d => `translate(${xGroup(d.key)},0)`)
149
- .style('fill', d => colorScale.range[groupNames.indexOf(d.key)])
150
- .append('path')
151
- .datum(d => d.value)
152
- .style('stroke', 'none')
153
- .attr('d', d => area(d));
133
+ if (isStratified) {
134
+ const violinG = g
135
+ .selectAll('violin')
136
+ .data(groupBins)
137
+ .enter()
138
+ .append('g')
139
+ .attr('transform', d => `translate(${xGroup(d.key)},0)`);
140
+ violinG.append('path')
141
+ .datum(d => d.value[0])
142
+ .style('stroke', 'none')
143
+ .style('fill', d => stratificationColor(d.key))
144
+ .attr('d', d => sideToAreaFunc[stratificationSide(d.key)](d.value));
145
+ violinG.append('path')
146
+ .datum(d => d.value[1])
147
+ .style('stroke', 'none')
148
+ .style('fill', d => stratificationColor(d.key))
149
+ .attr('d', d => sideToAreaFunc[stratificationSide(d.key)](d.value));
150
+ }
151
+ else {
152
+ g
153
+ .selectAll('violin')
154
+ .data(groupBins)
155
+ .enter()
156
+ .append('g')
157
+ .attr('transform', d => `translate(${xGroup(d.key)},0)`)
158
+ .style('fill', d => colorScale(d.key))
159
+ .append('path')
160
+ .datum(d => d.value[0])
161
+ .style('stroke', 'none')
162
+ .attr('d', d => area(d.value));
163
+ }
154
164
  // Whiskers
155
165
  const whiskerGroups = g.selectAll('whiskers')
156
166
  .data(groupedSummaries)
157
167
  .enter()
158
168
  .append('g')
159
169
  .attr('transform', d => `translate(${xGroup(d.key)},0)`);
160
- whiskerGroups.append('line')
161
- .datum(d => d.value)
162
- .attr('stroke', rectColor)
163
- .attr('x1', xGroup.bandwidth() / 2)
164
- .attr('x2', xGroup.bandwidth() / 2)
165
- .attr('y1', d => y(d.quartiles[0]))
166
- .attr('y2', d => y(d.quartiles[2]))
167
- .attr('stroke-width', 2);
168
- whiskerGroups.append('line')
169
- .datum(d => d.value)
170
- .attr('stroke', rectColor)
171
- .attr('x1', xGroup.bandwidth() / 2 - (jitter ? 0 : 4))
172
- .attr('x2', xGroup.bandwidth() / 2 + 4)
173
- .attr('y1', d => y(d.quartiles[1]))
174
- .attr('y2', d => y(d.quartiles[1]))
175
- .attr('stroke-width', 2);
170
+ if (isStratified) {
171
+ // Vertical line
172
+ whiskerGroups.append('line')
173
+ .datum(d => d.value[0])
174
+ .attr('stroke', rectColor)
175
+ .attr('x1', d => xGroup.bandwidth() / 2 + (stratificationSide(d.key) === 'left' ? -1.5 : 1.5))
176
+ .attr('x2', d => xGroup.bandwidth() / 2 + (stratificationSide(d.key) === 'left' ? -1.5 : 1.5))
177
+ .attr('y1', d => y(d.value.quartiles[0]))
178
+ .attr('y2', d => y(d.value.quartiles[2]))
179
+ .attr('stroke-width', 2);
180
+ whiskerGroups.append('line')
181
+ .datum(d => d.value[1])
182
+ .attr('stroke', rectColor)
183
+ .attr('x1', d => xGroup.bandwidth() / 2 + (stratificationSide(d.key) === 'left' ? -1.5 : 1.5))
184
+ .attr('x2', d => xGroup.bandwidth() / 2 + (stratificationSide(d.key) === 'left' ? -1.5 : 1.5))
185
+ .attr('y1', d => y(d.value.quartiles[0]))
186
+ .attr('y2', d => y(d.value.quartiles[2]))
187
+ .attr('stroke-width', 2);
188
+ // Horizontal line
189
+ whiskerGroups.append('line')
190
+ .datum(d => d.value[0])
191
+ .attr('stroke', rectColor)
192
+ .attr('x1', d => xGroup.bandwidth() / 2 + (stratificationSide(d.key) === 'left' ? -5.5 : 1.5))
193
+ .attr('x2', d => xGroup.bandwidth() / 2 + (stratificationSide(d.key) === 'left' ? -1.5 : 5.5))
194
+ .attr('y1', d => y(d.value.quartiles[1]))
195
+ .attr('y2', d => y(d.value.quartiles[1]))
196
+ .attr('stroke-width', 2);
197
+ whiskerGroups.append('line')
198
+ .datum(d => d.value[1])
199
+ .attr('stroke', rectColor)
200
+ .attr('x1', d => xGroup.bandwidth() / 2 + (stratificationSide(d.key) === 'left' ? -5.5 : 1.5))
201
+ .attr('x2', d => xGroup.bandwidth() / 2 + (stratificationSide(d.key) === 'left' ? -1.5 : 5.5))
202
+ .attr('y1', d => y(d.value.quartiles[1]))
203
+ .attr('y2', d => y(d.value.quartiles[1]))
204
+ .attr('stroke-width', 2);
205
+ }
206
+ else {
207
+ // Vertical line
208
+ whiskerGroups.append('line')
209
+ .datum(d => d.value[0].value)
210
+ .attr('stroke', rectColor)
211
+ .attr('x1', xGroup.bandwidth() / 2)
212
+ .attr('x2', xGroup.bandwidth() / 2)
213
+ .attr('y1', d => y(d.quartiles[0]))
214
+ .attr('y2', d => y(d.quartiles[2]))
215
+ .attr('stroke-width', 2);
216
+ // Horizontal line
217
+ whiskerGroups.append('line')
218
+ .datum(d => d.value[0].value)
219
+ .attr('stroke', rectColor)
220
+ .attr('x1', xGroup.bandwidth() / 2 - (jitter ? 0 : 4))
221
+ .attr('x2', xGroup.bandwidth() / 2 + 4)
222
+ .attr('y1', d => y(d.quartiles[1]))
223
+ .attr('y2', d => y(d.quartiles[1]))
224
+ .attr('stroke-width', 2);
225
+ }
176
226
  // Jittered points
177
227
  if (jitter) {
178
228
  groupedData.forEach(({ key, value }) => {
179
- const groupG = g.append('g');
180
- groupG.selectAll('point')
181
- .data(value)
182
- .enter()
183
- .append('circle')
184
- .attr('transform', `translate(${xGroup(key)},0)`)
185
- .style('stroke', 'none')
186
- .style('fill', 'silver')
187
- .style('opacity', '0.1')
188
- .attr('cx', () => 5 + Math.random() * ((xGroup.bandwidth() / 2) - 10))
189
- .attr('cy', d => y(d))
190
- .attr('r', 2);
229
+ value.forEach(({ value: subValue }) => {
230
+ if (isStratified) {
231
+ // TODO
232
+ }
233
+ else {
234
+ const groupG = g.append('g');
235
+ groupG.selectAll('point')
236
+ .data(subValue)
237
+ .enter()
238
+ .append('circle')
239
+ .attr('transform', `translate(${xGroup(key)},0)`)
240
+ .style('stroke', 'none')
241
+ .style('fill', 'silver')
242
+ .style('opacity', '0.1')
243
+ .attr('cx', () => 5 + Math.random() * ((xGroup.bandwidth() / 2) - 10))
244
+ .attr('cy', d => y(d))
245
+ .attr('r', 2);
246
+ }
247
+ });
191
248
  });
192
249
  }
193
250
  // Y-axis ticks
@@ -202,7 +259,7 @@ export default function CellSetExpressionPlot(props) {
202
259
  .append('g')
203
260
  .attr('transform', `translate(0,${innerHeight})`)
204
261
  .style('font-size', '14px')
205
- .call(axisBottom(xGroup))
262
+ .call(axisBottom(xGroup).tickFormat(d => d.at(-1)))
206
263
  .selectAll('text')
207
264
  .style('font-size', '11px')
208
265
  .attr('dx', '-6px')
@@ -218,7 +275,7 @@ export default function CellSetExpressionPlot(props) {
218
275
  .attr('transform', 'rotate(-90)')
219
276
  .text(yTitle)
220
277
  .style('font-size', '12px')
221
- .style('fill', 'white');
278
+ .style('fill', fgColor);
222
279
  // X-axis title
223
280
  g
224
281
  .append('text')
@@ -227,11 +284,55 @@ export default function CellSetExpressionPlot(props) {
227
284
  .attr('y', height - 10)
228
285
  .text(xTitle)
229
286
  .style('font-size', '12px')
230
- .style('fill', 'white');
287
+ .style('fill', fgColor);
288
+ // Legend
289
+ if (isStratified) {
290
+ const legendG = g
291
+ .append('g')
292
+ .attr('transform', `translate(${marginLeft + innerWidth - 150},${marginTop})`);
293
+ legendG.append('rect')
294
+ .attr('width', 150)
295
+ .attr('height', 56)
296
+ .attr('x', 0)
297
+ .attr('y', 0)
298
+ .style('fill', 'rgba(215, 215, 215, 0.2)')
299
+ .attr('rx', 4);
300
+ legendG.append('text')
301
+ .text('Sample Group')
302
+ .style('font-size', '11px')
303
+ .style('line-height', 20)
304
+ .attr('x', 4)
305
+ .attr('y', 14)
306
+ .style('fill', fgColor);
307
+ legendG.append('rect')
308
+ .attr('width', 10)
309
+ .attr('height', 10)
310
+ .attr('x', 5)
311
+ .attr('y', 23)
312
+ .style('fill', stratificationColor(sampleSetNames[0]));
313
+ legendG.append('text')
314
+ .text(sampleSetNames[0])
315
+ .style('font-size', '11px')
316
+ .attr('x', 20)
317
+ .attr('y', 32)
318
+ .style('fill', fgColor);
319
+ legendG.append('rect')
320
+ .attr('width', 10)
321
+ .attr('height', 10)
322
+ .attr('x', 5)
323
+ .attr('y', 39)
324
+ .style('fill', stratificationColor(sampleSetNames[1]));
325
+ legendG.append('text')
326
+ .text(sampleSetNames[1])
327
+ .style('font-size', '11px')
328
+ .attr('x', 20)
329
+ .attr('y', 48)
330
+ .style('fill', fgColor);
331
+ }
231
332
  }, [width, height, data, marginLeft, marginBottom, colors,
232
333
  jitter, theme, yMinProp, marginTop, marginRight, featureType,
233
334
  featureValueType, featureValueTransformName, yUnits, obsType,
234
- maxCharactersForLabel,
335
+ maxCharactersForLabel, sampleSetSelection,
235
336
  ]);
236
337
  return (_jsx("svg", { ref: svgRef, style: {
237
338
  top: 0,
@@ -1 +1 @@
1
- {"version":3,"file":"CellSetExpressionPlotOptions.d.ts","sourceRoot":"","sources":["../src/CellSetExpressionPlotOptions.js"],"names":[],"mappings":"AAKA,8EAiFC"}
1
+ {"version":3,"file":"CellSetExpressionPlotOptions.d.ts","sourceRoot":"","sources":["../src/CellSetExpressionPlotOptions.js"],"names":[],"mappings":"AAMA,8EAwIC"}
@@ -1,15 +1,22 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React from 'react';
3
3
  import { useId } from 'react-aria';
4
- import { TableCell, TableRow, TextField } from '@material-ui/core';
4
+ import { TableCell, TableRow, TextField, Slider } from '@material-ui/core';
5
5
  import { usePlotOptionsStyles, OptionsContainer, OptionSelect } from '@vitessce/vit-s';
6
+ import { GLSL_COLORMAPS } from '@vitessce/gl';
6
7
  export default function CellSetExpressionPlotOptions(props) {
7
- const { featureValueTransform, setFeatureValueTransform, featureValueTransformCoefficient, setFeatureValueTransformCoefficient, transformOptions, } = props;
8
+ const { featureValueTransform, setFeatureValueTransform, featureValueTransformCoefficient, setFeatureValueTransformCoefficient, transformOptions, featureValuePositivityThreshold, setFeatureValuePositivityThreshold, featureValueColormap, setFeatureValueColormap, } = props;
8
9
  const cellSetExpressionPlotOptionsId = useId();
9
10
  const classes = usePlotOptionsStyles();
11
+ function handleFeatureValueColormapChange(event) {
12
+ setFeatureValueColormap(event.target.value);
13
+ }
10
14
  const handleTransformChange = (event) => {
11
15
  setFeatureValueTransform(event.target.value === '' ? null : event.target.value);
12
16
  };
17
+ function handlePositivityThresholdChange(event, value) {
18
+ setFeatureValuePositivityThreshold(value);
19
+ }
13
20
  // Feels a little hacky, but I think this is the best way to handle
14
21
  // the limitations of the v4 material-ui number input.
15
22
  const handleTransformCoefficientChange = (event) => {
@@ -24,9 +31,12 @@ export default function CellSetExpressionPlotOptions(props) {
24
31
  }
25
32
  }
26
33
  };
27
- return (_jsxs(OptionsContainer, { children: [_jsxs(TableRow, { children: [_jsx(TableCell, { className: classes.labelCell, variant: "head", scope: "row", children: _jsx("label", { htmlFor: `cellset-expression-transform-select-${cellSetExpressionPlotOptionsId}`, children: "Transform" }) }), _jsx(TableCell, { className: classes.inputCell, variant: "body", children: _jsx(OptionSelect, { className: classes.select, value: featureValueTransform === null ? '' : featureValueTransform, onChange: handleTransformChange, inputProps: {
34
+ return (_jsxs(OptionsContainer, { children: [setFeatureValueColormap ? (_jsxs(TableRow, { children: [_jsx(TableCell, { className: classes.labelCell, variant: "head", scope: "row", children: _jsx("label", { htmlFor: `cellset-expression-feature-value-colormap-${cellSetExpressionPlotOptionsId}`, children: "Feature Value Colormap" }) }), _jsx(TableCell, { className: classes.inputCell, variant: "body", children: _jsx(OptionSelect, { className: classes.select, value: featureValueColormap, onChange: handleFeatureValueColormapChange, inputProps: {
35
+ 'aria-label': 'Select feature value colormap',
36
+ id: `cellset-expression-feature-value-colormap-${cellSetExpressionPlotOptionsId}`,
37
+ }, children: GLSL_COLORMAPS.map(cmap => (_jsx("option", { value: cmap, children: cmap }, cmap))) }) })] })) : null, _jsxs(TableRow, { children: [_jsx(TableCell, { className: classes.labelCell, variant: "head", scope: "row", children: _jsx("label", { htmlFor: `cellset-expression-transform-select-${cellSetExpressionPlotOptionsId}`, children: "Transform" }) }), _jsx(TableCell, { className: classes.inputCell, variant: "body", children: _jsx(OptionSelect, { className: classes.select, value: featureValueTransform === null ? '' : featureValueTransform, onChange: handleTransformChange, inputProps: {
28
38
  id: `cellset-expression-transform-select-${cellSetExpressionPlotOptionsId}`,
29
39
  }, children: transformOptions.map(opt => (_jsx("option", { value: opt.value === null ? '' : opt.value, children: opt.name }, opt.name))) }) })] }), _jsxs(TableRow, { children: [_jsx(TableCell, { className: classes.labelCell, variant: "head", scope: "row", children: _jsx("label", { htmlFor: `cellset-expression-transform-coeff-${cellSetExpressionPlotOptionsId}`, children: "Transform Coefficient" }) }), _jsx(TableCell, { className: classes.inputCell, variant: "body", children: _jsx(TextField, { label: "Transform Coefficient", type: "number", onChange: handleTransformCoefficientChange, value: featureValueTransformCoefficient, InputLabelProps: {
30
40
  shrink: true,
31
- }, id: `cellset-expression-transform-coeff-${cellSetExpressionPlotOptionsId}` }) })] })] }));
41
+ }, id: `cellset-expression-transform-coeff-${cellSetExpressionPlotOptionsId}` }) })] }), setFeatureValuePositivityThreshold ? (_jsxs(TableRow, { children: [_jsx(TableCell, { className: classes.labelCell, children: "Positivity Threshold" }), _jsx(TableCell, { className: classes.inputCell, children: _jsx(Slider, { classes: { root: classes.slider, valueLabel: classes.sliderValueLabel }, value: featureValuePositivityThreshold, onChange: handlePositivityThresholdChange, "aria-labelledby": "pos-threshold-slider", valueLabelDisplay: "auto", step: 1.0, min: 0.0, max: 100.0 }) })] }, "transform-coefficient-option-row")) : null] }));
32
42
  }
@@ -1 +1 @@
1
- {"version":3,"file":"CellSetExpressionPlotSubscriber.d.ts","sourceRoot":"","sources":["../src/CellSetExpressionPlotSubscriber.js"],"names":[],"mappings":"AAmGA;;;;;;;;;GASG;AACH;IAL2B,mBAAmB;IACrB,kBAAkB,EAAhC,MAAM;IAEQ,KAAK,EAAnB,MAAM;gBAkJhB"}
1
+ {"version":3,"file":"CellSetExpressionPlotSubscriber.d.ts","sourceRoot":"","sources":["../src/CellSetExpressionPlotSubscriber.js"],"names":[],"mappings":"AAkGA;;;;;;;;;GASG;AACH,uDALG;IAAwB,mBAAmB;IACrB,kBAAkB,EAAhC,MAAM;IAEQ,KAAK,EAAnB,MAAM;CAChB,eAyJA"}
@@ -2,11 +2,12 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React, { useMemo } from 'react';
3
3
  import { TitleInfo, useCoordination, useLoaders, useUrls, useReady, useGridItemSize, useFeatureSelection, useObsSetsData, useObsFeatureMatrixIndices, useFeatureLabelsData, useSampleSetsData, useSampleEdgesData, } from '@vitessce/vit-s';
4
4
  import { ViewType, COMPONENT_COORDINATION_TYPES } from '@vitessce/constants-internal';
5
- import { VALUE_TRANSFORM_OPTIONS, capitalize, getValueTransformFunction } from '@vitessce/utils';
6
- import { treeToObjectsBySetNames, treeToSetSizesBySetNames, mergeObsSets } from '@vitessce/sets-utils';
5
+ import { VALUE_TRANSFORM_OPTIONS, capitalize } from '@vitessce/utils';
6
+ import { treeToSetSizesBySetNames, mergeObsSets, stratifyExpressionData, aggregateStratifiedExpressionData, } from '@vitessce/sets-utils';
7
7
  import CellSetExpressionPlotOptions from './CellSetExpressionPlotOptions.js';
8
8
  import CellSetExpressionPlot from './CellSetExpressionPlot.js';
9
9
  import { useStyles } from './styles.js';
10
+ import { summarizeStratifiedExpressionData, histogramStratifiedExpressionData, } from './expr-hooks.js';
10
11
  /**
11
12
  * Get expression data for the cells
12
13
  * in the selected cell sets.
@@ -25,39 +26,24 @@ import { useStyles } from './styles.js';
25
26
  * feature value transform function.
26
27
  * @param {number} featureValueTransformCoefficient A coefficient
27
28
  * to be used in the transform function.
28
- * @param {string} theme "light" or "dark" for the vitessce theme
29
- * `path` and `color`.
30
29
  */
31
- function useExpressionByCellSet(expressionData, obsIndex, cellSets, additionalCellSets, geneSelection, cellSetSelection, cellSetColor, featureValueTransform, featureValueTransformCoefficient, theme) {
30
+ function useExpressionByCellSet(sampleEdges, sampleSets, sampleSetSelection, expressionData, obsIndex, cellSets, additionalCellSets, geneSelection, cellSetSelection, cellSetColor, featureValueTransform, featureValueTransformCoefficient, theme, yMinProp) {
32
31
  const mergedCellSets = useMemo(() => mergeObsSets(cellSets, additionalCellSets), [cellSets, additionalCellSets]);
33
32
  // From the expression matrix and the list of selected genes / cell sets,
34
33
  // generate the array of data points for the plot.
35
34
  const [expressionArr, expressionMax] = useMemo(() => {
36
- if (mergedCellSets && cellSetSelection
37
- && geneSelection && geneSelection.length >= 1
38
- && expressionData) {
39
- const cellObjects = treeToObjectsBySetNames(mergedCellSets, cellSetSelection, cellSetColor, theme);
40
- const firstGeneSelected = geneSelection[0];
41
- // Create new cellColors map based on the selected gene.
42
- let exprMax = -Infinity;
43
- const cellIndices = {};
44
- for (let i = 0; i < obsIndex.length; i += 1) {
45
- cellIndices[obsIndex[i]] = i;
46
- }
47
- const exprValues = cellObjects.map((cell) => {
48
- const cellIndex = cellIndices[cell.obsId];
49
- const value = expressionData[0][cellIndex];
50
- const transformFunction = getValueTransformFunction(featureValueTransform, featureValueTransformCoefficient);
51
- const transformedValue = transformFunction(value);
52
- exprMax = Math.max(transformedValue, exprMax);
53
- return { value: transformedValue, gene: firstGeneSelected, set: cell.name };
54
- });
55
- return [exprValues, exprMax];
35
+ const [stratifiedData, exprMax] = stratifyExpressionData(sampleEdges, sampleSets, sampleSetSelection, expressionData, obsIndex, mergedCellSets, geneSelection, cellSetSelection, cellSetColor, featureValueTransform, featureValueTransformCoefficient);
36
+ if (stratifiedData) {
37
+ const aggregateData = aggregateStratifiedExpressionData(stratifiedData, geneSelection);
38
+ const summarizedData = summarizeStratifiedExpressionData(aggregateData, true);
39
+ const histogramData = histogramStratifiedExpressionData(summarizedData, 16, yMinProp);
40
+ return [histogramData, exprMax];
56
41
  }
57
42
  return [null, null];
58
43
  }, [expressionData, obsIndex, geneSelection, theme,
59
44
  mergedCellSets, cellSetSelection, cellSetColor,
60
45
  featureValueTransform, featureValueTransformCoefficient,
46
+ yMinProp, sampleEdges, sampleSets, sampleSetSelection,
61
47
  ]);
62
48
  // From the cell sets hierarchy and the list of selected cell sets,
63
49
  // generate the array of set sizes data points for the bar plot.
@@ -81,7 +67,7 @@ export function CellSetExpressionPlotSubscriber(props) {
81
67
  const classes = useStyles();
82
68
  const loaders = useLoaders();
83
69
  // Get "props" from the coordination space.
84
- const [{ dataset, obsType, featureType, featureValueType, featureSelection: geneSelection, featureValueTransform, featureValueTransformCoefficient, obsSetSelection: cellSetSelection, obsSetColor: cellSetColor, additionalObsSets: additionalCellSets, sampleType, }, { setFeatureValueTransform, setFeatureValueTransformCoefficient, }] = useCoordination(COMPONENT_COORDINATION_TYPES[ViewType.OBS_SET_FEATURE_VALUE_DISTRIBUTION], coordinationScopes);
70
+ const [{ dataset, obsType, featureType, featureValueType, featureSelection: geneSelection, featureValueTransform, featureValueTransformCoefficient, obsSetSelection: cellSetSelection, obsSetColor: cellSetColor, additionalObsSets: additionalCellSets, sampleType, sampleSetSelection, sampleSetColor, }, { setFeatureValueTransform, setFeatureValueTransformCoefficient, setSampleSetColor, }] = useCoordination(COMPONENT_COORDINATION_TYPES[ViewType.OBS_SET_FEATURE_VALUE_DISTRIBUTION], coordinationScopes);
85
71
  const [width, height, containerRef] = useGridItemSize();
86
72
  const transformOptions = VALUE_TRANSFORM_OPTIONS;
87
73
  // Get data from loaders using the data hooks.
@@ -91,9 +77,7 @@ export function CellSetExpressionPlotSubscriber(props) {
91
77
  const [{ featureLabelsMap }, featureLabelsStatus, featureLabelsUrls] = useFeatureLabelsData(loaders, dataset, false, {}, {}, { featureType });
92
78
  const [{ obsIndex }, matrixIndicesStatus, matrixIndicesUrls] = useObsFeatureMatrixIndices(loaders, dataset, false, { obsType, featureType, featureValueType });
93
79
  const [{ obsSets: cellSets }, obsSetsStatus, obsSetsUrls] = useObsSetsData(loaders, dataset, true, {}, {}, { obsType });
94
- // eslint-disable-next-line no-unused-vars
95
- const [{ sampleSets }, sampleSetsStatus, sampleSetsUrls] = useSampleSetsData(loaders, dataset, false, {}, {}, { sampleType });
96
- // eslint-disable-next-line no-unused-vars
80
+ const [{ sampleSets }, sampleSetsStatus, sampleSetsUrls] = useSampleSetsData(loaders, dataset, false, { setSampleSetColor }, { sampleSetColor }, { sampleType });
97
81
  const [{ sampleEdges }, sampleEdgesStatus, sampleEdgesUrls] = useSampleEdgesData(loaders, dataset, false, {}, {}, { obsType, sampleType });
98
82
  const isReady = useReady([
99
83
  featureSelectionStatus,
@@ -110,10 +94,10 @@ export function CellSetExpressionPlotSubscriber(props) {
110
94
  sampleSetsUrls,
111
95
  sampleEdgesUrls,
112
96
  ]);
113
- const [expressionArr, setArr] = useExpressionByCellSet(expressionData, obsIndex, cellSets, additionalCellSets, geneSelection, cellSetSelection, cellSetColor, featureValueTransform, featureValueTransformCoefficient, theme);
97
+ const [histogramData, setArr, exprMax] = useExpressionByCellSet(sampleEdges, sampleSets, sampleSetSelection, expressionData, obsIndex, cellSets, additionalCellSets, geneSelection, cellSetSelection, cellSetColor, featureValueTransform, featureValueTransformCoefficient, theme, yMin);
114
98
  const firstGeneSelected = geneSelection && geneSelection.length >= 1
115
99
  ? (featureLabelsMap?.get(geneSelection[0]) || geneSelection[0])
116
100
  : null;
117
101
  const selectedTransformName = transformOptions.find(o => o.value === featureValueTransform)?.name;
118
- return (_jsx(TitleInfo, { title: `Expression by ${capitalize(obsType)} Set${(firstGeneSelected ? ` (${firstGeneSelected})` : '')}`, closeButtonVisible: closeButtonVisible, downloadButtonVisible: downloadButtonVisible, removeGridComponent: removeGridComponent, urls: urls, theme: theme, isReady: isReady, options: (_jsx(CellSetExpressionPlotOptions, { featureValueTransform: featureValueTransform, setFeatureValueTransform: setFeatureValueTransform, featureValueTransformCoefficient: featureValueTransformCoefficient, setFeatureValueTransformCoefficient: setFeatureValueTransformCoefficient, transformOptions: transformOptions })), children: _jsx("div", { ref: containerRef, className: classes.vegaContainer, children: expressionArr ? (_jsx(CellSetExpressionPlot, { yMin: yMin, yUnits: yUnits, jitter: jitter, colors: setArr, data: expressionArr, theme: theme, width: width, height: height, obsType: obsType, featureType: featureType, featureValueType: featureValueType, featureValueTransformName: selectedTransformName })) : (_jsxs("span", { children: ["Select a ", featureType, "."] })) }) }));
102
+ return (_jsx(TitleInfo, { title: `Expression by ${capitalize(obsType)} Set${(firstGeneSelected ? ` (${firstGeneSelected})` : '')}`, closeButtonVisible: closeButtonVisible, downloadButtonVisible: downloadButtonVisible, removeGridComponent: removeGridComponent, urls: urls, theme: theme, isReady: isReady, options: (_jsx(CellSetExpressionPlotOptions, { featureValueTransform: featureValueTransform, setFeatureValueTransform: setFeatureValueTransform, featureValueTransformCoefficient: featureValueTransformCoefficient, setFeatureValueTransformCoefficient: setFeatureValueTransformCoefficient, transformOptions: transformOptions })), children: _jsx("div", { ref: containerRef, className: classes.vegaContainer, children: histogramData ? (_jsx(CellSetExpressionPlot, { yMin: yMin, yUnits: yUnits, jitter: jitter, cellSetSelection: cellSetSelection, sampleSetSelection: sampleSetSelection, sampleSetColor: sampleSetColor, colors: setArr, data: histogramData, exprMax: exprMax, theme: theme, width: width, height: height, obsType: obsType, featureType: featureType, featureValueType: featureValueType, featureValueTransformName: selectedTransformName })) : (_jsxs("span", { children: ["Select a ", featureType, "."] })) }) }));
119
103
  }
@@ -1 +1 @@
1
- {"version":3,"file":"CellSetSizesPlot.d.ts","sourceRoot":"","sources":["../src/CellSetSizesPlot.js"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;;GAkBG;AACH;IAf2B,IAAI,EAApB,MAAM,EAAE;IAEM,KAAK,EAAnB,MAAM;IACQ,KAAK,EAAnB,MAAM;IACQ,MAAM,EAApB,MAAM;IACQ,WAAW,EAAzB,MAAM;IAGQ,YAAY,EAA1B,MAAM;IAGQ,SAAS,EAAvB,MAAM;gBAuJhB"}
1
+ {"version":3,"file":"CellSetSizesPlot.d.ts","sourceRoot":"","sources":["../src/CellSetSizesPlot.js"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;;GAkBG;AACH,gDAfG;IAAwB,IAAI,EAApB,MAAM,EAAE;IAEM,KAAK,EAAnB,MAAM;IACQ,KAAK,EAAnB,MAAM;IACQ,MAAM,EAApB,MAAM;IACQ,WAAW,EAAzB,MAAM;IAGQ,YAAY,EAA1B,MAAM;IAGQ,SAAS,EAAvB,MAAM;CAGhB,eAoJA"}
@@ -1 +1 @@
1
- {"version":3,"file":"CellSetSizesPlotSubscriber.d.ts","sourceRoot":"","sources":["../src/CellSetSizesPlotSubscriber.js"],"names":[],"mappings":"AAgBA;;;;;;;;;;GAUG;AACH;IAN2B,mBAAmB;IACnB,OAAO;IAET,KAAK,EAAnB,MAAM;IACQ,KAAK,EAAnB,MAAM;gBA+HhB"}
1
+ {"version":3,"file":"CellSetSizesPlotSubscriber.d.ts","sourceRoot":"","sources":["../src/CellSetSizesPlotSubscriber.js"],"names":[],"mappings":"AAgBA;;;;;;;;;;GAUG;AACH,kDANG;IAAwB,mBAAmB;IACnB,OAAO;IAET,KAAK,EAAnB,MAAM;IACQ,KAAK,EAAnB,MAAM;CAChB,eA8HA"}