jarvisplot 2.1.3__tar.gz → 2.1.4__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 (242) hide show
  1. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/JarvisPLOT.egg-info/PKG-INFO +1 -1
  2. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/JarvisPLOT.egg-info/SOURCES.txt +1 -0
  3. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/PKG-INFO +1 -1
  4. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/Figure/layout_runtime.py +22 -2
  5. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/Figure/preprocessor.py +13 -10
  6. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/Figure/profile_runtime.py +54 -32
  7. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/cards/colors/colormaps.json +15 -0
  8. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/transform_contracts.py +2 -1
  9. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/utils/cmaps.py +3 -1
  10. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/verbs/cap.py +1 -0
  11. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/pyproject.toml +1 -1
  12. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_axis_title.py +26 -0
  13. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_cmaps.py +15 -0
  14. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_figure_contracts.py +10 -3
  15. jarvisplot-2.1.4/tests/test_profile_runtime.py +141 -0
  16. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/JarvisPLOT.egg-info/dependency_links.txt +0 -0
  17. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/JarvisPLOT.egg-info/entry_points.txt +0 -0
  18. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/JarvisPLOT.egg-info/requires.txt +0 -0
  19. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/JarvisPLOT.egg-info/top_level.txt +0 -0
  20. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/README.md +0 -0
  21. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/Figure/__init__.py +0 -0
  22. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/Figure/adapters_rect.py +0 -0
  23. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/Figure/adapters_ternary.py +0 -0
  24. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/Figure/bin_stat_runtime.py +0 -0
  25. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/Figure/cards/std_axes_adapter_config.json +0 -0
  26. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/Figure/colorbar_runtime.py +0 -0
  27. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/Figure/config_runtime.py +0 -0
  28. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/Figure/corr_layout.py +0 -0
  29. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/Figure/corr_layout_diamond.py +0 -0
  30. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/Figure/corr_order.py +0 -0
  31. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/Figure/correlation_runtime.py +0 -0
  32. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/Figure/corrplot_runtime.py +0 -0
  33. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/Figure/data_pipelines.py +0 -0
  34. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/Figure/debug_config.py +0 -0
  35. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/Figure/density_cell_runtime.py +0 -0
  36. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/Figure/design_runtime.py +0 -0
  37. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/Figure/dynesty_runtime.py +0 -0
  38. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/Figure/figure.py +0 -0
  39. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/Figure/figure_types.py +0 -0
  40. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/Figure/helper.py +0 -0
  41. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/Figure/interp_2d_runtime.py +0 -0
  42. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/Figure/interp_natural_neighbor.py +0 -0
  43. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/Figure/interp_natural_neighbor_exact.py +0 -0
  44. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/Figure/layer_runtime.py +0 -0
  45. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/Figure/method_registry.py +0 -0
  46. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/Figure/posterior_density_runtime.py +0 -0
  47. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/Figure/posterior_hpd.py +0 -0
  48. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/Figure/posterior_mesh.py +0 -0
  49. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/Figure/preprocessor_runtime.py +0 -0
  50. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/Figure/significance_runtime.py +0 -0
  51. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/Figure/style_runtime.py +0 -0
  52. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/__init__.py +0 -0
  53. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/agent_digest.py +0 -0
  54. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/agent_io.py +0 -0
  55. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/cache_store.py +0 -0
  56. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/capabilities.py +0 -0
  57. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/cards/a4paper/1x1/ternary.json +0 -0
  58. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/cards/a4paper/2x1/dynesty_runplot.json +0 -0
  59. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/cards/a4paper/2x1/rect.json +0 -0
  60. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/cards/a4paper/2x1/rect5x1.json +0 -0
  61. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/cards/a4paper/2x1/rect_cmap.json +0 -0
  62. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/cards/a4paper/2x1/rect_marginal.json +0 -0
  63. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/cards/a4paper/2x1/rectratio.json +0 -0
  64. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/cards/a4paper/2x1/ternary.json +0 -0
  65. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/cards/a4paper/2x1/ternary_cmap.json +0 -0
  66. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/cards/a4paper/4x1/rect.json +0 -0
  67. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/cards/a4paper/4x1/rect_cmap.json +0 -0
  68. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/cards/a4paper/4x1/ternary.json +0 -0
  69. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/cards/a4paper/4x1/ternary_cmap.json +0 -0
  70. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/cards/args.json +0 -0
  71. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/cards/corrplot/diamond.json +0 -0
  72. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/cards/corrplot/matrix.json +0 -0
  73. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/cards/default/output.json +0 -0
  74. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/cards/flowchart/default.json +0 -0
  75. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/cards/gambit/1x1/ternary.json +0 -0
  76. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/cards/gambit/2x1/rect_cmap.json +0 -0
  77. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/cards/gambit/2x1/ternary.json +0 -0
  78. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/cards/gambit/2x1/ternary_cmap.json +0 -0
  79. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/cards/icons/JarvisHEP.png +0 -0
  80. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/cards/icons/flowchart/JarvisHEP.png +0 -0
  81. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/cards/icons/flowchart/calculator.png +0 -0
  82. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/cards/icons/flowchart/inputfile.png +0 -0
  83. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/cards/icons/flowchart/opera.png +0 -0
  84. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/cards/icons/flowchart/outputfile.png +0 -0
  85. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/cards/icons/flowchart/sampler.png +0 -0
  86. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/cards/icons/gambit.png +0 -0
  87. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/cards/icons/gambit_small.png +0 -0
  88. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/cards/style_preference.json +0 -0
  89. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/cli.py +0 -0
  90. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/cli_help.py +0 -0
  91. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/client.py +0 -0
  92. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/column_demand.py +0 -0
  93. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/column_probe.py +0 -0
  94. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/config.py +0 -0
  95. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/config_address.py +0 -0
  96. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/core.py +0 -0
  97. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/core_assets.py +0 -0
  98. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/core_runtime.py +0 -0
  99. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/data_access.py +0 -0
  100. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/data_loader.py +0 -0
  101. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/data_loader_hdf5.py +0 -0
  102. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/data_loader_runtime.py +0 -0
  103. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/data_loader_summary.py +0 -0
  104. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/dataset_types.py +0 -0
  105. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/diagnostic_guidance.py +0 -0
  106. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/diagnostics.py +0 -0
  107. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/dryrun_runtime.py +0 -0
  108. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/expr_names.py +0 -0
  109. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/fix_apply.py +0 -0
  110. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/flowchart.py +0 -0
  111. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/inner_func.py +0 -0
  112. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/man_catalog.py +0 -0
  113. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/man_methods.py +0 -0
  114. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/man_render_agent.py +0 -0
  115. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/man_render_human.py +0 -0
  116. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/man_transforms.py +0 -0
  117. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/manual_cards/agent-output.yaml +0 -0
  118. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/manual_cards/cli-map.yaml +0 -0
  119. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/manual_cards/data-columns.yaml +0 -0
  120. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/manual_cards/debug-overlay.yaml +0 -0
  121. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/manual_cards/diagnostics.yaml +0 -0
  122. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/manual_cards/dryrun-viz.yaml +0 -0
  123. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/manual_cards/layer-method.yaml +0 -0
  124. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/manual_cards/manifest.yaml +0 -0
  125. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/manual_cards/method_notes.yaml +0 -0
  126. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/manual_cards/style-axes.yaml +0 -0
  127. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/manual_cards/type-correlation-matrix.yaml +0 -0
  128. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/manual_cards/type-posterior-2d.yaml +0 -0
  129. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/manual_cards/type-profile-2d.yaml +0 -0
  130. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/manual_cards/validate.yaml +0 -0
  131. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/manual_cards/workflow.yaml +0 -0
  132. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/manual_cards/yaml-root.yaml +0 -0
  133. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/memtrace.py +0 -0
  134. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/method_contracts.py +0 -0
  135. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/render_health.py +0 -0
  136. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/schema/core/common.json +0 -0
  137. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/schema/core/dataset.json +0 -0
  138. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/schema/core/figure.json +0 -0
  139. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/schema/core/layer.json +0 -0
  140. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/schema/core/transform.json +0 -0
  141. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/schema/manifest.json +0 -0
  142. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/schema/methods/bar.json +0 -0
  143. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/schema/methods/barh.json +0 -0
  144. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/schema/methods/contour.json +0 -0
  145. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/schema/methods/contourf.json +0 -0
  146. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/schema/methods/corrplot.json +0 -0
  147. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/schema/methods/dynesty_runplot.json +0 -0
  148. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/schema/methods/errorbar.json +0 -0
  149. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/schema/methods/fill.json +0 -0
  150. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/schema/methods/fill_between.json +0 -0
  151. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/schema/methods/fill_betweenx.json +0 -0
  152. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/schema/methods/hist.json +0 -0
  153. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/schema/methods/hist2d.json +0 -0
  154. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/schema/methods/imshow.json +0 -0
  155. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/schema/methods/jpcontour.json +0 -0
  156. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/schema/methods/jpcontourf.json +0 -0
  157. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/schema/methods/jpfield.json +0 -0
  158. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/schema/methods/pcolor.json +0 -0
  159. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/schema/methods/pcolormesh.json +0 -0
  160. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/schema/methods/plot.json +0 -0
  161. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/schema/methods/quiver.json +0 -0
  162. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/schema/methods/scatter.json +0 -0
  163. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/schema/methods/stairs.json +0 -0
  164. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/schema/methods/step.json +0 -0
  165. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/schema/methods/tricontour.json +0 -0
  166. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/schema/methods/tricontourf.json +0 -0
  167. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/schema/methods/tripcolor.json +0 -0
  168. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/schema/methods/tripcolor_axes.json +0 -0
  169. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/schema/methods/triplot.json +0 -0
  170. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/schema/methods/voronoi.json +0 -0
  171. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/schema/methods/voronoif.json +0 -0
  172. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/schema/plot-config.json +0 -0
  173. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/schema_catalog.py +0 -0
  174. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/schema_diagnostics.py +0 -0
  175. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/templates_catalog.py +0 -0
  176. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/utils/__init__.py +0 -0
  177. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/utils/dataframes.py +0 -0
  178. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/utils/expression.py +0 -0
  179. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/utils/interpolator.py +0 -0
  180. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/utils/pathing.py +0 -0
  181. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/validation.py +0 -0
  182. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/verbs/__init__.py +0 -0
  183. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/verbs/config_cmd.py +0 -0
  184. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/verbs/data.py +0 -0
  185. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/verbs/doctor.py +0 -0
  186. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/verbs/dryrun.py +0 -0
  187. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/verbs/explain.py +0 -0
  188. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/verbs/man.py +0 -0
  189. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/verbs/suggest.py +0 -0
  190. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/verbs/template.py +0 -0
  191. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/verbs/validate.py +0 -0
  192. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/jarvisplot/yaml_io.py +0 -0
  193. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/setup.cfg +0 -0
  194. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_agent_digest.py +0 -0
  195. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_agent_envelope.py +0 -0
  196. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_capabilities.py +0 -0
  197. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_card_debug_shape.py +0 -0
  198. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_cli_help.py +0 -0
  199. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_cli_verbs.py +0 -0
  200. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_column_existence.py +0 -0
  201. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_config_addressing.py +0 -0
  202. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_config_edit.py +0 -0
  203. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_config_expand.py +0 -0
  204. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_correlation_runtime.py +0 -0
  205. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_corrplot.py +0 -0
  206. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_data_access.py +0 -0
  207. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_data_describe.py +0 -0
  208. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_data_head_eval.py +0 -0
  209. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_data_loader_contracts.py +0 -0
  210. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_data_pipelines.py +0 -0
  211. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_debug_config.py +0 -0
  212. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_debug_switches.py +0 -0
  213. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_debug_yaml_override.py +0 -0
  214. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_density_cell_transform.py +0 -0
  215. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_design_runtime.py +0 -0
  216. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_diagnostic_guidance.py +0 -0
  217. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_docs_status_labels.py +0 -0
  218. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_doctor_partial.py +0 -0
  219. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_explain.py +0 -0
  220. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_figure_type_expander.py +0 -0
  221. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_fix_application.py +0 -0
  222. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_flowchart_renderer.py +0 -0
  223. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_imshow_adapter.py +0 -0
  224. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_interp_2d_transform.py +0 -0
  225. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_interp_natural_neighbor.py +0 -0
  226. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_interpolator_contracts.py +0 -0
  227. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_jpcontour_methods.py +0 -0
  228. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_man_cli.py +0 -0
  229. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_method_contracts.py +0 -0
  230. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_parse_data_metadata.py +0 -0
  231. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_pathing_contracts.py +0 -0
  232. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_posterior_hpd.py +0 -0
  233. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_render_health.py +0 -0
  234. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_row_ledger.py +0 -0
  235. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_schema_catalog.py +0 -0
  236. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_suggest.py +0 -0
  237. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_suggest_axes.py +0 -0
  238. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_template_catalog.py +0 -0
  239. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_template_json.py +0 -0
  240. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_ternary_debug.py +0 -0
  241. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_transform_contracts.py +0 -0
  242. {jarvisplot-2.1.3 → jarvisplot-2.1.4}/tests/test_validate_no_matplotlib.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: JarvisPLOT
