diff --git a/src/mol-theme/color.ts b/src/mol-theme/color.ts index a2c2417686935eb95a0ea3385591d38e0f8ba0f5..5d49660e57af6b6b08dd31a2b7253b7f31e7edb7 100644 --- a/src/mol-theme/color.ts +++ b/src/mol-theme/color.ts @@ -1,5 +1,5 @@ /** - * 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> */ @@ -27,6 +27,7 @@ import { UnitIndexColorThemeProvider } from './color/unit-index'; import { ScaleLegend } from 'mol-util/color/scale'; import { TableLegend } from 'mol-util/color/tables'; import { UncertaintyColorThemeProvider } from './color/uncertainty'; +import { GeneColorThemeProvider } from './color/gene'; export type LocationColor = (location: Location, isSecondary: boolean) => Color @@ -73,6 +74,7 @@ export const BuiltInColorThemes = { 'cross-link': CrossLinkColorThemeProvider, 'element-index': ElementIndexColorThemeProvider, 'element-symbol': ElementSymbolColorThemeProvider, + 'gene': GeneColorThemeProvider, 'molecule-type': MoleculeTypeColorThemeProvider, 'polymer-id': PolymerIdColorThemeProvider, 'polymer-index': PolymerIndexColorThemeProvider, diff --git a/src/mol-theme/color/chain-id.ts b/src/mol-theme/color/chain-id.ts index 6167f4a6d1e258c4129b8b9439578cbb5ffca1e1..46629e03e925f6481011a9bd175b918cfca2b88e 100644 --- a/src/mol-theme/color/chain-id.ts +++ b/src/mol-theme/color/chain-id.ts @@ -51,6 +51,7 @@ export function ChainIdColorTheme(ctx: ThemeDataContext, props: PD.Values<ChainI const scale = ColorScale.create({ listOrName: props.list, minLabel: 'Start', maxLabel: 'End' }) if (ctx.structure) { + // TODO same asym ids in different models should get different color const l = StructureElement.create() const { models } = ctx.structure const asymIdSerialMap = new Map<string, number>() diff --git a/src/mol-theme/color/gene.ts b/src/mol-theme/color/gene.ts new file mode 100644 index 0000000000000000000000000000000000000000..1f0110e846f700fdb7e0a3e01319248d35b0b585 --- /dev/null +++ b/src/mol-theme/color/gene.ts @@ -0,0 +1,127 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { StructureProperties, StructureElement, Link } from 'mol-model/structure'; +import { ColorScale, Color } from 'mol-util/color'; +import { Location } from 'mol-model/location'; +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/scale'; +import { NumberArray } from 'mol-util/type-helpers'; + +const DefaultColor = Color(0xCCCCCC) +const Description = 'Gives ranges of a polymer chain a color based on the gene (or linker/terminal extension) it originates from.' + +export const GeneColorThemeParams = { + list: PD.ColorScale<ColorListName>('RedYellowBlue', ColorListOptions), +} +export type GeneColorThemeParams = typeof GeneColorThemeParams +export function getGeneColorThemeParams(ctx: ThemeDataContext) { + return GeneColorThemeParams // TODO return copy +} + +function modelEntityKey(modelIndex: number, entityId: string) { + return `${modelIndex}|${entityId}` +} + +function addGene(geneSerialMap: Map<string, number>, geneNames: string[], beg: number, end: number, seqToSrcGen: NumberArray) { + const gene = geneNames.map(s => s.toUpperCase()).sort().join(',') + let geneIndex = 0 // serial no starting from 1 + if (gene === '') { + geneIndex = geneSerialMap.size + 1 + geneSerialMap.set(`UNKNOWN${geneIndex}`, geneIndex) + } else if (geneSerialMap.has(gene)) { + geneIndex = geneSerialMap.get(gene)! + } else { + geneIndex = geneSerialMap.size + 1 + geneSerialMap.set(gene, geneIndex) + } + for (let i = beg, il = end; i <= il; ++i) { + seqToSrcGen[i - 1] = geneIndex + } +} + +export function GeneColorTheme(ctx: ThemeDataContext, props: PD.Values<GeneColorThemeParams>): ColorTheme<GeneColorThemeParams> { + let color: LocationColor + const scale = ColorScale.create({ listOrName: props.list, minLabel: 'Start', maxLabel: 'End' }) + const { structure } = ctx + + if (structure) { + const l = StructureElement.create() + const { models } = structure + const seqToSrcGenByModelEntity = new Map<string, NumberArray>() + const geneSerialMap = new Map<string, number>() // serial no starting from 1 + for (let i = 0, il = models.length; i <il; ++i) { + const m = models[i] + if (m.sourceData.kind !== 'mmCIF') continue + const { entity_src_gen } = m.sourceData.data + + const { entity_id, pdbx_beg_seq_num, pdbx_end_seq_num, pdbx_gene_src_gene } = entity_src_gen + for (let j = 0, jl = entity_src_gen._rowCount; j < jl; ++j) { + const entityId = entity_id.value(j) + const k = modelEntityKey(i, entityId) + if (!seqToSrcGenByModelEntity.has(k)) { + const entityIndex = m.entities.getEntityIndex(entityId) + const seq = m.sequence.sequences[entityIndex].sequence + const seqLength = seq.sequence.length + const seqToGene = new Int16Array(seqLength) + addGene(geneSerialMap, pdbx_gene_src_gene.value(j), pdbx_beg_seq_num.value(j), pdbx_end_seq_num.value(j), seqToGene) + seqToSrcGenByModelEntity.set(k, seqToGene) + } else { + const seqToGene = seqToSrcGenByModelEntity.get(k)! + addGene(geneSerialMap, pdbx_gene_src_gene.value(j), pdbx_beg_seq_num.value(j), pdbx_end_seq_num.value(j), seqToGene) + seqToSrcGenByModelEntity.set(k, seqToGene) + } + } + } + scale.setDomain(1, geneSerialMap.size) + const scaleColor = scale.color + + const getGeneColor = (location: StructureElement) => { + const modelIndex = structure.models.indexOf(location.unit.model) + const entityId = StructureProperties.entity.id(location) + const k = modelEntityKey(modelIndex, entityId) + const seqToGene = seqToSrcGenByModelEntity.get(k) + if (seqToGene) { + // minus 1 to convert seqId to array index + return scaleColor(seqToGene[StructureProperties.residue.label_seq_id(location) - 1]) + } else { + return DefaultColor + } + } + + color = (location: Location): Color => { + if (StructureElement.isLocation(location)) { + return getGeneColor(location) + } else if (Link.isLocation(location)) { + l.unit = location.aUnit + l.element = location.aUnit.elements[location.aIndex] + return getGeneColor(l) + } + return DefaultColor + } + } else { + color = () => DefaultColor + } + + return { + factory: GeneColorTheme, + granularity: 'group', + color, + props, + description: Description, + legend: scale ? scale.legend : undefined + } +} + +export const GeneColorThemeProvider: ColorTheme.Provider<GeneColorThemeParams> = { + label: 'Gene', + factory: GeneColorTheme, + getParams: getGeneColorThemeParams, + defaultValues: PD.getDefaultValues(GeneColorThemeParams), + isApplicable: (ctx: ThemeDataContext) => !!ctx.structure +} \ No newline at end of file diff --git a/src/mol-theme/color/polymer-id.ts b/src/mol-theme/color/polymer-id.ts index a19c84d6423ed18670f47fbaf3864e73e43b20b0..f97def2d4c011ad40c5ee6952bd649e60fc382d1 100644 --- a/src/mol-theme/color/polymer-id.ts +++ b/src/mol-theme/color/polymer-id.ts @@ -56,17 +56,16 @@ export function PolymerIdColorTheme(ctx: ThemeDataContext, props: PD.Values<Poly const scale = ColorScale.create({ listOrName: props.list, minLabel: 'Start', maxLabel: 'End' }) if (ctx.structure) { + // TODO same asym ids in different models should get different color const l = StructureElement.create() const { models } = ctx.structure const polymerAsymIdSerialMap = new Map<string, number>() for (let i = 0, il = models.length; i <il; ++i) { - for (let i = 0, il = models.length; i <il; ++i) { - const m = models[i] - addPolymerAsymIds(polymerAsymIdSerialMap, m.atomicHierarchy.chains.label_asym_id, m.atomicHierarchy.chains.label_entity_id, m.entities) - if (m.coarseHierarchy.isDefined) { - addPolymerAsymIds(polymerAsymIdSerialMap, m.coarseHierarchy.spheres.asym_id, m.coarseHierarchy.spheres.entity_id, m.entities) - addPolymerAsymIds(polymerAsymIdSerialMap, m.coarseHierarchy.gaussians.asym_id, m.coarseHierarchy.spheres.entity_id, m.entities) - } + const m = models[i] + addPolymerAsymIds(polymerAsymIdSerialMap, m.atomicHierarchy.chains.label_asym_id, m.atomicHierarchy.chains.label_entity_id, m.entities) + if (m.coarseHierarchy.isDefined) { + addPolymerAsymIds(polymerAsymIdSerialMap, m.coarseHierarchy.spheres.asym_id, m.coarseHierarchy.spheres.entity_id, m.entities) + addPolymerAsymIds(polymerAsymIdSerialMap, m.coarseHierarchy.gaussians.asym_id, m.coarseHierarchy.spheres.entity_id, m.entities) } } scale.setDomain(0, polymerAsymIdSerialMap.size - 1)