@vitessce/statistical-plots 2.0.0-beta.0

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2018
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,248 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import clamp from 'lodash/clamp';
4
+ import { VegaPlot, VEGA_THEMES, DATASET_NAME } from '@vitessce/vega';
5
+ import { colorArrayToString } from '@vitessce/sets';
6
+ import { capitalize } from '@vitessce/utils';
7
+ /**
8
+ * Gene expression histogram displayed as a bar chart,
9
+ * implemented with the VegaPlot component.
10
+ * @param {object} props
11
+ * @param {object[]} props.data The expression data, an array
12
+ * of objects with properties `value`, `gene`, and `set`.
13
+ * @param {number} props.domainMax The maximum gene expression value.
14
+ * @param {object[]} props.colors An object for each
15
+ * cell set, with properties `name` and `color`.
16
+ * @param {string} props.theme The name of the current Vitessce theme.
17
+ * @param {number} props.width The container width.
18
+ * @param {number} props.height The container height.
19
+ * @param {number} props.marginRight The size of the margin
20
+ * on the right side of the plot, to account for the vega menu button.
21
+ * By default, 90.
22
+ * @param {number} props.marginBottom The size of the margin
23
+ * on the bottom of the plot, to account for long x-axis labels.
24
+ * Default is allowing the component to automatically determine the margin.
25
+ * @param {string|null} props.featureValueTransformName A name
26
+ * for the feature value transformation function.
27
+ */
28
+ export default function CellSetExpressionPlot(props) {
29
+ const { domainMax = 100, colors, data, theme, width, height, marginRight = 90, marginBottom, obsType, featureValueType, featureValueTransformName, } = props;
30
+ // Get the max characters in an axis label for autsizing the bottom margin.
31
+ const maxCharactersForLabel = data.reduce((acc, val) => {
32
+ // eslint-disable-next-line no-param-reassign
33
+ acc = acc === undefined || val.set.length > acc ? val.set.length : acc;
34
+ 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
138
+ ? [`${featureValueTransformName}-Transformed`, `Normalized ${capitalize(featureValueType)} Values`]
139
+ : `Normalized ${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 }));
248
+ }
@@ -0,0 +1,32 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import TableCell from '@material-ui/core/TableCell';
4
+ import TableRow from '@material-ui/core/TableRow';
5
+ import TextField from '@material-ui/core/TextField';
6
+ import { usePlotOptionsStyles, OptionsContainer, OptionSelect } from '@vitessce/vit-s';
7
+ export default function CellSetExpressionPlotOptions(props) {
8
+ const { featureValueTransform, setFeatureValueTransform, featureValueTransformCoefficient, setFeatureValueTransformCoefficient, transformOptions, } = props;
9
+ const classes = usePlotOptionsStyles();
10
+ const handleTransformChange = (event) => {
11
+ setFeatureValueTransform(event.target.value === '' ? null : event.target.value);
12
+ };
13
+ // Feels a little hacky, but I think this is the best way to handle
14
+ // the limitations of the v4 material-ui number input.
15
+ const handleTransformCoefficientChange = (event) => {
16
+ const { value } = event.target;
17
+ if (!value) {
18
+ setFeatureValueTransformCoefficient(value);
19
+ }
20
+ else {
21
+ const newCoefficient = Number(value);
22
+ if (!Number.isNaN(newCoefficient) && newCoefficient >= 0) {
23
+ setFeatureValueTransformCoefficient(value);
24
+ }
25
+ }
26
+ };
27
+ return (_jsxs(OptionsContainer, { children: [_jsxs(TableRow, { children: [_jsx(TableCell, { className: classes.labelCell, children: "Transform" }), _jsx(TableCell, { className: classes.inputCell, children: _jsx(OptionSelect, { className: classes.select, value: featureValueTransform === null ? '' : featureValueTransform, onChange: handleTransformChange, inputProps: {
28
+ id: 'scatterplot-transform-select',
29
+ }, children: transformOptions.map(opt => (_jsx("option", { value: opt.value === null ? '' : opt.value, children: opt.name }, opt.name))) }, "gating-transform-select") })] }), _jsxs(TableRow, { children: [_jsx(TableCell, { className: classes.labelCell, children: "Transform Coefficient" }), _jsx(TableCell, { className: classes.inputCell, children: _jsx(TextField, { label: "Number", type: "number", onChange: handleTransformCoefficientChange, value: featureValueTransformCoefficient, InputLabelProps: {
30
+ shrink: true,
31
+ } }) })] }, "transform-coefficient-option-row")] }));
32
+ }
@@ -0,0 +1,108 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React, { useMemo } from 'react';
3
+ import { TitleInfo, useCoordination, useLoaders, useUrls, useReady, useGridItemSize, useFeatureSelection, useObsSetsData, useObsFeatureMatrixIndices, registerPluginViewType, } from '@vitessce/vit-s';
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';
7
+ import CellSetExpressionPlotOptions from './CellSetExpressionPlotOptions';
8
+ import CellSetExpressionPlot from './CellSetExpressionPlot';
9
+ import { useStyles } from './styles';
10
+ /**
11
+ * Get expression data for the cells
12
+ * in the selected cell sets.
13
+ * @param {object} expressionMatrix
14
+ * @param {string[]} expressionMatrix.rows Cell IDs.
15
+ * @param {string[]} expressionMatrix.cols Gene names.
16
+ * @param {Uint8Array} expressionMatrix.matrix The
17
+ * flattened expression matrix as a typed array.
18
+ * @param {object} cellSets The cell sets from the dataset.
19
+ * @param {object} additionalCellSets The user-defined cell sets
20
+ * from the coordination space.
21
+ * @param {array} geneSelection Array of selected genes.
22
+ * @param {array} cellSetSelection Array of selected cell set paths.
23
+ * @param {object[]} cellSetColor Array of objects with properties
24
+ * @param {string|null} featureValueTransform The name of the
25
+ * feature value transform function.
26
+ * @param {number} featureValueTransformCoefficient A coefficient
27
+ * to be used in the transform function.
28
+ * @param {string} theme "light" or "dark" for the vitessce theme
29
+ * `path` and `color`.
30
+ */
31
+ export function useExpressionByCellSet(expressionData, obsIndex, cellSets, additionalCellSets, geneSelection, cellSetSelection, cellSetColor, featureValueTransform, featureValueTransformCoefficient, theme) {
32
+ const mergedCellSets = useMemo(() => mergeObsSets(cellSets, additionalCellSets), [cellSets, additionalCellSets]);
33
+ // From the expression matrix and the list of selected genes / cell sets,
34
+ // generate the array of data points for the plot.
35
+ 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 normValue = value * 100 / 255;
51
+ const transformFunction = getValueTransformFunction(featureValueTransform, featureValueTransformCoefficient);
52
+ const transformedValue = transformFunction(normValue);
53
+ exprMax = Math.max(transformedValue, exprMax);
54
+ return { value: transformedValue, gene: firstGeneSelected, set: cell.name };
55
+ });
56
+ return [exprValues, exprMax];
57
+ }
58
+ return [null, null];
59
+ }, [expressionData, obsIndex, geneSelection, theme,
60
+ mergedCellSets, cellSetSelection, cellSetColor,
61
+ featureValueTransform, featureValueTransformCoefficient,
62
+ ]);
63
+ // From the cell sets hierarchy and the list of selected cell sets,
64
+ // generate the array of set sizes data points for the bar plot.
65
+ const setArr = useMemo(() => (mergedCellSets && cellSetSelection && cellSetColor
66
+ ? treeToSetSizesBySetNames(mergedCellSets, cellSetSelection, cellSetColor, theme)
67
+ : []), [mergedCellSets, cellSetSelection, cellSetColor, theme]);
68
+ return [expressionArr, setArr, expressionMax];
69
+ }
70
+ /**
71
+ * A subscriber component for `CellSetExpressionPlot`,
72
+ * which listens for gene selection updates and
73
+ * `GRID_RESIZE` events.
74
+ * @param {object} props
75
+ * @param {function} props.removeGridComponent The grid component removal function.
76
+ * @param {object} props.coordinationScopes An object mapping coordination
77
+ * types to coordination scopes.
78
+ * @param {string} props.theme The name of the current Vitessce theme.
79
+ */
80
+ export function CellSetExpressionPlotSubscriber(props) {
81
+ const { coordinationScopes, removeGridComponent, theme, } = props;
82
+ const classes = useStyles();
83
+ const loaders = useLoaders();
84
+ // Get "props" from the coordination space.
85
+ const [{ dataset, obsType, featureType, featureValueType, featureSelection: geneSelection, featureValueTransform, featureValueTransformCoefficient, obsSetSelection: cellSetSelection, obsSetColor: cellSetColor, additionalObsSets: additionalCellSets, }, { setFeatureValueTransform, setFeatureValueTransformCoefficient, }] = useCoordination(COMPONENT_COORDINATION_TYPES[ViewType.OBS_SET_FEATURE_VALUE_DISTRIBUTION], coordinationScopes);
86
+ const [width, height, containerRef] = useGridItemSize();
87
+ const [urls, addUrl] = useUrls(loaders, dataset);
88
+ const transformOptions = VALUE_TRANSFORM_OPTIONS;
89
+ // Get data from loaders using the data hooks.
90
+ // eslint-disable-next-line no-unused-vars
91
+ const [expressionData, loadedFeatureSelection, featureSelectionStatus] = useFeatureSelection(loaders, dataset, false, geneSelection, { obsType, featureType, featureValueType });
92
+ const [{ obsIndex }, matrixIndicesStatus] = useObsFeatureMatrixIndices(loaders, dataset, addUrl, false, { obsType, featureType, featureValueType });
93
+ const [{ obsSets: cellSets }, obsSetsStatus] = useObsSetsData(loaders, dataset, addUrl, true, {}, {}, { obsType });
94
+ const isReady = useReady([
95
+ featureSelectionStatus,
96
+ matrixIndicesStatus,
97
+ obsSetsStatus,
98
+ ]);
99
+ const [expressionArr, setArr, expressionMax] = useExpressionByCellSet(expressionData, obsIndex, cellSets, additionalCellSets, geneSelection, cellSetSelection, cellSetColor, featureValueTransform, featureValueTransformCoefficient, theme);
100
+ const firstGeneSelected = geneSelection && geneSelection.length >= 1
101
+ ? geneSelection[0]
102
+ : null;
103
+ const selectedTransformName = transformOptions.find(o => o.value === featureValueTransform)?.name;
104
+ return (_jsx(TitleInfo, { title: `Expression by ${capitalize(obsType)} Set${(firstGeneSelected ? ` (${firstGeneSelected})` : '')}`, 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, "."] })) }) }));
105
+ }
106
+ export function register() {
107
+ registerPluginViewType(ViewType.OBS_SET_FEATURE_VALUE_DISTRIBUTION, CellSetExpressionPlotSubscriber, COMPONENT_COORDINATION_TYPES[ViewType.OBS_SET_FEATURE_VALUE_DISTRIBUTION]);
108
+ }
@@ -0,0 +1,77 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import clamp from 'lodash/clamp';
4
+ import { VegaPlot, VEGA_THEMES } from '@vitessce/vega';
5
+ import { colorArrayToString } from '@vitessce/sets';
6
+ import { capitalize } from '@vitessce/utils';
7
+ /**
8
+ * Cell set sizes displayed as a bar chart,
9
+ * implemented with the VegaPlot component.
10
+ * @param {object} props
11
+ * @param {object[]} props.data The set size data, an array
12
+ * of objects with properties `name`, `key`, `color`, and `size`.
13
+ * @param {string} props.theme The name of the current Vitessce theme.
14
+ * @param {number} props.width The container width.
15
+ * @param {number} props.height The container height.
16
+ * @param {number} props.marginRight The size of the margin
17
+ * on the right side of the plot, to account for the vega menu button.
18
+ * By default, 90.
19
+ * @param {number} props.marginBottom The size of the margin
20
+ * on the bottom of the plot, to account for long x-axis labels.
21
+ * By default, 120.
22
+ * @param {number} props.keyLength The length of the `key` property of
23
+ * each data point. Assumes all key strings have the same length.
24
+ * By default, 36.
25
+ */
26
+ export default function CellSetSizesPlot(props) {
27
+ const { data: rawData, theme, width, height, marginRight = 90, marginBottom = 120, keyLength = 36, obsType, } = props;
28
+ // Add a property `keyName` which concatenates the key and the name,
29
+ // which is both unique and can easily be converted
30
+ // back to the name by taking a substring.
31
+ // Add a property `colorString` which contains the `[r, g, b]` color
32
+ // after converting to a color hex string.
33
+ const data = rawData.map(d => ({
34
+ ...d,
35
+ keyName: d.key + d.name,
36
+ colorString: colorArrayToString(d.color),
37
+ }));
38
+ // Manually set the color scale so that Vega-Lite does
39
+ // not choose the colors automatically.
40
+ const colors = {
41
+ domain: data.map(d => d.key),
42
+ range: data.map(d => d.colorString),
43
+ };
44
+ // Get an array of keys for sorting purposes.
45
+ const keys = data.map(d => d.keyName);
46
+ const spec = {
47
+ mark: { type: 'bar' },
48
+ encoding: {
49
+ x: {
50
+ field: 'keyName',
51
+ type: 'nominal',
52
+ axis: { labelExpr: `substring(datum.label, ${keyLength})` },
53
+ title: 'Cell Set',
54
+ sort: keys,
55
+ },
56
+ y: {
57
+ field: 'size',
58
+ type: 'quantitative',
59
+ title: `${capitalize(obsType)} Set Size`,
60
+ },
61
+ color: {
62
+ field: 'key',
63
+ type: 'nominal',
64
+ scale: colors,
65
+ legend: null,
66
+ },
67
+ tooltip: {
68
+ field: 'size',
69
+ type: 'quantitative',
70
+ },
71
+ },
72
+ width: clamp(width - marginRight, 10, Infinity),
73
+ height: clamp(height - marginBottom, 10, Infinity),
74
+ config: VEGA_THEMES[theme],
75
+ };
76
+ return (_jsx(VegaPlot, { data: data, spec: spec }));
77
+ }
@@ -0,0 +1,44 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import React, { useMemo } from 'react';
3
+ import { TitleInfo, useCoordination, useLoaders, useUrls, useReady, useGridItemSize, useObsSetsData, registerPluginViewType, } from '@vitessce/vit-s';
4
+ import { ViewType, COMPONENT_COORDINATION_TYPES } from '@vitessce/constants-internal';
5
+ import { mergeObsSets, treeToSetSizesBySetNames } from '@vitessce/sets';
6
+ import { capitalize } from '@vitessce/utils';
7
+ import CellSetSizesPlot from './CellSetSizesPlot';
8
+ import { useStyles } from './styles';
9
+ /**
10
+ * A subscriber component for `CellSetSizePlot`,
11
+ * which listens for cell sets data updates and
12
+ * `GRID_RESIZE` events.
13
+ * @param {object} props
14
+ * @param {function} props.removeGridComponent The grid component removal function.
15
+ * @param {function} props.onReady The function to call when the subscriptions
16
+ * have been made.
17
+ * @param {string} props.theme The name of the current Vitessce theme.
18
+ * @param {string} props.title The component title.
19
+ */
20
+ export function CellSetSizesPlotSubscriber(props) {
21
+ const { coordinationScopes, removeGridComponent, theme, title: titleOverride, } = props;
22
+ const classes = useStyles();
23
+ const loaders = useLoaders();
24
+ // Get "props" from the coordination space.
25
+ const [{ dataset, obsType, obsSetSelection: cellSetSelection, obsSetColor: cellSetColor, additionalObsSets: additionalCellSets, }, { setObsSetSelection: setCellSetSelection, setObsSetColor: setCellSetColor, }] = useCoordination(COMPONENT_COORDINATION_TYPES[ViewType.OBS_SET_SIZES], coordinationScopes);
26
+ const title = titleOverride || `${capitalize(obsType)} Set Sizes`;
27
+ const [width, height, containerRef] = useGridItemSize();
28
+ const [urls, addUrl] = useUrls(loaders, dataset);
29
+ // Get data from loaders using the data hooks.
30
+ const [{ obsSets: cellSets }, obsSetsStatus] = useObsSetsData(loaders, dataset, addUrl, true, { setObsSetSelection: setCellSetSelection, setObsSetColor: setCellSetColor }, { obsSetSelection: cellSetSelection, obsSetColor: cellSetColor }, { obsType });
31
+ const isReady = useReady([
32
+ obsSetsStatus,
33
+ ]);
34
+ const mergedCellSets = useMemo(() => mergeObsSets(cellSets, additionalCellSets), [cellSets, additionalCellSets]);
35
+ // From the cell sets hierarchy and the list of selected cell sets,
36
+ // generate the array of set sizes data points for the bar plot.
37
+ const data = useMemo(() => (mergedCellSets && cellSetSelection && cellSetColor
38
+ ? treeToSetSizesBySetNames(mergedCellSets, cellSetSelection, cellSetColor, theme)
39
+ : []), [mergedCellSets, cellSetSelection, cellSetColor, theme]);
40
+ return (_jsx(TitleInfo, { title: title, removeGridComponent: removeGridComponent, urls: urls, theme: theme, isReady: isReady, children: _jsx("div", { ref: containerRef, className: classes.vegaContainer, children: _jsx(CellSetSizesPlot, { data: data, theme: theme, width: width, height: height, obsType: obsType }) }) }));
41
+ }
42
+ export function register() {
43
+ registerPluginViewType(ViewType.OBS_SET_SIZES, CellSetSizesPlotSubscriber, COMPONENT_COORDINATION_TYPES[ViewType.OBS_SET_SIZES]);
44
+ }
@@ -0,0 +1,49 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import clamp from 'lodash/clamp';
4
+ import { VegaPlot, VEGA_THEMES } from '@vitessce/vega';
5
+ /**
6
+ * Gene expression histogram displayed as a bar chart,
7
+ * implemented with the VegaPlot component.
8
+ * @param {object} props
9
+ * @param {string[]} props.geneSelection The list of genes
10
+ * currently selected.
11
+ * @param {object[]} props.data The expression data, an array
12
+ * of objects with properties `value` and `gene`.
13
+ * @param {string} props.theme The name of the current Vitessce theme.
14
+ * @param {number} props.width The container width.
15
+ * @param {number} props.height The container height.
16
+ * @param {number} props.marginRight The size of the margin
17
+ * on the right side of the plot, to account for the vega menu button.
18
+ * By default, 90.
19
+ * @param {number} props.marginBottom The size of the margin
20
+ * on the bottom of the plot, to account for long x-axis labels.
21
+ * By default, 50.
22
+ */
23
+ export default function ExpressionHistogram(props) {
24
+ const { geneSelection, data, theme, width, height, marginRight = 90, marginBottom = 50, } = props;
25
+ const xTitle = geneSelection && geneSelection.length >= 1
26
+ ? 'Normalized Expression Value'
27
+ : 'Total Normalized Transcript Count';
28
+ const spec = {
29
+ mark: { type: 'bar' },
30
+ encoding: {
31
+ x: {
32
+ field: 'value',
33
+ type: 'quantitative',
34
+ bin: { maxbins: 50 },
35
+ title: xTitle,
36
+ },
37
+ y: {
38
+ type: 'quantitative',
39
+ aggregate: 'count',
40
+ title: 'Number of Cells',
41
+ },
42
+ color: { value: 'gray' },
43
+ },
44
+ width: clamp(width - marginRight, 10, Infinity),
45
+ height: clamp(height - marginBottom, 10, Infinity),
46
+ config: VEGA_THEMES[theme],
47
+ };
48
+ return (_jsx(VegaPlot, { data: data, spec: spec }));
49
+ }
@@ -0,0 +1,63 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import React, { useMemo } from 'react';
3
+ import { sum } from 'd3-array';
4
+ import { TitleInfo, useCoordination, useLoaders, useUrls, useReady, useGridItemSize, useObsFeatureMatrixData, useFeatureSelection, registerPluginViewType, } from '@vitessce/vit-s';
5
+ import { ViewType, COMPONENT_COORDINATION_TYPES } from '@vitessce/constants-internal';
6
+ import ExpressionHistogram from './ExpressionHistogram';
7
+ import { useStyles } from './styles';
8
+ /**
9
+ * A subscriber component for `ExpressionHistogram`,
10
+ * which listens for gene selection updates and
11
+ * `GRID_RESIZE` events.
12
+ * @param {object} props
13
+ * @param {function} props.removeGridComponent The grid component removal function.
14
+ * @param {object} props.coordinationScopes An object mapping coordination
15
+ * types to coordination scopes.
16
+ * @param {string} props.theme The name of the current Vitessce theme.
17
+ */
18
+ export function ExpressionHistogramSubscriber(props) {
19
+ const { coordinationScopes, removeGridComponent, theme, } = props;
20
+ const classes = useStyles();
21
+ const loaders = useLoaders();
22
+ // Get "props" from the coordination space.
23
+ const [{ dataset, obsType, featureType, featureValueType, featureSelection: geneSelection, }] = useCoordination(COMPONENT_COORDINATION_TYPES[ViewType.FEATURE_VALUE_HISTOGRAM], coordinationScopes);
24
+ const [width, height, containerRef] = useGridItemSize();
25
+ const [urls, addUrl] = useUrls(loaders, dataset);
26
+ // Get data from loaders using the data hooks.
27
+ const [{ obsIndex, featureIndex, obsFeatureMatrix }, matrixStatus] = useObsFeatureMatrixData(loaders, dataset, addUrl, true, {}, {}, { obsType, featureType, featureValueType });
28
+ // eslint-disable-next-line no-unused-vars
29
+ const [expressionData, loadedFeatureSelection, featureSelectionStatus] = useFeatureSelection(loaders, dataset, false, geneSelection, { obsType, featureType, featureValueType });
30
+ const isReady = useReady([
31
+ matrixStatus,
32
+ featureSelectionStatus,
33
+ ]);
34
+ const firstGeneSelected = geneSelection && geneSelection.length >= 1
35
+ ? geneSelection[0]
36
+ : null;
37
+ // From the expression matrix and the list of selected genes,
38
+ // generate the array of data points for the histogram.
39
+ const data = useMemo(() => {
40
+ if (firstGeneSelected && obsFeatureMatrix && expressionData) {
41
+ // Create new cellColors map based on the selected gene.
42
+ return Array.from(expressionData[0]).map((_, index) => {
43
+ const value = expressionData[0][index];
44
+ const normValue = value * 100 / 255;
45
+ return { value: normValue, gene: firstGeneSelected };
46
+ });
47
+ }
48
+ if (obsFeatureMatrix) {
49
+ const numGenes = featureIndex.length;
50
+ return obsIndex.map((cellId, cellIndex) => {
51
+ const values = obsFeatureMatrix.data
52
+ .subarray(cellIndex * numGenes, (cellIndex + 1) * numGenes);
53
+ const sumValue = sum(values) * 100 / 255;
54
+ return { value: sumValue, gene: null };
55
+ });
56
+ }
57
+ return null;
58
+ }, [obsIndex, featureIndex, obsFeatureMatrix, firstGeneSelected, expressionData]);
59
+ return (_jsx(TitleInfo, { title: `Expression Histogram${(firstGeneSelected ? ` (${firstGeneSelected})` : '')}`, removeGridComponent: removeGridComponent, urls: urls, theme: theme, isReady: isReady, children: _jsx("div", { ref: containerRef, className: classes.vegaContainer, children: _jsx(ExpressionHistogram, { geneSelection: geneSelection, data: data, theme: theme, width: width, height: height }) }) }));
60
+ }
61
+ export function register() {
62
+ registerPluginViewType(ViewType.FEATURE_VALUE_HISTOGRAM, ExpressionHistogramSubscriber, COMPONENT_COORDINATION_TYPES[ViewType.FEATURE_VALUE_HISTOGRAM]);
63
+ }
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export { CellSetExpressionPlotSubscriber, register as registerCellSetExpression } from './CellSetExpressionPlotSubscriber';
2
+ export { CellSetSizesPlotSubscriber, register as registerCellSetSizes } from './CellSetSizesPlotSubscriber';
3
+ export { ExpressionHistogramSubscriber, register as registerExpressionHistogram } from './ExpressionHistogramSubscriber';
4
+ export { default as CellSetSizesPlot } from './CellSetSizesPlot';
5
+ export { default as CellSetExpressionPlot } from './CellSetExpressionPlot';
6
+ export { default as ExpressionHistogram } from './ExpressionHistogram';
package/dist/styles.js ADDED
@@ -0,0 +1,8 @@
1
+ import { makeStyles } from '@material-ui/core/styles';
2
+ export const useStyles = makeStyles(() => ({
3
+ vegaContainer: {
4
+ display: 'flex',
5
+ flex: '1 1 auto',
6
+ overflow: 'hidden',
7
+ },
8
+ }));
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@vitessce/statistical-plots",
3
+ "version": "2.0.0-beta.0",
4
+ "author": "Gehlenborg Lab",
5
+ "homepage": "http://vitessce.io",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/vitessce/vitessce.git"
9
+ },
10
+ "license": "MIT",
11
+ "main": "dist/index.js",
12
+ "files": [
13
+ "dist"
14
+ ],
15
+ "dependencies": {
16
+ "@material-ui/core": "~4.12.3",
17
+ "@vitessce/constants-internal": "2.0.0-beta.0",
18
+ "@vitessce/sets": "2.0.0-beta.0",
19
+ "@vitessce/utils": "2.0.0-beta.0",
20
+ "@vitessce/vega": "2.0.0-beta.0",
21
+ "@vitessce/vit-s": "2.0.0-beta.0",
22
+ "d3-array": "^2.4.0",
23
+ "lodash": "^4.17.21"
24
+ },
25
+ "devDependencies": {
26
+ "react": "^18.0.0",
27
+ "vite": "^3.0.0",
28
+ "vitest": "^0.23.4"
29
+ },
30
+ "peerDependencies": {
31
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
32
+ },
33
+ "scripts": {
34
+ "start": "tsc --watch",
35
+ "build": "tsc",
36
+ "test": "pnpm exec vitest --run -r ../../../ --dir ."
37
+ }
38
+ }