3
- Version: 2.1.3
3
+ Version: 2.1.4
4
4
  Summary: Jarvis-PLOT: YAML-driven plotting engine
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -227,6 +227,7 @@ tests/test_method_contracts.py
227
227
  tests/test_parse_data_metadata.py
228
228
  tests/test_pathing_contracts.py
229
229
  tests/test_posterior_hpd.py
230
+ tests/test_profile_runtime.py
230
231
  tests/test_render_health.py
231
232
  tests/test_row_ledger.py
232
233
  tests/test_schema_catalog.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: JarvisPLOT
3
- Version: 2.1.3
3
+ Version: 2.1.4
4
4
  Summary: Jarvis-PLOT: YAML-driven plotting engine
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -533,15 +533,35 @@ def apply_manual_ticks(fig, ax_obj, which: str, ticks_cfg: dict):
533
533
  if pos is None:
534
534
  return
535
535
  target = ax_obj.ax if hasattr(ax_obj, "ax") else ax_obj
536
+
537
+ # Matplotlib's set_xticks/set_yticks expands the view limits when a
538
+ # requested tick lies outside the current range. That is surprising for
539
+ # Jarvis-PLOT cards, where an explicit frame.xlim/ylim is authoritative
540
+ # (e.g. ylim: [200, 400] with a stale tick at 600). Preserve the range
541
+ # only when autoscaling was already disabled; otherwise leave the normal
542
+ # autoscaling behaviour untouched for axes whose limits are data-driven.
543
+ axis = str(which).lower()
544
+ if axis not in {"x", "y"}:
545
+ return
546
+ get_lim = target.get_xlim if axis == "x" else target.get_ylim
547
+ set_lim = target.set_xlim if axis == "x" else target.set_ylim
548
+ get_autoscale = (
549
+ target.get_autoscalex_on if axis == "x" else target.get_autoscaley_on
550
+ )
551
+ original_lim = get_lim()
552
+ autoscale_was_on = get_autoscale()
536
553
  try:
