Skip to content

Commit 0e69037

Browse files
committed
String demo data loader: allowed space as separator, added route to fetch single nodes, fix when entering 0 score in form
1 parent 7f7f35f commit 0e69037

2 files changed

Lines changed: 150 additions & 27 deletions

File tree

src/gll.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ async function loadDemoData() {
325325
formContent.innerHTML = `
326326
<h3>Load STRING Demo Data</h3>
327327
<div style="margin-bottom: 10px;">
328-
<label for="genes-input" style="display: block; margin-bottom: 5px;">Genes (comma-separated):</label>
328+
<label for="genes-input" style="display: block; margin-bottom: 5px;">Genes (comma- or space-separated):</label>
329329
<input type="text" id="genes-input" value="TP53" style="width: 100%; padding: 5px; border: 1px solid #ccc; border-radius: 3px;">
330330
</div>
331331
<div style="margin-bottom: 10px;">
@@ -374,10 +374,10 @@ async function loadDemoData() {
374374
return;
375375
}
376376

377-
const genes = genesText.split(',').map(g => g.trim()).filter(g => g);
377+
const genes = genesText.split(/[\s,]+/).map(g => g.trim()).filter(g => g);
378378
const species = parseInt(speciesInput.value) || 9606;
379-
const amountOfNodes = parseInt(nodesInput.value) || 50;
380-
const requiredScore = parseInt(scoreInput.value) || 400;
379+
const amountOfNodes = nodesInput.value !== '' ? parseInt(nodesInput.value) : 50;
380+
const requiredScore = scoreInput.value !== '' ? parseInt(scoreInput.value) : 400;
381381

382382
if (genes.length === 0) {
383383
cache.ui.error('Please enter at least one valid gene');

src/utilities/demo_loader.js

Lines changed: 146 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,38 +8,161 @@ class StringDemoDataLoader {
88
this.requiredScore = requiredScore;
99
}
1010

11-
async loadNetwork() {
12-
const params = new URLSearchParams({
13-
identifiers: this.genes.join('%0d'),
14-
species: this.species,
15-
add_nodes: this.amountOfNodes,
16-
required_score: this.requiredScore
11+
async loadNetwork() {
12+
if (this.genes.length === 1 && this.amountOfNodes === 0) {
13+
return await this._loadSingleProtein();
14+
}
15+
16+
const params = new URLSearchParams({
17+
identifiers: this.genes.join('%0d'),
18+
species: this.species,
19+
add_nodes: this.amountOfNodes,
20+
required_score: this.requiredScore
21+
});
22+
23+
const url = `${this.baseUrl}/network?${params}`;
24+
25+
await this.cache.ui.showLoading("Demo Data", `Loading STRING network with ${this.genes.length} genes: ${this.genes.join(',')}, ${this.amountOfNodes} additional nodes, species ${this.species}, minimum confidence: ${this.requiredScore}. URL: ${url}`);
26+
try {
27+
const stringData = await this._fetchFromString(url);
28+
29+
if (!stringData || stringData.length === 0) {
30+
this.cache.ui.warning('No interaction data returned from STRING');
31+
return null;
32+
}
33+
34+
const allProteins = new Set();
35+
stringData.forEach(interaction => {
36+
allProteins.add(interaction.preferredName_A);
37+
allProteins.add(interaction.preferredName_B);
1738
});
1839

19-
const url = `${this.baseUrl}/network?${params}`;
40+
const annotations = await this._fetchFunctionalAnnotations(Array.from(allProteins));
41+
return this._convertToAppFormat(stringData, annotations);
42+
} catch (err) {
43+
this.cache.ui.error(`Failed to load STRING network. Make sure gene symbols and species ID exist. ${url}`);
44+
return null;
45+
}
46+
}
47+
48+
async _loadSingleProtein() {
49+
const params = new URLSearchParams({
50+
identifiers: this.genes[0],
51+
species: this.species
52+
});
2053

21-
await this.cache.ui.showLoading("Demo Data", `Loading STRING network with ${this.genes.length} genes: ${this.genes.join(',')}, ${this.amountOfNodes} additional nodes, species ${this.species}, minimum confidence: ${this.requiredScore}. URL: ${url}`);
22-
try {
23-
const stringData = await this._fetchFromString(url);
54+
const url = `${this.baseUrl}/get_string_ids?${params}`;
2455

25-
if (!stringData || stringData.length === 0) {
26-
this.cache.ui.warning('No interaction data returned from STRING');
27-
return null;
28-
}
56+
await this.cache.ui.showLoading("Demo Data", `Loading protein info for ${this.genes[0]}, species ${this.species}. URL: ${url}`);
2957

30-
const allProteins = new Set();
31-
stringData.forEach(interaction => {
32-
allProteins.add(interaction.preferredName_A);
33-
allProteins.add(interaction.preferredName_B);
34-
});
58+
try {
59+
const stringData = await this._fetchFromString(url);
3560

36-
const annotations = await this._fetchFunctionalAnnotations(Array.from(allProteins));
37-
return this._convertToAppFormat(stringData, annotations);
38-
} catch (err) {
39-
this.cache.ui.error(`Failed to load STRING network. Make sure gene symbols and species ID exist. ${url}`);
61+
if (!stringData || stringData.length === 0) {
62+
this.cache.ui.warning('No protein data returned from STRING');
4063
return null;
4164
}
65+
66+
const proteinInfo = stringData[0];
67+
const annotationData = await this._fetchFunctionalAnnotations([proteinInfo.preferredName]);
68+
69+
// Convert single protein to app format (no edges, just one node)
70+
return this._convertSingleProteinToAppFormat(proteinInfo, annotationData);
71+
} catch (err) {
72+
this.cache.ui.error(`Failed to load protein info from STRING. Make sure gene symbol and species ID exist. ${url}`);
73+
return null;
4274
}
75+
}
76+
77+
_convertSingleProteinToAppFormat(proteinInfo, annotationData) {
78+
// Process annotations the same way as in _convertToAppFormat
79+
const annotationsBySourceAndSubcategory = new Map();
80+
const proteinToAnnotations = new Map();
81+
82+
annotationData.forEach(ann => {
83+
const category = ann.category;
84+
const description = this._sanitizeForAST(ann.description);
85+
const proteins = ann.preferredNames || [];
86+
87+
const { source, subcategory } = this._getSourceAndSubcategory(category);
88+
89+
if (!annotationsBySourceAndSubcategory.has(source)) {
90+
annotationsBySourceAndSubcategory.set(source, new Map());
91+
}
92+
if (!annotationsBySourceAndSubcategory.get(source).has(subcategory)) {
93+
annotationsBySourceAndSubcategory.get(source).set(subcategory, new Set());
94+
}
95+
annotationsBySourceAndSubcategory.get(source).get(subcategory).add(description);
96+
97+
proteins.forEach(protein => {
98+
if (!proteinToAnnotations.has(protein)) {
99+
proteinToAnnotations.set(protein, new Map());
100+
}
101+
if (!proteinToAnnotations.get(protein).has(source)) {
102+
proteinToAnnotations.get(protein).set(source, new Map());
103+
}
104+
if (!proteinToAnnotations.get(protein).get(source).has(subcategory)) {
105+
proteinToAnnotations.get(protein).get(source).set(subcategory, []);
106+
}
107+
proteinToAnnotations.get(protein).get(source).get(subcategory).push(description);
108+
});
109+
});
110+
111+
// Filter out categories with only 1 unique value
112+
const filteredAnnotations = new Map();
113+
annotationsBySourceAndSubcategory.forEach((subcategories, source) => {
114+
const filteredSubcategories = new Map();
115+
subcategories.forEach((annotations, subcategory) => {
116+
if (annotations.size > 1) {
117+
filteredSubcategories.set(subcategory, annotations);
118+
}
119+
});
120+
if (filteredSubcategories.size > 0) {
121+
filteredAnnotations.set(source, filteredSubcategories);
122+
}
123+
});
124+
125+
// Build headers
126+
const nodeDataHeaders = [];
127+
filteredAnnotations.forEach((subcategories, source) => {
128+
subcategories.forEach((annotations, subcategory) => {
129+
nodeDataHeaders.push({subGroup: source, key: subcategory});
130+
});
131+
});
132+
133+
// Build node with filters
134+
const proteinAnnotations = proteinToAnnotations.get(proteinInfo.preferredName) || new Map();
135+
const nodeFilters = {};
136+
137+
filteredAnnotations.forEach((subcategories, source) => {
138+
if (!nodeFilters[source]) {
139+
nodeFilters[source] = {};
140+
}
141+
142+
subcategories.forEach((annotations, subcategory) => {
143+
const proteinSourceAnnotations = proteinAnnotations.get(source) || new Map();
144+
const proteinSubcategoryAnnotations = proteinSourceAnnotations.get(subcategory) || [];
145+
nodeFilters[source][subcategory] = proteinSubcategoryAnnotations.length > 0 ?
146+
proteinSubcategoryAnnotations[0] : `No ${subcategory}`;
147+
});
148+
});
149+
150+
return {
151+
nodes: [{
152+
id: proteinInfo.stringId,
153+
label: proteinInfo.preferredName,
154+
style: {
155+
labelText: proteinInfo.preferredName,
156+
},
157+
D4Data: {
158+
'Node filters': nodeFilters
159+
}
160+
}],
161+
edges: [],
162+
nodeDataHeaders,
163+
edgeDataHeaders: []
164+
};
165+
}
43166

44167
_getEdgeColor(score, minScore, maxScore) {
45168
const normalizedScore = maxScore === minScore ? 0 : (score - minScore) / (maxScore - minScore);

0 commit comments

Comments
 (0)