umber 0.3.0__tar.gz → 0.3.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 (239) hide show
  1. {umber-0.3.0 → umber-0.3.2}/Cargo.lock +2 -2
  2. {umber-0.3.0 → umber-0.3.2}/Cargo.toml +1 -1
  3. {umber-0.3.0 → umber-0.3.2}/PKG-INFO +24 -16
  4. {umber-0.3.0 → umber-0.3.2}/README-ZH.md +23 -15
  5. {umber-0.3.0 → umber-0.3.2}/README.md +23 -15
  6. {umber-0.3.0 → umber-0.3.2}/THIRD-PARTY-LICENSES.txt +2 -2
  7. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/category_label_tests.rs +1 -0
  8. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/coverage.rs +71 -3
  9. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/cells.rs +64 -0
  10. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/heatmap.rs +63 -3
  11. umber-0.3.2/crates/umber-core/src/draw/mesh.rs +391 -0
  12. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/mod.rs +50 -0
  13. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/mod.rs +1 -0
  14. umber-0.3.2/crates/umber-core/src/draw/tests/raster.rs +1189 -0
  15. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/figure.rs +16 -1
  16. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/header_tests.rs +23 -2
  17. umber-0.3.2/crates/umber-core/src/label_cache_tests.rs +125 -0
  18. umber-0.3.2/crates/umber-core/src/layout/cache_tests.rs +87 -0
  19. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/layout/grid.rs +1 -0
  20. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/layout/mod.rs +99 -2
  21. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/layout/panel.rs +32 -9
  22. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/lib.rs +6 -2
  23. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/axes_mut.rs +25 -3
  24. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/heatmap.rs +8 -2
  25. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/mesh.rs +500 -4
  26. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/mod.rs +2 -0
  27. umber-0.3.2/crates/umber-core/src/model/render.rs +90 -0
  28. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/pipeline.rs +303 -29
  29. umber-0.3.2/crates/umber-core/src/render/image.rs +687 -0
  30. umber-0.3.2/crates/umber-core/src/render/image_encoding_tests.rs +506 -0
  31. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/render/mod.rs +1 -0
  32. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/render/normalize.rs +22 -0
  33. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/render/png.rs +76 -2
  34. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/render/svg.rs +59 -4
  35. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/scene.rs +24 -1
  36. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/stats/bin2d.rs +30 -11
  37. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/test_support/scene.rs +10 -0
  38. {umber-0.3.0 → umber-0.3.2}/crates/umber-py/src/enums.rs +37 -3
  39. {umber-0.3.0 → umber-0.3.2}/crates/umber-py/src/figure.rs +14 -8
  40. {umber-0.3.0 → umber-0.3.2}/pyproject.toml +4 -0
  41. {umber-0.3.0 → umber-0.3.2}/python/umber/__init__.py +3 -0
  42. {umber-0.3.0 → umber-0.3.2}/python/umber/_axes.py +57 -3
  43. {umber-0.3.0 → umber-0.3.2}/python/umber/_enums.py +33 -0
  44. {umber-0.3.0 → umber-0.3.2}/python/umber/_umber.pyi +3 -0
  45. umber-0.3.0/crates/umber-core/src/draw/mesh.rs +0 -177
  46. {umber-0.3.0 → umber-0.3.2}/LICENSE-APACHE +0 -0
  47. {umber-0.3.0 → umber-0.3.2}/LICENSE-MIT +0 -0
  48. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/Cargo.toml +0 -0
  49. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/LICENSE-APACHE +0 -0
  50. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/LICENSE-MIT +0 -0
  51. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/README.md +0 -0
  52. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/fonts/Geist-Regular.ttf +0 -0
  53. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/fonts/Geist-SemiBold.ttf +0 -0
  54. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/fonts/IBMPlexSans-Regular.ttf +0 -0
  55. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/fonts/IBMPlexSans-SemiBold.ttf +0 -0
  56. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/fonts/Inter-Regular.ttf +0 -0
  57. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/fonts/Inter-SemiBold.ttf +0 -0
  58. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/fonts/JetBrainsMono-Regular.ttf +0 -0
  59. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/fonts/JetBrainsMono-SemiBold.ttf +0 -0
  60. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/fonts/LICENSES/Geist-OFL.txt +0 -0
  61. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/fonts/LICENSES/IBMPlexSans-OFL.txt +0 -0
  62. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/fonts/LICENSES/Inter-OFL.txt +0 -0
  63. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/fonts/LICENSES/JetBrainsMono-OFL.txt +0 -0
  64. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/fonts/LICENSES/SourceSerif4-OFL.txt +0 -0
  65. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/fonts/MANIFEST.md +0 -0
  66. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/fonts/SourceSerif4-Regular.ttf +0 -0
  67. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/fonts/SourceSerif4-SemiBold.ttf +0 -0
  68. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/binning.rs +0 -0
  69. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/bubble_fit.rs +0 -0
  70. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/bubble_fit_tests.rs +0 -0
  71. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/bubble_scale.rs +0 -0
  72. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/bubbles_tests.rs +0 -0
  73. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/cmap/tables.rs +0 -0
  74. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/cmap.rs +0 -0
  75. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/color.rs +0 -0
  76. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/color_by_tests.rs +0 -0
  77. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/color_scale.rs +0 -0
  78. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/downsample.rs +0 -0
  79. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/area.rs +0 -0
  80. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/band.rs +0 -0
  81. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/bar.rs +0 -0
  82. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/bar_shape.rs +0 -0
  83. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/box_plot.rs +0 -0
  84. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/bubbles.rs +0 -0
  85. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/chrome.rs +0 -0
  86. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/clip/mod.rs +0 -0
  87. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/colorbar.rs +0 -0
  88. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/error_bars.rs +0 -0
  89. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/fill.rs +0 -0
  90. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/hexbin.rs +0 -0
  91. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/hist.rs +0 -0
  92. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/legend.rs +0 -0
  93. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/line.rs +0 -0
  94. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/mapping.rs +0 -0
  95. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/marker.rs +0 -0
  96. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/rule.rs +0 -0
  97. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/scatter.rs +0 -0
  98. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/stacked_area.rs +0 -0
  99. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/stem.rs +0 -0
  100. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/step.rs +0 -0
  101. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/stroke.rs +0 -0
  102. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/area.rs +0 -0
  103. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/band.rs +0 -0
  104. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/bar.rs +0 -0
  105. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/baseline.rs +0 -0
  106. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/box_plot.rs +0 -0
  107. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/cells.rs +0 -0
  108. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/chrome.rs +0 -0
  109. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/clip.rs +0 -0
  110. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/downsample.rs +0 -0
  111. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/error_bars.rs +0 -0
  112. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/hist.rs +0 -0
  113. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/horizontal.rs +0 -0
  114. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/line.rs +0 -0
  115. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/mapping.rs +0 -0
  116. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/marker.rs +0 -0
  117. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/marks.rs +0 -0
  118. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/oracle.rs +0 -0
  119. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/rule.rs +0 -0
  120. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/scatter.rs +0 -0
  121. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/stacked_area.rs +0 -0
  122. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/stem.rs +0 -0
  123. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/step.rs +0 -0
  124. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/violin.rs +0 -0
  125. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/violin.rs +0 -0
  126. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/error.rs +0 -0
  127. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/exact/dyadic.rs +0 -0
  128. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/exact/expansion.rs +0 -0
  129. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/exact/mod.rs +0 -0
  130. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/fonts.rs +0 -0
  131. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/geometry.rs +0 -0
  132. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/layout/colorbar.rs +0 -0
  133. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/layout/inset.rs +0 -0
  134. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/layout/legend.rs +0 -0
  135. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/layout/nudge.rs +0 -0
  136. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/layout/placement.rs +0 -0
  137. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/layout/program.rs +0 -0
  138. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/legend.rs +0 -0
  139. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/legend_position_tests.rs +0 -0
  140. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/legend_swatch_tests.rs +0 -0
  141. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/axes.rs +0 -0
  142. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/axis_data.rs +0 -0
  143. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/band.rs +0 -0
  144. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/bar.rs +0 -0
  145. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/box_plot.rs +0 -0
  146. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/bubbles.rs +0 -0
  147. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/color_by.rs +0 -0
  148. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/error_bars.rs +0 -0
  149. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/figure.rs +0 -0
  150. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/grid.rs +0 -0
  151. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/hist.rs +0 -0
  152. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/mark.rs +0 -0
  153. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/rule.rs +0 -0
  154. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/share.rs +0 -0
  155. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/share_tests.rs +0 -0
  156. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/stacked_area.rs +0 -0
  157. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/stem.rs +0 -0
  158. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/step.rs +0 -0
  159. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/ticks.rs +0 -0
  160. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/validation.rs +0 -0
  161. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/violin.rs +0 -0
  162. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/render/snapshots/umber_core__render__svg__tests__fill_stroke_and_markers_snapshot.snap +0 -0
  163. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/resolve/data_limits_tests.rs +0 -0
  164. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/resolve/horizontal_tests.rs +0 -0
  165. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/resolve/nice_ticks_tests.rs +0 -0
  166. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/resolve.rs +0 -0
  167. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/scale.rs +0 -0
  168. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/scene_text.rs +0 -0
  169. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/size.rs +0 -0
  170. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/snapshots/umber_core__draw__tests__chrome_snapshot.snap +0 -0
  171. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/stacking.rs +0 -0
  172. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/stats.rs +0 -0
  173. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/test_support/image.rs +0 -0
  174. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/test_support/mod.rs +0 -0
  175. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/test_support/polygon.rs +0 -0
  176. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/cmap.rs +0 -0
  177. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/font.rs +0 -0
  178. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/guard.rs +0 -0
  179. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/layout.rs +0 -0
  180. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/library.rs +0 -0
  181. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/marks.rs +0 -0
  182. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/mod.rs +0 -0
  183. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/outline.rs +0 -0
  184. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/runs.rs +0 -0
  185. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/shaping.rs +0 -0
  186. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/style.rs +0 -0
  187. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/system/index.rs +0 -0
  188. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/system/lookup.rs +0 -0
  189. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/system/mod.rs +0 -0
  190. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/system/shaping.rs +0 -0
  191. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/tests/cmap.rs +0 -0
  192. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/tests/fallback.rs +0 -0
  193. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/tests/fixtures.rs +0 -0
  194. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/tests/layout.rs +0 -0
  195. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/tests/malformed.rs +0 -0
  196. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/tests/mod.rs +0 -0
  197. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/tests/outline.rs +0 -0
  198. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/tests/runs.rs +0 -0
  199. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/tests/shaping.rs +0 -0
  200. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/tests/system_fallback.rs +0 -0
  201. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/tests/system_index.rs +0 -0
  202. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/theme.rs +0 -0
  203. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/tick_format.rs +0 -0
  204. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/ticks.rs +0 -0
  205. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/time/tests.rs +0 -0
  206. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/time.rs +0 -0
  207. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/time_axes_tests.rs +0 -0
  208. {umber-0.3.0 → umber-0.3.2}/crates/umber-core/tests/fixtures/fallback-only.ttf +0 -0
  209. {umber-0.3.0 → umber-0.3.2}/crates/umber-py/Cargo.toml +0 -0
  210. {umber-0.3.0 → umber-0.3.2}/crates/umber-py/src/cmap.rs +0 -0
  211. {umber-0.3.0 → umber-0.3.2}/crates/umber-py/src/color.rs +0 -0
  212. {umber-0.3.0 → umber-0.3.2}/crates/umber-py/src/errors.rs +0 -0
  213. {umber-0.3.0 → umber-0.3.2}/crates/umber-py/src/lib.rs +0 -0
  214. {umber-0.3.0 → umber-0.3.2}/crates/umber-py/src/scale.rs +0 -0
  215. {umber-0.3.0 → umber-0.3.2}/crates/umber-py/src/size.rs +0 -0
  216. {umber-0.3.0 → umber-0.3.2}/crates/umber-py/src/test_support.rs +0 -0
  217. {umber-0.3.0 → umber-0.3.2}/crates/umber-py/src/theme.rs +0 -0
  218. {umber-0.3.0 → umber-0.3.2}/crates/umber-py/src/ticks.rs +0 -0
  219. {umber-0.3.0 → umber-0.3.2}/crates/umber-py/src/values.rs +0 -0
  220. {umber-0.3.0 → umber-0.3.2}/python/umber/_ansi.py +0 -0
  221. {umber-0.3.0 → umber-0.3.2}/python/umber/_args.py +0 -0
  222. {umber-0.3.0 → umber-0.3.2}/python/umber/_cell.py +0 -0
  223. {umber-0.3.0 → umber-0.3.2}/python/umber/_cmap.py +0 -0
  224. {umber-0.3.0 → umber-0.3.2}/python/umber/_color.py +0 -0
  225. {umber-0.3.0 → umber-0.3.2}/python/umber/_color_by.py +0 -0
  226. {umber-0.3.0 → umber-0.3.2}/python/umber/_errors.py +0 -0
  227. {umber-0.3.0 → umber-0.3.2}/python/umber/_figure.py +0 -0
  228. {umber-0.3.0 → umber-0.3.2}/python/umber/_highlight.py +0 -0
  229. {umber-0.3.0 → umber-0.3.2}/python/umber/_quick.py +0 -0
  230. {umber-0.3.0 → umber-0.3.2}/python/umber/_report.py +0 -0
  231. {umber-0.3.0 → umber-0.3.2}/python/umber/_scale.py +0 -0
  232. {umber-0.3.0 → umber-0.3.2}/python/umber/_series.py +0 -0
  233. {umber-0.3.0 → umber-0.3.2}/python/umber/_size.py +0 -0
  234. {umber-0.3.0 → umber-0.3.2}/python/umber/_theme.py +0 -0
  235. {umber-0.3.0 → umber-0.3.2}/python/umber/_ticks.py +0 -0
  236. {umber-0.3.0 → umber-0.3.2}/python/umber/_time.py +0 -0
  237. {umber-0.3.0 → umber-0.3.2}/python/umber/_types.py +0 -0
  238. {umber-0.3.0 → umber-0.3.2}/python/umber/_values.py +0 -0
  239. {umber-0.3.0 → umber-0.3.2}/python/umber/py.typed +0 -0
