plotlet 0.6.0__tar.gz → 0.7.0__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.
- plotlet-0.7.0/PKG-INFO +73 -0
- plotlet-0.7.0/README.md +35 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/pyproject.toml +15 -2
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/__init__.py +24 -10
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/_coord_registry.py +13 -8
- plotlet-0.7.0/src/plotlet/_datasets/earth.npz +0 -0
- plotlet-0.7.0/src/plotlet/_json_layer.py +227 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/_regions.py +37 -2
- plotlet-0.7.0/src/plotlet/_spec.py +187 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/_tree.py +1 -1
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/artists/__init__.py +9 -3
- plotlet-0.7.0/src/plotlet/artists/_shared.py +389 -0
- plotlet-0.7.0/src/plotlet/artists/annotation_strip.py +742 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/artists/bar.py +258 -161
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/artists/boxplot.py +92 -62
- {plotlet-0.6.0/src/plotlet/extensions → plotlet-0.7.0/src/plotlet/artists}/chord_links.py +9 -36
- {plotlet-0.6.0/src/plotlet/extensions → plotlet-0.7.0/src/plotlet/artists}/chord_ribbon.py +21 -44
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/artists/contour.py +2 -2
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/artists/dendrogram.py +43 -12
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/artists/density_1d.py +4 -4
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/artists/ecdf.py +30 -6
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/artists/errorbar.py +33 -27
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/artists/fills.py +13 -11
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/artists/freqpoly.py +4 -4
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/artists/heatmap.py +154 -79
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/artists/hexbin.py +6 -6
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/artists/hist.py +37 -26
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/artists/hist2d.py +4 -4
- plotlet-0.7.0/src/plotlet/artists/image_cmap.py +323 -0
- plotlet-0.7.0/src/plotlet/artists/image_rgba.py +236 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/artists/kde_2d.py +12 -10
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/artists/line.py +56 -32
- {plotlet-0.6.0/src/plotlet/extensions → plotlet-0.7.0/src/plotlet/artists}/numeric_bar.py +13 -37
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/artists/pointplot.py +53 -22
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/artists/qq.py +53 -17
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/artists/references.py +23 -19
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/artists/regression.py +17 -14
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/artists/ridge.py +11 -9
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/artists/rug.py +50 -18
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/artists/scatter.py +166 -73
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/artists/shapes.py +28 -9
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/artists/strip.py +70 -44
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/artists/swarm.py +67 -42
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/artists/text.py +18 -14
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/artists/violin.py +23 -12
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/cluster.py +19 -4
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/datasets.py +21 -9
- plotlet-0.7.0/src/plotlet/describe.py +122 -0
- plotlet-0.7.0/src/plotlet/docs/AI_ATTRS.md +170 -0
- plotlet-0.7.0/src/plotlet/docs/API.md +501 -0
- plotlet-0.7.0/src/plotlet/docs/ARCHITECTURE.md +202 -0
- plotlet-0.7.0/src/plotlet/docs/COORDINATES.md +203 -0
- plotlet-0.7.0/src/plotlet/docs/EXTENDING.md +287 -0
- plotlet-0.7.0/src/plotlet/docs/PHILOSOPHY.md +125 -0
- plotlet-0.7.0/src/plotlet/docs/SUBPLOTS.md +149 -0
- plotlet-0.7.0/src/plotlet/docs/THEMES.md +195 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/draw/__init__.py +11 -4
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/draw/_png.py +44 -1
- plotlet-0.7.0/src/plotlet/draw/_raster.py +259 -0
- plotlet-0.7.0/src/plotlet/draw/_simplify.py +100 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/draw/colormaps.py +30 -4
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/draw/colors.py +2 -2
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/draw/font.py +30 -11
- plotlet-0.7.0/src/plotlet/draw/linestyles.py +58 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/draw/primitives.py +56 -20
- plotlet-0.7.0/src/plotlet/examples/01_scatter.py +24 -0
- plotlet-0.7.0/src/plotlet/examples/02_line.py +16 -0
- plotlet-0.7.0/src/plotlet/examples/03_bar.py +20 -0
- plotlet-0.7.0/src/plotlet/examples/04_violin.py +21 -0
- plotlet-0.7.0/src/plotlet/examples/05_anscombe.py +24 -0
- plotlet-0.7.0/src/plotlet/examples/06_heatmap.py +22 -0
- plotlet-0.7.0/src/plotlet/examples/07_annotated_heatmap.py +39 -0
- plotlet-0.7.0/src/plotlet/examples/08_tracks.py +41 -0
- plotlet-0.7.0/src/plotlet/examples/09_circular.py +17 -0
- plotlet-0.7.0/src/plotlet/examples/10_chord.py +30 -0
- plotlet-0.7.0/src/plotlet/examples/12_hist.py +19 -0
- plotlet-0.7.0/src/plotlet/examples/13_ecdf.py +14 -0
- plotlet-0.7.0/src/plotlet/examples/14_box_strip.py +17 -0
- plotlet-0.7.0/src/plotlet/examples/15_swarm.py +15 -0
- plotlet-0.7.0/src/plotlet/examples/16_pointplot.py +26 -0
- plotlet-0.7.0/src/plotlet/examples/17_ridge.py +23 -0
- plotlet-0.7.0/src/plotlet/examples/18_qq.py +20 -0
- plotlet-0.7.0/src/plotlet/examples/19_hexbin.py +20 -0
- plotlet-0.7.0/src/plotlet/examples/20_kde_2d.py +23 -0
- plotlet-0.7.0/src/plotlet/examples/21_image_cmap.py +19 -0
- plotlet-0.7.0/src/plotlet/examples/22_facets.py +21 -0
- plotlet-0.7.0/src/plotlet/examples/23_ring_tree.py +43 -0
- plotlet-0.7.0/src/plotlet/examples/24_image_rgba.py +19 -0
- plotlet-0.7.0/src/plotlet/examples/json/01_scatter.json +42 -0
- plotlet-0.7.0/src/plotlet/examples/json/02_line.json +32 -0
- plotlet-0.7.0/src/plotlet/examples/json/03_bar.json +21 -0
- plotlet-0.7.0/src/plotlet/examples/json/04_violin.json +42 -0
- plotlet-0.7.0/src/plotlet/examples/json/05_anscombe.json +82 -0
- plotlet-0.7.0/src/plotlet/examples/json/06_heatmap.json +63 -0
- plotlet-0.7.0/src/plotlet/examples/json/07_annotated_heatmap.json +99 -0
- plotlet-0.7.0/src/plotlet/examples/json/08_tracks.json +81 -0
- plotlet-0.7.0/src/plotlet/examples/json/09_circular.json +36 -0
- plotlet-0.7.0/src/plotlet/examples/json/10_chord.json +99 -0
- plotlet-0.7.0/src/plotlet/examples/json/12_hist.json +37 -0
- plotlet-0.7.0/src/plotlet/examples/json/13_ecdf.json +53 -0
- plotlet-0.7.0/src/plotlet/examples/json/14_box_strip.json +59 -0
- plotlet-0.7.0/src/plotlet/examples/json/15_swarm.json +58 -0
- plotlet-0.7.0/src/plotlet/examples/json/16_pointplot.json +46 -0
- plotlet-0.7.0/src/plotlet/examples/json/17_ridge.json +34 -0
- plotlet-0.7.0/src/plotlet/examples/json/18_qq.json +29 -0
- plotlet-0.7.0/src/plotlet/examples/json/19_hexbin.json +62 -0
- plotlet-0.7.0/src/plotlet/examples/json/20_kde_2d.json +38 -0
- plotlet-0.7.0/src/plotlet/examples/json/22_facets.json +52 -0
- plotlet-0.7.0/src/plotlet/examples/json/23_ring_tree.json +94 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/layout_diagram.py +3 -3
- plotlet-0.7.0/src/plotlet/lint.py +263 -0
- plotlet-0.7.0/src/plotlet/op_help.py +508 -0
- plotlet-0.7.0/src/plotlet/record/__init__.py +12 -0
- {plotlet-0.6.0/src/plotlet → plotlet-0.7.0/src/plotlet/record}/chart.py +412 -104
- plotlet-0.7.0/src/plotlet/record/codegen.py +550 -0
- {plotlet-0.6.0/src/plotlet → plotlet-0.7.0/src/plotlet/record}/facet.py +14 -6
- plotlet-0.6.0/src/plotlet/_ir.py → plotlet-0.7.0/src/plotlet/record/figure_ir.py +74 -34
- plotlet-0.6.0/src/plotlet/_journal.py → plotlet-0.7.0/src/plotlet/record/journal.py +200 -49
- plotlet-0.7.0/src/plotlet/record/journal_lite.py +225 -0
- {plotlet-0.6.0/src/plotlet → plotlet-0.7.0/src/plotlet/record}/legend.py +1 -1
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/registry.py +43 -19
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/render/__init__.py +18 -12
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/render/_attachments.py +6 -6
- plotlet-0.7.0/src/plotlet/render/_chrome_bands.py +402 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/render/_chrome_circular.py +81 -6
- plotlet-0.7.0/src/plotlet/render/_chrome_emit.py +788 -0
- plotlet-0.7.0/src/plotlet/render/_chrome_visibility.py +97 -0
- plotlet-0.6.0/src/plotlet/render/coordinates.py → plotlet-0.7.0/src/plotlet/render/_coord_circular.py +17 -343
- plotlet-0.7.0/src/plotlet/render/_layout_circular.py +324 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/render/_layout_engine.py +206 -145
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/render/_legend.py +304 -118
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/render/_nodes.py +84 -57
- plotlet-0.7.0/src/plotlet/render/_resolution.py +1713 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/render/_validate.py +54 -19
- plotlet-0.7.0/src/plotlet/render/emit.py +723 -0
- plotlet-0.6.0/src/plotlet/render/resolved.py → plotlet-0.7.0/src/plotlet/render/resolved_ir.py +145 -106
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/scales.py +137 -78
- plotlet-0.7.0/src/plotlet/schemas/journal_lite.schema.json +823 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/sectors.py +1 -1
- plotlet-0.7.0/src/plotlet/skill.py +40 -0
- plotlet-0.7.0/src/plotlet/skills/developers.md +143 -0
- plotlet-0.7.0/src/plotlet/skills/json.md +183 -0
- plotlet-0.7.0/src/plotlet/skills/users.md +220 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/spec.json +40 -48
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/themes/dark.json +4 -6
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/themes/minimal.json +6 -7
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/themes/void.json +2 -1
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/themes.py +6 -1
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/utils.py +215 -17
- plotlet-0.7.0/src/plotlet.egg-info/PKG-INFO +73 -0
- plotlet-0.7.0/src/plotlet.egg-info/SOURCES.txt +245 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet.egg-info/requires.txt +1 -0
- plotlet-0.7.0/tests/test_aes.py +137 -0
- plotlet-0.7.0/tests/test_authoring_errors.py +69 -0
- plotlet-0.7.0/tests/test_chart_annotation_strip.py +200 -0
- plotlet-0.7.0/tests/test_chart_axis.py +518 -0
- plotlet-0.7.0/tests/test_chart_bar.py +353 -0
- plotlet-0.7.0/tests/test_chart_boxplot.py +105 -0
- plotlet-0.7.0/tests/test_chart_chord_links.py +68 -0
- plotlet-0.7.0/tests/test_chart_chord_ribbon.py +56 -0
- plotlet-0.7.0/tests/test_chart_contour.py +84 -0
- plotlet-0.7.0/tests/test_chart_dendrogram.py +186 -0
- plotlet-0.7.0/tests/test_chart_density_1d.py +59 -0
- plotlet-0.7.0/tests/test_chart_ecdf.py +40 -0
- plotlet-0.7.0/tests/test_chart_errorbar.py +57 -0
- plotlet-0.7.0/tests/test_chart_facets.py +237 -0
- plotlet-0.7.0/tests/test_chart_fills.py +119 -0
- plotlet-0.7.0/tests/test_chart_freqpoly.py +40 -0
- plotlet-0.7.0/tests/test_chart_heatmap.py +378 -0
- plotlet-0.7.0/tests/test_chart_hexbin.py +50 -0
- plotlet-0.7.0/tests/test_chart_hist.py +136 -0
- plotlet-0.7.0/tests/test_chart_hist2d.py +108 -0
- plotlet-0.7.0/tests/test_chart_image_cmap.py +284 -0
- plotlet-0.7.0/tests/test_chart_image_rgba.py +214 -0
- plotlet-0.7.0/tests/test_chart_kde_2d.py +77 -0
- plotlet-0.7.0/tests/test_chart_labels.py +233 -0
- plotlet-0.7.0/tests/test_chart_line.py +240 -0
- plotlet-0.7.0/tests/test_chart_numeric_bar.py +50 -0
- plotlet-0.7.0/tests/test_chart_pointplot.py +90 -0
- plotlet-0.7.0/tests/test_chart_qq.py +74 -0
- plotlet-0.7.0/tests/test_chart_references.py +68 -0
- plotlet-0.7.0/tests/test_chart_regression.py +159 -0
- plotlet-0.7.0/tests/test_chart_ridge.py +72 -0
- plotlet-0.7.0/tests/test_chart_rug.py +36 -0
- plotlet-0.7.0/tests/test_chart_scatter.py +200 -0
- plotlet-0.7.0/tests/test_chart_shapes.py +189 -0
- plotlet-0.7.0/tests/test_chart_spines.py +52 -0
- plotlet-0.7.0/tests/test_chart_strip.py +100 -0
- plotlet-0.7.0/tests/test_chart_swarm.py +58 -0
- plotlet-0.7.0/tests/test_chart_text.py +89 -0
- plotlet-0.7.0/tests/test_chart_violin.py +64 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/tests/test_colors.py +32 -3
- plotlet-0.6.0/tests/test_circular_coordinate.py → plotlet-0.7.0/tests/test_coord_circular.py +302 -116
- plotlet-0.6.0/tests/test_sectors.py → plotlet-0.7.0/tests/test_coord_sectors.py +108 -101
- {plotlet-0.6.0 → plotlet-0.7.0}/tests/test_datasets.py +7 -1
- plotlet-0.7.0/tests/test_describe.py +54 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/tests/test_fonts.py +80 -41
- {plotlet-0.6.0 → plotlet-0.7.0}/tests/test_formatters.py +5 -2
- plotlet-0.7.0/tests/test_help.py +66 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/tests/test_import_boundary.py +120 -13
- plotlet-0.7.0/tests/test_io.py +188 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/tests/test_ir.py +43 -37
- {plotlet-0.6.0 → plotlet-0.7.0}/tests/test_ir_resolved.py +109 -56
- {plotlet-0.6.0 → plotlet-0.7.0}/tests/test_ir_validate.py +60 -20
- plotlet-0.7.0/tests/test_journal_lite.py +188 -0
- plotlet-0.7.0/tests/test_journal_roundtrip.py +366 -0
- plotlet-0.7.0/tests/test_json_examples.py +53 -0
- plotlet-0.7.0/tests/test_json_schema.py +66 -0
- plotlet-0.6.0/tests/test_attachments.py → plotlet-0.7.0/tests/test_layout_attachments.py +152 -79
- {plotlet-0.6.0 → plotlet-0.7.0}/tests/test_layout_diagram.py +38 -19
- plotlet-0.7.0/tests/test_layout_legend.py +580 -0
- plotlet-0.6.0/tests/test_subplots.py → plotlet-0.7.0/tests/test_layout_subplots.py +221 -110
- plotlet-0.7.0/tests/test_lint.py +212 -0
- plotlet-0.7.0/tests/test_op_help.py +191 -0
- plotlet-0.7.0/tests/test_orientation.py +175 -0
- plotlet-0.7.0/tests/test_record_signatures.py +317 -0
- plotlet-0.7.0/tests/test_scale_sweep.py +189 -0
- plotlet-0.7.0/tests/test_skill.py +44 -0
- plotlet-0.7.0/tests/test_spec_ownership.py +113 -0
- plotlet-0.7.0/tests/test_themes.py +320 -0
- plotlet-0.7.0/tests/test_to_python.py +196 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/tests/test_units.py +10 -5
- plotlet-0.6.0/PKG-INFO +0 -95
- plotlet-0.6.0/README.md +0 -58
- plotlet-0.6.0/src/plotlet/_json_layer.py +0 -144
- plotlet-0.6.0/src/plotlet/_spec.py +0 -147
- plotlet-0.6.0/src/plotlet/artists/_shared.py +0 -204
- plotlet-0.6.0/src/plotlet/artists/imshow.py +0 -237
- plotlet-0.6.0/src/plotlet/draw/linestyles.py +0 -27
- plotlet-0.6.0/src/plotlet/extensions/annotation_strip.py +0 -675
- plotlet-0.6.0/src/plotlet/extensions/curved_tree.py +0 -244
- plotlet-0.6.0/src/plotlet/lint.py +0 -130
- plotlet-0.6.0/src/plotlet/render/_chrome.py +0 -953
- plotlet-0.6.0/src/plotlet/render/core.py +0 -2100
- plotlet-0.6.0/src/plotlet.egg-info/PKG-INFO +0 -95
- plotlet-0.6.0/src/plotlet.egg-info/SOURCES.txt +0 -129
- plotlet-0.6.0/tests/test_chart.py +0 -3594
- plotlet-0.6.0/tests/test_extensions.py +0 -40
- plotlet-0.6.0/tests/test_help.py +0 -25
- plotlet-0.6.0/tests/test_io.py +0 -61
- plotlet-0.6.0/tests/test_journal_roundtrip.py +0 -127
- plotlet-0.6.0/tests/test_legend.py +0 -288
- plotlet-0.6.0/tests/test_lint.py +0 -23
- plotlet-0.6.0/tests/test_record_signatures.py +0 -75
- plotlet-0.6.0/tests/test_themes.py +0 -89
- {plotlet-0.6.0 → plotlet-0.7.0}/LICENSE +0 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/setup.cfg +0 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/_datasets/anscombe.csv +0 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/_datasets/flights.csv +0 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/_datasets/penguins.csv +0 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/_datasets/tips.csv +0 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/_splits.py +0 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/artists/_marching.py +0 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/draw/_cm_data.py +0 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/draw/_css_colors.py +0 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/draw/fonts/Arimo-Bold.ttf +0 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/draw/fonts/Arimo-BoldItalic.ttf +0 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/draw/fonts/Arimo-Italic.ttf +0 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/draw/fonts/Arimo-Regular.ttf +0 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/draw/fonts/DejaVuSans-Bold.ttf +0 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/draw/fonts/DejaVuSans-BoldOblique.ttf +0 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/draw/fonts/DejaVuSans-Oblique.ttf +0 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/draw/fonts/DejaVuSans.ttf +0 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/draw/fonts/LICENSE-Arimo.txt +0 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/draw/fonts/LICENSE-DejaVuSans.txt +0 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/draw/format.py +0 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet/formatters.py +0 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet.egg-info/dependency_links.txt +0 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/src/plotlet.egg-info/top_level.txt +0 -0
- {plotlet-0.6.0 → plotlet-0.7.0}/tests/test_cluster.py +0 -0
plotlet-0.7.0/PKG-INFO
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: plotlet
|
|
3
|
+
Version: 0.7.0
|
|
4
|
+
Summary: Python library for reproducible, multi-panel scientific figures, with byte-identical output across machines and an extension API for custom plot types.
|
|
5
|
+
Author: gitbamboo42
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/gitbamboo42/plotlet
|
|
8
|
+
Project-URL: Repository, https://github.com/gitbamboo42/plotlet
|
|
9
|
+
Project-URL: Issues, https://github.com/gitbamboo42/plotlet/issues
|
|
10
|
+
Keywords: plot,svg,scientific,visualization,jupyter,reproducible
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
21
|
+
Classifier: Topic :: Multimedia :: Graphics
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: fonttools>=4.0
|
|
26
|
+
Requires-Dist: scipy>=1.10
|
|
27
|
+
Requires-Dist: resvg_py>=0.3
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: jupyter; extra == "dev"
|
|
30
|
+
Requires-Dist: nbconvert; extra == "dev"
|
|
31
|
+
Provides-Extra: test
|
|
32
|
+
Requires-Dist: pytest>=7; extra == "test"
|
|
33
|
+
Requires-Dist: pandas; extra == "test"
|
|
34
|
+
Requires-Dist: jsonschema; extra == "test"
|
|
35
|
+
Provides-Extra: pdf
|
|
36
|
+
Requires-Dist: cairosvg; extra == "pdf"
|
|
37
|
+
Dynamic: license-file
|
|
38
|
+
|
|
39
|
+
# plotlet
|
|
40
|
+
|
|
41
|
+
plotlet is a Python library for reproducible, multi-panel scientific figures, built for AI authorship: figures are written, inspected, and verified as data, not pixels. Byte-identical output across machines, a standard plotting vocabulary, built-in Cartesian and circular coordinate systems, and an easy way to add your own plot types.
|
|
42
|
+
|
|
43
|
+
## Documentation
|
|
44
|
+
|
|
45
|
+
Online documentation is available at [gitbamboo42.github.io/plotlet](https://gitbamboo42.github.io/plotlet/).
|
|
46
|
+
|
|
47
|
+
The docs include a [tutorial](https://gitbamboo42.github.io/plotlet/tutorial.html), a [plot-type reference](https://gitbamboo42.github.io/plotlet/reference.html), a [cookbook](https://gitbamboo42.github.io/plotlet/cookbook.html), an [extensions gallery](https://gitbamboo42.github.io/plotlet/extensions.html), [deep-dive guides](https://gitbamboo42.github.io/plotlet/docs-api.html), and a [page for AI agents](https://gitbamboo42.github.io/plotlet/agents.html).
|
|
48
|
+
|
|
49
|
+
## Dependencies
|
|
50
|
+
|
|
51
|
+
plotlet supports Python 3.10+.
|
|
52
|
+
|
|
53
|
+
Required: `fonttools`, `scipy`, `resvg_py`. numpy / pandas / polars inputs work transparently.
|
|
54
|
+
|
|
55
|
+
Optional: `cairosvg` for PDF export (`pip install plotlet[pdf]`).
|
|
56
|
+
|
|
57
|
+
## Installation
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pip install plotlet
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## For AI assistants
|
|
64
|
+
|
|
65
|
+
Tell your assistant to run plotlet's `skill()` and follow it. The guides, docs, and worked examples ship inside the installed package; the [page for AI agents](https://gitbamboo42.github.io/plotlet/agents.html) shows the full workflow.
|
|
66
|
+
|
|
67
|
+
## Development
|
|
68
|
+
|
|
69
|
+
Development takes place on GitHub. Please submit bugs to the issue tracker with a reproducible example.
|
|
70
|
+
|
|
71
|
+
## License
|
|
72
|
+
|
|
73
|
+
MIT
|
plotlet-0.7.0/README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# plotlet
|
|
2
|
+
|
|
3
|
+
plotlet is a Python library for reproducible, multi-panel scientific figures, built for AI authorship: figures are written, inspected, and verified as data, not pixels. Byte-identical output across machines, a standard plotting vocabulary, built-in Cartesian and circular coordinate systems, and an easy way to add your own plot types.
|
|
4
|
+
|
|
5
|
+
## Documentation
|
|
6
|
+
|
|
7
|
+
Online documentation is available at [gitbamboo42.github.io/plotlet](https://gitbamboo42.github.io/plotlet/).
|
|
8
|
+
|
|
9
|
+
The docs include a [tutorial](https://gitbamboo42.github.io/plotlet/tutorial.html), a [plot-type reference](https://gitbamboo42.github.io/plotlet/reference.html), a [cookbook](https://gitbamboo42.github.io/plotlet/cookbook.html), an [extensions gallery](https://gitbamboo42.github.io/plotlet/extensions.html), [deep-dive guides](https://gitbamboo42.github.io/plotlet/docs-api.html), and a [page for AI agents](https://gitbamboo42.github.io/plotlet/agents.html).
|
|
10
|
+
|
|
11
|
+
## Dependencies
|
|
12
|
+
|
|
13
|
+
plotlet supports Python 3.10+.
|
|
14
|
+
|
|
15
|
+
Required: `fonttools`, `scipy`, `resvg_py`. numpy / pandas / polars inputs work transparently.
|
|
16
|
+
|
|
17
|
+
Optional: `cairosvg` for PDF export (`pip install plotlet[pdf]`).
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install plotlet
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## For AI assistants
|
|
26
|
+
|
|
27
|
+
Tell your assistant to run plotlet's `skill()` and follow it. The guides, docs, and worked examples ship inside the installed package; the [page for AI agents](https://gitbamboo42.github.io/plotlet/agents.html) shows the full workflow.
|
|
28
|
+
|
|
29
|
+
## Development
|
|
30
|
+
|
|
31
|
+
Development takes place on GitHub. Please submit bugs to the issue tracker with a reproducible example.
|
|
32
|
+
|
|
33
|
+
## License
|
|
34
|
+
|
|
35
|
+
MIT
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "plotlet"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.7.0"
|
|
8
8
|
description = "Python library for reproducible, multi-panel scientific figures, with byte-identical output across machines and an extension API for custom plot types."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -48,6 +48,7 @@ dev = [
|
|
|
48
48
|
test = [
|
|
49
49
|
"pytest>=7",
|
|
50
50
|
"pandas",
|
|
51
|
+
"jsonschema",
|
|
51
52
|
]
|
|
52
53
|
pdf = [
|
|
53
54
|
"cairosvg",
|
|
@@ -70,4 +71,16 @@ namespaces = true
|
|
|
70
71
|
# draw/fonts/*.txt are the font licenses — DejaVu (Bitstream Vera–derived)
|
|
71
72
|
# and Arimo (SIL OFL 1.1) both require the license text to accompany
|
|
72
73
|
# redistribution, so they must ship in the wheel next to the TTFs.
|
|
73
|
-
plotlet = ["spec.json", "themes/*.json", "draw/fonts/*.ttf", "draw/fonts/*.txt", "_datasets/*.csv"
|
|
74
|
+
plotlet = ["spec.json", "themes/*.json", "draw/fonts/*.ttf", "draw/fonts/*.txt", "_datasets/*.csv", "_datasets/*.npz",
|
|
75
|
+
# AI onboarding: pt.skill() reads skills/, and the guides point
|
|
76
|
+
# at the shipped docs/ and examples/ so an installed package is
|
|
77
|
+
# self-sufficient — no repo clone, no web access needed.
|
|
78
|
+
"skills/*.md", "docs/*.md", "examples/*.py",
|
|
79
|
+
# JSON twins of the examples, for authoring plots as JSON
|
|
80
|
+
# journals (skills/json.md). Generated by
|
|
81
|
+
# tests/gen_json_examples.py — never edited by hand.
|
|
82
|
+
"examples/json/*.json",
|
|
83
|
+
# JSON Schema for journal-lite blobs, for editor/server-side
|
|
84
|
+
# validation. Generated by tests/gen_json_schema.py from
|
|
85
|
+
# pt.op_schema() — never edited by hand.
|
|
86
|
+
"schemas/*.json"]
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
"""plotlet — pure-Python deferred-rendering SVG plot library.
|
|
2
2
|
|
|
3
3
|
import plotlet as pt
|
|
4
|
+
from plotlet import aes
|
|
5
|
+
|
|
4
6
|
c = pt.chart(df, title="...", xlabel="x", ylabel="y", legend=True, gridlines=True)
|
|
5
|
-
c.
|
|
7
|
+
c.add_line(aes(x="time", y="value", color="series"))
|
|
6
8
|
c # auto-renders in Jupyter
|
|
7
9
|
|
|
8
10
|
Chart methods chain for incremental composition:
|
|
9
11
|
|
|
10
12
|
df = {"x": [1, 2, 3], "y": [1, 4, 9]}
|
|
11
13
|
c = pt.chart()
|
|
12
|
-
c.
|
|
14
|
+
c.add_line(df, aes(x="x", y="y"), label="squares")
|
|
13
15
|
c.title("Hello").legend().gridlines(True)
|
|
14
16
|
c
|
|
15
17
|
"""
|
|
@@ -17,21 +19,31 @@ from ._spec import SPEC
|
|
|
17
19
|
from .draw import TAB10, palette, list_palettes
|
|
18
20
|
from .draw import colormap, list_colormaps, register_colormap
|
|
19
21
|
from . import artists # noqa: F401 — registers built-in artists on import
|
|
20
|
-
from .chart import Chart, Layout, chart, grid
|
|
21
|
-
from .
|
|
22
|
+
from .record.chart import Chart, Layout, chart, grid
|
|
23
|
+
from .utils import aes
|
|
24
|
+
from .record.legend import legend
|
|
22
25
|
from .registry import ArtistSpec, add_artist, artist_table, declare_coord_support
|
|
23
26
|
from .sectors import Sectors
|
|
24
27
|
from .layout_diagram import layout_diagram
|
|
25
28
|
from .themes import load_theme, list_themes, register_theme
|
|
26
|
-
from .facet import facet, FacetGrid
|
|
29
|
+
from .record.facet import facet, FacetGrid
|
|
27
30
|
from .datasets import load_dataset, list_datasets
|
|
28
31
|
from .cluster import linkage, linkage_split, SplitTree
|
|
29
32
|
from .formatters import (register_formatter, list_formatters,
|
|
30
33
|
superscript, subscript)
|
|
31
34
|
from ._coord_registry import register_coord_codec
|
|
32
|
-
from .
|
|
35
|
+
from .record.journal import (to_journal, from_journal, to_json, from_json,
|
|
33
36
|
JournalNode, Journal)
|
|
34
|
-
from .
|
|
37
|
+
from .record.journal_lite import to_json_lite, from_json_lite
|
|
38
|
+
from .record.codegen import to_python
|
|
39
|
+
from .record.figure_ir import to_ir, from_ir, FigureIR, IRNode
|
|
40
|
+
from .skill import skill
|
|
41
|
+
# Shadows the `plotlet.lint` submodule attribute with the function —
|
|
42
|
+
# the os.path-style pattern: `pt.lint(c)` calls it, and
|
|
43
|
+
# `from plotlet.lint import lint` still resolves via sys.modules.
|
|
44
|
+
from .lint import lint
|
|
45
|
+
from .describe import describe
|
|
46
|
+
from .op_help import op_help, op_schema # same shadowing pattern as lint
|
|
35
47
|
from . import draw, utils
|
|
36
48
|
|
|
37
49
|
|
|
@@ -41,7 +53,7 @@ def __getattr__(name):
|
|
|
41
53
|
# the render half loads on first render (or first touch of this
|
|
42
54
|
# name). Enforced by tests/test_import_boundary.py.
|
|
43
55
|
if name == "CircularCoordinate":
|
|
44
|
-
from .render.
|
|
56
|
+
from .render._coord_circular import CircularCoordinate
|
|
45
57
|
return CircularCoordinate
|
|
46
58
|
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
47
59
|
|
|
@@ -58,9 +70,11 @@ __all__ = ["chart", "Chart", "Layout", "SPEC", "TAB10",
|
|
|
58
70
|
"linkage", "linkage_split", "SplitTree",
|
|
59
71
|
"register_formatter", "list_formatters",
|
|
60
72
|
"superscript", "subscript",
|
|
61
|
-
"to_json", "from_json", "
|
|
73
|
+
"to_json", "from_json", "to_json_lite", "from_json_lite",
|
|
74
|
+
"to_python", "register_coord_codec",
|
|
62
75
|
"to_journal", "from_journal", "JournalNode", "Journal",
|
|
63
|
-
"to_ir", "from_ir", "FigureIR", "IRNode",
|
|
76
|
+
"to_ir", "from_ir", "FigureIR", "IRNode",
|
|
77
|
+
"skill", "lint", "describe", "op_help", "op_schema",
|
|
64
78
|
"draw", "utils"]
|
|
65
79
|
|
|
66
80
|
# Single source of truth: pyproject.toml. importlib.metadata reads it at
|
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
"""Coord codec registry —
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
"""Coord codec registry — the `class_name → class` map behind
|
|
2
|
+
`{"$coord": ...}` envelopes, for coord objects like
|
|
3
|
+
`CircularCoordinate`.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
`record/journal.py` encodes a coord object to its class name;
|
|
6
|
+
`_json_layer._decode` turns the name back into the class via
|
|
7
|
+
`resolve_coord`, which imports the built-in coord module on the first
|
|
8
|
+
miss so cold processes decode without a prior render. The render half
|
|
9
|
+
reads the map too (`render/_validate.py` cross-checks the `container`
|
|
10
|
+
flag, `render/resolved_ir.py` rehydrates coords), so the registry sits
|
|
11
|
+
at the package root, where `record/` and `render/` may both import it.
|
|
12
|
+
The public API `plotlet.register_coord_codec` re-exports from this
|
|
13
|
+
module directly.
|
|
9
14
|
"""
|
|
10
15
|
from __future__ import annotations
|
|
11
16
|
|
|
@@ -38,7 +43,7 @@ def resolve_coord(name: str) -> type:
|
|
|
38
43
|
return _COORD_REGISTRY[name]
|
|
39
44
|
except KeyError:
|
|
40
45
|
pass
|
|
41
|
-
from .render import
|
|
46
|
+
from .render import _coord_circular # noqa: F401 — registers built-ins
|
|
42
47
|
try:
|
|
43
48
|
return _COORD_REGISTRY[name]
|
|
44
49
|
except KeyError:
|
|
Binary file
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""JSON envelope layer — shared by `record/` and `render/`.
|
|
2
|
+
|
|
3
|
+
Envelopes Python values that aren't JSON-native (tuple, set, date,
|
|
4
|
+
datetime, dicts with non-string keys, DataFrameLite) so they can be
|
|
5
|
+
dumped through `json.dumps` and rehydrated. Four consumers: the
|
|
6
|
+
journal's JSON form (`record/journal.py`), the FigureIR wire format
|
|
7
|
+
(`record/figure_ir.py`), value-envelope decoding at render hydration
|
|
8
|
+
(`_decode`, `render/_nodes.py`), and the resolved IR's debug view
|
|
9
|
+
(`render/resolved_ir.py`). It sits at the package root because both
|
|
10
|
+
`record/` and `render/` need it and neither may import the other
|
|
11
|
+
(`tests/test_import_boundary.py`). It also keeps JSON support out of
|
|
12
|
+
`journal.py` itself — the journal stays a plain event log.
|
|
13
|
+
|
|
14
|
+
DataFrame-shaped inputs never reach this layer: they're normalized to
|
|
15
|
+
`DataFrameLite` / plain lists at the recorder boundary in
|
|
16
|
+
`record/chart.py` (via `utils._normalize_data`), so it never imports
|
|
17
|
+
pandas or grows a branch per data library. numpy is the one exception:
|
|
18
|
+
a matrix artist's data rides through record as a 2-D ndarray (numpy is
|
|
19
|
+
a guaranteed dependency, via scipy), and `json_safe` lowers it to
|
|
20
|
+
nested lists — the JSON wire format itself carries no array types.
|
|
21
|
+
|
|
22
|
+
Envelope keys used here:
|
|
23
|
+
$dataframe utils.DataFrameLite (canonical DataFrame form)
|
|
24
|
+
$tuple tuple (JSON has no tuple type; without this every
|
|
25
|
+
tuple would silently degrade to list, breaking
|
|
26
|
+
isinstance dispatch inside artist code)
|
|
27
|
+
$set set
|
|
28
|
+
$date datetime.date
|
|
29
|
+
$datetime datetime.datetime
|
|
30
|
+
$dict_pairs dict whose keys aren't all JSON-native strings
|
|
31
|
+
|
|
32
|
+
`_decode` at the bottom handles the other envelope family — the
|
|
33
|
+
*reference* envelopes ($node / $coord / $sectors / $data) that journals
|
|
34
|
+
and IRs carry whether or not they ever touch JSON. It lives here because
|
|
35
|
+
both halves need it (the render tree's hydrator and the front half's
|
|
36
|
+
facet expansion) and it resolves only against shared vocabulary.
|
|
37
|
+
"""
|
|
38
|
+
from __future__ import annotations
|
|
39
|
+
import json
|
|
40
|
+
from typing import Any
|
|
41
|
+
|
|
42
|
+
import numpy as np
|
|
43
|
+
|
|
44
|
+
from .utils import DataFrameLite, all_primitive
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def json_safe(value: Any) -> Any:
|
|
48
|
+
"""Walk `value`, replace non-JSON types with envelopes. Plotlet
|
|
49
|
+
envelopes ($node / $coord / $sectors) already added at `to_journal`
|
|
50
|
+
time pass through as regular dicts — their inner values still get
|
|
51
|
+
recursed."""
|
|
52
|
+
if value is None or isinstance(value, (bool, int, float, str)):
|
|
53
|
+
return value
|
|
54
|
+
# datetime.datetime is a subclass of datetime.date — check the more
|
|
55
|
+
# specific type first so datetimes get $datetime, not $date.
|
|
56
|
+
import datetime as _dt
|
|
57
|
+
if isinstance(value, _dt.datetime):
|
|
58
|
+
return {"$datetime": value.isoformat()}
|
|
59
|
+
if isinstance(value, _dt.date):
|
|
60
|
+
return {"$date": value.isoformat()}
|
|
61
|
+
if isinstance(value, DataFrameLite):
|
|
62
|
+
# index and cell values recurse — a date/datetime column must
|
|
63
|
+
# wire as `$date` / `$datetime` cells, same as anywhere else.
|
|
64
|
+
return {"$dataframe": {
|
|
65
|
+
"columns": value.columns,
|
|
66
|
+
"index": [json_safe(v) for v in value.index],
|
|
67
|
+
"values": [[json_safe(v) for v in row] for row in value.values],
|
|
68
|
+
}}
|
|
69
|
+
from .sectors import Sectors
|
|
70
|
+
if isinstance(value, Sectors):
|
|
71
|
+
# Live instance → the same envelope `to_journal` emits and
|
|
72
|
+
# `_decode` reconstructs (`Sectors._from_dict`). Reached by
|
|
73
|
+
# values that never passed through the journal emitter, e.g. a
|
|
74
|
+
# resolved-IR state dict.
|
|
75
|
+
return {"$sectors": json_safe(value._to_dict())}
|
|
76
|
+
if isinstance(value, np.ndarray):
|
|
77
|
+
# A matrix artist's data section entry. The wire format stays
|
|
78
|
+
# plain nested lists — identical JSON whether the matrix came
|
|
79
|
+
# in as an array or as lists. Recursing catches the rare
|
|
80
|
+
# object-dtype cell.
|
|
81
|
+
return json_safe(value.tolist())
|
|
82
|
+
if isinstance(value, tuple):
|
|
83
|
+
return {"$tuple": [json_safe(v) for v in value]}
|
|
84
|
+
if isinstance(value, (set, frozenset)):
|
|
85
|
+
# Iteration order is hash order, which varies across processes
|
|
86
|
+
# (PYTHONHASHSEED) — sort the serialized elements so $set
|
|
87
|
+
# payloads are byte-stable.
|
|
88
|
+
return {"$set": sorted((json_safe(v) for v in value), key=repr)}
|
|
89
|
+
if isinstance(value, dict):
|
|
90
|
+
if all(isinstance(k, str) for k in value):
|
|
91
|
+
return {k: json_safe(v) for k, v in value.items()}
|
|
92
|
+
# Non-string key means the whole dict can't be a JSON object;
|
|
93
|
+
# emit as a list of [key, value] pairs.
|
|
94
|
+
return {"$dict_pairs": [[json_safe(k), json_safe(v)]
|
|
95
|
+
for k, v in value.items()]}
|
|
96
|
+
if isinstance(value, list):
|
|
97
|
+
# Bulk numeric rows are already JSON-native — one flat scan
|
|
98
|
+
# instead of per-cell recursion (same bail-out in json_hydrate,
|
|
99
|
+
# _decode, and the journal/IR walkers).
|
|
100
|
+
if all_primitive(value):
|
|
101
|
+
return list(value)
|
|
102
|
+
return [json_safe(v) for v in value]
|
|
103
|
+
return value
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def json_hydrate(value: Any) -> Any:
|
|
107
|
+
"""Inverse of `json_safe`. Plotlet envelopes ($node / $coord /
|
|
108
|
+
$sectors) are left as dicts — the journal's own `_decode` handles
|
|
109
|
+
them at replay time."""
|
|
110
|
+
if isinstance(value, dict):
|
|
111
|
+
if "$dataframe" in value:
|
|
112
|
+
d = value["$dataframe"]
|
|
113
|
+
return DataFrameLite(
|
|
114
|
+
values=[[json_hydrate(v) for v in row]
|
|
115
|
+
for row in d["values"]],
|
|
116
|
+
columns=d["columns"],
|
|
117
|
+
index=[json_hydrate(v) for v in d["index"]],
|
|
118
|
+
)
|
|
119
|
+
if "$tuple" in value:
|
|
120
|
+
return tuple(json_hydrate(v) for v in value["$tuple"])
|
|
121
|
+
if "$set" in value:
|
|
122
|
+
return {json_hydrate(v) for v in value["$set"]}
|
|
123
|
+
if "$date" in value:
|
|
124
|
+
import datetime as _dt
|
|
125
|
+
return _dt.date.fromisoformat(value["$date"])
|
|
126
|
+
if "$datetime" in value:
|
|
127
|
+
import datetime as _dt
|
|
128
|
+
return _dt.datetime.fromisoformat(value["$datetime"])
|
|
129
|
+
if "$dict_pairs" in value:
|
|
130
|
+
return {json_hydrate(k): json_hydrate(v)
|
|
131
|
+
for k, v in value["$dict_pairs"]}
|
|
132
|
+
return {k: json_hydrate(v) for k, v in value.items()}
|
|
133
|
+
if isinstance(value, list):
|
|
134
|
+
if all_primitive(value):
|
|
135
|
+
return list(value)
|
|
136
|
+
return [json_hydrate(v) for v in value]
|
|
137
|
+
return value
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
_PRETTY_WIDTH = 100 # target line width for pretty_dumps
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def pretty_dumps(value: Any) -> str:
|
|
144
|
+
"""Canonical JSON text form — what `pt.to_json` returns and the
|
|
145
|
+
shipped example twins use. Objects and arrays stay on one line when
|
|
146
|
+
they fit in `_PRETTY_WIDTH` columns; a too-long dict or mixed list
|
|
147
|
+
expands one element per line (1-space indent steps); a too-long
|
|
148
|
+
list of primitives wraps at the width, continuation lines aligned
|
|
149
|
+
under its opening bracket — so journal entries read structurally
|
|
150
|
+
while data columns stay a few dense lines. Deterministic (same
|
|
151
|
+
value → same bytes) and whitespace-only: `json.loads` of the
|
|
152
|
+
result equals `json.loads(json.dumps(value))`."""
|
|
153
|
+
return _pretty(value, 0, "")
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _pretty(value: Any, col: int, pad: str) -> str:
|
|
157
|
+
"""Render `value` starting at column `col`; `pad` is the
|
|
158
|
+
indentation of the enclosing construct."""
|
|
159
|
+
compact = json.dumps(value)
|
|
160
|
+
if col + len(compact) <= _PRETTY_WIDTH \
|
|
161
|
+
or not isinstance(value, (dict, list)):
|
|
162
|
+
return compact
|
|
163
|
+
inner = pad + " "
|
|
164
|
+
if isinstance(value, dict):
|
|
165
|
+
lines = []
|
|
166
|
+
for k, v in value.items():
|
|
167
|
+
key = f"{inner}{json.dumps(k)}: "
|
|
168
|
+
lines.append(key + _pretty(v, len(key), inner))
|
|
169
|
+
return "{\n" + ",\n".join(lines) + "\n" + pad + "}"
|
|
170
|
+
if all(not isinstance(v, (dict, list)) for v in value):
|
|
171
|
+
room = max(_PRETTY_WIDTH - col - 1, 20) # width left after "["
|
|
172
|
+
parts, lines, used = [json.dumps(v) for v in value], [], -2
|
|
173
|
+
for part in parts:
|
|
174
|
+
if used < 0 or used + 2 + len(part) > room:
|
|
175
|
+
lines.append([part])
|
|
176
|
+
used = len(part)
|
|
177
|
+
else:
|
|
178
|
+
lines[-1].append(part)
|
|
179
|
+
used += 2 + len(part)
|
|
180
|
+
joined = (",\n" + " " * (col + 1)).join(
|
|
181
|
+
", ".join(line) for line in lines)
|
|
182
|
+
return "[" + joined + "]"
|
|
183
|
+
lines = [inner + _pretty(v, len(inner), inner) for v in value]
|
|
184
|
+
return "[\n" + ",\n".join(lines) + "\n" + pad + "]"
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _decode(value: Any, nid_to_node: dict, data_table: dict) -> Any:
|
|
188
|
+
"""Resolve plotlet's *reference* envelopes back to live objects —
|
|
189
|
+
`{"$node"}` via `nid_to_node`, `{"$coord"}` via the coord registry,
|
|
190
|
+
`{"$sectors"}` via `Sectors`, `{"$data"}` via `data_table` (the
|
|
191
|
+
Journal's / FigureIR's data section). Containers recurse; everything
|
|
192
|
+
else passes through.
|
|
193
|
+
|
|
194
|
+
Distinct from `json_hydrate` above: that undoes the JSON-native
|
|
195
|
+
envelopes at the JSON boundary, while these reference envelopes live
|
|
196
|
+
in journals and IRs whether or not they ever touch JSON, and decode
|
|
197
|
+
at hydration time. Shared vocabulary — used by the render tree's
|
|
198
|
+
hydrator (`render.hydrate`) and by the facet expansion in
|
|
199
|
+
`record/figure_ir.py`."""
|
|
200
|
+
if isinstance(value, dict):
|
|
201
|
+
if "$node" in value and len(value) == 1:
|
|
202
|
+
return nid_to_node[value["$node"]]
|
|
203
|
+
if "$data" in value and len(value) == 1:
|
|
204
|
+
return data_table[value["$data"]]
|
|
205
|
+
if "$coord" in value:
|
|
206
|
+
from ._coord_registry import resolve_coord
|
|
207
|
+
cls = resolve_coord(value["$coord"])
|
|
208
|
+
return cls._from_dict(
|
|
209
|
+
_decode(value.get("kwargs", {}), nid_to_node, data_table))
|
|
210
|
+
if "$sectors" in value:
|
|
211
|
+
from .sectors import Sectors
|
|
212
|
+
return Sectors._from_dict(
|
|
213
|
+
_decode(value["$sectors"], nid_to_node, data_table))
|
|
214
|
+
if "$aes" in value and len(value) == 1:
|
|
215
|
+
from .utils import Aes
|
|
216
|
+
return Aes(value["$aes"])
|
|
217
|
+
return {k: _decode(v, nid_to_node, data_table)
|
|
218
|
+
for k, v in value.items()}
|
|
219
|
+
if isinstance(value, list):
|
|
220
|
+
if all_primitive(value):
|
|
221
|
+
return list(value)
|
|
222
|
+
return [_decode(v, nid_to_node, data_table) for v in value]
|
|
223
|
+
if isinstance(value, tuple):
|
|
224
|
+
if all_primitive(value):
|
|
225
|
+
return value
|
|
226
|
+
return tuple(_decode(v, nid_to_node, data_table) for v in value)
|
|
227
|
+
return value
|
|
@@ -15,7 +15,9 @@ The mechanism enforces this:
|
|
|
15
15
|
When set, the primitive records its bbox under that name. When
|
|
16
16
|
unset (the default), nothing is recorded — so artist `draw()`
|
|
17
17
|
bodies, which never pass `tag=`, produce no regions for free.
|
|
18
|
-
* Central code (`
|
|
18
|
+
* Central code (`render/_chrome_emit.py`, `render/emit.py`,
|
|
19
|
+
`render/_legend.py`)
|
|
20
|
+
passes `tag="title"` /
|
|
19
21
|
`"spine"` / `"tick-x"` / `"legend-text"` etc. at the chrome
|
|
20
22
|
emission sites. The tag is right there in the call, grep-able,
|
|
21
23
|
no contextvar magic.
|
|
@@ -56,6 +58,11 @@ class _Sink:
|
|
|
56
58
|
# offset to recorded bboxes so consumers receive outer-SVG coords
|
|
57
59
|
# regardless of whether the chart is single- or multi-panel.
|
|
58
60
|
translate_stack: list[tuple[float, float]] = field(default_factory=list)
|
|
61
|
+
# Depth of active `structural()` blocks — chrome recorded inside
|
|
62
|
+
# one lives inside the panel rect by design (coordinate-owned
|
|
63
|
+
# chrome, an inside-positioned legend), so overlap lint can tell
|
|
64
|
+
# it from misplaced chrome.
|
|
65
|
+
structural_depth: int = 0
|
|
59
66
|
|
|
60
67
|
def _offset(self) -> tuple[float, float]:
|
|
61
68
|
dx = dy = 0.0
|
|
@@ -73,6 +80,12 @@ class _Sink:
|
|
|
73
80
|
# the bbox in outer-SVG coords.
|
|
74
81
|
if "polygon" in meta:
|
|
75
82
|
meta["polygon"] = [(px + dx, py + dy) for px, py in meta["polygon"]]
|
|
83
|
+
# Multi-line text ships one quad per inked line under `lines`.
|
|
84
|
+
if "lines" in meta:
|
|
85
|
+
meta["lines"] = [[(px + dx, py + dy) for px, py in quad]
|
|
86
|
+
for quad in meta["lines"]]
|
|
87
|
+
if self.structural_depth:
|
|
88
|
+
meta.setdefault("structural", True)
|
|
76
89
|
self.regions.append(Region(kind=kind, bbox=(x + dx, y + dy, w, h),
|
|
77
90
|
name=name, meta=meta))
|
|
78
91
|
|
|
@@ -85,7 +98,9 @@ _CURRENT: contextvars.ContextVar = contextvars.ContextVar(
|
|
|
85
98
|
def record(kind: str, bbox, *, name: str, **meta) -> None:
|
|
86
99
|
"""Record a region into the active sink, if any. Called by
|
|
87
100
|
`draw.*` primitives when they receive a `tag=` kwarg, and by
|
|
88
|
-
central code (`
|
|
101
|
+
central code (`render/_chrome_emit.py`, `render/emit.py`,
|
|
102
|
+
`render/_legend.py`)
|
|
103
|
+
for chrome that has no
|
|
89
104
|
primitive call to wrap (panel boundary, canonical legend swatch).
|
|
90
105
|
No-op when no sink is active — the common render path."""
|
|
91
106
|
sink = _CURRENT.get()
|
|
@@ -124,6 +139,26 @@ def suppressed():
|
|
|
124
139
|
_CURRENT.reset(token)
|
|
125
140
|
|
|
126
141
|
|
|
142
|
+
@contextmanager
|
|
143
|
+
def structural():
|
|
144
|
+
"""Mark every region recorded in the block as `structural=True` —
|
|
145
|
+
chrome that lives inside the panel rect *by design*, so its overlap
|
|
146
|
+
with the panel/spines is rendering geometry, not a layout bug.
|
|
147
|
+
Wrapped around coordinate-owned chrome emission (a circular chart's
|
|
148
|
+
angular tick labels and sector walls all sit inside the data ring)
|
|
149
|
+
and an inside-positioned legend's body. Overlap lint skips
|
|
150
|
+
structural-vs-container pairs. No-op when no sink is active."""
|
|
151
|
+
sink = _CURRENT.get()
|
|
152
|
+
if sink is None:
|
|
153
|
+
yield
|
|
154
|
+
return
|
|
155
|
+
sink.structural_depth += 1
|
|
156
|
+
try:
|
|
157
|
+
yield
|
|
158
|
+
finally:
|
|
159
|
+
sink.structural_depth -= 1
|
|
160
|
+
|
|
161
|
+
|
|
127
162
|
@contextmanager
|
|
128
163
|
def translate(dx: float, dy: float):
|
|
129
164
|
"""Push a translate offset onto the sink's transform stack for the
|