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.
Files changed (324) hide show
  1. densitty-0.8.2/.gitignore +13 -0
  2. densitty-0.8.2/LICENSE +21 -0
  3. densitty-0.8.2/Makefile +57 -0
  4. densitty-0.8.2/PKG-INFO +36 -0
  5. densitty-0.8.2/README.md +14 -0
  6. densitty-0.8.2/densitty/__init__.py +0 -0
  7. densitty-0.8.2/densitty/ansi.py +109 -0
  8. densitty-0.8.2/densitty/ascii_art.py +24 -0
  9. densitty-0.8.2/densitty/axis.py +265 -0
  10. densitty-0.8.2/densitty/binning.py +240 -0
  11. densitty-0.8.2/densitty/detect.py +465 -0
  12. densitty-0.8.2/densitty/lineart.py +130 -0
  13. densitty-0.8.2/densitty/plot.py +201 -0
  14. densitty-0.8.2/densitty/truecolor.py +170 -0
  15. densitty-0.8.2/densitty/util.py +234 -0
  16. densitty-0.8.2/densitty.egg-info/PKG-INFO +36 -0
  17. densitty-0.8.2/densitty.egg-info/SOURCES.txt +322 -0
  18. densitty-0.8.2/densitty.egg-info/dependency_links.txt +1 -0
  19. densitty-0.8.2/densitty.egg-info/top_level.txt +1 -0
  20. densitty-0.8.2/docs/FAQ.md +12 -0
  21. densitty-0.8.2/docs/examples.md +68 -0
  22. densitty-0.8.2/docs/eyeplot.png +0 -0
  23. densitty-0.8.2/docs/hist2d-axes.png +0 -0
  24. densitty-0.8.2/docs/hist2d-basic.png +0 -0
  25. densitty-0.8.2/docs/hist2d-finer-color-borderline.png +0 -0
  26. densitty-0.8.2/docs/hist2d-helper.png +0 -0
  27. densitty-0.8.2/docs/hist2d-scaled.png +0 -0
  28. densitty-0.8.2/docs/inline-histplot2d.png +0 -0
  29. densitty-0.8.2/docs/terminal_support.md +50 -0
  30. densitty-0.8.2/docs/usage.md +38 -0
  31. densitty-0.8.2/examples/eye_plot.py +46 -0
  32. densitty-0.8.2/examples/run_examples.py +70 -0
  33. densitty-0.8.2/pyproject.toml +71 -0
  34. densitty-0.8.2/setup.cfg +4 -0
  35. densitty-0.8.2/tests/#lineart_tests.py# +132 -0
  36. densitty-0.8.2/tests/axis_tests.py +160 -0
  37. densitty-0.8.2/tests/binning_tests.py +169 -0
  38. densitty-0.8.2/tests/color_tests.py +129 -0
  39. densitty-0.8.2/tests/convert_tests.py +198 -0
  40. densitty-0.8.2/tests/detect_tests.py +48 -0
  41. densitty-0.8.2/tests/gen_norm_data.py +25 -0
  42. densitty-0.8.2/tests/golden.py +31 -0
  43. densitty-0.8.2/tests/golden_diff.py +171 -0
  44. densitty-0.8.2/tests/goldens/ansi.BLUE_RED +1 -0
  45. densitty-0.8.2/tests/goldens/ansi.BLUE_RED_halfheight +1 -0
  46. densitty-0.8.2/tests/goldens/ansi.COOL +1 -0
  47. densitty-0.8.2/tests/goldens/ansi.COOL_halfheight +1 -0
  48. densitty-0.8.2/tests/goldens/ansi.FADE_IN +1 -0
  49. densitty-0.8.2/tests/goldens/ansi.FADE_IN_halfheight +1 -0
  50. densitty-0.8.2/tests/goldens/ansi.GRAYSCALE +1 -0
  51. densitty-0.8.2/tests/goldens/ansi.GRAYSCALE_halfheight +1 -0
  52. densitty-0.8.2/tests/goldens/ansi.HOT +1 -0
  53. densitty-0.8.2/tests/goldens/ansi.HOT_halfheight +1 -0
  54. densitty-0.8.2/tests/goldens/ansi.RAINBOW +1 -0
  55. densitty-0.8.2/tests/goldens/ansi.RAINBOW_16 +1 -0
  56. densitty-0.8.2/tests/goldens/ansi.RAINBOW_16_halfheight +1 -0
  57. densitty-0.8.2/tests/goldens/ansi.RAINBOW_halfheight +1 -0
  58. densitty-0.8.2/tests/goldens/ascii-map +1 -0
  59. densitty-0.8.2/tests/goldens/auto_allzero +1 -0
  60. densitty-0.8.2/tests/goldens/auto_color_limits +1 -0
  61. densitty-0.8.2/tests/goldens/halfheight +1 -0
  62. densitty-0.8.2/tests/goldens/halfheight_single_row +1 -0
  63. densitty-0.8.2/tests/goldens/merge_chars +1 -0
  64. densitty-0.8.2/tests/goldens/merge_lines +1 -0
  65. densitty-0.8.2/tests/goldens/simple_glyph_ascii +1 -0
  66. densitty-0.8.2/tests/goldens/simple_glyph_basic +1 -0
  67. densitty-0.8.2/tests/goldens/simple_glyph_extended +1 -0
  68. densitty-0.8.2/tests/goldens/test_axes-False-(-1-0)-ext-False-False-False-False +1 -0
  69. densitty-0.8.2/tests/goldens/test_axes-False-(-1-0)-ext-False-False-False-True +1 -0
  70. densitty-0.8.2/tests/goldens/test_axes-False-(-1-0)-ext-False-False-True-False +1 -0
  71. densitty-0.8.2/tests/goldens/test_axes-False-(-1-0)-ext-False-False-True-True +1 -0
  72. densitty-0.8.2/tests/goldens/test_axes-False-(-1-0)-ext-False-True-False-False +1 -0
  73. densitty-0.8.2/tests/goldens/test_axes-False-(-1-0)-ext-False-True-False-True +1 -0
  74. densitty-0.8.2/tests/goldens/test_axes-False-(-1-0)-ext-False-True-True-False +1 -0
  75. densitty-0.8.2/tests/goldens/test_axes-False-(-1-0)-ext-False-True-True-True +1 -0
  76. densitty-0.8.2/tests/goldens/test_axes-False-(-1-0)-ext-True-False-False-False +1 -0
  77. densitty-0.8.2/tests/goldens/test_axes-False-(-1-0)-ext-True-False-False-True +1 -0
  78. densitty-0.8.2/tests/goldens/test_axes-False-(-1-0)-ext-True-False-True-False +1 -0
  79. densitty-0.8.2/tests/goldens/test_axes-False-(-1-0)-ext-True-False-True-True +1 -0
  80. densitty-0.8.2/tests/goldens/test_axes-False-(-1-0)-ext-True-True-False-False +1 -0
  81. densitty-0.8.2/tests/goldens/test_axes-False-(-1-0)-ext-True-True-False-True +1 -0
  82. densitty-0.8.2/tests/goldens/test_axes-False-(-1-0)-ext-True-True-True-False +1 -0
  83. densitty-0.8.2/tests/goldens/test_axes-False-(-1-0)-ext-True-True-True-True +1 -0
  84. densitty-0.8.2/tests/goldens/test_axes-False-(-1-0.1)-ext-False-False-False-False +1 -0
  85. densitty-0.8.2/tests/goldens/test_axes-False-(-1-0.1)-ext-False-False-False-True +1 -0
  86. densitty-0.8.2/tests/goldens/test_axes-False-(-1-0.1)-ext-False-False-True-False +1 -0
  87. densitty-0.8.2/tests/goldens/test_axes-False-(-1-0.1)-ext-False-False-True-True +1 -0
  88. densitty-0.8.2/tests/goldens/test_axes-False-(-1-0.1)-ext-False-True-False-False +1 -0
  89. densitty-0.8.2/tests/goldens/test_axes-False-(-1-0.1)-ext-False-True-False-True +1 -0
  90. densitty-0.8.2/tests/goldens/test_axes-False-(-1-0.1)-ext-False-True-True-False +1 -0
  91. densitty-0.8.2/tests/goldens/test_axes-False-(-1-0.1)-ext-False-True-True-True +1 -0
  92. densitty-0.8.2/tests/goldens/test_axes-False-(-1-0.1)-ext-True-False-False-False +1 -0
  93. densitty-0.8.2/tests/goldens/test_axes-False-(-1-0.1)-ext-True-False-False-True +1 -0
  94. densitty-0.8.2/tests/goldens/test_axes-False-(-1-0.1)-ext-True-False-True-False +1 -0
  95. densitty-0.8.2/tests/goldens/test_axes-False-(-1-0.1)-ext-True-False-True-True +1 -0
  96. densitty-0.8.2/tests/goldens/test_axes-False-(-1-0.1)-ext-True-True-False-False +1 -0
  97. densitty-0.8.2/tests/goldens/test_axes-False-(-1-0.1)-ext-True-True-False-True +1 -0
  98. densitty-0.8.2/tests/goldens/test_axes-False-(-1-0.1)-ext-True-True-True-False +1 -0
  99. densitty-0.8.2/tests/goldens/test_axes-False-(-1-0.1)-ext-True-True-True-True +1 -0
  100. densitty-0.8.2/tests/goldens/test_axes-False-(-1-1)-ext-False-False-False-False +1 -0
  101. densitty-0.8.2/tests/goldens/test_axes-False-(-1-1)-ext-False-False-False-True +1 -0
  102. densitty-0.8.2/tests/goldens/test_axes-False-(-1-1)-ext-False-False-True-False +1 -0
  103. densitty-0.8.2/tests/goldens/test_axes-False-(-1-1)-ext-False-False-True-True +1 -0
  104. densitty-0.8.2/tests/goldens/test_axes-False-(-1-1)-ext-False-True-False-False +1 -0
  105. densitty-0.8.2/tests/goldens/test_axes-False-(-1-1)-ext-False-True-False-True +1 -0
  106. densitty-0.8.2/tests/goldens/test_axes-False-(-1-1)-ext-False-True-True-False +1 -0
  107. densitty-0.8.2/tests/goldens/test_axes-False-(-1-1)-ext-False-True-True-True +1 -0
  108. densitty-0.8.2/tests/goldens/test_axes-False-(-1-1)-ext-True-False-False-False +1 -0
  109. densitty-0.8.2/tests/goldens/test_axes-False-(-1-1)-ext-True-False-False-True +1 -0
  110. densitty-0.8.2/tests/goldens/test_axes-False-(-1-1)-ext-True-False-True-False +1 -0
  111. densitty-0.8.2/tests/goldens/test_axes-False-(-1-1)-ext-True-False-True-True +1 -0
  112. densitty-0.8.2/tests/goldens/test_axes-False-(-1-1)-ext-True-True-False-False +1 -0
  113. densitty-0.8.2/tests/goldens/test_axes-False-(-1-1)-ext-True-True-False-True +1 -0
  114. densitty-0.8.2/tests/goldens/test_axes-False-(-1-1)-ext-True-True-True-False +1 -0
  115. densitty-0.8.2/tests/goldens/test_axes-False-(-1-1)-ext-True-True-True-True +1 -0
  116. densitty-0.8.2/tests/goldens/test_axes-False-(-1-1.5)-ext-False-False-False-False +1 -0
  117. densitty-0.8.2/tests/goldens/test_axes-False-(-1-1.5)-ext-False-False-False-True +1 -0
  118. densitty-0.8.2/tests/goldens/test_axes-False-(-1-1.5)-ext-False-False-True-False +1 -0
  119. densitty-0.8.2/tests/goldens/test_axes-False-(-1-1.5)-ext-False-False-True-True +1 -0
  120. densitty-0.8.2/tests/goldens/test_axes-False-(-1-1.5)-ext-False-True-False-False +1 -0
  121. densitty-0.8.2/tests/goldens/test_axes-False-(-1-1.5)-ext-False-True-False-True +1 -0
  122. densitty-0.8.2/tests/goldens/test_axes-False-(-1-1.5)-ext-False-True-True-False +1 -0
  123. densitty-0.8.2/tests/goldens/test_axes-False-(-1-1.5)-ext-False-True-True-True +1 -0
  124. densitty-0.8.2/tests/goldens/test_axes-False-(-1-1.5)-ext-True-False-False-False +1 -0
  125. densitty-0.8.2/tests/goldens/test_axes-False-(-1-1.5)-ext-True-False-False-True +1 -0
  126. densitty-0.8.2/tests/goldens/test_axes-False-(-1-1.5)-ext-True-False-True-False +1 -0
  127. densitty-0.8.2/tests/goldens/test_axes-False-(-1-1.5)-ext-True-False-True-True +1 -0
  128. densitty-0.8.2/tests/goldens/test_axes-False-(-1-1.5)-ext-True-True-False-False +1 -0
  129. densitty-0.8.2/tests/goldens/test_axes-False-(-1-1.5)-ext-True-True-False-True +1 -0
  130. densitty-0.8.2/tests/goldens/test_axes-False-(-1-1.5)-ext-True-True-True-False +1 -0
  131. densitty-0.8.2/tests/goldens/test_axes-False-(-1-1.5)-ext-True-True-True-True +1 -0
  132. densitty-0.8.2/tests/goldens/test_axes-False-(-1-10)-ext-False-False-False-False +1 -0
  133. densitty-0.8.2/tests/goldens/test_axes-False-(-1-10)-ext-False-False-False-True +1 -0
  134. densitty-0.8.2/tests/goldens/test_axes-False-(-1-10)-ext-False-False-True-False +1 -0
  135. densitty-0.8.2/tests/goldens/test_axes-False-(-1-10)-ext-False-False-True-True +1 -0
  136. densitty-0.8.2/tests/goldens/test_axes-False-(-1-10)-ext-False-True-False-False +1 -0
  137. densitty-0.8.2/tests/goldens/test_axes-False-(-1-10)-ext-False-True-False-True +1 -0
  138. densitty-0.8.2/tests/goldens/test_axes-False-(-1-10)-ext-False-True-True-False +1 -0
  139. densitty-0.8.2/tests/goldens/test_axes-False-(-1-10)-ext-False-True-True-True +1 -0
  140. densitty-0.8.2/tests/goldens/test_axes-False-(-1-10)-ext-True-False-False-False +1 -0
  141. densitty-0.8.2/tests/goldens/test_axes-False-(-1-10)-ext-True-False-False-True +1 -0
  142. densitty-0.8.2/tests/goldens/test_axes-False-(-1-10)-ext-True-False-True-False +1 -0
  143. densitty-0.8.2/tests/goldens/test_axes-False-(-1-10)-ext-True-False-True-True +1 -0
  144. densitty-0.8.2/tests/goldens/test_axes-False-(-1-10)-ext-True-True-False-False +1 -0
  145. densitty-0.8.2/tests/goldens/test_axes-False-(-1-10)-ext-True-True-False-True +1 -0
  146. densitty-0.8.2/tests/goldens/test_axes-False-(-1-10)-ext-True-True-True-False +1 -0
  147. densitty-0.8.2/tests/goldens/test_axes-False-(-1-10)-ext-True-True-True-True +1 -0
  148. densitty-0.8.2/tests/goldens/test_axes-False-(-1-2)-ext-False-False-False-False +1 -0
  149. densitty-0.8.2/tests/goldens/test_axes-False-(-1-2)-ext-False-False-False-True +1 -0
  150. densitty-0.8.2/tests/goldens/test_axes-False-(-1-2)-ext-False-False-True-False +1 -0
  151. densitty-0.8.2/tests/goldens/test_axes-False-(-1-2)-ext-False-False-True-True +1 -0
  152. densitty-0.8.2/tests/goldens/test_axes-False-(-1-2)-ext-False-True-False-False +1 -0
  153. densitty-0.8.2/tests/goldens/test_axes-False-(-1-2)-ext-False-True-False-True +1 -0
  154. densitty-0.8.2/tests/goldens/test_axes-False-(-1-2)-ext-False-True-True-False +1 -0
  155. densitty-0.8.2/tests/goldens/test_axes-False-(-1-2)-ext-False-True-True-True +1 -0
  156. densitty-0.8.2/tests/goldens/test_axes-False-(-1-2)-ext-True-False-False-False +1 -0
  157. densitty-0.8.2/tests/goldens/test_axes-False-(-1-2)-ext-True-False-False-True +1 -0
  158. densitty-0.8.2/tests/goldens/test_axes-False-(-1-2)-ext-True-False-True-False +1 -0
  159. densitty-0.8.2/tests/goldens/test_axes-False-(-1-2)-ext-True-False-True-True +1 -0
  160. densitty-0.8.2/tests/goldens/test_axes-False-(-1-2)-ext-True-True-False-False +1 -0
  161. densitty-0.8.2/tests/goldens/test_axes-False-(-1-2)-ext-True-True-False-True +1 -0
  162. densitty-0.8.2/tests/goldens/test_axes-False-(-1-2)-ext-True-True-True-False +1 -0
  163. densitty-0.8.2/tests/goldens/test_axes-False-(-1-2)-ext-True-True-True-True +1 -0
  164. densitty-0.8.2/tests/goldens/test_axes-True-(-1-0)-ext-False-False-False-False +1 -0
  165. densitty-0.8.2/tests/goldens/test_axes-True-(-1-0)-ext-False-False-False-True +1 -0
  166. densitty-0.8.2/tests/goldens/test_axes-True-(-1-0)-ext-False-False-True-False +1 -0
  167. densitty-0.8.2/tests/goldens/test_axes-True-(-1-0)-ext-False-False-True-True +1 -0
  168. densitty-0.8.2/tests/goldens/test_axes-True-(-1-0)-ext-False-True-False-False +1 -0
  169. densitty-0.8.2/tests/goldens/test_axes-True-(-1-0)-ext-False-True-False-True +1 -0
  170. densitty-0.8.2/tests/goldens/test_axes-True-(-1-0)-ext-False-True-True-False +1 -0
  171. densitty-0.8.2/tests/goldens/test_axes-True-(-1-0)-ext-False-True-True-True +1 -0
  172. densitty-0.8.2/tests/goldens/test_axes-True-(-1-0)-ext-True-False-False-False +1 -0
  173. densitty-0.8.2/tests/goldens/test_axes-True-(-1-0)-ext-True-False-False-True +1 -0
  174. densitty-0.8.2/tests/goldens/test_axes-True-(-1-0)-ext-True-False-True-False +1 -0
  175. densitty-0.8.2/tests/goldens/test_axes-True-(-1-0)-ext-True-False-True-True +1 -0
  176. densitty-0.8.2/tests/goldens/test_axes-True-(-1-0)-ext-True-True-False-False +1 -0
  177. densitty-0.8.2/tests/goldens/test_axes-True-(-1-0)-ext-True-True-False-True +1 -0
  178. densitty-0.8.2/tests/goldens/test_axes-True-(-1-0)-ext-True-True-True-False +1 -0
  179. densitty-0.8.2/tests/goldens/test_axes-True-(-1-0)-ext-True-True-True-True +1 -0
  180. densitty-0.8.2/tests/goldens/test_axes-True-(-1-0.1)-ext-False-False-False-False +1 -0
  181. densitty-0.8.2/tests/goldens/test_axes-True-(-1-0.1)-ext-False-False-False-True +1 -0
  182. densitty-0.8.2/tests/goldens/test_axes-True-(-1-0.1)-ext-False-False-True-False +1 -0
  183. densitty-0.8.2/tests/goldens/test_axes-True-(-1-0.1)-ext-False-False-True-True +1 -0
  184. densitty-0.8.2/tests/goldens/test_axes-True-(-1-0.1)-ext-False-True-False-False +1 -0
  185. densitty-0.8.2/tests/goldens/test_axes-True-(-1-0.1)-ext-False-True-False-True +1 -0
  186. densitty-0.8.2/tests/goldens/test_axes-True-(-1-0.1)-ext-False-True-True-False +1 -0
  187. densitty-0.8.2/tests/goldens/test_axes-True-(-1-0.1)-ext-False-True-True-True +1 -0
  188. densitty-0.8.2/tests/goldens/test_axes-True-(-1-0.1)-ext-True-False-False-False +1 -0
  189. densitty-0.8.2/tests/goldens/test_axes-True-(-1-0.1)-ext-True-False-False-True +1 -0
  190. densitty-0.8.2/tests/goldens/test_axes-True-(-1-0.1)-ext-True-False-True-False +1 -0
  191. densitty-0.8.2/tests/goldens/test_axes-True-(-1-0.1)-ext-True-False-True-True +1 -0
  192. densitty-0.8.2/tests/goldens/test_axes-True-(-1-0.1)-ext-True-True-False-False +1 -0
  193. densitty-0.8.2/tests/goldens/test_axes-True-(-1-0.1)-ext-True-True-False-True +1 -0
  194. densitty-0.8.2/tests/goldens/test_axes-True-(-1-0.1)-ext-True-True-True-False +1 -0
  195. densitty-0.8.2/tests/goldens/test_axes-True-(-1-0.1)-ext-True-True-True-True +1 -0
  196. densitty-0.8.2/tests/goldens/test_axes-True-(-1-1)-ext-False-False-False-False +1 -0
  197. densitty-0.8.2/tests/goldens/test_axes-True-(-1-1)-ext-False-False-False-True +1 -0
  198. densitty-0.8.2/tests/goldens/test_axes-True-(-1-1)-ext-False-False-True-False +1 -0
  199. densitty-0.8.2/tests/goldens/test_axes-True-(-1-1)-ext-False-False-True-True +1 -0
  200. densitty-0.8.2/tests/goldens/test_axes-True-(-1-1)-ext-False-True-False-False +1 -0
  201. densitty-0.8.2/tests/goldens/test_axes-True-(-1-1)-ext-False-True-False-True +1 -0
  202. densitty-0.8.2/tests/goldens/test_axes-True-(-1-1)-ext-False-True-True-False +1 -0
  203. densitty-0.8.2/tests/goldens/test_axes-True-(-1-1)-ext-False-True-True-True +1 -0
  204. densitty-0.8.2/tests/goldens/test_axes-True-(-1-1)-ext-True-False-False-False +1 -0
  205. densitty-0.8.2/tests/goldens/test_axes-True-(-1-1)-ext-True-False-False-True +1 -0
  206. densitty-0.8.2/tests/goldens/test_axes-True-(-1-1)-ext-True-False-True-False +1 -0
  207. densitty-0.8.2/tests/goldens/test_axes-True-(-1-1)-ext-True-False-True-True +1 -0
  208. densitty-0.8.2/tests/goldens/test_axes-True-(-1-1)-ext-True-True-False-False +1 -0
  209. densitty-0.8.2/tests/goldens/test_axes-True-(-1-1)-ext-True-True-False-True +1 -0
  210. densitty-0.8.2/tests/goldens/test_axes-True-(-1-1)-ext-True-True-True-False +1 -0
  211. densitty-0.8.2/tests/goldens/test_axes-True-(-1-1)-ext-True-True-True-True +1 -0
  212. densitty-0.8.2/tests/goldens/test_axes-True-(-1-1.5)-ext-False-False-False-False +1 -0
  213. densitty-0.8.2/tests/goldens/test_axes-True-(-1-1.5)-ext-False-False-False-True +1 -0
  214. densitty-0.8.2/tests/goldens/test_axes-True-(-1-1.5)-ext-False-False-True-False +1 -0
  215. densitty-0.8.2/tests/goldens/test_axes-True-(-1-1.5)-ext-False-False-True-True +1 -0
  216. densitty-0.8.2/tests/goldens/test_axes-True-(-1-1.5)-ext-False-True-False-False +1 -0
  217. densitty-0.8.2/tests/goldens/test_axes-True-(-1-1.5)-ext-False-True-False-True +1 -0
  218. densitty-0.8.2/tests/goldens/test_axes-True-(-1-1.5)-ext-False-True-True-False +1 -0
  219. densitty-0.8.2/tests/goldens/test_axes-True-(-1-1.5)-ext-False-True-True-True +1 -0
  220. densitty-0.8.2/tests/goldens/test_axes-True-(-1-1.5)-ext-True-False-False-False +1 -0
  221. densitty-0.8.2/tests/goldens/test_axes-True-(-1-1.5)-ext-True-False-False-True +1 -0
  222. densitty-0.8.2/tests/goldens/test_axes-True-(-1-1.5)-ext-True-False-True-False +1 -0
  223. densitty-0.8.2/tests/goldens/test_axes-True-(-1-1.5)-ext-True-False-True-True +1 -0
  224. densitty-0.8.2/tests/goldens/test_axes-True-(-1-1.5)-ext-True-True-False-False +1 -0
  225. densitty-0.8.2/tests/goldens/test_axes-True-(-1-1.5)-ext-True-True-False-True +1 -0
  226. densitty-0.8.2/tests/goldens/test_axes-True-(-1-1.5)-ext-True-True-True-False +1 -0
  227. densitty-0.8.2/tests/goldens/test_axes-True-(-1-1.5)-ext-True-True-True-True +1 -0
  228. densitty-0.8.2/tests/goldens/test_axes-True-(-1-10)-ext-False-False-False-False +1 -0
  229. densitty-0.8.2/tests/goldens/test_axes-True-(-1-10)-ext-False-False-False-True +1 -0
  230. densitty-0.8.2/tests/goldens/test_axes-True-(-1-10)-ext-False-False-True-False +1 -0
  231. densitty-0.8.2/tests/goldens/test_axes-True-(-1-10)-ext-False-False-True-True +1 -0
  232. densitty-0.8.2/tests/goldens/test_axes-True-(-1-10)-ext-False-True-False-False +1 -0
  233. densitty-0.8.2/tests/goldens/test_axes-True-(-1-10)-ext-False-True-False-True +1 -0
  234. densitty-0.8.2/tests/goldens/test_axes-True-(-1-10)-ext-False-True-True-False +1 -0
  235. densitty-0.8.2/tests/goldens/test_axes-True-(-1-10)-ext-False-True-True-True +1 -0
  236. densitty-0.8.2/tests/goldens/test_axes-True-(-1-10)-ext-True-False-False-False +1 -0
  237. densitty-0.8.2/tests/goldens/test_axes-True-(-1-10)-ext-True-False-False-True +1 -0
  238. densitty-0.8.2/tests/goldens/test_axes-True-(-1-10)-ext-True-False-True-False +1 -0
  239. densitty-0.8.2/tests/goldens/test_axes-True-(-1-10)-ext-True-False-True-True +1 -0
  240. densitty-0.8.2/tests/goldens/test_axes-True-(-1-10)-ext-True-True-False-False +1 -0
  241. densitty-0.8.2/tests/goldens/test_axes-True-(-1-10)-ext-True-True-False-True +1 -0
  242. densitty-0.8.2/tests/goldens/test_axes-True-(-1-10)-ext-True-True-True-False +1 -0
  243. densitty-0.8.2/tests/goldens/test_axes-True-(-1-10)-ext-True-True-True-True +1 -0
  244. densitty-0.8.2/tests/goldens/test_axes-True-(-1-2)-ext-False-False-False-False +1 -0
  245. densitty-0.8.2/tests/goldens/test_axes-True-(-1-2)-ext-False-False-False-True +1 -0
  246. densitty-0.8.2/tests/goldens/test_axes-True-(-1-2)-ext-False-False-True-False +1 -0
  247. densitty-0.8.2/tests/goldens/test_axes-True-(-1-2)-ext-False-False-True-True +1 -0
  248. densitty-0.8.2/tests/goldens/test_axes-True-(-1-2)-ext-False-True-False-False +1 -0
  249. densitty-0.8.2/tests/goldens/test_axes-True-(-1-2)-ext-False-True-False-True +1 -0
  250. densitty-0.8.2/tests/goldens/test_axes-True-(-1-2)-ext-False-True-True-False +1 -0
  251. densitty-0.8.2/tests/goldens/test_axes-True-(-1-2)-ext-False-True-True-True +1 -0
  252. densitty-0.8.2/tests/goldens/test_axes-True-(-1-2)-ext-True-False-False-False +1 -0
  253. densitty-0.8.2/tests/goldens/test_axes-True-(-1-2)-ext-True-False-False-True +1 -0
  254. densitty-0.8.2/tests/goldens/test_axes-True-(-1-2)-ext-True-False-True-False +1 -0
  255. densitty-0.8.2/tests/goldens/test_axes-True-(-1-2)-ext-True-False-True-True +1 -0
  256. densitty-0.8.2/tests/goldens/test_axes-True-(-1-2)-ext-True-True-False-False +1 -0
  257. densitty-0.8.2/tests/goldens/test_axes-True-(-1-2)-ext-True-True-False-True +1 -0
  258. densitty-0.8.2/tests/goldens/test_axes-True-(-1-2)-ext-True-True-True-False +1 -0
  259. densitty-0.8.2/tests/goldens/test_axes-True-(-1-2)-ext-True-True-True-True +1 -0
  260. densitty-0.8.2/tests/goldens/test_axes_ascii-False-False-False-False +1 -0
  261. densitty-0.8.2/tests/goldens/test_axes_ascii-False-False-False-True +1 -0
  262. densitty-0.8.2/tests/goldens/test_axes_ascii-False-False-True-False +1 -0
  263. densitty-0.8.2/tests/goldens/test_axes_ascii-False-False-True-True +1 -0
  264. densitty-0.8.2/tests/goldens/test_axes_ascii-False-True-False-False +1 -0
  265. densitty-0.8.2/tests/goldens/test_axes_ascii-False-True-False-True +1 -0
  266. densitty-0.8.2/tests/goldens/test_axes_ascii-False-True-True-False +1 -0
  267. densitty-0.8.2/tests/goldens/test_axes_ascii-False-True-True-True +1 -0
  268. densitty-0.8.2/tests/goldens/test_axes_ascii-True-False-False-False +1 -0
  269. densitty-0.8.2/tests/goldens/test_axes_ascii-True-False-False-True +1 -0
  270. densitty-0.8.2/tests/goldens/test_axes_ascii-True-False-True-False +1 -0
  271. densitty-0.8.2/tests/goldens/test_axes_ascii-True-False-True-True +1 -0
  272. densitty-0.8.2/tests/goldens/test_axes_ascii-True-True-False-False +1 -0
  273. densitty-0.8.2/tests/goldens/test_axes_ascii-True-True-False-True +1 -0
  274. densitty-0.8.2/tests/goldens/test_axes_ascii-True-True-True-False +1 -0
  275. densitty-0.8.2/tests/goldens/test_axes_ascii-True-True-True-True +1 -0
  276. densitty-0.8.2/tests/goldens/test_axes_labelsgiven +1 -0
  277. densitty-0.8.2/tests/goldens/test_axes_small +1 -0
  278. densitty-0.8.2/tests/goldens/test_bin_data_1 +1 -0
  279. densitty-0.8.2/tests/goldens/test_bin_data_2 +1 -0
  280. densitty-0.8.2/tests/goldens/test_bin_data_3 +1 -0
  281. densitty-0.8.2/tests/goldens/test_bin_data_unaligned +1 -0
  282. densitty-0.8.2/tests/goldens/test_binning_auto_spec_range_1 +1 -0
  283. densitty-0.8.2/tests/goldens/test_binning_drop_data +1 -0
  284. densitty-0.8.2/tests/goldens/test_binning_edge_1 +1 -0
  285. densitty-0.8.2/tests/goldens/test_binning_edge_2 +1 -0
  286. densitty-0.8.2/tests/goldens/test_binning_fixed_1 +1 -0
  287. densitty-0.8.2/tests/goldens/test_binning_fixed_2 +1 -0
  288. densitty-0.8.2/tests/goldens/test_binning_no_drop_data +1 -0
  289. densitty-0.8.2/tests/goldens/test_binning_provide_edges +1 -0
  290. densitty-0.8.2/tests/goldens/test_binning_simple_1 +1 -0
  291. densitty-0.8.2/tests/goldens/test_binning_simple_2 +1 -0
  292. densitty-0.8.2/tests/goldens/test_border_nonhist_toscreen +1 -0
  293. densitty-0.8.2/tests/goldens/test_empty_data +1 -0
  294. densitty-0.8.2/tests/goldens/test_histplot2d_1 +1 -0
  295. densitty-0.8.2/tests/goldens/test_histplot2d_2 +1 -0
  296. densitty-0.8.2/tests/goldens/test_histplot2d_3 +1 -0
  297. densitty-0.8.2/tests/goldens/test_histplot2d_4 +1 -0
  298. densitty-0.8.2/tests/goldens/test_maxsize_fitscreen +1 -0
  299. densitty-0.8.2/tests/goldens/test_maxsize_fitscreen_noaxes +1 -0
  300. densitty-0.8.2/tests/goldens/test_maxsize_keepaspect +1 -0
  301. densitty-0.8.2/tests/goldens/test_maxsize_reservemargin +1 -0
  302. densitty-0.8.2/tests/goldens/test_maxsize_set_default_size +1 -0
  303. densitty-0.8.2/tests/goldens/test_simple_hist_toscreen +1 -0
  304. densitty-0.8.2/tests/goldens/test_single_data +1 -0
  305. densitty-0.8.2/tests/goldens/test_single_valued_data +1 -0
  306. densitty-0.8.2/tests/goldens/truecolor.BLUE_RED +1 -0
  307. densitty-0.8.2/tests/goldens/truecolor.BLUE_RED_halfheight +1 -0
  308. densitty-0.8.2/tests/goldens/truecolor.COOL +1 -0
  309. densitty-0.8.2/tests/goldens/truecolor.COOL_halfheight +1 -0
  310. densitty-0.8.2/tests/goldens/truecolor.FADE_IN +1 -0
  311. densitty-0.8.2/tests/goldens/truecolor.FADE_IN_halfheight +1 -0
  312. densitty-0.8.2/tests/goldens/truecolor.GRAYSCALE +1 -0
  313. densitty-0.8.2/tests/goldens/truecolor.GRAYSCALE_LINEAR +1 -0
  314. densitty-0.8.2/tests/goldens/truecolor.GRAYSCALE_LINEAR_halfheight +1 -0
  315. densitty-0.8.2/tests/goldens/truecolor.GRAYSCALE_halfheight +1 -0
  316. densitty-0.8.2/tests/goldens/truecolor.HOT +1 -0
  317. densitty-0.8.2/tests/goldens/truecolor.HOT_halfheight +1 -0
  318. densitty-0.8.2/tests/goldens/truecolor.RAINBOW +1 -0
  319. densitty-0.8.2/tests/goldens/truecolor.RAINBOW_halfheight +1 -0
  320. densitty-0.8.2/tests/helper_tests.py +50 -0
  321. densitty-0.8.2/tests/lineart_tests.py +133 -0
  322. densitty-0.8.2/tests/numpy_tests.py +33 -0
  323. densitty-0.8.2/tests/scale_tests.py +140 -0
  324. densitty-0.8.2/tests/util_tests.py +10 -0
@@ -0,0 +1,13 @@
1
+ # pyenv version file
2
+ .python-version
3
+
4
+ # uv
5
+ uv.lock
6
+
7
+ # test and build artifacts
8
+ .coverage
9
+ .pytest_cache/
10
+ __pycache__/
11
+ *.pyc
12
+ *.egg-info/
13
+ .venv/
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.
@@ -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/*
@@ -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
+ ![Plot Output](https://billtompkins.github.io/densitty/docs/inline-histplot2d.png "example output image")
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)
@@ -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
+ ![Plot Output](https://billtompkins.github.io/densitty/docs/inline-histplot2d.png "example output image")
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)