visualifyjs 2.5.3 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (252) hide show
  1. package/.claude/mem/TIMELINE.md +36 -0
  2. package/.claude/mem/notes/2026-02-11-3d-visualization-docs-fix-external-script-solution.md +24 -0
  3. package/.claude/mem/notes/2026-02-11-3d-visualization-docs-fix-session-summary.md +43 -0
  4. package/.claude/mem/notes/2026-02-11-cli-fix-editor-command-alias.md +26 -0
  5. package/.claude/mem/notes/2026-02-11-phase-3-developer-experience-completed.md +51 -0
  6. package/.claude/mem/notes/2026-02-11-phase-4-web-workers-implementation-complete.md +59 -0
  7. package/.claude/mem/notes/2026-02-11-visualify-phase-2-3d-visualization-complete.md +50 -0
  8. package/.claude/mem/notes/2026-02-11-visualify-phase-2-committed-ready-for-phase-3.md +33 -0
  9. package/.claude/mem/notes/2026-02-11-visualify-phase-3-complete-developer-experience.md +52 -0
  10. package/.claude/mem/notes/2026-02-11-visualify-repository-cleanup-complete.md +28 -0
  11. package/.claude/mem/notes/2026-02-18-codebase-cleanup-docsify-plugin-documentation.md +37 -0
  12. package/.claude/mem/notes/2026-02-19-css-grid-layout-fix-displaycontents-on-vcontroller.md +18 -0
  13. package/.claude/mem/notes/2026-02-19-docsify-plugin-fixes-latex-and-visualify-code-bloc.md +26 -0
  14. package/.claude/mem/notes/2026-02-19-page-mode-docs-update-decisions.md +23 -0
  15. package/.claude/mem/notes/2026-02-19-react-context-infinite-re-render-loop-fix-pattern.md +31 -0
  16. package/.claude/mem/notes/2026-02-19-version-300-bump-and-build-fixes.md +32 -0
  17. package/.claude/mem/notes/2026-02-19-visualify-build-deployment-architecture-bug-fixes.md +25 -0
  18. package/.claude/mem/notes/2026-02-19-visualify-dist-iife-self-contained-build-config.md +30 -0
  19. package/.claude/mem/notes/2026-02-19-visualify-infinite-loop-i18n-fixes.md +31 -0
  20. package/.claude/mem/notes/2026-02-19-visualify-v3-bundle-splitting-docs-restructuring.md +32 -0
  21. package/.claude/mem/notes/2026-02-20-bundle-externalization-final-architecture.md +29 -0
  22. package/.claude/mem/notes/2026-02-20-chromium-page-fix-unstable-keys-and-double-event-b.md +27 -0
  23. package/.claude/mem/notes/2026-02-20-console-cleanup-bundle-optimization-commit.md +20 -0
  24. package/.claude/mem/notes/2026-02-20-dotbio-dot-plot-fix-useeffect-dependency.md +21 -0
  25. package/.claude/mem/notes/2026-02-20-public-folder-cleanup-and-readme-rewrite.md +25 -0
  26. package/.claude/mem/notes/2026-02-20-v300-release-and-beta-channel-strategy.md +29 -0
  27. package/.claude/mem/notes/2026-02-20-visium-background-image-unknown-legend-fix.md +19 -0
  28. package/.claude/mem/notes/2026-02-20-visualify-cdn-loader-bundle-externalization.md +34 -0
  29. package/.claude/mem/sessions/session-2026-02-20-031524.md +54 -0
  30. package/.claude/settings.local.json +21 -0
  31. package/.github/workflows/static.yml.bak +51 -51
  32. package/.sisyphus/boulder.json +65 -0
  33. package/.sisyphus/plans/phase-4-advanced-optimizations.md +217 -0
  34. package/LICENSE +674 -674
  35. package/README.md +94 -59
  36. package/config-overrides.js +31 -31
  37. package/dist/stats.html +4949 -0
  38. package/dist/visualify-3d.esm.js +1 -0
  39. package/dist/visualify-3d.js +1 -0
  40. package/dist/visualify-core.esm.js +1 -0
  41. package/dist/visualify-core.js +1 -0
  42. package/dist/visualify-docs.esm.js +1 -0
  43. package/dist/visualify-docs.js +1 -0
  44. package/dist/visualify-loader.js +1 -0
  45. package/dist/visualify-pages.esm.js +1 -0
  46. package/dist/visualify-pages.js +1 -0
  47. package/dist/visualify-portal.esm.js +1 -0
  48. package/dist/visualify-portal.js +1 -0
  49. package/dist/visualify-shared.js +26571 -0
  50. package/dist/visualify.js +1 -188
  51. package/docs/CHANGELOG.md +148 -0
  52. package/docs/cli/commands.md +513 -0
  53. package/docs/configuration/visualify-json.md +474 -0
  54. package/docs/docs/3d-visualization.md +374 -0
  55. package/docs/docs/CLI.md +303 -34
  56. package/docs/docs/README.md +65 -65
  57. package/docs/docs/Rechart/bar.md +190 -190
  58. package/docs/docs/Rechart/funnel.md +241 -193
  59. package/docs/docs/Rechart/line.md +355 -355
  60. package/docs/docs/Rechart/pie.md +225 -225
  61. package/docs/docs/Rechart/radar.md +253 -253
  62. package/docs/docs/Rechart/scatter.md +298 -0
  63. package/docs/docs/_404.md +51 -51
  64. package/docs/docs/_coverpage.md +11 -11
  65. package/docs/docs/_sidebar.md +54 -43
  66. package/docs/docs/components/dotBio.md +87 -34
  67. package/docs/docs/components/echart.md +171 -82
  68. package/docs/docs/components/html.md +61 -34
  69. package/docs/docs/components/macaron.md +156 -145
  70. package/docs/docs/components/markdown.md +42 -0
  71. package/docs/docs/components/more.md +183 -142
  72. package/docs/docs/components/plotly.md +132 -62
  73. package/docs/docs/components/scatterL.md +171 -70
  74. package/docs/docs/components/visium.md +112 -57
  75. package/docs/docs/configuration.md +121 -123
  76. package/docs/docs/deploy.md +31 -31
  77. package/docs/docs/docsify-plugin.md +655 -0
  78. package/docs/docs/hmr.md +165 -0
  79. package/docs/docs/i18n.md +332 -0
  80. package/docs/docs/log.md +30 -1
  81. package/docs/docs/more-pages.md +23 -23
  82. package/docs/docs/quickstart.md +148 -119
  83. package/docs/docs/rechart-attributes.md +74 -74
  84. package/docs/docs/rechart-basic-usage.md +160 -162
  85. package/docs/docs/theme.md +5 -5
  86. package/docs/docs/typescript.md +306 -0
  87. package/docs/docs/visual-editor.md +359 -0
  88. package/docs/index.html +85 -71
  89. package/docs/manifest.json +23 -23
  90. package/docs/migration/v3-migration.md +392 -0
  91. package/docs/static/css/fluff-stuff.css +169 -169
  92. package/docs/static/css/font-awesome.min.css +4 -4
  93. package/docs/static/css/visualify.css +6 -25
  94. package/docs/static/js/3d-viz-examples.js +181 -0
  95. package/docs/static/js/configuration.js +630 -448
  96. package/docs/static/js/visualify.js +1 -188
  97. package/package.json +106 -84
  98. package/rollup.config.mjs +766 -76
  99. package/src/_css/404.css +115 -115
  100. package/src/_css/App.css +37 -37
  101. package/src/_css/autoSuggestion.css +26 -26
  102. package/src/_css/circular-progress.css +32 -32
  103. package/src/_css/index.css +36 -36
  104. package/src/_css/modern.css +350 -25
  105. package/src/_media/corner.svg +8 -8
  106. package/src/_media/download.svg +3 -3
  107. package/src/_media/logo.svg +14 -14
  108. package/src/_test/App.test.js +15 -15
  109. package/src/_utils/reportWebVitals.js +13 -13
  110. package/src/a11y/README.md +177 -0
  111. package/src/a11y/aria-labels.js +339 -0
  112. package/src/a11y/color-contrast.js +535 -0
  113. package/src/a11y/index.js +197 -0
  114. package/src/a11y/keyboard-nav.js +523 -0
  115. package/src/a11y/styles.css +165 -0
  116. package/src/cli/commands/dev.js +214 -0
  117. package/src/cli/commands/docs.js +521 -0
  118. package/src/cli/commands/edit.js +379 -0
  119. package/src/cli/commands/init.js +213 -0
  120. package/src/cli/commands/portal.js +236 -0
  121. package/src/cli/dev-server.js +530 -0
  122. package/src/cli/hmr.js +456 -0
  123. package/src/cli/index.js +180 -0
  124. package/src/cli/utils/config.js +207 -0
  125. package/src/cli/utils/logger.js +241 -0
  126. package/src/config/defaults.ts +122 -0
  127. package/src/config/index.ts +72 -0
  128. package/src/config/loader.ts +478 -0
  129. package/src/config/schema.ts +227 -0
  130. package/src/config/validator.ts +337 -0
  131. package/src/core/appContext.js +34 -27
  132. package/src/core/components/Bar.js +383 -0
  133. package/src/core/components/Bar3D.js +473 -0
  134. package/src/core/components/LargeDatasetChart.js +296 -0
  135. package/src/core/components/Line3D.js +310 -0
  136. package/src/core/components/Scatter.js +392 -188
  137. package/src/core/components/Scatter3D.js +455 -0
  138. package/src/core/components/ScatterBio.js +601 -572
  139. package/src/core/components/Surface3D.js +326 -0
  140. package/src/core/components/ThreeCustom.js +648 -0
  141. package/src/core/components/ThreeScene.js +459 -0
  142. package/src/core/components/VisiumPlot.js +191 -165
  143. package/src/core/components/browser.js +42 -42
  144. package/src/core/components/dotplot.js +413 -413
  145. package/src/core/components/html.js +29 -29
  146. package/src/core/components/list.js +178 -178
  147. package/src/core/components/macaron.js +206 -201
  148. package/src/core/components/markdown.js +56 -56
  149. package/src/core/components/parser.scatterBio.js +582 -579
  150. package/src/core/components/ratio.js +82 -80
  151. package/src/core/components/scatterL.js +206 -173
  152. package/src/core/components/searchbar.js +156 -131
  153. package/src/core/components/selection.js +310 -193
  154. package/src/core/components/timeline.js +236 -281
  155. package/src/core/components/visium.js +114 -97
  156. package/src/core/data-processor.js +413 -0
  157. package/src/core/fetch/condfetch.js +82 -82
  158. package/src/core/fetch/fetch.js +92 -92
  159. package/src/core/fetch/json.js +29 -29
  160. package/src/core/fetch/vfetch.js +42 -42
  161. package/src/core/hmr-client.js +724 -0
  162. package/src/core/liveEditor.js +44 -44
  163. package/src/core/modules/codeEditorWithPreview.js +104 -104
  164. package/src/core/modules/echarts/common.js +20 -20
  165. package/src/core/modules/echarts/gl.js +228 -0
  166. package/src/core/modules/echarts/presetHandler.js +41 -41
  167. package/src/core/modules/echarts/presets/esodev.chromium.js +172 -172
  168. package/src/core/modules/echarts/presets/esodev.codex.js +130 -130
  169. package/src/core/modules/echarts/presets/esodev.visium.js +123 -123
  170. package/src/core/modules/echarts/presets/mmtrbc.js +186 -186
  171. package/src/core/modules/echarts.js +70 -71
  172. package/src/core/modules/echartsUtils.js +43 -43
  173. package/src/core/modules/echartswitcher.js +227 -152
  174. package/src/core/modules/replotly/presetHandler.js +24 -24
  175. package/src/core/modules/replotly/presets/minimum.js +18 -18
  176. package/src/core/modules/replotly/presets/mmtrbc.dot.js +114 -114
  177. package/src/core/modules/replotly/presets/mmtrbc.violin.js +100 -100
  178. package/src/core/modules/replotly.js +74 -71
  179. package/src/core/modules/threejs/Camera.js +373 -0
  180. package/src/core/modules/threejs/Lighting.js +459 -0
  181. package/src/core/modules/threejs/Renderer.js +364 -0
  182. package/src/core/modules/threejs/Scene.js +266 -0
  183. package/src/core/modules/threejs/index.js +155 -0
  184. package/src/core/pages/404.js +50 -50
  185. package/src/core/pages/error.js +27 -27
  186. package/src/core/pages/jsonPage.js +62 -62
  187. package/src/core/pages/loading.js +44 -44
  188. package/src/core/parser/echart.data.js +204 -183
  189. package/src/core/parser/echart.features.js +125 -125
  190. package/src/core/parser/echart.general.js +147 -143
  191. package/src/core/parser/echart.hilbert.js +57 -57
  192. package/src/core/parser/echart.parser.js +210 -210
  193. package/src/core/parser/echart.series.js +67 -67
  194. package/src/core/parser/echart.types.js +76 -76
  195. package/src/core/parser/plotly.config.js +10 -10
  196. package/src/core/parser/plotly.data.js +132 -132
  197. package/src/core/parser/plotly.layout.js +9 -9
  198. package/src/core/parser/plotly.violin.js +18 -18
  199. package/src/core/recharts.js +361 -62
  200. package/src/core/router/alias.js +49 -49
  201. package/src/core/router/jsonRouter.js +31 -31
  202. package/src/core/themes/modern.js +32 -32
  203. package/src/core/themes/themeSelector.js +33 -33
  204. package/src/core/visualify.js +213 -47
  205. package/src/core/widgets/circularProgress.js +23 -23
  206. package/src/core/widgets/controller.js +116 -83
  207. package/src/core/widgets/errorBoundary.js +36 -36
  208. package/src/core/widgets/footer.js +185 -177
  209. package/src/core/widgets/header.js +238 -234
  210. package/src/core/widgets/layout/Grid.js +31 -31
  211. package/src/core/widgets/layout.js +36 -36
  212. package/src/core/widgets/mapping.js +56 -42
  213. package/src/core/workers/data-worker.js +349 -0
  214. package/src/core/workers/worker-pool.js +396 -0
  215. package/src/docsify/bundle.js +215 -0
  216. package/src/docsify/markdown.js +271 -0
  217. package/src/docsify/plugin.js +268 -0
  218. package/src/editor/README.md +172 -0
  219. package/src/editor/components/ChartBuilder.jsx +341 -0
  220. package/src/editor/components/ChartTypeSidebar.jsx +91 -0
  221. package/src/editor/components/Editor.jsx +367 -0
  222. package/src/editor/components/Preview.jsx +446 -0
  223. package/src/editor/components/PropertyPanel.jsx +468 -0
  224. package/src/editor/components/StatusBar.jsx +85 -0
  225. package/src/editor/context/EditorContext.js +248 -0
  226. package/src/editor/hooks/useDebounce.js +32 -0
  227. package/src/editor/index.js +315 -0
  228. package/src/editor/styles/editor.css +637 -0
  229. package/src/editor/utils/chartValidator.js +263 -0
  230. package/src/entries/charts3d.js +70 -0
  231. package/src/entries/core.js +78 -0
  232. package/src/entries/docs.js +154 -0
  233. package/src/entries/pages.js +93 -0
  234. package/src/entries/portal.js +204 -0
  235. package/src/entries/shared.js +50 -0
  236. package/src/i18n/formatters.js +455 -0
  237. package/src/i18n/index.js +169 -0
  238. package/src/i18n/locales/ar.json +137 -0
  239. package/src/i18n/locales/de.json +137 -0
  240. package/src/i18n/locales/en.json +137 -0
  241. package/src/i18n/locales/es.json +137 -0
  242. package/src/i18n/locales/he.json +137 -0
  243. package/src/i18n/locales/zh.json +137 -0
  244. package/src/i18n/rtl.css +183 -0
  245. package/src/index.js +82 -62
  246. package/src/loader.js +103 -0
  247. package/src/setupTests.js +5 -5
  248. package/tsconfig.json +51 -0
  249. package/types/charts.d.ts +569 -0
  250. package/types/components.d.ts +441 -0
  251. package/types/config.d.ts +199 -0
  252. package/types/index.d.ts +353 -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
+ // processing logic here...
382
+ setUpdatedProps((updatedProps) => ({
383
+ ...updatedProps,
384
+ data: config.data,
385
+ }));
386
+ };
387
+
388
+ fetchData()
389
+ .then(processData)
390
+ .then(() => setLoading(false))
391
+ .catch((err) => {
392
+ console.error(err);
393
+ setError(err);
394
+ setLoading(false);
395
+ });
396
+ // eslint-disable-next-line react-hooks/exhaustive-deps
397
+ }, [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;