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,587 +1,579 @@
|
|
1
|
-
/*
|
2
|
-
* @Author : Lihao leolihao@arizona.edu
|
3
|
-
* @Date : 2023-11-12 17:35:02
|
4
|
-
* @FilePath : /
|
5
|
-
* @Description :
|
6
|
-
* Copyright (c) 2023 by Lihao (leolihao@arizona.edu), All Rights Reserved.
|
7
|
-
*/
|
8
|
-
import simplefetch from '../fetch/fetch';
|
9
|
-
|
10
|
-
var cached_color = {};
|
11
|
-
export function rcolor(seed = 0) {
|
12
|
-
const maxHexValue = 16777215;
|
13
|
-
const randomSeed = seed || Math.random();
|
14
|
-
const randomColor = Math.floor(randomSeed * maxHexValue).toString(16);
|
15
|
-
const color = '#' + randomColor.padStart(6, '0');
|
16
|
-
|
17
|
-
return color;
|
18
|
-
}
|
19
|
-
|
20
|
-
export function isEmpty(obj) {
|
21
|
-
return Object.keys(obj).length === 0 && obj.constructor === Object;
|
22
|
-
}
|
23
|
-
|
24
|
-
export const parseConfig = (props) => {
|
25
|
-
const { echart } = props;
|
26
|
-
var option = {
|
27
|
-
series: [
|
28
|
-
{
|
29
|
-
data: null, // Use the fetched data here
|
30
|
-
type: 'scatter',
|
31
|
-
areaStyle: {},
|
32
|
-
},
|
33
|
-
],
|
34
|
-
toolbox: {
|
35
|
-
feature: {
|
36
|
-
saveAsImage: {
|
37
|
-
icon: `path://M30 20.75c-0.69 0.001-1.249 0.56-1.25 1.25v6.75h-25.5v-6.75c0-0.69-0.56-1.25-1.25-1.25s-1.25 0.56-1.25 1.25v0 8c0 0.69 0.56 1.25 1.25 1.25h28c0.69-0.001 1.249-0.56 1.25-1.25v-8c-0.001-0.69-0.56-1.249-1.25-1.25h-0zM15.116 24.885c0.012 0.012 0.029 0.016 0.041 0.027 0.103 0.099 0.223 0.18 0.356 0.239l0.008 0.003 0.001 0c0.141 0.060 0.306 0.095 0.478 0.095 0.345 0 0.657-0.139 0.883-0.365l5.001-5c0.226-0.226 0.366-0.539 0.366-0.884 0-0.691-0.56-1.251-1.251-1.251-0.345 0-0.658 0.14-0.884 0.366l-2.865 2.867v-18.982c0-0.69-0.56-1.25-1.25-1.25s-1.25 0.56-1.25 1.25v0 18.981l-2.866-2.866c-0.226-0.226-0.539-0.366-0.884-0.366-0.691 0-1.251 0.56-1.251 1.251 0 0.346 0.14 0.658 0.367 0.885v0z`,
|
38
|
-
},
|
39
|
-
},
|
40
|
-
},
|
41
|
-
dataZoom: [
|
42
|
-
{
|
43
|
-
type: 'inside',
|
44
|
-
xAxisIndex: 0,
|
45
|
-
filterMode: 'filter',
|
46
|
-
},
|
47
|
-
{
|
48
|
-
type: 'inside',
|
49
|
-
yAxisIndex: 0,
|
50
|
-
filterMode: 'filter',
|
51
|
-
orient: 'vertical',
|
52
|
-
},
|
53
|
-
],
|
54
|
-
tooltip: {
|
55
|
-
trigger: 'item',
|
56
|
-
axisPointer: {
|
57
|
-
type: 'cross',
|
58
|
-
},
|
59
|
-
formatter: (params) => {
|
60
|
-
let express =
|
61
|
-
Math.round(params.data.Expression * 10000) / 10000;
|
62
|
-
return `
|
63
|
-
<div style="text-align: center;">
|
64
|
-
${params.data.Cell_ID}
|
65
|
-
<br/> Type: <strong>${params.data.Cell_Type} </strong>
|
66
|
-
<br/> Stage: <strong>${params.data.Stage}</strong>
|
67
|
-
<br/> #UMI: <strong>${params.data.UMI}</strong>
|
68
|
-
<br/> #Gene: <strong>${params.data.Gene}</strong>
|
69
|
-
<br/> MT%: <strong>${params.data.MT}</strong>
|
70
|
-
${
|
71
|
-
express != null
|
72
|
-
? '<br/> Expression: <strong>' +
|
73
|
-
express +
|
74
|
-
'</strong>'
|
75
|
-
: ''
|
76
|
-
}
|
77
|
-
</div>
|
78
|
-
`;
|
79
|
-
},
|
80
|
-
},
|
81
|
-
title: {
|
82
|
-
textStyle: {
|
83
|
-
fontSize: 20,
|
84
|
-
},
|
85
|
-
left: 'center',
|
86
|
-
top: 0,
|
87
|
-
},
|
88
|
-
animation: false,
|
89
|
-
legend: {
|
90
|
-
textStyle: {
|
91
|
-
fontSize: 18,
|
92
|
-
},
|
93
|
-
orient: 'horizontal',
|
94
|
-
right: 'center', // "top" | "bottom" | "center"
|
95
|
-
itemWidth: 20,
|
96
|
-
width: 600,
|
97
|
-
top: 20,
|
98
|
-
},
|
99
|
-
|
100
|
-
xAxis: {
|
101
|
-
name: 'UMAP 1',
|
102
|
-
type: 'value',
|
103
|
-
nameLocation: 'middle',
|
104
|
-
nameGap: 10,
|
105
|
-
nameTextStyle: {
|
106
|
-
fontSize: 18, // Set font size
|
107
|
-
fontWeight: 'bold', // Set font weight to bold
|
108
|
-
},
|
109
|
-
axisLine: { show: false },
|
110
|
-
axisTick: { show: false },
|
111
|
-
splitLine: { show: false },
|
112
|
-
axisLabel: { show: false },
|
113
|
-
//min: -20,
|
114
|
-
//max: 20,
|
115
|
-
},
|
116
|
-
yAxis: {
|
117
|
-
name: 'UMAP 2',
|
118
|
-
type: 'value',
|
119
|
-
nameLocation: 'middle',
|
120
|
-
nameGap: 10,
|
121
|
-
nameTextStyle: {
|
122
|
-
fontSize: 18, // Set font size
|
123
|
-
fontWeight: 'bold', // Set font weight to bold
|
124
|
-
},
|
125
|
-
//min: -20,
|
126
|
-
//max: 20,
|
127
|
-
axisLine: { show: false },
|
128
|
-
axisTick: { show: false },
|
129
|
-
splitLine: { show: false },
|
130
|
-
axisLabel: { show: false },
|
131
|
-
},
|
132
|
-
grid: {
|
133
|
-
top: '20%',
|
134
|
-
bottom: '12%',
|
135
|
-
left: '10%',
|
136
|
-
right: '10%',
|
137
|
-
},
|
138
|
-
...echart,
|
139
|
-
};
|
140
|
-
|
141
|
-
return option;
|
142
|
-
};
|
143
|
-
|
144
|
-
export const handleSimplyLoad = (simpleload) => {
|
145
|
-
console.log(`handleSimplyLoad: `, simpleload);
|
146
|
-
// fetch data direcly from config
|
147
|
-
// fetch data from simply api
|
148
|
-
};
|
149
|
-
|
150
|
-
export const handleAPI = async (config, sharedData, bbox = false) => {
|
151
|
-
let fetched_dat = {};
|
152
|
-
let dependencies = {};
|
153
|
-
|
154
|
-
// Get corresponding api and its attributes
|
155
|
-
for (const [item, attr] of Object.entries(config.api)) {
|
156
|
-
const { href, val, dep = null } = attr;
|
157
|
-
let id = sharedData[val];
|
158
|
-
|
159
|
-
if (typeof id === 'object' && config.mapping && config.mapping.api) {
|
160
|
-
id = [...new Set(id.map((i) => config.mapping.api[i] ?? i))];
|
161
|
-
id = id[0] ? id : null;
|
162
|
-
} else if (config.mapping && config.mapping.api)
|
163
|
-
id = config.mapping.api[id] ?? id;
|
164
|
-
|
165
|
-
//console.log(`item: ${item}, href: ${href}, val: ${val}, id: ${id}`);
|
166
|
-
dependencies[item] = id;
|
167
|
-
|
168
|
-
if (id && typeof id === 'object') {
|
169
|
-
//console.log(`id for ${item} : `, id);
|
170
|
-
const promises = id.map(async (i) => {
|
171
|
-
//console.log(`request for ${item} : `, i);
|
172
|
-
return await simplefetch(href, {
|
173
|
-
id: i,
|
174
|
-
type: item,
|
175
|
-
debug: false,
|
176
|
-
bbox: bbox,
|
177
|
-
});
|
178
|
-
});
|
179
|
-
//console.log(`promises for ${item} : `, promises);
|
180
|
-
|
181
|
-
const resp = await Promise.all(promises);
|
182
|
-
const result = resp.reduce((acc, cur) => {
|
183
|
-
return {
|
184
|
-
...acc,
|
185
|
-
...cur,
|
186
|
-
};
|
187
|
-
}, {});
|
188
|
-
//console.log(`result for ${item} : `, result);
|
189
|
-
fetched_dat[item] = result;
|
190
|
-
} else if (id) {
|
191
|
-
//console.log(`
|
192
|
-
|
193
|
-
const result = await simplefetch(href, {
|
194
|
-
id: dependencies[dep] + '/' + id,
|
195
|
-
type: item,
|
196
|
-
debug: sharedData.debug,
|
197
|
-
bbox: bbox,
|
198
|
-
});
|
199
|
-
fetched_dat[item] = result;
|
200
|
-
dependencies[item] = id;
|
201
|
-
}
|
202
|
-
}
|
203
|
-
|
204
|
-
if (isEmpty(fetched_dat)) {
|
205
|
-
throw new Error(
|
206
|
-
config.startup_msg ?? 'Please select metadata to load data',
|
207
|
-
);
|
208
|
-
} else {
|
209
|
-
fetched_dat.fetched_ID = dependencies;
|
210
|
-
return fetched_dat;
|
211
|
-
}
|
212
|
-
|
213
|
-
//wait for user to provide values for api
|
214
|
-
};
|
215
|
-
|
216
|
-
// Function to output the axis values
|
217
|
-
export const outputAxisValues = (myChart) => {
|
218
|
-
if (!myChart) {
|
219
|
-
return;
|
220
|
-
}
|
221
|
-
|
222
|
-
const yAxis = myChart.getModel().getComponent('yAxis').axis;
|
223
|
-
const yAxisMin = yAxis.scale.getExtent()[0];
|
224
|
-
const yAxisMax = yAxis.scale.getExtent()[1];
|
225
|
-
|
226
|
-
const xAxis = myChart.getModel().getComponent('xAxis').axis;
|
227
|
-
const XAxisMin = Math.round(xAxis.scale.getExtent()[0]);
|
228
|
-
const XAxisMax = Math.round(xAxis.scale.getExtent()[1]);
|
229
|
-
|
230
|
-
//console.log(
|
231
|
-
// `${msgPrefix} X Axis Display Range: [${XAxisMin}, ${XAxisMax}]`,
|
232
|
-
// `${msgPrefix} Y Axis Display Range: [${yAxisMin}, ${yAxisMax}]`,
|
233
|
-
//);
|
234
|
-
|
235
|
-
return {
|
236
|
-
xMin: XAxisMin,
|
237
|
-
yMin: yAxisMin,
|
238
|
-
xMax: XAxisMax,
|
239
|
-
yMax: yAxisMax,
|
240
|
-
};
|
241
|
-
};
|
242
|
-
|
243
|
-
export const validateConfig = (config) => {
|
244
|
-
if (!config) {
|
245
|
-
throw new Error('config is required, minial config is {}');
|
246
|
-
}
|
247
|
-
// api or simpleload is required
|
248
|
-
if (!config.api && !config.simpleload) {
|
249
|
-
throw new Error('config.api or config.simpleload is required');
|
250
|
-
}
|
251
|
-
};
|
252
|
-
|
253
|
-
export const parseData = (fetched, config, sharedData) => {
|
254
|
-
if (!fetched) {
|
255
|
-
throw new Error('fetched data is not valid: ' + fetched);
|
256
|
-
}
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
const
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
//
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
//console.log(
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
),
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
if (typeof dotsize === '
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
return
|
313
|
-
}
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
const
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
mapping
|
373
|
-
|
374
|
-
const
|
375
|
-
//
|
376
|
-
|
377
|
-
|
378
|
-
//
|
379
|
-
|
380
|
-
//
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
)
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
option.series =
|
497
|
-
}
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
legend.
|
539
|
-
legend.
|
540
|
-
legend.
|
541
|
-
legend
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
visualMap.
|
568
|
-
visualMap.
|
569
|
-
visualMap.
|
570
|
-
visualMap.
|
571
|
-
|
572
|
-
|
573
|
-
visualMap.
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
writingMode: 'vertical-lr',
|
581
|
-
};
|
582
|
-
|
583
|
-
visualMap = { ...visualMap, ...visualmap };
|
584
|
-
|
585
|
-
//console.log(`handlesVisualMap: `, visualMap);
|
586
|
-
return visualMap;
|
587
|
-
};
|
1
|
+
/*
|
2
|
+
* @Author : Lihao leolihao@arizona.edu
|
3
|
+
* @Date : 2023-11-12 17:35:02
|
4
|
+
* @FilePath : /visualifyjs/src/core/components/parser.scatterBio.js
|
5
|
+
* @Description :
|
6
|
+
* Copyright (c) 2023 by Lihao (leolihao@arizona.edu), All Rights Reserved.
|
7
|
+
*/
|
8
|
+
import simplefetch from '../fetch/fetch';
|
9
|
+
|
10
|
+
var cached_color = {};
|
11
|
+
export function rcolor(seed = 0) {
|
12
|
+
const maxHexValue = 16777215;
|
13
|
+
const randomSeed = seed || Math.random();
|
14
|
+
const randomColor = Math.floor(randomSeed * maxHexValue).toString(16);
|
15
|
+
const color = '#' + randomColor.padStart(6, '0');
|
16
|
+
|
17
|
+
return color;
|
18
|
+
}
|
19
|
+
|
20
|
+
export function isEmpty(obj) {
|
21
|
+
return Object.keys(obj).length === 0 && obj.constructor === Object;
|
22
|
+
}
|
23
|
+
|
24
|
+
export const parseConfig = (props) => {
|
25
|
+
const { echart } = props;
|
26
|
+
var option = {
|
27
|
+
series: [
|
28
|
+
{
|
29
|
+
data: null, // Use the fetched data here
|
30
|
+
type: 'scatter',
|
31
|
+
areaStyle: {},
|
32
|
+
},
|
33
|
+
],
|
34
|
+
toolbox: {
|
35
|
+
feature: {
|
36
|
+
saveAsImage: {
|
37
|
+
icon: `path://M30 20.75c-0.69 0.001-1.249 0.56-1.25 1.25v6.75h-25.5v-6.75c0-0.69-0.56-1.25-1.25-1.25s-1.25 0.56-1.25 1.25v0 8c0 0.69 0.56 1.25 1.25 1.25h28c0.69-0.001 1.249-0.56 1.25-1.25v-8c-0.001-0.69-0.56-1.249-1.25-1.25h-0zM15.116 24.885c0.012 0.012 0.029 0.016 0.041 0.027 0.103 0.099 0.223 0.18 0.356 0.239l0.008 0.003 0.001 0c0.141 0.060 0.306 0.095 0.478 0.095 0.345 0 0.657-0.139 0.883-0.365l5.001-5c0.226-0.226 0.366-0.539 0.366-0.884 0-0.691-0.56-1.251-1.251-1.251-0.345 0-0.658 0.14-0.884 0.366l-2.865 2.867v-18.982c0-0.69-0.56-1.25-1.25-1.25s-1.25 0.56-1.25 1.25v0 18.981l-2.866-2.866c-0.226-0.226-0.539-0.366-0.884-0.366-0.691 0-1.251 0.56-1.251 1.251 0 0.346 0.14 0.658 0.367 0.885v0z`,
|
38
|
+
},
|
39
|
+
},
|
40
|
+
},
|
41
|
+
dataZoom: [
|
42
|
+
{
|
43
|
+
type: 'inside',
|
44
|
+
xAxisIndex: 0,
|
45
|
+
filterMode: 'filter',
|
46
|
+
},
|
47
|
+
{
|
48
|
+
type: 'inside',
|
49
|
+
yAxisIndex: 0,
|
50
|
+
filterMode: 'filter',
|
51
|
+
orient: 'vertical',
|
52
|
+
},
|
53
|
+
],
|
54
|
+
tooltip: {
|
55
|
+
trigger: 'item',
|
56
|
+
axisPointer: {
|
57
|
+
type: 'cross',
|
58
|
+
},
|
59
|
+
formatter: (params) => {
|
60
|
+
let express =
|
61
|
+
Math.round(params.data.Expression * 10000) / 10000;
|
62
|
+
return `
|
63
|
+
<div style="text-align: center;">
|
64
|
+
${params.data.Cell_ID}
|
65
|
+
<br/> Type: <strong>${params.data.Cell_Type} </strong>
|
66
|
+
<br/> Stage: <strong>${params.data.Stage}</strong>
|
67
|
+
<br/> #UMI: <strong>${params.data.UMI}</strong>
|
68
|
+
<br/> #Gene: <strong>${params.data.Gene}</strong>
|
69
|
+
<br/> MT%: <strong>${params.data.MT}</strong>
|
70
|
+
${
|
71
|
+
express != null
|
72
|
+
? '<br/> Expression: <strong>' +
|
73
|
+
express +
|
74
|
+
'</strong>'
|
75
|
+
: ''
|
76
|
+
}
|
77
|
+
</div>
|
78
|
+
`;
|
79
|
+
},
|
80
|
+
},
|
81
|
+
title: {
|
82
|
+
textStyle: {
|
83
|
+
fontSize: 20,
|
84
|
+
},
|
85
|
+
left: 'center',
|
86
|
+
top: 0,
|
87
|
+
},
|
88
|
+
animation: false,
|
89
|
+
legend: {
|
90
|
+
textStyle: {
|
91
|
+
fontSize: 18,
|
92
|
+
},
|
93
|
+
orient: 'horizontal',
|
94
|
+
right: 'center', // "top" | "bottom" | "center"
|
95
|
+
itemWidth: 20,
|
96
|
+
width: 600,
|
97
|
+
top: 20,
|
98
|
+
},
|
99
|
+
|
100
|
+
xAxis: {
|
101
|
+
name: 'UMAP 1',
|
102
|
+
type: 'value',
|
103
|
+
nameLocation: 'middle',
|
104
|
+
nameGap: 10,
|
105
|
+
nameTextStyle: {
|
106
|
+
fontSize: 18, // Set font size
|
107
|
+
fontWeight: 'bold', // Set font weight to bold
|
108
|
+
},
|
109
|
+
axisLine: { show: false },
|
110
|
+
axisTick: { show: false },
|
111
|
+
splitLine: { show: false },
|
112
|
+
axisLabel: { show: false },
|
113
|
+
//min: -20,
|
114
|
+
//max: 20,
|
115
|
+
},
|
116
|
+
yAxis: {
|
117
|
+
name: 'UMAP 2',
|
118
|
+
type: 'value',
|
119
|
+
nameLocation: 'middle',
|
120
|
+
nameGap: 10,
|
121
|
+
nameTextStyle: {
|
122
|
+
fontSize: 18, // Set font size
|
123
|
+
fontWeight: 'bold', // Set font weight to bold
|
124
|
+
},
|
125
|
+
//min: -20,
|
126
|
+
//max: 20,
|
127
|
+
axisLine: { show: false },
|
128
|
+
axisTick: { show: false },
|
129
|
+
splitLine: { show: false },
|
130
|
+
axisLabel: { show: false },
|
131
|
+
},
|
132
|
+
grid: {
|
133
|
+
top: '20%',
|
134
|
+
bottom: '12%',
|
135
|
+
left: '10%',
|
136
|
+
right: '10%',
|
137
|
+
},
|
138
|
+
...echart,
|
139
|
+
};
|
140
|
+
|
141
|
+
return option;
|
142
|
+
};
|
143
|
+
|
144
|
+
export const handleSimplyLoad = (simpleload) => {
|
145
|
+
console.log(`handleSimplyLoad: `, simpleload);
|
146
|
+
// fetch data direcly from config
|
147
|
+
// fetch data from simply api
|
148
|
+
};
|
149
|
+
|
150
|
+
export const handleAPI = async (config, sharedData, bbox = false) => {
|
151
|
+
let fetched_dat = {};
|
152
|
+
let dependencies = {};
|
153
|
+
|
154
|
+
// Get corresponding api and its attributes
|
155
|
+
for (const [item, attr] of Object.entries(config.api)) {
|
156
|
+
const { href, val, dep = null } = attr;
|
157
|
+
let id = sharedData[val];
|
158
|
+
|
159
|
+
if (typeof id === 'object' && config.mapping && config.mapping.api) {
|
160
|
+
id = [...new Set(id.map((i) => config.mapping.api[i] ?? i))];
|
161
|
+
id = id[0] ? id : null;
|
162
|
+
} else if (config.mapping && config.mapping.api)
|
163
|
+
id = config.mapping.api[id] ?? id;
|
164
|
+
|
165
|
+
//console.log(`item: ${item}, href: ${href}, val: ${val}, id: ${id}`);
|
166
|
+
dependencies[item] = id;
|
167
|
+
|
168
|
+
if (id && typeof id === 'object') {
|
169
|
+
//console.log(`id for ${item} : `, id);
|
170
|
+
const promises = id.map(async (i) => {
|
171
|
+
//console.log(`request for ${item} : `, i);
|
172
|
+
return await simplefetch(href, {
|
173
|
+
id: i,
|
174
|
+
type: item,
|
175
|
+
debug: false,
|
176
|
+
bbox: bbox,
|
177
|
+
});
|
178
|
+
});
|
179
|
+
//console.log(`promises for ${item} : `, promises);
|
180
|
+
|
181
|
+
const resp = await Promise.all(promises);
|
182
|
+
const result = resp.reduce((acc, cur) => {
|
183
|
+
return {
|
184
|
+
...acc,
|
185
|
+
...cur,
|
186
|
+
};
|
187
|
+
}, {});
|
188
|
+
//console.log(`result for ${item} : `, result);
|
189
|
+
fetched_dat[item] = result;
|
190
|
+
} else if (id) {
|
191
|
+
//console.log(`id for ${item} : `, id, dependencies);
|
192
|
+
|
193
|
+
const result = await simplefetch(href, {
|
194
|
+
id: dependencies[dep] + '/' + id,
|
195
|
+
type: item,
|
196
|
+
debug: sharedData.debug,
|
197
|
+
bbox: bbox,
|
198
|
+
});
|
199
|
+
fetched_dat[item] = result;
|
200
|
+
dependencies[item] = id;
|
201
|
+
}
|
202
|
+
}
|
203
|
+
|
204
|
+
if (isEmpty(fetched_dat)) {
|
205
|
+
throw new Error(
|
206
|
+
config.startup_msg ?? 'Please select metadata to load data',
|
207
|
+
);
|
208
|
+
} else {
|
209
|
+
fetched_dat.fetched_ID = dependencies;
|
210
|
+
return fetched_dat;
|
211
|
+
}
|
212
|
+
|
213
|
+
//wait for user to provide values for api
|
214
|
+
};
|
215
|
+
|
216
|
+
// Function to output the axis values
|
217
|
+
export const outputAxisValues = (myChart) => {
|
218
|
+
if (!myChart) {
|
219
|
+
return;
|
220
|
+
}
|
221
|
+
|
222
|
+
const yAxis = myChart.getModel().getComponent('yAxis').axis;
|
223
|
+
const yAxisMin = yAxis.scale.getExtent()[0];
|
224
|
+
const yAxisMax = yAxis.scale.getExtent()[1];
|
225
|
+
|
226
|
+
const xAxis = myChart.getModel().getComponent('xAxis').axis;
|
227
|
+
const XAxisMin = Math.round(xAxis.scale.getExtent()[0]);
|
228
|
+
const XAxisMax = Math.round(xAxis.scale.getExtent()[1]);
|
229
|
+
|
230
|
+
//console.log(
|
231
|
+
// `${msgPrefix} X Axis Display Range: [${XAxisMin}, ${XAxisMax}]`,
|
232
|
+
// `${msgPrefix} Y Axis Display Range: [${yAxisMin}, ${yAxisMax}]`,
|
233
|
+
//);
|
234
|
+
|
235
|
+
return {
|
236
|
+
xMin: XAxisMin,
|
237
|
+
yMin: yAxisMin,
|
238
|
+
xMax: XAxisMax,
|
239
|
+
yMax: yAxisMax,
|
240
|
+
};
|
241
|
+
};
|
242
|
+
|
243
|
+
export const validateConfig = (config) => {
|
244
|
+
if (!config) {
|
245
|
+
throw new Error('config is required, minial config is {}');
|
246
|
+
}
|
247
|
+
// api or simpleload is required
|
248
|
+
if (!config.api && !config.simpleload) {
|
249
|
+
throw new Error('config.api or config.simpleload is required');
|
250
|
+
}
|
251
|
+
};
|
252
|
+
|
253
|
+
export const parseData = (fetched, config, sharedData) => {
|
254
|
+
if (!fetched) {
|
255
|
+
throw new Error('fetched data is not valid: ' + fetched);
|
256
|
+
}
|
257
|
+
|
258
|
+
const metadata = fetched.metadata;
|
259
|
+
const genes = fetched.gene;
|
260
|
+
|
261
|
+
const {
|
262
|
+
colors: givenColors = [],
|
263
|
+
exclusion = [],
|
264
|
+
dotsize = 'auto',
|
265
|
+
} = config;
|
266
|
+
//console.log(`givenColors: `, givenColors);
|
267
|
+
let colourby = config.colourby;
|
268
|
+
|
269
|
+
if (colourby && sharedData[config.colourby]) {
|
270
|
+
colourby = sharedData[config.colourby].replace(' ', '_');
|
271
|
+
}
|
272
|
+
//console.log(`colourby: `, colourby);
|
273
|
+
|
274
|
+
// Exclude items if given by config
|
275
|
+
const category = [...new Set(metadata[colourby])].filter(
|
276
|
+
(_category) => !exclusion.includes(_category),
|
277
|
+
);
|
278
|
+
//console.log(`category: `, category);
|
279
|
+
|
280
|
+
const seriesAttr = processSeriesAttr(givenColors, category);
|
281
|
+
//console.log(`seriescolor: `, seriesAttr);
|
282
|
+
|
283
|
+
//console.log(fetched);
|
284
|
+
try {
|
285
|
+
return {
|
286
|
+
series: processData(
|
287
|
+
metadata,
|
288
|
+
genes,
|
289
|
+
colourby,
|
290
|
+
seriesAttr,
|
291
|
+
dotsize,
|
292
|
+
config?.mapping?.axis,
|
293
|
+
),
|
294
|
+
legend: handleLegend(category),
|
295
|
+
visualMap: handleVisualMap(genes),
|
296
|
+
title: fetched?.fetched_ID?.gene ?? '',
|
297
|
+
};
|
298
|
+
} catch (error) {
|
299
|
+
console.log(error);
|
300
|
+
throw new Error('Failed to parse data');
|
301
|
+
}
|
302
|
+
};
|
303
|
+
|
304
|
+
const processSymbolSize = (dotsize, totalcount) => {
|
305
|
+
if (typeof dotsize === 'number') {
|
306
|
+
return dotsize;
|
307
|
+
} else if (dotsize === 'auto' || typeof dotsize === 'object') {
|
308
|
+
const { dotFactor = 500, min = 2, max = 10 } = dotsize;
|
309
|
+
const sizeFactor = Math.max(1 - totalcount / dotFactor, 0.2);
|
310
|
+
return (max - min) * sizeFactor + min;
|
311
|
+
} else {
|
312
|
+
return 3;
|
313
|
+
}
|
314
|
+
};
|
315
|
+
|
316
|
+
const processSeriesAttr = (givenColors, category) => {
|
317
|
+
var __colors = givenColors;
|
318
|
+
let visualcolor = [];
|
319
|
+
let seriescolor = {};
|
320
|
+
|
321
|
+
if (!isEmpty(cached_color)) {
|
322
|
+
//console.log(`cached_color: `, cached_color);
|
323
|
+
seriescolor = cached_color;
|
324
|
+
}
|
325
|
+
|
326
|
+
if (Array.isArray(__colors) && __colors.length > 0) {
|
327
|
+
__colors.forEach((item) => {
|
328
|
+
if (item.color) visualcolor.push(item.color);
|
329
|
+
else visualcolor.push(rcolor());
|
330
|
+
});
|
331
|
+
category.forEach((celltype, index) => {
|
332
|
+
const cell = __colors.find((item) => item.name === celltype);
|
333
|
+
const _color = cell?.color ?? visualcolor[index];
|
334
|
+
const _symbol =
|
335
|
+
__colors.find((item) => item.name === celltype)?.symbol ??
|
336
|
+
'circle';
|
337
|
+
seriescolor[celltype] = {
|
338
|
+
color: _color,
|
339
|
+
symbol: _symbol,
|
340
|
+
};
|
341
|
+
});
|
342
|
+
} else {
|
343
|
+
let no_zcolor = false;
|
344
|
+
if (visualcolor.length === 0) {
|
345
|
+
no_zcolor = true;
|
346
|
+
}
|
347
|
+
category.forEach((celltype, index) => {
|
348
|
+
if (no_zcolor) {
|
349
|
+
visualcolor.push(rcolor());
|
350
|
+
}
|
351
|
+
if (!seriescolor[celltype]) {
|
352
|
+
seriescolor[celltype] = {
|
353
|
+
color: rcolor(),
|
354
|
+
symbol: 'circle',
|
355
|
+
};
|
356
|
+
}
|
357
|
+
});
|
358
|
+
}
|
359
|
+
|
360
|
+
cached_color = seriescolor;
|
361
|
+
return seriescolor;
|
362
|
+
};
|
363
|
+
|
364
|
+
const processData = (
|
365
|
+
metadata,
|
366
|
+
genes,
|
367
|
+
colourby,
|
368
|
+
seriesAttr,
|
369
|
+
dotsize,
|
370
|
+
mapping,
|
371
|
+
) => {
|
372
|
+
const { x = 'x', y = 'y', extra = [] } = mapping;
|
373
|
+
// get the key from metadata
|
374
|
+
const keys = Object.keys(metadata);
|
375
|
+
//console.log(`keys: `, keys);
|
376
|
+
// if colourby is not given, randomly pick one from keys
|
377
|
+
colourby = colourby ?? keys[Math.floor(Math.random() * keys.length)];
|
378
|
+
//console.log(`colourby: `, colourby);
|
379
|
+
|
380
|
+
// Aggregate data based on the "colourby" attribute
|
381
|
+
const aggregatedData = {};
|
382
|
+
|
383
|
+
if (metadata[colourby] === undefined) return Object.values(aggregatedData);
|
384
|
+
|
385
|
+
metadata[colourby].forEach((item, index) => {
|
386
|
+
if (!aggregatedData[item]) {
|
387
|
+
aggregatedData[item] = {
|
388
|
+
name: item,
|
389
|
+
type: 'scatter',
|
390
|
+
data: [],
|
391
|
+
itemStyle: {
|
392
|
+
color: seriesAttr[item].color,
|
393
|
+
},
|
394
|
+
symbol: seriesAttr[item].symbol,
|
395
|
+
symbolSize: processSymbolSize(
|
396
|
+
dotsize,
|
397
|
+
metadata[colourby].length,
|
398
|
+
),
|
399
|
+
};
|
400
|
+
}
|
401
|
+
|
402
|
+
// console.log(metadata['Cell_ID'][index]);
|
403
|
+
// Todo: Leave for gene expression later
|
404
|
+
const extraProperties = {};
|
405
|
+
for (const property in mapping.extra) {
|
406
|
+
if (metadata[extra[property]]) {
|
407
|
+
extraProperties[property] = metadata[extra[property]][index];
|
408
|
+
}
|
409
|
+
}
|
410
|
+
// Todo: Leave for z-index of visualmap later
|
411
|
+
|
412
|
+
let Expression = genes ? genes[metadata['Cell_ID'][index]] : null;
|
413
|
+
aggregatedData[item].data.push({
|
414
|
+
value: [metadata[x][index], metadata[y][index], Expression ?? 0],
|
415
|
+
...extraProperties,
|
416
|
+
Expression: Expression,
|
417
|
+
});
|
418
|
+
});
|
419
|
+
|
420
|
+
//console.log(`aggregatedData: `, aggregatedData);
|
421
|
+
// Convert aggregated data to an array of series
|
422
|
+
return Object.values(aggregatedData);
|
423
|
+
};
|
424
|
+
|
425
|
+
export async function onDataZoom(
|
426
|
+
props,
|
427
|
+
sharedData,
|
428
|
+
fetched_dat,
|
429
|
+
myChart,
|
430
|
+
option,
|
431
|
+
) {
|
432
|
+
const bbox = outputAxisValues(myChart);
|
433
|
+
|
434
|
+
fetched_dat = await handleAPI(props.config, sharedData, bbox);
|
435
|
+
|
436
|
+
if (isEmpty(fetched_dat)) return;
|
437
|
+
|
438
|
+
var {
|
439
|
+
series: newseries,
|
440
|
+
legend,
|
441
|
+
visualMap,
|
442
|
+
title,
|
443
|
+
} = parseData(fetched_dat, props.config, sharedData);
|
444
|
+
|
445
|
+
const MAX_POINTS = 8000;
|
446
|
+
|
447
|
+
if (props.config.merge) {
|
448
|
+
// Ensure that both option.series and newseries are arrays
|
449
|
+
option.series = Array.isArray(option.series) ? option.series : [];
|
450
|
+
newseries = Array.isArray(newseries) ? newseries : [];
|
451
|
+
|
452
|
+
// Merge the existing series and new series while ensuring uniqueness based on the series name
|
453
|
+
const mergedSeries = [...option.series, ...newseries].reduce(
|
454
|
+
(uniqueSeries, currentSeries) => {
|
455
|
+
const existingIndex = uniqueSeries.findIndex(
|
456
|
+
(series) => series.name === currentSeries.name,
|
457
|
+
);
|
458
|
+
|
459
|
+
if (existingIndex === -1) {
|
460
|
+
// Series with this name doesn't exist yet, add it to the uniqueSeries array
|
461
|
+
uniqueSeries.push(currentSeries);
|
462
|
+
} else {
|
463
|
+
// Series with this name already exists, update its data
|
464
|
+
uniqueSeries[existingIndex].data = currentSeries.data;
|
465
|
+
}
|
466
|
+
|
467
|
+
return uniqueSeries;
|
468
|
+
},
|
469
|
+
[],
|
470
|
+
);
|
471
|
+
|
472
|
+
// Calculate the total number of points in the merged series
|
473
|
+
const totalPoints = mergedSeries.reduce(
|
474
|
+
(count, series) =>
|
475
|
+
(count += Array.isArray(series.data) ? series.data.length : 0),
|
476
|
+
0,
|
477
|
+
);
|
478
|
+
|
479
|
+
// Check if the total number of points exceeds the MAX_POINTS limit
|
480
|
+
if (totalPoints > MAX_POINTS) {
|
481
|
+
// Calculate the number of excess points
|
482
|
+
let excess = totalPoints - MAX_POINTS;
|
483
|
+
|
484
|
+
// Iterate over the last option.series and reduce their data points
|
485
|
+
for (const series of option.series.slice().reverse()) {
|
486
|
+
if (excess > 0 && Array.isArray(series.data)) {
|
487
|
+
const removedPoints = series.data.splice(-excess);
|
488
|
+
excess -= removedPoints.length;
|
489
|
+
}
|
490
|
+
}
|
491
|
+
}
|
492
|
+
|
493
|
+
// Set the option's series to the merged series
|
494
|
+
option.series = mergedSeries;
|
495
|
+
} else {
|
496
|
+
option.series = newseries;
|
497
|
+
}
|
498
|
+
|
499
|
+
option.legend = {
|
500
|
+
...option.legend,
|
501
|
+
...legend,
|
502
|
+
};
|
503
|
+
|
504
|
+
option.visualMap = visualMap;
|
505
|
+
option.title = {
|
506
|
+
...option.title,
|
507
|
+
text: title,
|
508
|
+
};
|
509
|
+
myChart.setOption(option);
|
510
|
+
return bbox;
|
511
|
+
}
|
512
|
+
|
513
|
+
const handleLegend = (category) => {
|
514
|
+
let legend = {};
|
515
|
+
let legendType = category.length > 10 ? 'scroll' : 'plain';
|
516
|
+
|
517
|
+
const sortedItems = category.slice().sort((a, b) => {
|
518
|
+
// Extract the character part from the strings
|
519
|
+
const charPartA = a.match(/[^0-9]+/)[0];
|
520
|
+
const charPartB = b.match(/[^0-9]+/)[0];
|
521
|
+
|
522
|
+
// Compare the character parts alphabetically
|
523
|
+
const charComparison = charPartA.localeCompare(charPartB);
|
524
|
+
|
525
|
+
if (charComparison !== 0) {
|
526
|
+
return charComparison;
|
527
|
+
}
|
528
|
+
|
529
|
+
// If the character parts are the same, extract and compare the numeric part
|
530
|
+
const numPartA = parseInt(a.match(/\d+/)[0], 10);
|
531
|
+
const numPartB = parseInt(b.match(/\d+/)[0], 10);
|
532
|
+
|
533
|
+
return numPartA - numPartB;
|
534
|
+
});
|
535
|
+
|
536
|
+
legend.orient = 'horizontal';
|
537
|
+
legend.top = 25;
|
538
|
+
legend.right = 'center';
|
539
|
+
legend.type = legendType;
|
540
|
+
legend.data = sortedItems;
|
541
|
+
return legend;
|
542
|
+
};
|
543
|
+
|
544
|
+
const handleVisualMap = (genes) => {
|
545
|
+
if (!genes) return [];
|
546
|
+
// Initialize max and min variables with the first value in the object
|
547
|
+
let maxValue = Number.NEGATIVE_INFINITY;
|
548
|
+
let minValue = Number.POSITIVE_INFINITY;
|
549
|
+
let visualMap = {};
|
550
|
+
// Iterate through the values of the object
|
551
|
+
for (const value of Object.values(genes)) {
|
552
|
+
// Compare the current value to the max and min values
|
553
|
+
if (value > maxValue) {
|
554
|
+
maxValue = value;
|
555
|
+
}
|
556
|
+
if (value < minValue) {
|
557
|
+
minValue = value;
|
558
|
+
}
|
559
|
+
}
|
560
|
+
//console.log(`handlesVisualMap: `, maxValue, minValue);
|
561
|
+
visualMap.min = minValue;
|
562
|
+
visualMap.max = maxValue;
|
563
|
+
visualMap.dimension = 2;
|
564
|
+
visualMap.orient = 'vertical';
|
565
|
+
visualMap.top = 'center';
|
566
|
+
visualMap.left = 0;
|
567
|
+
visualMap.text = ['log2\n(tpm+1)', ''];
|
568
|
+
visualMap.textGap = 10;
|
569
|
+
visualMap.calculable = true;
|
570
|
+
visualMap.inRange = {
|
571
|
+
color: ['#808080', '#FFA500', '#FF0000'],
|
572
|
+
};
|
573
|
+
visualMap.textStyle = {
|
574
|
+
writingMode: 'vertical-lr',
|
575
|
+
};
|
576
|
+
|
577
|
+
//console.log(`handlesVisualMap: `, visualMap);
|
578
|
+
return visualMap;
|
579
|
+
};
|