visualifyjs 2.5.3-2.dev

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 +241 -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 +298 -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 +44 -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 +121 -0
  35. package/docs/docs/deploy.md +31 -0
  36. package/docs/docs/log.md +9 -0
  37. package/docs/docs/more-pages.md +23 -0
  38. package/docs/docs/quickstart.md +124 -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 +587 -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 +147 -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,281 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import simplefetch from '../fetch/fetch';
3
+ import { useAppContext } from '../appContext';
4
+
5
+ const generateNodes = (data, split_pattern) => {
6
+ return data.reduce((groups, item) => {
7
+ const parts = item.split(split_pattern);
8
+ const main = parts[0];
9
+ const sub = parts.slice(1).join(split_pattern);
10
+
11
+ if (sub) {
12
+ if (!groups[main]) {
13
+ groups[main] = [];
14
+ }
15
+ groups[main].push(sub);
16
+ } else {
17
+ groups[main] = null;
18
+ }
19
+
20
+ return groups;
21
+ }, {});
22
+ };
23
+
24
+ function Timeline({ props, style }) {
25
+ const { debug } = props;
26
+
27
+ const { style: _style_parsed = {} } = props;
28
+ const { class: className, ...customStyle } = _style_parsed;
29
+
30
+ // if debug is true, then border is red, else no border
31
+ style = {
32
+ ...style,
33
+ border: debug ? '1px solid red' : 'none',
34
+ maxHeight: '500px',
35
+ overflowY: 'auto',
36
+ overflowX: 'hidden',
37
+ ...customStyle,
38
+ position: 'relative',
39
+ };
40
+
41
+ const { id, val, config = {}, title } = props;
42
+
43
+ const {
44
+ split_pattern = '_',
45
+ node_width = '50%',
46
+ sort_pattern = new RegExp(/E(\d+)/),
47
+ distance_pattern = undefined,
48
+ basicGap = 1,
49
+ } = config;
50
+ // Render title if it exists
51
+ const renderTitle = () => {
52
+ return title && <h3>{title}</h3>;
53
+ };
54
+
55
+ const [nodes, setNodes] = useState([]);
56
+
57
+ useEffect(() => {
58
+ const { selection, urlval, rm_suffix } = props;
59
+
60
+ const fetchData = async () => {
61
+ try {
62
+ const response = await simplefetch(selection, { key: urlval });
63
+ // remove the suffix "_metadata" from the options
64
+ try {
65
+ const removed_suffix = response.map((item) =>
66
+ item.replace(rm_suffix, ''),
67
+ );
68
+ //console.log('Removed suffix:', removed_suffix);
69
+ const groupedNodes = generateNodes(
70
+ removed_suffix,
71
+ split_pattern,
72
+ );
73
+ setNodes(groupedNodes);
74
+ } catch (error) {
75
+ setNodes(response);
76
+ }
77
+ } catch (error) {
78
+ console.error('Error fetching options:', error);
79
+ }
80
+ };
81
+
82
+ fetchData();
83
+ }, [props, split_pattern, debug]);
84
+
85
+ const { setSharedData } = useAppContext();
86
+
87
+ const handleNodeClick = (node) => {
88
+ if (debug) console.log(`Node [${node}] clicked!`);
89
+ if (val) {
90
+ setSharedData((prevSharedData) => {
91
+ return { ...prevSharedData, [val]: [node] };
92
+ });
93
+ alert(`Clicked: ${node}`);
94
+ }
95
+ };
96
+
97
+ const _sort_pattern =
98
+ typeof sort_pattern === 'string'
99
+ ? new RegExp(sort_pattern)
100
+ : sort_pattern;
101
+
102
+ const sortedNodes = Object.keys(nodes)
103
+ .sort((a, b) => {
104
+ //console.log('a:', a, 'b:', b, 'sort_pattern:', _sort_pattern);
105
+ const numA = parseInt(a.match(_sort_pattern)[1]);
106
+ const numB = parseInt(b.match(_sort_pattern)[1]);
107
+ return numA - numB;
108
+ })
109
+ .reduce((acc, key) => {
110
+ acc[key] = nodes[key];
111
+ return acc;
112
+ }, {});
113
+
114
+ const extractNumber = (str) => {
115
+ const matchE = str.match(/E(\d+)/);
116
+
117
+ if (distance_pattern && distance_pattern.regex) {
118
+ const dis_regex =
119
+ typeof distance_pattern?.regex === 'string'
120
+ ? new RegExp(distance_pattern.regex)
121
+ : distance_pattern.regex;
122
+
123
+ const match = str.match(dis_regex);
124
+
125
+ //console.log('str', str, 'match:', match, 'distance_pattern:', dis_regex);
126
+ if (match) {
127
+ for (let i = distance_pattern.pos; i >= 1; i--) {
128
+ if (match[i]) {
129
+ return parseInt(match[i]);
130
+ }
131
+ }
132
+ }
133
+ } else if (matchE) {
134
+ return parseInt(matchE[1]);
135
+ }
136
+ return 0;
137
+ };
138
+
139
+ const calculateDistance = (main, previousMain) => {
140
+ if (!previousMain) return 0;
141
+
142
+ const numA = extractNumber(main);
143
+ const numB = extractNumber(previousMain);
144
+
145
+ //console.log('numA:', numA, 'numB:', numB, 'diff:', Math.abs(numA - numB));
146
+
147
+ return Math.abs(numA - numB) * basicGap; // Distance based on the basic gap value
148
+ };
149
+
150
+ let previousMain = null;
151
+
152
+ return (
153
+ <div
154
+ key={id}
155
+ style={{ ...style }}
156
+ className={className}>
157
+ {renderTitle()}
158
+ <div
159
+ key={id + '.timeline'}
160
+ style={{
161
+ display: 'flex',
162
+ flexDirection: 'column',
163
+ alignItems: 'flex-start',
164
+ position: 'relative',
165
+ }}>
166
+ <div
167
+ style={{
168
+ borderLeft: '10px solid black',
169
+ //minHeight: '350px',
170
+ height: '100%',
171
+ position: 'absolute',
172
+ left: '0px',
173
+ top: '0px',
174
+ }}
175
+ />
176
+ <div
177
+ style={{ ...style }}
178
+ className={className}>
179
+ <div
180
+ style={{
181
+ maxHeight: '500px',
182
+ overflowY: 'auto',
183
+ overflowX: 'hidden',
184
+ }}>
185
+ {Object.keys(sortedNodes).map((main, index) => {
186
+ const distance = calculateDistance(
187
+ main,
188
+ previousMain,
189
+ );
190
+ previousMain = main; // Update previousMain for the next iteration
191
+ return (
192
+ <div
193
+ key={index}
194
+ style={{
195
+ display: 'flex',
196
+ alignItems: 'center',
197
+ marginTop: distance + 'px', // Apply distance here
198
+ position: 'relative',
199
+ flexDirection: 'column',
200
+ }}>
201
+ <div
202
+ style={{
203
+ position: 'absolute',
204
+ left: '0%',
205
+ top: '10px',
206
+ width: node_width ?? '50%',
207
+ height: '2px',
208
+ background: 'black',
209
+ }}
210
+ />
211
+ <div
212
+ id='Timeline.Dot'
213
+ style={{
214
+ position: 'absolute',
215
+ left: '0%',
216
+ top: '15px',
217
+ width: '10px',
218
+ height: '10px',
219
+ borderRadius: '50%',
220
+ background: 'black',
221
+ }}
222
+ />
223
+ {nodes[main] ? (
224
+ <>
225
+ <div style={{ marginLeft: '15%' }}>
226
+ {main}
227
+ </div>
228
+ <div
229
+ style={{
230
+ marginLeft: '15%',
231
+ display: 'flex',
232
+ flexWrap: 'wrap',
233
+ }}>
234
+ {nodes[main].map(
235
+ (sub, subIndex) => (
236
+ <button
237
+ key={subIndex}
238
+ style={{
239
+ padding:
240
+ '5px 3px',
241
+ marginLeft:
242
+ '2px',
243
+ border: '1px solid black',
244
+ cursor: 'pointer',
245
+ }}
246
+ onClick={() =>
247
+ handleNodeClick(
248
+ `${main}_${sub}`,
249
+ )
250
+ }>
251
+ {sub}
252
+ </button>
253
+ ),
254
+ )}
255
+ </div>
256
+ </>
257
+ ) : (
258
+ <div
259
+ style={{
260
+ marginLeft: '15%',
261
+ padding: '5px 3px',
262
+ border: '1px solid black',
263
+ cursor: 'pointer',
264
+ }}
265
+ onClick={() =>
266
+ handleNodeClick(main)
267
+ }>
268
+ {main}
269
+ </div>
270
+ )}
271
+ </div>
272
+ );
273
+ })}
274
+ </div>
275
+ </div>
276
+ </div>
277
+ </div>
278
+ );
279
+ }
280
+
281
+ export default Timeline;
@@ -0,0 +1,97 @@
1
+ /*
2
+ * @Author : Lihao leolihao@arizona.edu
3
+ * @Date : 2024-01-08 16:34:20
4
+ * @FilePath : /visualifyjs/src/core/components/Visium.js
5
+ * @Description :
6
+ * Copyright (c) 2024 by Lihao (leolihao@arizona.edu), All Rights Reserved.
7
+ */
8
+ import React, { useEffect, useState } from 'react';
9
+ import { useAppContext } from '../appContext';
10
+ import conditionalFetch from '../fetch/condfetch';
11
+ import EChartSwitcher from '../modules/echartswitcher';
12
+ import Loading from '../pages/loading';
13
+ import { isEmpty } from 'lodash';
14
+
15
+ const Visium = ({ props, style }) => {
16
+ const [loading, setLoading] = useState({
17
+ active: true,
18
+ message: 'Please Select the Section',
19
+ });
20
+ const { sharedData } = useAppContext();
21
+ const [Options, setOptions] = useState(props);
22
+
23
+ useEffect(() => {
24
+ const { cellfrac, metadata, gene, image } = props.settings;
25
+ if (!metadata || !gene || !image)
26
+ throw new Error('missing metadata, gene, or image');
27
+ let trigger = {
28
+ metadata: metadata.trigger,
29
+ gene: gene.trigger,
30
+ image: image.trigger,
31
+ };
32
+
33
+ const updatePlot = async () => {
34
+ try {
35
+ const cellval = isEmpty(sharedData[cellfrac])
36
+ ? 'All'
37
+ : sharedData[cellfrac][0];
38
+
39
+ if (sharedData?.[trigger.image]) {
40
+ console.log('image trigger', sharedData?.[trigger.image]);
41
+ const imageBuffer = await conditionalFetch(
42
+ image,
43
+ sharedData,
44
+ {},
45
+ );
46
+
47
+ const blob = new Blob([new Uint8Array(imageBuffer.data)], {
48
+ type: 'image/png',
49
+ });
50
+ const dataURL = URL.createObjectURL(blob);
51
+
52
+ console.log('image', dataURL);
53
+ setOptions((prev) => ({
54
+ ...prev,
55
+ config: {
56
+ ...prev.config,
57
+ overrides: {
58
+ ...prev.config.overrides,
59
+ backgroundColor: {
60
+ image: dataURL,
61
+ },
62
+ },
63
+ },
64
+ }));
65
+ }
66
+
67
+ console.log('update plot', cellval, trigger);
68
+ } catch (e) {
69
+ setLoading({ active: true, message: e.message });
70
+ }
71
+ };
72
+
73
+ updatePlot();
74
+ }, [props, sharedData]);
75
+
76
+ return (
77
+ <div
78
+ id={props.id}
79
+ style={{ ...style, position: 'relative' }}>
80
+ {loading.active && (
81
+ <Loading
82
+ message={loading.message}
83
+ style={loading.style}
84
+ />
85
+ )}
86
+ <EChartSwitcher
87
+ props={Options}
88
+ style={{
89
+ opacity: loading.active ? 0 : 1,
90
+ ...style,
91
+ }}
92
+ />
93
+ </div>
94
+ );
95
+ };
96
+
97
+ export default Visium;
@@ -0,0 +1,82 @@
1
+ /*
2
+ * @Author : Lihao leolihao@arizona.edu
3
+ * @Date : 2023-12-27 17:34:06
4
+ * @FilePath : /visualifyjs/src/core/fetch/condfetch.js
5
+ * @Description :
6
+ * Copyright (c) 2023 by Lihao (leolihao@arizona.edu), All Rights Reserved.
7
+ */
8
+ import fetchApi from './vfetch';
9
+ import { isEmpty } from 'lodash';
10
+
11
+ const conditionalFetch = async (
12
+ source,
13
+ sharedData,
14
+ title,
15
+ visualify = {},
16
+ body = null,
17
+ ) => {
18
+ const {
19
+ name,
20
+ method = 'GET',
21
+ url,
22
+ responseKey,
23
+ mapping: api_mapping = visualify?.api_mapping ?? {},
24
+ trigger,
25
+ } = source;
26
+
27
+ if (!name) throw new Error('name is required for api sources');
28
+ if (!url) throw new Error('url is required for api sources');
29
+
30
+ if (Array.isArray(trigger)) {
31
+ // Handle complex trigger configurations
32
+ const triggerValues = trigger.map((triggerItem) => {
33
+ const triggerValue =
34
+ sharedData[triggerItem.name] || sharedData[triggerItem];
35
+ if (triggerItem.title && title) title.text = triggerValue;
36
+
37
+ return triggerValue !== undefined &&
38
+ triggerValue !== null &&
39
+ triggerValue !== '' &&
40
+ !isEmpty(triggerValue)
41
+ ? api_mapping?.[triggerValue] || triggerValue
42
+ : null;
43
+ });
44
+
45
+ if (triggerValues.some((val) => val === null)) {
46
+ // If any trigger value is not available, skip this source
47
+ return null;
48
+ }
49
+
50
+ // Replace multiple {trigger} placeholders in the URL with trigger values
51
+ let finalUrl = url;
52
+ triggerValues.forEach((triggerValue) => {
53
+ finalUrl = finalUrl.replace('{trigger}', triggerValue);
54
+ });
55
+
56
+ const response = await fetchApi(finalUrl, method, body, {}, true);
57
+
58
+ return responseKey ? response[responseKey] : response;
59
+ } else if (trigger) {
60
+ // Handle simple string trigger
61
+ const triggerValue = sharedData[trigger.name] || sharedData[trigger];
62
+
63
+ if (
64
+ triggerValue !== undefined &&
65
+ triggerValue !== null &&
66
+ triggerValue !== '' &&
67
+ !isEmpty(triggerValue)
68
+ ) {
69
+ if (trigger.title && title) title.text = triggerValue;
70
+ const triggerVal = api_mapping?.[triggerValue] || triggerValue;
71
+ const finalUrl = url.replace('{trigger}', triggerVal);
72
+
73
+ const response = await fetchApi(finalUrl, method, null, {}, true);
74
+ return responseKey ? response[responseKey] : response;
75
+ }
76
+ } else {
77
+ const response = await fetchApi(url, method, null, {}, true);
78
+ return responseKey ? response[responseKey] : response;
79
+ }
80
+ };
81
+
82
+ export default conditionalFetch;
@@ -0,0 +1,92 @@
1
+ /*
2
+ * @Author : Lihao leolihao@arizona.edu
3
+ * @Date : 2023-09-22 21:19:16
4
+ * @FilePath : /visualifyjs/src/core/fetch/fetch.js
5
+ * @Description :
6
+ * Copyright (c) 2023 by Lihao (leolihao@arizona.edu), All Rights Reserved.
7
+ */
8
+
9
+ const MAX_CACHE_SIZE = 512 * 1024 * 1024; // 512MB
10
+
11
+ const cache = {};
12
+ const lruOrder = [];
13
+
14
+ // Function to manage the cache size and eviction
15
+ const manageCache = (cacheKey, data) => {
16
+ // Add the cacheKey to the LRU order
17
+ lruOrder.push(cacheKey);
18
+
19
+ // Check if the cache size exceeds the maximum limit
20
+ let currentCacheSize = 0;
21
+ for (const key of lruOrder) {
22
+ if (cache[key]) {
23
+ currentCacheSize += JSON.stringify(cache[key]).length;
24
+ }
25
+ if (currentCacheSize > MAX_CACHE_SIZE) {
26
+ // Remove the least recently used item from the cache and the LRU order
27
+ const lruKey = lruOrder.shift();
28
+ delete cache[lruKey];
29
+ }
30
+ }
31
+
32
+ // Cache the data
33
+ cache[cacheKey] = data;
34
+ };
35
+
36
+ const simplefetch = async (url, options = {}) => {
37
+ const { id = undefined, type, key, debug = false, bbox = {} } = options;
38
+ const hasBbox = Object.keys(bbox).length > 0;
39
+ const cacheKey = hasBbox ? `${id}_${JSON.stringify(bbox)}` : `${id}_nobbox`;
40
+
41
+ if (cache[cacheKey]) {
42
+ if (debug) console.warn('simplefetch: cache hit ', cacheKey);
43
+ return cache[cacheKey];
44
+ }
45
+
46
+ if (typeof url === 'object') {
47
+ if (id && id !== '') return url[id] ?? {};
48
+ else return url;
49
+ } else if (typeof url === 'string') {
50
+ let result = {};
51
+ if (type && type === 'gene' && (id === '' || id === undefined))
52
+ return {};
53
+ if (type && (type === 'gene' || type === 'metadata') && id && id !== '')
54
+ url += (url.charAt(url.length - 1) === '/' ? '' : '/') + id;
55
+ if (type && type === 'metadata' && id && id !== '' && hasBbox) {
56
+ const { xMin, yMin, xMax, yMax } = bbox;
57
+ url += `/${xMin}/${yMin}/${xMax}/${yMax}`;
58
+ //console.log('simplefetch: url', url);
59
+ }
60
+
61
+ //console.log("simplefetch: fetch url", url);
62
+ const res = await fetch(url, debug ? { mode: 'cors' } : {}); // using await here
63
+ //console.log("simplefetch: fetch res", res);
64
+
65
+ if (res.ok) {
66
+ const data = await res.json(); // using await here
67
+ if (data.code === 200) {
68
+ if (key) {
69
+ result = data[key];
70
+ } else if (type === 'metadata') result = data.metadata;
71
+ else if (type === 'gene') result = data.gene;
72
+ else if (type === 'image') result = data.image;
73
+ else result = data;
74
+
75
+ // When caching data, use the manageCache function
76
+ if (id && !cache[cacheKey]) {
77
+ manageCache(cacheKey, result);
78
+ }
79
+
80
+ if (debug) console.log('simplefetch: fetch success', result);
81
+ } else console.error('simplefetch: fetch failed', data.code, data.msg);
82
+ } else console.error('simplefetch: fetch error', res);
83
+
84
+ //console.log("simplefetch: after fetch", result);
85
+ return result;
86
+ } else {
87
+ console.error('Unsupported type or Invalid url:', url);
88
+ return {};
89
+ }
90
+ };
91
+
92
+ export default simplefetch;
@@ -0,0 +1,29 @@
1
+ /*
2
+ * @Author : Lihao leolihao@arizona.edu
3
+ * @Date : 2023-12-01 12:28:29
4
+ * @FilePath : /visualifyjs/src/core/fetch/json.js
5
+ * @Description :
6
+ * Copyright (c) 2023 by Lihao (leolihao@arizona.edu), All Rights Reserved.
7
+ */
8
+ function fetchJson(jsonFileName, externalURL) {
9
+ let jsonUrl;
10
+ if (externalURL) {
11
+ jsonUrl = externalURL;
12
+ } else {
13
+ jsonUrl = `./${jsonFileName}.json`;
14
+ }
15
+
16
+ return fetch(jsonUrl).then((response) => {
17
+ if (!response.ok) {
18
+ throw new Error(`Failed to fetch JSON for ${jsonFileName}`);
19
+ }
20
+ if (
21
+ !response.headers.get('content-type')?.includes('application/json')
22
+ ) {
23
+ throw new TypeError('Response not in JSON format');
24
+ }
25
+ return response.json();
26
+ });
27
+ }
28
+
29
+ export default fetchJson;
@@ -0,0 +1,42 @@
1
+ /*
2
+ * @Author : Lihao leolihao@arizona.edu
3
+ * @Date : 2023-12-21 16:49:22
4
+ * @FilePath : /visualifyjs/src/core/fetch/vfetch.js
5
+ * @Description :
6
+ * Copyright (c) 2023 by Lihao (leolihao@arizona.edu), All Rights Reserved.
7
+ */
8
+ const fetchApi = async (
9
+ url,
10
+ method = 'GET',
11
+ body = null,
12
+ headers = {},
13
+ debug = false,
14
+ ) => {
15
+ // Setup for CORS and headers
16
+ let fetchOptions = {
17
+ method: method,
18
+ headers: {
19
+ 'Content-Type': 'application/json',
20
+ ...headers,
21
+ },
22
+ mode: debug ? 'cors' : 'same-origin',
23
+ };
24
+
25
+ // Add body for POST request
26
+ if (method === 'POST' && body) {
27
+ fetchOptions.body = JSON.stringify(body);
28
+ }
29
+
30
+ try {
31
+ const response = await fetch(url, fetchOptions);
32
+ if (!response.ok) {
33
+ throw new Error(`HTTP error! status: ${response.status}`);
34
+ }
35
+ return await response.json();
36
+ } catch (err) {
37
+ console.error('Fetch API Error:', err);
38
+ throw err;
39
+ }
40
+ };
41
+
42
+ export default fetchApi;
@@ -0,0 +1,44 @@
1
+ /*
2
+ * @Author : Lihao leolihao@arizona.edu
3
+ * @Date : 2023-12-01 22:51:45
4
+ * @FilePath : /visualifyjs/src/core/liveEditor.js
5
+ * @Description :
6
+ * Copyright (c) 2023 by Lihao (leolihao@arizona.edu), All Rights Reserved.
7
+ */
8
+ import React from 'react';
9
+ import Recharts from './recharts';
10
+ import { createRoot } from 'react-dom/client';
11
+ import CodeEditorWithPreview from './modules/codeEditorWithPreview';
12
+ import { VisualifyProvider } from './appContext';
13
+ class LiveEditor extends Recharts {
14
+ constructor(config) {
15
+ super();
16
+ this.config = config;
17
+ }
18
+
19
+ mount(selector) {
20
+ const el =
21
+ typeof selector === 'string'
22
+ ? document.querySelector(selector)
23
+ : selector;
24
+
25
+ if (!el) {
26
+ throw new Error(`Element not found with selector: ${selector}`);
27
+ }
28
+
29
+ try {
30
+ // Assuming createRoot is imported from 'react-dom/client'
31
+ const charts = createRoot(el);
32
+ charts.render(
33
+ <VisualifyProvider>
34
+ <CodeEditorWithPreview config={this.config} />
35
+ </VisualifyProvider>,
36
+ );
37
+ console.log('mounted ' + selector, el);
38
+ } catch (e) {
39
+ console.error('Error mounting chart:', e);
40
+ }
41
+ }
42
+ }
43
+
44
+ export default LiveEditor;