TSUMUGI 1.0.1__py3-none-any.whl

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 (64) hide show
  1. TSUMUGI/annotator.py +103 -0
  2. TSUMUGI/argparser.py +599 -0
  3. TSUMUGI/core.py +185 -0
  4. TSUMUGI/data/impc_phenodigm.csv +3406 -0
  5. TSUMUGI/data/mp.obo +143993 -0
  6. TSUMUGI/filterer.py +36 -0
  7. TSUMUGI/formatter.py +122 -0
  8. TSUMUGI/genewise_annotation_builder.py +94 -0
  9. TSUMUGI/io_handler.py +189 -0
  10. TSUMUGI/main.py +300 -0
  11. TSUMUGI/network_constructor.py +603 -0
  12. TSUMUGI/ontology_handler.py +62 -0
  13. TSUMUGI/pairwise_similarity_builder.py +66 -0
  14. TSUMUGI/report_generator.py +122 -0
  15. TSUMUGI/similarity_calculator.py +498 -0
  16. TSUMUGI/subcommands/count_filterer.py +47 -0
  17. TSUMUGI/subcommands/genes_filterer.py +89 -0
  18. TSUMUGI/subcommands/graphml_builder.py +158 -0
  19. TSUMUGI/subcommands/life_stage_filterer.py +48 -0
  20. TSUMUGI/subcommands/mp_filterer.py +142 -0
  21. TSUMUGI/subcommands/score_filterer.py +22 -0
  22. TSUMUGI/subcommands/sex_filterer.py +48 -0
  23. TSUMUGI/subcommands/webapp_builder.py +358 -0
  24. TSUMUGI/subcommands/zygosity_filterer.py +48 -0
  25. TSUMUGI/validator.py +65 -0
  26. TSUMUGI/web/app/css/app.css +1129 -0
  27. TSUMUGI/web/app/genelist/network_genelist.html +339 -0
  28. TSUMUGI/web/app/genelist/network_genelist.js +421 -0
  29. TSUMUGI/web/app/js/data/dataLoader.js +41 -0
  30. TSUMUGI/web/app/js/export/graphExporter.js +214 -0
  31. TSUMUGI/web/app/js/graph/centrality.js +495 -0
  32. TSUMUGI/web/app/js/graph/components.js +30 -0
  33. TSUMUGI/web/app/js/graph/filters.js +158 -0
  34. TSUMUGI/web/app/js/graph/highlighter.js +52 -0
  35. TSUMUGI/web/app/js/graph/layoutController.js +454 -0
  36. TSUMUGI/web/app/js/graph/valueScaler.js +43 -0
  37. TSUMUGI/web/app/js/search/geneSearcher.js +93 -0
  38. TSUMUGI/web/app/js/search/phenotypeSearcher.js +292 -0
  39. TSUMUGI/web/app/js/ui/dynamicFontSize.js +30 -0
  40. TSUMUGI/web/app/js/ui/mobilePanel.js +77 -0
  41. TSUMUGI/web/app/js/ui/slider.js +22 -0
  42. TSUMUGI/web/app/js/ui/tooltips.js +514 -0
  43. TSUMUGI/web/app/js/viewer/pageSetup.js +217 -0
  44. TSUMUGI/web/app/viewer.html +515 -0
  45. TSUMUGI/web/app/viewer.js +1593 -0
  46. TSUMUGI/web/css/sanitize.css +363 -0
  47. TSUMUGI/web/css/top.css +391 -0
  48. TSUMUGI/web/image/tsumugi-favicon.ico +0 -0
  49. TSUMUGI/web/image/tsumugi-icon.png +0 -0
  50. TSUMUGI/web/image/tsumugi-logo.png +0 -0
  51. TSUMUGI/web/image/tsumugi-logo.svg +69 -0
  52. TSUMUGI/web/js/genelist_formatter.js +123 -0
  53. TSUMUGI/web/js/top.js +338 -0
  54. TSUMUGI/web/open_webapp_linux.sh +25 -0
  55. TSUMUGI/web/open_webapp_mac.command +25 -0
  56. TSUMUGI/web/open_webapp_windows.bat +37 -0
  57. TSUMUGI/web/serve_index.py +110 -0
  58. TSUMUGI/web/template/template_index.html +197 -0
  59. TSUMUGI/web_deployer.py +150 -0
  60. tsumugi-1.0.1.dist-info/METADATA +504 -0
  61. tsumugi-1.0.1.dist-info/RECORD +64 -0
  62. tsumugi-1.0.1.dist-info/WHEEL +4 -0
  63. tsumugi-1.0.1.dist-info/entry_points.txt +3 -0
  64. tsumugi-1.0.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,391 @@
