diff --git a/CHANGES.txt b/CHANGES.txt
index 02971fe034b1f375a966ce2a028128d27c48b8f2..43c079552bbec0e4e19abc32dd1fc3caafbfeb3e 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -2,6 +2,14 @@ Changelog
 =========
 
 
+Version 0.14.1
+--------------
+
+Document the colon-separated ``node[:port[:compass]]`` format used for ``tail``
+and ``head`` points in the ``edge()``- and ``edges()``-methods' (PR Michał
+Góral).
+
+
 Version 0.14
 ------------
 
diff --git a/PKG-INFO b/PKG-INFO
index 38c82cdca158ee9735056d74c646807156f20f8f..192f10feac76466de64bbb4ab3cf18f7d2f1f6ac 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: graphviz
-Version: 0.14
+Version: 0.14.1
 Summary: Simple Python interface for Graphviz
 Home-page: https://github.com/xflr6/graphviz
 Author: Sebastian Bank
diff --git a/docs/conf.py b/docs/conf.py
index 179abdb35212664b02d3db30f5de51ccf014a064..e83c4d66d5c5c2e03fc616ec6a2301c29edc79de 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -25,7 +25,7 @@ copyright = '2013-2020, Sebastian Bank'
 author = 'Sebastian Bank'
 
 # The short X.Y version
-version = '0.14'
+version = '0.14.1'
 # The full version, including alpha/beta/rc tags
 release = version
 
diff --git a/docs/examples.rst b/docs/examples.rst
index f7bb00244f2b5cbb9733ebc87156a572f3aa981b..4c6c7d817d35417f83a01aefdc0c4bcfeea38fd5 100644
--- a/docs/examples.rst
+++ b/docs/examples.rst
@@ -94,6 +94,8 @@ structs_revisited.py
     :align: center
 
 
+.. _btree.py:
+
 btree.py
 --------
 
diff --git a/docs/manual.rst b/docs/manual.rst
index 5e1ba8cb39065aca0731ca1cfd9d0bb8e765fb85..61c7cbd84377326e41e2417c0d19ad341ffe043d 100644
--- a/docs/manual.rst
+++ b/docs/manual.rst
@@ -52,7 +52,7 @@ Their constructors allow to set the graph's :attr:`~.Graph.name`, the
 :attr:`~.Graph.comment` for the first source code line, etc.
 
 Add nodes and edges to the graph object using its :meth:`~.Graph.node` and
-:meth:`~.Graph.edge` or :meth:`~.Graph.edges` methods:
+:meth:`~.Graph.edge`- or :meth:`~.Graph.edges`-methods:
 
 .. code:: python
 
