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,572 +1,572 @@
|
|
1
|
-
import React, { useEffect, useState } from 'react';
|
2
|
-
import Scatter from './Scatter';
|
3
|
-
import CircularProgress from '../widgets/circularProgress';
|
4
|
-
import { useAppContext } from '../appContext';
|
5
|
-
import simplefetch from '../fetch/fetch';
|
6
|
-
|
7
|
-
function rcolor(seed = 0) {
|
8
|
-
const maxHexValue = 16777215;
|
9
|
-
const randomSeed = seed || Math.random();
|
10
|
-
const randomColor = Math.floor(randomSeed * maxHexValue).toString(16);
|
11
|
-
const color = '#' + randomColor.padStart(6, '0');
|
12
|
-
|
13
|
-
return color;
|
14
|
-
}
|
15
|
-
|
16
|
-
function isEmpty(obj) {
|
17
|
-
return Object.keys(obj).length === 0 && obj.constructor === Object;
|
18
|
-
}
|
19
|
-
|
20
|
-
function ScatterBio({ props, style, reset }) {
|
21
|
-
// Declare states at the beginning
|
22
|
-
const { sharedData } = useAppContext();
|
23
|
-
const { debug } = props;
|
24
|
-
|
25
|
-
const [updatedProps, setUpdatedProps] = useState(props);
|
26
|
-
const [loading, setLoading] = useState(true);
|
27
|
-
const [error, setError] = useState(null);
|
28
|
-
|
29
|
-
useEffect(() => {
|
30
|
-
// ------------------------------ Fetch Data ------------------------------
|
31
|
-
const {
|
32
|
-
meta,
|
33
|
-
simpleload = true,
|
34
|
-
gene,
|
35
|
-
geneval,
|
36
|
-
metaval,
|
37
|
-
colour = 'Cell_Type', // 'Cell_Type' or 'Stage'
|
38
|
-
colourval = undefined,
|
39
|
-
axis_mapping,
|
40
|
-
config = {},
|
41
|
-
exclude_celltype = [],
|
42
|
-
entry_mapping = undefined,
|
43
|
-
exp_condition = 0,
|
44
|
-
startup_msg = '',
|
45
|
-
} = props;
|
46
|
-
|
47
|
-
// error handling for missing config --------------------------------------
|
48
|
-
|
49
|
-
// if meta is missing
|
50
|
-
if (!meta) {
|
51
|
-
setError({ message: 'meta is missing', data: { meta: meta } });
|
52
|
-
setLoading(false);
|
53
|
-
return;
|
54
|
-
} else if (!gene) {
|
55
|
-
setError({ message: 'gene is missing', data: { gene: gene } });
|
56
|
-
setLoading(false);
|
57
|
-
return;
|
58
|
-
}
|
59
|
-
// missing axis_mapping
|
60
|
-
// that are required for this widget to work
|
61
|
-
// represented as x, y while processing the data
|
62
|
-
else if (!axis_mapping || !axis_mapping.x || !axis_mapping.y) {
|
63
|
-
setError({
|
64
|
-
message: 'axis_mapping is missing/incorrect',
|
65
|
-
data: { axis_mapping: axis_mapping },
|
66
|
-
});
|
67
|
-
setLoading(false);
|
68
|
-
return;
|
69
|
-
}
|
70
|
-
// if not simpleload, then metaval is required
|
71
|
-
else if (!simpleload && !metaval) {
|
72
|
-
setError({
|
73
|
-
message: 'metaval is missing',
|
74
|
-
data: { metaval: metaval },
|
75
|
-
});
|
76
|
-
setLoading(false);
|
77
|
-
return;
|
78
|
-
}
|
79
|
-
|
80
|
-
/*
|
81
|
-
// missing config
|
82
|
-
else if (!config) {
|
83
|
-
setError({message: "config is missing", data: {config: config}});
|
84
|
-
setLoading(false);
|
85
|
-
return;
|
86
|
-
}
|
87
|
-
|
88
|
-
// config.xAxis & config.yAxis are required for this widget to work
|
89
|
-
else if (!config.xAxis || !config.yAxis) {
|
90
|
-
setError({message: "config.xAxis or config.yAxis is missing", data: {config: config}});
|
91
|
-
setLoading(false);
|
92
|
-
return;
|
93
|
-
}
|
94
|
-
*/
|
95
|
-
|
96
|
-
// ------------------------------------------------------------------------
|
97
|
-
|
98
|
-
const _query_gene = geneval ? sharedData[geneval] : sharedData.gene;
|
99
|
-
const _query_meta = metaval ? sharedData[metaval] : sharedData.meta;
|
100
|
-
|
101
|
-
const updatePlot = async () => {
|
102
|
-
try {
|
103
|
-
setLoading(true);
|
104
|
-
|
105
|
-
let metadata = {};
|
106
|
-
let genedata = {};
|
107
|
-
|
108
|
-
// if not simpleload, then we wait user to provide the data
|
109
|
-
if (!simpleload && (!_query_meta || _query_meta.length === 0)) {
|
110
|
-
setLoading({
|
111
|
-
message: `Please select ${startup_msg} to load data`,
|
112
|
-
});
|
113
|
-
return;
|
114
|
-
} else if (simpleload) {
|
115
|
-
metadata = await simplefetch(meta, {
|
116
|
-
id: _query_meta,
|
117
|
-
type: 'metadata',
|
118
|
-
debug: debug,
|
119
|
-
});
|
120
|
-
|
121
|
-
genedata = await simplefetch(gene, {
|
122
|
-
id: _query_gene,
|
123
|
-
key: 'gene', //TODO: obtain key from config which can override the type
|
124
|
-
type: 'gene',
|
125
|
-
debug: debug,
|
126
|
-
});
|
127
|
-
} else {
|
128
|
-
//console.log("ScatterBio: _query_meta", _query_meta);
|
129
|
-
//console.log("ScatterBio: nodes_mapping", nodes_mapping);
|
130
|
-
|
131
|
-
// Mapping logic to filter and transform IDs
|
132
|
-
const mapped_nodes = entry_mapping
|
133
|
-
? _query_meta.map((node) => entry_mapping[node])
|
134
|
-
: _query_meta;
|
135
|
-
|
136
|
-
//removed duplicated IDs
|
137
|
-
const _filted_query_meta = [...new Set(mapped_nodes)];
|
138
|
-
|
139
|
-
//console.log("ScatterBio: _filted_query_meta", _filted_query_meta);
|
140
|
-
|
141
|
-
const metadataPromises = _filted_query_meta.map(
|
142
|
-
async (metaId) => {
|
143
|
-
return await simplefetch(meta, {
|
144
|
-
id: metaId,
|
145
|
-
type: 'metadata',
|
146
|
-
debug: debug,
|
147
|
-
});
|
148
|
-
},
|
149
|
-
);
|
150
|
-
|
151
|
-
const metadataResponses = await Promise.all(
|
152
|
-
metadataPromises,
|
153
|
-
);
|
154
|
-
|
155
|
-
// Combine the metadata from different IDs
|
156
|
-
metadata = metadataResponses.reduce(
|
157
|
-
(combined, metadata) => {
|
158
|
-
return {
|
159
|
-
...combined,
|
160
|
-
...metadata,
|
161
|
-
};
|
162
|
-
},
|
163
|
-
{},
|
164
|
-
);
|
165
|
-
|
166
|
-
if (
|
167
|
-
_query_gene &&
|
168
|
-
_query_gene.length > 0 &&
|
169
|
-
!_query_gene.includes('None')
|
170
|
-
) {
|
171
|
-
if (debug)
|
172
|
-
console.log('ScatterBio: _query_gene', _query_gene);
|
173
|
-
|
174
|
-
const genedataPromises = _query_meta.map(
|
175
|
-
async (metaId) => {
|
176
|
-
const _mapped_metaId = entry_mapping
|
177
|
-
? entry_mapping[metaId]
|
178
|
-
: metaId;
|
179
|
-
const _merged_query_gene =
|
180
|
-
_mapped_metaId + '/' + _query_gene;
|
181
|
-
//console.log(_merged_query_gene);
|
182
|
-
return await simplefetch(gene, {
|
183
|
-
id: _merged_query_gene,
|
184
|
-
key: 'gene', //TODO: obtain key from config which can override the type
|
185
|
-
type: 'gene',
|
186
|
-
debug: debug,
|
187
|
-
});
|
188
|
-
},
|
189
|
-
);
|
190
|
-
|
191
|
-
genedata = await Promise.any(genedataPromises);
|
192
|
-
//console.log("ScatterBio: genedata", genedata);
|
193
|
-
}
|
194
|
-
}
|
195
|
-
|
196
|
-
const _nogene = isEmpty(genedata);
|
197
|
-
if (debug) console.log('ScatterBio: _nogene', _nogene);
|
198
|
-
if (debug)
|
199
|
-
console.log(
|
200
|
-
'ScatterBio: genedata',
|
201
|
-
genedata,
|
202
|
-
'metadata',
|
203
|
-
metadata,
|
204
|
-
);
|
205
|
-
|
206
|
-
// ------------------------------ Proccess Data ------------------------------
|
207
|
-
const { colors: __colors = [] } = config;
|
208
|
-
|
209
|
-
const filted_colour = sharedData[colourval]
|
210
|
-
? sharedData[colourval].replace(' ', '_')
|
211
|
-
: colour;
|
212
|
-
|
213
|
-
if (debug)
|
214
|
-
console.log('ScatterBio - Colour by:', filted_colour);
|
215
|
-
|
216
|
-
const __celltypes = [
|
217
|
-
...new Set(metadata[filted_colour]),
|
218
|
-
].filter((celltype) => !exclude_celltype.includes(celltype));
|
219
|
-
|
220
|
-
let { zcolor: visualcolor = [] } = config;
|
221
|
-
let seriescolor = {};
|
222
|
-
|
223
|
-
if (Array.isArray(__colors) && __colors.length > 0) {
|
224
|
-
__colors.forEach((item) => {
|
225
|
-
if (item.color) visualcolor.push(item.color);
|
226
|
-
else visualcolor.push(rcolor());
|
227
|
-
});
|
228
|
-
__celltypes.forEach((celltype, index) => {
|
229
|
-
const cell = __colors.find(
|
230
|
-
(item) => item.name === celltype,
|
231
|
-
);
|
232
|
-
const _color = cell?.color ?? visualcolor[index];
|
233
|
-
const _symbol =
|
234
|
-
__colors.find((item) => item.name === celltype)
|
235
|
-
?.symbol ?? 'circle';
|
236
|
-
seriescolor[celltype] = {
|
237
|
-
color: _color,
|
238
|
-
symbol: _symbol,
|
239
|
-
};
|
240
|
-
});
|
241
|
-
} else {
|
242
|
-
let no_zcolor = false;
|
243
|
-
if (visualcolor.length === 0) {
|
244
|
-
no_zcolor = true;
|
245
|
-
}
|
246
|
-
__celltypes.forEach((celltype, index) => {
|
247
|
-
if (no_zcolor) {
|
248
|
-
visualcolor.push(rcolor());
|
249
|
-
}
|
250
|
-
seriescolor[celltype] = {
|
251
|
-
color: rcolor(),
|
252
|
-
symbol: 'circle',
|
253
|
-
};
|
254
|
-
});
|
255
|
-
}
|
256
|
-
|
257
|
-
// console.log("ScatterBio: axis_mapping", axis_mapping);
|
258
|
-
// console.log("metadata: ", metadata);
|
259
|
-
const { extra } = axis_mapping;
|
260
|
-
|
261
|
-
let processedData;
|
262
|
-
|
263
|
-
try {
|
264
|
-
processedData = metadata['Cell_ID'].map((cellId, index) => {
|
265
|
-
const _expression =
|
266
|
-
genedata[cellId] >= exp_condition
|
267
|
-
? genedata[cellId]
|
268
|
-
: _nogene
|
269
|
-
? 999
|
270
|
-
: -1;
|
271
|
-
const _Cell_Type = metadata[filted_colour] ?? 'Unknown';
|
272
|
-
if (
|
273
|
-
!__celltypes.includes('Unknown') &&
|
274
|
-
__celltypes.length === 0
|
275
|
-
) {
|
276
|
-
__celltypes.push('Unknown');
|
277
|
-
seriescolor['Unknown'] = {
|
278
|
-
color: '#000000',
|
279
|
-
symbol: 'circle',
|
280
|
-
};
|
281
|
-
}
|
282
|
-
// const _UMI = metadata.N_UMI ? Math.round(metadata.N_UMI[index] * 100) / 100 : 0;
|
283
|
-
// round to 2 decimal places
|
284
|
-
const extraProperties = {};
|
285
|
-
for (const property in extra) {
|
286
|
-
if (metadata[extra[property]]) {
|
287
|
-
extraProperties[property] =
|
288
|
-
metadata[extra[property]][index];
|
289
|
-
}
|
290
|
-
}
|
291
|
-
|
292
|
-
const z_index =
|
293
|
-
_expression === -1 || _expression === 999
|
294
|
-
? config.visium_cell_fraction
|
295
|
-
? extraProperties[
|
296
|
-
config.visium_cell_fraction
|
297
|
-
] ?? _expression
|
298
|
-
: _expression
|
299
|
-
: _expression;
|
300
|
-
|
301
|
-
return {
|
302
|
-
name: cellId,
|
303
|
-
value: [
|
304
|
-
metadata[axis_mapping.x][index],
|
305
|
-
metadata[axis_mapping.y][index],
|
306
|
-
z_index,
|
307
|
-
],
|
308
|
-
Type:
|
309
|
-
_Cell_Type === 'Unknown'
|
310
|
-
? 'Unknown'
|
311
|
-
: _Cell_Type[index],
|
312
|
-
Expression: _expression,
|
313
|
-
...extraProperties,
|
314
|
-
};
|
315
|
-
});
|
316
|
-
|
317
|
-
//console.log("ScatterBio: processedData", processedData[0], processedData[1]);
|
318
|
-
} catch (err) {
|
319
|
-
// if TypeError, then set error message to remind user to check the api
|
320
|
-
setError({
|
321
|
-
message:
|
322
|
-
'Please check the API, \n' +
|
323
|
-
'Your API may not return the correct data format. \n' +
|
324
|
-
meta +
|
325
|
-
'\n' +
|
326
|
-
err,
|
327
|
-
});
|
328
|
-
setLoading(false);
|
329
|
-
return;
|
330
|
-
}
|
331
|
-
|
332
|
-
if (debug) console.log(processedData[2], processedData[3]);
|
333
|
-
|
334
|
-
const {
|
335
|
-
dotsize,
|
336
|
-
dotFactor = 2000,
|
337
|
-
minSymbolSize = 2,
|
338
|
-
maxSymbolSize = 10,
|
339
|
-
} = config;
|
340
|
-
const pointCount = processedData.length;
|
341
|
-
|
342
|
-
// Calculate the symbol size based on the number of points
|
343
|
-
let symbolSize;
|
344
|
-
if (dotsize === 'auto') {
|
345
|
-
const sizeFactor = Math.max(
|
346
|
-
1 - (pointCount - 1000) / dotFactor,
|
347
|
-
0.2,
|
348
|
-
);
|
349
|
-
symbolSize =
|
350
|
-
(maxSymbolSize - minSymbolSize) * sizeFactor +
|
351
|
-
minSymbolSize;
|
352
|
-
//console.log(pointCount, symbolSize);
|
353
|
-
} else {
|
354
|
-
symbolSize = typeof dotsize === 'number' ? dotsize : 5;
|
355
|
-
}
|
356
|
-
|
357
|
-
// Create a series for each unique cell type
|
358
|
-
const series = __celltypes.map((cellType) => {
|
359
|
-
return {
|
360
|
-
name: cellType,
|
361
|
-
type: 'scatter',
|
362
|
-
data: processedData.filter(
|
363
|
-
(data) =>
|
364
|
-
data.Type === cellType && data.Expression > -1,
|
365
|
-
),
|
366
|
-
itemStyle: {
|
367
|
-
color: seriescolor[cellType].color,
|
368
|
-
},
|
369
|
-
symbol: seriescolor[cellType].symbol,
|
370
|
-
symbolSize: symbolSize,
|
371
|
-
};
|
372
|
-
});
|
373
|
-
|
374
|
-
//console.log("series: ", series);
|
375
|
-
|
376
|
-
const zshift =
|
377
|
-
-config.zshift || -Math.max(config.chartWidth * 0.02, 10);
|
378
|
-
|
379
|
-
const auto_zmax = Math.max(
|
380
|
-
...processedData.map((item) => item.value[2]),
|
381
|
-
);
|
382
|
-
const auto_zmin = Math.min(
|
383
|
-
...processedData.map((item) => item.value[2]),
|
384
|
-
);
|
385
|
-
|
386
|
-
//console.log("ScatterBio: auto_zmax", auto_zmax, "auto_zmin", auto_zmin, config.visium_cell_fraction);
|
387
|
-
|
388
|
-
let visualMap =
|
389
|
-
_nogene && !config.visium_cell_fraction
|
390
|
-
? []
|
391
|
-
: {
|
392
|
-
min: config.zmin ?? auto_zmin,
|
393
|
-
max: config.zmax ?? auto_zmax,
|
394
|
-
dimension: 2,
|
395
|
-
orient: 'vertical',
|
396
|
-
top: 'center',
|
397
|
-
right: zshift,
|
398
|
-
text: config.labels
|
399
|
-
? config.labels.z ?? ''
|
400
|
-
: '',
|
401
|
-
textGap: 10,
|
402
|
-
calculable: true,
|
403
|
-
inRange: {
|
404
|
-
color: visualcolor,
|
405
|
-
},
|
406
|
-
textStyle: {
|
407
|
-
writingMode: 'vertical-lr',
|
408
|
-
},
|
409
|
-
...config.visualMap,
|
410
|
-
};
|
411
|
-
|
412
|
-
const { legend: _config_legend = {} } = config;
|
413
|
-
const legendItemCount = __celltypes.length;
|
414
|
-
const { type: _legendType, ...__config_legend } =
|
415
|
-
_config_legend;
|
416
|
-
let legendType = 'scroll';
|
417
|
-
if (_legendType === 'auto') {
|
418
|
-
legendType = legendItemCount > 10 ? 'scroll' : 'plain';
|
419
|
-
}
|
420
|
-
|
421
|
-
// console.log("__celltypes:", __celltypes);
|
422
|
-
// Sort __celltypes by extracting and comparing the numerical part
|
423
|
-
const sortedCellTypes = __celltypes.slice().sort((a, b) => {
|
424
|
-
// Handle null, undefined, or other unexpected values
|
425
|
-
if (a === null || b === null) {
|
426
|
-
return 0; // Handle the case as per your requirement
|
427
|
-
}
|
428
|
-
const numA = parseInt(a.substring(1), 10);
|
429
|
-
const numB = parseInt(b.substring(1), 10);
|
430
|
-
return numA - numB;
|
431
|
-
});
|
432
|
-
|
433
|
-
// legend
|
434
|
-
const _legend =
|
435
|
-
_config_legend === false
|
436
|
-
? false
|
437
|
-
: {
|
438
|
-
data: sortedCellTypes, //__celltypes,
|
439
|
-
orient: 'horizontal',
|
440
|
-
top: 5, // "top" | "bottom" | "center"
|
441
|
-
right: 'center', // "top" | "bottom" | "center",
|
442
|
-
...__config_legend,
|
443
|
-
type: legendType,
|
444
|
-
};
|
445
|
-
//console.log("ScatterBio: legend", _legend);
|
446
|
-
|
447
|
-
const gridSettings = [
|
448
|
-
{
|
449
|
-
top: '5%',
|
450
|
-
bottom: '5%',
|
451
|
-
left: '5%',
|
452
|
-
right: '5%',
|
453
|
-
...(config.grid || {}), // Merge with config.grid if available, otherwise an empty object
|
454
|
-
},
|
455
|
-
];
|
456
|
-
|
457
|
-
const title = config.title ?? '';
|
458
|
-
// Update props for Scatter
|
459
|
-
|
460
|
-
let scatterBioProps = {
|
461
|
-
...props,
|
462
|
-
config: {
|
463
|
-
chartWidth: 800,
|
464
|
-
chartHeight: 600,
|
465
|
-
...config,
|
466
|
-
series: series,
|
467
|
-
legend: _legend,
|
468
|
-
visualMap: visualMap,
|
469
|
-
title: _nogene
|
470
|
-
? title
|
471
|
-
: {
|
472
|
-
...title,
|
473
|
-
text: _query_gene,
|
474
|
-
},
|
475
|
-
// Use formatter from config if provided, otherwise use the default formatter
|
476
|
-
formatter: config.formatter
|
477
|
-
? (params) => config.formatter(params)
|
478
|
-
: (params) => {
|
479
|
-
return `
|
480
|
-
${params.name}
|
481
|
-
<br/> Type: ${params.data.Type}
|
482
|
-
<br/> UMI: ${params.data.UMI ?? 0}
|
483
|
-
`;
|
484
|
-
},
|
485
|
-
grid: gridSettings,
|
486
|
-
},
|
487
|
-
};
|
488
|
-
|
489
|
-
/*
|
490
|
-
const imageDom = new Image();
|
491
|
-
imageDom.src = './resources/YY0-44_image.png';
|
492
|
-
scatterBioProps.config.backgroundColor = {
|
493
|
-
image: imageDom,
|
494
|
-
repeat: 'no-repeat' // or 'repeat', 'repeat-x', 'repeat-y' depending on your needs
|
495
|
-
};
|
496
|
-
*/
|
497
|
-
|
498
|
-
//console.log("ScatterBio: scatterBioProps.config", scatterBioProps.config);
|
499
|
-
setUpdatedProps(scatterBioProps);
|
500
|
-
|
501
|
-
setLoading(false);
|
502
|
-
} catch (err) {
|
503
|
-
console.log(err);
|
504
|
-
if (err.name === 'TypeError' && simpleload)
|
505
|
-
setError({
|
506
|
-
message: `invalid URL for metadata/gene,\nplease check your URL or set simpleload to false`,
|
507
|
-
});
|
508
|
-
else setError(err);
|
509
|
-
setLoading(false);
|
510
|
-
}
|
511
|
-
};
|
512
|
-
updatePlot();
|
513
|
-
}, [props, sharedData, debug]);
|
514
|
-
|
515
|
-
if (reset) {
|
516
|
-
reset(props.id, (vals) => {
|
517
|
-
// logic to reset the data
|
518
|
-
const { gene: rec_gene = ' ' } = vals;
|
519
|
-
console.log('received data', sharedData[rec_gene]);
|
520
|
-
});
|
521
|
-
}
|
522
|
-
|
523
|
-
if (loading.message) {
|
524
|
-
return (
|
525
|
-
<div style={{ marginTop: '10px', ...style }}>{loading.message}</div>
|
526
|
-
);
|
527
|
-
} else if (loading) {
|
528
|
-
// Render a loading animation
|
529
|
-
return (
|
530
|
-
<div
|
531
|
-
style={{
|
532
|
-
display: 'flex',
|
533
|
-
flexDirection: 'column',
|
534
|
-
alignItems: 'center',
|
535
|
-
justifyContent: 'center',
|
536
|
-
height: '100%',
|
537
|
-
...style,
|
538
|
-
}}>
|
539
|
-
<CircularProgress color='primary' />
|
540
|
-
</div>
|
541
|
-
);
|
542
|
-
}
|
543
|
-
|
544
|
-
if (error) {
|
545
|
-
const errorLines = error.message.split('\n');
|
546
|
-
const errorElements = errorLines.map((line, index) => (
|
547
|
-
<p
|
548
|
-
key={index}
|
549
|
-
style={{ color: 'red' }}>
|
550
|
-
{line}
|
551
|
-
</p>
|
552
|
-
));
|
553
|
-
return (
|
554
|
-
<div style={style}>
|
555
|
-
An error has occurred: <br />
|
556
|
-
<br /> {errorElements}
|
557
|
-
</div>
|
558
|
-
);
|
559
|
-
}
|
560
|
-
// Only render Scatter when not loading
|
561
|
-
return (
|
562
|
-
<>
|
563
|
-
<Scatter
|
564
|
-
key={updatedProps.id + '.inherent'}
|
565
|
-
props={updatedProps}
|
566
|
-
style={style}
|
567
|
-
/>
|
568
|
-
</>
|
569
|
-
);
|
570
|
-
}
|
571
|
-
|
572
|
-
export default ScatterBio;
|
1
|
+
import React, { useEffect, useState } from 'react';
|
2
|
+
import Scatter from './Scatter';
|
3
|
+
import CircularProgress from '../widgets/circularProgress';
|
4
|
+
import { useAppContext } from '../appContext';
|
5
|
+
import simplefetch from '../fetch/fetch';
|
6
|
+
|
7
|
+
function rcolor(seed = 0) {
|
8
|
+
const maxHexValue = 16777215;
|
9
|
+
const randomSeed = seed || Math.random();
|
10
|
+
const randomColor = Math.floor(randomSeed * maxHexValue).toString(16);
|
11
|
+
const color = '#' + randomColor.padStart(6, '0');
|
12
|
+
|
13
|
+
return color;
|
14
|
+
}
|
15
|
+
|
16
|
+
function isEmpty(obj) {
|
17
|
+
return Object.keys(obj).length === 0 && obj.constructor === Object;
|
18
|
+
}
|
19
|
+
|
20
|
+
function ScatterBio({ props, style, reset }) {
|
21
|
+
// Declare states at the beginning
|
22
|
+
const { sharedData } = useAppContext();
|
23
|
+
const { debug } = props;
|
24
|
+
|
25
|
+
const [updatedProps, setUpdatedProps] = useState(props);
|
26
|
+
const [loading, setLoading] = useState(true);
|
27
|
+
const [error, setError] = useState(null);
|
28
|
+
|
29
|
+
useEffect(() => {
|
30
|
+
// ------------------------------ Fetch Data ------------------------------
|
31
|
+
const {
|
32
|
+
meta,
|
33
|
+
simpleload = true,
|
34
|
+
gene,
|
35
|
+
geneval,
|
36
|
+
metaval,
|
37
|
+
colour = 'Cell_Type', // 'Cell_Type' or 'Stage'
|
38
|
+
colourval = undefined,
|
39
|
+
axis_mapping,
|
40
|
+
config = {},
|
41
|
+
exclude_celltype = [],
|
42
|
+
entry_mapping = undefined,
|
43
|
+
exp_condition = 0,
|
44
|
+
startup_msg = '',
|
45
|
+
} = props;
|
46
|
+
|
47
|
+
// error handling for missing config --------------------------------------
|
48
|
+
|
49
|
+
// if meta is missing
|
50
|
+
if (!meta) {
|
51
|
+
setError({ message: 'meta is missing', data: { meta: meta } });
|
52
|
+
setLoading(false);
|
53
|
+
return;
|
54
|
+
} else if (!gene) {
|
55
|
+
setError({ message: 'gene is missing', data: { gene: gene } });
|
56
|
+
setLoading(false);
|
57
|
+
return;
|
58
|
+
}
|
59
|
+
// missing axis_mapping
|
60
|
+
// that are required for this widget to work
|
61
|
+
// represented as x, y while processing the data
|
62
|
+
else if (!axis_mapping || !axis_mapping.x || !axis_mapping.y) {
|
63
|
+
setError({
|
64
|
+
message: 'axis_mapping is missing/incorrect',
|
65
|
+
data: { axis_mapping: axis_mapping },
|
66
|
+
});
|
67
|
+
setLoading(false);
|
68
|
+
return;
|
69
|
+
}
|
70
|
+
// if not simpleload, then metaval is required
|
71
|
+
else if (!simpleload && !metaval) {
|
72
|
+
setError({
|
73
|
+
message: 'metaval is missing',
|
74
|
+
data: { metaval: metaval },
|
75
|
+
});
|
76
|
+
setLoading(false);
|
77
|
+
return;
|
78
|
+
}
|
79
|
+
|
80
|
+
/*
|
81
|
+
// missing config
|
82
|
+
else if (!config) {
|
83
|
+
setError({message: "config is missing", data: {config: config}});
|
84
|
+
setLoading(false);
|
85
|
+
return;
|
86
|
+
}
|
87
|
+
|
88
|
+
// config.xAxis & config.yAxis are required for this widget to work
|
89
|
+
else if (!config.xAxis || !config.yAxis) {
|
90
|
+
setError({message: "config.xAxis or config.yAxis is missing", data: {config: config}});
|
91
|
+
setLoading(false);
|
92
|
+
return;
|
93
|
+
}
|
94
|
+
*/
|
95
|
+
|
96
|
+
// ------------------------------------------------------------------------
|
97
|
+
|
98
|
+
const _query_gene = geneval ? sharedData[geneval] : sharedData.gene;
|
99
|
+
const _query_meta = metaval ? sharedData[metaval] : sharedData.meta;
|
100
|
+
|
101
|
+
const updatePlot = async () => {
|
102
|
+
try {
|
103
|
+
setLoading(true);
|
104
|
+
|
105
|
+
let metadata = {};
|
106
|
+
let genedata = {};
|
107
|
+
|
108
|
+
// if not simpleload, then we wait user to provide the data
|
109
|
+
if (!simpleload && (!_query_meta || _query_meta.length === 0)) {
|
110
|
+
setLoading({
|
111
|
+
message: `Please select ${startup_msg} to load data`,
|
112
|
+
});
|
113
|
+
return;
|
114
|
+
} else if (simpleload) {
|
115
|
+
metadata = await simplefetch(meta, {
|
116
|
+
id: _query_meta,
|
117
|
+
type: 'metadata',
|
118
|
+
debug: debug,
|
119
|
+
});
|
120
|
+
|
121
|
+
genedata = await simplefetch(gene, {
|
122
|
+
id: _query_gene,
|
123
|
+
key: 'gene', //TODO: obtain key from config which can override the type
|
124
|
+
type: 'gene',
|
125
|
+
debug: debug,
|
126
|
+
});
|
127
|
+
} else {
|
128
|
+
//console.log("ScatterBio: _query_meta", _query_meta);
|
129
|
+
//console.log("ScatterBio: nodes_mapping", nodes_mapping);
|
130
|
+
|
131
|
+
// Mapping logic to filter and transform IDs
|
132
|
+
const mapped_nodes = entry_mapping
|
133
|
+
? _query_meta.map((node) => entry_mapping[node])
|
134
|
+
: _query_meta;
|
135
|
+
|
136
|
+
//removed duplicated IDs
|
137
|
+
const _filted_query_meta = [...new Set(mapped_nodes)];
|
138
|
+
|
139
|
+
//console.log("ScatterBio: _filted_query_meta", _filted_query_meta);
|
140
|
+
|
141
|
+
const metadataPromises = _filted_query_meta.map(
|
142
|
+
async (metaId) => {
|
143
|
+
return await simplefetch(meta, {
|
144
|
+
id: metaId,
|
145
|
+
type: 'metadata',
|
146
|
+
debug: debug,
|
147
|
+
});
|
148
|
+
},
|
149
|
+
);
|
150
|
+
|
151
|
+
const metadataResponses = await Promise.all(
|
152
|
+
metadataPromises,
|
153
|
+
);
|
154
|
+
|
155
|
+
// Combine the metadata from different IDs
|
156
|
+
metadata = metadataResponses.reduce(
|
157
|
+
(combined, metadata) => {
|
158
|
+
return {
|
159
|
+
...combined,
|
160
|
+
...metadata,
|
161
|
+
};
|
162
|
+
},
|
163
|
+
{},
|
164
|
+
);
|
165
|
+
|
166
|
+
if (
|
167
|
+
_query_gene &&
|
168
|
+
_query_gene.length > 0 &&
|
169
|
+
!_query_gene.includes('None')
|
170
|
+
) {
|
171
|
+
if (debug)
|
172
|
+
console.log('ScatterBio: _query_gene', _query_gene);
|
173
|
+
|
174
|
+
const genedataPromises = _query_meta.map(
|
175
|
+
async (metaId) => {
|
176
|
+
const _mapped_metaId = entry_mapping
|
177
|
+
? entry_mapping[metaId]
|
178
|
+
: metaId;
|
179
|
+
const _merged_query_gene =
|
180
|
+
_mapped_metaId + '/' + _query_gene;
|
181
|
+
//console.log(_merged_query_gene);
|
182
|
+
return await simplefetch(gene, {
|
183
|
+
id: _merged_query_gene,
|
184
|
+
key: 'gene', //TODO: obtain key from config which can override the type
|
185
|
+
type: 'gene',
|
186
|
+
debug: debug,
|
187
|
+
});
|
188
|
+
},
|
189
|
+
);
|
190
|
+
|
191
|
+
genedata = await Promise.any(genedataPromises);
|
192
|
+
//console.log("ScatterBio: genedata", genedata);
|
193
|
+
}
|
194
|
+
}
|
195
|
+
|
196
|
+
const _nogene = isEmpty(genedata);
|
197
|
+
if (debug) console.log('ScatterBio: _nogene', _nogene);
|
198
|
+
if (debug)
|
199
|
+
console.log(
|
200
|
+
'ScatterBio: genedata',
|
201
|
+
genedata,
|
202
|
+
'metadata',
|
203
|
+
metadata,
|
204
|
+
);
|
205
|
+
|
206
|
+
// ------------------------------ Proccess Data ------------------------------
|
207
|
+
const { colors: __colors = [] } = config;
|
208
|
+
|
209
|
+
const filted_colour = sharedData[colourval]
|
210
|
+
? sharedData[colourval].replace(' ', '_')
|
211
|
+
: colour;
|
212
|
+
|
213
|
+
if (debug)
|
214
|
+
console.log('ScatterBio - Colour by:', filted_colour);
|
215
|
+
|
216
|
+
const __celltypes = [
|
217
|
+
...new Set(metadata[filted_colour]),
|
218
|
+
].filter((celltype) => !exclude_celltype.includes(celltype));
|
219
|
+
|
220
|
+
let { zcolor: visualcolor = [] } = config;
|
221
|
+
let seriescolor = {};
|
222
|
+
|
223
|
+
if (Array.isArray(__colors) && __colors.length > 0) {
|
224
|
+
__colors.forEach((item) => {
|
225
|
+
if (item.color) visualcolor.push(item.color);
|
226
|
+
else visualcolor.push(rcolor());
|
227
|
+
});
|
228
|
+
__celltypes.forEach((celltype, index) => {
|
229
|
+
const cell = __colors.find(
|
230
|
+
(item) => item.name === celltype,
|
231
|
+
);
|
232
|
+
const _color = cell?.color ?? visualcolor[index];
|
233
|
+
const _symbol =
|
234
|
+
__colors.find((item) => item.name === celltype)
|
235
|
+
?.symbol ?? 'circle';
|
236
|
+
seriescolor[celltype] = {
|
237
|
+
color: _color,
|
238
|
+
symbol: _symbol,
|
239
|
+
};
|
240
|
+
});
|
241
|
+
} else {
|
242
|
+
let no_zcolor = false;
|
243
|
+
if (visualcolor.length === 0) {
|
244
|
+
no_zcolor = true;
|
245
|
+
}
|
246
|
+
__celltypes.forEach((celltype, index) => {
|
247
|
+
if (no_zcolor) {
|
248
|
+
visualcolor.push(rcolor());
|
249
|
+
}
|
250
|
+
seriescolor[celltype] = {
|
251
|
+
color: rcolor(),
|
252
|
+
symbol: 'circle',
|
253
|
+
};
|
254
|
+
});
|
255
|
+
}
|
256
|
+
|
257
|
+
// console.log("ScatterBio: axis_mapping", axis_mapping);
|
258
|
+
// console.log("metadata: ", metadata);
|
259
|
+
const { extra } = axis_mapping;
|
260
|
+
|
261
|
+
let processedData;
|
262
|
+
|
263
|
+
try {
|
264
|
+
processedData = metadata['Cell_ID'].map((cellId, index) => {
|
265
|
+
const _expression =
|
266
|
+
genedata[cellId] >= exp_condition
|
267
|
+
? genedata[cellId]
|
268
|
+
: _nogene
|
269
|
+
? 999
|
270
|
+
: -1;
|
271
|
+
const _Cell_Type = metadata[filted_colour] ?? 'Unknown';
|
272
|
+
if (
|
273
|
+
!__celltypes.includes('Unknown') &&
|
274
|
+
__celltypes.length === 0
|
275
|
+
) {
|
276
|
+
__celltypes.push('Unknown');
|
277
|
+
seriescolor['Unknown'] = {
|
278
|
+
color: '#000000',
|
279
|
+
symbol: 'circle',
|
280
|
+
};
|
281
|
+
}
|
282
|
+
// const _UMI = metadata.N_UMI ? Math.round(metadata.N_UMI[index] * 100) / 100 : 0;
|
283
|
+
// round to 2 decimal places
|
284
|
+
const extraProperties = {};
|
285
|
+
for (const property in extra) {
|
286
|
+
if (metadata[extra[property]]) {
|
287
|
+
extraProperties[property] =
|
288
|
+
metadata[extra[property]][index];
|
289
|
+
}
|
290
|
+
}
|
291
|
+
|
292
|
+
const z_index =
|
293
|
+
_expression === -1 || _expression === 999
|
294
|
+
? config.visium_cell_fraction
|
295
|
+
? extraProperties[
|
296
|
+
config.visium_cell_fraction
|
297
|
+
] ?? _expression
|
298
|
+
: _expression
|
299
|
+
: _expression;
|
300
|
+
|
301
|
+
return {
|
302
|
+
name: cellId,
|
303
|
+
value: [
|
304
|
+
metadata[axis_mapping.x][index],
|
305
|
+
metadata[axis_mapping.y][index],
|
306
|
+
z_index,
|
307
|
+
],
|
308
|
+
Type:
|
309
|
+
_Cell_Type === 'Unknown'
|
310
|
+
? 'Unknown'
|
311
|
+
: _Cell_Type[index],
|
312
|
+
Expression: _expression,
|
313
|
+
...extraProperties,
|
314
|
+
};
|
315
|
+
});
|
316
|
+
|
317
|
+
//console.log("ScatterBio: processedData", processedData[0], processedData[1]);
|
318
|
+
} catch (err) {
|
319
|
+
// if TypeError, then set error message to remind user to check the api
|
320
|
+
setError({
|
321
|
+
message:
|
322
|
+
'Please check the API, \n' +
|
323
|
+
'Your API may not return the correct data format. \n' +
|
324
|
+
meta +
|
325
|
+
'\n' +
|
326
|
+
err,
|
327
|
+
});
|
328
|
+
setLoading(false);
|
329
|
+
return;
|
330
|
+
}
|
331
|
+
|
332
|
+
if (debug) console.log(processedData[2], processedData[3]);
|
333
|
+
|
334
|
+
const {
|
335
|
+
dotsize,
|
336
|
+
dotFactor = 2000,
|
337
|
+
minSymbolSize = 2,
|
338
|
+
maxSymbolSize = 10,
|
339
|
+
} = config;
|
340
|
+
const pointCount = processedData.length;
|
341
|
+
|
342
|
+
// Calculate the symbol size based on the number of points
|
343
|
+
let symbolSize;
|
344
|
+
if (dotsize === 'auto') {
|
345
|
+
const sizeFactor = Math.max(
|
346
|
+
1 - (pointCount - 1000) / dotFactor,
|
347
|
+
0.2,
|
348
|
+
);
|
349
|
+
symbolSize =
|
350
|
+
(maxSymbolSize - minSymbolSize) * sizeFactor +
|
351
|
+
minSymbolSize;
|
352
|
+
//console.log(pointCount, symbolSize);
|
353
|
+
} else {
|
354
|
+
symbolSize = typeof dotsize === 'number' ? dotsize : 5;
|
355
|
+
}
|
356
|
+
|
357
|
+
// Create a series for each unique cell type
|
358
|
+
const series = __celltypes.map((cellType) => {
|
359
|
+
return {
|
360
|
+
name: cellType,
|
361
|
+
type: 'scatter',
|
362
|
+
data: processedData.filter(
|
363
|
+
(data) =>
|
364
|
+
data.Type === cellType && data.Expression > -1,
|
365
|
+
),
|
366
|
+
itemStyle: {
|
367
|
+
color: seriescolor[cellType].color,
|
368
|
+
},
|
369
|
+
symbol: seriescolor[cellType].symbol,
|
370
|
+
symbolSize: symbolSize,
|
371
|
+
};
|
372
|
+
});
|
373
|
+
|
374
|
+
//console.log("series: ", series);
|
375
|
+
|
376
|
+
const zshift =
|
377
|
+
-config.zshift || -Math.max(config.chartWidth * 0.02, 10);
|
378
|
+
|
379
|
+
const auto_zmax = Math.max(
|
380
|
+
...processedData.map((item) => item.value[2]),
|
381
|
+
);
|
382
|
+
const auto_zmin = Math.min(
|
383
|
+
...processedData.map((item) => item.value[2]),
|
384
|
+
);
|
385
|
+
|
386
|
+
//console.log("ScatterBio: auto_zmax", auto_zmax, "auto_zmin", auto_zmin, config.visium_cell_fraction);
|
387
|
+
|
388
|
+
let visualMap =
|
389
|
+
_nogene && !config.visium_cell_fraction
|
390
|
+
? []
|
391
|
+
: {
|
392
|
+
min: config.zmin ?? auto_zmin,
|
393
|
+
max: config.zmax ?? auto_zmax,
|
394
|
+
dimension: 2,
|
395
|
+
orient: 'vertical',
|
396
|
+
top: 'center',
|
397
|
+
right: zshift,
|
398
|
+
text: config.labels
|
399
|
+
? config.labels.z ?? ''
|
400
|
+
: '',
|
401
|
+
textGap: 10,
|
402
|
+
calculable: true,
|
403
|
+
inRange: {
|
404
|
+
color: visualcolor,
|
405
|
+
},
|
406
|
+
textStyle: {
|
407
|
+
writingMode: 'vertical-lr',
|
408
|
+
},
|
409
|
+
...config.visualMap,
|
410
|
+
};
|
411
|
+
|
412
|
+
const { legend: _config_legend = {} } = config;
|
413
|
+
const legendItemCount = __celltypes.length;
|
414
|
+
const { type: _legendType, ...__config_legend } =
|
415
|
+
_config_legend;
|
416
|
+
let legendType = 'scroll';
|
417
|
+
if (_legendType === 'auto') {
|
418
|
+
legendType = legendItemCount > 10 ? 'scroll' : 'plain';
|
419
|
+
}
|
420
|
+
|
421
|
+
// console.log("__celltypes:", __celltypes);
|
422
|
+
// Sort __celltypes by extracting and comparing the numerical part
|
423
|
+
const sortedCellTypes = __celltypes.slice().sort((a, b) => {
|
424
|
+
// Handle null, undefined, or other unexpected values
|
425
|
+
if (a === null || b === null) {
|
426
|
+
return 0; // Handle the case as per your requirement
|
427
|
+
}
|
428
|
+
const numA = parseInt(a.substring(1), 10);
|
429
|
+
const numB = parseInt(b.substring(1), 10);
|
430
|
+
return numA - numB;
|
431
|
+
});
|
432
|
+
|
433
|
+
// legend
|
434
|
+
const _legend =
|
435
|
+
_config_legend === false
|
436
|
+
? false
|
437
|
+
: {
|
438
|
+
data: sortedCellTypes, //__celltypes,
|
439
|
+
orient: 'horizontal',
|
440
|
+
top: 5, // "top" | "bottom" | "center"
|
441
|
+
right: 'center', // "top" | "bottom" | "center",
|
442
|
+
...__config_legend,
|
443
|
+
type: legendType,
|
444
|
+
};
|
445
|
+
//console.log("ScatterBio: legend", _legend);
|
446
|
+
|
447
|
+
const gridSettings = [
|
448
|
+
{
|
449
|
+
top: '5%',
|
450
|
+
bottom: '5%',
|
451
|
+
left: '5%',
|
452
|
+
right: '5%',
|
453
|
+
...(config.grid || {}), // Merge with config.grid if available, otherwise an empty object
|
454
|
+
},
|
455
|
+
];
|
456
|
+
|
457
|
+
const title = config.title ?? '';
|
458
|
+
// Update props for Scatter
|
459
|
+
|
460
|
+
let scatterBioProps = {
|
461
|
+
...props,
|
462
|
+
config: {
|
463
|
+
chartWidth: 800,
|
464
|
+
chartHeight: 600,
|
465
|
+
...config,
|
466
|
+
series: series,
|
467
|
+
legend: _legend,
|
468
|
+
visualMap: visualMap,
|
469
|
+
title: _nogene
|
470
|
+
? title
|
471
|
+
: {
|
472
|
+
...title,
|
473
|
+
text: _query_gene,
|
474
|
+
},
|
475
|
+
// Use formatter from config if provided, otherwise use the default formatter
|
476
|
+
formatter: config.formatter
|
477
|
+
? (params) => config.formatter(params)
|
478
|
+
: (params) => {
|
479
|
+
return `
|
480
|
+
${params.name}
|
481
|
+
<br/> Type: ${params.data.Type}
|
482
|
+
<br/> UMI: ${params.data.UMI ?? 0}
|
483
|
+
`;
|
484
|
+
},
|
485
|
+
grid: gridSettings,
|
486
|
+
},
|
487
|
+
};
|
488
|
+
|
489
|
+
/*
|
490
|
+
const imageDom = new Image();
|
491
|
+
imageDom.src = './resources/YY0-44_image.png';
|
492
|
+
scatterBioProps.config.backgroundColor = {
|
493
|
+
image: imageDom,
|
494
|
+
repeat: 'no-repeat' // or 'repeat', 'repeat-x', 'repeat-y' depending on your needs
|
495
|
+
};
|
496
|
+
*/
|
497
|
+
|
498
|
+
//console.log("ScatterBio: scatterBioProps.config", scatterBioProps.config);
|
499
|
+
setUpdatedProps(scatterBioProps);
|
500
|
+
|
501
|
+
setLoading(false);
|
502
|
+
} catch (err) {
|
503
|
+
console.log(err);
|
504
|
+
if (err.name === 'TypeError' && simpleload)
|
505
|
+
setError({
|
506
|
+
message: `invalid URL for metadata/gene,\nplease check your URL or set simpleload to false`,
|
507
|
+
});
|
508
|
+
else setError(err);
|
509
|
+
setLoading(false);
|
510
|
+
}
|
511
|
+
};
|
512
|
+
updatePlot();
|
513
|
+
}, [props, sharedData, debug]);
|
514
|
+
|
515
|
+
if (reset) {
|
516
|
+
reset(props.id, (vals) => {
|
517
|
+
// logic to reset the data
|
518
|
+
const { gene: rec_gene = ' ' } = vals;
|
519
|
+
console.log('received data', sharedData[rec_gene]);
|
520
|
+
});
|
521
|
+
}
|
522
|
+
|
523
|
+
if (loading.message) {
|
524
|
+
return (
|
525
|
+
<div style={{ marginTop: '10px', ...style }}>{loading.message}</div>
|
526
|
+
);
|
527
|
+
} else if (loading) {
|
528
|
+
// Render a loading animation
|
529
|
+
return (
|
530
|
+
<div
|
531
|
+
style={{
|
532
|
+
display: 'flex',
|
533
|
+
flexDirection: 'column',
|
534
|
+
alignItems: 'center',
|
535
|
+
justifyContent: 'center',
|
536
|
+
height: '100%',
|
537
|
+
...style,
|
538
|
+
}}>
|
539
|
+
<CircularProgress color='primary' />
|
540
|
+
</div>
|
541
|
+
);
|
542
|
+
}
|
543
|
+
|
544
|
+
if (error) {
|
545
|
+
const errorLines = error.message.split('\n');
|
546
|
+
const errorElements = errorLines.map((line, index) => (
|
547
|
+
<p
|
548
|
+
key={index}
|
549
|
+
style={{ color: 'red' }}>
|
550
|
+
{line}
|
551
|
+
</p>
|
552
|
+
));
|
553
|
+
return (
|
554
|
+
<div style={style}>
|
555
|
+
An error has occurred: <br />
|
556
|
+
<br /> {errorElements}
|
557
|
+
</div>
|
558
|
+
);
|
559
|
+
}
|
560
|
+
// Only render Scatter when not loading
|
561
|
+
return (
|
562
|
+
<>
|
563
|
+
<Scatter
|
564
|
+
key={updatedProps.id + '.inherent'}
|
565
|
+
props={updatedProps}
|
566
|
+
style={style}
|
567
|
+
/>
|
568
|
+
</>
|
569
|
+
);
|
570
|
+
}
|
571
|
+
|
572
|
+
export default ScatterBio;
|