├── .github ├── ISSUE_TEMPLATE.md └── workflows │ └── CI.yml ├── .gitignore ├── AUTHORS.rst ├── CITATION.cff ├── CONTRIBUTING.rst ├── LICENSE ├── MANIFEST.in ├── README.rst ├── docs ├── Makefile ├── assets │ ├── by=gender+column=age+viz=box+ext=.png │ ├── by=gender+column=age+viz=box+ext=_padded.png │ ├── c=c+s=d+viz=scatter+x=a+y=b+ext=.png │ ├── c=c+s=d+viz=scatter+x=a+y=b+ext=_padded.png │ ├── col=time+hue=sex+post=add_legend+viz=lmplot+x=total-bill+y=tip+ext=.png │ ├── col=time+hue=sex+post=add_legend+viz=lmplot+x=total-bill+y=tip+ext=_padded.png │ ├── col=time+hue=sex+post=teed-map-dataframe-sns-scatterplot-x-total-bill-y-tip+viz=facetgrid+ext=.png │ ├── col=time+hue=sex+post=teed-map-dataframe-sns-scatterplot-x-total-bill-y-tip+viz=facetgrid+ext=_padded.png │ ├── hue=method+palette=vlag+post=teed-set-xscale-log+viz=boxplot+x=distance+y=method+ext=.png │ ├── hue=method+palette=vlag+post=teed-set-xscale-log+viz=boxplot+x=distance+y=method+ext=_padded.png │ ├── pad_images.sh │ ├── teeplot-wordmark.png │ ├── viz=dot-plot+x-vars=index-total-speeding-alcohol-not-distracted-no-previous-dtype-object+y-vars=abbrev+ext=.png │ └── viz=dot-plot+x-vars=index-total-speeding-alcohol-not-distracted-no-previous-dtype-object+y-vars=abbrev+ext=_padded.png ├── authors.rst ├── conf.py ├── contributing.rst ├── index.rst ├── installation.rst ├── make.bat ├── readme.rst └── usage.rst ├── requirements ├── requirements_dev-mypy.txt ├── requirements_dev-py310.txt ├── requirements_dev-py311.txt ├── requirements_dev-py312.txt ├── requirements_dev-py313.txt ├── requirements_dev-py38.txt ├── requirements_dev-py39.txt ├── requirements_dev.in └── requirements_dev.sh ├── setup.cfg ├── setup.py ├── teeplot ├── __init__.py └── teeplot.py ├── tests ├── __init__.py ├── test_tee.py ├── test_teed.py └── test_teewrap.py └── tox.ini /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | * teeplot version: 2 | * Python version: 3 | * Operating System: 4 | 5 | ### Description 6 | 7 | Describe what you were trying to get done. 8 | Tell us what happened, what went wrong, and what you expected to happen. 9 | 10 | ### What I Did 11 | 12 | ``` 13 | Paste the command(s) you ran and the output. 14 | If there was a crash, please include the traceback here. 15 | ``` 16 | -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: teeplot 2 | 3 | on: 4 | - push 5 | - pull_request 6 | - workflow_dispatch 7 | 8 | jobs: 9 | build: 10 | strategy: 11 | matrix: 12 | python-version: [3.8, 3.9, "3.10", 3.11, 3.12, 3.13] 13 | os: [ubuntu-latest, windows-latest, macos-latest] 14 | fail-fast: false 15 | 16 | runs-on: ${{ matrix.os }} 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | 21 | - name: Set up Python ${{ matrix.python-version }} 22 | uses: actions/setup-python@v2 23 | with: 24 | python-version: ${{ matrix.python-version }} 25 | 26 | - name: Install dependencies 27 | run: | 28 | python -m pip install --upgrade pip 29 | python -m pip install tox tox-gh-actions pypandoc-binary 30 | 31 | # Install TeX distribution for PGF output from matplotlib 32 | - name: Install TeX distribution for PGF output (Ubuntu) 33 | if: runner.os == 'Linux' 34 | run: | 35 | sudo apt-get update 36 | sudo apt-get install -y texlive-latex-extra texlive-xetex 37 | 38 | - name: Install TeX distribution for PGF output (Windows) 39 | if: runner.os == 'Windows' 40 | run: | 41 | choco install miktex -y 42 | echo "C:\Program Files\MiKTeX\miktex\bin\x64" >> $env:GITHUB_PATH 43 | 44 | - name: Install TeX distribution for PGF output (macOS) 45 | if: runner.os == 'macOS' 46 | run: | 47 | brew install --cask mactex-no-gui 48 | echo "/Library/TeX/texbin" >> $GITHUB_PATH 49 | 50 | - name: Test with tox 51 | run: tox 52 | 53 | deploy: 54 | needs: build 55 | runs-on: ubuntu-latest 56 | if: startsWith(github.ref, 'refs/tags/v') 57 | 58 | steps: 59 | - uses: actions/checkout@v2 60 | - name: Set up Python 3.8 61 | uses: actions/setup-python@v2 62 | with: 63 | python-version: 3.8 64 | - name: Install dependencies 65 | run: | 66 | python -m pip install --upgrade pip 67 | python -m pip install setuptools wheel twine pypandoc-binary 68 | - run: python setup.py sdist bdist_wheel 69 | - name: Publish package 70 | uses: pypa/gh-action-pypi-publish@release/v1 71 | with: 72 | user: __token__ 73 | password: ${{ secrets.PYPI_API_TOKEN }} 74 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # test artifacts 2 | teeplots/ 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | env/ 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | 61 | # Flask stuff: 62 | instance/ 63 | .webassets-cache 64 | 65 | # Scrapy stuff: 66 | .scrapy 67 | 68 | # Sphinx documentation 69 | docs/_build/ 70 | 71 | # PyBuilder 72 | target/ 73 | 74 | # Jupyter Notebook 75 | .ipynb_checkpoints 76 | 77 | # pyenv 78 | .python-version 79 | 80 | # celery beat schedule file 81 | celerybeat-schedule 82 | 83 | # SageMath parsed files 84 | *.sage.py 85 | 86 | # dotenv 87 | .env 88 | 89 | # virtualenv 90 | .venv 91 | venv/ 92 | ENV/ 93 | 94 | # Spyder project settings 95 | .spyderproject 96 | .spyproject 97 | 98 | # Rope project settings 99 | .ropeproject 100 | 101 | # mkdocs documentation 102 | /site 103 | 104 | # mypy 105 | .mypy_cache/ 106 | 107 | # IDE settings 108 | .vscode/ 109 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | Credits 3 | ======= 4 | 5 | Development Lead 6 | ---------------- 7 | 8 | * Matthew Andres Moreno 9 | 10 | Contributors 11 | ------------ 12 | 13 | None yet. Why not be the first? 14 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.1.0 2 | message: "If you use this software, please cite it as below." 3 | title: 'teeplot: a Python library to wrangle data visualizations' 4 | abstract: "Organize data visualization output, automatically picking meaningful filenames based on semantic plotting variables." 5 | authors: 6 | - family-names: Moreno 7 | given-names: Matthew Andres 8 | orcid: 0000-0003-4726-4479 9 | date-released: 2023-12-29 10 | doi: 10.5281/zenodo.10440670 11 | license: MIT 12 | repository-code: https://github.com/mmore500/teeplot 13 | url: "https://github.com/mmore500/teeplot" 14 | version: v1.0.1 15 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: shell 2 | 3 | ============ 4 | Contributing 5 | ============ 6 | 7 | Contributions are welcome, and they are greatly appreciated! Every little bit 8 | helps, and credit will always be given. 9 | 10 | You can contribute in many ways: 11 | 12 | Types of Contributions 13 | ---------------------- 14 | 15 | Report Bugs 16 | ~~~~~~~~~~~ 17 | 18 | Report bugs at https://github.com/mmore500/teeplot/issues. 19 | 20 | If you are reporting a bug, please include: 21 | 22 | * Your operating system name and version. 23 | * Any details about your local setup that might be helpful in troubleshooting. 24 | * Detailed steps to reproduce the bug. 25 | 26 | Fix Bugs 27 | ~~~~~~~~ 28 | 29 | Look through the GitHub issues for bugs. Anything tagged with "bug" and "help 30 | wanted" is open to whoever wants to implement it. 31 | 32 | Implement Features 33 | ~~~~~~~~~~~~~~~~~~ 34 | 35 | Look through the GitHub issues for features. Anything tagged with "enhancement" 36 | and "help wanted" is open to whoever wants to implement it. 37 | 38 | Write Documentation 39 | ~~~~~~~~~~~~~~~~~~~ 40 | 41 | teeplot could always use more documentation, whether as part of the 42 | official teeplot docs, in docstrings, or even on the web in blog posts, 43 | articles, and such. 44 | 45 | Submit Feedback 46 | ~~~~~~~~~~~~~~~ 47 | 48 | The best way to send feedback is to file an issue at https://github.com/mmore500/teeplot/issues. 49 | 50 | If you are proposing a feature: 51 | 52 | * Explain in detail how it would work. 53 | * Keep the scope as narrow as possible, to make it easier to implement. 54 | * Remember that this is a volunteer-driven project, and that contributions 55 | are welcome :) 56 | 57 | Get Started! 58 | ------------ 59 | 60 | Ready to contribute? Here's how to set up `teeplot` for local development. 61 | 62 | 1. Fork the `teeplot` repo on GitHub. 63 | 2. Clone your fork locally:: 64 | 65 | $ git clone git@github.com:your_name_here/teeplot.git 66 | 67 | 3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development:: 68 | 69 | $ mkvirtualenv teeplot 70 | $ cd teeplot/ 71 | $ python setup.py develop 72 | 73 | 4. Create a branch for local development:: 74 | 75 | $ git checkout -b name-of-your-bugfix-or-feature 76 | 77 | Now you can make your changes locally. 78 | 79 | 5. When you're done making changes, check that your changes pass flake8 and the 80 | tests, including testing other Python versions with tox:: 81 | 82 | $ flake8 teeplot tests 83 | $ python setup.py test or pytest 84 | $ tox 85 | 86 | To get flake8 and tox, just pip install them into your virtualenv. 87 | 88 | 6. Commit your changes and push your branch to GitHub:: 89 | 90 | $ git add . 91 | $ git commit -m "Your detailed description of your changes." 92 | $ git push origin name-of-your-bugfix-or-feature 93 | 94 | 7. Submit a pull request through the GitHub website. 95 | 96 | Pull Request Guidelines 97 | ----------------------- 98 | 99 | Before you submit a pull request, check that it meets these guidelines: 100 | 101 | 1. The pull request should include tests. 102 | 2. If the pull request adds functionality, the docs should be updated. Put 103 | your new functionality into a function with a docstring, and add the 104 | feature to the list in README.rst. 105 | 3. The pull request should work for Python 3.6, 3.7 and 3.8, and for PyPy. Check 106 | https://travis-ci.com/mmore500/teeplot/pull_requests 107 | and make sure that the tests pass for all supported Python versions. 108 | 109 | Tips 110 | ---- 111 | 112 | To run a subset of tests:: 113 | 114 | $ pytest tests.test_teeplot 115 | 116 | 117 | Deploying 118 | --------- 119 | 120 | A reminder for the maintainers on how to deploy. 121 | Make sure all your changes are committed. 122 | Then run:: 123 | 124 | $ bump2version patch # possible: major / minor / patch 125 | $ git push 126 | $ git push --tags 127 | 128 | Travis will then deploy to PyPI if tests pass. 129 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020, Matthew Andres Moreno 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS.rst 2 | include CONTRIBUTING.rst 3 | include LICENSE 4 | include README.rst 5 | 6 | recursive-include tests * 7 | recursive-exclude * __pycache__ 8 | recursive-exclude * *.py[co] 9 | 10 | recursive-include docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif 11 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. figure:: docs/assets/teeplot-wordmark.png 2 | :target: https://github.com/mmore500/teeplot 3 | :alt: teeplot wordmark 4 | 5 | 6 | *teeplot* wrangles your data visualizations out of notebooks for you 7 | -------------------------------------------------------------------- 8 | 9 | |PyPi| |docs| |GitHub stars| |CI| |zenodo| 10 | 11 | * Free software: MIT license 12 | * Installation: ``python3 -m pip install teeplot`` 13 | * Documentation: https://github.com/mmore500/teeplot/blob/master/README.rst 14 | * Repository: https://github.com/mmore500/teeplot 15 | 16 | *teeplot*'s ``tee`` function can wrap your plotting calls to **automatically manage matplotlib file output**, picking meaningful names based on semantic plotting variables. 17 | 18 | .. code-block:: python 19 | 20 | # adapted from https://seaborn.pydata.org/generated/seaborn.FacetGrid.html#seaborn.FacetGrid 21 | import seaborn as sns; from teeplot import teeplot as tp 22 | 23 | tp.tee(sns.lmplot, # plotter 24 | sns.load_dataset("tips"), col="time", hue="sex", x="total_bill", y="tip", # fwded kw/args 25 | teeplot_postprocess=sns.FacetGrid.add_legend) # teeplot options 26 | 27 | .. 28 | 29 | .. 30 | 31 | .. code-block:: 32 | 33 | teeplots/col=time+hue=sex+post=add_legend+viz=lmplot+x=total-bill+y=tip+ext=.pdf 34 | teeplots/col=time+hue=sex+post=add_legend+viz=lmplot+x=total-bill+y=tip+ext=.png 35 | 36 | .. image:: docs/assets/col=time+hue=sex+post=add_legend+viz=lmplot+x=total-bill+y=tip+ext=_padded.png 37 | 38 | 39 | **Here's how it works:** *teeplot*'s ``tee`` function that acts as a wrapper around your plotting calls. 40 | Give ``tee`` your plotting function (e.g., ``sns.lineplot``) as the first argument and then add the arguments you want to call it with. 41 | 42 | *teeplot* automatically captures the function and its arguments, calls the plotter as instructed, and then it handles the matplotlib file output for you. 43 | It generates descriptive filenames for the saved plots by extracting key information from the plot parameters and arguments. 44 | This feature allows you to *keep track of your visualizations easily* by making the process of saving and cataloging your plots more *efficient*, *systematic* and *meaningful*, taking the hassle out of manual file management. 45 | 46 | *teeplot* contains several advanced features, such as a ``draftmode`` flag, which will disable file output globally, and the ``teeplot_callback`` kwarg, which delays plot output to allow for figure tweaks. 47 | Read on for details. 48 | 49 | Contents 50 | -------- 51 | 52 | - **Usage** : `Example 1 <#example-1>`_ | `Example 2 <#example-2>`_ | `Example 3 <#example-3>`_ | `Example 4 <#example-4>`_ | `Example 5 <#example-5>`_ 53 | - **API** : `teeplot.tee() <#teeplottee>`_ | `Module-Level Configuration <#module-level-configuration>`_ | `Environment Variables <#environment-variables>`_ 54 | - **Citing** `here <#citing>`_ | **Credits** `link <#credits>`_ 55 | 56 | Usage 57 | ----- 58 | 59 | Example 1 60 | ^^^^^^^^^ 61 | 62 | Simple example demonstrating use with *pandas* built-in plotting. 63 | 64 | .. code-block:: python 65 | 66 | # adapted from https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.plot.box.html 67 | import pandas as pd; from teeplot import teeplot as tp 68 | 69 | age_list = [8, 10, 12, 14, 72, 74, 76, 78, 20, 25, 30, 35, 60, 85] 70 | df = pd.DataFrame({"gender": list("MMMMMMMMFFFFFF"), "age": age_list}) 71 | 72 | tp.tee(df.plot.box, # plotter... 73 | column="age", by="gender", figsize=(4, 3)) # ...forwarded kwargs 74 | 75 | .. 76 | 77 | .. 78 | 79 | .. code-block:: 80 | 81 | teeplots/by=gender+column=age+viz=box+ext=.pdf 82 | teeplots/by=gender+column=age+viz=box+ext=.png 83 | 84 | .. image:: docs/assets/by=gender+column=age+viz=box+ext=_padded.png 85 | 86 | ---- 87 | 88 | Example 2 89 | ^^^^^^^^^ 90 | 91 | Example with *seaborn* showing use of ``teed`` context manager interface to allow for plot tweaks before saving. 92 | 93 | .. code-block:: python 94 | 95 | # adapted from https://seaborn.pydata.org/examples/horizontal_boxplot.html 96 | from matplotlib import pyplot as plt 97 | import seaborn as sns 98 | from teeplot import teeplot as tp 99 | 100 | with tp.teed( # plot is finalized upon leaving context 101 | sns.boxplot, # plotter... 102 | sns.load_dataset("planets"), # ...forwarded arg & kwargs 103 | x="distance", y="method", hue="method", palette="vlag", 104 | whis=[0, 100], width=.6, # ... and then teeplot options 105 | teeplot_callback=True, teeplot_postprocess="teed.set_xscale('log')" 106 | ) as ax: 107 | ax.xaxis.grid(True) # now some tweaks 108 | ax.set(ylabel="") 109 | sns.despine() 110 | plt.gcf().set_size_inches(10, 4) 111 | 112 | .. 113 | 114 | .. 115 | 116 | .. code-block:: 117 | 118 | teeplots/hue=method+palette=vlag+post=teed-set-xscale-log+viz=boxplot+x=distance+y=method+ext=.pdf 119 | teeplots/hue=method+palette=vlag+post=teed-set-xscale-log+viz=boxplot+x=distance+y=method+ext=.png 120 | 121 | .. image:: docs/assets/hue=method+palette=vlag+post=teed-set-xscale-log+viz=boxplot+x=distance+y=method+ext=_padded.png 122 | 123 | ---- 124 | 125 | Example 3 126 | ^^^^^^^^^ 127 | 128 | Example with matplotlib, showing alternate ``teeplot_callback`` kwarg interface. 129 | We've also used the global configuration option ``save`` to change default output format. 130 | 131 | .. code-block:: python 132 | 133 | # adapted from https://matplotlib.org/stable/tutorials/pyplot.html 134 | from matplotlib import pyplot as plt 135 | import numpy as np; from teeplot import teeplot as tp 136 | tp.save = {".eps": True} # make default output only .eps 137 | 138 | data = {'a': np.arange(50), 'c': np.random.randint(0, 50, 50), 139 | 'd': np.random.randn(50)} 140 | data['b'], data['d'] = data['a'] + 10 * np.random.randn(50), np.abs(data['d']) * 100 141 | 142 | saveit, __ = tp.tee( # create a callback object to finalize plot 143 | plt.scatter, # plotter... 144 | data=data, x='a', y='b', c='c', s='d', # ...forwarded kwargs 145 | teeplot_callback=True) # teeplot options 146 | plt.xlabel('entry a') # now some tweaks 147 | plt.ylabel('entry b') 148 | plt.gcf().set_size_inches(5, 3) 149 | saveit() # dispatch output callback 150 | 151 | .. 152 | 153 | .. 154 | 155 | .. code-block:: 156 | 157 | teeplots/c=c+s=d+viz=scatter+x=a+y=b+ext=.eps 158 | 159 | .. image:: docs/assets/c=c+s=d+viz=scatter+x=a+y=b+ext=_padded.png 160 | 161 | ---- 162 | 163 | Example 4 164 | ^^^^^^^^^ 165 | 166 | Example with *seaborn* ``FacetGrid`` demonstrating use of ``exec``'ed ``teeplot_postprocess`` that adds a ``map_dataframe`` step over the ``teed`` result value and also results in additional semantic information being added to plot filenames (under the "``post=``" key). 167 | 168 | .. code-block:: python 169 | 170 | # adapted from https://seaborn.pydata.org/generated/seaborn.FacetGrid.html#seaborn.FacetGrid 171 | import seaborn as sns 172 | from teeplot import teeplot as tp 173 | 174 | tp.tee( 175 | sns.FacetGrid, # plotter... 176 | sns.load_dataset("tips"), # ...forwarded args & kwwargs 177 | col="time", hue="sex", aspect=1.5, 178 | teeplot_postprocess="teed.map_dataframe(sns.scatterplot, x='total_bill', y='tip')") 179 | 180 | .. 181 | 182 | .. 183 | 184 | .. code-block:: 185 | 186 | teeplots/col=time+hue=sex+post=teed-map-dataframe-sns-scatterplot-x-total-bill-y-tip+viz=facetgrid+ext=.pdf 187 | teeplots/col=time+hue=sex+post=teed-map-dataframe-sns-scatterplot-x-total-bill-y-tip+viz=facetgrid+ext=.png 188 | 189 | .. image:: docs/assets/col=time+hue=sex+post=teed-map-dataframe-sns-scatterplot-x-total-bill-y-tip+viz=facetgrid+ext=_padded.png 190 | 191 | ---- 192 | 193 | Example 5 194 | ^^^^^^^^^ 195 | 196 | Demonstration of teeplot use with a custom function. 197 | Note the function name automatically used as "``viz=``" key in output filenames. 198 | 199 | .. code-block:: python 200 | 201 | # adapted from https://seaborn.pydata.org/examples/pairgrid_dotplot.html 202 | import seaborn as sns; from teeplot import teeplot as tp 203 | df = sns.load_dataset("car_crashes") 204 | 205 | def dot_plot(data, x_vars, y_vars): # custom plotter 206 | g = sns.PairGrid(data.sort_values("total", ascending=False), 207 | x_vars=x_vars, y_vars=y_vars, 208 | height=5, aspect=0.66) 209 | g.map(sns.stripplot, size=10, orient="h", jitter=False, 210 | palette="flare_r", linewidth=1, edgecolor="w") 211 | for ax in g.axes.flat: 212 | ax.xaxis.grid(False) 213 | ax.yaxis.grid(True) 214 | 215 | 216 | tp.tee( 217 | dot_plot, # plotter, then forwarded args/kwargs 218 | df[df["abbrev"].str.contains("A")], x_vars=df.columns[:-3], y_vars=["abbrev"], 219 | teeplot_outinclude=["x_vars", "y_vars"], teeplot_save={".eps", ".png"}) 220 | 221 | .. 222 | 223 | .. 224 | 225 | .. code-block:: 226 | 227 | teeplots/viz=dot-plot+x-vars=index-total-speeding-alcohol-not-distracted-no-previous-dtype-object+y-vars=abbrev+ext=.eps 228 | teeplots/viz=dot-plot+x-vars=index-total-speeding-alcohol-not-distracted-no-previous-dtype-object+y-vars=abbrev+ext=.png 229 | 230 | 231 | .. image:: docs/assets/viz=dot-plot+x-vars=index-total-speeding-alcohol-not-distracted-no-previous-dtype-object+y-vars=abbrev+ext=_padded.png 232 | 233 | ---- 234 | 235 | Further Examples 236 | ^^^^^^^^^^^^^^^^ 237 | 238 | Find more examples and use cases `on medium `_. 239 | 240 | 241 | API 242 | --- 243 | 244 | ``teeplot.tee()`` 245 | ^^^^^^^^^^^^^^^^^ 246 | 247 | Executes a plotting function and saves the resulting plot to specified formats using a descriptive filename automatically generated from plotting function arguments. 248 | 249 | 250 | +----------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 251 | | Parameter | Description | 252 | +============================+==========================================================================================================================================================================================================================================+ 253 | | ``plotter`` | The plotting function to be executed. *Required.* | 254 | +----------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 255 | | *Additional args & kwargs* | Forwarded to the plotting function and used to build the output filename. | 256 | +----------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 257 | | ``teeplot_callback`` | If True, returns a tuple with a callback to dispatch plot save instead of immediately saving the plot after running the plotter. Default is False. | 258 | +----------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 259 | | ``teeplot_dpi`` | Resolution for rasterized components of saved plots, default is publication-quality 300 dpi. | 260 | +----------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 261 | | ``teeplot_oncollision`` | Strategy for handling filename collisions: "error", "fix", "ignore", or "warn", default "warn"; inferred from environment if not specified. | 262 | +----------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 263 | | ``teeplot_outattrs`` | Dict with additional key-value attributes to include in the output filename. | 264 | +----------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 265 | | ``teeplot_outdir`` | Base directory for saving plots, default "teeplots". | 266 | +----------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 267 | | ``teeplot_outinclude`` | Attribute keys to always include, if present, in the output filename. | 268 | +----------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 269 | | ``teeplot_outexclude`` | Attribute keys to always exclude, if present, from the output filename. | 270 | +----------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 271 | | ``teeplot_postprocess`` | Actions to perform after plotting but before saving. Can be a string of code to ``exec`` or a callable function. If a string, it's executed with access to ``plt`` and ``sns`` (if installed), and the plotter return value as ``teed``. | 272 | +----------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 273 | | ``teeplot_save`` | File formats to save the plots in. Defaults to global settings if ``True``, all output suppressed if ``False``. Default global setting is ``{" .png", ".pdf"}``. Supported: ".eps", ".png", ".pdf", ".pgf", ".ps", ".svg". | 274 | +----------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 275 | | ``teeplot_show`` | Dictates whether ``plt.show()`` should be called after plot is saved. If True, the plot is displayed using ``plt.show()``. Default behavior is to display if an interactive environment is detected (e.g., a notebook). | 276 | +----------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 277 | | ``teeplot_subdir`` | Optionally, subdirectory within the main output directory for plot organization. | 278 | +----------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 279 | | ``teeplot_transparent`` | Option to save the plot with a transparent background, default True. | 280 | +----------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 281 | | ``teeplot_verbose`` | Toggles printing of saved filenames, default True. | 282 | +----------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 283 | 284 | **Return Value**: returned result from plotter call if ``teeplot_callback`` is ``False``, otherwise tuple of save-plot callback and result from plotter call. 285 | 286 | 287 | Module-Level Configuration 288 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 289 | 290 | - ``teeplot.draftmode``: A boolean indicating whether to suppress output to all file formats. 291 | - ``teeplot.oncollision``: Default strategy for handling filename collisions, options are 'error', 'fix', 'ignore', or 'warn'. 292 | - ``teeplot.save``: A dictionary mapping file formats (e.g., ".png") to default save behavior as ``True`` (always output), ``False`` (never output), or ``None`` (defer to call kwargs). 293 | 294 | Environment Variables 295 | ^^^^^^^^^^^^^^^^^^^^^ 296 | 297 | - ``TEEPLOT_ONCOLLISION``: Configures the default collision handling strategy. See ``teeplot_oncollision`` kwarg 298 | - ``TEEPLOT_DRAFTMODE``: If set, enables draft mode globally. 299 | - ``TEEPLOT_``: Boolean flags that determine default behavior for each format (e.g., ``EPS``, ``PNG``, ``PDF``, ``PGF``, ``PS``, ``SVG``); "defer" defers to call kwargs. 300 | 301 | Citing 302 | ------ 303 | 304 | If *teeplot* contributes to a scholarly publication, please cite it as 305 | 306 | Matthew Andres Moreno. (2023). mmore500/teeplot. Zenodo. https://doi.org/10.5281/zenodo.10440670 307 | 308 | .. code:: bibtex 309 | 310 | @software{moreno2023teeplot, 311 | author = {Matthew Andres Moreno}, 312 | title = {mmore500/teeplot}, 313 | month = dec, 314 | year = 2023, 315 | publisher = {Zenodo}, 316 | doi = {10.5281/zenodo.10440670}, 317 | url = {https://doi.org/10.5281/zenodo.10440670} 318 | } 319 | 320 | And don't forget to leave a `star on GitHub `__! 321 | 322 | Credits 323 | ------- 324 | 325 | Output filenames are constructed using the `keyname `_ package. 326 | 327 | This package was created with Cookiecutter_ and the `audreyr/cookiecutter-pypackage`_ project template. 328 | 329 | .. _Cookiecutter: https://github.com/audreyr/cookiecutter 330 | .. _`audreyr/cookiecutter-pypackage`: https://github.com/audreyr/cookiecutter-pypackage 331 | 332 | .. |PyPi| image:: https://img.shields.io/pypi/v/teeplot.svg 333 | :target: https://pypi.python.org/pypi/teeplot 334 | .. |CI| image:: https://github.com/mmore500/teeplot/actions/workflows/CI.yml/badge.svg 335 | :target: https://github.com/mmore500/teeplot/actions 336 | .. |GitHub stars| image:: https://img.shields.io/github/stars/mmore500/teeplot.svg?style=round-square&logo=github&label=Stars&logoColor=white 337 | :target: https://github.com/mmore500/teeplot 338 | .. |docs| image:: https://img.shields.io/badge/docs%20-%20readme%20-%20fedcba?logo=github 339 | :target: https://github.com/mmore500/teeplot/blob/master/README.rst 340 | .. |zenodo| image:: https://zenodo.org/badge/DOI/10.5281/zenodo.10440670.svg 341 | :target: https://doi.org/10.5281/zenodo.10440670 342 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = python -msphinx 7 | SPHINXPROJ = teeplot 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/assets/by=gender+column=age+viz=box+ext=.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmore500/teeplot/11b80594ff32df5da43fa34fabca81f89054fd3f/docs/assets/by=gender+column=age+viz=box+ext=.png -------------------------------------------------------------------------------- /docs/assets/by=gender+column=age+viz=box+ext=_padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmore500/teeplot/11b80594ff32df5da43fa34fabca81f89054fd3f/docs/assets/by=gender+column=age+viz=box+ext=_padded.png -------------------------------------------------------------------------------- /docs/assets/c=c+s=d+viz=scatter+x=a+y=b+ext=.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmore500/teeplot/11b80594ff32df5da43fa34fabca81f89054fd3f/docs/assets/c=c+s=d+viz=scatter+x=a+y=b+ext=.png -------------------------------------------------------------------------------- /docs/assets/c=c+s=d+viz=scatter+x=a+y=b+ext=_padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmore500/teeplot/11b80594ff32df5da43fa34fabca81f89054fd3f/docs/assets/c=c+s=d+viz=scatter+x=a+y=b+ext=_padded.png -------------------------------------------------------------------------------- /docs/assets/col=time+hue=sex+post=add_legend+viz=lmplot+x=total-bill+y=tip+ext=.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmore500/teeplot/11b80594ff32df5da43fa34fabca81f89054fd3f/docs/assets/col=time+hue=sex+post=add_legend+viz=lmplot+x=total-bill+y=tip+ext=.png -------------------------------------------------------------------------------- /docs/assets/col=time+hue=sex+post=add_legend+viz=lmplot+x=total-bill+y=tip+ext=_padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmore500/teeplot/11b80594ff32df5da43fa34fabca81f89054fd3f/docs/assets/col=time+hue=sex+post=add_legend+viz=lmplot+x=total-bill+y=tip+ext=_padded.png -------------------------------------------------------------------------------- /docs/assets/col=time+hue=sex+post=teed-map-dataframe-sns-scatterplot-x-total-bill-y-tip+viz=facetgrid+ext=.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmore500/teeplot/11b80594ff32df5da43fa34fabca81f89054fd3f/docs/assets/col=time+hue=sex+post=teed-map-dataframe-sns-scatterplot-x-total-bill-y-tip+viz=facetgrid+ext=.png -------------------------------------------------------------------------------- /docs/assets/col=time+hue=sex+post=teed-map-dataframe-sns-scatterplot-x-total-bill-y-tip+viz=facetgrid+ext=_padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmore500/teeplot/11b80594ff32df5da43fa34fabca81f89054fd3f/docs/assets/col=time+hue=sex+post=teed-map-dataframe-sns-scatterplot-x-total-bill-y-tip+viz=facetgrid+ext=_padded.png -------------------------------------------------------------------------------- /docs/assets/hue=method+palette=vlag+post=teed-set-xscale-log+viz=boxplot+x=distance+y=method+ext=.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmore500/teeplot/11b80594ff32df5da43fa34fabca81f89054fd3f/docs/assets/hue=method+palette=vlag+post=teed-set-xscale-log+viz=boxplot+x=distance+y=method+ext=.png -------------------------------------------------------------------------------- /docs/assets/hue=method+palette=vlag+post=teed-set-xscale-log+viz=boxplot+x=distance+y=method+ext=_padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmore500/teeplot/11b80594ff32df5da43fa34fabca81f89054fd3f/docs/assets/hue=method+palette=vlag+post=teed-set-xscale-log+viz=boxplot+x=distance+y=method+ext=_padded.png -------------------------------------------------------------------------------- /docs/assets/pad_images.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | for img in *=.png; do 4 | echo "img ${img}" 5 | # Get current dimensions of the image. 6 | height=$(identify -format "%h" "$img") # Height of the current image 7 | 8 | # Calculate the desired width for a 4:1 aspect ratio. 9 | desired_width=$((4 * height)) 10 | 11 | # Add padding to the left and right sides of the image. 12 | convert "$img" -gravity center -background white -extent "${desired_width}x${height}" "${img%.png}_padded.png" 13 | done 14 | -------------------------------------------------------------------------------- /docs/assets/teeplot-wordmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmore500/teeplot/11b80594ff32df5da43fa34fabca81f89054fd3f/docs/assets/teeplot-wordmark.png -------------------------------------------------------------------------------- /docs/assets/viz=dot-plot+x-vars=index-total-speeding-alcohol-not-distracted-no-previous-dtype-object+y-vars=abbrev+ext=.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmore500/teeplot/11b80594ff32df5da43fa34fabca81f89054fd3f/docs/assets/viz=dot-plot+x-vars=index-total-speeding-alcohol-not-distracted-no-previous-dtype-object+y-vars=abbrev+ext=.png -------------------------------------------------------------------------------- /docs/assets/viz=dot-plot+x-vars=index-total-speeding-alcohol-not-distracted-no-previous-dtype-object+y-vars=abbrev+ext=_padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmore500/teeplot/11b80594ff32df5da43fa34fabca81f89054fd3f/docs/assets/viz=dot-plot+x-vars=index-total-speeding-alcohol-not-distracted-no-previous-dtype-object+y-vars=abbrev+ext=_padded.png -------------------------------------------------------------------------------- /docs/authors.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../AUTHORS.rst 2 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # teeplot documentation build configuration file, created by 4 | # sphinx-quickstart on Fri Jun 9 13:47:02 2017. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | # If extensions (or modules to document with autodoc) are in another 16 | # directory, add these directories to sys.path here. If the directory is 17 | # relative to the documentation root, use os.path.abspath to make it 18 | # absolute, like shown here. 19 | # 20 | import os 21 | import sys 22 | sys.path.insert(0, os.path.abspath('..')) 23 | 24 | import teeplot 25 | 26 | # -- General configuration --------------------------------------------- 27 | 28 | # If your documentation needs a minimal Sphinx version, state it here. 29 | # 30 | # needs_sphinx = '1.0' 31 | 32 | # Add any Sphinx extension module names here, as strings. They can be 33 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 34 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] 35 | 36 | # Add any paths that contain templates here, relative to this directory. 37 | templates_path = ['_templates'] 38 | 39 | # The suffix(es) of source filenames. 40 | # You can specify multiple suffix as a list of string: 41 | # 42 | # source_suffix = ['.rst', '.md'] 43 | source_suffix = '.rst' 44 | 45 | # The master toctree document. 46 | master_doc = 'index' 47 | 48 | # General information about the project. 49 | project = 'teeplot' 50 | copyright = "2020, Matthew Andres Moreno" 51 | author = "Matthew Andres Moreno" 52 | 53 | # The version info for the project you're documenting, acts as replacement 54 | # for |version| and |release|, also used in various other places throughout 55 | # the built documents. 56 | # 57 | # The short X.Y version. 58 | version = teeplot.__version__ 59 | # The full version, including alpha/beta/rc tags. 60 | release = teeplot.__version__ 61 | 62 | # The language for content autogenerated by Sphinx. Refer to documentation 63 | # for a list of supported languages. 64 | # 65 | # This is also used if you do content translation via gettext catalogs. 66 | # Usually you set "language" from the command line for these cases. 67 | language = None 68 | 69 | # List of patterns, relative to source directory, that match files and 70 | # directories to ignore when looking for source files. 71 | # This patterns also effect to html_static_path and html_extra_path 72 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 73 | 74 | # The name of the Pygments (syntax highlighting) style to use. 75 | pygments_style = 'sphinx' 76 | 77 | # If true, `todo` and `todoList` produce output, else they produce nothing. 78 | todo_include_todos = False 79 | 80 | 81 | # -- Options for HTML output ------------------------------------------- 82 | 83 | # The theme to use for HTML and HTML Help pages. See the documentation for 84 | # a list of builtin themes. 85 | # 86 | html_theme = 'alabaster' 87 | 88 | # Theme options are theme-specific and customize the look and feel of a 89 | # theme further. For a list of options available for each theme, see the 90 | # documentation. 91 | # 92 | # html_theme_options = {} 93 | 94 | # Add any paths that contain custom static files (such as style sheets) here, 95 | # relative to this directory. They are copied after the builtin static files, 96 | # so a file named "default.css" will overwrite the builtin "default.css". 97 | html_static_path = ['_static'] 98 | 99 | 100 | # -- Options for HTMLHelp output --------------------------------------- 101 | 102 | # Output file base name for HTML help builder. 103 | htmlhelp_basename = 'teeplotdoc' 104 | 105 | 106 | # -- Options for LaTeX output ------------------------------------------ 107 | 108 | latex_elements = { 109 | # The paper size ('letterpaper' or 'a4paper'). 110 | # 111 | # 'papersize': 'letterpaper', 112 | 113 | # The font size ('10pt', '11pt' or '12pt'). 114 | # 115 | # 'pointsize': '10pt', 116 | 117 | # Additional stuff for the LaTeX preamble. 118 | # 119 | # 'preamble': '', 120 | 121 | # Latex figure (float) alignment 122 | # 123 | # 'figure_align': 'htbp', 124 | } 125 | 126 | # Grouping the document tree into LaTeX files. List of tuples 127 | # (source start file, target name, title, author, documentclass 128 | # [howto, manual, or own class]). 129 | latex_documents = [ 130 | (master_doc, 'teeplot.tex', 131 | 'teeplot Documentation', 132 | 'Matthew Andres Moreno', 'manual'), 133 | ] 134 | 135 | 136 | # -- Options for manual page output ------------------------------------ 137 | 138 | # One entry per manual page. List of tuples 139 | # (source start file, name, description, authors, manual section). 140 | man_pages = [ 141 | (master_doc, 'teeplot', 142 | 'teeplot Documentation', 143 | [author], 1) 144 | ] 145 | 146 | 147 | # -- Options for Texinfo output ---------------------------------------- 148 | 149 | # Grouping the document tree into Texinfo files. List of tuples 150 | # (source start file, target name, title, author, 151 | # dir menu entry, description, category) 152 | texinfo_documents = [ 153 | (master_doc, 'teeplot', 154 | 'teeplot Documentation', 155 | author, 156 | 'teeplot', 157 | 'One line description of project.', 158 | 'Miscellaneous'), 159 | ] 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CONTRIBUTING.rst 2 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to teeplot's documentation! 2 | ====================================== 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | :caption: Contents: 7 | 8 | readme 9 | installation 10 | usage 11 | modules 12 | contributing 13 | authors 14 | 15 | Indices and tables 16 | ================== 17 | * :ref:`genindex` 18 | * :ref:`modindex` 19 | * :ref:`search` 20 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: shell 2 | 3 | ============ 4 | Installation 5 | ============ 6 | 7 | 8 | Stable release 9 | -------------- 10 | 11 | To install teeplot, run this command in your terminal: 12 | 13 | .. code-block:: console 14 | 15 | $ pip install teeplot 16 | 17 | This is the preferred method to install teeplot, as it will always install the most recent stable release. 18 | 19 | If you don't have `pip`_ installed, this `Python installation guide`_ can guide 20 | you through the process. 21 | 22 | .. _pip: https://pip.pypa.io 23 | .. _Python installation guide: http://docs.python-guide.org/en/latest/starting/installation/ 24 | 25 | 26 | From sources 27 | ------------ 28 | 29 | The sources for teeplot can be downloaded from the `Github repo`_. 30 | 31 | You can either clone the public repository: 32 | 33 | .. code-block:: console 34 | 35 | $ git clone git://github.com/mmore500/teeplot 36 | 37 | Or download the `tarball`_: 38 | 39 | .. code-block:: console 40 | 41 | $ curl -OJL https://github.com/mmore500/teeplot/tarball/master 42 | 43 | Once you have a copy of the source, you can install it with: 44 | 45 | .. code-block:: console 46 | 47 | $ python setup.py install 48 | 49 | 50 | .. _Github repo: https://github.com/mmore500/teeplot 51 | .. _tarball: https://github.com/mmore500/teeplot/tarball/master 52 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=python -msphinx 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | set SPHINXPROJ=teeplot 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The Sphinx module was not found. Make sure you have Sphinx installed, 20 | echo.then set the SPHINXBUILD environment variable to point to the full 21 | echo.path of the 'sphinx-build' executable. Alternatively you may add the 22 | echo.Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /docs/readme.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../README.rst 2 | -------------------------------------------------------------------------------- /docs/usage.rst: -------------------------------------------------------------------------------- 1 | ===== 2 | Usage 3 | ===== 4 | 5 | To use teeplot in a project:: 6 | 7 | from teeplot import teeplot as ib 8 | -------------------------------------------------------------------------------- /requirements/requirements_dev-mypy.txt: -------------------------------------------------------------------------------- 1 | requirements_dev-py38.txt -------------------------------------------------------------------------------- /requirements/requirements_dev-py310.txt: -------------------------------------------------------------------------------- 1 | # This file was autogenerated by uv via the following command: 2 | # uv pip compile requirements_dev.in --python 3.10 -o requirements_dev-py310.txt 3 | alabaster==0.7.16 4 | # via sphinx 5 | attrs==25.1.0 6 | # via pytest 7 | babel==2.17.0 8 | # via sphinx 9 | backports-tarfile==1.2.0 10 | # via jaraco-context 11 | bump2version==0.5.11 12 | # via -r requirements_dev.in 13 | cachetools==5.5.2 14 | # via tox 15 | certifi==2025.1.31 16 | # via requests 17 | cffi==1.17.1 18 | # via cryptography 19 | chardet==5.2.0 20 | # via tox 21 | charset-normalizer==3.4.1 22 | # via requests 23 | click==8.1.8 24 | # via keyname 25 | colorama==0.4.6 26 | # via tox 27 | contourpy==1.3.1 28 | # via matplotlib 29 | coverage==7.6.12 30 | # via -r requirements_dev.in 31 | cryptography==44.0.1 32 | # via secretstorage 33 | cycler==0.12.1 34 | # via matplotlib 35 | decorator==5.2.0 36 | # via retry 37 | distlib==0.3.9 38 | # via virtualenv 39 | distutils-strtobool==0.1.0 40 | # via -r requirements_dev.in 41 | docutils==0.21.2 42 | # via 43 | # readme-renderer 44 | # sphinx 45 | exceptiongroup==1.2.2 46 | # via pytest 47 | filelock==3.17.0 48 | # via 49 | # -r requirements_dev.in 50 | # tox 51 | # virtualenv 52 | flake8==7.1.2 53 | # via -r requirements_dev.in 54 | fonttools==4.56.0 55 | # via matplotlib 56 | id==1.5.0 57 | # via twine 58 | idna==3.10 59 | # via requests 60 | imagesize==1.4.1 61 | # via sphinx 62 | importlib-metadata==8.6.1 63 | # via 64 | # -r requirements_dev.in 65 | # keyring 66 | iniconfig==2.0.0 67 | # via pytest 68 | jaraco-classes==3.4.0 69 | # via keyring 70 | jaraco-context==6.0.1 71 | # via keyring 72 | jaraco-functools==4.1.0 73 | # via keyring 74 | jeepney==0.8.0 75 | # via 76 | # keyring 77 | # secretstorage 78 | jinja2==3.1.5 79 | # via sphinx 80 | keyname==0.6.0 81 | # via -r requirements_dev.in 82 | keyring==25.6.0 83 | # via twine 84 | kiwisolver==1.4.8 85 | # via matplotlib 86 | markdown-it-py==3.0.0 87 | # via rich 88 | markupsafe==3.0.2 89 | # via 90 | # -r requirements_dev.in 91 | # jinja2 92 | matplotlib==3.10.0 93 | # via 94 | # -r requirements_dev.in 95 | # seaborn 96 | mccabe==0.7.0 97 | # via flake8 98 | mdurl==0.1.2 99 | # via markdown-it-py 100 | more-itertools==10.6.0 101 | # via 102 | # jaraco-classes 103 | # jaraco-functools 104 | # keyname 105 | nh3==0.2.20 106 | # via readme-renderer 107 | numpy==2.2.3 108 | # via 109 | # contourpy 110 | # matplotlib 111 | # pandas 112 | # seaborn 113 | packaging==24.2 114 | # via 115 | # matplotlib 116 | # pyproject-api 117 | # pytest 118 | # sphinx 119 | # tox 120 | # twine 121 | pandas==2.2.3 122 | # via seaborn 123 | pillow==11.1.0 124 | # via matplotlib 125 | platformdirs==4.3.6 126 | # via 127 | # -r requirements_dev.in 128 | # tox 129 | # virtualenv 130 | pluggy==1.5.0 131 | # via 132 | # pytest 133 | # tox 134 | py==1.11.0 135 | # via retry 136 | pycodestyle==2.12.1 137 | # via flake8 138 | pycparser==2.22 139 | # via cffi 140 | pyflakes==3.2.0 141 | # via flake8 142 | pygments==2.19.1 143 | # via 144 | # readme-renderer 145 | # rich 146 | # sphinx 147 | pypandoc-binary==1.15 148 | # via -r requirements_dev.in 149 | pyparsing==3.2.1 150 | # via matplotlib 151 | pyproject-api==1.9.0 152 | # via tox 153 | pytest==7.2.2 154 | # via -r requirements_dev.in 155 | python-dateutil==2.9.0.post0 156 | # via 157 | # matplotlib 158 | # pandas 159 | python-slugify==8.0.4 160 | # via -r requirements_dev.in 161 | pytz==2025.1 162 | # via pandas 163 | readme-renderer==44.0 164 | # via twine 165 | requests==2.32.3 166 | # via 167 | # id 168 | # requests-toolbelt 169 | # sphinx 170 | # twine 171 | requests-toolbelt==1.0.0 172 | # via twine 173 | retry==0.9.2 174 | # via keyname 175 | rfc3986==2.0.0 176 | # via twine 177 | rich==13.9.0 178 | # via twine 179 | seaborn==0.13.2 180 | # via -r requirements_dev.in 181 | secretstorage==3.3.3 182 | # via keyring 183 | setuptools==75.8.0 184 | # via sphinx 185 | six==1.17.0 186 | # via 187 | # python-dateutil 188 | # sphinx 189 | snowballstemmer==2.2.0 190 | # via sphinx 191 | sphinx==1.8.5 192 | # via -r requirements_dev.in 193 | sphinxcontrib-serializinghtml==2.0.0 194 | # via sphinxcontrib-websupport 195 | sphinxcontrib-websupport==1.2.4 196 | # via sphinx 197 | text-unidecode==1.3 198 | # via python-slugify 199 | tomli==2.2.1 200 | # via 201 | # pyproject-api 202 | # pytest 203 | # tox 204 | tox==4.20.0 205 | # via -r requirements_dev.in 206 | twine==6.1.0 207 | # via -r requirements_dev.in 208 | typing-extensions==3.10.0.2 209 | # via -r requirements_dev.in 210 | tzdata==2025.1 211 | # via pandas 212 | urllib3==2.3.0 213 | # via 214 | # requests 215 | # twine 216 | virtualenv==20.29.2 217 | # via tox 218 | watchdog==6.0.0 219 | # via -r requirements_dev.in 220 | wheel==0.45.1 221 | # via -r requirements_dev.in 222 | zipp==3.21.0 223 | # via 224 | # -r requirements_dev.in 225 | # importlib-metadata 226 | -------------------------------------------------------------------------------- /requirements/requirements_dev-py311.txt: -------------------------------------------------------------------------------- 1 | # This file was autogenerated by uv via the following command: 2 | # uv pip compile requirements_dev.in --python 3.11 -o requirements_dev-py311.txt 3 | alabaster==0.7.16 4 | # via sphinx 5 | attrs==25.1.0 6 | # via pytest 7 | babel==2.17.0 8 | # via sphinx 9 | backports-tarfile==1.2.0 10 | # via jaraco-context 11 | bump2version==0.5.11 12 | # via -r requirements_dev.in 13 | cachetools==5.5.2 14 | # via tox 15 | certifi==2025.1.31 16 | # via requests 17 | cffi==1.17.1 18 | # via cryptography 19 | chardet==5.2.0 20 | # via tox 21 | charset-normalizer==3.4.1 22 | # via requests 23 | click==8.1.8 24 | # via keyname 25 | colorama==0.4.6 26 | # via tox 27 | contourpy==1.3.1 28 | # via matplotlib 29 | coverage==7.6.12 30 | # via -r requirements_dev.in 31 | cryptography==44.0.1 32 | # via secretstorage 33 | cycler==0.12.1 34 | # via matplotlib 35 | decorator==5.2.0 36 | # via retry 37 | distlib==0.3.9 38 | # via virtualenv 39 | distutils-strtobool==0.1.0 40 | # via -r requirements_dev.in 41 | docutils==0.21.2 42 | # via 43 | # readme-renderer 44 | # sphinx 45 | filelock==3.17.0 46 | # via 47 | # -r requirements_dev.in 48 | # tox 49 | # virtualenv 50 | flake8==7.1.2 51 | # via -r requirements_dev.in 52 | fonttools==4.56.0 53 | # via matplotlib 54 | id==1.5.0 55 | # via twine 56 | idna==3.10 57 | # via requests 58 | imagesize==1.4.1 59 | # via sphinx 60 | importlib-metadata==8.6.1 61 | # via 62 | # -r requirements_dev.in 63 | # keyring 64 | iniconfig==2.0.0 65 | # via pytest 66 | jaraco-classes==3.4.0 67 | # via keyring 68 | jaraco-context==6.0.1 69 | # via keyring 70 | jaraco-functools==4.1.0 71 | # via keyring 72 | jeepney==0.8.0 73 | # via 74 | # keyring 75 | # secretstorage 76 | jinja2==3.1.5 77 | # via sphinx 78 | keyname==0.6.0 79 | # via -r requirements_dev.in 80 | keyring==25.6.0 81 | # via twine 82 | kiwisolver==1.4.8 83 | # via matplotlib 84 | markdown-it-py==3.0.0 85 | # via rich 86 | markupsafe==3.0.2 87 | # via 88 | # -r requirements_dev.in 89 | # jinja2 90 | matplotlib==3.10.0 91 | # via 92 | # -r requirements_dev.in 93 | # seaborn 94 | mccabe==0.7.0 95 | # via flake8 96 | mdurl==0.1.2 97 | # via markdown-it-py 98 | more-itertools==10.6.0 99 | # via 100 | # jaraco-classes 101 | # jaraco-functools 102 | # keyname 103 | nh3==0.2.20 104 | # via readme-renderer 105 | numpy==2.2.3 106 | # via 107 | # contourpy 108 | # matplotlib 109 | # pandas 110 | # seaborn 111 | packaging==24.2 112 | # via 113 | # matplotlib 114 | # pyproject-api 115 | # pytest 116 | # sphinx 117 | # tox 118 | # twine 119 | pandas==2.2.3 120 | # via seaborn 121 | pillow==11.1.0 122 | # via matplotlib 123 | platformdirs==4.3.6 124 | # via 125 | # -r requirements_dev.in 126 | # tox 127 | # virtualenv 128 | pluggy==1.5.0 129 | # via 130 | # pytest 131 | # tox 132 | py==1.11.0 133 | # via retry 134 | pycodestyle==2.12.1 135 | # via flake8 136 | pycparser==2.22 137 | # via cffi 138 | pyflakes==3.2.0 139 | # via flake8 140 | pygments==2.19.1 141 | # via 142 | # readme-renderer 143 | # rich 144 | # sphinx 145 | pypandoc-binary==1.15 146 | # via -r requirements_dev.in 147 | pyparsing==3.2.1 148 | # via matplotlib 149 | pyproject-api==1.9.0 150 | # via tox 151 | pytest==7.2.2 152 | # via -r requirements_dev.in 153 | python-dateutil==2.9.0.post0 154 | # via 155 | # matplotlib 156 | # pandas 157 | python-slugify==8.0.4 158 | # via -r requirements_dev.in 159 | pytz==2025.1 160 | # via pandas 161 | readme-renderer==44.0 162 | # via twine 163 | requests==2.32.3 164 | # via 165 | # id 166 | # requests-toolbelt 167 | # sphinx 168 | # twine 169 | requests-toolbelt==1.0.0 170 | # via twine 171 | retry==0.9.2 172 | # via keyname 173 | rfc3986==2.0.0 174 | # via twine 175 | rich==13.9.4 176 | # via twine 177 | seaborn==0.13.2 178 | # via -r requirements_dev.in 179 | secretstorage==3.3.3 180 | # via keyring 181 | setuptools==75.8.0 182 | # via sphinx 183 | six==1.17.0 184 | # via 185 | # python-dateutil 186 | # sphinx 187 | snowballstemmer==2.2.0 188 | # via sphinx 189 | sphinx==1.8.5 190 | # via -r requirements_dev.in 191 | sphinxcontrib-serializinghtml==2.0.0 192 | # via sphinxcontrib-websupport 193 | sphinxcontrib-websupport==1.2.4 194 | # via sphinx 195 | text-unidecode==1.3 196 | # via python-slugify 197 | tox==4.24.1 198 | # via -r requirements_dev.in 199 | twine==6.1.0 200 | # via -r requirements_dev.in 201 | typing-extensions==3.10.0.2 202 | # via -r requirements_dev.in 203 | tzdata==2025.1 204 | # via pandas 205 | urllib3==2.3.0 206 | # via 207 | # requests 208 | # twine 209 | virtualenv==20.29.2 210 | # via tox 211 | watchdog==6.0.0 212 | # via -r requirements_dev.in 213 | wheel==0.45.1 214 | # via -r requirements_dev.in 215 | zipp==3.21.0 216 | # via 217 | # -r requirements_dev.in 218 | # importlib-metadata 219 | -------------------------------------------------------------------------------- /requirements/requirements_dev-py312.txt: -------------------------------------------------------------------------------- 1 | # This file was autogenerated by uv via the following command: 2 | # uv pip compile requirements_dev.in --python 3.12 -o requirements_dev-py312.txt 3 | alabaster==0.7.16 4 | # via sphinx 5 | attrs==25.1.0 6 | # via pytest 7 | babel==2.17.0 8 | # via sphinx 9 | bump2version==0.5.11 10 | # via -r requirements_dev.in 11 | cachetools==5.5.2 12 | # via tox 13 | certifi==2025.1.31 14 | # via requests 15 | cffi==1.17.1 16 | # via cryptography 17 | chardet==5.2.0 18 | # via tox 19 | charset-normalizer==3.4.1 20 | # via requests 21 | click==8.1.8 22 | # via keyname 23 | colorama==0.4.6 24 | # via tox 25 | contourpy==1.3.1 26 | # via matplotlib 27 | coverage==7.6.12 28 | # via -r requirements_dev.in 29 | cryptography==44.0.1 30 | # via secretstorage 31 | cycler==0.12.1 32 | # via matplotlib 33 | decorator==5.2.0 34 | # via retry 35 | distlib==0.3.9 36 | # via virtualenv 37 | distutils-strtobool==0.1.0 38 | # via -r requirements_dev.in 39 | docutils==0.21.2 40 | # via 41 | # readme-renderer 42 | # sphinx 43 | filelock==3.17.0 44 | # via 45 | # -r requirements_dev.in 46 | # tox 47 | # virtualenv 48 | flake8==7.1.2 49 | # via -r requirements_dev.in 50 | fonttools==4.56.0 51 | # via matplotlib 52 | id==1.5.0 53 | # via twine 54 | idna==3.10 55 | # via requests 56 | imagesize==1.4.1 57 | # via sphinx 58 | importlib-metadata==8.6.1 59 | # via -r requirements_dev.in 60 | iniconfig==2.0.0 61 | # via pytest 62 | jaraco-classes==3.4.0 63 | # via keyring 64 | jaraco-context==6.0.1 65 | # via keyring 66 | jaraco-functools==4.1.0 67 | # via keyring 68 | jeepney==0.8.0 69 | # via 70 | # keyring 71 | # secretstorage 72 | jinja2==3.1.5 73 | # via sphinx 74 | keyname==0.6.0 75 | # via -r requirements_dev.in 76 | keyring==25.6.0 77 | # via twine 78 | kiwisolver==1.4.8 79 | # via matplotlib 80 | markdown-it-py==3.0.0 81 | # via rich 82 | markupsafe==3.0.2 83 | # via 84 | # -r requirements_dev.in 85 | # jinja2 86 | matplotlib==3.10.0 87 | # via 88 | # -r requirements_dev.in 89 | # seaborn 90 | mccabe==0.7.0 91 | # via flake8 92 | mdurl==0.1.2 93 | # via markdown-it-py 94 | more-itertools==10.6.0 95 | # via 96 | # jaraco-classes 97 | # jaraco-functools 98 | # keyname 99 | nh3==0.2.20 100 | # via readme-renderer 101 | numpy==2.2.3 102 | # via 103 | # contourpy 104 | # matplotlib 105 | # pandas 106 | # seaborn 107 | packaging==24.2 108 | # via 109 | # matplotlib 110 | # pyproject-api 111 | # pytest 112 | # sphinx 113 | # tox 114 | # twine 115 | pandas==2.2.3 116 | # via seaborn 117 | pillow==11.1.0 118 | # via matplotlib 119 | platformdirs==4.3.6 120 | # via 121 | # -r requirements_dev.in 122 | # tox 123 | # virtualenv 124 | pluggy==1.5.0 125 | # via 126 | # pytest 127 | # tox 128 | py==1.11.0 129 | # via retry 130 | pycodestyle==2.12.1 131 | # via flake8 132 | pycparser==2.22 133 | # via cffi 134 | pyflakes==3.2.0 135 | # via flake8 136 | pygments==2.19.1 137 | # via 138 | # readme-renderer 139 | # rich 140 | # sphinx 141 | pypandoc-binary==1.15 142 | # via -r requirements_dev.in 143 | pyparsing==3.2.1 144 | # via matplotlib 145 | pyproject-api==1.9.0 146 | # via tox 147 | pytest==7.2.2 148 | # via -r requirements_dev.in 149 | python-dateutil==2.9.0.post0 150 | # via 151 | # matplotlib 152 | # pandas 153 | python-slugify==8.0.4 154 | # via -r requirements_dev.in 155 | pytz==2025.1 156 | # via pandas 157 | readme-renderer==44.0 158 | # via twine 159 | requests==2.32.3 160 | # via 161 | # id 162 | # requests-toolbelt 163 | # sphinx 164 | # twine 165 | requests-toolbelt==1.0.0 166 | # via twine 167 | retry==0.9.2 168 | # via keyname 169 | rfc3986==2.0.0 170 | # via twine 171 | rich==13.9.4 172 | # via twine 173 | seaborn==0.13.2 174 | # via -r requirements_dev.in 175 | secretstorage==3.3.3 176 | # via keyring 177 | setuptools==75.8.0 178 | # via sphinx 179 | six==1.17.0 180 | # via 181 | # python-dateutil 182 | # sphinx 183 | snowballstemmer==2.2.0 184 | # via sphinx 185 | sphinx==1.8.5 186 | # via -r requirements_dev.in 187 | sphinxcontrib-serializinghtml==2.0.0 188 | # via sphinxcontrib-websupport 189 | sphinxcontrib-websupport==1.2.4 190 | # via sphinx 191 | text-unidecode==1.3 192 | # via python-slugify 193 | tox==4.24.1 194 | # via -r requirements_dev.in 195 | twine==6.1.0 196 | # via -r requirements_dev.in 197 | typing-extensions==3.10.0.2 198 | # via -r requirements_dev.in 199 | tzdata==2025.1 200 | # via pandas 201 | urllib3==2.3.0 202 | # via 203 | # requests 204 | # twine 205 | virtualenv==20.29.2 206 | # via tox 207 | watchdog==6.0.0 208 | # via -r requirements_dev.in 209 | wheel==0.45.1 210 | # via -r requirements_dev.in 211 | zipp==3.21.0 212 | # via 213 | # -r requirements_dev.in 214 | # importlib-metadata 215 | -------------------------------------------------------------------------------- /requirements/requirements_dev-py313.txt: -------------------------------------------------------------------------------- 1 | # This file was autogenerated by uv via the following command: 2 | # uv pip compile requirements_dev.in --python 3.13 -o requirements_dev-py313.txt 3 | alabaster==0.7.16 4 | # via sphinx 5 | attrs==25.1.0 6 | # via pytest 7 | babel==2.17.0 8 | # via sphinx 9 | bump2version==0.5.11 10 | # via -r requirements_dev.in 11 | cachetools==5.5.2 12 | # via tox 13 | certifi==2025.1.31 14 | # via requests 15 | cffi==1.17.1 16 | # via cryptography 17 | chardet==5.2.0 18 | # via tox 19 | charset-normalizer==3.4.1 20 | # via requests 21 | click==8.1.8 22 | # via keyname 23 | colorama==0.4.6 24 | # via tox 25 | contourpy==1.3.1 26 | # via matplotlib 27 | coverage==7.6.12 28 | # via -r requirements_dev.in 29 | cryptography==44.0.1 30 | # via secretstorage 31 | cycler==0.12.1 32 | # via matplotlib 33 | decorator==5.2.0 34 | # via retry 35 | distlib==0.3.9 36 | # via virtualenv 37 | distutils-strtobool==0.1.0 38 | # via -r requirements_dev.in 39 | docutils==0.21.2 40 | # via 41 | # readme-renderer 42 | # sphinx 43 | filelock==3.17.0 44 | # via 45 | # -r requirements_dev.in 46 | # tox 47 | # virtualenv 48 | flake8==7.1.2 49 | # via -r requirements_dev.in 50 | fonttools==4.56.0 51 | # via matplotlib 52 | id==1.5.0 53 | # via twine 54 | idna==3.10 55 | # via requests 56 | imagesize==1.4.1 57 | # via sphinx 58 | importlib-metadata==8.6.1 59 | # via -r requirements_dev.in 60 | iniconfig==2.0.0 61 | # via pytest 62 | jaraco-classes==3.4.0 63 | # via keyring 64 | jaraco-context==6.0.1 65 | # via keyring 66 | jaraco-functools==4.1.0 67 | # via keyring 68 | jeepney==0.8.0 69 | # via 70 | # keyring 71 | # secretstorage 72 | jinja2==3.1.5 73 | # via sphinx 74 | keyname==0.6.0 75 | # via -r requirements_dev.in 76 | keyring==25.6.0 77 | # via twine 78 | kiwisolver==1.4.8 79 | # via matplotlib 80 | markdown-it-py==3.0.0 81 | # via rich 82 | markupsafe==3.0.2 83 | # via 84 | # -r requirements_dev.in 85 | # jinja2 86 | matplotlib==3.10.0 87 | # via 88 | # -r requirements_dev.in 89 | # seaborn 90 | mccabe==0.7.0 91 | # via flake8 92 | mdurl==0.1.2 93 | # via markdown-it-py 94 | more-itertools==10.6.0 95 | # via 96 | # jaraco-classes 97 | # jaraco-functools 98 | # keyname 99 | nh3==0.2.20 100 | # via readme-renderer 101 | numpy==2.2.3 102 | # via 103 | # contourpy 104 | # matplotlib 105 | # pandas 106 | # seaborn 107 | packaging==24.2 108 | # via 109 | # matplotlib 110 | # pyproject-api 111 | # pytest 112 | # sphinx 113 | # tox 114 | # twine 115 | pandas==2.2.3 116 | # via seaborn 117 | pillow==11.1.0 118 | # via matplotlib 119 | platformdirs==4.3.6 120 | # via 121 | # -r requirements_dev.in 122 | # tox 123 | # virtualenv 124 | pluggy==1.5.0 125 | # via 126 | # pytest 127 | # tox 128 | py==1.11.0 129 | # via retry 130 | pycodestyle==2.12.1 131 | # via flake8 132 | pycparser==2.22 133 | # via cffi 134 | pyflakes==3.2.0 135 | # via flake8 136 | pygments==2.19.1 137 | # via 138 | # readme-renderer 139 | # rich 140 | # sphinx 141 | pypandoc-binary==1.15 142 | # via -r requirements_dev.in 143 | pyparsing==3.2.1 144 | # via matplotlib 145 | pyproject-api==1.9.0 146 | # via tox 147 | pytest==7.2.2 148 | # via -r requirements_dev.in 149 | python-dateutil==2.9.0.post0 150 | # via 151 | # matplotlib 152 | # pandas 153 | python-slugify==8.0.4 154 | # via -r requirements_dev.in 155 | pytz==2025.1 156 | # via pandas 157 | readme-renderer==44.0 158 | # via twine 159 | requests==2.32.3 160 | # via 161 | # id 162 | # requests-toolbelt 163 | # sphinx 164 | # twine 165 | requests-toolbelt==1.0.0 166 | # via twine 167 | retry==0.9.2 168 | # via keyname 169 | rfc3986==2.0.0 170 | # via twine 171 | rich==13.9.4 172 | # via twine 173 | seaborn==0.13.2 174 | # via -r requirements_dev.in 175 | secretstorage==3.3.3 176 | # via keyring 177 | setuptools==75.8.0 178 | # via sphinx 179 | six==1.17.0 180 | # via 181 | # python-dateutil 182 | # sphinx 183 | snowballstemmer==2.2.0 184 | # via sphinx 185 | sphinx==1.8.5 186 | # via -r requirements_dev.in 187 | sphinxcontrib-serializinghtml==2.0.0 188 | # via sphinxcontrib-websupport 189 | sphinxcontrib-websupport==1.2.4 190 | # via sphinx 191 | text-unidecode==1.3 192 | # via python-slugify 193 | tox==4.24.1 194 | # via -r requirements_dev.in 195 | twine==6.1.0 196 | # via -r requirements_dev.in 197 | typing-extensions==3.10.0.2 198 | # via -r requirements_dev.in 199 | tzdata==2025.1 200 | # via pandas 201 | urllib3==2.3.0 202 | # via 203 | # requests 204 | # twine 205 | virtualenv==20.29.2 206 | # via tox 207 | watchdog==6.0.0 208 | # via -r requirements_dev.in 209 | wheel==0.45.1 210 | # via -r requirements_dev.in 211 | zipp==3.21.0 212 | # via 213 | # -r requirements_dev.in 214 | # importlib-metadata 215 | -------------------------------------------------------------------------------- /requirements/requirements_dev-py38.txt: -------------------------------------------------------------------------------- 1 | # This file was autogenerated by uv via the following command: 2 | # uv pip compile requirements_dev.in --python 3.8 -o requirements_dev-py38.txt 3 | alabaster==0.7.13 4 | # via sphinx 5 | attrs==25.1.0 6 | # via pytest 7 | babel==2.17.0 8 | # via sphinx 9 | backports-tarfile==1.2.0 10 | # via jaraco-context 11 | bump2version==0.5.11 12 | # via -r requirements_dev.in 13 | cachetools==5.5.2 14 | # via tox 15 | certifi==2025.1.31 16 | # via requests 17 | cffi==1.17.1 18 | # via cryptography 19 | chardet==5.2.0 20 | # via tox 21 | charset-normalizer==3.4.1 22 | # via requests 23 | click==8.1.8 24 | # via keyname 25 | colorama==0.4.6 26 | # via tox 27 | commonmark==0.9.1 28 | # via rich 29 | contourpy==1.1.1 30 | # via matplotlib 31 | coverage==7.6.1 32 | # via -r requirements_dev.in 33 | cryptography==44.0.1 34 | # via secretstorage 35 | cycler==0.12.1 36 | # via matplotlib 37 | decorator==5.2.0 38 | # via retry 39 | distlib==0.3.9 40 | # via virtualenv 41 | distutils-strtobool==0.1.0 42 | # via -r requirements_dev.in 43 | docutils==0.20.1 44 | # via 45 | # readme-renderer 46 | # sphinx 47 | exceptiongroup==1.2.2 48 | # via pytest 49 | filelock==3.16.1 50 | # via 51 | # -r requirements_dev.in 52 | # tox 53 | # virtualenv 54 | flake8==7.1.2 55 | # via -r requirements_dev.in 56 | fonttools==4.56.0 57 | # via matplotlib 58 | id==1.5.0 59 | # via twine 60 | idna==3.10 61 | # via requests 62 | imagesize==1.4.1 63 | # via sphinx 64 | importlib-metadata==8.5.0 65 | # via 66 | # -r requirements_dev.in 67 | # keyring 68 | # twine 69 | importlib-resources==6.4.5 70 | # via 71 | # keyring 72 | # matplotlib 73 | iniconfig==2.0.0 74 | # via pytest 75 | jaraco-classes==3.4.0 76 | # via keyring 77 | jaraco-context==6.0.1 78 | # via keyring 79 | jaraco-functools==4.1.0 80 | # via keyring 81 | jeepney==0.8.0 82 | # via 83 | # keyring 84 | # secretstorage 85 | jinja2==3.1.5 86 | # via sphinx 87 | keyname==0.6.0 88 | # via -r requirements_dev.in 89 | keyring==25.5.0 90 | # via twine 91 | kiwisolver==1.4.7 92 | # via matplotlib 93 | markupsafe==2.1.5 94 | # via 95 | # -r requirements_dev.in 96 | # jinja2 97 | matplotlib==3.7.5 98 | # via 99 | # -r requirements_dev.in 100 | # seaborn 101 | mccabe==0.7.0 102 | # via flake8 103 | more-itertools==10.5.0 104 | # via 105 | # jaraco-classes 106 | # jaraco-functools 107 | # keyname 108 | nh3==0.2.20 109 | # via readme-renderer 110 | numpy==1.24.4 111 | # via 112 | # contourpy 113 | # matplotlib 114 | # pandas 115 | # seaborn 116 | packaging==24.2 117 | # via 118 | # matplotlib 119 | # pyproject-api 120 | # pytest 121 | # sphinx 122 | # tox 123 | # twine 124 | pandas==2.0.3 125 | # via seaborn 126 | pillow==10.4.0 127 | # via matplotlib 128 | platformdirs==4.3.6 129 | # via 130 | # -r requirements_dev.in 131 | # tox 132 | # virtualenv 133 | pluggy==1.5.0 134 | # via 135 | # pytest 136 | # tox 137 | py==1.11.0 138 | # via retry 139 | pycodestyle==2.12.1 140 | # via flake8 141 | pycparser==2.22 142 | # via cffi 143 | pyflakes==3.2.0 144 | # via flake8 145 | pygments==2.19.1 146 | # via 147 | # readme-renderer 148 | # rich 149 | # sphinx 150 | pypandoc-binary==1.15 151 | # via -r requirements_dev.in 152 | pyparsing==3.1.4 153 | # via matplotlib 154 | pyproject-api==1.8.0 155 | # via tox 156 | pytest==7.2.2 157 | # via -r requirements_dev.in 158 | python-dateutil==2.9.0.post0 159 | # via 160 | # matplotlib 161 | # pandas 162 | python-slugify==8.0.4 163 | # via -r requirements_dev.in 164 | pytz==2025.1 165 | # via 166 | # babel 167 | # pandas 168 | readme-renderer==43.0 169 | # via twine 170 | requests==2.32.3 171 | # via 172 | # id 173 | # requests-toolbelt 174 | # sphinx 175 | # twine 176 | requests-toolbelt==1.0.0 177 | # via twine 178 | retry==0.9.2 179 | # via keyname 180 | rfc3986==2.0.0 181 | # via twine 182 | rich==12.0.1 183 | # via twine 184 | seaborn==0.13.2 185 | # via -r requirements_dev.in 186 | secretstorage==3.3.3 187 | # via keyring 188 | setuptools==75.3.0 189 | # via sphinx 190 | six==1.17.0 191 | # via 192 | # python-dateutil 193 | # sphinx 194 | snowballstemmer==2.2.0 195 | # via sphinx 196 | sphinx==1.8.5 197 | # via -r requirements_dev.in 198 | sphinxcontrib-serializinghtml==1.1.5 199 | # via sphinxcontrib-websupport 200 | sphinxcontrib-websupport==1.2.4 201 | # via sphinx 202 | text-unidecode==1.3 203 | # via python-slugify 204 | tomli==2.2.1 205 | # via 206 | # pyproject-api 207 | # pytest 208 | # tox 209 | tox==4.20.0 210 | # via -r requirements_dev.in 211 | twine==6.1.0 212 | # via -r requirements_dev.in 213 | typing-extensions==3.10.0.2 214 | # via -r requirements_dev.in 215 | tzdata==2025.1 216 | # via pandas 217 | urllib3==2.2.3 218 | # via 219 | # requests 220 | # twine 221 | virtualenv==20.29.2 222 | # via tox 223 | watchdog==4.0.2 224 | # via -r requirements_dev.in 225 | wheel==0.45.1 226 | # via -r requirements_dev.in 227 | zipp==3.20.2 228 | # via 229 | # -r requirements_dev.in 230 | # importlib-metadata 231 | # importlib-resources 232 | -------------------------------------------------------------------------------- /requirements/requirements_dev-py39.txt: -------------------------------------------------------------------------------- 1 | # This file was autogenerated by uv via the following command: 2 | # uv pip compile requirements_dev.in --python 3.9 -o requirements_dev-py39.txt 3 | alabaster==0.7.16 4 | # via sphinx 5 | attrs==25.1.0 6 | # via pytest 7 | babel==2.17.0 8 | # via sphinx 9 | backports-tarfile==1.2.0 10 | # via jaraco-context 11 | bump2version==0.5.11 12 | # via -r requirements_dev.in 13 | cachetools==5.5.2 14 | # via tox 15 | certifi==2025.1.31 16 | # via requests 17 | cffi==1.17.1 18 | # via cryptography 19 | chardet==5.2.0 20 | # via tox 21 | charset-normalizer==3.4.1 22 | # via requests 23 | click==8.1.8 24 | # via keyname 25 | colorama==0.4.6 26 | # via tox 27 | contourpy==1.3.0 28 | # via matplotlib 29 | coverage==7.6.12 30 | # via -r requirements_dev.in 31 | cryptography==44.0.1 32 | # via secretstorage 33 | cycler==0.12.1 34 | # via matplotlib 35 | decorator==5.2.0 36 | # via retry 37 | distlib==0.3.9 38 | # via virtualenv 39 | distutils-strtobool==0.1.0 40 | # via -r requirements_dev.in 41 | docutils==0.21.2 42 | # via 43 | # readme-renderer 44 | # sphinx 45 | exceptiongroup==1.2.2 46 | # via pytest 47 | filelock==3.17.0 48 | # via 49 | # -r requirements_dev.in 50 | # tox 51 | # virtualenv 52 | flake8==7.1.2 53 | # via -r requirements_dev.in 54 | fonttools==4.56.0 55 | # via matplotlib 56 | id==1.5.0 57 | # via twine 58 | idna==3.10 59 | # via requests 60 | imagesize==1.4.1 61 | # via sphinx 62 | importlib-metadata==8.6.1 63 | # via 64 | # -r requirements_dev.in 65 | # keyring 66 | # twine 67 | importlib-resources==6.5.2 68 | # via matplotlib 69 | iniconfig==2.0.0 70 | # via pytest 71 | jaraco-classes==3.4.0 72 | # via keyring 73 | jaraco-context==6.0.1 74 | # via keyring 75 | jaraco-functools==4.1.0 76 | # via keyring 77 | jeepney==0.8.0 78 | # via 79 | # keyring 80 | # secretstorage 81 | jinja2==3.1.5 82 | # via sphinx 83 | keyname==0.6.0 84 | # via -r requirements_dev.in 85 | keyring==25.6.0 86 | # via twine 87 | kiwisolver==1.4.7 88 | # via matplotlib 89 | markdown-it-py==3.0.0 90 | # via rich 91 | markupsafe==3.0.2 92 | # via 93 | # -r requirements_dev.in 94 | # jinja2 95 | matplotlib==3.9.4 96 | # via 97 | # -r requirements_dev.in 98 | # seaborn 99 | mccabe==0.7.0 100 | # via flake8 101 | mdurl==0.1.2 102 | # via markdown-it-py 103 | more-itertools==10.6.0 104 | # via 105 | # jaraco-classes 106 | # jaraco-functools 107 | # keyname 108 | nh3==0.2.20 109 | # via readme-renderer 110 | numpy==2.0.2 111 | # via 112 | # contourpy 113 | # matplotlib 114 | # pandas 115 | # seaborn 116 | packaging==24.2 117 | # via 118 | # matplotlib 119 | # pyproject-api 120 | # pytest 121 | # sphinx 122 | # tox 123 | # twine 124 | pandas==2.2.3 125 | # via seaborn 126 | pillow==11.1.0 127 | # via matplotlib 128 | platformdirs==4.3.6 129 | # via 130 | # -r requirements_dev.in 131 | # tox 132 | # virtualenv 133 | pluggy==1.5.0 134 | # via 135 | # pytest 136 | # tox 137 | py==1.11.0 138 | # via retry 139 | pycodestyle==2.12.1 140 | # via flake8 141 | pycparser==2.22 142 | # via cffi 143 | pyflakes==3.2.0 144 | # via flake8 145 | pygments==2.19.1 146 | # via 147 | # readme-renderer 148 | # rich 149 | # sphinx 150 | pypandoc-binary==1.15 151 | # via -r requirements_dev.in 152 | pyparsing==3.2.1 153 | # via matplotlib 154 | pyproject-api==1.9.0 155 | # via tox 156 | pytest==7.2.2 157 | # via -r requirements_dev.in 158 | python-dateutil==2.9.0.post0 159 | # via 160 | # matplotlib 161 | # pandas 162 | python-slugify==8.0.4 163 | # via -r requirements_dev.in 164 | pytz==2025.1 165 | # via pandas 166 | readme-renderer==44.0 167 | # via twine 168 | requests==2.32.3 169 | # via 170 | # id 171 | # requests-toolbelt 172 | # sphinx 173 | # twine 174 | requests-toolbelt==1.0.0 175 | # via twine 176 | retry==0.9.2 177 | # via keyname 178 | rfc3986==2.0.0 179 | # via twine 180 | rich==13.9.0 181 | # via twine 182 | seaborn==0.13.2 183 | # via -r requirements_dev.in 184 | secretstorage==3.3.3 185 | # via keyring 186 | setuptools==75.8.0 187 | # via sphinx 188 | six==1.17.0 189 | # via 190 | # python-dateutil 191 | # sphinx 192 | snowballstemmer==2.2.0 193 | # via sphinx 194 | sphinx==1.8.5 195 | # via -r requirements_dev.in 196 | sphinxcontrib-serializinghtml==2.0.0 197 | # via sphinxcontrib-websupport 198 | sphinxcontrib-websupport==1.2.4 199 | # via sphinx 200 | text-unidecode==1.3 201 | # via python-slugify 202 | tomli==2.2.1 203 | # via 204 | # pyproject-api 205 | # pytest 206 | # tox 207 | tox==4.20.0 208 | # via -r requirements_dev.in 209 | twine==6.1.0 210 | # via -r requirements_dev.in 211 | typing-extensions==3.10.0.2 212 | # via -r requirements_dev.in 213 | tzdata==2025.1 214 | # via pandas 215 | urllib3==2.3.0 216 | # via 217 | # requests 218 | # twine 219 | virtualenv==20.29.2 220 | # via tox 221 | watchdog==6.0.0 222 | # via -r requirements_dev.in 223 | wheel==0.45.1 224 | # via -r requirements_dev.in 225 | zipp==3.21.0 226 | # via 227 | # -r requirements_dev.in 228 | # importlib-metadata 229 | # importlib-resources 230 | -------------------------------------------------------------------------------- /requirements/requirements_dev.in: -------------------------------------------------------------------------------- 1 | bump2version==0.5.11 2 | wheel>=0.33.6 3 | watchdog>=0.9.0 4 | flake8>=3.7.8 5 | tox>=3.26.0 6 | coverage>=4.5.4 7 | Sphinx==1.8.5 8 | twine>=1.14.0 9 | seaborn>=0.11.2 10 | keyname==0.6.0 11 | matplotlib>=3.5.3 12 | python-slugify>=4.0.1 13 | pytest==7.2.2 14 | typing-extensions==3.10.0.2 15 | distutils-strtobool==0.1.0 16 | 17 | filelock>=3.4.1 18 | importlib-metadata>=4.8.3 19 | markupsafe>=2.0.1 20 | pypandoc-binary==1.15 21 | platformdirs>=2.4.0 22 | zipp>=3.6.0 23 | -------------------------------------------------------------------------------- /requirements/requirements_dev.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd "$(dirname "$0")" 4 | 5 | for x in "8" "9" "10" "11" "12" "13"; do 6 | python3 -m uv pip compile requirements_dev.in \ 7 | --python "3.${x}" \ 8 | --upgrade -o requirements_dev-py3${x}.txt & 9 | done 10 | 11 | wait 12 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 1.4.2 3 | commit = True 4 | tag = True 5 | 6 | [bumpversion:file:setup.py] 7 | search = version='{current_version}' 8 | replace = version='{new_version}' 9 | 10 | [bumpversion:file:teeplot/__init__.py] 11 | search = __version__ = '{current_version}' 12 | replace = __version__ = '{new_version}' 13 | 14 | [bdist_wheel] 15 | universal = 1 16 | 17 | [flake8] 18 | exclude = docs 19 | 20 | [aliases] 21 | test = pytest 22 | 23 | [tool:pytest] 24 | collect_ignore = ['setup.py'] 25 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """The setup script.""" 4 | 5 | from setuptools import setup, find_packages 6 | 7 | try: 8 | import pypandoc 9 | with open('README.rst') as readme_file: 10 | readme = pypandoc.convert_text(readme_file.read(), to='md', format='rst') 11 | except ImportError: 12 | readme = 0 13 | 14 | requirements = [ 15 | 'keyname', 16 | 'matplotlib', 17 | 'python-slugify', 18 | 'distutils-strtobool', 19 | 'typing-extensions', 20 | ] 21 | 22 | setup_requirements = ['pytest-runner', 'pypandoc-binary'] 23 | 24 | test_requirements = ['pytest>=3', ] 25 | 26 | setup( 27 | author="Matthew Andres Moreno", 28 | author_email='m.more500@gmail.com', 29 | python_requires='>=3.8', 30 | classifiers=[ 31 | 'Development Status :: 2 - Pre-Alpha', 32 | 'Intended Audience :: Developers', 33 | 'License :: OSI Approved :: MIT License', 34 | 'Natural Language :: English', 35 | 'Programming Language :: Python :: 3', 36 | 'Programming Language :: Python :: 3.8', 37 | 'Programming Language :: Python :: 3.9', 38 | 'Programming Language :: Python :: 3.10', 39 | 'Programming Language :: Python :: 3.11', 40 | ], 41 | description="teeplot automatically saves a copy of rendered Jupyter notebook plots", 42 | install_requires=requirements, 43 | license="MIT license", 44 | long_description=readme, 45 | long_description_content_type="text/markdown", 46 | include_package_data=True, 47 | keywords='teeplot', 48 | name='teeplot', 49 | packages=find_packages(include=['teeplot', 'teeplot.*']), 50 | setup_requires=setup_requirements, 51 | test_suite='tests', 52 | tests_require=test_requirements, 53 | url='https://github.com/mmore500/teeplot', 54 | version='1.4.2', 55 | zip_safe=False, 56 | ) 57 | -------------------------------------------------------------------------------- /teeplot/__init__.py: -------------------------------------------------------------------------------- 1 | """Top-level package for teeplot.""" 2 | 3 | __author__ = """Matthew Andres Moreno""" 4 | __email__ = "m.more500@gmail.com" 5 | __version__ = "__version__ = '1.4.2'" 6 | -------------------------------------------------------------------------------- /teeplot/teeplot.py: -------------------------------------------------------------------------------- 1 | from collections import abc, Counter 2 | from contextlib import contextmanager 3 | import copy 4 | import functools 5 | import os 6 | import pathlib 7 | import typing 8 | import warnings 9 | import sys 10 | 11 | from keyname import keyname as kn 12 | import typing_extensions as typext 13 | import matplotlib 14 | import matplotlib.pyplot as plt 15 | from slugify import slugify 16 | from strtobool import strtobool 17 | 18 | 19 | def _is_running_on_ci() -> bool: 20 | ci_envs = ['CI', 'TRAVIS', 'GITHUB_ACTIONS', 'GITLAB_CI', 'JENKINS_URL'] 21 | return any(env in os.environ for env in ci_envs) 22 | 23 | draftmode: bool = False 24 | 25 | oncollision: typext.Literal[ 26 | "error", "fix", "ignore", "warn" 27 | ] = os.environ.get( 28 | "TEEPLOT_ONCOLLISION", 29 | "warn" if (_is_running_on_ci() or not hasattr(sys, 'ps1')) else "ignore", 30 | ).lower() 31 | if not oncollision in ("error", "fix", "ignore", "warn"): 32 | raise RuntimeError( 33 | f"invalid env var value TEEPLOT_ONCOLLISION={oncollision}", 34 | ) 35 | 36 | save = { 37 | ".eps": None, 38 | ".pdf": True, 39 | ".pgf": None, 40 | ".png": True, 41 | ".ps": None, 42 | ".svg": None, 43 | } 44 | """Global format output defaults. 45 | 46 | True enables format globally and False disables. 47 | None defers to teeplot_save kwarg.""" 48 | 49 | _history = Counter() 50 | 51 | 52 | # enable TrueType fonts 53 | # see https://gecco-2021.sigevo.org/Paper-Submission-Instructions 54 | @matplotlib.rc_context( 55 | { 56 | 'pdf.fonttype': 42, 57 | 'ps.fonttype': 42, 58 | }, 59 | ) 60 | def tee( 61 | plotter: typing.Callable[..., typing.Any], 62 | *args: typing.Any, 63 | teeplot_callback: bool = False, 64 | teeplot_dpi: int = 300, 65 | teeplot_oncollision: typing.Optional[ 66 | typext.Literal["error", "fix", "ignore", "warn"]] = None, 67 | teeplot_outattrs: typing.Dict[str, str] = {}, 68 | teeplot_outdir: str = "teeplots", 69 | teeplot_outinclude: typing.Iterable[str] = tuple(), 70 | teeplot_outexclude: typing.Iterable[str] = tuple(), 71 | teeplot_postprocess: typing.Union[str, typing.Callable] = "", 72 | teeplot_save: typing.Union[typing.Iterable[str], bool] = True, 73 | teeplot_show: typing.Optional[bool] = None, 74 | teeplot_subdir: str = '', 75 | teeplot_transparent: bool = True, 76 | teeplot_verbose: bool = True, 77 | **kwargs: typing.Any 78 | ) -> typing.Any: 79 | """Executes a plotting function and saves the resulting plot to specified 80 | formats using a descriptive filename automatically generated from plotting 81 | function arguments. 82 | 83 | Parameters 84 | ---------- 85 | plotter : Callable[..., Any] 86 | The plotting function to execute. 87 | *args : Any 88 | Positional arguments forwarded to the plotting function. 89 | teeplot_callback : bool, default False 90 | If True, return a tuple with callback to dispatch plot save instead of 91 | immediately saving plot after running plotter. 92 | teeplot_dpi : int, default 300 93 | Resolution for rasterized components of the saved plot in dots per inch. 94 | 95 | Default is publication-quality 300 dpi. 96 | teeplot_oncollision : Literal["error", "fix", "ignore", "warn"], optional 97 | Strategy for handling collisions between generated filenames. 98 | 99 | Default "ignore" if executing in interactive mode, else default "warn". 100 | teeplot_outattrs : Dict[str, str], optional 101 | Additional attributes to include in the output filename. 102 | teeplot_outdir : str, default "teeplots" 103 | Base directory for saving plots. 104 | teeplot_outexclude : Iterable[str], default tuple() 105 | Attributes to always exclude, if present, from the output filename. 106 | 107 | Under default settings, all kwargs with string values are included in 108 | the output filename. 109 | teeplot_outinclude : Iterable[str], default tuple() 110 | Attributes to always include, if present, in the output filename. 111 | 112 | Under default settings, all kwargs with string values are included in 113 | the output filename. 114 | teeplot_postprocess : Union[str, Callable], default "" 115 | Actions to perform on plot result before saving. 116 | 117 | A `str` kwarg will be `exec`'ed, with `plt` and `sns` (if installed) 118 | available, as well as the plotter return value as `teed`. If `str` value 119 | ends with ';', the postprocess step will not be included in output filename. 120 | 121 | A Callable kwarg will have invocation attempted first with the plotter 122 | return value as the `teed` kwarg, second with the plotter return value 123 | as the `ax` kwarg, third with no args, and last with the plotter 124 | return value as a positional arg. 125 | teeplot_save : Union[str, Iterable[str], bool], default True 126 | File formats to save the plots in. 127 | 128 | If `True`, defaults to global settings. If `False`, suppresses output 129 | to all file formats. 130 | teeplot_show : Optional[bool], optional 131 | Should `plt.show()` be called? 132 | 133 | If default, call `plt.show()` if interactive environment detected (e.g., 134 | notebook). 135 | teeplot_subdir : str, default "" 136 | Subdirectory within `teeplot_outdir` to save plots. 137 | teeplot_transparent : bool, default True 138 | Save the plot with a transparent background. 139 | teeplot_verbose : bool, default True 140 | Print saved filenames if True. 141 | **kwargs : Any 142 | Additional keyword arguments forwarded to the plotting function. 143 | 144 | Returns 145 | ------- 146 | Union[Any, Tuple[Callable, Any]] 147 | The result from the `plotter` function. 148 | 149 | Raises 150 | ------ 151 | RuntimeError 152 | If a file collision occurs and `teeplot_oncollision` is "error". 153 | ValueError 154 | For invalid format or `teeplot_oncollision` settings. 155 | 156 | Notes 157 | ----- 158 | - The output filename is generated based on the `plotter` function name and 159 | provided attributes. 160 | - Directories are created as needed based on specified output paths. 161 | - Enforces TrueType fonts for PDF and PS formats. 162 | """ 163 | formats = copy.copy(save) 164 | 165 | # incorporate environment variable settings 166 | for format in [*formats]: 167 | format_env_var = f"TEEPLOT_{format[1:].upper()}" 168 | if format_env_var in os.environ: # strip leading . 169 | format_env_value = os.environ[format_env_var] 170 | if format_env_value.lower() in ("none", "defer"): 171 | formats[format] = None 172 | else: 173 | formats[format] = strtobool(format_env_value) 174 | 175 | if teeplot_save is None or teeplot_save is True: 176 | # default formats 177 | teeplot_save = set(filter(formats.__getitem__, formats)) 178 | elif ( 179 | teeplot_save is False 180 | or strtobool(os.environ.get("TEEPLOT_DRAFTMODE", "F")) 181 | or draftmode 182 | ): 183 | # remove all outputs 184 | teeplot_save = set() 185 | elif isinstance(teeplot_save, str): 186 | if not teeplot_save in formats: 187 | raise ValueError( 188 | f"only {[*formats]} save formats are supported, " 189 | f"not {teeplot_save}", 190 | ) 191 | # remove explicitly disabled outputs 192 | blacklist = set(k for k, v in formats.items() if v is False) 193 | exclusions = {teeplot_save} & blacklist 194 | if teeplot_verbose and exclusions: 195 | print(f"skipping {exclusions}") 196 | teeplot_save = {teeplot_save} - exclusions 197 | 198 | elif isinstance(teeplot_save, abc.Iterable): 199 | if not {*teeplot_save} <= {*formats}: 200 | raise ValueError( 201 | f"only {[*formats]} save formats are supported, " 202 | f"not {list({*teeplot_save} - {*formats})}", 203 | ) 204 | # remove explicitly disabled outputs 205 | blacklist = set(k for k, v in formats.items() if v is False) 206 | exclusions = set(teeplot_save) & blacklist 207 | if teeplot_verbose and exclusions: 208 | print(f"skipping {exclusions}") 209 | teeplot_save = set(teeplot_save) - exclusions 210 | else: 211 | raise TypeError( 212 | "teeplot_save kwarg must be str, bool, or iterable, " 213 | f"not {type(teeplot_save)} {teeplot_save}", 214 | ) 215 | 216 | if teeplot_oncollision is None: 217 | teeplot_oncollision = oncollision 218 | 219 | if isinstance(teeplot_outinclude, str): 220 | teeplot_outinclude = [teeplot_outinclude] 221 | if isinstance(teeplot_outexclude, str): 222 | teeplot_outexclude = [teeplot_outexclude] 223 | 224 | # ----- end argument parsing 225 | # ----- begin plotting 226 | 227 | teed = plotter(*args, **{k: v for k, v in kwargs.items()}) 228 | 229 | if isinstance(teeplot_postprocess, abc.Callable): 230 | while "make breakable": 231 | try: 232 | teeplot_postprocess(teed=teed) # first attempt 233 | break 234 | except TypeError: 235 | pass 236 | try: 237 | teeplot_postprocess(ax=teed) # second attempt 238 | break 239 | except TypeError: 240 | pass 241 | try: 242 | teeplot_postprocess() # third attempt 243 | break 244 | except TypeError: 245 | pass 246 | try: 247 | teeplot_postprocess(teed) # fourth attempt 248 | break 249 | except TypeError: 250 | pass 251 | raise TypeError( # give up 252 | f"teeplot_postprocess={teeplot_postprocess} threw TypeError " 253 | "or call signature incompatible with attempted invocations", 254 | ) 255 | elif teeplot_postprocess: 256 | if not isinstance(teeplot_postprocess, str): 257 | raise TypeError( 258 | "teeplot_postprocess must be str or Callable, " 259 | f"not {type(teeplot_postprocess)} {teeplot_postprocess}" 260 | ) 261 | try: 262 | import seaborn as sns 263 | import seaborn 264 | except ModuleNotFoundError: 265 | pass 266 | exec(teeplot_postprocess) 267 | 268 | incl = [*teeplot_outinclude] 269 | attr_maker = lambda ext: { 270 | **{ 271 | slugify(k) : slugify(str(v)) 272 | for k, v in kwargs.items() 273 | if isinstance(v, str) or k in incl 274 | }, 275 | **{ 276 | 'viz' : slugify(plotter.__name__), 277 | 'ext' : ext, 278 | }, 279 | **( 280 | {"post": teeplot_postprocess.__name__} 281 | if teeplot_postprocess and isinstance(teeplot_postprocess, abc.Callable) 282 | else {"post": slugify(teeplot_postprocess)} 283 | if teeplot_postprocess and not teeplot_postprocess.endswith(";") 284 | else {} 285 | ), 286 | **teeplot_outattrs, 287 | } 288 | excl = [*teeplot_outexclude] 289 | out_filenamer = lambda ext: kn.pack({ 290 | k : v 291 | for k, v in attr_maker(ext).items() 292 | if not k.startswith('_') and not k in excl 293 | }) 294 | 295 | out_folder = pathlib.Path(teeplot_outdir, teeplot_subdir) 296 | out_folder.mkdir(parents=True, exist_ok=True) 297 | 298 | def save_callback(): 299 | for ext in save: 300 | 301 | if ext not in teeplot_save: 302 | if teeplot_verbose > 1: 303 | print(f"skipping {out_path}") 304 | continue 305 | 306 | out_path = pathlib.Path( 307 | kn.chop( 308 | str(out_folder / out_filenamer(ext)), 309 | mkdir=True, 310 | ), 311 | ) 312 | 313 | if out_path in _history: 314 | if teeplot_oncollision == "error": 315 | raise RuntimeError(f"teeplot already created file {out_path}") 316 | elif teeplot_oncollision == "fix": 317 | count = _history[out_path] 318 | suffix = f"ext={ext}" 319 | assert str(out_path).endswith(suffix) 320 | out_path = str(out_path)[:-len(suffix)] + f"#={count}+" + suffix 321 | elif teeplot_oncollision == "ignore": 322 | pass 323 | elif teeplot_oncollision == "warn": 324 | warnings.warn( 325 | f"teeplot already created file {out_path}, overwriting it", 326 | ) 327 | else: 328 | raise ValueError( 329 | "teeplot_oncollision must be one of 'error', 'fix', " 330 | f"'ignore', or 'warn', not {teeplot_oncollision}", 331 | ) 332 | _history[out_path] += 1 333 | 334 | if teeplot_verbose: 335 | print(out_path) 336 | plt.savefig( 337 | str(out_path), 338 | bbox_inches='tight', 339 | transparent=teeplot_transparent, 340 | dpi=teeplot_dpi, 341 | # see https://matplotlib.org/2.1.1/users/whats_new.html#reproducible-ps-pdf-and-svg-output 342 | **dict( 343 | metadata={ 344 | key: None 345 | for key in { 346 | ".png": [], 347 | ".pdf": ["CreationDate"], 348 | ".svg": ["Date"], 349 | }.get(ext, []) 350 | }, 351 | ) if ext != ".pgf" else {}, 352 | ) 353 | 354 | if teeplot_show or (teeplot_show is None and hasattr(sys, 'ps1')): 355 | plt.show() 356 | 357 | return teed 358 | 359 | if teeplot_callback: 360 | return save_callback, teed 361 | else: 362 | return save_callback() 363 | 364 | 365 | @contextmanager 366 | def teed(*args, **kwargs): 367 | """Context manager interface to `teeplot.tee`. 368 | 369 | Plot save is dispatched upon exiting the context. Return value is the 370 | plotter return value. See `teeplot.tee` for kwarg options. 371 | """ 372 | if "teeplot_callback" in kwargs: 373 | raise ValueError( 374 | "teeplot_callback kwarg is not allowed in teed context manager", 375 | ) 376 | kwargs["teeplot_callback"] = True 377 | 378 | saveit = lambda *_args, **_kwargs: None 379 | try: 380 | saveit, handle = tee(*args, **kwargs) 381 | yield handle 382 | finally: 383 | saveit() 384 | 385 | 386 | def teewrap( 387 | **teeplot_kwargs: object, 388 | ): 389 | """Decorator interface to `teeplot.tee` 390 | 391 | Works by returning a decorator that wraps `f` by calling `teeplot.tee` using 392 | `f` and any passed in arguments and keyword arguments. However, using 393 | `teeplot_outattrs` like in `teeplot.tee` will cause printed attributes to be 394 | the same across function calls. For printing attributes on a per-call basis, 395 | see `teeplot_outinclude` in `teeplot.tee`. 396 | """ 397 | if not all(k.startswith("teeplot_") for k in teeplot_kwargs): 398 | raise ValueError( 399 | "The `teewrap` decorator only accepts teeplot_* keyword arguments" 400 | ) 401 | 402 | def decorator(f: typing.Callable): 403 | @functools.wraps(f) 404 | def inner(*args, **kwargs): 405 | return tee( 406 | f, 407 | *args, 408 | **teeplot_kwargs, 409 | **kwargs, 410 | ) 411 | 412 | return inner 413 | 414 | return decorator 415 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | """Unit test package for teeplot.""" 2 | -------------------------------------------------------------------------------- /tests/test_tee.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | ''' 4 | `tee` tests for `teeplot` package. 5 | ''' 6 | 7 | from matplotlib import pyplot as plt 8 | import numpy as np 9 | from keyname import keyname as kn 10 | import os 11 | import pytest 12 | import seaborn as sns 13 | 14 | from teeplot import teeplot as tp 15 | 16 | def test(): 17 | 18 | tp.tee( 19 | sns.lineplot, 20 | x='timepoint', 21 | y='signal', 22 | hue='region', 23 | style='event', 24 | data=sns.load_dataset('fmri'), 25 | teeplot_outattrs={ 26 | 'additional' : 'metadata', 27 | 'for' : 'output-filename', 28 | '_one-for' : 'exclusion', 29 | }, 30 | ) 31 | 32 | for ext in '.pdf', '.png': 33 | assert os.path.exists( 34 | os.path.join('teeplots', f'additional=metadata+for=output-filename+hue=region+style=event+viz=lineplot+x=timepoint+y=signal+ext={ext}'), 35 | ) 36 | 37 | 38 | def test_ndarray(): 39 | 40 | # adapted from https://seaborn.pydata.org/generated/seaborn.lineplot.html 41 | np.random.seed(1) 42 | x, y = np.random.normal(size=(2, 5000)).cumsum(axis=1) 43 | 44 | tp.tee( 45 | sns.lineplot, 46 | x=x, 47 | y=y, 48 | sort=False, 49 | lw=1, 50 | ) 51 | 52 | for ext in '.pdf', '.png': 53 | assert os.path.exists( 54 | os.path.join('teeplots', f'viz=lineplot+ext={ext}'), 55 | ) 56 | 57 | def test_datafordigest(): 58 | 59 | # adapted from https://seaborn.pydata.org/generated/seaborn.lineplot.html 60 | np.random.seed(1) 61 | x, y = np.random.normal(size=(2, 5000)).cumsum(axis=1) 62 | 63 | tp.tee( 64 | sns.lineplot, 65 | x=x, 66 | y=y, 67 | sort=False, 68 | lw=1, 69 | teeplot_outattrs={ 70 | 'additional' : 'metadata', 71 | '_datafordigest' : x, 72 | }, 73 | ) 74 | 75 | for ext in '.pdf', '.png': 76 | assert os.path.exists( 77 | os.path.join('teeplots', f'additional=metadata+viz=lineplot+ext={ext}'), 78 | ) 79 | 80 | 81 | def test_outpath(): 82 | 83 | # adapted from https://seaborn.pydata.org/generated/seaborn.lineplot.html 84 | np.random.seed(1) 85 | x, y = np.random.normal(size=(2, 5000)).cumsum(axis=1) 86 | 87 | tp.tee( 88 | sns.lineplot, 89 | x=x, 90 | y=y, 91 | sort=False, 92 | lw=1, 93 | teeplot_outattrs={ 94 | 'additional' : 'metadata', 95 | }, 96 | teeplot_subdir='mydirectory', 97 | ) 98 | 99 | for ext in '.pdf', '.png': 100 | assert os.path.exists( 101 | os.path.join('teeplots', 'mydirectory', f'additional=metadata+viz=lineplot+ext={ext}'), 102 | ) 103 | 104 | def test_longname(): 105 | 106 | # adapted from https://seaborn.pydata.org/generated/seaborn.lineplot.html 107 | np.random.seed(1) 108 | x, y = np.random.normal(size=(2, 5000)).cumsum(axis=1) 109 | 110 | tp.tee( 111 | sns.lineplot, 112 | x=x, 113 | y=y, 114 | sort=False, 115 | lw=1, 116 | teeplot_outattrs={ 117 | f'additional{i}' : 'metadata' 118 | for i in range(30) 119 | }, 120 | ) 121 | 122 | for ext in '.pdf', '.png': 123 | assert os.path.exists( 124 | kn.chop( 125 | os.path.join('teeplots', kn.pack({ 126 | f'additional{i}' : 'metadata' 127 | for i in range(30) 128 | }) + f'+viz=lineplot+ext={ext}'), 129 | ) 130 | ) 131 | 132 | @pytest.mark.parametrize("oncollision", ["warn", "ignore"]) 133 | def test_oncollision_warn_ignore(oncollision: str): 134 | 135 | # adapted from https://seaborn.pydata.org/generated/seaborn.lineplot.html 136 | np.random.seed(1) 137 | x, y = np.random.normal(size=(2, 5000)).cumsum(axis=1) 138 | 139 | for i in range(2): 140 | tp.tee( 141 | sns.lineplot, 142 | x=x, 143 | y=y, 144 | sort=False, 145 | lw=i+1, 146 | teeplot_oncollision=oncollision, 147 | teeplot_outattrs={ 148 | 'additional' : 'metadata', 149 | }, 150 | teeplot_subdir='mydirectory', 151 | ) 152 | 153 | for ext in '.pdf', '.png': 154 | assert os.path.exists( 155 | os.path.join('teeplots', 'mydirectory', f'additional=metadata+viz=lineplot+ext={ext}'), 156 | ) 157 | 158 | 159 | def test_oncollision_error(): 160 | 161 | # adapted from https://seaborn.pydata.org/generated/seaborn.lineplot.html 162 | np.random.seed(1) 163 | x, y = np.random.normal(size=(2, 5000)).cumsum(axis=1) 164 | 165 | with pytest.raises(RuntimeError): 166 | for i in range(2): 167 | tp.tee( 168 | sns.lineplot, 169 | x=x, 170 | y=y, 171 | sort=False, 172 | lw=i+1, 173 | teeplot_oncollision="error", 174 | teeplot_outattrs={ 175 | 'additional' : 'metadata', 176 | }, 177 | teeplot_subdir='mydirectory', 178 | ) 179 | 180 | for ext in '.pdf', '.png': 181 | assert os.path.exists( 182 | os.path.join('teeplots', 'mydirectory', f'additional=metadata+viz=lineplot+ext={ext}'), 183 | ) 184 | 185 | 186 | def test_oncollision_fix(): 187 | 188 | # adapted from https://seaborn.pydata.org/generated/seaborn.lineplot.html 189 | np.random.seed(1) 190 | x, y = np.random.normal(size=(2, 5000)).cumsum(axis=1) 191 | 192 | for i in range(2): 193 | tp.tee( 194 | sns.lineplot, 195 | x=x, 196 | y=y, 197 | sort=False, 198 | lw=i+1, 199 | teeplot_oncollision="fix", 200 | teeplot_outattrs={ 201 | 'additional' : 'metadata__', 202 | }, 203 | teeplot_subdir='mydirectory', 204 | ) 205 | 206 | for ext in '.pdf', '.png': 207 | assert os.path.exists( 208 | os.path.join('teeplots', 'mydirectory', f'additional=metadata__+viz=lineplot+#=1+ext={ext}'), 209 | ) 210 | 211 | 212 | @pytest.mark.parametrize("format", [".png", ".pdf", ".pgf", ".ps", ".eps", ".svg"]) 213 | def test_outformat(format): 214 | 215 | # adapted from https://seaborn.pydata.org/generated/seaborn.lineplot.html 216 | np.random.seed(1) 217 | x, y = np.random.normal(size=(2, 5000)).cumsum(axis=1) 218 | 219 | tp.tee( 220 | sns.lineplot, 221 | x=x, 222 | y=y, 223 | sort=False, 224 | lw=1, 225 | teeplot_outattrs={ 226 | 'outformat' : 'metadata', 227 | }, 228 | teeplot_subdir='mydirectory', 229 | teeplot_save={format}, 230 | ) 231 | 232 | assert os.path.exists( 233 | os.path.join('teeplots', 'mydirectory', f'outformat=metadata+viz=lineplot+ext={format}'), 234 | ) 235 | 236 | 237 | def test_savefalse(): 238 | 239 | # adapted from https://seaborn.pydata.org/generated/seaborn.lineplot.html 240 | np.random.seed(1) 241 | x, y = np.random.normal(size=(2, 5000)).cumsum(axis=1) 242 | 243 | tp.tee( 244 | sns.lineplot, 245 | x=x, 246 | y=y, 247 | sort=False, 248 | lw=1, 249 | teeplot_outattrs={ 250 | 'savefalse' : 'metadata', 251 | }, 252 | teeplot_subdir='mydirectory', 253 | teeplot_save=False, 254 | ) 255 | 256 | for ext in '.pdf', '.png': 257 | assert not os.path.exists( 258 | os.path.join('teeplots', 'mydirectory', f'savefalse=metadata+viz=lineplot+ext={ext}'), 259 | ) 260 | 261 | 262 | def test_postprocess_str(): 263 | 264 | tp.tee( 265 | sns.lineplot, 266 | x='timepoint', 267 | y='signal', 268 | hue='region', 269 | style='event', 270 | data=sns.load_dataset('fmri'), 271 | teeplot_postprocess="teed.set_yscale('log')", 272 | ) 273 | 274 | for ext in '.pdf', '.png': 275 | assert os.path.exists( 276 | os.path.join('teeplots', f'hue=region+post=teed-set-yscale-log+style=event+viz=lineplot+x=timepoint+y=signal+ext={ext}'), 277 | ) 278 | 279 | def test_postprocess_hidden(): 280 | 281 | tp.tee( 282 | sns.lineplot, 283 | x='timepoint', 284 | y='signal', 285 | hue='event', 286 | style='region', 287 | data=sns.load_dataset('fmri'), 288 | teeplot_postprocess="teed.set_yscale('log');", 289 | ) 290 | 291 | for ext in '.pdf', '.png': 292 | assert os.path.exists( 293 | os.path.join('teeplots', f'hue=event+style=region+viz=lineplot+x=timepoint+y=signal+ext={ext}'), 294 | ) 295 | 296 | 297 | def test_postprocess_callable(): 298 | 299 | tp.tee( 300 | sns.lineplot, 301 | x='timepoint', 302 | y='signal', 303 | hue='region', 304 | style='event', 305 | data=sns.load_dataset('fmri'), 306 | teeplot_postprocess=plt.viridis, 307 | ) 308 | 309 | for ext in '.pdf', '.png': 310 | assert os.path.exists( 311 | os.path.join('teeplots', f'hue=region+post=teed-set-yscale-log+style=event+viz=lineplot+x=timepoint+y=signal+ext={ext}'), 312 | ) 313 | 314 | 315 | def test_outinclude(): 316 | 317 | tp.tee( 318 | sns.lineplot, 319 | x='timepoint', 320 | y='signal', 321 | hue='region', 322 | style='event', 323 | lw=4, 324 | data=sns.load_dataset('fmri'), 325 | teeplot_outinclude=["lw"], 326 | ) 327 | 328 | for ext in '.pdf', '.png': 329 | assert os.path.exists( 330 | os.path.join('teeplots', f'hue=region+lw=4+style=event+viz=lineplot+x=timepoint+y=signal+ext={ext}'), 331 | ) 332 | 333 | 334 | def test_outexclude(): 335 | 336 | tp.tee( 337 | sns.lineplot, 338 | x='timepoint', 339 | y='signal', 340 | hue='region', 341 | style='event', 342 | lw=4, 343 | data=sns.load_dataset('fmri'), 344 | teeplot_outexclude=["viz"], 345 | ) 346 | 347 | for ext in '.pdf', '.png': 348 | assert os.path.exists( 349 | os.path.join('teeplots', f'hue=region+style=event+x=timepoint+y=signal+ext={ext}'), 350 | ) 351 | 352 | 353 | def test_callback(): 354 | 355 | saveit, ax = tp.tee( 356 | sns.lineplot, 357 | x='timepoint', 358 | y='signal', 359 | hue='region', 360 | style='event', 361 | data=sns.load_dataset('fmri'), 362 | teeplot_callback=True, 363 | ) 364 | ax.set_yscale('log') 365 | saveit() 366 | 367 | for ext in '.pdf', '.png': 368 | assert os.path.exists( 369 | os.path.join('teeplots', f'hue=region+style=event+viz=lineplot+x=timepoint+y=signal+ext={ext}'), 370 | ) 371 | -------------------------------------------------------------------------------- /tests/test_teed.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | ''' 4 | `tee` tests for `teeplot` package. 5 | ''' 6 | 7 | from matplotlib import pyplot as plt 8 | import numpy as np 9 | from keyname import keyname as kn 10 | import os 11 | import pytest 12 | import seaborn as sns 13 | 14 | from teeplot import teeplot as tp 15 | 16 | def test(): 17 | 18 | with tp.teed( 19 | sns.lineplot, 20 | x='timepoint', 21 | y='signal', 22 | hue='region', 23 | style='event', 24 | data=sns.load_dataset('fmri'), 25 | teeplot_outattrs={ 26 | 'additional' : 'teedmetadata', 27 | 'for' : 'output-filename', 28 | '_one-for' : 'exclusion', 29 | }, 30 | ): 31 | pass 32 | 33 | for ext in '.pdf', '.png': 34 | assert os.path.exists( 35 | os.path.join('teeplots', f'additional=teedmetadata+for=output-filename+hue=region+style=event+viz=lineplot+x=timepoint+y=signal+ext={ext}'), 36 | ) 37 | 38 | 39 | @pytest.mark.parametrize("format", [".png", ".pdf", ".ps", ".eps", ".svg"]) 40 | def test_outformat(format): 41 | 42 | # adapted from https://seaborn.pydata.org/generated/seaborn.lineplot.html 43 | np.random.seed(1) 44 | x, y = np.random.normal(size=(2, 5000)).cumsum(axis=1) 45 | 46 | with tp.teed( 47 | sns.lineplot, 48 | x=x, 49 | y=y, 50 | sort=False, 51 | lw=1, 52 | teeplot_outattrs={ 53 | 'outformat' : 'teedmetadata', 54 | }, 55 | teeplot_subdir='mydirectory', 56 | teeplot_save={format}, 57 | ): 58 | pass 59 | 60 | assert os.path.exists( 61 | os.path.join('teeplots', 'mydirectory', f'outformat=teedmetadata+viz=lineplot+ext={format}'), 62 | ) 63 | 64 | def test_postprocess_callable(): 65 | 66 | with tp.teed( 67 | sns.lineplot, 68 | x='signal', 69 | y='timepoint', 70 | hue='region', 71 | style='event', 72 | data=sns.load_dataset('fmri'), 73 | ) as teed: 74 | plt.viridis() 75 | 76 | for ext in '.pdf', '.png': 77 | assert os.path.exists( 78 | os.path.join('teeplots', f'hue=region+style=event+viz=lineplot+x=signal+y=timepoint+ext={ext}'), 79 | ) 80 | 81 | 82 | def test_callback(): 83 | 84 | with tp.teed( 85 | sns.lineplot, 86 | x='timepoint', 87 | y='signal', 88 | hue='region', 89 | data=sns.load_dataset('fmri'), 90 | ) as ax: 91 | ax.set_yscale('log') 92 | 93 | for ext in '.pdf', '.png': 94 | assert os.path.exists( 95 | os.path.join('teeplots', f'hue=region+viz=lineplot+x=timepoint+y=signal+ext={ext}'), 96 | ) 97 | -------------------------------------------------------------------------------- /tests/test_teewrap.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | ''' 4 | `tee` tests for `teeplot` package. 5 | ''' 6 | 7 | import functools 8 | import os 9 | 10 | import numpy as np 11 | import pytest 12 | import seaborn as sns 13 | 14 | from teeplot import teeplot as tp 15 | 16 | 17 | @tp.teewrap( 18 | teeplot_outattrs={ 19 | 'additional' : 'teedmetadata', 20 | 'for' : 'output-filename', 21 | '_one-for' : 'exclusion', 22 | }, 23 | ) 24 | @functools.wraps(sns.lineplot) 25 | def teed_snslineplot_outattrs(*args, **kwargs): 26 | return sns.lineplot(*args, **kwargs) 27 | 28 | def test(): 29 | 30 | teed_snslineplot_outattrs( 31 | x='timepoint', 32 | y='signal', 33 | hue='region', 34 | style='event', 35 | data=sns.load_dataset('fmri'), 36 | ) 37 | 38 | for ext in '.pdf', '.png': 39 | assert os.path.exists( 40 | os.path.join('teeplots', f'additional=teedmetadata+for=output-filename+hue=region+style=event+viz=lineplot+x=timepoint+y=signal+ext={ext}'), 41 | ) 42 | 43 | 44 | @pytest.mark.parametrize("format", [".png", ".pdf", ".ps", ".eps", ".svg"]) 45 | def test_outformat(format): 46 | 47 | # adapted from https://seaborn.pydata.org/generated/seaborn.lineplot.html 48 | np.random.seed(1) 49 | x, y = np.random.normal(size=(2, 5000)).cumsum(axis=1) 50 | 51 | @tp.teewrap( 52 | teeplot_outattrs={ 53 | 'outformat' : 'teedmetadata', 54 | }, 55 | teeplot_subdir='mydirectory', 56 | teeplot_save={format}, 57 | ) 58 | @functools.wraps(sns.lineplot) 59 | def teed_lineplot_outformat(*args, **kwargs): 60 | return sns.lineplot(*args, **kwargs) 61 | 62 | teed_lineplot_outformat( 63 | x=x, 64 | y=y, 65 | sort=False, 66 | lw=1, 67 | ) 68 | 69 | assert os.path.exists( 70 | os.path.join('teeplots', 'mydirectory', f'outformat=teedmetadata+viz=lineplot+ext={format}'), 71 | ) 72 | 73 | 74 | @tp.teewrap(teeplot_outinclude=['a', 'b']) 75 | @functools.wraps(sns.lineplot) 76 | def teed_snslineplot_extra_args(*args, a, b, **kwargs): 77 | return sns.lineplot(*args, **kwargs) 78 | 79 | 80 | @pytest.mark.parametrize('a', [False, 1, 1]) 81 | @pytest.mark.parametrize('b', ['asdf', '']) 82 | def test_included_outattrs(a, b): 83 | 84 | teed_snslineplot_extra_args( 85 | a=a, 86 | b=b, 87 | x='timepoint', 88 | y='signal', 89 | hue='region', 90 | data=sns.load_dataset('fmri'), 91 | ) 92 | 93 | for ext in '.pdf', '.png': 94 | assert os.path.exists( 95 | os.path.join('teeplots', f'a={a}+b={b}+hue=region+viz=lineplot+x=timepoint+y=signal+ext={ext}'.lower()), 96 | ) 97 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py38, py39, py310, py311, py312, py313, flake8, mypy 3 | 4 | [gh-actions] 5 | python = 6 | 3.8: py38, mypy 7 | 3.9: py39 8 | 3.10: py310 9 | 3.11: py311 10 | 3.12: py312 11 | 3.13: py313 12 | 13 | [testenv:flake8] 14 | basepython = python 15 | deps = flake8 16 | commands = flake8 teeplot tests 17 | 18 | [testenv] 19 | setenv = 20 | PYTHONPATH = {toxinidir} 21 | deps = 22 | -r{toxinidir}/requirements/requirements_dev-{envname}.txt 23 | ; If you want to make tox run the tests with the same versions, create a 24 | ; requirements.txt with the pinned versions and uncomment the following line: 25 | ; -r{toxinidir}/requirements.txt 26 | commands = 27 | pytest --basetemp={envtmpdir} 28 | --------------------------------------------------------------------------------