{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "b188df16",
   "metadata": {},
   "source": [
    "# momapy: a python library for molecular maps"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "38c02689",
   "metadata": {},
   "source": [
    "The momapy library is a new python library for working with molecular maps such as SBGN maps.\n",
    "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.\n",
    "This definition is borrowed from SBML and its extensions layout/render, that allowed users to add a layout to an SBML model.\n",
    "MomaPy aims at extending this definition to all types of molecular maps, and in particular to SBGN maps.\n",
    "\n",
    "MomaPy offers the following features:\n",
    "* support for SBGN PD and AF maps (read/write SBGN-ML with annotations, rendering information, and notes)\n",
    "* decomposition of a map object into:\n",
    "    - a model object;\n",
    "    - a layout object;\n",
    "    - a mapping between the model and layout objects' subelements\n",
    "* map, model, layout and mapping objects comparison; fast object in set checking\n",
    "* rendering of maps to images (SVG, PDF, JPEG, PNG, WebP) and other surfaces (e.g. GLFW window)\n",
    "* support for styling and css like stylesheets (including effects such as shadows)\n",
    "* automatic geometry and anchors (for arcs, shape borders)\n",
    "* local positioning (e.g. right of shape, fit set of shapes)\n",
    "* easy extension with new model and layout subelements"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "71b516ba",
   "metadata": {},
   "outputs": [],
   "source": [
    "import momapy.io\n",
    "import momapy.builder\n",
    "import momapy.coloring\n",
    "import momapy.styling\n",
    "import momapy.utils\n",
    "\n",
    "import momapy.sbgn.io.sbgnml\n",
    "import momapy.sbgn.styling\n",
    "import momapy.sbgn.utils"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "7f00e3d3",
   "metadata": {},
   "outputs": [],
   "source": [
    "from utils import display, display_at, show_room, macromolecule_toy, production_toy"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2738f562",
   "metadata": {},
   "source": [
    "## The Map object"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "ea8c9f24",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Fontconfig warning: \"/usr/share/fontconfig/conf.avail/05-reset-dirs-sample.conf\", line 6: unknown element \"reset-dirs\"\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 7556 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "r = momapy.io.read(\"phospho1.sbgn\")\n",
    "m = r.obj\n",
    "display(m)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "05edf792",
   "metadata": {},
   "source": [
    "A `Map` object always contains a `Model`, a `Layout` and a `LayoutModelMapping` (that maps model elements to layout elements).\n",
    "It may also have additional attributes depending on its nature. For example, an SBGN PD map also has an `id`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "a63e2eb1",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[32m<class 'momapy.sbgn.pd.SBGNPDMap'>\u001b[31m: SBGNPDMap(id_='969ca0f0-dac8-4...\u001b[0m\n",
      "  \u001b[34m* id_\u001b[35m: <class 'str'> = \u001b[31m969ca0f0-dac8-42f2-9980-aaf06f...\u001b[0m\n",
      "  \u001b[34m* model\u001b[35m: <class 'momapy.sbgn.pd.SBGNPDModel'> = \u001b[31mSBGNPDModel(id_='fb091b4e-780e...\u001b[0m\n",
      "  \u001b[34m* layout\u001b[35m: <class 'momapy.sbgn.pd.SBGNPDLayout'> = \u001b[31mSBGNPDLayout(id_='c37f77e2-3e4...\u001b[0m\n",
      "  \u001b[34m* layout_model_mapping\u001b[35m: momapy.core.LayoutModelMapping | None = \u001b[31mLayoutModelMapping(_singleton_...\u001b[0m\n"
     ]
    }
   ],
   "source": [
    "momapy.utils.pretty_print(m)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "670f0e3e",
   "metadata": {},
   "source": [
    "## The Model object"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "95d4786f",
   "metadata": {},
   "source": [
    "A `Model` may have an arbitrary number of attributes, depending on its nature.\n",
    "For example, an `SBGNPDModel` has the following attributes: `entity_pools`, `processes`, `compartments`, `modulations`, `logical_operators`, `equivalence_operators`, `submaps` and `tags`, but also an `id`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "c47b3ee3",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[32m<class 'momapy.sbgn.pd.SBGNPDModel'>\u001b[31m: SBGNPDModel(id_='fb091b4e-780e...\u001b[0m\n",
      "  \u001b[34m* id_\u001b[35m: <class 'str'> = \u001b[31mfb091b4e-780e-4b45-9444-a190e6...\u001b[0m\n",
      "  \u001b[34m* entity_pools\u001b[35m: frozenset[momapy.sbgn.pd.EntityPool] = \u001b[31mfrozenset({Macromolecule(id_='...\u001b[0m\n",
      "  \u001b[34m* processes\u001b[35m: frozenset[momapy.sbgn.pd.Process] = \u001b[31mfrozenset({GenericProcess(id_=...\u001b[0m\n",
      "  \u001b[34m* compartments\u001b[35m: frozenset[momapy.sbgn.pd.Compartment] = \u001b[31mfrozenset()\u001b[0m\n",
      "  \u001b[34m* modulations\u001b[35m: frozenset[momapy.sbgn.pd.Modulation] = \u001b[31mfrozenset({Catalysis(id_='arc3...\u001b[0m\n",
      "  \u001b[34m* logical_operators\u001b[35m: frozenset[momapy.sbgn.pd.LogicalOperator] = \u001b[31mfrozenset()\u001b[0m\n",
      "  \u001b[34m* equivalence_operators\u001b[35m: frozenset[momapy.sbgn.pd.EquivalenceOperator] = \u001b[31mfrozenset()\u001b[0m\n",
      "  \u001b[34m* submaps\u001b[35m: frozenset[momapy.sbgn.pd.Submap] = \u001b[31mfrozenset()\u001b[0m\n",
      "  \u001b[34m* tags\u001b[35m: frozenset[momapy.sbgn.pd.Tag] = \u001b[31mfrozenset()\u001b[0m\n"
     ]
    }
   ],
   "source": [
    "momapy.utils.pretty_print(m.model)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0554ca24",
   "metadata": {},
   "source": [
    "These attributes may be `ModelElement`s or collections of `ModelElements`. For example, the `entity_pools` attribute of an `SBGNPDModel` may contain zero or more `EntityPool`s.\n",
    "We pick the first element from the collection:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "21b6b343",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[32m<class 'momapy.sbgn.pd.Macromolecule'>\u001b[31m: Macromolecule(id_='glyph2', co...\u001b[0m\n",
      "  \u001b[34m* id_\u001b[35m: <class 'str'> = \u001b[31mglyph2\u001b[0m\n",
      "  \u001b[34m* compartment\u001b[35m: momapy.sbgn.pd.Compartment | None = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* label\u001b[35m: str | None = \u001b[31mA\u001b[0m\n",
      "  \u001b[34m* state_variables\u001b[35m: frozenset[momapy.sbgn.pd.StateVariable] = \u001b[31mfrozenset({StateVariable(id_='...\u001b[0m\n",
      "  \u001b[34m* units_of_information\u001b[35m: frozenset[momapy.sbgn.pd.UnitOfInformation] = \u001b[31mfrozenset()\u001b[0m\n"
     ]
    }
   ],
   "source": [
    "for e in m.model.entity_pools:\n",
    "    break\n",
    "momapy.utils.pretty_print(e)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "16832313",
   "metadata": {},
   "source": [
    "Here, the element is a `Macromolecule`.\n",
    "The data model for SBGN PD is built on a hierarchy of classes following the corresponding ontology.\n",
    "Hence this element is also an `EntityPool`, and more generally a `ModelElement`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "2c76ab0d",
   "metadata": {},
   "outputs": [],
   "source": [
    "assert isinstance(e, momapy.sbgn.pd.EntityPool)\n",
    "assert isinstance(e, momapy.core.ModelElement)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "04d417d8",
   "metadata": {},
   "source": [
    "## The Layout object"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "003e622d",
   "metadata": {},
   "source": [
    "A `Layout` is some sort of canvas that may contain other `LayoutElement`s that correspond to shapes that represent `ModelElement`s and that may be rendered.\n",
    "In SBGN, `LayoutElement`s are either `Node`s, `Arc`s or `TextLayout`s.\n",
    "The different `LayoutElement`s of a `Layout` are contained in its `layout_elements` attribute:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "eb960ce0",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[32m<class 'momapy.sbgn.pd.SBGNPDLayout'>\u001b[31m: SBGNPDLayout(id_='c37f77e2-3e4...\u001b[0m\n",
      "  \u001b[34m* id_\u001b[35m: <class 'str'> = \u001b[31mc37f77e2-3e48-491d-9d33-7094a2...\u001b[0m\n",
      "  \u001b[34m* layout_elements\u001b[35m: tuple[momapy.core.LayoutElement] = \u001b[31m(MacromoleculeLayout(id_='glyp...\u001b[0m\n",
      "  \u001b[34m* group_fill\u001b[35m: momapy.drawing.NoneValueType | momapy.coloring.Color | None = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* group_fill_rule\u001b[35m: momapy.drawing.FillRule | None = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* group_filter\u001b[35m: momapy.drawing.NoneValueType | momapy.drawing.Filter | None = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* group_font_family\u001b[35m: str | None = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* group_font_size\u001b[35m: float | None = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* group_font_style\u001b[35m: momapy.drawing.FontStyle | None = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* group_font_weight\u001b[35m: momapy.drawing.FontWeight | float | None = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* group_stroke\u001b[35m: momapy.drawing.NoneValueType | momapy.coloring.Color | None = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* group_stroke_dasharray\u001b[35m: tuple[float] | None = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* group_stroke_dashoffset\u001b[35m: float | None = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* group_stroke_width\u001b[35m: float | None = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* group_text_anchor\u001b[35m: momapy.drawing.TextAnchor | None = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* group_transform\u001b[35m: momapy.drawing.NoneValueType | tuple[momapy.geometry.Transformation] | None = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* fill\u001b[35m: momapy.drawing.NoneValueType | momapy.coloring.Color | None = \u001b[31mColor(red=255, green=255, blue...\u001b[0m\n",
      "  \u001b[34m* filter\u001b[35m: momapy.drawing.NoneValueType | momapy.drawing.Filter | None = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* height\u001b[35m: <class 'float'> = \u001b[31m250.0\u001b[0m\n",
      "  \u001b[34m* label\u001b[35m: momapy.core.TextLayout | None = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* position\u001b[35m: <class 'momapy.geometry.Point'> = \u001b[31mPoint(x=240.0, y=205.0)\u001b[0m\n",
      "  \u001b[34m* stroke\u001b[35m: momapy.drawing.NoneValueType | momapy.coloring.Color | None = \u001b[31mColor(red=255, green=0, blue=0...\u001b[0m\n",
      "  \u001b[34m* stroke_dasharray\u001b[35m: momapy.drawing.NoneValueType | tuple[float] | None = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* stroke_dashoffset\u001b[35m: float | None = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* stroke_width\u001b[35m: float | None = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* transform\u001b[35m: momapy.drawing.NoneValueType | tuple[momapy.geometry.Transformation] | None = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* width\u001b[35m: <class 'float'> = \u001b[31m450.0\u001b[0m\n"
     ]
    }
   ],
   "source": [
    "momapy.utils.pretty_print(m.layout)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8408d722",
   "metadata": {},
   "source": [
    "A `LayoutElement` always has a `drawing_elements` method that returns `DrawingElement`s that may be rendered using a `Renderer`.\n",
    "The `DrawingElement`s are built on the fly based on the other attributes of the `LayoutElement`.\n",
    "`DrawingElement`s 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).\n",
    "A `Layout` itself is a `Node`, whose returned unique `DrawingElement` ultimately represents a rectangle built from its `position`, `width` and `height` attributes.\n",
    "The style of this rectangle depends on the styling attribute of the `Layout` (e.g. the `stroke`, `stroke_width`, and `fill` attributes):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "dae7e775",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[32m<class 'list'>\u001b[31m: [Group(class_='SBGNPDLayout_gr...\u001b[0m\n",
      "  \u001b[34m- 0\u001b[35m: <class 'momapy.drawing.Group'> = \u001b[31mGroup(class_='SBGNPDLayout_gro...\u001b[0m\n"
     ]
    }
   ],
   "source": [
    "momapy.utils.pretty_print(m.layout.drawing_elements())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7254f36b",
   "metadata": {},
   "source": [
    "A `Layout` may contain other `LayoutElement`s, which themselves may contain other `LayoutElement`s, recursively forming a hierarchy of `LayoutElement`s, and thus of `DrawingElement`s.\n",
    "While a `Layout` represents a `Model`, contained `LayoutElement`s represent `ModelElement`s contained by the `Model`."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d6cf70f6",
   "metadata": {},
   "source": [
    "We pick the first `LayoutElement` from the `SBGNPDLayout`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "56352c9c",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[32m<class 'momapy.sbgn.pd.MacromoleculeLayout'>\u001b[31m: MacromoleculeLayout(id_='glyph...\u001b[0m\n",
      "  \u001b[34m* id_\u001b[35m: <class 'str'> = \u001b[31mglyph2\u001b[0m\n",
      "  \u001b[34m* layout_elements\u001b[35m: tuple[momapy.core.LayoutElement] = \u001b[31m(StateVariableLayout(id_='glyp...\u001b[0m\n",
      "  \u001b[34m* group_fill\u001b[35m: momapy.drawing.NoneValueType | momapy.coloring.Color | None = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* group_fill_rule\u001b[35m: momapy.drawing.FillRule | None = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* group_filter\u001b[35m: momapy.drawing.NoneValueType | momapy.drawing.Filter | None = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* group_font_family\u001b[35m: str | None = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* group_font_size\u001b[35m: float | None = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* group_font_style\u001b[35m: momapy.drawing.FontStyle | None = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* group_font_weight\u001b[35m: momapy.drawing.FontWeight | float | None = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* group_stroke\u001b[35m: momapy.drawing.NoneValueType | momapy.coloring.Color | None = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* group_stroke_dasharray\u001b[35m: tuple[float] | None = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* group_stroke_dashoffset\u001b[35m: float | None = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* group_stroke_width\u001b[35m: float | None = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* group_text_anchor\u001b[35m: momapy.drawing.TextAnchor | None = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* group_transform\u001b[35m: momapy.drawing.NoneValueType | tuple[momapy.geometry.Transformation] | None = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* fill\u001b[35m: momapy.drawing.NoneValueType | momapy.coloring.Color | None = \u001b[31mColor(red=255, green=255, blue...\u001b[0m\n",
      "  \u001b[34m* filter\u001b[35m: momapy.drawing.NoneValueType | momapy.drawing.Filter | None = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* height\u001b[35m: <class 'float'> = \u001b[31m60.0\u001b[0m\n",
      "  \u001b[34m* label\u001b[35m: momapy.core.TextLayout | None = \u001b[31mTextLayout(id_='eec8226a-c99f-...\u001b[0m\n",
      "  \u001b[34m* position\u001b[35m: <class 'momapy.geometry.Point'> = \u001b[31mPoint(x=85.0, y=270.0)\u001b[0m\n",
      "  \u001b[34m* stroke\u001b[35m: momapy.drawing.NoneValueType | momapy.coloring.Color | None = \u001b[31mColor(red=0, green=0, blue=0, ...\u001b[0m\n",
      "  \u001b[34m* stroke_dasharray\u001b[35m: momapy.drawing.NoneValueType | tuple[float] | None = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* stroke_dashoffset\u001b[35m: float | None = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* stroke_width\u001b[35m: float | None = \u001b[31m1.25\u001b[0m\n",
      "  \u001b[34m* transform\u001b[35m: momapy.drawing.NoneValueType | tuple[momapy.geometry.Transformation] | None = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* width\u001b[35m: <class 'float'> = \u001b[31m108.0\u001b[0m\n",
      "  \u001b[34m* rounded_corners\u001b[35m: <class 'float'> = \u001b[31m5.0\u001b[0m\n"
     ]
    }
   ],
   "source": [
    "for l in m.layout.layout_elements:\n",
    "    break\n",
    "momapy.utils.pretty_print(l)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c5c91cac",
   "metadata": {},
   "source": [
    "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 `LayoutElement`s in its `layout_elements` attribute, it will also produce the `DrawingElement`s of these contained `LayoutElement`s (here, a `StateVariableLayout`):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "3781bb46",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "display(l)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "764b565a",
   "metadata": {},
   "source": [
    "## The LayoutModelMapping object"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "44788606",
   "metadata": {},
   "source": [
    "A `LayoutModelMapping` is a mapping from `LayoutElement`s to `ModelElement`s and vice-versa.\n",
    "It is used to map the `LayoutElement`s of a `Map` to the `ModelElement`s they represent.\n",
    "It is intended to be as generic as possible so it maps sets of elements rather than elements themselves."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0b944171",
   "metadata": {},
   "source": [
    "We pick the `ModelElement` mapped to the `MacromoleculeLayout` we had picked.\n",
    "We know that there is only one `ModelElement` mapped to it so we can safely use the `unpack` option that removes the containing set:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "6a441c1b",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[32m<class 'momapy.sbgn.pd.Macromolecule'>\u001b[31m: Macromolecule(id_='glyph2', co...\u001b[0m\n",
      "  \u001b[34m* id_\u001b[35m: <class 'str'> = \u001b[31mglyph2\u001b[0m\n",
      "  \u001b[34m* compartment\u001b[35m: momapy.sbgn.pd.Compartment | None = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* label\u001b[35m: str | None = \u001b[31mA\u001b[0m\n",
      "  \u001b[34m* state_variables\u001b[35m: frozenset[momapy.sbgn.pd.StateVariable] = \u001b[31mfrozenset({StateVariable(id_='...\u001b[0m\n",
      "  \u001b[34m* units_of_information\u001b[35m: frozenset[momapy.sbgn.pd.UnitOfInformation] = \u001b[31mfrozenset()\u001b[0m\n"
     ]
    }
   ],
   "source": [
    "e = m.layout_model_mapping.get_mapping(l, unpack=True)[0]\n",
    "momapy.utils.pretty_print(e)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c034e17c",
   "metadata": {},
   "source": [
    "## Equality and-sub relation for Maps, Models, Layouts and LayoutModelMappings"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "03e425df",
   "metadata": {},
   "source": [
    "`Map`s, `Model`s, `Layout`s, `LayoutModelMapping`s, `ModelElement`s and `LayoutElement`s are programmed so they can be easily compared.\n",
    "Their identity relies on the value of a subset of their attributes (generally all attributes but their `id`).\n",
    "This way, two `Map`s can be easily compared.\n",
    "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)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8271dfaa",
   "metadata": {},
   "source": [
    "### Equality"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "48ceb8ab",
   "metadata": {},
   "source": [
    "#### Definition"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fb691aa6",
   "metadata": {},
   "source": [
    "Two `Map`s are equal if and only if:\n",
    "* their `Model`s are equal;\n",
    "* their `Layout`s are equal;\n",
    "* and their `LayoutModelMapping`s are equal."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8a6ede69",
   "metadata": {},
   "source": [
    "#### Example"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "9272b504",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 7556 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "r1 = momapy.io.read(\"phospho1.sbgn\")\n",
    "m1 = r1.obj\n",
    "display(m1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "a5dada7f",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 9512 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "r2 = momapy.io.read(\"phospho2.sbgn\")\n",
    "m2 = r2.obj\n",
    "display(m2)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "325540b1",
   "metadata": {},
   "source": [
    "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.\n",
    "This can be checked easily by comparing the `Map`, `Model`, `Layout` and `LayoutModelMapping` objects representing the two maps:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "3d25a5b8",
   "metadata": {},
   "outputs": [],
   "source": [
    "assert m1 != m2\n",
    "assert m1.model == m2.model\n",
    "assert m1.layout != m2.layout\n",
    "assert m1.layout_model_mapping != m2.layout_model_mapping"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0d37a254",
   "metadata": {},
   "source": [
    "### Sub-map/model/layout/mapping"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bd06325a",
   "metadata": {},
   "source": [
    "#### Definition"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bec5a377",
   "metadata": {},
   "source": [
    "A `Map` `M` is a sub-map of another `Map` `M'` if and only if:\n",
    "* the `Model` of `M` is a sub-model of the `Model` of `M'`;\n",
    "* the `Layout` of `M` is a sub-layout of the `Layout` of `M'`;\n",
    "* and the `LayoutModelMapping` of `M` is a sub-mapping of the `LayoutModelMapping` of `M'`.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6590e038",
   "metadata": {},
   "source": [
    "#### Example 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "47080316",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 7556 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "r1 = momapy.io.read(\"phospho1.sbgn\")\n",
    "m1 = r1.obj\n",
    "display(m1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "7458590e",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 6116 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "r3 = momapy.io.read(\"phospho3.sbgn\")\n",
    "m3 = r3.obj\n",
    "display(m3)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d2a19e4f",
   "metadata": {},
   "source": [
    "The second map is an excerpt of the first map:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "887fbede",
   "metadata": {},
   "outputs": [],
   "source": [
    "assert m3.is_submap(m1)\n",
    "assert m3.model.is_submodel(m1.model)\n",
    "assert m3.layout.is_sublayout(m1.layout)\n",
    "assert m3.layout_model_mapping.is_submapping(m1.layout_model_mapping)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "90579fed",
   "metadata": {},
   "source": [
    "#### Example 2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "620ee53a",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 6672 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "r4 = momapy.io.read(\"phospho4.sbgn\")\n",
    "m4 = r4.obj\n",
    "display(m4)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2a3150b2",
   "metadata": {},
   "source": [
    "Because of the compartment, the model of the first map is not an excerpt of the model of the second map.\n",
    "However, the layout of the first map is an excerpt of the layout of the second map:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "e1e1ce9d",
   "metadata": {},
   "outputs": [],
   "source": [
    "assert not m3.is_submap(m4)\n",
    "assert not m3.model.is_submodel(m4.model)\n",
    "assert m3.layout.is_sublayout(m4.layout)\n",
    "assert not m3.layout_model_mapping.is_submapping(m4.layout_model_mapping)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5d92c9d6",
   "metadata": {},
   "source": [
    "## Frozen and builder objects"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ec88c643",
   "metadata": {},
   "source": [
    "`Map`, `Model`, `Layout`, `ModelElement` and `LayoutElement` objects cannot be modified; they are frozen:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "523ea2f7",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 7556 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "r = momapy.io.read(\"phospho1.sbgn\")\n",
    "m = r.obj\n",
    "display(m)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "bb25543f",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAlgAAAHCCAYAAAAzc7dkAAAABHNCSVQICAgIfAhkiAAACrBJREFUeJzt3F9oXYUBx/HfvcluTNISq+uUsgmWVgttB7PaPmwi/l3t3MNmHWUPqaxSpJQKezBYIxVrRxWRvQxkTFBoERT2oqwSwYiuaK1Va63tlAg67WCdbmlokzRNzh60Qzdc2vTYk8TPB/KQm9xzfwcO5EvO5SYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwP9Vq3rA12xBkoVJLvr8+w+T7E9ysLJFAABT0Mx6vf6blpaWfyYpkhTz5s07MW/evJGT359zzjmf1Ov1+5K0V7oUAGAKWF6v1//R3t5+4p577il6enqK/v7+4qT+/v6ip6enuPvuu4u2trbRpqamvye5turRAACT1a9rtdrYbbfddvyTTz4pxnP48OFizZo1x2u12miSDVWPBwCYbH6aZOyll14aN6z+24svvljUarWxJCuqPgkAgMlidr1eH7jrrruOn3Zdfa6rq+t4vV7/V5Lzqj4ZAIDJ4Hfz588/OjIyMtG+KoaHh4uLL774WJLfVn0yAABVa9RqteOPPvrohOPqpEceeaSo1+vHkrRUfVIAAFX6SZJi//79ZxxYb775ZpHPPsbh+orPCQCgUt1tbW0Tvzf4BaOjo0Wj0TiRpKvqkwIAprbmqgd8wYIkP0zyvdN4zoqlS5eW8mn09Xo9S5YsKV5++eWfJWkt45hT3F+T/DnJX6oeAgBTzWQIrGs7Ojp+39/fP3fx4sWj5513XtOpPvG9995LU9Mp//q4Wltbm+fMmbNs/vz5y0o76BT16aefju7bt6+po6Ojr7+//7YkL1S9CQA4BY1G4xdJig0bNhSDg4OnfVvvvvvuKy688MIy7hAWRVEUHR0dxf3331/a8aa6wcHBYt26dUWSotFo3Fz19QIAjG92c3PzwPr16yccAE8//XSRpDiVT24fz8cff1wkKXbs2HHGx5pubr/99rHm5uaBJN+u+JoBAMbxy7a2tpGBgYEJ/+EfHh4uGo1G8eyzz55xRDzzzDNFW1tbMTw8fMbHmm4GBgaK1tbWE0lWVX3RAMBUUK/wtX+wbNmysRkzZkz4AI1GI2vXrs1DDz2UoigmfJyiKLJ169asXbs2jUZjwseZrmbMmJHLL798NMn3q94CAFNBlYE1+6KLLjrjmrn33nuze/fuPPzwwxM+xoMPPpj9+/dn06ZNZzpn2po7d24jyZyqdwDAVFBlYJXi/PPPz7Zt27Jx48bs2rXrtJ+/c+fOdHd3Z/v27Tn33HO/hoUAwDfNlA+sJLnpppuyZcuWXHnlldm8eXPGxsbGfc6JEyeyefPmXH311XnggQdy4403noWlAABfr8dWr15d6puxd+zYUcyePbtYsmRJceDAga/8vXfeeae47LLLigsuuKB47rnnSt0wXa1evbpI8ljVFw0ATAWT4YNGS7N8+fL09fVl69atWbp0aWbNmpVLLrkkixYtysjISN59990cPHgwR44cyYYNG9LV1ZX29vaqZwMA08y0CqwkmTlzZrZs2ZLu7u489dRT2b17d/r6+lKr1XLppZems7Mzt9xyS1paWqqeCgBMU9MusE5qbW1NZ2dnOjs7q54CAHzDTIs3uQMATCYCCwCgZAILAKBkAgsAoGQCCwCgZAILAKBkAgsAoGQCCwCgZAILAKBkAgsAoGQCCwCgZAKrZHv27EmtVsvGjRurngIAVERglay3tzeLFy/Otm3bcuzYsarnAAAVEFglGh0dzZNPPplNmzYlSV599dWKFwEAVRBYJXrrrbeyd+/eXHPNNVm5cmWef/75qicBABUQWCXq7e3NrbfemlmzZuWqq67K9u3bMzQ0VPUsAOAsE1glOXl78IYbbkiSXHHFFXn//ffz2muvVbwMADjbBFZJ9u3bl127dmXRokUZGBjIzJkzs3z58vT29lY9DQA4y5qrHjBdnAypBQsWfOnxDz74IHfeeWdaWlqqmAUAVMB/sEpw8vbg448/nqIo/vPV19eXAwcO5I033qh6IgBwFgmsErz99tt55ZVXct11133p8blz52blypVuEwLAN4zAKsELL7yQNWvWZM6cOf/zs5tvvjlPPPFERkZGKlgGAFTBe7BKcMcdd3zlz1atWpVVq1adxTUAQNX8BwsAoGQCCwCgZAILAKBkAgsAoGQCCwCgZAILAKBkAgsAoGQCCwCgZAILAKBkAgsAoGQCCwCgZAILAKBkAgsAoGQCCwCgZAILAKBkAgsAoGQCCwCgZAILAKBkAgsAoGQCCwCgZFUG1t8OHTp0vMLX5zR89NFHx5McqnoHAEwFVQbWG6+//npR4etzGvbs2ZMke6reAQBTQZWB9fTRo0ePdHd3VziBU9HV1VUMDQ0dSfKnqrcAAOP7cZJi/fr1xeDgYMHkMjg4WKxbt65IUiS5vuJrBQCmjFrVA5KsbGpq+kNLS0vrihUrRhYuXNhe9SCSvXv3Hu3p6fnW0NDQ4NjY2K+S/LHqTQAwVUyGwEqS7yT5eZIfJfluxVv4zIdJduazsDpc8RYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADK9G+qDB2zlAuOHgAAAABJRU5ErkJggg==",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "for l in m.layout.layout_elements:\n",
    "    break\n",
    "display(l)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "19525a56",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "cannot assign to field 'stroke_width'\n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    l.stroke_width = 3.0\n",
    "except Exception as e:\n",
    "    print(e)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "52c644c3",
   "metadata": {},
   "source": [
    "This way they can be hashed, which is necessary to check whether a `Map` object belongs to a `set` efficiently, for example.\n",
    "However, we want to be able to modify them programmatically (e.g., change the stroke width of the border of a shape).\n",
    "To this end, each class has a corresponding builder class, that allows the production of objects that are not frozen.\n",
    "Such objects may be built directly from the frozen objects:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "cd8ef1a9",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[32m<class 'momapy.builder.MacromoleculeLayoutBuilder'>\u001b[31m: MacromoleculeLayoutBuilder(id_...\u001b[0m\n",
      "  \u001b[34m* id_\u001b[35m: <class 'str'> = \u001b[31mglyph2\u001b[0m\n",
      "  \u001b[34m* layout_elements\u001b[35m: typing.Union[tuple[momapy.core.LayoutElement], momapy.core.TupleBuilder[momapy.builder.LayoutElementBuilder]] = \u001b[31m[StateVariableLayoutBuilder(id...\u001b[0m\n",
      "  \u001b[34m* group_fill\u001b[35m: typing.Union[momapy.drawing.NoneValueType, momapy.coloring.Color, NoneType, momapy.builder.ColorBuilder] = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* group_fill_rule\u001b[35m: typing.Optional[momapy.drawing.FillRule] = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* group_filter\u001b[35m: typing.Union[momapy.drawing.NoneValueType, momapy.drawing.Filter, NoneType, momapy.builder.FilterBuilder] = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* group_font_family\u001b[35m: typing.Optional[str] = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* group_font_size\u001b[35m: typing.Optional[float] = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* group_font_style\u001b[35m: typing.Optional[momapy.drawing.FontStyle] = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* group_font_weight\u001b[35m: typing.Union[momapy.drawing.FontWeight, float, NoneType] = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* group_stroke\u001b[35m: typing.Union[momapy.drawing.NoneValueType, momapy.coloring.Color, NoneType, momapy.builder.ColorBuilder] = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* group_stroke_dasharray\u001b[35m: typing.Union[tuple[float], NoneType, momapy.core.TupleBuilder[float]] = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* group_stroke_dashoffset\u001b[35m: typing.Optional[float] = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* group_stroke_width\u001b[35m: typing.Optional[float] = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* group_text_anchor\u001b[35m: typing.Optional[momapy.drawing.TextAnchor] = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* group_transform\u001b[35m: typing.Union[momapy.drawing.NoneValueType, tuple[momapy.geometry.Transformation], NoneType, momapy.core.TupleBuilder[momapy.builder.TransformationBuilder]] = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* fill\u001b[35m: typing.Union[momapy.drawing.NoneValueType, momapy.coloring.Color, NoneType, momapy.builder.ColorBuilder] = \u001b[31mColorBuilder(red=255, green=25...\u001b[0m\n",
      "  \u001b[34m* filter\u001b[35m: typing.Union[momapy.drawing.NoneValueType, momapy.drawing.Filter, NoneType, momapy.builder.FilterBuilder] = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* height\u001b[35m: <class 'float'> = \u001b[31m60.0\u001b[0m\n",
      "  \u001b[34m* label\u001b[35m: typing.Union[momapy.core.TextLayout, NoneType, momapy.builder.TextLayoutBuilder] = \u001b[31mTextLayoutBuilder(id_='d171f45...\u001b[0m\n",
      "  \u001b[34m* position\u001b[35m: typing.Union[momapy.geometry.Point, momapy.builder.PointBuilder, NoneType] = \u001b[31mPointBuilder(x=85.0, y=270.0)\u001b[0m\n",
      "  \u001b[34m* stroke\u001b[35m: typing.Union[momapy.drawing.NoneValueType, momapy.coloring.Color, NoneType, momapy.builder.ColorBuilder] = \u001b[31mColorBuilder(red=0, green=0, b...\u001b[0m\n",
      "  \u001b[34m* stroke_dasharray\u001b[35m: typing.Union[momapy.drawing.NoneValueType, tuple[float], NoneType, momapy.core.TupleBuilder[float]] = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* stroke_dashoffset\u001b[35m: typing.Optional[float] = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* stroke_width\u001b[35m: typing.Optional[float] = \u001b[31m1.25\u001b[0m\n",
      "  \u001b[34m* transform\u001b[35m: typing.Union[momapy.drawing.NoneValueType, tuple[momapy.geometry.Transformation], NoneType, momapy.core.TupleBuilder[momapy.builder.TransformationBuilder]] = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* width\u001b[35m: <class 'float'> = \u001b[31m108.0\u001b[0m\n",
      "  \u001b[34m* rounded_corners\u001b[35m: <class 'float'> = \u001b[31m5.0\u001b[0m\n"
     ]
    }
   ],
   "source": [
    "lb = momapy.builder.builder_from_object(l)\n",
    "momapy.utils.pretty_print(lb)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "4e795b8b",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "lb.stroke_width = 3.0\n",
    "display(lb)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "67b8f379",
   "metadata": {},
   "source": [
    "The frozen object may then be built back from the builder:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "a2240a6b",
   "metadata": {},
   "outputs": [],
   "source": [
    "l = momapy.builder.object_from_builder(lb)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "cca9b9f1",
   "metadata": {},
   "outputs": [],
   "source": [
    "assert l.stroke_width == 3.0"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "94a41116",
   "metadata": {},
   "source": [
    "## Reading and writing"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a6656249-dfa1-45a3-8f83-efeb1fe12755",
   "metadata": {},
   "source": [
    "`SBGNMap`s may be read from and written to SBGN-ML files using `read` and `write` functions:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "53d93d22-3746-4d41-8a5a-17f35300f33e",
   "metadata": {},
   "outputs": [],
   "source": [
    "r = momapy.io.read(\"phospho1.sbgn\")\n",
    "m = r.obj\n",
    "momapy.io.write(m, \"phospho1_output.sbgn\", writer=\"sbgnml\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c2ebfdd7-3eb8-430c-a6f3-1af388029eee",
   "metadata": {},
   "source": [
    "## Rendering"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "41f2ae37",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 7556 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "r = momapy.io.read(\"phospho1.sbgn\")\n",
    "m = r.obj\n",
    "display(m)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "67f5cac4",
   "metadata": {},
   "source": [
    "`Map`s can be rendered in different formats using a simple render function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "id": "7da44392",
   "metadata": {},
   "outputs": [],
   "source": [
    "momapy.rendering.core.render_map(m, \"phospho1.pdf\", format_=\"pdf\")\n",
    "momapy.rendering.core.render_map(m, \"phospho1.png\", format_=\"png\")\n",
    "momapy.rendering.core.render_map(m, \"phospho1.svg\", format_=\"svg\")\n",
    "momapy.rendering.core.render_map(m, \"phospho1.webp\", format_=\"webp\")\n",
    "momapy.rendering.core.render_map(m, \"phospho1.jpeg\", format_=\"jpeg\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "60838863",
   "metadata": {},
   "source": [
    "`Layout`s can be moved to the top left using the `top_left` option:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "212bb0b4",
   "metadata": {},
   "outputs": [],
   "source": [
    "momapy.rendering.core.render_map(m, \"phospho1.pdf\", format_=\"pdf\", to_top_left=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "32ac65b3",
   "metadata": {},
   "outputs": [],
   "source": [
    "r1 = momapy.io.read(\"phospho1.sbgn\")\n",
    "m1 = r1.obj\n",
    "r2 = momapy.io.read(\"phospho2.sbgn\")\n",
    "m2 = r2.obj\n",
    "r3 = momapy.io.read(\"phospho3.sbgn\")\n",
    "m3 = r3.obj\n",
    "r4 = momapy.io.read(\"phospho4.sbgn\")\n",
    "m4 = r4.obj"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b6b05c50",
   "metadata": {},
   "source": [
    "Multiple `Map`s can be rendered in one document using a simple function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "id": "ce14d73c",
   "metadata": {},
   "outputs": [],
   "source": [
    "momapy.rendering.core.render_maps([m1, m2, m3, m4], \"phospho_multi.pdf\", format_=\"pdf\", multi_pages=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1b1deff3",
   "metadata": {},
   "source": [
    "## Styling"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2ed1ea2b",
   "metadata": {},
   "source": [
    "### Basic styling"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2a059032",
   "metadata": {},
   "source": [
    "Basic styling can be easily applied to `LayoutElement`s:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "id": "f5768ef0",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 7556 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "r = momapy.io.read(\"phospho1.sbgn\")\n",
    "m = r.obj\n",
    "mb = momapy.builder.builder_from_object(m)\n",
    "display(mb)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "id": "99699990",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "for lb in mb.layout.layout_elements:\n",
    "    break\n",
    "display(lb)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "id": "46a33894",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[32m<class 'list'>\u001b[31m: [Group(class_='MacromoleculeLa...\u001b[0m\n",
      "  \u001b[34m- 0\u001b[35m: <class 'momapy.drawing.Group'> = \u001b[31mGroup(class_='MacromoleculeLay...\u001b[0m\n",
      "    \u001b[32m<class 'momapy.drawing.Group'>\u001b[31m: Group(class_='MacromoleculeLay...\u001b[0m\n",
      "      \u001b[34m* class_\u001b[35m: str | None = \u001b[31mMacromoleculeLayoutBuilder_gro...\u001b[0m\n",
      "      \u001b[34m* fill\u001b[35m: momapy.drawing.NoneValueType | momapy.coloring.Color | None = \u001b[31mNone\u001b[0m\n",
      "      \u001b[34m* fill_rule\u001b[35m: momapy.drawing.FillRule | None = \u001b[31mNone\u001b[0m\n",
      "      \u001b[34m* filter\u001b[35m: momapy.drawing.NoneValueType | momapy.drawing.Filter | None = \u001b[31mNone\u001b[0m\n",
      "      \u001b[34m* font_family\u001b[35m: str | None = \u001b[31mNone\u001b[0m\n",
      "      \u001b[34m* font_size\u001b[35m: float | None = \u001b[31mNone\u001b[0m\n",
      "      \u001b[34m* font_style\u001b[35m: momapy.drawing.FontStyle | None = \u001b[31mNone\u001b[0m\n",
      "      \u001b[34m* font_weight\u001b[35m: momapy.drawing.FontWeight | float | None = \u001b[31mNone\u001b[0m\n",
      "      \u001b[34m* id_\u001b[35m: str | None = \u001b[31mglyph2_group\u001b[0m\n",
      "      \u001b[34m* stroke\u001b[35m: momapy.drawing.NoneValueType | momapy.coloring.Color | None = \u001b[31mNone\u001b[0m\n",
      "      \u001b[34m* stroke_dasharray\u001b[35m: tuple[float] | None = \u001b[31mNone\u001b[0m\n",
      "      \u001b[34m* stroke_dashoffset\u001b[35m: float | None = \u001b[31mNone\u001b[0m\n",
      "      \u001b[34m* stroke_width\u001b[35m: float | None = \u001b[31mNone\u001b[0m\n",
      "      \u001b[34m* text_anchor\u001b[35m: momapy.drawing.TextAnchor | None = \u001b[31mNone\u001b[0m\n",
      "      \u001b[34m* transform\u001b[35m: momapy.drawing.NoneValueType | momapy.geometry.Transformation | None = \u001b[31mNone\u001b[0m\n",
      "      \u001b[34m* elements\u001b[35m: tuple[momapy.drawing.DrawingElement] = \u001b[31m[Group(class_='MacromoleculeLa...\u001b[0m\n",
      "        \u001b[32m<class 'list'>\u001b[31m: [Group(class_='MacromoleculeLa...\u001b[0m\n",
      "          \u001b[34m- 0\u001b[35m: <class 'momapy.drawing.Group'> = \u001b[31mGroup(class_='MacromoleculeLay...\u001b[0m\n",
      "            \u001b[32m<class 'momapy.drawing.Group'>\u001b[31m: Group(class_='MacromoleculeLay...\u001b[0m\n",
      "              \u001b[34m* class_\u001b[35m: str | None = \u001b[31mMacromoleculeLayoutBuilder\u001b[0m\n",
      "              \u001b[34m* fill\u001b[35m: momapy.drawing.NoneValueType | momapy.coloring.Color | None = \u001b[31mColor(red=173, green=216, blue...\u001b[0m\n",
      "              \u001b[34m* fill_rule\u001b[35m: momapy.drawing.FillRule | None = \u001b[31mNone\u001b[0m\n",
      "              \u001b[34m* filter\u001b[35m: momapy.drawing.NoneValueType | momapy.drawing.Filter | None = \u001b[31mNone\u001b[0m\n",
      "              \u001b[34m* font_family\u001b[35m: str | None = \u001b[31mNone\u001b[0m\n",
      "              \u001b[34m* font_size\u001b[35m: float | None = \u001b[31mNone\u001b[0m\n",
      "              \u001b[34m* font_style\u001b[35m: momapy.drawing.FontStyle | None = \u001b[31mNone\u001b[0m\n",
      "              \u001b[34m* font_weight\u001b[35m: momapy.drawing.FontWeight | float | None = \u001b[31mNone\u001b[0m\n",
      "              \u001b[34m* id_\u001b[35m: str | None = \u001b[31mglyph2\u001b[0m\n",
      "              \u001b[34m* stroke\u001b[35m: momapy.drawing.NoneValueType | momapy.coloring.Color | None = \u001b[31mColor(red=165, green=42, blue=...\u001b[0m\n",
      "              \u001b[34m* stroke_dasharray\u001b[35m: tuple[float] | None = \u001b[31m(5, 5)\u001b[0m\n",
      "              \u001b[34m* stroke_dashoffset\u001b[35m: float | None = \u001b[31mNone\u001b[0m\n",
      "              \u001b[34m* stroke_width\u001b[35m: float | None = \u001b[31m3.0\u001b[0m\n",
      "              \u001b[34m* text_anchor\u001b[35m: momapy.drawing.TextAnchor | None = \u001b[31mNone\u001b[0m\n",
      "              \u001b[34m* transform\u001b[35m: momapy.drawing.NoneValueType | momapy.geometry.Transformation | None = \u001b[31mNone\u001b[0m\n",
      "              \u001b[34m* elements\u001b[35m: tuple[momapy.drawing.DrawingElement] = \u001b[31m[Path(class_=None, fill=None, ...\u001b[0m\n",
      "          \u001b[34m- 1\u001b[35m: <class 'momapy.drawing.Text'> = \u001b[31mText(class_=None, fill=ColorBu...\u001b[0m\n",
      "            \u001b[32m<class 'momapy.drawing.Text'>\u001b[31m: Text(class_=None, fill=ColorBu...\u001b[0m\n",
      "              \u001b[34m* class_\u001b[35m: str | None = \u001b[31mNone\u001b[0m\n",
      "              \u001b[34m* fill\u001b[35m: momapy.drawing.NoneValueType | momapy.coloring.Color | None = \u001b[31mColorBuilder(red=0, green=0, b...\u001b[0m\n",
      "              \u001b[34m* fill_rule\u001b[35m: momapy.drawing.FillRule | None = \u001b[31mNone\u001b[0m\n",
      "              \u001b[34m* filter\u001b[35m: momapy.drawing.NoneValueType | momapy.drawing.Filter | None = \u001b[31mNone\u001b[0m\n",
      "              \u001b[34m* font_family\u001b[35m: str | None = \u001b[31mHelvetica\u001b[0m\n",
      "              \u001b[34m* font_size\u001b[35m: float | None = \u001b[31m14.0\u001b[0m\n",
      "              \u001b[34m* font_style\u001b[35m: momapy.drawing.FontStyle | None = \u001b[31mFontStyle.NORMAL\u001b[0m\n",
      "              \u001b[34m* font_weight\u001b[35m: momapy.drawing.FontWeight | float | None = \u001b[31mFontWeight.NORMAL\u001b[0m\n",
      "              \u001b[34m* id_\u001b[35m: str | None = \u001b[31mNone\u001b[0m\n",
      "              \u001b[34m* stroke\u001b[35m: momapy.drawing.NoneValueType | momapy.coloring.Color | None = \u001b[31m<momapy.drawing.NoneValueType ...\u001b[0m\n",
      "              \u001b[34m* stroke_dasharray\u001b[35m: tuple[float] | None = \u001b[31mNone\u001b[0m\n",
      "              \u001b[34m* stroke_dashoffset\u001b[35m: float | None = \u001b[31mNone\u001b[0m\n",
      "              \u001b[34m* stroke_width\u001b[35m: float | None = \u001b[31mNone\u001b[0m\n",
      "              \u001b[34m* text_anchor\u001b[35m: momapy.drawing.TextAnchor | None = \u001b[31mNone\u001b[0m\n",
      "              \u001b[34m* transform\u001b[35m: momapy.drawing.NoneValueType | momapy.geometry.Transformation | None = \u001b[31mNone\u001b[0m\n",
      "              \u001b[34m* text\u001b[35m: <class 'str'> = \u001b[31mA\u001b[0m\n",
      "              \u001b[34m* point\u001b[35m: <class 'momapy.geometry.Point'> = \u001b[31mPoint(x=80.5, y=273.5)\u001b[0m\n",
      "          \u001b[34m- 2\u001b[35m: <class 'momapy.drawing.Group'> = \u001b[31mGroup(class_='StateVariableLay...\u001b[0m\n",
      "            \u001b[32m<class 'momapy.drawing.Group'>\u001b[31m: Group(class_='StateVariableLay...\u001b[0m\n",
      "              \u001b[34m* class_\u001b[35m: str | None = \u001b[31mStateVariableLayoutBuilder_gro...\u001b[0m\n",
      "              \u001b[34m* fill\u001b[35m: momapy.drawing.NoneValueType | momapy.coloring.Color | None = \u001b[31mNone\u001b[0m\n",
      "              \u001b[34m* fill_rule\u001b[35m: momapy.drawing.FillRule | None = \u001b[31mNone\u001b[0m\n",
      "              \u001b[34m* filter\u001b[35m: momapy.drawing.NoneValueType | momapy.drawing.Filter | None = \u001b[31mNone\u001b[0m\n",
      "              \u001b[34m* font_family\u001b[35m: str | None = \u001b[31mNone\u001b[0m\n",
      "              \u001b[34m* font_size\u001b[35m: float | None = \u001b[31mNone\u001b[0m\n",
      "              \u001b[34m* font_style\u001b[35m: momapy.drawing.FontStyle | None = \u001b[31mNone\u001b[0m\n",
      "              \u001b[34m* font_weight\u001b[35m: momapy.drawing.FontWeight | float | None = \u001b[31mNone\u001b[0m\n",
      "              \u001b[34m* id_\u001b[35m: str | None = \u001b[31mglyph2a_group\u001b[0m\n",
      "              \u001b[34m* stroke\u001b[35m: momapy.drawing.NoneValueType | momapy.coloring.Color | None = \u001b[31mNone\u001b[0m\n",
      "              \u001b[34m* stroke_dasharray\u001b[35m: tuple[float] | None = \u001b[31mNone\u001b[0m\n",
      "              \u001b[34m* stroke_dashoffset\u001b[35m: float | None = \u001b[31mNone\u001b[0m\n",
      "              \u001b[34m* stroke_width\u001b[35m: float | None = \u001b[31mNone\u001b[0m\n",
      "              \u001b[34m* text_anchor\u001b[35m: momapy.drawing.TextAnchor | None = \u001b[31mNone\u001b[0m\n",
      "              \u001b[34m* transform\u001b[35m: momapy.drawing.NoneValueType | momapy.geometry.Transformation | None = \u001b[31mNone\u001b[0m\n",
      "              \u001b[34m* elements\u001b[35m: tuple[momapy.drawing.DrawingElement] = \u001b[31m[Group(class_='StateVariableLa...\u001b[0m\n"
     ]
    }
   ],
   "source": [
    "lb.fill = momapy.coloring.lightblue\n",
    "lb.stroke = momapy.coloring.brown\n",
    "lb.stroke_width = 3.0\n",
    "lb.stroke_dasharray = (5, 5)\n",
    "momapy.utils.pretty_print(lb.drawing_elements(), max_depth=3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "id": "47c5a6c5-d837-4fed-8097-a2bab38e9434",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 4576 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "display(lb)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3bea215c",
   "metadata": {},
   "source": [
    "The `label` of a `Node` can also be styled:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "id": "015c124d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 5336 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "lb.label.font_family = \"Times\"\n",
    "lb.label.font_size = 30.0\n",
    "lb.label.fill = momapy.coloring.red\n",
    "lb.label.stroke = momapy.coloring.black\n",
    "lb.label.stroke_width = 2.0\n",
    "display(lb)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4bf56189",
   "metadata": {},
   "source": [
    "### Advanced styling"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7179ce40",
   "metadata": {},
   "source": [
    "Advanced effects such as transformations (translation, rotation, ...) and filter effects can be applied to `LayoutElement`s:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "id": "bf23f55b",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 18780 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "lb.group_transform = (momapy.geometry.Scaling(2, 1), momapy.geometry.Rotation(0.5, lb.position),)\n",
    "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),))\n",
    "display(lb)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "84aea5ab",
   "metadata": {},
   "source": [
    "### CSS-like style sheets"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9e6d530a",
   "metadata": {},
   "source": [
    "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."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "id": "6dda91e5",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 7556 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "r = momapy.io.read(\"phospho1.sbgn\")\n",
    "m = r.obj\n",
    "display(m)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0e1ddea8",
   "metadata": {},
   "source": [
    "There are pre-built `StyleSheet` objects for SBGN-ED and Newt-like styles, for colors, and for shadows:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "id": "fd3ac6fa",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 6772 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "m = momapy.styling.apply_style_sheet(m, momapy.sbgn.styling.newt)\n",
    "display(m)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e69aeab2",
   "metadata": {},
   "source": [
    "Applying a `StyleSheet` to a `Map` may change the size of the nodes.\n",
    "Some simple functions can be used to tidy the `Map`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "id": "7a46da0a",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 6568 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "m = momapy.sbgn.utils.newt_tidy(m)\n",
    "display(m)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "id": "ecc991ca",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 7672 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "m = momapy.styling.apply_style_sheet(m, momapy.sbgn.styling.sbgned)\n",
    "m = momapy.sbgn.utils.sbgned_tidy(m)\n",
    "display(m)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "id": "4f4c17ae",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 7772 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "m = momapy.styling.apply_style_sheet(m, momapy.sbgn.styling.cs_default)\n",
    "display(m)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "id": "84a98dbf",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 11140 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "m = momapy.styling.apply_style_sheet(m, momapy.sbgn.styling.fs_shadows)\n",
    "display(m)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8d98eeba",
   "metadata": {},
   "source": [
    "These pre-built `StyleSheet`s are built from CSS-like text files:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "id": "f630e11e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "AndOperatorLayout {\n",
      "\tfilter: unset;\n",
      "\tstroke-dasharray: unset;\n",
      "\tstroke-dashoffset: unset;\n",
      "\tstroke-width: 2.0;\n",
      "\ttransform: unset;\n",
      "\tfilter: unset;\n",
      "\tgroup-font-family: unset;\n",
      "\tgroup-font-size: unset;\n",
      "\tgroup-font-style: unset;\n",
      "\tgroup-font-weight: unset;\n",
      "\theight: 42.0;\n",
      "\tleft-connector-filter: unset;\n",
      "\tleft-connector-length: 10.0;\n",
      "\tleft-connector-stroke-dasharray: unset;\n",
      "\tleft-connector-stroke-dashoffset: unset;\n",
      "\tleft-connector-stroke-width: unset;\n",
      "\tright-connector-filter: unset;\n",
      "\tright-connector-length: 10.0;\n",
      "\tright-connector-stroke-dasharray: unset;\n",
      "\tright-connector-stroke-dashoffset: unset;\n",
      "\tright-connector-stroke-width: unset;\n",
      "\tstroke-dasharray: unset;\n",
      "\tstroke-dashoffset: unset;\n",
      "\tstroke-width: unset;\n",
      "\ttransform: unset;\n",
      "\twidth: 42.0;\n",
      "}\n",
      "\n",
      "\n",
      "AssociationLayout {\n",
      "\tfilter: unset;\n",
      "\tstroke-dasharray: unset;\n",
      "\tstroke-dashoffset: unset;\n",
      "\tstroke-width: 2.0;\n"
     ]
    }
   ],
   "source": [
    "with open(\"../src/momapy/sbgn/styling/sbgned_no_cs.css\") as f:\n",
    "    for line in f.readlines()[:35]:\n",
    "        print(line[:-1])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "025148cf",
   "metadata": {},
   "source": [
    "One may build a `StyleSheet` from a file and apply it to a `Map` with simple functions:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "id": "2050c913",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 11964 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "with open(\"my_style_sheet.css\", \"w\") as f:\n",
    "    f.write(\"\"\"\n",
    "    SBGNPDLayout {\n",
    "        stroke: red;\n",
    "        fill: lightyellow;\n",
    "    }\n",
    "    \n",
    "    MacromoleculeLayout {\n",
    "        fill: green;\n",
    "    }\n",
    "    \"\"\")\n",
    "my_style_sheet = momapy.styling.StyleSheet.from_file(\"my_style_sheet.css\")\n",
    "m = momapy.styling.apply_style_sheet(m, my_style_sheet)\n",
    "display(m)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d1e153aa",
   "metadata": {},
   "source": [
    "## Automatic geometry"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7b25f894",
   "metadata": {},
   "source": [
    "`LayoutElement`s support automatic geometry: their \"shape\" can be automatically computed from the `DrawingElement`s they return, and be accessed with various methods.\n",
    "These methods depend on the nature of the `LayoutElement` (`Node` or `Arc`)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "756981df",
   "metadata": {},
   "source": [
    "### For Nodes"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "id": "d61f3cdd",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 7760 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "lb = macromolecule_toy()\n",
    "display(lb)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fa90149e",
   "metadata": {},
   "source": [
    "#### Anchors"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c0cfbc7d-7ebe-4210-85cb-2b1cad0367d4",
   "metadata": {},
   "source": [
    "`Node`s have anchor points, that are specific `Point`s on their border:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "id": "45d995e9",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Point(x=80.01, y=77.51)"
      ]
     },
     "execution_count": 49,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "lb.north_west()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "id": "637214c9",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 7856 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "display_at(lb, lb.north_west())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1f69ae53",
   "metadata": {},
   "source": [
    "All `Node`s have at least the following anchor points:\n",
    "* `north_west`\n",
    "* `north`\n",
    "* `north_east`\n",
    "* `east`\n",
    "* `south_east`\n",
    "* `south`\n",
    "* `south_west`\n",
    "* `west`\n",
    "* `center`\n",
    "* `label_center`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "id": "6ddacee6",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 14848 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "show_room(momapy.sbgn.pd.MacromoleculeLayout)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "id": "b26c1842",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 8392 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "show_room(momapy.sbgn.pd.GenericProcessLayout)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "id": "ed8874a8",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 16104 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "show_room(momapy.sbgn.pd.NucleicAcidFeatureMultimerLayout)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8c7904a0",
   "metadata": {},
   "source": [
    "#### Angles"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dcd42202-5fef-4597-ac2f-88ee90cfbb83",
   "metadata": {},
   "source": [
    "`Node`s also have angle points:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "id": "2edadf5a",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Point(x=161.97, y=75.0)"
      ]
     },
     "execution_count": 54,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "lb.self_angle(130)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "id": "e065b82d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 7844 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "display_at(lb, lb.self_angle(130))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "id": "08c4f801",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 17392 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "show_room(momapy.sbgn.pd.MacromoleculeLayout, \"self_angle\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "id": "36ae9841",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 7920 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "display_at(lb, lb.angle(130))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "id": "cc7af700",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 18432 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "show_room(momapy.sbgn.pd.MacromoleculeLayout, \"angle\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ea66b483",
   "metadata": {},
   "source": [
    "### For Arcs"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "id": "7bf7ebaf",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAlgAAAHCCAYAAAAzc7dkAAAABHNCSVQICAgIfAhkiAAACcFJREFUeJzt3EFrnAkdx/HfzGZ2ynZTUgo59O6CWvbi1RfQU7304MlXENBjwdWuiuRSmiKkoKggohf1pAuuYBFEPPgGopJupC9Bq03SdjxMnm42bbqb5D/PM3nm84HnkMM883/m9OX553kSAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABg4Q26HgBgTryb5GaSd5J8teNZAADOrXeTfDfJwySTg+ObnU4EAHAOXUvynSTbSZ4neZyP42qS5HPdjQYAcH40UdXcqfpvPhlVzbHV1YAAAOdBE1UfZRpP/8uro6o59pK818mkAABz7GhUPcnro+roYT0IAJCXo2o3J4uq5vh724MDAMyTa5k+/beTj9d7p4mqw+vBb7V5AQAA8+BoVO3nbFFlPQgALKQmqv6VaQQ9TW1UWQ8CAAvhWpLvZfZRZT0IACyEH2QaPc8y26g6erzTxsUBAHTl90n+nfbiystFgXLDrgcAOOJGkr8m+U8L37Wb5JctfA8AQOfeTPKHtHMny9ODAMDCeDPJnzLbyPpHWxcDADAvLmR2kbWX5NutXQkAwByZZWRZDwIAC2sWkWU9CAAsvMrI2ktyu9XpAQDmVGVkWQ8CABy4kOQvmb4ny3oQmEteNAqcN0+S/CzJKMnjU3x+P8kvSicCADjnvpbkeZLvZ3on6zTrQutBAIADTVy9f/D3Wzl5ZP2z1YkBAObY0bhqnCSyPD0IAHDguLhqnCSyvjDjWQEA5t6nxVWjiazHOT6uPprZlAAA58RnjavGW0n+lldH1t4JzgMA0EsnjavG2zk+sr5YOB8AwLly2rhqNJF1+GWk1oMAwMI6a1w1DkfWbjw9CAAsqKq4aixnGlmTJJ8vOicAwLlRHVeNt5P8vPicAABzb1ZxBQCwkMQVAEChtYgrAIAya5n+8/n7Hc8BANAL4goAoJC4AgAoJK4AAAqJKwCAQuIKAKCQuAIAKCSuAAAKiSsAgELiCgCgkLgCACgkrgAACokrAIBC4goAoJC4AgAoJK4AAAqJKwCAQuIKAKCQuAIAKCSuAAAKiSsAgELiCgCgkLgCACgkrgAACokrAIBC4goA6IWbSX6V5I2O5xBXAEAv3EwyuXjx4t5gMPh1h3OIKwCgF24mmWxubj7b2tqaXLp0aTfJ75IstTyHuAIAeuHmYDB4trm5+WxyYGtra7KysvJkMBh8kPYiS1wBAL3wUlw1tre3J5cvX95tKbKauHpvxt8DADBTN46Lq5Yjq4mrtRmdHwCgFTcGg8HTu3fvHhtXLUWWuAIAeuEzx9UxkTUqmkNcAQC9cOK4OhpZS0tLf8zZI0tcAQC9cOq4OhxZq6urZ40scQUA9MKZ46qxs7NzlsgSVwBAL5TF1RkjS1wBAL1QHlenjCxxBQD0wszi6mhkjUajB0nGx8whrgCAXph5XB2JrL3RaPTnvBxZ4goA6IXW4upTIktcAQC90HpcHY6sq1ev7h5E1jcirgCAHvhKksmdO3fabqsXHj16NFleXn4ecQUA9MCNwWDwdGNj43lndTWZTNbX1yeDwWCS5Idd/yAAAGcxN3E1HA7FFQBw7okrAIBC4goAoNCNwWDwdH19XVwBABQQVwAAhcQVAECh6/MQVxsbG+IKAOiF68PhcF9cAQDUEFcAAIXEFQBAIXEFAFBIXAEAFBJXAACFxBUAQCFxBQBQSFwBABQSVwAAhcQVAECh68PhcP/27dtdtpW4AgB64/pwONy/deuWuAIAKCCuAAAKiSsAgGIfXLhw4dnDhw87i6t79+5NkkyS/KjrHwMAoMJ4NBo9WF1d3dvZ2Wk9ru7fv9/cufpx1z8EAEClUReRJa4AgL5rNbLEFQCwKFqJLHEFACyaF5G1vb0trgAAioxGo9GDK1eu7FdGlrgCABbdaDwef1gVWeIKAGBqqSKyxBUAwCedKbLEFQDAq50qssQVAMDrvYisra0tcQUAUGRpPB5/uLKy8trIElcAACezNB6Pf3tcZIkrAIDTeeNVkSWuAADO6HBkiSsAgCLj8fg3y8vLz5NMkvyk63kAAPpgbTQaTZL8tOtBAAD6YC3TO1drXQ8CANAH4goAoJC4AgAoJK4AAAqJKwCAQuIKAKCQuAIAKCSuAAAKiSsAgELiCgCgkLgCACgkrgAACokrAIBC4goAoJC4AgAoJK4AAAqJKwCAQuIKAKCQuAIAKCSuAAAKiSsAgELiCgCgkLgCACgkrgAACokrAIBC4goAoJC4AgAoJK4AAAqJKwCAQuIKAKCQuAIAKCSuAAAKiSsAgELiCgCgkLgCACgkrgAACokrAIBC4goAoJC4AgAoJK4AAAqJKwCAQuIKAKCQuAIAKCSuAAAKiSsAgELiCgCgkLgCACgkrgAACokrAIBC4goAoJC4AgAoJK4AAAqJKwCAQuIKAKCQuAIAKCSuAAAKiSsAgELiCgCgkLgCACgkrgAACokrAIBC4goAoJC4AgAoJK4AAAqJKwCAQuIKAKCQuAIAKCSuAAAKiSsAgELiCgCgkLgCACgkrgAACokrAIBC4goAoJC4AgAoJK4AAAqJKwCAQuIKAKDQlzKNq693PQgAQJ98uesBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgR/4PGDVojIoGaGQAAAAASUVORK5CYII=",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "lb = production_toy()\n",
    "display(lb)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "df025525",
   "metadata": {},
   "source": [
    "#### Anchors"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "abdd9149-a92a-4fb8-b635-d61b31164e16",
   "metadata": {},
   "source": [
    "Analogously to `Node`s, `Arc`s have a few anchor points:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "id": "18230b56",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Point(x=303.79, y=71.21)"
      ]
     },
     "execution_count": 60,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "lb.arrowhead_base()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "id": "414552d2",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "display_at(lb, lb.arrowhead_base())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "57e14599-e1a3-42f9-92a9-7a3132d14e65",
   "metadata": {},
   "source": [
    "All `Arc`s have at least the following anchor points:\n",
    "* `end_point`\n",
    "* `start_point`\n",
    "\n",
    "`SingleHeadedArcs` have the following additional anchor points:\n",
    "* `arrowhead_base`\n",
    "* `arrowhead_tip`\n",
    "\n",
    "And `DoubleHeadedArcs` have the following additional ones:\n",
    "* `start_arrowhead_base`\n",
    "* `start_arrowhead_tip`\n",
    "* `end_arrowhead_base`\n",
    "* `end_arrowhead_tip`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 62,
   "id": "ef1fc64a",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 7864 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "show_room(momapy.sbgn.pd.ProductionLayout)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fb7364c1",
   "metadata": {},
   "source": [
    "#### Fraction"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b8ed1171-d538-4c98-9457-12843f81a373",
   "metadata": {},
   "source": [
    "`Arc`s also have fraction points:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 63,
   "id": "bb5df456",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(Point(x=225.0, y=150.0), 5.497787143782138)"
      ]
     },
     "execution_count": 63,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "lb.fraction(0.50)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 64,
   "id": "c1dab76d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "pos, angle = lb.fraction(0.50)\n",
    "display_at(lb, pos)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 65,
   "id": "6fb1100f",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 8288 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "show_room(momapy.sbgn.pd.ProductionLayout, \"fraction\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "94fd5ad5",
   "metadata": {},
   "source": [
    "## Relative positioning"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d0d7e2db",
   "metadata": {},
   "source": [
    "Automatic geometry enables positioning `LayoutElement`s relatively to one another:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 66,
   "id": "2eb6bba5",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 7556 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "r = momapy.io.read(\"phospho1.sbgn\")\n",
    "m = r.obj\n",
    "mb = momapy.builder.builder_from_object(m)\n",
    "display(m)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 67,
   "id": "5447c6d2",
   "metadata": {},
   "outputs": [],
   "source": [
    "for lb in mb.layout.layout_elements:\n",
    "    if hasattr(lb, \"label\") and lb.label is not None and lb.label.text == \"B\": # we select the layout for B\n",
    "        eb = lb\n",
    "    elif momapy.builder.isinstance_or_builder(lb, momapy.sbgn.pd.GenericProcessLayout): # we select the process layout\n",
    "        pb = lb"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 68,
   "id": "1938bbfd",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 10368 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "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\n",
    "eb.label.position = eb.position\n",
    "momapy.sbgn.utils.tidy(mb) # sets the arcs to the borders\n",
    "display(mb)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2ae4f2f2",
   "metadata": {},
   "source": [
    "The following functions are available:\n",
    "* above_left_of\n",
    "* above_of\n",
    "* above_right_of\n",
    "* right_of\n",
    "* below_right_of\n",
    "* below_of\n",
    "* below_left_of\n",
    "* left_of"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8e93aae8",
   "metadata": {},
   "source": [
    "For container `Node`s, the `fit` function is also available:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 69,
   "id": "2cbc4545",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Bbox(position=Point(x=237.5, y=164.0), width=453.0, height=292.0)"
      ]
     },
     "execution_count": 69,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "momapy.positioning.fit(mb.layout.layout_elements, xsep=20, ysep=10)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4cb6c587",
   "metadata": {},
   "source": [
    "Each of the above functions has a corresponding `set` function, which directly sets the returned value(s) to the correct `Node`'s attributes:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 70,
   "id": "9c8dec7e",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 10288 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "mb.layout.stroke = momapy.coloring.red\n",
    "momapy.positioning.set_fit(mb.layout, mb.layout.layout_elements, xsep=20, ysep=10)\n",
    "display(mb)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3acde95c",
   "metadata": {},
   "source": [
    "The `set` functions have an `anchor` option that sets the relative target anchor of the `Node` receiving the new position (default is `center`):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 71,
   "id": "c9b59172",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 11656 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "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\n",
    "eb.label.position = eb.position\n",
    "momapy.sbgn.utils.tidy(mb)\n",
    "display(mb)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "887b09f7",
   "metadata": {},
   "source": [
    "## Building new types of Nodes and Arcs"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8259ff40",
   "metadata": {},
   "source": [
    "New types of `Node`s and `Arc`s can be easily programmed.\n",
    "Since the geometry is automatic, it is only required to program the general shape of the `LayoutElement` using `DrawingElement`s."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a6b341e8",
   "metadata": {},
   "source": [
    "### New Node"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 72,
   "id": "5e33eb0d-8a07-46be-8961-180009857ce6",
   "metadata": {},
   "outputs": [],
   "source": [
    "import dataclasses"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 73,
   "id": "188f5cca",
   "metadata": {},
   "outputs": [],
   "source": [
    "@dataclasses.dataclass(frozen=True)\n",
    "class MyTriangle(momapy.core.Node):\n",
    "    height: float = 30.0\n",
    "    width: float = 30.0\n",
    "    fill: momapy.coloring.Color = momapy.coloring.white\n",
    "    stroke: momapy.coloring.Color = momapy.coloring.black\n",
    "    \n",
    "    def _border_drawing_elements(self):\n",
    "        actions = [\n",
    "            momapy.drawing.MoveTo(self.position - (0, self.height / 2)), # top\n",
    "            momapy.drawing.LineTo(self.position + (self.width / 2, self.height / 2)), # bottom right\n",
    "            momapy.drawing.LineTo(self.position + (-self.width / 2, self.height / 2)), # bottom left\n",
    "            momapy.drawing.ClosePath()\n",
    "        ]\n",
    "        path = momapy.drawing.Path(actions=actions)\n",
    "        return [path]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 74,
   "id": "a79b98ea",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 10908 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "show_room(MyTriangle)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 75,
   "id": "2b08e972",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 14104 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "show_room(MyTriangle, \"angle\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0a548f55",
   "metadata": {},
   "source": [
    "### New Arc"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 76,
   "id": "cb28bf21",
   "metadata": {},
   "outputs": [],
   "source": [
    "@dataclasses.dataclass(frozen=True)\n",
    "class MyRectangleArrow(momapy.core.SingleHeadedArc):\n",
    "    arrowhead_width: float = 10.0\n",
    "    arrowhead_height: float = 10.0\n",
    "    arrowhead_fill: momapy.coloring.Color | momapy.drawing.NoneValueType = momapy.coloring.white\n",
    "    arrowhead_stroke: momapy.coloring.Color = momapy.coloring.black\n",
    "    path_fill: momapy.coloring.Color | momapy.drawing.NoneValueType = momapy.drawing.NoneValue\n",
    "    path_stroke: momapy.coloring.Color = momapy.coloring.black\n",
    "\n",
    "\n",
    "    def _arrowhead_border_drawing_elements(self):\n",
    "        actions = [\n",
    "            momapy.drawing.MoveTo(momapy.geometry.Point(0,0)), # we draw the arrowhead as if its base is at (0, 0)\n",
    "            momapy.drawing.LineTo(momapy.geometry.Point(0, -self.arrowhead_height / 2)), # top left\n",
    "            momapy.drawing.LineTo(momapy.geometry.Point(self.arrowhead_width, -self.arrowhead_height / 2)), # top right\n",
    "            momapy.drawing.LineTo(momapy.geometry.Point(self.arrowhead_width, self.arrowhead_height / 2)), # bottom right\n",
    "            momapy.drawing.LineTo(momapy.geometry.Point(0, self.arrowhead_height / 2)), # bottom left\n",
    "            momapy.drawing.ClosePath()\n",
    "        ]\n",
    "        path = momapy.drawing.Path(actions=actions)\n",
    "        return [path]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 77,
   "id": "2936e6ec",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 8100 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "show_room(MyRectangleArrow)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 78,
   "id": "c5355973",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 8548 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "show_room(MyRectangleArrow, \"fraction\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "02564df7-93f0-4961-bce7-1e78f94ecaca",
   "metadata": {},
   "source": [
    "New `LayoutElement`s can be even more easily programmed using the `Shape`s already available in the `meta` module:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 79,
   "id": "cde6f602-43ee-4cbd-9064-8096a8028d28",
   "metadata": {},
   "outputs": [],
   "source": [
    "import momapy.meta.shapes"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 80,
   "id": "60e32556-56d6-47ae-b745-3b769e3ae08e",
   "metadata": {},
   "outputs": [],
   "source": [
    "@dataclasses.dataclass(frozen=True)\n",
    "class MyDoubleRectangleArrow(momapy.core.DoubleHeadedArc):\n",
    "    start_arrowhead_width: float = 10.0\n",
    "    start_arrowhead_height: float = 10.0\n",
    "    start_arrowhead_fill: momapy.coloring.Color | momapy.drawing.NoneValueType = momapy.coloring.white\n",
    "    start_arrowhead_stroke: momapy.coloring.Color = momapy.coloring.black\n",
    "    end_arrowhead_width: float = 20.0\n",
    "    end_arrowhead_height: float = 20.0\n",
    "    end_arrowhead_fill: momapy.coloring.Color | momapy.drawing.NoneValueType = momapy.coloring.white\n",
    "    end_arrowhead_stroke: momapy.coloring.Color = momapy.coloring.black\n",
    "    path_fill: momapy.coloring.Color | momapy.drawing.NoneValueType = momapy.drawing.NoneValue\n",
    "    path_stroke: momapy.coloring.Color = momapy.coloring.black\n",
    "    \n",
    "    def _start_arrowhead_border_drawing_elements(self):\n",
    "        return momapy.meta.shapes.Rectangle(\n",
    "            position=momapy.geometry.Point(-self.start_arrowhead_width/2, 0),\n",
    "            width=self.start_arrowhead_width,\n",
    "            height=self.start_arrowhead_height\n",
    "        ).drawing_elements()\n",
    "\n",
    "    def _end_arrowhead_border_drawing_elements(self):\n",
    "        return momapy.meta.shapes.Rectangle(\n",
    "            position=momapy.geometry.Point(self.end_arrowhead_width/2, 0),\n",
    "            width=self.end_arrowhead_width,\n",
    "            height=self.end_arrowhead_height\n",
    "        ).drawing_elements()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 81,
   "id": "fe6c88b5-9897-4a25-8d88-43e53789f146",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 12164 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "show_room(MyDoubleRectangleArrow)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aa114cdb",
   "metadata": {},
   "source": [
    "## Ongoing and future work"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2c0e2523",
   "metadata": {},
   "source": [
    "### Ongoing work:\n",
    "* Support for SBML layout/render\n",
    "* Improve performance of geometry (currently slow)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dee490d4",
   "metadata": {},
   "source": [
    "### Future work:\n",
    "* Support for background images and gradients\n",
    "* Support for SBGN ER maps\n",
    "* Support for BioPAX models\n",
    "* Automatic XML/JSON format\n",
    "* Developpment of converters"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
