circlify


Namecirclify JSON
Version 0.15.0 PyPI version JSON
download
home_pagehttp://github.com/elmotec/circlify
SummaryCircle packing algorithm for Python
upload_time2022-07-10 20:09:42
maintainer
docs_urlNone
authorElmotec
requires_python>=3.5
licenseMIT
keywords circle packing enclosure hierarchy graph display visualization
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            .. image:: https://img.shields.io/pypi/v/circlify.svg
    :target: https://pypi.org/pypi/circlify/
    :alt: PyPi version

.. image:: https://img.shields.io/pypi/pyversions/circlify.svg
    :target: https://pypi.org/pypi/circlify/
    :alt: Python compatibility

.. image:: https://img.shields.io/github/workflow/status/elmotec/circlify/Python%20package/main
    :target: https://github.com/elmotec/circlify/actions
    :alt: GitHub Workflow Status (branch)

.. image:: https://codecov.io/gh/elmotec/circlify/branch/master/graph/badge.svg?token=PSE4TFPGTV 
    :target: https://codecov.io/gh/elmotec/circlify
    :alt: Codecov


========
circlify
========

Pure Python implementation of a circle packing layout algorithm, inspired by `d3js <https://observablehq.com/@d3/zoomable-circle-packing>`_ and `squarify <https://github.com/laserson/squarify>`_.

Circles are first arranged with a euristic inspired by the A1.0 of [Huang-2006]_, then enclosed in a circle created around them using [MSW-1996]_ algorithm used in [Bostock-2017]_.  I hope to implement A1.5 at some point in the future but the results are good enough for my use case.

Installation
------------

Using pip:

::

    pip install circlify

or using the source:

:: 

    git clone git://github.com/elmotec/circlify.git
    cd circlify
    python setup.py install


The last step may require ``sudo`` if you don't have root access.


Usage
-----

The main function ``circlify`` is supported by a small data class ``circlify.Circle`` and takes 3 parameters:

* A list of positive values sorted from largest to smallest.
* (optional) A target enclosure where the packed circles should fit. It defaults to the unit circle (0, 0, 1).
* (optional) A boolean indicating if the target enclosure should be appended to the output.

The function returns a list of ``circlify.Circle`` whose *area* is proportional to the corresponding input value.


Example
^^^^^^^

.. code:: python

    >>> from pprint import pprint as pp
    >>> import circlify as circ
    >>> circles = circ.circlify([19, 17, 13, 11, 7, 5, 3, 2, 1], show_enclosure=True)
    >>> pp(circles)
    [Circle(x=0.0, y=0.0, r=1.0, level=0, ex=None),
     Circle(x=-0.633232604611031, y=-0.47732413442115296, r=0.09460444572843042, level=1, ex={'datum': 1}),
     Circle(x=-0.7720311587589236, y=0.19946176418549022, r=0.13379089020993573, level=1, ex={'datum': 2}),
     Circle(x=-0.43168871955473165, y=-0.6391381648617572, r=0.16385970662353394, level=1, ex={'datum': 3}),
     Circle(x=0.595447603036083, y=0.5168251295666467, r=0.21154197162246005, level=1, ex={'datum': 5}),
     Circle(x=-0.5480911056188739, y=0.5115139053491098, r=0.2502998363185337, level=1, ex={'datum': 7}),
     Circle(x=0.043747233552068686, y=-0.6848366902134195, r=0.31376744998074435, level=1, ex={'datum': 11}),
     Circle(x=0.04298737651230445, y=0.5310431146935967, r=0.34110117996070605, level=1, ex={'datum': 13}),
     Circle(x=-0.3375943908160698, y=-0.09326467617622711, r=0.39006412239133215, level=1, ex={'datum': 17}),
     Circle(x=0.46484095011516874, y=-0.09326467617622711, r=0.4123712185399064, level=1, ex={'datum': 19})]


A simple matplotlib representation. See ``circlify.bubbles`` helper function (requires ``matplotlib``):

.. figure:: https://github.com/elmotec/circlify/blob/main/static/Figure_3.png
   :alt: visualization of circlify circle packing of first 9 prime numbers.

