diff --git a/src/apps/canvas/component/structure-representation.tsx b/src/apps/canvas/component/structure-representation.tsx index 13cc8cac48dfe6179a956df3cc56147a5d33c513..4cc29f75c99100dc55dbcefd2073afe7e0500d05 100644 --- a/src/apps/canvas/component/structure-representation.tsx +++ b/src/apps/canvas/component/structure-representation.tsx @@ -220,8 +220,8 @@ export class StructureRepresentationComponent extends React.Component<StructureR 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> + <span style={{float: 'left', padding: '6px', color: 'white', fontWeight: 'bold', backgroundColor: 'rgba(0, 0, 0, 0.2)'}}>{ct.legend.minLabel}</span> + <span style={{float: 'right', padding: '6px', color: 'white', fontWeight: 'bold', backgroundColor: 'rgba(0, 0, 0, 0.2)'}}>{ct.legend.maxLabel}</span> </div> : ct.legend && ct.legend.kind === 'table-legend' ? <div> diff --git a/src/mol-geo/representation/structure/visual/util/polymer/trace-iterator.ts b/src/mol-geo/representation/structure/visual/util/polymer/trace-iterator.ts index e4b98c009cfe449ea2bd183a5280b6c564fcff9c..37bfc18d317b5c96c015f13bda2c9c15bafbb37b 100644 --- a/src/mol-geo/representation/structure/visual/util/polymer/trace-iterator.ts +++ b/src/mol-geo/representation/structure/visual/util/polymer/trace-iterator.ts @@ -11,7 +11,7 @@ import Iterator from 'mol-data/iterator'; import { Vec3 } from 'mol-math/linear-algebra'; import SortedRanges from 'mol-data/int/sorted-ranges'; import { CoarseSphereConformation, CoarseGaussianConformation } from 'mol-model/structure/model/properties/coarse'; -import { getMoleculeType, getElementIndexForAtomRole } from 'mol-model/structure/util'; +import { getAtomicMoleculeType, getElementIndexForAtomRole } from 'mol-model/structure/util'; import { getPolymerRanges } from '../polymer'; /** @@ -165,7 +165,7 @@ export class AtomicPolymerTraceIterator implements Iterator<PolymerTraceElement> value.first = residueIndex === this.residueSegmentMin value.last = residueIndex === this.residueSegmentMax value.secStrucChange = this.unit.model.properties.secondaryStructure.key[residueIndex] !== this.unit.model.properties.secondaryStructure.key[residueIndex + 1] - value.moleculeType = getMoleculeType(this.unit.model, residueIndex) + value.moleculeType = getAtomicMoleculeType(this.unit.model, residueIndex) if (!residueIt.hasNext) { this.state = AtomicPolymerTraceIteratorState.nextPolymer diff --git a/src/mol-model/structure/structure/carbohydrates/compute.ts b/src/mol-model/structure/structure/carbohydrates/compute.ts index d983593c0036ca5c3107a784781e6406c984e366..68dae32ba3bca65faaac36855f150d215c5f513d 100644 --- a/src/mol-model/structure/structure/carbohydrates/compute.ts +++ b/src/mol-model/structure/structure/carbohydrates/compute.ts @@ -13,7 +13,7 @@ import PrincipalAxes from 'mol-math/linear-algebra/matrix/principal-axes'; import { fillSerial } from 'mol-util/array'; import { ResidueIndex } from '../../model'; import { ElementSymbol, MoleculeType } from '../../model/types'; -import { getMoleculeType, getPositionMatrix } from '../../util'; +import { getAtomicMoleculeType, getPositionMatrix } from '../../util'; import StructureElement from '../element'; import Structure from '../structure'; import Unit from '../unit'; @@ -164,7 +164,7 @@ export function computeCarbohydrates(structure: Structure): Carbohydrates { const saccharideComp = SaccharideNameMap.get(label_comp_id.value(residueIndex)) || UnknownSaccharideComponent if (saccharideComp === UnknownSaccharideComponent) { - if (getMoleculeType(unit.model, residueIndex) !== MoleculeType.saccharide) continue + if (getAtomicMoleculeType(unit.model, residueIndex) !== MoleculeType.saccharide) continue } if (!sugarResidueMap) { diff --git a/src/mol-model/structure/util.ts b/src/mol-model/structure/util.ts index ba416db932b2ef93f2ae7a66884dcbc214773c16..d09d0c6378ba524a2e05a2e0f24bb3b645dd9fdc 100644 --- a/src/mol-model/structure/util.ts +++ b/src/mol-model/structure/util.ts @@ -10,7 +10,30 @@ import { Vec3 } from 'mol-math/linear-algebra'; import { Unit } from './structure'; import Matrix from 'mol-math/linear-algebra/matrix/matrix'; -export function getMoleculeType(model: Model, rI: ResidueIndex) { +export function getCoarseBegCompId(unit: Unit.Spheres | Unit.Gaussians, element: ElementIndex) { + const entityKey = unit.coarseElements.entityKey[element] + const seq = unit.model.sequence.byEntityKey[entityKey] + const seq_id_begin = unit.coarseElements.seq_id_begin.value(element) + return seq.compId.value(seq_id_begin - 1) // 1-indexed +} + +export function getElementMoleculeType(unit: Unit, element: ElementIndex) { + let compId = '' + switch (unit.kind) { + case Unit.Kind.Atomic: + compId = unit.model.atomicHierarchy.residues.label_comp_id.value(unit.residueIndex[element]) + break + case Unit.Kind.Spheres: + case Unit.Kind.Gaussians: + compId = getCoarseBegCompId(unit, element) + break + } + const chemCompMap = unit.model.properties.chemicalComponentMap + const cc = chemCompMap.get(compId) + return cc ? cc.moleculeType : MoleculeType.unknown +} + +export function getAtomicMoleculeType(model: Model, rI: ResidueIndex) { const compId = model.atomicHierarchy.residues.label_comp_id.value(rI) const chemCompMap = model.properties.chemicalComponentMap const cc = chemCompMap.get(compId) @@ -36,7 +59,7 @@ export function getElementIndexForAtomId(model: Model, rI: ResidueIndex, atomId: } export function getElementIndexForAtomRole(model: Model, rI: ResidueIndex, atomRole: AtomRole) { - const atomId = getAtomIdForAtomRole(getMoleculeType(model, rI), atomRole) + const atomId = getAtomIdForAtomRole(getAtomicMoleculeType(model, rI), atomRole) return getElementIndexForAtomId(model, rI, atomId) } diff --git a/src/mol-util/color/tables.ts b/src/mol-util/color/tables.ts index eff1da231b8a5e3f26ff1b21c158a1206d84b542..6ee4aa931104f3307f87d2717ec01a9f36d391c4 100644 --- a/src/mol-util/color/tables.ts +++ b/src/mol-util/color/tables.ts @@ -4,7 +4,7 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { ColorMap, ColorTable, Color } from './color'; +import { ColorMap, ColorTable } from './color'; /** * Brewer Color Lists diff --git a/src/mol-view/label.ts b/src/mol-view/label.ts index 39fad6853ccecbd87d3a706083bc9708eba2d550..840ca3f4bed5f2a2776612b7966d8e3253197983 100644 --- a/src/mol-view/label.ts +++ b/src/mol-view/label.ts @@ -53,24 +53,24 @@ export function linkLabel(link: Link.Location) { return `${elementLabel(elementLocA)} - ${elementLabel(elementLocB)}` } -export function elementLabel(element: StructureElement) { - const model = element.unit.model.label - const instance = element.unit.conformation.operator.name +export function elementLabel(location: StructureElement) { + const model = location.unit.model.label + const instance = location.unit.conformation.operator.name let label = '' - if (Unit.isAtomic(element.unit)) { - const asym_id = Props.chain.auth_asym_id(element) - const seq_id = Props.residue.auth_seq_id(element) - const comp_id = Props.residue.auth_comp_id(element) - const atom_id = Props.atom.auth_atom_id(element) + if (Unit.isAtomic(location.unit)) { + const asym_id = Props.chain.auth_asym_id(location) + const seq_id = Props.residue.auth_seq_id(location) + const comp_id = Props.residue.auth_comp_id(location) + const atom_id = Props.atom.auth_atom_id(location) label = `[${comp_id}]${seq_id}:${asym_id}.${atom_id}` - } else if (Unit.isCoarse(element.unit)) { - const asym_id = Props.coarse.asym_id(element) - const seq_id_begin = Props.coarse.seq_id_begin(element) - const seq_id_end = Props.coarse.seq_id_end(element) + } else if (Unit.isCoarse(location.unit)) { + const asym_id = Props.coarse.asym_id(location) + const seq_id_begin = Props.coarse.seq_id_begin(location) + const seq_id_end = Props.coarse.seq_id_end(location) if (seq_id_begin === seq_id_end) { - const entityKey = Props.coarse.entityKey(element) - const seq = element.unit.model.sequence.byEntityKey[entityKey] + const entityIndex = Props.coarse.entityKey(location) + const seq = location.unit.model.sequence.byEntityKey[entityIndex] const comp_id = seq.compId.value(seq_id_begin - 1) // 1-indexed label = `[${comp_id}]${seq_id_begin}:${asym_id}` } else { diff --git a/src/mol-view/theme/color.ts b/src/mol-view/theme/color.ts index 4f6ff16bd608337a7e4b11d37275836ce53c59e1..6f0cecf67eb5bc476adf0a83157f05702a53073c 100644 --- a/src/mol-view/theme/color.ts +++ b/src/mol-view/theme/color.ts @@ -19,6 +19,8 @@ import { CrossLinkColorTheme } from './color/cross-link'; import { ShapeGroupColorTheme } from './color/shape-group'; import { CustomColorTheme } from './color/custom'; import { ResidueNameColorTheme } from './color/residue-name'; +import { SequenceIdColorTheme } from './color/sequence-id'; +import { SecondaryStructureColorTheme } from './color/secondary-structure'; export type LocationColor = (location: Location, isSecondary: boolean) => Color @@ -49,16 +51,18 @@ export interface ColorTheme { export function ColorTheme(props: ColorThemeProps): ColorTheme { switch (props.name) { - case 'element-index': return ElementIndexColorTheme(props) case 'carbohydrate-symbol': return CarbohydrateSymbolColorTheme(props) - case 'cross-link': return CrossLinkColorTheme(props) case 'chain-id': return ChainIdColorTheme(props) + case 'cross-link': return CrossLinkColorTheme(props) + case 'custom': return CustomColorTheme(props) + case 'element-index': return ElementIndexColorTheme(props) case 'element-symbol': return ElementSymbolColorTheme(props) case 'residue-name': return ResidueNameColorTheme(props) + case 'secondary-structure': return SecondaryStructureColorTheme(props) + case 'sequence-id': return SequenceIdColorTheme(props) + case 'shape-group': return ShapeGroupColorTheme(props) case 'unit-index': return UnitIndexColorTheme(props) case 'uniform': return UniformColorTheme(props) - case 'shape-group': return ShapeGroupColorTheme(props) - case 'custom': return CustomColorTheme(props) } } @@ -74,16 +78,18 @@ export interface ColorThemeProps { } export const ColorThemeInfo = { - 'element-index': {}, 'carbohydrate-symbol': {}, - 'cross-link': {}, 'chain-id': {}, + 'cross-link': {}, + 'custom': {}, + 'element-index': {}, 'element-symbol': {}, 'residue-name': {}, + 'secondary-structure': {}, + 'sequence-id': {}, + 'shape-group': {}, 'unit-index': {}, 'uniform': {}, - 'shape-group': {}, - 'custom': {} } export type ColorThemeName = keyof typeof ColorThemeInfo export const ColorThemeNames = Object.keys(ColorThemeInfo) \ No newline at end of file diff --git a/src/mol-view/theme/color/secondary-structure.ts b/src/mol-view/theme/color/secondary-structure.ts new file mode 100644 index 0000000000000000000000000000000000000000..55e76a3da44858ad6703322fc7956abfc38f3434 --- /dev/null +++ b/src/mol-view/theme/color/secondary-structure.ts @@ -0,0 +1,82 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { Color, ColorMap } from 'mol-util/color'; +import { StructureElement, Unit, Link, ElementIndex } from 'mol-model/structure'; +import { Location } from 'mol-model/location'; +import { ColorThemeProps, ColorTheme, TableLegend } from '../color'; +import { SecondaryStructureType, MoleculeType } from 'mol-model/structure/model/types'; +import { getElementMoleculeType } from 'mol-model/structure/util'; + +// from Jmol http://jmol.sourceforge.net/jscolors/ (shapely) +const SecondaryStructureColors = ColorMap({ + 'alphaHelix': 0xFF0080, + 'threeTenHelix': 0xA00080, + 'piHelix': 0x600080, + 'betaTurn': 0x6080FF, + 'betaStrand': 0xFFC800, + 'coil': 0xFFFFFF, + + 'dna': 0xAE00FE, + 'rna': 0xFD0162, + + 'carbohydrate': 0xA6A6FA +}) + +const DefaultSecondaryStructureColor = Color(0x808080) +const Description = 'Assigns a color based on the type of secondary structure and basic molecule type.' + +export function secondaryStructureColor(unit: Unit, element: ElementIndex): Color { + let secStrucType = SecondaryStructureType.create(SecondaryStructureType.Flag.None) + if (Unit.isAtomic(unit)) { + secStrucType = unit.model.properties.secondaryStructure.type[unit.residueIndex[element]] + } + + if (SecondaryStructureType.is(secStrucType, SecondaryStructureType.Flag.Helix)) { + if (SecondaryStructureType.is(secStrucType, SecondaryStructureType.Flag.Helix3Ten)) { + return SecondaryStructureColors.threeTenHelix + } else if (SecondaryStructureType.is(secStrucType, SecondaryStructureType.Flag.HelixPi)) { + return SecondaryStructureColors.piHelix + } + return SecondaryStructureColors.alphaHelix + } else if (SecondaryStructureType.is(secStrucType, SecondaryStructureType.Flag.Beta)) { + return SecondaryStructureColors.betaStrand + } else if (SecondaryStructureType.is(secStrucType, SecondaryStructureType.Flag.Turn)) { + return SecondaryStructureColors.coil + } else { + const moleculeType = getElementMoleculeType(unit, element) + if (moleculeType === MoleculeType.DNA) { + return SecondaryStructureColors.dna + } else if (moleculeType === MoleculeType.RNA) { + return SecondaryStructureColors.rna + } else if (moleculeType === MoleculeType.saccharide) { + return SecondaryStructureColors.carbohydrate + } else if (moleculeType === MoleculeType.protein) { + return SecondaryStructureColors.coil + } + } + return DefaultSecondaryStructureColor +} + +export function SecondaryStructureColorTheme(props: ColorThemeProps): ColorTheme { + function color(location: Location): Color { + if (StructureElement.isLocation(location)) { + return secondaryStructureColor(location.unit, location.element) + } else if (Link.isLocation(location)) { + return secondaryStructureColor(location.aUnit, location.aUnit.elements[location.aIndex]) + } + return DefaultSecondaryStructureColor + } + + return { + granularity: 'group', + color, + description: Description, + legend: TableLegend(Object.keys(SecondaryStructureColors).map(name => { + return [name, (SecondaryStructureColors as any)[name] as Color] as [string, Color] + }).concat([[ 'Other', DefaultSecondaryStructureColor ]])) + } +} \ No newline at end of file diff --git a/src/mol-view/theme/color/sequence-id.ts b/src/mol-view/theme/color/sequence-id.ts new file mode 100644 index 0000000000000000000000000000000000000000..7f68bf57e787716054a599e67c789c1214e5814e --- /dev/null +++ b/src/mol-view/theme/color/sequence-id.ts @@ -0,0 +1,92 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { Unit, StructureElement, Link, ElementIndex } from 'mol-model/structure'; + +import { ColorScale, Color } from 'mol-util/color'; +import { Location } from 'mol-model/location'; +import { ColorThemeProps, ColorTheme } from '../color'; +import { ColorOther } from 'mol-util/color/tables'; + +const DefaultColor = Color(0xCCCCCC) +const Description = 'Gives every polymer residue a color based on its `seq_id` value.' + +function getSeqId(unit: Unit, element: ElementIndex): number { + const { model } = unit + switch (unit.kind) { + case Unit.Kind.Atomic: + const residueIndex = model.atomicHierarchy.residueAtomSegments.index[element] + return model.atomicHierarchy.residues.label_seq_id.value(residueIndex) + case Unit.Kind.Spheres: + return Math.round( + (model.coarseHierarchy.spheres.seq_id_begin.value(element) + + model.coarseHierarchy.spheres.seq_id_end.value(element)) / 2 + ) + case Unit.Kind.Gaussians: + return Math.round( + (model.coarseHierarchy.gaussians.seq_id_begin.value(element) + + model.coarseHierarchy.gaussians.seq_id_end.value(element)) / 2 + ) + } +} + +function getSequenceLength(unit: Unit, element: ElementIndex) { + const { model } = unit + let entityId = '' + switch (unit.kind) { + case Unit.Kind.Atomic: + const chainIndex = model.atomicHierarchy.chainAtomSegments.index[element] + entityId = model.atomicHierarchy.chains.label_entity_id.value(chainIndex) + break + case Unit.Kind.Spheres: + entityId = model.coarseHierarchy.spheres.entity_id.value(element) + break + case Unit.Kind.Gaussians: + entityId = model.coarseHierarchy.gaussians.entity_id.value(element) + break + } + if (entityId === '') return 0 + const entityIndex = model.entities.getEntityIndex(entityId) + if (entityIndex === -1) return 0 + return model.sequence.byEntityKey[entityIndex].sequence.sequence.length +} + +export function SequenceIdColorTheme(props: ColorThemeProps): ColorTheme { + const p = { + ...props, + colors: ColorOther.rainbow, + minLabel: 'Start', + maxLabel: 'End', + } + + const scale = ColorScale.create(p) + const color = (location: Location): Color => { + if (StructureElement.isLocation(location)) { + const { unit, element } = location + const seq_id = getSeqId(unit, element) + if (seq_id > 0) { + scale.setDomain(0, getSequenceLength(unit, element) - 1) + return scale.color(seq_id) + } + } else if (Link.isLocation(location)) { + const { aUnit, aIndex } = location + const seq_id = getSeqId(aUnit, aUnit.elements[aIndex]) + if (seq_id > 0) { + scale.setDomain(0, getSequenceLength(aUnit, aUnit.elements[aIndex]) - 1) + return scale.color(seq_id) + } + } + return DefaultColor + } + + return { + granularity: 'group', + color, + description: Description, + // legend: scale ? TableLegend(table) : undefined + legend: scale ? scale.legend : undefined + } +} \ No newline at end of file