@vitessce/statistical-plots 2.0.2 → 2.0.3-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.
@@ -0,0 +1,110 @@
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 { capitalize } from '@vitessce/utils';
6
+ import plur from 'plur';
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 DotPlot(props) {
29
+ const { data: rawData, theme, width, height, marginRight, marginBottom, obsType, keyLength = 36, featureType, featureValueType, featureValueTransformName, } = props;
30
+ // Add a property `keyGroup` and `keyFeature` which concatenates the key and the name,
31
+ // which is both unique and can easily be converted
32
+ // back to the name by taking a substring.
33
+ const data = rawData.map(d => ({
34
+ ...d,
35
+ keyGroup: d.groupKey + d.group,
36
+ keyFeature: d.featureKey + d.feature,
37
+ }));
38
+ // Get the max characters in an axis label for autsizing the bottom margin.
39
+ const maxCharactersForGroup = data.reduce((acc, val) => {
40
+ // eslint-disable-next-line no-param-reassign
41
+ acc = acc === undefined || val.group.length > acc ? val.group.length : acc;
42
+ return acc;
43
+ }, 0);
44
+ const maxCharactersForFeature = data.reduce((acc, val) => {
45
+ // eslint-disable-next-line no-param-reassign
46
+ acc = acc === undefined || val.feature.length > acc ? val.feature.length : acc;
47
+ return acc;
48
+ }, 0);
49
+ // Use a square-root term because the angle of the labels is 45 degrees (see below)
50
+ // so the perpendicular distance to the bottom of the labels is proportional to the
51
+ // square root of the length of the labels along the imaginary hypotenuse.
52
+ // 30 is an estimate of the pixel size of a given character and seems to work well.
53
+ const autoMarginVertical = marginBottom
54
+ || 30 + Math.sqrt(maxCharactersForFeature / 2) * 30;
55
+ const autoMarginHorizontal = marginRight
56
+ || 30 + Math.sqrt(maxCharactersForGroup / 2) * 30;
57
+ const plotWidth = clamp(width - autoMarginHorizontal - 120, 10, Infinity);
58
+ const plotHeight = clamp(height - autoMarginVertical, 10, Infinity);
59
+ // Get an array of keys for sorting purposes.
60
+ const groupKeys = data.map(d => d.keyGroup);
61
+ const featureKeys = data.map(d => d.keyFeature);
62
+ const meanTransform = (featureValueTransformName && featureValueTransformName !== 'None')
63
+ // Mean Log-Transformed Normalized Expression
64
+ ? [`Mean ${featureValueTransformName}-transformed`, `normalized ${featureValueType}`, 'in set']
65
+ // Mean Normalized Expression
66
+ : ['Mean normalized', `${featureValueType} in set`];
67
+ const spec = {
68
+ mark: { type: 'circle' },
69
+ encoding: {
70
+ x: {
71
+ field: 'keyFeature',
72
+ type: 'nominal',
73
+ axis: { labelExpr: `substring(datum.label, ${keyLength})` },
74
+ title: capitalize(featureType),
75
+ sort: featureKeys,
76
+ },
77
+ y: {
78
+ field: 'keyGroup',
79
+ type: 'nominal',
80
+ axis: { labelExpr: `substring(datum.label, ${keyLength})` },
81
+ title: `${capitalize(obsType)} Set`,
82
+ sort: groupKeys,
83
+ },
84
+ color: {
85
+ field: 'meanExpInGroup',
86
+ type: 'quantitative',
87
+ title: meanTransform,
88
+ scale: {
89
+ scheme: 'plasma',
90
+ },
91
+ legend: {
92
+ direction: 'horizontal',
93
+ tickCount: 2,
94
+ },
95
+ },
96
+ size: {
97
+ field: 'fracPosInGroup',
98
+ type: 'quantitative',
99
+ title: [`Fraction of ${plur(obsType, 2)}`, 'in set'],
100
+ legend: {
101
+ symbolFillColor: 'white',
102
+ },
103
+ },
104
+ },
105
+ width: plotWidth,
106
+ height: plotHeight,
107
+ config: VEGA_THEMES[theme],
108
+ };
109
+ return (_jsx(VegaPlot, { data: data, spec: spec }));
110
+ }
@@ -0,0 +1,126 @@
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, useFeatureLabelsData, registerPluginViewType, } from '@vitessce/vit-s';
4
+ import { ViewType, COMPONENT_COORDINATION_TYPES } from '@vitessce/constants-internal';
5
+ import { VALUE_TRANSFORM_OPTIONS, getValueTransformFunction } from '@vitessce/utils';
6
+ import { treeToObsIndicesBySetNames, mergeObsSets } from '@vitessce/sets-utils';
7
+ import { mean } from 'd3-array';
8
+ import uuidv4 from 'uuid/v4';
9
+ import CellSetExpressionPlotOptions from './CellSetExpressionPlotOptions';
10
+ import DotPlot from './DotPlot';
11
+ import { useStyles } from './styles';
12
+ /**
13
+ * Get expression data for the cells
14
+ * in the selected cell sets.
15
+ * @param {object} expressionMatrix
16
+ * @param {string[]} expressionMatrix.rows Cell IDs.
17
+ * @param {string[]} expressionMatrix.cols Gene names.
18
+ * @param {Uint8Array} expressionMatrix.matrix The
19
+ * flattened expression matrix as a typed array.
20
+ * @param {object} cellSets The cell sets from the dataset.
21
+ * @param {object} additionalCellSets The user-defined cell sets
22
+ * from the coordination space.
23
+ * @param {array} geneSelection Array of selected genes.
24
+ * @param {array} cellSetSelection Array of selected cell set paths.
25
+ * @param {object[]} cellSetColor Array of objects with properties
26
+ * @param {string|null} featureValueTransform The name of the
27
+ * feature value transform function.
28
+ * @param {number} featureValueTransformCoefficient A coefficient
29
+ * to be used in the transform function.
30
+ * @param {string} theme "light" or "dark" for the vitessce theme
31
+ * `path` and `color`.
32
+ */
33
+ export function useExpressionSummaries(expressionData, obsIndex, cellSets, additionalCellSets, geneSelection, cellSetSelection, cellSetColor, featureValueTransform, featureValueTransformCoefficient, posThreshold, featureLabelsMap) {
34
+ const mergedCellSets = useMemo(() => mergeObsSets(cellSets, additionalCellSets), [cellSets, additionalCellSets]);
35
+ // From the expression matrix and the list of selected genes / cell sets,
36
+ // generate the array of data points for the plot.
37
+ const [resultArr, meanExpressionMax] = useMemo(() => {
38
+ if (mergedCellSets && cellSetSelection
39
+ && geneSelection && geneSelection.length >= 1
40
+ && expressionData && expressionData.length === geneSelection.length) {
41
+ let exprMax = -Infinity;
42
+ const result = [];
43
+ const cellIndices = {};
44
+ for (let i = 0; i < obsIndex.length; i += 1) {
45
+ cellIndices[obsIndex[i]] = i;
46
+ }
47
+ const setObjects = treeToObsIndicesBySetNames(mergedCellSets, cellSetSelection, cellIndices);
48
+ geneSelection.forEach((featureName, featureI) => {
49
+ const featureKey = uuidv4();
50
+ let numPos = 0;
51
+ setObjects.forEach((setObj) => {
52
+ const exprValues = setObj.indices.map((cellIndex) => {
53
+ const value = expressionData[featureI][cellIndex];
54
+ const normValue = value * 100 / 255;
55
+ const transformFunction = getValueTransformFunction(featureValueTransform, featureValueTransformCoefficient);
56
+ const transformedValue = transformFunction(normValue);
57
+ if (transformedValue > posThreshold) {
58
+ numPos += 1;
59
+ }
60
+ return transformedValue;
61
+ });
62
+ const exprMean = mean(exprValues);
63
+ const fracPos = numPos / setObj.size;
64
+ result.push({
65
+ key: uuidv4(),
66
+ featureKey,
67
+ groupKey: setObj.key,
68
+ group: setObj.name,
69
+ feature: featureLabelsMap?.get(featureName) || featureName,
70
+ meanExpInGroup: exprMean,
71
+ fracPosInGroup: fracPos,
72
+ });
73
+ exprMax = Math.max(exprMean, exprMax);
74
+ });
75
+ });
76
+ return [result, exprMax];
77
+ }
78
+ return [null, null];
79
+ }, [expressionData, obsIndex, geneSelection,
80
+ mergedCellSets, cellSetSelection,
81
+ featureValueTransform, featureValueTransformCoefficient,
82
+ posThreshold, featureLabelsMap,
83
+ ]);
84
+ return [resultArr, meanExpressionMax];
85
+ }
86
+ /**
87
+ * A subscriber component for `DotPlot`,
88
+ * which listens for gene selection updates and
89
+ * `GRID_RESIZE` events.
90
+ * @param {object} props
91
+ * @param {function} props.removeGridComponent The grid component removal function.
92
+ * @param {object} props.coordinationScopes An object mapping coordination
93
+ * types to coordination scopes.
94
+ * @param {string} props.theme The name of the current Vitessce theme.
95
+ */
96
+ export function DotPlotSubscriber(props) {
97
+ const { coordinationScopes, removeGridComponent, theme, title = 'Dot Plot', posThreshold = 0, } = props;
98
+ const classes = useStyles();
99
+ const loaders = useLoaders();
100
+ // Get "props" from the coordination space.
101
+ const [{ dataset, obsType, featureType, featureValueType, featureSelection: geneSelection, featureValueTransform, featureValueTransformCoefficient, obsSetSelection: cellSetSelection, obsSetColor: cellSetColor, additionalObsSets: additionalCellSets,
102
+ // TODO: coordination type for mean expression colormap
103
+ }, { setFeatureValueTransform, setFeatureValueTransformCoefficient, }] = useCoordination(COMPONENT_COORDINATION_TYPES[ViewType.DOT_PLOT], coordinationScopes);
104
+ const [width, height, containerRef] = useGridItemSize();
105
+ const [urls, addUrl] = useUrls(loaders, dataset);
106
+ const transformOptions = VALUE_TRANSFORM_OPTIONS;
107
+ // Get data from loaders using the data hooks.
108
+ // eslint-disable-next-line no-unused-vars
109
+ const [expressionData, loadedFeatureSelection, featureSelectionStatus] = useFeatureSelection(loaders, dataset, false, geneSelection, { obsType, featureType, featureValueType });
110
+ // TODO: support multiple feature labels using featureLabelsType coordination values.
111
+ const [{ featureLabelsMap }, featureLabelsStatus] = useFeatureLabelsData(loaders, dataset, addUrl, false, {}, {}, { featureType });
112
+ const [{ obsIndex }, matrixIndicesStatus] = useObsFeatureMatrixIndices(loaders, dataset, addUrl, false, { obsType, featureType, featureValueType });
113
+ const [{ obsSets: cellSets }, obsSetsStatus] = useObsSetsData(loaders, dataset, addUrl, true, {}, {}, { obsType });
114
+ const isReady = useReady([
115
+ featureSelectionStatus,
116
+ matrixIndicesStatus,
117
+ obsSetsStatus,
118
+ featureLabelsStatus,
119
+ ]);
120
+ const [resultArr, meanExpressionMax] = useExpressionSummaries(expressionData, obsIndex, cellSets, additionalCellSets, geneSelection, cellSetSelection, cellSetColor, featureValueTransform, featureValueTransformCoefficient, posThreshold, featureLabelsMap);
121
+ const selectedTransformName = transformOptions.find(o => o.value === featureValueTransform)?.name;
122
+ return (_jsx(TitleInfo, { title: title, 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: resultArr ? (_jsx(DotPlot, { domainMax: meanExpressionMax, data: resultArr, theme: theme, width: width, height: height, obsType: obsType, featureType: featureType, featureValueType: featureValueType, featureValueTransformName: selectedTransformName })) : (_jsxs("span", { children: ["Select at least one ", featureType, "."] })) }) }));
123
+ }
124
+ export function register() {
125
+ registerPluginViewType(ViewType.DOT_PLOT, DotPlotSubscriber, COMPONENT_COORDINATION_TYPES[ViewType.DOT_PLOT]);
126
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitessce/statistical-plots",
3
- "version": "2.0.2",
3
+ "version": "2.0.3-beta.0",
4
4
  "author": "Gehlenborg Lab",
5
5
  "homepage": "http://vitessce.io",
6
6
  "repository": {
@@ -16,11 +16,11 @@
16
16
  "@material-ui/core": "~4.12.3",
17
17
  "d3-array": "^2.4.0",
18
18
  "lodash": "^4.17.21",
19
- "@vitessce/constants-internal": "2.0.2",
20
- "@vitessce/sets-utils": "2.0.2",
21
- "@vitessce/utils": "2.0.2",
22
- "@vitessce/vega": "2.0.2",
23
- "@vitessce/vit-s": "2.0.2"
19
+ "@vitessce/utils": "2.0.3-beta.0",
20
+ "@vitessce/sets-utils": "2.0.3-beta.0",
21
+ "@vitessce/vit-s": "2.0.3-beta.0",
22
+ "@vitessce/vega": "2.0.3-beta.0",
23
+ "@vitessce/constants-internal": "2.0.3-beta.0"
24
24
  },
25
25
  "devDependencies": {
26
26
  "react": "^18.0.0",