sequence-view.tsx 3.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. /**
  2. * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. */
  6. import * as React from 'react'
  7. import { View } from '../view';
  8. import { SequenceViewController } from '../../controller/visualization/sequence-view';
  9. import { Structure, StructureSequence, Queries, Selection } from 'mol-model/structure';
  10. import { Context } from '../../context/context';
  11. import { InteractivityEvents } from '../../event/basic';
  12. import { SyncRuntimeContext } from 'mol-task/execution/synchronous';
  13. export class SequenceView extends View<SequenceViewController, {}, {}> {
  14. render() {
  15. const s = this.controller.latestState.structure;
  16. if (!s) return <div className='molstar-sequence-view-wrap'>No structure available.</div>;
  17. const seqs = Structure.getModels(s)[0].sequence.sequences;
  18. return <div className='molstar-sequence-view-wrap'>
  19. {seqs.map((seq, i) => <EntitySequence key={i} ctx={this.controller.context} seq={seq} structure={s} /> )}
  20. </div>;
  21. }
  22. }
  23. function createQuery(entityId: string, label_seq_id: number) {
  24. return Queries.generators.atoms({
  25. entityTest: l => Queries.props.entity.id(l) === entityId,
  26. residueTest: l => Queries.props.residue.label_seq_id(l) === label_seq_id
  27. });
  28. }
  29. // TODO: this is really ineffective and should be done using a canvas.
  30. class EntitySequence extends React.Component<{ ctx: Context, seq: StructureSequence.Entity, structure: Structure }> {
  31. async raiseInteractityEvent(seqId?: number) {
  32. if (typeof seqId === 'undefined') {
  33. InteractivityEvents.HighlightElementLoci.dispatch(this.props.ctx, void 0);
  34. return;
  35. }
  36. const query = createQuery(this.props.seq.entityId, seqId);
  37. const loci = Selection.toLoci(await query(this.props.structure, SyncRuntimeContext));
  38. if (loci.elements.length === 0) InteractivityEvents.HighlightElementLoci.dispatch(this.props.ctx, void 0);
  39. else InteractivityEvents.HighlightElementLoci.dispatch(this.props.ctx, loci);
  40. }
  41. render() {
  42. const { ctx, seq } = this.props;
  43. const { offset, sequence } = seq.sequence;
  44. const elems: JSX.Element[] = [];
  45. for (let i = 0, _i = sequence.length; i < _i; i++) {
  46. elems[elems.length] = <ResidueView ctx={ctx} seqId={offset + i} letter={sequence[i]} parent={this} key={i} />;
  47. }
  48. return <div style={{ wordWrap: 'break-word' }}>
  49. <span style={{ fontWeight: 'bold' }}>{this.props.seq.entityId}:{offset}&nbsp;</span>
  50. {elems}
  51. </div>;
  52. }
  53. }
  54. class ResidueView extends React.Component<{ ctx: Context, seqId: number, letter: string, parent: EntitySequence }, { isHighlighted: boolean }> {
  55. state = { isHighlighted: false }
  56. mouseEnter = () => {
  57. this.setState({ isHighlighted: true });
  58. this.props.parent.raiseInteractityEvent(this.props.seqId);
  59. }
  60. mouseLeave = () => {
  61. this.setState({ isHighlighted: false });
  62. this.props.parent.raiseInteractityEvent();
  63. }
  64. render() {
  65. return <span onMouseEnter={this.mouseEnter} onMouseLeave={this.mouseLeave}
  66. style={{ cursor: 'pointer', backgroundColor: this.state.isHighlighted ? 'yellow' : void 0 }}>
  67. {this.props.letter}
  68. </span>;
  69. }
  70. }