@vitessce/statistical-plots 3.3.3 → 3.3.5

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.
@@ -1 +1 @@
1
- {"version":3,"file":"CellSetExpressionPlot.d.ts","sourceRoot":"","sources":["../src/CellSetExpressionPlot.js"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;;;;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;gBA6PrB"}
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,9 +1,52 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import React from 'react';
3
- import { clamp } from 'lodash-es';
4
- import { VegaPlot, VEGA_THEMES, DATASET_NAME } from '@vitessce/vega';
2
+ /* eslint-disable indent */
3
+ /* eslint-disable camelcase */
4
+ import React, { useMemo, useEffect, useRef } from 'react';
5
+ import { scaleLinear } from 'd3-scale';
6
+ import { scale as vega_scale } from 'vega-scale';
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
+ import { area as d3_area, curveBasis } from 'd3-shape';
10
+ import { select } from 'd3-selection';
5
11
  import { colorArrayToString } from '@vitessce/sets-utils';
6
12
  import { capitalize } from '@vitessce/utils';
13
+ 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
+ }
7
50
  /**
8
51
  * Gene expression histogram displayed as a bar chart,
9
52
  * implemented with the VegaPlot component.
@@ -26,223 +69,175 @@ import { capitalize } from '@vitessce/utils';
26
69
  * for the feature value transformation function.
27
70
  */
28
71
  export default function CellSetExpressionPlot(props) {
29
- const { domainMax = 100, colors, data, theme, width, height, marginRight = 90, marginBottom, obsType, featureValueType, featureValueTransformName, } = props;
72
+ const { yMin: yMinProp, yUnits, jitter, colors, data, theme, width, height, marginTop = 5, marginRight = 5, marginLeft = 50, marginBottom, obsType, featureType, featureValueType, featureValueTransformName, } = props;
73
+ const svgRef = useRef();
30
74
  // Get the max characters in an axis label for autsizing the bottom margin.
31
- const maxCharactersForLabel = data.reduce((acc, val) => {
75
+ const maxCharactersForLabel = useMemo(() => data.reduce((acc, val) => {
32
76
  // eslint-disable-next-line no-param-reassign
33
- acc = acc === undefined || val.set.length > acc ? val.set.length : acc;
77
+ acc = acc === undefined || val[GROUP_KEY].length > acc ? val[GROUP_KEY].length : acc;
34
78
  return acc;
35
- }, 0);
36
- // Use a square-root term because the angle of the labels is 45 degrees (see below)
37
- // so the perpendicular distance to the bottom of the labels is proportional to the
38
- // square root of the length of the labels along the imaginary hypotenuse.
39
- // 30 is an estimate of the pixel size of a given character and seems to work well.
40
- const autoMarginBottom = marginBottom
41
- || 30 + Math.sqrt(maxCharactersForLabel / 2) * 30;
42
- // Manually set the color scale so that Vega-Lite does
43
- // not choose the colors automatically.
44
- const colorScale = {
45
- domain: colors.map(d => d.name),
46
- range: colors.map(d => colorArrayToString(d.color)),
47
- };
48
- const plotWidth = clamp(width - marginRight, 10, Infinity);
49
- const plotHeight = clamp(height - autoMarginBottom, 10, Infinity);
50
- const numBands = colors.length;
51
- const bandWidth = plotWidth / numBands;
52
- const rectColor = (theme === 'dark' ? 'white' : 'black');
53
- const spec = {
54
- $schema: 'https://vega.github.io/schema/vega/v5.json',
55
- description: `A violin plot showing distributions of expression levels for selected ${obsType} sets.`,
56
- width: plotWidth,
57
- height: plotHeight,
58
- config: {
59
- ...VEGA_THEMES[theme],
60
- axisBand: {
61
- bandPosition: 1,
62
- tickExtra: true,
63
- tickOffset: 0,
64
- },
65
- },
66
- signals: [
67
- { name: 'bandWidth', value: bandWidth },
68
- { name: 'width', value: plotWidth },
69
- { name: 'height', value: plotHeight },
70
- { name: 'trim', value: true },
71
- ],
72
- data: [
73
- {
74
- name: 'density',
75
- source: DATASET_NAME,
76
- transform: [
77
- {
78
- type: 'kde',
79
- field: 'value',
80
- groupby: ['set'],
81
- bandwidth: 0,
82
- extent: [0, domainMax],
83
- },
84
- ],
85
- },
86
- {
87
- name: 'stats',
88
- source: DATASET_NAME,
89
- transform: [
90
- {
91
- type: 'aggregate',
92
- groupby: ['set'],
93
- fields: ['value', 'value', 'value'],
94
- ops: ['q1', 'median', 'q3'],
95
- as: ['q1', 'median', 'q3'],
96
- },
97
- ],
98
- },
99
- ],
100
- scales: [
101
- {
102
- name: 'layout',
103
- type: 'band',
104
- range: 'width',
105
- domain: { data: DATASET_NAME, field: 'set' },
106
- },
107
- {
108
- name: 'yscale',
109
- type: 'linear',
110
- range: 'height',
111
- domain: [0, domainMax],
112
- },
113
- {
114
- name: 'wscale',
115
- type: 'linear',
116
- range: [0, { signal: 'bandWidth' }],
117
- domain: { data: 'density', field: 'density' },
118
- },
119
- {
120
- name: 'wscaleReversed',
121
- type: 'linear',
122
- reverse: true,
123
- range: [0, { signal: 'bandWidth' }],
124
- domain: { data: 'density', field: 'density' },
125
- },
126
- {
127
- name: 'color',
128
- type: 'ordinal',
129
- ...colorScale,
130
- },
131
- ],
132
- axes: [
133
- {
134
- orient: 'left',
135
- scale: 'yscale',
136
- zindex: 1,
137
- title: (featureValueTransformName && featureValueTransformName !== 'None')
138
- ? [`${featureValueTransformName}-Transformed`, `${capitalize(featureValueType)} Values`]
139
- : `${capitalize(featureValueType)} Values`,
140
- },
141
- {
142
- orient: 'bottom',
143
- scale: 'layout',
144
- tickCount: 5,
145
- zindex: 1,
146
- title: `${capitalize(obsType)} Set`,
147
- labelAngle: -45,
148
- labelAlign: 'right',
149
- },
150
- ],
151
- marks: [
152
- {
153
- type: 'group',
154
- from: {
155
- facet: {
156
- data: 'density',
157
- name: 'violin',
158
- groupby: 'set',
159
- },
160
- },
161
- encode: {
162
- enter: {
163
- xc: { scale: 'layout', field: 'set', band: 0.5 },
164
- width: { signal: 'bandWidth' },
165
- height: { signal: 'height' },
166
- },
167
- },
168
- data: [
169
- {
170
- name: 'summary',
171
- source: 'stats',
172
- transform: [
173
- {
174
- type: 'filter',
175
- expr: 'datum.set === parent.set',
176
- },
177
- ],
178
- },
179
- ],
180
- marks: [
181
- {
182
- type: 'area',
183
- orient: 'vertical',
184
- from: { data: 'violin' },
185
- encode: {
186
- enter: {
187
- fill: { scale: 'color', field: { parent: 'set' } },
188
- },
189
- update: {
190
- width: { scale: 'wscale', field: 'density' },
191
- xc: { signal: 'bandWidth / 2' },
192
- y2: { scale: 'yscale', field: 'value' },
193
- y: { scale: 'yscale', value: 0 },
194
- },
195
- },
196
- },
197
- {
198
- type: 'area',
199
- orient: 'vertical',
200
- from: { data: 'violin' },
201
- encode: {
202
- enter: {
203
- fill: { scale: 'color', field: { parent: 'set' } },
204
- },
205
- update: {
206
- width: { scale: 'wscaleReversed', field: 'density' },
207
- xc: { signal: 'bandWidth' },
208
- y2: { scale: 'yscale', field: 'value' },
209
- y: { scale: 'yscale', value: 0 },
210
- },
211
- },
212
- },
213
- {
214
- type: 'rect',
215
- from: { data: 'summary' },
216
- encode: {
217
- enter: {
218
- fill: { value: rectColor },
219
- width: { value: 2 },
220
- },
221
- update: {
222
- y: { scale: 'yscale', field: 'q1' },
223
- y2: { scale: 'yscale', field: 'q3' },
224
- xc: { signal: 'bandWidth / 2' },
225
- },
226
- },
227
- },
228
- {
229
- type: 'rect',
230
- from: { data: 'summary' },
231
- encode: {
232
- enter: {
233
- fill: { value: rectColor },
234
- height: { value: 2 },
235
- width: { value: 8 },
236
- },
237
- update: {
238
- y: { scale: 'yscale', field: 'median' },
239
- xc: { signal: 'bandWidth / 2' },
240
- },
241
- },
242
- },
243
- ],
244
- },
245
- ],
246
- };
247
- return (_jsx(VegaPlot, { data: data, spec: spec }));
79
+ }, 0), [data]);
80
+ useEffect(() => {
81
+ const domElement = svgRef.current;
82
+ const transformPrefix = (featureValueTransformName && featureValueTransformName !== 'None')
83
+ ? `${featureValueTransformName}-Transformed `
84
+ : '';
85
+ const unitSuffix = yUnits ? ` (${yUnits})` : '';
86
+ const yTitle = `${transformPrefix}${capitalize(featureValueType)}${unitSuffix}`;
87
+ const xTitle = `${capitalize(obsType)} Set`;
88
+ // Use a square-root term because the angle of the labels is 45 degrees (see below)
89
+ // so the perpendicular distance to the bottom of the labels is proportional to the
90
+ // square root of the length of the labels along the imaginary hypotenuse.
91
+ // 30 is an estimate of the pixel size of a given character and seems to work well.
92
+ const autoMarginBottom = marginBottom
93
+ || 30 + Math.sqrt(maxCharactersForLabel / 2) * 30;
94
+ const rectColor = (theme === 'dark' ? 'white' : 'black');
95
+ const svg = select(domElement);
96
+ svg.selectAll('g').remove();
97
+ svg
98
+ .attr('width', width)
99
+ .attr('height', height);
100
+ const g = svg
101
+ .append('g')
102
+ .attr('width', width)
103
+ .attr('height', height);
104
+ const groupNames = colors.map(d => d.name);
105
+ // Manually set the color scale so that Vega-Lite does
106
+ // not choose the colors automatically.
107
+ const colorScale = {
108
+ domain: colors.map(d => d.name),
109
+ range: colors.map(d => colorArrayToString(d.color)),
110
+ };
111
+ // 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();
116
+ const innerWidth = width - marginLeft;
117
+ const innerHeight = height - autoMarginBottom;
118
+ const xGroup = scaleBand()
119
+ .range([marginLeft, width - marginRight])
120
+ .domain(groupNames)
121
+ .padding(0.1);
122
+ const yMin = (yMinProp === null ? Math.min(0, min(trimmedData)) : yMinProp);
123
+ // For the y domain, use the yMin prop
124
+ // to support a use case such as 'Aspect Ratio',
125
+ // 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)));
134
+ const x = scaleLinear()
135
+ .domain([-groupBinsMax, groupBinsMax])
136
+ .range([0, xGroup.bandwidth()]);
137
+ const area = d3_area()
138
+ .x0(d => (jitter ? x(0) : x(-d.length)))
139
+ .x1(d => x(d.length))
140
+ .y(d => y(d.x0))
141
+ .curve(curveBasis);
142
+ // 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));
154
+ // Whiskers
155
+ const whiskerGroups = g.selectAll('whiskers')
156
+ .data(groupedSummaries)
157
+ .enter()
158
+ .append('g')
159
+ .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);
176
+ // Jittered points
177
+ if (jitter) {
178
+ 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);
191
+ });
192
+ }
193
+ // Y-axis ticks
194
+ g
195
+ .append('g')
196
+ .attr('transform', `translate(${marginLeft},0)`)
197
+ .call(axisLeft(y))
198
+ .selectAll('text')
199
+ .style('font-size', '11px');
200
+ // X-axis ticks
201
+ g
202
+ .append('g')
203
+ .attr('transform', `translate(0,${innerHeight})`)
204
+ .style('font-size', '14px')
205
+ .call(axisBottom(xGroup))
206
+ .selectAll('text')
207
+ .style('font-size', '11px')
208
+ .attr('dx', '-6px')
209
+ .attr('dy', '6px')
210
+ .attr('transform', 'rotate(-45)')
211
+ .style('text-anchor', 'end');
212
+ // Y-axis title
213
+ g
214
+ .append('text')
215
+ .attr('text-anchor', 'middle')
216
+ .attr('x', -innerHeight / 2)
217
+ .attr('y', 15)
218
+ .attr('transform', 'rotate(-90)')
219
+ .text(yTitle)
220
+ .style('font-size', '12px')
221
+ .style('fill', 'white');
222
+ // X-axis title
223
+ g
224
+ .append('text')
225
+ .attr('text-anchor', 'middle')
226
+ .attr('x', marginLeft + innerWidth / 2)
227
+ .attr('y', height - 10)
228
+ .text(xTitle)
229
+ .style('font-size', '12px')
230
+ .style('fill', 'white');
231
+ }, [width, height, data, marginLeft, marginBottom, colors,
232
+ jitter, theme, yMinProp, marginTop, marginRight, featureType,
233
+ featureValueType, featureValueTransformName, yUnits, obsType,
234
+ maxCharactersForLabel,
235
+ ]);
236
+ return (_jsx("svg", { ref: svgRef, style: {
237
+ top: 0,
238
+ left: 0,
239
+ width: `${width}px`,
240
+ height: `${height}px`,
241
+ position: 'relative',
242
+ } }));
248
243
  }
