densitty 0.8.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- densitty-0.8.2/.gitignore +13 -0
- densitty-0.8.2/LICENSE +21 -0
- densitty-0.8.2/Makefile +57 -0
- densitty-0.8.2/PKG-INFO +36 -0
- densitty-0.8.2/README.md +14 -0
- densitty-0.8.2/densitty/__init__.py +0 -0
- densitty-0.8.2/densitty/ansi.py +109 -0
- densitty-0.8.2/densitty/ascii_art.py +24 -0
- densitty-0.8.2/densitty/axis.py +265 -0
- densitty-0.8.2/densitty/binning.py +240 -0
- densitty-0.8.2/densitty/detect.py +465 -0
- densitty-0.8.2/densitty/lineart.py +130 -0
- densitty-0.8.2/densitty/plot.py +201 -0
- densitty-0.8.2/densitty/truecolor.py +170 -0
- densitty-0.8.2/densitty/util.py +234 -0
- densitty-0.8.2/densitty.egg-info/PKG-INFO +36 -0
- densitty-0.8.2/densitty.egg-info/SOURCES.txt +322 -0
- densitty-0.8.2/densitty.egg-info/dependency_links.txt +1 -0
- densitty-0.8.2/densitty.egg-info/top_level.txt +1 -0
- densitty-0.8.2/docs/FAQ.md +12 -0
- densitty-0.8.2/docs/examples.md +68 -0
- densitty-0.8.2/docs/eyeplot.png +0 -0
- densitty-0.8.2/docs/hist2d-axes.png +0 -0
- densitty-0.8.2/docs/hist2d-basic.png +0 -0
- densitty-0.8.2/docs/hist2d-finer-color-borderline.png +0 -0
- densitty-0.8.2/docs/hist2d-helper.png +0 -0
- densitty-0.8.2/docs/hist2d-scaled.png +0 -0
- densitty-0.8.2/docs/inline-histplot2d.png +0 -0
- densitty-0.8.2/docs/terminal_support.md +50 -0
- densitty-0.8.2/docs/usage.md +38 -0
- densitty-0.8.2/examples/eye_plot.py +46 -0
- densitty-0.8.2/examples/run_examples.py +70 -0
- densitty-0.8.2/pyproject.toml +71 -0
- densitty-0.8.2/setup.cfg +4 -0
- densitty-0.8.2/tests/#lineart_tests.py# +132 -0
- densitty-0.8.2/tests/axis_tests.py +160 -0
- densitty-0.8.2/tests/binning_tests.py +169 -0
- densitty-0.8.2/tests/color_tests.py +129 -0
- densitty-0.8.2/tests/convert_tests.py +198 -0
- densitty-0.8.2/tests/detect_tests.py +48 -0
- densitty-0.8.2/tests/gen_norm_data.py +25 -0
- densitty-0.8.2/tests/golden.py +31 -0
- densitty-0.8.2/tests/golden_diff.py +171 -0
- densitty-0.8.2/tests/goldens/ansi.BLUE_RED +1 -0
- densitty-0.8.2/tests/goldens/ansi.BLUE_RED_halfheight +1 -0
- densitty-0.8.2/tests/goldens/ansi.COOL +1 -0
- densitty-0.8.2/tests/goldens/ansi.COOL_halfheight +1 -0
- densitty-0.8.2/tests/goldens/ansi.FADE_IN +1 -0
- densitty-0.8.2/tests/goldens/ansi.FADE_IN_halfheight +1 -0
- densitty-0.8.2/tests/goldens/ansi.GRAYSCALE +1 -0
- densitty-0.8.2/tests/goldens/ansi.GRAYSCALE_halfheight +1 -0
- densitty-0.8.2/tests/goldens/ansi.HOT +1 -0
- densitty-0.8.2/tests/goldens/ansi.HOT_halfheight +1 -0
- densitty-0.8.2/tests/goldens/ansi.RAINBOW +1 -0
- densitty-0.8.2/tests/goldens/ansi.RAINBOW_16 +1 -0
- densitty-0.8.2/tests/goldens/ansi.RAINBOW_16_halfheight +1 -0
- densitty-0.8.2/tests/goldens/ansi.RAINBOW_halfheight +1 -0
- densitty-0.8.2/tests/goldens/ascii-map +1 -0
- densitty-0.8.2/tests/goldens/auto_allzero +1 -0
- densitty-0.8.2/tests/goldens/auto_color_limits +1 -0
- densitty-0.8.2/tests/goldens/halfheight +1 -0
- densitty-0.8.2/tests/goldens/halfheight_single_row +1 -0
- densitty-0.8.2/tests/goldens/merge_chars +1 -0
- densitty-0.8.2/tests/goldens/merge_lines +1 -0
- densitty-0.8.2/tests/goldens/simple_glyph_ascii +1 -0
- densitty-0.8.2/tests/goldens/simple_glyph_basic +1 -0
- densitty-0.8.2/tests/goldens/simple_glyph_extended +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-0)-ext-False-False-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-0)-ext-False-False-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-0)-ext-False-False-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-0)-ext-False-False-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-0)-ext-False-True-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-0)-ext-False-True-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-0)-ext-False-True-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-0)-ext-False-True-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-0)-ext-True-False-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-0)-ext-True-False-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-0)-ext-True-False-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-0)-ext-True-False-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-0)-ext-True-True-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-0)-ext-True-True-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-0)-ext-True-True-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-0)-ext-True-True-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-0.1)-ext-False-False-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-0.1)-ext-False-False-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-0.1)-ext-False-False-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-0.1)-ext-False-False-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-0.1)-ext-False-True-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-0.1)-ext-False-True-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-0.1)-ext-False-True-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-0.1)-ext-False-True-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-0.1)-ext-True-False-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-0.1)-ext-True-False-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-0.1)-ext-True-False-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-0.1)-ext-True-False-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-0.1)-ext-True-True-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-0.1)-ext-True-True-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-0.1)-ext-True-True-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-0.1)-ext-True-True-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-1)-ext-False-False-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-1)-ext-False-False-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-1)-ext-False-False-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-1)-ext-False-False-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-1)-ext-False-True-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-1)-ext-False-True-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-1)-ext-False-True-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-1)-ext-False-True-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-1)-ext-True-False-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-1)-ext-True-False-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-1)-ext-True-False-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-1)-ext-True-False-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-1)-ext-True-True-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-1)-ext-True-True-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-1)-ext-True-True-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-1)-ext-True-True-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-1.5)-ext-False-False-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-1.5)-ext-False-False-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-1.5)-ext-False-False-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-1.5)-ext-False-False-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-1.5)-ext-False-True-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-1.5)-ext-False-True-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-1.5)-ext-False-True-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-1.5)-ext-False-True-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-1.5)-ext-True-False-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-1.5)-ext-True-False-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-1.5)-ext-True-False-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-1.5)-ext-True-False-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-1.5)-ext-True-True-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-1.5)-ext-True-True-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-1.5)-ext-True-True-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-1.5)-ext-True-True-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-10)-ext-False-False-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-10)-ext-False-False-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-10)-ext-False-False-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-10)-ext-False-False-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-10)-ext-False-True-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-10)-ext-False-True-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-10)-ext-False-True-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-10)-ext-False-True-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-10)-ext-True-False-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-10)-ext-True-False-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-10)-ext-True-False-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-10)-ext-True-False-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-10)-ext-True-True-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-10)-ext-True-True-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-10)-ext-True-True-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-10)-ext-True-True-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-2)-ext-False-False-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-2)-ext-False-False-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-2)-ext-False-False-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-2)-ext-False-False-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-2)-ext-False-True-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-2)-ext-False-True-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-2)-ext-False-True-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-2)-ext-False-True-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-2)-ext-True-False-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-2)-ext-True-False-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-2)-ext-True-False-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-2)-ext-True-False-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-2)-ext-True-True-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-2)-ext-True-True-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-2)-ext-True-True-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-False-(-1-2)-ext-True-True-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-0)-ext-False-False-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-0)-ext-False-False-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-0)-ext-False-False-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-0)-ext-False-False-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-0)-ext-False-True-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-0)-ext-False-True-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-0)-ext-False-True-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-0)-ext-False-True-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-0)-ext-True-False-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-0)-ext-True-False-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-0)-ext-True-False-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-0)-ext-True-False-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-0)-ext-True-True-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-0)-ext-True-True-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-0)-ext-True-True-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-0)-ext-True-True-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-0.1)-ext-False-False-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-0.1)-ext-False-False-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-0.1)-ext-False-False-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-0.1)-ext-False-False-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-0.1)-ext-False-True-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-0.1)-ext-False-True-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-0.1)-ext-False-True-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-0.1)-ext-False-True-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-0.1)-ext-True-False-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-0.1)-ext-True-False-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-0.1)-ext-True-False-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-0.1)-ext-True-False-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-0.1)-ext-True-True-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-0.1)-ext-True-True-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-0.1)-ext-True-True-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-0.1)-ext-True-True-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-1)-ext-False-False-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-1)-ext-False-False-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-1)-ext-False-False-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-1)-ext-False-False-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-1)-ext-False-True-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-1)-ext-False-True-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-1)-ext-False-True-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-1)-ext-False-True-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-1)-ext-True-False-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-1)-ext-True-False-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-1)-ext-True-False-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-1)-ext-True-False-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-1)-ext-True-True-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-1)-ext-True-True-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-1)-ext-True-True-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-1)-ext-True-True-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-1.5)-ext-False-False-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-1.5)-ext-False-False-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-1.5)-ext-False-False-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-1.5)-ext-False-False-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-1.5)-ext-False-True-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-1.5)-ext-False-True-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-1.5)-ext-False-True-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-1.5)-ext-False-True-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-1.5)-ext-True-False-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-1.5)-ext-True-False-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-1.5)-ext-True-False-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-1.5)-ext-True-False-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-1.5)-ext-True-True-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-1.5)-ext-True-True-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-1.5)-ext-True-True-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-1.5)-ext-True-True-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-10)-ext-False-False-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-10)-ext-False-False-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-10)-ext-False-False-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-10)-ext-False-False-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-10)-ext-False-True-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-10)-ext-False-True-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-10)-ext-False-True-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-10)-ext-False-True-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-10)-ext-True-False-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-10)-ext-True-False-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-10)-ext-True-False-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-10)-ext-True-False-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-10)-ext-True-True-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-10)-ext-True-True-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-10)-ext-True-True-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-10)-ext-True-True-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-2)-ext-False-False-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-2)-ext-False-False-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-2)-ext-False-False-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-2)-ext-False-False-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-2)-ext-False-True-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-2)-ext-False-True-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-2)-ext-False-True-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-2)-ext-False-True-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-2)-ext-True-False-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-2)-ext-True-False-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-2)-ext-True-False-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-2)-ext-True-False-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-2)-ext-True-True-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-2)-ext-True-True-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-2)-ext-True-True-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes-True-(-1-2)-ext-True-True-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes_ascii-False-False-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes_ascii-False-False-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes_ascii-False-False-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes_ascii-False-False-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes_ascii-False-True-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes_ascii-False-True-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes_ascii-False-True-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes_ascii-False-True-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes_ascii-True-False-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes_ascii-True-False-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes_ascii-True-False-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes_ascii-True-False-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes_ascii-True-True-False-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes_ascii-True-True-False-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes_ascii-True-True-True-False +1 -0
- densitty-0.8.2/tests/goldens/test_axes_ascii-True-True-True-True +1 -0
- densitty-0.8.2/tests/goldens/test_axes_labelsgiven +1 -0
- densitty-0.8.2/tests/goldens/test_axes_small +1 -0
- densitty-0.8.2/tests/goldens/test_bin_data_1 +1 -0
- densitty-0.8.2/tests/goldens/test_bin_data_2 +1 -0
- densitty-0.8.2/tests/goldens/test_bin_data_3 +1 -0
- densitty-0.8.2/tests/goldens/test_bin_data_unaligned +1 -0
- densitty-0.8.2/tests/goldens/test_binning_auto_spec_range_1 +1 -0
- densitty-0.8.2/tests/goldens/test_binning_drop_data +1 -0
- densitty-0.8.2/tests/goldens/test_binning_edge_1 +1 -0
- densitty-0.8.2/tests/goldens/test_binning_edge_2 +1 -0
- densitty-0.8.2/tests/goldens/test_binning_fixed_1 +1 -0
- densitty-0.8.2/tests/goldens/test_binning_fixed_2 +1 -0
- densitty-0.8.2/tests/goldens/test_binning_no_drop_data +1 -0
- densitty-0.8.2/tests/goldens/test_binning_provide_edges +1 -0
- densitty-0.8.2/tests/goldens/test_binning_simple_1 +1 -0
- densitty-0.8.2/tests/goldens/test_binning_simple_2 +1 -0
- densitty-0.8.2/tests/goldens/test_border_nonhist_toscreen +1 -0
- densitty-0.8.2/tests/goldens/test_empty_data +1 -0
- densitty-0.8.2/tests/goldens/test_histplot2d_1 +1 -0
- densitty-0.8.2/tests/goldens/test_histplot2d_2 +1 -0
- densitty-0.8.2/tests/goldens/test_histplot2d_3 +1 -0
- densitty-0.8.2/tests/goldens/test_histplot2d_4 +1 -0
- densitty-0.8.2/tests/goldens/test_maxsize_fitscreen +1 -0
- densitty-0.8.2/tests/goldens/test_maxsize_fitscreen_noaxes +1 -0
- densitty-0.8.2/tests/goldens/test_maxsize_keepaspect +1 -0
- densitty-0.8.2/tests/goldens/test_maxsize_reservemargin +1 -0
- densitty-0.8.2/tests/goldens/test_maxsize_set_default_size +1 -0
- densitty-0.8.2/tests/goldens/test_simple_hist_toscreen +1 -0
- densitty-0.8.2/tests/goldens/test_single_data +1 -0
- densitty-0.8.2/tests/goldens/test_single_valued_data +1 -0
- densitty-0.8.2/tests/goldens/truecolor.BLUE_RED +1 -0
- densitty-0.8.2/tests/goldens/truecolor.BLUE_RED_halfheight +1 -0
- densitty-0.8.2/tests/goldens/truecolor.COOL +1 -0
- densitty-0.8.2/tests/goldens/truecolor.COOL_halfheight +1 -0
- densitty-0.8.2/tests/goldens/truecolor.FADE_IN +1 -0
- densitty-0.8.2/tests/goldens/truecolor.FADE_IN_halfheight +1 -0
- densitty-0.8.2/tests/goldens/truecolor.GRAYSCALE +1 -0
- densitty-0.8.2/tests/goldens/truecolor.GRAYSCALE_LINEAR +1 -0
- densitty-0.8.2/tests/goldens/truecolor.GRAYSCALE_LINEAR_halfheight +1 -0
- densitty-0.8.2/tests/goldens/truecolor.GRAYSCALE_halfheight +1 -0
- densitty-0.8.2/tests/goldens/truecolor.HOT +1 -0
- densitty-0.8.2/tests/goldens/truecolor.HOT_halfheight +1 -0
- densitty-0.8.2/tests/goldens/truecolor.RAINBOW +1 -0
- densitty-0.8.2/tests/goldens/truecolor.RAINBOW_halfheight +1 -0
- densitty-0.8.2/tests/helper_tests.py +50 -0
- densitty-0.8.2/tests/lineart_tests.py +133 -0
- densitty-0.8.2/tests/numpy_tests.py +33 -0
- densitty-0.8.2/tests/scale_tests.py +140 -0
- densitty-0.8.2/tests/util_tests.py +10 -0
densitty-0.8.2/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 William Tompkins
|
|
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.
|
densitty-0.8.2/Makefile
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
.PHONY: test
|
|
2
|
+
test:
|
|
3
|
+
PYTHONPATH=. uv run --with numpy,pytest,readchar,rich python -m pytest tests/*.py
|
|
4
|
+
PYTHONPATH=. uv run --with numpy,pytest,readchar,pytest-cov python -m pytest --cov=densitty tests/*.py
|
|
5
|
+
|
|
6
|
+
.PHONY: testcov
|
|
7
|
+
testcov:
|
|
8
|
+
PYTHONPATH=. uv run --with numpy,pytest,pytest-cov,readchar python -m pytest --cov=densitty --cov-report=html tests/*.py
|
|
9
|
+
|
|
10
|
+
.PHONY: golden-accept
|
|
11
|
+
golden-accept:
|
|
12
|
+
PYTHONPATH=. uv run --with readchar python tests/golden_diff.py
|
|
13
|
+
|
|
14
|
+
.PHONY: lint
|
|
15
|
+
lint:
|
|
16
|
+
PYTHONPATH=. uv run --with pylint,rich python -m pylint densitty
|
|
17
|
+
|
|
18
|
+
.PHONY: format
|
|
19
|
+
format:
|
|
20
|
+
uv run --with black python -m black -l 99 densitty/*.py
|
|
21
|
+
uv run --with black python -m black -l 99 tests/*.py
|
|
22
|
+
|
|
23
|
+
.PHONY: typecheck
|
|
24
|
+
typecheck:
|
|
25
|
+
PYTHONPATH=. uv run --with mypy,rich python -m mypy densitty
|
|
26
|
+
PYTHONPATH=. uv run --with mypy,numpy,rich python -m mypy tests/numpy_tests.py
|
|
27
|
+
PYTHONPATH=. uv run --with mypy,rich python -m mypy tests/axis_tests.py
|
|
28
|
+
|
|
29
|
+
.PHONY: colors
|
|
30
|
+
colors:
|
|
31
|
+
PYTHONPATH=. uv run --with pytest,rich python tests/color_tests.py
|
|
32
|
+
|
|
33
|
+
.PHONY: build
|
|
34
|
+
build: ## Build wheel file
|
|
35
|
+
rm -rf ./dist
|
|
36
|
+
uvx --from build pyproject-build --installer uv
|
|
37
|
+
|
|
38
|
+
.PHONY: tag
|
|
39
|
+
tag: ## Add a Git tag and push it to origin with syntax: make tag TAG=tag_name
|
|
40
|
+
ifeq ($(origin TAG),undefined)
|
|
41
|
+
$(error "ERROR: use like 'make tag TAG=tag_name'")
|
|
42
|
+
else
|
|
43
|
+
@echo "Creating git tag: ${TAG}"
|
|
44
|
+
git tag -a ${TAG} -m ""
|
|
45
|
+
@echo "Pushing tag to origin: ${TAG}"
|
|
46
|
+
git push origin ${TAG}
|
|
47
|
+
endif
|
|
48
|
+
|
|
49
|
+
.PHONY: publish-test
|
|
50
|
+
publish-test: build
|
|
51
|
+
@echo "Publishing to testpypi"
|
|
52
|
+
uvx twine upload --repository testpypi dist/*
|
|
53
|
+
|
|
54
|
+
.PHONY: publish
|
|
55
|
+
publish: build
|
|
56
|
+
@echo "Publishing to REAL pypi"
|
|
57
|
+
uvx twine upload dist/*
|
densitty-0.8.2/PKG-INFO
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: densitty
|
|
3
|
+
Version: 0.8.2
|
|
4
|
+
Summary: densitty - create textual 2-D density plots, heatmaps, and 2-D histograms in Python
|
|
5
|
+
Author: Bill Tompkins
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/BillTompkins/densitty
|
|
8
|
+
Keywords: densitty,ascii,ascii-art,plotting,terminal,Python
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Classifier: Programming Language :: Python :: Free Threading :: 3 - Stable
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
+
Requires-Python: >=3.12
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
<h1 align="center">densitty</h1>
|
|
24
|
+
<h2 align="center"> Terminal-based 2-D Histogram, Density Plots, and Heatmaps</h2>
|
|
25
|
+
|
|
26
|
+
Generate 2-D histograms (density plots, heat maps, eye diagrams) similar to [matplotlib's hist2d](https://matplotlib.org/stable/gallery/statistics/hist.html "hist2d"), but with output in the terminal, and no external dependencies.
|
|
27
|
+
|
|
28
|
+

|
|
29
|
+
|
|
30
|
+
## [Examples/Gallery](https://billtompkins.github.io/densitty/docs/examples.html)
|
|
31
|
+
|
|
32
|
+
## [Sub-modules / Usage Notes](https://billtompkins.github.io/densitty/docs/usage.html)
|
|
33
|
+
|
|
34
|
+
## [Color, Size, and Glyph Support](https://billtompkins.github.io/densitty/docs/terminal_support.html)
|
|
35
|
+
|
|
36
|
+
## API (TODO)
|
densitty-0.8.2/README.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<h1 align="center">densitty</h1>
|
|
2
|
+
<h2 align="center"> Terminal-based 2-D Histogram, Density Plots, and Heatmaps</h2>
|
|
3
|
+
|
|
4
|
+
Generate 2-D histograms (density plots, heat maps, eye diagrams) similar to [matplotlib's hist2d](https://matplotlib.org/stable/gallery/statistics/hist.html "hist2d"), but with output in the terminal, and no external dependencies.
|
|
5
|
+
|
|
6
|
+

|
|
7
|
+
|
|
8
|
+
## [Examples/Gallery](https://billtompkins.github.io/densitty/docs/examples.html)
|
|
9
|
+
|
|
10
|
+
## [Sub-modules / Usage Notes](https://billtompkins.github.io/densitty/docs/usage.html)
|
|
11
|
+
|
|
12
|
+
## [Color, Size, and Glyph Support](https://billtompkins.github.io/densitty/docs/terminal_support.html)
|
|
13
|
+
|
|
14
|
+
## API (TODO)
|
|
File without changes
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""ANSI code/color support"""
|
|
2
|
+
|
|
3
|
+
# <pedantic> that the 256-color support here is not actually ANSI X3.64, though it uses ANSI-ish
|
|
4
|
+
# escape sequences. I believe it was originally done in Xterm. And 4b colors (16-color)
|
|
5
|
+
# are really an aixterm extension to the ANSI-specified 8-color standard. </pedantic>
|
|
6
|
+
|
|
7
|
+
from typing import Optional, Sequence
|
|
8
|
+
|
|
9
|
+
from .util import nearest
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
RESET = "\033[0m"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def compose(codes: Sequence[str]) -> str:
|
|
16
|
+
"""Given a list of individual color codes, produce the full escape sequence."""
|
|
17
|
+
return f"\033[{';'.join(codes)}m"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def colormap_16(colors):
|
|
21
|
+
"""Produce a function that returns closest 4b/16color ANSI color codes from colormap.
|
|
22
|
+
Parameters
|
|
23
|
+
----------
|
|
24
|
+
colors: Sequence[int]
|
|
25
|
+
Ordered 16-color ANSI colors corresponding to the 0.0..1.0 range
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def as_colorcodes(bg_frac: Optional[float], fg_frac: Optional[float]) -> str:
|
|
29
|
+
"""Return ANSI color code for 16-color value(s)
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
fg_frac: Optional[float]
|
|
33
|
+
Value 0.0..1.0 for foreground, or None if background-only
|
|
34
|
+
fg_frac: Optional[float]
|
|
35
|
+
Value 0.0..1.0 for background, or None for foreground-only
|
|
36
|
+
"""
|
|
37
|
+
codes = []
|
|
38
|
+
if fg_frac is not None:
|
|
39
|
+
codes += [f"{30 + nearest(colors, fg_frac)}"]
|
|
40
|
+
if bg_frac is not None:
|
|
41
|
+
codes += [f"{40 + nearest(colors, bg_frac)}"]
|
|
42
|
+
return compose(codes)
|
|
43
|
+
|
|
44
|
+
return as_colorcodes
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def colormap_256(colors):
|
|
48
|
+
"""Produce a function that returns closest 8b/256color ANSI color codes from colormap.
|
|
49
|
+
Parameters
|
|
50
|
+
----------
|
|
51
|
+
colors: Sequence[int]
|
|
52
|
+
Ordered 256-color ANSI colors corresponding to the 0.0..1.0 range
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def as_colorcodes(bg_frac: Optional[float], fg_frac: Optional[float]):
|
|
56
|
+
"""Return ANSI color code for 256-color value(s)
|
|
57
|
+
Parameters
|
|
58
|
+
----------
|
|
59
|
+
fg_frac: Optional[float]
|
|
60
|
+
Value 0.0..1.0 for foreground, or None if background-only
|
|
61
|
+
fg_frac: Optional[float]
|
|
62
|
+
Value 0.0..1.0 for background, or None for foreground-only
|
|
63
|
+
"""
|
|
64
|
+
codes = []
|
|
65
|
+
if fg_frac is not None:
|
|
66
|
+
fg = nearest(colors, fg_frac)
|
|
67
|
+
codes += [f"38;5;{fg}"]
|
|
68
|
+
if bg_frac is not None:
|
|
69
|
+
bg = nearest(colors, bg_frac)
|
|
70
|
+
codes += [f"48;5;{bg}"]
|
|
71
|
+
return compose(codes)
|
|
72
|
+
|
|
73
|
+
return as_colorcodes
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
########################################################
|
|
77
|
+
# Colormaps. Assumed 256-color unless suffixed with _16
|
|
78
|
+
# pylint: disable=invalid-name
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# ANSI 16-color map colors in ROYGBIV order: Red, Yellow, Green, Cyan, Blue
|
|
82
|
+
RAINBOW_16 = colormap_16((5, 1, 3, 2, 6))
|
|
83
|
+
|
|
84
|
+
# ANSI 16-color 'rainbow', Reversed:
|
|
85
|
+
REV_RAINBOW_16 = colormap_16((6, 2, 3, 1, 5))
|
|
86
|
+
|
|
87
|
+
# ANSI 16-color map colors: Black, Blue, Cyan, Green, Yellow, Red, Magenta, White
|
|
88
|
+
FADE_IN_16 = colormap_16((0, 4, 6, 2, 3, 1, 5, 7))
|
|
89
|
+
|
|
90
|
+
# ANSI 256-color map colors in a grayscale black->white
|
|
91
|
+
GRAYSCALE = colormap_256([0] + list(range(232, 256)) + [15])
|
|
92
|
+
|
|
93
|
+
rainbow_256_colors = (
|
|
94
|
+
# fmt: off
|
|
95
|
+
(196, 202, 208, 214, 220, 190, 154, 118, 82, 46, 47, 48, 43, 80, 81, 39, 27, 21, 56, 91)
|
|
96
|
+
# fmt: on
|
|
97
|
+
)
|
|
98
|
+
RAINBOW = colormap_256(rainbow_256_colors)
|
|
99
|
+
REV_RAINBOW = colormap_256(tuple(reversed(rainbow_256_colors)))
|
|
100
|
+
|
|
101
|
+
BLUE_RED = colormap_256((21, 56, 91, 126, 161, 196))
|
|
102
|
+
FADE_IN = colormap_256(
|
|
103
|
+
# fmt: off
|
|
104
|
+
(16, 53, 54, 55, 56, 57, 21, 21, 27, 39, 81, 80, 43, 48, 47,
|
|
105
|
+
46, 82, 118, 154, 190, 220, 214, 208, 202, 196)
|
|
106
|
+
# fmt: on
|
|
107
|
+
)
|
|
108
|
+
HOT = colormap_256((16, 52, 88, 124, 160, 196, 202, 208, 214, 220, 226, 227, 228, 229, 230, 231))
|
|
109
|
+
COOL = colormap_256((50, 81, 111, 141, 171, 201))
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""ASCII-art support"""
|
|
2
|
+
|
|
3
|
+
from typing import Callable, Sequence
|
|
4
|
+
|
|
5
|
+
from .util import nearest
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def color_map(values: Sequence[str]) -> Callable:
|
|
9
|
+
"""Returns the closest ascii-art pixel."""
|
|
10
|
+
|
|
11
|
+
def compute_pixel_value(frac: float, _=None) -> str:
|
|
12
|
+
return nearest(values, frac)
|
|
13
|
+
|
|
14
|
+
return compute_pixel_value
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
#
|
|
18
|
+
# Some example/useful color scales
|
|
19
|
+
# Character (glyph) density is dependent on font choice, unfortunately
|
|
20
|
+
|
|
21
|
+
# Allow the all-caps colormap names:
|
|
22
|
+
# pylint: disable=invalid-name
|
|
23
|
+
DEFAULT = color_map(" .:-=+*#%@")
|
|
24
|
+
EXTENDED = color_map(" .'`^\",:;Il!i>~+?[{1(|/o*#MW&8%B$@")
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"""Axis-generation support."""
|
|
2
|
+
|
|
3
|
+
import dataclasses
|
|
4
|
+
from decimal import Decimal
|
|
5
|
+
import itertools
|
|
6
|
+
import math
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
from . import lineart
|
|
10
|
+
from .util import FloatLike, ValueRange, pick_step_size
|
|
11
|
+
|
|
12
|
+
MIN_X_TICKS_PER_LABEL = 4
|
|
13
|
+
MIN_Y_TICKS_PER_LABEL = 2
|
|
14
|
+
DEFAULT_X_COLS_PER_TICK = 4
|
|
15
|
+
DEFAULT_Y_ROWS_PER_TICK = 2
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclasses.dataclass
|
|
19
|
+
class BorderChars:
|
|
20
|
+
"""Characters to use for X/Y border"""
|
|
21
|
+
|
|
22
|
+
first: str
|
|
23
|
+
middle: str
|
|
24
|
+
last: str
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
y_border = {False: BorderChars(" ", " ", " "), True: BorderChars("╷", "│", "╵")}
|
|
28
|
+
x_border = {False: BorderChars(" ", " ", " "), True: BorderChars("╶", "─", "╴")}
|
|
29
|
+
|
|
30
|
+
###############################################
|
|
31
|
+
# Helper functions used by the Axis class below
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def add_label(line: list[str], label: str, ctr_pos: int):
|
|
35
|
+
"""Adds the label string to the output line, centered at specified position
|
|
36
|
+
The output line is a list of single-character strings, to make this kind of thing
|
|
37
|
+
straightforward"""
|
|
38
|
+
width = len(label)
|
|
39
|
+
start_col = max(ctr_pos - width // 2, 0)
|
|
40
|
+
end_col = start_col + width
|
|
41
|
+
line[start_col:end_col] = list(label)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def gen_tick_values(value_range, tick_step):
|
|
45
|
+
"""Produce tick values in the specified range. Basically numpy.arange"""
|
|
46
|
+
|
|
47
|
+
tick = math.ceil(value_range.min / tick_step) * tick_step
|
|
48
|
+
while tick <= value_range.max:
|
|
49
|
+
yield tick
|
|
50
|
+
tick += tick_step
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def gen_labels(
|
|
54
|
+
value_range: ValueRange, num_ticks, min_ticks_per_label, fmt, label_end_ticks=False
|
|
55
|
+
):
|
|
56
|
+
"""Generate positions for labels (plain ticks & ticks with value)"""
|
|
57
|
+
tick_step, label_step = pick_step_size(value_range, num_ticks, min_ticks_per_label)
|
|
58
|
+
|
|
59
|
+
ticks = list(gen_tick_values(value_range, tick_step))
|
|
60
|
+
label_values = list(gen_tick_values(value_range, label_step))
|
|
61
|
+
if label_end_ticks or len(label_values) <= 2:
|
|
62
|
+
# ensure that first & last ticks have labels:
|
|
63
|
+
if label_values[0] != ticks[0]:
|
|
64
|
+
label_values = ticks[:1] + label_values
|
|
65
|
+
if label_values[-1] != ticks[-1]:
|
|
66
|
+
label_values += ticks[-1:]
|
|
67
|
+
|
|
68
|
+
# sanity: if all but one ticks have labels, just label them all
|
|
69
|
+
if len(label_values) >= len(ticks) - 1:
|
|
70
|
+
label_values = ticks
|
|
71
|
+
|
|
72
|
+
ticks_only = {value: "" for value in ticks}
|
|
73
|
+
labeled_ticks = {value: fmt.format(value) for value in label_values}
|
|
74
|
+
|
|
75
|
+
return ticks_only | labeled_ticks
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def calc_edges(value_range, num_bins, values_are_edges):
|
|
79
|
+
"""Calculate the top/bottom or left/right values for each of 'num_bins' bins
|
|
80
|
+
|
|
81
|
+
Parameters
|
|
82
|
+
----------
|
|
83
|
+
value_range: util.ValueRange
|
|
84
|
+
Coordinate values for first/last bin
|
|
85
|
+
Can be center of bin, or outside edge (see values_are_edges)
|
|
86
|
+
num_bins: int
|
|
87
|
+
Number of bins/intervals to produce edges for
|
|
88
|
+
values_are_edges: bool
|
|
89
|
+
Indicates that value_range specifies outside edges rather than bin centers
|
|
90
|
+
"""
|
|
91
|
+
if values_are_edges:
|
|
92
|
+
bin_delta = (value_range.max - value_range.min) / num_bins
|
|
93
|
+
first_bin_min = value_range.min
|
|
94
|
+
else:
|
|
95
|
+
bin_delta = (value_range.max - value_range.min) / (num_bins - 1)
|
|
96
|
+
first_bin_min = value_range.min - (bin_delta / 2)
|
|
97
|
+
bin_edges = tuple(first_bin_min + i * bin_delta for i in range(num_bins + 1))
|
|
98
|
+
return itertools.pairwise(bin_edges)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
###############################################
|
|
102
|
+
# The User-facing interface: the Axis class
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@dataclasses.dataclass
|
|
106
|
+
class Axis:
|
|
107
|
+
"""Options for axis generation."""
|
|
108
|
+
|
|
109
|
+
value_range: ValueRange # can also specify as a tuple of (min, max)
|
|
110
|
+
labels: Optional[dict[float, str]] = None # map axis value to label (plus tick) at that value
|
|
111
|
+
label_fmt: str = "{}" # format for generated labels
|
|
112
|
+
border_line: bool = False # embed ticks in a horizontal X-axis or vertical Y-axis line
|
|
113
|
+
values_are_edges: bool = False # N+1 values, indicating boundaries between pixels, not centers
|
|
114
|
+
fractional_tick_pos: bool = False # Use "▔", "▁", or "╱╲" for non-centered ticks
|
|
115
|
+
|
|
116
|
+
def __init__(
|
|
117
|
+
self,
|
|
118
|
+
value_range: ValueRange | tuple[FloatLike, FloatLike],
|
|
119
|
+
labels: Optional[dict[float, str]] = None,
|
|
120
|
+
label_fmt: str = "{}",
|
|
121
|
+
border_line: bool = False,
|
|
122
|
+
values_are_edges: bool = False,
|
|
123
|
+
fractional_tick_pos: bool = False,
|
|
124
|
+
# pylint: disable=too-many-arguments,too-many-positional-arguments
|
|
125
|
+
):
|
|
126
|
+
# Sanitize value_range: allow user to provide it as a tuple of FloatLike (without
|
|
127
|
+
# needing to import ValueRange), and convert to ValueRange(Decimal, Decimal)
|
|
128
|
+
self.value_range = ValueRange(
|
|
129
|
+
Decimal(float(value_range[0])), Decimal(float(value_range[1]))
|
|
130
|
+
)
|
|
131
|
+
self.labels = labels
|
|
132
|
+
self.label_fmt = label_fmt
|
|
133
|
+
self.border_line = border_line
|
|
134
|
+
self.values_are_edges = values_are_edges
|
|
135
|
+
self.fractional_tick_pos = fractional_tick_pos
|
|
136
|
+
|
|
137
|
+
def _unjustified_y_axis(self, num_rows: int):
|
|
138
|
+
"""Returns the Y axis string for each line of the plot"""
|
|
139
|
+
if self.labels is None:
|
|
140
|
+
labels = gen_labels(
|
|
141
|
+
self.value_range,
|
|
142
|
+
num_rows // DEFAULT_Y_ROWS_PER_TICK,
|
|
143
|
+
MIN_Y_TICKS_PER_LABEL,
|
|
144
|
+
self.label_fmt,
|
|
145
|
+
)
|
|
146
|
+
else:
|
|
147
|
+
labels = self.labels
|
|
148
|
+
|
|
149
|
+
label_values = sorted(labels.keys())
|
|
150
|
+
bins = calc_edges(self.value_range, num_rows, self.values_are_edges)
|
|
151
|
+
|
|
152
|
+
use_combining = self.border_line and self.fractional_tick_pos
|
|
153
|
+
for row_min, row_max in bins:
|
|
154
|
+
if label_values and row_min <= label_values[0] <= row_max:
|
|
155
|
+
label_str = labels[label_values[0]]
|
|
156
|
+
|
|
157
|
+
offset_frac = (label_values[0] - row_min) / (row_max - row_min)
|
|
158
|
+
if offset_frac < 0.25 and self.fractional_tick_pos:
|
|
159
|
+
tick_char = "▔"
|
|
160
|
+
elif offset_frac > 0.75 and self.fractional_tick_pos:
|
|
161
|
+
tick_char = "▁"
|
|
162
|
+
else:
|
|
163
|
+
tick_char = "─"
|
|
164
|
+
label_str += lineart.merge_chars(
|
|
165
|
+
tick_char,
|
|
166
|
+
y_border[self.border_line].middle,
|
|
167
|
+
use_combining_unicode=use_combining,
|
|
168
|
+
)
|
|
169
|
+
yield label_str
|
|
170
|
+
label_values = label_values[1:]
|
|
171
|
+
else:
|
|
172
|
+
yield y_border[self.border_line].middle
|
|
173
|
+
|
|
174
|
+
def render_as_y(self, num_rows: int, pad_top: bool, pad_bot: bool, flip: bool):
|
|
175
|
+
"""Create a Y axis as a list of strings for the left margin of a plot
|
|
176
|
+
|
|
177
|
+
Parameters
|
|
178
|
+
----------
|
|
179
|
+
num_rows: int
|
|
180
|
+
Number of data rows
|
|
181
|
+
pad_top: bool
|
|
182
|
+
Emit a line for an X axis line/row at the top
|
|
183
|
+
pad_bot: bool
|
|
184
|
+
Emit a line for an X axis line/row at the bottom
|
|
185
|
+
flip: bool
|
|
186
|
+
Put the minimum Y on the last line rather than the first
|
|
187
|
+
"""
|
|
188
|
+
unpadded_labels = list(self._unjustified_y_axis(num_rows))
|
|
189
|
+
if flip:
|
|
190
|
+
unpadded_labels = [
|
|
191
|
+
s.translate(lineart.flip_vertical) for s in reversed(unpadded_labels)
|
|
192
|
+
]
|
|
193
|
+
|
|
194
|
+
if pad_top:
|
|
195
|
+
unpadded_labels = [y_border[self.border_line].first] + unpadded_labels
|
|
196
|
+
if pad_bot:
|
|
197
|
+
unpadded_labels = unpadded_labels + [y_border[self.border_line].last]
|
|
198
|
+
|
|
199
|
+
lengths = [lineart.display_len(label_str) for label_str in unpadded_labels]
|
|
200
|
+
max_width = max(lengths)
|
|
201
|
+
pad_lengths = [max_width - length for length in lengths]
|
|
202
|
+
padded_labels = [
|
|
203
|
+
" " * pad_length + label_str
|
|
204
|
+
for (label_str, pad_length) in zip(unpadded_labels, pad_lengths)
|
|
205
|
+
]
|
|
206
|
+
return padded_labels
|
|
207
|
+
|
|
208
|
+
def render_as_x(self, num_cols: int, left_margin: int):
|
|
209
|
+
"""Generate X tick line and X label line.
|
|
210
|
+
|
|
211
|
+
Parameters
|
|
212
|
+
----------
|
|
213
|
+
num_cols: int
|
|
214
|
+
Number of data columns
|
|
215
|
+
left_margin: int
|
|
216
|
+
chars to the left of leftmost data col. May have Labels/border-line.
|
|
217
|
+
"""
|
|
218
|
+
|
|
219
|
+
if self.labels is None:
|
|
220
|
+
labels = gen_labels(
|
|
221
|
+
self.value_range,
|
|
222
|
+
num_cols // DEFAULT_X_COLS_PER_TICK,
|
|
223
|
+
MIN_X_TICKS_PER_LABEL,
|
|
224
|
+
self.label_fmt,
|
|
225
|
+
)
|
|
226
|
+
else:
|
|
227
|
+
labels = self.labels
|
|
228
|
+
|
|
229
|
+
label_values = sorted(labels.keys())
|
|
230
|
+
|
|
231
|
+
bins = calc_edges(self.value_range, num_cols, self.values_are_edges)
|
|
232
|
+
|
|
233
|
+
tick_line = list(
|
|
234
|
+
" " * (left_margin - 1)
|
|
235
|
+
+ x_border[self.border_line].first
|
|
236
|
+
+ x_border[self.border_line].middle * num_cols
|
|
237
|
+
+ x_border[self.border_line].last
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
label_line = [" "] * len(tick_line) # labels under the ticks
|
|
241
|
+
|
|
242
|
+
for col_idx, (col_min, col_max) in enumerate(bins):
|
|
243
|
+
# use Decimal.next_plus to accomodate rounding error/truncation
|
|
244
|
+
if label_values and col_min <= label_values[0] <= col_max.next_plus():
|
|
245
|
+
add_label(label_line, labels[label_values[0]], col_idx + left_margin)
|
|
246
|
+
tick_idx = left_margin + col_idx
|
|
247
|
+
offset_frac = (label_values[0] - col_min) / (col_max - col_min)
|
|
248
|
+
if self.fractional_tick_pos and offset_frac < 0.25:
|
|
249
|
+
if col_idx == 0:
|
|
250
|
+
tick_line[tick_idx - 1] = lineart.merge_chars("│", tick_line[tick_idx - 1])
|
|
251
|
+
else:
|
|
252
|
+
tick_line[tick_idx - 1] = "╱"
|
|
253
|
+
tick_line[tick_idx] = "╲"
|
|
254
|
+
elif self.fractional_tick_pos and offset_frac > 0.75:
|
|
255
|
+
tick_line[tick_idx] = "╱"
|
|
256
|
+
if col_idx < num_cols - 1:
|
|
257
|
+
tick_line[tick_idx + 1] = "╲"
|
|
258
|
+
else:
|
|
259
|
+
tick_line[tick_idx + 1] = lineart.merge_chars("│", tick_line[tick_idx + 1])
|
|
260
|
+
else:
|
|
261
|
+
tick_line[tick_idx] = lineart.merge_chars("│", tick_line[tick_idx])
|
|
262
|
+
|
|
263
|
+
label_values = label_values[1:] # pop that first label since we added it
|
|
264
|
+
|
|
265
|
+
return "".join(tick_line), "".join(label_line)
|