Browse Source

Merge pull request #421 from molstar/color-theme-typing

better color theme typing
Alexander Rose 2 years ago
parent
commit
769220bd82

+ 13 - 18
src/mol-geo/geometry/color-data.ts

@@ -12,10 +12,12 @@ import { Vec2, Vec3, Vec4 } from '../../mol-math/linear-algebra';
 import { LocationIterator } from '../util/location-iterator';
 import { NullLocation } from '../../mol-model/location';
 import { LocationColor, ColorTheme, ColorVolume } from '../../mol-theme/color';
-import { Geometry } from './geometry';
 import { createNullTexture, Texture } from '../../mol-gl/webgl/texture';
 
-export type ColorType = 'uniform' | 'instance' | 'group' | 'groupInstance' | 'vertex' | 'vertexInstance' | 'volume' | 'volumeInstance' | 'direct'
+export type ColorTypeLocation = 'uniform' | 'instance' | 'group' | 'groupInstance' | 'vertex' | 'vertexInstance';
+export type ColorTypeGrid = 'volume' | 'volumeInstance';
+export type ColorTypeDirect = 'direct';
+export type ColorType = ColorTypeLocation | ColorTypeGrid | ColorTypeDirect;
 
 export type ColorData = {
     uColor: ValueCell<Vec3>,
@@ -29,7 +31,7 @@ export type ColorData = {
     dUsePalette: ValueCell<boolean>,
 }
 
-export function createColors(locationIt: LocationIterator, positionIt: LocationIterator, colorTheme: ColorTheme<any>, colorData?: ColorData): ColorData {
+export function createColors(locationIt: LocationIterator, positionIt: LocationIterator, colorTheme: ColorTheme<any, any>, colorData?: ColorData): ColorData {
     const data = _createColors(locationIt, positionIt, colorTheme, colorData);
     if (colorTheme.palette) {
         ValueCell.updateIfChanged(data.dUsePalette, true);
@@ -40,26 +42,19 @@ export function createColors(locationIt: LocationIterator, positionIt: LocationI
     return data;
 }
 
-function _createColors(locationIt: LocationIterator, positionIt: LocationIterator, colorTheme: ColorTheme<any>, colorData?: ColorData): ColorData {
-    switch (Geometry.getGranularity(locationIt, colorTheme.granularity)) {
+function _createColors(locationIt: LocationIterator, positionIt: LocationIterator, colorTheme: ColorTheme<any, any>, colorData?: ColorData): ColorData {
+    switch (colorTheme.granularity) {
         case 'uniform': return createUniformColor(locationIt, colorTheme.color, colorData);
-        case 'instance': return createInstanceColor(locationIt, colorTheme.color, colorData);
+        case 'instance':
+            return locationIt.nonInstanceable
+                ? createInstanceColor(locationIt, colorTheme.color, colorData)
+                : createGroupColor(locationIt, colorTheme.color, colorData);
         case 'group': return createGroupColor(locationIt, colorTheme.color, colorData);
         case 'groupInstance': return createGroupInstanceColor(locationIt, colorTheme.color, colorData);
         case 'vertex': return createVertexColor(positionIt, colorTheme.color, colorData);
         case 'vertexInstance': return createVertexInstanceColor(positionIt, colorTheme.color, colorData);
-        case 'volume':
-            if (colorTheme.grid) {
-                return createGridColor(colorTheme.grid, 'volume', colorData);
-            } else {
-                throw new Error('Grid missing for "volume" color theme');
-            }
-        case 'volumeInstance':
-            if (colorTheme.grid) {
-                return createGridColor(colorTheme.grid, 'volumeInstance', colorData);
-            } else {
-                throw new Error('Grid missing for "volume" color theme');
-            }
+        case 'volume': return createGridColor(colorTheme.grid, 'volume', colorData);
+        case 'volumeInstance': return createGridColor(colorTheme.grid, 'volumeInstance', colorData);
         case 'direct': return createDirectColor(colorData);
     }
 }

+ 1 - 1
src/mol-repr/volume/slice.ts

@@ -32,7 +32,7 @@ export async function createImage(ctx: VisualContext, volume: Volume, theme: The
     const isoVal = Volume.IsoValue.toAbsolute(isoValue, volume.grid.stats).absoluteValue;
 
     // TODO more color themes
-    const color = theme.color.color(NullLocation, false);
+    const color = 'color' in theme.color ? theme.color.color(NullLocation, false) : Color(0xffffff);
     const [r, g, b] = Color.toRgbNormalized(color);
 
     const {

+ 29 - 11
src/mol-theme/color.ts

@@ -6,7 +6,7 @@
 
 import { Color } from '../mol-util/color';
 import { Location } from '../mol-model/location';
-import { ColorType } from '../mol-geo/geometry/color-data';
+import { ColorType, ColorTypeDirect, ColorTypeGrid, ColorTypeLocation } from '../mol-geo/geometry/color-data';
 import { CarbohydrateSymbolColorThemeProvider } from './color/carbohydrate-symbol';
 import { UniformColorThemeProvider } from './color/uniform';
 import { deepEqual } from '../mol-util';
@@ -48,12 +48,10 @@ export interface ColorVolume {
 }
 
 export { ColorTheme };
-interface ColorTheme<P extends PD.Params> {
-    readonly factory: ColorTheme.Factory<P>
-    readonly granularity: ColorType
-    readonly color: LocationColor
+
+type ColorThemeShared<P extends PD.Params, G extends ColorType> = {
+    readonly factory: ColorTheme.Factory<P, G>
     readonly props: Readonly<PD.Values<P>>
-    readonly grid?: ColorVolume
     /**
      * if palette is defined, 24bit RGB color value normalized to interval [0, 1]
      * is used as index to the colors
@@ -64,6 +62,26 @@ interface ColorTheme<P extends PD.Params> {
     readonly description?: string
     readonly legend?: Readonly<ScaleLegend | TableLegend>
 }
+
+type ColorThemeLocation<P extends PD.Params> = {
+    readonly granularity: ColorTypeLocation
+    readonly color: LocationColor
+} & ColorThemeShared<P, ColorTypeLocation>
+
+type ColorThemeGrid<P extends PD.Params> = {
+    readonly granularity: ColorTypeGrid
+    readonly grid: ColorVolume
+} & ColorThemeShared<P, ColorTypeGrid>
+
+type ColorThemeDirect<P extends PD.Params> = {
+    readonly granularity: ColorTypeDirect
+} & ColorThemeShared<P, ColorTypeDirect>
+
+type ColorTheme<P extends PD.Params, G extends ColorType = ColorTypeLocation> =
+    G extends ColorTypeLocation ? ColorThemeLocation<P> :
+        G extends ColorTypeGrid ? ColorThemeGrid<P> :
+            G extends ColorTypeDirect ? ColorThemeDirect<P> : never
+
 namespace ColorTheme {
     export const enum Category {
         Atom = 'Atom Property',
@@ -82,7 +100,7 @@ namespace ColorTheme {
     export const PaletteScale = (1 << 24) - 1;
 
     export type Props = { [k: string]: any }
-    export type Factory<P extends PD.Params> = (ctx: ThemeDataContext, props: PD.Values<P>) => ColorTheme<P>
+    export type Factory<P extends PD.Params, G extends ColorType> = (ctx: ThemeDataContext, props: PD.Values<P>) => ColorTheme<P, G>
     export const EmptyFactory = () => Empty;
     const EmptyColor = Color(0xCCCCCC);
     export const Empty: ColorTheme<{}> = {
@@ -92,16 +110,16 @@ namespace ColorTheme {
         props: {}
     };
 
-    export function areEqual(themeA: ColorTheme<any>, themeB: ColorTheme<any>) {
+    export function areEqual(themeA: ColorTheme<any, any>, themeB: ColorTheme<any, any>) {
         return themeA.contextHash === themeB.contextHash && themeA.factory === themeB.factory && deepEqual(themeA.props, themeB.props);
     }
 
-    export interface Provider<P extends PD.Params = any, Id extends string = string> extends ThemeProvider<ColorTheme<P>, P, Id> { }
+    export interface Provider<P extends PD.Params = any, Id extends string = string, G extends ColorType = ColorType> extends ThemeProvider<ColorTheme<P, G>, P, Id, G> { }
     export const EmptyProvider: Provider<{}> = { name: '', label: '', category: '', factory: EmptyFactory, getParams: () => ({}), defaultValues: {}, isApplicable: () => true };
 
-    export type Registry = ThemeRegistry<ColorTheme<any>>
+    export type Registry = ThemeRegistry<ColorTheme<any, any>>
     export function createRegistry() {
-        return new ThemeRegistry(BuiltIn as { [k: string]: Provider<any> }, EmptyProvider);
+        return new ThemeRegistry(BuiltIn as { [k: string]: Provider<any, any, any> }, EmptyProvider);
     }
 
     export const BuiltIn = {

+ 3 - 4
src/mol-theme/color/volume-value.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2021-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -9,8 +9,8 @@ import { Color, ColorScale } from '../../mol-util/color';
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
 import { ThemeDataContext } from '../theme';
 import { ColorNames } from '../../mol-util/color/names';
+import { ColorTypeDirect } from '../../mol-geo/geometry/color-data';
 
-const DefaultColor = Color(0xCCCCCC);
 const Description = 'Assign color based on the given value of a volume cell.';
 
 export const VolumeValueColorThemeParams = {
@@ -30,7 +30,7 @@ export function getVolumeValueColorThemeParams(ctx: ThemeDataContext) {
     return VolumeValueColorThemeParams; // TODO return copy
 }
 
-export function VolumeValueColorTheme(ctx: ThemeDataContext, props: PD.Values<VolumeValueColorThemeParams>): ColorTheme<VolumeValueColorThemeParams> {
+export function VolumeValueColorTheme(ctx: ThemeDataContext, props: PD.Values<VolumeValueColorThemeParams>): ColorTheme<VolumeValueColorThemeParams, ColorTypeDirect> {
     const scale = ColorScale.create({ domain: [0, 1], listOrName: props.colorList.colors });
 
     const colors: Color[] = [];
@@ -43,7 +43,6 @@ export function VolumeValueColorTheme(ctx: ThemeDataContext, props: PD.Values<Vo
     return {
         factory: VolumeValueColorTheme,
         granularity: 'direct',
-        color: () => DefaultColor,
         props: props,
         description: Description,
         legend: scale.legend,

+ 5 - 4
src/mol-theme/theme.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -12,6 +12,7 @@ import { ParamDefinition as PD } from '../mol-util/param-definition';
 import { Shape } from '../mol-model/shape';
 import { CustomProperty } from '../mol-model-props/common/custom-property';
 import { objectForEach } from '../mol-util/object';
+import { ColorType } from '../mol-geo/geometry/color-data';
 
 export interface ThemeRegistryContext {
     colorThemeRegistry: ColorTheme.Registry
@@ -28,7 +29,7 @@ export interface ThemeDataContext {
 export { Theme };
 
 interface Theme {
-    color: ColorTheme<any>
+    color: ColorTheme<any, any>
     size: SizeTheme<any>
     // label: LabelTheme // TODO
 }
@@ -65,7 +66,7 @@ namespace Theme {
 
 //
 
-export interface ThemeProvider<T extends ColorTheme<P> | SizeTheme<P>, P extends PD.Params, Id extends string = string> {
+export interface ThemeProvider<T extends ColorTheme<P, G> | SizeTheme<P>, P extends PD.Params, Id extends string = string, G extends ColorType = ColorType> {
     readonly name: Id
     readonly label: string
     readonly category: string
@@ -83,7 +84,7 @@ function getTypes(list: { name: string, provider: ThemeProvider<any, any> }[]) {
     return list.map(e => [e.name, e.provider.label, e.provider.category] as [string, string, string]);
 }
 
-export class ThemeRegistry<T extends ColorTheme<any> | SizeTheme<any>> {
+export class ThemeRegistry<T extends ColorTheme<any, any> | SizeTheme<any>> {
     private _list: { name: string, provider: ThemeProvider<T, any> }[] = [];
     private _map = new Map<string, ThemeProvider<T, any>>();
     private _name = new Map<ThemeProvider<T, any>, string>();