{
 "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 style sheets (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 momapy.demo.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": {
    "scrolled": false
   },
   "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, 7384 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "m = momapy.io.read(\"phospho1.sbgn\")\n",
    "display(m)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a3a883e7",
   "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`, `notes`, and `annotations`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "a63e2eb1",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[30m<class 'momapy.sbgn.pd.SBGNPDMap'>\u001b[31m: SBGNPDMap(id=UUID('00154aeb-7c...\u001b[0m\n",
      "  \u001b[34m* id\u001b[35m: <class 'str'> = \u001b[31m00154aeb-7c3f-4843-88b5-fc0311...\u001b[0m\n",
      "  \u001b[34m* model\u001b[35m: <class 'momapy.sbgn.pd.SBGNPDModel'> = \u001b[31mSBGNPDModel(id=UUID('f0311c1a-...\u001b[0m\n",
      "  \u001b[34m* layout\u001b[35m: <class 'momapy.sbgn.pd.SBGNPDLayout'> = \u001b[31mSBGNPDLayout(id=UUID('37cf7891...\u001b[0m\n",
      "  \u001b[34m* layout_model_mapping\u001b[35m: <class 'momapy.core.LayoutModelMapping'> = \u001b[31mLayoutModelMapping(_singleton_...\u001b[0m\n",
      "  \u001b[34m* notes\u001b[35m: str | None = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* annotations\u001b[35m: frozenset[momapy.sbgn.core.Annotation] = \u001b[31mfrozenset()\u001b[0m\n"
     ]
    }
   ],
   "source": [
    "momapy.utils.pretty_print(m)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b797a4ab",
   "metadata": {},
   "source": [
    "## The `Model` object"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5f0a53f4",
   "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`, `notes` and `annotations`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "c47b3ee3",
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[30m<class 'momapy.sbgn.pd.SBGNPDModel'>\u001b[31m: SBGNPDModel(id=UUID('f0311c1a-...\u001b[0m\n",
      "  \u001b[34m* id\u001b[35m: <class 'str'> = \u001b[31mf0311c1a-dfce-4784-b32e-fb6d98...\u001b[0m\n",
      "  \u001b[34m* notes\u001b[35m: str | None = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* annotations\u001b[35m: frozenset[momapy.sbgn.core.Annotation] = \u001b[31mfrozenset()\u001b[0m\n",
      "  \u001b[34m* entity_pools\u001b[35m: frozenset[momapy.sbgn.pd.EntityPool] = \u001b[31mfrozenset({Macromolecule(id='g...\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": "2ddc8a92",
   "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[30m<class 'momapy.sbgn.pd.Macromolecule'>\u001b[31m: Macromolecule(id='glyph3', not...\u001b[0m\n",
      "  \u001b[34m* id\u001b[35m: <class 'str'> = \u001b[31mglyph3\u001b[0m\n",
      "  \u001b[34m* notes\u001b[35m: str | None = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* annotations\u001b[35m: frozenset[momapy.sbgn.core.Annotation] = \u001b[31mfrozenset()\u001b[0m\n",
      "  \u001b[34m* compartment\u001b[35m: typing.Optional[momapy.sbgn.pd.Compartment] = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* label\u001b[35m: typing.Optional[str] = \u001b[31mB\u001b[0m\n",
      "  \u001b[34m* state_variables\u001b[35m: frozenset[momapy.sbgn.pd.StateVariable] = \u001b[31mfrozenset()\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": "54d7e132",
   "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": "3c6ea76d",
   "metadata": {},
   "outputs": [],
   "source": [
    "assert isinstance(e, momapy.sbgn.pd.EntityPool)\n",
    "assert isinstance(e, momapy.core.ModelElement)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9ee5fc2f",
   "metadata": {},
   "source": [
    "## The `Layout` object"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "488ff8d6",
   "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",
    "The `LayoutElement`s of a `Layout` are contained in its `layout_elements` attribute:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "eb960ce0",
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[30m<class 'momapy.sbgn.pd.SBGNPDLayout'>\u001b[31m: SBGNPDLayout(id=UUID('37cf7891...\u001b[0m\n",
      "  \u001b[34m* id\u001b[35m: <class 'str'> = \u001b[31m37cf7891-cdf6-45e7-9582-dc6087...\u001b[0m\n",
      "  \u001b[34m* layout_elements\u001b[35m: tuple[momapy.core.LayoutElement] = \u001b[31m(MacromoleculeLayout(id='glyph...\u001b[0m\n",
      "  \u001b[34m* stroke\u001b[35m: typing.Union[momapy.coloring.Color, momapy.drawing.NoneValueType, NoneType] = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* stroke_width\u001b[35m: typing.Optional[float] = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* stroke_dasharray\u001b[35m: typing.Union[momapy.drawing.NoneValueType, tuple[float], NoneType] = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* stroke_dashoffset\u001b[35m: typing.Optional[float] = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* fill\u001b[35m: typing.Union[momapy.coloring.Color, momapy.drawing.NoneValueType, NoneType] = \u001b[31mColor(red=255, green=255, blue...\u001b[0m\n",
      "  \u001b[34m* transform\u001b[35m: typing.Union[momapy.drawing.NoneValueType, tuple[momapy.geometry.Transformation], NoneType] = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* filter\u001b[35m: typing.Union[momapy.drawing.NoneValueType, momapy.drawing.Filter, NoneType] = \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* width\u001b[35m: <class 'float'> = \u001b[31m450.0\u001b[0m\n",
      "  \u001b[34m* height\u001b[35m: <class 'float'> = \u001b[31m250.0\u001b[0m\n"
     ]
    }
   ],
   "source": [
    "momapy.utils.pretty_print(m.layout)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c41dade2",
   "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 `LayoutElement`, 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": "a3599418",
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[30m<class 'list'>\u001b[31m: [Group(stroke_width=None, stro...\u001b[0m\n",
      "  \u001b[34m- 0\u001b[35m: <class 'momapy.drawing.Group'> = \u001b[31mGroup(stroke_width=None, strok...\u001b[0m\n"
     ]
    }
   ],
   "source": [
    "momapy.utils.pretty_print(m.layout.drawing_elements())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e39eb48f",
   "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": "f8460184",
   "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[30m<class 'momapy.sbgn.pd.MacromoleculeLayout'>\u001b[31m: MacromoleculeLayout(id='glyph2...\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='glyph...\u001b[0m\n",
      "  \u001b[34m* stroke\u001b[35m: typing.Union[momapy.coloring.Color, momapy.drawing.NoneValueType, NoneType] = \u001b[31mColor(red=0, green=0, blue=0, ...\u001b[0m\n",
      "  \u001b[34m* stroke_width\u001b[35m: typing.Optional[float] = \u001b[31m1.0\u001b[0m\n",
      "  \u001b[34m* stroke_dasharray\u001b[35m: typing.Union[momapy.drawing.NoneValueType, tuple[float], NoneType] = \u001b[31m<momapy.drawing.NoneValueType ...\u001b[0m\n",
      "  \u001b[34m* stroke_dashoffset\u001b[35m: typing.Optional[float] = \u001b[31m0.0\u001b[0m\n",
      "  \u001b[34m* fill\u001b[35m: typing.Union[momapy.coloring.Color, momapy.drawing.NoneValueType, NoneType] = \u001b[31mColor(red=255, green=255, blue...\u001b[0m\n",
      "  \u001b[34m* transform\u001b[35m: typing.Union[momapy.drawing.NoneValueType, tuple[momapy.geometry.Transformation], NoneType] = \u001b[31m<momapy.drawing.NoneValueType ...\u001b[0m\n",
      "  \u001b[34m* filter\u001b[35m: typing.Union[momapy.drawing.NoneValueType, momapy.drawing.Filter, NoneType] = \u001b[31m<momapy.drawing.NoneValueType ...\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* width\u001b[35m: <class 'float'> = \u001b[31m108.0\u001b[0m\n",
      "  \u001b[34m* height\u001b[35m: <class 'float'> = \u001b[31m60.0\u001b[0m\n",
      "  \u001b[34m* label\u001b[35m: typing.Optional[momapy.core.TextLayout] = \u001b[31mTextLayout(id=UUID('6832288b-c...\u001b[0m\n",
      "  \u001b[34m* rounded_corners\u001b[35m: <class 'float'> = \u001b[31m10.0\u001b[0m\n"
     ]
    }
   ],
   "source": [
    "for l in m.layout.layout_elements:\n",
    "    break\n",
    "momapy.utils.pretty_print(l)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5bd9520d",
   "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": "c3952500",
   "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": "eb2acd1a",
   "metadata": {},
   "source": [
    "## The `LayoutModelMapping` object"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aee2d693",
   "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": "a638885b",
   "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[30m<class 'momapy.sbgn.pd.Macromolecule'>\u001b[31m: Macromolecule(id='glyph2', not...\u001b[0m\n",
      "  \u001b[34m* id\u001b[35m: <class 'str'> = \u001b[31mglyph2\u001b[0m\n",
      "  \u001b[34m* notes\u001b[35m: str | None = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* annotations\u001b[35m: frozenset[momapy.sbgn.core.Annotation] = \u001b[31mfrozenset()\u001b[0m\n",
      "  \u001b[34m* compartment\u001b[35m: typing.Optional[momapy.sbgn.pd.Compartment] = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* label\u001b[35m: typing.Optional[str] = \u001b[31mA\u001b[0m\n",
      "  \u001b[34m* state_variables\u001b[35m: frozenset[momapy.sbgn.pd.StateVariable] = \u001b[31mfrozenset({StateVariable(id='g...\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": "eeaef017",
   "metadata": {},
   "source": [
    "# Equality and-sub relation for `Map`s, `Model`s, `Layout`s and `LayoutModelMapping`s"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4f852bab",
   "metadata": {},
   "source": [
    "`Map`s, `Model`s, `Layout`s, `LayoutModelMapping`s, `ModelElement`s and `LayoutElement`s are developped 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`, `notes` and `annotations`).\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": "e600023e",
   "metadata": {},
   "source": [
    "## Equality"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a4947cf6",
   "metadata": {},
   "source": [
    "### Definition"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "683e058d",
   "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": "8fbeeebb",
   "metadata": {},
   "source": [
    "### Example"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "a4a4304c",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 7384 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "m1 = momapy.io.read(\"phospho1.sbgn\")\n",
    "display(m1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "a5dada7f",
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 9000 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "m2 = momapy.io.read(\"phospho2.sbgn\")\n",
    "display(m2)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7868e447",
   "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": "79b17c6a",
   "metadata": {},
   "source": [
    "## Sub-map/model/layout/mapping"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9d1a0a29",
   "metadata": {},
   "source": [
    "### Definition"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bf3a5f38",
   "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": "e7187617",
   "metadata": {},
   "source": [
    "### Example 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "2e1021c7",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 7384 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "m1 = momapy.io.read(\"phospho1.sbgn\")\n",
    "display(m1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "7458590e",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 5472 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "m3 = momapy.io.read(\"phospho3.sbgn\")\n",
    "display(m3)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9f66a5e7",
   "metadata": {},
   "source": [
    "The second map is an excerpt of the first map:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "7fd65b92",
   "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": "caec8f24",
   "metadata": {},
   "source": [
    "### Example 2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "c39ee44f",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 6644 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "m4 = momapy.io.read(\"phospho4.sbgn\")\n",
    "display(m4)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2731481b",
   "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": "e956663e",
   "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": "b96a60b5",
   "metadata": {},
   "source": [
    "# Frozen and builder objects"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "abb418cf",
   "metadata": {},
   "source": [
    "`Map`, `Model`, `Layout`, `ModelElement` and `LayoutElement` objects cannot be modified; they are frozen:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "1dc0f754",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 7384 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "m = momapy.io.read(\"phospho1.sbgn\")\n",
    "display(m)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "a14b6eac",
   "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": [
    "for l in m.layout.layout_elements:\n",
    "    break\n",
    "display(l)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "3c1d12dd",
   "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": "6b00d62b",
   "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 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": "0a399163",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[30m<class 'abc.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: momapy.core.TupleBuilder[abc.LayoutElementBuilder] = \u001b[31m[StateVariableLayoutBuilder(id...\u001b[0m\n",
      "  \u001b[34m* stroke\u001b[35m: typing.Union[abc.ColorBuilder, momapy.drawing.NoneValueType, NoneType] = \u001b[31mColorBuilder(red=0, green=0, b...\u001b[0m\n",
      "  \u001b[34m* stroke_width\u001b[35m: typing.Optional[float] = \u001b[31m1.0\u001b[0m\n",
      "  \u001b[34m* stroke_dasharray\u001b[35m: typing.Union[momapy.drawing.NoneValueType, momapy.core.TupleBuilder[float], NoneType] = \u001b[31m<momapy.drawing.NoneValueType ...\u001b[0m\n",
      "  \u001b[34m* stroke_dashoffset\u001b[35m: typing.Optional[float] = \u001b[31m0.0\u001b[0m\n",
      "  \u001b[34m* fill\u001b[35m: typing.Union[abc.ColorBuilder, momapy.drawing.NoneValueType, NoneType] = \u001b[31mColorBuilder(red=255, green=25...\u001b[0m\n",
      "  \u001b[34m* transform\u001b[35m: typing.Union[momapy.drawing.NoneValueType, momapy.core.TupleBuilder[abc.TransformationBuilder], NoneType] = \u001b[31m<momapy.drawing.NoneValueType ...\u001b[0m\n",
      "  \u001b[34m* filter\u001b[35m: typing.Union[momapy.drawing.NoneValueType, abc.FilterBuilder, NoneType] = \u001b[31m<momapy.drawing.NoneValueType ...\u001b[0m\n",
      "  \u001b[34m* position\u001b[35m: typing.Optional[abc.PointBuilder] = \u001b[31mPointBuilder(x=85.0, y=270.0)\u001b[0m\n",
      "  \u001b[34m* width\u001b[35m: <class 'float'> = \u001b[31m108.0\u001b[0m\n",
      "  \u001b[34m* height\u001b[35m: <class 'float'> = \u001b[31m60.0\u001b[0m\n",
      "  \u001b[34m* label\u001b[35m: typing.Optional[abc.TextLayoutBuilder] = \u001b[31mTextLayoutBuilder(id=UUID('64a...\u001b[0m\n",
      "  \u001b[34m* rounded_corners\u001b[35m: <class 'float'> = \u001b[31m10.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": "70a3d930",
   "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": "27404235",
   "metadata": {},
   "source": [
    "The frozen object may then be built back from the builder:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "5d4a691a",
   "metadata": {},
   "outputs": [],
   "source": [
    "l = momapy.builder.object_from_builder(lb)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "14c3768c",
   "metadata": {},
   "outputs": [],
   "source": [
    "assert l.stroke_width == 3.0"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3f30d47b",
   "metadata": {},
   "source": [
    "The builder version of a map may be returned directly when reading the SBGN-ML file:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "24dd1e0c",
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[30m<class 'abc.SBGNPDMapBuilder'>\u001b[31m: SBGNPDMapBuilder(id=UUID('bc92...\u001b[0m\n",
      "  \u001b[34m* id\u001b[35m: <class 'str'> = \u001b[31mbc921860-bda4-4d4d-8f07-7de297...\u001b[0m\n",
      "  \u001b[34m* model\u001b[35m: typing.Optional[abc.SBGNPDModelBuilder] = \u001b[31mSBGNPDModelBuilder(id=UUID('10...\u001b[0m\n",
      "  \u001b[34m* layout\u001b[35m: typing.Optional[abc.SBGNPDLayoutBuilder] = \u001b[31mSBGNPDLayoutBuilder(id=UUID('5...\u001b[0m\n",
      "  \u001b[34m* layout_model_mapping\u001b[35m: typing.Optional[momapy.core.LayoutModelMappingBuilder] = \u001b[31mLayoutModelMappingBuilder(_sin...\u001b[0m\n",
      "  \u001b[34m* notes\u001b[35m: typing.Optional[str] = \u001b[31mNone\u001b[0m\n",
      "  \u001b[34m* annotations\u001b[35m: momapy.core.FrozensetBuilder[abc.AnnotationBuilder] = \u001b[31mFrozensetBuilder()\u001b[0m\n"
     ]
    }
   ],
   "source": [
    "mb = momapy.io.read(\"phospho1.sbgn\", return_builder=True)\n",
    "momapy.utils.pretty_print(mb)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e395babd",
   "metadata": {},
   "source": [
    "# Rendering"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "9a7922d9",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 7384 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "m = momapy.io.read(\"phospho1.sbgn\")\n",
    "display(m)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "770ea2d4",
   "metadata": {},
   "source": [
    "`Map`s can be rendered in different formats using a simple render function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "id": "92c35dec",
   "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": "8c155225",
   "metadata": {},
   "source": [
    "`MapLayout`s can be moved to the top left using the `top_left` option:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "c17d6d94",
   "metadata": {},
   "outputs": [],
   "source": [
    "momapy.rendering.core.render_map(m, \"phospho1.pdf\", format_=\"pdf\", to_top_left=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "ceff9a52",
   "metadata": {},
   "outputs": [],
   "source": [
    "m1 = momapy.io.read(\"phospho1.sbgn\")\n",
    "m2 = momapy.io.read(\"phospho2.sbgn\")\n",
    "m3 = momapy.io.read(\"phospho3.sbgn\")\n",
    "m4 = momapy.io.read(\"phospho4.sbgn\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ec2f4ee5",
   "metadata": {},
   "source": [
    "Multiple `Map`s can be rendered in one document using a simple function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "id": "122ea81b",
   "metadata": {},
   "outputs": [],
   "source": [
    "momapy.rendering.core.render_maps([m1, m2, m3, m4], \"phospho_multi.pdf\", format_=\"pdf\", multi_pages=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "81014551",
   "metadata": {},
   "source": [
    "# Styling"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0dca215e",
   "metadata": {},
   "source": [
    "## Styling `LayoutElement` objects"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cceb790a",
   "metadata": {},
   "source": [
    "### Basic styling"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "39a6178e",
   "metadata": {},
   "source": [
    "Basic styling can be easily applied to `LayoutElement`s:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "id": "8956e76b",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 7384 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "mb = momapy.io.read(\"phospho1.sbgn\", return_builder=True)\n",
    "display(mb)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "id": "24beadb5",
   "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": "b3f52bb5",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 4736 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "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",
    "display(lb)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a873d036",
   "metadata": {},
   "source": [
    "The `label` of a `NodeLayout` can also be styled:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "id": "bf60005d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 5316 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.font_color = momapy.coloring.red\n",
    "display(lb)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b65d4647",
   "metadata": {},
   "source": [
    "### Advanced styling"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "22c16a9a",
   "metadata": {},
   "source": [
    "Advanced effects such as transformations (translation, rotation, ...) and filter effects can be applied to `LayoutElement`s:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "id": "7baec88f",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 18072 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "lb.transform = (momapy.geometry.Scaling(2, 1), momapy.geometry.Rotation(0.5, lb.position),)\n",
    "lb.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": "0839947b",
   "metadata": {},
   "source": [
    "## CSS-like style sheets"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2a74df51",
   "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": 39,
   "id": "6dda91e5",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 7384 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "mb = momapy.io.read(\"phospho1.sbgn\", return_builder=True)\n",
    "display(mb)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8f3f1cbb",
   "metadata": {},
   "source": [
    "There are pre-built `StyleSheet` objects for Vanted and Newt-like styles, for colors, and for shadows:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "id": "fd3ac6fa",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 6424 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "momapy.styling.apply_style_sheet(mb, momapy.sbgn.styling.newt)\n",
    "display(mb)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0e745b91",
   "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": 41,
   "id": "8cdd1db9",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 6236 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "momapy.sbgn.utils.newt_tidy(mb)\n",
    "display(mb)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "id": "a1842745",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 6684 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "momapy.styling.apply_style_sheet(mb, momapy.sbgn.styling.vanted)\n",
    "momapy.sbgn.utils.vanted_tidy(mb)\n",
    "display(mb)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "id": "4f4c17ae",
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 7344 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "momapy.styling.apply_style_sheet(mb, momapy.sbgn.styling.default_colorscheme)\n",
    "display(mb)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "id": "84a98dbf",
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 10556 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "momapy.styling.apply_style_sheet(mb, momapy.sbgn.styling.shadows)\n",
    "display(mb)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "30f01a01",
   "metadata": {},
   "source": [
    "These pre-built `StyleSheet`s are built from CSS-like text files:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "id": "745b80b0",
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "TextLayout {\n",
      "    font-family: \"Cantarell\";\n",
      "    font-size: 14.0;\n",
      "    font-color: black;\n",
      "}\n",
      "\n",
      "MapLayout {\n",
      "    stroke: none;\n",
      "    stroke-width: 2.0;\n",
      "    fill: white;\n",
      "    filter: none;\n",
      "}\n",
      "\n",
      "CompartmentLayout {\n",
      "    stroke: black;\n",
      "    stroke-width: 7.0;\n",
      "    fill: white;\n",
      "    rounded-corners: 20.0;\n",
      "    filter: none;\n",
      "}\n",
      "\n",
      "GenericProcessLayout {\n",
      "    width: 24;\n",
      "    height: 24;\n",
      "    stroke: black;\n",
      "    stroke-width: 2.0;\n",
      "    fill: white;\n",
      "    left-connector-length: 10.0;\n",
      "    right-connector-length: 10.0;\n",
      "    left-connector-stroke-width: 1.5;\n",
      "    right-connector-stroke-width: 1.5;\n",
      "    filter: none;\n",
      "}\n",
      "\n",
      "UncertainProcessLayout {\n"
     ]
    }
   ],
   "source": [
    "with open(\"../sbgn/styling/vanted.css\") as f:\n",
    "    for line in f.readlines()[:35]:\n",
    "        print(line[:-1])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e2a37443",
   "metadata": {},
   "source": [
    "One may build a `StyleSheet` from a file and apply it to a `Map` with simple functions:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "id": "2050c913",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 11180 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.read_file(\"my_style_sheet.css\")\n",
    "momapy.styling.apply_style_sheet(mb, my_style_sheet)\n",
    "display(mb)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8fb5fa19",
   "metadata": {},
   "source": [
    "# Automatic geometry"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "60121510",
   "metadata": {},
   "source": [
    "`LayoutElement`s support automatic geometry: their borders can be automatically computed from the `DrawingElement`s they return."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4aa2c3e4",
   "metadata": {},
   "source": [
    "## For `NodeLayout`s"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "id": "a90ab5b1",
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 8368 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": "71d57a74",
   "metadata": {},
   "source": [
    "### Anchors"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "id": "042be24e",
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Point(x=85.02983425275443, y=80.01491712637721)"
      ]
     },
     "execution_count": 48,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "lb.north_west()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "id": "a75274ac",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 8512 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": "0648da32",
   "metadata": {},
   "source": [
    "All `NodeLayout`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": 50,
   "id": "6ddacee6",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 16052 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": 51,
   "id": "b26c1842",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 8244 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": 52,
   "id": "ed8874a8",
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 15956 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "show_room(momapy.sbgn.pd.ComplexMultimerLayout)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "360ed66d",
   "metadata": {},
   "source": [
    "### Angles"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "id": "2edadf5a",
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Point(x=162.06752766170396, y=75.0)"
      ]
     },
     "execution_count": 53,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "lb.self_angle(130)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "id": "5d97f08a",
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 8440 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": 55,
   "id": "02bf193c",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 18876 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": 56,
   "id": "9cbf14e5",
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 8536 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": 57,
   "id": "86c0b585",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 19748 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": "febaea15",
   "metadata": {},
   "source": [
    "## For `ArcLayout`s"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "id": "704d65d1",
   "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": [
    "lb = production_toy()\n",
    "display(lb)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "039e26eb",
   "metadata": {},
   "source": [
    "### Anchors"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "id": "5c7880ad",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Point(x=299.5441558772843, y=75.45584412271572)"
      ]
     },
     "execution_count": 59,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "lb.arrowhead_base()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "id": "3e320261",
   "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": "code",
   "execution_count": 61,
   "id": "87c3b93f",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 7596 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": "9b302b7b",
   "metadata": {},
   "source": [
    "### Fraction"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 62,
   "id": "511fc471",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(Point(x=225.0, y=150.0), 5.497787143782138)"
      ]
     },
     "execution_count": 62,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "lb.fraction(0.50)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 63,
   "id": "cf8160d1",
   "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": 64,
   "id": "507c7922",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 8184 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": "abf6afde",
   "metadata": {},
   "source": [
    "# Relative positioning"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "da39890f",
   "metadata": {},
   "source": [
    "Automatic geometry enables positioning `LayoutElement`s relatively to one another."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 65,
   "id": "de186a30",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 7384 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "mb = momapy.io.read(\"phospho1.sbgn\", return_builder=True)\n",
    "display(mb)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 66,
   "id": "68e73710",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgs/VvQidJeDxKNhIAAAAASUVORK5CYII=",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAlgAAAHCCAYAAAAzc7dkAAAABHNCSVQICAgIfAhkiAAABVdJREFUeJzt3bFNw0AYhuEvCImSLeiZwF6JCeyMREl3XoCWkp6OkgoKkIyEQnBsxzh5Hsld5Py5K/IqsnIJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsDqbpQeAM1B/XXMqXxcA/8Dl0gPAGairqmrqup7l5l3XpZSSCCwA4Iy0TdO8z6Vt2/ck7dIfEoDexdIDAACcGoEFADAxgQUAMDGBBdCrlh4AOA0CC+BTk+QhyWOEFjCSwALoXSW5TXIfoQWMILAAfrqO0AJGEFgAuwkt4CACC2A/oQUM4qgchmiWHmCljvFlXMX+jPWXffoeWi9JbmadCFgtgcUQDgc/zDHWbXOk9zllQ9fPegM7CSyGaJceYKXazP8rVon9GatJUu95zWuS5yR3cbg28AuBBbCfsAIGEVgAuwkr4CACC+AnYQWMIrAAem9JniKsgJEEFsCnbZIuwgqYgD8aBeiVpQcAToPAAgCYmMACAJiYwAIAmJiH3OEIuq7Ldrud5d6llFnuC8DhnKUF86uz/wiWsUo8oA0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGvyAXJTkqjYFMZZAAAAAElFTkSuQmCC",
      "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",
    "    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\n",
    "display(eb)\n",
    "display(pb)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 67,
   "id": "5489abd2",
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 9532 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)\n",
    "display(mb)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2b0ec71d",
   "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": "524f796a",
   "metadata": {},
   "source": [
    "For container `NodeElement`s, the `fit` function is also available:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 68,
   "id": "f622146b",
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(Point(x=237.5, y=164.0), 453.0, 292.0)"
      ]
     },
     "execution_count": 68,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "momapy.positioning.fit(mb.layout.layout_elements, xsep=20, ysep=10)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "093afe12",
   "metadata": {},
   "source": [
    "Each of the above functions has a corresponding `set` function, which directly sets the returned value(s) to the correct `NodeElement`'s attributes:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 69,
   "id": "a799261f",
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 8972 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": "e7a27f25",
   "metadata": {},
   "source": [
    "The `set` functions have an `anchor` option that sets the relative target anchor of the `NodeLayout` receiving the new position (default is `center`):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 70,
   "id": "2c6a4675",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 10072 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": "c26c3d9d",
   "metadata": {},
   "source": [
    "# Building new types of `NodeElement`s and `ArcElement`s"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "39c74be1",
   "metadata": {},
   "source": [
    "New types of `NodeElement`s and `ArcElement`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": "9c26753e",
   "metadata": {},
   "source": [
    "## New `NodeLayout`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 71,
   "id": "188f5cca",
   "metadata": {},
   "outputs": [],
   "source": [
    "class MyTriangle(momapy.core.NodeLayout):\n",
    "    \n",
    "    def border_drawing_element(self):\n",
    "        path = momapy.drawing.Path()\n",
    "        path += momapy.drawing.MoveTo(self.position - (0, self.height / 2)) # top\n",
    "        path += momapy.drawing.LineTo(self.position + (self.width / 2, self.height / 2)) # bottom right\n",
    "        path += momapy.drawing.LineTo(self.position + (-self.width / 2, self.height / 2)) # bottom left\n",
    "        path += momapy.drawing.Close()\n",
    "        return path\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 72,
   "id": "a79b98ea",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 12524 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": 73,
   "id": "2b08e972",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 17308 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": "20852c8d",
   "metadata": {},
   "source": [
    "## New `ArcLayout`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 74,
   "id": "520d79fc",
   "metadata": {},
   "outputs": [],
   "source": [
    "class MySquareArrow(momapy.core.ArcLayout):\n",
    "    width: float = 10.0\n",
    "    height: float = 10.0\n",
    "\n",
    "    def arrowhead_drawing_element(self):\n",
    "        path = momapy.drawing.Path()\n",
    "        path += momapy.drawing.MoveTo(self.arrowhead_base())\n",
    "        path += momapy.drawing.LineTo(self.arrowhead_base() + (0, -self.height / 2)) # top left\n",
    "        path += momapy.drawing.LineTo(self.arrowhead_base() + (self.width, -self.height / 2)) # top right\n",
    "        path += momapy.drawing.LineTo(self.arrowhead_base() + (self.width, self.height / 2)) # bottom right\n",
    "        path += momapy.drawing.LineTo(self.arrowhead_base() + (0, self.height / 2)) # bottom left\n",
    "        path += momapy.drawing.Close()\n",
    "        return path\n",
    "\n",
    "    def arrowhead_length(self):\n",
    "        return self.width\n",
    "\n",
    "    def arrowhead_bbox(self):\n",
    "        return momapy.geometry.Bbox(self.arrowhead_tip(), 0, 0)\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 75,
   "id": "3e0aa6c0",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 7488 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "show_room(MySquareArrow)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 76,
   "id": "4af039fb",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 7952 characters]",
      "text/plain": [
       "Image(600, 450, ColorType.kBGRA_8888_ColorType, AlphaType.kPremul_AlphaType)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "show_room(MySquareArrow, \"fraction\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9ce72eeb",
   "metadata": {},
   "source": [
    "# Ongoing and future work"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8f5ba147",
   "metadata": {},
   "source": [
    "### Ongoing work:\n",
    "* Support for CellDesigner\n",
    "* Improve performance of geometry (currently slow)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "28f4a8fe",
   "metadata": {},
   "source": [
    "### Future work:\n",
    "* Support for background images and gradients\n",
    "* Support for SBGN ER\n",
    "* Support for SBML models (map skeleton) and layouts\n",
    "* Automatic XML/JSON format\n",
    "* Automatic Neo4j storing (StonPy2)\n",
    "* Developpment of converters\n",
    "* Support for BioPAX models"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "my-virtualenv-name",
   "language": "python",
   "name": "my-virtualenv-name"
  },
  "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.11.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
