├── .github └── workflows │ ├── docs.yml │ └── testing.yml ├── .gitignore ├── LICENSE.txt ├── README.rst ├── doc ├── Makefile ├── _templates │ └── autosummary │ │ └── class.rst ├── make.bat └── source │ ├── conf.py │ └── index.rst ├── requirements-dev.txt ├── requirements-doc.txt ├── setup.py └── trendvis ├── __init__.py ├── gridclass.py ├── gridwrapper.py ├── testing.py ├── tests ├── baseline_images │ └── test_trendvis │ │ ├── xgrid_clean.png │ │ ├── xgrid_cutout.png │ │ ├── xgrid_frame.png │ │ ├── xgrid_multitwin.png │ │ ├── xgrid_twins.png │ │ ├── xgrid_ylabels.png │ │ ├── ygrid_clean.png │ │ ├── ygrid_cutout.png │ │ ├── ygrid_frame.png │ │ ├── ygrid_multitwin.png │ │ ├── ygrid_twins.png │ │ └── ygrid_xlabels.png └── test_trendvis.py ├── xgrid_ystack.py └── ygrid_xstack.py /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Docs 2 | 3 | on: [push, pull_request] 4 | 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-20.04 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Install Python dependencies 12 | run: pip install -r requirements-doc.txt 13 | 14 | - name: Install mpl-gui 15 | run: python -m pip install -v . 16 | - name: Build 17 | run: make -Cdoc html 18 | - name: Publish 19 | if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} 20 | uses: peaceiris/actions-gh-pages@v3 21 | with: 22 | github_token: ${{ secrets.GITHUB_TOKEN }} 23 | publish_dir: ./doc/build/html 24 | force_orphan: true 25 | -------------------------------------------------------------------------------- /.github/workflows/testing.yml: -------------------------------------------------------------------------------- 1 | name: Unit Tests 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: 7 | - cron: '00 4 * * *' # daily at 4AM 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | python-version: ['3.8', '3.9', '3.10', '3.11'] 16 | fail-fast: false 17 | steps: 18 | 19 | - uses: actions/checkout@v2 20 | with: 21 | fetch-depth: 0 22 | 23 | - name: Set up Python ${{ matrix.python-version }} 24 | uses: actions/setup-python@v2 25 | with: 26 | python-version: ${{ matrix.python-version }} 27 | 28 | - name: Install test requirements 29 | shell: bash -l {0} 30 | run: | 31 | set -vxeuo pipefail 32 | python -m pip install -r requirements-dev.txt 33 | python -m pip install -v . 34 | python -m pip list 35 | 36 | - name: Test with pytest 37 | shell: bash -l {0} 38 | run: | 39 | set -vxeuo pipefail 40 | coverage run -m pytest -v 41 | coverage report 42 | 43 | - name: Upload code coverage 44 | uses: codecov/codecov-action@v1 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *egg-info 3 | trendvis/version.py 4 | result_images 5 | *~ 6 | doc/build 7 | doc/source/generated 8 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, mscross 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of trendvis nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | 30 | 31 | ############################################################################## 32 | setup.py is derived from scikit-image. This is also covered by the 3-clause 33 | BSD license. The original code for setup.py: 34 | 35 | Copyright (C) 2011, the scikit-image team 36 | All rights reserved. 37 | 38 | Redistribution and use in source and binary forms, with or without 39 | modification, are permitted provided that the following conditions are 40 | met: 41 | 42 | 1. Redistributions of source code must retain the above copyright 43 | notice, this list of conditions and the following disclaimer. 44 | 2. Redistributions in binary form must reproduce the above copyright 45 | notice, this list of conditions and the following disclaimer in 46 | the documentation and/or other materials provided with the 47 | distribution. 48 | 3. Neither the name of skimage nor the names of its contributors may be 49 | used to endorse or promote products derived from this software without 50 | specific prior written permission. 51 | 52 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 53 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 54 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 55 | DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 56 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 57 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 58 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 59 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 60 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 61 | IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 62 | POSSIBILITY OF SUCH DAMAGE. 63 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ======== 2 | TrendVis 3 | ======== 4 | 5 | TrendVis is a plotting package that uses 6 | `matplotlib `_ to create information-dense, 7 | sparkline-like, quantitative visualizations of multiple disparate data sets in 8 | a common plot area against a common variable. This plot type is particularly 9 | well-suited for time-series data. The results speak for themselves: 10 | 11 | .. image:: https://raw.githubusercontent.com/mscross/scipy_proceedings/trendvis/papers/mellissa_cross_t/barredplot.png 12 | :target: https://raw.githubusercontent.com/mscross/scipy_proceedings/trendvis/papers/mellissa_cross_t/barredplot.png 13 | :alt: TrendVis example 14 | 15 | For further reading on TrendVis, see the `SciPy 2015 Proceedings `_. 16 | 17 | ============ 18 | Installation 19 | ============ 20 | 21 | TrendVis is pure Python, natively supports Python 2 and 3, and depends only on 22 | `matplotlib `_ version 1.2 or greater. 23 | 24 | Setup and installation is simple:: 25 | 26 | pip install -U trendvis 27 | 28 | or, if you would like to develop the package, fork and clone the repo then run:: 29 | 30 | python setup.py develop 31 | 32 | at package root. 33 | 34 | ============== 35 | Quick Examples 36 | ============== 37 | 38 | Below are several examples showing various features in TrendVis and a typical 39 | workflow. Version >= 0.2.1 is required. 40 | 41 | Single column ``XGrid`` 42 | ----------------------- 43 | 44 | .. code-block:: python 45 | 46 | import numpy as np 47 | import matplotlib.pyplot as plt 48 | import trendvis 49 | 50 | # Pseudorandom data and plot attributes 51 | random_generator = np.random.RandomState(seed=123) 52 | yvals = random_generator.rand(10) 53 | 54 | # Plot attributes 55 | nums = 10 56 | lw = 1.5 57 | 58 | # convenience function trendvis.gridwrapper() is available 59 | # to initialize XGrid and do most of the formatting shown here 60 | ex0 = trendvis.XGrid([1,2,1], figsize=(5,5)) 61 | 62 | # Convenience function for plotting line data 63 | # Automatically colors y axis spines to 64 | # match line colors (auto_spinecolor=True) 65 | trendvis.plot_data(ex0, 66 | [[(np.linspace(0, 9.5, num=nums), yvals, 'blue')], 67 | [(np.linspace(1, 9, num=nums), yvals*5, 'red')], 68 | [(np.linspace(0.5, 10, num=nums), yvals*10, 'green')]], 69 | lw=lw, markeredgecolor='none', marker='s') 70 | 71 | # Get rid of extra spines 72 | ex0.cleanup_grid() 73 | ex0.set_spinewidth(lw) 74 | 75 | ex0.set_all_ticknums([(2, 1)], [(0.2, 0.1), (1, 0.5), (2, 1)]) 76 | ex0.set_ticks(major_dim=(7, 3), minor_dim=(4, 2)) 77 | 78 | ex0.set_ylabels(['stack axis 0', 'stack axis 1', 'stack axis 2']) 79 | 80 | # In XGrid.fig.axes, axes live in a 1 level list 81 | # In XGrid.axes, axes live in a nested list of [row][column] 82 | ex0.axes[2][0].set_xlabel('Main Axis', fontsize=14) 83 | 84 | # Compact the plot 85 | ex0.fig.subplots_adjust(hspace=-0.3) 86 | 87 | .. image:: https://cloud.githubusercontent.com/assets/2184487/8859118/f4706b72-3140-11e5-9351-5182977a991c.png 88 | :target: https://cloud.githubusercontent.com/assets/2184487/8859118/f4706b72-3140-11e5-9351-5182977a991c.png 89 | :alt: Single column XGrid 90 | 91 | Two-row ``YGrid`` with frame 92 | ---------------------------- 93 | 94 | .. code-block:: python 95 | 96 | import numpy as np 97 | import matplotlib.pyplot as plt 98 | import trendvis 99 | 100 | # Pseudorandom data 101 | random_generator = np.random.RandomState(seed=1234) 102 | xvals = random_generator.rand(20) 103 | 104 | # Plot attributes 105 | numpts = 20 106 | lw = 1.5 107 | 108 | # Initialize a YGrid 109 | ex1 = trendvis.YGrid([1, 2, 1], yratios=[1, 2], figsize=(5,5)) 110 | 111 | # Convenience function 112 | trendvis.plot_data(ex1, 113 | [[(xvals, np.linspace(2, 18.5, num=numpts), 'blue')], 114 | [(xvals*5, np.linspace(1, 17, num=numpts), 'red')], 115 | [(xvals*10, np.linspace(0.5, 20, num=numpts), 'green')]], 116 | lw=lw, auto_spinecolor=True, markeredgecolor='none', marker='s') 117 | 118 | # Remove extra spines, color stack (y) ticks 119 | ex1.cleanup_grid() 120 | ex1.set_spinewidth(lw) 121 | 122 | # Tick, tick label formatting 123 | ex1.set_all_ticknums([(0.2, 0.1), (1, 0.5), (2, 1)], [(2, 1), (2, 1)]) 124 | ex1.set_ticks(major_dim=(7, 3), minor_dim=(4, 2)) 125 | ex1.set_ylim([(0, 15, 20), (1, 0, 11)]) 126 | 127 | # Axes labels 128 | ex1.set_xlabels(['stack axis 0', 'stack axis 1', 'stack axis 2']) 129 | ex1.axes[0][0].set_ylabel('Main Axis 0', fontsize=14) 130 | ex1.axes[2][1].set_ylabel('Main Axis 1', fontsize=14, 131 | rotation=270, labelpad=14) 132 | 133 | # Draw boxes around each row 134 | ex1.draw_frame() 135 | 136 | # Broken axis cutout marks also available, try this instead of the frame: 137 | # ex0.draw_cutout(di=0.05) 138 | 139 | # Compact the plot 140 | ex1.fig.subplots_adjust(wspace=-0.3) 141 | 142 | .. image:: https://cloud.githubusercontent.com/assets/2184487/8859244/b07c0f9c-3141-11e5-8c1c-7d20f77ce7ee.png 143 | :target: https://cloud.githubusercontent.com/assets/2184487/8859244/b07c0f9c-3141-11e5-8c1c-7d20f77ce7ee.png 144 | :alt: Two-column YGrid 145 | 146 | Three-column ``XGrid`` with advanced features 147 | --------------------------------------------- 148 | 149 | .. code-block:: python 150 | 151 | import numpy as np 152 | import matplotlib.pyplot as plt 153 | import trendvis 154 | 155 | # Make some pseudorandom data 156 | random_generator = np.random.RandomState(seed=123) 157 | yvals = random_generator.rand(40) 158 | yvals1 = np.copy(yvals) 159 | yvals1[20:] = np.array([0.2, 0.3, 0.2, 0.5, 0.34, 0.24, 160 | 0.15, 0.23, 0.26, 0.21] * 2) 161 | numpts = 40 162 | lw = 1.5 163 | x0 = np.linspace(2, 49.5, num=numpts) 164 | x1 = np.linspace(1, 49, num=numpts) 165 | x11 = np.linspace(1.5, 47.5, num=numpts) 166 | twin0 = np.linspace(2, 50, num=numpts) 167 | twin1 = np.linspace(0.5, 48, num=numpts) 168 | 169 | # Initialize XGrid and twin axes 170 | ex2 = trendvis.XGrid([3, 4], xratios=[1, 3, 2], figsize=(5, 5), 171 | startside='right') 172 | ex2.make_twins([0, 1]) 173 | 174 | # Convenience function 175 | trendvis.plot_data(ex2, 176 | [[(x0, yvals, 'blue')], 177 | [(x1, yvals1*5, 'red'), (x11, yvals1*5.2, 'orchid')], 178 | [], 179 | [(twin1, yvals*2, '0.5')]], 180 | lw=lw, marker=None) 181 | 182 | # Adjust twinned y-axis positions for readability 183 | ex2.move_spines(twin_shift=0.6) 184 | 185 | # For any other kind of plot (fill_between, scatter, errorbar, etc), 186 | # get axis and plot directly 187 | # Note: ex2.axes[2][2] == ex2.get_axis(0, xpos=2, is_twin=True) 188 | for ax in ex2.axes[2]: 189 | ax.fill_between(twin0, yvals+0.075, yvals-0.1, 190 | edgecolor='none', color='darkorange') 191 | 192 | # Handle axis ticks 193 | ex2.cleanup_grid() 194 | ex2.set_spinewidth(lw) 195 | ex2.autocolor_spines() 196 | ex2.set_all_ticknums([(2, 1), (2, 1), (2, 1)], 197 | [(0.2, 0.1), (1, 0.5), (1, 0.25), (0.5, 0.25)]) 198 | ex2.set_ticks(major_dim=(6, 1.5), minor_dim=(3, 1)) 199 | 200 | ex2.set_ylabels(['row 0', 'row 1', 'twin row 0', 'twin row 1']) 201 | 202 | # Rotate x-axis tick labels 203 | for ax in ex2.fig.axes: 204 | plt.setp(ax.xaxis.get_majorticklabels(), rotation=45) 205 | 206 | # Draw a vertical bar behind the data - horizontal bars available too 207 | ex2.draw_bar(ex2.axes[1][2], ex2.axes[0][2], (45, 47), color='lightblue') 208 | 209 | # Ok to set axis limits after drawing on figure using TrendVis methods, 210 | # TrendVis will reset the bar to the right place! 211 | ex2.set_xlim([(0, 0, 3), (1, 13, 24), (2, 43, 50)]) 212 | ex2.set_ylim([(2, 0, 2)]) 213 | 214 | # matplotlib annotations supported 215 | ex2.get_axis(0).text(0, 0.75, 'Text') 216 | 217 | # Cutouts instead of frames 218 | ex2.draw_cutout(lw=lw) 219 | 220 | # Set the suptitle and compact the plot 221 | ex2.fig.suptitle('Title', fontsize=16, y=1.05); 222 | ex2.fig.subplots_adjust(hspace=-0.1) 223 | 224 | .. image:: https://cloud.githubusercontent.com/assets/2184487/8860699/097e51fa-314b-11e5-93e5-eb158aa5b801.png 225 | :target: https://cloud.githubusercontent.com/assets/2184487/8860699/097e51fa-314b-11e5-93e5-eb158aa5b801.png 226 | :alt: Three-column XGrid with advanced features 227 | 228 | =========================== 229 | Examples in Published Works 230 | =========================== 231 | *Great Basin hydrology, paleoclimate, and connections with the North Atlantic: A speleothem stable isotope and trace element record from Lehman Caves, NV* by Mellissa Cross, David McGee, Wallace S. Broecker, Jay Quade, Jeremy D. Shakun, Hai Cheng, Yanbin Lu, and R. Lawrence Edwards. doi:`10.1016/j.quascirev.2015.06.016 `_ 232 | Figures 2, 3, 4, 5, and panels 1 and 2 in figure 6 made with TrendVis. 233 | 234 | Additional references to works containing TrendVis figures are welcome! 235 | 236 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/trendvis.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/trendvis.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/trendvis" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/trendvis" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /doc/_templates/autosummary/class.rst: -------------------------------------------------------------------------------- 1 | {% extends "!autosummary/class.rst" %} 2 | 3 | {% block methods %} 4 | {% if methods %} 5 | 6 | .. HACK -- the point here is that we don't want this to appear in the output, but the autosummary should still generate the pages. 7 | .. autosummary:: 8 | :toctree: 9 | {% for item in all_methods %} 10 | {%- if not item.startswith('_') or item in ['__call__'] %} 11 | {{ name }}.{{ item }} 12 | {%- endif -%} 13 | {%- endfor %} 14 | 15 | {% endif %} 16 | {% endblock %} 17 | 18 | {% block attributes %} 19 | {% if attributes %} 20 | 21 | .. HACK -- the point here is that we don't want this to appear in the output, but the autosummary should still generate the pages. 22 | .. autosummary:: 23 | :toctree: 24 | {% for item in all_attributes %} 25 | {%- if not item.startswith('_') %} 26 | {{ name }}.{{ item }} 27 | {%- endif -%} 28 | {%- endfor %} 29 | 30 | {% endif %} 31 | {% endblock %} 32 | -------------------------------------------------------------------------------- /doc/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source 10 | set I18NSPHINXOPTS=%SPHINXOPTS% source 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | goto end 41 | ) 42 | 43 | if "%1" == "clean" ( 44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 45 | del /q /s %BUILDDIR%\* 46 | goto end 47 | ) 48 | 49 | 50 | %SPHINXBUILD% 2> nul 51 | if errorlevel 9009 ( 52 | echo. 53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 54 | echo.installed, then set the SPHINXBUILD environment variable to point 55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 56 | echo.may add the Sphinx directory to PATH. 57 | echo. 58 | echo.If you don't have Sphinx installed, grab it from 59 | echo.http://sphinx-doc.org/ 60 | exit /b 1 61 | ) 62 | 63 | if "%1" == "html" ( 64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 68 | goto end 69 | ) 70 | 71 | if "%1" == "dirhtml" ( 72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 76 | goto end 77 | ) 78 | 79 | if "%1" == "singlehtml" ( 80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 84 | goto end 85 | ) 86 | 87 | if "%1" == "pickle" ( 88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can process the pickle files. 92 | goto end 93 | ) 94 | 95 | if "%1" == "json" ( 96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 97 | if errorlevel 1 exit /b 1 98 | echo. 99 | echo.Build finished; now you can process the JSON files. 100 | goto end 101 | ) 102 | 103 | if "%1" == "htmlhelp" ( 104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 105 | if errorlevel 1 exit /b 1 106 | echo. 107 | echo.Build finished; now you can run HTML Help Workshop with the ^ 108 | .hhp project file in %BUILDDIR%/htmlhelp. 109 | goto end 110 | ) 111 | 112 | if "%1" == "qthelp" ( 113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 117 | .qhcp project file in %BUILDDIR%/qthelp, like this: 118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\trendvis.qhcp 119 | echo.To view the help file: 120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\trendvis.ghc 121 | goto end 122 | ) 123 | 124 | if "%1" == "devhelp" ( 125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished. 129 | goto end 130 | ) 131 | 132 | if "%1" == "epub" ( 133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 137 | goto end 138 | ) 139 | 140 | if "%1" == "latex" ( 141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 145 | goto end 146 | ) 147 | 148 | if "%1" == "latexpdf" ( 149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 150 | cd %BUILDDIR%/latex 151 | make all-pdf 152 | cd %BUILDDIR%/.. 153 | echo. 154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 155 | goto end 156 | ) 157 | 158 | if "%1" == "latexpdfja" ( 159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 160 | cd %BUILDDIR%/latex 161 | make all-pdf-ja 162 | cd %BUILDDIR%/.. 163 | echo. 164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 165 | goto end 166 | ) 167 | 168 | if "%1" == "text" ( 169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 170 | if errorlevel 1 exit /b 1 171 | echo. 172 | echo.Build finished. The text files are in %BUILDDIR%/text. 173 | goto end 174 | ) 175 | 176 | if "%1" == "man" ( 177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 178 | if errorlevel 1 exit /b 1 179 | echo. 180 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 181 | goto end 182 | ) 183 | 184 | if "%1" == "texinfo" ( 185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 186 | if errorlevel 1 exit /b 1 187 | echo. 188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 189 | goto end 190 | ) 191 | 192 | if "%1" == "gettext" ( 193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 194 | if errorlevel 1 exit /b 1 195 | echo. 196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 197 | goto end 198 | ) 199 | 200 | if "%1" == "changes" ( 201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 202 | if errorlevel 1 exit /b 1 203 | echo. 204 | echo.The overview file is in %BUILDDIR%/changes. 205 | goto end 206 | ) 207 | 208 | if "%1" == "linkcheck" ( 209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 210 | if errorlevel 1 exit /b 1 211 | echo. 212 | echo.Link check complete; look for any errors in the above output ^ 213 | or in %BUILDDIR%/linkcheck/output.txt. 214 | goto end 215 | ) 216 | 217 | if "%1" == "doctest" ( 218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 219 | if errorlevel 1 exit /b 1 220 | echo. 221 | echo.Testing of doctests in the sources finished, look at the ^ 222 | results in %BUILDDIR%/doctest/output.txt. 223 | goto end 224 | ) 225 | 226 | if "%1" == "xml" ( 227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 228 | if errorlevel 1 exit /b 1 229 | echo. 230 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 231 | goto end 232 | ) 233 | 234 | if "%1" == "pseudoxml" ( 235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 236 | if errorlevel 1 exit /b 1 237 | echo. 238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 239 | goto end 240 | ) 241 | 242 | :end 243 | -------------------------------------------------------------------------------- /doc/source/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # trendvis documentation build configuration file, created by 5 | # sphinx-quickstart on Wed Jul 1 13:32:53 2015. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | import sys 17 | import os 18 | 19 | # If extensions (or modules to document with autodoc) are in another directory, 20 | # add these directories to sys.path here. If the directory is relative to the 21 | # documentation root, use os.path.abspath to make it absolute, like shown here. 22 | #sys.path.insert(0, os.path.abspath('.')) 23 | 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | needs_sphinx = '1.3' 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be 30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 31 | # ones. 32 | extensions = [ 33 | 'sphinx.ext.autodoc', 34 | 'sphinx.ext.intersphinx', 35 | 'sphinx.ext.coverage', 36 | 'sphinx.ext.mathjax', 37 | 'sphinx.ext.viewcode', 38 | 'sphinx.ext.autosummary', 39 | 'matplotlib.sphinxext.plot_directive', 40 | 'IPython.sphinxext.ipython_directive', 41 | 'IPython.sphinxext.ipython_console_highlighting', 42 | 'numpydoc', 43 | ] 44 | 45 | 46 | autosummary_generate = True 47 | 48 | numpydoc_show_class_members = False 49 | autodoc_default_flags = ['members'] 50 | 51 | 52 | # Add any paths that contain templates here, relative to this directory. 53 | templates_path = ['_templates'] 54 | 55 | # The suffix of source filenames. 56 | source_suffix = '.rst' 57 | 58 | # The encoding of source files. 59 | #source_encoding = 'utf-8-sig' 60 | 61 | # The master toctree document. 62 | master_doc = 'index' 63 | 64 | # General information about the project. 65 | project = 'trendvis' 66 | copyright = '2015, Mellissa Cross' 67 | 68 | # The version info for the project you're documenting, acts as replacement for 69 | # |version| and |release|, also used in various other places throughout the 70 | # built documents. 71 | # 72 | # The short X.Y version. 73 | version = '0.9.0' 74 | # The full version, including alpha/beta/rc tags. 75 | release = '0.9.0' 76 | 77 | # The language for content autogenerated by Sphinx. Refer to documentation 78 | # for a list of supported languages. 79 | #language = None 80 | 81 | # There are two options for replacing |today|: either, you set today to some 82 | # non-false value, then it is used: 83 | #today = '' 84 | # Else, today_fmt is used as the format for a strftime call. 85 | #today_fmt = '%B %d, %Y' 86 | 87 | # List of patterns, relative to source directory, that match files and 88 | # directories to ignore when looking for source files. 89 | exclude_patterns = [] 90 | 91 | # The reST default role (used for this markup: `text`) to use for all 92 | # documents. 93 | default_role = 'obj' 94 | 95 | # If true, '()' will be appended to :func: etc. cross-reference text. 96 | #add_function_parentheses = True 97 | 98 | # If true, the current module name will be prepended to all description 99 | # unit titles (such as .. function::). 100 | #add_module_names = True 101 | 102 | # If true, sectionauthor and moduleauthor directives will be shown in the 103 | # output. They are ignored by default. 104 | #show_authors = False 105 | 106 | # The name of the Pygments (syntax highlighting) style to use. 107 | pygments_style = 'sphinx' 108 | 109 | # A list of ignored prefixes for module index sorting. 110 | #modindex_common_prefix = [] 111 | 112 | # If true, keep warnings as "system message" paragraphs in the built documents. 113 | #keep_warnings = False 114 | 115 | 116 | # -- Options for HTML output ---------------------------------------------- 117 | 118 | # The theme to use for HTML and HTML Help pages. See the documentation for 119 | # a list of builtin themes. 120 | # html_theme = 'basic' 121 | 122 | # Theme options are theme-specific and customize the look and feel of a theme 123 | # further. For a list of options available for each theme, see the 124 | # documentation. 125 | #html_theme_options = {} 126 | 127 | # Add any paths that contain custom themes here, relative to this directory. 128 | #html_theme_path = [] 129 | 130 | # The name for this set of Sphinx documents. If None, it defaults to 131 | # " v documentation". 132 | #html_title = None 133 | 134 | # A shorter title for the navigation bar. Default is the same as html_title. 135 | #html_short_title = None 136 | 137 | # The name of an image file (relative to this directory) to place at the top 138 | # of the sidebar. 139 | #html_logo = None 140 | 141 | # The name of an image file (within the static path) to use as favicon of the 142 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 143 | # pixels large. 144 | #html_favicon = None 145 | 146 | # Add any paths that contain custom static files (such as style sheets) here, 147 | # relative to this directory. They are copied after the builtin static files, 148 | # so a file named "default.css" will overwrite the builtin "default.css". 149 | html_static_path = ['_static'] 150 | 151 | # Add any extra paths that contain custom files (such as robots.txt or 152 | # .htaccess) here, relative to this directory. These files are copied 153 | # directly to the root of the documentation. 154 | #html_extra_path = [] 155 | 156 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 157 | # using the given strftime format. 158 | #html_last_updated_fmt = '%b %d, %Y' 159 | 160 | # If true, SmartyPants will be used to convert quotes and dashes to 161 | # typographically correct entities. 162 | #html_use_smartypants = True 163 | 164 | # Custom sidebar templates, maps document names to template names. 165 | #html_sidebars = {} 166 | 167 | # Additional templates that should be rendered to pages, maps page names to 168 | # template names. 169 | #html_additional_pages = {} 170 | 171 | # If false, no module index is generated. 172 | #html_domain_indices = True 173 | 174 | # If false, no index is generated. 175 | #html_use_index = True 176 | 177 | # If true, the index is split into individual pages for each letter. 178 | #html_split_index = False 179 | 180 | # If true, links to the reST sources are added to the pages. 181 | #html_show_sourcelink = True 182 | 183 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 184 | #html_show_sphinx = True 185 | 186 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 187 | #html_show_copyright = True 188 | 189 | # If true, an OpenSearch description file will be output, and all pages will 190 | # contain a tag referring to it. The value of this option must be the 191 | # base URL from which the finished HTML is served. 192 | #html_use_opensearch = '' 193 | 194 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 195 | #html_file_suffix = None 196 | 197 | # Output file base name for HTML help builder. 198 | htmlhelp_basename = 'trendvisdoc' 199 | 200 | 201 | # -- Options for LaTeX output --------------------------------------------- 202 | 203 | latex_elements = { 204 | # The paper size ('letterpaper' or 'a4paper'). 205 | #'papersize': 'letterpaper', 206 | 207 | # The font size ('10pt', '11pt' or '12pt'). 208 | #'pointsize': '10pt', 209 | 210 | # Additional stuff for the LaTeX preamble. 211 | #'preamble': '', 212 | } 213 | 214 | # Grouping the document tree into LaTeX files. List of tuples 215 | # (source start file, target name, title, 216 | # author, documentclass [howto, manual, or own class]). 217 | latex_documents = [ 218 | ('index', 'trendvis.tex', 'trendvis Documentation', 219 | 'Mellissa Cross', 'manual'), 220 | ] 221 | 222 | # The name of an image file (relative to this directory) to place at the top of 223 | # the title page. 224 | #latex_logo = None 225 | 226 | # For "manual" documents, if this is true, then toplevel headings are parts, 227 | # not chapters. 228 | #latex_use_parts = False 229 | 230 | # If true, show page references after internal links. 231 | #latex_show_pagerefs = False 232 | 233 | # If true, show URL addresses after external links. 234 | #latex_show_urls = False 235 | 236 | # Documents to append as an appendix to all manuals. 237 | #latex_appendices = [] 238 | 239 | # If false, no module index is generated. 240 | #latex_domain_indices = True 241 | 242 | 243 | # -- Options for manual page output --------------------------------------- 244 | 245 | # One entry per manual page. List of tuples 246 | # (source start file, name, description, authors, manual section). 247 | man_pages = [ 248 | ('index', 'trendvis', 'trendvis Documentation', 249 | ['Mellissa Cross'], 1) 250 | ] 251 | 252 | # If true, show URL addresses after external links. 253 | #man_show_urls = False 254 | 255 | 256 | # -- Options for Texinfo output ------------------------------------------- 257 | 258 | # Grouping the document tree into Texinfo files. List of tuples 259 | # (source start file, target name, title, author, 260 | # dir menu entry, description, category) 261 | texinfo_documents = [ 262 | ('index', 'trendvis', 'trendvis Documentation', 263 | 'Mellissa Cross', 'trendvis', 'One line description of project.', 264 | 'Miscellaneous'), 265 | ] 266 | 267 | # Documents to append as an appendix to all manuals. 268 | #texinfo_appendices = [] 269 | 270 | # If false, no module index is generated. 271 | #texinfo_domain_indices = True 272 | 273 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 274 | #texinfo_show_urls = 'footnote' 275 | 276 | # If true, do not generate a @detailmenu in the "Top" node's menu. 277 | #texinfo_no_detailmenu = False 278 | 279 | intersphinx_mapping = {'python': ('https://docs.python.org/3.4', None), 280 | 'matplotlb': ('http://matplotlib.org', None)} 281 | 282 | ################# numpydoc config #################### 283 | numpydoc_show_class_members = False 284 | -------------------------------------------------------------------------------- /doc/source/index.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: trendvis 2 | 3 | ========== 4 | TrendVis 5 | ========== 6 | 7 | :py:mod:`trendvis` API 8 | ====================== 9 | 10 | .. autosummary:: 11 | :toctree: generated/ 12 | 13 | XGrid 14 | YGrid 15 | make_grid 16 | plot_data 17 | 18 | 19 | The public API of :py:mod:`trendvis` consists of two classes, `XGrid` and `YGrid`, and two convenience functions :func:`make_grid` and :func:`plot_data`. The preferred interface is through `XGrid` and `YGrid`, but the convenience functions are provided to quickly create and format an `XGrid` or `YGrid` and draw line plots. 20 | 21 | 22 | Motivation 23 | ========== 24 | 25 | 26 | When plotting multiple datasets against a common x or y axis, a figure can quickly become cluttered with overlain curves and/or unnecessary axis spines. `TrendVis` enables construction of complex figures with data plotted in a common plot area against different y or x ('stack') axes and a common x or y (main) axis respectively. 27 | 28 | 29 | Examples 30 | ======== 31 | 32 | Simple XGrid 33 | ------------ 34 | 35 | .. plot:: 36 | :include-source: 37 | 38 | import numpy as np 39 | import matplotlib.pyplot as plt 40 | import trendvis 41 | 42 | # Simple XGrid with frame 43 | yvals = np.random.rand(10) 44 | nums = 10 45 | lw = 1.5 46 | 47 | ex0 = trendvis.XGrid([1,2,1], figsize=(5,5)) 48 | 49 | trendvis.plot_data(ex0, [[(np.linspace(0, 9.5, num=nums), yvals, 'blue')], 50 | [(np.linspace(1, 9, num=nums), yvals*5, 'red')], 51 | [(np.linspace(0.5, 10, num=nums), yvals*10, 'green')]], 52 | lw=1.5, auto_spinecolor=False, markeredgecolor='none', marker='s') 53 | 54 | ex0.cleanup_grid() 55 | ex0.autocolor_spines(ticks_only=True) 56 | 57 | ex0.set_spinewidth(lw) 58 | 59 | ex0.set_all_ticknums([(2, 1)], [(0.2, 0.1), (1, 0.5), (2, 1)]) 60 | ex0.set_ticks(major_dim=(7, 3), minor_dim=(4, 2)) 61 | 62 | ex0.set_ylabels(['axis 0', 'axis 1', 'axis 2']) 63 | ex0.axes[2][0].set_xlabel('Main Axis', fontsize=14) 64 | 65 | ex0.draw_frame() 66 | ex0.fig.subplots_adjust(hspace=-0.3) 67 | 68 | 69 | Simple YGrid 70 | ------------ 71 | Creating a `YGrid` is essentially the same as an `XGrid`. 72 | 73 | .. plot:: 74 | :include-source: 75 | 76 | import numpy as np 77 | import matplotlib.pyplot as plt 78 | import trendvis 79 | 80 | # Simple YGrid without frame 81 | xvals = np.random.rand(10) 82 | nums = 10 83 | lw = 1.5 84 | 85 | ex0 = trendvis.YGrid([1,2,1], figsize=(5,5)) 86 | 87 | trendvis.plot_data(ex0, [[(xvals, np.linspace(0, 9.5, num=nums), 'blue')], 88 | [(xvals*5, np.linspace(1, 9, num=nums), 'red')], 89 | [(xvals*10, np.linspace(0.5, 10, num=nums),'green')]], 90 | lw=1.5, auto_spinecolor=True, markeredgecolor='none', marker='s') 91 | 92 | ex0.cleanup_grid() 93 | ex0.set_spinewidth(lw) 94 | 95 | ex0.set_all_ticknums([(0.2, 0.1), (1, 0.5), (2, 1)], [(2, 1)]) 96 | ex0.set_ticks(major_dim=(7, 3), minor_dim=(4, 2)) 97 | 98 | ex0.set_xlabels(['axis 0', 'axis 1', 'axis 2']) 99 | ex0.axes[0][0].set_ylabel('Main Axis', fontsize=14) 100 | 101 | ex0.fig.subplots_adjust(wspace=-0.3) 102 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | # These are required for developing the package (running the tests, building 2 | # the documentation) but not necessarily required for _using_ it. 3 | codecov 4 | coverage 5 | flake8 6 | pytest 7 | sphinx 8 | twine 9 | black 10 | # These are dependencies of various sphinx extensions for documentation. 11 | -r requirements-doc.txt 12 | -------------------------------------------------------------------------------- /requirements-doc.txt: -------------------------------------------------------------------------------- 1 | # List required packages in this file, one per line. 2 | ipython 3 | jinja2>3 4 | matplotlib 5 | mpl-sphinx-theme 6 | numpydoc 7 | sphinx 8 | sphinx-copybutton 9 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | import os 4 | import sys 5 | import re 6 | import setuptools 7 | from setuptools import setup 8 | 9 | 10 | descr = """TrendVis (a.k.a. `trendvis`): Complex plotting in matplotlib. 11 | 12 | This package is designed to programmatically create complex, publication- 13 | quality figures using matplotlib. TrendVis' speciality is creating 14 | multiple vertically or horizontally offset plots with a 15 | common x or y axis, respectively, in what is visually one compact 16 | plotting area, facilitating comparisons among various datasets. 17 | """ 18 | 19 | 20 | DISTNAME = 'TrendVis' 21 | DESCRIPTION = 'Publication-quality data trend visualization' 22 | LONG_DESCRIPTION = descr 23 | MAINTAINER = 'Mellissa Cross' 24 | MAINTAINER_EMAIL = 'mellissa.cross@gmail.com' 25 | LICENSE = 'Modified BSD' 26 | DOWNLOAD_URL = '' 27 | VERSION = '0.2.2' 28 | PYTHON_VERSION = (2, 6) 29 | DEPENDENCIES = {'matplotlib': (1, 2)} 30 | 31 | 32 | def check_requirements(): 33 | if sys.version_info < PYTHON_VERSION: 34 | raise SystemExit('Python version %d.%d required; found %d.%d.' 35 | % (PYTHON_VERSION[0], PYTHON_VERSION[1], 36 | sys.version_info[0], sys.version_info[1])) 37 | 38 | for package_name, min_version in DEPENDENCIES.items(): 39 | dep_err = False 40 | try: 41 | package = __import__(package_name) 42 | except ImportError: 43 | dep_err = True 44 | else: 45 | package_version = get_package_version(package) 46 | if min_version > package_version: 47 | dep_err = True 48 | 49 | if dep_err: 50 | raise ImportError('`%s` version %d.%d or later required.' 51 | % ((package_name, ) + min_version)) 52 | 53 | 54 | def get_package_version(package): 55 | version = [] 56 | for version_attr in ('version', 'VERSION', '__version__'): 57 | if hasattr(package, version_attr) \ 58 | and isinstance(getattr(package, version_attr), str): 59 | version_info = getattr(package, version_attr, '') 60 | for part in re.split('\D+', version_info): 61 | try: 62 | version.append(int(part)) 63 | except ValueError: 64 | pass 65 | 66 | return tuple(version) 67 | 68 | 69 | def write_version_py(filename='trendvis/version.py'): 70 | template = """# THIS FILE IS GENERATED FROM THE TRENDVIS SETUP.PY 71 | version='%s' 72 | """ 73 | 74 | vfile = open(os.path.join(os.path.dirname(__file__), 75 | filename), 'w') 76 | 77 | try: 78 | vfile.write(template % VERSION) 79 | finally: 80 | vfile.close() 81 | 82 | 83 | if __name__ == '__main__': 84 | check_requirements() 85 | write_version_py() 86 | 87 | setup( 88 | name=DISTNAME, 89 | description=DESCRIPTION, 90 | long_description=LONG_DESCRIPTION, 91 | maintainer=MAINTAINER, 92 | maintainer_email=MAINTAINER_EMAIL, 93 | license=LICENSE, 94 | download_url=DOWNLOAD_URL, 95 | version=VERSION, 96 | 97 | classifiers=[ 98 | 'Development Status :: 4 - Beta', 99 | 'Environment :: Console', 100 | 'Intended Audience :: Developers', 101 | 'Intended Audience :: Science/Research', 102 | 'License :: OSI Approved :: BSD License', 103 | 'Programming Language :: Python', 104 | 'Programming Language :: Python :: 3', 105 | 'Topic :: Scientific/Engineering', 106 | 'Operating System :: Microsoft :: Windows', 107 | 'Operating System :: POSIX', 108 | 'Operating System :: Unix', 109 | 'Operating System :: MacOS'], 110 | 111 | 112 | packages=setuptools.find_packages(), 113 | include_package_data=True, 114 | zip_safe=False 115 | ) 116 | -------------------------------------------------------------------------------- /trendvis/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | TrendVis package containing tools for creating complex, 3 | publication quality figures in a few styles: 4 | 5 | * y-axes stacked against a common x-axis or a common broken x-axis 6 | (which may be demarcated as breaks or columns) 7 | * x-axes stack3d against a common y-axis or a common broken y-axis 8 | (which may be demarcated as breaks or rows) 9 | 10 | Additionally, TrendVis contains drawing tools to frame subplot area(s) 11 | or highlight various columns, bars, or rectangles of interest. 12 | 13 | """ 14 | 15 | __all__ = ['XGrid', 16 | 'YGrid', 17 | 'make_grid', 18 | 'plot_data'] 19 | 20 | from .xgrid_ystack import XGrid 21 | 22 | from .ygrid_xstack import YGrid 23 | 24 | from .gridwrapper import make_grid, plot_data 25 | -------------------------------------------------------------------------------- /trendvis/gridclass.py: -------------------------------------------------------------------------------- 1 | from __future__ import division, print_function, absolute_import 2 | from matplotlib.ticker import MultipleLocator 3 | import matplotlib.pyplot as plt 4 | import matplotlib.lines as lines 5 | 6 | 7 | class Grid(object): 8 | """ 9 | Superclass for ``YGrid`` and ``XGrid``. 10 | 11 | """ 12 | 13 | def __init__(self, xratios, yratios, mainax_x, figsize, **kwargs): 14 | """ 15 | Initialize grid attributes. Should only be called through 16 | ``XGrid`` or ``YGrid`` subclasses. 17 | 18 | Parameters 19 | ---------- 20 | xratios : int or list of ints 21 | The relative sizes of the columns. Not directly comparable 22 | to ``yratios`` 23 | yratios : int or list of ints 24 | The relative sizes of the rows. Not directly comparable to 25 | ``xratios`` 26 | mainax_x : Boolean 27 | [True|False]. Indicates if x is the main axis. Determines 28 | some attributes 29 | figsize : tuple of ints or floats 30 | The figure dimensions in inches. If None, defaults to matplotlib 31 | rc figure.figsize. 32 | **kwargs 33 | Any plt.figure arguments 34 | 35 | """ 36 | figsize = figsize or plt.rcParams['figure.figsize'] 37 | self.fig = plt.figure(figsize=figsize) 38 | 39 | self.gridrows, self.yratios = self._ratios_arelists(yratios) 40 | self.gridcols, self.xratios = self._ratios_arelists(xratios) 41 | 42 | self.numrows = len(self.yratios) 43 | self.numcols = len(self.xratios) 44 | 45 | self.axes = [] 46 | 47 | self.bf_urcorners = [] 48 | self.bf_llcorners = [] 49 | self.bf_patchinds = [] 50 | self.bf_uraxis = [] 51 | self.bf_llaxis = [] 52 | 53 | self.relative_shifts = None 54 | self.stack_shifts = None 55 | 56 | self.twinds = None 57 | self.twin_dim = 0 58 | self.reltwin_shifts = None 59 | self.twin_shifts = None 60 | 61 | self.grid_isclean = False 62 | 63 | self.spinelist = ['top', 'bottom', 'left', 'right'] 64 | self.spinewidth = 1 65 | 66 | if mainax_x: 67 | self.mainax_id = 'x' 68 | self.stackax_id = 'y' 69 | self.stackdim = self.numrows 70 | self.mainax_dim = self.numcols 71 | self.sp1 = 'right' 72 | self.sp2 = 'left' 73 | 74 | self.startpos = 'top' 75 | 76 | self.mainax_ticks = {'top' : ('on', 'off'), 77 | 'both' : ('on', 'on'), 78 | 'bottom': ('off', 'on'), 79 | 'none' : ('off', 'off')} 80 | 81 | self.alt_sides = {'left' : 'right', 82 | 'right': 'left'} 83 | 84 | self.side_inds = {'left' : 0, 85 | 'right': -1} 86 | 87 | self.spine_begone = {'top' : {'left' : ['bottom', 'right'], 88 | 'right': ['bottom', 'left'], 89 | 'none' : ['bottom', 'left', 90 | 'right']}, 91 | 'none' : {'left' : ['top', 'bottom', 'right'], 92 | 'right': ['top', 'bottom', 'left'], 93 | 'none' : ['top', 'bottom', 'left', 94 | 'right']}, 95 | 'bottom': {'left' : ['top', 'right'], 96 | 'right': ['top', 'left'], 97 | 'none' : ['top', 'left', 98 | 'right']}, 99 | 'both' : {'left' : ['right'], 100 | 'right': ['left'], 101 | 'none' : ['right', 'left']}} 102 | else: 103 | self.mainax_id = 'y' 104 | self.stackax_id = 'x' 105 | self.stackdim = self.numcols 106 | self.mainax_dim = self.numrows 107 | self.sp1 = 'top' 108 | self.sp2 = 'bottom' 109 | 110 | self.startpos = 'left' 111 | 112 | self.mainax_ticks = {'left' : ('on', 'off'), 113 | 'both' : ('on', 'on'), 114 | 'right': ('off', 'on'), 115 | 'none' : ('off', 'off')} 116 | 117 | self.alt_sides = {'top' : 'bottom', 118 | 'bottom': 'top'} 119 | 120 | self.side_inds = {'top' : 0, 121 | 'bottom': -1} 122 | 123 | self.spine_begone = {'left' : {'top' : ['bottom', 'right'], 124 | 'bottom': ['top', 'right'], 125 | 'none' : ['bottom', 'top', 126 | 'right']}, 127 | 'none' : {'top' : ['bottom', 'left', 128 | 'right'], 129 | 'bottom': ['top', 'left', 'right'], 130 | 'none' : ['top', 'bottom', 'left', 131 | 'right']}, 132 | 'right': {'top' : ['bottom', 'left'], 133 | 'bottom': ['top', 'left'], 134 | 'none' : ['top', 'bottom', 135 | 'left']}, 136 | 'both' : {'top' : ['bottom'], 137 | 'bottom': ['top'], 138 | 'none' : ['top', 'bottom']}} 139 | 140 | self._update_total_stackdim() 141 | 142 | def set_dataside(self, startside, alternate_sides): 143 | """ 144 | Set the ``dataside_list`` that indicates which stacked ax spine will be 145 | the visible data spine. 146 | 147 | Parameters 148 | ---------- 149 | startside : string 150 | ['left'|'right'] or ['top'|'bottom']. The side the first row 151 | (column) in ``self.axes`` will have its y (x) axis spine on. 152 | alternate_sides : Boolean 153 | [True|False]. Stacked axis spines alternate sides or are all on 154 | ``startside``. 155 | 156 | """ 157 | 158 | self.dataside_list = [startside] 159 | 160 | if alternate_sides: 161 | for i in range(1, self.stackdim): 162 | newside = self.alt_sides[self.dataside_list[i - 1]] 163 | self.dataside_list.append(newside) 164 | 165 | else: 166 | self.dataside_list = self.dataside_list * self.stackdim 167 | 168 | self._update_twinsides() 169 | 170 | def set_stackposition(self, onespine_forboth): 171 | """ 172 | Set ``self.stackpos_list`` indicating which non-stacked axes are shown. 173 | 174 | Parameters 175 | ---------- 176 | onespine_forboth : Boolean 177 | [True|False]. If the plot stack is only 1 row (column), then 178 | both main axis spines may be visible (False), or only the bottom 179 | (left) spine. 180 | 181 | """ 182 | 183 | alt_pos = {'left': 'right', 184 | 'top' : 'bottom'} 185 | 186 | # Position list in the case of a stack of 1 187 | if self.stackdim == 1: 188 | if onespine_forboth: 189 | if self.mainax_id == 'x': 190 | self.startpos = 'bottom' 191 | self.stackpos_list = [self.startpos] 192 | else: 193 | self.stackpos_list = ['both'] 194 | 195 | else: 196 | num_nones = self.stackdim - 2 197 | self.stackpos_list = ([self.startpos] + ['none'] * num_nones + 198 | [alt_pos[self.startpos]]) 199 | 200 | def move_spines(self, axis_shift=None, twin_shift=None): 201 | """ 202 | Wrapper around self.set_relative_axshift(), 203 | self.set_absolute_axshift(), self.excecute_spineshift() 204 | 205 | Parameters 206 | ----------------- 207 | axis_shift : float or list of floats 208 | Default None. Set universal (float) or individual (list of 209 | floats where len(``axis_shift``) = ``self.stackdim``) 210 | axis spine shift 211 | twin_shift : float or list of floats 212 | Default None. Set universal (float) or individual (list of 213 | floats where len(``twin_shift``) = ``self.twin_dim``) 214 | twinned axis spine shift. 215 | """ 216 | 217 | self.set_relative_axshift(axis_shift=axis_shift, twin_shift=twin_shift) 218 | self.set_absolute_axshift() 219 | self.excecute_spineshift() 220 | 221 | def set_relative_axshift(self, axis_shift=None, twin_shift=None): 222 | """ 223 | Set relative shift of stacked axis spines. 224 | 225 | Parameters 226 | ----------------- 227 | axis_shift : float or list of floats 228 | Default None. Set universal (float) or individual (list of 229 | floats where len(``axis_shift``) = ``self.stackdim``) 230 | axis spine shift 231 | twin_shift : float or list of floats 232 | Default None. Set universal (float) or individual (list of 233 | floats where len(``twin_shift``) = ``self.twin_dim``) 234 | twinned axis spine shift. 235 | 236 | """ 237 | 238 | if axis_shift is not None: 239 | try: 240 | # Check if iterable 241 | axis_shift[0] 242 | except: 243 | # if not a list, apply same shift to both 244 | self.relative_shifts = [axis_shift] * self.stackdim 245 | else: 246 | if len(axis_shift) != self.stackdim: 247 | print('Warning: len(axis_shift) != ' + str(self.stackdim)) 248 | self.relative_shifts = [axis_shift[0]] * self.stackdim 249 | else: 250 | self.relative_shifts = axis_shift 251 | 252 | if twin_shift is not None and self.twinds is not None: 253 | try: 254 | twin_shift[0] 255 | except: 256 | self.reltwin_shifts = [twin_shift] * self.twin_dim 257 | else: 258 | if len(twin_shift) != self.twin_dim: 259 | print('Warning: len(twin_shift) != number of twinned ax!') 260 | self.reltwin_shifts = [twin_shift[0]] * self.twin_dim 261 | else: 262 | self.reltwin_shifts = twin_shift 263 | 264 | def set_absolute_axshift(self): 265 | """ 266 | Translate ``self.relative_shifts`` to absolute values. Absolute values 267 | are based on which side the axis to be moved appears. 268 | 269 | """ 270 | 271 | if self.relative_shifts is not None: 272 | 273 | # Reset aboslute shifts 274 | self.stack_shifts = [] 275 | 276 | for shift, dataside in zip(self.relative_shifts, 277 | self.dataside_list): 278 | 279 | if dataside == self.sp2: 280 | self.stack_shifts.append(0 - shift) 281 | else: 282 | self.stack_shifts.append(1 + shift) 283 | 284 | if self.reltwin_shifts is not None: 285 | self.twin_shifts = [] 286 | 287 | for shift, dataside in zip(self.reltwin_shifts, 288 | self.dataside_list[self.stackdim:]): 289 | if dataside == self.sp2: 290 | self.twin_shifts.append(0 - shift) 291 | else: 292 | self.twin_shifts.append(1 + shift) 293 | 294 | def excecute_spineshift(self): 295 | """ 296 | Move the stacked spines around. 297 | 298 | """ 299 | 300 | if self.stack_shifts is not None: 301 | 302 | for subgrid, ds, sh in zip(self.axes, self.dataside_list, 303 | self.stack_shifts): 304 | 305 | for ax in subgrid: 306 | ax.spines[ds].set_position(('axes', sh)) 307 | 308 | if self.twin_shifts is not None: 309 | 310 | for subgrid, ds, sh in zip(self.axes[self.stackdim:], 311 | self.dataside_list[self.stackdim:], 312 | self.twin_shifts): 313 | for ax in subgrid: 314 | ax.spines[ds].set_position(('axes', sh)) 315 | 316 | def move_one_spine(self, ax, which, shift): 317 | """ 318 | Move ``which`` stacked ``ax`` spine by relative ``shift``. 319 | 320 | Parameters 321 | ---------- 322 | ax : axes instance 323 | The axes instance which needs a spine moved. 324 | Can be obtained via ``self.get_axis()`` 325 | which : string 326 | If ``XGrid``, ['left'|'right'], ``YGrid`` ['top'|'bottom']. 327 | Used to indentify spine and to calculate outward shift 328 | from ``shift``. 329 | shift : float 330 | Change in position relative to figure size. 331 | 332 | """ 333 | 334 | if which is self.sp1: 335 | shift = 1 + shift 336 | elif which is self.sp2: 337 | shift = 0 - shift 338 | else: 339 | raise ValueError('Not a stacked ax spine') 340 | 341 | ax.spines[which].set_position(('axes', shift)) 342 | 343 | def reset_spineshift(self): 344 | """ 345 | Reset all spines to normal position. 346 | 347 | """ 348 | 349 | shifts = {'x': {'left' : 0.0, 350 | 'right' : 1.0}, 351 | 'y': {'bottom' : 0.0, 352 | 'top' : 1.0}} 353 | 354 | sd = shifts[self.mainax_id] 355 | 356 | for subgrid in self.axes: 357 | for ax in subgrid: 358 | for key in sd: 359 | ax.spines[key].set_position(('axes', sd[key])) 360 | 361 | self.relative_shifts = None 362 | self.stack_shifts = None 363 | self.reltwin_shifts = None 364 | self.twin_shifts = None 365 | 366 | def reveal_spines(self): 367 | """ 368 | Undo the spine-hiding actions of ``self.cleanup_grid()``. 369 | 370 | """ 371 | 372 | for subgrid in self.axes: 373 | for ax in subgrid: 374 | for sp in self.spinelist: 375 | ax.spines[sp].set_visible(True) 376 | 377 | self.grid_isclean = False 378 | 379 | def set_ax_visibility(self, ax, which, visible): 380 | """ 381 | Hide (``visible``=``False``) or show (``visible``=``True``) an axis 382 | side (``which``). Will hide/show spine, ticks, and ticklabels. 383 | 384 | Parameters 385 | ---------- 386 | ax : axes instance 387 | The axes instance to set spine, tick visibility for. 388 | Can be obtained via ``self.get_axis()`` 389 | which : string 390 | The axis spine, ticks, ticklabels to hide/show. 391 | ['left'|'right'|'top'|'bottom'] 392 | visible : Boolean 393 | Set visible or invisible 394 | 395 | """ 396 | 397 | ax.spines[which].set_visible(visible) 398 | 399 | if which == 'left' or which == 'right': 400 | if visible: 401 | ytick_dict = {'both' : {'left' : 'both', 402 | 'right': 'both'}, 403 | 'left' : {'left' : 'left', 404 | 'right': 'both'}, 405 | 'right' : {'left' : 'both', 406 | 'right': 'right'}, 407 | 'unknown': {'left' : 'left', 408 | 'right': 'right'}, 409 | 'default': {'left' : 'both', 410 | 'right': 'both'}} 411 | else: 412 | ytick_dict = {'both' : {'left' : 'right', 413 | 'right': 'left'}, 414 | 'left' : {'left' : 'none', 415 | 'right': 'left'}, 416 | 'right' : {'left' : 'right', 417 | 'right': 'none'}, 418 | 'unknown': {'left' : 'none', 419 | 'right': 'none'}, 420 | 'default': {'left' : 'right', 421 | 'right': 'left'}} 422 | # Key is new tick pos, value is labelright, labelleft 423 | ylabeldict = {'left' : ['off', 'on'], 424 | 'right': ['on', 'off'], 425 | 'both' : ['on', 'on'], 426 | 'none' : ['off', 'off']} 427 | 428 | old_tickpos = ax.yaxis.get_ticks_position() 429 | new_tickpos = ytick_dict[old_tickpos][which] 430 | ax.yaxis.set_ticks_position(new_tickpos) 431 | r, f = ylabeldict[new_tickpos] 432 | ax.yaxis.set_tick_params(labelright=r, labelleft=f) 433 | self.grid_isclean = False 434 | 435 | if which == 'top' or which == 'bottom': 436 | if visible: 437 | xtick_dict = {'both' : {'top' : 'both', 438 | 'bottom': 'both'}, 439 | 'top' : {'top' : 'top', 440 | 'bottom': 'both'}, 441 | 'bottom' : {'top' : 'both', 442 | 'bottom': 'bottom'}, 443 | 'unknown': {'top' : 'top', 444 | 'bottom': 'bottom'}, 445 | 'default': {'top' : 'both', 446 | 'bottom': 'both'}} 447 | else: 448 | xtick_dict = {'both' : {'top' : 'bottom', 449 | 'bottom': 'top'}, 450 | 'top' : {'top' : 'none', 451 | 'bottom': 'top'}, 452 | 'bottom' : {'top' : 'bottom', 453 | 'bottom': 'none'}, 454 | 'unknown': {'top' : 'none', 455 | 'bottom': 'none'}, 456 | 'default': {'top' : 'bottom', 457 | 'bottom': 'top'}} 458 | # Key is new tick pos, value is labeltop, labelbottom 459 | xlabeldict = {'bottom': ['off', 'on'], 460 | 'top' : ['on', 'off'], 461 | 'none' : ['off', 'off']} 462 | 463 | old_tickpos = ax.xaxis.get_ticks_position() 464 | new_tickpos = xtick_dict[old_tickpos][which] 465 | ax.xaxis.set_ticks_position(new_tickpos) 466 | t, b = xlabeldict[new_tickpos] 467 | ax.xaxis.set_tick_params(labeltop=t, labelbottom=b) 468 | self.grid_isclean = False 469 | 470 | def set_spinewidth(self, spinewidth): 471 | """ 472 | Edit the linewidth of the axis spines. ``self.spinewidth`` used as 473 | default linewidths in drawing frames and cutouts. 474 | 475 | Parameters 476 | ---------- 477 | spinewidth : int 478 | Linewidth in points. 479 | 480 | """ 481 | 482 | for subgrid in self.axes: 483 | for ax in subgrid: 484 | for sp in self.spinelist: 485 | ax.spines[sp].set_linewidth(spinewidth) 486 | 487 | self.spinewidth = spinewidth 488 | 489 | def remove_twins(self): 490 | """ 491 | Get rid of all twinned axes. 492 | 493 | """ 494 | 495 | for subgrid in self.axes[self.stackdim:]: 496 | for ax in subgrid: 497 | self.fig.delaxes(ax) 498 | 499 | self.twinds = None 500 | self.twin_dim = 0 501 | self.reltwin_shifts = None 502 | self.twin_shifts = None 503 | self.dataside_list = self.dataside_list[:self.stackdim] 504 | self.stackpos_list = self.stackpos_list[:self.stackdim] 505 | self.axes = self.axes[:self.stackdim] 506 | 507 | self._update_total_stackdim() 508 | 509 | def set_xaxis_ticknum(self, axis, xticks, scale='linear'): 510 | """ 511 | Set x tick scale and, if linear, major and minor tick locators. 512 | 513 | Parameters 514 | ---------- 515 | axis : ``matplotlib Axes`` instance 516 | ``Axes`` instance to set x-axis scale and potentially 517 | major and minor tick locators. Can get with ``self.get_axis()`` 518 | xticks : tuple 519 | Tuple of (major, minor) x axis tick multiples. 520 | scale : string 521 | Default 'linear'. ['log'|'linear']. X axis scale. 522 | 523 | """ 524 | 525 | if scale == 'linear': 526 | 527 | xmajor_loc = MultipleLocator(xticks[0]) 528 | xminor_loc = MultipleLocator(xticks[1]) 529 | 530 | axis.xaxis.set_major_locator(xmajor_loc) 531 | axis.xaxis.set_minor_locator(xminor_loc) 532 | 533 | else: 534 | axis.set_xscale(scale) 535 | 536 | def set_yaxis_ticknum(self, axis, yticks, scale='linear'): 537 | """ 538 | Set y tick scale and, if linear, major and minor tick locators. 539 | 540 | Parameters 541 | ---------- 542 | axis : ``matplotlib Axes`` instance 543 | ``Axes`` instance to set y-axis scale and potentially 544 | major and minor tick locators. Can get with ``self.get_axis()`` 545 | xticks : tuple 546 | Tuple of (major, minor) y axis tick multiples. 547 | scale : string 548 | Default 'linear'. ['log'|'linear']. Y axis scale. 549 | 550 | """ 551 | 552 | if scale == 'linear': 553 | ymajor_loc = MultipleLocator(yticks[0]) 554 | yminor_loc = MultipleLocator(yticks[1]) 555 | 556 | axis.yaxis.set_major_locator(ymajor_loc) 557 | axis.yaxis.set_minor_locator(yminor_loc) 558 | 559 | else: 560 | axis.set_yscale(scale) 561 | 562 | def autocolor_spines(self, ticks_only=False): 563 | """ 564 | Set the axis stacked ax spine and/or tick color based on the color of 565 | the first line on the axis. 566 | 567 | If no line is found on an axis, then ``autocolor_spines()`` will 568 | default to the color of the first item in the list of axis children. 569 | 570 | Parameters 571 | ---------- 572 | ticks_only : Boolean 573 | Default ``False``. If ``True``, then the tick color will change 574 | and the axis color will not. If ``False``, both will change. 575 | 576 | """ 577 | 578 | for subgrid in self.axes: 579 | for ax in subgrid: 580 | # Default is first child, unless the first line is found 581 | # later among the children. Should account for difference 582 | # among recent versions of matplotlib 583 | child = ax.get_children()[0] 584 | for kid in ax.get_children(): 585 | if isinstance(kid, lines.Line2D): 586 | child = kid 587 | break 588 | try: 589 | color = child.get_color() 590 | except AttributeError: 591 | color = child.get_facecolor() 592 | if len(color) < 3: 593 | color = color[0] 594 | 595 | try: 596 | self.set_axcolor(ax, color, ticks_only=ticks_only) 597 | except: 598 | pass 599 | 600 | def set_axcolor(self, ax, color, ticks_only=False, spines_only=False): 601 | """ 602 | Set the stacked ax spine and tick color of the given Axes. 603 | 604 | Parameters 605 | ---------- 606 | ax : ``matplotlib Axes`` instance 607 | Can get with ``self.get_axis()`` 608 | color : string, tuple of floats 609 | Any color accepted by ``matplotlib``. 610 | ticks_only : Boolean 611 | Default ``False``. If ``True``, then the tick color will change 612 | and the axis color will not. If ``False``, both will change. 613 | 614 | """ 615 | 616 | if not spines_only: 617 | ax.tick_params(axis=self.stackax_id, color=color, which='both') 618 | 619 | if not ticks_only: 620 | ax.spines[self.sp1].set_color(color) 621 | ax.spines[self.sp2].set_color(color) 622 | 623 | def reset_spinecolor(self, spines_only=False): 624 | """ 625 | Reset all spine colors to black. 626 | 627 | """ 628 | 629 | for subgrid in self.axes: 630 | for ax in subgrid: 631 | self.set_axcolor(ax, 'black', spines_only=spines_only) 632 | 633 | def draw_frame(self, lw='default', zorder=-1, edgecolor='black', 634 | facecolor='none', **kwargs): 635 | """ 636 | Draw frame around each column (``XGrid``) or row (``YGrid`) of plot. 637 | 638 | E.g., if ``self.mainax_dim`` == 1, then a frame will be drawn around 639 | the whole figure, visually anchoring axes; if ``self.mainax_dim`` > 1, 640 | then each mainax section will have a frame drawn around it. 641 | 642 | Parameters 643 | ---------- 644 | lw : int 645 | Default 'default'. If default, then ``lw`` will be set to 646 | ``self.spinewidth``. 647 | zorder : int 648 | Default -1. The zorder of the frame. 649 | edgecolor : string or tuple of floats 650 | Default 'black'. Any ``matplotlib``-accepted color. 651 | facecolor : string or tuple of floats 652 | Default 'none'. The background color. Any ``matplotlib``-accepted 653 | color. 654 | **kwargs 655 | Passed to ``plt.Rectangle``; any valid 656 | ``matplotlib.patches.Patch`` kwargs 657 | 658 | """ 659 | 660 | last_instack = self.stackdim - 1 661 | patchlist = self.fig.patches 662 | 663 | if lw == 'default': 664 | lw = self.spinewidth 665 | 666 | for main in range(0, self.mainax_dim): 667 | if self.mainax_id == 'x': 668 | ll_axis = self.axes[last_instack][main] 669 | ur_axis = self.axes[0][main] 670 | else: 671 | ll_axis = self.axes[0][main] 672 | ur_axis = self.axes[last_instack][main] 673 | 674 | lldx = ll_axis.get_xlim()[0] 675 | lldy = ll_axis.get_ylim()[0] 676 | 677 | urdx = ur_axis.get_xlim()[1] 678 | urdy = ur_axis.get_ylim()[1] 679 | 680 | self.bf_llcorners.append((lldx, lldy)) 681 | self.bf_urcorners.append((urdx, urdy)) 682 | self.bf_llaxis.append(ll_axis) 683 | self.bf_uraxis.append(ur_axis) 684 | 685 | ll_corner = self._convert_coords(ll_axis, (lldx, lldy)) 686 | ur_corner = self._convert_coords(ur_axis, (urdx, urdy)) 687 | 688 | width, height = self._rect_dim(ur_corner, ll_corner) 689 | 690 | patchlist.append(plt.Rectangle(ll_corner, width, height, 691 | zorder=zorder, facecolor=facecolor, 692 | edgecolor=edgecolor, lw=lw, 693 | transform=self.fig.transFigure, 694 | **kwargs)) 695 | 696 | self.bf_patchinds.append(len(patchlist) - 1) 697 | 698 | def draw_bar(self, ll_axis, ur_axis, bar_limits, orientation='vertical', 699 | zorder=-1, **kwargs): 700 | """ 701 | Draws vertical or horizontal bars across the ENTIRE plot space, 702 | anchoring them on opposite axes. 703 | 704 | If horizontal (vertical) bars are drawn on ``XGrid`` (``YGrid``), then 705 | adjusting plot spacing afterwards will appear to displace bar, because 706 | they are drawn on the figure and not the axes. 707 | 708 | Parameters 709 | ---------- 710 | ll_axis : ``matplotlib Axes`` instance 711 | The axis that will contain the lower left corner of the bar 712 | ur_axis : ``matplotlib Axes`` instance 713 | The axis that will contain the upper right corner of the bar 714 | bar_limits : length 2 tuple of ints or floats, 715 | The lower, upper data limits of the bar 716 | orientation : string 717 | Default 'vertical'. Indicates the orientation of the long 718 | axis of the bar 719 | zorder : int 720 | Default -1. Zorder of the bar. 721 | **kwargs 722 | Passed to ``plt.Rectangle``; any valid 723 | ``matplotlib.patches.Patch`` kwargs 724 | 725 | """ 726 | 727 | if orientation == 'vertical': 728 | lldx, urdx = bar_limits 729 | lldy = ll_axis.get_ylim()[0] 730 | urdy = ur_axis.get_ylim()[1] 731 | else: 732 | lldy, urdy = bar_limits 733 | lldx = ll_axis.get_xlim()[0] 734 | urdx = ur_axis.get_xlim()[1] 735 | 736 | self.bf_llcorners.append((lldx, lldy)) 737 | self.bf_urcorners.append((urdx, urdy)) 738 | self.bf_llaxis.append(ll_axis) 739 | self.bf_uraxis.append(ur_axis) 740 | 741 | ll_corner = self._convert_coords(ll_axis, (lldx, lldy)) 742 | ur_corner = self._convert_coords(ur_axis, (urdx, urdy)) 743 | 744 | width, height = self._rect_dim(ur_corner, ll_corner) 745 | 746 | self.fig.patches.append(plt.Rectangle(ll_corner, width, height, 747 | zorder=zorder, 748 | transform=self.fig.transFigure, 749 | **kwargs)) 750 | 751 | self.bf_patchinds.append(len(self.fig.patches) - 1) 752 | 753 | def adjust_bar_frame(self): 754 | """ 755 | Bars and frames made via ``self.draw_frame()`` and ``self.draw_bar()`` 756 | are drawn on figure coordinates, so these patches and the axes 757 | move relative to each other when axis limits or subplot spacings are 758 | changed. 759 | 760 | This function realigns existing bars and frames with the original data 761 | coordinates. 762 | 763 | """ 764 | # Check that there are any items to adjust 765 | if len(self.bf_llcorners) > 0: 766 | for ll, ur, llax, urax, ind in zip(self.bf_llcorners, 767 | self.bf_urcorners, 768 | self.bf_llaxis, 769 | self.bf_uraxis, 770 | self.bf_patchinds): 771 | # Grab rectangle 772 | rect = self.fig.patches[ind] 773 | 774 | # Get new figure coordinates 775 | new_x, new_y = self._convert_coords(llax, ll) 776 | ur_corner = self._convert_coords(urax, ur) 777 | 778 | # Get new figure dimensions 779 | width, height = self._rect_dim(ur_corner, (new_x, new_y)) 780 | 781 | rect.set_bounds(new_x, new_y, width, height) 782 | 783 | def _update_twinsides(self): 784 | """ 785 | Update the sides that twinned axes appear on in the event of a 786 | change to ``self.dataside_list``. 787 | 788 | """ 789 | 790 | if self.twinds is not None: 791 | self.dataside_list = self.dataside_list[:self.stackdim] 792 | for ind in self.twinds: 793 | twinside = self.alt_sides[self.dataside_list[ind]] 794 | self.dataside_list.append(twinside) 795 | 796 | def _update_total_stackdim(self): 797 | """ 798 | Update the number of combined original and twinned stacked axes. 799 | 800 | """ 801 | 802 | self.total_stackdim = self.stackdim + self.twin_dim 803 | 804 | def _pop_data_ax(self, subgrid, side): 805 | """ 806 | Pop out data axis from row or column. 807 | 808 | Parameters 809 | ---------- 810 | subgrid : list of ``Axes`` instances 811 | Row or column of ``Axes`` 812 | side : string 813 | The side that the visible stacked spine is on. 814 | 815 | """ 816 | 817 | data_ind = self.side_inds[side] 818 | data_ax = subgrid.pop(data_ind) 819 | 820 | return data_ind, data_ax 821 | 822 | def _replace_data_ax(self, subgrid, data_ind, data_ax): 823 | """ 824 | Put data axis back in its original place in row or column. 825 | 826 | Parameters 827 | ---------- 828 | subgrid : list of axes 829 | Row or column of axes 830 | data_ind : int 831 | [0|-1]. The side that the visible stacked spine is on. 832 | data_ax : ``matplotlib Axes`` instance 833 | The ``Axes`` instance from ``subgrid`` that has the 834 | visible stacked spine. 835 | 836 | """ 837 | 838 | if data_ind == 0: 839 | subgrid.insert(data_ind, data_ax) 840 | else: 841 | subgrid.append(data_ax) 842 | 843 | def _make_lists(self, dim, item, choice1, choice2): 844 | """ 845 | Make a list of ``choice1`` and/or ``choice2`` of length dim. 846 | 847 | Parameters 848 | ---------- 849 | dim : int 850 | The length of the list, usually ``self.mainax_dim`` or 851 | ``self.total_stackdim`` 852 | item : string or list of ints 853 | ['all'|'none'|list of indices]. 854 | Determines whether returned ``itemlist`` is all ``choice1`` 855 | ('none'), all ``choice2`` ('all'), or is 856 | ``choice1`` with ``choice2`` at given indices. 857 | choice1 : any object instance 858 | One of the items to potentially make a list of 859 | choice2 : any object instance 860 | The other item to potentially create a list of 861 | 862 | Returns 863 | ------- 864 | itemlist : list 865 | List of some combination of ``choice1`` and ``choice2``. 866 | 867 | """ 868 | 869 | if item == 'none': 870 | itemlist = [choice1] * dim 871 | elif item == 'all': 872 | itemlist = [choice2] * dim 873 | else: 874 | itemlist = [] 875 | for i in range(0, dim): 876 | if i in item: 877 | itemlist.append(choice2) 878 | else: 879 | itemlist.append(choice1) 880 | 881 | return itemlist 882 | 883 | def _set_ticks(self, subgrid_inds, ax_inds, xy_axis, which, 884 | tick_dim, labelsize, pad, direction): 885 | """ 886 | Set the x and/or y axis major and/or minor ticks at the given 887 | ``subgrid_inds``, ``ax_inds`` locations. 888 | 889 | Parameters 890 | ---------- 891 | subgrid_inds : list of ints 892 | The indices of the rows (``XGrid``) or columns (``YGrid``) 893 | containing the axes that need tick parameters adjusted 894 | ax_inds : list of ints 895 | The indices of the axes within the indicated subgrids that need 896 | tick parameters adjusted 897 | xy_axis : string 898 | ['x'|'y'|'both'] 899 | which : string 900 | The set of ticks to adjust. ['major'|'minor'] 901 | tick_dim : tuple of ints or floats 902 | The (length, width) of ``which`` ticks. 903 | labelsize : int 904 | Tick label fontsize in points. 905 | pad : int 906 | Spacing between the tick and tick label in points. 907 | direction : string 908 | Tick direction. ['in'|'out'|'inout'] 909 | 910 | """ 911 | 912 | for s in subgrid_inds: 913 | for ax in ax_inds: 914 | self.axes[s][ax].tick_params(axis=xy_axis, which=which, 915 | length=tick_dim[0], 916 | width=tick_dim[1], 917 | labelsize=labelsize, pad=pad, 918 | direction=direction) 919 | 920 | def _convert_coords(self, axis, coordinates): 921 | """ 922 | Convert data ``coordinates`` to axis coordinates. 923 | 924 | Parameters 925 | ---------- 926 | axis : ``matplotlib Axes`` instance 927 | The axes instance used to convert data ``coordinates`` into axes 928 | and figure coordinates 929 | coordinates : tuple of floats 930 | Coordinates in the data coordinate system 931 | 932 | Returns 933 | ------- 934 | fig_coords : tuple of floats 935 | ``coordinates`` translated to the figure coordinate system 936 | 937 | """ 938 | 939 | # Convert to axes coordinates 940 | ax_coords = axis.transData.transform(coordinates) 941 | 942 | # Convert to figure coordinates 943 | fig_coords = self.fig.transFigure.inverted().transform(ax_coords) 944 | 945 | return fig_coords 946 | 947 | def _rect_dim(self, ur_corner, ll_corner): 948 | """ 949 | Find w, h dimensions of rectange drawn in figure coordinates. 950 | 951 | """ 952 | 953 | width, height = (ur - ll for ur, ll in zip(ur_corner, ll_corner)) 954 | 955 | return width, height 956 | 957 | def _ratios_arelists(self, ratios): 958 | """ 959 | Check if ``ratios`` are lists; rectify if not. 960 | 961 | """ 962 | 963 | try: 964 | rsum = sum(ratios) 965 | except TypeError: 966 | rsum = ratios 967 | rlist = [ratios] 968 | else: 969 | rlist = ratios 970 | 971 | return rsum, rlist 972 | -------------------------------------------------------------------------------- /trendvis/gridwrapper.py: -------------------------------------------------------------------------------- 1 | from __future__ import division, print_function, absolute_import 2 | from .xgrid_ystack import XGrid 3 | from .ygrid_xstack import YGrid 4 | 5 | 6 | def make_grid(xratios, yratios, figsize, xticks, yticks, main_axis, 7 | plotdata=None, xlim=None, ylim=None, to_twin=None, 8 | axis_shift=None, twinax_shift=None, tick_fontsize=10, **kwargs): 9 | 10 | """ 11 | Build a plot with a stack of multiple y (x) axes against a main x (y) axis 12 | 13 | This is an easy grid and grid formatting set-up, but not all options are 14 | accessible via this interface. 15 | 16 | Parameters 17 | ---------- 18 | xratios : int or list of ints 19 | The relative sizes of the columns. Not directly comparable 20 | to ``yratios`` 21 | yratios : int or list of ints 22 | The relative sizes of the rows. Not directly comparable to ``xratios`` 23 | figsize : tuple of ints or floats 24 | The figure dimensions in inches 25 | xticks : list of tuples 26 | List of (major, minor) tick mark multiples. Used to set major and 27 | minor locators. One tuple per x axis 28 | yticks : list of tuples 29 | List of (major, minor) tick mark multiples. Used to set major and 30 | minor locators. One tuple per y axis 31 | main_axis : string 32 | ['x'|'y']. The common axis that all plots will share. 33 | 34 | Keyword Arguments 35 | ----------------- 36 | plotdata : list of lists of tuples 37 | Default ``None``. 38 | Tuple format: (x, y, color, {}, [ax inds within row/col]) 39 | One sublist per row or column (including twins). To skip plotting on a 40 | row or column, insert empty sublist at position corresponding to 41 | the index of the row or column. 42 | xlim : list of tuples of ints and/or floats 43 | Default ``None``. List of (column, min, max). 44 | If xdim is 1, then column is ignored. 45 | Also, if only one x axis needs ``xlim``, can just pass the tuple 46 | ylim : List of tuples of ints and/or flaots 47 | Default ``None``. List of (row, min, max). 48 | If ydim is 1, then row is ignored. 49 | Also, if only one y axis needs ``ylim``, can just pass a tuple. 50 | to_twin : list of ints 51 | Default ``None``. The indices of the rows (columns) to twin 52 | axis_shift : float or list of floats 53 | Default ``None``. Universal (float) or individual (list of floats) 54 | original axis spine relative shift. Units are fraction of figure 55 | twinax_shift : float or list of floats 56 | Default ``None``. Universal (float) or individual (list of floats) 57 | twinned axis spine relative shift. Units are fraction of figure 58 | tick_fontsize : int 59 | Default 10. The fontsize of tick labels. 60 | 61 | Other Parameters 62 | ---------------- 63 | kwargs : passed to ``plot_data()``, then to ``axes.plot()`` 64 | 65 | Returns 66 | ------- 67 | grid : ``YGrid`` or ``XGrid`` instance 68 | 69 | """ 70 | 71 | mainax = main_axis.lower()[0] 72 | 73 | startside_dict = {'x': 'left', 74 | 'y': 'top'} 75 | 76 | startside = startside_dict[mainax] 77 | 78 | if mainax == 'y': 79 | # Set up grid, grid formatting 80 | grid = XGrid(xratios, yratios, figsize, startside=startside, 81 | alternate_sides=True, onespine_forboth=False) 82 | 83 | elif mainax == 'x': 84 | grid = YGrid(xratios, yratios, figsize, startside=startside, 85 | alternate_sides=True, onespine_forboth=False) 86 | 87 | else: 88 | raise ValueError('main_axis arg, ' + main_axis + ' is not recognized') 89 | 90 | if to_twin is not None: 91 | grid.make_twins(to_twin) 92 | 93 | grid.set_ticknums(xticks, yticks) 94 | 95 | grid.set_ticks(labelsize=tick_fontsize) 96 | 97 | if xlim is not None: 98 | grid.set_xlim(xlim) 99 | 100 | if ylim is not None: 101 | grid.set_ylim(ylim) 102 | 103 | if axis_shift is not None or twinax_shift is not None: 104 | grid.set_relative_axshift(axis_shift=axis_shift, 105 | twin_shift=twinax_shift) 106 | grid.set_absolute_axshift() 107 | grid.move_spines() 108 | 109 | grid.set_spinewidth(2) 110 | 111 | grid.cleanup_grid() 112 | 113 | if plotdata is not None: 114 | plot_data(grid, plotdata, **kwargs) 115 | 116 | return grid 117 | 118 | 119 | def plot_data(grid, plotdata, auto_spinecolor=True, marker='o', ls='-', 120 | zorder=10, lw=1, **kwargs): 121 | """ 122 | Easy way to plot a lot of line data at once. Other plotting calls 123 | can be made by accessing individual axes in ``grid.axes``. 124 | 125 | Parameters 126 | ---------- 127 | grid : ``XGrid`` or ``YGrid`` instance 128 | The Grid of axes on which to plot. 129 | plotdata : list of lists of tuples 130 | Default ``None``. 131 | Tuple format: (x, y, color, [ax inds within row/col]) 132 | One sublist per row or column (including twins). To skip plotting on a 133 | row or column, insert empty sublist at position corresponding to 134 | the index of the row or column. 135 | 136 | Keyword Arguments 137 | ----------------- 138 | auto_spinecolor : Boolean 139 | If ``True``, will color each stacked axis spines and ticks with 140 | the color of the first plot on the axis. 141 | marker : string 142 | Default 'o'. Any ``matplotlib`` marker. 143 | ls : string 144 | Default '-'. Any ``matplotlib`` linestyle. 145 | zorder : int 146 | Default 10. The zorder of the plot. 147 | lw : string 148 | Default 1. Linewidth in points. 149 | 150 | Other Parameters 151 | ---------------- 152 | kwargs : passed to ``axes.plot()`` 153 | 154 | """ 155 | 156 | for subgrid, subgrid_data in zip(grid.axes, plotdata): 157 | for ax_ind in range(0, grid.mainax_dim): 158 | for dataset in subgrid_data: 159 | try: 160 | if ax_ind in dataset[3]: 161 | subgrid[ax_ind].plot(dataset[0], dataset[1], lw=lw, 162 | color=dataset[2], marker=marker, 163 | ls=ls, zorder=zorder, **kwargs) 164 | except: 165 | subgrid[ax_ind].plot(dataset[0], dataset[1], 166 | color=dataset[2], marker=marker, 167 | zorder=zorder, lw=lw, ls=ls, **kwargs) 168 | if auto_spinecolor: 169 | grid.autocolor_spines(0) 170 | -------------------------------------------------------------------------------- /trendvis/testing.py: -------------------------------------------------------------------------------- 1 | from matplotlib.testing.decorators import image_comparison as _ic 2 | from functools import partial 3 | 4 | image_comparison = partial( 5 | _ic, 6 | extensions=["png"], 7 | tol=22, 8 | style=["classic", "_classic_test_patch", {"figure.figsize": (10, 10)}], 9 | ) 10 | -------------------------------------------------------------------------------- /trendvis/tests/baseline_images/test_trendvis/xgrid_clean.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matplotlib/trendvis/de646e14b143122df79395af9eb7382c526a9910/trendvis/tests/baseline_images/test_trendvis/xgrid_clean.png -------------------------------------------------------------------------------- /trendvis/tests/baseline_images/test_trendvis/xgrid_cutout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matplotlib/trendvis/de646e14b143122df79395af9eb7382c526a9910/trendvis/tests/baseline_images/test_trendvis/xgrid_cutout.png -------------------------------------------------------------------------------- /trendvis/tests/baseline_images/test_trendvis/xgrid_frame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matplotlib/trendvis/de646e14b143122df79395af9eb7382c526a9910/trendvis/tests/baseline_images/test_trendvis/xgrid_frame.png -------------------------------------------------------------------------------- /trendvis/tests/baseline_images/test_trendvis/xgrid_multitwin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matplotlib/trendvis/de646e14b143122df79395af9eb7382c526a9910/trendvis/tests/baseline_images/test_trendvis/xgrid_multitwin.png -------------------------------------------------------------------------------- /trendvis/tests/baseline_images/test_trendvis/xgrid_twins.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matplotlib/trendvis/de646e14b143122df79395af9eb7382c526a9910/trendvis/tests/baseline_images/test_trendvis/xgrid_twins.png -------------------------------------------------------------------------------- /trendvis/tests/baseline_images/test_trendvis/xgrid_ylabels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matplotlib/trendvis/de646e14b143122df79395af9eb7382c526a9910/trendvis/tests/baseline_images/test_trendvis/xgrid_ylabels.png -------------------------------------------------------------------------------- /trendvis/tests/baseline_images/test_trendvis/ygrid_clean.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matplotlib/trendvis/de646e14b143122df79395af9eb7382c526a9910/trendvis/tests/baseline_images/test_trendvis/ygrid_clean.png -------------------------------------------------------------------------------- /trendvis/tests/baseline_images/test_trendvis/ygrid_cutout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matplotlib/trendvis/de646e14b143122df79395af9eb7382c526a9910/trendvis/tests/baseline_images/test_trendvis/ygrid_cutout.png -------------------------------------------------------------------------------- /trendvis/tests/baseline_images/test_trendvis/ygrid_frame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matplotlib/trendvis/de646e14b143122df79395af9eb7382c526a9910/trendvis/tests/baseline_images/test_trendvis/ygrid_frame.png -------------------------------------------------------------------------------- /trendvis/tests/baseline_images/test_trendvis/ygrid_multitwin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matplotlib/trendvis/de646e14b143122df79395af9eb7382c526a9910/trendvis/tests/baseline_images/test_trendvis/ygrid_multitwin.png -------------------------------------------------------------------------------- /trendvis/tests/baseline_images/test_trendvis/ygrid_twins.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matplotlib/trendvis/de646e14b143122df79395af9eb7382c526a9910/trendvis/tests/baseline_images/test_trendvis/ygrid_twins.png -------------------------------------------------------------------------------- /trendvis/tests/baseline_images/test_trendvis/ygrid_xlabels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matplotlib/trendvis/de646e14b143122df79395af9eb7382c526a9910/trendvis/tests/baseline_images/test_trendvis/ygrid_xlabels.png -------------------------------------------------------------------------------- /trendvis/tests/test_trendvis.py: -------------------------------------------------------------------------------- 1 | from __future__ import division, absolute_import, print_function 2 | 3 | """ 4 | TrendVis testing 5 | 6 | ??? 7 | 8 | """ 9 | 10 | import trendvis 11 | 12 | from trendvis.testing import image_comparison 13 | 14 | import matplotlib.pyplot as plt 15 | 16 | # baseline images use the old default figsize of 10x10 inches. 17 | plt.rcParams["figure.figsize"] = "10, 10" 18 | 19 | 20 | def test_xgrid_init(): 21 | trendvis.XGrid([2, 3, 1, 2]) 22 | 23 | 24 | def test_ygrid_init(): 25 | trendvis.YGrid([2, 3, 1, 2]) 26 | 27 | 28 | @image_comparison(baseline_images=["xgrid_clean"]) 29 | def test_xgrid_clean(): 30 | testgrid = trendvis.XGrid([2, 3, 1, 2]) 31 | testgrid.cleanup_grid() 32 | 33 | 34 | @image_comparison(baseline_images=["ygrid_clean"]) 35 | def test_ygrid_clean(): 36 | testgrid = trendvis.YGrid([4, 6]) 37 | testgrid.cleanup_grid() 38 | 39 | 40 | @image_comparison(baseline_images=["xgrid_twins"]) 41 | def test_xgrid_twins(): 42 | testgrid = trendvis.XGrid([2, 5, 1], alternate_sides=False, xratios=[2, 1]) 43 | testgrid.make_twins([0, 2]) 44 | testgrid.cleanup_grid() 45 | 46 | 47 | @image_comparison(baseline_images=["ygrid_twins"]) 48 | def test_ygrid_twins(): 49 | testgrid = trendvis.YGrid([2, 2, 1], alternate_sides=False, yratios=[2, 1]) 50 | testgrid.make_twins([0, 2]) 51 | testgrid.cleanup_grid() 52 | 53 | 54 | @image_comparison(baseline_images=["xgrid_multitwin"]) 55 | def test_xgrid_multitwin(): 56 | testgrid = trendvis.XGrid([2, 1]) 57 | testgrid.make_twins([0, 1]) 58 | testgrid.make_twins(3) 59 | 60 | 61 | @image_comparison(baseline_images=["ygrid_multitwin"]) 62 | def test_ygrid_multitwin(): 63 | testgrid = trendvis.YGrid([2, 1]) 64 | testgrid.make_twins([0, 1]) 65 | testgrid.make_twins(3) 66 | 67 | 68 | def test_get_axes_xgrid(): 69 | testgrid = trendvis.XGrid([2, 1]) 70 | testgrid.make_twins([0]) 71 | 72 | assert testgrid.get_axis(0, is_twin=True) == testgrid.axes[2][0] 73 | 74 | 75 | def test_get_axes_ygrid(): 76 | testgrid = trendvis.YGrid([2, 1]) 77 | testgrid.make_twins([0]) 78 | 79 | assert testgrid.get_axis(0, is_twin=True) == testgrid.axes[2][0] 80 | 81 | 82 | # 83 | # def test_get_twin_rownum(): 84 | 85 | 86 | # 87 | # def test_get_twin_colnum(): 88 | 89 | 90 | def test_reverse_axes_xgrid(): 91 | testgrid = trendvis.XGrid([2, 1]) 92 | lim0 = testgrid.fig.axes[0].get_ylim() 93 | lim1 = testgrid.fig.axes[1].get_ylim() 94 | mainlim = testgrid.fig.axes[0].get_xlim() 95 | 96 | testgrid.reverse_yaxis() 97 | testgrid.reverse_xaxis() 98 | 99 | assert lim0 == testgrid.fig.axes[0].get_ylim()[::-1] 100 | assert lim1 == testgrid.fig.axes[1].get_ylim()[::-1] 101 | assert mainlim == testgrid.fig.axes[1].get_xlim()[::-1] 102 | 103 | 104 | def test_reverse_axes_ygrid(): 105 | testgrid = trendvis.YGrid([2, 1]) 106 | lim0 = testgrid.fig.axes[0].get_xlim() 107 | lim1 = testgrid.fig.axes[1].get_xlim() 108 | mainlim = testgrid.fig.axes[0].get_ylim() 109 | 110 | testgrid.reverse_xaxis() 111 | testgrid.reverse_yaxis() 112 | 113 | assert lim0 == testgrid.fig.axes[0].get_xlim()[::-1] 114 | assert lim1 == testgrid.fig.axes[1].get_xlim()[::-1] 115 | assert mainlim == testgrid.fig.axes[1].get_ylim()[::-1] 116 | 117 | 118 | @image_comparison(baseline_images=["xgrid_cutout"]) 119 | def test_xgrid_cutout(): 120 | testgrid = trendvis.XGrid([1], xratios=[1, 2]) 121 | testgrid.cleanup_grid() 122 | testgrid.draw_cutout() 123 | 124 | 125 | @image_comparison(baseline_images=["ygrid_cutout"]) 126 | def test_ygrid_cutout(): 127 | testgrid = trendvis.YGrid([1], yratios=[1, 2]) 128 | testgrid.cleanup_grid() 129 | testgrid.draw_cutout() 130 | 131 | 132 | @image_comparison(baseline_images=["xgrid_ylabels"]) 133 | def test_xgrid_ylabels(): 134 | testgrid = trendvis.XGrid([1, 2]) 135 | testgrid.set_ylabels(["0", "1"]) 136 | 137 | 138 | @image_comparison(baseline_images=["ygrid_xlabels"]) 139 | def test_ygrid_xlabels(): 140 | testgrid = trendvis.YGrid([1, 2, 1]) 141 | testgrid.set_xlabels(["0", "1", None]) 142 | 143 | 144 | @image_comparison(baseline_images=["xgrid_frame"]) 145 | def test_xgrid_frame(): 146 | testgrid = trendvis.XGrid([1, 2, 1], xratios=[1, 1]) 147 | testgrid.cleanup_grid() 148 | testgrid.draw_frame() 149 | 150 | 151 | @image_comparison(baseline_images=["ygrid_frame"]) 152 | def test_ygrid_frame(): 153 | testgrid = trendvis.YGrid([1, 2, 1], yratios=[1, 1]) 154 | testgrid.cleanup_grid() 155 | testgrid.draw_frame() 156 | 157 | 158 | if __name__ == "__main__": 159 | import nose 160 | import sys 161 | 162 | nose.main(addplugins=[KnownFailure()]) 163 | 164 | args = ["-s", "--with-doctest"] 165 | argv = sys.argv 166 | argv = argv[:1] + args + argv[1:] 167 | nose.runmodule(argv=argv, exit=False) 168 | -------------------------------------------------------------------------------- /trendvis/xgrid_ystack.py: -------------------------------------------------------------------------------- 1 | from __future__ import division, print_function, absolute_import 2 | import matplotlib.pyplot as plt 3 | from matplotlib.ticker import FormatStrFormatter 4 | from .gridclass import Grid 5 | 6 | 7 | class XGrid(Grid): 8 | """ 9 | Construct a plot with the x axis as the main axis and a stack of y axes. 10 | 11 | """ 12 | 13 | def __init__(self, ystack_ratios, xratios=1, figsize=None, 14 | startside='left', alternate_sides=True, 15 | onespine_forboth=False, **kwargs): 16 | """ 17 | Initialize X_Grid 18 | 19 | Parameters 20 | ---------- 21 | ystack_ratiosratios : int or list of ints 22 | The relative sizes of the rows. Not directly comparable 23 | to ``xratios`` 24 | xratios : int or list of ints 25 | Default 1. The relative sizes of the main axis column(s). 26 | Not directly comparable to ``ystack_ratios`` 27 | figsize : tuple of ints or floats 28 | Default None. The figure dimensions in inches. 29 | If not provided, defaults to matplotlib rc figure.figsize. 30 | startside : string 31 | Default 'left'. ['left'|'right']. The side the topmost y axis 32 | will be on. 33 | alternate_sides : Boolean 34 | Default ``True``. [True|False]. 35 | Stacked axis spines alternate sides or are all on ``startside``. 36 | onespine_forboth : Boolean 37 | Default ``False``. [True|False]. If the plot stack is only 1 row, 38 | then both main axis spines can be visible (``False``), 39 | or only the bottom spine (``True``). 40 | **kwargs 41 | Any plt.figure arguments. Passed to Grid.__init__(), 42 | plt.figure() 43 | 44 | """ 45 | 46 | # Initialize parent class 47 | # Last arg is True because mainax_x 48 | Grid.__init__(self, xratios, ystack_ratios, True, figsize, **kwargs) 49 | 50 | # Set initial x and y grid positions (top left) 51 | xpos = 0 52 | ypos = 0 53 | 54 | # Create axes row by row 55 | for rowspan in self.yratios: 56 | row = [] 57 | 58 | for c, colspan in enumerate(self.xratios): 59 | sharex = None 60 | sharey = None 61 | 62 | # All axes in a row share y axis with first axis in row 63 | if xpos > 0: 64 | sharey = row[0] 65 | 66 | # All axes in a column share x axis with first axis in column 67 | if ypos > 0: 68 | sharex = self.axes[0][c] 69 | 70 | ax = plt.subplot2grid((self.gridrows, self.gridcols), 71 | (ypos, xpos), rowspan=rowspan, 72 | colspan=colspan, sharey=sharey, 73 | sharex=sharex) 74 | 75 | ax.patch.set_visible(False) 76 | 77 | row.append(ax) 78 | xpos += colspan 79 | 80 | self.axes.append(row) 81 | 82 | # Reset x position to left side, move to next y position 83 | xpos = 0 84 | ypos += rowspan 85 | 86 | for ax in self.axes[0]: 87 | ax.xaxis.set_label_position('top') 88 | 89 | self.set_dataside(startside, alternate_sides) 90 | self.set_stackposition(onespine_forboth) 91 | 92 | def make_twins(self, rows_to_twin): 93 | """ 94 | Twin rows 95 | 96 | Parameters 97 | ---------- 98 | rows_to_twin : int or list of ints 99 | Indices of the row or rows to twin 100 | 101 | """ 102 | 103 | try: 104 | new_twin_dim = len(rows_to_twin) 105 | except TypeError: 106 | new_twin_dim = 1 107 | rows_to_twin = [rows_to_twin] 108 | 109 | if self.twinds is None: 110 | self.twinds = rows_to_twin 111 | self.twin_dim = new_twin_dim 112 | else: 113 | self.twinds.extend(rows_to_twin) 114 | self.twin_dim += new_twin_dim 115 | 116 | self._update_total_stackdim() 117 | 118 | for ind in rows_to_twin: 119 | 120 | twin_row = [] 121 | 122 | for ax in self.axes[ind]: 123 | twin = ax.twinx() 124 | ax.set_zorder(twin.get_zorder() + 1) 125 | twin_row.append(twin) 126 | 127 | twinside = self.alt_sides[self.dataside_list[ind]] 128 | self.dataside_list.append(twinside) 129 | self.stackpos_list.append('none') 130 | 131 | # Make the y-axes shared 132 | if len(twin_row) > 1: 133 | twin_row[0].get_shared_y_axes().join(*twin_row) 134 | 135 | self.axes.append(twin_row) 136 | 137 | self.grid_isclean = False 138 | 139 | def adjust_spacing(self, hspace, adjust_bar_frame=True): 140 | """ 141 | Adjust the vertical spacing between rows. 142 | 143 | Parameters 144 | ---------- 145 | hspace : float 146 | Spacing between rows 147 | adjust_bar_frame : Boolean 148 | Default True. Realign ``matplotlib Rectangle patches`` 149 | made via ``self.draw_bar`` and ``self.draw_frame``. 150 | 151 | """ 152 | 153 | self.fig.subplots_adjust(hspace=hspace) 154 | 155 | if adjust_bar_frame: 156 | self.adjust_bar_frame() 157 | 158 | def cleanup_grid(self): 159 | """ 160 | Remove unnecessary spines from grid 161 | 162 | """ 163 | 164 | if not self.grid_isclean: 165 | 166 | for row, dataside, stackpos in zip(self.axes, self.dataside_list, 167 | self.stackpos_list): 168 | 169 | # Get mainax tick labelling settings 170 | ltop, lbottom = self.mainax_ticks[stackpos] 171 | 172 | # Set mainax tick parameters and positions 173 | for ax in row: 174 | ax.xaxis.set_tick_params(labeltop=ltop, 175 | labelbottom=lbottom) 176 | ax.xaxis.set_ticks_position(stackpos) 177 | 178 | data_ind, data_ax = self._pop_data_ax(row, dataside) 179 | 180 | # Set tick marks and label position, spines 181 | data_ax.yaxis.set_ticks_position(dataside) 182 | 183 | for sp in self.spine_begone[stackpos][dataside]: 184 | data_ax.spines[sp].set_visible(False) 185 | 186 | for ax in row: 187 | # Remove tick marks, tick labels 188 | ax.yaxis.set_ticks_position('none') 189 | ax.yaxis.set_tick_params(labelright='off', labelleft='off') 190 | 191 | # Remove spines 192 | for sp in self.spine_begone[stackpos]['none']: 193 | ax.spines[sp].set_visible(False) 194 | 195 | self._replace_data_ax(row, data_ind, data_ax) 196 | 197 | self.grid_isclean = True 198 | 199 | def get_axis(self, ypos, xpos=0, is_twin=False, twinstance=0): 200 | """ 201 | Get axis at a particular x, y location. 202 | 203 | If a twin is desired, then there are two options: 204 | 1. Set ``ypos`` to actual storage position in ``self.axes`` 205 | Can find twin ``ypos`` via ``self.get_twin_rownum()`` 206 | 2. Set ``ypos`` to the physical position in ``XGrid``, set 207 | ``is_twin``=``True``, and if there is more than one twin at that 208 | location, set ``twinstance`` to indicate desired twin (e.g. 209 | 0 indicates the first twin to be created in that row position). 210 | 211 | For original axes, storage position and physical position are the same, 212 | except if twins exist and negative ``ypos`` indices are used. 213 | 214 | Parameters 215 | ---------- 216 | ypos : int 217 | The row that the axis is located in 218 | xpos : int 219 | Default 0. The column the axis is in 220 | is_twin : Boolean 221 | Default ``False``. If ``is_twin``, ``self.get_axis()`` will grab 222 | the twin at the given physical ``xpos``, ``ypos`` rather than the 223 | original axis. 224 | twinstance : int 225 | Default 0. If there is more than one twin at ``xpos``, ``ypos``, 226 | then this will indicate which twin to grab 227 | 228 | Returns 229 | ------- 230 | ax : ``Axes`` instance 231 | ``matplotlib Axes`` instance at the given ``xpos``, ``ypos``, 232 | (``twinstance``) 233 | 234 | """ 235 | 236 | if is_twin: 237 | # ypos corresponds to twind(s), which are in a particular order 238 | # Get indices of where ypos appears in the list of twins 239 | twindices = [i for i, tw in enumerate(self.twinds) if tw == ypos] 240 | 241 | # Get position of desired instance of ypos 242 | which_twin = twindices[twinstance] 243 | 244 | # New ypos is the original axis count, plus the location of twin 245 | ypos = self.stackdim + which_twin 246 | 247 | # Subgrid (row, y), ax (col, x) 248 | ax = self.axes[ypos][xpos] 249 | 250 | return ax 251 | 252 | def get_twin_rownum(self, ypos, twinstance=None): 253 | """ 254 | Original axes are easily located by row number in ``self.axes``. 255 | If there are multiple twins, finding those in ``self.axes`` may be 256 | difficult, esp. if twins were created haphazardly. 257 | 258 | This prints the index required by ``self.axes`` to fetch the 259 | twin row. 260 | 261 | Parameters 262 | ---------- 263 | ypos : int 264 | The row that was twinned 265 | twinstance : int 266 | Default ``None``, print all twin row indices at ``ypos``. 267 | Indicates which twin row index to print 268 | 269 | """ 270 | 271 | twindices = [i for i, tw in enumerate(self.twinds) if tw == ypos] 272 | 273 | if twinstance is None and len(twindices) > 1: 274 | newypos = [i + self.stackdim for i in twindices] 275 | elif twinstance is None: 276 | newypos = [self.stackdim + twindices[0]] 277 | else: 278 | newypos = [self.stackdim + twindices[twinstance]] 279 | 280 | print('The twin(s) of row ' + str(ypos) + ' are stored in ' + 281 | '`self.axes` as row(s):') 282 | 283 | for ny in newypos: 284 | print(ny) 285 | 286 | def set_all_ticknums(self, xticks, yticks, logxscale='none', 287 | logyscale='none'): 288 | """ 289 | Set the y and x axis scales, the y and x axis ticks (if linear), and 290 | the tick number format. Wrapper around ``Grid.set_yaxis_ticknum()``, 291 | ``Grid.set_xaxis_ticknum()``. 292 | 293 | Parameters 294 | ---------- 295 | xticks : list of tuples 296 | List of (major, minor) tick mark multiples. Used to set major and 297 | minor locators. One tuple per main axis. 298 | Use ``None`` to skip setting a major, minor ``MultipleLocator`` 299 | for an axis 300 | yticks : list of tuples 301 | List of (major, minor) tick mark multiples. Used to set major and 302 | minor locators. One tuple per y axis (original stack + twins). 303 | Use ``None`` to skip setting a major, minor ``MultipleLocator`` 304 | for an axis 305 | logxscale : string or list of ints 306 | Default 'none'. ['none'|'all'|list of x-axis indices]. 307 | Indicate which x axes should be log scaled instead of linear. 308 | logyscale : string or list of ints 309 | Default 'none'. ['none'|'all'|list of y-axis indices]. 310 | Indicate which y axes should be log scaled instead of linear. 311 | 312 | """ 313 | 314 | if len(xticks) != self.mainax_dim: 315 | raise ValueError('xticks provided for ' + str(len(xticks)) + '/' + 316 | str(self.mainax_dim) + ' x-axes') 317 | if len(yticks) != self.total_stackdim: 318 | raise ValueError('yticks provided for ' + str(len(yticks)) + '/' + 319 | str(self.total_stackdim) + ' y-axes') 320 | 321 | xscale = self._make_lists(self.mainax_dim, logxscale, 'linear', 'log') 322 | yscale = self._make_lists(self.total_stackdim, logyscale, 323 | 'linear', 'log') 324 | 325 | for row, yt, ysc in zip(self.axes, yticks, yscale): 326 | for ax, xt, xsc in zip(row, xticks, xscale): 327 | 328 | if yt is not None or ysc == 'log': 329 | self.set_yaxis_ticknum(ax, yt, scale=ysc) 330 | if xt is not None or xsc == 'log': 331 | self.set_xaxis_ticknum(ax, xt, scale=xsc) 332 | 333 | def ticknum_format(self, ax='all', xformatter='%d', yformatter='%d'): 334 | """ 335 | Set tick number formatters for x and/or y axes. 336 | 337 | Parameters 338 | ---------- 339 | ax : string or axes instance 340 | Default 'all', cycle through axes and set formatters. 341 | If axes instance, will only set x and/or y formatter of that axes 342 | instance. Can acquire axis using ``self.get_axis()`` 343 | xformatter : string or list of strings 344 | Default '%d'. String formatting magic to apply to all x axes 345 | Can use ``None`` to skip setting ``xformatter`` 346 | yformatter : string or list of strings 347 | Default '%d'. String formatting magic to apply to all y axes 348 | (string) or individual y axes (list of strings, 349 | length = ``self.total_stackdim``). Can use ``None`` to 350 | skip setting ``yformatter``, or insert ``None`` into list to 351 | skip setting formatter for a particular axis row 352 | 353 | """ 354 | 355 | if ax != 'all': 356 | if xformatter is not None: 357 | xfrmttr = FormatStrFormatter(xformatter) 358 | ax.xaxis.set_major_formatter(xfrmttr) 359 | if yformatter is not None: 360 | yfrmttr = FormatStrFormatter(yformatter) 361 | ax.yaxis.set_major_formatter(xfrmttr) 362 | 363 | else: 364 | if yformatter is not None: 365 | if type(yformatter) is str: 366 | yfrmttr = FormatStrFormatter(yformatter) 367 | for row in self.axes: 368 | for ax in row: 369 | ax.yaxis.set_major_formatter(yfrmttr) 370 | else: 371 | for yf, row in zip(yformatter, self.axes): 372 | if yf is not None: 373 | yfrmttr = FormatStrFormatter(yf) 374 | for ax in row: 375 | ax.yaxis.set_major_formatter(yfrmttr) 376 | 377 | if xformatter is not None: 378 | xfrmttr = FormatStrFormatter(xformatter) 379 | for row in self.axes: 380 | for ax in row: 381 | ax.xaxis.set_major_formatter(xfrmttr) 382 | 383 | def reverse_yaxis(self, reverse_y='all', adjust_bar_frame=True): 384 | """ 385 | Reverse all or any y axis. 386 | 387 | Parameters 388 | ---------- 389 | reverse_y : string or list of ints 390 | Default 'all'. 'all' or list of indices of the y axes to be 391 | reversed accepted. If unsure of index for a twin y axis in 392 | ``self.axes``, find using ``self.get_twin_rownum()`` 393 | adjust_bar_frame : Boolean 394 | Default True. Realign ``matplotlib Rectangle patches`` 395 | made via ``self.draw_bar`` and ``self.draw_frame``. 396 | 397 | """ 398 | 399 | if reverse_y == 'all': 400 | reverse_y = range(0, self.total_stackdim) 401 | 402 | # Invert yaxis of first axis in each row 403 | for r in reverse_y: 404 | self.axes[r][0].invert_yaxis() 405 | 406 | if adjust_bar_frame: 407 | self.adjust_bar_frame() 408 | 409 | def reverse_xaxis(self, reverse_x='all', adjust_bar_frame=True): 410 | """ 411 | Reverse all or any x axis. 412 | 413 | Parameters 414 | ---------- 415 | reverse_x : string or list of ints 416 | Default 'all'. 'all' or list of indices of the x axes to be 417 | reversed accepted. If unsure of index for a twin y axis in 418 | ``self.axes``, find using ``self.get_twin_rownum()`` 419 | adjust_bar_frame : Boolean 420 | Default True. Realign ``matplotlib Rectangle patches`` 421 | made via ``self.draw_bar`` and ``self.draw_frame``. 422 | 423 | """ 424 | 425 | if reverse_x == 'all': 426 | reverse_x = range(0, self.mainax_dim) 427 | 428 | # Invert x axis of each axis in first row 429 | for r in reverse_x: 430 | self.axes[0][r].invert_xaxis() 431 | 432 | if adjust_bar_frame: 433 | self.adjust_bar_frame() 434 | 435 | def set_ylim(self, ylim, adjust_bar_frame=True): 436 | """ 437 | Set y limits. 438 | 439 | Parameters 440 | ---------- 441 | ylim : list of tuples of ints and/or floats 442 | List of (row, min, max). If ydim is 1, then row is ignored. 443 | Also, if only one y axis needs ``ylim``, can just pass the tuple. 444 | If unsure of row index for a twin y axis in ``self.axes``, 445 | find using ``self.get_twin_rownum()`` 446 | adjust_bar_frame : Boolean 447 | Default True. Realign ``matplotlib Rectangle patches`` 448 | made via ``self.draw_bar`` and ``self.draw_frame``. 449 | 450 | """ 451 | 452 | if self.total_stackdim == 1: 453 | try: 454 | ylim.extend([]) 455 | except AttributeError: 456 | pass 457 | else: 458 | ylim = ylim[0] 459 | 460 | for row in self.axes: 461 | for ax in row: 462 | ax.set_ylim(ylim[-2], ylim[-1]) 463 | 464 | else: 465 | try: 466 | ylim.extend([]) 467 | except AttributeError: 468 | ylim = [ylim] 469 | 470 | for yl in ylim: 471 | for ax in self.axes[yl[0]]: 472 | ax.set_ylim(yl[1], yl[2]) 473 | 474 | if adjust_bar_frame: 475 | self.adjust_bar_frame() 476 | 477 | def set_xlim(self, xlim, adjust_bar_frame=True): 478 | """ 479 | Set x limits. 480 | 481 | Parameters 482 | ---------- 483 | xlim : List of tuples of ints and/or flaots 484 | List of (column, min, max). If xdim is 1, then column is ignored. 485 | Also, if only one x axis needs ``xlim``, can just pass a tuple. 486 | adjust_bar_frame : Boolean 487 | Default True. Realign ``matplotlib Rectangle patches`` 488 | made via ``self.draw_bar`` and ``self.draw_frame``. 489 | 490 | """ 491 | 492 | if self.mainax_dim == 1: 493 | try: 494 | xlim.extend([]) 495 | except AttributeError: 496 | pass 497 | else: 498 | xlim = xlim[0] 499 | 500 | for row in self.axes: 501 | for ax in row: 502 | ax.set_xlim(xlim[-2], xlim[-1]) 503 | 504 | else: 505 | try: 506 | xlim.extend([]) 507 | except AttributeError: 508 | xlim = [xlim] 509 | 510 | for xl in xlim: 511 | for row in self.axes: 512 | row[xl[0]].set_xlim(xl[1], xl[2]) 513 | 514 | if adjust_bar_frame: 515 | self.adjust_bar_frame() 516 | 517 | def set_ticks(self, row='all', column='all', xy_axis='both', which='both', 518 | major_dim=(6, 2), minor_dim=(4, 1), labelsize=10, pad=10, 519 | major_dir='out', minor_dir='out'): 520 | """ 521 | Set x and/or y axis ticks for all or specified axes. 522 | 523 | Does not set axis color. 524 | 525 | Parameters 526 | ---------- 527 | row : string or list of ints 528 | Default 'all'. The rows containing the axes that need tick 529 | parameters adjusted, 'all' or list of indices. If unsure of row 530 | index for a twin y axis in ``self.axes``, find using 531 | ``self.get_twin_rownum()`` 532 | column: string or list of ints 533 | Default 'all'. The columns containing the axes that need tick 534 | parameters adjusted, 'all' or list of indices 535 | xy_axis : string 536 | Default 'both'. ['x'|'y'|'both'] 537 | which : string 538 | Default 'both'. ['major'|'minor'|'both'], the set of ticks 539 | to adjust. 540 | major_dim : tuple of ints or floats 541 | Default (6, 2). The (length, width) of the major ticks. 542 | minor_dim : tuple of ints or floats 543 | Default (4, 1). The (length, width) of the minor ticks. 544 | labelsize : int 545 | Default 10. Tick label fontsize in points. 546 | pad : int 547 | Default 10. Spacing between the tick and tick label in points. 548 | major_tickdir : string 549 | Default 'out'. ['out'|'in'|'inout']. The major tick direction. 550 | minor_tickdir : string 551 | Default 'out'. ['out'|'in'|'inout']. The minor tick direction. 552 | 553 | """ 554 | 555 | if row == 'all': 556 | row = range(0, self.total_stackdim) 557 | if column == 'all': 558 | column = range(0, self.mainax_dim) 559 | 560 | if which != 'major': 561 | Grid._set_ticks(self, row, column, xy_axis, 'minor', minor_dim, 562 | labelsize, pad, minor_dir) 563 | 564 | if which != 'minor': 565 | Grid._set_ticks(self, row, column, xy_axis, 'major', major_dim, 566 | labelsize, pad, major_dir) 567 | 568 | def draw_cutout(self, di=0.025, lw='default', **kwargs): 569 | """ 570 | Draw cut marks to signify broken x axes. 571 | 572 | Only drawn when ``self.mainax_dim`` > 1. 573 | 574 | Parameters 575 | ---------- 576 | di : float 577 | Default 0.025. The dimensions of the cutout mark as a 578 | fraction of the smallest axis length. 579 | lw : int 580 | Default 'default'. If 'default', ``lw = self.spinewidth``. 581 | **kwargs 582 | Passed to ``axes.plot()``. Any valid ``kwargs``. 583 | 584 | """ 585 | 586 | if self.mainax_dim > 1: 587 | 588 | # Adjust di so that cutouts will look exactly the same 589 | # on every axis, no matter their relative sizes 590 | minx = min(self.xratios) 591 | x = [di * (minx / x) for x in self.xratios] 592 | 593 | miny = min(self.yratios) 594 | y0 = di * (miny / self.yratios[0]) 595 | y1 = di * (miny / self.yratios[-1]) 596 | 597 | # Upper and lower y position 598 | upper_y = (1 - (2 * y0), 1 + (2 * y0)) 599 | lower_y = (-(2 * y1), (2 * y1)) 600 | 601 | low_ind = self.stackdim - 1 602 | 603 | if lw == 'default': 604 | lw = self.spinewidth 605 | 606 | top_ax = self.axes[0][0] 607 | low_ax = self.axes[low_ind][0] 608 | right = (1 - x[0], 1 + x[0]) 609 | 610 | # first axes in rows, right only 611 | kwargs = dict(transform=top_ax.transAxes, clip_on=False, 612 | color='black', lw=lw, **kwargs) 613 | top_ax.plot(right, upper_y, **kwargs) 614 | 615 | kwargs.update(transform=low_ax.transAxes) 616 | low_ax.plot(right, lower_y, **kwargs) 617 | 618 | # Middle axes 619 | for i in range(1, self.mainax_dim - 1): 620 | top_ax = self.axes[0][i] 621 | low_ax = self.axes[low_ind][i] 622 | left = (-x[i], x[i]) 623 | right = (1 - x[i], 1 + x[i]) 624 | 625 | kwargs.update(transform=top_ax.transAxes) 626 | top_ax.plot(left, upper_y, **kwargs) 627 | top_ax.plot(right, upper_y, **kwargs) 628 | 629 | kwargs.update(transform=low_ax.transAxes) 630 | low_ax.plot(left, lower_y, **kwargs) 631 | low_ax.plot(right, lower_y, **kwargs) 632 | 633 | # Last axes in rows, left only 634 | top_ax = self.axes[0][-1] 635 | low_ax = self.axes[low_ind][-1] 636 | left = (-x[-1], x[-1]) 637 | 638 | kwargs.update(transform=top_ax.transAxes) 639 | top_ax.plot(left, upper_y, **kwargs) 640 | 641 | kwargs.update(transform=low_ax.transAxes) 642 | low_ax.plot(left, lower_y, **kwargs) 643 | 644 | def set_ylabels(self, ylabels, fontsize=None, labelpad=12, **kwargs): 645 | """ 646 | Tool for setting all y axis labels at once. Can skip labelling an axis 647 | by providing a ``None`` in corresponding position in list. 648 | 649 | Parameters 650 | ---------- 651 | ylabels : list of strings 652 | The list of labels, one per y-axis. Insert ``None`` in list to 653 | skip an axis. 654 | fontsize : int 655 | Default None. The font size of ``ylabels`` 656 | labelpad : int 657 | Default 12. The spacing between the tick labels and the axis 658 | labels. 659 | **kwargs 660 | Passed to ``axes.set_ylabel()``. 661 | Any ``matplotlib Text`` properties 662 | 663 | Notes 664 | ----- 665 | Caution- this will set twin axis labels in the order that twins were 666 | created, which may not correspond to physical position in grid. 667 | 668 | """ 669 | 670 | for row, side, yl in zip(self.axes, self.dataside_list, ylabels): 671 | if yl is not None: 672 | if side == 'right': 673 | row[-1].yaxis.set_label_position('right') 674 | row[-1].set_ylabel(yl, fontsize=fontsize, 675 | labelpad=labelpad, rotation=270, 676 | verticalalignment='bottom', **kwargs) 677 | else: 678 | row[0].yaxis.set_label_position('left') 679 | row[0].set_ylabel(yl, fontsize=fontsize, labelpad=labelpad, 680 | **kwargs) 681 | -------------------------------------------------------------------------------- /trendvis/ygrid_xstack.py: -------------------------------------------------------------------------------- 1 | from __future__ import division, print_function, absolute_import 2 | import matplotlib.pyplot as plt 3 | from matplotlib.ticker import FormatStrFormatter 4 | from .gridclass import Grid 5 | 6 | 7 | class YGrid(Grid): 8 | """ 9 | Construct a plot with the y axis as the main axis and a stack of x axes. 10 | 11 | """ 12 | 13 | def __init__(self, xstack_ratios, yratios=1, figsize=None, 14 | startside='top', alternate_sides=True, 15 | onespine_forboth=False, **kwargs): 16 | """ 17 | Initialize Y_Grid 18 | 19 | Parameters 20 | ---------- 21 | xstack_ratios : int or list of ints 22 | The relative sizes of the columns. Not directly comparable 23 | to ``yratios`` 24 | yratios : int or list of ints 25 | Default 1. The relative sizes of the main axis row(s). 26 | Not directly comparable to ``xstack_ratios`` 27 | figsize : tuple of ints or floats 28 | Default None. The figure dimensions in inches. 29 | If not provided, defaults to matplotlib rc figure.figsize. 30 | startside : string 31 | Default 'top'. ['top'|'bottom']. The side the leftmost x axis 32 | will be on. 33 | alternate_sides : Boolean 34 | Default ``True``. [True|False]. 35 | Stacked axis spines alternate sides or are all on ``startside``. 36 | onespine_forboth : Boolean 37 | Default ``False``. [True|False]. If the plot stack is only 1 38 | column, then both main axis spines can be visible (``False``), 39 | or only the left spine is visible (``True``). 40 | **kwargs 41 | Any plt.figure arguments. Passed to Grid.__init__(), 42 | plt.figure(). 43 | 44 | """ 45 | 46 | # Initialize parent class 47 | # Last arg is False because mainax_x 48 | Grid.__init__(self, xstack_ratios, yratios, False, figsize, **kwargs) 49 | 50 | # Set initial x and y grid positions (top left) 51 | xpos = 0 52 | ypos = 0 53 | 54 | # Create axes column by column 55 | for colspan in self.xratios: 56 | col = [] 57 | 58 | for r, rowspan in enumerate(self.yratios): 59 | sharex = None 60 | sharey = None 61 | 62 | # All axes in column share x axis with first in column 63 | if ypos > 0: 64 | sharex = col[0] 65 | 66 | # All axes in row share y axis with first in row 67 | if xpos > 0: 68 | sharey = self.axes[0][r] 69 | 70 | ax = plt.subplot2grid((self.gridrows, self.gridcols), 71 | (ypos, xpos), rowspan=rowspan, 72 | colspan=colspan, sharex=sharex, 73 | sharey=sharey) 74 | 75 | ax.patch.set_visible(False) 76 | 77 | col.append(ax) 78 | ypos += rowspan 79 | 80 | self.axes.append(col) 81 | 82 | # Reset y position to top, move to next x position 83 | ypos = 0 84 | xpos += colspan 85 | 86 | for ax in self.axes[-1]: 87 | ax.yaxis.set_label_position('right') 88 | 89 | self.set_dataside(startside, alternate_sides) 90 | self.set_stackposition(onespine_forboth) 91 | 92 | def make_twins(self, cols_to_twin): 93 | """ 94 | Twin columns 95 | 96 | Parameters 97 | ---------- 98 | cols_to_twin : int or list of ints 99 | Indices of the column or columns to twin 100 | 101 | """ 102 | 103 | try: 104 | new_twin_dim = len(cols_to_twin) 105 | except TypeError: 106 | new_twin_dim = 1 107 | cols_to_twin = [cols_to_twin] 108 | 109 | if self.twinds is None: 110 | self.twinds = cols_to_twin 111 | self.twin_dim = new_twin_dim 112 | else: 113 | self.twinds.extend(cols_to_twin) 114 | self.twin_dim += new_twin_dim 115 | 116 | self._update_total_stackdim() 117 | 118 | for ind in cols_to_twin: 119 | 120 | twin_col = [] 121 | 122 | for ax in self.axes[ind]: 123 | twin = ax.twiny() 124 | ax.set_zorder(twin.get_zorder() + 1) 125 | twin_col.append(twin) 126 | 127 | twinside = self.alt_sides[self.dataside_list[ind]] 128 | self.dataside_list.append(twinside) 129 | self.stackpos_list.append('none') 130 | 131 | # Make the x axes shared 132 | if len(twin_col) > 1: 133 | twin_col[0].get_shared_x_axes().join(*twin_col) 134 | 135 | self.axes.append(twin_col) 136 | 137 | self.grid_isclean = False 138 | 139 | def adjust_spacing(self, wspace, adjust_bar_frame=True): 140 | """ 141 | Adjust the horizontal spacing between columns. 142 | 143 | Parameters 144 | ---------- 145 | wspace : float 146 | Spacing between columns 147 | adjust_bar_frame : Boolean 148 | Default True. Realign ``matplotlib Rectangle patches`` 149 | made via ``self.draw_bar`` and ``self.draw_frame``. 150 | 151 | """ 152 | 153 | self.fig.subplots_adjust(wspace=wspace) 154 | 155 | if adjust_bar_frame: 156 | self.adjust_bar_frame() 157 | 158 | def cleanup_grid(self): 159 | """ 160 | Remove unnecessary spines from grid 161 | 162 | """ 163 | 164 | if not self.grid_isclean: 165 | 166 | for col, dataside, stackpos in zip(self.axes, self.dataside_list, 167 | self.stackpos_list): 168 | 169 | # Get mainax tick labelling settings 170 | lleft, lright = self.mainax_ticks[stackpos] 171 | 172 | # Set mainax tick parameters and positions 173 | for ax in col: 174 | ax.yaxis.set_tick_params(labelleft=lleft, 175 | labelright=lright) 176 | ax.yaxis.set_ticks_position(stackpos) 177 | 178 | data_ind, data_ax = self._pop_data_ax(col, dataside) 179 | 180 | # Set tick marks and label position, spines 181 | data_ax.xaxis.set_ticks_position(dataside) 182 | 183 | for sp in self.spine_begone[stackpos][dataside]: 184 | data_ax.spines[sp].set_visible(False) 185 | 186 | for ax in col: 187 | # Remove tick marks, tick labels 188 | ax.xaxis.set_ticks_position('none') 189 | ax.xaxis.set_tick_params(labeltop='off', labelbottom='off') 190 | 191 | # Remove spines 192 | for sp in self.spine_begone[stackpos]['none']: 193 | ax.spines[sp].set_visible(False) 194 | 195 | self._replace_data_ax(col, data_ind, data_ax) 196 | 197 | self.grid_isclean = True 198 | 199 | def get_axis(self, xpos, ypos=0, is_twin=False, twinstance=0): 200 | """ 201 | Get axis at a particular x, y position. 202 | 203 | If a twin is desired, then there are two options: 204 | 1. Set ``xpos`` to actual storage position in ``self.axes`` 205 | Can find twin ``xpos`` via ``self.get_twin_colnum()`` 206 | 2. Set ``xpos`` to the physical position in ``YGrid``, set 207 | ``is_twin``=``True``, and if there is more than one twin at that 208 | location, set ``twinstance`` to indicate desired twin (e.g. 209 | 0 indicates the first twin to be created in that col position). 210 | 211 | For original axes, storage position and physical position are the same, 212 | except if twins exist and negative ``xpos`` indices are used. 213 | 214 | Parameters 215 | ---------- 216 | xpos : int 217 | The column that the axis is located in. 218 | ypos : int 219 | Default 0. The row the axis is in. 220 | is_twin : Boolean 221 | Default ``False``. If ``is_twin``, ``self.get_axis()`` will grab 222 | the twin at the given ``xpos``, ``ypos`` rather than the 223 | original axis. 224 | twinstance : int 225 | Default 0. If there is more than one twin at ``xpos``, ``ypos``, 226 | then this will indicate which twin to grab. 227 | 228 | Returns 229 | ------- 230 | ax : ``Axes`` instance 231 | ``matplotlib Axes`` instance at the given ``xpos``, ``ypos``, 232 | (``twinstance``) 233 | 234 | """ 235 | 236 | if is_twin: 237 | # xpos corresponds to twind(s), which are in a particular order 238 | # Get indices of where xpos appears in the list of twins 239 | twindices = [i for i, tw in enumerate(self.twinds) if tw == xpos] 240 | 241 | # Get position of desired instance of xpos 242 | which_twin = twindices[twinstance] 243 | 244 | # New xpos is the original axis count, plus the location of twin 245 | xpos = self.stackdim + which_twin 246 | 247 | # Subgrid (col, x), ax (row, y) 248 | ax = self.axes[xpos][ypos] 249 | 250 | return ax 251 | 252 | def get_twin_colnum(self, xpos, twinstance=None): 253 | """ 254 | Original axes are easily located by column number in ``self.axes``. 255 | If there are are multiple twins, finding those in ``self.axes`` 256 | may be difficult, esp. if twins were created haphazardly 257 | 258 | This prints the index required by ``self.axes`` to fetch the 259 | twin column. 260 | 261 | Parameters 262 | ---------- 263 | xpos : int 264 | The column that was twinned 265 | twinstance : int 266 | Default ``None``, print all twin column indices at ``xpos``. 267 | Indicates which twin column index to print 268 | 269 | """ 270 | 271 | twindices = [i for i, tw in enumerate(self.twinds) if tw == xpos] 272 | 273 | if twinstance is None and len(twindices) > 1: 274 | newxpos = [i + self.stackdim for i in twindices] 275 | elif twinstance is None: 276 | newxpos = [self.stackdim + twindices[0]] 277 | else: 278 | newxpos = [self.stackdim + twindices[twinstance]] 279 | 280 | print('The twin(s) of column ' + str(xpos) + ' are stored in ' + 281 | '`self.axes` as column(s):') 282 | 283 | for nx in newxpos: 284 | print(nx) 285 | 286 | def set_all_ticknums(self, xticks, yticks, logxscale='none', 287 | logyscale='none'): 288 | """ 289 | Set the y and x axis scales, the y and x axis ticks (if linear), and 290 | the tick number format. Wrapper around ``Grid.set_yaxis_ticknum()``, 291 | ``Grid.set_xaxis_ticknum()``. 292 | 293 | Parameters 294 | ---------- 295 | xticks : list of tuples 296 | List of (major, minor) tick mark multiples. Used to set major and 297 | minor locators. One tuple per x axis (original stack + twins). 298 | Use ``None`` to skip setting a major, minor ``MultipleLocator`` 299 | for an axis 300 | yticks : list of tuples 301 | List of (major, minor) tick mark multiples. Used to set major and 302 | minor locators. One tuple per main axis. 303 | Use ``None`` to skip setting a major, minor ``MultipleLocator`` 304 | for an axis 305 | logxscale : string or list of ints 306 | Default 'none'. ['none'|'all'|list of x-axis indices]. 307 | Indicate which x axes should be log scaled instead of linear. 308 | logyscale : string or list of ints 309 | Default 'none'. ['none'|'all'|list of y-axis indices]. 310 | Indicate which y axes should be log scaled instead of linear. 311 | 312 | """ 313 | 314 | if len(xticks) != self.total_stackdim: 315 | raise ValueError('xticks provided for ' + str(len(xticks)) + '/' + 316 | str(self.total_stackdim) + ' x-axes') 317 | if len(yticks) != self.mainax_dim: 318 | raise ValueError('yticks provided for ' + str(len(yticks)) + '/' + 319 | str(self.mainax_dim) + ' y-axes') 320 | 321 | xscale = self._make_lists(self.total_stackdim, logxscale, 322 | 'linear', 'log') 323 | yscale = self._make_lists(self.mainax_dim, logyscale, 'linear', 'log') 324 | 325 | for col, xt, xsc in zip(self.axes, xticks, xscale): 326 | for ax, yt, ysc in zip(col, yticks, yscale): 327 | 328 | if yt is not None or ysc == 'log': 329 | self.set_yaxis_ticknum(ax, yt, scale=ysc) 330 | if xt is not None or xsc == 'log': 331 | self.set_xaxis_ticknum(ax, xt, scale=xsc) 332 | 333 | def ticknum_format(self, ax='all', xformatter='%d', yformatter='%d'): 334 | """ 335 | Set tick number formatters for x and/or y axes. 336 | 337 | Parameters 338 | ---------- 339 | ax : string or axes instance 340 | Default 'all', cycle through axes and set formatters. 341 | If axes instance, will only set x and/or y formatter of that axes 342 | instance. Can acquire axis using ``self.get_axis()`` 343 | xformatter : string or list of strings 344 | Default '%d'. String formatting magic to apply to all x axes 345 | (string) or individual x axes (list of strings, 346 | length = ``self.total_stackdim``). Can use ``None`` to skip 347 | setting ``xformatter``, or insert ``None`` into list to skip 348 | setting ``xformatter`` for a particular axis column 349 | yformatter : string 350 | Default '%d'. String formatting magic to apply to all y axes. 351 | Can use ``None`` to skip setting ``yformatter`` 352 | 353 | """ 354 | 355 | if ax != 'all': 356 | if xformatter is not None: 357 | xfrmttr = FormatStrFormatter(xformatter) 358 | ax.xaxis.set_major_formatter(xfrmttr) 359 | if yformatter is not None: 360 | yfrmttr = FormatStrFormatter(yformatter) 361 | ax.yaxis.set_major_formatter(xfrmttr) 362 | 363 | else: 364 | if xformatter is not None: 365 | if type(xformatter) is str: 366 | xfrmttr = FormatStrFormatter(xformatter) 367 | for col in self.axes: 368 | for ax in col: 369 | ax.xaxis.set_major_formatter(xfrmttr) 370 | else: 371 | for xf, col in zip(xformatter, self.axes): 372 | if xf is not None: 373 | xfrmttr = FormatStrFormatter(xf) 374 | for ax in col: 375 | ax.xaxis.set_major_formatter(xfrmttr) 376 | 377 | if yformatter is not None: 378 | yfrmttr = FormatStrFormatter(yformatter) 379 | for col in self.axes: 380 | for ax in col: 381 | ax.yaxis.set_major_formatter(yfrmttr) 382 | 383 | def reverse_yaxis(self, reverse_y='all', adjust_bar_frame=True): 384 | """ 385 | Reverse any or all y axes. 386 | 387 | Parameters 388 | ---------- 389 | reverse_y : string or list of ints 390 | Default 'all'. 'all' or list of indices of the y axes to be 391 | reversed accepted. If unsure of index for a twin x axis in 392 | ``self.axes``, find using ``self.get_twin_colnum()``. 393 | adjust_bar_frame : Boolean 394 | Default True. Realign ``matplotlib Rectangle patches`` 395 | made via ``self.draw_bar`` and ``self.draw_frame``. 396 | 397 | """ 398 | 399 | if reverse_y == 'all': 400 | reverse_y = range(0, self.mainax_dim) 401 | 402 | # Invert y axis of each axis in the first column 403 | for r in reverse_y: 404 | self.axes[0][r].invert_yaxis() 405 | 406 | if adjust_bar_frame: 407 | self.adjust_bar_frame() 408 | 409 | def reverse_xaxis(self, reverse_x='all', adjust_bar_frame=True): 410 | """ 411 | Reverse any or all x axes. 412 | 413 | Parameters 414 | ---------- 415 | reverse_x : string or list of ints 416 | Default 'all'. 'all' or list of indices of the x axes to be 417 | reversed accepted. If unsure of index for a twin x axis in 418 | ``self.axes``, find using ``self.get_twin_colnum()``. 419 | adjust_bar_frame : Boolean 420 | Default True. Realign ``matplotlib Rectangle patches`` 421 | made via ``self.draw_bar`` and ``self.draw_frame``. 422 | 423 | """ 424 | 425 | if reverse_x == 'all': 426 | reverse_x = range(0, self.total_stackdim) 427 | 428 | # Invert x axis of first axis in each column 429 | for r in reverse_x: 430 | self.axes[r][0].invert_xaxis() 431 | 432 | if adjust_bar_frame: 433 | self.adjust_bar_frame() 434 | 435 | def set_xlim(self, xlim, adjust_bar_frame=True): 436 | """ 437 | Set x limits. 438 | 439 | Parameters 440 | ---------- 441 | xlim : list of tuples of ints and/or floats 442 | List of (column, min, max). If xdim is 1, then column is ignored. 443 | Also, if only one x axis needs ``xlim``, can just pass the tuple. 444 | If unsure of column index for a twin x axis in ``self.axes``, 445 | find using ``self.get_twin_colnum()`` 446 | adjust_bar_frame : Boolean 447 | Default True. Realign ``matplotlib Rectangle patches`` 448 | made via ``self.draw_bar`` and ``self.draw_frame``. 449 | 450 | """ 451 | 452 | if self.total_stackdim == 1: 453 | try: 454 | xlim.extend([]) 455 | except AttributeError: 456 | pass 457 | else: 458 | xlim = xlim[0] 459 | 460 | for col in self.axes: 461 | for ax in col: 462 | ax.set_xlim(xlim[-2], xlim[-1]) 463 | 464 | else: 465 | try: 466 | xlim.extend([]) 467 | except AttributeError: 468 | xlim = [xlim] 469 | 470 | for xl in xlim: 471 | for ax in self.axes[xl[0]]: 472 | ax.set_xlim(xl[1], xl[2]) 473 | 474 | if adjust_bar_frame: 475 | self.adjust_bar_frame() 476 | 477 | def set_ylim(self, ylim, adjust_bar_frame=True): 478 | """ 479 | Set y limits. 480 | 481 | Parameters 482 | ---------- 483 | ylim : List of tuples of ints and/or flaots 484 | List of (row, min, max). If ydim is 1, then row is ignored. 485 | Also, if only one y axis need ``ylim``, can just pass a tuple. 486 | adjust_bar_frame : Boolean 487 | Default True. Realign ``matplotlib Rectangle patches`` 488 | made via ``self.draw_bar`` and ``self.draw_frame``. 489 | 490 | """ 491 | 492 | if self.mainax_dim == 1: 493 | try: 494 | ylim.extend([]) 495 | except AttributeError: 496 | pass 497 | else: 498 | ylim = ylim[0] 499 | 500 | for col in self.axes: 501 | for ax in col: 502 | ax.set_ylim(ylim[-2], ylim[-1]) 503 | 504 | else: 505 | try: 506 | ylim.extend([]) 507 | except AttributeError: 508 | ylim = [ylim] 509 | 510 | for yl in ylim: 511 | for col in self.axes: 512 | col[yl[0]].set_ylim(yl[1], yl[2]) 513 | 514 | if adjust_bar_frame: 515 | self.adjust_bar_frame() 516 | 517 | def set_ticks(self, row='all', column='all', xy_axis='both', which='both', 518 | major_dim=(6, 2), minor_dim=(4, 1), labelsize=10, pad=10, 519 | major_tickdir='out', minor_tickdir='out'): 520 | """ 521 | Set x and/or y axis ticks for all or specified axes. 522 | 523 | Does not set axis color 524 | 525 | Parameters 526 | ---------- 527 | row : string or list of ints 528 | Default 'all'. The rows containing the axes that need tick 529 | parameters adjusted, 'all' or list of indices 530 | column: string or list of ints 531 | Default 'all'. The columns containing the axes that need tick 532 | parameters adjusted, 'all' or list of indices. If unsure of column 533 | index for a twin x axis in ``self.axes``, find using 534 | ``self.get_twin_colnum()`` 535 | xy_axis : string 536 | Default 'both'. ['x'|'y'|'both'] 537 | which : string 538 | Default 'both'. ['major'|'minor'|'both'], the set of ticks 539 | to adjust. 540 | major_dim : tuple of ints or floats 541 | Default (6, 2). The (length, width) of the major ticks. 542 | minor_dim : tuple of ints or floats 543 | Default (4, 1). The (length, width) of the minor ticks. 544 | labelsize : int 545 | Default 10. Tick label fontsize. 546 | pad : int 547 | Default 10. Spacing between the tick and tick label. 548 | major_tickdir : string 549 | Default 'out'. ['out'|'in'|'inout']. The major tick direction. 550 | minor_tickdir : string 551 | Default 'out'. ['out'|'in'|'inout']. The minor tick direction. 552 | 553 | """ 554 | 555 | if row == 'all': 556 | row = range(0, self.mainax_dim) 557 | if column == 'all': 558 | column = range(0, self.total_stackdim) 559 | 560 | if which != 'major': 561 | Grid._set_ticks(self, column, row, xy_axis, 'minor', minor_dim, 562 | labelsize, pad, minor_tickdir) 563 | 564 | if which != 'minor': 565 | Grid._set_ticks(self, column, row, xy_axis, 'major', major_dim, 566 | labelsize, pad, major_tickdir) 567 | 568 | def draw_cutout(self, di=0.025, lw='default', **kwargs): 569 | """ 570 | Draw cut marks to signifiy broken y axes. 571 | 572 | Only drawn when ``self.mainax_dim`` > 1. 573 | 574 | Parameters 575 | ---------- 576 | di : float 577 | Default 0.025. The dimensions of the cutout mark as a 578 | fraction of the smallest axis length. 579 | lw : int 580 | Default 'default'. If 'default', ``lw = self.spinewidth``. 581 | **kwargs 582 | Passed to ``axes.plot()``. Any valid ``kwargs``. 583 | 584 | """ 585 | 586 | if self.mainax_dim > 1: 587 | 588 | # Adjust di so that cutouts will look exactly the same 589 | # on every axis, no matter their relative sizes 590 | miny = min(self.yratios) 591 | y = [di * (miny / y) for y in self.yratios] 592 | 593 | minx = min(self.xratios) 594 | x0 = di * (minx / self.xratios[0]) 595 | x1 = di * (minx / self.xratios[-1]) 596 | 597 | # Left and right x position 598 | left_x = (-(2 * x0), (2 * x0)) 599 | right_x = (1 - (2 * x1), 1 + (2 * x1)) 600 | 601 | right_ind = self.stackdim - 1 602 | 603 | if lw == 'default': 604 | lw = self.spinewidth 605 | 606 | l_ax = self.axes[0][0] 607 | r_ax = self.axes[right_ind][0] 608 | lower = (-y[0], y[0]) 609 | 610 | # first axes in columns, lower only 611 | kwargs = dict(transform=l_ax.transAxes, clip_on=False, 612 | color='black', lw=lw, **kwargs) 613 | l_ax.plot(left_x, lower, **kwargs) 614 | 615 | kwargs.update(transform=r_ax.transAxes) 616 | r_ax.plot(right_x, lower, **kwargs) 617 | 618 | # Middle axes 619 | for i in range(1, self.mainax_dim - 1): 620 | l_ax = self.axes[0][i] 621 | r_ax = self.axes[right_ind][i] 622 | upper = (1 - y[i], 1 + y[i]) 623 | lower = (-y[i], y[i]) 624 | 625 | kwargs.update(transform=l_ax.transAxes) 626 | l_ax.plot(left_x, upper, **kwargs) 627 | l_ax.plot(left_x, lower, **kwargs) 628 | 629 | kwargs.update(transform=r_ax.transAxes) 630 | r_ax.plot(right_x, upper, **kwargs) 631 | r_ax.plot(right_x, lower, **kwargs) 632 | 633 | # Last axes in columns, upper only 634 | l_ax = self.axes[0][-1] 635 | r_ax = self.axes[right_ind][-1] 636 | upper = (1 - y[-1], 1 + y[-1]) 637 | 638 | kwargs.update(transform=l_ax.transAxes) 639 | l_ax.plot(left_x, upper, **kwargs) 640 | 641 | kwargs.update(transform=r_ax.transAxes) 642 | r_ax.plot(right_x, upper, **kwargs) 643 | 644 | def set_xlabels(self, xlabels, fontsize=None, labelpad=12, **kwargs): 645 | """ 646 | Tool for setting all x axis labels at once. Can skip labelling an axis 647 | by providing a ``None`` in corresponding poiition in list. 648 | 649 | Parameters 650 | ---------- 651 | xlabels : list of strings 652 | The list of labels, one per x-axis. Insert ``None`` in list to 653 | skip an axis. 654 | fontsize : int 655 | Default None. The font size of ``xlabels`` 656 | labelpad : int 657 | Default 12. The spacing between the tick labels adn the axis 658 | labels. 659 | **kwargs 660 | Passed to ``axes.set_xlabel()``. 661 | Any ``matplotlib Text`` properties 662 | 663 | Notes 664 | ----- 665 | Caution- this will set twin axis labels in the order that twins were 666 | created, which may not correspond to physical position in grid. 667 | 668 | """ 669 | 670 | for col, side, xl in zip(self.axes, self.dataside_list, xlabels): 671 | if xl is not None: 672 | 673 | if side == 'top': 674 | ind = 0 675 | else: 676 | ind = -1 677 | 678 | col[ind].xaxis.set_label_position(side) 679 | col[ind].set_xlabel(xl, fontsize=fontsize, 680 | labelpad=labelpad, **kwargs) 681 | --------------------------------------------------------------------------------