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,165 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import ScatterBio from './ScatterBio';
3
+ import simplefetch from '../fetch/fetch';
4
+ import { useAppContext } from '../appContext';
5
+
6
+ const VisiumPlot = ({ props, style }) => {
7
+ // Declare states at the beginning
8
+ const { sharedData } = useAppContext();
9
+ const { debug } = props;
10
+
11
+ const [updatedProps, setUpdatedProps] = useState(props);
12
+
13
+ useEffect(() => {
14
+ const { metaval } = props;
15
+
16
+ let section = sharedData[metaval] ?? [];
17
+ let VisiumProps = {
18
+ ...props,
19
+ config: {
20
+ ...props.config,
21
+ //visium_cell_fraction: "BC",
22
+ visualMap: {
23
+ calculable: true,
24
+ top: 'bottom',
25
+ orient: 'horizontal',
26
+ right: 'center',
27
+ },
28
+ zcolor: ['#FFFF80', '#FFA500', '#FF0000'],
29
+ toolbox: {
30
+ saveAsImage: {
31
+ show: true,
32
+ },
33
+ },
34
+ //dataZoom: "inside", // "inside", "slider", 'both'
35
+ dotsize: '2',
36
+ labels: {
37
+ z: ['log2\n(tpm+1)', ''],
38
+ },
39
+
40
+ xAxis: {
41
+ show: false,
42
+ },
43
+ yAxis: {
44
+ show: false,
45
+ },
46
+ legend: false,
47
+ formatter: (params) => {
48
+ let resultHtml = '';
49
+ for (const [key, value] of Object.entries(params.data)) {
50
+ if (key === 'Expression') {
51
+ continue;
52
+ }
53
+ if (value > 0.5) {
54
+ // Only show the cell type with likelihood >= 0.5
55
+ resultHtml += `<br>${key}: ${value.toFixed(3)}`;
56
+ }
57
+ }
58
+ return `
59
+ <div style="text-align: center;">
60
+ ${params.name}
61
+ <br> Cell2Location Cell Type Likelihood
62
+ <br> (Likelihood >= 0.5)
63
+ ${resultHtml}
64
+ </div>
65
+ `;
66
+ },
67
+ grid: {
68
+ top: '15%',
69
+ bottom: '12%',
70
+ left: '5%',
71
+ right: '10%',
72
+ },
73
+ },
74
+ };
75
+
76
+ const { cellval } = props;
77
+
78
+ if (
79
+ cellval &&
80
+ sharedData[cellval] !== undefined &&
81
+ sharedData[cellval].length > 0
82
+ ) {
83
+ let selected_celltype = sharedData[cellval][0];
84
+ if (
85
+ selected_celltype === 'Default' ||
86
+ selected_celltype === 'None' ||
87
+ selected_celltype === 'NA' ||
88
+ selected_celltype === 'N/A' ||
89
+ selected_celltype === 'Unknown' ||
90
+ selected_celltype === 'Unassigned' ||
91
+ selected_celltype === 'All'
92
+ )
93
+ VisiumProps.config.visium_cell_fraction = null;
94
+ else {
95
+ VisiumProps.config.visium_cell_fraction =
96
+ sharedData[cellval][0];
97
+ VisiumProps.config.visualMap.text = [
98
+ 'Likelihood of being a ' + sharedData[cellval][0],
99
+ '',
100
+ ];
101
+ }
102
+ }
103
+
104
+ function BufferImage(imageBuffer) {
105
+ // Create a Blob object from the binary data
106
+ const blob = new Blob([new Uint8Array(imageBuffer)], {
107
+ type: 'image/png',
108
+ });
109
+ // Create a URL from the Blob
110
+ const dataURL = URL.createObjectURL(blob);
111
+ // Create the Image object
112
+ const imageDom = new Image();
113
+ imageDom.onload = () => {
114
+ // Wait until the image is loaded
115
+ // Set the background properties
116
+ VisiumProps.config.backgroundColor = {
117
+ image: dataURL,
118
+ repeat: 'no-repeat',
119
+ };
120
+
121
+ //console.log("VisiumProps", VisiumProps);
122
+ // Update the state with the loaded image
123
+ setUpdatedProps(VisiumProps);
124
+ };
125
+ imageDom.src = dataURL;
126
+ }
127
+
128
+ const fetchImage = async () => {
129
+ const imageBuffer = await simplefetch(image + section, {
130
+ type: 'image',
131
+ debug: debug,
132
+ });
133
+ BufferImage(imageBuffer.data);
134
+ };
135
+
136
+ const { image } = props;
137
+ if (section.length > 0) {
138
+ section = section[0];
139
+ VisiumProps.config.title = {
140
+ text: 'Section ' + section,
141
+ left: 'center',
142
+ top: 'top',
143
+ textStyle: {
144
+ fontSize: 25,
145
+ fontWeight: 'bold',
146
+ color: 'black',
147
+ },
148
+ };
149
+ fetchImage();
150
+ }
151
+ }, [props, sharedData, debug]);
152
+
153
+ return (
154
+ <>
155
+ <ScatterBio
156
+ key={updatedProps.id + '.inherent'}
157
+ props={updatedProps}
158
+ style={style}
159
+ reset={null}
160
+ />
161
+ </>
162
+ );
163
+ };
164
+
165
+ export default VisiumPlot;
@@ -0,0 +1,42 @@
1
+ /*
2
+ * @Author : Lihao leolihao@arizona.edu
3
+ * @Date : 2023-12-25 21:57:39
4
+ * @FilePath : /visualify.js/src/core/components/browser.js
5
+ * @Description :
6
+ * Copyright (c) 2023 by Lihao (leolihao@arizona.edu), All Rights Reserved.
7
+ */
8
+ import React, { useEffect, useState } from 'react';
9
+
10
+ function Browser({ props, style }) {
11
+ const { src, style: _style, title, width, height } = props;
12
+ const [error, setError] = useState(null);
13
+
14
+ useEffect(() => {
15
+ // Clear any previous error when the src changes
16
+ setError(null);
17
+ }, [src]);
18
+
19
+ return (
20
+ <>
21
+ <iframe
22
+ src={src}
23
+ title={title}
24
+ width={width}
25
+ height={height}
26
+ style={{ ...style }}
27
+ onError={() => {
28
+ setError('Error loading content. Please check the URL.');
29
+ }}
30
+ />
31
+ {error && (
32
+ <div
33
+ className='iframe-error'
34
+ style={{ ...style, ..._style }}>
35
+ {error}
36
+ </div>
37
+ )}
38
+ </>
39
+ );
40
+ }
41
+
42
+ export default Browser;
@@ -0,0 +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;
@@ -0,0 +1,29 @@
1
+ /*
2
+ * @Author : Lihao leolihao@arizona.edu
3
+ * @Date : 2023-12-25 21:00:26
4
+ * @FilePath : /visualifyjs/src/core/components/html.js
5
+ * @Description :
6
+ * Copyright (c) 2023 by Lihao (leolihao@arizona.edu), All Rights Reserved.
7
+ */
8
+ import React from 'react';
9
+ import DOMPurify from 'dompurify';
10
+
11
+ function NaiveHTML({ props, style }) {
12
+ const { className, style: _style } = props;
13
+
14
+ let { html } = props;
15
+
16
+ // Sanitize the HTML using DOMPurify
17
+ html = DOMPurify.sanitize(html);
18
+
19
+ return (
20
+ <div
21
+ style={{ ...style, ..._style }}
22
+ className={className}
23
+ dangerouslySetInnerHTML={{ __html: html }}>
24
+ {/* The sanitized HTML will be inserted here */}
25
+ </div>
26
+ );
27
+ }
28
+
29
+ export default NaiveHTML;