diff --git a/src/mol-theme/color.ts b/src/mol-theme/color.ts index 0b7cf68dc79c7a859aacd33ab327907c2c34d113..9288607d6d919b675f76a7ddd2b686f63c5bbb55 100644 --- a/src/mol-theme/color.ts +++ b/src/mol-theme/color.ts @@ -31,6 +31,8 @@ import { IllustrativeColorThemeProvider } from './color/illustrative'; import { HydrophobicityColorThemeProvider } from './color/hydrophobicity'; import { ModelIndexColorThemeProvider } from './color/model-index'; import { OccupancyColorThemeProvider } from './color/occupancy'; +import { OperatorNameColorThemeProvider } from './color/operator-name'; +import { OperatorHklColorThemeProvider } from './color/operator-hkl'; export type LocationColor = (location: Location, isSecondary: boolean) => Color @@ -83,6 +85,8 @@ export const BuiltInColorThemes = { 'model-index': ModelIndexColorThemeProvider, 'molecule-type': MoleculeTypeColorThemeProvider, 'occupancy': OccupancyColorThemeProvider, + 'operator-hkl': OperatorHklColorThemeProvider, + 'operator-name': OperatorNameColorThemeProvider, 'polymer-id': PolymerIdColorThemeProvider, 'polymer-index': PolymerIndexColorThemeProvider, 'residue-name': ResidueNameColorThemeProvider, diff --git a/src/mol-theme/color/operator-hkl.ts b/src/mol-theme/color/operator-hkl.ts new file mode 100644 index 0000000000000000000000000000000000000000..679a18c438b65a109e5d215a2c534ba1a7db7eaa --- /dev/null +++ b/src/mol-theme/color/operator-hkl.ts @@ -0,0 +1,124 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { Color } from '../../mol-util/color'; +import { StructureElement, Link, Structure } from '../../mol-model/structure'; +import { Location } from '../../mol-model/location'; +import { ColorTheme, LocationColor } from '../color'; +import { ParamDefinition as PD } from '../../mol-util/param-definition' +import { ThemeDataContext } from '../theme'; +import { getPaletteParams, getPalette } from '../../mol-util/color/palette'; +import { ScaleLegend, TableLegend } from '../../mol-util/legend'; +import { Vec3 } from '../../mol-math/linear-algebra'; +import { integerDigitCount } from '../../mol-util/number'; + +const DefaultColor = Color(0xCCCCCC) +const Description = `Assigns a color based on the operator HKL value of a transformed chain.` + +export const OperatorHklColorThemeParams = { + ...getPaletteParams({ type: 'set', setList: 'set-3' }), +} +export type OperatorHklColorThemeParams = typeof OperatorHklColorThemeParams +export function getOperatorHklColorThemeParams(ctx: ThemeDataContext) { + const params = PD.clone(OperatorHklColorThemeParams) + if (ctx.structure) { + if (getOperatorHklSerialMap(ctx.structure.root).map.size > 12) { + params.palette.defaultValue.name = 'scale' + params.palette.defaultValue.params = { + ...params.palette.defaultValue.params, + list: 'red-yellow-blue' + } + } + } + return params +} + +const hklOffset = 10000 + +function hklKey(hkl: Vec3) { + return hkl.map(v => `${v + hklOffset}`.padStart(5, '0')).join('') +} + +function hklKeySplit(key: string) { + const len = integerDigitCount(hklOffset, 0) + const h = parseInt(key.substr(0, len)) + const k = parseInt(key.substr(len, len)) + const l = parseInt(key.substr(len + len, len)) + return [ h - hklOffset, k - hklOffset, l - hklOffset ] as Vec3 +} + +function formatHkl(hkl: Vec3) { + return hkl.map(v => v + 5).join('') +} + +function getOperatorHklSerialMap(structure: Structure) { + const map = new Map<string, number>() + const set = new Set<string>() + for (let i = 0, il = structure.units.length; i < il; ++i) { + const k = hklKey(structure.units[i].conformation.operator.hkl) + set.add(k) + } + const arr = Array.from(set).sort() + arr.forEach(k => map.set(k, map.size)) + const min = hklKeySplit(arr[0]) + const max = hklKeySplit(arr[arr.length - 1]) + return { min, max, map } +} + +export function OperatorHklColorTheme(ctx: ThemeDataContext, props: PD.Values<OperatorHklColorThemeParams>): ColorTheme<OperatorHklColorThemeParams> { + let color: LocationColor + let legend: ScaleLegend | TableLegend | undefined + + if (ctx.structure) { + const { min, max, map } = getOperatorHklSerialMap(ctx.structure.root) + + const labelTable: string[] = [] + map.forEach((v, k) => { + const i = v % map.size + const label = formatHkl(hklKeySplit(k)) + if (labelTable[i] === undefined) labelTable[i] = label + else labelTable[i] += `, ${label}` + }) + + props.palette.params.minLabel = formatHkl(min) + props.palette.params.maxLabel = formatHkl(max) + props.palette.params.valueLabel = (i: number) => labelTable[i] + + const palette = getPalette(map.size, props) + legend = palette.legend + + color = (location: Location): Color => { + let serial: number | undefined = undefined + if (StructureElement.Location.is(location)) { + const k = hklKey(location.unit.conformation.operator.hkl) + serial = map.get(k) + } else if (Link.isLocation(location)) { + const k = hklKey(location.aUnit.conformation.operator.hkl) + serial = map.get(k) + } + return serial === undefined ? DefaultColor : palette.color(serial) + } + } else { + color = () => DefaultColor + } + + return { + factory: OperatorHklColorTheme, + granularity: 'instance', + color, + props, + description: Description, + legend + } +} + +export const OperatorHklColorThemeProvider: ColorTheme.Provider<OperatorHklColorThemeParams> = { + label: 'Operator HKL', + factory: OperatorHklColorTheme, + getParams: getOperatorHklColorThemeParams, + defaultValues: PD.getDefaultValues(OperatorHklColorThemeParams), + isApplicable: (ctx: ThemeDataContext) => !!ctx.structure +} \ No newline at end of file diff --git a/src/mol-theme/color/operator-name.ts b/src/mol-theme/color/operator-name.ts new file mode 100644 index 0000000000000000000000000000000000000000..934cc2a43efd44ceee8fe1e819e0b4281b782a66 --- /dev/null +++ b/src/mol-theme/color/operator-name.ts @@ -0,0 +1,90 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { Color } from '../../mol-util/color'; +import { StructureElement, Link, Structure } from '../../mol-model/structure'; +import { Location } from '../../mol-model/location'; +import { ColorTheme, LocationColor } from '../color'; +import { ParamDefinition as PD } from '../../mol-util/param-definition' +import { ThemeDataContext } from '../theme'; +import { getPaletteParams, getPalette } from '../../mol-util/color/palette'; +import { ScaleLegend, TableLegend } from '../../mol-util/legend'; + +const DefaultColor = Color(0xCCCCCC) +const Description = `Assigns a color based on the operator name of a transformed chain.` + +export const OperatorNameColorThemeParams = { + ...getPaletteParams({ type: 'set', setList: 'set-3' }), +} +export type OperatorNameColorThemeParams = typeof OperatorNameColorThemeParams +export function getOperatorNameColorThemeParams(ctx: ThemeDataContext) { + const params = PD.clone(OperatorNameColorThemeParams) + if (ctx.structure) { + if (getOperatorNameSerialMap(ctx.structure.root).size > 12) { + params.palette.defaultValue.name = 'scale' + params.palette.defaultValue.params = { + ...params.palette.defaultValue.params, + list: 'red-yellow-blue' + } + } + } + return params +} + +function getOperatorNameSerialMap(structure: Structure) { + const map = new Map<string, number>() + for (let i = 0, il = structure.units.length; i < il; ++i) { + const name = structure.units[i].conformation.operator.name + if (!map.has(name)) map.set(name, map.size) + } + return map +} + +export function OperatorNameColorTheme(ctx: ThemeDataContext, props: PD.Values<OperatorNameColorThemeParams>): ColorTheme<OperatorNameColorThemeParams> { + let color: LocationColor + let legend: ScaleLegend | TableLegend | undefined + + if (ctx.structure) { + const operatorNameSerialMap = getOperatorNameSerialMap(ctx.structure.root) + + const labelTable = Array.from(operatorNameSerialMap.keys()) + props.palette.params.valueLabel = (i: number) => labelTable[i] + + const palette = getPalette(operatorNameSerialMap.size, props) + legend = palette.legend + + color = (location: Location): Color => { + let serial: number | undefined = undefined + if (StructureElement.Location.is(location)) { + const name = location.unit.conformation.operator.name + serial = operatorNameSerialMap.get(name) + } else if (Link.isLocation(location)) { + const name = location.aUnit.conformation.operator.name + serial = operatorNameSerialMap.get(name) + } + return serial === undefined ? DefaultColor : palette.color(serial) + } + } else { + color = () => DefaultColor + } + + return { + factory: OperatorNameColorTheme, + granularity: 'instance', + color, + props, + description: Description, + legend + } +} + +export const OperatorNameColorThemeProvider: ColorTheme.Provider<OperatorNameColorThemeParams> = { + label: 'Operator Name', + factory: OperatorNameColorTheme, + getParams: getOperatorNameColorThemeParams, + defaultValues: PD.getDefaultValues(OperatorNameColorThemeParams), + isApplicable: (ctx: ThemeDataContext) => !!ctx.structure +} \ No newline at end of file diff --git a/src/mol-util/number.ts b/src/mol-util/number.ts index cf398ed591eecf1f680053c6bb03c9dfd5b494ac..a1c642c51fb6b9cf3eb234112e21ed3998a29a50 100644 --- a/src/mol-util/number.ts +++ b/src/mol-util/number.ts @@ -1,5 +1,7 @@ /** * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> */ /**