Skip to content

Latest commit

 

History

History
405 lines (253 loc) · 8.53 KB

index.rst

File metadata and controls

405 lines (253 loc) · 8.53 KB
.. currentmodule:: cycler

Composable cycles

.. only:: html

    :Version: |version|
    :Date: |today|


Docs https://matplotlib.org/cycler
PyPI https://pypi.python.org/pypi/Cycler
GitHub https://github.com/matplotlib/cycler
.. autosummary::
   :toctree: generated/

   cycler
   Cycler
   concat

The public API of :py:mod:`cycler` consists of a class Cycler, a factory function :func:`cycler`, and a concatenation function :func:`concat`. The factory function provides a simple interface for creating 'base' Cycler objects while the class takes care of the composition and iteration logic.

Cycler Usage

Base

A single entry Cycler object can be used to easily cycle over a single style. To create the Cycler use the :py:func:`cycler` function to link a key/style/keyword argument to series of values. The key must be hashable (as it will eventually be used as the key in a :obj:`dict`).

.. ipython:: python

   from __future__ import print_function
   from cycler import cycler

   color_cycle = cycler(color=['r', 'g', 'b'])
   color_cycle

The Cycler knows its length and keys:

.. ipython:: python


   len(color_cycle)
   color_cycle.keys

Iterating over this object will yield a series of :obj:`dict` objects keyed on the label

.. ipython:: python

   for v in color_cycle:
       print(v)

Cycler objects can be passed as the argument to :func:`cycler` which returns a new Cycler with a new label, but the same values.

.. ipython:: python

   cycler(ec=color_cycle)


Iterating over a Cycler results in the finite list of entries, to get an infinite cycle, call the Cycler object (a-la a generator)

.. ipython:: python

   cc = color_cycle()
   for j, c in zip(range(5),  cc):
       print(j, c)


Composition

A single Cycler can just as easily be replaced by a single for loop. The power of Cycler objects is that they can be composed to easily create complex multi-key cycles.

Addition

Equal length Cyclers with different keys can be added to get the 'inner' product of two cycles

.. ipython:: python

   lw_cycle = cycler(lw=range(1, 4))

   wc = lw_cycle + color_cycle

The result has the same length and has keys which are the union of the two input Cycler's.

.. ipython:: python

   len(wc)
   wc.keys

and iterating over the result is the zip of the two input cycles

.. ipython:: python

   for s in wc:
       print(s)

As with arithmetic, addition is commutative

.. ipython:: python

   lw_c = lw_cycle + color_cycle
   c_lw = color_cycle + lw_cycle

   for j, (a, b) in enumerate(zip(lw_c, c_lw)):
      print('({j}) A: {A!r} B: {B!r}'.format(j=j, A=a, B=b))

For convenience, the :func:`cycler` function can have multiple key-value pairs and will automatically compose them into a single Cycler via addition

.. ipython:: python

    wc = cycler(c=['r', 'g', 'b'], lw=range(3))

    for s in wc:
        print(s)


Multiplication

Any pair of Cycler can be multiplied

.. ipython:: python

   m_cycle = cycler(marker=['s', 'o'])

   m_c = m_cycle * color_cycle

which gives the 'outer product' of the two cycles (same as :func:`itertools.product` )

.. ipython:: python

   len(m_c)
   m_c.keys
   for s in m_c:
       print(s)

Note that unlike addition, multiplication is not commutative (like matrices)

.. ipython:: python

   c_m = color_cycle * m_cycle

   for j, (a, b) in enumerate(zip(c_m, m_c)):
      print('({j}) A: {A!r} B: {B!r}'.format(j=j, A=a, B=b))




Integer Multiplication

Cyclers can also be multiplied by integer values to increase the length.

.. ipython:: python

   color_cycle * 2
   2 * color_cycle


Concatenation

Cycler objects can be concatenated either via the :py:meth:`Cycler.concat` method

.. ipython:: python

   color_cycle.concat(color_cycle)

or the top-level :py:func:`concat` function

.. ipython:: python

   from cycler import concat
   concat(color_cycle, color_cycle)


Slicing

Cycles can be sliced with :obj:`slice` objects

.. ipython:: python

   color_cycle[::-1]
   color_cycle[:2]
   color_cycle[1:]

to return a sub-set of the cycle as a new Cycler.

Inspecting the Cycler

To inspect the values of the transposed Cycler use the Cycler.by_key method:

.. ipython:: python

   c_m.by_key()

This dict can be mutated and used to create a new Cycler with the updated values

.. ipython:: python

   bk = c_m.by_key()
   bk['color'] = ['green'] * len(c_m)
   cycler(**bk)


