Module: AsciiChem::Layout

Defined in:
lib/asciichem/layout.rb

Overview

2D structural layout for molecules. Walks an AsciiChem::Model::Molecule, builds an elkrb graph (atoms as nodes, bonds as edges), runs a layout algorithm, and returns a Layout::Result ready for SVG rendering.

Three concerns, MECE:

  • MoleculeWalker — walks the AsciiChem tree, assigns stable IDs, produces a neutral atom+bond list. Knows nothing about elkrb.
  • GraphBuilder — converts the walker's neutral list into an elkrb graph. Knows nothing about AsciiChem::Model.
  • ResultExtractor — maps elkrb's laid-out positions back onto the walker's neutral list, producing a Layout::Result.

Each is independently testable. New algorithms (ring detection, stereo placement) slot in as additional visitors over the same Layout::Result; no edits to the walker or extractor.

Defined Under Namespace

Classes: PositionedAtom, PositionedBond, Result

Constant Summary collapse

ATOM_WIDTH =
40.0
ATOM_HEIGHT =
30.0
PADDING =
20.0

Class Method Summary collapse

Class Method Details

.empty_resultObject

Empty result returned when layout is not applicable (e.g. molecule has no atoms). Keeps callers from special-casing nil.



45
46
47
# File 'lib/asciichem/layout.rb', line 45

def self.empty_result
  Result.new(atoms: [], bonds: [], width: 0.0, height: 0.0)
end

.layout(molecule, algorithm: 'layered') ⇒ Object

Compute 2D positions for a molecule's atoms and bonds. Returns a Layout::Result. Algorithm defaults to layered (Sugiyama-style hierarchical) which is deterministic across runs — essential for visual regression testing. Pass algorithm: "force" for organic-looking layouts, with the caveat that output may vary between runs.



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/asciichem/layout.rb', line 55

def self.layout(molecule, algorithm: 'layered')
  walker = MoleculeWalker.new(molecule)
  walk = walker.walk
  return empty_result if walk.atoms.empty?

  # If all atoms carry pre-positioned coordinates (e.g. from CML
  # x2/y2 attributes), skip elkrb and use the provided positions
  # directly. This preserves the molecule's original geometry when
  # round-tripping through CML.
  return pre_positioned_result(walk) if all_positioned?(walk)

  graph = GraphBuilder.new(walk).build(algorithm: algorithm)
  laid_out = Elkrb.layout(graph, algorithm: algorithm)
  ResultExtractor.new(laid_out, walk).extract
end