@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
@@ -1,8 +1,10 @@
1
1
  export { CellSetExpressionPlotSubscriber } from "./CellSetExpressionPlotSubscriber.js";
2
2
  export { CellSetSizesPlotSubscriber } from "./CellSetSizesPlotSubscriber.js";
3
3
  export { ExpressionHistogramSubscriber } from "./ExpressionHistogramSubscriber.js";
4
+ export { DotPlotSubscriber } from "./DotPlotSubscriber.js";
4
5
  export { FeatureBarPlotSubscriber } from "./FeatureBarPlotSubscriber.js";
5
6
  export { default as CellSetSizesPlot } from "./CellSetSizesPlot.js";
6
7
  export { default as CellSetExpressionPlot } from "./CellSetExpressionPlot.js";
7
8
  export { default as ExpressionHistogram } from "./ExpressionHistogram.js";
9
+ export { default as DotPlot } from "./DotPlot.js";
8
10
  //# sourceMappingURL=index.d.ts.map
package/dist-tsc/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  export { CellSetExpressionPlotSubscriber } from './CellSetExpressionPlotSubscriber.js';
2
2
  export { CellSetSizesPlotSubscriber } from './CellSetSizesPlotSubscriber.js';
3
3
  export { ExpressionHistogramSubscriber } from './ExpressionHistogramSubscriber.js';
4
+ export { DotPlotSubscriber } from './DotPlotSubscriber.js';
4
5
  export { FeatureBarPlotSubscriber } from './FeatureBarPlotSubscriber.js';
5
6
  export { default as CellSetSizesPlot } from './CellSetSizesPlot.js';
6
7
  export { default as CellSetExpressionPlot } from './CellSetExpressionPlot.js';
7
8
  export { default as ExpressionHistogram } from './ExpressionHistogram.js';
9
+ export { default as DotPlot } from './DotPlot.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitessce/statistical-plots",
3
- "version": "3.4.6",
3
+ "version": "3.4.7",
4
4
  "author": "Gehlenborg Lab",
5
5
  "homepage": "http://vitessce.io",
6
6
  "repository": {
@@ -22,15 +22,18 @@
22
22
  "d3-shape": "^3.2.0",
23
23
  "d3-axis": "^3.0.0",
24
24
  "d3-selection": "^3.0.0",
25
+ "d3-format": "^3.1.0",
25
26
  "vega-scale": "^6.0.0",
26
27
  "lodash-es": "^4.17.21",
27
28
  "react-aria": "^3.28.0",
28
- "d3-format": "^3.1.0",
29
- "@vitessce/constants-internal": "3.4.6",
30
- "@vitessce/sets-utils": "3.4.6",
31
- "@vitessce/utils": "3.4.6",
32
- "@vitessce/vega": "3.4.6",
33
- "@vitessce/vit-s": "3.4.6"
29
+ "internmap": "^2.0.3",
30
+ "uuid": "^9.0.0",
31
+ "@vitessce/constants-internal": "3.4.7",
32
+ "@vitessce/sets-utils": "3.4.7",
33
+ "@vitessce/utils": "3.4.7",
34
+ "@vitessce/vega": "3.4.7",
35
+ "@vitessce/vit-s": "3.4.7",
36
+ "@vitessce/gl": "3.4.7"
34
37
  },