537
- if which == "x":
554
+ if axis == "x":
538
555
  target.set_xticks(pos)
539
556
  if labs is not None:
540
557
  target.set_xticklabels(labs)
541
- elif which == "y":
558
+ elif axis == "y":
542
559
  target.set_yticks(pos)
543
560
  if labs is not None:
544
561
  target.set_yticklabels(labs)
545
562
  except Exception as e:
546
563
  if fig.logger:
547
564
  fig.logger.warning(f"Manual ticks apply failed on {which}-axis: {e}")
565
+ finally:
566
+ if not autoscale_was_on:
567
+ set_lim(original_lim)
@@ -851,7 +851,7 @@ class DataPreprocessor:
851
851
  eff_transform = self._effective_transform(source, transform)
852
852
  payload = {
853
853
  "kind": "pipeline",
854
- "algo": "pregrid-v6",
854
+ "algo": "pregrid-v7-objective-finite-voronoi",
855
855
  "source": self._source_token(source, combine=combine),
856
856
  "transform": eff_transform,
857
857
  "combine": str(combine),
@@ -900,12 +900,11 @@ class DataPreprocessor:
900
900
  if isinstance(item, Mapping):
901
901
  src = item.get("source")
902
902
  tf = self._effective_transform(src, item.get("transform"))
903
- tokens.append(
904
- {
905
- "source": self._source_token(src),
906
- "transform": tf,
907
- }
908
- )
903
+ token = {"source": self._source_token(src), "transform": tf}
904
+ profile_sig = self._runtime_profile_signature(tf)
905
+ if profile_sig is not None:
906
+ token["profile_signature"] = profile_sig
907
+ tokens.append(token)
909
908
  payload = {
910
909
  "name": layer.get("name"),
911
910
  "axes": layer.get("axes"),
@@ -928,7 +927,7 @@ class DataPreprocessor:
928
927
  cfg = deepcopy(cfg)
929
928
  else:
930
929
  cfg = {"value": cfg}
931
- tokens.append({"kind": "profile", "cfg": cfg})
930
+ tokens.append({"kind": "profile", "algo": "objective-finite-voronoi-v2", "cfg": cfg})
932
931
  return tokens
933
932
 
934
933
  def _runtime_profile_signature(self, transform: Any) -> Optional[str]:
@@ -1332,13 +1331,17 @@ class DataPreprocessor:
1332
1331
 
1333
1332
  @staticmethod
1334
1333
  def _preprofile_profile_cfg(profile_cfg: Any) -> Any:
1335
- # Preprofile cache identity is intentionally coordinates-only:
1336
- # runtime profile method/bin/objective changes must reuse same preprofile.
1334
+ # Runtime method/bin can share a preprofile, but objective and pregrid
1335
+ # controls change which source rows survive and must enter its identity.
1337
1336
  if not isinstance(profile_cfg, Mapping):
1338
1337
  return {}
1339
1338
  slim: Dict[str, Any] = {}
1340
1339
  if "coordinates" in profile_cfg:
1341
1340
  slim["coordinates"] = deepcopy(profile_cfg["coordinates"])
1341
+ slim["objective"] = str(profile_cfg.get("objective", "max")).lower()
1342
+ for key in ("pregrid", "pregrid_bin"):
1343
+ if key in profile_cfg:
1344
+ slim[key] = deepcopy(profile_cfg[key])
1342
1345
  return slim
1343
1346
 
1344
1347
  def _split_prebuild_transform(self, transform: Any):
@@ -451,11 +451,26 @@ def profiling(df, prof, logger):
451
451
  else:
452
452
  z = df["z"]
453
453
 
454
- logger.debug("After loading profiling x, y, z. ")
454
+ if logger:
455
+ logger.debug("After loading profiling x, y, z. ")
455
456
 
456
- x = np.asarray(x)
457
- y = np.asarray(y)
458
- z = np.asarray(z)
457
+ x = np.asarray(x, dtype=float)
458
+ y = np.asarray(y, dtype=float)
459
+ z = np.asarray(z, dtype=float)
460
+ # A single invalid objective used to poison min/max(z), making every
461
+ # normalized dz NaN and silently disabling the z-aware exclusion rule.
462
+ valid = np.isfinite(x) & np.isfinite(y) & np.isfinite(z)
463
+ for cfg, values in ((xcfg, x), (ycfg, y), (zcfg, z)):
464
+ if str(cfg.get("scale", "linear")).lower() == "log":
465
+ valid &= values > 0
466
+ if not np.all(valid):
467
+ df = df.iloc[np.flatnonzero(valid)]
468
+ x, y, z = x[valid], y[valid], z[valid]
469
+ if not len(z):
470
+ out = df.copy()
471
+ for cfg, values, default in ((xcfg, x, "x"), (ycfg, y, "y"), (zcfg, z, "z")):
472
+ out[cfg.get("name", default)] = values
473
+ return out
459
474
 
460
475
  if grid == "ternary":
461
476
  xlim = xcfg.get("lim", [0, 1])
@@ -535,20 +550,12 @@ def profiling(df, prof, logger):
535
550
  },
536
551
  )
