label-text.ts 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. /**
  2. * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. * @author David Sehnal <david.sehnal@gmail.com>
  6. */
  7. import { ParamDefinition as PD } from '../../../mol-util/param-definition';
  8. import { VisualUpdateState } from '../../../mol-repr/util';
  9. import { VisualContext } from '../../../mol-repr/visual';
  10. import { Structure, StructureElement, StructureProperties } from '../../../mol-model/structure';
  11. import { Theme } from '../../../mol-theme/theme';
  12. import { Text } from '../../../mol-geo/geometry/text/text';
  13. import { TextBuilder } from '../../../mol-geo/geometry/text/text-builder';
  14. import { ComplexTextVisual, ComplexTextParams, ComplexVisual } from '../complex-visual';
  15. import { ElementIterator, getSerialElementLoci, eachSerialElement } from './util/element';
  16. import { ColorNames } from '../../../mol-util/color/names';
  17. import { Vec3 } from '../../../mol-math/linear-algebra';
  18. import { PhysicalSizeTheme } from '../../../mol-theme/size/physical';
  19. import { BoundaryHelper } from '../../../mol-math/geometry/boundary-helper';
  20. export const LabelTextParams = {
  21. ...ComplexTextParams,
  22. background: PD.Boolean(true),
  23. backgroundMargin: PD.Numeric(0, { min: 0, max: 1, step: 0.01 }),
  24. backgroundColor: PD.Color(ColorNames.black),
  25. backgroundOpacity: PD.Numeric(0.5, { min: 0, max: 1, step: 0.01 }),
  26. level: PD.Select('residue', [['chain', 'Chain'], ['residue', 'Residue'], ['element', 'Element']] as const),
  27. chainScale: PD.Numeric(10, { min: 0, max: 20, step: 0.1 }),
  28. residueScale: PD.Numeric(1, { min: 0, max: 20, step: 0.1 }),
  29. elementScale: PD.Numeric(0.5, { min: 0, max: 20, step: 0.1 }),
  30. };
  31. export type LabelTextParams = typeof LabelTextParams
  32. export type LabelTextProps = PD.Values<LabelTextParams>
  33. export type LabelLevels = LabelTextProps['level']
  34. export function LabelTextVisual(materialId: number): ComplexVisual<LabelTextParams> {
  35. return ComplexTextVisual<LabelTextParams>({
  36. defaultProps: PD.getDefaultValues(LabelTextParams),
  37. createGeometry: createLabelText,
  38. createLocationIterator: ElementIterator.fromStructure,
  39. getLoci: getSerialElementLoci,
  40. eachLocation: eachSerialElement,
  41. setUpdateState: (state: VisualUpdateState, newProps: PD.Values<LabelTextParams>, currentProps: PD.Values<LabelTextParams>) => {
  42. state.createGeometry = (
  43. newProps.level !== currentProps.level ||
  44. (newProps.level === 'chain' && newProps.chainScale !== currentProps.chainScale) ||
  45. (newProps.level === 'residue' && newProps.residueScale !== currentProps.residueScale) ||
  46. (newProps.level === 'element' && newProps.elementScale !== currentProps.elementScale)
  47. );
  48. }
  49. }, materialId);
  50. }
  51. function createLabelText(ctx: VisualContext, structure: Structure, theme: Theme, props: LabelTextProps, text?: Text): Text {
  52. switch (props.level) {
  53. case 'chain': return createChainText(ctx, structure, theme, props, text);
  54. case 'residue': return createResidueText(ctx, structure, theme, props, text);
  55. case 'element': return createElementText(ctx, structure, theme, props, text);
  56. }
  57. }
  58. //
  59. const tmpVec = Vec3();
  60. const boundaryHelper = new BoundaryHelper('98');
  61. function createChainText(ctx: VisualContext, structure: Structure, theme: Theme, props: LabelTextProps, text?: Text): Text {
  62. const l = StructureElement.Location.create(structure);
  63. const { units, serialMapping } = structure;
  64. const { auth_asym_id, label_asym_id } = StructureProperties.chain;
  65. const { cumulativeUnitElementCount } = serialMapping;
  66. const count = units.length;
  67. const { chainScale } = props;
  68. const builder = TextBuilder.create(props, count, count / 2, text);
  69. for (let i = 0, il = units.length; i < il; ++i) {
  70. const unit = units[i];
  71. l.unit = unit;
  72. l.element = unit.elements[0];
  73. const { center, radius } = unit.lookup3d.boundary.sphere;
  74. Vec3.transformMat4(tmpVec, center, unit.conformation.operator.matrix);
  75. const authId = auth_asym_id(l);
  76. const labelId = label_asym_id(l);
  77. const text = authId === labelId ? labelId : `${labelId} [${authId}]`;
  78. builder.add(text, tmpVec[0], tmpVec[1], tmpVec[2], radius, chainScale, cumulativeUnitElementCount[i]);
  79. }
  80. return builder.getText();
  81. }
  82. function createResidueText(ctx: VisualContext, structure: Structure, theme: Theme, props: LabelTextProps, text?: Text): Text {
  83. const l = StructureElement.Location.create(structure);
  84. const { units, serialMapping } = structure;
  85. const { label_comp_id } = StructureProperties.atom;
  86. const { auth_seq_id } = StructureProperties.residue;
  87. const { cumulativeUnitElementCount } = serialMapping;
  88. const count = structure.polymerResidueCount * 2;
  89. const { residueScale } = props;
  90. const builder = TextBuilder.create(props, count, count / 2, text);
  91. for (let i = 0, il = units.length; i < il; ++i) {
  92. const unit = units[i];
  93. const pos = unit.conformation.position;
  94. const { elements } = unit;
  95. l.unit = unit;
  96. l.element = unit.elements[0];
  97. const residueIndex = unit.model.atomicHierarchy.residueAtomSegments.index;
  98. const groupOffset = cumulativeUnitElementCount[i];
  99. let j = 0, jl = elements.length;
  100. while (j < jl) {
  101. const start = j, rI = residueIndex[elements[j]];
  102. j++;
  103. while (j < jl && residueIndex[elements[j]] === rI) j++;
  104. boundaryHelper.reset();
  105. for (let eI = start; eI < j; eI++) {
  106. pos(elements[eI], tmpVec);
  107. boundaryHelper.includePosition(tmpVec);
  108. }
  109. boundaryHelper.finishedIncludeStep();
  110. for (let eI = start; eI < j; eI++) {
  111. pos(elements[eI], tmpVec);
  112. boundaryHelper.radiusPosition(tmpVec);
  113. }
  114. l.element = elements[start];
  115. const { center, radius } = boundaryHelper.getSphere();
  116. const authSeqId = auth_seq_id(l);
  117. const compId = label_comp_id(l);
  118. const text = `${compId} ${authSeqId}`;
  119. builder.add(text, center[0], center[1], center[2], radius, residueScale, groupOffset + start);
  120. }
  121. }
  122. return builder.getText();
  123. }
  124. function createElementText(ctx: VisualContext, structure: Structure, theme: Theme, props: LabelTextProps, text?: Text): Text {
  125. const l = StructureElement.Location.create(structure);
  126. const { units, serialMapping } = structure;
  127. const { label_atom_id, label_alt_id } = StructureProperties.atom;
  128. const { cumulativeUnitElementCount } = serialMapping;
  129. const sizeTheme = PhysicalSizeTheme({}, { scale: 1 });
  130. const count = structure.elementCount;
  131. const { elementScale } = props;
  132. const builder = TextBuilder.create(props, count, count / 2, text);
  133. for (let i = 0, il = units.length; i < il; ++i) {
  134. const unit = units[i];
  135. const pos = unit.conformation.position;
  136. const { elements } = unit;
  137. l.unit = unit;
  138. const groupOffset = cumulativeUnitElementCount[i];
  139. for (let j = 0, _j = elements.length; j < _j; j++) {
  140. l.element = elements[j];
  141. pos(l.element, tmpVec);
  142. const atomId = label_atom_id(l);
  143. const altId = label_alt_id(l);
  144. const text = altId ? `${atomId}%${altId}` : atomId;
  145. builder.add(text, tmpVec[0], tmpVec[1], tmpVec[2], sizeTheme.size(l), elementScale, groupOffset + j);
  146. }
  147. }
  148. return builder.getText();
  149. }