@@ -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