537
552
 
538
- if xscale == "log":
539
- xx = (np.log(xx) - np.log(xlim[0])) / (np.log(xlim[1]) - np.log(xlim[0]))
540
- else:
541
- xx = (xx - xlim[0]) / (xlim[1] - xlim[0])
542
-
543
- if yscale == "log":
544
- yy = (np.log(yy) - np.log(ylim[0])) / (np.log(ylim[1]) - np.log(ylim[0]))
545
- else:
546
- yy = (yy - ylim[0]) / (ylim[1] - ylim[0])
547
-
548
- if zscale == "log":
549
- zz = (np.log(zz) - np.log(zlim[0])) / (np.log(zlim[1]) - np.log(zlim[0]))
550
- else:
551
- zz = (zz - zlim[0]) / (zlim[1] - zlim[0])
553
+ xx = _normalize_profile_axis(xx, xlim, xscale)
554
+ yy = _normalize_profile_axis(yy, ylim, yscale)
555
+ zz = _normalize_profile_axis(zz, zlim, zscale)
556
+ # Empty-grid seeds are processed last and carry NaN in the output. Give
557
+ # them a finite exclusion coordinate, including for logarithmic z.
558
+ zz[len(order):] = 2.0 if obj == "min" else -1.0
552
559
 
553
560
  msk = profile_bridson_sorted(xx, yy, zz, radius)
