Ver Fonte

basic support for color-theme description and legend

Alexander Rose há 6 anos atrás
pai
commit
aa898f39dd

+ 6 - 6
src/apps/canvas/component/app.tsx

@@ -46,13 +46,13 @@ export class AppComponent extends React.Component<AppProps, AppState> {
         const { structureView } = this.state
 
         return <div style={{width: '100%', height: '100%'}}>
-            <div style={{float: 'left', width: '70%', height: '100%'}}>
+            <div style={{left: '0px', right: '350px', height: '100%', position: 'absolute'}}>
                 <Viewport app={this.props.app} />
             </div>
 
-            <div style={{float: 'right', width: '25%', height: '100%'}}>
-                <div>
-                    <span>Load PDB ID</span>
+            <div style={{width: '330px', paddingLeft: '10px', paddingRight: '10px', right: '0px', height: '100%', position: 'absolute', overflow: 'auto'}}>
+                <div style={{marginTop: '10px'}}>
+                    <span>Load PDB ID </span>
                     <input
                         type='text'
                         onKeyDown={e => {
@@ -66,7 +66,7 @@ export class AppComponent extends React.Component<AppProps, AppState> {
                     />
                 </div>
                 <div>
-                    <span>Load CIF file</span>
+                    <span>Load CIF file </span>
                     <input
                         accept='*.cif'
                         type='file'
@@ -76,7 +76,7 @@ export class AppComponent extends React.Component<AppProps, AppState> {
                     />
                 </div>
                 <hr/>
-                <div>
+                <div style={{marginBottom: '10px'}}>
                     {structureView ? <StructureViewComponent structureView={structureView} /> : ''}
                 </div>
             </div>

+ 36 - 4
src/apps/canvas/component/structure-representation.tsx

@@ -8,7 +8,8 @@ import * as React from 'react'
 import { StructureRepresentation, StructureProps } from 'mol-geo/representation/structure';
 import Viewer from 'mol-view/viewer';
 import { VisualQuality, VisualQualityNames } from 'mol-geo/representation/util';
-import { ColorThemeProps, ColorThemeName, ColorThemeNames } from 'mol-view/theme/color';
+import { ColorThemeProps, ColorThemeName, ColorThemeNames, ColorTheme } from 'mol-view/theme/color';
+import { Color } from 'mol-util/color';
 
 export interface StructureRepresentationComponentProps {
     viewer: Viewer
@@ -67,28 +68,59 @@ export class StructureRepresentationComponent extends React.Component<StructureR
     render() {
         const { label, visible, quality, colorTheme } = this.state
 
+        const ct = ColorTheme(colorTheme)
+
+        if (ct.legend && ct.legend.kind === 'scale-legend') {
+            // console.log(`linear-gradient(to right, ${ct.legend.colors.map(c => Color.toStyle(c)).join(', ')})`)
+        }
+
         return <div>
             <div>
                 <h4>{label}</h4>
             </div>
             <div>
                 <div>
-                    <span>Visible</span>
+                    <span>Visible </span>
                     <button onClick={(e) => this.update({ visible: !visible }) }>
                         {visible ? 'Hide' : 'Show'}
                     </button>
                 </div>
                 <div>
-                    <span>Quality</span>
+                    <span>Quality </span>
                     <select value={quality} onChange={(e) => this.update({ quality: e.target.value as VisualQuality }) }>
                         {VisualQualityNames.map(name => <option key={name} value={name}>{name}</option>)}
                     </select>
                 </div>
                 <div>
-                    <span>Color Theme</span>
+                    <span>Color Theme </span>
                     <select value={colorTheme.name} onChange={(e) => this.update({ colorTheme: { name: e.target.value as ColorThemeName } }) }>
                         {ColorThemeNames.map(name => <option key={name} value={name}>{name}</option>)}
                     </select>
+                    {ct.description ? <div><i>{ct.description}</i></div> : ''}
+                    {
+                        ct.legend && ct.legend.kind === 'scale-legend'
+                            ? <div
+                                style={{
+                                    width: '100%',
+                                    height: '30px',
+                                    background: `linear-gradient(to right, ${ct.legend.colors.map(c => Color.toStyle(c)).join(', ')})`
+                                }}
+                            >
+                                <span style={{float: 'left', padding: '6px', color: 'white', fontWeight: 'bold', backgroundColor: 'rgba(0, 0, 0, 0.2)'}}>{ct.legend.min}</span>
+                                <span style={{float: 'right', padding: '6px', color: 'white', fontWeight: 'bold', backgroundColor: 'rgba(0, 0, 0, 0.2)'}}>{ct.legend.max}</span>
+                            </div>
+                        : ct.legend && ct.legend.kind === 'table-legend'
+                            ? <div>
+                                {ct.legend.table.map((value, i) => {
+                                    const [name, color] = value
+                                    return <div key={i} style={{minWidth: '60px', marginRight: '5px', display: 'inline-block'}}>
+                                        <div style={{width: '30px', height: '20px', backgroundColor: Color.toStyle(color), display: 'inline-block'}}></div>
+                                        {name}
+                                    </div>
+                                })}
+                            </div>
+                        : ''
+                    }
                 </div>
             </div>
         </div>;

+ 7 - 3
src/apps/canvas/component/structure-view.tsx

@@ -112,8 +112,9 @@ export class StructureViewComponent extends React.Component<StructureViewCompone
             </div>
             <div>
                 <div>
-                    <span>Model</span>
+                    <span>Model </span>
                     <select
+                        style={{width: '100px'}}
                         value={this.state.modelId}
                         onChange={(e) => {
                             this.update({ modelId: parseInt(e.target.value) })
@@ -121,6 +122,7 @@ export class StructureViewComponent extends React.Component<StructureViewCompone
                     >
                         {modelIdOptions}
                     </select>
+                    <span> </span>
                     <input type='range'
                         value={this.state.modelId}
                         min={Math.min(...modelIds.map(m => m.id))}
@@ -133,8 +135,9 @@ export class StructureViewComponent extends React.Component<StructureViewCompone
                     </input>
                 </div>
                 <div>
-                    <span>Assembly</span>
+                    <span>Assembly </span>
                     <select
+                        style={{width: '150px'}}
                         value={this.state.assemblyId}
                         onChange={(e) => {
                             this.update({ assemblyId: e.target.value })
@@ -144,8 +147,9 @@ export class StructureViewComponent extends React.Component<StructureViewCompone
                     </select>
                 </div>
                 <div>
-                    <span>Symmetry Feature</span>
+                    <span>Symmetry Feature </span>
                     <select
+                        style={{width: '150px'}}
                         value={this.state.symmetryFeatureId}
                         onChange={(e) => {
                             this.update({ symmetryFeatureId: parseInt(e.target.value) })

+ 10 - 0
src/apps/canvas/index.html

@@ -14,6 +14,16 @@
                 height: 100%;
                 overflow: hidden;
             }
+            hr {
+                margin: 10px;
+            }
+            h1, h2, h3, h4, h5 {
+                margin-top: 5px;
+                margin-bottom: 3px;
+            }
+            button {
+                padding: 2px;
+            }
         </style>
     </head>
     <body>

+ 13 - 0
src/mol-model/structure/structure/carbohydrates/constants.ts

@@ -174,6 +174,19 @@ const Monosaccharides: SaccharideComponent[] = [
     { abbr: 'Psi', name: 'Psicose', color: SaccharideColors.Pink, type: SaccharideType.Assigned },
 ]
 
+export const MonosaccharidesColorTable: [string, Color][] = [
+    ['Glc-family', SaccharideColors.Blue],
+    ['Man-family', SaccharideColors.Green],
+    ['Gal-family', SaccharideColors.Yellow],
+    ['Gul-family', SaccharideColors.Orange],
+    ['Alt-family', SaccharideColors.Pink],
+    ['All-family', SaccharideColors.Purple],
+    ['Tal-family', SaccharideColors.LightBlue],
+    ['Ido-family', SaccharideColors.Blue],
+    ['Fuc-family', SaccharideColors.Red],
+    ['Generic/Unknown/Secondary', SaccharideColors.Secondary],
+]
+
 const CommonSaccharideNames: { [k: string]: string[] } = {
     // Hexose
     Glc: [

+ 4 - 0
src/mol-util/color/color.ts

@@ -10,6 +10,10 @@ export type Color = { readonly '@type': 'color' } & number
 export function Color(hex: number) { return hex as Color }
 
 export namespace Color {
+    export function toStyle(hexColor: Color) {
+        return `rgb(${hexColor >> 16 & 255}, ${hexColor >> 8 & 255}, ${hexColor & 255})`
+    }
+
     export function toRgb(hexColor: Color) {
         return [ hexColor >> 16 & 255, hexColor >> 8 & 255, hexColor & 255 ]
     }

+ 8 - 3
src/mol-util/color/scale.ts

@@ -6,6 +6,7 @@
 
 import { Color } from './color'
 import { ColorBrewer } from './tables'
+import { ScaleLegend } from 'mol-view/theme/color';
 
 export interface ColorScale {
     /** Returns hex color for given value */
@@ -14,19 +15,22 @@ export interface ColorScale {
     colorToArray: (value: number, array: Helpers.NumberArray, offset: number) => void
     /** Copies normalized (0 to 1) hex color to rgb array */
     normalizedColorToArray: (value: number, array: Helpers.NumberArray, offset: number) => void
+    /** */
+    readonly legend: ScaleLegend
 }
 
 export const DefaultColorScale = {
     domain: [0, 1],
     reverse: false,
-    colors: ColorBrewer.RdYlBu as Color[]
+    colors: ColorBrewer.RdYlBu,
 }
 export type ColorScaleProps = Partial<typeof DefaultColorScale>
 
 export namespace ColorScale {
     export function create(props: ColorScaleProps): ColorScale {
-        const { domain, reverse, colors } = { ...DefaultColorScale, ...props }
-        const [ min, max ] = reverse ? domain.slice().reverse() : domain
+        const { domain, reverse, colors: _colors } = { ...DefaultColorScale, ...props }
+        const [ min, max ] = domain
+        const colors = reverse ? _colors.slice().reverse() : _colors
         const count1 = colors.length - 1
         const diff = (max - min) || 1
 
@@ -45,6 +49,7 @@ export namespace ColorScale {
             normalizedColorToArray: (value: number, array: Helpers.NumberArray, offset: number) => {
                 Color.toArrayNormalized(color(value), array, offset)
             },
+            get legend() { return ScaleLegend(min, max, colors) }
         }
     }
 }

+ 23 - 1
src/mol-view/theme/color.ts

@@ -21,9 +21,29 @@ import { ColorType } from 'mol-geo/util/color-data';
 
 export type LocationColor = (location: Location, isSecondary: boolean) => Color
 
+export interface ScaleLegend {
+    kind: 'scale-legend'
+    min: number,
+    max: number,
+    colors: Color[]
+}
+export function ScaleLegend(min: number, max: number, colors: Color[]): ScaleLegend {
+    return { kind: 'scale-legend', min, max, colors }
+}
+
+export interface TableLegend {
+    kind: 'table-legend'
+    table: [ string, Color ][]
+}
+export function TableLegend(table: [ string, Color ][]): TableLegend {
+    return { kind: 'table-legend', table }
+}
+
 export interface ColorTheme {
     granularity: ColorType
     color: LocationColor
+    description?: string
+    legend?: ScaleLegend | TableLegend
 }
 
 export function ColorTheme(props: ColorThemeProps): ColorTheme {
@@ -46,7 +66,9 @@ export interface ColorThemeProps {
     value?: Color
     structure?: Structure
     color?: LocationColor
-    granularity?: ColorType
+    granularity?: ColorType,
+    description?: string,
+    legend?: ScaleLegend | TableLegend
 }
 
 export const ColorThemeInfo = {

+ 9 - 3
src/mol-view/theme/color/carbohydrate-symbol.ts

@@ -6,12 +6,13 @@
 
 import { StructureElement, Link, ElementIndex, Unit } from 'mol-model/structure';
 
-import { SaccharideColors } from 'mol-model/structure/structure/carbohydrates/constants';
+import { SaccharideColors, MonosaccharidesColorTable } from 'mol-model/structure/structure/carbohydrates/constants';
 import { Location } from 'mol-model/location';
-import { ColorThemeProps, ColorTheme, LocationColor } from '../color';
+import { ColorThemeProps, ColorTheme, LocationColor, TableLegend } from '../color';
 import { Color } from 'mol-util/color';
 
 const DefaultColor = Color(0xCCCCCC)
+const Description = 'Assigns colors according to the Symbol Nomenclature for Glycans (SNFG).'
 
 export function CarbohydrateSymbolColorTheme(props: ColorThemeProps): ColorTheme {
     let color: LocationColor
@@ -45,5 +46,10 @@ export function CarbohydrateSymbolColorTheme(props: ColorThemeProps): ColorTheme
         color = () => DefaultColor
     }
 
-    return { granularity: 'group', color: color }
+    return {
+        granularity: 'group',
+        color: color,
+        description: Description,
+        legend: TableLegend(MonosaccharidesColorTable)
+    }
 }

+ 42 - 26
src/mol-view/theme/color/chain-id.ts

@@ -8,9 +8,10 @@ import { Unit, StructureProperties, StructureElement, Link } from 'mol-model/str
 
 import { ColorScale, Color } from 'mol-util/color';
 import { Location } from 'mol-model/location';
-import { ColorThemeProps, ColorTheme } from '../color';
+import { ColorThemeProps, ColorTheme, LocationColor } from '../color';
 
 const DefaultColor = Color(0xCCCCCC)
+const Description = 'Gives every chain a color based on its `asym_id` value.'
 
 function getAsymId(unit: Unit): StructureElement.Property<string> {
     switch (unit.kind) {
@@ -23,34 +24,49 @@ function getAsymId(unit: Unit): StructureElement.Property<string> {
 }
 
 export function ChainIdColorTheme(props: ColorThemeProps): ColorTheme {
-    const l = StructureElement.create()
-
-    const scaleMap = new Map<number, ColorScale>()
-    function getScale(size: number) {
-        let scale = scaleMap.get(size)
-        if (!scale) {
-            scale = ColorScale.create({ domain: [ 0, size - 1 ] })
-            scaleMap.set(size, scale)
+    let color: LocationColor
+    let scale: ColorScale | undefined = undefined
+    // const table: [string, Color][] = []
+
+    if (props.structure) {
+        const l = StructureElement.create()
+        const { models } = props.structure
+        const asymIdSerialMap = new Map<string, number>()
+        let j = 0
+        for (let i = 0, il = models.length; i <il; ++i) {
+            models[i].properties.asymIdSerialMap.forEach((v, k) => {
+                if (!asymIdSerialMap.has(k)) {
+                    asymIdSerialMap.set(k, j)
+                    j += 1
+                }
+            })
         }
-        return scale
-    }
+        scale = ColorScale.create({ domain: [ 0, asymIdSerialMap.size - 1 ] })
+        const scaleColor = scale.color
 
-    function color(location: Location): Color {
-        if (StructureElement.isLocation(location)) {
-            const map = location.unit.model.properties.asymIdSerialMap
-            const scale = getScale(map.size)
-            const asym_id = getAsymId(location.unit)
-            return scale.color(map.get(asym_id(location)) || 0)
-        } else if (Link.isLocation(location)) {
-            const map = location.aUnit.model.properties.asymIdSerialMap
-            const scale = getScale(map.size)
-            const asym_id = getAsymId(location.aUnit)
-            l.unit = location.aUnit
-            l.element = location.aUnit.elements[location.aIndex]
-            return scale.color(map.get(asym_id(l)) || 0)
+        // asymIdSerialMap.forEach((v, k) => table.push([k, scaleColor(v)]))
+
+        color = (location: Location): Color => {
+            if (StructureElement.isLocation(location)) {
+                const asym_id = getAsymId(location.unit)
+                return scaleColor(asymIdSerialMap.get(asym_id(location)) || 0)
+            } else if (Link.isLocation(location)) {
+                const asym_id = getAsymId(location.aUnit)
+                l.unit = location.aUnit
+                l.element = location.aUnit.elements[location.aIndex]
+                return scaleColor(asymIdSerialMap.get(asym_id(l)) || 0)
+            }
+            return DefaultColor
         }
-        return DefaultColor
+    } else {
+        color = () => DefaultColor
     }
 
-    return { granularity: 'group', color }
+    return {
+        granularity: 'group',
+        color,
+        description: Description,
+        // legend: scale ? TableLegend(table) : undefined
+        legend: scale ? scale.legend : undefined
+    }
 }

+ 11 - 3
src/mol-view/theme/color/cross-link.ts

@@ -12,6 +12,7 @@ import { ColorThemeProps, ColorTheme, LocationColor } from '../color';
 import { Vec3 } from 'mol-math/linear-algebra';
 
 const DefaultColor = Color(0xCCCCCC)
+const Description = 'Colors cross-links by the deviation of the observed distance versus the modeled distance (e.g. `ihm_cross_link_restraint.distance_threshold`).'
 
 const distVecA = Vec3.zero(), distVecB = Vec3.zero()
 function linkDistance(link: Link.Location) {
@@ -22,16 +23,18 @@ function linkDistance(link: Link.Location) {
 
 export function CrossLinkColorTheme(props: ColorThemeProps): ColorTheme {
     let color: LocationColor
+    let scale: ColorScale | undefined = undefined
 
     if (props.structure) {
         const crosslinks = props.structure.crossLinkRestraints
-        const scale = ColorScale.create({ domain: [ -10, 10 ], colors: ColorBrewer.RdYlBu })
+        scale = ColorScale.create({ domain: [ -10, 10 ], colors: ColorBrewer.RdYlBu })
+        const scaleColor = scale.color
 
         color = (location: Location): Color => {
             if (Link.isLocation(location)) {
                 const pairs = crosslinks.getPairs(location.aIndex, location.aUnit, location.bIndex, location.bUnit)
                 if (pairs) {
-                    return scale.color(linkDistance(location) - pairs[0].distanceThreshold)
+                    return scaleColor(linkDistance(location) - pairs[0].distanceThreshold)
                 }
             }
             return DefaultColor
@@ -40,5 +43,10 @@ export function CrossLinkColorTheme(props: ColorThemeProps): ColorTheme {
         color = () => DefaultColor
     }
 
-    return { granularity: 'group', color }
+    return {
+        granularity: 'group',
+        color,
+        description: Description,
+        legend: scale ? scale.legend : undefined
+    }
 }

+ 3 - 1
src/mol-view/theme/color/custom.ts

@@ -14,6 +14,8 @@ export function CustomColorTheme(props: ColorThemeProps): ColorTheme {
     const value = defaults(props.value, DefaultColor)
     return {
         granularity: defaults(props.granularity, 'uniform'),
-        color: defaults(props.color, () => value)
+        color: defaults(props.color, () => value),
+        description: props.description,
+        legend: props.legend
     }
 }

+ 12 - 4
src/mol-view/theme/color/element-index.ts

@@ -11,9 +11,11 @@ import { OrderedSet } from 'mol-data/int';
 import { ColorThemeProps, ColorTheme, LocationColor } from '../color';
 
 const DefaultColor = Color(0xCCCCCC)
+const Description = 'Gives every element (atom or coarse sphere/gaussian) a unique color based on the position (index) of the element in the list of elements in the structure.'
 
 export function ElementIndexColorTheme(props: ColorThemeProps): ColorTheme {
     let color: LocationColor
+    let scale: ColorScale | undefined = undefined
 
     if (props.structure) {
         const { units } = props.structure
@@ -27,16 +29,17 @@ export function ElementIndexColorTheme(props: ColorThemeProps): ColorTheme {
             elementCount += units[i].elements.length
             unitIdIndex.set(units[i].id, i)
         }
-        const scale = ColorScale.create({ domain: [ 0, elementCount - 1 ] })
+        scale = ColorScale.create({ domain: [ 0, elementCount - 1 ] })
+        const scaleColor = scale.color
 
         color = (location: Location): Color => {
             if (StructureElement.isLocation(location)) {
                 const unitIndex = unitIdIndex.get(location.unit.id)!
                 const unitElementIndex = OrderedSet.findPredecessorIndex(location.unit.elements, location.element)
-                return scale.color(cummulativeElementCount.get(unitIndex)! + unitElementIndex)
+                return scaleColor(cummulativeElementCount.get(unitIndex)! + unitElementIndex)
             } else if (Link.isLocation(location)) {
                 const unitIndex = unitIdIndex.get(location.aUnit.id)!
-                return scale.color(cummulativeElementCount.get(unitIndex)! + location.aIndex)
+                return scaleColor(cummulativeElementCount.get(unitIndex)! + location.aIndex)
             }
             return DefaultColor
         }
@@ -44,5 +47,10 @@ export function ElementIndexColorTheme(props: ColorThemeProps): ColorTheme {
         color = () => DefaultColor
     }
 
-    return { granularity: 'groupInstance', color }
+    return {
+        granularity: 'groupInstance',
+        color,
+        description: Description,
+        legend: scale ? scale.legend : undefined
+    }
 }

+ 11 - 3
src/mol-view/theme/color/element-symbol.ts

@@ -8,14 +8,15 @@ import { ElementSymbol } from 'mol-model/structure/model/types';
 import { Color, ColorMap } from 'mol-util/color';
 import { StructureElement, Unit, Link } from 'mol-model/structure';
 import { Location } from 'mol-model/location';
-import { ColorThemeProps, ColorTheme } from '../color';
+import { ColorThemeProps, ColorTheme, TableLegend } from '../color';
 
 // from Jmol http://jmol.sourceforge.net/jscolors/ (or 0xFFFFFF)
 export const ElementSymbolColors = ColorMap({
-    'H': 0xFFFFFF, 'HE': 0xD9FFFF, 'LI': 0xCC80FF, 'BE': 0xC2FF00, 'B': 0xFFB5B5, 'C': 0x909090, 'N': 0x3050F8, 'O': 0xFF0D0D, 'F': 0x90E050, 'NE': 0xB3E3F5, 'NA': 0xAB5CF2, 'MG': 0x8AFF00, 'AL': 0xBFA6A6, 'SI': 0xF0C8A0, 'P': 0xFF8000, 'S': 0xFFFF30, 'CL': 0x1FF01F, 'AR': 0x80D1E3, 'K': 0x8F40D4, 'CA': 0x3DFF00, 'SC': 0xE6E6E6, 'TI': 0xBFC2C7, 'V': 0xA6A6AB, 'CR': 0x8A99C7, 'MN': 0x9C7AC7, 'FE': 0xE06633, 'CO': 0xF090A0, 'NI': 0x50D050, 'CU': 0xC88033, 'ZN': 0x7D80B0, 'GA': 0xC28F8F, 'GE': 0x668F8F, 'AS': 0xBD80E3, 'SE': 0xFFA100, 'BR': 0xA62929, 'KR': 0x5CB8D1, 'RB': 0x702EB0, 'SR': 0x00FF00, 'Y': 0x94FFFF, 'ZR': 0x94E0E0, 'NB': 0x73C2C9, 'MO': 0x54B5B5, 'TC': 0x3B9E9E, 'RU': 0x248F8F, 'RH': 0x0A7D8C, 'PD': 0x006985, 'AG': 0xC0C0C0, 'CD': 0xFFD98F, 'IN': 0xA67573, 'SN': 0x668080, 'SB': 0x9E63B5, 'TE': 0xD47A00, 'I': 0x940094, 'XE': 0x940094, 'CS': 0x57178F, 'BA': 0x00C900, 'LA': 0x70D4FF, 'CE': 0xFFFFC7, 'PR': 0xD9FFC7, 'ND': 0xC7FFC7, 'PM': 0xA3FFC7, 'SM': 0x8FFFC7, 'EU': 0x61FFC7, 'GD': 0x45FFC7, 'TB': 0x30FFC7, 'DY': 0x1FFFC7, 'HO': 0x00FF9C, 'ER': 0x00E675, 'TM': 0x00D452, 'YB': 0x00BF38, 'LU': 0x00AB24, 'HF': 0x4DC2FF, 'TA': 0x4DA6FF, 'W': 0x2194D6, 'RE': 0x267DAB, 'OS': 0x266696, 'IR': 0x175487, 'PT': 0xD0D0E0, 'AU': 0xFFD123, 'HG': 0xB8B8D0, 'TL': 0xA6544D, 'PB': 0x575961, 'BI': 0x9E4FB5, 'PO': 0xAB5C00, 'AT': 0x754F45, 'RN': 0x428296, 'FR': 0x420066, 'RA': 0x007D00, 'AC': 0x70ABFA, 'TH': 0x00BAFF, 'PA': 0x00A1FF, 'U': 0x008FFF, 'NP': 0x0080FF, 'PU': 0x006BFF, 'AM': 0x545CF2, 'CM': 0x785CE3, 'BK': 0x8A4FE3, 'CF': 0xA136D4, 'ES': 0xB31FD4, 'FM': 0xB31FBA, 'MD': 0xB30DA6, 'NO': 0xBD0D87, 'LR': 0xC70066, 'RF': 0xCC0059, 'DB': 0xD1004F, 'SG': 0xD90045, 'BH': 0xE00038, 'HS': 0xE6002E, 'MT': 0xEB0026, 'DS': 0xFFFFFF, 'RG': 0xFFFFFF, 'CN': 0xFFFFFF, 'UUT': 0xFFFFFF, 'FL': 0xFFFFFF, 'UUP': 0xFFFFFF, 'LV': 0xFFFFFF, 'UUH': 0xFFFFFF, 'D': 0xFFFFC0, 'T': 0xFFFFA0
+    'H': 0xFFFFFF, 'D': 0xFFFFC0, 'T': 0xFFFFA0, 'HE': 0xD9FFFF, 'LI': 0xCC80FF, 'BE': 0xC2FF00, 'B': 0xFFB5B5, 'C': 0x909090, 'N': 0x3050F8, 'O': 0xFF0D0D, 'F': 0x90E050, 'NE': 0xB3E3F5, 'NA': 0xAB5CF2, 'MG': 0x8AFF00, 'AL': 0xBFA6A6, 'SI': 0xF0C8A0, 'P': 0xFF8000, 'S': 0xFFFF30, 'CL': 0x1FF01F, 'AR': 0x80D1E3, 'K': 0x8F40D4, 'CA': 0x3DFF00, 'SC': 0xE6E6E6, 'TI': 0xBFC2C7, 'V': 0xA6A6AB, 'CR': 0x8A99C7, 'MN': 0x9C7AC7, 'FE': 0xE06633, 'CO': 0xF090A0, 'NI': 0x50D050, 'CU': 0xC88033, 'ZN': 0x7D80B0, 'GA': 0xC28F8F, 'GE': 0x668F8F, 'AS': 0xBD80E3, 'SE': 0xFFA100, 'BR': 0xA62929, 'KR': 0x5CB8D1, 'RB': 0x702EB0, 'SR': 0x00FF00, 'Y': 0x94FFFF, 'ZR': 0x94E0E0, 'NB': 0x73C2C9, 'MO': 0x54B5B5, 'TC': 0x3B9E9E, 'RU': 0x248F8F, 'RH': 0x0A7D8C, 'PD': 0x006985, 'AG': 0xC0C0C0, 'CD': 0xFFD98F, 'IN': 0xA67573, 'SN': 0x668080, 'SB': 0x9E63B5, 'TE': 0xD47A00, 'I': 0x940094, 'XE': 0x940094, 'CS': 0x57178F, 'BA': 0x00C900, 'LA': 0x70D4FF, 'CE': 0xFFFFC7, 'PR': 0xD9FFC7, 'ND': 0xC7FFC7, 'PM': 0xA3FFC7, 'SM': 0x8FFFC7, 'EU': 0x61FFC7, 'GD': 0x45FFC7, 'TB': 0x30FFC7, 'DY': 0x1FFFC7, 'HO': 0x00FF9C, 'ER': 0x00E675, 'TM': 0x00D452, 'YB': 0x00BF38, 'LU': 0x00AB24, 'HF': 0x4DC2FF, 'TA': 0x4DA6FF, 'W': 0x2194D6, 'RE': 0x267DAB, 'OS': 0x266696, 'IR': 0x175487, 'PT': 0xD0D0E0, 'AU': 0xFFD123, 'HG': 0xB8B8D0, 'TL': 0xA6544D, 'PB': 0x575961, 'BI': 0x9E4FB5, 'PO': 0xAB5C00, 'AT': 0x754F45, 'RN': 0x428296, 'FR': 0x420066, 'RA': 0x007D00, 'AC': 0x70ABFA, 'TH': 0x00BAFF, 'PA': 0x00A1FF, 'U': 0x008FFF, 'NP': 0x0080FF, 'PU': 0x006BFF, 'AM': 0x545CF2, 'CM': 0x785CE3, 'BK': 0x8A4FE3, 'CF': 0xA136D4, 'ES': 0xB31FD4, 'FM': 0xB31FBA, 'MD': 0xB30DA6, 'NO': 0xBD0D87, 'LR': 0xC70066, 'RF': 0xCC0059, 'DB': 0xD1004F, 'SG': 0xD90045, 'BH': 0xE00038, 'HS': 0xE6002E, 'MT': 0xEB0026, 'DS': 0xFFFFFF, 'RG': 0xFFFFFF, 'CN': 0xFFFFFF, 'UUT': 0xFFFFFF, 'FL': 0xFFFFFF, 'UUP': 0xFFFFFF, 'LV': 0xFFFFFF, 'UUH': 0xFFFFFF
 })
 
 const DefaultElementSymbolColor = Color(0xFFFFFF)
+const Description = 'Assigns a color to every atom according to its chemical element.'
 
 export function elementSymbolColor(element: ElementSymbol): Color {
     const c = (ElementSymbolColors as { [k: string]: Color })[element];
@@ -38,5 +39,12 @@ export function ElementSymbolColorTheme(props: ColorThemeProps): ColorTheme {
         return DefaultElementSymbolColor
     }
 
-    return { granularity: 'group', color }
+    return {
+        granularity: 'group',
+        color,
+        description: Description,
+        legend: TableLegend(Object.keys(ElementSymbolColors).map(name => {
+            return [name, (ElementSymbolColors as any)[name] as Color] as [string, Color]
+        }))
+    }
 }

+ 3 - 1
src/mol-view/theme/color/shape-group.ts

@@ -19,6 +19,8 @@ export function ShapeGroupColorTheme(props: ColorThemeProps): ColorTheme {
                 return location.shape.colors.ref.value[location.group]
             }
             return DefaultColor
-        }
+        },
+        description: props.description,
+        legend: props.legend
     }
 }

+ 5 - 2
src/mol-view/theme/color/uniform.ts

@@ -4,16 +4,19 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { ColorTheme, ColorThemeProps } from '../color';
+import { ColorTheme, ColorThemeProps, TableLegend } from '../color';
 import { Color } from 'mol-util/color';
 
 const DefaultColor = Color(0xCCCCCC)
+const Description = 'Gives everything the same, uniform color.'
 
 export function UniformColorTheme(props: ColorThemeProps): ColorTheme {
     const color = props.value || DefaultColor
 
     return {
         granularity: 'uniform',
-        color: () => color
+        color: () => color,
+        description: Description,
+        legend: TableLegend([['uniform', color]])
     }
 }

+ 9 - 2
src/mol-view/theme/color/unit-index.ts

@@ -10,13 +10,15 @@ import { StructureElement, Link } from 'mol-model/structure';
 import { ColorTheme, ColorThemeProps, LocationColor } from '../color';
 
 const DefaultColor = Color(0xCCCCCC)
+const Description = 'Gives every unit (single chain or collection of single elements) a unique color based on the position (index) of the unit in the list of units in the structure.'
 
 export function UnitIndexColorTheme(props: ColorThemeProps): ColorTheme {
     let color: LocationColor
+    let scale: ColorScale | undefined = undefined
 
     if (props.structure) {
         const { units } = props.structure
-        const scale = ColorScale.create({ domain: [ 0, units.length - 1 ] })
+        scale = ColorScale.create({ domain: [ 0, units.length - 1 ] })
         const unitIdColor = new Map<number, Color>()
         for (let i = 0, il = units.length; i <il; ++i) {
             unitIdColor.set(units[i].id, scale.color(units[i].id))
@@ -34,5 +36,10 @@ export function UnitIndexColorTheme(props: ColorThemeProps): ColorTheme {
         color = () => DefaultColor
     }
 
-    return { granularity: 'instance', color }
+    return {
+        granularity: 'instance',
+        color,
+        description: Description,
+        legend: scale ? scale.legend : undefined
+    }
 }