visualifyjs 2.5.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (147) hide show
  1. package/.github/workflows/static.yml.bak +51 -0
  2. package/LICENSE +674 -0
  3. package/README.md +59 -0
  4. package/config-overrides.js +31 -0
  5. package/dist/visualify.js +188 -0
  6. package/docs/.nojekyll +0 -0
  7. package/docs/docs/CLI.md +34 -0
  8. package/docs/docs/README.md +65 -0
  9. package/docs/docs/Rechart/bar.md +190 -0
  10. package/docs/docs/Rechart/funnel.md +193 -0
  11. package/docs/docs/Rechart/geo.md +0 -0
  12. package/docs/docs/Rechart/line.md +355 -0
  13. package/docs/docs/Rechart/liquidfill.md +0 -0
  14. package/docs/docs/Rechart/pie.md +225 -0
  15. package/docs/docs/Rechart/polar.md +0 -0
  16. package/docs/docs/Rechart/radar.md +253 -0
  17. package/docs/docs/Rechart/sankey.md +0 -0
  18. package/docs/docs/Rechart/scatter.md +0 -0
  19. package/docs/docs/Rechart/sunburst.md +0 -0
  20. package/docs/docs/Rechart/tree.md +0 -0
  21. package/docs/docs/Rechart/wordcloud.md +0 -0
  22. package/docs/docs/_404.md +52 -0
  23. package/docs/docs/_coverpage.md +11 -0
  24. package/docs/docs/_sidebar.md +43 -0
  25. package/docs/docs/components/dotBio.md +34 -0
  26. package/docs/docs/components/echart.md +82 -0
  27. package/docs/docs/components/html.md +34 -0
  28. package/docs/docs/components/macaron.md +145 -0
  29. package/docs/docs/components/markdown.md +0 -0
  30. package/docs/docs/components/more.md +142 -0
  31. package/docs/docs/components/plotly.md +62 -0
  32. package/docs/docs/components/scatterL.md +70 -0
  33. package/docs/docs/components/visium.md +57 -0
  34. package/docs/docs/configuration.md +123 -0
  35. package/docs/docs/deploy.md +31 -0
  36. package/docs/docs/log.md +1 -0
  37. package/docs/docs/more-pages.md +23 -0
  38. package/docs/docs/quickstart.md +119 -0
  39. package/docs/docs/rechart-attributes.md +74 -0
  40. package/docs/docs/rechart-basic-usage.md +162 -0
  41. package/docs/docs/static/_images/deploy-github-pages.png +0 -0
  42. package/docs/docs/static/logo/favicon.ico +0 -0
  43. package/docs/docs/static/logo/logo_128x128.png +0 -0
  44. package/docs/docs/static/logo/logo_192x192.png +0 -0
  45. package/docs/docs/static/logo/logo_256x256.png +0 -0
  46. package/docs/docs/static/logo/logo_512x512.png +0 -0
  47. package/docs/docs/static/logo/logo_64x64.png +0 -0
  48. package/docs/docs/theme.md +5 -0
  49. package/docs/index.html +71 -0
  50. package/docs/manifest.json +24 -0
  51. package/docs/static/css/fluff-stuff.css +170 -0
  52. package/docs/static/css/font-awesome.min.css +4 -0
  53. package/docs/static/css/visualify.css +25 -0
  54. package/docs/static/fonts/fontawesome-webfont.woff2 +0 -0
  55. package/docs/static/images/star.png +0 -0
  56. package/docs/static/js/configuration.js +448 -0
  57. package/docs/static/js/fluff.js +1 -0
  58. package/docs/static/js/visualify.js +188 -0
  59. package/docs/static/logo/favicon.ico +0 -0
  60. package/docs/static/logo/logo_128x128.png +0 -0
  61. package/docs/static/logo/logo_192x192.png +0 -0
  62. package/docs/static/logo/logo_256x256.png +0 -0
  63. package/docs/static/logo/logo_512x512.png +0 -0
  64. package/docs/static/logo/logo_64x64.png +0 -0
  65. package/package.json +84 -0
  66. package/rollup.config.mjs +76 -0
  67. package/src/_css/404.css +116 -0
  68. package/src/_css/App.css +38 -0
  69. package/src/_css/autoSuggestion.css +27 -0
  70. package/src/_css/circular-progress.css +33 -0
  71. package/src/_css/index.css +37 -0
  72. package/src/_css/modern.css +25 -0
  73. package/src/_media/404.png +0 -0
  74. package/src/_media/corner.svg +8 -0
  75. package/src/_media/download.svg +3 -0
  76. package/src/_media/icon.svg +1 -0
  77. package/src/_media/logo.svg +14 -0
  78. package/src/_test/App.test.js +15 -0
  79. package/src/_utils/reportWebVitals.js +13 -0
  80. package/src/core/appContext.js +27 -0
  81. package/src/core/components/Scatter.js +188 -0
  82. package/src/core/components/ScatterBio.js +572 -0
  83. package/src/core/components/VisiumPlot.js +165 -0
  84. package/src/core/components/browser.js +42 -0
  85. package/src/core/components/dotplot.js +413 -0
  86. package/src/core/components/html.js +29 -0
  87. package/src/core/components/list.js +178 -0
  88. package/src/core/components/macaron.js +201 -0
  89. package/src/core/components/markdown.js +56 -0
  90. package/src/core/components/parser.scatterBio.js +579 -0
  91. package/src/core/components/ratio.js +80 -0
  92. package/src/core/components/scatterL.js +173 -0
  93. package/src/core/components/searchbar.js +131 -0
  94. package/src/core/components/selection.js +193 -0
  95. package/src/core/components/timeline.js +281 -0
  96. package/src/core/components/visium.js +97 -0
  97. package/src/core/fetch/condfetch.js +82 -0
  98. package/src/core/fetch/fetch.js +92 -0
  99. package/src/core/fetch/json.js +29 -0
  100. package/src/core/fetch/vfetch.js +42 -0
  101. package/src/core/liveEditor.js +44 -0
  102. package/src/core/modules/codeEditorWithPreview.js +104 -0
  103. package/src/core/modules/echarts/common.js +20 -0
  104. package/src/core/modules/echarts/presetHandler.js +41 -0
  105. package/src/core/modules/echarts/presets/esodev.chromium.js +172 -0
  106. package/src/core/modules/echarts/presets/esodev.codex.js +130 -0
  107. package/src/core/modules/echarts/presets/esodev.visium.js +123 -0
  108. package/src/core/modules/echarts/presets/mmtrbc.js +186 -0
  109. package/src/core/modules/echarts.js +71 -0
  110. package/src/core/modules/echartsUtils.js +43 -0
  111. package/src/core/modules/echartswitcher.js +152 -0
  112. package/src/core/modules/replotly/presetHandler.js +24 -0
  113. package/src/core/modules/replotly/presets/minimum.js +18 -0
  114. package/src/core/modules/replotly/presets/mmtrbc.dot.js +114 -0
  115. package/src/core/modules/replotly/presets/mmtrbc.violin.js +100 -0
  116. package/src/core/modules/replotly.js +71 -0
  117. package/src/core/pages/404.js +50 -0
  118. package/src/core/pages/error.js +27 -0
  119. package/src/core/pages/jsonPage.js +62 -0
  120. package/src/core/pages/loading.js +44 -0
  121. package/src/core/parser/echart.data.js +183 -0
  122. package/src/core/parser/echart.features.js +125 -0
  123. package/src/core/parser/echart.general.js +143 -0
  124. package/src/core/parser/echart.hilbert.js +57 -0
  125. package/src/core/parser/echart.parser.js +210 -0
  126. package/src/core/parser/echart.series.js +67 -0
  127. package/src/core/parser/echart.types.js +76 -0
  128. package/src/core/parser/plotly.config.js +10 -0
  129. package/src/core/parser/plotly.data.js +132 -0
  130. package/src/core/parser/plotly.layout.js +10 -0
  131. package/src/core/parser/plotly.violin.js +18 -0
  132. package/src/core/recharts.js +62 -0
  133. package/src/core/router/alias.js +49 -0
  134. package/src/core/router/jsonRouter.js +31 -0
  135. package/src/core/themes/modern.js +32 -0
  136. package/src/core/themes/themeSelector.js +33 -0
  137. package/src/core/visualify.js +47 -0
  138. package/src/core/widgets/circularProgress.js +24 -0
  139. package/src/core/widgets/controller.js +83 -0
  140. package/src/core/widgets/errorBoundary.js +36 -0
  141. package/src/core/widgets/footer.js +177 -0
  142. package/src/core/widgets/header.js +234 -0
  143. package/src/core/widgets/layout/Grid.js +31 -0
  144. package/src/core/widgets/layout.js +36 -0
  145. package/src/core/widgets/mapping.js +42 -0
  146. package/src/index.js +62 -0
  147. package/src/setupTests.js +5 -0