554
561
  keep_pos = np.flatnonzero(msk).astype(np.int64, copy=False)
@@ -557,6 +564,22 @@ def profiling(df, prof, logger):
557
564
  keep_source = keep_pos[keep_pos < source_count]
558
565
  keep_grid = keep_pos[keep_pos >= source_count] - source_count
559
566
 
567
+ # Greedy thinning only chooses the spatial partition: a high-valued point
568
+ # can be suppressed by an earlier seed, then a worse seed can survive on
569
+ # the other side of it. Re-profile *all* input rows within the nearest-seed
570
+ # (Voronoi) cells so those suppressed candidates still contribute. Select
571
+ # the entire winning source row, not just its z at another row's x/y.
572
+ if keep_source.size:
573
+ from scipy.spatial import cKDTree
574
+
575
+ source_xy = np.column_stack([xx[:source_count], yy[:source_count]])
576
+ tree = cKDTree(source_xy[keep_source])
577
+ _, cell = tree.query(source_xy)
578
+ winners = np.full(keep_source.size, source_count, dtype=np.int64)
579
+ # Input is already ordered best first for either max or min.
580
+ np.minimum.at(winners, cell, np.arange(source_count, dtype=np.int64))
581
+ keep_source = np.sort(winners[winners < source_count])
582
+
560
583
  source_rows = order[keep_source]
561
584
  source_x = x[source_rows]
562
585
  source_y = y[source_rows]
@@ -594,7 +617,7 @@ def profiling(df, prof, logger):
594
617
 
595
618
 
596
619
  def _preprofiling(df, prof, logger):
597
- """Lightweight pre-profiling for cache prebuild."""
620
+ """Keep the requested finite extremum per pre-bin for cache prebuild."""
598
621
 
599
622
  def _norm(arr, lim, scale):
600
623
  arr = np.asarray(arr, dtype=float)
@@ -765,14 +788,14 @@ def _preprofiling(df, prof, logger):
765
788
 
766
789
  xnorm = _norm(xvals, xlim, xscale)
767
790
  ynorm = _norm(yvals, ylim, yscale)
768
- valid = np.isfinite(xnorm) & np.isfinite(ynorm)
791
+ valid = np.isfinite(xnorm) & np.isfinite(ynorm) & np.isfinite(zvals)
769
792
  if not np.any(valid):
770
793
  if logger:
771
- logger.warning("Preprofiling got no finite points; returning original dataframe.")
772
- out = df.copy(deep=False)
773
- out[xind] = xvals
774
- out[yind] = yvals
775
- out[zind] = zvals
794
+ logger.warning("Preprofiling got no finite points; returning an empty dataframe.")
795
+ out = df.iloc[:0].copy()
796
+ out[xind] = xvals[:0]
797
+ out[yind] = yvals[:0]
798
+ out[zind] = zvals[:0]
776
799
  return out
777
800
 
778
801
  xv = np.clip(xnorm[valid], 0.0, 1.0 - 1e-12)
@@ -783,20 +806,19 @@ def _preprofiling(df, prof, logger):
783
806
 
784
807
  idx_src = np.flatnonzero(valid).astype(np.int64)
785
808
  zraw = np.asarray(zvals, dtype=float)[valid]
786
- zmax = np.where(np.isfinite(zraw), zraw, -np.inf)
787
- zmin = np.where(np.isfinite(zraw), zraw, np.inf)
788
809
  tmp = pd.DataFrame(
789
810
  {
790
811
  "__cell__": cell,
791
- "__zmax__": zmax,
792
- "__zmin__": zmin,
812
+ "__z__": zraw,
793
813
  "__pos__": idx_src,
794
814
  }
795
815
  )
796
816
 
797
- loc_max = tmp.groupby("__cell__", sort=False)["__zmax__"].idxmax().to_numpy(dtype=np.int64, copy=False)
798
- loc_min = tmp.groupby("__cell__", sort=False)["__zmin__"].idxmin().to_numpy(dtype=np.int64, copy=False)
799
- loc_all = np.unique(np.concatenate([loc_max, loc_min], axis=0))
817
+ if str(prof.get("objective", "max")).lower() == "min":
818
+ loc = tmp.groupby("__cell__", sort=False)["__z__"].idxmin()
819
+ else:
820
+ loc = tmp.groupby("__cell__", sort=False)["__z__"].idxmax()
821
+ loc_all = np.sort(loc.to_numpy(dtype=np.int64, copy=False))
800
822
  keep_pos = tmp.iloc[loc_all]["__pos__"].to_numpy(dtype=np.int64, copy=False)
801
823
  reduced = df.iloc[keep_pos].copy()
802
824
  reduced[xind] = xvals[keep_pos]
@@ -95,6 +95,21 @@
95
95
  [1.0, "#575AA6"]
96
96
  ]
97
97
  },