@@ -261,6 +261,39 @@ key-value pairs targeting the current (sub-)graph (e.g. for ``rankdir``,
     :align: center
 
 
+.. _ports:
+
+Node ports & compass
+--------------------
+
+The :meth:`~.Graph.edge`- and :meth:`~.Graph.edges`-methods use the
+colon-separated format ``node[:port[:compass]]`` for ``tail`` and ``head``
+nodes. This allows to specify an optional node ``port`` plus an optional
+``compass`` point the edge should aim at for the given tail or head node
+(:ref:`example <btree.py>`).
+
+As colons are used to indicate ``port`` and ``compass``, node names with
+literal colon(s) (``:``) are not supported. Note that there is no such
+restriction for the ``label`` argument, so you can work around by choosing a
+colon-free ``name`` together with the wanted ``label``:
+
+.. code:: python
+
+    >>> cpp = Digraph('C++')
+    
+    >>> cpp.node('A', 'std::string')
+    >>> cpp.node('B', '"spam"')
+    
+    >>> cpp.edge('A', 'B')
+    
+    >>> print(cpp.source)  # doctest: +NORMALIZE_WHITESPACE
+    digraph "C++" {
+        A [label="std::string"]
+        B [label="\"spam\""]
+        A -> B
+    }
+
+
 Backslash escapes
 -----------------
 
@@ -445,7 +478,7 @@ Custom DOT statements
 To add arbitrary statements to the created DOT_ source, use the
 :attr:`~.Graph.body` attribute of the :class:`.Graph` or :class:`.Digraph`
 object. It holds the verbatim list of lines to be written to the source file.
-Use its ``append()`` or ``extend()`` method:
+Use its ``append()``- or ``extend()``-method:
 
 .. code:: python
 
diff --git a/docs/requirements.txt b/docs/requirements.txt
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/graphviz.egg-info/PKG-INFO b/graphviz.egg-info/PKG-INFO
index 38c82cdca158ee9735056d74c646807156f20f8f..192f10feac76466de64bbb4ab3cf18f7d2f1f6ac 100644
--- a/graphviz.egg-info/PKG-INFO
+++ b/graphviz.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: graphviz
-Version: 0.14
+Version: 0.14.1
 Summary: Simple Python interface for Graphviz
 Home-page: https://github.com/xflr6/graphviz
 Author: Sebastian Bank
diff --git a/graphviz.egg-info/SOURCES.txt b/graphviz.egg-info/SOURCES.txt
index 2c8f5ad3557dca679b238d54aa30f63676cb5ad7..f941c8619ef1c5a07d172b7a0fa051fa04fa7c7e 100644
--- a/graphviz.egg-info/SOURCES.txt
+++ b/graphviz.egg-info/SOURCES.txt
@@ -17,7 +17,6 @@ docs/license.rst
 docs/manual.rst
 docs/notebooks.rst
 docs/pet-shop.png
-docs/requirements.txt
 docs/round-table.png
 docs/_static/angles.svg
 docs/_static/btree.svg
diff --git a/graphviz.egg-info/requires.txt b/graphviz.egg-info/requires.txt
index a65868c23ff48e0cd473ec7bdbfa97fcd6eefd75..a5942336dffbabf796e909a68c08fccc37ebcb02 100644
--- a/graphviz.egg-info/requires.txt
+++ b/graphviz.egg-info/requires.txt
@@ -7,11 +7,11 @@ wheel
 twine
 
 [docs]
-sphinx>=1.7
+sphinx>=1.8
 sphinx-rtd-theme
 
 [test]
 mock>=3
-pytest!=3.10.0,>=3.4
-pytest-mock>=1.8
+pytest>=4
+pytest-mock>=2
 pytest-cov
diff --git a/graphviz/__init__.py b/graphviz/__init__.py
index c105de683c0ef30b28e344e1ec8d6b2c958d8886..8b28058fe4dab5b98aa58055a24cdd1da7984d02 100644
--- a/graphviz/__init__.py
+++ b/graphviz/__init__.py
@@ -41,7 +41,7 @@ __all__ = [
 ]
 
 __title__ = 'graphviz'
-__version__ = '0.14'
+__version__ = '0.14.1'
 __author__ = 'Sebastian Bank <sebastian.bank@uni-leipzig.de>'
 __license__ = 'MIT, see LICENSE.txt'
 __copyright__ = 'Copyright (c) 2013-2020 Sebastian Bank'
diff --git a/graphviz/dot.py b/graphviz/dot.py
index 535ae21d76e899a17184c29f21c679eaa7f99ece..f0de7796f226849643b14d1ceac9d29e3de4f5a2 100644
--- a/graphviz/dot.py
+++ b/graphviz/dot.py
@@ -133,13 +133,19 @@ class Dot(files.File):
         self.body.append(line)
 
     def edge(self, tail_name, head_name, label=None, _attributes=None, **attrs):
-        """Create an edge between two nodes.
+        """Create an edge between two nodes. 
 
         Args:
-            tail_name: Start node identifier.
-            head_name: End node identifier.
+            tail_name: Start node identifier (format: ``node[:port[:compass]]``).
+            head_name: End node identifier (format: ``node[:port[:compass]]``).
             label: Caption to be displayed near the edge.
             attrs: Any additional edge attributes (must be strings).
+
+        Note:
+            The ``tail_name`` and ``head_name`` strings are separated by
+            (optional) colon(s) into ``node`` name, ``port`` name, and
+            ``compass`` (e.g. ``sw``).
+            See :ref:`details in the User Guide <ports>`.
         """
         tail_name = self._quote_edge(tail_name)
         head_name = self._quote_edge(head_name)
@@ -151,7 +157,14 @@ class Dot(files.File):
         """Create a bunch of edges.
 
         Args:
-            tail_head_iter: Iterable of ``(tail_name, head_name)`` pairs.
+            tail_head_iter: Iterable of ``(tail_name, head_name)`` pairs (format:``node[:port[:compass]]``).
+
+
+        Note:
+            The ``tail_name`` and ``head_name`` strings are separated by
+            (optional) colon(s) into ``node`` name, ``port`` name, and
+            ``compass`` (e.g. ``sw``).
+            See :ref:`details in the User Guide <ports>`.
         """
         edge = self._edge_plain
         quote = self._quote_edge
@@ -198,7 +211,7 @@ class Dot(files.File):
 
         See the :ref:`usage examples in the User Guide <subgraphs>`.
 
-        .. note::
+        Note:
             If the ``name`` of the subgraph begins with ``'cluster'`` (all lowercase)
             the layout engine will treat it as a special cluster subgraph.
         """
@@ -255,7 +268,7 @@ class Graph(Dot):
         strict (bool): Rendering should merge multi-edges.
 
     Note:
-        All parameters are optional and can be changed under their
+        All parameters are `optional` and can be changed under their
         corresponding attribute name after instance creation.
     """
 
diff --git a/setup.cfg b/setup.cfg
index 2c4c3e0a40ca2698e6df32f5a6cc995667d88685..5258b8834f2faca00ca553459b3bc7e23fafcaa9 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -8,7 +8,7 @@ formats = zip
 universal = 1
 
 [tool:pytest]
-minversion = 3.4
+minversion = 4
 testpaths = README.rst docs graphviz tests
 addopts = 
 	--doctest-modules --doctest-glob='*.rst' --ignore=docs/conf.py
diff --git a/setup.py b/setup.py
index 40f8bd61575e8f9af7970fee9ff162e62eaf9fab..a94f6bc47cf8ff55224b5a775288f20950346dab 100644
--- a/setup.py
+++ b/setup.py
@@ -5,7 +5,7 @@ from setuptools import setup, find_packages
 
 setup(
     name='graphviz',
-    version='0.14',
+    version='0.14.1',
     author='Sebastian Bank',
     author_email='sebastian.bank@uni-leipzig.de',
     description='Simple Python interface for Graphviz',
@@ -22,8 +22,8 @@ setup(
     python_requires='>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*',
     extras_require={
         'dev': ['tox>=3', 'flake8', 'pep8-naming', 'wheel', 'twine'],
-        'test': ['mock>=3', 'pytest>=3.4,!=3.10.0', 'pytest-mock>=1.8', 'pytest-cov'],
-        'docs': ['sphinx>=1.7', 'sphinx-rtd-theme'],
+        'test': ['mock>=3', 'pytest>=4', 'pytest-mock>=2', 'pytest-cov'],
+        'docs': ['sphinx>=1.8', 'sphinx-rtd-theme'],
     },
     long_description=io.open('README.rst', encoding='utf-8').read(),
     classifiers=[