11import { loadMVS } from 'molstar/lib/extensions/mvs/load' ;
22import { MVSData , Snapshot } from 'molstar/lib/extensions/mvs/mvs-data' ;
33import Builder from 'molstar/lib/extensions/mvs/tree/mvs/mvs-builder' ;
4- import { ColorT , ComponentExpressionT } from 'molstar/lib/extensions/mvs/tree/mvs/param-types' ;
4+ import { MVSNodeParams } from 'molstar/lib/extensions/mvs/tree/mvs/mvs-tree' ;
5+ import { ComponentExpressionT , HexColorT } from 'molstar/lib/extensions/mvs/tree/mvs/param-types' ;
56import { PluginContext } from 'molstar/lib/mol-plugin/context' ;
67import { Color } from 'molstar/lib/mol-util/color' ;
78import { normalizeColor } from '../../helpers' ;
89import { PisaAssembliesData , PisaAssemblyRecord , PisaInterfaceData , PisaTransform } from './types' ;
910
1011
11- const ManyDistinctColors : ColorT [ ] = [
12- '#1b9e77' , '#d95f02' , '#7570b3' , '#e7298a' , '#66a61e' , '#e6ab02' , '#a6761d' ,
13- '#e41a1c' , '#377eb8' , '#4daf4a' , '#984ea3' , '#ff7f00' , '#ffff33' , '#a65628' , '#f781bf' ,
14- '#66c2a5' , '#fc8d62' , '#8da0cb' , '#e78ac3' , '#a6d854' , '#ffd92f' , '#e5c494' ,
12+ const COMPONENT_COLORS : HexColorT [ ] = [
13+ '#1b9e77' , '#d95f02' , '#7570b3' , '#e7298a' , '#66a61e' , '#e6ab02' , '#a6761d' , // Dark-2 without gray
14+ '#1f77b4' , '#2ca02c' , '#d62728' , '#927ba7' , '#8c564b' , '#e377c2' , '#bcbd22' , '#17becf' , // More non-conflicting colors from other palettes
15+ '#fc8d62' , '#9eb9f3' , '#ff9da7' , '#ffff33' , '#8be0a4' , '#e15759' , '#c69fbb' , '#76b7b2' , // More non-conflicting colors from other palettes
16+ // 'many-distinct-colors' without grays:
17+ // '#1b9e77', '#d95f02', '#7570b3', '#e7298a', '#66a61e', '#e6ab02', '#a6761d',
18+ // '#e41a1c', '#377eb8', '#4daf4a', '#984ea3', '#ff7f00', '#ffff33', '#a65628', '#f781bf',
19+ // '#66c2a5', '#fc8d62', '#8da0cb', '#e78ac3', '#a6d854', '#ffd92f', '#e5c494',
1520] ;
16- const DEFAULT_COMPONENT_COLOR : ColorT = '#999999' ;
21+ const DEFAULT_COMPONENT_COLOR : HexColorT = '#999999' ;
1722
1823async function getAssembliesData ( pdbId : string ) : Promise < PisaAssembliesData > {
1924 return await ( await fetch ( `/tmp/pisa/${ pdbId } /assembly.json` ) ) . json ( ) ;
@@ -31,13 +36,43 @@ function loadStructureData(builder: Builder.Root, pdbId: string) {
3136}
3237
3338/** Positive values for lighter interfaces, negative values for darker interfaces. */
34- const INTERFACE_COLOR_CHANGE_STRENGTH = - 1.25 ;
35- function bulkColorFn ( baseColor : ColorT ) {
36- return Color . toHexStyle ( Color . darken ( normalizeColor ( baseColor ) , INTERFACE_COLOR_CHANGE_STRENGTH ) ) as ColorT ;
39+ const INTERFACE_COLOR_CHANGE_STRENGTH = - 1 ;
40+ function bulkColorFn ( baseColor : HexColorT ) : HexColorT {
41+ return Color . toHexStyle ( Color . darken ( normalizeColor ( baseColor ) , INTERFACE_COLOR_CHANGE_STRENGTH ) ) as HexColorT ;
3742}
38- function interfaceColorFn ( baseColor : ColorT ) {
39- return Color . toHexStyle ( Color . lighten ( normalizeColor ( baseColor ) , INTERFACE_COLOR_CHANGE_STRENGTH ) ) as ColorT ;
43+ function interfaceColorFn ( baseColor : HexColorT ) : HexColorT {
44+ return Color . toHexStyle ( Color . lighten ( normalizeColor ( baseColor ) , INTERFACE_COLOR_CHANGE_STRENGTH ) ) as HexColorT ;
4045}
46+ // const BULK_COLOR_CHANGE_AMOUNT = 0.3;
47+ // const INTERFACE_COLOR_CHANGE_AMOUNT = -0.3;
48+ // function bulkColorFn(baseColor: HexColorT): HexColorT {
49+ // return lighten(baseColor, BULK_COLOR_CHANGE_AMOUNT);
50+ // }
51+ // function interfaceColorFn(baseColor: HexColorT): HexColorT {
52+ // return lighten(baseColor, INTERFACE_COLOR_CHANGE_AMOUNT);
53+ // }
54+ // function hexColorToRgb(hex: HexColorT): [number, number, number] {
55+ // const hexNum = parseInt(hex.replace('#', '0x'));
56+ // return [hexNum >> 16 & 255, hexNum >> 8 & 255, hexNum & 255];
57+ // }
58+ // function rgbToHexColor(rgb: [number, number, number]): HexColorT {
59+ // const hexNum = (rgb[0] << 16) | (rgb[1] << 8) | rgb[2];
60+ // return '#' + ('000000' + hexNum.toString(16)).slice(-6) as HexColorT;
61+ // }
62+ // function lighten(color: HexColorT, amount: number): HexColorT {
63+ // const rgb = hexColorToRgb(color);
64+ // amount = Math.min(1, Math.max(-1, amount));
65+ // if (amount > 0) {
66+ // rgb[0] = Math.round(rgb[0] * (1 - amount)) + 255 * amount;
67+ // rgb[1] = Math.round(rgb[1] * (1 - amount)) + 255 * amount;
68+ // rgb[2] = Math.round(rgb[2] * (1 - amount)) + 255 * amount;
69+ // } else if (amount < 0) {
70+ // rgb[0] = Math.round(rgb[0] * (1 + amount));
71+ // rgb[1] = Math.round(rgb[1] * (1 + amount));
72+ // rgb[2] = Math.round(rgb[2] * (1 + amount));
73+ // }
74+ // return rgbToHexColor(rgb);
75+ // }
4176
4277export async function pisaDemo ( plugin : PluginContext ) {
4378 const pdbId = '3gcb' ;
@@ -47,19 +82,21 @@ export async function pisaDemo(plugin: PluginContext) {
4782 const interfaceIds = Array . from ( new Set (
4883 allAssemblies . flatMap ( ass => ass . interfaces . interface . map ( int => int . id ) )
4984 ) ) . sort ( ( a , b ) => Number ( a ) - Number ( b ) ) ;
50- // Assembly 1: 6x8 molecules
51- // Assembly 2: 2x8 molecules (big interface)
52- // Assembly 3: 2x8 molecules (tiny interface)
53- // ASU (Assembly 4): 8 molecules
5485
5586 const snapshots : Snapshot [ ] = [ ] ;
5687 for ( const assembly of allAssemblies ) {
57- snapshots . push ( await pisaAsseblyView ( pdbId , assembly . id , { ghostRepr : false } ) ) ;
88+ snapshots . push ( await pisaAsseblyView ( pdbId , assembly . id , { } ) ) ;
89+ // snapshots.push(await pisaAsseblyView(pdbId, assembly.id, { reprParams: { type: 'surface' } }));
90+ // snapshots.push(await pisaAsseblyView(pdbId, assembly.id, { reprParams: { type: 'surface', surface_type: 'gaussian' } }));
91+ // snapshots.push(await pisaAsseblyView(pdbId, assembly.id, { reprParams: { type: 'cartoon' } }));
5892 }
5993 for ( const interfaceId of interfaceIds ) {
6094 snapshots . push ( await pisaInterfaceView ( pdbId , interfaceId , { ghostMolecules : [ ] } ) ) ;
61- snapshots . push ( await pisaInterfaceView ( pdbId , interfaceId , { ghostMolecules : [ 0 ] } ) ) ;
62- snapshots . push ( await pisaInterfaceView ( pdbId , interfaceId , { ghostMolecules : [ 1 ] } ) ) ;
95+ // snapshots.push(await pisaInterfaceView(pdbId, interfaceId, { ghostMolecules: [0] }));
96+ // snapshots.push(await pisaInterfaceView(pdbId, interfaceId, { ghostMolecules: [1] }));
97+ snapshots . push ( await pisaInterfaceView ( pdbId , interfaceId , { detailMolecules : [ 0 ] } ) ) ;
98+ snapshots . push ( await pisaInterfaceView ( pdbId , interfaceId , { detailMolecules : [ 1 ] } ) ) ;
99+ snapshots . push ( await pisaInterfaceView ( pdbId , interfaceId , { detailMolecules : [ 0 , 1 ] } ) ) ;
63100 }
64101 const mvs = MVSData . createMultistate ( snapshots ) ;
65102 console . log ( MVSData . toMVSJ ( mvs ) )
@@ -80,7 +117,7 @@ export async function pisaDemo(plugin: PluginContext) {
80117}
81118
82119
83- export async function pisaAsseblyView ( pdbId : string , assemblyId : string , options : { } = { } ) {
120+ export async function pisaAsseblyView ( pdbId : string , assemblyId : string , options : { reprParams ?: Partial < MVSNodeParams < 'representation' > > } = { } ) {
84121 const assembliesData = await getAssembliesData ( pdbId ) ;
85122 const allAssemblies = getAllAssemblies ( assembliesData ) ;
86123
@@ -109,6 +146,7 @@ export async function pisaAsseblyView(pdbId: string, assemblyId: string, options
109146 . representation ( {
110147 type : 'spacefill' ,
111148 size_factor : component . isLigand ? 1.05 : 1 , // distinguish ligands/waters from the main chain, in case of PDB format
149+ ...options . reprParams ,
112150 } )
113151 . color ( { color : bulkColorFn ( color ) } ) ;
114152 if ( ! component . isLigand ) {
@@ -138,13 +176,13 @@ export async function pisaAsseblyView(pdbId: string, assemblyId: string, options
138176 const resSelector : ComponentExpressionT [ ] = interfaceResidues . map ( r => ( { auth_seq_id : Number ( r . seq_num ) , pdbx_PDB_ins_code : r . ins_code ?? undefined } ) ) ;
139177 if ( ! isSelfInterface ) {
140178 const comps = componentIdsByPisaChainId [ molecule . chain_id ] . map ( componentId => components [ componentId ] )
141- markInterface ( comps , resSelector , interfaceTooltip ) ;
179+ markInterface ( comps , resSelector , { tooltip : interfaceTooltip , color : true } ) ;
142180 }
143181 bothSurfaces . push ( ...resSelector ) ;
144182 }
145183 if ( isSelfInterface ) {
146184 const comps = componentIdsByPisaChainId [ int . interface . molecule [ 0 ] . chain_id ] . map ( componentId => components [ componentId ] )
147- markInterface ( comps , bothSurfaces , interfaceTooltip ) ;
185+ markInterface ( comps , bothSurfaces , { tooltip : interfaceTooltip , color : true } ) ;
148186 }
149187 }
150188 const name = assembly . serial_no === '0' ? 'ASU complex' : `Assembly ${ assembly . id } ` ;
@@ -156,7 +194,7 @@ export async function pisaAsseblyView(pdbId: string, assemblyId: string, options
156194 } ) ;
157195}
158196
159- export async function pisaInterfaceView ( pdbId : string , interfaceId : string , options : { ghostMolecules ?: ( 0 | 1 ) [ ] } = { } ) {
197+ export async function pisaInterfaceView ( pdbId : string , interfaceId : string , options : { ghostMolecules ?: ( 0 | 1 ) [ ] , detailMolecules ?: ( 0 | 1 ) [ ] } = { } ) {
160198 const assembliesData = await getAssembliesData ( pdbId ) ;
161199 const interfaceData = await getInterfaceData ( pdbId , interfaceId ) ;
162200 const allAssemblies = getAllAssemblies ( assembliesData ) ;
@@ -174,7 +212,9 @@ export async function pisaInterfaceView(pdbId: string, interfaceId: string, opti
174212 . transform ( transformFromPisaStyle ( molecule ) ) ;
175213 struct . component ( ) . tooltip ( { text : `<hr>Component <b>${ molecule . chain_id } (${ molecule . symop } )</b>` } ) ;
176214 const componentSelector : ComponentExpressionT = { label_asym_id : component . chainId , label_seq_id : component . seqId } ; // chain_id appears to be label_asym_id (if mmcif format)
177- if ( options . ghostMolecules ?. includes ( i as 0 | 1 ) ) {
215+ const showGhost = options . ghostMolecules ?. includes ( i as 0 | 1 ) ;
216+ const showDetails = options . detailMolecules ?. includes ( i as 0 | 1 ) ;
217+ if ( showGhost ) {
178218 const reprGhost = struct
179219 . component ( { selector : componentSelector } )
180220 . representation ( {
@@ -190,14 +230,21 @@ export async function pisaInterfaceView(pdbId: string, interfaceId: string, opti
190230 } )
191231 . color ( { color : color } )
192232 . opacity ( { opacity : .49 } ) ;
193- } else {
233+ }
234+ if ( ! showGhost || showDetails ) {
194235 const repr = struct
195236 . component ( { selector : componentSelector } )
196237 . representation ( {
197- type : 'spacefill' ,
198- size_factor : component . isLigand ? 1.05 : 1 , // distinguish ligands/waters from the main chain, in case of PDB format
238+ type : showDetails ? 'cartoon' : 'spacefill' ,
239+ size_factor : ( showDetails ? 0.5 : 1 )
240+ * ( component . isLigand ? 1.05 : 1 ) , // distinguish ligands/waters from the main chain, in case of PDB format
199241 } )
200242 . color ( { color : bulkColorFn ( color ) } ) ;
243+ const residues = Array . isArray ( molecule . residues . residue ) ? molecule . residues . residue : [ molecule . residues . residue ] ;
244+ const interfaceResidues = residues . filter ( r => Number ( r . bsa ) !== 0 ) ; // Secret undocumented knowledge
245+ const resSelector : ComponentExpressionT [ ] = interfaceResidues . map ( r => ( { auth_seq_id : Number ( r . seq_num ) , pdbx_PDB_ins_code : r . ins_code ?? undefined } ) ) ;
246+ markInterface ( [ { struct, repr, color } ] , resSelector , { tooltip : interfaceTooltip , color : ! showDetails , ball_and_stick : showDetails } ) ;
247+ if ( options . detailMolecules ?. length ) applyElementColors ( repr ) ;
201248 if ( ! component . isLigand ) {
202249 // distinguish ligands/waters from the main chain, in case of PDB format
203250 repr
@@ -206,10 +253,6 @@ export async function pisaInterfaceView(pdbId: string, interfaceId: string, opti
206253 . color ( { selector : 'branched' , color : 'white' } )
207254 . color ( { selector : 'ion' , color : 'white' } ) ;
208255 } ;
209- const residues = Array . isArray ( molecule . residues . residue ) ? molecule . residues . residue : [ molecule . residues . residue ] ;
210- const interfaceResidues = residues . filter ( r => Number ( r . bsa ) !== 0 ) ; // Secret undocumented knowledge
211- const resSelector : ComponentExpressionT [ ] = interfaceResidues . map ( r => ( { auth_seq_id : Number ( r . seq_num ) , pdbx_PDB_ins_code : r . ins_code ?? undefined } ) ) ;
212- markInterface ( [ { struct, repr, color } ] , resSelector , interfaceTooltip ) ;
213256 }
214257 } ) ;
215258
@@ -222,15 +265,23 @@ export async function pisaInterfaceView(pdbId: string, interfaceId: string, opti
222265}
223266
224267
225- interface ComponentState { struct : Builder . Structure , repr : Builder . Representation , color : ColorT } ;
268+ interface ComponentState { struct : Builder . Structure , repr : Builder . Representation , color : HexColorT } ;
226269
227- function markInterface ( theComponents : ComponentState [ ] , interfaceSelector : ComponentExpressionT [ ] , tooltip : string ) {
270+ function markInterface ( theComponents : ComponentState [ ] , interfaceSelector : ComponentExpressionT [ ] , options : { color ?: boolean , ball_and_stick ?: boolean , tooltip ? : string } = { } ) {
228271 for ( const component of theComponents ) {
229- const interfaceColor = interfaceColorFn ( component . color ) ;
230- component . repr . color ( { selector : interfaceSelector , color : interfaceColor } ) ;
231- component . struct . component ( { selector : interfaceSelector } ) . tooltip ( { text : tooltip } ) ;
232- // const bs = component.struct.component({ selector: interfaceSelector }).representation({ type: 'ball_and_stick' }).color({ color: bulkColorFn(component.color) })
233- // applyElementColors(bs);
272+ if ( options . color ) {
273+ component . repr . color ( { selector : interfaceSelector , color : interfaceColorFn ( component . color ) } ) ;
274+ }
275+ if ( options . ball_and_stick ) {
276+ const bs = component . struct
277+ . component ( { selector : interfaceSelector } )
278+ . representation ( { type : 'ball_and_stick' } )
279+ . color ( { color : bulkColorFn ( component . color ) } ) ;
280+ applyElementColors ( bs ) ;
281+ }
282+ if ( options . tooltip ) {
283+ component . struct . component ( { selector : interfaceSelector } ) . tooltip ( { text : options . tooltip } ) ;
284+ }
234285 }
235286}
236287
@@ -255,11 +306,24 @@ function componentKey(component: { chain_id: string } & PisaTransform) {
255306}
256307
257308function assignComponentColors ( assemblies : PisaAssemblyRecord [ ] ) {
258- const colors = { } as Record < string , ColorT > ;
309+ const colors = { } as Record < string , HexColorT > ;
259310 let iColor = 0 ;
311+ function isPolymer ( component : { chain_id : string } ) {
312+ return ! component . chain_id . startsWith ( '[' ) ;
313+ }
314+ // First assign colors to polymers, then ligands
315+ for ( const assembly of assemblies ) {
316+ for ( const molecule of assembly . molecule ) {
317+ if ( isPolymer ( molecule ) ) {
318+ colors [ componentKey ( molecule ) ] ??= COMPONENT_COLORS [ iColor ++ % COMPONENT_COLORS . length ] ;
319+ }
320+ }
321+ }
260322 for ( const assembly of assemblies ) {
261323 for ( const molecule of assembly . molecule ) {
262- colors [ componentKey ( molecule ) ] ??= ManyDistinctColors [ iColor ++ % ManyDistinctColors . length ] ;
324+ if ( ! isPolymer ( molecule ) ) {
325+ colors [ componentKey ( molecule ) ] ??= COMPONENT_COLORS [ iColor ++ % COMPONENT_COLORS . length ] ;
326+ }
263327 }
264328 }
265329 return colors ;
0 commit comments