diff --git a/doc/source/api.rst b/doc/source/api.rst new file mode 100644 index 0000000..23d2835 --- /dev/null +++ b/doc/source/api.rst @@ -0,0 +1,15 @@ +API +=== + +.. 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. diff --git a/doc/source/conf.py b/doc/source/conf.py index 906a782..adab75b 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -113,12 +113,22 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -# html_theme = 'basic' +html_theme = 'mpl_sphinx_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -# html_theme_options = {} +html_theme_options = { + 'navbar_center': 'navbar-nav.html', + "icon_links": [ + { + "name": "GitHub", + "url": "https://github.com/matplotlib/cycler", + "icon": "fa-brands fa-square-github", + "type": "fontawesome", + } + ] + } # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] @@ -142,7 +152,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -# html_static_path = [] +html_static_path = ['static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied @@ -272,8 +282,8 @@ # If true, do not generate a @detailmenu in the "Top" node's menu. # texinfo_no_detailmenu = False -intersphinx_mapping = {'python': ('https://docs.python.org/3', None), - 'matplotlb': ('https://matplotlib.org', None)} +# intersphinx_mapping = {'python': ('https://docs.python.org/3', None), +# 'matplotlb': ('https://matplotlib.org', None)} # ################ numpydoc config #################### numpydoc_show_class_members = False diff --git a/doc/source/index.rst b/doc/source/index.rst index 126de4b..ec75b15 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -4,402 +4,9 @@ Composable cycles =================== -.. only:: html +.. toctree:: + :maxdepth: 1 - :Version: |version| - :Date: |today| - - -====== ==================================== -Docs https://matplotlib.org/cycler -PyPI https://pypi.python.org/pypi/Cycler -GitHub https://github.com/matplotlib/cycler -====== ==================================== - - -:py:mod:`cycler` API -==================== - -.. 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 `Cycler`\s 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 -~~~~~~~~~~~~~~~~~~~~~~ - -`Cycler`\s 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 `Cycler`\s 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. + usage + api + motivation diff --git a/doc/source/motivation.rst b/doc/source/motivation.rst new file mode 100644 index 0000000..16fe5e2 --- /dev/null +++ b/doc/source/motivation.rst @@ -0,0 +1,55 @@ +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. diff --git a/doc/source/static/navbar-nav.html b/doc/source/static/navbar-nav.html new file mode 100644 index 0000000..42e08b9 --- /dev/null +++ b/doc/source/static/navbar-nav.html @@ -0,0 +1,6 @@ +{# Displays links to the top-level TOCtree elements, in the header navbar. #} + diff --git a/doc/source/usage.rst b/doc/source/usage.rst new file mode 100644 index 0000000..b5d6388 --- /dev/null +++ b/doc/source/usage.rst @@ -0,0 +1,312 @@ +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 `Cycler`\s 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 +~~~~~~~~~~~~~~~~~~~~~~ + +`Cycler`\s 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 `Cycler`\s 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 diff --git a/pyproject.toml b/pyproject.toml index c7c79cb..c4b0768 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,9 @@ repository = "https://github.com/matplotlib/cycler" docs = [ "ipython", "matplotlib", + "mpl-sphinx-theme", "numpydoc", + "pydata-sphinx-theme==0.13.3", "sphinx", ] tests = [