Hierarchical circle packing
---------------------------

Starting with version 0.10, circlify also handle hierarchical input so that:

.. code:: python

    >>> from pprint import pprint as pp
    >>> import circlify as circ
    >>> data = [
            0.05, {'id': 'a2', 'datum': 0.05},
            {'id': 'a0', 'datum': 0.8, 'children': [0.3, 0.2, 0.2, 0.1], },
            {'id': 'a1', 'datum': 0.1, 'children': [
                {'id': 'a1_1', 'datum': 0.05}, {'datum': 0.04}, 0.01],
            },
        ]
    >>> circles = circ.circlify(data, show_enclosure=True)
    >>> pp(circles)
    [Circle(x=0.0, y=0.0, r=1.0, level=0, ex=None),
     Circle(x=-0.5658030759977484, y=0.4109778665114514, r=0.18469903125906464, level=1, ex={'datum': 0.05}),
     Circle(x=-0.5658030759977484, y=-0.4109778665114514, r=0.18469903125906464, level=1, ex={'id': 'a2', 'datum': 0.05}),
     Circle(x=-0.7387961250362587, y=0.0, r=0.2612038749637415, level=1, ex={'id': 'a1', 'datum': 0.1, 'children': [{'id': 'a1_1', 'datum': 0.05}, {'datum': 0.04}, 0.01]}),
     Circle(x=0.2612038749637414, y=0.0, r=0.7387961250362586, level=1, ex={'id': 'a0', 'datum': 0.8, 'children': [0.3, 0.2, 0.2, 0.1]}),
     Circle(x=-0.7567888163564135, y=0.1408782365133844, r=0.0616618704777984, level=2, ex={'datum': 0.01}),
     Circle(x=-0.8766762590444033, y=0.0, r=0.1233237409555968, level=2, ex={'datum': 0.04}),
     Circle(x=-0.6154723840806618, y=0.0, r=0.13788013400814464, level=2, ex={'id': 'a1_1', 'datum': 0.05}),
     Circle(x=0.6664952237042414, y=0.33692908734605553, r=0.21174557028487648, level=2, ex={'datum': 0.1}),
     Circle(x=-0.1128831469183017, y=-0.23039288135707192, r=0.29945345726929773, level=2, ex={'datum': 0.2}),
     Circle(x=0.1563193680487183, y=0.304601976765483, r=0.29945345726929773, level=2, ex={'datum': 0.2}),
     Circle(x=0.5533243963620487, y=-0.23039288135707192, r=0.3667540860110527, level=2, ex={'datum': 0.3})]


A simple matplotlib representation. See ``circlify.bubbles`` helper function (requires ``matplotlib``):

.. figure:: https://github.com/elmotec/circlify/blob/main/static/Figure_4.png
   :alt: visualization of circlify nested circle packing for a hierarchical input.

Relative size of circles in hierachy
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The area of the circles are proportional to the values passed in input only if the circles are at the same hierarchical level.
For instance: circles *a1_1* and *a2* both have a value of 0.05, yet *a1_1* is smaller than *a2* because *a1_1* is fitted within its parent circle *a1* one level below the level of *a2*.
In other words, the level 1 circles *a1* and *a2* are both proportional to their respective values but *a1_1* is proportional to the values on level 2 witin *a1*.

Invalid input
^^^^^^^^^^^^^

A warning is issued if a key is not understood.  The check is disabled if the program is running with -O or -OO option.  One can also disable the warning with the `regular logging filters <https://docs.python.org/3/library/logging.html#filter-objects>`_.

For instance:

.. code:: python

    >>> import logging
    >>> import sys
    >>> import circlify as circ
    >>> data = [ 0.05, {'id': 'a2', 'datum': 0.05, "bogus": {}}]
    >>> logging.getLogger().addHandler(logging.StreamHandler(sys.stdout))
    >>> _ = circ.circlify(data)
    unexpected 'bogus' in input is ignored  # not issued if __debug__ is false


.. [Huang-2006]
   WenQi HUANG, Yu LI, ChuMin LI, RuChu XU, New Heuristics for Packing Unequal Circles into a Circular Container, https://home.mis.u-picardie.fr/~cli/Publis/circle.pdf

