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.

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