├── .github └── workflows │ └── python-package.yml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── examples ├── bar_chart.py ├── bar_chart.txt ├── bar_color.py ├── bar_color.txt ├── bar_label_1.py ├── bar_label_1.txt ├── bar_label_2.py ├── bar_label_2.txt ├── bar_label_3.py ├── bar_label_3.txt ├── bar_label_4.py ├── bar_label_4.txt ├── bar_label_5.py ├── bar_label_5.txt ├── bar_stacked.py ├── bar_stacked.txt ├── barh.py ├── barh.txt ├── box_plot_basic.py ├── box_plot_basic.txt ├── broken_line_plot.py ├── broken_line_plot.txt ├── categorical_variables.py ├── categorical_variables.txt ├── cohere.py ├── cohere.txt ├── color_bar_scatter.py ├── color_bar_scatter.txt ├── contour_double.py ├── contour_double.txt ├── contour_simple.py ├── contour_simple.txt ├── csd_demo.py ├── csd_demo.txt ├── double_plot.py ├── double_plot.txt ├── errorbars.py ├── errorbars.txt ├── errorbars_2.py ├── errorbars_2.txt ├── errorbars_3.py ├── errorbars_3.txt ├── errorbars_4.py ├── errorbars_4.txt ├── hist_best_fit_line.py ├── hist_best_fit_line.txt ├── hist_simple.py ├── hist_simple.txt ├── hist_simple_colors.py ├── hist_simple_colors.txt ├── line_markers.py ├── line_markers.txt ├── lines_multi_color.py ├── lines_multi_color.txt ├── long_tick_labels.py ├── long_tick_labels.txt ├── long_tick_labels_2.py ├── long_tick_labels_2.txt ├── scatter_multi_color.py ├── scatter_multi_color.txt ├── simple_plot.py ├── simple_plot.txt ├── violin_plot.py └── violin_plot.txt ├── mpl_ascii ├── __init__.py ├── ascii_canvas.py ├── ax.py ├── bar.py ├── color.py ├── color_map.py ├── colorbar.py ├── contour.py ├── format.py ├── legend.py ├── line.py ├── overlay.py ├── poly.py ├── scatter.py └── tools.py ├── pyproject.toml ├── requirements.txt └── tests ├── __init__.py └── test_overlay.py /.github/workflows/python-package.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python 3 | 4 | name: Python package 5 | 6 | on: 7 | push: 8 | branches: [ "main" ] 9 | pull_request: 10 | branches: [ "main" ] 11 | release: 12 | types: ["published"] 13 | 14 | jobs: 15 | test: 16 | 17 | runs-on: ubuntu-latest 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] 22 | 23 | steps: 24 | - uses: actions/checkout@v4 25 | - name: Set up Python ${{ matrix.python-version }} 26 | uses: actions/setup-python@v5 27 | with: 28 | python-version: ${{ matrix.python-version }} 29 | - name: Install dependencies 30 | run: | 31 | python -m pip install --upgrade pip 32 | python -m pip install pytest 33 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 34 | - name: Test with pytest 35 | run: | 36 | pytest tests 37 | - name: Acceptance test 38 | run: | 39 | make test 40 | 41 | build-and-publish: 42 | needs: test 43 | if: github.ref == 'refs/heads/main' || github.event_name == 'release' 44 | runs-on: ubuntu-latest 45 | steps: 46 | - uses: actions/checkout@v4 47 | with: 48 | fetch-depth: 0 49 | - name: Set up Python 50 | uses: actions/setup-python@v5 51 | with: 52 | python-version: '3.10' 53 | 54 | - name: Install packaging tools 55 | run: | 56 | python -m pip install --upgrade pip 57 | python -m pip install build twine 58 | 59 | - name: Build package 60 | run: | 61 | python -m build 62 | 63 | - name: Publish to Private PyPI 64 | run: | 65 | python -m twine upload --repository-url https://pypi.fury.io/chriscave/ -u ${{ secrets.GEMFURY_UPLOAD_PYPI_API }} -p dist/* 66 | 67 | download-and-test: 68 | needs: build-and-publish 69 | runs-on: ubuntu-latest 70 | strategy: 71 | fail-fast: false 72 | matrix: 73 | python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] 74 | steps: 75 | - name: Set up Python ${{ matrix.python-version }} 76 | uses: actions/setup-python@v5 77 | with: 78 | python-version: ${{ matrix.python-version }} 79 | - name: Install dependencies 80 | run: | 81 | python -m pip install --upgrade pip 82 | pip install --pre --index-url https://${{ secrets.GEMFURY_DOWNLOAD_PYPI_API }}:@pypi.fury.io/chriscave/ --extra-index-url https://pypi.org/simple mpl_ascii 83 | - name: Test package 84 | run: | 85 | python -c "import mpl_ascii" 86 | 87 | release-to-pypi: 88 | needs: download-and-test 89 | if: github.event_name == 'release' && github.event.action == 'published' 90 | runs-on: ubuntu-latest 91 | steps: 92 | - uses: actions/checkout@v4 93 | - name: Set up Python 94 | uses: actions/setup-python@v5 95 | with: 96 | python-version: '3.10' 97 | - name: Install packaging tools 98 | run: | 99 | python -m pip install --upgrade pip 100 | pip install build 101 | - name: Build package 102 | run: python -m build 103 | - name: Publish to PyPI 104 | uses: pypa/gh-action-pypi-publish@release/v1 105 | with: 106 | password: ${{ secrets.PYPI_API_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | venv-*/ 131 | 132 | # Spyder project settings 133 | .spyderproject 134 | .spyproject 135 | 136 | # Rope project settings 137 | .ropeproject 138 | 139 | # mkdocs documentation 140 | /site 141 | 142 | # mypy 143 | .mypy_cache/ 144 | .dmypy.json 145 | dmypy.json 146 | 147 | # Pyre type checker 148 | .pyre/ 149 | 150 | # pytype static type analyzer 151 | .pytype/ 152 | 153 | # Cython debug symbols 154 | cython_debug/ 155 | 156 | # PyCharm 157 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 158 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 159 | # and can be added to the global gitignore or merged into this file. For a more nuclear 160 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 161 | #.idea/ 162 | 163 | ### mpl_ascii specific 164 | data/ -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [0.10.0] 2024-07-01 9 | 10 | ### Added 11 | - Axes width and height can now be adjusted through environment variables called `AXES_WIDTH` and `AXES_HEIGHT`. 12 | 13 | ## [0.9.1] 2024-06-15 14 | 15 | ### Changed 16 | 17 | - Updated tick rendering logic to prevent overlap. Xticks now alternate direction or extend downwards, with possible truncation for both xticks and yticks. 18 | - Changed axes labels, title and legend so they are now centered. 19 | 20 | ## [0.9.0] 2024-06-11 21 | 22 | ### Added 23 | 24 | - Add support for `figure.show()` which will now display the text plot of the figure. 25 | 26 | ## [0.8.0] 2024-06-10 27 | 28 | ### Added 29 | 30 | - Add support for contour plots. 31 | - Add support for text objects in plot. 32 | - Add error handling for when the plots are not part of the library. 33 | 34 | ## [0.7.2] 2024-05-30 35 | 36 | ### Fixed 37 | 38 | - Fixed the padding between the colorbar and the axes when saving to a txt file. 39 | 40 | ## [0.7.1] 2024-05-30 41 | 42 | ### Fixed 43 | 44 | - Fix position of yaxis labels on colorbar axis so they are on the right side instead of the left side. 45 | 46 | ## [0.7.0] 2024-05-26 47 | 48 | ### Added 49 | 50 | - Add support for color bars on scatter plots 51 | 52 | ## [0.6.4] 2024-05-26 53 | 54 | ### Fixed 55 | 56 | - Add fix so if there is a `None` value in only one axis then the line plot does not raise an error and instead skips over it. 57 | 58 | ## [0.6.3] 2024-05-24 59 | 60 | ### Fixed 61 | 62 | - Line plots now handle None values as matplotlib does. If a `None` value is passed into a line plot then it has line break. 63 | 64 | ## [0.6.2] 2024-05-19 65 | 66 | ### Fixed 67 | 68 | - `mpl_ascii` is now compatible with matplotlib 3.9. 69 | 70 | ## [0.6.1] 2024-05-11 71 | 72 | ### Fixed 73 | 74 | - Contour plots will return empty frame instead of raising an error. 75 | 76 | ## [0.6.0] 2024-05-10 77 | 78 | ### Added 79 | 80 | - Add support for violin plots 81 | 82 | ### Fixed 83 | 84 | - Fixed empty line markers with box plots. 85 | 86 | ## [0.5.0] 2024-05-10 87 | 88 | ### Added 89 | 90 | - Added support for python 3.7+ 91 | 92 | ## [0.4.0] 2024-05-05 93 | 94 | ### Added 95 | 96 | - Added support for errorbars and line markers on line plots. 97 | 98 | ## [0.3.0] - 2024-04-30 99 | 100 | ### Added 101 | 102 | - You can now enable/disable colors to the ascii plots by setting the global variable `mpl_ascii.ENABLE_COLOR`. It is set to `True` by default. 103 | 104 | ## [0.2.0] - 2024-04-28 105 | 106 | ### Added 107 | 108 | - The width and height of each axes can be set using `mpl_ascii.AXES_WIDTH` and `mpl_ascii.AXES_HEIGHT`. It defaults to 150 characters in width and 40 characters in height. 109 | 110 | ## [0.1.0] - 2024-04-25 111 | 112 | ### Added 113 | 114 | When using the `mpl_ascii` backend then `plt.show()` will print the plot as a string consisting of basic ASCII characters. This is supported for: 115 | 116 | - Bar charts 117 | - Horizontal bar charts 118 | - Line plots 119 | - Scatter plots 120 | 121 | You can also save figures as a text file. You can do this by using the savefig `figure.savefig("my_figure.txt")` and this will save the ASCII plot as a txt file. 122 | 123 | 124 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to `mpl_ascii` 2 | 3 | Thank you for your interest in contributing to `mpl_ascii`! Here are some guidelines to help you get started. 4 | 5 | ## Pull Requests 6 | 7 | - **Fork the Repository**: Please make PRs from your own fork of the repository. 8 | 9 | ## Setting Up Your Environment 10 | 11 | - **pyenv**: We use pyenv to manage multiple Python versions. Ensure you have pyenv installed and set up before proceeding. 12 | - **Creating Virtual Environments**: You can create virtual environments using venv with the following commands: 13 | 14 | ```bash 15 | make venv-dev 16 | make venv-3.7 17 | make venv-3.8 18 | ``` 19 | 20 | ## Running Tests and Examples 21 | 22 | - **make accept**: This command runs all example plots and saves the figures to text files. Ensure you run make accept and commit the resulting text files as the last step before submitting your PR. This ensures the example text files are up to date with the current state of the code and prevents build failures. 23 | 24 | ## Adding New Plots 25 | 26 | - **Example Programs**: New plots should come with a Python program demonstrating the plot. These programs should be placed in the examples folder. 27 | 28 | - **Template**: Each example program follows this template: 29 | 30 | ```python 31 | import matplotlib 32 | matplotlib.use('mpl_ascii') 33 | import matplotlib.pyplot as plt 34 | 35 | if __name__ == "__main__": 36 | parser = argparse.ArgumentParser(allow_abbrev=False) 37 | parser.add_argument("--out", type=str) 38 | 39 | args = parser.parse_args() 40 | out = args.out 41 | 42 | fig, ax = plt.subplots() 43 | 44 | # Your plotting code here 45 | 46 | if out: 47 | fig.savefig(out) 48 | 49 | plt.show() 50 | ``` 51 | 52 | - **Running Examples**: To run and save the example plot, use the command: 53 | 54 | ```bash 55 | make name_of_program.txt 56 | ``` 57 | This will save the text file to the examples folder. 58 | 59 | ## Summary 60 | 61 | 1. 🏗️ Fork the repository. 62 | 1. 🐍 Use pyenv to manage Python versions. 63 | 1. 📦 Create virtual environments with make venv-dev, make venv-3.7, make venv-3.8, etc. 64 | 1. 📁 Place new example programs in the examples folder, following the provided template. 65 | 1. 🛠️ Run new examples with make name_of_example.txt. 66 | 1. ✅ Run make accept and commit the resulting text files as the last step before submitting your PR to ensure the example text files are up to date and to prevent build failures. 67 | 68 | Thank you for contributing! -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 chriscave 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DATA_DIR=data 2 | LOCATION:=$(DATA_DIR)/plots 3 | TEST_LOCATION:=$(DATA_DIR)/tests 4 | 5 | ACCEPTANCE_DIR=examples 6 | 7 | MPL_ASCII_FILES=$(wildcard mpl_ascii/*.py) 8 | EXAMPLES_PY_FILES=$(wildcard examples/*.py) 9 | EXAMPLES_FILE_NAMES_WO_EXT=$(basename $(notdir $(EXAMPLES_PY_FILES))) 10 | 11 | ALL_PLOTS_NAMES=$(basename $(notdir $(EXAMPLES_PY_FILES))) 12 | 13 | ALL_PLOTS_TXT=$(addsuffix .txt,$(EXAMPLES_FILE_NAMES_WO_EXT)) 14 | ALL_PLOTS_PNG=$(addsuffix .png,$(EXAMPLES_FILE_NAMES_WO_EXT)) 15 | 16 | .SECONDARY: accept 17 | 18 | $(LOCATION)/%.txt: $(MPL_ASCII_FILES) examples/%.py 19 | @mkdir -p $(LOCATION) 20 | @echo -e $* 21 | @python -m examples.$* --out $@ 22 | 23 | $(LOCATION)/%.png: $(MPL_ASCII_FILES) examples/%.py 24 | @mkdir -p $(LOCATION) 25 | @echo -e $* 26 | @python -m examples.$* --out $@ 27 | 28 | 29 | $(ACCEPTANCE_DIR)/%.txt: $(LOCATION)/%.txt 30 | mkdir -p $(ACCEPTANCE_DIR) 31 | TEMP_HASH=$$(basename $$(mktemp)); \ 32 | cp $< $@.$$TEMP_HASH; \ 33 | mv $@.$$TEMP_HASH $@; 34 | 35 | .PHONY: accept all 36 | 37 | all: $(addprefix $(LOCATION)/,$(ALL_PLOTS_TXT)) 38 | @true 39 | 40 | accept: $(addprefix $(ACCEPTANCE_DIR)/,$(ALL_PLOTS_TXT)) 41 | @true 42 | 43 | all.png: $(addprefix $(LOCATION)/,$(ALL_PLOTS_PNG)) 44 | @true 45 | 46 | clear: 47 | -rm -rf $(DATA_DIR) 48 | 49 | %.txt: $(LOCATION)/%.txt 50 | @true 51 | 52 | %.png: $(LOCATION)/%.png 53 | @true 54 | 55 | test-%.success: $(ACCEPTANCE_DIR)/%.txt 56 | @mkdir -p $(TEST_LOCATION) 57 | @if [ -n "$$(git diff $<)" ]; then \ 58 | echo "\033[1m[\033[1;31mTEST FAILED:\033[1;93m $<\033[0m\033[1m]\033[0m"; \ 59 | exit 1; \ 60 | fi; 61 | @echo "\033[1m[\033[1;32mSUCCESS:\033[1;93m $<\033[0m\033[1m]\033[0m" 62 | 63 | test: $(patsubst %,test-%.success,$(ALL_PLOTS_NAMES)) 64 | @true 65 | 66 | 67 | venv-dev: 68 | eval "$$(pyenv init -)"; \ 69 | pyenv shell 3.11; \ 70 | python -m venv $@; \ 71 | . $@/bin/activate; \ 72 | pip install -r requirements.txt 73 | 74 | venv-%: 75 | eval "$$(pyenv init -)"; \ 76 | pyenv shell $*; \ 77 | python -m venv $@; \ 78 | . $@/bin/activate; \ 79 | pip install -r requirements.txt 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mpl_ascii 2 | 3 | A matplotlib backend that produces plots using only ASCII characters. It is available for python 3.7+. 4 | 5 | ## Quick start 6 | 7 | Install `mpl_ascii` using pip 8 | 9 | ```bash 10 | pip install mpl_ascii 11 | ``` 12 | 13 | To use mpl_ascii, add to your python program 14 | 15 | ```python 16 | import matplotlib as mpl 17 | 18 | mpl.use("module://mpl_ascii") 19 | ``` 20 | 21 | When you use `plt.show()` then it will print the plots as strings that consists of ASCII characters. 22 | 23 | If you want to save a figure to a `.txt` file then just use `figure.savefig("my_figure.txt")` 24 | 25 | See more information about using backends here: https://matplotlib.org/stable/users/explain/figure/backends.html 26 | 27 | ## Examples 28 | 29 | ### Bar chart 30 | 31 | The following is taken from the example in `examples/bar_color.py` 32 | 33 | ```python 34 | import matplotlib.pyplot as plt 35 | import matplotlib as mpl 36 | import mpl_ascii 37 | 38 | mpl_ascii.AXES_WIDTH=100 39 | mpl_ascii.AXES_HEIGHT=40 40 | 41 | mpl.use("module://mpl_ascii") 42 | 43 | import matplotlib.pyplot as plt 44 | 45 | # Example data 46 | fruits = ['apple', 'blueberry', 'cherry', 'orange'] 47 | counts = [10, 15, 7, 5] 48 | colors = ['red', 'blue', 'red', 'orange'] # Colors corresponding to each fruit 49 | 50 | fig, ax = plt.subplots() 51 | 52 | # Plot each bar individually 53 | for fruit, count, color in zip(fruits, counts, colors): 54 | ax.bar(fruit, count, color=color, label=color) 55 | 56 | # Display the legend 57 | ax.legend(title='Fruit color') 58 | 59 | plt.show() 60 | ``` 61 | 62 | ![bar chart with color](https://imgur.com/u4pRU3E.png) 63 | 64 | ### Scatter plot 65 | 66 | The following is taken from the example in `examples/scatter_multi_color.py` 67 | 68 | ```python 69 | import matplotlib.pyplot as plt 70 | import numpy as np 71 | import matplotlib as mpl 72 | import mpl_ascii 73 | 74 | mpl_ascii.AXES_WIDTH=100 75 | mpl_ascii.AXES_HEIGHT=40 76 | 77 | 78 | mpl.use("module://mpl_ascii") 79 | 80 | np.random.seed(0) 81 | x = np.random.rand(40) 82 | y = np.random.rand(40) 83 | colors = np.random.choice(['red', 'green', 'blue', 'yellow'], size=40) 84 | color_labels = ['Red', 'Green', 'Blue', 'Yellow'] # Labels corresponding to colors 85 | 86 | # Create a scatter plot 87 | fig, ax = plt.subplots() 88 | for color, label in zip(['red', 'green', 'blue', 'yellow'], color_labels): 89 | # Plot each color as a separate scatter plot to enable legend tracking 90 | idx = np.where(colors == color) 91 | ax.scatter(x[idx], y[idx], color=color, label=label) 92 | 93 | # Set title and labels 94 | ax.set_title('Scatter Plot with 4 Different Colors') 95 | ax.set_xlabel('X axis') 96 | ax.set_ylabel('Y axis') 97 | 98 | # Add a legend 99 | ax.legend(title='Point Colors') 100 | plt.show() 101 | ``` 102 | 103 | ![Scatter plot with color](https://imgur.com/6LOv6L3.png) 104 | 105 | ### Line plot 106 | 107 | The following is taken from the example in `examples/double_plot.py` 108 | 109 | 110 | ```python 111 | import matplotlib.pyplot as plt 112 | import numpy as np 113 | import matplotlib as mpl 114 | import mpl_ascii 115 | 116 | mpl_ascii.AXES_WIDTH=100 117 | mpl_ascii.AXES_HEIGHT=40 118 | 119 | 120 | mpl.use("module://mpl_ascii") 121 | 122 | 123 | # Data for plotting 124 | t = np.arange(0.0, 2.0, 0.01) 125 | s = 1 + np.sin(2 * np.pi * t) 126 | c = 1 + np.cos(2 * np.pi * t) 127 | 128 | fig, ax = plt.subplots() 129 | ax.plot(t, s) 130 | ax.plot(t, c) 131 | 132 | ax.set(xlabel='time (s)', ylabel='voltage (mV)', 133 | title='About as simple as it gets, folks') 134 | 135 | plt.show() 136 | ``` 137 | ![Double plot with colors](https://imgur.com/PyTPR4C.png) 138 | 139 | You can find more examples and their outputs in the `examples` folder. 140 | 141 | ## Global Variables 142 | 143 | ### mpl_ascii.AXES_WIDTH 144 | 145 | Adjust the width of each axis according to the number of characters. The library first looks for the `AXES_WIDTH` as an environment variable. This can then be overwritten in the Python program by setting `mpl_ascii.AXES_WIDTH`. The final width of the image might extend a few characters beyond this, depending on the size of the ticks and axis labels. Default is `150`. 146 | 147 | ### mpl_ascii.AXES_HEIGHT 148 | 149 | Adjust the height of each axis according to the number of characters. The library first looks for the `AXES_HEIGHT` as an environment variable. This can then be overwritten in the Python program by setting `mpl_ascii.AXES_HEIGHT`. The final height of the image might extend a few characters beyond this, depending on the size of the ticks and axis labels. Default is `40`. 150 | 151 | ### mpl_ascii.ENABLE_COLORS 152 | 153 | Executing `plt.show()` will render the image in colored text. Default is `True` 154 | 155 | 156 | ## Use cases 157 | 158 | ### Using Version Control for Plots 159 | 160 | Handling plots with version control can pose challenges, especially when dealing with binary files. Here are some issues you might encounter: 161 | 162 | - Binary Files: Committing binary files like PNGs can significantly increase your repository’s size. They are also difficult to compare (diff) and can lead to complex merge conflicts. 163 | 164 | - SVG Files: Although SVGs are more version control-friendly than binary formats, they can still cause problems: 165 | - Large or complex graphics can result in excessively large SVG files. 166 | - Diffs can be hard to interpret. 167 | 168 | To mitigate these issues, ASCII plots serve as an effective alternative: 169 | 170 | - Size: ASCII representations are much smaller in size. 171 | - Version Control Compatibility: They are straightforward to diff and simplify resolving merge conflicts. 172 | 173 | 174 | This package acts as a backend for Matplotlib, enabling you to continue creating plots in your usual formats (PNG, SVG) during development. When you’re ready to commit your plots to a repository, simply switch to the `mpl_ascii` backend to convert them into ASCII format. 175 | 176 | ## Feedback 177 | 178 | Please help make this package better by: 179 | - reporting bugs. 180 | - making feature requests. Matplotlib is an enormous library and this supports only a part of it. Let me know if there particular charts that you would like to be converted to ASCII 181 | - letting me know what you use this for. 182 | 183 | If you want to tell me about any of the above just use the Issues tab for now. 184 | 185 | Thanks for reading and I hope you will like these plots as much as I do :-) -------------------------------------------------------------------------------- /examples/bar_chart.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import matplotlib as mpl 3 | 4 | mpl.use("module://mpl_ascii") 5 | 6 | if __name__ == "__main__": 7 | parser = argparse.ArgumentParser(allow_abbrev=False) 8 | parser.add_argument("--out", type=str) 9 | 10 | args = parser.parse_args() 11 | out = args.out 12 | 13 | # data from https://allisonhorst.github.io/palmerpenguins/ 14 | 15 | import matplotlib.pyplot as plt 16 | import numpy as np 17 | 18 | 19 | labels = ['G1', 'G2', 'G3', 'G4', 'G5'] 20 | men_means = [20, 34, 30, 35, 27] 21 | women_means = [25, 32, 34, 20, 25] 22 | 23 | x = np.arange(len(labels)) # the label locations 24 | width = 0.35 # the width of the bars 25 | 26 | fig, ax = plt.subplots() 27 | rects1 = ax.bar(x - width/2, men_means, width, label='Men') 28 | rects2 = ax.bar(x + width/2, women_means, width, label='Women') 29 | 30 | # Add some text for labels, title and custom x-axis tick labels, etc. 31 | ax.set_ylabel('Scores') 32 | ax.set_title('Scores by group and gender') 33 | ax.set_xticks(x, labels) 34 | ax.legend() 35 | 36 | ax.bar_label(rects1, padding=3) 37 | ax.bar_label(rects2, padding=3) 38 | 39 | fig.tight_layout() 40 | 41 | if out: 42 | fig.savefig(out) 43 | 44 | plt.show() 45 | -------------------------------------------------------------------------------- /examples/bar_chart.txt: -------------------------------------------------------------------------------- 1 | Scores by group and gender 2 | 3 | +------------------------------------------------------------------------------------------------------------------------------------------------------+ 4 | | | 5 | | | 6 | 35-- | 7 | | ########## | 8 | | ########## %%%%%%%%%% ########## | 9 | | ########## %%%%%%%%%% ########## | 10 | | ##########%%%%%%%%%% %%%%%%%%%% ########## | 11 | 30-- ##########%%%%%%%%%% %%%%%%%%%% ########## | 12 | | ##########%%%%%%%%%% ##########%%%%%%%%%% ########## | 13 | | ##########%%%%%%%%%% ##########%%%%%%%%%% ########## | 14 | | ##########%%%%%%%%%% ##########%%%%%%%%%% ########## | 15 | | ##########%%%%%%%%%% ##########%%%%%%%%%% ########## ########## | 16 | 25-- ##########%%%%%%%%%% ##########%%%%%%%%%% ########## ########## | 17 | | %%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ########## ##########%%%%%%%%%% | 18 | | %%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ########## ##########%%%%%%%%%% | 19 | | %%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ########## ##########%%%%%%%%%% | 20 | | %%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ########## ##########%%%%%%%%%% | 21 | S | %%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ########## ##########%%%%%%%%%% | 22 | c 20-- %%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ########## ##########%%%%%%%%%% | 23 | o | ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% | 24 | r | ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% | 25 | e | ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% | 26 | s | ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% | 27 | 15-- ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% | 28 | | ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% | 29 | | ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% | 30 | | ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% | 31 | | ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% | 32 | 10-- ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% | 33 | | ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% | 34 | | ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% | 35 | | ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% | 36 | | ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% | 37 | | ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% | 38 | 5-- ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% | 39 | | ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% | 40 | | ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% | 41 | | ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% | 42 | | ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% | 43 | 0-- ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% ##########%%%%%%%%%% | 44 | +-----------------|----------------------------|---------------------------|----------------------------|----------------------------|-----------------+ 45 | G1 G2 G3 G4 G5 46 | 47 | 48 | +-----------+ 49 | | Legend | 50 | | | 51 | | ### Men | 52 | | %%% Women | 53 | +-----------+ -------------------------------------------------------------------------------- /examples/bar_color.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import matplotlib as mpl 3 | import mpl_ascii 4 | 5 | mpl.use("module://mpl_ascii") 6 | mpl_ascii.AXES_WIDTH=100 7 | mpl_ascii.AXES_HEIGHT=40 8 | 9 | if __name__ == "__main__": 10 | parser = argparse.ArgumentParser(allow_abbrev=False) 11 | parser.add_argument("--out", type=str) 12 | 13 | args = parser.parse_args() 14 | out = args.out 15 | 16 | import matplotlib.pyplot as plt 17 | 18 | # Example data 19 | fruits = ['apple', 'blueberry', 'cherry', 'orange'] 20 | counts = [10, 15, 7, 5] 21 | colors = ['red', 'blue', 'red', 'orange'] # Colors corresponding to each fruit 22 | 23 | fig, ax = plt.subplots() 24 | 25 | # Plot each bar individually 26 | for fruit, count, color in zip(fruits, counts, colors): 27 | ax.bar(fruit, count, color=color, label=color) 28 | 29 | # Display the legend 30 | ax.legend(title='Fruit color') 31 | 32 | 33 | if out: 34 | fig.savefig(out) 35 | plt.show() -------------------------------------------------------------------------------- /examples/bar_color.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | +----------------------------------------------------------------------------------------------------+ 4 | | | 5 | | | 6 | | | 7 | | %%%%%%%%%%%%%%%%%%% | 8 | 14-- %%%%%%%%%%%%%%%%%%% | 9 | | %%%%%%%%%%%%%%%%%%% | 10 | | %%%%%%%%%%%%%%%%%%% | 11 | | %%%%%%%%%%%%%%%%%%% | 12 | | %%%%%%%%%%%%%%%%%%% | 13 | 12-- %%%%%%%%%%%%%%%%%%% | 14 | | %%%%%%%%%%%%%%%%%%% | 15 | | %%%%%%%%%%%%%%%%%%% | 16 | | %%%%%%%%%%%%%%%%%%% | 17 | | %%%%%%%%%%%%%%%%%%% | 18 | 10-- %%%%%%%%%%%%%%%%%%% | 19 | | ################### %%%%%%%%%%%%%%%%%%% | 20 | | ################### %%%%%%%%%%%%%%%%%%% | 21 | | ################### %%%%%%%%%%%%%%%%%%% | 22 | | ################### %%%%%%%%%%%%%%%%%%% | 23 | 8-- ################### %%%%%%%%%%%%%%%%%%% | 24 | | ################### %%%%%%%%%%%%%%%%%%% | 25 | | ################### %%%%%%%%%%%%%%%%%%% | 26 | | ################### %%%%%%%%%%%%%%%%%%% | 27 | | ################### %%%%%%%%%%%%%%%%%%% ################### | 28 | 6-- ################### %%%%%%%%%%%%%%%%%%% ################### | 29 | | ################### %%%%%%%%%%%%%%%%%%% ################### | 30 | | ################### %%%%%%%%%%%%%%%%%%% ################### | 31 | | ################### %%%%%%%%%%%%%%%%%%% ################### | 32 | | ################### %%%%%%%%%%%%%%%%%%% ################### &&&&&&&&&&&&&&&&&&& | 33 | 4-- ################### %%%%%%%%%%%%%%%%%%% ################### &&&&&&&&&&&&&&&&&&& | 34 | | ################### %%%%%%%%%%%%%%%%%%% ################### &&&&&&&&&&&&&&&&&&& | 35 | | ################### %%%%%%%%%%%%%%%%%%% ################### &&&&&&&&&&&&&&&&&&& | 36 | | ################### %%%%%%%%%%%%%%%%%%% ################### &&&&&&&&&&&&&&&&&&& | 37 | | ################### %%%%%%%%%%%%%%%%%%% ################### &&&&&&&&&&&&&&&&&&& | 38 | 2-- ################### %%%%%%%%%%%%%%%%%%% ################### &&&&&&&&&&&&&&&&&&& | 39 | | ################### %%%%%%%%%%%%%%%%%%% ################### &&&&&&&&&&&&&&&&&&& | 40 | | ################### %%%%%%%%%%%%%%%%%%% ################### &&&&&&&&&&&&&&&&&&& | 41 | | ################### %%%%%%%%%%%%%%%%%%% ################### &&&&&&&&&&&&&&&&&&& | 42 | | ################### %%%%%%%%%%%%%%%%%%% ################### &&&&&&&&&&&&&&&&&&& | 43 | 0-- ################### %%%%%%%%%%%%%%%%%%% ################### &&&&&&&&&&&&&&&&&&& | 44 | +--------------|-----------------------|----------------------|-----------------------|--------------+ 45 | apple blueberry cherry orange 46 | 47 | 48 | +-------------+ 49 | | Fruit color | 50 | | | 51 | | ### red | 52 | | %%% blue | 53 | | ### red | 54 | | &&& orange | 55 | +-------------+ -------------------------------------------------------------------------------- /examples/bar_label_1.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import matplotlib as mpl 3 | 4 | mpl.use("module://mpl_ascii") 5 | 6 | if __name__ == "__main__": 7 | parser = argparse.ArgumentParser(allow_abbrev=False) 8 | parser.add_argument("--out", type=str) 9 | 10 | args = parser.parse_args() 11 | out = args.out 12 | 13 | import matplotlib.pyplot as plt 14 | import numpy as np 15 | 16 | species = ('Adelie', 'Chinstrap', 'Gentoo') 17 | sex_counts = { 18 | 'Male': np.array([73, 34, 61]), 19 | 'Female': np.array([73, 34, 58]), 20 | } 21 | width = 0.6 # the width of the bars: can also be len(x) sequence 22 | 23 | 24 | fig, ax = plt.subplots() 25 | bottom = np.zeros(3) 26 | 27 | for sex, sex_count in sex_counts.items(): 28 | p = ax.bar(species, sex_count, width, label=sex, bottom=bottom) 29 | bottom += sex_count 30 | 31 | ax.bar_label(p, label_type='center') 32 | 33 | ax.set_title('Number of penguins by sex') 34 | ax.legend() 35 | 36 | if out: 37 | fig.savefig(out) 38 | 39 | plt.show() 40 | -------------------------------------------------------------------------------- /examples/bar_label_2.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import matplotlib as mpl 3 | 4 | mpl.use("module://mpl_ascii") 5 | 6 | if __name__ == "__main__": 7 | parser = argparse.ArgumentParser(allow_abbrev=False) 8 | parser.add_argument("--out", type=str) 9 | 10 | args = parser.parse_args() 11 | out = args.out 12 | 13 | import matplotlib.pyplot as plt 14 | import numpy as np 15 | 16 | np.random.seed(19680801) 17 | 18 | # Example data 19 | people = ('Tom', 'Dick', 'Harry', 'Slim', 'Jim') 20 | y_pos = np.arange(len(people)) 21 | performance = 3 + 10 * np.random.rand(len(people)) 22 | error = np.random.rand(len(people)) 23 | 24 | fig, ax = plt.subplots() 25 | 26 | hbars = ax.barh(y_pos, performance, xerr=error, align='center') 27 | ax.set_yticks(y_pos, labels=people) 28 | ax.invert_yaxis() # labels read top-to-bottom 29 | ax.set_xlabel('Performance') 30 | ax.set_title('How fast do you want to go today?') 31 | 32 | # Label with specially formatted floats 33 | ax.bar_label(hbars, fmt='%.2f') 34 | ax.set_xlim(right=15) # adjust xlim to fit labels 35 | 36 | if out: 37 | fig.savefig(out) 38 | plt.show() 39 | 40 | -------------------------------------------------------------------------------- /examples/bar_label_2.txt: -------------------------------------------------------------------------------- 1 | How fast do you want to go today? 2 | 3 | +------------------------------------------------------------------------------------------------------------------------------------------------------+ 4 | | | 5 | | | 6 | | | 7 | |############################################################################################################################### | 8 | |############################################################################################################################### | 9 | Jim--########################################################################################################################--------------- | 10 | |############################################################################################################################### | 11 | |############################################################################################################################### | 12 | |############################################################################################################################### | 13 | | | 14 | |###################################################################################### | 15 | |###################################################################################### | 16 | Slim--###############################################################################--------------- | 17 | |###################################################################################### | 18 | |###################################################################################### | 19 | |###################################################################################### | 20 | | | 21 | |#################################################################################################### | 22 | |#################################################################################################### | 23 | |#################################################################################################### | 24 | Harry--###################################################################################################---- | 25 | |#################################################################################################### | 26 | |#################################################################################################### | 27 | | | 28 | | | 29 | |######################################################################################################## | 30 | |######################################################################################################## | 31 | Dick--#####################################################################################################------ | 32 | |######################################################################################################## | 33 | |######################################################################################################## | 34 | |######################################################################################################## | 35 | | | 36 | |################################################################################################### | 37 | |################################################################################################### | 38 | Tom--############################################################################################--------------- | 39 | |################################################################################################### | 40 | |################################################################################################### | 41 | |################################################################################################### | 42 | | | 43 | | | 44 | +|-------------------|-------------------|-------------------|------------------|-------------------|-------------------|-------------------|----------+ 45 | 0 2 4 6 8 10 12 14 46 | Performance -------------------------------------------------------------------------------- /examples/bar_label_3.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import matplotlib as mpl 3 | 4 | mpl.use("module://mpl_ascii") 5 | 6 | if __name__ == "__main__": 7 | parser = argparse.ArgumentParser(allow_abbrev=False) 8 | parser.add_argument("--out", type=str) 9 | 10 | args = parser.parse_args() 11 | out = args.out 12 | 13 | import matplotlib.pyplot as plt 14 | import numpy as np 15 | 16 | 17 | np.random.seed(19680801) 18 | 19 | # Example data 20 | people = ('Tom', 'Dick', 'Harry', 'Slim', 'Jim') 21 | y_pos = np.arange(len(people)) 22 | performance = 3 + 10 * np.random.rand(len(people)) 23 | error = np.random.rand(len(people)) 24 | 25 | fig, ax = plt.subplots() 26 | 27 | hbars = ax.barh(y_pos, performance, xerr=error, align='center') 28 | ax.set_yticks(y_pos, labels=people) 29 | ax.invert_yaxis() # labels read top-to-bottom 30 | ax.set_xlabel('Performance') 31 | ax.set_title('How fast do you want to go today?') 32 | 33 | # Label with given captions, custom padding and annotate options 34 | ax.bar_label(hbars, labels=[f'±{e:.2f}' for e in error], 35 | padding=8, color='b', fontsize=14) 36 | ax.set_xlim(right=16) 37 | 38 | if out: 39 | fig.savefig(out) 40 | 41 | plt.show() -------------------------------------------------------------------------------- /examples/bar_label_3.txt: -------------------------------------------------------------------------------- 1 | How fast do you want to go today? 2 | 3 | +------------------------------------------------------------------------------------------------------------------------------------------------------+ 4 | | | 5 | | | 6 | | | 7 | |####################################################################################################################### | 8 | |####################################################################################################################### | 9 | Jim--################################################################################################################--------------- | 10 | |####################################################################################################################### | 11 | |####################################################################################################################### | 12 | |####################################################################################################################### | 13 | | | 14 | |################################################################################# | 15 | |################################################################################# | 16 | Slim--##########################################################################-------------- | 17 | |################################################################################# | 18 | |################################################################################# | 19 | |################################################################################# | 20 | | | 21 | |############################################################################################## | 22 | |############################################################################################## | 23 | |############################################################################################## | 24 | Harry--#############################################################################################--- | 25 | |############################################################################################## | 26 | |############################################################################################## | 27 | | | 28 | | | 29 | |################################################################################################# | 30 | |################################################################################################# | 31 | Dick--###############################################################################################----- | 32 | |################################################################################################# | 33 | |################################################################################################# | 34 | |################################################################################################# | 35 | | | 36 | |############################################################################################# | 37 | |############################################################################################# | 38 | Tom--#######################################################################################-------------- | 39 | |############################################################################################# | 40 | |############################################################################################# | 41 | |############################################################################################# | 42 | | | 43 | | | 44 | +|------------------|-----------------|------------------|-----------------|------------------|------------------|-----------------|------------------|+ 45 | 0 2 4 6 8 10 12 14 16 46 | Performance -------------------------------------------------------------------------------- /examples/bar_label_4.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import matplotlib as mpl 3 | 4 | mpl.use("module://mpl_ascii") 5 | 6 | if __name__ == "__main__": 7 | parser = argparse.ArgumentParser(allow_abbrev=False) 8 | parser.add_argument("--out", type=str) 9 | 10 | args = parser.parse_args() 11 | out = args.out 12 | 13 | import matplotlib.pyplot as plt 14 | import numpy as np 15 | 16 | fruit_names = ['Coffee', 'Salted Caramel', 'Pistachio'] 17 | fruit_counts = [4000, 2000, 7000] 18 | 19 | fig, ax = plt.subplots() 20 | bar_container = ax.bar(fruit_names, fruit_counts) 21 | ax.set(ylabel='pints sold', title='Gelato sales by flavor', ylim=(0, 8000)) 22 | ax.bar_label(bar_container, fmt='{:,.0f}') 23 | 24 | if out: 25 | fig.savefig(out) 26 | 27 | plt.show() 28 | -------------------------------------------------------------------------------- /examples/bar_label_4.txt: -------------------------------------------------------------------------------- 1 | Gelato sales by flavor 2 | 3 | +------------------------------------------------------------------------------------------------------------------------------------------------------+ 4 | 8000-- | 5 | | | 6 | | | 7 | | | 8 | | | 9 | 7000-- | 10 | | ####################################### | 11 | | ####################################### | 12 | | ####################################### | 13 | | ####################################### | 14 | 6000-- ####################################### | 15 | | ####################################### | 16 | | ####################################### | 17 | | ####################################### | 18 | | ####################################### | 19 | p 5000-- ####################################### | 20 | i | ####################################### | 21 | n | ####################################### | 22 | t | ####################################### | 23 | s | ####################################### | 24 | 4000-- ####################################### ####################################### | 25 | s | ####################################### ####################################### | 26 | o | ####################################### ####################################### | 27 | l | ####################################### ####################################### | 28 | d 3000-- ####################################### ####################################### | 29 | | ####################################### ####################################### | 30 | | ####################################### ####################################### | 31 | | ####################################### ####################################### | 32 | | ####################################### ####################################### | 33 | 2000-- ####################################### ####################################### | 34 | | ####################################### ####################################### ####################################### | 35 | | ####################################### ####################################### ####################################### | 36 | | ####################################### ####################################### ####################################### | 37 | | ####################################### ####################################### ####################################### | 38 | 1000-- ####################################### ####################################### ####################################### | 39 | | ####################################### ####################################### ####################################### | 40 | | ####################################### ####################################### ####################################### | 41 | | ####################################### ####################################### ####################################### | 42 | | ####################################### ####################################### ####################################### | 43 | 0-- ####################################### ####################################### ####################################### | 44 | +--------------------------|-----------------------------------------------|------------------------------------------------|--------------------------+ 45 | Coffee Salted Caramel Pistachio 46 | -------------------------------------------------------------------------------- /examples/bar_label_5.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import matplotlib as mpl 3 | 4 | mpl.use("module://mpl_ascii") 5 | 6 | if __name__ == "__main__": 7 | parser = argparse.ArgumentParser(allow_abbrev=False) 8 | parser.add_argument("--out", type=str) 9 | 10 | args = parser.parse_args() 11 | out = args.out 12 | 13 | import matplotlib.pyplot as plt 14 | import numpy as np 15 | 16 | animal_names = ['Lion', 'Gazelle', 'Cheetah'] 17 | mph_speed = [50, 60, 75] 18 | 19 | fig, ax = plt.subplots() 20 | bar_container = ax.bar(animal_names, mph_speed) 21 | ax.set(ylabel='speed in MPH', title='Running speeds', ylim=(0, 80)) 22 | 23 | # ax.bar_label(bar_container, fmt=lambda x: f'{x * 1.61:.1f} km/h') 24 | 25 | if out: 26 | fig.savefig(out) 27 | 28 | plt.show() 29 | -------------------------------------------------------------------------------- /examples/bar_label_5.txt: -------------------------------------------------------------------------------- 1 | Running speeds 2 | 3 | +------------------------------------------------------------------------------------------------------------------------------------------------------+ 4 | 80-- | 5 | | | 6 | | | 7 | | ####################################### | 8 | | ####################################### | 9 | 70-- ####################################### | 10 | | ####################################### | 11 | | ####################################### | 12 | | ####################################### | 13 | | ####################################### | 14 | 60-- ####################################### | 15 | | ####################################### ####################################### | 16 | | ####################################### ####################################### | 17 | | ####################################### ####################################### | 18 | s | ####################################### ####################################### | 19 | p 50-- ####################################### ####################################### | 20 | e | ####################################### ####################################### ####################################### | 21 | e | ####################################### ####################################### ####################################### | 22 | d | ####################################### ####################################### ####################################### | 23 | | ####################################### ####################################### ####################################### | 24 | i 40-- ####################################### ####################################### ####################################### | 25 | n | ####################################### ####################################### ####################################### | 26 | | ####################################### ####################################### ####################################### | 27 | M | ####################################### ####################################### ####################################### | 28 | P 30-- ####################################### ####################################### ####################################### | 29 | H | ####################################### ####################################### ####################################### | 30 | | ####################################### ####################################### ####################################### | 31 | | ####################################### ####################################### ####################################### | 32 | | ####################################### ####################################### ####################################### | 33 | 20-- ####################################### ####################################### ####################################### | 34 | | ####################################### ####################################### ####################################### | 35 | | ####################################### ####################################### ####################################### | 36 | | ####################################### ####################################### ####################################### | 37 | | ####################################### ####################################### ####################################### | 38 | 10-- ####################################### ####################################### ####################################### | 39 | | ####################################### ####################################### ####################################### | 40 | | ####################################### ####################################### ####################################### | 41 | | ####################################### ####################################### ####################################### | 42 | | ####################################### ####################################### ####################################### | 43 | 0-- ####################################### ####################################### ####################################### | 44 | +--------------------------|-----------------------------------------------|------------------------------------------------|--------------------------+ 45 | Lion Gazelle Cheetah 46 | -------------------------------------------------------------------------------- /examples/bar_stacked.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import matplotlib as mpl 3 | 4 | 5 | mpl.use("module://mpl_ascii") 6 | 7 | if __name__ == "__main__": 8 | parser = argparse.ArgumentParser(allow_abbrev=False) 9 | parser.add_argument("--out", type=str) 10 | 11 | args = parser.parse_args() 12 | out = args.out 13 | 14 | import matplotlib.pyplot as plt 15 | import numpy as np 16 | 17 | # data from https://allisonhorst.github.io/palmerpenguins/ 18 | 19 | species = ( 20 | "Adelie\n $\\mu=$3700.66g", 21 | "Chinstrap\n $\\mu=$3733.09g", 22 | "Gentoo\n $\\mu=5076.02g$", 23 | ) 24 | weight_counts = { 25 | "Below": np.array([70, 31, 58]), 26 | "Above": np.array([82, 37, 66]), 27 | } 28 | width = 0.5 29 | 30 | fig, ax = plt.subplots() 31 | bottom = np.zeros(3) 32 | 33 | for boolean, weight_count in weight_counts.items(): 34 | p = ax.bar(species, weight_count, width, label=boolean, bottom=bottom) 35 | bottom += weight_count 36 | 37 | ax.set_title("Number of penguins with above average body mass") 38 | ax.legend(loc="upper right") 39 | 40 | if out: 41 | fig.savefig(out) 42 | plt.show() -------------------------------------------------------------------------------- /examples/barh.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import matplotlib as mpl 3 | 4 | mpl.use("module://mpl_ascii") 5 | 6 | if __name__ == "__main__": 7 | parser = argparse.ArgumentParser(allow_abbrev=False) 8 | parser.add_argument("--out", type=str) 9 | 10 | args = parser.parse_args() 11 | out = args.out 12 | 13 | # data from https://allisonhorst.github.io/palmerpenguins/ 14 | 15 | import matplotlib.pyplot as plt 16 | import numpy as np 17 | 18 | import matplotlib.pyplot as plt 19 | import numpy as np 20 | 21 | # Fixing random state for reproducibility 22 | np.random.seed(19680801) 23 | 24 | fig, ax = plt.subplots() 25 | 26 | # Example data 27 | people = ('Tom', 'Dick', 'Harry', 'Slim', 'Jim') 28 | y_pos = np.arange(len(people)) 29 | performance = 3 + 10 * np.random.rand(len(people)) 30 | error = np.random.rand(len(people)) 31 | 32 | ax.barh(y_pos, performance, xerr=error, align='center') 33 | ax.set_yticks(y_pos, labels=people) 34 | ax.invert_yaxis() # labels read top-to-bottom 35 | ax.set_xlabel('Performance') 36 | ax.set_title('How fast do you want to go today?') 37 | 38 | if out: 39 | fig.savefig(out) 40 | plt.show() 41 | -------------------------------------------------------------------------------- /examples/barh.txt: -------------------------------------------------------------------------------- 1 | How fast do you want to go today? 2 | 3 | +------------------------------------------------------------------------------------------------------------------------------------------------------+ 4 | | | 5 | | | 6 | | | 7 | |###################################################################################################################################### | 8 | |###################################################################################################################################### | 9 | Jim--###############################################################################################################################---------------- | 10 | |###################################################################################################################################### | 11 | |###################################################################################################################################### | 12 | |###################################################################################################################################### | 13 | | | 14 | |########################################################################################### | 15 | |########################################################################################### | 16 | Slim--####################################################################################--------------- | 17 | |########################################################################################### | 18 | |########################################################################################### | 19 | |########################################################################################### | 20 | | | 21 | |########################################################################################################## | 22 | |########################################################################################################## | 23 | |########################################################################################################## | 24 | Harry--########################################################################################################----- | 25 | |########################################################################################################## | 26 | |########################################################################################################## | 27 | | | 28 | | | 29 | |############################################################################################################## | 30 | |############################################################################################################## | 31 | Dick--###########################################################################################################------ | 32 | |############################################################################################################## | 33 | |############################################################################################################## | 34 | |############################################################################################################## | 35 | | | 36 | |######################################################################################################### | 37 | |######################################################################################################### | 38 | Tom--##################################################################################################---------------- | 39 | |######################################################################################################### | 40 | |######################################################################################################### | 41 | |######################################################################################################### | 42 | | | 43 | | | 44 | +|--------------------|--------------------|--------------------|--------------------|--------------------|--------------------|--------------------|--+ 45 | 0 2 4 6 8 10 12 14 46 | Performance -------------------------------------------------------------------------------- /examples/box_plot_basic.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import matplotlib as mpl 3 | import mpl_ascii 4 | 5 | mpl.use("module://mpl_ascii") 6 | mpl_ascii.ENABLE_COLORS=False 7 | mpl_ascii.AXES_HEIGHT=60 8 | 9 | if __name__ == "__main__": 10 | parser = argparse.ArgumentParser(allow_abbrev=False) 11 | parser.add_argument("--out", type=str) 12 | 13 | args = parser.parse_args() 14 | out = args.out 15 | 16 | import matplotlib.pyplot as plt 17 | import numpy as np 18 | 19 | from matplotlib.patches import Polygon 20 | 21 | # Fixing random state for reproducibility 22 | np.random.seed(19680801) 23 | 24 | # fake up some data 25 | spread = np.random.rand(50) * 100 26 | center = np.ones(25) * 50 27 | flier_high = np.random.rand(10) * 100 + 100 28 | flier_low = np.random.rand(10) * -100 29 | data = np.concatenate((spread, center, flier_high, flier_low)) 30 | 31 | fig, axs = plt.subplots(1,3) 32 | 33 | # basic plot 34 | axs[0].boxplot(data) 35 | axs[0].set_title('basic plot') 36 | 37 | 38 | # notched plot 39 | axs[1].boxplot(data, 1) 40 | axs[1].set_title('notched plot') 41 | 42 | 43 | # horizontal boxes 44 | axs[2].boxplot(data, 0, 'rs', 0) 45 | axs[2].set_title('horizontal boxes') 46 | 47 | if out: 48 | fig.savefig(out) 49 | 50 | plt.show() -------------------------------------------------------------------------------- /examples/broken_line_plot.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import matplotlib as mpl 3 | 4 | mpl.use("module://mpl_ascii") 5 | 6 | if __name__ == "__main__": 7 | parser = argparse.ArgumentParser(allow_abbrev=False) 8 | parser.add_argument("--out", type=str) 9 | 10 | args = parser.parse_args() 11 | out = args.out 12 | import matplotlib.pyplot as plt 13 | import numpy as np 14 | 15 | fig, axes = plt.subplots(1,2) 16 | 17 | axes[0].plot([0, 0, None, 1, 1], [0, 1, None, 0, 1]) 18 | axes[1].plot([0,0,1], [None, 2,3]) 19 | 20 | if out: 21 | fig.savefig(out) 22 | 23 | plt.show() 24 | -------------------------------------------------------------------------------- /examples/categorical_variables.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import matplotlib as mpl 3 | 4 | mpl.use("module://mpl_ascii") 5 | 6 | if __name__ == "__main__": 7 | parser = argparse.ArgumentParser(allow_abbrev=False) 8 | parser.add_argument("--out", type=str) 9 | 10 | args = parser.parse_args() 11 | out = args.out 12 | 13 | import matplotlib.pyplot as plt 14 | 15 | # Horizontal bar plot with gaps 16 | import matplotlib.pyplot as plt 17 | 18 | data = {'apple': 10, 'orange': 15, 'lemon': 5, 'lime': 20} 19 | names = list(data.keys()) 20 | values = list(data.values()) 21 | 22 | fig, axs = plt.subplots(1, 3, figsize=(9, 3), sharey=True) 23 | axs[0].bar(names, values) 24 | axs[1].scatter(names, values) 25 | axs[2].plot(names, values) 26 | fig.suptitle('Categorical Plotting') 27 | if out: 28 | fig.savefig(out) 29 | plt.show() 30 | -------------------------------------------------------------------------------- /examples/cohere.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import matplotlib as mpl 3 | 4 | mpl.use("module://mpl_ascii") 5 | 6 | if __name__ == "__main__": 7 | parser = argparse.ArgumentParser(allow_abbrev=False) 8 | parser.add_argument("--out", type=str) 9 | 10 | args = parser.parse_args() 11 | out = args.out 12 | 13 | import matplotlib.pyplot as plt 14 | import numpy as np 15 | 16 | # Fixing random state for reproducibility 17 | np.random.seed(19680801) 18 | 19 | dt = 0.01 20 | t = np.arange(0, 30, dt) 21 | nse1 = np.random.randn(len(t)) # white noise 1 22 | nse2 = np.random.randn(len(t)) # white noise 2 23 | 24 | # Two signals with a coherent part at 10 Hz and a random part 25 | s1 = np.sin(2 * np.pi * 10 * t) + nse1 26 | s2 = np.sin(2 * np.pi * 10 * t) + nse2 27 | 28 | fig, axs = plt.subplots(2, 1, layout='constrained') 29 | axs[0].plot(t, s1, t, s2) 30 | axs[0].set_xlim(0, 2) 31 | axs[0].set_xlabel('Time (s)') 32 | axs[0].set_ylabel('s1 and s2') 33 | axs[0].grid(True) 34 | 35 | cxy, f = axs[1].cohere(s1, s2, 256, 1. / dt) 36 | axs[1].set_ylabel('Coherence') 37 | if args.out: 38 | fig.savefig(args.out) 39 | plt.show() 40 | -------------------------------------------------------------------------------- /examples/color_bar_scatter.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import matplotlib as mpl 3 | 4 | mpl.use("module://mpl_ascii") 5 | 6 | if __name__ == "__main__": 7 | parser = argparse.ArgumentParser(allow_abbrev=False) 8 | parser.add_argument("--out", type=str) 9 | 10 | args = parser.parse_args() 11 | out = args.out 12 | 13 | import numpy as np 14 | import matplotlib.pyplot as plt 15 | import mpl_ascii 16 | 17 | # Generating some data 18 | np.random.seed(19680802) 19 | 20 | x = np.random.rand(100) * 100 # Random values for x-axis 21 | y = np.random.rand(100) * 100 # Random values for y-axis 22 | values = np.random.randint(low=0, high=16, size=100) # Values to determine the color of each point 23 | # values = np.random.rand(100) # Values to determine the color of each point 24 | # Create the plot 25 | fig, ax = plt.subplots() 26 | 27 | # Create a scatter plot 28 | scatter = ax.scatter(x, y, c=values, cmap='viridis') 29 | 30 | # Create a colorbar 31 | colorbar = plt.colorbar(scatter) 32 | colorbar.set_label('Intensity') 33 | 34 | # Setting labels and title 35 | ax.set_xlabel('X axis') 36 | ax.set_ylabel('Y axis') 37 | ax.set_title('Scatter Plot with Color Bar') 38 | 39 | if out: 40 | fig.savefig(out) 41 | 42 | plt.show() 43 | -------------------------------------------------------------------------------- /examples/color_bar_scatter.txt: -------------------------------------------------------------------------------- 1 | Scatter Plot with Color Bar 2 | 3 | +------------------------------------------------------------------------------------------------------------------------------------------------------+ +----------+ 4 | 100-- | |>>>>>>>>>>| 5 | | | |>>>>>>>>>>| 6 | | ? @ ? | |>>>>>>>>>>| 7 | | $ | |<<<<<<<<<<--14 8 | | @ @ % % = = | |<<<<<<<<<<| 9 | | @ ? = $ | |<<<<<<<<<<| 10 | | < < % @ < > | |<<<<<<<<<<| 11 | | % @ @ = ? | |<<<<<<<<<<| 12 | 80-- $ | |??????????--12 13 | | < | |??????????| 14 | | # # | |??????????| 15 | | % <# | |??????????| 16 | | = % # | |??????????| 17 | | $ # @ | |==========--10 18 | | @ ? $= | |==========| 19 | 60-- < ? | |==========| I 20 | | % % $ | |==========| n 21 | Y | ? < ? $ | |==========| t 22 | | @ % | |$$$$$$$$$$--8 e 23 | a | > < | |$$$$$$$$$$| n 24 | x | ? | |$$$$$$$$$$| s 25 | i | | |$$$$$$$$$$| i 26 | s 40-- $ ? | |$$$$$$$$$$| t 27 | | | |@@@@@@@@@@--6 y 28 | | @ | |@@@@@@@@@@| 29 | | ? | |@@@@@@@@@@| 30 | | % & $ | |@@@@@@@@@@| 31 | | < ? ? < @ < | |@@@@@@@@@@| 32 | | % | |@@@@@@@@@@| 33 | | > & | |&&&&&&&&&&--4 34 | 20-- $ = $ | |&&&&&&&&&&| 35 | | % % = ? | |&&&&&&&&&&| 36 | | > = = & | |&&&&&&&&&&| 37 | | # | |&&&&&&&&&&| 38 | | & $ < % | |%%%%%%%%%%--2 39 | | & $ $ $ = | |%%%%%%%%%%| 40 | | @ ? | |%%%%%%%%%%| 41 | 0-- $ @ & # = | |%%%%%%%%%%| 42 | | | |%%%%%%%%%%| 43 | | | |%%%%%%%%%%--0 44 | +----|---------------------------|--------------------------|---------------------------|---------------------------|--------------------------|-------+ +----------+ 45 | 0 20 40 60 80 100 46 | X axis -------------------------------------------------------------------------------- /examples/contour_double.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import matplotlib as mpl 3 | 4 | mpl.use("module://mpl_ascii") 5 | 6 | if __name__ == "__main__": 7 | parser = argparse.ArgumentParser(allow_abbrev=False) 8 | parser.add_argument("--out", type=str) 9 | 10 | args = parser.parse_args() 11 | out = args.out 12 | 13 | import matplotlib.pyplot as plt 14 | import numpy as np 15 | 16 | # Create a grid of x and y values 17 | x = np.linspace(-5, 5, 100) 18 | y = np.linspace(-5, 5, 100) 19 | X, Y = np.meshgrid(x, y) 20 | 21 | # Define two functions to generate contour data 22 | Z1 = np.sin(np.sqrt(X**2 + Y**2)) 23 | Z2 = np.cos(np.sqrt(X**2 + Y**2)) 24 | 25 | # Create a figure and axis object 26 | fig, ax = plt.subplots() 27 | 28 | # Create the contour plots 29 | contour1 = ax.contour(X, Y, Z1, colors='blue') 30 | contour2 = ax.contour(X, Y, Z2, colors='red') 31 | 32 | # Add labels and title 33 | ax.set_title('Contour Plots of Two Functions') 34 | ax.set_xlabel('X-axis') 35 | ax.set_ylabel('Y-axis') 36 | 37 | # Add a legend 38 | h1,_ = contour1.legend_elements() 39 | h2,_ = contour2.legend_elements() 40 | ax.legend([h1[0], h2[0]], ['sin(sqrt(x^2 + y^2))', 'cos(sqrt(x^2 + y^2))']) 41 | 42 | # Show the plot 43 | 44 | if out: 45 | fig.savefig(out) 46 | 47 | plt.show() 48 | -------------------------------------------------------------------------------- /examples/contour_double.txt: -------------------------------------------------------------------------------- 1 | Contour Plots of Two Functions 2 | 3 | +------------------------------------------------------------------------------------------------------------------------------------------------------+ 4 | |oo +++ +++ +++ +++ ++++ oooo+++++ ooooo ooooooooooooooooooooooooooo oooooo +++++ooooo ++++ +++ +++ +++ +++ oo| 5 | |++++ ++++ ++++ ++++ +++++oooooo++++oooooooo oooooooooooooooooooooooooooooooooooooooooooooooooooooo ooooooo+++++ooooo+++++ ++++ ++++ ++++ ++++| 6 | | ++++ ++++ +++++ +++++ooooo+++ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo++ooooo+++++ +++++ +++++ ++++ | 7 | |+++ +++ ++++ ++++ oooo+++ooooo oooooo ooooooo oooooooooooo oooooooooooooooooooooooo oooooooooooo ooooooo oooooo oooo+++ooooo++++ ++++ +++ +++| 8 | 4-- ++++ +++ ++++ooooo++ooooo ooooo oooooo oooooooo oooooooooooo++++++oooooooooooo++++++oooooooooooo ooooooo ooooooooooooo ooooo+++oooo++++ +++ +++ | 9 | |+ +++ ++++ooo+++oooo ooooo ooooo oooooo ooooooo+++ooooooooooooooo++++++++++++ooooooooooooooo+++oooooooo oooooo oooo oooo oooo+++ooo++++ +++ +| 10 | |+++ +++ ooo+++oooo oooo oooo ooooo oooooo+++ooooooo+++++++++++ ++++++++++++++++++ ++++++++++oooooooo+++oooooo ooooo ooooo oooo oooo+++ooo +++ +++| 11 | | ++++ooo++oooo ooooooooooooooo oooooo++ooooooo++++++++++++++++++++++++++++++++++++++++++++++++++ ++++++oooooo+++ooooo ooooo ooooooooo oooo++ooo++++ | 12 | |+++ oo+++ooo oo ooo ooo oooo+++oooo ++++ +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +++++oooo+++oooo ooo ooo oo ooo+++oo +++| 13 | | ooo++ oo oooo ooo oooo oooo+++oooo+++++ +++++ ++++++ +++++++++++ +++++++++++++++ +++++++++++ ++++++ ++++ ++++ooooo++oooo oooo ooo oooo oo ++ooo | 14 | |o ++ oo ooo oo ooo oo ++ooo +++ ++++ ++++ +++++ ++++++++ooooooooooooooooooooo+++++++ ++++++ +++++ ++++ +++ oo++ oo ooo oo ooo oo ++ o| 15 | |++ oo ooo ooo oo ooo ++ooo++++ ++++ +++++++++++ +++++ooooooooo++++oooooooooooo+++++ooooooooo++++ +++++ ++++ ++++ ++++ooo++ ooo oo ooo ooo oo ++| 16 | 2-- o oo oo oo oo ++ooo+++ ++ +++ +++ ++++ooooo+++oooooooooo ooooooooo oooooooooo++++oooo++++ +++ +++ ++ +++ooo++ oo oo oo oo o | 17 | |oo oo oo ooo oo ++oo +++ ++ +++ ++++ ++++oooo+++ooooo oooooooooooooooooooooooooooooo ooooo+++oooo++++ ++++ +++ ++ +++ oo++ oo ooo oo oo oo| 18 | | oo oo oo oo ++o + ++ ++ +++ ++ ooo++ ooo oooo oooooooooooooooooooooooooo oooo ooo +ooo ++ +++ ++ ++ + o++ oo oo oo oo | 19 | | oo oo oo oo ++oo ++ +++ +++ +++ ++ ooo++ ooo oooo ooooo oooooooo oooooo ooooooo ooooo oooo ooo ++ooo ++ +++ +++ +++ ++ oo++ oo oo oo oo | 20 | | o oo oo oo ++oo ++ + ++ ++ + o ++ oo o ooo oooo ooooooo++++++ooooooo oooo ooo o oo ++ o + ++ ++ + ++ oo++ oo oo oo o | 21 | Y | o o o o + o + + ++ ++ ++ oo++ oo oo oo oo oooo+++ooooooooooooooo++oooo oo oo oo oo ++oo ++ ++ ++ + + o + o o o o | 22 | - | o oo o o + o + + + + + o + o o o o o ++ooo++++++ +++++ oo++ o o o o o + o + + + + + o + o o oo o | 23 | a | o o o o + o + + + ++ + oo++ oo oo o o o + o ++ ++++++++ ++ o + o o o oo oo ++oo + ++ + + + o + o o o o | 24 | x 0-- o o o o + o + + + ++ + oo++ oo oo o o o + o ++ ++++++++ ++ o + o o o oo oo ++oo + ++ + + + o + o o o o | 25 | i | o oo o o + o + + + + + o + o o o o o ++ooo++++++ +++++ oo++ o o o o o + o + + + + + o + o o oo o | 26 | s | o o o o + o + + ++ ++ ++ oo++ oo oo oo oo oooo+++ooooooooooooooo++oooo oo oo oo oo ++oo ++ ++ ++ + + o + o o o o | 27 | | o oo oo oo ++oo ++ + ++ ++ + o ++ oo o ooo oooo ooooooo++++++ooooooo oooo ooo o oo ++ o + ++ ++ + ++ oo++ oo oo oo o | 28 | | oo oo oo oo ++oo ++ +++ +++ +++ ++ ooo++ ooo oooo ooooo oooooooo oooooo ooooooo ooooo oooo ooo ++ooo ++ +++ +++ +++ ++ oo++ oo oo oo oo | 29 | | oo oo oo oo ++o + ++ ++ +++ ++ ooo++ ooo oooo oooooooooooooooooooooooooo oooo ooo +ooo ++ +++ ++ ++ + o++ oo oo oo oo | 30 | |oo oo oo ooo oo ++oo +++ ++ +++ ++++ ++++oooo+++ooooo oooooooooooooooooooooooooooooo ooooo+++oooo++++ ++++ +++ ++ +++ oo++ oo ooo oo oo oo| 31 | −2-- o oo oo oo oo ++ooo+++ ++ +++ +++ ++++ooooo+++oooooooooo ooooooooo oooooooooo++++oooo++++ +++ +++ ++ +++ooo++ oo oo oo oo o | 32 | |++ oo ooo ooo oo ooo ++ooo++++ ++++ +++++++++++ +++++ooooooooo++++oooooooooooo+++++ooooooooo++++ +++++ ++++ ++++ ++++ooo++ ooo oo ooo ooo oo ++| 33 | |o ++ oo ooo oo ooo oo ++ooo +++ ++++ ++++ +++++ ++++++++ooooooooooooooooooooo+++++++ ++++++ +++++ ++++ +++ oo++ oo ooo oo ooo oo ++ o| 34 | | ooo++ oo oooo ooo oooo oooo+++oooo+++++ +++++ ++++++ +++++++++++ +++++++++++++++ +++++++++++ ++++++ ++++ ++++ooooo++oooo oooo ooo oooo oo ++ooo | 35 | |+++ oo+++ooo oo ooo ooo oooo+++oooo ++++ +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +++++oooo+++oooo ooo ooo oo ooo+++oo +++| 36 | | ++++ooo++oooo ooooooooooooooo oooooo++ooooooo++++++++++++++++++++++++++++++++++++++++++++++++++ ++++++oooooo+++ooooo ooooo ooooooooo oooo++ooo++++ | 37 | |+++ +++ ooo+++oooo oooo oooo ooooo oooooo+++ooooooo+++++++++++ ++++++++++++++++++ ++++++++++oooooooo+++oooooo ooooo ooooo oooo oooo+++ooo +++ +++| 38 | |+ +++ ++++ooo+++oooo ooooo ooooo oooooo ooooooo+++ooooooooooooooo++++++++++++ooooooooooooooo+++oooooooo oooooo oooo oooo oooo+++ooo++++ +++ +| 39 | −4-- ++++ +++ ++++ooooo++ooooo ooooo oooooo oooooooo oooooooooooo++++++oooooooooooo++++++oooooooooooo ooooooo ooooooooooooo ooooo+++oooo++++ +++ +++ | 40 | |+++ +++ ++++ ++++ oooo+++ooooo oooooo ooooooo oooooooooooo oooooooooooooooooooooooo oooooooooooo ooooooo oooooo oooo+++ooooo++++ ++++ +++ +++| 41 | | ++++ ++++ +++++ +++++ooooo+++ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo++ooooo+++++ +++++ +++++ ++++ | 42 | |++++ ++++ ++++ ++++ +++++oooooo++++oooooooo oooooooooooooooooooooooooooooooooooooooooooooooooooooo ooooooo+++++ooooo+++++ ++++ ++++ ++++ ++++| 43 | |oo +++ +++ +++ +++ ++++ oooo+++++ ooooo ooooooooooooooooooooooooooo oooooo +++++ooooo ++++ +++ +++ +++ +++ oo| 44 | +---------------|-----------------------------|----------------------------|-----------------------------|-----------------------------|---------------+ 45 | −4 −2 0 2 4 46 | X-axis 47 | 48 | +--------------------------+ 49 | | Legend | 50 | | | 51 | | +++ sin(sqrt(x^2 + y^2)) | 52 | | ooo cos(sqrt(x^2 + y^2)) | 53 | +--------------------------+ -------------------------------------------------------------------------------- /examples/contour_simple.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import matplotlib as mpl 3 | 4 | mpl.use("module://mpl_ascii") 5 | 6 | if __name__ == "__main__": 7 | parser = argparse.ArgumentParser(allow_abbrev=False) 8 | parser.add_argument("--out", type=str) 9 | 10 | args = parser.parse_args() 11 | out = args.out 12 | 13 | import matplotlib.pyplot as plt 14 | import numpy as np 15 | 16 | import matplotlib.cm as cm 17 | 18 | delta = 0.025 19 | x = np.arange(-3.0, 3.0, delta) 20 | y = np.arange(-2.0, 2.0, delta) 21 | X, Y = np.meshgrid(x, y) 22 | Z1 = np.exp(-X**2 - Y**2) 23 | Z2 = np.exp(-(X - 1)**2 - (Y - 1)**2) 24 | Z = (Z1 - Z2) * 2 25 | 26 | fig, ax = plt.subplots() 27 | CS = ax.contour(X, Y, Z) 28 | ax.clabel(CS, inline=True, fontsize=10) 29 | ax.set_title('Simplest default with labels') 30 | 31 | if out: 32 | fig.savefig(out) 33 | 34 | plt.show() 35 | -------------------------------------------------------------------------------- /examples/contour_simple.txt: -------------------------------------------------------------------------------- 1 | Simplest default with labels 2 | 3 | +------------------------------------------------------------------------------------------------------------------------------------------------------+ 4 | 2.0-- +++ aaa aaa | 5 | | +++ aaa aaaa | 6 | | +++ aa rrrrrrrrrrrrrrrrrr aaa | 7 | | +++ aa rrr r aaa | 8 | | +++ a rr −1.0 aa | 9 | 1.5-- +++ a r tttttttttttt aa | 10 | | +++ a rr tt ttt rr aa | 11 | | +++ aa r t tt r a | 12 | | +++ aa r t tt r a | 13 | | +++ aa rr tt t r a | 14 | 1.0-- +++ aa rrr tt r a | 15 | | ooooooooooooooo +++ aaa rr −1.5 tt rr a | 16 | | oooo 0.5 +++ aaa rrr ttttttt rr a | 17 | | oooo oo ++ aaa rrrr rr aa | 18 | 0.5-- oo dddddddddddddd oooo 0.0aaaa rrrr rrr aa | 19 | | oo ddd dddd oooo + aaa rrrrrrrrrrrrrr aa | 20 | | o ddd ddd ooo +++ aaa | 21 | | oo dd yyyyyyy 1.5 d ooo +++ −0.5 aaaa | 22 | | o d yyy 1.0 oo +++ aaaaaaaaaaaaaa | 23 | 0.0-- o dd y yy d oo +++ | 24 | | o dd y y dd oo +++ | 25 | | o d y y dd oo +++ | 26 | | oo dd yy yy d o +++ | 27 | | o dd yyy yyy d o +++ | 28 | −0.5-- o ddd yyyyyyyyyyyy dd o +++ | 29 | | oo ddd dd o +++ | 30 | | ooo ddddd dddd oo ++++ | 31 | | ooo dddddddddddddddddd oo +++ | 32 | | oooo oooo +++ | 33 | −1.0-- oooo oooo +++ | 34 | | ooooooo oooooo +++ | 35 | | oooooooooooooooo +++ | 36 | | +++ | 37 | | +++ | 38 | −1.5-- +++ | 39 | | +++ | 40 | | +++ | 41 | | +++ | 42 | | +++ | 43 | −2.0-- ++| 44 | +|------------------------|------------------------|------------------------|------------------------|------------------------|------------------------+ 45 | −3 −2 −1 0 1 2 46 | -------------------------------------------------------------------------------- /examples/csd_demo.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import matplotlib as mpl 3 | 4 | mpl.use("module://mpl_ascii") 5 | 6 | if __name__ == "__main__": 7 | parser = argparse.ArgumentParser(allow_abbrev=False) 8 | parser.add_argument("--out", type=str) 9 | 10 | args = parser.parse_args() 11 | out = args.out 12 | 13 | # data from https://allisonhorst.github.io/palmerpenguins/ 14 | 15 | import matplotlib.pyplot as plt 16 | import numpy as np 17 | 18 | fig, (ax1, ax2) = plt.subplots(2, 1, layout='constrained') 19 | 20 | dt = 0.01 21 | t = np.arange(0, 30, dt) 22 | 23 | # Fixing random state for reproducibility 24 | np.random.seed(19680801) 25 | 26 | 27 | nse1 = np.random.randn(len(t)) # white noise 1 28 | nse2 = np.random.randn(len(t)) # white noise 2 29 | r = np.exp(-t / 0.05) 30 | 31 | cnse1 = np.convolve(nse1, r, mode='same') * dt # colored noise 1 32 | cnse2 = np.convolve(nse2, r, mode='same') * dt # colored noise 2 33 | 34 | # two signals with a coherent part and a random part 35 | s1 = 0.01 * np.sin(2 * np.pi * 10 * t) + cnse1 36 | s2 = 0.01 * np.sin(2 * np.pi * 10 * t) + cnse2 37 | 38 | ax1.plot(t, s1, t, s2) 39 | ax1.set_xlim(0, 5) 40 | ax1.set_xlabel('Time (s)') 41 | ax1.set_ylabel('s1 and s2') 42 | ax1.grid(True) 43 | 44 | cxy, f = ax2.csd(s1, s2, 256, 1. / dt) 45 | ax2.set_ylabel('CSD (dB)') 46 | 47 | if out: 48 | fig.savefig(out) 49 | 50 | plt.show() 51 | -------------------------------------------------------------------------------- /examples/double_plot.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import matplotlib as mpl 3 | import mpl_ascii 4 | 5 | mpl_ascii.AXES_WIDTH=100 6 | mpl_ascii.AXES_HEIGHT=40 7 | 8 | 9 | mpl.use("module://mpl_ascii") 10 | 11 | if __name__ == "__main__": 12 | parser = argparse.ArgumentParser(allow_abbrev=False) 13 | parser.add_argument("--out", type=str) 14 | 15 | args = parser.parse_args() 16 | out = args.out 17 | 18 | import matplotlib.pyplot as plt 19 | import numpy as np 20 | 21 | # Data for plotting 22 | t = np.arange(0.0, 2.0, 0.01) 23 | s = 1 + np.sin(2 * np.pi * t) 24 | c = 1 + np.cos(2 * np.pi * t) 25 | 26 | fig, ax = plt.subplots() 27 | ax.plot(t, s) 28 | ax.plot(t, c) 29 | 30 | ax.set(xlabel='time (s)', ylabel='voltage (mV)', 31 | title='About as simple as it gets, folks') 32 | # ax.grid() 33 | 34 | plt.show() 35 | if out: 36 | fig.savefig(out) 37 | -------------------------------------------------------------------------------- /examples/double_plot.txt: -------------------------------------------------------------------------------- 1 | About as simple as it gets, folks 2 | 3 | +----------------------------------------------------------------------------------------------------+ 4 | | | 5 | | | 6 | 2.00-- ooo +++++ ooooo +++++ oo | 7 | | oo ++ ++ o oo ++ ++ oo | 8 | | o + + o o + + o | 9 | | o ++ + oo o + ++ oo | 10 | 1.75-- o + + o o+ + o | 11 | | o ++ o oo ++ oo | 12 | | + o + o +o + o | 13 | | + o + o + o + o | 14 | | + o + o + o + o | 15 | 1.50-- + o + o + o + o | 16 | | + o + o + o + o | 17 | | + o + o + o + o | 18 | v | + o + o + o + o | 19 | o 1.25-- + o + o + o + o | 20 | l | + o + o + o + o | 21 | t | + o + o + o + o | 22 | a | + o + o + o + o | 23 | g | + o + o + o + o | 24 | e 1.00-- + o + o + o + o | 25 | | o + o + o + o + | 26 | ( | o + o + o + o + | 27 | m | o + o + o + o + | 28 | V 0.75-- o + o + o + o + | 29 | ) | o + o + o + o + | 30 | | o + o + o + o + | 31 | | o + o + o + o + | 32 | 0.50-- o + o + o + o + | 33 | | o + o + o + o + | 34 | | o + o + o + o + | 35 | | o +o + o + o + | 36 | | oo o + oo o ++ | 37 | 0.25-- o o+ + o o + + | 38 | | o oo + ++ o oo ++ + | 39 | | o o + + o o + + | 40 | | oo oo ++ ++ oo oo ++ ++ | 41 | 0.00-- ooooo ++++ oooo ++++ | 42 | | | 43 | | | 44 | +----|-----------|----------|----------|-----------|----------|----------|-----------|----------|----+ 45 | 0.00 0.25 0.50 0.75 1.00 1.25 1.50 1.75 2.00 46 | time (s) -------------------------------------------------------------------------------- /examples/errorbars.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import matplotlib as mpl 3 | 4 | mpl.use("module://mpl_ascii") 5 | 6 | if __name__ == "__main__": 7 | parser = argparse.ArgumentParser(allow_abbrev=False) 8 | parser.add_argument("--out", type=str) 9 | 10 | args = parser.parse_args() 11 | out = args.out 12 | 13 | import matplotlib.pyplot as plt 14 | import numpy as np 15 | 16 | # example data 17 | x = np.arange(0.1, 4, 0.5) 18 | y = np.exp(-x) 19 | 20 | fig, ax = plt.subplots() 21 | ax.errorbar(x, y, xerr=0.2, yerr=0.4) 22 | if out: 23 | fig.savefig(out) 24 | 25 | plt.show() 26 | -------------------------------------------------------------------------------- /examples/errorbars.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | +------------------------------------------------------------------------------------------------------------------------------------------------------+ 4 | | | 5 | | | 6 | | | | 7 | 1.25-- | | 8 | | | | 9 | | | | 10 | | | | 11 | | | | 12 | 1.00-- | | 13 | | | | | 14 | | -------|------- | | 15 | | | ++ | | 16 | | | ++ | | 17 | 0.75-- | ++ | | 18 | | | ++ | | | 19 | | | ++ | | | 20 | | | ++ | | | 21 | | | ++ | | | | 22 | | | -------|------- | | | | 23 | 0.50-- | | ++++ | | | | | 24 | | | ++++ | | | | | | | 25 | | | ++++ | | | | | | | 26 | | | ------|------- | | | | | | 27 | | | | ++++++ | | | | | | 28 | 0.25-- | | ++++++ | | | | | | 29 | | | | -------|------- | | | | | 30 | | | | | ++++++++ | | | | | 31 | | | | -------|-------++ | | | | 32 | | | | | +-------|------+++-------|-------+ | | 33 | 0.00-- | | | | | +-------|------- | 34 | | | | | | | | | 35 | | | | | | | | | 36 | | | | | | | | 37 | | | | | | | | 38 | | | | | | | | 39 | −0.25-- | | | | | 40 | | | | | | 41 | | | | | 42 | | | 43 | | | 44 | +----------|-----------------|----------------|----------------|-----------------|----------------|----------------|-----------------|----------------|+ 45 | 0.0 0.5 1.0 1.5 2.0 2.5 3.0 3.5 4.0 46 | -------------------------------------------------------------------------------- /examples/errorbars_2.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import matplotlib as mpl 3 | 4 | mpl.use("module://mpl_ascii") 5 | 6 | if __name__ == "__main__": 7 | parser = argparse.ArgumentParser(allow_abbrev=False) 8 | parser.add_argument("--out", type=str) 9 | 10 | args = parser.parse_args() 11 | out = args.out 12 | 13 | import matplotlib.pyplot as plt 14 | import numpy as np 15 | 16 | # example data 17 | x = np.arange(0.1, 4, 0.5) 18 | y = np.exp(-x) 19 | 20 | # example error bar values that vary with x-position 21 | error = 0.1 + 0.2 * x 22 | 23 | fig, (ax0, ax1) = plt.subplots(nrows=2, sharex=True) 24 | ax0.errorbar(x, y, yerr=error, fmt='-o') 25 | ax0.set_title('variable, symmetric error') 26 | 27 | # error bar values w/ different -/+ errors that 28 | # also vary with the x-position 29 | lower_error = 0.4 * error 30 | upper_error = error 31 | asymmetric_error = [lower_error, upper_error] 32 | 33 | ax1.errorbar(x, y, xerr=asymmetric_error, fmt='o') 34 | ax1.set_title('variable, asymmetric error') 35 | ax1.set_yscale('log') 36 | 37 | if out: 38 | fig.savefig(out) 39 | 40 | plt.show() 41 | -------------------------------------------------------------------------------- /examples/errorbars_3.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import matplotlib as mpl 3 | 4 | mpl.use("module://mpl_ascii") 5 | 6 | if __name__ == "__main__": 7 | parser = argparse.ArgumentParser(allow_abbrev=False) 8 | parser.add_argument("--out", type=str) 9 | 10 | args = parser.parse_args() 11 | out = args.out 12 | 13 | import matplotlib.pyplot as plt 14 | import numpy as np 15 | 16 | import matplotlib.pyplot as plt 17 | import numpy as np 18 | 19 | fig = plt.figure() 20 | x = np.arange(10) 21 | y = 2.5 * np.sin(x / 20 * np.pi) 22 | yerr = np.linspace(0.05, 0.2, 10) 23 | 24 | plt.errorbar(x, y + 3, yerr=yerr, label='both limits (default)') 25 | 26 | plt.errorbar(x, y + 2, yerr=yerr, uplims=True, label='uplims=True') 27 | 28 | plt.errorbar(x, y + 1, yerr=yerr, uplims=True, lolims=True, 29 | label='uplims=True, lolims=True') 30 | 31 | upperlimits = [True, False] * 5 32 | lowerlimits = [False, True] * 5 33 | plt.errorbar(x, y, yerr=yerr, uplims=upperlimits, lolims=lowerlimits, 34 | label='subsets of uplims and lolims') 35 | 36 | plt.legend(loc='lower right') 37 | 38 | if out: 39 | fig.savefig(out) 40 | 41 | plt.show() 42 | -------------------------------------------------------------------------------- /examples/errorbars_4.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import matplotlib as mpl 3 | 4 | mpl.use("module://mpl_ascii") 5 | 6 | if __name__ == "__main__": 7 | parser = argparse.ArgumentParser(allow_abbrev=False) 8 | parser.add_argument("--out", type=str) 9 | 10 | args = parser.parse_args() 11 | out = args.out 12 | 13 | import matplotlib.pyplot as plt 14 | import numpy as np 15 | 16 | fig = plt.figure() 17 | x = np.arange(10) / 10 18 | y = (x + 0.1)**2 19 | upperlimits = [True, False] * 5 20 | lowerlimits = [False, True] * 5 21 | 22 | plt.errorbar(x, y, xerr=0.1, xlolims=True, label='xlolims=True') 23 | y = (x + 0.1)**3 24 | 25 | plt.errorbar(x + 0.6, y, xerr=0.1, xuplims=upperlimits, xlolims=lowerlimits, 26 | label='subsets of xuplims and xlolims') 27 | 28 | y = (x + 0.1)**4 29 | plt.errorbar(x + 1.2, y, xerr=0.1, xuplims=True, label='xuplims=True') 30 | 31 | plt.legend() 32 | 33 | if out: 34 | fig.savefig(out) 35 | 36 | plt.show() 37 | -------------------------------------------------------------------------------- /examples/hist_best_fit_line.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import matplotlib as mpl 3 | 4 | mpl.use("module://mpl_ascii") 5 | 6 | if __name__ == "__main__": 7 | parser = argparse.ArgumentParser(allow_abbrev=False) 8 | parser.add_argument("--out", type=str) 9 | 10 | args = parser.parse_args() 11 | out = args.out 12 | 13 | import matplotlib.pyplot as plt 14 | import numpy as np 15 | 16 | rng = np.random.default_rng(19680801) 17 | 18 | # example data 19 | mu = 106 # mean of distribution 20 | sigma = 17 # standard deviation of distribution 21 | x = rng.normal(loc=mu, scale=sigma, size=420) 22 | 23 | num_bins = 42 24 | 25 | fig, ax = plt.subplots() 26 | 27 | # the histogram of the data 28 | n, bins, patches = ax.hist(x, num_bins, density=True) 29 | 30 | # add a 'best fit' line 31 | y = ((1 / (np.sqrt(2 * np.pi) * sigma)) * 32 | np.exp(-0.5 * (1 / sigma * (bins - mu))**2)) 33 | ax.plot(bins, y, '--') 34 | ax.set_xlabel('Value') 35 | ax.set_ylabel('Probability density') 36 | ax.set_title('Histogram of normal distribution sample: ' 37 | fr'$\mu={mu:.0f}$, $\sigma={sigma:.0f}$') 38 | 39 | # Tweak spacing to prevent clipping of ylabel 40 | fig.tight_layout() 41 | 42 | if out: 43 | fig.savefig(out) 44 | 45 | plt.show() 46 | -------------------------------------------------------------------------------- /examples/hist_best_fit_line.txt: -------------------------------------------------------------------------------- 1 | Histogram of normal distribution sample: $\mu=106$, $\sigma=17$ 2 | 3 | +------------------------------------------------------------------------------------------------------------------------------------------------------+ 4 | | | 5 | | | 6 | 0.030-- | 7 | | ### | 8 | | ### | 9 | | ### | 10 | | ### ### | 11 | | ### ### ### | 12 | 0.025-- ### ### ### | 13 | | ### ### ### ### | 14 | P | ### ###+++++++++ ### | 15 | r | ### ##++++ ### +++# | 16 | o | ### +++#### ### ##+++ | 17 | b | ### ##+++ ###### ### ### ++ | 18 | a 0.020-- ###### ++# ###### ############ ++ | 19 | b | #####++### ###### ############ ++ | 20 | i | ###++# ### ###### ############ #++ | 21 | l | #++### ############ ############ ###+ | 22 | i | +##### ############ ############ ### ++ | 23 | t | ### ### ++###### ############ ############ ### ++ | 24 | y 0.015-- ### ###+ ###### ############ ############ ########++ | 25 | | ### #++######### ############ ############ ######### + | 26 | d | ### +########### ############ ############ ######### + | 27 | e | ### +############ ############ ############ ######### ##+### | 28 | n | ### ++ ############ ############ ############ ######### ###+## | 29 | s | ###+ ############ ############ ############ ######### ####++ | 30 | i | #++ ############ ############ ############ ######### ######+ ### | 31 | t 0.010-- +## ############ ############ ############ ######### ###### ++ ### | 32 | y | ++### ############ ############ ############ ######### ###### ++ ### | 33 | | ++######## ############ ############ ############ ############ ######### +++ ### | 34 | | ++ ######### ############ ############ ############ ############ ######### ++### | 35 | | ### #++############ ############ ############ ############### ############ ############ ++# | 36 | | ### ++############## ############ ############ ############### ############ ############ ##++ | 37 | 0.005-- ##++++ ############### ############ ############ ############### ############ ############ ### ++ | 38 | | ### ### +++#### ############### ############ ############ ############### ############ ############### ### ++ | 39 | | ### ##+++ ###### ############### ############ ############ ############### ############ ############### ### ++++ | 40 | | ###### +++########## ############### ############ ############ ############### ############ ############### ### ###++++++ | 41 | | ##+++++++ ############ ############### ############ ############ ############### ############ ############### ### ### +++ | 42 | | +++++#### ############ ############### ############ ############ ############### ############ ############### ### ### ++ | 43 | 0.000-- ############ ############ ############### ############ ############ ############### ############ ############### ### ###### ######### | 44 | +-----|------------------------------|-------------------------------|------------------------------|------------------------------|-------------------+ 45 | 60 80 100 120 140 46 | Value -------------------------------------------------------------------------------- /examples/hist_simple.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import matplotlib as mpl 3 | 4 | mpl.use("module://mpl_ascii") 5 | 6 | if __name__ == "__main__": 7 | parser = argparse.ArgumentParser(allow_abbrev=False) 8 | parser.add_argument("--out", type=str) 9 | 10 | args = parser.parse_args() 11 | out = args.out 12 | 13 | # data from https://allisonhorst.github.io/palmerpenguins/ 14 | 15 | import matplotlib.pyplot as plt 16 | import numpy as np 17 | 18 | from matplotlib import colors 19 | from matplotlib.ticker import PercentFormatter 20 | 21 | # Create a random number generator with a fixed seed for reproducibility 22 | rng = np.random.default_rng(19680801) 23 | 24 | N_points = 100000 25 | n_bins = 20 26 | 27 | # Generate two normal distributions 28 | dist1 = rng.standard_normal(N_points) 29 | dist2 = 0.4 * rng.standard_normal(N_points) + 5 30 | 31 | fig, axs = plt.subplots(1, 2, sharey=True, tight_layout=True) 32 | 33 | # We can set the number of bins with the *bins* keyword argument. 34 | axs[0].hist(dist1, bins=n_bins) 35 | axs[1].hist(dist2, bins=n_bins) 36 | 37 | if out: 38 | fig.savefig(out) 39 | 40 | plt.show() 41 | -------------------------------------------------------------------------------- /examples/hist_simple_colors.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import matplotlib as mpl 3 | 4 | mpl.use("module://mpl_ascii") 5 | 6 | if __name__ == "__main__": 7 | parser = argparse.ArgumentParser(allow_abbrev=False) 8 | parser.add_argument("--out", type=str) 9 | 10 | args = parser.parse_args() 11 | out = args.out 12 | 13 | # data from https://allisonhorst.github.io/palmerpenguins/ 14 | 15 | import matplotlib.pyplot as plt 16 | import numpy as np 17 | 18 | from matplotlib import colors 19 | from matplotlib.ticker import PercentFormatter 20 | 21 | # Create a random number generator with a fixed seed for reproducibility 22 | rng = np.random.default_rng(19680801) 23 | 24 | N_points = 100000 25 | n_bins = 20 26 | 27 | # Generate two normal distributions 28 | dist1 = rng.standard_normal(N_points) 29 | dist2 = 0.4 * rng.standard_normal(N_points) + 5 30 | 31 | fig, axs = plt.subplots(1, 2, tight_layout=True) 32 | 33 | # N is the count in each bin, bins is the lower-limit of the bin 34 | N, bins, patches = axs[0].hist(dist1, bins=n_bins) 35 | 36 | # We'll color code by height, but you could use any scalar 37 | fracs = N / N.max() 38 | 39 | # we need to normalize the data to 0..1 for the full range of the colormap 40 | norm = colors.Normalize(fracs.min(), fracs.max()) 41 | 42 | # Now, we'll loop through our objects and set the color of each accordingly 43 | for thisfrac, thispatch in zip(fracs, patches): 44 | color = plt.cm.viridis(norm(thisfrac)) 45 | thispatch.set_facecolor(color) 46 | 47 | # We can also normalize our inputs by the total number of counts 48 | axs[1].hist(dist1, bins=n_bins, density=True) 49 | 50 | # Now we format the y-axis to display percentage 51 | axs[1].yaxis.set_major_formatter(PercentFormatter(xmax=1)) 52 | if out: 53 | fig.savefig(out) 54 | 55 | plt.show() 56 | -------------------------------------------------------------------------------- /examples/line_markers.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import matplotlib as mpl 3 | 4 | mpl.use("module://mpl_ascii") 5 | 6 | if __name__ == "__main__": 7 | parser = argparse.ArgumentParser(allow_abbrev=False) 8 | parser.add_argument("--out", type=str) 9 | 10 | args = parser.parse_args() 11 | out = args.out 12 | 13 | import matplotlib.pyplot as plt 14 | import numpy as np 15 | 16 | # Sample data 17 | x = np.linspace(0, 10, 50) 18 | y = np.sin(x) 19 | 20 | # Create a figure and a set of subplots 21 | fig, axs = plt.subplots(3, 2, figsize=(12, 18)) 22 | 23 | # Basic Line Plot using fmt 24 | axs[0, 0].plot(x, y, 'b1') # 'b-' is blue solid line 25 | axs[0, 0].set_title('Basic Line Plot') 26 | 27 | # Plot with Markers using fmt 28 | axs[0, 1].plot(x, y, 'r^') # 'ro' is red circles 29 | axs[0, 1].set_title('Plot with Markers') 30 | 31 | # Dashed Line Plot using fmt 32 | axs[1, 0].plot(x, y, 'g--s') # 'g--' is green dashed line 33 | axs[1, 0].set_title('Dashed Line Plot') 34 | 35 | # Customized Plot using fmt 36 | axs[1, 1].plot(x, y, 'c-.^') # 'c-.^' is cyan dash-dot line with triangle up markers 37 | axs[1, 1].set_title('Customized Plot') 38 | 39 | # Multiple Lines in One Plot using fmt 40 | axs[2, 0].plot(x, y, 'r-<', label='sin(x)') # 'k-' is black solid line 41 | axs[2, 0].plot(x, np.cos(x), 'b:>', label='cos(x)') # 'm:s' is magenta dotted line with square markers 42 | axs[2, 0].set_title('Multiple Lines in One Plot') 43 | axs[2, 0].legend() 44 | 45 | # Remove empty subplot for cleaner appearance 46 | fig.delaxes(axs[2, 1]) 47 | 48 | # Adjust layout 49 | plt.tight_layout() 50 | 51 | if out: 52 | fig.savefig(out) 53 | 54 | plt.show() 55 | -------------------------------------------------------------------------------- /examples/lines_multi_color.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import matplotlib as mpl 3 | 4 | 5 | mpl.use("module://mpl_ascii") 6 | 7 | if __name__ == "__main__": 8 | parser = argparse.ArgumentParser(allow_abbrev=False) 9 | parser.add_argument("--out", type=str) 10 | 11 | args = parser.parse_args() 12 | out = args.out 13 | import matplotlib.pyplot as plt 14 | import numpy as np 15 | 16 | # Set up the figure and axes 17 | fig, ax = plt.subplots(figsize=(10, 6)) 18 | 19 | # Create an array of x values 20 | x = np.linspace(-2 * np.pi, 2 * np.pi, 400) 21 | 22 | # Generate different continuous functions 23 | y1 = -x**2 - 4*x - 1 24 | y2 = np.tan(x) 25 | y3 = 8*np.exp(-x**2) # Gaussian 26 | y4 = x**3 - 6*x**2 + 9*x # Cubic polynomial 27 | 28 | 29 | 30 | # Plot the functions 31 | ax.plot(x, y1, label='-x**2 - 4x - 1') 32 | ax.plot(x, y2, label='tan(x)', clip_on=True) 33 | ax.plot(x, y3, label='8exp(-x^2)') 34 | ax.plot(x, y4, label='x^3 - 6x^2 + 9x') 35 | 36 | # Set labels, title, and legend 37 | ax.set_title('Plot of Continuous Functions') 38 | ax.set_xlabel('x') 39 | ax.set_ylabel('f(x)') 40 | ax.set_ylim(-10, 10) # Limit y-axis to avoid extreme values from tan(x) 41 | ax.legend() 42 | 43 | # Add grid 44 | ax.grid(True) 45 | 46 | # Show the plot 47 | 48 | if out: 49 | fig.savefig(out) 50 | 51 | plt.show() -------------------------------------------------------------------------------- /examples/long_tick_labels.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import matplotlib as mpl 3 | 4 | import mpl_ascii 5 | 6 | mpl.use("module://mpl_ascii") 7 | mpl_ascii.AXES_WIDTH=60 8 | mpl_ascii.AXES_HEIGHT=40 9 | 10 | 11 | if __name__ == "__main__": 12 | parser = argparse.ArgumentParser(allow_abbrev=False) 13 | parser.add_argument("--out", type=str) 14 | 15 | args = parser.parse_args() 16 | out = args.out 17 | 18 | import matplotlib.pyplot as plt 19 | 20 | # Example data 21 | x = range(10) 22 | y = [i**2 for i in x] 23 | 24 | # Long tick labels 25 | x_tick_labels = [ 26 | 'This is a very long tick label 1', 27 | '0123456789', 28 | 'This is a very long tick label 3', 29 | 'This is a very long tick label 4', 30 | 'This is a very long tick label 5', 31 | 'This is a very long tick label 6abcde', 32 | 'This is a very long tick label 7', 33 | 'This is a very long tick label 8', 34 | 'This is a very long tick label 9', 35 | 'This is a very long tick label 10' 36 | ] 37 | 38 | 39 | y_tick_labels = [ 40 | 'Another very long tick label 1', 41 | 'Another very long tick label 2', 42 | 'Another very long tick label 3', 43 | 'Another very long tick label 4', 44 | 'Another very long tick label 5', 45 | ] 46 | 47 | # Create the plot 48 | 49 | # Create the plot 50 | fig, ax = plt.subplots() 51 | ax.plot(x, y, marker='o') 52 | 53 | # Set the tick labels 54 | ax.set_xticks(x) 55 | ax.set_xticklabels(x_tick_labels, rotation=45, ha='right') 56 | 57 | ax.set_yticks([0,20,40,60,80]) 58 | ax.set_yticklabels(y_tick_labels) 59 | 60 | # Set labels and title 61 | ax.set_xlabel('X Axis with Long Labels') 62 | ax.set_ylabel('Y Axis with Long Labels') 63 | ax.set_title('Plot with Long Tick Labels') 64 | 65 | fig.tight_layout() 66 | 67 | if out: 68 | fig.savefig(out) 69 | 70 | plt.show() 71 | -------------------------------------------------------------------------------- /examples/long_tick_labels.txt: -------------------------------------------------------------------------------- 1 | Plot with Long Tick Labels 2 | 3 | +------------------------------------------------------------+ 4 | | | 5 | | | 6 | …abel 5-- O | 7 | | + | 8 | | + | 9 | | + | 10 | | + | 11 | | + | 12 | Y | + | 13 | | O | 14 | A | + | 15 | x …abel 4-- + | 16 | i | + | 17 | s | + | 18 | | + | 19 | w | + | 20 | i | O | 21 | t | + | 22 | h | + | 23 | | ++ | 24 | L …abel 3-- + | 25 | o | O | 26 | n | + | 27 | g | + | 28 | | ++ | 29 | L | + | 30 | a | O | 31 | b | + | 32 | e …abel 2-- ++ | 33 | l | + | 34 | s | O | 35 | | ++ | 36 | | ++ | 37 | | +O+ | 38 | | +++ | 39 | | +O+ | 40 | | +++ | 41 | …abel 1-- O+++++O+ | 42 | | | 43 | | | 44 | +---|-----|-----|-----|-----|----|-----|-----|-----|-----|---+ 45 | … 0 … … … … … … … … 46 | a 1 a a a a a a a a 47 | b 2 b b b b b b b b 48 | e 3 e e e e e e e e 49 | l 4 l l l l l l l l 50 | 5 51 | 1 6 3 4 5 6 7 8 9 1 52 | 7 a 0 53 | 8 b 54 | 9 … 55 | X Axis with Long Labels -------------------------------------------------------------------------------- /examples/long_tick_labels_2.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import matplotlib as mpl 3 | 4 | import mpl_ascii 5 | 6 | 7 | mpl.use("module://mpl_ascii") 8 | mpl_ascii.AXES_WIDTH=60 9 | mpl_ascii.AXES_HEIGHT=40 10 | 11 | 12 | if __name__ == "__main__": 13 | parser = argparse.ArgumentParser(allow_abbrev=False) 14 | parser.add_argument("--out", type=str) 15 | 16 | args = parser.parse_args() 17 | out = args.out 18 | 19 | import matplotlib.pyplot as plt 20 | import numpy as np 21 | 22 | # data from https://allisonhorst.github.io/palmerpenguins/ 23 | 24 | species = ( 25 | "Adelie\n $\\mu=$3700.66g", 26 | "Chinstrap\n $\\mu=$3733.09g", 27 | "Gentoo\n $\\mu=5076.02g$", 28 | ) 29 | weight_counts = { 30 | "Below": np.array([70, 31, 58]), 31 | "Above": np.array([82, 37, 66]), 32 | } 33 | width = 0.5 34 | 35 | fig, ax = plt.subplots() 36 | bottom = np.zeros(3) 37 | 38 | for boolean, weight_count in weight_counts.items(): 39 | p = ax.bar(species, weight_count, width, label=boolean, bottom=bottom) 40 | bottom += weight_count 41 | 42 | ax.set_title("Number of penguins with above average body mass") 43 | ax.legend(loc="upper right") 44 | 45 | if out: 46 | fig.savefig(out) 47 | plt.show() -------------------------------------------------------------------------------- /examples/long_tick_labels_2.txt: -------------------------------------------------------------------------------- 1 | Number of penguins with above average body mass 2 | 3 | +------------------------------------------------------------+ 4 | 160-- | 5 | | | 6 | | | 7 | | %%%%%%%%%%% | 8 | | %%%%%%%%%%% | 9 | 140-- %%%%%%%%%%% | 10 | | %%%%%%%%%%% | 11 | | %%%%%%%%%%% | 12 | | %%%%%%%%%%% | 13 | | %%%%%%%%%%% | 14 | 120-- %%%%%%%%%%% %%%%%%%%%%% | 15 | | %%%%%%%%%%% %%%%%%%%%%% | 16 | | %%%%%%%%%%% %%%%%%%%%%% | 17 | | %%%%%%%%%%% %%%%%%%%%%% | 18 | | %%%%%%%%%%% %%%%%%%%%%% | 19 | 100-- %%%%%%%%%%% %%%%%%%%%%% | 20 | | %%%%%%%%%%% %%%%%%%%%%% | 21 | | %%%%%%%%%%% %%%%%%%%%%% | 22 | | %%%%%%%%%%% %%%%%%%%%%% | 23 | 80-- %%%%%%%%%%% %%%%%%%%%%% | 24 | | %%%%%%%%%%% %%%%%%%%%%% | 25 | | %%%%%%%%%%% %%%%%%%%%%% | 26 | | %%%%%%%%%%% %%%%%%%%%%% | 27 | | ########### %%%%%%%%%%% %%%%%%%%%%% | 28 | 60-- ########### %%%%%%%%%%% %%%%%%%%%%% | 29 | | ########### %%%%%%%%%%% %%%%%%%%%%% | 30 | | ########### %%%%%%%%%%% ########### | 31 | | ########### %%%%%%%%%%% ########### | 32 | | ########### %%%%%%%%%%% ########### | 33 | 40-- ########### %%%%%%%%%%% ########### | 34 | | ########### %%%%%%%%%%% ########### | 35 | | ########### %%%%%%%%%%% ########### | 36 | | ########### ########### ########### | 37 | | ########### ########### ########### | 38 | 20-- ########### ########### ########### | 39 | | ########### ########### ########### | 40 | | ########### ########### ########### | 41 | | ########### ########### ########### | 42 | | ########### ########### ########### | 43 | 0-- ########### ########### ########### | 44 | +--------|---------------------|--------------------|--------+ 45 | Adelie $\mu=$3700.66g Chinstrap… Gentoo $\… 46 | 47 | 48 | +-----------+ 49 | | Legend | 50 | | | 51 | | ### Below | 52 | | %%% Above | 53 | +-----------+ -------------------------------------------------------------------------------- /examples/scatter_multi_color.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import matplotlib as mpl 3 | import mpl_ascii 4 | 5 | mpl_ascii.AXES_WIDTH=100 6 | mpl_ascii.AXES_HEIGHT=40 7 | 8 | mpl.use("module://mpl_ascii") 9 | 10 | if __name__ == "__main__": 11 | parser = argparse.ArgumentParser(allow_abbrev=False) 12 | parser.add_argument("--out", type=str) 13 | 14 | args = parser.parse_args() 15 | out = args.out 16 | import matplotlib.pyplot as plt 17 | import numpy as np 18 | 19 | # Generate some random data 20 | np.random.seed(0) 21 | x = np.random.rand(40) 22 | y = np.random.rand(40) 23 | colors = np.random.choice(['red', 'green', 'blue', 'yellow'], size=40) 24 | color_labels = ['Red', 'Green', 'Blue', 'Yellow'] # Labels corresponding to colors 25 | 26 | # Create a scatter plot 27 | fig, ax = plt.subplots() 28 | for color, label in zip(['red', 'green', 'blue', 'yellow'], color_labels): 29 | # Plot each color as a separate scatter plot to enable legend tracking 30 | idx = np.where(colors == color) 31 | ax.scatter(x[idx], y[idx], color=color, label=label) 32 | 33 | # Set title and labels 34 | ax.set_title('Scatter Plot with 4 Different Colors') 35 | ax.set_xlabel('X axis') 36 | ax.set_ylabel('Y axis') 37 | 38 | # Add a legend 39 | ax.legend(title='Point Colors') 40 | 41 | 42 | if out: 43 | fig.savefig(out) 44 | 45 | plt.show() -------------------------------------------------------------------------------- /examples/scatter_multi_color.txt: -------------------------------------------------------------------------------- 1 | Scatter Plot with 4 Different Colors 2 | 3 | +----------------------------------------------------------------------------------------------------+ 4 | | | 5 | 1.0-- | 6 | | s z s | 7 | | | 8 | | | 9 | | | 10 | | | 11 | | x | 12 | | x | 13 | 0.8-- | 14 | | | 15 | | x | 16 | | | 17 | | s | 18 | | v s s s | 19 | | | 20 | 0.6-- s | 21 | Y | v | 22 | | | 23 | a | | 24 | x | | 25 | i | zz | 26 | s | z z | 27 | | | 28 | 0.4-- | 29 | | x z x | 30 | | | 31 | | s | 32 | | x z | 33 | | x | 34 | | z | 35 | 0.2-- v s z | 36 | | | 37 | | z s | 38 | | v x z z | 39 | | z x zz | 40 | | z | 41 | | x | 42 | | | 43 | 0.0-- | 44 | +---|-----------------|------------------|------------------|------------------|------------------|--+ 45 | 0.0 0.2 0.4 0.6 0.8 1.0 46 | X axis 47 | 48 | +--------------+ 49 | | Point Colors | 50 | | | 51 | | xxx Red | 52 | | vvv Green | 53 | | zzz Blue | 54 | | sss Yellow | 55 | +--------------+ -------------------------------------------------------------------------------- /examples/simple_plot.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import matplotlib as mpl 3 | 4 | 5 | mpl.use("module://mpl_ascii") 6 | 7 | if __name__ == "__main__": 8 | parser = argparse.ArgumentParser(allow_abbrev=False) 9 | parser.add_argument("--out", type=str) 10 | 11 | args = parser.parse_args() 12 | out = args.out 13 | 14 | import matplotlib.pyplot as plt 15 | import numpy as np 16 | 17 | # Data for plotting 18 | t = np.arange(0.0, 2.0, 0.01) 19 | s = 1 + np.sin(2 * np.pi * t) 20 | 21 | fig, ax = plt.subplots() 22 | ax.plot(t, s) 23 | 24 | ax.set(xlabel='time (s)', ylabel='voltage (mV)', 25 | title='About as simple as it gets, folks') 26 | # ax.grid() 27 | 28 | if out: 29 | fig.savefig(out) 30 | 31 | plt.show() -------------------------------------------------------------------------------- /examples/simple_plot.txt: -------------------------------------------------------------------------------- 1 | About as simple as it gets, folks 2 | 3 | +------------------------------------------------------------------------------------------------------------------------------------------------------+ 4 | | | 5 | | | 6 | 2.00-- +++++++ +++++++ | 7 | | ++ +++ ++ +++ | 8 | | + + + + | 9 | | ++ ++ ++ ++ | 10 | 1.75-- + + + + | 11 | | ++ ++ ++ ++ | 12 | | + + + + | 13 | | + + + + | 14 | | + + + + | 15 | 1.50-- + + + + | 16 | | + + + + | 17 | | + + + + | 18 | v | + + + + | 19 | o 1.25-- + + + + | 20 | l | + + + + | 21 | t | + + + + | 22 | a | + + + + | 23 | g | + + + + | 24 | e 1.00-- + + + + | 25 | | + + + + | 26 | ( | + + + + | 27 | m | + + + + | 28 | V 0.75-- + + + + | 29 | ) | + + + + | 30 | | + + + + | 31 | | + + + + | 32 | 0.50-- + + + + | 33 | | + + + + | 34 | | + + + + | 35 | | + + + + | 36 | | ++ ++ ++ ++ | 37 | 0.25-- + + + + | 38 | | ++ ++ ++ ++ | 39 | | + + + + | 40 | | ++ +++ ++ +++ | 41 | 0.00-- +++++++ +++++++ | 42 | | | 43 | | | 44 | +-------|----------------|----------------|----------------|----------------|----------------|----------------|----------------|----------------|------+ 45 | 0.00 0.25 0.50 0.75 1.00 1.25 1.50 1.75 2.00 46 | time (s) -------------------------------------------------------------------------------- /examples/violin_plot.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import matplotlib as mpl 3 | from matplotlib import pyplot as plt 4 | import numpy as np 5 | 6 | 7 | mpl.use("module://mpl_ascii") 8 | 9 | if __name__ == "__main__": 10 | parser = argparse.ArgumentParser(allow_abbrev=False) 11 | parser.add_argument("--out", type=str) 12 | 13 | args = parser.parse_args() 14 | out = args.out 15 | 16 | fig, ax = plt.subplots() 17 | 18 | # Fixing random state for reproducibility 19 | np.random.seed(19680801) 20 | 21 | 22 | # generate some random test data 23 | all_data = [np.random.normal(0, std, 100) for std in range(6, 10)] 24 | 25 | # plot violin plot 26 | ax.violinplot(all_data, 27 | showmeans=False, 28 | showmedians=True) 29 | ax.set_title('Violin plot') 30 | 31 | # adding horizontal grid lines 32 | ax.yaxis.grid(True) 33 | ax.set_xticks([y + 1 for y in range(len(all_data))], 34 | labels=['x1', 'x2', 'x3', 'x4']) 35 | ax.set_xlabel('Four separate samples') 36 | ax.set_ylabel('Observed values') 37 | 38 | 39 | if out: 40 | fig.savefig(out) 41 | 42 | plt.show() -------------------------------------------------------------------------------- /examples/violin_plot.txt: -------------------------------------------------------------------------------- 1 | Violin plot 2 | 3 | +------------------------------------------------------------------------------------------------------------------------------------------------------+ 4 | | | 5 | | | 6 | | ++++++++++ | 7 | | +++ | 8 | 20-- + ++ | 9 | | + ++ | 10 | | +++++++++++ ++ + + | 11 | | ++ + + + + ++ | 12 | | + + + + + + | 13 | | ++++++++++ +++++++++++ ++ + + + + + | 14 | | +++ ++ ++ + ++ + + + + + + | 15 | 10-- ++ + ++ + + ++ ++ + + + + + | 16 | O | + + + ++ + ++ + + + ++ + + | 17 | b | + + + ++ + ++ + + ++ + + + | 18 | s | ++ + + ++ + ++ + + ++ ++ + ++ | 19 | e | ++ + + ++ + + + + ++ + + + | 20 | r | ++ + + ++ + + ++ + ++ + + ++ | 21 | v | + + + + + ++ ++ + + + + + | 22 | e | + + + ++ + + + +++++++++++ ++ + + + | 23 | d 0-- + ++++++++++ + ++ +++++++++++ + + + ++ + ++++++++++ + | 24 | | + + + + + + ++ + + + + + | 25 | v | ++ + ++ + + ++ ++ + ++ ++ + + | 26 | a | ++ + ++ ++ + ++ ++ + ++ + + + | 27 | l | + + + ++ + ++ ++ + + ++ + + | 28 | u | +++ + ++ ++ + + ++ + + + + + | 29 | e | ++ + ++ ++ + + ++ + + ++ + ++ | 30 | s −10-- ++ + +++ + + + ++ + + ++ + ++ | 31 | | +++ ++ + + ++ + + ++ + + + | 32 | | ++++ ++ + ++ + + + + + ++ | 33 | | ++ +++ + + +++ ++ + ++ | 34 | | ++ +++ ++++ + + + | 35 | | ++ +++ +++ + + + | 36 | | ++ ++ +++ ++ + + | 37 | | ++++++++++ ++ +++ + ++ | 38 | −20-- + +++++++++++ + ++ | 39 | | +++++++++++ +++ | 40 | | ++ | 41 | | ++++++++++ | 42 | | | 43 | | | 44 | +----------------|--------------------------------------|--------------------------------------|--------------------------------------|----------------+ 45 | x1 x2 x3 x4 46 | Four separate samples -------------------------------------------------------------------------------- /mpl_ascii/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import Optional 3 | 4 | from matplotlib.backends.backend_agg import ( 5 | FigureManagerBase, 6 | FigureCanvasAgg, 7 | ) 8 | from matplotlib.figure import Figure 9 | 10 | from mpl_ascii.ascii_canvas import AsciiCanvas 11 | from mpl_ascii.ax import AxesPlot 12 | from mpl_ascii.color_map import FigureColorMap 13 | 14 | from rich.console import Console 15 | 16 | AXES_HEIGHT = int(os.getenv("AXES_HEIGHT", 40)) 17 | AXES_WIDTH = int(os.getenv("AXES_WIDTH", 150)) 18 | 19 | ENABLE_COLORS = True 20 | 21 | UNRELEASED = False 22 | 23 | class FigureCanvasAscii(FigureCanvasAgg): 24 | 25 | def __init__(self, figure: Optional[Figure] = ...) -> None: 26 | super().__init__(figure) 27 | 28 | def to_txt_with_color(self, sep="\n", tw=240, invert=False, threshold=200): 29 | self.draw() 30 | axes_height = AXES_HEIGHT 31 | axes_width = AXES_WIDTH 32 | 33 | all_axes_plots = [] 34 | for ax in self.figure.axes: 35 | ax_plot = AxesPlot(ax, axes_height, axes_width) 36 | all_axes_plots.append(ax_plot) 37 | 38 | fig_color_map = FigureColorMap(all_axes_plots) 39 | 40 | image_canvas = AsciiCanvas() 41 | for ax_plot in all_axes_plots: 42 | color_map = fig_color_map(ax_plot) 43 | ax_plot.draw_canvas(color_map) 44 | if ax_plot.is_colorbar(): 45 | image_canvas = image_canvas.update(ax_plot.color_canvas, (0, image_canvas.shape[1] + 3)) 46 | else: 47 | image_canvas = image_canvas.update(ax_plot.color_canvas, (image_canvas.shape[0], 0)) 48 | 49 | return image_canvas 50 | 51 | 52 | def to_txt(self, sep="\n", tw=240, invert=False, threshold=200): 53 | self.draw() 54 | axes_height = AXES_HEIGHT 55 | axes_width = AXES_WIDTH 56 | 57 | all_axes_plots = [] 58 | for ax in self.figure.axes: 59 | ax_plot = AxesPlot(ax, axes_height, axes_width) 60 | all_axes_plots.append(ax_plot) 61 | 62 | fig_color_map = FigureColorMap(all_axes_plots) 63 | 64 | image_canvas = AsciiCanvas() 65 | for ax_plot in all_axes_plots: 66 | color_map = fig_color_map(ax_plot) 67 | ax_plot.draw_canvas(color_map) 68 | if ax_plot.is_colorbar(): 69 | image_canvas = image_canvas.update(ax_plot.canvas, (0, image_canvas.shape[1] + 3)) 70 | else: 71 | image_canvas = image_canvas.update(ax_plot.canvas, (image_canvas.shape[0], 0)) 72 | 73 | return image_canvas 74 | 75 | def print_txt(self, filename, **kwargs): 76 | if isinstance(filename, str): 77 | with open(filename, "w") as f: 78 | f.write(str(self.to_txt())) 79 | else: 80 | filename.write(str(self.to_txt()).encode()) 81 | 82 | class FigureManagerAscii(FigureManagerBase): 83 | def __init__(self, canvas, num): 84 | super().__init__(canvas, num) 85 | self.canvas = canvas 86 | 87 | def show(self): 88 | canvas = self.canvas 89 | canvas.draw() 90 | console = Console() 91 | if ENABLE_COLORS: 92 | fig = canvas.to_txt_with_color() 93 | console.print(fig, highlight=False) 94 | else: 95 | fig = canvas.to_txt() 96 | print(fig) 97 | 98 | FigureCanvasAscii.manager_class = FigureManagerAscii 99 | FigureCanvas = FigureCanvasAscii 100 | FigureManager = FigureManagerAscii 101 | -------------------------------------------------------------------------------- /mpl_ascii/ascii_canvas.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from mpl_ascii.overlay import overlay 4 | 5 | 6 | class AsciiCanvas: 7 | def __init__(self, array=None) -> None: 8 | if isinstance(array, AsciiCanvas): 9 | array = array.array 10 | self.array = array 11 | 12 | def update(self, other, location=None): 13 | if self.array is None: 14 | return other 15 | res = overlay(self.array, other.array, location[0], location[1]) 16 | 17 | return AsciiCanvas(res) 18 | 19 | @property 20 | def shape(self): 21 | if self.array is None: 22 | return (0,0) 23 | return self.array.shape 24 | 25 | @property 26 | def debug(self): 27 | res = np.where(self.array==" ", ".", self.array) 28 | return AsciiCanvas(res) 29 | 30 | 31 | def __str__(self) -> str: 32 | res = [] 33 | for row in self.array: 34 | res.append("".join([str(char) for char in row.tolist()])) 35 | 36 | res = "\n".join(res) 37 | return res 38 | 39 | def __rich__(self) -> str: 40 | res = [] 41 | for row in self.array: 42 | row_colors = [] 43 | for char in row: 44 | if hasattr(char, "__rich__"): 45 | char = char.__rich__() 46 | row_colors.append(char) 47 | res.append("".join(row_colors)) 48 | 49 | res = "\n".join(res) 50 | return res 51 | -------------------------------------------------------------------------------- /mpl_ascii/ax.py: -------------------------------------------------------------------------------- 1 | from matplotlib.axes import Axes 2 | import numpy as np 3 | 4 | from mpl_ascii.ascii_canvas import AsciiCanvas 5 | from mpl_ascii.bar import BarPlots, get_bars 6 | from mpl_ascii.colorbar import ColorbarPlot, get_colorbar 7 | from mpl_ascii.contour import get_contour_plots 8 | from mpl_ascii.format import add_ax_title, add_text, add_ticks_and_frame 9 | from mpl_ascii.legend import add_legend 10 | from mpl_ascii.line import Errorbars, LineMarkers, LinePlots, get_errorbars, get_lines_plots, get_lines_with_markers 11 | from mpl_ascii.poly import ViolinPlots, get_violin_plots 12 | from mpl_ascii.scatter import ScatterPlot, get_scatter_plots 13 | 14 | 15 | class AxesPlot: 16 | def __init__(self, ax, axes_height, axes_width, color_map=None, colorbar=None) -> None: 17 | self.ax = ax 18 | self.plots = get_plots(ax) 19 | self._axes_height = axes_height 20 | self._axes_width = axes_width 21 | self._colorbar = colorbar 22 | self._color_map = color_map 23 | self._canvas = AsciiCanvas() 24 | 25 | def is_colorbar(self): 26 | if len(self.plots) > 0 and type(self.plots[0]) == ColorbarPlot: 27 | return True 28 | return False 29 | 30 | @property 31 | def axes_height(self): 32 | return self._axes_height 33 | 34 | @property 35 | def axes_width(self): 36 | if self.is_colorbar(): 37 | return 10 38 | return self._axes_width 39 | 40 | def draw_canvas(self, color_map): 41 | self._color_map = color_map 42 | self._canvas = draw_ax(self.ax, self.plots, self.axes_height, self.axes_width, color_map) 43 | self.create_color_canvas() 44 | 45 | @property 46 | def color_map(self): 47 | return self._color_map 48 | 49 | @property 50 | def canvas(self): 51 | return self._canvas 52 | 53 | @property 54 | def color_canvas(self): 55 | return self._color_canvas 56 | 57 | def create_color_canvas(self): 58 | color_canvas = AsciiCanvas(self.canvas.array.copy()) 59 | arr = color_canvas.array 60 | for color in self.color_map: 61 | arr[arr==self.color_map[color]]=f"[{color}]{self.color_map[color]}[/{color}]" 62 | color_canvas.array = arr 63 | 64 | self._color_canvas = color_canvas 65 | 66 | 67 | def draw_ax(ax: Axes, all_plots, axes_height, axes_width, color_to_ascii): 68 | 69 | canvas = init_canvas(all_plots, axes_height, axes_width) 70 | 71 | for plot in all_plots: 72 | canvas = plot.update(canvas, color_to_ascii) 73 | 74 | canvas = add_text(canvas, ax) 75 | 76 | canvas = add_ticks_and_frame(canvas, ax) 77 | 78 | canvas = add_ax_title(canvas, ax.get_title()) 79 | 80 | canvas = add_legend(canvas, ax.get_legend(), color_to_ascii) 81 | 82 | return canvas 83 | 84 | def init_canvas(all_plots, axes_height, axes_width): 85 | if type(all_plots[0]) == ColorbarPlot: 86 | axes_width = 10 87 | canvas = AsciiCanvas(np.full((axes_height, axes_width), fill_value=" ")) 88 | return canvas 89 | 90 | def get_plots(ax): 91 | all_plots = [] 92 | if has_bar_plots(ax): 93 | all_plots.append(BarPlots(ax)) 94 | if has_colorbar(ax): 95 | all_plots.append(ColorbarPlot(ax)) 96 | if has_line_plots(ax): 97 | all_plots.append(LinePlots(ax)) 98 | if has_errorbars(ax): 99 | all_plots.append(Errorbars(ax)) 100 | if has_line_markers(ax): 101 | all_plots.append(LineMarkers(ax)) 102 | if has_violin_plots(ax): 103 | all_plots.append(ViolinPlots(ax)) 104 | if has_scatter_plots(ax): 105 | all_plots.append(ScatterPlot(ax)) 106 | 107 | contour_plots = get_contour_plots(ax) 108 | if contour_plots: 109 | all_plots.append(contour_plots) 110 | 111 | if len(all_plots) == 0: 112 | raise Exception("Sorry, this type of plot is not yet a available for mpl_ascii.\n\ 113 | Please submit feature requests to https://github.com/chriscave/mpl_ascii") 114 | 115 | return all_plots 116 | 117 | def has_colorbar(ax): 118 | if get_colorbar(ax): 119 | return True 120 | return False 121 | 122 | def has_bar_plots(ax): 123 | if len(get_bars(ax)) > 0: 124 | return True 125 | return False 126 | 127 | def has_line_plots(ax): 128 | if len(get_lines_plots(ax)) > 0: 129 | return True 130 | return False 131 | 132 | def has_errorbars(ax): 133 | errorbar_caplines, error_barlinescols = get_errorbars(ax) 134 | if len(errorbar_caplines) > 0 or len(error_barlinescols) > 0: 135 | return True 136 | return False 137 | 138 | def has_scatter_plots(ax): 139 | if len(get_scatter_plots(ax)) > 0: 140 | return True 141 | return False 142 | 143 | def has_line_markers(ax): 144 | if len(get_lines_with_markers(ax)) > 0: 145 | return True 146 | return False 147 | 148 | def has_violin_plots(ax): 149 | pcoll, linecolls = get_violin_plots(ax) 150 | if len(pcoll) > 0 and len(linecolls) > 0: 151 | return True 152 | return False -------------------------------------------------------------------------------- /mpl_ascii/bar.py: -------------------------------------------------------------------------------- 1 | from matplotlib.axes import Axes 2 | from matplotlib.container import BarContainer 3 | from matplotlib.patches import Rectangle 4 | import numpy as np 5 | 6 | from mpl_ascii.ascii_canvas import AsciiCanvas 7 | 8 | from mpl_ascii.color import std_color 9 | from mpl_ascii.tools import linear_transform, scale_factor, get_xrange, get_yrange 10 | 11 | class BarPlots: 12 | def __init__(self, ax: Axes) -> None: 13 | self.ax = ax 14 | 15 | def update(self, canvas: AsciiCanvas, color_to_ascii) -> AsciiCanvas: 16 | return add_bar_chart(canvas, self.ax, color_to_ascii) 17 | 18 | @property 19 | def colors(self): 20 | colors = [] 21 | bars = get_bars(self.ax) 22 | for bar in bars: 23 | color = std_color(bar.get_facecolor()) 24 | colors.append(color) 25 | return colors 26 | 27 | 28 | 29 | def get_bars(ax): 30 | bars = [] 31 | for container in ax.containers: 32 | # Draw Bar chart 33 | if not isinstance(container, BarContainer): 34 | continue 35 | for bar in container.patches: 36 | if not isinstance(bar, Rectangle): 37 | continue 38 | bars.append(bar) 39 | 40 | return bars 41 | 42 | def draw_bar(bar_height, bar_width, ax_height, ax_width, x_range, y_range, char): 43 | x_min, x_max = x_range[0], x_range[1] 44 | y_min, y_max = y_range[0], y_range[1] 45 | 46 | ascii_width_bar = round(bar_width * scale_factor(x_min, x_max, 0, ax_width-1)) 47 | ascii_height_bar = round(bar_height * scale_factor(y_min, y_max, 1, ax_height)) 48 | 49 | return np.full((ascii_height_bar, ascii_width_bar), fill_value=char) 50 | 51 | def add_bar_chart(canvas, ax, color_to_ascii): 52 | x_range, y_range = get_xrange(ax), get_yrange(ax) 53 | axes_height, axes_width = canvas.shape 54 | x_min, x_max = x_range 55 | y_min, y_max = y_range 56 | 57 | bars = get_bars(ax) 58 | for bar in bars: 59 | 60 | char = color_to_ascii[std_color(bar.get_facecolor())] 61 | 62 | canvas_bar = AsciiCanvas( 63 | draw_bar( 64 | bar.get_height(), 65 | bar.get_width(), 66 | axes_height, 67 | axes_width, 68 | x_range, 69 | y_range, 70 | char 71 | ) 72 | ) 73 | ascii_x_bar = round(linear_transform(bar.xy[0], x_min, x_max, 0, axes_width-1)) 74 | ascii_y_bar = round(linear_transform(bar.xy[1], y_min, y_max, 1, axes_height)) 75 | 76 | canvas = canvas.update(canvas_bar, (axes_height - ascii_y_bar - canvas_bar.shape[0]+1, ascii_x_bar)) 77 | 78 | return canvas 79 | -------------------------------------------------------------------------------- /mpl_ascii/color.py: -------------------------------------------------------------------------------- 1 | 2 | import matplotlib 3 | 4 | 5 | def std_color(color): 6 | return matplotlib.colors.to_hex(color) 7 | 8 | 9 | class Char: 10 | def __init__(self, character: str, color: str) -> None: 11 | self.character=character 12 | self.color=color 13 | 14 | def __str__(self) -> str: 15 | return self.character 16 | 17 | 18 | def __rich__(self) -> str: 19 | return f"[{self.color}]{self.character}[/{self.color}]" -------------------------------------------------------------------------------- /mpl_ascii/color_map.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Dict, List 2 | from matplotlib.collections import QuadMesh 3 | 4 | from mpl_ascii.ax import AxesPlot, get_plots, has_colorbar 5 | from mpl_ascii.bar import get_bars 6 | from mpl_ascii.color import Char, std_color 7 | from mpl_ascii.colorbar import get_colorbar 8 | from mpl_ascii.contour import get_contour_plots 9 | from mpl_ascii.line import get_lines_plots 10 | from mpl_ascii.poly import get_violin_plots 11 | from mpl_ascii.scatter import ScatterPlot, get_scatter_plots 12 | 13 | bar_chars = [ 14 | "#", 15 | "%", 16 | "&", 17 | "@", 18 | "$", 19 | "=", 20 | "?", 21 | "<", 22 | ">", 23 | "!", 24 | "^", 25 | "~" 26 | ] 27 | 28 | line_chars = [ 29 | "+", 30 | "o", 31 | "a", 32 | "r", 33 | "t", 34 | "d", 35 | "y", 36 | "~", 37 | "[", 38 | "]", 39 | "{", 40 | "}", 41 | ] 42 | 43 | scatter_chars = [ 44 | "x", 45 | "v", 46 | "z", 47 | "s", 48 | "i", 49 | "n", 50 | ] 51 | 52 | 53 | 54 | class FigureColorMap: 55 | def __init__(self, all_axes_plots: List[AxesPlot]) -> None: 56 | self.all_axes_plots = all_axes_plots 57 | 58 | def associate_color_bar(self, axes_plot: AxesPlot): 59 | 60 | index = self.all_axes_plots.index(axes_plot) 61 | if index == len(self.all_axes_plots) - 1: 62 | return None 63 | 64 | colorbar_ax = self.all_axes_plots[index + 1] 65 | if colorbar_ax.is_colorbar(): 66 | return colorbar_ax.plots[0] 67 | 68 | return None 69 | 70 | def __call__(self, axes_plot: AxesPlot) -> Any: 71 | 72 | cbplot = self.associate_color_bar(axes_plot) 73 | 74 | if not cbplot: 75 | return ax_color_map(axes_plot.ax) 76 | 77 | color_to_ascii = {} 78 | color_bar_map = ax_color_map(cbplot.ax) 79 | tick_data = cbplot.tick_data 80 | cmap, norm = cbplot.cmap, cbplot.norm 81 | for plot in axes_plot.plots: 82 | if type(plot) == ScatterPlot: 83 | scatter_plot = plot 84 | for values, colors in zip(scatter_plot.values, scatter_plot.colors): 85 | 86 | for val, color in zip(values, colors): 87 | min_greater = min([tick for tick in tick_data if tick >= val]) 88 | color = std_color(color) 89 | char = color_bar_map[std_color(cmap(norm(min_greater)))] 90 | if color in color_to_ascii: 91 | continue 92 | color_to_ascii[color] = char 93 | return color_to_ascii 94 | 95 | return ax_color_map(axes_plot.ax) 96 | 97 | def ax_color_map(ax): 98 | 99 | def ascii_chars(ls): 100 | index = 0 101 | while True: 102 | yield ls[index] 103 | index = (index + 1) % len(ls) 104 | 105 | gen = ascii_chars(bar_chars) 106 | color_to_ascii = {} 107 | bars = get_bars(ax) 108 | for bar in bars: 109 | color = std_color(bar.get_facecolor()) 110 | if color in color_to_ascii: 111 | continue 112 | color_to_ascii[color] = Char(next(gen), color) 113 | 114 | gen = ascii_chars(bar_chars) 115 | for container in ax.collections: 116 | if not isinstance(container, QuadMesh): 117 | continue 118 | tick_data = [tick.get_loc() for tick in ax.yaxis.get_major_ticks()] 119 | cmap, norm = container.cmap, container.norm 120 | for td in tick_data: 121 | color = std_color(cmap(norm(td))) 122 | if color in color_to_ascii: 123 | continue 124 | color_to_ascii[color] = Char(next(gen), color) 125 | 126 | gen = ascii_chars(line_chars) 127 | lines = get_lines_plots(ax) 128 | for line in lines: 129 | color = std_color(line.get_color()) 130 | if color in color_to_ascii: 131 | continue 132 | color_to_ascii[color] = Char(next(gen), color) 133 | 134 | pcolls, linecolls = get_violin_plots(ax) 135 | for collection in pcolls: 136 | for color in collection.get_facecolor(): 137 | color = std_color(tuple(color.tolist())) 138 | if color in color_to_ascii: 139 | continue 140 | color_to_ascii[color] = Char(next(gen), color) 141 | 142 | for collection in linecolls: 143 | for color in collection.get_color(): 144 | color = std_color(tuple(color.tolist())) 145 | if color in color_to_ascii: 146 | continue 147 | color_to_ascii[color] = Char(next(gen), color) 148 | 149 | gen = ascii_chars(scatter_chars) 150 | collection = get_scatter_plots(ax) 151 | for collection in ax.collections: 152 | for color in collection.get_facecolor(): 153 | color = std_color(tuple(color.tolist())) 154 | if color in color_to_ascii: 155 | continue 156 | color_to_ascii[color] = Char(next(gen), color) 157 | 158 | gen = ascii_chars(line_chars) 159 | contour_plots = get_contour_plots(ax) 160 | if contour_plots: 161 | for color in contour_plots.colors: 162 | if color in color_to_ascii: 163 | continue 164 | color_to_ascii[color] = Char(next(gen), color) 165 | return color_to_ascii 166 | -------------------------------------------------------------------------------- /mpl_ascii/colorbar.py: -------------------------------------------------------------------------------- 1 | from matplotlib.collections import QuadMesh 2 | 3 | from mpl_ascii.ascii_canvas import AsciiCanvas 4 | from mpl_ascii.bar import draw_bar 5 | from mpl_ascii.color import std_color 6 | from mpl_ascii.tools import linear_transform, get_yrange 7 | 8 | 9 | class ColorbarPlot: 10 | def __init__(self, ax) -> None: 11 | self.ax = ax 12 | self.colorbar = get_colorbar(ax) 13 | self.tick_data = [tick.get_loc() for tick in self.ax.yaxis.get_major_ticks()] 14 | self.cmap = self.colorbar.cmap 15 | self.norm = self.colorbar.norm 16 | 17 | def update(self, canvas: AsciiCanvas, color_to_ascii) -> AsciiCanvas: 18 | return add_colorbar(self.ax, canvas, color_to_ascii) 19 | 20 | 21 | def get_colorbar(ax): 22 | colorbar = None 23 | for collection in ax.collections: 24 | if isinstance(collection, QuadMesh): 25 | colorbar = collection 26 | 27 | return colorbar 28 | 29 | def add_colorbar(ax, canvas, color_to_ascii): 30 | colorbar = get_colorbar(ax) 31 | axes_height, axes_width = canvas.shape 32 | y_range = get_yrange(ax) 33 | 34 | color_bar_width = axes_width 35 | 36 | tick_data = [tick.get_loc() for tick in ax.yaxis.get_major_ticks()] 37 | for i in range(1, len(tick_data)): 38 | top_value = tick_data[i] 39 | bottom_value = tick_data[i-1] 40 | if tick_data[i] > y_range[1]: 41 | top_value = y_range[1] 42 | top = round(linear_transform(top_value, y_range[0], y_range[1], 1, axes_height)) 43 | bottom = round(linear_transform(bottom_value, y_range[0], y_range[1], 1, axes_height)) 44 | cmap, norm = colorbar.cmap, colorbar.norm 45 | char = color_to_ascii[std_color(cmap(norm(top_value)))] 46 | bar_height = top - bottom 47 | if i == 1: 48 | bar_height = top 49 | 50 | c = AsciiCanvas(draw_bar( 51 | bar_height, 52 | color_bar_width, 53 | axes_height, 54 | color_bar_width, 55 | (0,color_bar_width-1), 56 | (1,axes_height), 57 | char 58 | )) 59 | 60 | canvas = canvas.update(c, (axes_height - top, 0)) 61 | 62 | return canvas -------------------------------------------------------------------------------- /mpl_ascii/contour.py: -------------------------------------------------------------------------------- 1 | import matplotlib 2 | from matplotlib.collections import PathCollection 3 | from matplotlib.contour import QuadContourSet 4 | 5 | from mpl_ascii.ascii_canvas import AsciiCanvas 6 | from mpl_ascii.color import std_color 7 | from mpl_ascii.line import draw_line 8 | from mpl_ascii.tools import get_xrange, get_yrange 9 | 10 | mpl_version = matplotlib.__version__ 11 | mpl_version = tuple(map(int, mpl_version.split("."))) 12 | 13 | 14 | class ContourPlots: 15 | def __init__(self, ax) -> None: 16 | self.ax = ax 17 | self.collections = [] 18 | if mpl_version >= (3,8,0): 19 | self.collections = [coll for coll in self.ax.collections if isinstance(coll, QuadContourSet)] 20 | else: 21 | for pc in ax.collections: 22 | if isinstance(pc, PathCollection): 23 | paths = pc.get_paths() 24 | sizes = pc.get_sizes() 25 | if len(paths) > 0 and len(sizes) == 0: 26 | self.collections.append(pc) 27 | 28 | 29 | self._colors = [] 30 | for collection in self.collections: 31 | if mpl_version >= (3,8,0): 32 | for seg, color in zip(collection.allsegs, collection.get_edgecolor()): 33 | if len(seg) == 0: 34 | continue 35 | color = std_color(color) 36 | if color in self._colors: 37 | continue 38 | self._colors.append(color) 39 | 40 | else: 41 | for color in collection.get_edgecolor(): 42 | color = std_color(color) 43 | if color in self._colors: 44 | continue 45 | self._colors.append(color) 46 | 47 | self._colors = sorted(self._colors) 48 | 49 | @property 50 | def colors(self): 51 | return self._colors 52 | 53 | def update(self, canvas, color_to_ascii): 54 | x_range, y_range = get_xrange(self.ax), get_yrange(self.ax) 55 | axes_height, axes_width = canvas.shape 56 | if mpl_version >= (3,8,0): 57 | 58 | for collection in self.collections: 59 | for seg, color in zip(collection.allsegs, collection.get_edgecolor()): 60 | if len(seg) == 0: 61 | continue 62 | char = color_to_ascii[std_color(color)] 63 | for xy_data in seg: 64 | 65 | x_data, y_data = [dat[0] for dat in xy_data], [dat[1] for dat in xy_data] 66 | line = AsciiCanvas( 67 | draw_line( 68 | width=axes_width, 69 | height=axes_height, 70 | x_data=x_data, 71 | y_data=y_data, 72 | x_range=x_range, 73 | y_range=y_range, 74 | char = char, 75 | ) 76 | ) 77 | canvas = canvas.update(line, (0,0)) 78 | return canvas 79 | 80 | else: 81 | for collection in self.collections: 82 | color = collection.get_edgecolor()[0] 83 | for path in collection.get_paths(): 84 | char = color_to_ascii[std_color(color)] 85 | xy_data = path.vertices 86 | x_data, y_data = [dat[0] for dat in xy_data], [dat[1] for dat in xy_data] 87 | line = AsciiCanvas( 88 | draw_line( 89 | width=axes_width, 90 | height=axes_height, 91 | x_data=x_data, 92 | y_data=y_data, 93 | x_range=x_range, 94 | y_range=y_range, 95 | char = char, 96 | ) 97 | ) 98 | canvas = canvas.update(line, (0,0)) 99 | return canvas 100 | 101 | def get_contour_plots(ax): 102 | if mpl_version >= (3,8,0): 103 | for collection in ax.collections: 104 | if isinstance(collection, QuadContourSet): 105 | return ContourPlots(ax) 106 | 107 | else: 108 | for pc in ax.collections: 109 | if isinstance(pc, PathCollection): 110 | paths = pc.get_paths() 111 | sizes = pc.get_sizes() 112 | if len(paths) > 0 and len(sizes) == 0: 113 | return ContourPlots(ax) 114 | 115 | return None 116 | -------------------------------------------------------------------------------- /mpl_ascii/legend.py: -------------------------------------------------------------------------------- 1 | import matplotlib 2 | from matplotlib.collections import PathCollection 3 | from matplotlib.lines import Line2D 4 | from matplotlib.patches import Rectangle 5 | import numpy as np 6 | 7 | from mpl_ascii.ascii_canvas import AsciiCanvas 8 | from mpl_ascii.color import std_color 9 | from mpl_ascii.format import draw_frame 10 | 11 | 12 | mpl_version = matplotlib.__version__ 13 | mpl_version = tuple(map(int, mpl_version.split("."))) 14 | 15 | def add_legend(canvas, legend, color_to_ascii): 16 | 17 | # Add legend 18 | if legend: 19 | texts = legend.texts 20 | if mpl_version >= (3,7,0): 21 | handles = legend.legend_handles 22 | else: 23 | handles = legend.legendHandles 24 | 25 | canvas_legend = AsciiCanvas() 26 | for handle, text in zip(handles, texts): 27 | char = " " 28 | if isinstance(handle, Rectangle): 29 | char = color_to_ascii[std_color(handle.get_facecolor())] 30 | if isinstance(handle, Line2D): 31 | char = color_to_ascii[std_color(handle.get_color())] 32 | if isinstance(handle, PathCollection): 33 | color = tuple(handle.get_facecolor()[0]) 34 | char = color_to_ascii[std_color(color)] 35 | 36 | arr = np.array([[char] * 3 + [" "] + list(text.get_text())]) 37 | canvas_legend = canvas_legend.update(AsciiCanvas(arr), (canvas_legend.shape[0], 0)) 38 | 39 | title = legend.get_title().get_text() or "Legend" 40 | title = AsciiCanvas(np.array([list(title)])) 41 | canvas_legend = canvas_legend.update(title, (-2,0)) 42 | legend_frame = AsciiCanvas(draw_frame(canvas_legend.shape[0]+2, canvas_legend.shape[1]+4)) 43 | canvas_legend = legend_frame.update(canvas_legend, (1,2)) 44 | 45 | start_idx = int((canvas.shape[1] / 2) - (canvas_legend.shape[1] / 2)) 46 | 47 | canvas = canvas.update(canvas_legend, (canvas.shape[0] + 1, start_idx )) 48 | 49 | return canvas -------------------------------------------------------------------------------- /mpl_ascii/overlay.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | def overlay(background, foreground, start_row, start_col): 4 | min_row = min(start_row, 0) 5 | min_col = min(start_col, 0) 6 | max_row = max(start_row + foreground.shape[0], background.shape[0]) 7 | max_col = max(start_col + foreground.shape[1], background.shape[1]) 8 | 9 | total_rows = max_row - min_row 10 | total_cols = max_col - min_col 11 | 12 | # Create the resulting array filled with spaces (or appropriate background fill) 13 | result = np.full((total_rows, total_cols), ' ', dtype="object") 14 | bg_start_row = max(-min_row, 0) # This will be 0 if start_row is >= 0 15 | bg_start_col = max(-min_col, 0) # This will be 0 if start_col is >= 0 16 | result[bg_start_row:bg_start_row+background.shape[0], 17 | bg_start_col:bg_start_col+background.shape[1]] = background 18 | 19 | fg_start_row = max(start_row - min_row, 0) 20 | fg_start_col = max(start_col - min_col, 0) 21 | fg_end_row = fg_start_row + foreground.shape[0] 22 | fg_end_col = fg_start_col + foreground.shape[1] 23 | 24 | overlap_section = result[fg_start_row:fg_end_row, fg_start_col:fg_end_col] 25 | result[fg_start_row:fg_end_row, fg_start_col:fg_end_col] = np.where( 26 | foreground != ' ', # Condition where foreground isn't just a space 27 | foreground, 28 | overlap_section # Keep original background if foreground is a space 29 | ) 30 | 31 | return result 32 | 33 | -------------------------------------------------------------------------------- /mpl_ascii/poly.py: -------------------------------------------------------------------------------- 1 | from matplotlib.collections import LineCollection, PolyCollection 2 | 3 | from mpl_ascii.ascii_canvas import AsciiCanvas 4 | from mpl_ascii.color import std_color 5 | from mpl_ascii.line import draw_line, get_errorbars 6 | from mpl_ascii.tools import get_xrange, get_yrange 7 | 8 | 9 | class ViolinPlots: 10 | def __init__(self, ax) -> None: 11 | self.ax = ax 12 | def update(self, canvas, color_to_ascii): 13 | return add_violin_plots(canvas, self.ax, color_to_ascii) 14 | 15 | def get_violin_plots(ax): 16 | poly_collection = [] 17 | line_collection = [] 18 | 19 | _, error_barlinescols = get_errorbars(ax) 20 | 21 | 22 | for collection in ax.collections: 23 | if isinstance(collection, PolyCollection): 24 | poly_collection.append(collection) 25 | 26 | if isinstance(collection, LineCollection): 27 | if collection in error_barlinescols: 28 | continue 29 | line_collection.append(collection) 30 | 31 | return poly_collection, line_collection 32 | 33 | def add_violin_plots(canvas, ax, color_to_ascii): 34 | x_range, y_range = get_xrange(ax), get_yrange(ax) 35 | pcolls, linecolls = get_violin_plots(ax) 36 | axes_height, axes_width = canvas.shape 37 | for collection in pcolls: 38 | char = color_to_ascii[std_color(collection.get_facecolor())] 39 | for path in collection.get_paths(): 40 | xy_data = path.vertices 41 | x_data, y_data = [dat[0] for dat in xy_data], [dat[1] for dat in xy_data] 42 | line = AsciiCanvas( 43 | draw_line( 44 | width=axes_width, 45 | height=axes_height, 46 | x_data=x_data, 47 | y_data=y_data, 48 | x_range=x_range, 49 | y_range=y_range, 50 | char = char, 51 | ) 52 | ) 53 | canvas = canvas.update(line, (0,0)) 54 | 55 | for collection in linecolls: 56 | 57 | for xy in collection.get_segments(): 58 | x_data = [p[0] for p in xy] 59 | y_data = [p[1] for p in xy] 60 | char = color_to_ascii[std_color(collection.get_color())] 61 | line = AsciiCanvas( 62 | draw_line( 63 | width=axes_width, 64 | height=axes_height, 65 | x_data=x_data, 66 | y_data=y_data, 67 | x_range=x_range, 68 | y_range=y_range, 69 | char = char, 70 | ) 71 | ) 72 | canvas = canvas.update(line, (0,0)) 73 | return canvas -------------------------------------------------------------------------------- /mpl_ascii/scatter.py: -------------------------------------------------------------------------------- 1 | from itertools import zip_longest 2 | from matplotlib.collections import PathCollection 3 | import numpy as np 4 | 5 | from mpl_ascii.ascii_canvas import AsciiCanvas 6 | from mpl_ascii.color import Char, std_color 7 | from mpl_ascii.tools import linear_transform, get_xrange, get_yrange 8 | 9 | 10 | class ScatterPlot: 11 | def __init__(self, ax) -> None: 12 | self.ax = ax 13 | self.scatter_plots = get_scatter_plots(self.ax) 14 | self.values = [coll.get_array() for coll in self.scatter_plots] 15 | self.colors = [coll.get_facecolor() for coll in self.scatter_plots] 16 | 17 | 18 | def update(self, canvas, color_to_ascii): 19 | return add_scatter_plots(canvas, self.ax, color_to_ascii) 20 | 21 | 22 | def get_scatter_plots(ax): 23 | scatter_plots = [] 24 | for collection in ax.collections: 25 | if isinstance(collection, PathCollection): 26 | paths = collection.get_paths() 27 | sizes = collection.get_sizes() 28 | if len(paths) > 0 and len(sizes) > 0: 29 | scatter_plots.append(collection) 30 | 31 | return scatter_plots 32 | 33 | 34 | def add_scatter_plots(canvas, ax, color_to_ascii): 35 | x_range, y_range = get_xrange(ax), get_yrange(ax) 36 | axes_height, axes_width = canvas.shape 37 | x_min, x_max = x_range 38 | y_min, y_max = y_range 39 | 40 | for collection in get_scatter_plots(ax): 41 | offsets = collection.get_offsets() 42 | if len(collection.get_facecolor()) > 0: 43 | default_color = collection.get_facecolor()[0] 44 | colors = collection.get_facecolor() 45 | 46 | if len(collection.get_edgecolor()) > 0: 47 | default_color = collection.get_edgecolor()[0] 48 | colors = collection.get_edgecolor() 49 | 50 | 51 | for point,color in zip_longest(offsets, colors, fillvalue=default_color): 52 | color = tuple(color) 53 | 54 | x_new = round(linear_transform(point[0], x_min, x_max, 0, axes_width-1)) 55 | y_new = round(linear_transform(point[1], y_min, y_max, 1, axes_height)) 56 | 57 | char = color_to_ascii.get(std_color(color), Char("+", "white")) 58 | canvas = canvas.update(AsciiCanvas(np.array([[char]])), (axes_height-y_new, x_new)) 59 | 60 | return canvas -------------------------------------------------------------------------------- /mpl_ascii/tools.py: -------------------------------------------------------------------------------- 1 | 2 | def linear_transform(x, old_min, old_max, new_min, new_max): 3 | x_new = new_min + (x - old_min) * scale_factor(old_min, old_max, new_min, new_max) 4 | return x_new 5 | 6 | def scale_factor(old_min, old_max, new_min, new_max): 7 | return (new_max - new_min) / (old_max - old_min) 8 | 9 | def get_xrange(ax): 10 | x_range = ax.get_xlim() 11 | if x_range[1] < x_range[0]: 12 | x_range = x_range[1], x_range[0] 13 | return x_range 14 | 15 | def get_yrange(ax): 16 | y_range = ax.get_ylim() 17 | if y_range[1] < y_range[0]: 18 | y_range = y_range[1], y_range[0] 19 | return y_range -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=62", "setuptools_scm>=7.1.0"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | requires-python = ">= 3.7" 7 | name = "mpl_ascii" 8 | dependencies = [ 9 | "matplotlib>=3.5.3", 10 | "rich>=13.7.1" 11 | ] 12 | dynamic = ["version"] 13 | 14 | description = "A matplotlib backend that produces plots using only ASCII characters" 15 | license = {text = "MIT License"} 16 | readme = "README.md" 17 | keywords = ["matplotlib", "plotting", "ASCII"] 18 | authors = [ 19 | {name = "Chris Cave"} 20 | ] 21 | maintainers = [ 22 | {name = "Chris Cave"} 23 | ] 24 | 25 | [tool.setuptools_scm] 26 | 27 | [project.urls] 28 | Homepage = "https://github.com/chriscave/mpl_ascii" 29 | 30 | [tool.setuptools] 31 | packages = ["mpl_ascii"] -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # Library requirements 2 | matplotlib >= 3.5.3 3 | rich >= 13.7.1 4 | 5 | # Test requirements 6 | pytest >= 7.4.4 7 | 8 | # Build requirements 9 | setuptools >= 62 10 | setuptools_scm >= 7.1.0 11 | twine >= 4.0.2 12 | build >= 1.1.1 -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscave/mpl_ascii/6b5166788b216f94772262360f0fff67a3e8ef01/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_overlay.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import numpy as np 4 | from numpy import array_equal 5 | 6 | from mpl_ascii.overlay import overlay 7 | 8 | class TestOverlay(unittest.TestCase): 9 | 10 | def test_basic_overlap(self): 11 | back = np.array( 12 | [ 13 | ["x", "x", "x"], 14 | ["x", "x", "x"], 15 | ["x", "x", "x"], 16 | ] 17 | ) 18 | fore = np.array( 19 | [ 20 | ["y", "y"], 21 | ["y", "y"], 22 | ] 23 | ) 24 | actual = overlay(back, fore, 1,1) 25 | expected = np.array( 26 | [ 27 | ["x", "x", "x"], 28 | ["x", "y", "y"], 29 | ["x", "y", "y"], 30 | ] 31 | ) 32 | 33 | self.assertTrue(array_equal(actual, expected)) 34 | 35 | def test_negative_start_conditions(self): 36 | back = np.array([["1"]*5]*5) 37 | fore = np.array( 38 | [ 39 | ["y", "x"], 40 | ["x", "y"], 41 | ] 42 | ) 43 | actual = overlay(back, fore, -1, -1) 44 | expected = np.array( 45 | [ 46 | ["y", "x", " ", " ", " ", " "], 47 | ["x", "y", "1", "1", "1", "1"], 48 | [" ", "1", "1", "1", "1", "1"], 49 | [" ", "1", "1", "1", "1", "1"], 50 | [" ", "1", "1", "1", "1", "1"], 51 | [" ", "1", "1", "1", "1", "1"], 52 | ] 53 | ) 54 | self.assertTrue(array_equal(actual, expected)) 55 | 56 | def test_foreground_bigger_than_background(self): 57 | background = np.array([ 58 | ["2", "2", "2"], 59 | ["2", "2", "2"], 60 | ["2", "2", "2"] 61 | ]) 62 | foreground = np.array([ 63 | ["A", "B", "C", "D", "E"], 64 | ["F", "G", "H", "I", "J"], 65 | ["K", "L", "M", "N", "O"], 66 | ["P", "Q", "R", "S", "T"], 67 | ["U", "V", "W", "X", "Y"] 68 | ]) 69 | expected = foreground 70 | actual = overlay(background, foreground, 0,0) 71 | self.assertTrue(array_equal(actual, expected)) 72 | 73 | def test_foreground_whitespace(self): 74 | background = np.array([ 75 | ["1", "2", "3", "4"], 76 | ["5", "6", "7", "8"], 77 | ["9", "A", "B", "C"], 78 | ["D", "E", "F", "G"] 79 | ]) 80 | foreground = np.array([ 81 | ["X", " "], 82 | ["Y", "Z"] 83 | ]) 84 | expected = np.array([ 85 | ["X", "2", "3", "4"], 86 | ["Y", "Z", "7", "8"], 87 | ["9", "A", "B", "C"], 88 | ["D", "E", "F", "G"] 89 | ]) 90 | actual = overlay(background, foreground, 0,0) 91 | 92 | self.assertTrue(array_equal(actual, expected)) 93 | 94 | def test_all_whitespace_foreground(self): 95 | background = np.array([ 96 | ["1", "2", "3", "4"], 97 | ["5", "6", "7", "8"], 98 | ["9", "A", "B", "C"], 99 | ["D", "E", "F", "G"] 100 | ]) 101 | 102 | foreground = np.array( 103 | [ 104 | [" ", " ", " "], 105 | [" ", " ", " "], 106 | [" ", " ", " "], 107 | ] 108 | ) 109 | expected = np.array( 110 | [ 111 | ["1", "2", "3", "4", " "], 112 | ["5", "6", "7", "8", " "], 113 | ["9", "A", "B", "C", " "], 114 | ["D", "E", "F", "G", " "], 115 | [" ", " ", " ", " ", " "] 116 | ] 117 | ) 118 | actual = overlay(background, foreground, 2, 2) 119 | self.assertTrue(array_equal(actual, expected)) 120 | 121 | def test_foreground_outside_backgroud(self): 122 | back = np.array( 123 | [ 124 | ["x", "x", "x"], 125 | ["x", "x", "x"], 126 | ["x", "x", "x"], 127 | ] 128 | ) 129 | fore = np.array( 130 | [ 131 | ["y", "y"], 132 | ["y", "y"], 133 | ] 134 | ) 135 | actual = overlay(back, fore, -3,-3) 136 | expected = np.array( 137 | [ 138 | ["y", "y", " ", " ", " ", " "], 139 | ["y", "y", " ", " ", " ", " "], 140 | [" ", " ", " ", " ", " ", " "], 141 | [" ", " ", " ", "x", "x", "x"], 142 | [" ", " ", " ", "x", "x", "x"], 143 | [" ", " ", " ", "x", "x", "x"], 144 | ] 145 | ) 146 | 147 | self.assertTrue(array_equal(actual, expected)) 148 | 149 | def test_empty_background(self): 150 | background = np.array([]).reshape(0, 0) # An empty (0x0) array 151 | foreground = np.array([ 152 | ["Q", "Q"], 153 | ["Q", "Q"] 154 | ]) 155 | actual = overlay(background, foreground, 0,0) 156 | 157 | expected = np.array([ 158 | ["Q", "Q"], 159 | ["Q", "Q"] 160 | ]) 161 | self.assertTrue(array_equal(actual, expected)) 162 | 163 | 164 | --------------------------------------------------------------------------------