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.
- {umber-0.3.0 → umber-0.3.2}/Cargo.lock +2 -2
- {umber-0.3.0 → umber-0.3.2}/Cargo.toml +1 -1
- {umber-0.3.0 → umber-0.3.2}/PKG-INFO +24 -16
- {umber-0.3.0 → umber-0.3.2}/README-ZH.md +23 -15
- {umber-0.3.0 → umber-0.3.2}/README.md +23 -15
- {umber-0.3.0 → umber-0.3.2}/THIRD-PARTY-LICENSES.txt +2 -2
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/category_label_tests.rs +1 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/coverage.rs +71 -3
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/cells.rs +64 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/heatmap.rs +63 -3
- umber-0.3.2/crates/umber-core/src/draw/mesh.rs +391 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/mod.rs +50 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/mod.rs +1 -0
- umber-0.3.2/crates/umber-core/src/draw/tests/raster.rs +1189 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/figure.rs +16 -1
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/header_tests.rs +23 -2
- umber-0.3.2/crates/umber-core/src/label_cache_tests.rs +125 -0
- umber-0.3.2/crates/umber-core/src/layout/cache_tests.rs +87 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/layout/grid.rs +1 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/layout/mod.rs +99 -2
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/layout/panel.rs +32 -9
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/lib.rs +6 -2
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/axes_mut.rs +25 -3
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/heatmap.rs +8 -2
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/mesh.rs +500 -4
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/mod.rs +2 -0
- umber-0.3.2/crates/umber-core/src/model/render.rs +90 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/pipeline.rs +303 -29
- umber-0.3.2/crates/umber-core/src/render/image.rs +687 -0
- umber-0.3.2/crates/umber-core/src/render/image_encoding_tests.rs +506 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/render/mod.rs +1 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/render/normalize.rs +22 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/render/png.rs +76 -2
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/render/svg.rs +59 -4
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/scene.rs +24 -1
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/stats/bin2d.rs +30 -11
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/test_support/scene.rs +10 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-py/src/enums.rs +37 -3
- {umber-0.3.0 → umber-0.3.2}/crates/umber-py/src/figure.rs +14 -8
- {umber-0.3.0 → umber-0.3.2}/pyproject.toml +4 -0
- {umber-0.3.0 → umber-0.3.2}/python/umber/__init__.py +3 -0
- {umber-0.3.0 → umber-0.3.2}/python/umber/_axes.py +57 -3
- {umber-0.3.0 → umber-0.3.2}/python/umber/_enums.py +33 -0
- {umber-0.3.0 → umber-0.3.2}/python/umber/_umber.pyi +3 -0
- umber-0.3.0/crates/umber-core/src/draw/mesh.rs +0 -177
- {umber-0.3.0 → umber-0.3.2}/LICENSE-APACHE +0 -0
- {umber-0.3.0 → umber-0.3.2}/LICENSE-MIT +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/Cargo.toml +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/LICENSE-APACHE +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/LICENSE-MIT +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/README.md +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/fonts/Geist-Regular.ttf +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/fonts/Geist-SemiBold.ttf +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/fonts/IBMPlexSans-Regular.ttf +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/fonts/IBMPlexSans-SemiBold.ttf +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/fonts/Inter-Regular.ttf +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/fonts/Inter-SemiBold.ttf +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/fonts/JetBrainsMono-Regular.ttf +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/fonts/JetBrainsMono-SemiBold.ttf +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/fonts/LICENSES/Geist-OFL.txt +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/fonts/LICENSES/IBMPlexSans-OFL.txt +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/fonts/LICENSES/Inter-OFL.txt +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/fonts/LICENSES/JetBrainsMono-OFL.txt +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/fonts/LICENSES/SourceSerif4-OFL.txt +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/fonts/MANIFEST.md +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/fonts/SourceSerif4-Regular.ttf +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/fonts/SourceSerif4-SemiBold.ttf +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/binning.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/bubble_fit.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/bubble_fit_tests.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/bubble_scale.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/bubbles_tests.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/cmap/tables.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/cmap.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/color.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/color_by_tests.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/color_scale.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/downsample.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/area.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/band.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/bar.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/bar_shape.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/box_plot.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/bubbles.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/chrome.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/clip/mod.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/colorbar.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/error_bars.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/fill.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/hexbin.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/hist.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/legend.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/line.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/mapping.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/marker.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/rule.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/scatter.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/stacked_area.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/stem.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/step.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/stroke.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/area.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/band.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/bar.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/baseline.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/box_plot.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/cells.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/chrome.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/clip.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/downsample.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/error_bars.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/hist.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/horizontal.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/line.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/mapping.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/marker.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/marks.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/oracle.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/rule.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/scatter.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/stacked_area.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/stem.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/step.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/tests/violin.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/draw/violin.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/error.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/exact/dyadic.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/exact/expansion.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/exact/mod.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/fonts.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/geometry.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/layout/colorbar.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/layout/inset.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/layout/legend.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/layout/nudge.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/layout/placement.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/layout/program.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/legend.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/legend_position_tests.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/legend_swatch_tests.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/axes.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/axis_data.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/band.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/bar.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/box_plot.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/bubbles.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/color_by.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/error_bars.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/figure.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/grid.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/hist.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/mark.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/rule.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/share.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/share_tests.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/stacked_area.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/stem.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/step.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/ticks.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/validation.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/model/violin.rs +0 -0
- {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
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/resolve/data_limits_tests.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/resolve/horizontal_tests.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/resolve/nice_ticks_tests.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/resolve.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/scale.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/scene_text.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/size.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/snapshots/umber_core__draw__tests__chrome_snapshot.snap +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/stacking.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/stats.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/test_support/image.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/test_support/mod.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/test_support/polygon.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/cmap.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/font.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/guard.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/layout.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/library.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/marks.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/mod.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/outline.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/runs.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/shaping.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/style.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/system/index.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/system/lookup.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/system/mod.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/system/shaping.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/tests/cmap.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/tests/fallback.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/tests/fixtures.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/tests/layout.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/tests/malformed.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/tests/mod.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/tests/outline.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/tests/runs.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/tests/shaping.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/tests/system_fallback.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/text/tests/system_index.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/theme.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/tick_format.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/ticks.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/time/tests.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/time.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/src/time_axes_tests.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-core/tests/fixtures/fallback-only.ttf +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-py/Cargo.toml +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-py/src/cmap.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-py/src/color.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-py/src/errors.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-py/src/lib.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-py/src/scale.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-py/src/size.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-py/src/test_support.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-py/src/theme.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-py/src/ticks.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/crates/umber-py/src/values.rs +0 -0
- {umber-0.3.0 → umber-0.3.2}/python/umber/_ansi.py +0 -0
- {umber-0.3.0 → umber-0.3.2}/python/umber/_args.py +0 -0
- {umber-0.3.0 → umber-0.3.2}/python/umber/_cell.py +0 -0
- {umber-0.3.0 → umber-0.3.2}/python/umber/_cmap.py +0 -0
- {umber-0.3.0 → umber-0.3.2}/python/umber/_color.py +0 -0
- {umber-0.3.0 → umber-0.3.2}/python/umber/_color_by.py +0 -0
- {umber-0.3.0 → umber-0.3.2}/python/umber/_errors.py +0 -0
- {umber-0.3.0 → umber-0.3.2}/python/umber/_figure.py +0 -0
- {umber-0.3.0 → umber-0.3.2}/python/umber/_highlight.py +0 -0
- {umber-0.3.0 → umber-0.3.2}/python/umber/_quick.py +0 -0
- {umber-0.3.0 → umber-0.3.2}/python/umber/_report.py +0 -0
- {umber-0.3.0 → umber-0.3.2}/python/umber/_scale.py +0 -0
- {umber-0.3.0 → umber-0.3.2}/python/umber/_series.py +0 -0
- {umber-0.3.0 → umber-0.3.2}/python/umber/_size.py +0 -0
- {umber-0.3.0 → umber-0.3.2}/python/umber/_theme.py +0 -0
- {umber-0.3.0 → umber-0.3.2}/python/umber/_ticks.py +0 -0
- {umber-0.3.0 → umber-0.3.2}/python/umber/_time.py +0 -0
- {umber-0.3.0 → umber-0.3.2}/python/umber/_types.py +0 -0
- {umber-0.3.0 → umber-0.3.2}/python/umber/_values.py +0 -0
- {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.
|
|
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.
|
|
976
|
+
version = "0.3.2"
|
|
977
977
|
dependencies = [
|
|
978
978
|
"pyo3",
|
|
979
979
|
"umber-core",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: umber
|
|
3
|
-
Version: 0.3.
|
|
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
|
+
[](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
|

|
|
40
42
|
|
|
41
|
-
- **Fast on large data.** Rendering runs in Rust: a 1,000,000-point line renders to PNG in about
|
|
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
|
-
|
|
268
|
-
|
|
269
|
-
| Case |
|
|
270
|
-
| --- | ---: |
|
|
271
|
-
| 1,000,000-point line → PNG |
|
|
272
|
-
| 1,000,000-point line on a time axis → PNG |
|
|
273
|
-
| 1,000,000-point line → SVG |
|
|
274
|
-
| 100,000-point scatter → PNG |
|
|
275
|
-
| Histogram of 1,000,000 values → PNG |
|
|
276
|
-
| 2-D histogram of 1,000,000 points, 100 × 100 bins → PNG |
|
|
277
|
-
| Hexbin of 1,000,000 points, grid size 50 → PNG |
|
|
278
|
-
| Heatmap of 1,000 × 1,000 cells → PNG |
|
|
279
|
-
|
|
|
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)
|
|
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
|

|
|
8
10
|
|
|
9
|
-
- **大数据量也快。** 渲染在 Rust
|
|
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
|
-
|
|
236
|
-
|
|
237
|
-
| 场景 |
|
|
238
|
-
| --- | ---: |
|
|
239
|
-
| 100 万点折线 → PNG |
|
|
240
|
-
| 时间轴上的 100 万点折线 → PNG |
|
|
241
|
-
| 100 万点折线 → SVG |
|
|
242
|
-
| 10 万点散点图 → PNG |
|
|
243
|
-
| 100 万个值的直方图 → PNG |
|
|
244
|
-
| 100 万个点的二维直方图,100 × 100 个 bin → PNG |
|
|
245
|
-
| 100 万个点的六边形分箱图,grid size 50 → PNG |
|
|
246
|
-
| 1,000 × 1,000 个格子的热力图 → PNG |
|
|
247
|
-
|
|
|
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
|
+
[](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
|

|
|
8
10
|
|
|
9
|
-
- **Fast on large data.** Rendering runs in Rust: a 1,000,000-point line renders to PNG in about
|
|
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
|
-
|
|
236
|
-
|
|
237
|
-
| Case |
|
|
238
|
-
| --- | ---: |
|
|
239
|
-
| 1,000,000-point line → PNG |
|
|
240
|
-
| 1,000,000-point line on a time axis → PNG |
|
|
241
|
-
| 1,000,000-point line → SVG |
|
|
242
|
-
| 100,000-point scatter → PNG |
|
|
243
|
-
| Histogram of 1,000,000 values → PNG |
|
|
244
|
-
| 2-D histogram of 1,000,000 points, 100 × 100 bins → PNG |
|
|
245
|
-
| Hexbin of 1,000,000 points, grid size 50 → PNG |
|
|
246
|
-
| Heatmap of 1,000 × 1,000 cells → PNG |
|
|
247
|
-
|
|
|
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.
|
|
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.
|
|
625
|
+
- umber-py 0.3.2
|
|
626
626
|
|
|
627
627
|
MIT License
|
|
628
628
|
|
|
@@ -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
|
|
141
|
-
///
|
|
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()
|