1
+ /* ======================================================== */
2
+ /* General body styling */
3
+ /* ======================================================== */
4
+ body {
5
+ color: #333;
6
+ background-color: #FFEBD8;
7
+ font-family: 'Lato', 'Arial', 'Helvetica', sans-serif;
8
+ font-size: 1.2em;
9
+ min-height: 100vh;
10
+ margin: 0 auto;
11
+ padding-top: 50px;
12
+ display: flex;
13
+ justify-content: flex-start;
14
+ text-align: center;
15
+ align-items: center;
16
+ flex-direction: column;
17
+ }
18
+
19
+ h1 {
20
+ margin: 0 auto;
21
+ }
22
+
23
+ /* Inherit font settings for form elements */
24
+ button,
25
+ input,
26
+ textarea,
27
+ select {
28
+ color: inherit;
29
+ font-size: inherit;
30
+ font-family: inherit;
31
+ }
32
+
33
+ /* Subtitle styling */
34
+ p.subtitle {
35
+ font-family: 'Lora', serif;
36
+ margin-bottom: 20px;
37
+ }
38
+
39
+ /* ======================================================== */
40
+ /* Form container styling */
41
+ /* ======================================================== */
42
+ form {
43
+ background-color: #fefefe;
44
+ padding: 30px;
45
+ border-radius: 10px;
46
+ max-width: 600px;
47
+ width: 100%;
48
+ margin: 20px 0 40px;
49
+ }
50
+
51
+ /* Image styling */
52
+ img {
53
+ width: 80%;
54
+ height: auto;
55
+ }
56
+
57
+ /* Label styling */
58
+ label {
59
+ margin-bottom: 10px;
60
+ display: block;
61
+ }
62
+
63
+ /* Text input field styling */
64
+ input[type="text"] {
65
+ width: 80%;
66
+ padding: 10px;
67
+ border: 2px solid #ccc;
68
+ border-radius: 10px;
69
+ margin-bottom: 10px;
70
+ box-sizing: border-box;
71
+ outline: none;
72
+ }
73
+
74
+ input[type="text"]:focus {
75
+ border-color: #ccc;
76
+ outline: none;
77
+ }
78
+
79
+ /* -------------------------------------------------------- */
80
+ /* Suggestions list styling */
81
+ /* -------------------------------------------------------- */
82
+
83
+ ul.suggestions {
84
+ list-style: none;
85
+ padding: 0;
86
+ border: 1px solid #ccc;
87
+ border-radius: 10px;
88
+ max-height: 30vh;
89
+ overflow-y: auto;
90
+ }
91
+
92
+ ul.suggestions li {
93
+ padding: 10px;
94
+ cursor: pointer;
95
+ background-color: #fff;
96
+ transition: background-color 0.3s ease;
97
+ }
98
+
99
+ ul.suggestions li:hover {
100
+ background-color: #FFEBD8;
101
+ }
102
+
103
+ /* -------------------------------------------------------- */
104
+ /* Tab styling */
105
+ /* -------------------------------------------------------- */
106
+
107
+ .Tab-container {
108
+ display: flex;
109
+ flex-wrap: nowrap;
110
+ justify-content: center;
111
+ gap: 5px;
112
+ flex-direction: row;
113
+ }
114
+
115
+ button.Tab {
116
+ flex: 0 1 auto;
117
+ padding: 10px 20px;
118
+ margin: 0 5px;
119
+ border: 2px solid #ff7800;
120
+ border-radius: 10px;
121
+ background-color: #fff;
122
+ cursor: pointer;
123
+ transition: background-color 0.3s ease, color 0.3s ease;
124
+ position: relative;
125
+ z-index: 1;
126
+
127
+ display: flex;
128
+ align-items: center;
129
+ justify-content: center;
130
+ text-align: center;
131
+ }
132
+
133
+ button.Tab:hover {
134
+ background-color: #ffe2c2;
135
+ }
136
+
137
+ button.Tab.active-tab {
138
+ background-color: #ff7800;
139
+ color: #fff;
140
+ }
141
+
142
+ button.Tab.active-tab::after {
143
+ content: '';
144
+ width: 0;
145
+ height: 0;
146
+ border-style: solid;
147
+ border-width: 10px 10px 0 10px;
148
+ border-color: #ff7800 transparent transparent transparent;
149
+ position: absolute;
150
+ bottom: -10px;
151
+ left: 50%;
152
+ margin-left: -10px;
153
+ }
154
+
155
+ /* -------------------------------------------------------- */
156
+ /* Input form of gene lists */
157
+ /* -------------------------------------------------------- */
158
+ textarea.geneList {
159
+ max-width: 1200px;
160
+ height: 120px;
161
+ padding: 12px;
162
+ border: 2px solid #ccc;
163
+ border-radius: 10px;
164
+ resize: both;
165
+ }
166
+
167
+ textarea.geneList:focus {
168
+ border-color: #ccc;
169
+ outline: none;
170
+ }
171
+
172
+ /* -------------------------------------------------------- */
173
+ /* Submit button styling */
174
+ /* -------------------------------------------------------- */
175
+ button.submitBtn {
176
+ padding: 12px 20px;
177
+ color: #fff;
178
+ background-color: #ff7800;
179
+ border: none;
180
+ border-radius: 10px;
181
+ cursor: pointer;
182
+ transition: background-color 0.3s ease;
183
+ }
184
+
185
+ button.submitBtn:disabled {
186
+ background-color: #999;
187
+ cursor: not-allowed;
188
+ }
189
+
190
+ button.submitBtn:hover:enabled {
191
+ background-color: #e66900;
192
+ }
193
+
194
+
195
+ /* ======================================================== */
196
+ /* Resources Section */
197
+ /* ======================================================== */
198
+
199
+ .resources-container {
200
+ max-width: 800px;
201
+ margin: 0 auto 40px;
202
+ display: flex;
203
+ flex-wrap: wrap;
204
+ gap: 30px;
205
+ justify-content: center;
206
+ }
207
+
208
+ .resource-section {
209
+ flex: 1;
210
+ min-width: 280px;
211
+ text-align: center;
212
+ }
213
+
214
+ .resource-section h3 {
215
+ margin-bottom: 15px;
216
+ font-size: 1.1em;
217
+ color: #555;
218
+ font-weight: normal;
219
+ }
220
+
221
+ .resource-buttons {
222
+ display: flex;
223
+ flex-direction: column;
224
+ gap: 10px;
225
+ }
226
+
227
+ .resource-btn {
228
+ display: inline-flex;
229
+ align-items: center;
230
+ justify-content: center;
231
+ background-color: #fff;
232
+ color: #555;
233
+ padding: 10px 16px;
234
+ border: 2px solid #ccc;
235
+ border-radius: 10px;
236
+ text-decoration: none;
237
+ font-size: 0.95em;
238
+ transition: all 0.2s ease;
239
+ }
240
+
241
+ .resource-btn:hover {
242
+ color: #333;
243
+ border-color: #ff7800;
244
+ background-color: rgba(255, 120, 0, 0.05);
245
+ }
246
+
247
+ .resource-btn i {
248
+ margin-right: 6px;
249
+ font-size: 0.9em;
250
+ }
251
+
252
+ /* ======================================================== */
253
+ /* Info Tooltip Styles */
254
+ /* ======================================================== */
255
+ .info-tooltip-container {
256
+ position: relative;
257
+ display: inline-block;
258
+ margin-left: 8px;
259
+ }
260
+
261
+ .info-tooltip-icon {
262
+ display: inline-flex;
263
+ align-items: center;
264
+ justify-content: center;
265
+ width: 16px;
266
+ height: 16px;
267
+ border-radius: 50%;
268
+ background-color: #555;
269
+ color: white;
270
+ font-size: 12px;
271
+ font-weight: bold;
272
+ cursor: pointer;
273
+ transition: background-color 0.2s ease;
274
+ }
275
+
276
+ .info-tooltip-icon:hover {
277
+ background-color: #333;
278
+ }
279
+
280
+ .info-tooltip-content {
281
+ visibility: hidden;
282
+ opacity: 0;
283
+ position: absolute;
284
+ bottom: 25px;
285
+ left: 50%;
286
+ transform: translateX(-50%);
287
+ background-color: #333;
288
+ color: white;
289
+ text-align: left;
290
+ padding: 8px 12px;
291
+ border-radius: 6px;
292
+ font-size: 14px;
293
+ font-weight: normal;
294
+ line-height: 1.4;
295
+ white-space: nowrap;
296
+ z-index: 1000;
297
+ transition: opacity 0.3s ease, visibility 0.3s ease;
298
+ box-shadow: 0 2px 10px rgba(0,0,0,0.2);
299
+ max-width: 300px;
300
+ white-space: normal;
301
+ }
302
+
303
+ .info-tooltip-content::after {
304
+ content: "";
305
+ position: absolute;
306
+ top: 100%;
307
+ left: 50%;
308
+ transform: translateX(-50%);
309
+ border: 5px solid transparent;
310
+ border-top-color: #333;
311
+ }
312
+
313
+ .info-tooltip-container:hover .info-tooltip-content,
314
+ .info-tooltip-container.active .info-tooltip-content {
315
+ visibility: visible;
316
+ opacity: 1;
317
+ }
318
+
319
+ .info-tooltip-content a {
320
+ color: #4fc3f7;
321
+ text-decoration: underline;
322
+ }
323
+
324
+ .info-tooltip-content a:hover {
325
+ color: #81d4fa;
326
+ }
327
+
328
+ /* ======================================================== */
329
+ /* Footer styling */
330
+ /* ======================================================== */
331
+
332
+ footer {
333
+ margin: 20px 0 10px;
334
+ padding: 20px;
335
+ width: 100%;
336
+ color: #666;
337
+ font-size: 0.9em;
338
+ }
339
+
340
+ footer p {
341
+ margin: 5px 0 0;
342
+ }
343
+
344
+ footer i {
345
+ margin-right: 8px;
346
+ }
347
+
348
+ footer a {
349
+ color: #666;
350
+ font-size: 1em;
351
+ text-decoration: none;
352
+ font-weight: bold;
353
+ }
354
+
355
+ footer a:hover {
356
+ text-decoration: underline;
357
+ }
358
+
359
+
360
+
361
+ /* ======================================================== */
362
+ /* Responsive design for mobile devices */
363
+ /* ======================================================== */
364
+ @media screen and (max-width: 600px) {
365
+ body {
366
+ font-size: 1em;
367
+ padding-top: 20px;
368
+ }
369
+
370
+ form {
371
+ width: 90%;
372
+ }
373
+
374
+ button.Tab {
375
+ padding: 8px 10px;
376
+ font-size: 0.9em;
377
+ margin: 2px;
378
+ min-width: 30px;
379
+ white-space: nowrap;
380
+ }
381
+
382
+ .resources-container {
383
+ flex-direction: column;
384
+ gap: 20px;
385
+ margin-bottom: 30px;
386
+ }
387
+
388
+ .resource-section {
389
+ min-width: auto;
390
+ }
391
+ }
Binary file
Binary file
Binary file
@@ -0,0 +1,69 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <svg
3
+ width="211.125pt"
4
+ height="89.0625pt"
5
+ viewBox="0 0 211.125 89.0625"
6
+ version="1.1"
7
+ id="svg9"
8
+ xml:space="preserve"
9
+ xmlns="http://www.w3.org/2000/svg"
10
+ xmlns:svg="http://www.w3.org/2000/svg"
11
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
12
+ xmlns:cc="http://creativecommons.org/ns#"
13
+ xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
14
+ id="metadata1"><rdf:RDF><cc:Work><dc:type
15
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:date>2024-09-02T05:33:13.416149</dc:date><dc:format>image/svg+xml</dc:format><dc:creator><cc:Agent><dc:title>Matplotlib v3.6.3, https://matplotlib.org/</dc:title></cc:Agent></dc:creator></cc:Work></rdf:RDF></metadata><defs
16
+ id="defs1"><style
17
+ type="text/css"
18
+ id="style1">*{stroke-linejoin: round; stroke-linecap: butt}</style></defs><text
19
+ xml:space="preserve"
20
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:50px;line-height:1;font-family:Rubik;-inkscape-font-specification:Rubik;text-align:center;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;fill:#241f31;stroke:none;stroke-width:1;stroke-dasharray:none;paint-order:stroke fill markers"
21
+ x="9.4352112"
22
+ y="38.838974"
23
+ id="text9"><tspan
24
+ id="tspan9"
25
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:50px;font-family:Sitka;-inkscape-font-specification:Sitka;fill:#241f31;stroke:#241f31;stroke-width:1;stroke-dasharray:none"
26
+ x="9.4352112"
27
+ y="38.838974">TSUMUGI</tspan></text><defs
28
+ id="defs9" /><g
29
+ id="g36"
30
+ transform="translate(-513.71215,-178.53485)"><path
31
+ style="fill:none;stroke:#9a9996;stroke-width:2;stroke-linecap:round;stroke-linejoin:bevel;stroke-dasharray:none;paint-order:stroke fill markers"
32
+ d="m 554.80558,244.25989 c 0,0 -3.11994,-9.82714 9.57484,-14.20237 8.68817,-2.81702 26.68892,6.90156 21.03104,14.87692 -2.04715,6.88644 14.82807,12.80336 18.94991,11.23936 11.80553,-4.47952 -2.22922,-18.21465 5.05772,-25.7882 2.41151,-2.50636 10.10116,-4.69468 19.69798,-1.6373 15.0872,4.80653 4.03664,16.85815 6.35645,23.20184 3.04075,4.5015 1.98067,9.04247 13.54931,8.30912 9.65289,-1.68304 9.27859,-8.0446 8.44725,-12.22987 -1.15959,-5.83777 -6.53765,-15.52976 2.87892,-17.84795 4.57253,-1.22483 11.06863,-1.20626 14.54124,7.71923 1.39314,3.58072 8.78514,4.46414 12.84501,4.87374"
33
+ id="path31-4" /><circle
34
+ style="fill:#ff7800;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;paint-order:stroke fill markers"
35
+ id="circle31-5"
36
+ cx="555.45587"
37
+ cy="244.82098"
38
+ r="6.4558659" /><circle
39
+ style="fill:#ff7800;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;paint-order:stroke fill markers"
40
+ id="circle32-8"
41
+ cx="585.62347"
42
+ cy="245.68243"
43
+ r="6.4558659" /><circle
44
+ style="fill:#ff7800;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;paint-order:stroke fill markers"
45
+ id="circle33-6"
46
+ cx="610.17987"
47
+ cy="230.70587"
48
+ r="6.4558659" /><circle
49
+ style="fill:#ff7800;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;paint-order:stroke fill markers"
50
+ id="circle34-3"
51
+ cx="636.00336"
52
+ cy="251.17627"
53
+ r="6.4558659" /><circle
54
+ style="fill:#ff7800;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;paint-order:stroke fill markers"
55
+ id="circle35-8"
56
+ cx="657.3017"
57
+ cy="248.20645"
58
+ r="6.4558659" /><circle
59
+ style="fill:#ff7800;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;paint-order:stroke fill markers"
60
+ id="circle36-8"
61
+ cx="688.01227"
62
+ cy="242.42432"
63
+ r="6.4558659" /></g><rect
64
+ style="fill:none;stroke:none;stroke-width:0.999997;stroke-linecap:round;stroke-linejoin:bevel;stroke-dasharray:none;paint-order:stroke fill markers"
65
+ id="rect36"
66
+ width="211.125"
67
+ height="89.0625"
68
+ x="-1.1368684e-13"
69
+ y="-2.8421709e-14" /></svg>
@@ -0,0 +1,123 @@
1
+ export async function fetchGzippedJson(url) {
2
+ try {
3
+ const response = await fetch(url);
4
+ if (!response.ok) {
5
+ throw new Error(`HTTP error: ${response.status}`);
6
+ }
7
+ const compressedData = await response.arrayBuffer();
8
+ const decompressedData = pako.inflate(compressedData, { to: "string" });
9
+ return JSON.parse(decompressedData);
10
+ } catch (error) {
11
+ console.error(`Data fetch error (${url}):`, error);
12
+ return null; // Skip on error
13
+ }
14
+ }
15
+
16
+ export function filterJson(jsonDataList, geneKeys) {
17
+ const elementsMap = new Map();
18
+
19
+ const serializePhenotypes = (phenotype) => {
20
+ if (Array.isArray(phenotype)) {
21
+ return JSON.stringify([...phenotype].sort());
22
+ }
23
+ if (typeof phenotype === "string") {
24
+ return JSON.stringify([phenotype]);
25
+ }
26
+ return JSON.stringify([]);
27
+ };
28
+
29
+ const buildEdgeKey = (data) => {
30
+ if (!("source" in data) || !("target" in data)) return null;
31
+ const pairKey = [data.source, data.target].sort().join("||");
32
+ return `edge:${pairKey}|${serializePhenotypes(data.phenotype)}`;
33
+ };
34
+
35
+ const buildNodeKey = (data) => ("id" in data ? `node:${data.id}` : null);
36
+
37
+ const upsertEdge = (key, item) => {
38
+ const existing = elementsMap.get(key);
39
+ const newEdgeSize = Number.isFinite(item.data.edge_size) ? item.data.edge_size : Number.NEGATIVE_INFINITY;
40
+ if (!existing) {
41
+ elementsMap.set(key, item);
42
+ return;
43
+ }
44
+ const existingEdgeSize = Number.isFinite(existing.data.edge_size)
45
+ ? existing.data.edge_size
46
+ : Number.NEGATIVE_INFINITY;
47
+ if (newEdgeSize > existingEdgeSize) {
48
+ elementsMap.set(key, item);
49
+ }
50
+ };
51
+
52
+ jsonDataList.forEach((jsonData) => {
53
+ jsonData.forEach((item) => {
54
+ const data = item.data;
55
+
56
+ if ("node_color" in data && data.node_color !== 1) return;
57
+
58
+ const isEdge = "source" in data && "target" in data;
59
+
60
+ if (isEdge) {
61
+ if (!geneKeys.includes(data.source) || !geneKeys.includes(data.target)) return;
62
+ const edgeKey = buildEdgeKey(data);
63
+ if (!edgeKey) return;
64
+ upsertEdge(edgeKey, item);
65
+ return;
66
+ }
67
+
68
+ if ("id" in data && !geneKeys.includes(data.id)) return;
69
+
70
+ const nodeKey = buildNodeKey(data);
71
+ if (!nodeKey) return;
72
+ if (!elementsMap.has(nodeKey)) {
73
+ elementsMap.set(nodeKey, item);
74
+ }
75
+ });
76
+ });
77
+
78
+ return Array.from(elementsMap.values());
79
+ }
80
+
81
+ export async function fetchGeneData() {
82
+ let jsonDataList = [];
83
+
84
+ const geneList = document.getElementById("geneList").value;
85
+ const geneKeys = geneList
86
+ .split(/\r?\n/)
87
+ .map((gene) => gene.trim())
88
+ .filter((gene) => gene !== "");
89
+
90
+ const fetchPromises = geneKeys.map((gene) => fetchGzippedJson(`./data/genesymbol/${gene}.json.gz`));
91
+
92
+ const results = await Promise.all(fetchPromises);
93
+ jsonDataList = results.filter((data) => data !== null);
94
+
95
+ const elements = filterJson(jsonDataList, geneKeys);
96
+
97
+ const uniqueIds = new Set(elements.map((el) => el.data.id).filter((id) => id !== undefined));
98
+
99
+ if (uniqueIds.size === 0) {
100
+ alert("No similar phenotypes were found among the entered genes.");
101
+ return;
102
+ } else if (uniqueIds.size >= 200) {
103
+ alert("Too many genes submitted. Please limit the number to 200 or fewer.");
104
+ return;
105
+ }
106
+
107
+ localStorage.removeItem("elements");
108
+ localStorage.setItem("elements", JSON.stringify(elements));
109
+ const query = new URLSearchParams({
110
+ mode: "genelist",
111
+ name: "geneList",
112
+ title: "Gene List",
113
+ });
114
+ window.open(`./app/viewer.html?${query.toString()}`, "_blank");
115
+ }
116
+
117
+ // Expose for form submission handler
118
+ window.fetchGeneData = fetchGeneData;
119
+
120
+ // Assign event listener to button
121
+ document.addEventListener("DOMContentLoaded", () => {
122
+ document.getElementById("submitBtn_List").addEventListener("click", fetchGeneData);
123
+ });