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 aLayout::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
-
.empty_result ⇒ Object
Empty result returned when layout is not applicable (e.g. molecule has no atoms).
-
.layout(molecule, algorithm: 'layered') ⇒ Object
Compute 2D positions for a molecule's atoms and bonds.
Class Method Details
.empty_result ⇒ Object
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 |