visualifyjs 2.5.3-2.dev → 2.5.3-9-dev
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of visualifyjs might be problematic. Click here for more details.
- package/.github/workflows/{static.yml.bak → build.yaml} +51 -51
- package/LICENSE +674 -674
- package/README.md +40 -58
- package/config-overrides.js +31 -31
- package/dist/visualify.js +3 -3
- package/docs/CLI.md +15 -0
- package/docs/{docs/README.md → README.md} +41 -65
- package/docs/{docs/Rechart → Rechart}/bar.md +190 -190
- package/docs/{docs/Rechart → Rechart}/funnel.md +193 -241
- package/docs/{docs/Rechart → Rechart}/line.md +355 -355
- package/docs/{docs/Rechart → Rechart}/pie.md +225 -225
- package/docs/{docs/Rechart → Rechart}/radar.md +253 -253
- package/docs/{docs/_404.md → _404.md} +51 -51
- package/docs/{docs/_coverpage.md → _coverpage.md} +11 -11
- package/docs/{docs/_sidebar.md → _sidebar.md} +42 -44
- package/docs/{docs/components → components}/dotBio.md +34 -34
- package/docs/{docs/components → components}/echart.md +82 -82
- package/docs/{docs/components → components}/html.md +34 -34
- package/docs/{docs/components → components}/macaron.md +145 -145
- package/docs/components/markdown.md +0 -0
- package/docs/{docs/components → components}/more.md +142 -142
- package/docs/{docs/components → components}/plotly.md +62 -62
- package/docs/{docs/components → components}/scatterL.md +70 -70
- package/docs/{docs/components → components}/visium.md +56 -56
- package/docs/{docs/configuration.md → configuration.md} +123 -121
- package/docs/{docs/deploy.md → deploy.md} +23 -31
- package/docs/index.html +70 -70
- package/docs/log.md +1 -0
- package/docs/manifest.json +23 -23
- package/docs/{docs/more-pages.md → more-pages.md} +23 -23
- package/docs/{docs/quickstart.md → quickstart.md} +115 -124
- package/docs/{docs/rechart-attributes.md → rechart-attributes.md} +74 -74
- package/docs/{docs/rechart-basic-usage.md → rechart-basic-usage.md} +162 -162
- package/docs/static/css/fluff-stuff.css +169 -169
- package/docs/static/css/font-awesome.min.css +4 -4
- package/docs/static/css/visualify.css +25 -25
- package/docs/static/js/configuration.js +448 -448
- package/docs/static/js/visualify.js +24 -23
- package/docs/theme.md +3 -0
- package/package.json +74 -83
- package/rollup.config.mjs +75 -75
- package/src/_css/404.css +115 -115
- package/src/_css/App.css +37 -37
- package/src/_css/autoSuggestion.css +26 -26
- package/src/_css/circular-progress.css +32 -32
- package/src/_css/index.css +36 -36
- package/src/_css/modern.css +24 -24
- package/src/_media/corner.svg +8 -8
- package/src/_media/download.svg +3 -3
- package/src/_media/logo.svg +14 -14
- package/src/_test/App.test.js +15 -15
- package/src/_utils/reportWebVitals.js +13 -13
- package/src/core/appContext.js +27 -27
- package/src/core/components/Scatter.js +188 -188
- package/src/core/components/ScatterBio.js +572 -572
- package/src/core/components/VisiumPlot.js +165 -165
- package/src/core/components/browser.js +42 -42
- package/src/core/components/dotplot.js +413 -413
- package/src/core/components/html.js +29 -29
- package/src/core/components/list.js +178 -178
- package/src/core/components/macaron.js +201 -201
- package/src/core/components/markdown.js +56 -56
- package/src/core/components/parser.scatterBio.js +579 -587
- package/src/core/components/ratio.js +80 -80
- package/src/core/components/scatterL.js +173 -173
- package/src/core/components/searchbar.js +131 -131
- package/src/core/components/selection.js +193 -193
- package/src/core/components/timeline.js +281 -281
- package/src/core/components/visium.js +97 -97
- package/src/core/fetch/condfetch.js +82 -82
- package/src/core/fetch/fetch.js +92 -92
- package/src/core/fetch/json.js +29 -29
- package/src/core/fetch/vfetch.js +42 -42
- package/src/core/liveEditor.js +44 -44
- package/src/core/modules/codeEditorWithPreview.js +104 -104
- package/src/core/modules/echarts/common.js +20 -20
- package/src/core/modules/echarts/presetHandler.js +41 -41
- package/src/core/modules/echarts/presets/esodev.chromium.js +172 -172
- package/src/core/modules/echarts/presets/esodev.codex.js +130 -130
- package/src/core/modules/echarts/presets/esodev.visium.js +123 -123
- package/src/core/modules/echarts/presets/mmtrbc.js +186 -186
- package/src/core/modules/echarts.js +71 -71
- package/src/core/modules/echartsUtils.js +43 -43
- package/src/core/modules/echartswitcher.js +152 -152
- package/src/core/modules/replotly/presetHandler.js +24 -24
- package/src/core/modules/replotly/presets/minimum.js +18 -18
- package/src/core/modules/replotly/presets/mmtrbc.dot.js +114 -114
- package/src/core/modules/replotly/presets/mmtrbc.violin.js +100 -100
- package/src/core/modules/replotly.js +71 -71
- package/src/core/pages/404.js +50 -50
- package/src/core/pages/error.js +27 -27
- package/src/core/pages/jsonPage.js +62 -62
- package/src/core/pages/loading.js +44 -44
- package/src/core/parser/echart.data.js +183 -183
- package/src/core/parser/echart.features.js +125 -125
- package/src/core/parser/echart.general.js +143 -147
- package/src/core/parser/echart.hilbert.js +57 -57
- package/src/core/parser/echart.parser.js +210 -210
- package/src/core/parser/echart.series.js +67 -67
- package/src/core/parser/echart.types.js +76 -76
- package/src/core/parser/plotly.config.js +10 -10
- package/src/core/parser/plotly.data.js +132 -132
- package/src/core/parser/plotly.layout.js +9 -9
- package/src/core/parser/plotly.violin.js +18 -18
- package/src/core/recharts.js +62 -62
- package/src/core/router/alias.js +49 -49
- package/src/core/router/jsonRouter.js +31 -31
- package/src/core/themes/modern.js +32 -32
- package/src/core/themes/themeSelector.js +33 -33
- package/src/core/visualify.js +47 -47
- package/src/core/widgets/circularProgress.js +23 -23
- package/src/core/widgets/controller.js +83 -83
- package/src/core/widgets/errorBoundary.js +36 -36
- package/src/core/widgets/footer.js +177 -177
- package/src/core/widgets/header.js +234 -234
- package/src/core/widgets/layout/Grid.js +31 -31
- package/src/core/widgets/layout.js +36 -36
- package/src/core/widgets/mapping.js +42 -42
- package/src/index.js +62 -62
- package/src/setupTests.js +5 -5
- package/docs/docs/CLI.md +0 -34
- package/docs/docs/Rechart/scatter.md +0 -298
- package/docs/docs/log.md +0 -9
- package/docs/docs/static/logo/favicon.ico +0 -0
- package/docs/docs/static/logo/logo_128x128.png +0 -0
- package/docs/docs/static/logo/logo_192x192.png +0 -0
- package/docs/docs/static/logo/logo_256x256.png +0 -0
- package/docs/docs/static/logo/logo_512x512.png +0 -0
- package/docs/docs/static/logo/logo_64x64.png +0 -0
- package/docs/docs/theme.md +0 -5
- /package/docs/{docs/Rechart → Rechart}/geo.md +0 -0
- /package/docs/{docs/Rechart → Rechart}/liquidfill.md +0 -0
- /package/docs/{docs/Rechart → Rechart}/polar.md +0 -0
- /package/docs/{docs/Rechart → Rechart}/sankey.md +0 -0
- /package/docs/{docs/Rechart/sunburst.md → Rechart/scatter.md} +0 -0
- /package/docs/{docs/Rechart/tree.md → Rechart/sunburst.md} +0 -0
- /package/docs/{docs/Rechart/wordcloud.md → Rechart/tree.md} +0 -0
- /package/docs/{docs/components/markdown.md → Rechart/wordcloud.md} +0 -0
- /package/docs/{docs/static → static}/_images/deploy-github-pages.png +0 -0
@@ -1,413 +1,413 @@
|
|
1
|
-
import React, { useEffect, useState } from 'react';
|
2
|
-
import RePlotly from '../modules/replotly';
|
3
|
-
import simplefetch from '../fetch/fetch';
|
4
|
-
import { useAppContext } from '../appContext';
|
5
|
-
import mmtrbc_dot from '../modules/replotly/presets/mmtrbc.dot';
|
6
|
-
import conditionalFetch from '../fetch/condfetch';
|
7
|
-
|
8
|
-
const DotBio = ({ props, style }) => {
|
9
|
-
// Declare states at the beginning
|
10
|
-
const { sharedData } = useAppContext();
|
11
|
-
const { debug } = props;
|
12
|
-
|
13
|
-
// Loading and error states
|
14
|
-
const [loading, setLoading] = useState(true);
|
15
|
-
const [error, setError] = useState(null);
|
16
|
-
const [updatedProps, setUpdatedProps] = useState(props);
|
17
|
-
|
18
|
-
useEffect(() => {
|
19
|
-
const fetchData = async () => {
|
20
|
-
const { meta, gene: url, genelist } = props;
|
21
|
-
|
22
|
-
if (meta === undefined) {
|
23
|
-
alert('DotBio: meta is undefined');
|
24
|
-
}
|
25
|
-
if (url === undefined) {
|
26
|
-
alert(
|
27
|
-
'DotBio: gene is undefined, need to be a url or data of gene',
|
28
|
-
);
|
29
|
-
}
|
30
|
-
if (genelist === undefined) {
|
31
|
-
console.log(
|
32
|
-
'DotBio: genelist is undefined, defaulting to genelist',
|
33
|
-
);
|
34
|
-
}
|
35
|
-
|
36
|
-
const metadata = await conditionalFetch(meta, sharedData, {});
|
37
|
-
|
38
|
-
const sharedgenelist = genelist
|
39
|
-
? sharedData[genelist]
|
40
|
-
: sharedData.genelist;
|
41
|
-
|
42
|
-
let genedata = {};
|
43
|
-
|
44
|
-
if (sharedgenelist !== undefined) {
|
45
|
-
// genelist is a list of genes names (strings)
|
46
|
-
for (const gene of sharedgenelist) {
|
47
|
-
//console.log("fetching", gene, url);
|
48
|
-
genedata[gene] = await simplefetch(url, {
|
49
|
-
id: gene,
|
50
|
-
key: 'gene',
|
51
|
-
type: 'gene',
|
52
|
-
debug: debug,
|
53
|
-
});
|
54
|
-
}
|
55
|
-
}
|
56
|
-
|
57
|
-
//if (debug)
|
58
|
-
// console.log("DotBio: genedata", genedata, "metadata", metadata);
|
59
|
-
|
60
|
-
return { metadata, genedata };
|
61
|
-
};
|
62
|
-
|
63
|
-
const processData = ({ metadata, genedata }) => {
|
64
|
-
// Set the title & size of the plot
|
65
|
-
const { showscale = false } = props.settings;
|
66
|
-
|
67
|
-
let config = mmtrbc_dot;
|
68
|
-
|
69
|
-
//console.log('DotBio: config', config);
|
70
|
-
// Dealing with the data
|
71
|
-
const { exclude_celltype } = props;
|
72
|
-
const _celltypes = [...new Set(metadata['Cell_Type'])].filter(
|
73
|
-
(celltype) => !exclude_celltype.includes(celltype),
|
74
|
-
);
|
75
|
-
|
76
|
-
// the length of the gene data
|
77
|
-
// const _genedata_length = Object.keys(_genedata).length;
|
78
|
-
// console.log("DotBio: _genedata_length", gene, _genedata_length);
|
79
|
-
|
80
|
-
let processedData = {};
|
81
|
-
let cellTypeCounts = {};
|
82
|
-
let expressionSums = {};
|
83
|
-
|
84
|
-
for (const gene in genedata) {
|
85
|
-
const _genedata = genedata[gene];
|
86
|
-
|
87
|
-
processedData[gene] = metadata['Cell_ID']
|
88
|
-
.map((cellId, index) => {
|
89
|
-
const _expression = _genedata[cellId] ?? -1;
|
90
|
-
return {
|
91
|
-
name: cellId,
|
92
|
-
value: [
|
93
|
-
metadata['x'][index],
|
94
|
-
metadata['y'][index],
|
95
|
-
_expression,
|
96
|
-
],
|
97
|
-
UMI: Math.round(metadata.N_UMI[index] * 100) / 100, // round to 2 decimal places
|
98
|
-
Type: metadata['Cell_Type'][index],
|
99
|
-
Expression: _expression,
|
100
|
-
};
|
101
|
-
})
|
102
|
-
.filter((item) => item.Expression > 0);
|
103
|
-
|
104
|
-
cellTypeCounts[gene] = processedData[gene].reduce(
|
105
|
-
(counts, item) => {
|
106
|
-
if (!counts[item.Type]) {
|
107
|
-
counts[item.Type] = 1;
|
108
|
-
} else {
|
109
|
-
counts[item.Type]++;
|
110
|
-
}
|
111
|
-
return counts;
|
112
|
-
},
|
113
|
-
{},
|
114
|
-
);
|
115
|
-
|
116
|
-
expressionSums[gene] = processedData[gene].reduce(
|
117
|
-
(sums, item) => {
|
118
|
-
if (!sums[item.Type]) {
|
119
|
-
sums[item.Type] = item.Expression;
|
120
|
-
} else {
|
121
|
-
sums[item.Type] += item.Expression;
|
122
|
-
}
|
123
|
-
return sums;
|
124
|
-
},
|
125
|
-
{},
|
126
|
-
);
|
127
|
-
}
|
128
|
-
|
129
|
-
let totalCellTypeCounts = metadata['Cell_Type'].reduce(
|
130
|
-
(counts, cellType) => {
|
131
|
-
if (!counts[cellType]) {
|
132
|
-
counts[cellType] = 1;
|
133
|
-
} else {
|
134
|
-
counts[cellType]++;
|
135
|
-
}
|
136
|
-
return counts;
|
137
|
-
},
|
138
|
-
{},
|
139
|
-
);
|
140
|
-
|
141
|
-
// If It provides the configuration of the plot
|
142
|
-
const {
|
143
|
-
colorMat: __colorMat,
|
144
|
-
sizeMat: __sizeMat,
|
145
|
-
xLabels: __xLabels,
|
146
|
-
yLabels: __yLabels,
|
147
|
-
} = props.settings;
|
148
|
-
|
149
|
-
// calculate the color matrix
|
150
|
-
const colorMat = __colorMat ?? {};
|
151
|
-
|
152
|
-
let averageExpression = {};
|
153
|
-
|
154
|
-
for (let gene in genedata) {
|
155
|
-
if (!averageExpression[gene]) {
|
156
|
-
averageExpression[gene] = {};
|
157
|
-
}
|
158
|
-
for (let cellType in cellTypeCounts[gene]) {
|
159
|
-
let count = totalCellTypeCounts[cellType];
|
160
|
-
let totalExpression = expressionSums[gene][cellType];
|
161
|
-
averageExpression[gene][cellType] = totalExpression / count;
|
162
|
-
// round to the nearest integer
|
163
|
-
averageExpression[gene][cellType] = Math.round(
|
164
|
-
averageExpression[gene][cellType],
|
165
|
-
);
|
166
|
-
}
|
167
|
-
}
|
168
|
-
|
169
|
-
//console.log("Average Expression: ", averageExpression);
|
170
|
-
|
171
|
-
// Calculate the maximum and minimum expression across all genes and cell types
|
172
|
-
let maxExpression = 0;
|
173
|
-
let minExpression = Infinity;
|
174
|
-
for (let gene in averageExpression) {
|
175
|
-
for (let cellType in averageExpression[gene]) {
|
176
|
-
maxExpression = Math.max(
|
177
|
-
maxExpression,
|
178
|
-
averageExpression[gene][cellType],
|
179
|
-
);
|
180
|
-
minExpression = Math.min(
|
181
|
-
minExpression,
|
182
|
-
averageExpression[gene][cellType],
|
183
|
-
);
|
184
|
-
}
|
185
|
-
}
|
186
|
-
|
187
|
-
// Create a color scale function that uses the maximum and minimum expression
|
188
|
-
function colorScale(expression) {
|
189
|
-
// Normalize the expression to a value between 0 and 1
|
190
|
-
let normalized =
|
191
|
-
(expression - minExpression) /
|
192
|
-
(maxExpression - minExpression);
|
193
|
-
|
194
|
-
// Map the normalized value to a color
|
195
|
-
let r = 70 + Math.round(185 * normalized);
|
196
|
-
let g = 125; // + Math.round(255 * normalized); //(1 - normalized)
|
197
|
-
let b = 255;
|
198
|
-
|
199
|
-
return 'rgb(' + r + ', ' + g + ', ' + b + ')';
|
200
|
-
}
|
201
|
-
|
202
|
-
// Assign colors based on average expression
|
203
|
-
for (let gene in averageExpression) {
|
204
|
-
if (!colorMat[gene]) {
|
205
|
-
colorMat[gene] = {};
|
206
|
-
}
|
207
|
-
for (let cellType in averageExpression[gene]) {
|
208
|
-
let expression = averageExpression[gene][cellType];
|
209
|
-
colorMat[gene][cellType] = colorScale(expression);
|
210
|
-
}
|
211
|
-
}
|
212
|
-
|
213
|
-
//console.log("Color Matrix: ", colorMat);
|
214
|
-
|
215
|
-
// calculate the size matrix
|
216
|
-
let sizeMat = __sizeMat ?? {};
|
217
|
-
|
218
|
-
for (let gene in cellTypeCounts) {
|
219
|
-
if (!sizeMat[gene]) {
|
220
|
-
sizeMat[gene] = {};
|
221
|
-
}
|
222
|
-
|
223
|
-
for (let cellType of _celltypes) {
|
224
|
-
if (!cellTypeCounts[gene][cellType]) {
|
225
|
-
sizeMat[gene][cellType] = 0;
|
226
|
-
} else {
|
227
|
-
sizeMat[gene][cellType] =
|
228
|
-
Math.round(
|
229
|
-
(cellTypeCounts[gene][cellType] /
|
230
|
-
totalCellTypeCounts[cellType]) *
|
231
|
-
1000,
|
232
|
-
) / 1000;
|
233
|
-
}
|
234
|
-
}
|
235
|
-
}
|
236
|
-
|
237
|
-
//console.log("Size Matrix: ", sizeMat);
|
238
|
-
|
239
|
-
// process the xLabels
|
240
|
-
const xLabels = __xLabels ?? _celltypes;
|
241
|
-
|
242
|
-
// process the yLabels
|
243
|
-
const yLabels = __yLabels ?? Object.keys(genedata);
|
244
|
-
|
245
|
-
// process the data
|
246
|
-
const {
|
247
|
-
annotation = false,
|
248
|
-
showlegend = false,
|
249
|
-
maxdotsize = 20,
|
250
|
-
} = props.settings;
|
251
|
-
|
252
|
-
// check list
|
253
|
-
const _genedata_length = Object.keys(genedata).length;
|
254
|
-
//console.log("DotBio: _genedata_length", _genedata_length, isEmpty(genedata));
|
255
|
-
const _hasData = _genedata_length > 0;
|
256
|
-
|
257
|
-
const data = xLabels.map((xLabel, i) => {
|
258
|
-
let sizes = yLabels.map(
|
259
|
-
(yLabel) => sizeMat[yLabel][xLabel] * maxdotsize,
|
260
|
-
);
|
261
|
-
let colors = yLabels.map((yLabel) => colorMat[yLabel][xLabel]);
|
262
|
-
// round to the nearest integer
|
263
|
-
let hoverTexts = yLabels.map((yLabel) => {
|
264
|
-
let avgExpress = _hasData
|
265
|
-
? averageExpression[yLabel][xLabel]
|
266
|
-
: 0;
|
267
|
-
return `${xLabel} <br> ${yLabel} <br> Avg Expression: ${avgExpress}`;
|
268
|
-
});
|
269
|
-
|
270
|
-
return {
|
271
|
-
mode: 'markers',
|
272
|
-
x: Array(sizes.length).fill(xLabel), // Repeat the xLabel for each y value
|
273
|
-
y: yLabels,
|
274
|
-
marker: {
|
275
|
-
size: sizes,
|
276
|
-
color: colors,
|
277
|
-
},
|
278
|
-
type: 'scatter',
|
279
|
-
name: xLabel,
|
280
|
-
hoverinfo: 'text',
|
281
|
-
hovertext: hoverTexts,
|
282
|
-
showlegend: showlegend,
|
283
|
-
};
|
284
|
-
});
|
285
|
-
|
286
|
-
//console.log('DotBio: data', data);
|
287
|
-
config.data = data;
|
288
|
-
|
289
|
-
// show the largest percentage of celltype and least percentage of celltype
|
290
|
-
let minVal = Number.MAX_VALUE;
|
291
|
-
let maxVal = Number.MIN_VALUE;
|
292
|
-
let minKey = '',
|
293
|
-
maxKey = '';
|
294
|
-
|
295
|
-
for (let key in sizeMat) {
|
296
|
-
for (let innerKey in sizeMat[key]) {
|
297
|
-
if (sizeMat[key][innerKey] < minVal) {
|
298
|
-
minVal = sizeMat[key][innerKey];
|
299
|
-
minKey = key + ' ' + innerKey;
|
300
|
-
}
|
301
|
-
if (sizeMat[key][innerKey] > maxVal) {
|
302
|
-
maxVal = sizeMat[key][innerKey];
|
303
|
-
maxKey = key + ' ' + innerKey;
|
304
|
-
}
|
305
|
-
}
|
306
|
-
}
|
307
|
-
|
308
|
-
//console.log("Min Value:", minVal, "Key:", minKey);
|
309
|
-
//console.log("Max Value:", maxVal, "Key:", maxKey);
|
310
|
-
|
311
|
-
if (annotation) {
|
312
|
-
// round the values
|
313
|
-
minVal = Math.round(minVal * 100000) / 1000;
|
314
|
-
maxVal = Math.round(maxVal * 100000) / 1000;
|
315
|
-
|
316
|
-
config.layout.annotations = _hasData
|
317
|
-
? [
|
318
|
-
{
|
319
|
-
text: `<b>${
|
320
|
-
minKey.split(' ')[1]
|
321
|
-
}</b>: ${minVal}%`,
|
322
|
-
x: -0.35,
|
323
|
-
y: -0.2 - minVal / 2000, // Adjust this formula to move the annotation as per your requirement
|
324
|
-
xref: 'paper',
|
325
|
-
yref: 'paper',
|
326
|
-
showarrow: false,
|
327
|
-
font: {
|
328
|
-
size: 12,
|
329
|
-
},
|
330
|
-
},
|
331
|
-
{
|
332
|
-
text: `<b>${
|
333
|
-
maxKey.split(' ')[1]
|
334
|
-
}: </b>${maxVal}%`,
|
335
|
-
x: -0.35,
|
336
|
-
y: -0.35 + maxVal / 2000, // Adjust this formula to move the annotation as per your requirement
|
337
|
-
xref: 'paper',
|
338
|
-
yref: 'paper',
|
339
|
-
showarrow: false,
|
340
|
-
font: {
|
341
|
-
size: 12,
|
342
|
-
},
|
343
|
-
},
|
344
|
-
]
|
345
|
-
: [];
|
346
|
-
}
|
347
|
-
//console.log(config.data);
|
348
|
-
// remove the title if data present
|
349
|
-
config.layout.title = _hasData ? '' : config.title;
|
350
|
-
config.layout.margin.t = _hasData ? 20 : 80;
|
351
|
-
//console.log(config);
|
352
|
-
|
353
|
-
if (showscale && _hasData) {
|
354
|
-
data.push({
|
355
|
-
type: 'scatter',
|
356
|
-
mode: 'markers',
|
357
|
-
x: [null], // No x or y data for this trace
|
358
|
-
y: [null],
|
359
|
-
marker: {
|
360
|
-
size: 0, // Marker size is 0, essentially making it invisible
|
361
|
-
color: [minExpression, maxExpression], // Use min and max expression as color range
|
362
|
-
colorscale: [
|
363
|
-
[0, 'rgb(70, 125, 255)'], // Color corresponding to min expression
|
364
|
-
[1, 'rgb(255, 125, 255)'], // Color corresponding to max expression
|
365
|
-
],
|
366
|
-
colorbar: {
|
367
|
-
title: 'Avg Expression', // Title of the color bar
|
368
|
-
titleside: 'right',
|
369
|
-
tickmode: 'array',
|
370
|
-
tickvals: [minExpression, maxExpression], // Tick values should match actual data range
|
371
|
-
ticktext: [minExpression, maxExpression], // Tick text indicating Low and High
|
372
|
-
ticks: 'outside',
|
373
|
-
},
|
374
|
-
showscale: true,
|
375
|
-
},
|
376
|
-
hoverinfo: 'none', // No hover info for this trace
|
377
|
-
showlegend: false, // No legend for this trace
|
378
|
-
});
|
379
|
-
}
|
380
|
-
|
381
|
-
//console.log('DotBio: config', config);
|
382
|
-
// processing logic here...
|
383
|
-
setUpdatedProps((updatedProps) => ({
|
384
|
-
...updatedProps,
|
385
|
-
data: config.data,
|
386
|
-
}));
|
387
|
-
};
|
388
|
-
|
389
|
-
fetchData()
|
390
|
-
.then(processData)
|
391
|
-
.then(() => setLoading(false))
|
392
|
-
.catch((err) => {
|
393
|
-
console.error(err);
|
394
|
-
setError(err);
|
395
|
-
setLoading(false);
|
396
|
-
});
|
397
|
-
}, [props, sharedData, debug]);
|
398
|
-
|
399
|
-
if (loading) return 'Loading...';
|
400
|
-
if (error) return 'An error has occurred: ' + error.message;
|
401
|
-
|
402
|
-
return (
|
403
|
-
<>
|
404
|
-
<RePlotly
|
405
|
-
key={updatedProps.id + '.inherent'}
|
406
|
-
props={updatedProps}
|
407
|
-
style={style}
|
408
|
-
/>
|
409
|
-
</>
|
410
|
-
);
|
411
|
-
};
|
412
|
-
|
413
|
-
export default DotBio;
|
1
|
+
import React, { useEffect, useState } from 'react';
|
2
|
+
import RePlotly from '../modules/replotly';
|
3
|
+
import simplefetch from '../fetch/fetch';
|
4
|
+
import { useAppContext } from '../appContext';
|
5
|
+
import mmtrbc_dot from '../modules/replotly/presets/mmtrbc.dot';
|
6
|
+
import conditionalFetch from '../fetch/condfetch';
|
7
|
+
|
8
|
+
const DotBio = ({ props, style }) => {
|
9
|
+
// Declare states at the beginning
|
10
|
+
const { sharedData } = useAppContext();
|
11
|
+
const { debug } = props;
|
12
|
+
|
13
|
+
// Loading and error states
|
14
|
+
const [loading, setLoading] = useState(true);
|
15
|
+
const [error, setError] = useState(null);
|
16
|
+
const [updatedProps, setUpdatedProps] = useState(props);
|
17
|
+
|
18
|
+
useEffect(() => {
|
19
|
+
const fetchData = async () => {
|
20
|
+
const { meta, gene: url, genelist } = props;
|
21
|
+
|
22
|
+
if (meta === undefined) {
|
23
|
+
alert('DotBio: meta is undefined');
|
24
|
+
}
|
25
|
+
if (url === undefined) {
|
26
|
+
alert(
|
27
|
+
'DotBio: gene is undefined, need to be a url or data of gene',
|
28
|
+
);
|
29
|
+
}
|
30
|
+
if (genelist === undefined) {
|
31
|
+
console.log(
|
32
|
+
'DotBio: genelist is undefined, defaulting to genelist',
|
33
|
+
);
|
34
|
+
}
|
35
|
+
|
36
|
+
const metadata = await conditionalFetch(meta, sharedData, {});
|
37
|
+
|
38
|
+
const sharedgenelist = genelist
|
39
|
+
? sharedData[genelist]
|
40
|
+
: sharedData.genelist;
|
41
|
+
|
42
|
+
let genedata = {};
|
43
|
+
|
44
|
+
if (sharedgenelist !== undefined) {
|
45
|
+
// genelist is a list of genes names (strings)
|
46
|
+
for (const gene of sharedgenelist) {
|
47
|
+
//console.log("fetching", gene, url);
|
48
|
+
genedata[gene] = await simplefetch(url, {
|
49
|
+
id: gene,
|
50
|
+
key: 'gene',
|
51
|
+
type: 'gene',
|
52
|
+
debug: debug,
|
53
|
+
});
|
54
|
+
}
|
55
|
+
}
|
56
|
+
|
57
|
+
//if (debug)
|
58
|
+
// console.log("DotBio: genedata", genedata, "metadata", metadata);
|
59
|
+
|
60
|
+
return { metadata, genedata };
|
61
|
+
};
|
62
|
+
|
63
|
+
const processData = ({ metadata, genedata }) => {
|
64
|
+
// Set the title & size of the plot
|
65
|
+
const { showscale = false } = props.settings;
|
66
|
+
|
67
|
+
let config = mmtrbc_dot;
|
68
|
+
|
69
|
+
//console.log('DotBio: config', config);
|
70
|
+
// Dealing with the data
|
71
|
+
const { exclude_celltype } = props;
|
72
|
+
const _celltypes = [...new Set(metadata['Cell_Type'])].filter(
|
73
|
+
(celltype) => !exclude_celltype.includes(celltype),
|
74
|
+
);
|
75
|
+
|
76
|
+
// the length of the gene data
|
77
|
+
// const _genedata_length = Object.keys(_genedata).length;
|
78
|
+
// console.log("DotBio: _genedata_length", gene, _genedata_length);
|
79
|
+
|
80
|
+
let processedData = {};
|
81
|
+
let cellTypeCounts = {};
|
82
|
+
let expressionSums = {};
|
83
|
+
|
84
|
+
for (const gene in genedata) {
|
85
|
+
const _genedata = genedata[gene];
|
86
|
+
|
87
|
+
processedData[gene] = metadata['Cell_ID']
|
88
|
+
.map((cellId, index) => {
|
89
|
+
const _expression = _genedata[cellId] ?? -1;
|
90
|
+
return {
|
91
|
+
name: cellId,
|
92
|
+
value: [
|
93
|
+
metadata['x'][index],
|
94
|
+
metadata['y'][index],
|
95
|
+
_expression,
|
96
|
+
],
|
97
|
+
UMI: Math.round(metadata.N_UMI[index] * 100) / 100, // round to 2 decimal places
|
98
|
+
Type: metadata['Cell_Type'][index],
|
99
|
+
Expression: _expression,
|
100
|
+
};
|
101
|
+
})
|
102
|
+
.filter((item) => item.Expression > 0);
|
103
|
+
|
104
|
+
cellTypeCounts[gene] = processedData[gene].reduce(
|
105
|
+
(counts, item) => {
|
106
|
+
if (!counts[item.Type]) {
|
107
|
+
counts[item.Type] = 1;
|
108
|
+
} else {
|
109
|
+
counts[item.Type]++;
|
110
|
+
}
|
111
|
+
return counts;
|
112
|
+
},
|
113
|
+
{},
|
114
|
+
);
|
115
|
+
|
116
|
+
expressionSums[gene] = processedData[gene].reduce(
|
117
|
+
(sums, item) => {
|
118
|
+
if (!sums[item.Type]) {
|
119
|
+
sums[item.Type] = item.Expression;
|
120
|
+
} else {
|
121
|
+
sums[item.Type] += item.Expression;
|
122
|
+
}
|
123
|
+
return sums;
|
124
|
+
},
|
125
|
+
{},
|
126
|
+
);
|
127
|
+
}
|
128
|
+
|
129
|
+
let totalCellTypeCounts = metadata['Cell_Type'].reduce(
|
130
|
+
(counts, cellType) => {
|
131
|
+
if (!counts[cellType]) {
|
132
|
+
counts[cellType] = 1;
|
133
|
+
} else {
|
134
|
+
counts[cellType]++;
|
135
|
+
}
|
136
|
+
return counts;
|
137
|
+
},
|
138
|
+
{},
|
139
|
+
);
|
140
|
+
|
141
|
+
// If It provides the configuration of the plot
|
142
|
+
const {
|
143
|
+
colorMat: __colorMat,
|
144
|
+
sizeMat: __sizeMat,
|
145
|
+
xLabels: __xLabels,
|
146
|
+
yLabels: __yLabels,
|
147
|
+
} = props.settings;
|
148
|
+
|
149
|
+
// calculate the color matrix
|
150
|
+
const colorMat = __colorMat ?? {};
|
151
|
+
|
152
|
+
let averageExpression = {};
|
153
|
+
|
154
|
+
for (let gene in genedata) {
|
155
|
+
if (!averageExpression[gene]) {
|
156
|
+
averageExpression[gene] = {};
|
157
|
+
}
|
158
|
+
for (let cellType in cellTypeCounts[gene]) {
|
159
|
+
let count = totalCellTypeCounts[cellType];
|
160
|
+
let totalExpression = expressionSums[gene][cellType];
|
161
|
+
averageExpression[gene][cellType] = totalExpression / count;
|
162
|
+
// round to the nearest integer
|
163
|
+
averageExpression[gene][cellType] = Math.round(
|
164
|
+
averageExpression[gene][cellType],
|
165
|
+
);
|
166
|
+
}
|
167
|
+
}
|
168
|
+
|
169
|
+
//console.log("Average Expression: ", averageExpression);
|
170
|
+
|
171
|
+
// Calculate the maximum and minimum expression across all genes and cell types
|
172
|
+
let maxExpression = 0;
|
173
|
+
let minExpression = Infinity;
|
174
|
+
for (let gene in averageExpression) {
|
175
|
+
for (let cellType in averageExpression[gene]) {
|
176
|
+
maxExpression = Math.max(
|
177
|
+
maxExpression,
|
178
|
+
averageExpression[gene][cellType],
|
179
|
+
);
|
180
|
+
minExpression = Math.min(
|
181
|
+
minExpression,
|
182
|
+
averageExpression[gene][cellType],
|
183
|
+
);
|
184
|
+
}
|
185
|
+
}
|
186
|
+
|
187
|
+
// Create a color scale function that uses the maximum and minimum expression
|
188
|
+
function colorScale(expression) {
|
189
|
+
// Normalize the expression to a value between 0 and 1
|
190
|
+
let normalized =
|
191
|
+
(expression - minExpression) /
|
192
|
+
(maxExpression - minExpression);
|
193
|
+
|
194
|
+
// Map the normalized value to a color
|
195
|
+
let r = 70 + Math.round(185 * normalized);
|
196
|
+
let g = 125; // + Math.round(255 * normalized); //(1 - normalized)
|
197
|
+
let b = 255;
|
198
|
+
|
199
|
+
return 'rgb(' + r + ', ' + g + ', ' + b + ')';
|
200
|
+
}
|
201
|
+
|
202
|
+
// Assign colors based on average expression
|
203
|
+
for (let gene in averageExpression) {
|
204
|
+
if (!colorMat[gene]) {
|
205
|
+
colorMat[gene] = {};
|
206
|
+
}
|
207
|
+
for (let cellType in averageExpression[gene]) {
|
208
|
+
let expression = averageExpression[gene][cellType];
|
209
|
+
colorMat[gene][cellType] = colorScale(expression);
|
210
|
+
}
|
211
|
+
}
|
212
|
+
|
213
|
+
//console.log("Color Matrix: ", colorMat);
|
214
|
+
|
215
|
+
// calculate the size matrix
|
216
|
+
let sizeMat = __sizeMat ?? {};
|
217
|
+
|
218
|
+
for (let gene in cellTypeCounts) {
|
219
|
+
if (!sizeMat[gene]) {
|
220
|
+
sizeMat[gene] = {};
|
221
|
+
}
|
222
|
+
|
223
|
+
for (let cellType of _celltypes) {
|
224
|
+
if (!cellTypeCounts[gene][cellType]) {
|
225
|
+
sizeMat[gene][cellType] = 0;
|
226
|
+
} else {
|
227
|
+
sizeMat[gene][cellType] =
|
228
|
+
Math.round(
|
229
|
+
(cellTypeCounts[gene][cellType] /
|
230
|
+
totalCellTypeCounts[cellType]) *
|
231
|
+
1000,
|
232
|
+
) / 1000;
|
233
|
+
}
|
234
|
+
}
|
235
|
+
}
|
236
|
+
|
237
|
+
//console.log("Size Matrix: ", sizeMat);
|
238
|
+
|
239
|
+
// process the xLabels
|
240
|
+
const xLabels = __xLabels ?? _celltypes;
|
241
|
+
|
242
|
+
// process the yLabels
|
243
|
+
const yLabels = __yLabels ?? Object.keys(genedata);
|
244
|
+
|
245
|
+
// process the data
|
246
|
+
const {
|
247
|
+
annotation = false,
|
248
|
+
showlegend = false,
|
249
|
+
maxdotsize = 20,
|
250
|
+
} = props.settings;
|
251
|
+
|
252
|
+
// check list
|
253
|
+
const _genedata_length = Object.keys(genedata).length;
|
254
|
+
//console.log("DotBio: _genedata_length", _genedata_length, isEmpty(genedata));
|
255
|
+
const _hasData = _genedata_length > 0;
|
256
|
+
|
257
|
+
const data = xLabels.map((xLabel, i) => {
|
258
|
+
let sizes = yLabels.map(
|
259
|
+
(yLabel) => sizeMat[yLabel][xLabel] * maxdotsize,
|
260
|
+
);
|
261
|
+
let colors = yLabels.map((yLabel) => colorMat[yLabel][xLabel]);
|
262
|
+
// round to the nearest integer
|
263
|
+
let hoverTexts = yLabels.map((yLabel) => {
|
264
|
+
let avgExpress = _hasData
|
265
|
+
? averageExpression[yLabel][xLabel]
|
266
|
+
: 0;
|
267
|
+
return `${xLabel} <br> ${yLabel} <br> Avg Expression: ${avgExpress}`;
|
268
|
+
});
|
269
|
+
|
270
|
+
return {
|
271
|
+
mode: 'markers',
|
272
|
+
x: Array(sizes.length).fill(xLabel), // Repeat the xLabel for each y value
|
273
|
+
y: yLabels,
|
274
|
+
marker: {
|
275
|
+
size: sizes,
|
276
|
+
color: colors,
|
277
|
+
},
|
278
|
+
type: 'scatter',
|
279
|
+
name: xLabel,
|
280
|
+
hoverinfo: 'text',
|
281
|
+
hovertext: hoverTexts,
|
282
|
+
showlegend: showlegend,
|
283
|
+
};
|
284
|
+
});
|
285
|
+
|
286
|
+
//console.log('DotBio: data', data);
|
287
|
+
config.data = data;
|
288
|
+
|
289
|
+
// show the largest percentage of celltype and least percentage of celltype
|
290
|
+
let minVal = Number.MAX_VALUE;
|
291
|
+
let maxVal = Number.MIN_VALUE;
|
292
|
+
let minKey = '',
|
293
|
+
maxKey = '';
|
294
|
+
|
295
|
+
for (let key in sizeMat) {
|
296
|
+
for (let innerKey in sizeMat[key]) {
|
297
|
+
if (sizeMat[key][innerKey] < minVal) {
|
298
|
+
minVal = sizeMat[key][innerKey];
|
299
|
+
minKey = key + ' ' + innerKey;
|
300
|
+
}
|
301
|
+
if (sizeMat[key][innerKey] > maxVal) {
|
302
|
+
maxVal = sizeMat[key][innerKey];
|
303
|
+
maxKey = key + ' ' + innerKey;
|
304
|
+
}
|
305
|
+
}
|
306
|
+
}
|
307
|
+
|
308
|
+
//console.log("Min Value:", minVal, "Key:", minKey);
|
309
|
+
//console.log("Max Value:", maxVal, "Key:", maxKey);
|
310
|
+
|
311
|
+
if (annotation) {
|
312
|
+
// round the values
|
313
|
+
minVal = Math.round(minVal * 100000) / 1000;
|
314
|
+
maxVal = Math.round(maxVal * 100000) / 1000;
|
315
|
+
|
316
|
+
config.layout.annotations = _hasData
|
317
|
+
? [
|
318
|
+
{
|
319
|
+
text: `<b>${
|
320
|
+
minKey.split(' ')[1]
|
321
|
+
}</b>: ${minVal}%`,
|
322
|
+
x: -0.35,
|
323
|
+
y: -0.2 - minVal / 2000, // Adjust this formula to move the annotation as per your requirement
|
324
|
+
xref: 'paper',
|
325
|
+
yref: 'paper',
|
326
|
+
showarrow: false,
|
327
|
+
font: {
|
328
|
+
size: 12,
|
329
|
+
},
|
330
|
+
},
|
331
|
+
{
|
332
|
+
text: `<b>${
|
333
|
+
maxKey.split(' ')[1]
|
334
|
+
}: </b>${maxVal}%`,
|
335
|
+
x: -0.35,
|
336
|
+
y: -0.35 + maxVal / 2000, // Adjust this formula to move the annotation as per your requirement
|
337
|
+
xref: 'paper',
|
338
|
+
yref: 'paper',
|
339
|
+
showarrow: false,
|
340
|
+
font: {
|
341
|
+
size: 12,
|
342
|
+
},
|
343
|
+
},
|
344
|
+
]
|
345
|
+
: [];
|
346
|
+
}
|
347
|
+
//console.log(config.data);
|
348
|
+
// remove the title if data present
|
349
|
+
config.layout.title = _hasData ? '' : config.title;
|
350
|
+
config.layout.margin.t = _hasData ? 20 : 80;
|
351
|
+
//console.log(config);
|
352
|
+
|
353
|
+
if (showscale && _hasData) {
|
354
|
+
data.push({
|
355
|
+
type: 'scatter',
|
356
|
+
mode: 'markers',
|
357
|
+
x: [null], // No x or y data for this trace
|
358
|
+
y: [null],
|
359
|
+
marker: {
|
360
|
+
size: 0, // Marker size is 0, essentially making it invisible
|
361
|
+
color: [minExpression, maxExpression], // Use min and max expression as color range
|
362
|
+
colorscale: [
|
363
|
+
[0, 'rgb(70, 125, 255)'], // Color corresponding to min expression
|
364
|
+
[1, 'rgb(255, 125, 255)'], // Color corresponding to max expression
|
365
|
+
],
|
366
|
+
colorbar: {
|
367
|
+
title: 'Avg Expression', // Title of the color bar
|
368
|
+
titleside: 'right',
|
369
|
+
tickmode: 'array',
|
370
|
+
tickvals: [minExpression, maxExpression], // Tick values should match actual data range
|
371
|
+
ticktext: [minExpression, maxExpression], // Tick text indicating Low and High
|
372
|
+
ticks: 'outside',
|
373
|
+
},
|
374
|
+
showscale: true,
|
375
|
+
},
|
376
|
+
hoverinfo: 'none', // No hover info for this trace
|
377
|
+
showlegend: false, // No legend for this trace
|
378
|
+
});
|
379
|
+
}
|
380
|
+
|
381
|
+
//console.log('DotBio: config', config);
|
382
|
+
// processing logic here...
|
383
|
+
setUpdatedProps((updatedProps) => ({
|
384
|
+
...updatedProps,
|
385
|
+
data: config.data,
|
386
|
+
}));
|
387
|
+
};
|
388
|
+
|
389
|
+
fetchData()
|
390
|
+
.then(processData)
|
391
|
+
.then(() => setLoading(false))
|
392
|
+
.catch((err) => {
|
393
|
+
console.error(err);
|
394
|
+
setError(err);
|
395
|
+
setLoading(false);
|
396
|
+
});
|
397
|
+
}, [props, sharedData, debug]);
|
398
|
+
|
399
|
+
if (loading) return 'Loading...';
|
400
|
+
if (error) return 'An error has occurred: ' + error.message;
|
401
|
+
|
402
|
+
return (
|
403
|
+
<>
|
404
|
+
<RePlotly
|
405
|
+
key={updatedProps.id + '.inherent'}
|
406
|
+
props={updatedProps}
|
407
|
+
style={style}
|
408
|
+
/>
|
409
|
+
</>
|
410
|
+
);
|
411
|
+
};
|
412
|
+
|
413
|
+
export default DotBio;
|