@@ -1832,14 +1832,30 @@ export const ProteinViewer = forwardRef<ProteinViewerRef, ProteinViewerProps>(({
18321832 } ) ;
18331833 } else if ( currentColoring === 'custom' ) {
18341834 // --- CUSTOM COLORING MODE: Smooth Gradient Rendering ---
1835- // This mode uses residue-level color processing with gradient blending
1835+ // Improved robustness and fallback logic
1836+
1837+ // 1. If no custom rules, just use element coloring directly (simple & robust)
1838+ if ( ! hasValidCustomRules ) {
1839+ if ( repType === 'cartoon' ) {
1840+ component . addRepresentation ( 'cartoon' , {
1841+ color : 'element' ,
1842+ aspectRatio : 5 ,
1843+ subdiv : 12 ,
1844+ radialSegments : 20
1845+ } ) ;
1846+ } else {
1847+ component . addRepresentation ( repType , { color : 'element' } ) ;
1848+ }
1849+ } else {
1850+ // 2. We have rules - use the custom scheme with gradients
1851+
1852+ // Use unique ID to prevent NGL caching stale schemes
1853+ const schemeId = `custom_smooth_${ Date . now ( ) } _${ Math . random ( ) . toString ( 36 ) . substr ( 2 , 5 ) } ` ;
18361854
1837- const schemeId = 'custom_smooth_coloring' ;
1838- const atomColorMap = new Map < number , number > ( ) ;
1839- const residueColorMap = new Map < string , number > ( ) ;
1855+ const atomColorMap = new Map < number , number > ( ) ;
1856+ const residueColorMap = new Map < string , number > ( ) ;
18401857
1841- // Build residue-level color map from custom rules
1842- if ( hasValidCustomRules ) {
1858+ // Build residue-level color map from custom rules
18431859 customColors . forEach ( rule => {
18441860 if ( rule . color && rule . target ) {
18451861 const colorHex = new window . NGL . Color ( rule . color ) . getHex ( ) ;
@@ -1853,95 +1869,107 @@ export const ProteinViewer = forwardRef<ProteinViewerRef, ProteinViewerProps>(({
18531869 }
18541870 }
18551871 } ) ;
1856- }
18571872
1858- // Helper: blend two colors
1859- const blendColors = ( c1 : number , c2 : number , factor : number ) => {
1860- const r = Math . round ( ( ( c1 >> 16 ) & 0xFF ) + ( ( ( c2 >> 16 ) & 0xFF ) - ( ( c1 >> 16 ) & 0xFF ) ) * factor ) ;
1861- const g = Math . round ( ( ( c1 >> 8 ) & 0xFF ) + ( ( ( c2 >> 8 ) & 0xFF ) - ( ( c1 >> 8 ) & 0xFF ) ) * factor ) ;
1862- const b = Math . round ( ( c1 & 0xFF ) + ( ( c2 & 0xFF ) - ( c1 & 0xFF ) ) * factor ) ;
1863- return ( r << 16 ) | ( g << 8 ) | b ;
1864- } ;
1873+ // Helper: blend two colors
1874+ const blendColors = ( c1 : number , c2 : number , factor : number ) => {
1875+ const r = Math . round ( ( ( c1 >> 16 ) & 0xFF ) + ( ( ( c2 >> 16 ) & 0xFF ) - ( ( c1 >> 16 ) & 0xFF ) ) * factor ) ;
1876+ const g = Math . round ( ( ( c1 >> 8 ) & 0xFF ) + ( ( ( c2 >> 8 ) & 0xFF ) - ( ( c1 >> 8 ) & 0xFF ) ) * factor ) ;
1877+ const b = Math . round ( ( c1 & 0xFF ) + ( ( c2 & 0xFF ) - ( c1 & 0xFF ) ) * factor ) ;
1878+ return ( r << 16 ) | ( g << 8 ) | b ;
1879+ } ;
1880+
1881+ // Apply gradient transitions at boundaries
1882+ const gradWidth = 2 ; // Number of residues to blend on each side
18651883
1866- // Apply gradient transitions at boundaries
1867- const gradWidth = 2 ; // Number of residues to blend on each side
1868- component . structure . eachResidue ( ( res : any ) => {
1869- const key = `${ res . chainname } :${ res . resno } ` ;
1870- const customCol = residueColorMap . get ( key ) ;
1871-
1872- // Get element color for this residue
1873- const firstAtom = Array . from ( res . iterateAtom ( ) ) [ 0 ] ;
1874- const ElementScheme = window . NGL . ColormakerRegistry . getScheme ( 'element' ) ;
1875- const baseCol = ElementScheme ? new ElementScheme ( { } ) . atomColor ( firstAtom ) : 0xCCCCCC ;
1876-
1877- let finalCol = customCol !== undefined ? customCol : baseCol ;
1878-
1879- // If not custom colored, check for nearby custom residues for gradient
1880- if ( customCol === undefined ) {
1881- let nearestCustom = null ;
1882- let nearestDist = gradWidth + 1 ;
1883-
1884- for ( let d = - gradWidth ; d <= gradWidth ; d ++ ) {
1885- if ( d === 0 ) continue ;
1886- const nKey = `${ res . chainname } :${ res . resno + d } ` ;
1887- const nCol = residueColorMap . get ( nKey ) ;
1888- if ( nCol !== undefined && Math . abs ( d ) < nearestDist ) {
1889- nearestDist = Math . abs ( d ) ;
1890- nearestCustom = nCol ;
1884+ try {
1885+ component . structure . eachResidue ( ( res : any ) => {
1886+ const key = `${ res . chainname } :${ res . resno } ` ;
1887+ const customCol = residueColorMap . get ( key ) ;
1888+
1889+ // Get element color for this residue
1890+ let baseCol = 0xCCCCCC ;
1891+ try {
1892+ const firstAtom = Array . from ( res . iterateAtom ( ) ) [ 0 ] ;
1893+ const ElementScheme = window . NGL . ColormakerRegistry . getScheme ( 'element' ) ;
1894+ if ( ElementScheme ) {
1895+ const es = new ElementScheme ( { } ) ;
1896+ baseCol = es . atomColor ( firstAtom ) ;
1897+ }
1898+ } catch ( e ) { /* ignore */ }
1899+
1900+ let finalCol = customCol !== undefined ? customCol : baseCol ;
1901+
1902+ // If not custom colored, check for nearby custom residues for gradient
1903+ if ( customCol === undefined ) {
1904+ let nearestCustom = null ;
1905+ let nearestDist = gradWidth + 1 ;
1906+
1907+ for ( let d = - gradWidth ; d <= gradWidth ; d ++ ) {
1908+ if ( d === 0 ) continue ;
1909+ const nKey = `${ res . chainname } :${ res . resno + d } ` ;
1910+ const nCol = residueColorMap . get ( nKey ) ;
1911+ if ( nCol !== undefined && Math . abs ( d ) < nearestDist ) {
1912+ nearestDist = Math . abs ( d ) ;
1913+ nearestCustom = nCol ;
1914+ }
1915+ }
1916+
1917+ if ( nearestCustom !== null ) {
1918+ const blend = 1 - ( nearestDist / ( gradWidth + 1 ) ) ;
1919+ finalCol = blendColors ( baseCol , nearestCustom , blend ) ;
1920+ }
18911921 }
1892- }
18931922
1894- if ( nearestCustom !== null ) {
1895- const blend = 1 - ( nearestDist / ( gradWidth + 1 ) ) ;
1896- finalCol = blendColors ( baseCol , nearestCustom , blend ) ;
1897- }
1923+ // Apply color to all atoms in residue
1924+ res . eachAtom ( ( atom : any ) => {
1925+ atomColorMap . set ( atom . index , finalCol ) ;
1926+ } ) ;
1927+ } ) ;
1928+ } catch ( e ) {
1929+ console . warn ( "Error in gradient processing" , e ) ;
18981930 }
18991931
1900- // Apply color to all atoms in residue
1901- res . eachAtom ( ( atom : any ) => {
1902- atomColorMap . set ( atom . index , finalCol ) ;
1903- } ) ;
1904- } ) ;
1905-
1906- // Fallback: if atomColorMap is empty, populate with element colors
1907- if ( atomColorMap . size === 0 ) {
1908- const ElementScheme = window . NGL . ColormakerRegistry . getScheme ( 'element' ) ;
1909- const elementColorer = ElementScheme ? new ElementScheme ( { } ) : null ;
1910- component . structure . eachAtom ( ( atom : any ) => {
1911- const col = elementColorer ? elementColorer . atomColor ( atom ) : 0xCCCCCC ;
1912- atomColorMap . set ( atom . index , col ) ;
1913- } ) ;
1914- }
1932+ // Fallback: if atomColorMap is still empty (e.g. error above), force populate
1933+ if ( atomColorMap . size === 0 ) {
1934+ try {
1935+ const ElementScheme = window . NGL . ColormakerRegistry . getScheme ( 'element' ) ;
1936+ const es = ElementScheme ? new ElementScheme ( { } ) : null ;
1937+ component . structure . eachAtom ( ( atom : any ) => {
1938+ atomColorMap . set ( atom . index , es ? es . atomColor ( atom ) : 0xCCCCCC ) ;
1939+ } ) ;
1940+ } catch ( e ) { console . error ( "Fallback population failed" , e ) ; }
1941+ }
19151942
1943+ // Register the custom color scheme
1944+ window . NGL . ColormakerRegistry . addScheme ( function ( this : any ) {
1945+ this . atomColor = function ( atom : any ) {
1946+ return atomColorMap . get ( atom . index ) || 0xCCCCCC ;
1947+ } ;
1948+ } , schemeId ) ;
19161949
1917- // Register the custom color scheme
1918- window . NGL . ColormakerRegistry . addScheme ( function ( this : any ) {
1919- this . atomColor = function ( atom : any ) {
1920- return atomColorMap . get ( atom . index ) || 0xCCCCCC ;
1921- } ;
1922- } , schemeId ) ;
1950+ // Create single representation with custom scheme
1951+ if ( repType === 'cartoon' ) {
1952+ try {
1953+ component . structure . eachModel ( ( m : any ) => {
1954+ if ( m . calculateSecondaryStructure ) m . calculateSecondaryStructure ( ) ;
1955+ } ) ;
1956+ } catch ( e ) { }
19231957
1924- // Create single representation with custom scheme
1925- if ( repType === 'cartoon' ) {
1926- try {
1927- component . structure . eachModel ( ( m : any ) => {
1928- if ( m . calculateSecondaryStructure ) m . calculateSecondaryStructure ( ) ;
1958+ component . addRepresentation ( 'cartoon' , {
1959+ color : schemeId ,
1960+ aspectRatio : 5 ,
1961+ subdiv : 12 ,
1962+ radialSegments : 20 ,
19291963 } ) ;
1930- } catch ( e ) { }
1964+ } else {
1965+ component . addRepresentation ( repType , {
1966+ color : schemeId
1967+ } ) ;
1968+ }
19311969
1932- component . addRepresentation ( 'cartoon' , {
1933- color : schemeId ,
1934- aspectRatio : 5 ,
1935- subdiv : 12 ,
1936- radialSegments : 20 ,
1937- } ) ;
1938- } else {
1939- component . addRepresentation ( repType , {
1940- color : schemeId
1941- } ) ;
1970+ console . log ( `Custom coloring applied: ${ schemeId } ` ) ;
19421971 }
19431972
1944- console . log ( `Custom coloring: ${ residueColorMap . size } residues colored, ${ atomColorMap . size } atoms total` ) ;
19451973 } else {
19461974 // Standard Coloring for other modes (sstruc, element, etc.) -> Robust Native NGL
19471975 // REVERTED to use 'color' property as previously working.
0 commit comments