An alignment linked to its structure

The spike and hemoglobin tutorials each end on a residueMappings layer, which records the structure residue for each residue of one row. The viewer below loads one of those alignments with its layers and the PDB entry the mapping names, and draws the structure in Mol* under the alignment. A residue hovered in the alignment lights in the structure, a residue hovered in the structure lights in the alignment, and the line under the structure reports what each lookup returned.

What to hover

The spike set opens on the PRRA insert. Hover the SARS-CoV-2 row: each residue from 14 to 1211 reaches chain A of 6VXX at its position plus 19. Residues 681 to 684 report no coordinates, because 6VXX declares structure residues 696 to 707 in its sequence and resolved none of them. Press HR1 and hover row residues 920 to 970: each one lights on chain A, structure residues 939 to 989. Click a cell to turn the camera to that residue, and click a residue in the structure to scroll the alignment to its column.

The hemoglobin set holds one row per species, alpha and beta concatenated. On the Human row, alpha columns reach chain A of 2HHB and beta columns reach chain B. Residues 1 and 143 are the initiator methionines, which the mature chains lack, and report outside the mapped region. The arc track above the alignment joins the residue pairs in contact between the chains.

The built-in control

Each mapping names one row and one chain. Chains B and C of 6VXX are copies of chain A, and chains C and D of 2HHB are copies of A and B, but no mapping names them. A hover on any of them, or on a glycan, heme or water, reports no row maps here and lights nothing in the alignment. The other rows of each alignment report no mapping onto 6VXX or onto 2HHB.

The viewer also checks each mapping against the loaded row and ignores one computed against a different sequence. This page prints each ignored mapping under the status line, and both sets print none.

Color the structure by conservation

The Color the structure by conservation checkbox paints each residue of a mapped chain with the conservation of the alignment column it falls in, the number the Conservation track draws. Five blue steps run from variable to conserved. A residue with no column is grey: the chains no mapping names, the residues outside a mapped region, and a residue in a column that Hide columns has hidden.

The conservation of a column counts the rows on screen. Collapse a clade in the tree, or show one subtree, and the page repaints the structure from the rows left. The page walks each residue of the structure through rowResidue to its row residue, then through seqPosToVisibleCol to a column, and reads model.conservation there.

Put it on your own page

The two bridges are one call each way. structureResidue takes a row and a 1-based residue and returns the structure residue as a label_seq_id, with its chain and whether it has coordinates. rowResidue takes a structure residue and its chain and returns the row and residue, which applyHighlight draws. Both return undefined where no mapping covers the residue. model comes from onModel, and plugin is the Mol* plugin with the structure loaded:

import { MSAViewer } from 'react-msaview'
import {
  StructureElement,
  StructureProperties,
  StructureSelection,
} from 'molstar/lib/mol-model/structure'
import { Script } from 'molstar/lib/mol-script/script'

function residueLoci(structure, labelSeqId, asymId) {
  const selection = Script.getStructureSelection(
    Q =>
      Q.struct.generator.atomGroups({
        'chain-test': Q.core.rel.eq([
          Q.struct.atomProperty.macromolecular.label_asym_id(),
          asymId,
        ]),
        'residue-test': Q.core.rel.eq([
          Q.struct.atomProperty.macromolecular.label_seq_id(),
          labelSeqId,
        ]),
      }),
    structure,
  )
  return StructureSelection.toLociWithSourceUnits(selection)
}

function onCellHover(cell) {
  const hit = cell?.residue && model.structureResidue(cell.row, cell.residue, '6VXX')
  const structure =
    plugin.managers.structure.hierarchy.current.structures[0]?.cell.obj?.data
  const { lociHighlights } = plugin.managers.interactivity
  if (hit && structure) {
    const loci = residueLoci(structure, hit.position, hit.structure.asymId)
    lociHighlights.highlightOnly({ loci }, false)
  } else {
    lociHighlights.clearHighlights()
  }
}

plugin.behaviors.interaction.hover.subscribe(({ current }) => {
  const loc = StructureElement.Loci.is(current.loci)
    ? StructureElement.Loci.getFirstLocation(current.loci)
    : undefined
  const hit =
    loc &&
    model.rowResidue(
      '6VXX',
      StructureProperties.residue.label_seq_id(loc),
      StructureProperties.chain.label_asym_id(loc),
    )
  if (hit) {
    model.applyHighlight('structure', [
      { row: hit.rowName, start: hit.seqPos, end: hit.seqPos },
    ])
  } else {
    model.clearHighlight('structure')
  }
})

<MSAViewer
  msaFilehandle={{ uri: 'spike.afa', locationType: 'UriLocation' }}
  residueMappings={layers.residueMappings}
  onModel={m => {
    model = m
  }}
  onCellHover={onCellHover}
/>

The page loads these files, all served with Access-Control-Allow-Origin:

See also