Browse Source

wip, color list refactoring

Alexander Rose 5 years ago
parent
commit
257340283b

+ 1 - 1
src/apps/state-docs/pd-to-md.ts

@@ -19,7 +19,7 @@ function paramInfo(param: PD.Any, offset: number): string {
         case 'conditioned': return getParams(param.conditionParams, offset);
         case 'multi-select': return `Array of ${oToS(param.options)}`;
         case 'color': return 'Color as 0xrrggbb';
-        case 'color-scale': return `One of ${oToS(param.options)}`;
+        case 'color-list': return `One of ${oToS(param.options)}`;
         case 'vec3': return `3D vector [x, y, z]`;
         case 'file': return `JavaScript File Handle`;
         case 'select': return `One of ${oToS(param.options)}`;

+ 1 - 1
src/mol-geo/geometry/direct-volume/direct-volume.ts

@@ -81,7 +81,7 @@ export namespace DirectVolume {
             Vec2.create(0.19, 0.0), Vec2.create(0.2, 0.05), Vec2.create(0.25, 0.05), Vec2.create(0.26, 0.0),
             Vec2.create(0.79, 0.0), Vec2.create(0.8, 0.05), Vec2.create(0.85, 0.05), Vec2.create(0.86, 0.0),
         ]),
-        list: PD.ColorScale<ColorListName>('red-yellow-blue', ColorListOptions),
+        list: PD.ColorList<ColorListName>('red-yellow-blue', ColorListOptions),
     }
     export type Params = typeof Params
 

+ 27 - 6
src/mol-plugin/ui/controls/parameters.tsx

@@ -7,8 +7,8 @@
 
 import { Vec2, Vec3 } from '../../../mol-math/linear-algebra';
 import { Color } from '../../../mol-util/color';
-import { ColorListName, getColorListFromName } from '../../../mol-util/color/scale';
-import { ColorNames, ColorNamesValueMap } from '../../../mol-util/color/tables';
+import { ColorListName, getColorListFromName } from '../../../mol-util/color/lists';
+import { ColorNames, ColorNamesValueMap } from '../../../mol-util/color/names';
 import { memoize1 } from '../../../mol-util/memoize';
 import { ParamDefinition as PD } from '../../../mol-util/param-definition';
 import { camelCaseToWords } from '../../../mol-util/string';
@@ -54,7 +54,7 @@ function controlFor(param: PD.Any): ParamControl | undefined {
         case 'conditioned': return ConditionedControl;
         case 'multi-select': return MultiSelectControl;
         case 'color': return ColorControl;
-        case 'color-scale': return ColorScaleControl;
+        case 'color-list': return ColorListControl;
         case 'vec3': return Vec3Control;
         case 'file': return FileControl;
         case 'select': return SelectControl;
@@ -298,14 +298,35 @@ export class ColorControl extends SimpleParam<PD.Color> {
     }
 }
 
-const colorScaleGradient = memoize1((n: ColorListName) => `linear-gradient(to right, ${getColorListFromName(n).map(c => Color.toStyle(c)).join(', ')})`);
+const colorGradientInterpolated = memoize1((colors: Color[]) => {
+    const styles = colors.map(c => Color.toStyle(c))
+    return `linear-gradient(to right, ${styles.join(', ')})`
+});
+
+const colorGradientBanded = memoize1((colors: Color[]) => {
+    const n = colors.length
+    const styles: string[] = [`${Color.toStyle(colors[0])} ${100 * (1 / n)}%`]
+    for (let i = 1, il = n - 1; i < il; ++i) {
+        styles.push(
+            `${Color.toStyle(colors[i])} ${100 * (i / n)}%`,
+            `${Color.toStyle(colors[i])} ${100 * ((i + 1) / n)}%`
+        )
+    }
+    styles.push(`${Color.toStyle(colors[n - 1])} ${100 * ((n - 1) / n)}%`)
+    return `linear-gradient(to right, ${styles.join(', ')})`
+});
+
+function colorGradient(name: ColorListName, banded: boolean) {
+    const { list, type } = getColorListFromName(name)
+    return type === 'qualitative' ? colorGradientBanded(list) : colorGradientInterpolated(list)
+}
 
