polars2svg 0.1.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.
Files changed (341) hide show
  1. polars2svg-0.1.0/.github/dependabot.yml +14 -0
  2. polars2svg-0.1.0/.github/workflows/ci.yml +96 -0
  3. polars2svg-0.1.0/.github/workflows/docs.yml +50 -0
  4. polars2svg-0.1.0/.github/workflows/release.yml +63 -0
  5. polars2svg-0.1.0/.gitignore +222 -0
  6. polars2svg-0.1.0/CHANGELOG.md +130 -0
  7. polars2svg-0.1.0/CITATION.cff +163 -0
  8. polars2svg-0.1.0/CONTRIBUTING.md +159 -0
  9. polars2svg-0.1.0/LICENSE +201 -0
  10. polars2svg-0.1.0/NOTICE +24 -0
  11. polars2svg-0.1.0/PKG-INFO +311 -0
  12. polars2svg-0.1.0/README.md +230 -0
  13. polars2svg-0.1.0/SECURITY.md +77 -0
  14. polars2svg-0.1.0/docs/components/chordp.md +39 -0
  15. polars2svg-0.1.0/docs/components/histop.md +37 -0
  16. polars2svg-0.1.0/docs/components/index.md +55 -0
  17. polars2svg-0.1.0/docs/components/linkp.md +48 -0
  18. polars2svg-0.1.0/docs/components/piep.md +29 -0
  19. polars2svg-0.1.0/docs/components/smallp.md +42 -0
  20. polars2svg-0.1.0/docs/components/spreadlinesp.md +30 -0
  21. polars2svg-0.1.0/docs/components/timep.md +44 -0
  22. polars2svg-0.1.0/docs/components/xyp.md +45 -0
  23. polars2svg-0.1.0/docs/examples/chordp_flows.svg +1 -0
  24. polars2svg-0.1.0/docs/examples/histop_bars.svg +1 -0
  25. polars2svg-0.1.0/docs/examples/linkp_network.svg +1 -0
  26. polars2svg-0.1.0/docs/examples/piep_donut.svg +1 -0
  27. polars2svg-0.1.0/docs/examples/smallp_facets.svg +33 -0
  28. polars2svg-0.1.0/docs/examples/spreadlinesp_ego.svg +1 -0
  29. polars2svg-0.1.0/docs/examples/timep_linear.svg +1 -0
  30. polars2svg-0.1.0/docs/examples/timep_periodic.svg +1 -0
  31. polars2svg-0.1.0/docs/examples/xyp_scatter.svg +9 -0
  32. polars2svg-0.1.0/docs/gen_examples.py +181 -0
  33. polars2svg-0.1.0/docs/getting-started.md +92 -0
  34. polars2svg-0.1.0/docs/guides/count-color.md +88 -0
  35. polars2svg-0.1.0/docs/guides/interactivity.md +64 -0
  36. polars2svg-0.1.0/docs/guides/t-fields.md +64 -0
  37. polars2svg-0.1.0/docs/index.md +57 -0
  38. polars2svg-0.1.0/mkdocs.yml +60 -0
  39. polars2svg-0.1.0/polars2svg/__init__.py +36 -0
  40. polars2svg-0.1.0/polars2svg/chordp.py +2239 -0
  41. polars2svg-0.1.0/polars2svg/circle_packer.py +313 -0
  42. polars2svg-0.1.0/polars2svg/config/udist_scatterplots_via_sectors_tile_opt.parquet +0 -0
  43. polars2svg-0.1.0/polars2svg/convey_proximity_layout.py +249 -0
  44. polars2svg-0.1.0/polars2svg/exceptions.py +8 -0
  45. polars2svg-0.1.0/polars2svg/export.py +86 -0
  46. polars2svg-0.1.0/polars2svg/fonts/NotoSans-Regular-subset.ttf +0 -0
  47. polars2svg-0.1.0/polars2svg/fonts/OFL.txt +93 -0
  48. polars2svg-0.1.0/polars2svg/histop.py +1209 -0
  49. polars2svg-0.1.0/polars2svg/interactive_controller.py +3098 -0
  50. polars2svg-0.1.0/polars2svg/laguerre_voronoi.py +290 -0
  51. polars2svg-0.1.0/polars2svg/layout_protocol.py +49 -0
  52. polars2svg-0.1.0/polars2svg/linkp.py +1843 -0
  53. polars2svg-0.1.0/polars2svg/mds_at_scale.py +267 -0
  54. polars2svg-0.1.0/polars2svg/p2s_colors_mixin.py +336 -0
  55. polars2svg-0.1.0/polars2svg/p2s_displaylist.py +569 -0
  56. polars2svg-0.1.0/polars2svg/p2s_geometry_mixin.py +253 -0
  57. polars2svg-0.1.0/polars2svg/p2s_glyph_atlas.py +133 -0
  58. polars2svg-0.1.0/polars2svg/p2s_graph_mixin.py +1462 -0
  59. polars2svg-0.1.0/polars2svg/p2s_interactive_mixin.py +94 -0
  60. polars2svg-0.1.0/polars2svg/p2s_legend_mixin.py +377 -0
  61. polars2svg-0.1.0/polars2svg/p2s_polars_mixin.py +86 -0
  62. polars2svg-0.1.0/polars2svg/p2s_render_mixin.py +362 -0
  63. polars2svg-0.1.0/polars2svg/p2s_text_mixin.py +170 -0
  64. polars2svg-0.1.0/polars2svg/p2s_time_mixin.py +258 -0
  65. polars2svg-0.1.0/polars2svg/p2s_webgpu_runtime.py +402 -0
  66. polars2svg-0.1.0/polars2svg/piep.py +1195 -0
  67. polars2svg-0.1.0/polars2svg/polars2svg.py +1950 -0
  68. polars2svg-0.1.0/polars2svg/polars_force_directed_layout.py +146 -0
  69. polars2svg-0.1.0/polars2svg/py.typed +0 -0
  70. polars2svg-0.1.0/polars2svg/smallp.py +517 -0
  71. polars2svg-0.1.0/polars2svg/spreadlinepi.py +385 -0
  72. polars2svg-0.1.0/polars2svg/spreadlinesp.py +1757 -0
  73. polars2svg-0.1.0/polars2svg/stack_control.py +250 -0
  74. polars2svg-0.1.0/polars2svg/tfdp_layout.py +417 -0
  75. polars2svg-0.1.0/polars2svg/timep.py +1459 -0
  76. polars2svg-0.1.0/polars2svg/udist_scatterplots_via_sectors_tile_opt.py +585 -0
  77. polars2svg-0.1.0/polars2svg/xyp.py +3517 -0
  78. polars2svg-0.1.0/pyproject.toml +194 -0
  79. polars2svg-0.1.0/tests/golden/basic_scatter.svg +9 -0
  80. polars2svg-0.1.0/tests/golden/chordp_count_weight.svg +1 -0
  81. polars2svg-0.1.0/tests/golden/chordp_default.svg +1 -0
  82. polars2svg-0.1.0/tests/golden/chordp_draw_labels_radial.svg +1 -0
  83. polars2svg-0.1.0/tests/golden/chordp_link_color_src.svg +1 -0
  84. polars2svg-0.1.0/tests/golden/chordp_link_color_vary.svg +1 -0
  85. polars2svg-0.1.0/tests/golden/chordp_link_shape_bundled.svg +1 -0
  86. polars2svg-0.1.0/tests/golden/chordp_node_color_by_name.svg +1 -0
  87. polars2svg-0.1.0/tests/golden/chordp_node_color_hex.svg +1 -0
  88. polars2svg-0.1.0/tests/golden/chordp_node_selection.svg +1 -0
  89. polars2svg-0.1.0/tests/golden/chordp_node_size_vary.svg +1 -0
  90. polars2svg-0.1.0/tests/golden/color_categorical.svg +9 -0
  91. polars2svg-0.1.0/tests/golden/color_continuous.svg +9 -0
  92. polars2svg-0.1.0/tests/golden/distributions.svg +9 -0
  93. polars2svg-0.1.0/tests/golden/histop_ascending_order.svg +1 -0
  94. polars2svg-0.1.0/tests/golden/histop_basic_barchart.svg +1 -0
  95. polars2svg-0.1.0/tests/golden/histop_color_categorical.svg +1 -0
  96. polars2svg-0.1.0/tests/golden/histop_color_crow_magnitude.svg +1 -0
  97. polars2svg-0.1.0/tests/golden/histop_color_crow_stretched.svg +1 -0
  98. polars2svg-0.1.0/tests/golden/histop_color_numeric.svg +1 -0
  99. polars2svg-0.1.0/tests/golden/histop_count_numeric_field.svg +1 -0
  100. polars2svg-0.1.0/tests/golden/histop_distribution_strip.svg +1 -0
  101. polars2svg-0.1.0/tests/golden/histop_multi_field_bin.svg +1 -0
  102. polars2svg-0.1.0/tests/golden/histop_no_draw_context.svg +1 -0
  103. polars2svg-0.1.0/tests/golden/histop_order_by_field.svg +1 -0
  104. polars2svg-0.1.0/tests/golden/histop_style_boxplot.svg +1 -0
  105. polars2svg-0.1.0/tests/golden/histop_style_stackedbar.svg +1 -0
  106. polars2svg-0.1.0/tests/golden/legend_histop_categorical_bottom.svg +1 -0
  107. polars2svg-0.1.0/tests/golden/legend_histop_colorbar_right.svg +1 -0
  108. polars2svg-0.1.0/tests/golden/legend_xyp_categorical_right.svg +9 -0
  109. polars2svg-0.1.0/tests/golden/legend_xyp_colorbar_bottom.svg +9 -0
  110. polars2svg-0.1.0/tests/golden/legend_xyp_colorbar_left.svg +9 -0
  111. polars2svg-0.1.0/tests/golden/legend_xyp_dict_top.svg +9 -0
  112. polars2svg-0.1.0/tests/golden/lines.svg +12 -0
  113. polars2svg-0.1.0/tests/golden/linkp_node_color_dict_single.svg +1 -0
  114. polars2svg-0.1.0/tests/golden/linkp_node_color_dict_three.svg +1 -0
  115. polars2svg-0.1.0/tests/golden/linkp_node_color_dict_two.svg +1 -0
  116. polars2svg-0.1.0/tests/golden/linkp_node_color_dict_unknown_key.svg +1 -0
  117. polars2svg-0.1.0/tests/golden/linkp_node_color_hex.svg +1 -0
  118. polars2svg-0.1.0/tests/golden/linkp_node_color_none.svg +1 -0
  119. polars2svg-0.1.0/tests/golden/no_context.svg +5 -0
  120. polars2svg-0.1.0/tests/golden/smallp_2x1_grid_with_remainder.svg +24 -0
  121. polars2svg-0.1.0/tests/golden/smallp_2x2_grid_include_all_no_collate_remainder.svg +45 -0
  122. polars2svg-0.1.0/tests/golden/smallp_2x2_grid_include_all_with_remainder.svg +47 -0
  123. polars2svg-0.1.0/tests/golden/smallp_2x2_grid_with_remainder.svg +42 -0
  124. polars2svg-0.1.0/tests/golden/smallp_basic_all_fit.svg +51 -0
  125. polars2svg-0.1.0/tests/golden/smallp_include_all_fits_exactly.svg +65 -0
  126. polars2svg-0.1.0/tests/golden/smallp_small_wxh_single_slot_all_remainder.svg +15 -0
  127. polars2svg-0.1.0/tests/golden/spreadlinesp_basic_ego_a.svg +1 -0
  128. polars2svg-0.1.0/tests/golden/spreadlinesp_count_field.svg +1 -0
  129. polars2svg-0.1.0/tests/golden/spreadlinesp_highlight_nodes.svg +1 -0
  130. polars2svg-0.1.0/tests/golden/spreadlinesp_node_color_field.svg +1 -0
  131. polars2svg-0.1.0/tests/golden/timep_linear_boxplot.svg +1 -0
  132. polars2svg-0.1.0/tests/golden/timep_linear_color_categorical.svg +1 -0
  133. polars2svg-0.1.0/tests/golden/timep_linear_color_numeric.svg +1 -0
  134. polars2svg-0.1.0/tests/golden/timep_linear_count_numeric.svg +1 -0
  135. polars2svg-0.1.0/tests/golden/timep_linear_crow_magnitude.svg +1 -0
  136. polars2svg-0.1.0/tests/golden/timep_linear_crow_stretched.svg +1 -0
  137. polars2svg-0.1.0/tests/golden/timep_linear_monthly.svg +1 -0
  138. polars2svg-0.1.0/tests/golden/timep_linear_stackedbar.svg +1 -0
  139. polars2svg-0.1.0/tests/golden/timep_no_draw_context.svg +1 -0
  140. polars2svg-0.1.0/tests/golden/timep_periodic_day_of_week.svg +1 -0
  141. polars2svg-0.1.0/tests/golden/timep_periodic_monthly.svg +1 -0
  142. polars2svg-0.1.0/tests/golden/variable_dot_size.svg +9 -0
  143. polars2svg-0.1.0/tests/golden_png/_tmp_test_color_none.png +0 -0
  144. polars2svg-0.1.0/tests/golden_png/basic_scatter.png +0 -0
  145. polars2svg-0.1.0/tests/golden_png/chordp_count_weight.png +0 -0
  146. polars2svg-0.1.0/tests/golden_png/chordp_default.png +0 -0
  147. polars2svg-0.1.0/tests/golden_png/chordp_draw_labels_radial.png +0 -0
  148. polars2svg-0.1.0/tests/golden_png/chordp_link_color_src.png +0 -0
  149. polars2svg-0.1.0/tests/golden_png/chordp_link_color_vary.png +0 -0
  150. polars2svg-0.1.0/tests/golden_png/chordp_link_shape_bundled.png +0 -0
  151. polars2svg-0.1.0/tests/golden_png/chordp_node_color_by_name.png +0 -0
  152. polars2svg-0.1.0/tests/golden_png/chordp_node_color_hex.png +0 -0
  153. polars2svg-0.1.0/tests/golden_png/chordp_node_selection.png +0 -0
  154. polars2svg-0.1.0/tests/golden_png/chordp_node_size_vary.png +0 -0
  155. polars2svg-0.1.0/tests/golden_png/color_categorical.png +0 -0
  156. polars2svg-0.1.0/tests/golden_png/color_continuous.png +0 -0
  157. polars2svg-0.1.0/tests/golden_png/distributions.png +0 -0
  158. polars2svg-0.1.0/tests/golden_png/histop_ascending_order.png +0 -0
  159. polars2svg-0.1.0/tests/golden_png/histop_basic_barchart.png +0 -0
  160. polars2svg-0.1.0/tests/golden_png/histop_color_categorical.png +0 -0
  161. polars2svg-0.1.0/tests/golden_png/histop_color_crow_magnitude.png +0 -0
  162. polars2svg-0.1.0/tests/golden_png/histop_color_crow_stretched.png +0 -0
  163. polars2svg-0.1.0/tests/golden_png/histop_color_numeric.png +0 -0
  164. polars2svg-0.1.0/tests/golden_png/histop_count_numeric_field.png +0 -0
  165. polars2svg-0.1.0/tests/golden_png/histop_distribution_strip.png +0 -0
  166. polars2svg-0.1.0/tests/golden_png/histop_multi_field_bin.png +0 -0
  167. polars2svg-0.1.0/tests/golden_png/histop_no_draw_context.png +0 -0
  168. polars2svg-0.1.0/tests/golden_png/histop_order_by_field.png +0 -0
  169. polars2svg-0.1.0/tests/golden_png/histop_style_boxplot.png +0 -0
  170. polars2svg-0.1.0/tests/golden_png/histop_style_stackedbar.png +0 -0
  171. polars2svg-0.1.0/tests/golden_png/legend_histop_categorical_bottom.png +0 -0
  172. polars2svg-0.1.0/tests/golden_png/legend_histop_colorbar_right.png +0 -0
  173. polars2svg-0.1.0/tests/golden_png/legend_xyp_categorical_right.png +0 -0
  174. polars2svg-0.1.0/tests/golden_png/legend_xyp_colorbar_bottom.png +0 -0
  175. polars2svg-0.1.0/tests/golden_png/legend_xyp_colorbar_left.png +0 -0
  176. polars2svg-0.1.0/tests/golden_png/legend_xyp_dict_top.png +0 -0
  177. polars2svg-0.1.0/tests/golden_png/lines.png +0 -0
  178. polars2svg-0.1.0/tests/golden_png/linkp_node_color_dict_single.png +0 -0
  179. polars2svg-0.1.0/tests/golden_png/linkp_node_color_dict_three.png +0 -0
  180. polars2svg-0.1.0/tests/golden_png/linkp_node_color_dict_two.png +0 -0
  181. polars2svg-0.1.0/tests/golden_png/linkp_node_color_dict_unknown_key.png +0 -0
  182. polars2svg-0.1.0/tests/golden_png/linkp_node_color_hex.png +0 -0
  183. polars2svg-0.1.0/tests/golden_png/linkp_node_color_none.png +0 -0
  184. polars2svg-0.1.0/tests/golden_png/no_context.png +0 -0
  185. polars2svg-0.1.0/tests/golden_png/smallp_2x1_grid_with_remainder.png +0 -0
  186. polars2svg-0.1.0/tests/golden_png/smallp_2x2_grid_include_all_no_collate_remainder.png +0 -0
  187. polars2svg-0.1.0/tests/golden_png/smallp_2x2_grid_include_all_with_remainder.png +0 -0
  188. polars2svg-0.1.0/tests/golden_png/smallp_2x2_grid_with_remainder.png +0 -0
  189. polars2svg-0.1.0/tests/golden_png/smallp_basic_all_fit.png +0 -0
  190. polars2svg-0.1.0/tests/golden_png/smallp_include_all_fits_exactly.png +0 -0
  191. polars2svg-0.1.0/tests/golden_png/smallp_small_wxh_single_slot_all_remainder.png +0 -0
  192. polars2svg-0.1.0/tests/golden_png/spreadlinesp_basic_ego_a.png +0 -0
  193. polars2svg-0.1.0/tests/golden_png/spreadlinesp_count_field.png +0 -0
  194. polars2svg-0.1.0/tests/golden_png/spreadlinesp_highlight_nodes.png +0 -0
  195. polars2svg-0.1.0/tests/golden_png/spreadlinesp_node_color_field.png +0 -0
  196. polars2svg-0.1.0/tests/golden_png/timep_linear_boxplot.png +0 -0
  197. polars2svg-0.1.0/tests/golden_png/timep_linear_color_categorical.png +0 -0
  198. polars2svg-0.1.0/tests/golden_png/timep_linear_color_numeric.png +0 -0
  199. polars2svg-0.1.0/tests/golden_png/timep_linear_count_numeric.png +0 -0
  200. polars2svg-0.1.0/tests/golden_png/timep_linear_crow_magnitude.png +0 -0
  201. polars2svg-0.1.0/tests/golden_png/timep_linear_crow_stretched.png +0 -0
  202. polars2svg-0.1.0/tests/golden_png/timep_linear_monthly.png +0 -0
  203. polars2svg-0.1.0/tests/golden_png/timep_linear_stackedbar.png +0 -0
  204. polars2svg-0.1.0/tests/golden_png/timep_no_draw_context.png +0 -0
  205. polars2svg-0.1.0/tests/golden_png/timep_periodic_day_of_week.png +0 -0
  206. polars2svg-0.1.0/tests/golden_png/timep_periodic_monthly.png +0 -0
  207. polars2svg-0.1.0/tests/golden_png/variable_dot_size.png +0 -0
  208. polars2svg-0.1.0/tests/histop_dataframes.py +36 -0
  209. polars2svg-0.1.0/tests/piep_dataframes.py +32 -0
  210. polars2svg-0.1.0/tests/random_dataframe.py +52 -0
  211. polars2svg-0.1.0/tests/svg_test_utils.py +162 -0
  212. polars2svg-0.1.0/tests/test_bar_chart_styles.py +129 -0
  213. polars2svg-0.1.0/tests/test_bar_chart_template.py +141 -0
  214. polars2svg-0.1.0/tests/test_bar_colorize_consistency.py +316 -0
  215. polars2svg-0.1.0/tests/test_changelog.py +59 -0
  216. polars2svg-0.1.0/tests/test_chordp_basic.py +323 -0
  217. polars2svg-0.1.0/tests/test_chordp_color.py +194 -0
  218. polars2svg-0.1.0/tests/test_chordp_golden.py +85 -0
  219. polars2svg-0.1.0/tests/test_chordp_interactive.py +211 -0
  220. polars2svg-0.1.0/tests/test_chordp_pos.py +363 -0
  221. polars2svg-0.1.0/tests/test_circle_packer.py +251 -0
  222. polars2svg-0.1.0/tests/test_color_overrides.py +103 -0
  223. polars2svg-0.1.0/tests/test_contributing.py +45 -0
  224. polars2svg-0.1.0/tests/test_convey_proximity_layout.py +118 -0
  225. polars2svg-0.1.0/tests/test_count_inert_warning.py +250 -0
  226. polars2svg-0.1.0/tests/test_cross_component_consistency.py +92 -0
  227. polars2svg-0.1.0/tests/test_crow_color_independence.py +110 -0
  228. polars2svg-0.1.0/tests/test_displaylist.py +228 -0
  229. polars2svg-0.1.0/tests/test_dtype_keyed_logging.py +352 -0
  230. polars2svg-0.1.0/tests/test_edge_case_inputs.py +312 -0
  231. polars2svg-0.1.0/tests/test_export_save.py +141 -0
  232. polars2svg-0.1.0/tests/test_gather_metrics.py +117 -0
  233. polars2svg-0.1.0/tests/test_global_config.py +221 -0
  234. polars2svg-0.1.0/tests/test_glyph_atlas.py +112 -0
  235. polars2svg-0.1.0/tests/test_hex_color_detection.py +195 -0
  236. polars2svg-0.1.0/tests/test_histop_basic.py +236 -0
  237. polars2svg-0.1.0/tests/test_histop_color.py +127 -0
  238. polars2svg-0.1.0/tests/test_histop_count.py +125 -0
  239. polars2svg-0.1.0/tests/test_histop_distribution_strip.py +364 -0
  240. polars2svg-0.1.0/tests/test_histop_exhaustive.py +75 -0
  241. polars2svg-0.1.0/tests/test_histop_extended.py +298 -0
  242. polars2svg-0.1.0/tests/test_histop_filter_by_rectangle.py +532 -0
  243. polars2svg-0.1.0/tests/test_histop_filter_by_substring.py +119 -0
  244. polars2svg-0.1.0/tests/test_histop_golden.py +160 -0
  245. polars2svg-0.1.0/tests/test_histop_order.py +124 -0
  246. polars2svg-0.1.0/tests/test_histop_smallp.py +95 -0
  247. polars2svg-0.1.0/tests/test_interactive_controller.py +1252 -0
  248. polars2svg-0.1.0/tests/test_laguerre_voronoi.py +372 -0
  249. polars2svg-0.1.0/tests/test_layout_protocol.py +65 -0
  250. polars2svg-0.1.0/tests/test_legend.py +429 -0
  251. polars2svg-0.1.0/tests/test_legend_golden.py +76 -0
  252. polars2svg-0.1.0/tests/test_linkp_basic.py +393 -0
  253. polars2svg-0.1.0/tests/test_linkp_color.py +130 -0
  254. polars2svg-0.1.0/tests/test_linkp_golden.py +75 -0
  255. polars2svg-0.1.0/tests/test_linkp_inert_params.py +89 -0
  256. polars2svg-0.1.0/tests/test_linkp_interactive.py +815 -0
  257. polars2svg-0.1.0/tests/test_linkp_labels.py +197 -0
  258. polars2svg-0.1.0/tests/test_linkp_stack_positioning.py +455 -0
  259. polars2svg-0.1.0/tests/test_mds_at_scale.py +190 -0
  260. polars2svg-0.1.0/tests/test_meta.py +29 -0
  261. polars2svg-0.1.0/tests/test_multifield_bin_collision.py +137 -0
  262. polars2svg-0.1.0/tests/test_no_data_placeholder.py +179 -0
  263. polars2svg-0.1.0/tests/test_optional_dependency_extras.py +139 -0
  264. polars2svg-0.1.0/tests/test_p2s.py +187 -0
  265. polars2svg-0.1.0/tests/test_p2s_colors_mixin.py +222 -0
  266. polars2svg-0.1.0/tests/test_p2s_geometry_mixin.py +94 -0
  267. polars2svg-0.1.0/tests/test_p2s_graph_mixin.py +856 -0
  268. polars2svg-0.1.0/tests/test_p2s_polars_mixin.py +73 -0
  269. polars2svg-0.1.0/tests/test_p2s_render.py +233 -0
  270. polars2svg-0.1.0/tests/test_p2s_render_colorize_order.py +107 -0
  271. polars2svg-0.1.0/tests/test_p2s_text_mixin.py +162 -0
  272. polars2svg-0.1.0/tests/test_p2s_tfields.py +228 -0
  273. polars2svg-0.1.0/tests/test_package_exports.py +49 -0
  274. polars2svg-0.1.0/tests/test_parse_consolidation.py +205 -0
  275. polars2svg-0.1.0/tests/test_performance.py +178 -0
  276. polars2svg-0.1.0/tests/test_piep_basic.py +531 -0
  277. polars2svg-0.1.0/tests/test_piep_smallp.py +121 -0
  278. polars2svg-0.1.0/tests/test_polars_force_directed_layout.py +151 -0
  279. polars2svg-0.1.0/tests/test_polars_mixin_no_eval.py +75 -0
  280. polars2svg-0.1.0/tests/test_positional_dispatch.py +143 -0
  281. polars2svg-0.1.0/tests/test_render_with.py +106 -0
  282. polars2svg-0.1.0/tests/test_reserved_columns.py +217 -0
  283. polars2svg-0.1.0/tests/test_security_automation.py +95 -0
  284. polars2svg-0.1.0/tests/test_singleton_init.py +122 -0
  285. polars2svg-0.1.0/tests/test_smallp_basic.py +105 -0
  286. polars2svg-0.1.0/tests/test_smallp_cycle_by.py +103 -0
  287. polars2svg-0.1.0/tests/test_smallp_geometry.py +197 -0
  288. polars2svg-0.1.0/tests/test_smallp_input_types.py +129 -0
  289. polars2svg-0.1.0/tests/test_smallp_order.py +169 -0
  290. polars2svg-0.1.0/tests/test_smallp_shared.py +569 -0
  291. polars2svg-0.1.0/tests/test_smallp_validation.py +142 -0
  292. polars2svg-0.1.0/tests/test_spreadlinepi.py +338 -0
  293. polars2svg-0.1.0/tests/test_spreadlinesp_basic.py +101 -0
  294. polars2svg-0.1.0/tests/test_spreadlinesp_golden.py +55 -0
  295. polars2svg-0.1.0/tests/test_spreadlinesp_packing.py +113 -0
  296. polars2svg-0.1.0/tests/test_stack_control.py +204 -0
  297. polars2svg-0.1.0/tests/test_svg_float_precision.py +165 -0
  298. polars2svg-0.1.0/tests/test_template_defaults.py +157 -0
  299. polars2svg-0.1.0/tests/test_template_state_isolation.py +189 -0
  300. polars2svg-0.1.0/tests/test_tfdp_backend.py +115 -0
  301. polars2svg-0.1.0/tests/test_tfdp_layout.py +199 -0
  302. polars2svg-0.1.0/tests/test_tfield_typed.py +323 -0
  303. polars2svg-0.1.0/tests/test_timep_basic.py +166 -0
  304. polars2svg-0.1.0/tests/test_timep_color.py +109 -0
  305. polars2svg-0.1.0/tests/test_timep_context.py +145 -0
  306. polars2svg-0.1.0/tests/test_timep_count.py +117 -0
  307. polars2svg-0.1.0/tests/test_timep_exhaustive.py +91 -0
  308. polars2svg-0.1.0/tests/test_timep_filter_by_rectangle.py +441 -0
  309. polars2svg-0.1.0/tests/test_timep_golden.py +193 -0
  310. polars2svg-0.1.0/tests/test_timep_smallp.py +105 -0
  311. polars2svg-0.1.0/tests/test_timep_time_fields.py +114 -0
  312. polars2svg-0.1.0/tests/test_typing_surface.py +87 -0
  313. polars2svg-0.1.0/tests/test_udist.py +121 -0
  314. polars2svg-0.1.0/tests/test_webgpu_components.py +366 -0
  315. polars2svg-0.1.0/tests/test_webgpu_histop.py +100 -0
  316. polars2svg-0.1.0/tests/test_webgpu_piep.py +95 -0
  317. polars2svg-0.1.0/tests/test_webgpu_xyp.py +137 -0
  318. polars2svg-0.1.0/tests/test_wxh_normalization.py +273 -0
  319. polars2svg-0.1.0/tests/test_xyp_background.py +205 -0
  320. polars2svg-0.1.0/tests/test_xyp_color.py +72 -0
  321. polars2svg-0.1.0/tests/test_xyp_distributions.py +68 -0
  322. polars2svg-0.1.0/tests/test_xyp_dot_size.py +60 -0
  323. polars2svg-0.1.0/tests/test_xyp_exhaustive.py +82 -0
  324. polars2svg-0.1.0/tests/test_xyp_filter_by_rectangle.py +200 -0
  325. polars2svg-0.1.0/tests/test_xyp_golden.py +119 -0
  326. polars2svg-0.1.0/tests/test_xyp_int_ranges.py +127 -0
  327. polars2svg-0.1.0/tests/test_xyp_label.py +86 -0
  328. polars2svg-0.1.0/tests/test_xyp_lines.py +89 -0
  329. polars2svg-0.1.0/tests/test_xyp_multiset_color.py +24 -0
  330. polars2svg-0.1.0/tests/test_xyp_order.py +59 -0
  331. polars2svg-0.1.0/tests/test_xyp_periodic_autobin.py +91 -0
  332. polars2svg-0.1.0/tests/test_xyp_periodic_time_contexts.py +255 -0
  333. polars2svg-0.1.0/tests/test_xyp_records_at.py +103 -0
  334. polars2svg-0.1.0/tests/test_xyp_supersample.py +242 -0
  335. polars2svg-0.1.0/tests/test_xyp_templates.py +16 -0
  336. polars2svg-0.1.0/tests/test_xyp_tfields.py +47 -0
  337. polars2svg-0.1.0/tests/test_xyp_transforms.py +86 -0
  338. polars2svg-0.1.0/tests/test_xyp_unhashable_spec.py +107 -0
  339. polars2svg-0.1.0/tests/test_xyp_xy_ranges.py +181 -0
  340. polars2svg-0.1.0/tests/timep_dataframes.py +57 -0
  341. polars2svg-0.1.0/tests/webgpu_test_utils.py +51 -0