@@ -956,7 +956,7 @@ checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee"
956
956
 
957
957
  [[package]]
958
958
  name = "umber-core"
959
- version = "0.3.0"
959
+ version = "0.3.2"
960
960
  dependencies = [
961
961
  "criterion",
962
962
  "fontdb",
@@ -973,7 +973,7 @@ dependencies = [
973
973
 
974
974
  [[package]]
975
975
  name = "umber-py"
976
- version = "0.3.0"
976
+ version = "0.3.2"
977
977
  dependencies = [
978
978
  "pyo3",
979
979
  "umber-core",
@@ -3,7 +3,7 @@ resolver = "3"
3
3
  members = ["crates/umber-core", "crates/umber-py"]
4
4
 
5
5
  [workspace.package]
6
- version = "0.3.0"
6
+ version = "0.3.2"
7
7
  edition = "2024"
8
8
  rust-version = "1.88"
9
9
  license = "MIT OR Apache-2.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: umber
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: Programming Language :: Python :: 3 :: Only
6
6
  Classifier: Programming Language :: Python :: 3.12
@@ -32,13 +32,15 @@ Project-URL: Source, https://github.com/TJ-Junhao/umber
32
32
 
33
33
  # umber
34
34
 
35
+ [![Coverage](https://codecov.io/gh/TJ-Junhao/umber/graph/badge.svg)](https://codecov.io/gh/TJ-Junhao/umber)
36
+
35
37
  umber is a plotting library for Python with a rendering core written in Rust. It draws line, step, scatter, area, stacked area, bar, histogram, box, violin, bubble, error bar, band, stem, reference-line, heatmap, mesh, 2-D histogram and hexbin charts on numeric, log, category and time axes, alone or on a grid of panels with shared axes. It has colormaps and colorbars, careful typography, three built-in themes, and PNG or SVG output.
36
38
 
37
39
  [简体中文](https://github.com/TJ-Junhao/umber/blob/main/README-ZH.md)
38
40
 
39
41
  ![A line chart of daily temperature at two stations, in the Graphite theme](https://raw.githubusercontent.com/TJ-Junhao/umber/main/assets/readme/lines-graphite.png)
40
42
 
41
- - **Fast on large data.** Rendering runs in Rust: a 1,000,000-point line renders to PNG in about 61 ms, and a 100,000-point scatter in about 49 ms (see [Performance](#performance)).
43
+ - **Fast on large data.** Rendering runs in Rust: a 1,000,000-point line renders to PNG in about 74 ms and a 100,000-point scatter in about 83 ms on a standard GitHub Actions runner, 2.8× and 3.5× faster than matplotlib (see [Performance](#performance)).
42
44
  - **Explicit.** Every option is a typed value (`Color`, `Size`, `Dash`, `TickFormat`, …). There are no format strings, no positional style arguments and no silent conversions.
43
45
  - **Fails fast, pointing at the problem.** Invalid input raises at the call that caused it, with a message, a hint and the offending argument highlighted in the traceback.
44
46
  - **Statically typed.** Category and numeric axes are distinct types: calling `bar` on a numeric axes is reported by pyright and mypy before the code runs.
@@ -147,7 +149,7 @@ Use `fig.axes_at(row=..., col=..., x=..., y=...)` to retrieve axes, including th
147
149
 
148
150
  ## Colors and colorbars
149
151
 
150
- Heatmaps, meshes, 2-D histograms and hexbins color each cell by its value, and `color=ub.ColorBy(values)` does the same for the points of a scatter or bubble chart. The color-mapped marks of one axes share a single color scale, and a colorbar beside the plot explains it.
152
+ Heatmaps, meshes, 2-D histograms and hexbins color each cell by its value, and `color=ub.ColorBy(values)` does the same for the points of a scatter or bubble chart. The color-mapped marks of one axes share a single color scale, and a colorbar beside the plot explains it. From 10,000 cells, heatmaps, meshes and 2-D histograms draw their cells as one image with one pixel per cell instead of one shape per cell, which keeps large grids small and fast; `render=ub.Render.VECTOR` or `render=ub.Render.RASTER` chooses the form explicitly.
151
153
 
152
154
  ```python
153
155
  import numpy as np
@@ -264,19 +266,25 @@ ax.bar(["a", "b"], [1, 2]) # pyright and mypy: bar() needs Axes[str, float]
264
266
 
265
267
  ## Performance
266
268
 
267
- Medians from the [criterion](https://github.com/criterion-rs/criterion.rs) suite in `crates/umber-core/benches`, 6 × 4 in at 200 dpi, on an Intel Core Ultra 9 290HX running Linux:
268
-
269
- | Case | Time |
270
- | --- | ---: |
271
- | 1,000,000-point line → PNG | 61 ms |
272
- | 1,000,000-point line on a time axis → PNG | 59 ms |
273
- | 1,000,000-point line → SVG | 20 ms |
274
- | 100,000-point scatter → PNG | 49 ms |
275
- | Histogram of 1,000,000 values → PNG | 21 ms |
276
- | 2-D histogram of 1,000,000 points, 100 × 100 bins → PNG | 15 ms |
277
- | Hexbin of 1,000,000 points, grid size 50 → PNG | 70 ms |
278
- | Heatmap of 1,000 × 1,000 cells → PNG | 0.92 s |
279
- | 4 × 5 grid, one 10,000-point line per panel → PNG | 111 ms |
269
+ End-to-end Python timings next to matplotlib 3.11.2 (Agg backend, default style), from the call that builds the figure to the encoded bytes. Figures are 6 × 4 in, PNG at 200 dpi, with the same input data for both. Each value is the median of 8 runs after a warm-up, with each library in its own process. Measured with umber 0.3.2 on GitHub Actions `ubuntu-latest` (AMD EPYC 9V74, 4 vCPUs, Python 3.12, NumPy 2.5) by the [Python bench workflow](https://github.com/TJ-Junhao/umber/actions/workflows/python-bench.yml) ([this run](https://github.com/TJ-Junhao/umber/actions/runs/39101279625)); the script is `scripts/bench/python_vs_matplotlib.py`. GitHub assigns different CPU models to the same runner label, so absolute times vary between runs; the speed-ups, measured side by side on one machine, are the figures to compare.
270
+
271
+ | Case | umber | matplotlib | Speed-up |
272
+ | --- | ---: | ---: | ---: |
273
+ | 1,000,000-point line → PNG | 74 ms | 205 ms | 2.8× |
274
+ | 1,000,000-point line on a time axis → PNG | 91 ms | 220 ms | 2.4× |
275
+ | 1,000,000-point line → SVG | 24 ms | 65 ms | 2.7× |
276
+ | 100,000-point scatter → PNG | 83 ms | 291 ms | 3.5× |
277
+ | Histogram of 1,000,000 values → PNG | 32 ms | 64 ms | 2.0× |
278
+ | 2-D histogram of 1,000,000 points, 100 × 100 bins → PNG | 100 ms | 168 ms | 1.7× |
279
+ | Hexbin of 1,000,000 points, grid size 50 → PNG | 127 ms | 141 ms | 1.1× |
280
+ | Heatmap of 1,000 × 1,000 cells → PNG | 194 ms | 252 ms | 1.3× |
281
+ | Heatmap of 1,000 × 1,000 cells → SVG | 175 ms | 443 ms | 2.5× |
282
+ | 4 × 5 grid, one 10,000-point line per panel → PNG | 180 ms | 552 ms | 3.1× |
283
+ | 1,000-point line with a second series, title, labels and legend → PNG | 22 ms | 81 ms | 3.7× |
284
+ | 5-bar chart → PNG | 12 ms | 44 ms | 3.6× |
285
+ | `import` in a fresh interpreter | 66 ms | 310 ms | 4.7× |
286
+
287
+ Pinned to one CPU, only the scatter plot is notably slower: 125 ms (2.3×). The 4 × 5 grid uses a 12 × 8 in figure. The script's docstring lists where the two libraries do different work, for example the hexbin grids, which are built differently.
280
288
 
281
289
  Lines with sorted numeric or time x values are downsampled when they have more than four points per pixel column of the plot (counted at a nominal 300 dpi), keeping the first, last, minimum and maximum point in each column, which is visually lossless. Large scatter plots are composited on up to 8 threads; the output is the same for any number of threads.
282
290
 
@@ -1,12 +1,14 @@
1
1
  # umber
2
2
 
3
+ [![测试覆盖率](https://codecov.io/gh/TJ-Junhao/umber/graph/badge.svg)](https://codecov.io/gh/TJ-Junhao/umber)
4
+
3
5
  umber 是一个 Python 绘图库,渲染核心用 Rust 编写。它可以绘制折线图、阶梯图、散点图、面积图、堆叠面积图、柱状图、直方图、箱线图、小提琴图、气泡图、误差棒、区间带、杆状图、参考线、热力图、网格色块图(mesh)、二维直方图和六边形分箱图(hexbin),支持数值轴、对数轴、类别轴和时间轴,可以单独成图,也可以排成共享坐标轴的多面板网格。它提供 colormap 和 colorbar,排版考究,内置三套主题,输出 PNG 或 SVG。
4
6
 
5
7
  [English](https://github.com/TJ-Junhao/umber/blob/main/README.md)
6
8
 
7
9
  ![Graphite 主题下两个站点的每日气温折线图](https://raw.githubusercontent.com/TJ-Junhao/umber/main/assets/readme/lines-graphite.png)
8
10
 
9
- - **大数据量也快。** 渲染在 Rust 中完成:100 万个点的折线渲染成 PNG 约 61 ms,10 万个点的散点图约 49 ms(见[性能](#性能))。
11
+ - **大数据量也快。** 渲染在 Rust 中完成:在标准的 GitHub Actions runner 上,100 万个点的折线渲染成 PNG 约 74 ms,10 万个点的散点图约 83 ms,分别比 matplotlib 快 2.8× 和 3.5×(见[性能](#性能))。
10
12
  - **显式。** 每个选项都是有类型的值(`Color`、`Size`、`Dash`、`TickFormat` 等)。没有格式字符串,没有位置式的样式参数,也没有隐式转换。
11
13
  - **尽早报错,并指出问题所在。** 非法输入在引发它的那次调用处就报错,附带说明、提示,并在 traceback 中高亮出错的参数。
12
14
  - **静态类型。** 类别轴和数值轴是不同的类型:在数值轴上调用 `bar`,pyright 和 mypy 在运行前就会报错。
@@ -115,7 +117,7 @@ variant.axes_at(row=0, col=1).title("Comparison") # 原图不变
115
117
 
116
118
  ## 颜色与 colorbar
117
119
 
118
- 热力图、网格色块图、二维直方图和六边形分箱图按数值为每个格子着色;`color=ub.ColorBy(values)` 对散点图或气泡图的点做同样的事。同一个 axes 上所有按值着色的 mark 共用一个 color scale,plot 旁的 colorbar 说明这一映射。
120
+ 热力图、网格色块图、二维直方图和六边形分箱图按数值为每个格子着色;`color=ub.ColorBy(values)` 对散点图或气泡图的点做同样的事。同一个 axes 上所有按值着色的 mark 共用一个 color scale,plot 旁的 colorbar 说明这一映射。热力图、网格色块图和二维直方图从 10,000 个格子起把格子画成一张每格一个像素的图像,而不是每格一个形状,使大网格的文件更小、渲染更快;`render=ub.Render.VECTOR` 或 `render=ub.Render.RASTER` 可显式指定画法。
119
121
 
120
122
  ```python
121
123
  import numpy as np
@@ -232,19 +234,25 @@ ax.bar(["a", "b"], [1, 2]) # pyright 和 mypy 报错:bar() 需要 Axes[str, f
232
234
 
233
235
  ## 性能
234
236
 
235
- 以下是 `crates/umber-core/benches` 中 [criterion](https://github.com/criterion-rs/criterion.rs) 测试套件的中位数,图幅 6 × 4 英寸、200 dpi,运行于 Linux 下的 Intel Core Ultra 9 290HX:
236
-
237
- | 场景 | 耗时 |
238
- | --- | ---: |
239
- | 100 万点折线 → PNG | 61 ms |
240
- | 时间轴上的 100 万点折线 → PNG | 59 ms |
241
- | 100 万点折线 → SVG | 20 ms |
242
- | 10 万点散点图 → PNG | 49 ms |
243
- | 100 万个值的直方图 → PNG | 21 ms |
244
- | 100 万个点的二维直方图,100 × 100 个 bin → PNG | 15 ms |
245
- | 100 万个点的六边形分箱图,grid size 50 → PNG | 70 ms |
246
- | 1,000 × 1,000 个格子的热力图 → PNG | 0.92 s |
247
- | 4 × 5 网格,每格一条 1 万个点的折线 → PNG | 111 ms |
237
+ 以下是与 matplotlib 3.11.2(Agg 后端、默认样式)对比的 Python 端到端耗时,从构建图形的调用开始,到得到编码后的字节为止。图幅 6 × 4 英寸,PNG 为 200 dpi,两者使用相同的输入数据。每个数值是预热一次后 8 次运行的中位数,两个库各在独立的进程中运行。测量环境为 umber 0.3.2,GitHub Actions `ubuntu-latest`(AMD EPYC 9V74,4 个 vCPU,Python 3.12,NumPy 2.5),由 [Python bench workflow](https://github.com/TJ-Junhao/umber/actions/workflows/python-bench.yml) 运行([本次运行](https://github.com/TJ-Junhao/umber/actions/runs/39101279625));脚本为 `scripts/bench/python_vs_matplotlib.py`。同一个 runner 标签每次可能分到不同型号的 CPU,所以绝对耗时会随运行变化;在同一台机器上交替测得的加速比才是可比较的数字。
238
+
239
+ | 场景 | umber | matplotlib | 加速比 |
240
+ | --- | ---: | ---: | ---: |
241
+ | 100 万点折线 → PNG | 74 ms | 205 ms | 2.8× |
242
+ | 时间轴上的 100 万点折线 → PNG | 91 ms | 220 ms | 2.4× |
243
+ | 100 万点折线 → SVG | 24 ms | 65 ms | 2.7× |
244
+ | 10 万点散点图 → PNG | 83 ms | 291 ms | 3.5× |
245
+ | 100 万个值的直方图 → PNG | 32 ms | 64 ms | 2.0× |
246
+ | 100 万个点的二维直方图,100 × 100 个 bin → PNG | 100 ms | 168 ms | 1.7× |
247
+ | 100 万个点的六边形分箱图,grid size 50 → PNG | 127 ms | 141 ms | 1.1× |
248
+ | 1,000 × 1,000 个格子的热力图 → PNG | 194 ms | 252 ms | 1.3× |
249
+ | 1,000 × 1,000 个格子的热力图 → SVG | 175 ms | 443 ms | 2.5× |
250
+ | 4 × 5 网格,每格一条 1 万个点的折线 → PNG | 180 ms | 552 ms | 3.1× |
251
+ | 1,000 个点的折线加第二条序列、标题、坐标轴标签和图例 → PNG | 22 ms | 81 ms | 3.7× |
252
+ | 5 根柱子的柱状图 → PNG | 12 ms | 44 ms | 3.6× |
253
+ | 在新解释器中 `import` | 66 ms | 310 ms | 4.7× |
254
+
255
+ 绑定到单个 CPU 时,只有散点图明显变慢:125 ms(2.3×)。4 × 5 网格使用 12 × 8 英寸的图幅。两个库在哪些场景下做的工作不同(例如六边形分箱的网格构建方式不同),见脚本的 docstring。
248
256
 
249
257
  对于 x 值有序的数值或时间折线,按 300 dpi 的标称分辨率计算绘图区的像素列数,点数超过列数的四倍时按列降采样,保留每列的首点、末点、最小值和最大值,视觉上无损。大型散点图最多使用 8 个线程合成;无论用几个线程,输出都完全相同。
250
258
 
@@ -1,12 +1,14 @@
1
1
  # umber
2
2
 
3
+ [![Coverage](https://codecov.io/gh/TJ-Junhao/umber/graph/badge.svg)](https://codecov.io/gh/TJ-Junhao/umber)
4
+
3
5
  umber is a plotting library for Python with a rendering core written in Rust. It draws line, step, scatter, area, stacked area, bar, histogram, box, violin, bubble, error bar, band, stem, reference-line, heatmap, mesh, 2-D histogram and hexbin charts on numeric, log, category and time axes, alone or on a grid of panels with shared axes. It has colormaps and colorbars, careful typography, three built-in themes, and PNG or SVG output.
4
6
 
5
7
  [简体中文](https://github.com/TJ-Junhao/umber/blob/main/README-ZH.md)
6
8
 
7
9
  ![A line chart of daily temperature at two stations, in the Graphite theme](https://raw.githubusercontent.com/TJ-Junhao/umber/main/assets/readme/lines-graphite.png)
8
10
 
9
- - **Fast on large data.** Rendering runs in Rust: a 1,000,000-point line renders to PNG in about 61 ms, and a 100,000-point scatter in about 49 ms (see [Performance](#performance)).
11
+ - **Fast on large data.** Rendering runs in Rust: a 1,000,000-point line renders to PNG in about 74 ms and a 100,000-point scatter in about 83 ms on a standard GitHub Actions runner, 2.8× and 3.5× faster than matplotlib (see [Performance](#performance)).
10
12
  - **Explicit.** Every option is a typed value (`Color`, `Size`, `Dash`, `TickFormat`, …). There are no format strings, no positional style arguments and no silent conversions.
11
13
  - **Fails fast, pointing at the problem.** Invalid input raises at the call that caused it, with a message, a hint and the offending argument highlighted in the traceback.
12
14
  - **Statically typed.** Category and numeric axes are distinct types: calling `bar` on a numeric axes is reported by pyright and mypy before the code runs.
@@ -115,7 +117,7 @@ Use `fig.axes_at(row=..., col=..., x=..., y=...)` to retrieve axes, including th
115
117
 
116
118
  ## Colors and colorbars
117
119
 
118
- Heatmaps, meshes, 2-D histograms and hexbins color each cell by its value, and `color=ub.ColorBy(values)` does the same for the points of a scatter or bubble chart. The color-mapped marks of one axes share a single color scale, and a colorbar beside the plot explains it.
120
+ Heatmaps, meshes, 2-D histograms and hexbins color each cell by its value, and `color=ub.ColorBy(values)` does the same for the points of a scatter or bubble chart. The color-mapped marks of one axes share a single color scale, and a colorbar beside the plot explains it. From 10,000 cells, heatmaps, meshes and 2-D histograms draw their cells as one image with one pixel per cell instead of one shape per cell, which keeps large grids small and fast; `render=ub.Render.VECTOR` or `render=ub.Render.RASTER` chooses the form explicitly.
119
121
 
120
122
  ```python
121
123
  import numpy as np
@@ -232,19 +234,25 @@ ax.bar(["a", "b"], [1, 2]) # pyright and mypy: bar() needs Axes[str, float]
232
234
 
233
235
  ## Performance
234
236
 
235
- Medians from the [criterion](https://github.com/criterion-rs/criterion.rs) suite in `crates/umber-core/benches`, 6 × 4 in at 200 dpi, on an Intel Core Ultra 9 290HX running Linux:
236
-
237
- | Case | Time |
238
- | --- | ---: |
239
- | 1,000,000-point line → PNG | 61 ms |
240
- | 1,000,000-point line on a time axis → PNG | 59 ms |
241
- | 1,000,000-point line → SVG | 20 ms |
242
- | 100,000-point scatter → PNG | 49 ms |
243
- | Histogram of 1,000,000 values → PNG | 21 ms |
244
- | 2-D histogram of 1,000,000 points, 100 × 100 bins → PNG | 15 ms |
245
- | Hexbin of 1,000,000 points, grid size 50 → PNG | 70 ms |
246
- | Heatmap of 1,000 × 1,000 cells → PNG | 0.92 s |
247
- | 4 × 5 grid, one 10,000-point line per panel → PNG | 111 ms |
237
+ End-to-end Python timings next to matplotlib 3.11.2 (Agg backend, default style), from the call that builds the figure to the encoded bytes. Figures are 6 × 4 in, PNG at 200 dpi, with the same input data for both. Each value is the median of 8 runs after a warm-up, with each library in its own process. Measured with umber 0.3.2 on GitHub Actions `ubuntu-latest` (AMD EPYC 9V74, 4 vCPUs, Python 3.12, NumPy 2.5) by the [Python bench workflow](https://github.com/TJ-Junhao/umber/actions/workflows/python-bench.yml) ([this run](https://github.com/TJ-Junhao/umber/actions/runs/39101279625)); the script is `scripts/bench/python_vs_matplotlib.py`. GitHub assigns different CPU models to the same runner label, so absolute times vary between runs; the speed-ups, measured side by side on one machine, are the figures to compare.
238
+
239
+ | Case | umber | matplotlib | Speed-up |
240
+ | --- | ---: | ---: | ---: |
241
+ | 1,000,000-point line → PNG | 74 ms | 205 ms | 2.8× |
242
+ | 1,000,000-point line on a time axis → PNG | 91 ms | 220 ms | 2.4× |
243
+ | 1,000,000-point line → SVG | 24 ms | 65 ms | 2.7× |
244
+ | 100,000-point scatter → PNG | 83 ms | 291 ms | 3.5× |
245
+ | Histogram of 1,000,000 values → PNG | 32 ms | 64 ms | 2.0× |
246
+ | 2-D histogram of 1,000,000 points, 100 × 100 bins → PNG | 100 ms | 168 ms | 1.7× |
247
+ | Hexbin of 1,000,000 points, grid size 50 → PNG | 127 ms | 141 ms | 1.1× |
248
+ | Heatmap of 1,000 × 1,000 cells → PNG | 194 ms | 252 ms | 1.3× |
249
+ | Heatmap of 1,000 × 1,000 cells → SVG | 175 ms | 443 ms | 2.5× |
250
+ | 4 × 5 grid, one 10,000-point line per panel → PNG | 180 ms | 552 ms | 3.1× |
251
+ | 1,000-point line with a second series, title, labels and legend → PNG | 22 ms | 81 ms | 3.7× |
252
+ | 5-bar chart → PNG | 12 ms | 44 ms | 3.6× |
253
+ | `import` in a fresh interpreter | 66 ms | 310 ms | 4.7× |
254
+
255
+ Pinned to one CPU, only the scatter plot is notably slower: 125 ms (2.3×). The 4 × 5 grid uses a 12 × 8 in figure. The script's docstring lists where the two libraries do different work, for example the hexbin grids, which are built differently.
248
256
 
249
257
  Lines with sorted numeric or time x values are downsampled when they have more than four points per pixel column of the plot (counted at a nominal 300 dpi), keeping the first, last, minimum and maximum point in each column, which is visually lossless. Large scatter plots are composited on up to 8 threads; the output is the same for any number of threads.
250
258
 
@@ -592,7 +592,7 @@ MIT License
592
592
  ================================================================================
593
593
 
594
594
  Used by:
595
- - umber-core 0.3.0
595
+ - umber-core 0.3.2
596
596
 
597
597
  MIT License
598
598
 
@@ -622,7 +622,7 @@ MIT License
622
622
  ================================================================================
623
623
 
624
624
  Used by:
625
- - umber-py 0.3.0
625
+ - umber-py 0.3.2
626
626
 
627
627
  MIT License
628
628
 
@@ -245,6 +245,7 @@ fn smallest_self_fitting_step(
245
245
  &candidate,
246
246
  grid.span(input.axes.cells()),
247
247
  [true, true],
248
+ &layout::LabelCache::new(lib),
248
249
  );
249
250
  if let Ok(solution) = layout::solve_grid(grid, &[bands])
250
251
  && Side::X
@@ -3,7 +3,8 @@
3
3
  //! Points and segments are counted once. Filled paths are measured in square points by
4
4
  //! horizontal scanlines at most one point apart, with the path's own winding rule. This also
5
5
  //! handles overlapping contours and areas that cross themselves, rather than cancelling their
6
- //! signed areas. Curves in rounded bar corners are flattened before scoring.
6
+ //! signed areas. Curves in rounded bar corners are flattened before scoring. Images count the
7
+ //! exact area of their pixels that are not transparent.
7
8
 
8
9
  use crate::layout::{Area, Frame};
9
10
  use crate::{FillRule, Item, Paint, Path, PathVerb, Point};
@@ -18,12 +19,60 @@ struct Filled {
18
19
  rule: FillRule,
19
20
  }
20
21
 
22
+ /// The pixels of one image that are not transparent, each a rectangle of its share of the
23
+ /// image's rectangle.
24
+ #[derive(Debug, Clone, PartialEq)]
25
+ struct Pixels {
26
+ /// The left, top, right and bottom edges of the image.
27
+ bounds: [f64; 4],
28
+ /// The number of pixel columns.
29
+ width: usize,
30
+ /// Whether each pixel, row by row from the top, is drawn (not transparent).
31
+ drawn: Vec<bool>,
32
+ }
33
+
34
+ impl Pixels {
35
+ /// The area of the drawn pixels inside `box_`, in square points.
36
+ fn overlap(&self, box_: &Frame) -> f64 {
37
+ let [left, top, right, bottom] = self.bounds;
38
+ let height = self.drawn.len() / self.width.max(1);
39
+ // The overlap of each column with the box along x, and of each row along y.
40
+ let lengths = |start: f64, end: f64, count: usize, lo: f64, hi: f64| -> Vec<f64> {
41
+ let size = (end - start) / count as f64;
42
+ (0..count)
43
+ .map(|i| {
44
+ let (a, b) = (start + size * i as f64, start + size * (i + 1) as f64);
45
+ (b.min(hi) - a.max(lo)).max(0.0)
46
+ })
47
+ .collect()
48
+ };
49
+ let columns = lengths(left, right, self.width, box_.x, box_.right());
50
+ let rows = lengths(top, bottom, height, box_.y, box_.bottom());
51
+ let mut area = 0.0;
52
+ for (row, &along_y) in rows.iter().enumerate() {
53
+ if along_y == 0.0 {
54
+ continue;
55
+ }
56
+ let drawn = &self.drawn[row * self.width..(row + 1) * self.width];
57
+ let along_x: f64 = columns
58
+ .iter()
59
+ .zip(drawn)
60
+ .filter(|&(_, &d)| d)
61
+ .map(|(&x, _)| x)
62
+ .sum();
63
+ area += along_x * along_y;
64
+ }
65
+ area
66
+ }
67
+ }
68
+
21
69
  /// Geometry after axis mapping, clipping and downsampling, in drawing order within each kind.
22
70
  #[derive(Debug, Clone, Default, PartialEq)]
23
71
  pub(crate) struct Geometry {
24
72
  points: Vec<XY>,
25
73
  segments: Vec<[XY; 2]>,
26
74
  fills: Vec<Filled>,
75
+ images: Vec<Pixels>,
27
76
  }
28
77
 
29
78
  fn xy(p: Point) -> XY {
@@ -83,6 +132,21 @@ impl Geometry {
83
132
  });
84
133
  }
85
134
  }
135
+ Item::Image {
136
+ rect,
137
+ width,
138
+ height,
139
+ pixels,
140
+ } => {
141
+ if *width > 0 && pixels.len() == (*width as usize) * (*height as usize) {
142
+ let (x, y) = (f64::from(rect.x), f64::from(rect.y));
143
+ self.images.push(Pixels {
144
+ bounds: [x, y, x + f64::from(rect.width), y + f64::from(rect.height)],
145
+ width: *width as usize,
146
+ drawn: pixels.iter().map(|c| c.a > 0).collect(),
147
+ });
148
+ }
149
+ }
86
150
  Item::Text(_) => {}
87
151
  }
88
152
  }
@@ -108,9 +172,12 @@ impl Geometry {
108
172
  .iter()
109
173
  .filter(|s| crosses(**s, &bounds))
110
174
  .count() as f64;
111
- self.fills.iter().fold(points + segments, |score, fill| {
175
+ let fills = self.fills.iter().fold(points + segments, |score, fill| {
112
176
  score + fill.overlap(&bounds)
113
- })
177
+ });
178
+ self.images
179
+ .iter()
180
+ .fold(fills, |score, image| score + image.overlap(&bounds))
114
181
  }
115
182
  }
116
183
 
@@ -612,6 +679,7 @@ mod tests {
612
679
  points: vec![[50.0, 50.0], [10.0, 10.0]],
613
680
  segments: vec![[[0.0, 50.0], [100.0, 50.0]], [[0.0, 0.0], [10.0, 10.0]]],
614
681
  fills: Vec::new(),
682
+ images: Vec::new(),
615
683
  };
616
684
  assert_eq!(geometry.coverage(&area, &box_), 2.0);
617
685
  }
@@ -9,9 +9,73 @@
9
9
  //! edges of its grid or into an empty cell, so every edge that faces the background stays
10
10
  //! exactly where it is. Where a cell reaches both right and down but the cell diagonally below
11
11
  //! it to the right is empty, the corner where the two reaches meet is cut out of it.
12
+ //!
13
+ //! A grid drawn as one image instead ([`Render`](crate::Render)) needs none of this: its cells
14
+ //! are the pixels of one [`Item::Image`], with transparent pixels for empty cells, and the
15
+ //! renderers meet them without seams.
12
16
 
13
17
  use super::mapping::point;
18
+ use crate::Rgba;
14
19
  use crate::geometry::Path;
20
+ use crate::scene::{Item, Rect};
21
+
22
+ /// A transparent pixel: an empty cell of an image.
23
+ pub(super) const CLEAR: Rgba = Rgba {
24
+ r: 0,
25
+ g: 0,
26
+ b: 0,
27
+ a: 0,
28
+ };
29
+
30
+ /// How far, in points, the image of a grid may reach past the part of it that is shown. Cells
31
+ /// cut by explicit limits are drawn whole and clipped, so an image reaches past the limits by
32
+ /// at most one cell; a cell wider than this, which only limits inside a few cells give, is too
33
+ /// wide to place exactly in the scene's precision: [`Render::Auto`](crate::Render::Auto) draws
34
+ /// such a grid as vector shapes, and [`Render::Raster`](crate::Render::Raster) fails.
35
+ pub(super) const MAX_IMAGE_REACH: f64 = 10_000.0;
36
+
37
+ /// How far, in points, an edge of a grid drawn as one image may lie from its place among the
38
+ /// image's evenly spaced pixels, on the final plot.
39
+ pub(super) const MAX_EDGE_OFFSET: f64 = 0.001;
40
+
41
+ /// The grid of `width` by `height` cells whose colors are `pixels` (row by row from the top,
42
+ /// [`CLEAR`] for an empty cell) as one image over `bounds`, the left, top, right and bottom
43
+ /// edges of its outer cells in plot points. When explicit limits cut its outer cells, `visible`
44
+ /// is the part of `bounds` shown, and the image is clipped to it; otherwise `visible` equals
45
+ /// `bounds`. `None` when every cell is empty.
46
+ pub(super) fn image_item(
47
+ bounds: [f64; 4],
48
+ visible: [f64; 4],
49
+ (width, height): (usize, usize),
50
+ pixels: Vec<Rgba>,
51
+ ) -> Option<Item> {
52
+ if pixels.iter().all(|c| c.a == 0) {
53
+ return None;
54
+ }
55
+ let rect = |[left, top, right, bottom]: [f64; 4]| {
56
+ let (x0, x1) = (left.min(right), left.max(right));
57
+ let (y0, y1) = (top.min(bottom), top.max(bottom));
58
+ Rect {
59
+ x: x0 as f32,
60
+ y: y0 as f32,
61
+ width: (x1 - x0) as f32,
62
+ height: (y1 - y0) as f32,
63
+ }
64
+ };
65
+ let image = Item::Image {
66
+ rect: rect(bounds),
67
+ width: u32::try_from(width).ok()?,
68
+ height: u32::try_from(height).ok()?,
69
+ pixels,
70
+ };
71
+ if visible == bounds {
72
+ return Some(image);
73
+ }
74
+ Some(Item::Group {
75
+ clip: Some(rect(visible)),
76
+ items: vec![image],
77
+ })
78
+ }
15
79
 
16
80
  /// How far, in points, a cell reaches under a neighbour drawn after it; at most half the
17
81
  /// neighbour's size along that axis.
@@ -6,9 +6,13 @@
6
6
  //! cell reaches under its drawn neighbours to the right and below. A cell ends exactly at its
7
7
  //! bands where it has no drawn neighbour: at the heatmap's right and bottom edges, and next to
8
8
  //! an empty cell.
9
+ //!
10
+ //! Drawn as one image ([`Render`](crate::Render)), a heatmap is one pixel per pair of category
11
+ //! bands from its first to its last category on each axis, over exactly those bands: a band of
12
+ //! another heatmap's category between them, and an empty cell, is a transparent pixel.
9
13
 
10
14
  use super::MarkContext;
11
- use super::cells::{Reach, cell_path, reach};
15
+ use super::cells::{CLEAR, Reach, cell_path, image_item, reach};
12
16
  use super::chrome::text;
13
17
  use super::mapping::AxisMaps;
14
18
  use crate::color_scale::ResolvedColorScale;
@@ -137,8 +141,56 @@ fn mark_indices(axis: &CategoryScale, names: &[String]) -> Vec<Option<usize>> {
137
141
  indices
138
142
  }
139
143
 
140
- /// The cells of `mark` as opaque fills, appended to `items`; nothing without a color scale
141
- /// (no finite value on the axes).
144
+ /// The cells of `mark` as one image, one pixel per pair of category bands from its first to
145
+ /// its last x and y category on the axes, over those bands (see the module documentation);
146
+ /// `None` when no cell is drawn or either axis is not a category axis, which the model rules
147
+ /// out.
148
+ fn image(
149
+ maps: AxisMaps,
150
+ resolved: &Resolved,
151
+ mark: &HeatmapMark,
152
+ scale: &ResolvedColorScale,
153
+ ) -> Option<Item> {
154
+ let (AxisScale::Category(columns), AxisScale::Category(rows)) =
155
+ (&resolved.x.scale, &resolved.y.scale)
156
+ else {
157
+ return None;
158
+ };
159
+ let column_at = mark_indices(columns, mark.x_categories());
160
+ let row_at = mark_indices(rows, mark.y_categories());
161
+ let span = |indices: &[Option<usize>]| {
162
+ let first = indices.iter().position(Option::is_some)?;
163
+ let end = indices.iter().rposition(Option::is_some)? + 1;
164
+ Some(first..end)
165
+ };
166
+ let (x_span, y_span) = (span(&column_at)?, span(&row_at)?);
167
+ let width = mark.x_categories().len();
168
+ let color = |row: Option<usize>, column: Option<usize>| {
169
+ let value = *mark.values().get(row? * width + column?)?;
170
+ scale.color(value)
171
+ };
172
+ let pixels: Vec<Rgba> = row_at[y_span.clone()]
173
+ .iter()
174
+ .flat_map(|&row| {
175
+ column_at[x_span.clone()]
176
+ .iter()
177
+ .map(move |&column| color(row, column).unwrap_or(CLEAR))
178
+ })
179
+ .collect();
180
+ let (left, top) = (columns.band(x_span.start).0, rows.band(y_span.start).0);
181
+ let (right, bottom) = (columns.band(x_span.end - 1).1, rows.band(y_span.end - 1).1);
182
+ let bounds = [
183
+ maps.x.plot_at(left),
184
+ maps.y.plot_at(top),
185
+ maps.x.plot_at(right),
186
+ maps.y.plot_at(bottom),
187
+ ];
188
+ image_item(bounds, bounds, (x_span.len(), y_span.len()), pixels)
189
+ }
190
+
191
+ /// The cells of `mark` appended to `items`: as one image when its options draw it as one
192
+ /// ([`Render`](crate::Render)), else as opaque fills; nothing without a color scale (no finite
193
+ /// value on the axes).
142
194
  pub(super) fn heatmap_items(
143
195
  context: &MarkContext<'_>,
144
196
  resolved: &Resolved,
@@ -148,6 +200,14 @@ pub(super) fn heatmap_items(
148
200
  let Some(scale) = &resolved.color else {
149
201
  return;
150
202
  };
203
+ let count = mark
204
+ .x_categories()
205
+ .len()
206
+ .saturating_mul(mark.y_categories().len());
207
+ if mark.options().render.draws_image(count, true) {
208
+ items.extend(image(context.maps, resolved, mark, scale));
209
+ return;
210
+ }
151
211
  items.extend(
152
212
  cells(context.maps, resolved, mark, scale)
153
213
  .iter()