From e643385c50bb2714b4aef939082d59a7f17112f0 Mon Sep 17 00:00:00 2001 From: Duncan Macleod Date: Wed, 13 Jun 2018 22:03:19 +0100 Subject: [PATCH] docs: improved `gwpy.plot` docs --- docs/plot/colorbars.rst | 88 +++++++++++++++++++++++++++++++++++++ docs/plot/gps.rst | 97 +++++++++++++++++++++++++++++++++++++++++ docs/plot/index.rst | 72 ++++++++++++++++++++++++++++-- 3 files changed, 253 insertions(+), 4 deletions(-) create mode 100644 docs/plot/colorbars.rst create mode 100644 docs/plot/gps.rst diff --git a/docs/plot/colorbars.rst b/docs/plot/colorbars.rst new file mode 100644 index 000000000..18e6f734b --- /dev/null +++ b/docs/plot/colorbars.rst @@ -0,0 +1,88 @@ +.. currentmodule:: gwpy.plot + +.. _gwpy-plot-colorbars: + +######### +Colorbars +######### + +====================================== +Modifications to the built-in colorbar +====================================== + +GWpy extends the built-in :mod:`matplotlib` +:meth:`~matplotlib.figure.Figure.colorbar` functionality to improve the +defaults in a number of ways: + +- callable from the :class:`~matplotlib.figure.Figure`, or directly from the + relevant :class:`~matplotlib.axes.Axes` +- don't resize the anchor :class:`~matplotlib.axes.Axes` +- simpler specification of log-norm colors +- better log-scale ticks + +See the following example for a summary of the improvements: + +.. plot:: + :include-source: + + import numpy + data = numpy.random.rand(100, 100) + from gwpy.timeseries import TimeSeries + from matplotlib import pyplot + fig, axes = pyplot.subplots(nrows=3, figsize=(6.4, 8)) + for ax in axes: # plot the same data on each + ax.imshow(data) + fig.colorbar(axes[1].images[0], ax=axes[1]) # matplotlib default + axes[2].colorbar() # gwpy colorbar + pyplot.show() + +============================ +Logarithmic colorbar scaling +============================ + +With GWpy, getting logarithmic scaling on a colorbar is as simple as +specifying, ``norm='log'``: + +.. plot:: + :include-source: + + from gwpy.timeseries import TimeSeries + data = TimeSeries.fetch_open_data('L1', 1187008866, 1187008898, tag='C00') + specgram = data.spectrogram2(fftlength=.5, overlap=.25, + window='hann') ** (1/2.) + plot = specgram.plot(yscale='log', ylim=(30, 1400)) + plot.colorbar(norm='log', clim=(1e-24, 1e-21), label='Strain ASD') + plot.show() + +.. note:: + + Log-scales can also be specified using :class:`~matplotlib.colors.LogNorm` + +=============== +Another example +=============== + +.. plot:: + :include-source: + + # load data + from gwpy.timeseries import TimeSeries + raw = TimeSeries.fetch_open_data('L1', 1126259446, 1126259478) + + # calculate filtered timeseries, and Q-transform spectrogram + data = raw.bandpass(50, 300).notch(60).crop(1126259446+1) + qtrans = raw.q_transform() + + # plot + from matplotlib import pyplot + plot, axes = pyplot.subplots(nrows=2, sharex=True, figsize=(8, 6)) + tax, qax = axes + tax.plot(data, color='gwpy:ligo-livingston') + qax.imshow(qtrans) + tax.set_xlabel('') + tax.set_xscale('auto-gps') + tax.set_xlim(1126259462.2, 1126259462.5) + tax.set_ylabel('Strain amplitude') + qax.set_yscale('log') + qax.set_ylabel('Frequency [Hz]') + qax.colorbar(clim=(0, 35), label='Normalised energy') diff --git a/docs/plot/gps.rst b/docs/plot/gps.rst new file mode 100644 index 000000000..f52badd5e --- /dev/null +++ b/docs/plot/gps.rst @@ -0,0 +1,97 @@ +.. currentmodule:: gwpy.plot.gps + +.. _gwpy-plot-gps: + +######################## +Plotting GPS time scales +######################## + +As we have seen, the default :mod:`matplotlib` representation of GPS scales +is not great, given the large zero-offset typically seen with 21st century GPS +times. + +To improve displays of data with GPS timestamps, GWpy provides a number of +custom :mod:`scales `. +Each scale uses an ``epoch`` and a ``unit`` to recentre and format the GPS +axis in a way that clearly displays the data, without large offsets or +multipliers. + +============== +``'auto-gps'`` +============== + +The ``'auto-gps'`` scale (the default for most GPS-based plots) automatically +calculates an ``epoch`` and ``unit`` each time the figure is drawn, based on +the current view limits and data limits: + +.. plot:: + :include-source: + :context: reset + + >>> from gwpy.timeseries import TimeSeries + >>> raw = TimeSeries.fetch_open_data('L1', 1126259446, 1126259478) + >>> data = raw.bandpass(50, 300).notch(60).crop(1126259446+1) + >>> plot = data.plot(xscale='auto-gps') + >>> plot.show() + +Here the default epoch is just the epoch for the given `TimeSeries`, and the +unit has been automatically chosen as ``'seconds'``. +However, if we zoom the axes to a tiny fraction of a second, the ``unit`` is +automatically reselected to something more sensible: + +.. plot:: + :include-source: + :context: + + >>> ax = plot.gca() + >>> ax.set_xlim(1126259462.415, 1126259462.425) + >>> plot.refresh() + +=========== +Fixed units +=========== + +A GPS axis can be fixed to a specific unit via the +:meth:`~matplotlib.axes.Axes.set_xscale` (or +:meth:`~matplotlib.axes.Axes.set_yscale`) method of the relevant axis. + +The available units are + +- ``'nanoseconds'`` +- ``'microseconds'`` +- ``'milliseconds'`` +- ``'seconds'`` +- ``'minutes'`` +- ``'hours'`` +- ``'days'`` +- ``'weeks'`` +- ``'years'`` + +.. plot:: + :include-source: + :context: + + >>> ax.set_xscale('seconds') + >>> plot.refresh() + +=========== +Fixed epoch +=========== + +A GPS axis can be fixed to a specific epoch via the same +:meth:`~matplotlib.axes.Axes.set_xscale` (or +:meth:`~matplotlib.axes.Axes.set_yscale`) method of the relevant axis, or +via the special :meth:`~gwpy.plot.Axes.set_epoch` method: + +.. plot:: + :include-source: + :context: + + >>> ax.set_xlim(1126259462.2, 1126259462.6) + >>> ax.set_epoch(1126259462.42) + >>> plot.refresh() + +.. note:: + + A fixed epoch can be used with ``'auto-gps'`` as well as any of the + fixed units. diff --git a/docs/plot/index.rst b/docs/plot/index.rst index 5cfcb027f..fd2b2197f 100644 --- a/docs/plot/index.rst +++ b/docs/plot/index.rst @@ -6,12 +6,16 @@ Plotting in GWpy (:mod:`gwpy.plot`) ################################### +============== +Basic plotting +============== + The :mod:`gwpy.plot` module provides integrated extensions to the fantastic data visualisation tools provided by |matplotlib|_. -============================================= +--------------------------------------------- Basic plotting with :mod:`~matplotlib.pyplot` -============================================= +--------------------------------------------- Each of the data representations provided by `gwpy` can be directly passed to the standard methods available in `~matplotlib.pyplot`: @@ -25,9 +29,9 @@ to the standard methods available in `~matplotlib.pyplot`: >>> plt.plot(data) >>> plt.show() -============================== +------------------------------ :meth:`.plot` instance methods -============================== +------------------------------ Each of the data representations provided by `gwpy` also come with a :meth:`.plot` method that provides a figure with improved defaults tailored @@ -40,3 +44,63 @@ to those data: >>> data = TimeSeries.fetch_open_data('L1', 1126259446, 1126259478) >>> plot = data.plot() >>> plot.show() + +The :meth:`.plot` methods accept any keywords that can be used to create the +:class:`~matplotlib.figure.Figure` and the :class:`~matplotlib.axes.Axes`, +and to draw the element itself, e.g: + +.. plot:: + :include-source: + + >>> from gwpy.timeseries import TimeSeries + >>> data = TimeSeries.fetch_open_data('L1', 1126259446, 1126259478) + >>> plot = data.plot(figsize=(8, 4), ylabel='Strain', + ... color='gwpy:ligo-livingston') + >>> plot.show() + +---------------- +Multi-data plots +---------------- + +GWpy enables trivial generation of plots with multiple datasets. +The :class:`~gwpy.plot.Plot` constructor will accept an arbitrary +collection of data objects and will build a figure with the required geometry +automatically. +By default, a flat set of objects are shown on the same axes: + +.. plot:: + :include-source: + :context: reset + + >>> from gwpy.timeseries import TimeSeries + >>> hdata = TimeSeries.fetch_open_data('H1', 1126259446, 1126259478) + >>> ldata = TimeSeries.fetch_open_data('L1', 1126259446, 1126259478) + >>> from gwpy.plot import Plot + >>> plot = Plot(hdata, ldata, figsize=(12, 4)) + >>> plot.show() + +However, `separate=True` can be given to show each dataset on a separate +`~gwpy.plot.Axes`: + +.. plot:: + :include-source: + :context: + + plot = Plot(hdata, ldata, figsize=(12, 6), separate=True, sharex=True) + plot.show() + +.. warning:: + + The `Plot` constructor can only handle one plotting method at any time + (e.g. ``ax.plot()``, ``ax.imshow()``), so you can't create plots with + a line and an image using this call, + for example. + +================== +Plot customisation +================== + +.. toctree:: + + gps + colorbars