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.
Files changed (233) hide show
  1. {ggrust-0.2.0 → ggrust-0.2.1}/Cargo.lock +2 -2
  2. {ggrust-0.2.0 → ggrust-0.2.1}/Cargo.toml +1 -1
  3. {ggrust-0.2.0 → ggrust-0.2.1}/PKG-INFO +1 -1
  4. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/build.rs +65 -8
  5. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/grid.rs +6 -0
  6. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/layout/zoom.rs +104 -0
  7. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/render/html.rs +5 -1
  8. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/render/svg.rs +8 -2
  9. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scene/mod.rs +12 -3
  10. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/spec/plot.rs +4 -1
  11. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/tooltip/mod.rs +4 -0
  12. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/build_lines.rs +2 -2
  13. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/build_point.rs +2 -2
  14. {ggrust-0.2.0 → ggrust-0.2.1}/js/ggrust.js +97 -6
  15. ggrust-0.2.1/python/ggrust/plot/ggtb.py +124 -0
  16. ggrust-0.2.0/python/ggrust/plot/ggtb.py +0 -71
  17. {ggrust-0.2.0 → ggrust-0.2.1}/LICENSE +0 -0
  18. {ggrust-0.2.0 → ggrust-0.2.1}/README.md +0 -0
  19. {ggrust-0.2.0 → ggrust-0.2.1}/THIRD_PARTY_NOTICES.md +0 -0
  20. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/Cargo.toml +0 -0
  21. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/examples/compare_letsplot.rs +0 -0
  22. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/examples/render_points.rs +0 -0
  23. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/fonts/LiberationMono-Bold.ttf +0 -0
  24. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/fonts/LiberationMono-BoldItalic.ttf +0 -0
  25. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/fonts/LiberationMono-Italic.ttf +0 -0
  26. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/fonts/LiberationMono-Regular.ttf +0 -0
  27. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/fonts/LiberationSans-Bold.ttf +0 -0
  28. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/fonts/LiberationSans-BoldItalic.ttf +0 -0
  29. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/fonts/LiberationSans-Italic.ttf +0 -0
  30. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/fonts/LiberationSans-Regular.ttf +0 -0
  31. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/fonts/OFL.txt +0 -0
  32. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/coord/cartesian.rs +0 -0
  33. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/coord/fixed.rs +0 -0
  34. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/coord/flip.rs +0 -0
  35. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/coord/mod.rs +0 -0
  36. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/data/as_discrete.rs +0 -0
  37. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/data/column.rs +0 -0
  38. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/data/group.rs +0 -0
  39. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/data/inline.rs +0 -0
  40. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/data/layer.rs +0 -0
  41. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/data/mod.rs +0 -0
  42. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/data/resolution.rs +0 -0
  43. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/error.rs +0 -0
  44. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/facet.rs +0 -0
  45. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/abline.rs +0 -0
  46. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/area.rs +0 -0
  47. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/area_ridges.rs +0 -0
  48. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/bar.rs +0 -0
  49. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/bin2d.rs +0 -0
  50. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/blank.rs +0 -0
  51. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/boxplot.rs +0 -0
  52. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/bracket.rs +0 -0
  53. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/contour.rs +0 -0
  54. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/hex.rs +0 -0
  55. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/hover.rs +0 -0
  56. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/image.rs +0 -0
  57. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/interval.rs +0 -0
  58. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/jitter.rs +0 -0
  59. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/lines.rs +0 -0
  60. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/mod.rs +0 -0
  61. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/path.rs +0 -0
  62. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/point.rs +0 -0
  63. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/polygon.rs +0 -0
  64. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/quantiles.rs +0 -0
  65. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/raster.rs +0 -0
  66. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/rect.rs +0 -0
  67. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/reference_line.rs +0 -0
  68. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/repel.rs +0 -0
  69. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/repel_layout.rs +0 -0
  70. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/segment.rs +0 -0
  71. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/sina.rs +0 -0
  72. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/smooth.rs +0 -0
  73. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/step.rs +0 -0
  74. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/text.rs +0 -0
  75. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/tile.rs +0 -0
  76. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/geom/violin.rs +0 -0
  77. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/grid_share.rs +0 -0
  78. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/layout/axis.rs +0 -0
  79. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/layout/colorbar.rs +0 -0
  80. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/layout/facet.rs +0 -0
  81. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/layout/grid.rs +0 -0
  82. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/layout/legend.rs +0 -0
  83. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/layout/mod.rs +0 -0
  84. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/layout/rotated.rs +0 -0
  85. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/lib.rs +0 -0
  86. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/parallel.rs +0 -0
  87. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/parameters.rs +0 -0
  88. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/position/dodge.rs +0 -0
  89. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/position/identity.rs +0 -0
  90. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/position/jitter.rs +0 -0
  91. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/position/mod.rs +0 -0
  92. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/position/nudge.rs +0 -0
  93. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/position/random.rs +0 -0
  94. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/position/shift.rs +0 -0
  95. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/position/stack.rs +0 -0
  96. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/render/canvas.rs +0 -0
  97. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/render/mod.rs +0 -0
  98. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/render/pdf.rs +0 -0
  99. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/render/png.rs +0 -0
  100. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/render/sprite.rs +0 -0
  101. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/breaks.rs +0 -0
  102. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/continuous.rs +0 -0
  103. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/datetime.rs +0 -0
  104. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/discrete.rs +0 -0
  105. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/format.rs +0 -0
  106. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/label_pattern.rs +0 -0
  107. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/mod.rs +0 -0
  108. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/number_format.rs +0 -0
  109. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/position.rs +0 -0
  110. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/spec.rs +0 -0
  111. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/style/brewer.rs +0 -0
  112. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/style/brewer_data.rs +0 -0
  113. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/style/color.rs +0 -0
  114. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/style/color_space.rs +0 -0
  115. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/style/colormap.rs +0 -0
  116. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/style/colormap_data.rs +0 -0
  117. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/style/gradient.rs +0 -0
  118. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/style/hue.rs +0 -0
  119. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/style/identity.rs +0 -0
  120. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/style/linetype.rs +0 -0
  121. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/style/manual.rs +0 -0
  122. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/style/mod.rs +0 -0
  123. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/style/numeric.rs +0 -0
  124. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/style/shape.rs +0 -0
  125. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/style/spec.rs +0 -0
  126. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scale/transform.rs +0 -0
  127. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scene/color.rs +0 -0
  128. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scene/color_names.rs +0 -0
  129. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scene/encoded_png.rs +0 -0
  130. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scene/geometry.rs +0 -0
  131. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/scene/marker.rs +0 -0
  132. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/spec/aesthetic.rs +0 -0
  133. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/spec/coord.rs +0 -0
  134. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/spec/facet.rs +0 -0
  135. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/spec/frames.rs +0 -0
  136. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/spec/guide.rs +0 -0
  137. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/spec/layer.rs +0 -0
  138. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/spec/mod.rs +0 -0
  139. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/spec/scale.rs +0 -0
  140. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/spec/subplots.rs +0 -0
  141. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/spec/theme.rs +0 -0
  142. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/stat/bin.rs +0 -0
  143. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/stat/bin2d.rs +0 -0
  144. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/stat/binhex.rs +0 -0
  145. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/stat/boxplot.rs +0 -0
  146. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/stat/contour.rs +0 -0
  147. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/stat/count.rs +0 -0
  148. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/stat/density.rs +0 -0
  149. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/stat/density2d.rs +0 -0
  150. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/stat/density_1d.rs +0 -0
  151. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/stat/densityridges.rs +0 -0
  152. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/stat/ecdf.rs +0 -0
  153. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/stat/groups.rs +0 -0
  154. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/stat/identity.rs +0 -0
  155. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/stat/mod.rs +0 -0
  156. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/stat/sina.rs +0 -0
  157. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/stat/smooth.rs +0 -0
  158. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/stat/sum.rs +0 -0
  159. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/stat/summary.rs +0 -0
  160. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/stat/ydensity.rs +0 -0
  161. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/style/mod.rs +0 -0
  162. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/suggest.rs +0 -0
  163. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/text/estimate.rs +0 -0
  164. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/text/mod.rs +0 -0
  165. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/theme/mod.rs +0 -0
  166. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/theme/values.rs +0 -0
  167. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/tooltip/content.rs +0 -0
  168. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/src/tooltip/template.rs +0 -0
  169. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/build_bracket.rs +0 -0
  170. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/build_regressions.rs +0 -0
  171. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/data_columns.rs +0 -0
  172. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/fixtures/number_format.json +0 -0
  173. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/fixtures/specs/discrete_ordering.json +0 -0
  174. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/fixtures/specs/distributions.json +0 -0
  175. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/fixtures/specs/dotplot.json +0 -0
  176. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/fixtures/specs/faceted.json +0 -0
  177. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/fixtures/specs/grid.json +0 -0
  178. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/fixtures/specs/heatmap.json +0 -0
  179. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/fixtures/specs/image.json +0 -0
  180. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/fixtures/specs/pandas_categorical.json +0 -0
  181. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/fixtures/specs/polars_enum.json +0 -0
  182. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/fixtures/specs/primitives.json +0 -0
  183. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/fixtures/specs/ridge.json +0 -0
  184. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/fixtures/specs/umap_categorical.json +0 -0
  185. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/fixtures/specs/umap_continuous.json +0 -0
  186. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/fixtures/specs/violin_box.json +0 -0
  187. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/fixtures/specs/void_theme.json +0 -0
  188. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/fixtures/specs/volcano.json +0 -0
  189. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/fixtures/specs/with_inline_data.json +0 -0
  190. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/golden/scene_showcase.svg +0 -0
  191. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/render_parity.rs +0 -0
  192. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/render_scene.rs +0 -0
  193. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/render_sprites.rs +0 -0
  194. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/spec_fixtures.rs +0 -0
  195. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-core/tests/spec_regressions.rs +0 -0
  196. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-python/Cargo.toml +0 -0
  197. {ggrust-0.2.0 → ggrust-0.2.1}/crates/ggrust-python/src/lib.rs +0 -0
  198. {ggrust-0.2.0 → ggrust-0.2.1}/pyproject.toml +0 -0
  199. {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/__init__.py +0 -0
  200. {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/_data.py +0 -0
  201. {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/_ggrust.pyi +0 -0
  202. {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/_lets_plot.py +0 -0
  203. {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/_render.py +0 -0
  204. {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/_typing.py +0 -0
  205. {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/errors.py +0 -0
  206. {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/export.py +0 -0
  207. {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/mapping.py +0 -0
  208. {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/__init__.py +0 -0
  209. {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/coord.py +0 -0
  210. {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/core.py +0 -0
  211. {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/expand_limits.py +0 -0
  212. {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/facet.py +0 -0
  213. {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/geom.py +0 -0
  214. {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/geom_extras.py +0 -0
  215. {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/geom_function.py +0 -0
  216. {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/geom_imshow.py +0 -0
  217. {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/gggrid.py +0 -0
  218. {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/guide.py +0 -0
  219. {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/label.py +0 -0
  220. {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/plot.py +0 -0
  221. {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/position.py +0 -0
  222. {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/scale.py +0 -0
  223. {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/scale_color.py +0 -0
  224. {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/scale_identity.py +0 -0
  225. {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/scale_shape.py +0 -0
  226. {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/scale_size.py +0 -0
  227. {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/series_meta.py +0 -0
  228. {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/stat.py +0 -0
  229. {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/subplots.py +0 -0
  230. {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/theme.py +0 -0
  231. {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/tooltip.py +0 -0
  232. {ggrust-0.2.0 → ggrust-0.2.1}/python/ggrust/plot/util.py +0 -0
  233. {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.0"
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.0"
473
+ version = "0.2.1"
474
474
  dependencies = [
475
475
  "ggrust-core",
476
476
  "pyo3",
@@ -3,7 +3,7 @@ resolver = "3"
3
3
  members = ["crates/ggrust-core", "crates/ggrust-python"]
4
4
 
5
5
  [workspace.package]
6
- version = "0.2.0"
6
+ version = "0.2.1"
7
7
  edition = "2024"
8
8
  license = "MIT"
9
9
  repository = "https://github.com/datavil/ggrust"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ggrust
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Rust
6
6
  Classifier: Topic :: Scientific/Engineering :: Visualization
@@ -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
- for (layer, setup) in layers.iter().zip(&self.tooltip_setups) {
953
- jobs.push((rect, tile_x, tile_y, layer, setup));
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
- ZoomPlot { tiles, shared }
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
- layer_nodes.extend(nodes?);
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(&note) {
@@ -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, PerItem,
20
- Points, Rect, Rects, SUPERSCRIPT_RISE, SUPERSCRIPT_SCALE, Scene, Stroke, Text, VerticalAlign,
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`]) replaced by their
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) if group.anchor.is_some() => walk(&group.nodes, flat),
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.nodes.iter().find_map(|node| match node {
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).nodes.is_empty());
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
@@ -66,8 +66,8 @@ fn panel(built: &BuiltPlot) -> &Group {
66
66
 
67
67
  fn points(built: &BuiltPlot) -> &Points {
68
68
  panel(built)
69
- .nodes
70
- .iter()
69
+ .flattened()
70
+ .into_iter()
71
71
  .find_map(|node| match node {
72
72
  Node::Points(points) => Some(points),
73
73
  _ => None,
@@ -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") === "ggrust-canvas") {
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
- marks.push({ kind: "shape", element: child });
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
- if (reset) e.removeAttribute("transform");
1053
- else e.setAttribute("transform", matrix);
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() };