@@ -0,0 +1,579 @@
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
+ };
@@ -0,0 +1,80 @@
1
+ /*
2
+ * @Author : Lihao leolihao@arizona.edu
3
+ * @Date : 2023-12-30 15:11:35
4
+ * @FilePath : /visualifyjs/src/core/components/ratio.js
5
+ * @Description :
6
+ * Copyright (c) 2023 by Lihao (leolihao@arizona.edu), All Rights Reserved.
7
+ */
8
+ import React, { useState, useEffect } from 'react';
9
+ import { useAppContext } from '../appContext';
10
+
11
+ function RatioBox({ props, style }) {
12
+ const { debug } = props;
13
+
14
+ const { style: blockStyle } = props;
15
+ // if debug is true, then border is red, else no border
16
+ style = {
17
+ ...style,
18
+ ...blockStyle,
19
+ border: debug ? '1px solid red' : 'none',
20
+ };
21
+
22
+ const { title } = props;
23
+ const renderTitle = () => {
24
+ return title && <h3>{title}</h3>;
25
+ };
26
+
27
+ const { setSharedData } = useAppContext();
28
+ const { choice = [] } = props;
29
+
30
+ // State to keep track of selected choice
31
+ const [selectedChoice, setSelectedChoice] = useState(
32
+ choice.length > 0 ? choice[0] : null,
33
+ );
34
+
35
+ // Handler for radio button change
36
+ const handleRadioChange = (event) => {
37
+ setSelectedChoice(event.target.value);
38
+ };
39
+
40
+ const {
41
+ id,
42
+ val,
43
+ ratio_style = {
44
+ margin: 'auto 15px',
45
+ },
46
+ } = props;
47
+
48
+ // Store the selected choice in shared data whenever it changes
49
+ useEffect(() => {
50
+ if (val) {
51
+ setSharedData((prevSharedData) => {
52
+ return { ...prevSharedData, [val]: selectedChoice };
53
+ });
54
+ }
55
+ }, [selectedChoice, val, setSharedData]);
56
+
57
+ return (
58
+ <div
59
+ key={id}
60
+ style={style}
61
+ className='ratio-box-container'>
62
+ {renderTitle()}
63
+ {choice.map((item, index) => (
64
+ <label
65
+ key={index}
66
+ style={ratio_style}>
67
+ <input
68
+ type='radio'
69
+ value={item}
70
+ checked={selectedChoice === item}
71
+ onChange={handleRadioChange}
72
+ />
73
+ {item}
74
+ </label>
75
+ ))}
76
+ </div>
77
+ );
78
+ }
79
+
80
+ export default RatioBox;