MomaPy

API reference
  • Home
  • User manual
  • Styling
  • CLI reference
  • Changelog
  • About

User manual

Previous Next

MomaPy: a python library for molecular maps¶

The MomaPy library is a new python library for working with molecular maps such as SBGN maps. Its key feature is its definition of a map, that is now formed of two entities: a model, that describes what concepts are represented, and a layout, that describes how these concepts are represented. This definition is borrowed from SBML and its extensions layout/render, that allowed users to add a layout to an SBML model. MomaPy aims at extending this definition to all types of molecular maps, and in particular to SBGN maps.

MomaPy offers the following features:

  • support for SBGN PD and AF maps (read/write SBGN-ML with annotations, rendering information, and notes)
  • decomposition of a map object into:
    • a model object;
    • a layout object;
    • a mapping between the model and layout objects' subelements
  • map, model, layout and mapping objects comparison; fast object in set checking
  • rendering of maps to images (SVG, PDF, JPEG, PNG, WebP) and other surfaces (e.g. GLFW window)
  • support for styling and css like stylesheets (including effects such as shadows)
  • automatic geometry and anchors (for arcs, shape borders)
  • local positioning (e.g. right of shape, fit set of shapes)
  • easy extension with new model and layout subelements
In [1]:
Copied!
import os

from momapy.core import DoubleHeadedArc, LayoutElement, ModelElement, Node, SingleHeadedArc
from momapy.io import read, write
from momapy.rendering import render_map, render_maps
import momapy.builder
import momapy.coloring
import momapy.styling
import momapy.utils

import momapy.sbgn.styling
import momapy.sbgn.utils
import os from momapy.core import DoubleHeadedArc, LayoutElement, ModelElement, Node, SingleHeadedArc from momapy.io import read, write from momapy.rendering import render_map, render_maps import momapy.builder import momapy.coloring import momapy.styling import momapy.utils import momapy.sbgn.styling import momapy.sbgn.utils
In [2]:
Copied!
from utils import display, show_room, macromolecule_toy, production_toy
from utils import display, show_room, macromolecule_toy, production_toy
In [3]:
Copied!
os.makedirs("build/", exist_ok=True)
os.makedirs("build/", exist_ok=True)

Maps¶

In [4]:
Copied!
r = read("data/phospho1.sbgn")
m = r.obj
display(m)
r = read("data/phospho1.sbgn") m = r.obj display(m)
No description has been provided for this image

A Map object always contains a Model, a Layout and a LayoutModelMapping (that maps model elements to layout elements). It may also have additional attributes depending on its nature. For example, an SBGN PD map also has an id.