35
38
  "devDependencies": {
36
39
  "react": "^18.0.0",
@@ -1,20 +1,9 @@
1
1
  /* eslint-disable indent */
2
2
  /* eslint-disable camelcase */
3
3
  import React, { useMemo, useEffect, useRef } from 'react';
4
- import { scaleLinear } from 'd3-scale';
4
+ import { scaleLinear, scaleOrdinal } from 'd3-scale';
5
5
  import { scale as vega_scale } from 'vega-scale';
6
6
  import { axisBottom, axisLeft } from 'd3-axis';
7
- import {
8
- bin,
9
- min,
10
- max,
11
- rollup as d3_rollup,
12
- mean as d3_mean,
13
- deviation as d3_deviation,
14
- ascending as d3_ascending,
15
- map as d3_map,
16
- quantileSorted,
17
- } from 'd3-array';
18
7
  import { area as d3_area, curveBasis } from 'd3-shape';
19
8
  import { select } from 'd3-selection';
20
9
  import { colorArrayToString } from '@vitessce/sets-utils';
@@ -22,44 +11,6 @@ import { capitalize } from '@vitessce/utils';
22
11
 
23
12
  const scaleBand = vega_scale('band');
24
13
 
25
- const GROUP_KEY = 'set';
26
- const VALUE_KEY = 'value';
27
-
28
- // Reference: https://github.com/d3/d3-array/issues/180#issuecomment-851378012
29
- function summarize(iterable, keepZeros) {
30
- const values = d3_map(iterable, d => d[VALUE_KEY])
31
- .filter(d => keepZeros || d !== 0.0)
32
- .sort(d3_ascending);
33
- const minVal = values[0];
34
- const maxVal = values[values.length - 1];
35
- const q1 = quantileSorted(values, 0.25);
36
- const q2 = quantileSorted(values, 0.5);
37
- const q3 = quantileSorted(values, 0.75);
38
- const iqr = q3 - q1; // interquartile range
39
- const r0 = Math.max(minVal, q1 - iqr * 1.5);
40
- const r1 = Math.min(maxVal, q3 + iqr * 1.5);
41
- let i = -1;
42
- while (values[++i] < r0);
43
- const w0 = values[i];
44
- while (values[++i] <= r1);
45
- const w1 = values[i - 1];
46
-
47
- // Chauvenet
48
- // Reference: https://en.wikipedia.org/wiki/Chauvenet%27s_criterion
49
- const mean = d3_mean(values);
50
- const stdv = d3_deviation(values);
51
- const c0 = mean - 3 * stdv;
52
- const c1 = mean + 3 * stdv;
53
-
54
- return {
55
- quartiles: [q1, q2, q3],
56
- range: [r0, r1],
57
- whiskers: [w0, w1],
58
- chauvenetRange: [c0, c1],
59
- nonOutliers: values.filter(v => c0 <= v && v <= c1),
60
- };
61
- }
62
-
63
14
  /**
64
15
  * Gene expression histogram displayed as a bar chart,
65
16
  * implemented with the VegaPlot component.
@@ -86,6 +37,9 @@ export default function CellSetExpressionPlot(props) {
86
37
  yMin: yMinProp,
87
38
  yUnits,
88
39
  jitter,
40
+ cellSetSelection,
41
+ sampleSetSelection,
42
+ sampleSetColor,
89
43
  colors,
90
44
  data,
91
45
  theme,
@@ -104,11 +58,19 @@ export default function CellSetExpressionPlot(props) {
104
58
  const svgRef = useRef();
105
59
 
106
60
  // Get the max characters in an axis label for autsizing the bottom margin.
107
- const maxCharactersForLabel = useMemo(() => data.reduce((acc, val) => {
108
- // eslint-disable-next-line no-param-reassign
109
- acc = acc === undefined || val[GROUP_KEY].length > acc ? val[GROUP_KEY].length : acc;
110
- return acc;
111
- }, 0), [data]);
61
+ const maxCharactersForLabel = useMemo(() => {
62
+ if (!cellSetSelection) {
63
+ return 0;
64
+ }
65
+ const cellSetNames = cellSetSelection.map(d => d.at(-1));
66
+ return cellSetNames.reduce((acc, name) => {
67
+ // eslint-disable-next-line no-param-reassign
68
+ acc = acc === undefined || name.length > acc ? name.length : acc;
69
+ return acc;
70
+ }, 0);
71
+ }, [cellSetSelection]);
72
+
73
+ const isStratified = (Array.isArray(sampleSetSelection) && sampleSetSelection.length === 2);
112
74
 
113
75
  useEffect(() => {
114
76
  const domElement = svgRef.current;
@@ -130,6 +92,8 @@ export default function CellSetExpressionPlot(props) {
130
92
 
131
93
  const rectColor = (theme === 'dark' ? 'white' : 'black');
132
94
 
95
+ const fgColor = (theme === 'dark' ? 'white' : 'black');
96
+
133
97
  const svg = select(domElement);
134
98
  svg.selectAll('g').remove();
135
99
  svg
@@ -141,48 +105,52 @@ export default function CellSetExpressionPlot(props) {
141
105
  .attr('width', width)
142
106
  .attr('height', height);
143
107
 
144
- const groupNames = colors.map(d => d.name);
145
-
146
108
  // Manually set the color scale so that Vega-Lite does
147
109
  // not choose the colors automatically.
148
- const colorScale = {
149
- domain: colors.map(d => d.name),
150
- range: colors.map(d => colorArrayToString(d.color)),
151
- };
110
+ const colorScale = scaleOrdinal()
111
+ .domain(colors.map(d => d.setNamePath))
112
+ .range(colors.map(d => colorArrayToString(d.color)));
113
+
114
+ const sampleSetNames = sampleSetSelection?.map(path => path.at(-1));
115
+
116
+ let stratificationSide;
117
+ let stratificationColor;
118
+ if (isStratified) {
119
+ stratificationSide = scaleOrdinal()
120
+ .domain(sampleSetNames)
121
+ .range(['left', 'right']);
122
+ stratificationColor = scaleOrdinal()
123
+ .domain(sampleSetNames)
124
+ .range(
125
+ // TODO: check for full path equality here.
126
+ sampleSetNames
127
+ .map(name => sampleSetColor?.find(d => d.path.at(-1) === name).color)
128
+ .map(colorArrayToString),
129
+ );
130
+ }
152
131
 
153
132
  // Remove outliers on a per-group basis.
154
- const groupedSummaries = Array.from(
155
- d3_rollup(data, groupData => summarize(groupData, true), d => d[GROUP_KEY]),
156
- ([key, value]) => ({ key, value }),
157
- );
158
- const groupedData = groupedSummaries
159
- .map(({ key, value }) => ({ key, value: value.nonOutliers }));
160
- const trimmedData = groupedData.map(kv => kv.value).flat();
133
+ const {
134
+ groupSummaries: groupedSummaries,
135
+ groupData: groupedData,
136
+ groupBins, // Array of [{ key, value: [{ key, value: histogram(nonOutliers) }] }]
137
+ groupBinsMax, // Number
138
+ } = data;
139
+ let { y } = data;
161
140
 
162
141
  const innerWidth = width - marginLeft;
163
142
  const innerHeight = height - autoMarginBottom;
164
143
 
165
144
  const xGroup = scaleBand()
166
145
  .range([marginLeft, width - marginRight])
167
- .domain(groupNames)
146
+ .domain(cellSetSelection)
168
147
  .padding(0.1);
169
148
 
170
- const yMin = (yMinProp === null ? Math.min(0, min(trimmedData)) : yMinProp);
171
149
 
172
150
  // For the y domain, use the yMin prop
173
151
  // to support a use case such as 'Aspect Ratio',
174
152
  // where the domain minimum should be 1 rather than 0.
175
- const y = scaleLinear()
176
- .domain([yMin, max(trimmedData)])
177
- .range([innerHeight, marginTop]);
178
-
179
- const histogram = bin()
180
- .thresholds(y.ticks(16))
181
- .domain(y.domain());
182
-
183
- const groupBins = groupedData.map(kv => ({ key: kv.key, value: histogram(kv.value) }));
184
-
185
- const groupBinsMax = max(groupBins.flatMap(d => d.value.map(v => v.length)));
153
+ y = y.range([innerHeight, marginTop]);
186
154
 
187
155
  const x = scaleLinear()
188
156
  .domain([-groupBinsMax, groupBinsMax])
@@ -194,18 +162,54 @@ export default function CellSetExpressionPlot(props) {
194
162
  .y(d => y(d.x0))
195
163
  .curve(curveBasis);
196
164
 
165
+ const leftArea = d3_area()
166
+ .x0(d => x(-d.length))
167
+ .x1(() => x(0))
168
+ .y(d => y(d.x0))
169
+ .curve(curveBasis);
170
+
171
+ const rightArea = d3_area()
172
+ .x0(() => x(0))
173
+ .x1(d => x(d.length))
174
+ .y(d => y(d.x0))
175
+ .curve(curveBasis);
176
+
177
+ const sideToAreaFunc = {
178
+ left: leftArea,
179
+ right: rightArea,
180
+ };
181
+
197
182
  // Violin areas
198
- g
199
- .selectAll('violin')
200
- .data(groupBins)
201
- .enter()
202
- .append('g')
203
- .attr('transform', d => `translate(${xGroup(d.key)},0)`)
204
- .style('fill', d => colorScale.range[groupNames.indexOf(d.key)])
205
- .append('path')
206
- .datum(d => d.value)
207
- .style('stroke', 'none')
208
- .attr('d', d => area(d));
183
+ if (isStratified) {
184
+ const violinG = g
185
+ .selectAll('violin')
186
+ .data(groupBins)
187
+ .enter()
188
+ .append('g')
189
+ .attr('transform', d => `translate(${xGroup(d.key)},0)`);
190
+ violinG.append('path')
191
+ .datum(d => d.value[0])
192
+ .style('stroke', 'none')
193
+ .style('fill', d => stratificationColor(d.key))
194
+ .attr('d', d => sideToAreaFunc[stratificationSide(d.key)](d.value));
195
+ violinG.append('path')
196
+ .datum(d => d.value[1])
197
+ .style('stroke', 'none')
198
+ .style('fill', d => stratificationColor(d.key))
199
+ .attr('d', d => sideToAreaFunc[stratificationSide(d.key)](d.value));
200
+ } else {
201
+ g
202
+ .selectAll('violin')
203
+ .data(groupBins)
204
+ .enter()
205
+ .append('g')
206
+ .attr('transform', d => `translate(${xGroup(d.key)},0)`)
207
+ .style('fill', d => colorScale(d.key))
208
+ .append('path')
209
+ .datum(d => d.value[0])
210
+ .style('stroke', 'none')
211
+ .attr('d', d => area(d.value));
212
+ }
209
213
 
210
214
  // Whiskers
211
215
  const whiskerGroups = g.selectAll('whiskers')
@@ -213,38 +217,88 @@ export default function CellSetExpressionPlot(props) {
213
217
  .enter()
214
218
  .append('g')
215
219
  .attr('transform', d => `translate(${xGroup(d.key)},0)`);
216
- whiskerGroups.append('line')
217
- .datum(d => d.value)
218
- .attr('stroke', rectColor)
219
- .attr('x1', xGroup.bandwidth() / 2)
220
- .attr('x2', xGroup.bandwidth() / 2)
221
- .attr('y1', d => y(d.quartiles[0]))
222
- .attr('y2', d => y(d.quartiles[2]))
223
- .attr('stroke-width', 2);
224
- whiskerGroups.append('line')
225
- .datum(d => d.value)
226
- .attr('stroke', rectColor)
227
- .attr('x1', xGroup.bandwidth() / 2 - (jitter ? 0 : 4))
228
- .attr('x2', xGroup.bandwidth() / 2 + 4)
229
- .attr('y1', d => y(d.quartiles[1]))
230
- .attr('y2', d => y(d.quartiles[1]))
231
- .attr('stroke-width', 2);
220
+
221
+ if (isStratified) {
222
+ // Vertical line
223
+ whiskerGroups.append('line')
224
+ .datum(d => d.value[0])
225
+ .attr('stroke', rectColor)
226
+ .attr('x1', d => xGroup.bandwidth() / 2 + (stratificationSide(d.key) === 'left' ? -1.5 : 1.5))
227
+ .attr('x2', d => xGroup.bandwidth() / 2 + (stratificationSide(d.key) === 'left' ? -1.5 : 1.5))
228
+ .attr('y1', d => y(d.value.quartiles[0]))
229
+ .attr('y2', d => y(d.value.quartiles[2]))
230
+ .attr('stroke-width', 2);
231
+
232
+ whiskerGroups.append('line')
233
+ .datum(d => d.value[1])
234
+ .attr('stroke', rectColor)
235
+ .attr('x1', d => xGroup.bandwidth() / 2 + (stratificationSide(d.key) === 'left' ? -1.5 : 1.5))
236
+ .attr('x2', d => xGroup.bandwidth() / 2 + (stratificationSide(d.key) === 'left' ? -1.5 : 1.5))
237
+ .attr('y1', d => y(d.value.quartiles[0]))
238
+ .attr('y2', d => y(d.value.quartiles[2]))
239
+ .attr('stroke-width', 2);
240
+
241
+ // Horizontal line
242
+ whiskerGroups.append('line')
243
+ .datum(d => d.value[0])
244
+ .attr('stroke', rectColor)
245
+ .attr('x1', d => xGroup.bandwidth() / 2 + (stratificationSide(d.key) === 'left' ? -5.5 : 1.5))
246
+ .attr('x2', d => xGroup.bandwidth() / 2 + (stratificationSide(d.key) === 'left' ? -1.5 : 5.5))
247
+ .attr('y1', d => y(d.value.quartiles[1]))
248
+ .attr('y2', d => y(d.value.quartiles[1]))
249
+ .attr('stroke-width', 2);
250
+
251
+ whiskerGroups.append('line')
252
+ .datum(d => d.value[1])
253
+ .attr('stroke', rectColor)
254
+ .attr('x1', d => xGroup.bandwidth() / 2 + (stratificationSide(d.key) === 'left' ? -5.5 : 1.5))
255
+ .attr('x2', d => xGroup.bandwidth() / 2 + (stratificationSide(d.key) === 'left' ? -1.5 : 5.5))
256
+ .attr('y1', d => y(d.value.quartiles[1]))
257
+ .attr('y2', d => y(d.value.quartiles[1]))
258
+ .attr('stroke-width', 2);
259
+ } else {
260
+ // Vertical line
261
+ whiskerGroups.append('line')
262
+ .datum(d => d.value[0].value)
263
+ .attr('stroke', rectColor)
264
+ .attr('x1', xGroup.bandwidth() / 2)
265
+ .attr('x2', xGroup.bandwidth() / 2)
266
+ .attr('y1', d => y(d.quartiles[0]))
267
+ .attr('y2', d => y(d.quartiles[2]))
268
+ .attr('stroke-width', 2);
269
+
270
+ // Horizontal line
271
+ whiskerGroups.append('line')
272
+ .datum(d => d.value[0].value)
273
+ .attr('stroke', rectColor)
274
+ .attr('x1', xGroup.bandwidth() / 2 - (jitter ? 0 : 4))
275
+ .attr('x2', xGroup.bandwidth() / 2 + 4)
276
+ .attr('y1', d => y(d.quartiles[1]))
277
+ .attr('y2', d => y(d.quartiles[1]))
278
+ .attr('stroke-width', 2);
279
+ }
232
280
 
233
281
  // Jittered points
234
282
  if (jitter) {
235
283
  groupedData.forEach(({ key, value }) => {
236
- const groupG = g.append('g');
237
- groupG.selectAll('point')
238
- .data(value)
239
- .enter()
240
- .append('circle')
241
- .attr('transform', `translate(${xGroup(key)},0)`)
242
- .style('stroke', 'none')
243
- .style('fill', 'silver')
244
- .style('opacity', '0.1')
245
- .attr('cx', () => 5 + Math.random() * ((xGroup.bandwidth() / 2) - 10))
246
- .attr('cy', d => y(d))
247
- .attr('r', 2);
284
+ value.forEach(({ value: subValue }) => {
285
+ if (isStratified) {
286
+ // TODO
287
+ } else {
288
+ const groupG = g.append('g');
289
+ groupG.selectAll('point')
290
+ .data(subValue)
291
+ .enter()
292
+ .append('circle')
293
+ .attr('transform', `translate(${xGroup(key)},0)`)
294
+ .style('stroke', 'none')
295
+ .style('fill', 'silver')
296
+ .style('opacity', '0.1')
297
+ .attr('cx', () => 5 + Math.random() * ((xGroup.bandwidth() / 2) - 10))
298
+ .attr('cy', d => y(d))
299
+ .attr('r', 2);
300
+ }
301
+ });
248
302
  });
249
303
  }
250
304
 
@@ -261,7 +315,7 @@ export default function CellSetExpressionPlot(props) {
261
315
  .append('g')
262
316
  .attr('transform', `translate(0,${innerHeight})`)
263
317
  .style('font-size', '14px')
264
- .call(axisBottom(xGroup))
318
+ .call(axisBottom(xGroup).tickFormat(d => d.at(-1)))
265
319
  .selectAll('text')
266
320
  .style('font-size', '11px')
267
321
  .attr('dx', '-6px')
@@ -278,7 +332,7 @@ export default function CellSetExpressionPlot(props) {
278
332
  .attr('transform', 'rotate(-90)')
279
333
  .text(yTitle)
280
334
  .style('font-size', '12px')
281
- .style('fill', 'white');
335
+ .style('fill', fgColor);
282
336
 
283
337
  // X-axis title
284
338
  g
@@ -288,11 +342,56 @@ export default function CellSetExpressionPlot(props) {
288
342
  .attr('y', height - 10)
289
343
  .text(xTitle)
290
344
  .style('font-size', '12px')
291
- .style('fill', 'white');
345
+ .style('fill', fgColor);
346
+
347
+ // Legend
348
+ if (isStratified) {
349
+ const legendG = g
350
+ .append('g')
351
+ .attr('transform', `translate(${marginLeft + innerWidth - 150},${marginTop})`);
352
+ legendG.append('rect')
353
+ .attr('width', 150)
354
+ .attr('height', 56)
355
+ .attr('x', 0)
356
+ .attr('y', 0)
357
+ .style('fill', 'rgba(215, 215, 215, 0.2)')
358
+ .attr('rx', 4);
359
+ legendG.append('text')
360
+ .text('Sample Group')
361
+ .style('font-size', '11px')
362
+ .style('line-height', 20)
363
+ .attr('x', 4)
364
+ .attr('y', 14)
365
+ .style('fill', fgColor);
366
+ legendG.append('rect')
367
+ .attr('width', 10)
368
+ .attr('height', 10)
369
+ .attr('x', 5)
370
+ .attr('y', 23)
371
+ .style('fill', stratificationColor(sampleSetNames[0]));
372
+ legendG.append('text')
373
+ .text(sampleSetNames[0])
374
+ .style('font-size', '11px')
375
+ .attr('x', 20)
376
+ .attr('y', 32)
377
+ .style('fill', fgColor);
378
+ legendG.append('rect')
379
+ .attr('width', 10)
380
+ .attr('height', 10)
381
+ .attr('x', 5)
382
+ .attr('y', 39)
383
+ .style('fill', stratificationColor(sampleSetNames[1]));
384
+ legendG.append('text')
385
+ .text(sampleSetNames[1])
386
+ .style('font-size', '11px')
387
+ .attr('x', 20)
388
+ .attr('y', 48)
389
+ .style('fill', fgColor);
390
+ }
292
391
  }, [width, height, data, marginLeft, marginBottom, colors,
293
392
  jitter, theme, yMinProp, marginTop, marginRight, featureType,
294
393
  featureValueType, featureValueTransformName, yUnits, obsType,
295
- maxCharactersForLabel,
394
+ maxCharactersForLabel, sampleSetSelection,
296
395
  ]);
297
396
 
298
397
  return (
@@ -1,7 +1,8 @@
1
1
  import React from 'react';
2
2
  import { useId } from 'react-aria';
3
- import { TableCell, TableRow, TextField } from '@material-ui/core';
3
+ import { TableCell, TableRow, TextField, Slider } from '@material-ui/core';
4
4
  import { usePlotOptionsStyles, OptionsContainer, OptionSelect } from '@vitessce/vit-s';
5
+ import { GLSL_COLORMAPS } from '@vitessce/gl';
5
6
 
6
7
  export default function CellSetExpressionPlotOptions(props) {
7
8
  const {
@@ -10,16 +11,28 @@ export default function CellSetExpressionPlotOptions(props) {
10
11
  featureValueTransformCoefficient,
11
12
  setFeatureValueTransformCoefficient,
12
13
  transformOptions,
14
+ featureValuePositivityThreshold,
15
+ setFeatureValuePositivityThreshold,
16
+ featureValueColormap,
17
+ setFeatureValueColormap,
13
18
  } = props;
14
19
 
15
20
  const cellSetExpressionPlotOptionsId = useId();
16
21
 
17
22
  const classes = usePlotOptionsStyles();
18
23
 
24
+ function handleFeatureValueColormapChange(event) {
25
+ setFeatureValueColormap(event.target.value);
26
+ }
27
+
19
28
  const handleTransformChange = (event) => {
20
29
  setFeatureValueTransform(event.target.value === '' ? null : event.target.value);
21
30
  };
22
31
 
32
+ function handlePositivityThresholdChange(event, value) {
33
+ setFeatureValuePositivityThreshold(value);
34
+ }
35
+
23
36
  // Feels a little hacky, but I think this is the best way to handle
24
37
  // the limitations of the v4 material-ui number input.
25
38
  const handleTransformCoefficientChange = (event) => {
@@ -36,6 +49,30 @@ export default function CellSetExpressionPlotOptions(props) {
36
49
 
37
50
  return (
38
51
  <OptionsContainer>
52
+ {setFeatureValueColormap ? (
53
+ <TableRow>
54
+ <TableCell className={classes.labelCell} variant="head" scope="row">
55
+ <label htmlFor={`cellset-expression-feature-value-colormap-${cellSetExpressionPlotOptionsId}`}>
56
+ Feature Value Colormap
57
+ </label>
58
+ </TableCell>
59
+ <TableCell className={classes.inputCell} variant="body">
60
+ <OptionSelect
61
+ className={classes.select}
62
+ value={featureValueColormap}
63
+ onChange={handleFeatureValueColormapChange}
64
+ inputProps={{
65
+ 'aria-label': 'Select feature value colormap',
66
+ id: `cellset-expression-feature-value-colormap-${cellSetExpressionPlotOptionsId}`,
67
+ }}
68
+ >
69
+ {GLSL_COLORMAPS.map(cmap => (
70
+ <option key={cmap} value={cmap}>{cmap}</option>
71
+ ))}
72
+ </OptionSelect>
73
+ </TableCell>
74
+ </TableRow>
75
+ ) : null}
39
76
  <TableRow>
40
77
  <TableCell className={classes.labelCell} variant="head" scope="row">
41
78
  <label
@@ -82,6 +119,25 @@ export default function CellSetExpressionPlotOptions(props) {
82
119
  />
83
120
  </TableCell>
84
121
  </TableRow>
122
+ {setFeatureValuePositivityThreshold ? (
123
+ <TableRow key="transform-coefficient-option-row">
124
+ <TableCell className={classes.labelCell}>
125
+ Positivity Threshold
126
+ </TableCell>
127
+ <TableCell className={classes.inputCell}>
128
+ <Slider
129
+ classes={{ root: classes.slider, valueLabel: classes.sliderValueLabel }}
130
+ value={featureValuePositivityThreshold}
131
+ onChange={handlePositivityThresholdChange}
132
+ aria-labelledby="pos-threshold-slider"
133
+ valueLabelDisplay="auto"
134
+ step={1.0}
135
+ min={0.0}
136
+ max={100.0}
137
+ />
138
+ </TableCell>
139
+ </TableRow>
140
+ ) : null}
85
141
  </OptionsContainer>
86
142
  );
87
143
  }