@@ -1 +1 @@
1
- {"version":3,"file":"CellSetExpressionPlotSubscriber.d.ts","sourceRoot":"","sources":["../src/CellSetExpressionPlotSubscriber.js"],"names":[],"mappings":"AAiGA;;;;;;;;;GASG;AACH;IAL2B,mBAAmB;IACrB,kBAAkB,EAAhC,MAAM;IAEQ,KAAK,EAAnB,MAAM;gBA0HhB"}
1
+ {"version":3,"file":"CellSetExpressionPlotSubscriber.d.ts","sourceRoot":"","sources":["../src/CellSetExpressionPlotSubscriber.js"],"names":[],"mappings":"AAiGA;;;;;;;;;GASG;AACH;IAL2B,mBAAmB;IACrB,kBAAkB,EAAhC,MAAM;IAEQ,KAAK,EAAnB,MAAM;gBAgIhB"}
@@ -77,7 +77,7 @@ function useExpressionByCellSet(expressionData, obsIndex, cellSets, additionalCe
77
77
  * @param {string} props.theme The name of the current Vitessce theme.
78
78
  */
79
79
  export function CellSetExpressionPlotSubscriber(props) {
80
- const { coordinationScopes, closeButtonVisible, downloadButtonVisible, removeGridComponent, theme, } = props;
80
+ const { coordinationScopes, closeButtonVisible, downloadButtonVisible, removeGridComponent, theme, jitter = false, yMin = null, yUnits = null, } = props;
81
81
  const classes = useStyles();
82
82
  const loaders = useLoaders();
83
83
  // Get "props" from the coordination space.
@@ -102,10 +102,10 @@ export function CellSetExpressionPlotSubscriber(props) {
102
102
  matrixIndicesUrls,
103
103
  obsSetsUrls,
104
104
  ]);
105
- const [expressionArr, setArr, expressionMax] = useExpressionByCellSet(expressionData, obsIndex, cellSets, additionalCellSets, geneSelection, cellSetSelection, cellSetColor, featureValueTransform, featureValueTransformCoefficient, theme);
105
+ const [expressionArr, setArr] = useExpressionByCellSet(expressionData, obsIndex, cellSets, additionalCellSets, geneSelection, cellSetSelection, cellSetColor, featureValueTransform, featureValueTransformCoefficient, theme);
106
106
  const firstGeneSelected = geneSelection && geneSelection.length >= 1
107
107
  ? (featureLabelsMap?.get(geneSelection[0]) || geneSelection[0])
108
108
  : null;
109
109
  const selectedTransformName = transformOptions.find(o => o.value === featureValueTransform)?.name;
110
- 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, { domainMax: expressionMax, colors: setArr, data: expressionArr, theme: theme, width: width, height: height, obsType: obsType, featureValueType: featureValueType, featureValueTransformName: selectedTransformName })) : (_jsxs("span", { children: ["Select a ", featureType, "."] })) }) }));
110
+ 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, "."] })) }) }));
111
111
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ExpressionHistogram.d.ts","sourceRoot":"","sources":["../src/ExpressionHistogram.js"],"names":[],"mappings":"AAIA;;;;;GAKG;AAEH;;;;;;;;;;;;;;;;;GAiBG;AACH;IAd2B,aAAa,EAA7B,MAAM,EAAE;IAEQ,IAAI,EAApB,MAAM,EAAE;IAEM,KAAK,EAAnB,MAAM;IACQ,KAAK,EAAnB,MAAM;IACQ,MAAM,EAApB,MAAM;IACQ,WAAW,EAAzB,MAAM;IAGQ,YAAY,EAA1B,MAAM;gBA8FhB"}
1
+ {"version":3,"file":"ExpressionHistogram.d.ts","sourceRoot":"","sources":["../src/ExpressionHistogram.js"],"names":[],"mappings":"AAKA;;;;;GAKG;AAEH;;;;;;;;;;;;;;;;;GAiBG;AACH;IAd2B,aAAa,EAA7B,MAAM,EAAE;IAEQ,IAAI,EAApB,MAAM,EAAE;IAEM,KAAK,EAAnB,MAAM;IACQ,KAAK,EAAnB,MAAM;IACQ,MAAM,EAApB,MAAM;IACQ,WAAW,EAAzB,MAAM;IAGQ,YAAY,EAA1B,MAAM;gBAqGhB"}
@@ -2,6 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import React, { useState, useEffect, useCallback } from 'react';
3
3
  import { clamp, debounce } from 'lodash-es';