In [5]:
Copied!
momapy.utils.pretty_print(m)
momapy.utils.pretty_print(m)
<class 'momapy.sbgn.pd.map.SBGNPDMap'>: SBGNPDMap(id_='d057d84f-aae1-4...
│ * id_: <class 'str'> = d057d84f-aae1-4706-9772-d72eae...
│ * model: <class 'momapy.sbgn.pd.model.SBGNPDModel'> = SBGNPDModel(id_='3571e538-f611...
│ * layout: <class 'momapy.sbgn.pd.layout.SBGNPDLayout'> = SBGNPDLayout(id_='f3627ca6-026...
│ * layout_model_mapping: momapy.core.mapping.LayoutModelMapping | None = LayoutModelMapping({StateVaria...

Models¶

A Model may have an arbitrary number of attributes, depending on its nature. For example, an SBGNPDModel has the following attributes: entity_pools, processes, compartments, modulations, logical_operators, equivalence_operators, submaps and tags, but also an id.

In [6]:
Copied!
momapy.utils.pretty_print(m.model)
momapy.utils.pretty_print(m.model)
<class 'momapy.sbgn.pd.model.SBGNPDModel'>: SBGNPDModel(id_='3571e538-f611...
│ * id_: <class 'str'> = 3571e538-f611-46a7-918e-42838d...
│ * entity_pools: frozenset[momapy.sbgn.pd.model.EntityPool] = frozenset({Macromolecule(id_='...
│ * processes: frozenset[momapy.sbgn.pd.model.Process] = frozenset({GenericProcess(id_=...
│ * compartments: frozenset[momapy.sbgn.pd.model.Compartment] = frozenset()
│ * modulations: frozenset[momapy.sbgn.pd.model.Modulation] = frozenset({Catalysis(id_='arc3...
│ * logical_operators: frozenset[momapy.sbgn.pd.model.LogicalOperator] = frozenset()
│ * equivalence_operators: frozenset[momapy.sbgn.pd.model.EquivalenceOperator] = frozenset()
│ * submaps: frozenset[momapy.sbgn.pd.model.Submap] = frozenset()
│ * tags: frozenset[momapy.sbgn.pd.model.Tag] = frozenset()

These attributes may be ModelElements or collections of ModelElements. For example, the entity_pools attribute of an SBGNPDModel may contain zero or more EntityPools. We pick the first element from the collection:

In [7]:
Copied!
for e in m.model.entity_pools:
    break
momapy.utils.pretty_print(e)
for e in m.model.entity_pools: break momapy.utils.pretty_print(e)
<class 'momapy.sbgn.pd.model.Macromolecule'>: Macromolecule(id_='glyph3_mode...
│ * id_: <class 'str'> = glyph3_model
│ * compartment: momapy.sbgn.pd.model.Compartment | None = None
│ * label: str | None = B
│ * state_variables: frozenset[momapy.sbgn.pd.model.StateVariable] = frozenset()
│ * units_of_information: frozenset[momapy.sbgn.pd.model.UnitOfInformation] = frozenset()

Here, the element is a Macromolecule. The data model for SBGN PD is built on a hierarchy of classes following the corresponding ontology. Hence this element is also an EntityPool, and more generally a ModelElement:

In [8]:
Copied!
assert isinstance(e, momapy.sbgn.pd.EntityPool)
assert isinstance(e, ModelElement)
assert isinstance(e, momapy.sbgn.pd.EntityPool) assert isinstance(e, ModelElement)

Layouts¶

A Layout is some sort of canvas that may contain other LayoutElements that correspond to shapes that represent ModelElements and that may be rendered. In SBGN, LayoutElements are either Nodes, Arcs or TextLayouts. The different LayoutElements of a Layout are contained in its layout_elements attribute:

In [9]:
Copied!
momapy.utils.pretty_print(m.layout)
momapy.utils.pretty_print(m.layout)
<class 'momapy.sbgn.pd.layout.SBGNPDLayout'>: SBGNPDLayout(id_='f3627ca6-026...
│ * id_: <class 'str'> = f3627ca6-026e-41aa-8a45-c25b20...
│ * layout_elements: tuple[momapy.core.elements.LayoutElement] = (MacromoleculeLayout(id_='glyp...
│ * group_fill: momapy.drawing.NoneValueType | momapy.coloring.Color | None = None
│ * group_fill_rule: momapy.drawing.FillRule | None = None
│ * group_filter: momapy.drawing.NoneValueType | momapy.drawing.Filter | None = None
│ * group_font_family: str | None = None
│ * group_font_size: float | None = None
│ * group_font_style: momapy.drawing.FontStyle | None = None
│ * group_font_weight: momapy.drawing.FontWeight | float | None = None
│ * group_stroke: momapy.drawing.NoneValueType | momapy.coloring.Color | None = None
│ * group_stroke_dasharray: momapy.drawing.NoneValueType | tuple[float, ...] | None = None
│ * group_stroke_dashoffset: momapy.drawing.NoneValueType | float | None = None
│ * group_stroke_width: momapy.drawing.NoneValueType | float | None = None
│ * group_text_anchor: momapy.drawing.TextAnchor | None = None
│ * group_transform: momapy.drawing.NoneValueType | tuple[momapy.geometry.Transformation] | None = None
│ * fill: momapy.drawing.NoneValueType | momapy.coloring.Color | None = Color(red=255, green=255, blue...
│ * filter: momapy.drawing.NoneValueType | momapy.drawing.Filter | None = None
│ * height: <class 'float'> = 250.0
│ * label: momapy.core.layout.TextLayout | None = None
│ * position: <class 'momapy.geometry.Point'> = Point(x=240.0, y=205.0)
│ * stroke: momapy.drawing.NoneValueType | momapy.coloring.Color | None = None
│ * stroke_dasharray: momapy.drawing.NoneValueType | tuple[float, ...] | None = None
│ * stroke_dashoffset: momapy.drawing.NoneValueType | float | None = None
│ * stroke_width: momapy.drawing.NoneValueType | float | None = None
│ * transform: momapy.drawing.NoneValueType | tuple[momapy.geometry.Transformation] | None = None
│ * width: <class 'float'> = 450.0

A LayoutElement always has a drawing_elements method that returns DrawingElements that may be rendered using a Renderer. The DrawingElements are built on the fly based on the other attributes of the LayoutElement. DrawingElements are like SVG elements: they may represent paths, rectangle, ellipses, text..., and generally have the same attributes as their SVG counterpart (or a subset of them). A Layout itself is a Node, whose returned unique DrawingElement ultimately represents a rectangle built from its position, width and height attributes. The style of this rectangle depends on the styling attribute of the Layout (e.g. the stroke, stroke_width, and fill attributes):

In [10]:
Copied!
momapy.utils.pretty_print(m.layout.drawing_elements())
momapy.utils.pretty_print(m.layout.drawing_elements())
<class 'list'>: [Group(class_='SBGNPDLayout', ...
│ - 0: <class 'momapy.drawing.Group'> = Group(class_='SBGNPDLayout', f...

A Layout may contain other LayoutElements, which themselves may contain other LayoutElements, recursively forming a hierarchy of LayoutElements, and thus of DrawingElements. While a Layout represents a Model, contained LayoutElements represent ModelElements contained by the Model.

We pick the first LayoutElement from the SBGNPDLayout:

In [11]:
Copied!
for l in m.layout.layout_elements:
    break
momapy.utils.pretty_print(l)
for l in m.layout.layout_elements: break momapy.utils.pretty_print(l)
<class 'momapy.sbgn.pd.layout.MacromoleculeLayout'>: MacromoleculeLayout(id_='glyph...
│ * id_: <class 'str'> = glyph2
│ * layout_elements: tuple[momapy.core.elements.LayoutElement] = (StateVariableLayout(id_='glyp...
│ * group_fill: momapy.drawing.NoneValueType | momapy.coloring.Color | None = None
│ * group_fill_rule: momapy.drawing.FillRule | None = None
│ * group_filter: momapy.drawing.NoneValueType | momapy.drawing.Filter | None = None
│ * group_font_family: str | None = None
│ * group_font_size: float | None = None
│ * group_font_style: momapy.drawing.FontStyle | None = None
│ * group_font_weight: momapy.drawing.FontWeight | float | None = None
│ * group_stroke: momapy.drawing.NoneValueType | momapy.coloring.Color | None = None
│ * group_stroke_dasharray: momapy.drawing.NoneValueType | tuple[float, ...] | None = None
│ * group_stroke_dashoffset: momapy.drawing.NoneValueType | float | None = None
│ * group_stroke_width: momapy.drawing.NoneValueType | float | None = None
│ * group_text_anchor: momapy.drawing.TextAnchor | None = None
│ * group_transform: momapy.drawing.NoneValueType | tuple[momapy.geometry.Transformation] | None = None
│ * fill: momapy.drawing.NoneValueType | momapy.coloring.Color | None = Color(red=255, green=255, blue...
│ * filter: momapy.drawing.NoneValueType | momapy.drawing.Filter | None = None
│ * height: <class 'float'> = 60.0
│ * label: momapy.core.layout.TextLayout | None = TextLayout(id_='0d634364-e478-...
│ * position: <class 'momapy.geometry.Point'> = Point(x=85.0, y=270.0)
│ * stroke: momapy.drawing.NoneValueType | momapy.coloring.Color | None = Color(red=0, green=0, blue=0, ...
│ * stroke_dasharray: momapy.drawing.NoneValueType | tuple[float, ...] | None = None
│ * stroke_dashoffset: momapy.drawing.NoneValueType | float | None = None
│ * stroke_width: float | None = 1.25
│ * transform: momapy.drawing.NoneValueType | tuple[momapy.geometry.Transformation] | None = None
│ * width: <class 'float'> = 108.0
│ * rounded_corners: <class 'float'> = 5.0

This element is a MacromoleculeLayout. Based on its position, width and height attributes, as well as on its styling attributes, it will produce a DrawingElement representing a rectangle with rounded corners, containing some text corresponding to its label. Since this element also contains other LayoutElements in its layout_elements attribute, it will also produce the DrawingElements of these contained LayoutElements (here, a StateVariableLayout):

In [12]:
Copied!
display(l)
display(l)
No description has been provided for this image

LayoutModelMappings¶

A LayoutModelMapping is a mapping from LayoutElements to ModelElements and vice-versa. It is used to map the LayoutElements of a Map to the ModelElements they represent.

We pick the ModelElement mapped to the MacromoleculeLayout we had picked:

In [13]:
Copied!
e = m.layout_model_mapping.get_mapping(l)
momapy.utils.pretty_print(e)
e = m.layout_model_mapping.get_mapping(l) momapy.utils.pretty_print(e)
<class 'momapy.sbgn.pd.model.Macromolecule'>: Macromolecule(id_='glyph2_mode...
│ * id_: <class 'str'> = glyph2_model
│ * compartment: momapy.sbgn.pd.model.Compartment | None = None
│ * label: str | None = A
│ * state_variables: frozenset[momapy.sbgn.pd.model.StateVariable] = frozenset({StateVariable(id_='...
│ * units_of_information: frozenset[momapy.sbgn.pd.model.UnitOfInformation] = frozenset()

Equality and-sub relation for Maps, Models, Layouts and LayoutModelMappings¶

Maps, Models, Layouts, LayoutModelMappings, ModelElements and LayoutElements are encoded so they can be easily compared. Their identity relies on the value of a subset of their attributes (generally all attributes but their id_). This way, two Maps can be easily compared. Is is also possible to check whether a Map is a sub-map of another Map (not to be confused with SBGN PD's submap glyph).

Equality¶

Definition¶

Two Maps are equal if and only if:

  • their Models are equal;
  • their Layouts are equal;
  • and their LayoutModelMappings are equal.

Example¶

In [14]:
Copied!
r1 = read("data/phospho1.sbgn")
m1 = r1.obj
display(m1)
r1 = read("data/phospho1.sbgn") m1 = r1.obj display(m1)
No description has been provided for this image
In [15]:
Copied!
r2 = read("data/phospho2.sbgn")
m2 = r2.obj
display(m2)
r2 = read("data/phospho2.sbgn") m2 = r2.obj display(m2)
No description has been provided for this image

The two maps represent the exact same concepts, and thus have the same model. However, they do not have the same layout. Hence, the two maps are different. This can be checked easily by comparing the Map, Model, Layout and LayoutModelMapping objects representing the two maps:

In [16]:
Copied!
assert m1 != m2
assert m1.model == m2.model
assert m1.layout != m2.layout
assert m1.layout_model_mapping != m2.layout_model_mapping
assert m1 != m2 assert m1.model == m2.model assert m1.layout != m2.layout assert m1.layout_model_mapping != m2.layout_model_mapping

Sub-map/model/layout/mapping¶

Definition¶

A Map M is a sub-map of another Map M' if and only if:

  • the Model of M is a sub-model of the Model of M';
  • the Layout of M is a sub-layout of the Layout of M';
  • and the LayoutModelMapping of M is a sub-mapping of the LayoutModelMapping of M'.

Example 1¶

In [17]:
Copied!
r1 = read("data/phospho1.sbgn")
m1 = r1.obj
display(m1)
r1 = read("data/phospho1.sbgn") m1 = r1.obj display(m1)
No description has been provided for this image
In [18]:
Copied!
r3 = read("data/phospho3.sbgn")
m3 = r3.obj
display(m3)
r3 = read("data/phospho3.sbgn") m3 = r3.obj display(m3)
No description has been provided for this image

The second map is an excerpt of the first map:

In [19]:
Copied!
assert m3.is_submap(m1)
assert m3.model.is_submodel(m1.model)
assert m3.layout.is_sublayout(m1.layout)
assert m3.layout_model_mapping.is_submapping(m1.layout_model_mapping)
assert m3.is_submap(m1) assert m3.model.is_submodel(m1.model) assert m3.layout.is_sublayout(m1.layout) assert m3.layout_model_mapping.is_submapping(m1.layout_model_mapping)

Example 2¶

In [20]:
Copied!
r4 = read("data/phospho4.sbgn")
m4 = r4.obj
display(m4)
r4 = read("data/phospho4.sbgn") m4 = r4.obj display(m4)
No description has been provided for this image

Because of the compartment, the model of the first map is not an excerpt of the model of the second map. However, the layout of the first map is an excerpt of the layout of the second map:

In [21]:
Copied!
assert not m3.is_submap(m4)
assert not m3.model.is_submodel(m4.model)
assert m3.layout.is_sublayout(m4.layout)
assert not m3.layout_model_mapping.is_submapping(m4.layout_model_mapping)
assert not m3.is_submap(m4) assert not m3.model.is_submodel(m4.model) assert m3.layout.is_sublayout(m4.layout) assert not m3.layout_model_mapping.is_submapping(m4.layout_model_mapping)

Frozen and builder objects¶

Map, Model, Layout, ModelElement and LayoutElement objects cannot be modified; they are frozen:

In [22]:
Copied!
r = read("data/phospho1.sbgn")
m = r.obj
display(m)
r = read("data/phospho1.sbgn") m = r.obj display(m)
No description has been provided for this image
In [23]:
Copied!
for l in m.layout.layout_elements:
    break
display(l)
for l in m.layout.layout_elements: break display(l)
No description has been provided for this image
In [24]:
Copied!
try:
    l.stroke_width = 3.0
except Exception as e:
    print(e)
try: l.stroke_width = 3.0 except Exception as e: print(e)
cannot assign to field 'stroke_width'

This way they can be hashed, which is necessary to check whether a Map object belongs to a set efficiently, for example. However, we want to be able to modify them programmatically (e.g., change the stroke width of the border of a shape). To this end, each class has a corresponding builder class, that allows the production of objects that are not frozen. Such objects may be built directly from the frozen objects:

In [25]:
Copied!
lb = momapy.builder.builder_from_object(l)
momapy.utils.pretty_print(lb)
lb = momapy.builder.builder_from_object(l) momapy.utils.pretty_print(lb)
<class 'momapy.builder.MacromoleculeLayoutBuilder'>: MacromoleculeLayoutBuilder(id_...
│ * id_: <class 'str'> = glyph2
│ * layout_elements: typing.Union[tuple[momapy.core.elements.LayoutElement], list[momapy.builder.LayoutElementBuilder]] = [StateVariableLayoutBuilder(id...
│ * group_fill: typing.Union[momapy.drawing.NoneValueType, momapy.coloring.Color, NoneType, momapy.builder.ColorBuilder] = None
│ * group_fill_rule: typing.Optional[momapy.drawing.FillRule] = None
│ * group_filter: typing.Union[momapy.drawing.NoneValueType, momapy.drawing.Filter, NoneType, momapy.builder.FilterBuilder] = None
│ * group_font_family: typing.Optional[str] = None
│ * group_font_size: typing.Optional[float] = None
│ * group_font_style: typing.Optional[momapy.drawing.FontStyle] = None
│ * group_font_weight: typing.Union[momapy.drawing.FontWeight, float, NoneType] = None
│ * group_stroke: typing.Union[momapy.drawing.NoneValueType, momapy.coloring.Color, NoneType, momapy.builder.ColorBuilder] = None
│ * group_stroke_dasharray: typing.Union[momapy.drawing.NoneValueType, tuple[float, ...], NoneType, list[float, ...]] = None
│ * group_stroke_dashoffset: typing.Union[momapy.drawing.NoneValueType, float, NoneType] = None
│ * group_stroke_width: typing.Union[momapy.drawing.NoneValueType, float, NoneType] = None
│ * group_text_anchor: typing.Optional[momapy.drawing.TextAnchor] = None
│ * group_transform: typing.Union[momapy.drawing.NoneValueType, tuple[momapy.geometry.Transformation], NoneType, list[momapy.builder.TransformationBuilder]] = None
│ * fill: typing.Union[momapy.drawing.NoneValueType, momapy.coloring.Color, NoneType, momapy.builder.ColorBuilder] = ColorBuilder(red=255, green=25...
│ * filter: typing.Union[momapy.drawing.NoneValueType, momapy.drawing.Filter, NoneType, momapy.builder.FilterBuilder] = None
│ * height: <class 'float'> = 60.0
│ * label: typing.Union[momapy.core.layout.TextLayout, NoneType, momapy.builder.TextLayoutBuilder] = TextLayoutBuilder(id_='3de4797...
│ * position: typing.Union[momapy.geometry.Point, momapy.builder.PointBuilder, NoneType] = PointBuilder(x=85.0, y=270.0)
│ * stroke: typing.Union[momapy.drawing.NoneValueType, momapy.coloring.Color, NoneType, momapy.builder.ColorBuilder] = ColorBuilder(red=0, green=0, b...
│ * stroke_dasharray: typing.Union[momapy.drawing.NoneValueType, tuple[float, ...], NoneType, list[float, ...]] = None
│ * stroke_dashoffset: typing.Union[momapy.drawing.NoneValueType, float, NoneType] = None
│ * stroke_width: typing.Optional[float] = 1.25
│ * transform: typing.Union[momapy.drawing.NoneValueType, tuple[momapy.geometry.Transformation], NoneType, list[momapy.builder.TransformationBuilder]] = None
│ * width: <class 'float'> = 108.0
│ * rounded_corners: <class 'float'> = 5.0
In [26]:
Copied!
lb.stroke_width = 3.0
l = momapy.builder.object_from_builder(lb)
display(l)
lb.stroke_width = 3.0 l = momapy.builder.object_from_builder(lb) display(l)
No description has been provided for this image

The frozen object may then be built back from the builder:

In [27]:
Copied!
l = momapy.builder.object_from_builder(lb)
l = momapy.builder.object_from_builder(lb)
In [28]:
Copied!
assert l.stroke_width == 3.0
assert l.stroke_width == 3.0

Reading and writing¶

SBGNMaps may be read from and written to SBGN-ML files using read and write functions:

In [29]:
Copied!
r = read("data/phospho1.sbgn")
m = r.obj
write(m, "build/phospho1_output.sbgn", writer="sbgnml")
r = read("data/phospho1.sbgn") m = r.obj write(m, "build/phospho1_output.sbgn", writer="sbgnml")

Rendering¶

In [30]:
Copied!
r = read("data/phospho1.sbgn")
m = r.obj
display(m)
r = read("data/phospho1.sbgn") m = r.obj display(m)
No description has been provided for this image

Maps can be rendered in different formats using a simple render function:

In [31]:
Copied!
render_map(m, "build/phospho1.pdf")
render_map(m, "build/phospho1.png")
render_map(m, "build/phospho1.svg")
render_map(m, "build/phospho1.webp")
render_map(m, "build/phospho1.jpeg")
render_map(m, "build/phospho1.pdf") render_map(m, "build/phospho1.png") render_map(m, "build/phospho1.svg") render_map(m, "build/phospho1.webp") render_map(m, "build/phospho1.jpeg")

Layouts can be moved to the top left using the top_left option:

In [32]:
Copied!
render_map(m, "build/phospho1.pdf", to_top_left=True)
render_map(m, "build/phospho1.pdf", to_top_left=True)
In [33]:
Copied!
r1 = read("data/phospho1.sbgn")
m1 = r1.obj
r2 = read("data/phospho2.sbgn")
m2 = r2.obj
r3 = read("data/phospho3.sbgn")
m3 = r3.obj
r4 = read("data/phospho4.sbgn")
m4 = r4.obj
r1 = read("data/phospho1.sbgn") m1 = r1.obj r2 = read("data/phospho2.sbgn") m2 = r2.obj r3 = read("data/phospho3.sbgn") m3 = r3.obj r4 = read("data/phospho4.sbgn") m4 = r4.obj

Multiple Maps can be rendered in one document using a simple function:

In [34]:
Copied!
render_maps(
    [m1, m2, m3, m4], "build/phospho_multi.pdf", multi_pages=True
)
render_maps( [m1, m2, m3, m4], "build/phospho_multi.pdf", multi_pages=True )

Styling¶

Basic styling¶

Basic styling can be easily applied to LayoutElements:

In [35]:
Copied!
r = read("data/phospho1.sbgn")
m = r.obj
display(m)
r = read("data/phospho1.sbgn") m = r.obj display(m)
No description has been provided for this image
In [36]:
Copied!
for l in m.layout.layout_elements:
    break
display(l)
for l in m.layout.layout_elements: break display(l)
No description has been provided for this image
In [37]:
Copied!
lb = momapy.builder.builder_from_object(l)
lb.fill = momapy.coloring.lightblue
lb.stroke = momapy.coloring.brown
lb.stroke_width = 3.0
lb.stroke_dasharray = (5, 5)
momapy.utils.pretty_print(lb.drawing_elements(), max_depth=3)
lb = momapy.builder.builder_from_object(l) lb.fill = momapy.coloring.lightblue lb.stroke = momapy.coloring.brown lb.stroke_width = 3.0 lb.stroke_dasharray = (5, 5) momapy.utils.pretty_print(lb.drawing_elements(), max_depth=3)
<class 'list'>: [Group(class_='MacromoleculeLa...
│ - 0: <class 'momapy.drawing.Group'> = Group(class_='MacromoleculeLay...
│ │ <class 'momapy.drawing.Group'>: Group(class_='MacromoleculeLay...
│ │ │ * class_: str | None = MacromoleculeLayoutBuilder
│ │ │ * fill: momapy.drawing.NoneValueType | momapy.coloring.Color | None = None
│ │ │ * fill_rule: momapy.drawing.FillRule | None = None
│ │ │ * filter: momapy.drawing.NoneValueType | momapy.drawing.Filter | None = None
│ │ │ * font_family: str | None = None
│ │ │ * font_size: float | None = None
│ │ │ * font_style: momapy.drawing.FontStyle | None = None
│ │ │ * font_weight: momapy.drawing.FontWeight | int | None = None
│ │ │ * id_: str | None = glyph2
│ │ │ * stroke: momapy.drawing.NoneValueType | momapy.coloring.Color | None = None
│ │ │ * stroke_dasharray: momapy.drawing.NoneValueType | tuple[float, ...] | None = None
│ │ │ * stroke_dashoffset: momapy.drawing.NoneValueType | float | None = None
│ │ │ * stroke_width: momapy.drawing.NoneValueType | float | None = None
│ │ │ * text_anchor: momapy.drawing.TextAnchor | None = None
│ │ │ * transform: momapy.drawing.NoneValueType | tuple[momapy.geometry.Transformation] | None = None
│ │ │ * elements: tuple[momapy.drawing.DrawingElement] = (Group(class_='MacromoleculeLa...
│ │ │ │ <class 'tuple'>: (Group(class_='MacromoleculeLa...
│ │ │ │ │ - 0: <class 'momapy.drawing.Group'> = Group(class_='MacromoleculeLay...
│ │ │ │ │ │ <class 'momapy.drawing.Group'>: Group(class_='MacromoleculeLay...
│ │ │ │ │ │ │ * class_: str | None = MacromoleculeLayoutBuilder_own
│ │ │ │ │ │ │ * fill: momapy.drawing.NoneValueType | momapy.coloring.Color | None = Color(red=173, green=216, blue...
│ │ │ │ │ │ │ * fill_rule: momapy.drawing.FillRule | None = None
│ │ │ │ │ │ │ * filter: momapy.drawing.NoneValueType | momapy.drawing.Filter | None = None
│ │ │ │ │ │ │ * font_family: str | None = None
│ │ │ │ │ │ │ * font_size: float | None = None
│ │ │ │ │ │ │ * font_style: momapy.drawing.FontStyle | None = None
│ │ │ │ │ │ │ * font_weight: momapy.drawing.FontWeight | int | None = None
│ │ │ │ │ │ │ * id_: str | None = glyph2_own
│ │ │ │ │ │ │ * stroke: momapy.drawing.NoneValueType | momapy.coloring.Color | None = Color(red=165, green=42, blue=...
│ │ │ │ │ │ │ * stroke_dasharray: momapy.drawing.NoneValueType | tuple[float, ...] | None = (5, 5)
│ │ │ │ │ │ │ * stroke_dashoffset: momapy.drawing.NoneValueType | float | None = None
│ │ │ │ │ │ │ * stroke_width: momapy.drawing.NoneValueType | float | None = 3.0
│ │ │ │ │ │ │ * text_anchor: momapy.drawing.TextAnchor | None = None
│ │ │ │ │ │ │ * transform: momapy.drawing.NoneValueType | tuple[momapy.geometry.Transformation] | None = None
│ │ │ │ │ │ │ * elements: tuple[momapy.drawing.DrawingElement] = (Path(class_=None, fill=None, ...
│ │ │ │ │ - 1: <class 'momapy.drawing.Group'> = Group(class_='TextLayoutBuilde...
│ │ │ │ │ │ <class 'momapy.drawing.Group'>: Group(class_='TextLayoutBuilde...
│ │ │ │ │ │ │ * class_: str | None = TextLayoutBuilder
│ │ │ │ │ │ │ * fill: momapy.drawing.NoneValueType | momapy.coloring.Color | None = ColorBuilder(red=0, green=0, b...
│ │ │ │ │ │ │ * fill_rule: momapy.drawing.FillRule | None = None
│ │ │ │ │ │ │ * filter: momapy.drawing.NoneValueType | momapy.drawing.Filter | None = None
│ │ │ │ │ │ │ * font_family: str | None = DejaVu Sans
│ │ │ │ │ │ │ * font_size: float | None = 11.0
│ │ │ │ │ │ │ * font_style: momapy.drawing.FontStyle | None = FontStyle.NORMAL
│ │ │ │ │ │ │ * font_weight: momapy.drawing.FontWeight | int | None = FontWeight.NORMAL
│ │ │ │ │ │ │ * id_: str | None = 31a51c95-68dc-4dfb-819f-97f1bb...
│ │ │ │ │ │ │ * stroke: momapy.drawing.NoneValueType | momapy.coloring.Color | None = <momapy.drawing.NoneValueType ...
│ │ │ │ │ │ │ * stroke_dasharray: momapy.drawing.NoneValueType | tuple[float, ...] | None = None
│ │ │ │ │ │ │ * stroke_dashoffset: momapy.drawing.NoneValueType | float | None = None
│ │ │ │ │ │ │ * stroke_width: momapy.drawing.NoneValueType | float | None = None
│ │ │ │ │ │ │ * text_anchor: momapy.drawing.TextAnchor | None = None
│ │ │ │ │ │ │ * transform: momapy.drawing.NoneValueType | tuple[momapy.geometry.Transformation] | None = None
│ │ │ │ │ │ │ * elements: tuple[momapy.drawing.DrawingElement] = (Text(class_=None, fill=None, ...
│ │ │ │ │ - 2: <class 'momapy.drawing.Group'> = Group(class_='StateVariableLay...
│ │ │ │ │ │ <class 'momapy.drawing.Group'>: Group(class_='StateVariableLay...
│ │ │ │ │ │ │ * class_: str | None = StateVariableLayoutBuilder
│ │ │ │ │ │ │ * fill: momapy.drawing.NoneValueType | momapy.coloring.Color | None = None
│ │ │ │ │ │ │ * fill_rule: momapy.drawing.FillRule | None = None
│ │ │ │ │ │ │ * filter: momapy.drawing.NoneValueType | momapy.drawing.Filter | None = None
│ │ │ │ │ │ │ * font_family: str | None = None
│ │ │ │ │ │ │ * font_size: float | None = None
│ │ │ │ │ │ │ * font_style: momapy.drawing.FontStyle | None = None
│ │ │ │ │ │ │ * font_weight: momapy.drawing.FontWeight | int | None = None
│ │ │ │ │ │ │ * id_: str | None = glyph2a
│ │ │ │ │ │ │ * stroke: momapy.drawing.NoneValueType | momapy.coloring.Color | None = None
│ │ │ │ │ │ │ * stroke_dasharray: momapy.drawing.NoneValueType | tuple[float, ...] | None = None
│ │ │ │ │ │ │ * stroke_dashoffset: momapy.drawing.NoneValueType | float | None = None
│ │ │ │ │ │ │ * stroke_width: momapy.drawing.NoneValueType | float | None = None
│ │ │ │ │ │ │ * text_anchor: momapy.drawing.TextAnchor | None = None
│ │ │ │ │ │ │ * transform: momapy.drawing.NoneValueType | tuple[momapy.geometry.Transformation] | None = None
│ │ │ │ │ │ │ * elements: tuple[momapy.drawing.DrawingElement] = (Group(class_='StateVariableLa...
In [38]:
Copied!
l = momapy.builder.object_from_builder(lb)
display(l)
l = momapy.builder.object_from_builder(lb) display(l)
No description has been provided for this image

The label of a Node can also be styled:

In [39]:
Copied!
lb.label.font_family = "Times New Roman"
lb.label.font_size = 30.0
lb.label.fill = momapy.coloring.red
lb.label.stroke = momapy.coloring.black
lb.label.stroke_width = 2.0
l = momapy.builder.object_from_builder(lb)
display(l)
lb.label.font_family = "Times New Roman" lb.label.font_size = 30.0 lb.label.fill = momapy.coloring.red lb.label.stroke = momapy.coloring.black lb.label.stroke_width = 2.0 l = momapy.builder.object_from_builder(lb) display(l)
No description has been provided for this image

Advanced styling¶

Advanced effects such as transformations (translation, rotation, ...) and filter effects can be applied to LayoutElements:

In [40]:
Copied!
lb.group_transform = (
    momapy.geometry.Scaling(2, 1),
    momapy.geometry.Rotation(0.5, lb.position),
)
lb.group_filter = momapy.drawing.Filter(
    effects=(
        momapy.drawing.DropShadowEffect(
            dx=3.0,
            dy=3.0,
            std_deviation=5.0,
            flood_opacity=0.5,
            flood_color=momapy.coloring.blue,
        ),
    )
)
l = momapy.builder.object_from_builder(lb)
display(l)
lb.group_transform = ( momapy.geometry.Scaling(2, 1), momapy.geometry.Rotation(0.5, lb.position), ) lb.group_filter = momapy.drawing.Filter( effects=( momapy.drawing.DropShadowEffect( dx=3.0, dy=3.0, std_deviation=5.0, flood_opacity=0.5, flood_color=momapy.coloring.blue, ), ) ) l = momapy.builder.object_from_builder(lb) display(l)
No description has been provided for this image

CSS-like style sheets¶

Styles may be applied to a Map using a StyleSheet. A StyleSheet can be built from a text document whose syntax is a subset of the CSS syntax.

In [41]:
Copied!
r = read("data/phospho1.sbgn")
m = r.obj
display(m)
r = read("data/phospho1.sbgn") m = r.obj display(m)
No description has been provided for this image

There are pre-built StyleSheet objects for SBGN-ED and Newt-like styles, for colors, and for shadows:

In [42]:
Copied!
m = momapy.styling.apply_style_sheet(m, momapy.sbgn.styling.newt)
display(m)
m = momapy.styling.apply_style_sheet(m, momapy.sbgn.styling.newt) display(m)
No description has been provided for this image

Applying a StyleSheet to a Map may change the size of the nodes. Some simple functions can be used to tidy the Map:

In [43]:
Copied!
m = momapy.sbgn.utils.newt_tidy(m)
display(m)
m = momapy.sbgn.utils.newt_tidy(m) display(m)
No description has been provided for this image
In [44]:
Copied!
m = momapy.styling.apply_style_sheet(m, momapy.sbgn.styling.sbgned)
m = momapy.sbgn.utils.sbgned_tidy(m)
display(m)
m = momapy.styling.apply_style_sheet(m, momapy.sbgn.styling.sbgned) m = momapy.sbgn.utils.sbgned_tidy(m) display(m)
No description has been provided for this image
In [45]:
Copied!
m = momapy.styling.apply_style_sheet(m, momapy.sbgn.styling.cs_default)
display(m)
m = momapy.styling.apply_style_sheet(m, momapy.sbgn.styling.cs_default) display(m)
No description has been provided for this image
In [46]:
Copied!
m = momapy.styling.apply_style_sheet(m, momapy.sbgn.styling.fs_shadows)
display(m)
m = momapy.styling.apply_style_sheet(m, momapy.sbgn.styling.fs_shadows) display(m)
No description has been provided for this image

These pre-built StyleSheets are built from CSS-like text files:

In [47]:
Copied!
with open("../../src/momapy/sbgn/styling/sbgned_no_cs.css") as f:
    for line in f.readlines()[:35]:
        print(line[:-1])
with open("../../src/momapy/sbgn/styling/sbgned_no_cs.css") as f: for line in f.readlines()[:35]: print(line[:-1])
AndOperatorLayout {
	filter: unset;
	stroke-dasharray: unset;
	stroke-dashoffset: unset;
	stroke-width: 2.0;
	transform: unset;
	filter: unset;
	group-font-family: unset;
	group-font-size: unset;
	group-font-style: unset;
	group-font-weight: unset;
	height: 42.0;
	left-connector-filter: unset;
	left-connector-length: 10.0;
	left-connector-stroke-dasharray: unset;
	left-connector-stroke-dashoffset: unset;
	left-connector-stroke-width: unset;
	right-connector-filter: unset;
	right-connector-length: 10.0;
	right-connector-stroke-dasharray: unset;
	right-connector-stroke-dashoffset: unset;
	right-connector-stroke-width: unset;
	stroke-dasharray: unset;
	stroke-dashoffset: unset;
	stroke-width: unset;
	transform: unset;
	width: 42.0;
}


AssociationLayout {
	filter: unset;
	stroke-dasharray: unset;
	stroke-dashoffset: unset;
	stroke-width: 2.0;

One may build a StyleSheet from a file and apply it to a Map with simple functions:

In [48]:
Copied!
with open("build/my_style_sheet.css", "w") as f:
    f.write("""
        StateVariableLayout {
        	fill: royalblue;
        	stroke: white;
            stroke-width: 1.5;
            group-filter: unset;
        }
        
        StateVariableLayout > TextLayout {
          font-size: 7.0;
          fill: white;
        }
        
        MacromoleculeLayout {
        	fill: royalblue;
        	stroke: white;
            stroke-width: 2.0;
            group-filter: unset;
        }
        
        MacromoleculeLayout > TextLayout {
            font-size: 14.0;
            fill: white;
        }
        
        
        SimpleChemicalLayout {
        	fill: gold;
        	stroke: white;
            stroke-width: 2.0;
            group-filter: unset;
        }
        
        SimpleChemicalLayout > TextLayout {
            font-size: 10.0;
            fill: black;
        }
        
        GenericProcessLayout {
            fill: gray;
        	stroke: white;
        	right-connector-stroke: gray;
            right-connector-length: 10.0;
            right-connector-stroke-width: 1.5;
        	left-connector-stroke: gray;
            left-connector-length: 10.0;
            left-connector-stroke-width: 1.5;
            group-filter: unset;
        }
        
        ConsumptionLayout {
        	arrowhead-fill: white;
        	arrowhead-stroke: black;
        	path-stroke: gray;
            path-stroke-width: 1.5;
            end-shorten: 1.0;
            group-filter: unset;
        }
        
        ProductionLayout {
        	arrowhead-fill: gray;
        	arrowhead-stroke: gray;
        	path-stroke: gray;
            path-stroke-width: 1.5;
            end-shorten: 1.0;
            group-filter: unset;
        }
        
        CatalysisLayout {
        	arrowhead-fill: white;
        	arrowhead-stroke: gray;
            arrowhead-stroke-width: 2.0;
        	path-stroke: gray;
            path-stroke-width: 1.5;
            end-shorten: 1.0;
            group-filter: unset;
            stroke-width: 1.0;
        }
    """)
with open("build/my_style_sheet.css", "w") as f: f.write(""" StateVariableLayout { fill: royalblue; stroke: white; stroke-width: 1.5; group-filter: unset; } StateVariableLayout > TextLayout { font-size: 7.0; fill: white; } MacromoleculeLayout { fill: royalblue; stroke: white; stroke-width: 2.0; group-filter: unset; } MacromoleculeLayout > TextLayout { font-size: 14.0; fill: white; } SimpleChemicalLayout { fill: gold; stroke: white; stroke-width: 2.0; group-filter: unset; } SimpleChemicalLayout > TextLayout { font-size: 10.0; fill: black; } GenericProcessLayout { fill: gray; stroke: white; right-connector-stroke: gray; right-connector-length: 10.0; right-connector-stroke-width: 1.5; left-connector-stroke: gray; left-connector-length: 10.0; left-connector-stroke-width: 1.5; group-filter: unset; } ConsumptionLayout { arrowhead-fill: white; arrowhead-stroke: black; path-stroke: gray; path-stroke-width: 1.5; end-shorten: 1.0; group-filter: unset; } ProductionLayout { arrowhead-fill: gray; arrowhead-stroke: gray; path-stroke: gray; path-stroke-width: 1.5; end-shorten: 1.0; group-filter: unset; } CatalysisLayout { arrowhead-fill: white; arrowhead-stroke: gray; arrowhead-stroke-width: 2.0; path-stroke: gray; path-stroke-width: 1.5; end-shorten: 1.0; group-filter: unset; stroke-width: 1.0; } """)

The map with no style sheet:

In [49]:
Copied!
m = read("data/mek_erk.sbgn").obj
display(m, scale=1.5)
m = read("data/mek_erk.sbgn").obj display(m, scale=1.5)
No description has been provided for this image

The map with the style sheet applied, and tided:

In [50]:
Copied!
my_style_sheet = momapy.styling.StyleSheet.from_file("build/my_style_sheet.css")
m = momapy.styling.apply_style_sheet(m, my_style_sheet)
m = momapy.sbgn.utils.newt_tidy(m)
display(m, scale=1.5)
my_style_sheet = momapy.styling.StyleSheet.from_file("build/my_style_sheet.css") m = momapy.styling.apply_style_sheet(m, my_style_sheet) m = momapy.sbgn.utils.newt_tidy(m) display(m, scale=1.5)
No description has been provided for this image

Automatic geometry¶

LayoutElements support automatic geometry: their "shape" can be automatically computed from the DrawingElements they return, and be accessed with various methods. These methods depend on the nature of the LayoutElement (Node or Arc).

For Nodes¶

In [51]:
Copied!
l = macromolecule_toy()
display(l)
l = macromolecule_toy() display(l)
No description has been provided for this image

Anchors¶

Nodes have anchor points, that are specific Points on their border:

In [52]:
Copied!
l.north_west()
l.north_west()
Out[52]:
Point(x=80.0001, y=77.5)
In [53]:
Copied!
display(l, l.north_west())
display(l, l.north_west())
No description has been provided for this image

All Nodes have at least the following anchor points:

  • north_west
  • north
  • north_east
  • east
  • south_east
  • south
  • south_west
  • west
  • center
  • label_center
In [54]:
Copied!
show_room(momapy.sbgn.pd.MacromoleculeLayout)
show_room(momapy.sbgn.pd.MacromoleculeLayout)
No description has been provided for this image
In [55]:
Copied!
show_room(momapy.sbgn.pd.GenericProcessLayout)
show_room(momapy.sbgn.pd.GenericProcessLayout)
No description has been provided for this image
In [56]:
Copied!
show_room(momapy.sbgn.pd.NucleicAcidFeatureMultimerLayout)
show_room(momapy.sbgn.pd.NucleicAcidFeatureMultimerLayout)
No description has been provided for this image

Angles¶

Nodes also have angle points:

In [57]:
Copied!
l.own_angle(130)
l.own_angle(130)
Out[57]:
Point(x=162.0675, y=75.0)
In [58]:
Copied!
display(l, l.own_angle(130))
display(l, l.own_angle(130))
No description has been provided for this image
In [59]:
Copied!
show_room(momapy.sbgn.pd.MacromoleculeLayout, "own_angle")
show_room(momapy.sbgn.pd.MacromoleculeLayout, "own_angle")
No description has been provided for this image
In [60]:
Copied!
display(l, l.angle(130))
display(l, l.angle(130))
No description has been provided for this image
In [61]:
Copied!
show_room(momapy.sbgn.pd.MacromoleculeLayout, "angle")
show_room(momapy.sbgn.pd.MacromoleculeLayout, "angle")
No description has been provided for this image

For Arcs¶

In [62]:
Copied!
l = production_toy()
display(l)
l = production_toy() display(l)
No description has been provided for this image

Anchors¶

Analogously to Nodes, Arcs have a few anchor points:

In [63]:
Copied!
l.arrowhead_base()
l.arrowhead_base()
Out[63]:
Point(x=303.7868, y=71.2132)
In [64]:
Copied!
display(l, l.arrowhead_base())
display(l, l.arrowhead_base())
No description has been provided for this image

All Arcs have at least the following anchor points:

  • end_point
  • start_point

SingleHeadedArcs have the following additional anchor points:

  • arrowhead_base
  • arrowhead_tip

And DoubleHeadedArcs have the following additional ones:

  • start_arrowhead_base
  • start_arrowhead_tip
  • end_arrowhead_base
  • end_arrowhead_tip
In [65]:
Copied!
show_room(momapy.sbgn.pd.ProductionLayout)
show_room(momapy.sbgn.pd.ProductionLayout)
No description has been provided for this image

Fraction¶

Arcs also have fraction points:

In [66]:
Copied!
l.fraction(0.50)
l.fraction(0.50)
Out[66]:
(Point(x=225.0, y=150.0), 5.497787143782138)
In [67]:
Copied!
pos, angle = l.fraction(0.50)
display(l, pos)
pos, angle = l.fraction(0.50) display(l, pos)
No description has been provided for this image
In [68]:
Copied!
show_room(momapy.sbgn.pd.ProductionLayout, "fraction")
show_room(momapy.sbgn.pd.ProductionLayout, "fraction")
No description has been provided for this image

Relative positioning¶

Automatic geometry enables positioning LayoutElements relatively to one another:

In [69]:
Copied!
r = read("data/phospho1.sbgn")
m = r.obj
display(m)
r = read("data/phospho1.sbgn") m = r.obj display(m)
No description has been provided for this image
In [70]:
Copied!
mb = momapy.builder.builder_from_object(m)
for lb in mb.layout.layout_elements:
    if (
        hasattr(lb, "label") and lb.label is not None and lb.label.text == "B"
    ):  # we select the layout for B
        eb = lb
    elif momapy.builder.isinstance_or_builder(
        lb, momapy.sbgn.pd.GenericProcessLayout
    ):  # we select the process layout
        pb = lb
mb = momapy.builder.builder_from_object(m) for lb in mb.layout.layout_elements: if ( hasattr(lb, "label") and lb.label is not None and lb.label.text == "B" ): # we select the layout for B eb = lb elif momapy.builder.isinstance_or_builder( lb, momapy.sbgn.pd.GenericProcessLayout ): # we select the process layout pb = lb
In [71]:
Copied!
eb.position = momapy.positioning.above_left_of(
    pb, 200, 50
)  # eb's position is set 200 units above and 50 units left of pb's position
eb.label.position = eb.position
momapy.sbgn.utils.tidy(mb)  # sets the arcs to the borders
m = momapy.builder.object_from_builder(mb)
display(m)
eb.position = momapy.positioning.above_left_of( pb, 200, 50 ) # eb's position is set 200 units above and 50 units left of pb's position eb.label.position = eb.position momapy.sbgn.utils.tidy(mb) # sets the arcs to the borders m = momapy.builder.object_from_builder(mb) display(m)
No description has been provided for this image

The following functions are available:

  • above_left_of
  • above_of
  • above_right_of
  • right_of
  • below_right_of
  • below_of
  • below_left_of
  • left_of

For container Nodes, the fit function is also available:

In [72]:
Copied!
momapy.positioning.fit(mb.layout.layout_elements, xsep=20, ysep=10)
momapy.positioning.fit(mb.layout.layout_elements, xsep=20, ysep=10)
Out[72]:
Bbox(position=Point(x=237.5, y=164.0), width=453.0, height=292.0)

Each of the above functions has a corresponding set function, which directly sets the returned value(s) to the correct Node's attributes:

In [73]:
Copied!
mb.layout.stroke = momapy.coloring.red
momapy.positioning.set_fit(mb.layout, mb.layout.layout_elements, xsep=20, ysep=10)
m = momapy.builder.object_from_builder(mb)
display(m)
mb.layout.stroke = momapy.coloring.red momapy.positioning.set_fit(mb.layout, mb.layout.layout_elements, xsep=20, ysep=10) m = momapy.builder.object_from_builder(mb) display(m)
No description has been provided for this image

The set functions have an anchor option that sets the relative target anchor of the Node receiving the new position (default is center):

In [74]:
Copied!
momapy.positioning.set_above_left_of(
    eb, pb, 200, 50, anchor="south_east"
)  # eb's position is set such that its south_east anchor is 200 units above and 50 units left of pb's position
eb.label.position = eb.position
momapy.sbgn.utils.tidy(mb)
m = momapy.builder.object_from_builder(mb)
display(m)
momapy.positioning.set_above_left_of( eb, pb, 200, 50, anchor="south_east" ) # eb's position is set such that its south_east anchor is 200 units above and 50 units left of pb's position eb.label.position = eb.position momapy.sbgn.utils.tidy(mb) m = momapy.builder.object_from_builder(mb) display(m)
No description has been provided for this image

Building new types of Nodes and Arcs¶

New types of Nodes and Arcs can be easily programmed. Since the geometry is automatic, it is only required to program the general shape of the LayoutElement using DrawingElements.

New Node¶

In [75]:
Copied!
import dataclasses
import dataclasses
In [76]:
Copied!
@dataclasses.dataclass(frozen=True)
class MyTriangle(Node):
    height: float = 30.0
    width: float = 30.0
    fill: momapy.coloring.Color = momapy.coloring.white
    stroke: momapy.coloring.Color = momapy.coloring.black

    def _border_drawing_elements(self):
        actions = [
            momapy.drawing.MoveTo(self.position - (0, self.height / 2)),  # top
            momapy.drawing.LineTo(
                self.position + (self.width / 2, self.height / 2)
            ),  # bottom right
            momapy.drawing.LineTo(
                self.position + (-self.width / 2, self.height / 2)
            ),  # bottom left
            momapy.drawing.ClosePath(),
        ]
        path = momapy.drawing.Path(actions=actions)
        return [path]
@dataclasses.dataclass(frozen=True) class MyTriangle(Node): height: float = 30.0 width: float = 30.0 fill: momapy.coloring.Color = momapy.coloring.white stroke: momapy.coloring.Color = momapy.coloring.black def _border_drawing_elements(self): actions = [ momapy.drawing.MoveTo(self.position - (0, self.height / 2)), # top momapy.drawing.LineTo( self.position + (self.width / 2, self.height / 2) ), # bottom right momapy.drawing.LineTo( self.position + (-self.width / 2, self.height / 2) ), # bottom left momapy.drawing.ClosePath(), ] path = momapy.drawing.Path(actions=actions) return [path]
In [77]:
Copied!
show_room(MyTriangle)
show_room(MyTriangle)
No description has been provided for this image
In [78]:
Copied!
show_room(MyTriangle, "angle")
show_room(MyTriangle, "angle")
No description has been provided for this image

New Arc¶

In [79]:
Copied!
@dataclasses.dataclass(frozen=True)
class MyRectangleArrow(SingleHeadedArc):
    arrowhead_width: float = 10.0
    arrowhead_height: float = 10.0
    arrowhead_fill: momapy.coloring.Color | momapy.drawing.NoneValueType = (
        momapy.coloring.white
    )
    arrowhead_stroke: momapy.coloring.Color = momapy.coloring.black
    path_fill: momapy.coloring.Color | momapy.drawing.NoneValueType = (
        momapy.drawing.NoneValue
    )
    path_stroke: momapy.coloring.Color = momapy.coloring.black

    def _arrowhead_border_drawing_elements(self):
        actions = [
            momapy.drawing.MoveTo(
                momapy.geometry.Point(0, 0)
            ),  # we draw the arrowhead as if its base is at (0, 0)
            momapy.drawing.LineTo(
                momapy.geometry.Point(0, -self.arrowhead_height / 2)
            ),  # top left
            momapy.drawing.LineTo(
                momapy.geometry.Point(self.arrowhead_width, -self.arrowhead_height / 2)
            ),  # top right
            momapy.drawing.LineTo(
                momapy.geometry.Point(self.arrowhead_width, self.arrowhead_height / 2)
            ),  # bottom right
            momapy.drawing.LineTo(
                momapy.geometry.Point(0, self.arrowhead_height / 2)
            ),  # bottom left
            momapy.drawing.ClosePath(),
        ]
        path = momapy.drawing.Path(actions=actions)
        return [path]
@dataclasses.dataclass(frozen=True) class MyRectangleArrow(SingleHeadedArc): arrowhead_width: float = 10.0 arrowhead_height: float = 10.0 arrowhead_fill: momapy.coloring.Color | momapy.drawing.NoneValueType = ( momapy.coloring.white ) arrowhead_stroke: momapy.coloring.Color = momapy.coloring.black path_fill: momapy.coloring.Color | momapy.drawing.NoneValueType = ( momapy.drawing.NoneValue ) path_stroke: momapy.coloring.Color = momapy.coloring.black def _arrowhead_border_drawing_elements(self): actions = [ momapy.drawing.MoveTo( momapy.geometry.Point(0, 0) ), # we draw the arrowhead as if its base is at (0, 0) momapy.drawing.LineTo( momapy.geometry.Point(0, -self.arrowhead_height / 2) ), # top left momapy.drawing.LineTo( momapy.geometry.Point(self.arrowhead_width, -self.arrowhead_height / 2) ), # top right momapy.drawing.LineTo( momapy.geometry.Point(self.arrowhead_width, self.arrowhead_height / 2) ), # bottom right momapy.drawing.LineTo( momapy.geometry.Point(0, self.arrowhead_height / 2) ), # bottom left momapy.drawing.ClosePath(), ] path = momapy.drawing.Path(actions=actions) return [path]
In [80]:
Copied!
show_room(MyRectangleArrow)
show_room(MyRectangleArrow)
No description has been provided for this image
In [81]:
Copied!
show_room(MyRectangleArrow, "fraction")
show_room(MyRectangleArrow, "fraction")
No description has been provided for this image

New LayoutElements can be even more easily programmed using the Shapes already available in the meta module:

In [82]:
Copied!
import momapy.meta.shapes
import momapy.meta.shapes
In [83]:
Copied!
@dataclasses.dataclass(frozen=True)
class MyDoubleRectangleArrow(DoubleHeadedArc):
    start_arrowhead_width: float = 10.0
    start_arrowhead_height: float = 10.0
    start_arrowhead_fill: momapy.coloring.Color | momapy.drawing.NoneValueType = (
        momapy.coloring.white
    )
    start_arrowhead_stroke: momapy.coloring.Color = momapy.coloring.black
    end_arrowhead_width: float = 20.0
    end_arrowhead_height: float = 20.0
    end_arrowhead_fill: momapy.coloring.Color | momapy.drawing.NoneValueType = (
        momapy.coloring.white
    )
    end_arrowhead_stroke: momapy.coloring.Color = momapy.coloring.black
    path_fill: momapy.coloring.Color | momapy.drawing.NoneValueType = (
        momapy.drawing.NoneValue
    )
    path_stroke: momapy.coloring.Color = momapy.coloring.black

    def _start_arrowhead_border_drawing_elements(self):
        return momapy.meta.shapes.Rectangle(
            position=momapy.geometry.Point(-self.start_arrowhead_width / 2, 0),
            width=self.start_arrowhead_width,
            height=self.start_arrowhead_height,
        ).drawing_elements()

    def _end_arrowhead_border_drawing_elements(self):
        return momapy.meta.shapes.Rectangle(
            position=momapy.geometry.Point(self.end_arrowhead_width / 2, 0),
            width=self.end_arrowhead_width,
            height=self.end_arrowhead_height,
        ).drawing_elements()
@dataclasses.dataclass(frozen=True) class MyDoubleRectangleArrow(DoubleHeadedArc): start_arrowhead_width: float = 10.0 start_arrowhead_height: float = 10.0 start_arrowhead_fill: momapy.coloring.Color | momapy.drawing.NoneValueType = ( momapy.coloring.white ) start_arrowhead_stroke: momapy.coloring.Color = momapy.coloring.black end_arrowhead_width: float = 20.0 end_arrowhead_height: float = 20.0 end_arrowhead_fill: momapy.coloring.Color | momapy.drawing.NoneValueType = ( momapy.coloring.white ) end_arrowhead_stroke: momapy.coloring.Color = momapy.coloring.black path_fill: momapy.coloring.Color | momapy.drawing.NoneValueType = ( momapy.drawing.NoneValue ) path_stroke: momapy.coloring.Color = momapy.coloring.black def _start_arrowhead_border_drawing_elements(self): return momapy.meta.shapes.Rectangle( position=momapy.geometry.Point(-self.start_arrowhead_width / 2, 0), width=self.start_arrowhead_width, height=self.start_arrowhead_height, ).drawing_elements() def _end_arrowhead_border_drawing_elements(self): return momapy.meta.shapes.Rectangle( position=momapy.geometry.Point(self.end_arrowhead_width / 2, 0), width=self.end_arrowhead_width, height=self.end_arrowhead_height, ).drawing_elements()
In [84]:
Copied!
show_room(MyDoubleRectangleArrow)
show_room(MyDoubleRectangleArrow)
No description has been provided for this image

Ongoing and future work¶

Ongoing work¶

  • Support for SBML layout/render

Future work¶

  • Improve performance of geometry (currently slow)
  • Support for background images and gradients in layouts
  • Support for SBGN ER maps?
  • Support for BioPAX models?
  • Automatic XML/JSON format
  • Developpment of converters
Home Styling
Menu
Home User manual Styling CLI reference Changelog About
API reference
Overview Core Builder Drawing Coloring Geometry I/O Positioning Styling

On This Page

Maps Models Layouts LayoutModelMappings Equality and-sub relation for Maps, Models, Layouts and LayoutModelMappings Equality Sub-map/model/layout/mapping Frozen and builder objects Reading and writing Rendering Styling Basic styling Advanced styling CSS-like style sheets Automatic geometry For Nodes For Arcs Relative positioning Building new types of Nodes and Arcs New Node New Arc Ongoing and future work Ongoing work Future work
shadcn theme provided by @asiffer