-export class ColorScaleControl extends SimpleParam<PD.ColorScale<any>> {
+export class ColorListControl extends SimpleParam<PD.ColorList<any>> {
     onChange = (e: React.ChangeEvent<HTMLSelectElement>) => { this.update(e.target.value); }
 
     stripStyle(): React.CSSProperties {
         return {
-            background: colorScaleGradient(this.props.value),
+            background: colorGradient(this.props.value, true),
             position: 'absolute',
             bottom: '0',
             height: '4px',

+ 2 - 2
src/mol-theme/color/cross-link.ts

@@ -11,14 +11,14 @@ import { ColorTheme, LocationColor } from '../color';
 import { Vec3 } from '../../mol-math/linear-algebra';
 import { ParamDefinition as PD } from '../../mol-util/param-definition'
 import { ThemeDataContext } from '../../mol-theme/theme';
-import { ColorListName, ColorListOptions } from '../../mol-util/color/lists';
+import { ColorListName, ColorListOptionsScale } from '../../mol-util/color/lists';
 
 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`).'
 
 export const CrossLinkColorThemeParams = {
     domain: PD.Interval([-10, 10]),
-    list: PD.ColorScale<ColorListName>('red-grey', ColorListOptions),
+    list: PD.ColorList<ColorListName>('red-grey', ColorListOptionsScale),
 }
 export type CrossLinkColorThemeParams = typeof CrossLinkColorThemeParams
 export function getCrossLinkColorThemeParams(ctx: ThemeDataContext) {

+ 13 - 10
src/mol-theme/color/element-index.ts

@@ -1,23 +1,25 @@
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { ColorScale, Color } from '../../mol-util/color';
+import { Color } from '../../mol-util/color';
 import { Location } from '../../mol-model/location';
 import { StructureElement, Link } from '../../mol-model/structure';
 import { OrderedSet } from '../../mol-data/int';
 import { ColorTheme, LocationColor } from '../color';
 import { ParamDefinition as PD } from '../../mol-util/param-definition'
 import { ThemeDataContext } from '../../mol-theme/theme';
-import { ColorListOptions, ColorListName } from '../../mol-util/color/lists';
+import { getPaletteParams, getPalette } from './util';
+import { TableLegend } from '../../mol-util/color/lists';
+import { ScaleLegend } from '../../mol-util/color/scale';
 
 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 const ElementIndexColorThemeParams = {
-    list: PD.ColorScale<ColorListName>('red-yellow-blue', ColorListOptions),
+    ...getPaletteParams({ scaleList: 'red-yellow-blue' }),
 }
 export type ElementIndexColorThemeParams = typeof ElementIndexColorThemeParams
 export function getElementIndexColorThemeParams(ctx: ThemeDataContext) {
@@ -26,7 +28,7 @@ export function getElementIndexColorThemeParams(ctx: ThemeDataContext) {
 
 export function ElementIndexColorTheme(ctx: ThemeDataContext, props: PD.Values<ElementIndexColorThemeParams>): ColorTheme<ElementIndexColorThemeParams> {
     let color: LocationColor
-    let scale = ColorScale.create({ listOrName: props.list, minLabel: 'Start', maxLabel: 'End' })
+    let legend: ScaleLegend | TableLegend | undefined
 
     if (ctx.structure) {
         const { units } = ctx.structure
@@ -40,17 +42,18 @@ export function ElementIndexColorTheme(ctx: ThemeDataContext, props: PD.Values<E
             elementCount += units[i].elements.length
             unitIdIndex.set(units[i].id, i)
         }
-        scale.setDomain(0, elementCount - 1)
-        const scaleColor = scale.color
+
+        const palette = getPalette(elementCount, props)
+        legend = palette.legend
 
         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 scaleColor(cummulativeElementCount.get(unitIndex)! + unitElementIndex)
+                return palette.color(cummulativeElementCount.get(unitIndex)! + unitElementIndex)
             } else if (Link.isLocation(location)) {
                 const unitIndex = unitIdIndex.get(location.aUnit.id)!
-                return scaleColor(cummulativeElementCount.get(unitIndex)! + location.aIndex)
+                return palette.color(cummulativeElementCount.get(unitIndex)! + location.aIndex)
             }
             return DefaultColor
         }
@@ -64,7 +67,7 @@ export function ElementIndexColorTheme(ctx: ThemeDataContext, props: PD.Values<E
         color,
         props,
         description: Description,
-        legend: scale ? scale.legend : undefined
+        legend
     }
 }
 

+ 2 - 2
src/mol-theme/color/hydrophobicity.ts

@@ -11,12 +11,12 @@ import { ColorTheme } from '../color';
 import { ParamDefinition as PD } from '../../mol-util/param-definition'
 import { ThemeDataContext } from '../theme';
 import { ResidueHydrophobicity } from '../../mol-model/structure/model/types';
-import { ColorListName, ColorListOptions } from '../../mol-util/color/lists';
+import { ColorListName, ColorListOptionsScale } from '../../mol-util/color/lists';
 
 const Description = 'Assigns a color to every amino acid according to the "Experimentally determined hydrophobicity scale for proteins at membrane interfaces" by Wimely and White (doi:10.1038/nsb1096-842).'
 
 export const HydrophobicityColorThemeParams = {
-    list: PD.ColorScale<ColorListName>('red-yellow-green', ColorListOptions),
+    list: PD.ColorList<ColorListName>('red-yellow-green', ColorListOptionsScale),
     scale: PD.Select('DGwif', [['DGwif', 'DG water-membrane'], ['DGwoct', 'DG water-octanol'], ['Oct-IF', 'DG difference']])
 }
 export type HydrophobicityColorThemeParams = typeof HydrophobicityColorThemeParams

+ 2 - 2
src/mol-theme/color/sequence-id.ts

@@ -11,13 +11,13 @@ import { Location } from '../../mol-model/location';
 import { ColorTheme } from '../color';
 import { ParamDefinition as PD } from '../../mol-util/param-definition'
 import { ThemeDataContext } from '../../mol-theme/theme';
-import { ColorListOptions, ColorListName } from '../../mol-util/color/lists';
+import { ColorListOptionsScale, ColorListName } from '../../mol-util/color/lists';
 
 const DefaultColor = Color(0xCCCCCC)
 const Description = 'Gives every polymer residue a color based on its `seq_id` value.'
 
 export const SequenceIdColorThemeParams = {
-    list: PD.ColorScale<ColorListName>('rainbow', ColorListOptions),
+    list: PD.ColorList<ColorListName>('rainbow', ColorListOptionsScale),
 }
 export type SequenceIdColorThemeParams = typeof SequenceIdColorThemeParams
 export function getSequenceIdColorThemeParams(ctx: ThemeDataContext) {

+ 2 - 2
src/mol-theme/color/uncertainty.ts

@@ -10,14 +10,14 @@ import { Location } from '../../mol-model/location';
 import { ColorTheme } from '../color';
 import { ParamDefinition as PD } from '../../mol-util/param-definition'
 import { ThemeDataContext } from '../theme';
-import { ColorListName, ColorListOptions } from '../../mol-util/color/lists';
+import { ColorListName, ColorListOptionsScale } from '../../mol-util/color/lists';
 
 const DefaultUncertaintyColor = Color(0xffff99)
 const Description = `Assigns a color based on the uncertainty of an element's position, , e.g. B-factor or RMSF, depending on the data availability and experimental technique.`
 
 export const UncertaintyColorThemeParams = {
     domain: PD.Interval([0, 100]),
-    list: PD.ColorScale<ColorListName>('red-white-blue', ColorListOptions),
+    list: PD.ColorList<ColorListName>('red-white-blue', ColorListOptionsScale),
 }
 export type UncertaintyColorThemeParams = typeof UncertaintyColorThemeParams
 export function getUncertaintyColorThemeParams(ctx: ThemeDataContext) {

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

@@ -21,10 +21,10 @@ export function getPaletteParams(props: Partial<GetPaletteProps> = {}) {
     return {
         palette: PD.MappedStatic('generate', {
             scale: PD.Group({
-                list: PD.ColorScale<ColorListName>(p.scaleList, ColorListOptionsScale),
+                list: PD.ColorList<ColorListName>(p.scaleList, ColorListOptionsScale),
             }, { isFlat: true }),
             set: PD.Group({
-                list: PD.ColorScale<ColorListName>(p.setList, ColorListOptionsSet),
+                list: PD.ColorList<ColorListName>(p.setList, ColorListOptionsSet),
             }, { isFlat: true }),
             generate: PD.Group({
                 ...DistinctColorsParams,
@@ -32,7 +32,7 @@ export function getPaletteParams(props: Partial<GetPaletteProps> = {}) {
             }, { isFlat: true })
         }, {
             options: [
-                ['scale', 'From Scale'],
+                ['scale', 'Interpolate'],
                 ['set', 'From Set'],
                 ['generate', 'Generate Distinct']
             ]
@@ -62,7 +62,7 @@ export function getPalette(count: number, props: PaletteProps) {
         let colors: Color[]
         if (props.palette.name === 'set') {
             const listOrName = props.palette.params.list
-            colors = typeof listOrName === 'string' ? getColorListFromName(listOrName) : listOrName
+            colors = typeof listOrName === 'string' ? getColorListFromName(listOrName).list : listOrName
         } else {
             count = Math.min(count, props.palette.params.maxCount)
             colors = distinctColors(count, props.palette.params)

+ 3 - 3
src/mol-util/color/lists.ts

@@ -125,7 +125,7 @@ export const ColorLists = {
         '',
         [0x67001f, 0xb2182b, 0xd6604d, 0xf4a582, 0xfddbc7, 0xffffff, 0xe0e0e0, 0xbababa, 0x878787, 0x4d4d4d, 0x1a1a1a]
     ),
-    'purple-orange': ColorList('Purple-Orange', 'diverging',
+    'orange-purple': ColorList('Orange-Purple', 'diverging',
         '',
         [0x7f3b08, 0xb35806, 0xe08214, 0xfdb863, 0xfee0b6, 0xf7f7f7, 0xd8daeb, 0xb2abd2, 0x8073ac, 0x542788, 0x2d004b]
     ),
@@ -209,7 +209,7 @@ export const ColorListOptionsScale = ColorListOptions.filter(v => ColorLists[v[0
 export const ColorListOptionsSet = ColorListOptions.filter(v => ColorLists[v[0]].type === 'qualitative')
 
 export function getColorListFromName(name: ColorListName) {
-    if (name in ColorLists) return ColorLists[name as ColorListName].list
+    if (name in ColorLists) return ColorLists[name as ColorListName]
     console.warn(`unknown color list named '${name}'`)
-    return ColorLists['red-yellow-blue'].list
+    return ColorLists['red-yellow-blue']
 }

+ 1 - 1
src/mol-util/color/scale.ts

@@ -44,7 +44,7 @@ export type ColorScaleProps = Partial<typeof DefaultColorScaleProps>
 export namespace ColorScale {
     export function create(props: ColorScaleProps): ColorScale {
         const { domain, reverse, listOrName } = { ...DefaultColorScaleProps, ...props }
-        const list = typeof listOrName === 'string' ? getColorListFromName(listOrName) : listOrName
+        const list = typeof listOrName === 'string' ? getColorListFromName(listOrName).list : listOrName
 
         const colors = reverse ? list.slice().reverse() : list
         const count1 = colors.length - 1

+ 5 - 5
src/mol-util/param-definition.ts

@@ -62,13 +62,13 @@ export namespace ParamDefinition {
         return setInfo<Select<T>>({ type: 'select', defaultValue, options }, info)
     }
 
-    export interface ColorScale<T extends string> extends Base<T> {
-        type: 'color-scale'
+    export interface ColorList<T extends string> extends Base<T> {
+        type: 'color-list'
         /** array of (value, label) tuples */
         options: [T, string][]
     }
-    export function ColorScale<T extends string>(defaultValue: T, options: [T, string][], info?: Info): ColorScale<T> {
-        return setInfo<ColorScale<T>>({ type: 'color-scale', defaultValue, options }, info)
+    export function ColorList<T extends string>(defaultValue: T, options: [T, string][], info?: Info): ColorList<T> {
+        return setInfo<ColorList<T>>({ type: 'color-list', defaultValue, options }, info)
     }
 
     export interface MultiSelect<E extends string, T = E[]> extends Base<T> {
@@ -242,7 +242,7 @@ export namespace ParamDefinition {
 
     export type Any =
         | Value<any> | Select<any> | MultiSelect<any> | BooleanParam | Text | Color | Vec3 | Numeric | FileParam | Interval | LineGraph
-        | ColorScale<any> | Group<any> | Mapped<any> | Converted<any, any> | Conditioned<any, any, any> | ScriptExpression | ObjectList
+        | ColorList<any> | Group<any> | Mapped<any> | Converted<any, any> | Conditioned<any, any, any> | ScriptExpression | ObjectList
 
     export type Params = { [k: string]: Any }
     export type Values<T extends Params> = { [k in keyof T]: T[k]['defaultValue'] }

+ 1 - 1
src/tests/browser/render-asa.ts

@@ -101,7 +101,7 @@ const DefaultColor = Color(0xFFFFFF)
 const Description = 'Assigns a color based on the relative accessible surface area of a residue.'
 
 export const AccessibleSurfaceAreaColorThemeParams = {
-    list: PD.ColorScale<ColorListName>('rainbow', ColorListOptions)
+    list: PD.ColorList<ColorListName>('rainbow', ColorListOptions)
 }
 export type AccessibleSurfaceAreaColorThemeParams = typeof AccessibleSurfaceAreaColorThemeParams
 export function getAccessibleSurfaceAreaColorThemeParams(ctx: ThemeDataContext) {