98
+ {
99
+ "name": "Jpurples",
100
+ "type": "linear",
101
+ "colors": [
102
+ [0.000, "#FFFFFF"],
103
+ [0.125, "#EFEDF5"],
104
+ [0.250, "#DADAEB"],
105
+ [0.375, "#BCBDDC"],
106
+ [0.500, "#9E9AC8"],
107
+ [0.625, "#807CBA"],
108
+ [0.750, "#6950A3"],
109
+ [0.875, "#53268F"],
110
+ [1.000, "#3F007D"]
111
+ ]
112
+ },
98
113
  {
99
114
  "name": "SpectralB",
100
115
  "type": "linear",
@@ -499,7 +499,8 @@ TRANSFORM_CONTRACTS: dict[str, dict[str, Any]] = {
499
499
  "description": (
500
500
  "Optional coarse pre-binning before Bridson/grid profile "
501
501
  "(large tables). Mapping: {bin, enable}; false disables; "
502
- "omit for auto-prebin from row count."
502
+ "omit for auto-prebin from row count. Keeps one finite "
503
+ "extremum per cell according to objective (max/min)."
503
504
  ),
504
505
  "properties": {
505
506
  "bin": {
@@ -40,7 +40,9 @@ def _register(name: str, cmap: Colormap, force: bool) -> bool:
40
40
  cmap.name = name
41
41
  except Exception:
42
42
  pass
43
- if "override" in sig.parameters:
43
+ if "force" in sig.parameters:
44
+ kwargs["force"] = bool(force)
45
+ elif "override" in sig.parameters:
44
46
  kwargs["override"] = bool(force)
45
47
  except Exception:
46
48
  # If signature introspection fails, try safest call (cmap only).
@@ -170,6 +170,7 @@ _CMAP_DESCRIPTIONS = {
170
170
  "gambit_cmap": "Continuous black → blue → cyan → yellow map.",
171
171
  "jarvis_rainbow": "Continuous purple → blue → cyan → green → orange → red map.",
172
172
  "jarvis_rainbow2": "Continuous red → yellow → green → blue map.",
173
+ "Jpurples": "Matplotlib Purples-style continuous map with a pure-white zero endpoint.",
173
174
  "SpectralB": "Spectral-style continuous map from red through green to blue.",
174
175
  "RdBuB": "Diverging red → white → blue map.",
175
176
  "chrisB": "Diverging black → red → white → blue → navy map.",
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "JarvisPLOT"
7
- version = "2.1.3"
7
+ version = "2.1.4"
8
8
  description = "Jarvis-PLOT: YAML-driven plotting engine"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -257,3 +257,29 @@ def test_side_axes_honour_manual_tick_positions():
257
257
  assert [t.get_text() for t in ax.ax.get_yticklabels()] == list("abcde")
258
258
  finally:
259
259
  plt.close(raw_fig)
260
+
261
+
262
+ def test_manual_ticks_do_not_expand_explicit_axis_limits():
263
+ """Out-of-range manual ticks must not override an explicit frame limit."""
264
+ import matplotlib
265
+
266
+ matplotlib.use("Agg")
267
+ import matplotlib.pyplot as plt
268
+
269
+ from jarvisplot.Figure.layout_runtime import apply_manual_ticks
270
+
271
+ raw_fig, ax = plt.subplots(figsize=(3.3, 3.0))
272
+ ax.set_ylim(200, 400)
273
+ apply_manual_ticks(
274
+ SimpleNamespace(logger=_logger()),
275
+ ax,
276
+ "y",
277
+ {
278
+ "positions": [200, 300, 400, 500, 600],
279
+ "labels": ["200", "300", "400", "500", "600"],
280
+ },
281
+ )
282
+
283
+ assert ax.get_ylim() == pytest.approx((200.0, 400.0))
284
+ assert list(ax.get_yticks()) == [200, 300, 400, 500, 600]
285
+ plt.close(raw_fig)
@@ -4,6 +4,7 @@ import json
4
4
  from pathlib import Path
5
5
 
6
6
  import matplotlib as mpl
7
+ import numpy as np
7
8
 
8
9
  from jarvisplot.utils import cmaps
9
10
 
@@ -40,3 +41,17 @@ def test_official_tab5_is_registered_as_five_discrete_tab10_colors():
40
41
  )
41
42
  assert spec["type"] == "listed"
42
43
  assert len(spec["colors"]) == 5
44
+
45
+
46
+ def test_jpurples_matches_purples_style_with_a_pure_white_zero_endpoint():
47
+ summary = cmaps.register_from_json(COLORMAPS_JSON, force=True)
48
+
49
+ assert "Jpurples" in summary["registered"]
50
+ assert "Jpurples_r" in summary["registered"]
51
+
52
+ jpurples = mpl.colormaps["Jpurples"]
53
+ purples = mpl.colormaps["Purples"]
54
+
55
+ assert mpl.colors.to_hex(jpurples(0.0), keep_alpha=False) == "#ffffff"
56
+ for value in (0.125, 0.25, 0.5, 0.75, 1.0):
57
+ assert np.allclose(jpurples(value), purples(value), atol=1 / 255)
@@ -1094,10 +1094,12 @@ def test_preprofile_identity_ignores_runtime_bin():
1094
1094
  "coordinates": {"x": {"expr": "x"}, "y": {"expr": "y"}},
1095
1095
  "method": "bridson",
1096
1096
  "bin": 32,
1097
- "objective": "min",
1097
+ "objective": "max",
1098
1098
  }
1099
1099
 
1100
1100
  assert dp._preprofile_profile_cfg(cfg1) == dp._preprofile_profile_cfg(cfg2)
1101
+ cfg2["objective"] = "min"
1102
+ assert dp._preprofile_profile_cfg(cfg1) != dp._preprofile_profile_cfg(cfg2)
1101
1103
 
1102
1104
 
1103
1105
  def test_runtime_projection_includes_row_identity_and_demand():
@@ -1159,7 +1161,7 @@ def test_runtime_transform_to_csv_exports_and_bypasses_cache(tmp_path):
1159
1161
  assert list(out.columns) == ["y", "z"]
1160
1162
 
1161
1163
 
1162
- def test_prebuild_split_keeps_profile_identity_coordinates_only():
1164
+ def test_prebuild_split_keeps_profile_reduction_controls():
1163
1165
  dp = DataPreprocessor(context=None)
1164
1166
  transform = [
1165
1167
  {"add_column": {"name": "c", "expr": "a + b"}},
@@ -1169,6 +1171,7 @@ def test_prebuild_split_keeps_profile_identity_coordinates_only():
1169
1171
  "method": "bridson",
1170
1172
  "bin": 16,
1171
1173
  "objective": "max",
1174
+ "pregrid": {"bin": 25, "enable": True},
1172
1175
  }
1173
1176
  },
1174
1177
  {"sortby": "c"},
@@ -1178,7 +1181,11 @@ def test_prebuild_split_keeps_profile_identity_coordinates_only():
1178
1181
 
1179
1182
  assert pre_transform is not None
1180
1183
  assert runtime_transform is not None
1181
- assert pre_transform[-1]["profile"] == {"coordinates": {"x": {"expr": "x"}, "y": {"expr": "y"}}}
1184
+ assert pre_transform[-1]["profile"] == {
1185
+ "coordinates": {"x": {"expr": "x"}, "y": {"expr": "y"}},
1186
+ "objective": "max",
1187
+ "pregrid": {"bin": 25, "enable": True},
1188
+ }
1182
1189
  assert runtime_transform[0]["profile"]["method"] == "bridson"
1183
1190
  assert runtime_transform[0]["profile"]["bin"] == 16
1184
1191
  assert runtime_transform[1]["sortby"] == "c"
@@ -0,0 +1,141 @@
1
+ """Regression coverage for finite, objective-aware profile support selection."""
2
+
3
+ import logging
4
+
5
+ import numpy as np
6
+ import pandas as pd
7
+ import pytest
8
+
9
+ from jarvisplot.Figure.preprocessor import DataPreprocessor
10
+ from jarvisplot.Figure.data_pipelines import DataContext, SharedContent
11
+ from jarvisplot.Figure.profile_runtime import _preprofiling, profiling
12
+ from jarvisplot.cache_store import ProjectCache
13
+
14
+
15
+ def config(objective="max"):
16
+ return {
17
+ "method": "bridson", "bin": 100, "objective": objective,
18
+ "grid_points": "none", "pregrid": {"bin": 10},
19
+ "coordinates": {
20
+ k: {"expr": k, "name": k, "lim": [0, 1]}
21
+ for k in ("x", "y")
22
+ } | {"z": {"expr": "z", "name": "z"}},
23
+ }
24
+
25
+
26
+ @pytest.mark.parametrize("objective,expected", [("max", [1.0, 0.8]), ("min", [0.0, 0.2])])
27
+ def test_preprofile_keeps_only_requested_extremum(objective, expected):
28
+ # Duplicate dataframe indices occur when scan files are concatenated.
29
+ df = pd.DataFrame({
30
+ "x": [0.11, 0.12, 0.41, 0.42, 0.8],
31
+ "y": [0.11, 0.12, 0.41, 0.42, 0.8],
32
+ "z": [0.0, 1.0, 0.2, 0.8, np.nan],
33
+ "row": ["low1", "high1", "low2", "high2", "invalid"],
34
+ }, index=[0, 1, 0, 1, 0])
35
+ out = _preprofiling(df, config(objective), None)
36
+ np.testing.assert_allclose(out.z, expected)
37
+ assert out.row.tolist() == (["high1", "high2"] if objective == "max" else ["low1", "low2"])
38
+
39
+
40
+ @pytest.mark.parametrize("objective", ["max", "min"])
41
+ def test_invalid_objective_does_not_disable_bridson_exclusion(objective):
42
+ # The inferior point lies in the annulus where z-aware rejection is needed.
43
+ z = [1.0, 0.0] if objective == "max" else [0.0, 1.0]
44
+ good = pd.DataFrame({"x": [0.1, 0.109], "y": [0.1, 0.1], "z": z})
45
+ dirty = pd.concat([good, pd.DataFrame({
46
+ "x": [0.8, 0.9, 0.7], "y": [0.8, 0.9, 0.7],
47
+ "z": [np.nan, np.inf, -np.inf],
48
+ })], ignore_index=True)
49
+ log = logging.getLogger(__name__)
50
+ expected = profiling(good, config(objective), log)
51
+ actual = profiling(dirty, config(objective), log)
52
+ assert len(actual) == 1
53
+ pd.testing.assert_frame_equal(actual, expected)
54
+
55
+
56
+ def test_constant_profile_still_thins_close_points():
57
+ df = pd.DataFrame({"x": [0.1, 0.101], "y": [0.1, 0.1], "z": [0.0, 0.0]})
58
+ with np.errstate(invalid="raise", divide="raise"):
59
+ out = profiling(df, config(), None)
60
+ assert len(out) == 1
61
+ assert out.z.iloc[0] == 0.0
62
+
63
+
64
+ @pytest.mark.parametrize("objective", ["max", "min"])
65
+ def test_suppressed_candidate_still_contributes_to_cell_extremum(objective):
66
+ # A suppresses B, but C lies outside A's exclusion radius. B belongs to
67
+ # C's nearest-seed cell and is better than C: thinning alone leaks C.
68
+ df = pd.DataFrame({
69
+ "x": [0.1, 0.1092, 0.1179], "y": [0.1, 0.1, 0.1],
70
+ "z": [1.0, 0.9, 0.0] if objective == "max" else [0.0, 0.1, 1.0],
71
+ "row": ["A", "B", "C"], "nuisance": [10.0, 20.0, 30.0],
72
+ })
73
+ actual = profiling(df, config(objective), None)
74
+ assert actual.row.tolist() == ["A", "B"]
75
+ pd.testing.assert_frame_equal(actual.reset_index(drop=True), df.iloc[:2].reset_index(drop=True))
76
+
77
+
78
+ def test_all_invalid_profile_returns_empty_named_columns():
79
+ df = pd.DataFrame({"x": [0.1], "y": [0.1], "z": [np.nan]})
80
+ cfg = config()
81
+ cfg["coordinates"]["z"]["name"] = "likelihood"
82
+ for reduce in (_preprofiling, profiling):
83
+ out = reduce(df, cfg, None)
84
+ assert out.empty
85
+ assert "likelihood" in out
86
+
87
+
88
+ def test_log_profile_ignores_nonpositive_coordinates_and_constant_z():
89
+ cfg = config()
90
+ cfg["grid_points"] = "rect"
91
+ cfg["bin"] = 4
92
+ for key in ("x", "y", "z"):
93
+ cfg["coordinates"][key]["scale"] = "log"
94
+ for key in ("x", "y"):
95
+ cfg["coordinates"][key]["lim"] = [1, 10]
96
+ df = pd.DataFrame({"x": [2.0, 0.0, -1.0], "y": [3.0, 3.0, 3.0], "z": [0.05, 0.05, 0.05]})
97
+ with np.errstate(invalid="raise", divide="raise"):
98
+ out = profiling(df, cfg, None)
99
+ actual = out[np.isfinite(out.z)]
100
+ assert actual.x.tolist() == [2.0]
101
+ assert actual.z.tolist() == [0.05]
102
+
103
+
104
+ def test_pregrid_controls_and_objective_change_cache_identity():
105
+ dp = DataPreprocessor(context=None)
106
+ cfg = config()
107
+ base = dp._preprofile_profile_cfg(cfg)
108
+ for change in ({"objective": "min"}, {"pregrid": False}, {"pregrid": {"bin": 20}}, {"pregrid_bin": 20}):
109
+ other = dp._preprofile_profile_cfg(cfg | change)
110
+ assert dp._pipeline_key("scan", [{"profile": base}], mode="preprofile") != dp._pipeline_key(
111
+ "scan", [{"profile": other}], mode="preprofile"
112
+ )
113
+ pre, _ = dp._split_prebuild_transform([{"profile": cfg | {"pregrid": False}}])
114
+ df = pd.DataFrame({"x": [0.11, 0.12], "y": [0.11, 0.12], "z": [0.0, 1.0]})
115
+ assert _preprofiling(df, pre[0]["profile"], None) is df
116
+
117
+
118
+ def test_old_named_profile_signature_is_invalidated(monkeypatch):
119
+ dp = DataPreprocessor(context=None)
120
+ layer = {"name": "support", "data": [{"source": "scan", "transform": [{"profile": config()}]}]}
121
+ current = dp._layer_signature(layer)
122
+ monkeypatch.setattr(dp, "_runtime_profile_signature", lambda tf: "old-algorithm")
123
+ assert dp._layer_signature(layer) != current
124
+
125
+
126
+ def test_prebuild_cached_max_and_min_do_not_reuse_each_others_rows(tmp_path):
127
+ df = pd.DataFrame({"x": [0.11, 0.12], "y": [0.11, 0.12], "z": [0.0, 1.0]})
128
+ for warm in (False, True):
129
+ ctx = DataContext(SharedContent())
130
+ ctx.register("scan", lambda _: df)
131
+ dp = DataPreprocessor(ctx, cache=ProjectCache(str(tmp_path)))
132
+ project = {"Figures": [{"name": "test", "layers": [
133
+ {"name": objective, "data": [{"source": "scan", "transform": [{"profile": config(objective)}]}]}
134
+ for objective in ("max", "min")
135
+ ]}]}
136
+ stats = dp.prebuild_profiles(project)
137
+ assert stats["hits"] == (2 if warm else 0)
138
+ entries = [layer["data"][0] for layer in project["Figures"][0]["layers"]]
139
+ assert entries[0]["source"] != entries[1]["source"]
140
+ assert ctx.get(entries[0]["source"]).z.tolist() == [1.0]
141
+ assert ctx.get(entries[1]["source"]).z.tolist() == [0.0]
File without changes