.. [Bostock-2017]
    Mike Bostock, D3.js, https://beta.observablehq.com/@mbostock/miniball, https://beta.observablehq.com/@mbostock/miniball

.. [MSW-1996]
   J. Matoušek, M. Sharir, and E. Welzl. A Subexponential Bound For Linear Programming. Algorithmica, 16(4/5):498–516, October/November 1996, http://www.inf.ethz.ch/personal/emo/PublFiles/SubexLinProg_ALG16_96.pdf

            

Raw data

            {
    "_id": null,
    "home_page": "http://github.com/elmotec/circlify",
    "name": "circlify",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.5",
    "maintainer_email": "",
    "keywords": "circle packing enclosure hierarchy graph display visualization",
    "author": "Elmotec",
    "author_email": "elmotec@gmx.com",
    "download_url": "https://files.pythonhosted.org/packages/c1/f9/70058b8691b38d54402d92fe0dd3b4ae080c5d67fa881b54280c657a36af/circlify-0.15.0.tar.gz",
    "platform": null,
    "description": ".. image:: https://img.shields.io/pypi/v/circlify.svg\n    :target: https://pypi.org/pypi/circlify/\n    :alt: PyPi version\n\n.. image:: https://img.shields.io/pypi/pyversions/circlify.svg\n    :target: https://pypi.org/pypi/circlify/\n    :alt: Python compatibility\n\n.. image:: https://img.shields.io/github/workflow/status/elmotec/circlify/Python%20package/main\n    :target: https://github.com/elmotec/circlify/actions\n    :alt: GitHub Workflow Status (branch)\n\n.. image:: https://codecov.io/gh/elmotec/circlify/branch/master/graph/badge.svg?token=PSE4TFPGTV \n    :target: https://codecov.io/gh/elmotec/circlify\n    :alt: Codecov\n\n\n========\ncirclify\n========\n\nPure Python implementation of a circle packing layout algorithm, inspired by `d3js <https://observablehq.com/@d3/zoomable-circle-packing>`_ and `squarify <https://github.com/laserson/squarify>`_.\n\nCircles are first arranged with a euristic inspired by the A1.0 of [Huang-2006]_, then enclosed in a circle created around them using [MSW-1996]_ algorithm used in [Bostock-2017]_.  I hope to implement A1.5 at some point in the future but the results are good enough for my use case.\n\nInstallation\n------------\n\nUsing pip:\n\n::\n\n    pip install circlify\n\nor using the source:\n\n:: \n\n    git clone git://github.com/elmotec/circlify.git\n    cd circlify\n    python setup.py install\n\n\nThe last step may require ``sudo`` if you don't have root access.\n\n\nUsage\n-----\n\nThe main function ``circlify`` is supported by a small data class ``circlify.Circle`` and takes 3 parameters:\n\n* A list of positive values sorted from largest to smallest.\n* (optional) A target enclosure where the packed circles should fit. It defaults to the unit circle (0, 0, 1).\n* (optional) A boolean indicating if the target enclosure should be appended to the output.\n\nThe function returns a list of ``circlify.Circle`` whose *area* is proportional to the corresponding input value.\n\n\nExample\n^^^^^^^\n\n.. code:: python\n\n    >>> from pprint import pprint as pp\n    >>> import circlify as circ\n    >>> circles = circ.circlify([19, 17, 13, 11, 7, 5, 3, 2, 1], show_enclosure=True)\n    >>> pp(circles)\n    [Circle(x=0.0, y=0.0, r=1.0, level=0, ex=None),\n     Circle(x=-0.633232604611031, y=-0.47732413442115296, r=0.09460444572843042, level=1, ex={'datum': 1}),\n     Circle(x=-0.7720311587589236, y=0.19946176418549022, r=0.13379089020993573, level=1, ex={'datum': 2}),\n     Circle(x=-0.43168871955473165, y=-0.6391381648617572, r=0.16385970662353394, level=1, ex={'datum': 3}),\n     Circle(x=0.595447603036083, y=0.5168251295666467, r=0.21154197162246005, level=1, ex={'datum': 5}),\n     Circle(x=-0.5480911056188739, y=0.5115139053491098, r=0.2502998363185337, level=1, ex={'datum': 7}),\n     Circle(x=0.043747233552068686, y=-0.6848366902134195, r=0.31376744998074435, level=1, ex={'datum': 11}),\n     Circle(x=0.04298737651230445, y=0.5310431146935967, r=0.34110117996070605, level=1, ex={'datum': 13}),\n     Circle(x=-0.3375943908160698, y=-0.09326467617622711, r=0.39006412239133215, level=1, ex={'datum': 17}),\n     Circle(x=0.46484095011516874, y=-0.09326467617622711, r=0.4123712185399064, level=1, ex={'datum': 19})]\n\n\nA simple matplotlib representation. See ``circlify.bubbles`` helper function (requires ``matplotlib``):\n\n.. figure:: https://github.com/elmotec/circlify/blob/main/static/Figure_3.png\n   :alt: visualization of circlify circle packing of first 9 prime numbers.\n\nHierarchical circle packing\n---------------------------\n\nStarting with version 0.10, circlify also handle hierarchical input so that:\n\n.. code:: python\n\n    >>> from pprint import pprint as pp\n    >>> import circlify as circ\n    >>> data = [\n            0.05, {'id': 'a2', 'datum': 0.05},\n            {'id': 'a0', 'datum': 0.8, 'children': [0.3, 0.2, 0.2, 0.1], },\n            {'id': 'a1', 'datum': 0.1, 'children': [\n                {'id': 'a1_1', 'datum': 0.05}, {'datum': 0.04}, 0.01],\n            },\n        ]\n    >>> circles = circ.circlify(data, show_enclosure=True)\n    >>> pp(circles)\n    [Circle(x=0.0, y=0.0, r=1.0, level=0, ex=None),\n     Circle(x=-0.5658030759977484, y=0.4109778665114514, r=0.18469903125906464, level=1, ex={'datum': 0.05}),\n     Circle(x=-0.5658030759977484, y=-0.4109778665114514, r=0.18469903125906464, level=1, ex={'id': 'a2', 'datum': 0.05}),\n     Circle(x=-0.7387961250362587, y=0.0, r=0.2612038749637415, level=1, ex={'id': 'a1', 'datum': 0.1, 'children': [{'id': 'a1_1', 'datum': 0.05}, {'datum': 0.04}, 0.01]}),\n     Circle(x=0.2612038749637414, y=0.0, r=0.7387961250362586, level=1, ex={'id': 'a0', 'datum': 0.8, 'children': [0.3, 0.2, 0.2, 0.1]}),\n     Circle(x=-0.7567888163564135, y=0.1408782365133844, r=0.0616618704777984, level=2, ex={'datum': 0.01}),\n     Circle(x=-0.8766762590444033, y=0.0, r=0.1233237409555968, level=2, ex={'datum': 0.04}),\n     Circle(x=-0.6154723840806618, y=0.0, r=0.13788013400814464, level=2, ex={'id': 'a1_1', 'datum': 0.05}),\n     Circle(x=0.6664952237042414, y=0.33692908734605553, r=0.21174557028487648, level=2, ex={'datum': 0.1}),\n     Circle(x=-0.1128831469183017, y=-0.23039288135707192, r=0.29945345726929773, level=2, ex={'datum': 0.2}),\n     Circle(x=0.1563193680487183, y=0.304601976765483, r=0.29945345726929773, level=2, ex={'datum': 0.2}),\n     Circle(x=0.5533243963620487, y=-0.23039288135707192, r=0.3667540860110527, level=2, ex={'datum': 0.3})]\n\n\nA simple matplotlib representation. See ``circlify.bubbles`` helper function (requires ``matplotlib``):\n\n.. figure:: https://github.com/elmotec/circlify/blob/main/static/Figure_4.png\n   :alt: visualization of circlify nested circle packing for a hierarchical input.\n\nRelative size of circles in hierachy\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe area of the circles are proportional to the values passed in input only if the circles are at the same hierarchical level.\nFor instance: circles *a1_1* and *a2* both have a value of 0.05, yet *a1_1* is smaller than *a2* because *a1_1* is fitted within its parent circle *a1* one level below the level of *a2*.\nIn other words, the level 1 circles *a1* and *a2* are both proportional to their respective values but *a1_1* is proportional to the values on level 2 witin *a1*.\n\nInvalid input\n^^^^^^^^^^^^^\n\nA warning is issued if a key is not understood.  The check is disabled if the program is running with -O or -OO option.  One can also disable the warning with the `regular logging filters <https://docs.python.org/3/library/logging.html#filter-objects>`_.\n\nFor instance:\n\n.. code:: python\n\n    >>> import logging\n    >>> import sys\n    >>> import circlify as circ\n    >>> data = [ 0.05, {'id': 'a2', 'datum': 0.05, \"bogus\": {}}]\n    >>> logging.getLogger().addHandler(logging.StreamHandler(sys.stdout))\n    >>> _ = circ.circlify(data)\n    unexpected 'bogus' in input is ignored  # not issued if __debug__ is false\n\n\n.. [Huang-2006]\n   WenQi HUANG, Yu LI, ChuMin LI, RuChu XU, New Heuristics for Packing Unequal Circles into a Circular Container, https://home.mis.u-picardie.fr/~cli/Publis/circle.pdf\n\n.. [Bostock-2017]\n    Mike Bostock, D3.js, https://beta.observablehq.com/@mbostock/miniball, https://beta.observablehq.com/@mbostock/miniball\n\n.. [MSW-1996]\n   J. Matou\u0161ek, M. Sharir, and E. Welzl. A Subexponential Bound For Linear Programming. Algorithmica, 16(4/5):498\u2013516, October/November 1996, http://www.inf.ethz.ch/personal/emo/PublFiles/SubexLinProg_ALG16_96.pdf\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Circle packing algorithm for Python",
    "version": "0.15.0",
    "project_urls": {
        "Homepage": "http://github.com/elmotec/circlify"
    },
    "split_keywords": [
        "circle",
        "packing",
        "enclosure",
        "hierarchy",
        "graph",
        "display",
        "visualization"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "74012a02f947fea52029f3bd869bccb087318c243b3e4f4f7a65213e3ffc144d",
                "md5": "799dc83ed26abd44ac425ce3aac8a6c1",
                "sha256": "4ed3a49a7af255a30c5d502c36db61f711669ac5c2f26a7bb9c6826ef8e748d4"
            },
            "downloads": -1,
            "filename": "circlify-0.15.0-py2.py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "799dc83ed26abd44ac425ce3aac8a6c1",
            "packagetype": "bdist_wheel",
            "python_version": "py2.py3",
            "requires_python": ">=3.5",
            "size": 11448,
            "upload_time": "2022-07-10T20:09:40",
            "upload_time_iso_8601": "2022-07-10T20:09:40.256487Z",
            "url": "https://files.pythonhosted.org/packages/74/01/2a02f947fea52029f3bd869bccb087318c243b3e4f4f7a65213e3ffc144d/circlify-0.15.0-py2.py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "c1f970058b8691b38d54402d92fe0dd3b4ae080c5d67fa881b54280c657a36af",
                "md5": "8eebe0b432e081f0f55c949c8838d180",
                "sha256": "43812a4d379032dc010a1302927f2d5a6086d3fc1f155c6259e3049fbba98eb0"
            },
            "downloads": -1,
            "filename": "circlify-0.15.0.tar.gz",
            "has_sig": false,
            "md5_digest": "8eebe0b432e081f0f55c949c8838d180",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.5",
            "size": 11606,
            "upload_time": "2022-07-10T20:09:42",
            "upload_time_iso_8601": "2022-07-10T20:09:42.030431Z",
            "url": "https://files.pythonhosted.org/packages/c1/f9/70058b8691b38d54402d92fe0dd3b4ae080c5d67fa881b54280c657a36af/circlify-0.15.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2022-07-10 20:09:42",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "elmotec",
    "github_project": "circlify",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "requirements": [],
    "lcname": "circlify"
}
        
Elapsed time: 0.11126s