@@ -0,0 +1,14 @@
1
+ version: 2
2
+ updates:
3
+ # Python dependencies (pyproject.toml: core deps, extras, and the dev
4
+ # dependency-group).
5
+ - package-ecosystem: "pip"
6
+ directory: "/"
7
+ schedule:
8
+ interval: "weekly"
9
+
10
+ # GitHub Actions used by ci.yml / release.yml.
11
+ - package-ecosystem: "github-actions"
12
+ directory: "/"
13
+ schedule:
14
+ interval: "weekly"
@@ -0,0 +1,96 @@
1
+ name: CI
2
+
3
+ # Clean-room install verification on Linux (PyPI must-do #9).
4
+ # Every prior test run was macOS/arm64; this exercises the built wheel on
5
+ # linux/amd64 inside a stock python:3.13-slim image — a genuine clean room
6
+ # (no preinstalled project deps, no system Python packages) — to catch
7
+ # platform-dependent path handling, the mlx import guard, and font-metric
8
+ # non-determinism in the golden-image tests.
9
+
10
+ on:
11
+ push:
12
+ branches: [main]
13
+ pull_request:
14
+ workflow_dispatch:
15
+
16
+ jobs:
17
+ # Type-check the public surface (PyPI nice-to-have #1). The package ships a
18
+ # py.typed marker, so downstream checkers read our hints; this guards that the
19
+ # public signatures stay valid. The [tool.mypy] config in pyproject.toml relaxes
20
+ # the error codes that fire only on the library's internal dynamic idiom.
21
+ type-check:
22
+ name: mypy (public surface)
23
+ runs-on: ubuntu-latest
24
+ steps:
25
+ - uses: actions/checkout@v4
26
+ - name: Install uv
27
+ run: pip install --no-cache-dir uv
28
+ - name: Run mypy
29
+ run: uvx mypy polars2svg
30
+
31
+ # Security/quality automation (PyPI nice-to-have #5).
32
+ security-and-quality:
33
+ name: bandit + pip-audit + ruff
34
+ runs-on: ubuntu-latest
35
+ steps:
36
+ - uses: actions/checkout@v4
37
+ - uses: astral-sh/setup-uv@v5
38
+
39
+ - name: bandit (static security scan)
40
+ # Findings are either fixed or annotated in-line with a justified
41
+ # `# nosec <code> - <reason>` (see SECURITY.md's threat model for why
42
+ # e.g. random.randint()-based SVG id scoping and the background=
43
+ # shape-descriptor XML parsing are not vulnerabilities here). A clean
44
+ # run means every remaining flag has been reviewed and accepted.
45
+ run: uvx bandit -r polars2svg/
46
+
47
+ - name: pip-audit (known-vulnerable dependency scan)
48
+ # Audits the full resolved dependency graph (every extra + the dev
49
+ # group), not just what a bare `pip install polars2svg` pulls in —
50
+ # broader coverage than METADATA alone would give.
51
+ run: |
52
+ uv export --no-hashes --all-extras --group dev -o /tmp/requirements-audit.txt
53
+ uvx pip-audit -r /tmp/requirements-audit.txt
54
+
55
+ - name: ruff (E9, F)
56
+ # Minimal rule set on purpose — see the [tool.ruff] comment in
57
+ # pyproject.toml. Catches syntax errors and pyflakes issues (undefined
58
+ # names, unused imports/variables, duplicate dict keys) without
59
+ # fighting the codebase's distinctive style.
60
+ run: uvx ruff check polars2svg/
61
+
62
+ linux-cleanroom:
63
+ name: Linux clean-room wheel install + tests
64
+ runs-on: ubuntu-latest
65
+ container: python:3.13-slim
66
+ steps:
67
+ # git lets actions/checkout use a fast clone instead of the REST tarball.
68
+ - name: Install git
69
+ run: apt-get update && apt-get install -y --no-install-recommends git
70
+
71
+ - uses: actions/checkout@v4
72
+
73
+ - name: Build the wheel
74
+ run: |
75
+ pip install --no-cache-dir uv
76
+ uv build
77
+
78
+ - name: Install the built wheel into a clean environment
79
+ run: pip install --no-cache-dir dist/*.whl
80
+
81
+ - name: Import smoke test
82
+ run: python -c "import polars2svg; print('import ok')"
83
+
84
+ - name: Install test-only dependencies
85
+ # Mirrors the [dependency-groups] dev set that the wheel METADATA does
86
+ # not carry: pytest + the rasterization/kaggle test extras. rlPyCairo is
87
+ # reportlab 5.x's renderPM backend on Linux (macOS has its own); without
88
+ # it svglib rasterization errors out. The PNG-RMS golden tests skip off
89
+ # macOS regardless, but the backend is needed for any that force-run.
90
+ run: pip install --no-cache-dir pytest kagglehub svglib reportlab rlPyCairo
91
+
92
+ - name: Run the test suite
93
+ # test_performance.py needs a machine-local baseline (gitignored);
94
+ # kaggle-backed t-field tests need network/credentials. Both excluded,
95
+ # matching must-do #9.
96
+ run: python -m pytest tests/ -q --ignore=tests/test_performance.py -k 'not tfield'
@@ -0,0 +1,50 @@
1
+ # Build the mkdocs site and deploy it to GitHub Pages.
2
+ #
3
+ # The site builds from static, committed content (the example SVGs are
4
+ # generated locally by docs/gen_examples.py), so this job does not need
5
+ # polars2svg installed. Once the package source is mirrored into this repo /
6
+ # published to PyPI, add an install step here and an mkdocstrings API page.
7
+ #
8
+ # Requires: repo Settings → Pages → Source = "GitHub Actions".
9
+ name: docs
10
+
11
+ on:
12
+ push:
13
+ branches: [main]
14
+ workflow_dispatch:
15
+
16
+ permissions:
17
+ contents: read
18
+ pages: write
19
+ id-token: write
20
+
21
+ concurrency:
22
+ group: pages
23
+ cancel-in-progress: true
24
+
25
+ jobs:
26
+ build:
27
+ runs-on: ubuntu-latest
28
+ steps:
29
+ - uses: actions/checkout@v4
30
+ - uses: actions/setup-python@v5
31
+ with:
32
+ python-version: '3.13'
33
+ - name: Install mkdocs-material
34
+ run: pip install --no-cache-dir mkdocs-material
35
+ - name: Build site
36
+ run: mkdocs build --strict
37
+ - uses: actions/configure-pages@v5
38
+ - uses: actions/upload-pages-artifact@v3
39
+ with:
40
+ path: site
41
+
42
+ deploy:
43
+ needs: build
44
+ runs-on: ubuntu-latest
45
+ environment:
46
+ name: github-pages
47
+ url: ${{ steps.deployment.outputs.page_url }}
48
+ steps:
49
+ - id: deployment
50
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,63 @@
1
+ name: Release
2
+
3
+ # Publishes polars2svg to PyPI via Trusted Publishing (OIDC) on release tags.
4
+ #
5
+ # This is NOT continuous integration: it runs only when a tag matching v*
6
+ # is pushed (e.g. `git tag v0.1.0 && git push origin v0.1.0`), so it does
7
+ # not run the test suite on every commit (see PyPI must-do #11 — CI-on-every-
8
+ # commit is intentionally disabled; local/standalone-server runs cover testing).
9
+ #
10
+ # One-time setup the maintainer must do (an agent cannot): register this repo
11
+ # as a Trusted Publisher on PyPI. Before the project exists, add it as a
12
+ # "pending publisher" in your PyPI account:
13
+ # https://pypi.org/manage/account/publishing/
14
+ # with workflow name `release.yml` and environment name `pypi`. This replaces
15
+ # the twine + API-token flow that used to live in pypi_instructions.txt.
16
+
17
+ on:
18
+ push:
19
+ tags: ['v*']
20
+ workflow_dispatch:
21
+
22
+ jobs:
23
+ build:
24
+ name: Build sdist + wheel
25
+ runs-on: ubuntu-latest
26
+ steps:
27
+ - uses: actions/checkout@v4
28
+
29
+ - name: Install uv
30
+ uses: astral-sh/setup-uv@v5
31
+
32
+ - name: Build distributions
33
+ run: uv build
34
+
35
+ - name: Check distribution metadata
36
+ run: uvx twine check dist/*
37
+
38
+ - name: Upload distributions
39
+ uses: actions/upload-artifact@v4
40
+ with:
41
+ name: dist
42
+ path: dist/
43
+
44
+ publish:
45
+ name: Publish to PyPI
46
+ needs: build
47
+ runs-on: ubuntu-latest
48
+ # Trusted Publishing: no API token/secret. PyPI verifies the OIDC identity
49
+ # of this workflow running in the `pypi` environment.
50
+ environment:
51
+ name: pypi
52
+ url: https://pypi.org/project/polars2svg/
53
+ permissions:
54
+ id-token: write
55
+ steps:
56
+ - name: Download distributions
57
+ uses: actions/download-artifact@v4
58
+ with:
59
+ name: dist
60
+ path: dist/
61
+
62
+ - name: Publish to PyPI
63
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,222 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py.cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+ coverage.json
54
+
55
+ # Translations
56
+ *.mo
57
+ *.pot
58
+
59
+ # Django stuff:
60
+ *.log
61
+ local_settings.py
62
+ db.sqlite3
63
+ db.sqlite3-journal
64
+
65
+ # Flask stuff:
66
+ instance/
67
+ .webassets-cache
68
+
69
+ # Scrapy stuff:
70
+ .scrapy
71
+
72
+ # Sphinx documentation
73
+ docs/_build/
74
+
75
+ # PyBuilder
76
+ .pybuilder/
77
+ target/
78
+
79
+ # Jupyter Notebook
80
+ .ipynb_checkpoints
81
+
82
+ # IPython
83
+ profile_default/
84
+ ipython_config.py
85
+
86
+ # pyenv
87
+ # For a library or package, you might want to ignore these files since the code is
88
+ # intended to run in multiple environments; otherwise, check them in:
89
+ # .python-version
90
+
91
+ # pipenv
92
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
93
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
94
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
95
+ # install all needed dependencies.
96
+ #Pipfile.lock
97
+
98
+ # UV
99
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
100
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
101
+ # commonly ignored for libraries.
102
+ #uv.lock
103
+ # uv.toml holds machine-local resolver settings (exclude-newer); keep it out of VCS.
104
+ uv.toml
105
+
106
+ # poetry
107
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
108
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
109
+ # commonly ignored for libraries.
110
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
111
+ #poetry.lock
112
+ #poetry.toml
113
+
114
+ # pdm
115
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
116
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
117
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
118
+ #pdm.lock
119
+ #pdm.toml
120
+ .pdm-python
121
+ .pdm-build/
122
+
123
+ # pixi
124
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
125
+ #pixi.lock
126
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
127
+ # in the .venv directory. It is recommended not to include this directory in version control.
128
+ .pixi
129
+
130
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
131
+ __pypackages__/
132
+
133
+ # Celery stuff
134
+ celerybeat-schedule
135
+ celerybeat.pid
136
+
137
+ # SageMath parsed files
138
+ *.sage.py
139
+
140
+ # Environments
141
+ .env
142
+ .envrc
143
+ .venv
144
+ env/
145
+ venv/
146
+ ENV/
147
+ env.bak/
148
+ venv.bak/
149
+
150
+ # Spyder project settings
151
+ .spyderproject
152
+ .spyproject
153
+
154
+ # Rope project settings
155
+ .ropeproject
156
+
157
+ # mkdocs documentation
158
+ /site
159
+
160
+ # mypy
161
+ .mypy_cache/
162
+ .dmypy.json
163
+ dmypy.json
164
+
165
+ # Pyre type checker
166
+ .pyre/
167
+
168
+ # pytype static type analyzer
169
+ .pytype/
170
+
171
+ # Cython debug symbols
172
+ cython_debug/
173
+
174
+ # PyCharm
175
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
176
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
177
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
178
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
179
+ #.idea/
180
+
181
+ # Abstra
182
+ # Abstra is an AI-powered process automation framework.
183
+ # Ignore directories containing user credentials, local state, and settings.
184
+ # Learn more at https://abstra.io/docs
185
+ .abstra/
186
+
187
+ # Visual Studio Code
188
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
189
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
190
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
191
+ # you could uncomment the following to ignore the entire vscode folder
192
+ # .vscode/
193
+
194
+ # Ruff stuff:
195
+ .ruff_cache/
196
+
197
+ # PyPI configuration file
198
+ .pypirc
199
+
200
+ # Cursor
201
+ # Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
202
+ # exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
203
+ # refer to https://docs.cursor.com/context/ignore-files
204
+ .cursorignore
205
+ .cursorindexingignore
206
+
207
+ # Marimo
208
+ marimo/_static/
209
+ marimo/_lsp/
210
+ __marimo__/
211
+ coverage_reports/20260212_2230_coverage.json
212
+
213
+ # Claude
214
+ .claude
215
+ # CLAUDE.md
216
+ # *.md
217
+
218
+ # Performance baseline (machine-specific, auto-generated by test_performance.py)
219
+ tests/perf_baseline.json
220
+
221
+ graphify-out/
222
+ .DS_Store
@@ -0,0 +1,130 @@
1
+ # Changelog
2
+
3
+ All notable changes to **polars2svg** are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ### Added
11
+
12
+ - **`TFDPLayout` now runs on NVIDIA GPUs via MLX's CUDA backend.** The layout was
13
+ previously documented and packaged as Apple-silicon-only. MLX ships an official
14
+ CUDA backend, and the eight t-FDP compute kernels are backend-agnostic
15
+ `mlx.core` — so the same code runs on Metal or CUDA with **no change to the
16
+ math**. New `[mlx-cuda]` / `[mlx-cuda13]` extras install it (Linux only; NVIDIA
17
+ SM ≥ 7.5, driver ≥ 550.54.14, glibc ≥ 2.35).
18
+ - **`polars2svg.gpu_backend()`** — reports which backend `TFDPLayout` resolved to:
19
+ `'metal'`, `'cuda'`, or `'cpu'`.
20
+ - **`tests/test_tfdp_backend.py`** — exercises each MLX op t-FDP depends on
21
+ (broadcast all-pairs diff, gather, `.at[].add()` scatter-add with duplicate
22
+ indices, keyed RNG) directly on the resolved device, so a backend gap fails by
23
+ name. Plus GPU-vs-CPU cross-check, convergence, and finiteness tests in
24
+ `test_tfdp_layout.py`.
25
+
26
+ - **Legends and colorbars: opt-in `legend=` on every rendered component**
27
+ (`xyp`, `histop`, `timep`, `piep`, `linkp`, `chordp`, `spreadlinesp`;
28
+ `smallp` panels inherit it from their template component). Layered value:
29
+ `legend=True` (≡ `'right'`), a position string
30
+ (`'right' | 'left' | 'top' | 'bottom'`), or a dict
31
+ (`{'pos', 'title', 'fmt', 'max_items', 'order'}`). The legend **kind is
32
+ auto-selected** from the resolved color mode — a categorical swatch list
33
+ for `CSETp`/bare-categorical color, a colorbar for the spectrum modes —
34
+ and the strip is reserved **from** `wxh` (the plot region shrinks; the
35
+ physical output size is unchanged). A truthy `legend` with nothing to
36
+ legend (flat/fixed color) silently renders nothing, so
37
+ `set_defaults(legend=True)` is safe as a global default. Legends are
38
+ recorded into the shared `DisplayList`, so SVG **and** WebGPU outputs both
39
+ carry them, and the captured scale/category metadata is exposed as
40
+ `component.legend_info` (a `polars2svg.LegendInfo`). v1 scope is the color
41
+ encoding only (no size legends yet). The default `legend=False` renders
42
+ byte-identically to previous output.
43
+
44
+ ### Fixed
45
+
46
+ - **`pip install polars2svg[mlx]` was broken.** `tfdp_layout.py` imports
47
+ `scipy`/`networkx`/`scikit-learn`, but the `mlx` extra did not chain `[layouts]`
48
+ (as `interactive` does), so the extra resolved to an install whose only module
49
+ could not import. `mlx` now chains `polars2svg[layouts]`.
50
+
51
+ ### Changed
52
+
53
+ - `TFDPLayout` no longer hardcodes `device=mx.gpu`. It probes the GPU once and
54
+ falls back to the CPU device with a one-time warning if none is usable — the
55
+ plain Linux MLX wheel has no GPU backend, which would otherwise have failed at
56
+ the first kernel.
57
+
58
+ - **Slimmed the core install; added `[layouts]` and `[interactive]` extras.**
59
+ `pip install polars2svg` now pulls only `polars`, `numpy`, `pyarrow`,
60
+ `pillow`, and `platformdirs` — enough for `xyp`, `histop`, `timep`, `piep`,
61
+ `smallp`, and `spreadlinesp`. `networkx`, `scikit-learn`, `scipy`, `shapely`,
62
+ and `squarify` move to the new `polars2svg[layouts]` extra (needed by
63
+ `linkp`'s/`chordp`'s pluggable layouts, the graph-layout mixin, and
64
+ shapely-typed `background=` shapes on `xyp`/`linkp`); `panel`,
65
+ `jupyter_bokeh`, and `param` move to `polars2svg[interactive]` (which
66
+ includes `layouts`, since the interactive graph views need it too).
67
+ `polars2svg[all]` restores the previous "batteries included" install.
68
+ Calling a component that needs a missing extra (`chordp()`, `panelize()`,
69
+ `xypi()`, a graph-layout mixin method, `background=` shapes) now raises a
70
+ clear `ImportError` naming the extra to install, rather than either
71
+ succeeding silently or failing with a bare `ModuleNotFoundError` — this
72
+ breaks nothing at runtime as long as the extra is installed, but is a
73
+ **breaking change to the default install** if you were relying on
74
+ `linkp`/`chordp`/interactive variants working out of the box with a bare
75
+ `pip install polars2svg`.
76
+
77
+ ## [0.1.0] — 2026-07-10
78
+
79
+ Initial public release: Polars-native DataFrame → SVG visualizations for Jupyter.
80
+ Pass a Polars `DataFrame` straight to a component and get a crisp, self-contained
81
+ SVG back. Every component also has a linked, interactive variant (built on Panel)
82
+ for brushing and cross-filtering in a notebook, plus optional WebGPU rendering for
83
+ large frames.
84
+
85
+ ### Added
86
+
87
+ - **Component roster** — eight rendered components, each a method on a
88
+ `Polars2SVG` instance:
89
+ - `xyp` — scatter / distribution plot (numeric or categorical x/y).
90
+ - `histop` — horizontal histogram bars, one per category/bin.
91
+ - `timep` — temporal bar chart with linear or periodic (day-of-week, month, …)
92
+ time modes.
93
+ - `linkp` — node-link graph / network with pluggable layouts.
94
+ - `chordp` — chord diagram of weighted flows around a circle.
95
+ - `piep` — pie chart.
96
+ - `spreadlinesp` — egocentric radial "spread" rings for influence/propagation.
97
+ - `smallp` — small multiples: a grid of one template component faceted by a field.
98
+ - **Interactive, cross-linked variants** — `xypi`, `histopi`, `timepi`, `linkpi`,
99
+ `smallpi` (and peers) sharing the static signatures, composed into a dashboard
100
+ with `panelize(layout)` for brushing and cross-filtering in a notebook.
101
+ - **WebGPU rendering** — pass `use_webgpu=True` to render through WebGPU for large
102
+ frames; a `webgpu()` / `gpuDisplayList()` path exists across all components.
103
+ - **Orthogonal `count=` / `color=` encoding** — a shared aggregation rule for
104
+ `count=` (row count, sum, distinct-count, struct distinct-count) and a
105
+ dtype-keyed `color=` inference (numeric → magnitude spectrum, otherwise
106
+ categorical), with enums (`SCALARp`, `SETp`, `CSETp`, `CMAGNITUDE_SUMp`,
107
+ `CROW_MAGNITUDEp`, `CROW_STRETCHEDp`, …) to pin intent explicitly.
108
+ - **T-fields** — `p2s.tField(column, enum)` for time-transformation fields
109
+ (returns a frozen `TField` `str` subclass); the legacy `'column|suffix'` string
110
+ form still works with a one-time deprecation warning.
111
+ - **Pluggable layout classes** exported from the package: `PolarsForceDirectedLayout`,
112
+ `ConveyProximityLayout`, `LandmarkMDSLayout`, `PivotMDSLayout`, `TFDPLayout`
113
+ (all satisfy the `LayoutAlgorithm` protocol via `.results()`).
114
+ - **Export API** — `save(path)` / `savePNG(path)` on every rendered component;
115
+ SVG save has no extra dependency, PNG rasterization is behind the `[export]`
116
+ extra (`svglib`, `reportlab`, `rlPyCairo`).
117
+ - **Typed package** — ships a `py.typed` marker with type hints on the public
118
+ surface (constructor, component factories, `tField`, `panelize`, layout classes).
119
+ - **Exception hierarchy** — `Polars2SVGError` base with `InvalidSpecError` and
120
+ `DataError` subclasses, all exported from the package.
121
+ - **`__version__`** attribute, read from installed package metadata.
122
+ - **Optional extras** — `[mlx]` (Apple-silicon MLX-accelerated force-directed
123
+ layout) and `[export]` (PNG rasterization).
124
+ - **Diagnostic INFO logging** through the `polars2svg_logger` for dtype-keyed
125
+ `count=`/`color=` inference choices (off by default).
126
+ - **SECURITY.md** documenting the SVG-injection threat model (row-data label text
127
+ is HTML-escaped; component configuration is trusted).
128
+
129
+ [Unreleased]: https://github.com/datrcode/polars2svg/compare/v0.1.0...HEAD
130
+ [0.1.0]: https://github.com/datrcode/polars2svg/releases/tag/v0.1.0