Examples

We can use Cycler instances to cycle over one or more kwarg to ~matplotlib.axes.Axes.plot :

.. plot::
   :include-source:

   from cycler import cycler
   from itertools import cycle

   fig, (ax1, ax2) = plt.subplots(1, 2, tight_layout=True,
                                  figsize=(8, 4))
   x = np.arange(10)

   color_cycle = cycler(c=['r', 'g', 'b'])

   for i, sty in enumerate(color_cycle):
      ax1.plot(x, x*(i+1), **sty)


   for i, sty in zip(range(1, 5), cycle(color_cycle)):
      ax2.plot(x, x*i, **sty)


.. plot::
   :include-source:

   from cycler import cycler
   from itertools import cycle

   fig, (ax1, ax2) = plt.subplots(1, 2, tight_layout=True,
                                  figsize=(8, 4))
   x = np.arange(10)

   color_cycle = cycler(c=['r', 'g', 'b'])
   ls_cycle = cycler('ls', ['-', '--'])
   lw_cycle = cycler('lw', range(1, 4))

   sty_cycle = ls_cycle * (color_cycle + lw_cycle)

   for i, sty in enumerate(sty_cycle):
      ax1.plot(x, x*(i+1), **sty)

   sty_cycle = (color_cycle + lw_cycle) * ls_cycle

   for i, sty in enumerate(sty_cycle):
      ax2.plot(x, x*(i+1), **sty)


Persistent Cycles

It can be useful to associate a given label with a style via dictionary lookup and to dynamically generate that mapping. This can easily be accomplished using a ~collections.defaultdict

.. ipython:: python

   from cycler import cycler as cy
   from collections import defaultdict

   cyl = cy('c', 'rgb') + cy('lw', range(1, 4))

To get a finite set of styles

.. ipython:: python

   finite_cy_iter = iter(cyl)
   dd_finite = defaultdict(lambda : next(finite_cy_iter))

or repeating

.. ipython:: python

   loop_cy_iter = cyl()
   dd_loop = defaultdict(lambda : next(loop_cy_iter))

This can be helpful when plotting complex data which has both a classification and a label

finite_cy_iter = iter(cyl)
styles = defaultdict(lambda : next(finite_cy_iter))
for group, label, data in DataSet:
    ax.plot(data, label=label, **styles[group])

which will result in every data with the same group being plotted with the same style.

Exceptions

A :obj:`ValueError` is raised if unequal length Cyclers are added together

.. ipython:: python
   :okexcept:

   cycler(c=['r', 'g', 'b']) + cycler(ls=['-', '--'])

or if two cycles which have overlapping keys are composed

.. ipython:: python
   :okexcept:

   color_cycle = cycler(c=['r', 'g', 'b'])

   color_cycle + color_cycle


Motivation

When plotting more than one line it is common to want to be able to cycle over one or more artist styles. For simple cases than can be done with out too much trouble:

.. plot::
   :include-source:

   fig, ax = plt.subplots(tight_layout=True)
   x = np.linspace(0, 2*np.pi, 1024)

   for i, (lw, c) in enumerate(zip(range(4), ['r', 'g', 'b', 'k'])):
      ax.plot(x, np.sin(x - i * np.pi / 4),
              label=r'$\phi = {{{0}}} \pi / 4$'.format(i),
              lw=lw + 1,
              c=c)

   ax.set_xlim([0, 2*np.pi])
   ax.set_title(r'$y=\sin(\theta + \phi)$')
   ax.set_ylabel(r'[arb]')
   ax.set_xlabel(r'$\theta$ [rad]')

   ax.legend(loc=0)

However, if you want to do something more complicated:

.. plot::
   :include-source:

   fig, ax = plt.subplots(tight_layout=True)
   x = np.linspace(0, 2*np.pi, 1024)

   for i, (lw, c) in enumerate(zip(range(4), ['r', 'g', 'b', 'k'])):
      if i % 2:
          ls = '-'
      else:
          ls = '--'
      ax.plot(x, np.sin(x - i * np.pi / 4),
              label=r'$\phi = {{{0}}} \pi / 4$'.format(i),
              lw=lw + 1,
              c=c,
              ls=ls)

   ax.set_xlim([0, 2*np.pi])
   ax.set_title(r'$y=\sin(\theta + \phi)$')
   ax.set_ylabel(r'[arb]')
   ax.set_xlabel(r'$\theta$ [rad]')

   ax.legend(loc=0)

the plotting logic can quickly become very involved. To address this and allow easy cycling over arbitrary kwargs the Cycler class, a composable keyword argument iterator, was developed.