4
4
  import { VegaPlot, VEGA_THEMES } from '@vitessce/vega';
5
+ import { capitalize, pluralize } from '@vitessce/utils';
5
6
  /**
6
7
  * We use debounce, so that onSelect is called only after the user has finished the selection.
7
8
  * Due to vega-lite limitations, we cannot use the vega-lite signals to implement this.
@@ -27,11 +28,13 @@ import { VegaPlot, VEGA_THEMES } from '@vitessce/vega';
27
28
  * By default, 50.
28
29
  */
29
30
  export default function ExpressionHistogram(props) {
30
- const { geneSelection, data, theme, width, height, marginRight = 90, marginBottom = 50, onSelect, } = props;
31
+ const { geneSelection, obsType, featureType, featureValueType, data, theme, width, height, marginRight = 90, marginBottom = 50, onSelect, } = props;
31
32
  const [selectedRanges, setSelectedRanges] = useState([]);
33
+ const isExpression = (featureType === 'gene' && featureValueType === 'expression');
34
+ // eslint-disable-next-line no-nested-ternary
32
35
  const xTitle = geneSelection && geneSelection.length >= 1
33
- ? 'Normalized Expression Value'
34
- : 'Total Normalized Transcript Count';
36
+ ? (isExpression ? `Expression Value (${geneSelection[0]})` : `${geneSelection[0]}`)
37
+ : (isExpression ? 'Total Transcript Count' : 'Sum of Feature Values');
35
38
  const spec = {
36
39
  data: { values: data },
37
40
  mark: 'bar',
@@ -45,7 +48,7 @@ export default function ExpressionHistogram(props) {
45
48
  y: {
46
49
  type: 'quantitative',
47
50
  aggregate: 'count',
48
- title: 'Number of Cells',
51
+ title: `Number of ${capitalize(pluralize(obsType, 2))}`,
49
52
  },
50
53
  color: { value: 'gray' },
51
54
  opacity: {
@@ -1 +1 @@
1
- {"version":3,"file":"ExpressionHistogramSubscriber.d.ts","sourceRoot":"","sources":["../src/ExpressionHistogramSubscriber.js"],"names":[],"mappings":"AAcA;;;;;;;;;GASG;AACH;IAL2B,mBAAmB;IACrB,kBAAkB,EAAhC,MAAM;IAEQ,KAAK,EAAnB,MAAM;gBAyHhB"}
1
+ {"version":3,"file":"ExpressionHistogramSubscriber.d.ts","sourceRoot":"","sources":["../src/ExpressionHistogramSubscriber.js"],"names":[],"mappings":"AAcA;;;;;;;;;GASG;AACH;IAL2B,mBAAmB;IACrB,kBAAkB,EAAhC,MAAM;IAEQ,KAAK,EAAnB,MAAM;gBA2HhB"}
@@ -44,8 +44,7 @@ export function ExpressionHistogramSubscriber(props) {
44
44
  return obsIndex.map((cellId, cellIndex) => {
45
45
  const value = expressionData[0][cellIndex];
46
46
  // Create new cellColors map based on the selected gene.
47
- const normValue = value * 100 / 255;
48
- const newItem = { value: normValue, gene: firstGeneSelected, cellId };
47
+ const newItem = { value, gene: firstGeneSelected, cellId };
49
48
  return newItem;
50
49
  });
51
50
  }
@@ -54,7 +53,7 @@ export function ExpressionHistogramSubscriber(props) {
54
53
  return obsIndex.map((cellId, cellIndex) => {
55
54
  const values = obsFeatureMatrix.data
56
55
  .subarray(cellIndex * numGenes, (cellIndex + 1) * numGenes);
57
- const sumValue = sum(values) * 100 / 255;
56
+ const sumValue = sum(values);
58
57
  const newItem = { value: sumValue, gene: null, cellId };
59
58
  return newItem;
60
59
  });
@@ -68,5 +67,5 @@ export function ExpressionHistogramSubscriber(props) {
68
67
  }, [additionalCellSets, cellSetColor, data, setAdditionalCellSets,
69
68
  setCellColorEncoding, setCellSetColor, setCellSetSelection, firstGeneSelected,
70
69
  ]);
71
- return (_jsx(TitleInfo, { title: `Expression Histogram${(firstGeneSelected ? ` (${firstGeneSelected})` : '')}`, closeButtonVisible: closeButtonVisible, downloadButtonVisible: downloadButtonVisible, removeGridComponent: removeGridComponent, urls: urls, theme: theme, isReady: isReady, children: _jsx("div", { ref: containerRef, className: classes.vegaContainer, children: _jsx(ExpressionHistogram, { geneSelection: geneSelection, onSelect: onSelect, data: data, theme: theme, width: width, height: height }) }) }));
70
+ return (_jsx(TitleInfo, { title: `Histogram${(firstGeneSelected ? ` (${firstGeneSelected})` : '')}`, closeButtonVisible: closeButtonVisible, downloadButtonVisible: downloadButtonVisible, removeGridComponent: removeGridComponent, urls: urls, theme: theme, isReady: isReady, children: _jsx("div", { ref: containerRef, className: classes.vegaContainer, children: _jsx(ExpressionHistogram, { geneSelection: geneSelection, obsType: obsType, featureType: featureType, featureValueType: featureValueType, onSelect: onSelect, data: data, theme: theme, width: width, height: height }) }) }));
72
71
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitessce/statistical-plots",
3
- "version": "3.3.3",
3
+ "version": "3.3.5",
4
4
  "author": "Gehlenborg Lab",
5
5
  "homepage": "http://vitessce.io",
6
6
  "repository": {
@@ -18,13 +18,18 @@
18
18
  "dependencies": {
19
19
  "@material-ui/core": "~4.12.3",
20
20
  "d3-array": "^2.4.0",
21
+ "d3-scale": "^4.0.0",
22
+ "d3-shape": "^3.2.0",
23
+ "d3-axis": "^3.0.0",
24
+ "d3-selection": "^3.0.0",
25
+ "vega-scale": "^6.0.0",
21
26
  "lodash-es": "^4.17.21",
22
27
  "react-aria": "^3.28.0",
23
- "@vitessce/constants-internal": "3.3.3",
24
- "@vitessce/sets-utils": "3.3.3",
25
- "@vitessce/utils": "3.3.3",
26
- "@vitessce/vega": "3.3.3",
27
- "@vitessce/vit-s": "3.3.3"
28
+ "@vitessce/sets-utils": "3.3.5",
29
+ "@vitessce/constants-internal": "3.3.5",
30
+ "@vitessce/utils": "3.3.5",
31
+ "@vitessce/vega": "3.3.5",
32
+ "@vitessce/vit-s": "3.3.5"
28
33
  },
29
34
  "devDependencies": {
30
35
  "react": "^18.0.0",