ggrust 0.2.0__tar.gz → 0.2.1__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.
- {ggrust-0.2.0 → ggrust-0.2.1}/Cargo.lock +2 -2
- {ggrust-0.2.0 → ggrust-0.2.1}/Cargo.toml +1 -1
- {ggrust-0.2.0 → ggrust-0.2.1}/PKG-INFO +1 -1
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/build.rs +65 -8
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/grid.rs +6 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/layout/zoom.rs +104 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/render/html.rs +5 -1
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/render/svg.rs +8 -2
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scene/mod.rs +12 -3
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/spec/plot.rs +4 -1
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/tooltip/mod.rs +4 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/build_lines.rs +2 -2
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/build_point.rs +2 -2
- {ggrust-0.2.0 → ggrust-0.2.1}/js/ggrust.js +97 -6
- ggrust-0.2.1/python/ggrust/plot/ggtb.py +124 -0
- ggrust-0.2.0/python/ggrust/plot/ggtb.py +0 -71
- {ggrust-0.2.0 → ggrust-0.2.1}/LICENSE +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/README.md +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/THIRD_PARTY_NOTICES.md +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/Cargo.toml +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/examples/compare_letsplot.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/examples/render_points.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/fonts/LiberationMono-Bold.ttf +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/fonts/LiberationMono-BoldItalic.ttf +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/fonts/LiberationMono-Italic.ttf +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/fonts/LiberationMono-Regular.ttf +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/fonts/LiberationSans-Bold.ttf +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/fonts/LiberationSans-BoldItalic.ttf +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/fonts/LiberationSans-Italic.ttf +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/fonts/LiberationSans-Regular.ttf +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/fonts/OFL.txt +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/coord/cartesian.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/coord/fixed.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/coord/flip.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/coord/mod.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/data/as_discrete.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/data/column.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/data/group.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/data/inline.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/data/layer.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/data/mod.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/data/resolution.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/error.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/facet.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/abline.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/area.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/area_ridges.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/bar.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/bin2d.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/blank.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/boxplot.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/bracket.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/contour.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/hex.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/hover.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/image.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/interval.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/jitter.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/lines.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/mod.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/path.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/point.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/polygon.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/quantiles.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/raster.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/rect.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/reference_line.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/repel.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/repel_layout.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/segment.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/sina.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/smooth.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/step.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/text.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/tile.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/violin.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/grid_share.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/layout/axis.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/layout/colorbar.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/layout/facet.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/layout/grid.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/layout/legend.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/layout/mod.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/layout/rotated.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/lib.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/parallel.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/parameters.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/position/dodge.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/position/identity.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/position/jitter.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/position/mod.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/position/nudge.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/position/random.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/position/shift.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/position/stack.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/render/canvas.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/render/mod.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/render/pdf.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/render/png.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/render/sprite.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/breaks.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/continuous.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/datetime.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/discrete.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/format.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/label_pattern.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/mod.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/number_format.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/position.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/spec.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/style/brewer.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/style/brewer_data.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/style/color.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/style/color_space.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/style/colormap.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/style/colormap_data.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/style/gradient.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/style/hue.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/style/identity.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/style/linetype.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/style/manual.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/style/mod.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/style/numeric.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/style/shape.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/style/spec.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/transform.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scene/color.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scene/color_names.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scene/encoded_png.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scene/geometry.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scene/marker.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/spec/aesthetic.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/spec/coord.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/spec/facet.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/spec/frames.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/spec/guide.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/spec/layer.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/spec/mod.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/spec/scale.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/spec/subplots.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/spec/theme.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/stat/bin.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/stat/bin2d.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/stat/binhex.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/stat/boxplot.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/stat/contour.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/stat/count.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/stat/density.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/stat/density2d.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/stat/density_1d.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/stat/densityridges.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/stat/ecdf.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/stat/groups.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/stat/identity.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/stat/mod.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/stat/sina.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/stat/smooth.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/stat/sum.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/stat/summary.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/stat/ydensity.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/style/mod.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/suggest.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/text/estimate.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/text/mod.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/theme/mod.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/theme/values.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/tooltip/content.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/tooltip/template.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/build_bracket.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/build_regressions.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/data_columns.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/fixtures/number_format.json +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/fixtures/specs/discrete_ordering.json +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/fixtures/specs/distributions.json +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/fixtures/specs/dotplot.json +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/fixtures/specs/faceted.json +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/fixtures/specs/grid.json +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/fixtures/specs/heatmap.json +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/fixtures/specs/image.json +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/fixtures/specs/pandas_categorical.json +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/fixtures/specs/polars_enum.json +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/fixtures/specs/primitives.json +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/fixtures/specs/ridge.json +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/fixtures/specs/umap_categorical.json +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/fixtures/specs/umap_continuous.json +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/fixtures/specs/violin_box.json +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/fixtures/specs/void_theme.json +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/fixtures/specs/volcano.json +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/fixtures/specs/with_inline_data.json +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/golden/scene_showcase.svg +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/render_parity.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/render_scene.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/render_sprites.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/spec_fixtures.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/spec_regressions.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-python/Cargo.toml +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-python/src/lib.rs +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/pyproject.toml +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/__init__.py +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/_data.py +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/_ggrust.pyi +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/_lets_plot.py +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/_render.py +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/_typing.py +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/errors.py +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/export.py +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/mapping.py +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/__init__.py +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/coord.py +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/core.py +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/expand_limits.py +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/facet.py +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/geom.py +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/geom_extras.py +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/geom_function.py +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/geom_imshow.py +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/gggrid.py +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/guide.py +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/label.py +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/plot.py +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/position.py +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/scale.py +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/scale_color.py +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/scale_identity.py +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/scale_shape.py +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/scale_size.py +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/series_meta.py +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/stat.py +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/subplots.py +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/theme.py +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/tooltip.py +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/util.py +0 -0
- {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/py.typed +0 -0
|
@@ -448,7 +448,7 @@ dependencies = [
|
|
|
448
448
|
|
|
449
449
|
[[package]]
|
|
450
450
|
name = "ggrust-core"
|
|
451
|
-
version = "0.2.
|
|
451
|
+
version = "0.2.1"
|
|
452
452
|
dependencies = [
|
|
453
453
|
"ahash",
|
|
454
454
|
"arrow-array",
|
|
@@ -470,7 +470,7 @@ dependencies = [
|
|
|
470
470
|
|
|
471
471
|
[[package]]
|
|
472
472
|
name = "ggrust-python"
|
|
473
|
-
version = "0.2.
|
|
473
|
+
version = "0.2.1"
|
|
474
474
|
dependencies = [
|
|
475
475
|
"ggrust-core",
|
|
476
476
|
"pyo3",
|
|
@@ -35,7 +35,7 @@ use crate::scale::number_format::{ExponentStyle, NumberFormat};
|
|
|
35
35
|
use crate::scale::style::spec::{StyleScaleSpec, style_scale_specs};
|
|
36
36
|
use crate::scale::style::{LegendEntry, StyleScale, default_mapper};
|
|
37
37
|
use crate::scale::{AxisScaleSpec, PositionScale, PositionScales, position_scales};
|
|
38
|
-
use crate::scene::{Group, Node, PerItem, Rect, Rects, Scene};
|
|
38
|
+
use crate::scene::{Group, LAYER_ROLE, Node, PerItem, Rect, Rects, Scene};
|
|
39
39
|
use crate::spec::{
|
|
40
40
|
Aesthetic, DataSource, GuideOptions, GuideSpec, LayerSpec, Mapping, MappingAnnotation,
|
|
41
41
|
MappingValue, Options, PlotSpec, SeriesAnnotation, TooltipsSpec, Value,
|
|
@@ -139,6 +139,9 @@ pub fn build_plot_with(spec: &PlotSpec, data: &PlotData, interactive: bool) -> R
|
|
|
139
139
|
..Interaction::default()
|
|
140
140
|
};
|
|
141
141
|
plot.interaction(&arrangement, &mut interaction, &mut warnings)?;
|
|
142
|
+
if let Some(zoom) = &interaction.zoom {
|
|
143
|
+
zoom.check_layer_labels("ggtoolbar")?;
|
|
144
|
+
}
|
|
142
145
|
Some(interaction)
|
|
143
146
|
} else {
|
|
144
147
|
None
|
|
@@ -949,14 +952,16 @@ impl PreparedPlot {
|
|
|
949
952
|
let Some((layers, tile_x, tile_y)) = self.tiles.get(tile) else {
|
|
950
953
|
continue;
|
|
951
954
|
};
|
|
952
|
-
|
|
953
|
-
|
|
955
|
+
let indices = toggle_indices(layers);
|
|
956
|
+
for ((layer, setup), index) in layers.iter().zip(&self.tooltip_setups).zip(indices) {
|
|
957
|
+
jobs.push((rect, tile_x, tile_y, index, layer, setup));
|
|
954
958
|
}
|
|
955
959
|
}
|
|
956
|
-
let layer_interaction = |&(rect, tile_x, tile_y, layer, setup): &(
|
|
960
|
+
let layer_interaction = |&(rect, tile_x, tile_y, layer_index, layer, setup): &(
|
|
957
961
|
Rect,
|
|
958
962
|
&PositionScale,
|
|
959
963
|
&PositionScale,
|
|
964
|
+
usize,
|
|
960
965
|
&PreparedLayer,
|
|
961
966
|
&LayerTooltipSetup,
|
|
962
967
|
),
|
|
@@ -1031,6 +1036,7 @@ impl PreparedPlot {
|
|
|
1031
1036
|
};
|
|
1032
1037
|
Ok(Some(LayerInteraction {
|
|
1033
1038
|
panel: LayerInteraction::panel_of(rect),
|
|
1039
|
+
layer: layer_index,
|
|
1034
1040
|
lookup,
|
|
1035
1041
|
targets,
|
|
1036
1042
|
content,
|
|
@@ -1091,7 +1097,22 @@ impl PreparedPlot {
|
|
|
1091
1097
|
scales.windows(2).all(|pair| pair[0].0 == pair[1].0),
|
|
1092
1098
|
scales.windows(2).all(|pair| pair[0].1 == pair[1].1),
|
|
1093
1099
|
);
|
|
1094
|
-
|
|
1100
|
+
let geoms = self
|
|
1101
|
+
.tiles
|
|
1102
|
+
.first()
|
|
1103
|
+
.map(|(layers, _, _)| {
|
|
1104
|
+
layers
|
|
1105
|
+
.iter()
|
|
1106
|
+
.filter(|layer| !layer.companion)
|
|
1107
|
+
.map(|layer| layer.geom.name())
|
|
1108
|
+
.collect()
|
|
1109
|
+
})
|
|
1110
|
+
.unwrap_or_default();
|
|
1111
|
+
ZoomPlot {
|
|
1112
|
+
tiles,
|
|
1113
|
+
shared,
|
|
1114
|
+
geoms,
|
|
1115
|
+
}
|
|
1095
1116
|
}
|
|
1096
1117
|
|
|
1097
1118
|
/// Draw the plot as laid out by `arrangement`: everything but the background
|
|
@@ -1111,9 +1132,21 @@ impl PreparedPlot {
|
|
|
1111
1132
|
.geom
|
|
1112
1133
|
.draw(&draw_layer, &panel.oriented(layer.y_oriented))
|
|
1113
1134
|
});
|
|
1114
|
-
let mut layer_nodes = Vec::new();
|
|
1115
|
-
for nodes in drawn {
|
|
1116
|
-
|
|
1135
|
+
let mut layer_nodes: Vec<Node> = Vec::new();
|
|
1136
|
+
for (nodes, layer) in drawn.into_iter().zip(layers) {
|
|
1137
|
+
let nodes = nodes?;
|
|
1138
|
+
if layer.companion
|
|
1139
|
+
&& let Some(Node::Group(group)) = layer_nodes.last_mut()
|
|
1140
|
+
{
|
|
1141
|
+
group.nodes.extend(nodes);
|
|
1142
|
+
continue;
|
|
1143
|
+
}
|
|
1144
|
+
layer_nodes.push(Node::Group(Group {
|
|
1145
|
+
clip: None,
|
|
1146
|
+
role: Some(LAYER_ROLE),
|
|
1147
|
+
anchor: None,
|
|
1148
|
+
nodes,
|
|
1149
|
+
}));
|
|
1117
1150
|
}
|
|
1118
1151
|
Ok(layer_nodes)
|
|
1119
1152
|
};
|
|
@@ -1540,6 +1573,26 @@ struct PreparedLayer {
|
|
|
1540
1573
|
styles: Styles,
|
|
1541
1574
|
/// `show_legend` (default: the geom's).
|
|
1542
1575
|
show_legend: bool,
|
|
1576
|
+
/// Drawn on behalf of the layer before it: the outlier points the Python
|
|
1577
|
+
/// `geom_boxplot` adds as a second spec layer. It shares that layer's group
|
|
1578
|
+
/// and toggle (`ggtb(layers=...)`), so toggle indices count the layers the
|
|
1579
|
+
/// user added.
|
|
1580
|
+
companion: bool,
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
/// The toggle index (`ggtb(layers=...)`) of every layer: companion layers take
|
|
1584
|
+
/// the index of the layer they belong to.
|
|
1585
|
+
fn toggle_indices(layers: &[PreparedLayer]) -> Vec<usize> {
|
|
1586
|
+
let mut next = 0;
|
|
1587
|
+
layers
|
|
1588
|
+
.iter()
|
|
1589
|
+
.map(|layer| {
|
|
1590
|
+
if !layer.companion || next == 0 {
|
|
1591
|
+
next += 1;
|
|
1592
|
+
}
|
|
1593
|
+
next - 1
|
|
1594
|
+
})
|
|
1595
|
+
.collect()
|
|
1543
1596
|
}
|
|
1544
1597
|
|
|
1545
1598
|
/// Stage 1 of a layer: look up geom/stat/position, validate options, fetch the
|
|
@@ -2138,12 +2191,15 @@ fn compute_layer(
|
|
|
2138
2191
|
y_oriented,
|
|
2139
2192
|
mapping_paths,
|
|
2140
2193
|
show_legend: spec.show_legend.unwrap_or(geom.shows_legend()),
|
|
2194
|
+
companion: layer_stat(spec).is_some_and(|stat| stat.name() == "boxplot_outlier"),
|
|
2141
2195
|
warnings,
|
|
2142
2196
|
})
|
|
2143
2197
|
}
|
|
2144
2198
|
|
|
2145
2199
|
/// A layer after [`compute_layer`]: positions scaled, stat and position applied.
|
|
2146
2200
|
struct ComputedLayer {
|
|
2201
|
+
/// See [`PreparedLayer::companion`].
|
|
2202
|
+
companion: bool,
|
|
2147
2203
|
/// Layer options with aliases normalised (`colour` -> `color`).
|
|
2148
2204
|
options: Options,
|
|
2149
2205
|
path: String,
|
|
@@ -2206,6 +2262,7 @@ fn style_layer(
|
|
|
2206
2262
|
y_span: layer.y_span,
|
|
2207
2263
|
styles,
|
|
2208
2264
|
show_legend: layer.show_legend,
|
|
2265
|
+
companion: layer.companion,
|
|
2209
2266
|
},
|
|
2210
2267
|
warnings,
|
|
2211
2268
|
))
|
|
@@ -81,6 +81,12 @@ pub fn build_figure_with(
|
|
|
81
81
|
nodes,
|
|
82
82
|
};
|
|
83
83
|
scene.validate()?;
|
|
84
|
+
if let Some(zoom) = collected
|
|
85
|
+
.as_ref()
|
|
86
|
+
.and_then(|(interaction, _)| interaction.zoom.as_ref())
|
|
87
|
+
{
|
|
88
|
+
zoom.check_layer_labels("ggtoolbar")?;
|
|
89
|
+
}
|
|
84
90
|
let interaction = collected.map(|(interaction, notes)| {
|
|
85
91
|
for note in notes {
|
|
86
92
|
if !warnings.contains(¬e) {
|
|
@@ -72,6 +72,9 @@ pub struct Zoom {
|
|
|
72
72
|
pub size_zoomin: f64,
|
|
73
73
|
/// Every plot of the figure (one, or the plots of a grid), in drawing order.
|
|
74
74
|
pub plots: Vec<ZoomPlot>,
|
|
75
|
+
/// Layer visibility toggles (`ggtb(layers=...)`): `None` without them,
|
|
76
|
+
/// else the user's labels as (label, layer index), possibly empty.
|
|
77
|
+
pub layer_labels: Option<Vec<(String, usize)>>,
|
|
75
78
|
}
|
|
76
79
|
|
|
77
80
|
/// One plot: its panels zoom together on the axes they share.
|
|
@@ -82,6 +85,8 @@ pub struct ZoomPlot {
|
|
|
82
85
|
/// Whether every panel has the same horizontal / vertical scale (facets with
|
|
83
86
|
/// fixed scales): zooming one panel then zooms all of them on that axis.
|
|
84
87
|
pub shared: (bool, bool),
|
|
88
|
+
/// The geom of each layer, in layer order (the default toggle labels).
|
|
89
|
+
pub geoms: Vec<&'static str>,
|
|
85
90
|
}
|
|
86
91
|
|
|
87
92
|
/// One panel.
|
|
@@ -160,8 +165,107 @@ impl Zoom {
|
|
|
160
165
|
size_basis,
|
|
161
166
|
size_zoomin,
|
|
162
167
|
plots: Vec::new(),
|
|
168
|
+
layer_labels: parse_layer_labels(toolbar.layers.as_ref(), &format!("{path}.layers"))?,
|
|
163
169
|
}))
|
|
164
170
|
}
|
|
171
|
+
|
|
172
|
+
/// The toggle label of every layer of `plot`, or `None` without toggles:
|
|
173
|
+
/// the user's label, else the geom name, numbered from its second unlabelled
|
|
174
|
+
/// use (`point`, `point 2`). `geom_imshow` layers (geom `image`) read `imshow`.
|
|
175
|
+
pub fn layer_toggle_labels(&self, plot: &ZoomPlot) -> Option<Vec<String>> {
|
|
176
|
+
let named = self.layer_labels.as_ref()?;
|
|
177
|
+
let mut seen: Vec<(&str, usize)> = Vec::new();
|
|
178
|
+
let labels = plot
|
|
179
|
+
.geoms
|
|
180
|
+
.iter()
|
|
181
|
+
.enumerate()
|
|
182
|
+
.map(|(index, geom)| {
|
|
183
|
+
if let Some((label, _)) = named.iter().find(|(_, layer)| *layer == index) {
|
|
184
|
+
return label.clone();
|
|
185
|
+
}
|
|
186
|
+
let geom = if *geom == "image" { "imshow" } else { geom };
|
|
187
|
+
let count = match seen.iter_mut().find(|(name, _)| *name == geom) {
|
|
188
|
+
Some((_, count)) => {
|
|
189
|
+
*count += 1;
|
|
190
|
+
*count
|
|
191
|
+
}
|
|
192
|
+
None => {
|
|
193
|
+
seen.push((geom, 1));
|
|
194
|
+
1
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
match count {
|
|
198
|
+
1 => geom.to_string(),
|
|
199
|
+
_ => format!("{geom} {count}"),
|
|
200
|
+
}
|
|
201
|
+
})
|
|
202
|
+
.collect();
|
|
203
|
+
Some(labels)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/// Check, once every plot is collected, that each labelled layer index
|
|
207
|
+
/// exists in at least one plot; `path` locates `ggtb` in error messages.
|
|
208
|
+
pub fn check_layer_labels(&self, path: &str) -> crate::Result<()> {
|
|
209
|
+
let Some(named) = &self.layer_labels else {
|
|
210
|
+
return Ok(());
|
|
211
|
+
};
|
|
212
|
+
let count = self
|
|
213
|
+
.plots
|
|
214
|
+
.iter()
|
|
215
|
+
.map(|plot| plot.geoms.len())
|
|
216
|
+
.max()
|
|
217
|
+
.unwrap_or(0);
|
|
218
|
+
match named.iter().find(|(_, layer)| *layer >= count) {
|
|
219
|
+
Some((label, layer)) => Err(crate::Error::spec(
|
|
220
|
+
format!("{path}.layers.{label}"),
|
|
221
|
+
format!(
|
|
222
|
+
"layer index {layer} does not exist; the plot has {count} layer(s), \
|
|
223
|
+
indexed from 0 in the order they were added"
|
|
224
|
+
),
|
|
225
|
+
)),
|
|
226
|
+
None => Ok(()),
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/// `ggtb(layers=...)`: `true` gives toggles with default labels, `false` or
|
|
232
|
+
/// nothing none, an object maps labels to layer indices.
|
|
233
|
+
fn parse_layer_labels(
|
|
234
|
+
value: Option<&crate::spec::Value>,
|
|
235
|
+
path: &str,
|
|
236
|
+
) -> crate::Result<Option<Vec<(String, usize)>>> {
|
|
237
|
+
use crate::spec::Value;
|
|
238
|
+
let accepted = "use true, false, or a dict of labels to layer indices such as {\"spots\": 1}";
|
|
239
|
+
match value {
|
|
240
|
+
None | Some(Value::Null) | Some(Value::Bool(false)) => Ok(None),
|
|
241
|
+
Some(Value::Bool(true)) => Ok(Some(Vec::new())),
|
|
242
|
+
Some(Value::Object(entries)) => {
|
|
243
|
+
let mut labels: Vec<(String, usize)> = Vec::new();
|
|
244
|
+
for (label, index) in entries {
|
|
245
|
+
let Some(layer) = index.as_u64().and_then(|value| usize::try_from(value).ok())
|
|
246
|
+
else {
|
|
247
|
+
return Err(crate::Error::spec(
|
|
248
|
+
format!("{path}.{label}"),
|
|
249
|
+
format!("{index} is not a layer index; use an integer >= 0"),
|
|
250
|
+
));
|
|
251
|
+
};
|
|
252
|
+
if let Some((other, _)) = labels.iter().find(|(_, existing)| *existing == layer) {
|
|
253
|
+
return Err(crate::Error::spec(
|
|
254
|
+
format!("{path}.{label}"),
|
|
255
|
+
format!(
|
|
256
|
+
"layer {layer} is already labelled '{other}'; give each layer one label"
|
|
257
|
+
),
|
|
258
|
+
));
|
|
259
|
+
}
|
|
260
|
+
labels.push((label.clone(), layer));
|
|
261
|
+
}
|
|
262
|
+
Ok(Some(labels))
|
|
263
|
+
}
|
|
264
|
+
Some(other) => Err(crate::Error::spec(
|
|
265
|
+
path,
|
|
266
|
+
format!("{other} is not accepted; {accepted}"),
|
|
267
|
+
)),
|
|
268
|
+
}
|
|
165
269
|
}
|
|
166
270
|
|
|
167
271
|
/// Placeholder text of a generated label template.
|
|
@@ -187,7 +187,11 @@ fn zoom_json(zoom: &Zoom, scene: &Scene) -> Value {
|
|
|
187
187
|
})
|
|
188
188
|
})
|
|
189
189
|
.collect();
|
|
190
|
-
json!({ "tiles": tiles, "shared": [plot.shared.0, plot.shared.1] })
|
|
190
|
+
let mut value = json!({ "tiles": tiles, "shared": [plot.shared.0, plot.shared.1] });
|
|
191
|
+
if let Some(labels) = zoom.layer_toggle_labels(plot) {
|
|
192
|
+
value["layers"] = json!(labels);
|
|
193
|
+
}
|
|
194
|
+
value
|
|
191
195
|
})
|
|
192
196
|
.collect();
|
|
193
197
|
json!({
|
|
@@ -16,8 +16,9 @@ use super::canvas;
|
|
|
16
16
|
use crate::parallel;
|
|
17
17
|
use crate::scene::geometry::{ClipBox, is_drawable_marker, is_drawable_stroke, is_drawable_width};
|
|
18
18
|
use crate::scene::{
|
|
19
|
-
Bitmap, Color, Group, HorizontalAlign, Image, MarkerPart, Node, Orientation, Path,
|
|
20
|
-
Points, Rect, Rects, SUPERSCRIPT_RISE, SUPERSCRIPT_SCALE, Scene, Stroke, Text,
|
|
19
|
+
Bitmap, Color, Group, HorizontalAlign, Image, LAYER_ROLE, MarkerPart, Node, Orientation, Path,
|
|
20
|
+
PerItem, Points, Rect, Rects, SUPERSCRIPT_RISE, SUPERSCRIPT_SCALE, Scene, Stroke, Text,
|
|
21
|
+
VerticalAlign,
|
|
21
22
|
};
|
|
22
23
|
use crate::text::{CSS_FONT_FAMILY, FontFamily, measure, single_line};
|
|
23
24
|
|
|
@@ -273,6 +274,11 @@ impl SvgWriter {
|
|
|
273
274
|
}
|
|
274
275
|
|
|
275
276
|
fn group(&mut self, group: &Group) {
|
|
277
|
+
if group.role == Some(LAYER_ROLE) && group.clip.is_none() && !self.zoomable {
|
|
278
|
+
// Static output draws layer groups inline.
|
|
279
|
+
self.nodes(&group.nodes);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
276
282
|
if let Some((x, y)) = group
|
|
277
283
|
.anchor
|
|
278
284
|
.filter(|_| group.clip.is_none() && group.role.is_none())
|
|
@@ -101,14 +101,23 @@ impl Scene {
|
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
/// Role of the group holding one layer's marks in a panel (one per layer, in
|
|
105
|
+
/// layer order, empty when the layer draws nothing): the HTML runtime hides and
|
|
106
|
+
/// shows layers by it (`ggtb(layers=...)`). Static output draws it inline.
|
|
107
|
+
pub const LAYER_ROLE: &str = "layer";
|
|
108
|
+
|
|
104
109
|
impl Group {
|
|
105
|
-
/// The children, with anchored groups ([`Group::anchor`])
|
|
106
|
-
/// own children: what is drawn, in order.
|
|
110
|
+
/// The children, with anchored groups ([`Group::anchor`]) and layer groups
|
|
111
|
+
/// ([`LAYER_ROLE`]) replaced by their own children: what is drawn, in order.
|
|
107
112
|
pub fn flattened(&self) -> Vec<&Node> {
|
|
108
113
|
fn walk<'a>(nodes: &'a [Node], flat: &mut Vec<&'a Node>) {
|
|
109
114
|
for node in nodes {
|
|
110
115
|
match node {
|
|
111
|
-
Node::Group(group)
|
|
116
|
+
Node::Group(group)
|
|
117
|
+
if group.anchor.is_some() || group.role == Some(LAYER_ROLE) =>
|
|
118
|
+
{
|
|
119
|
+
walk(&group.nodes, flat)
|
|
120
|
+
}
|
|
112
121
|
other => flat.push(other),
|
|
113
122
|
}
|
|
114
123
|
}
|
|
@@ -216,7 +216,7 @@ pub struct CaptionSpec {
|
|
|
216
216
|
pub unsupported: Options,
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
-
/// `ggtb(size_basis=..., size_zoomin=...)`.
|
|
219
|
+
/// `ggtb(size_basis=..., size_zoomin=..., layers=...)`.
|
|
220
220
|
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
|
|
221
221
|
#[serde(default)]
|
|
222
222
|
pub struct ToolbarSpec {
|
|
@@ -224,6 +224,9 @@ pub struct ToolbarSpec {
|
|
|
224
224
|
pub size_basis: Option<String>,
|
|
225
225
|
/// How symbol sizes react to zoom-in; `-1` means unlimited growth.
|
|
226
226
|
pub size_zoomin: Option<f64>,
|
|
227
|
+
/// Layer visibility toggles (a ggrust extension): `true`, or an object of
|
|
228
|
+
/// labels to layer indices (`{"spots": 1}`). Parsed by `Zoom::from_spec`.
|
|
229
|
+
pub layers: Option<super::Value>,
|
|
227
230
|
/// Keys no component consumes.
|
|
228
231
|
#[serde(flatten)]
|
|
229
232
|
pub unsupported: Options,
|
|
@@ -214,6 +214,9 @@ pub struct LayerInteraction {
|
|
|
214
214
|
/// The panel, `[x, y, width, height]` in scene pixels: axis tooltips sit on
|
|
215
215
|
/// its edges, anchored tooltips in its corners.
|
|
216
216
|
pub panel: [f64; 4],
|
|
217
|
+
/// The layer's toggle index in its plot (`ggtb(layers=...)`, layers as the user
|
|
218
|
+
/// added them): hidden layers do not answer the cursor.
|
|
219
|
+
pub layer: usize,
|
|
217
220
|
/// How targets are found.
|
|
218
221
|
pub lookup: Lookup,
|
|
219
222
|
/// The targets.
|
|
@@ -320,6 +323,7 @@ mod tests {
|
|
|
320
323
|
let interaction = Interaction {
|
|
321
324
|
layers: vec![LayerInteraction {
|
|
322
325
|
panel: [0.0, 0.0, 10.0, 10.0],
|
|
326
|
+
layer: 0,
|
|
323
327
|
lookup: Lookup::Nearest,
|
|
324
328
|
targets: HoverTargets::Points {
|
|
325
329
|
x: vec![1.0],
|
|
@@ -225,7 +225,7 @@ fn intercepts_train_the_scales() {
|
|
|
225
225
|
.groups_with_role("panel")
|
|
226
226
|
.into_iter()
|
|
227
227
|
.filter_map(|group| {
|
|
228
|
-
group.
|
|
228
|
+
group.flattened().into_iter().find_map(|node| match node {
|
|
229
229
|
Node::Points(points) => Some(points.x[1]),
|
|
230
230
|
_ => None,
|
|
231
231
|
})
|
|
@@ -318,7 +318,7 @@ fn blank_draws_nothing_but_trains_the_scales() {
|
|
|
318
318
|
frame(vec![0.0, 10.0], vec![0.0, 10.0], vec!["a", "a"]),
|
|
319
319
|
);
|
|
320
320
|
assert!(built.warnings.is_empty(), "{:?}", built.warnings);
|
|
321
|
-
assert!(panel(&built).
|
|
321
|
+
assert!(panel(&built).flattened().is_empty());
|
|
322
322
|
// The axis shows the data range, so tick labels exist.
|
|
323
323
|
let has_text = built
|
|
324
324
|
.scene
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
// The SVG is complete without this script: notebooks that do not run JavaScript
|
|
12
12
|
// (GitHub, nbviewer) still show the static plot.
|
|
13
13
|
// With `ggtb()` it also adds lets-plot's toolbar (pan, rubber band zoom,
|
|
14
|
-
// centerpoint zoom, reset) and zooms panels: see "Zoom and pan" below.
|
|
14
|
+
// centerpoint zoom, reset) and zooms panels: see "Zoom and pan" below. With
|
|
15
|
+
// `ggtb(layers=...)` the toolbar also hides and shows layers: see "Layer toggles".
|
|
15
16
|
// Dense point batches (more than 50k markers) arrive as base64 typed arrays
|
|
16
17
|
// and are stamped on a canvas inside their SVG placeholder: see "Canvas points".
|
|
17
18
|
//
|
|
@@ -238,7 +239,7 @@
|
|
|
238
239
|
function lookup(plot, x, y) {
|
|
239
240
|
var best = null;
|
|
240
241
|
plot.layers.forEach(function (layer) {
|
|
241
|
-
if (!inPanel(layer, x, y)) {
|
|
242
|
+
if (layer.hidden || !inPanel(layer, x, y)) {
|
|
242
243
|
return;
|
|
243
244
|
}
|
|
244
245
|
var result = search(layer, x, y);
|
|
@@ -530,6 +531,7 @@
|
|
|
530
531
|
var width = layer.canvas.width, height = layer.canvas.height;
|
|
531
532
|
context.setTransform(1, 0, 0, 1, 0, 0);
|
|
532
533
|
context.clearRect(0, 0, width, height);
|
|
534
|
+
if (layer.hidden) return;
|
|
533
535
|
var direct = layer.styles.length > MAX_SPRITES;
|
|
534
536
|
if (!direct && layer.sprites.k !== k) layer.sprites = { k: k };
|
|
535
537
|
var hs = h.scale * ratio, ho = h.offset * ratio, vs = v.scale * ratio, vo = v.offset * ratio;
|
|
@@ -976,7 +978,9 @@
|
|
|
976
978
|
// with their anchor, everything else is scaled.
|
|
977
979
|
function collectMarks(container, marks) {
|
|
978
980
|
for (var child = container.firstElementChild; child; child = child.nextElementSibling) {
|
|
979
|
-
if (child.getAttribute("class") === "
|
|
981
|
+
if (child.getAttribute("class") === "layer") {
|
|
982
|
+
collectMarks(child, marks); // one layer's marks (see "Layer toggles")
|
|
983
|
+
} else if (child.getAttribute("class") === "ggrust-canvas") {
|
|
980
984
|
if (child.ggrustCanvas) marks.push({ kind: "canvas", layer: child.ggrustCanvas });
|
|
981
985
|
} else if (child.getAttribute("class") === "ggrust-points") {
|
|
982
986
|
for (var m = child.firstElementChild; m; m = m.nextElementSibling) {
|
|
@@ -994,7 +998,8 @@
|
|
|
994
998
|
} else if (child.tagName.toLowerCase() === "g" && child.querySelector(".ggrust-points,.ggrust-canvas,[data-anchor]")) {
|
|
995
999
|
collectMarks(child, marks);
|
|
996
1000
|
} else {
|
|
997
|
-
|
|
1001
|
+
// A shape may carry its own transform (a mirrored image): zoom composes with it.
|
|
1002
|
+
marks.push({ kind: "shape", element: child, transform: child.getAttribute("transform") });
|
|
998
1003
|
}
|
|
999
1004
|
}
|
|
1000
1005
|
}
|
|
@@ -1049,8 +1054,13 @@
|
|
|
1049
1054
|
}
|
|
1050
1055
|
var e = mark.element;
|
|
1051
1056
|
if (mark.kind === "shape") {
|
|
1052
|
-
|
|
1053
|
-
|
|
1057
|
+
var own = mark.transform;
|
|
1058
|
+
if (reset) {
|
|
1059
|
+
if (own) e.setAttribute("transform", own);
|
|
1060
|
+
else e.removeAttribute("transform");
|
|
1061
|
+
} else {
|
|
1062
|
+
e.setAttribute("transform", own ? matrix + " " + own : matrix);
|
|
1063
|
+
}
|
|
1054
1064
|
return;
|
|
1055
1065
|
}
|
|
1056
1066
|
var x = h.scale * mark.x + h.offset, y = v.scale * mark.y + v.offset;
|
|
@@ -1382,9 +1392,88 @@
|
|
|
1382
1392
|
if (plot.zoom.tool) selectTool(plot, plot.zoom.tool);
|
|
1383
1393
|
resetViews(plot);
|
|
1384
1394
|
});
|
|
1395
|
+
if (plot.zoom.plots.some(function (zoomPlot) { return zoomPlot.layers; })) {
|
|
1396
|
+
layersMenu(plot, bar);
|
|
1397
|
+
}
|
|
1385
1398
|
plot.root.insertBefore(holder, plot.svg);
|
|
1386
1399
|
}
|
|
1387
1400
|
|
|
1401
|
+
// -- Layer toggles (`ggtb(layers=...)`, a ggrust extension) --
|
|
1402
|
+
//
|
|
1403
|
+
// Every panel holds one `g.layer` per layer, in layer order (empty when the
|
|
1404
|
+
// layer draws nothing). Hiding layer i of a plot hides that group in each of
|
|
1405
|
+
// its panels, stops its canvas points from drawing and its tooltip targets
|
|
1406
|
+
// from answering. Each plot of a grid has its own list; legends stay.
|
|
1407
|
+
|
|
1408
|
+
var LAYERS_ICON = icon('<path fill-rule="evenodd" d="M8,1.5 1.5,5 8,8.5 14.5,5ZM8,2.64 12.39,5 8,7.36 3.61,5Z"/>' +
|
|
1409
|
+
'<path d="M2.24,7.6 1.76,8.48 8,11.84 14.24,8.48 13.76,7.6 8,10.7Z"/>' +
|
|
1410
|
+
'<path d="M2.24,10.6 1.76,11.48 8,14.84 14.24,11.48 13.76,10.6 8,13.7Z"/>');
|
|
1411
|
+
|
|
1412
|
+
function layerGroup(tile, index) {
|
|
1413
|
+
var panel = childByClass(tile.element, "panel");
|
|
1414
|
+
var count = 0;
|
|
1415
|
+
for (var child = panel ? panel.firstElementChild : null; child; child = child.nextElementSibling) {
|
|
1416
|
+
if (child.getAttribute("class") === "layer" && count++ === index) return child;
|
|
1417
|
+
}
|
|
1418
|
+
return null;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
function setLayerVisible(plot, zoomPlot, index, visible) {
|
|
1422
|
+
zoomPlot.hidden[index] = !visible;
|
|
1423
|
+
zoomPlot.tiles.forEach(function (tile) {
|
|
1424
|
+
var group = layerGroup(tile, index);
|
|
1425
|
+
if (group) {
|
|
1426
|
+
group.style.display = visible ? "" : "none";
|
|
1427
|
+
var holders = group.querySelectorAll("foreignObject.ggrust-canvas");
|
|
1428
|
+
for (var i = 0; i < holders.length; i++) {
|
|
1429
|
+
if (holders[i].ggrustCanvas) holders[i].ggrustCanvas.hidden = !visible;
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
(plot.layers || []).forEach(function (entry) {
|
|
1433
|
+
if (entry.layer === index && samePanel(entry.panel, tile.spec.panel)) entry.hidden = !visible;
|
|
1434
|
+
});
|
|
1435
|
+
tile.dirty = true; // redraws its canvas points at the current view
|
|
1436
|
+
});
|
|
1437
|
+
if (plot.overlay) show(plot, null);
|
|
1438
|
+
schedule(plot);
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
function layersMenu(plot, bar) {
|
|
1442
|
+
var menu = element("div", "position:absolute;top:34px;left:50%;transform:translateX(-50%);z-index:2;" +
|
|
1443
|
+
"display:none;box-sizing:border-box;min-width:120px;padding:6px 8px;background-color:rgba(255, 255, 255, 0.97);" +
|
|
1444
|
+
"border:" + BAR_BORDER + ";border-radius:10px;box-shadow:0 2px 8px rgba(0, 0, 0, 0.12);" +
|
|
1445
|
+
"font:12px sans-serif;color:" + BUTTON_SELECTED + ";text-align:left;white-space:nowrap;", bar);
|
|
1446
|
+
menu.className = "ggrust-layers";
|
|
1447
|
+
var listed = plot.zoom.plots.filter(function (zoomPlot) { return zoomPlot.layers; });
|
|
1448
|
+
plot.zoom.plots.forEach(function (zoomPlot, plotIndex) {
|
|
1449
|
+
if (!zoomPlot.layers) return;
|
|
1450
|
+
if (listed.length > 1) {
|
|
1451
|
+
var heading = element("div", "margin:4px 0 2px 0;font-weight:bold;color:" + BUTTON_STROKE + ";", menu);
|
|
1452
|
+
heading.textContent = "Plot " + (plotIndex + 1);
|
|
1453
|
+
}
|
|
1454
|
+
zoomPlot.layers.forEach(function (label, index) {
|
|
1455
|
+
var row = element("label", "display:flex;align-items:center;gap:6px;padding:2px 0;cursor:pointer;", menu);
|
|
1456
|
+
var box = element("input", "margin:0;cursor:pointer;", row);
|
|
1457
|
+
box.type = "checkbox";
|
|
1458
|
+
box.checked = !zoomPlot.hidden[index];
|
|
1459
|
+
box.addEventListener("change", function () { setLayerVisible(plot, zoomPlot, index, box.checked); });
|
|
1460
|
+
element("span", "", row).textContent = label;
|
|
1461
|
+
});
|
|
1462
|
+
});
|
|
1463
|
+
var toggle = button("Layers", LAYERS_ICON, bar, function () {
|
|
1464
|
+
var open = menu.style.display === "none";
|
|
1465
|
+
menu.style.display = open ? "block" : "none";
|
|
1466
|
+
setButton(toggle, open);
|
|
1467
|
+
});
|
|
1468
|
+
bar.insertBefore(toggle, bar.children[TOOLS.length]); // after the zoom tools, before Reset
|
|
1469
|
+
document.addEventListener("mousedown", function (event) {
|
|
1470
|
+
if (menu.style.display !== "none" && !bar.contains(event.target)) {
|
|
1471
|
+
menu.style.display = "none";
|
|
1472
|
+
setButton(toggle, false);
|
|
1473
|
+
}
|
|
1474
|
+
});
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1388
1477
|
// Set up zoom/pan when the engine wrote a zoom payload (`ggtb()`).
|
|
1389
1478
|
function attachZoom(plot) {
|
|
1390
1479
|
var data = plot.root.querySelector("script.ggrust-zoom");
|
|
@@ -1404,6 +1493,8 @@
|
|
|
1404
1493
|
plots: payload.plots.map(function (zoomPlot) {
|
|
1405
1494
|
return {
|
|
1406
1495
|
shared: zoomPlot.shared,
|
|
1496
|
+
layers: zoomPlot.layers || null,
|
|
1497
|
+
hidden: [],
|
|
1407
1498
|
tiles: zoomPlot.tiles.map(function (spec) {
|
|
1408
1499
|
var tile = { spec: spec, element: tileElements[next++] || null };
|
|
1409
1500
|
tile.views = { h: spec.horizontal.domain.slice(), v: spec.vertical.domain.slice() };
|