maidr 1.7.2__tar.gz → 1.8.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 (164) hide show
  1. {maidr-1.7.2 → maidr-1.8.0}/CHANGELOG.md +33 -0
  2. {maidr-1.7.2 → maidr-1.8.0}/PKG-INFO +1 -1
  3. {maidr-1.7.2 → maidr-1.8.0}/example/flask/test_flask_app.py +13 -6
  4. {maidr-1.7.2 → maidr-1.8.0}/maidr/__init__.py +1 -1
  5. maidr-1.8.0/maidr/api.py +155 -0
  6. {maidr-1.7.2 → maidr-1.8.0}/maidr/core/figure_manager.py +53 -8
  7. {maidr-1.7.2 → maidr-1.8.0}/maidr/core/maidr.py +47 -6
  8. {maidr-1.7.2 → maidr-1.8.0}/maidr/core/plot/candlestick.py +17 -4
  9. {maidr-1.7.2 → maidr-1.8.0}/maidr/core/plot/lineplot.py +3 -1
  10. {maidr-1.7.2 → maidr-1.8.0}/maidr/core/plot/maidr_plot.py +1 -0
  11. {maidr-1.7.2 → maidr-1.8.0}/maidr/core/plot/mplfinance_lineplot.py +4 -1
  12. maidr-1.8.0/maidr/patch/barplot.py +218 -0
  13. {maidr-1.7.2 → maidr-1.8.0}/maidr/patch/mplfinance.py +2 -1
  14. {maidr-1.7.2 → maidr-1.8.0}/maidr/util/datetime_conversion.py +35 -20
  15. {maidr-1.7.2 → maidr-1.8.0}/maidr/util/environment.py +5 -9
  16. {maidr-1.7.2 → maidr-1.8.0}/maidr/util/mixin/extractor_mixin.py +3 -1
  17. {maidr-1.7.2 → maidr-1.8.0}/maidr/util/mplfinance_utils.py +8 -8
  18. {maidr-1.7.2 → maidr-1.8.0}/pyproject.toml +3 -2
  19. {maidr-1.7.2 → maidr-1.8.0}/uv.lock +1425 -1082
  20. maidr-1.7.2/example/stacked/matplotlib/example_mpl_stacked.html +0 -9
  21. maidr-1.7.2/example/stacked/seaborn/example_sns_stacked.html +0 -479
  22. maidr-1.7.2/maidr/api.py +0 -64
  23. maidr-1.7.2/maidr/patch/barplot.py +0 -77
  24. {maidr-1.7.2 → maidr-1.8.0}/.commitlintrc.cjs +0 -0
  25. {maidr-1.7.2 → maidr-1.8.0}/.editorconfig +0 -0
  26. {maidr-1.7.2 → maidr-1.8.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  27. {maidr-1.7.2 → maidr-1.8.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  28. {maidr-1.7.2 → maidr-1.8.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  29. {maidr-1.7.2 → maidr-1.8.0}/.github/copilot-instructions.md +0 -0
  30. {maidr-1.7.2 → maidr-1.8.0}/.github/workflows/ci.yml +0 -0
  31. {maidr-1.7.2 → maidr-1.8.0}/.github/workflows/docs.yml +0 -0
  32. {maidr-1.7.2 → maidr-1.8.0}/.github/workflows/release.yml +0 -0
  33. {maidr-1.7.2 → maidr-1.8.0}/.gitignore +0 -0
  34. {maidr-1.7.2 → maidr-1.8.0}/.pre-commit-config.yaml +0 -0
  35. {maidr-1.7.2 → maidr-1.8.0}/.vscode/extensions.json +0 -0
  36. {maidr-1.7.2 → maidr-1.8.0}/.vscode/settings.json +0 -0
  37. {maidr-1.7.2 → maidr-1.8.0}/CONDUCT.md +0 -0
  38. {maidr-1.7.2 → maidr-1.8.0}/CONTRIBUTING.md +0 -0
  39. {maidr-1.7.2 → maidr-1.8.0}/LICENSE +0 -0
  40. {maidr-1.7.2 → maidr-1.8.0}/README.md +0 -0
  41. {maidr-1.7.2 → maidr-1.8.0}/docs/.gitignore +0 -0
  42. {maidr-1.7.2 → maidr-1.8.0}/docs/CNAME +0 -0
  43. {maidr-1.7.2 → maidr-1.8.0}/docs/_environment +0 -0
  44. {maidr-1.7.2 → maidr-1.8.0}/docs/_extensions/machow/interlinks/.gitignore +0 -0
  45. {maidr-1.7.2 → maidr-1.8.0}/docs/_extensions/machow/interlinks/_extension.yml +0 -0
  46. {maidr-1.7.2 → maidr-1.8.0}/docs/_extensions/machow/interlinks/interlinks.lua +0 -0
  47. {maidr-1.7.2 → maidr-1.8.0}/docs/_extensions/shafayetShafee/line-highlight/_extension.yml +0 -0
  48. {maidr-1.7.2 → maidr-1.8.0}/docs/_extensions/shafayetShafee/line-highlight/line-highlight.lua +0 -0
  49. {maidr-1.7.2 → maidr-1.8.0}/docs/_extensions/shafayetShafee/line-highlight/resources/css/line-highlight.css +0 -0
  50. {maidr-1.7.2 → maidr-1.8.0}/docs/_extensions/shafayetShafee/line-highlight/resources/js/line-highlight.js +0 -0
  51. {maidr-1.7.2 → maidr-1.8.0}/docs/_quarto.yml +0 -0
  52. {maidr-1.7.2 → maidr-1.8.0}/docs/examples.qmd +0 -0
  53. {maidr-1.7.2 → maidr-1.8.0}/docs/index.qmd +0 -0
  54. {maidr-1.7.2 → maidr-1.8.0}/docs/styles.css +0 -0
  55. {maidr-1.7.2 → maidr-1.8.0}/example/bar/example_bar_plot.ipynb +0 -0
  56. {maidr-1.7.2 → maidr-1.8.0}/example/bar/matplotlib/example_mpl_bar_plot.py +0 -0
  57. {maidr-1.7.2 → maidr-1.8.0}/example/bar/seaborn/example_sns_bar_plot.py +0 -0
  58. {maidr-1.7.2 → maidr-1.8.0}/example/box/example_box_plot.ipynb +0 -0
  59. {maidr-1.7.2 → maidr-1.8.0}/example/box/matplotlib/example_mpl_box.py +0 -0
  60. {maidr-1.7.2 → maidr-1.8.0}/example/box/seaborn/example_sns_box.py +0 -0
  61. {maidr-1.7.2 → maidr-1.8.0}/example/candle_stick/legacy_candlestick_example.py +0 -0
  62. {maidr-1.7.2 → maidr-1.8.0}/example/candle_stick/mplfinance_candlestick_example.py +0 -0
  63. {maidr-1.7.2 → maidr-1.8.0}/example/candle_stick/test_data_daily_current_year.csv +0 -0
  64. {maidr-1.7.2 → maidr-1.8.0}/example/candle_stick/test_data_daily_mixed_years.csv +0 -0
  65. {maidr-1.7.2 → maidr-1.8.0}/example/candle_stick/test_data_hourly.csv +0 -0
  66. {maidr-1.7.2 → maidr-1.8.0}/example/candle_stick/test_data_minute.csv +0 -0
  67. {maidr-1.7.2 → maidr-1.8.0}/example/candle_stick/test_data_weekly.csv +0 -0
  68. {maidr-1.7.2 → maidr-1.8.0}/example/candle_stick/volcandat.csv +0 -0
  69. {maidr-1.7.2 → maidr-1.8.0}/example/count/example_count_plot.ipynb +0 -0
  70. {maidr-1.7.2 → maidr-1.8.0}/example/count/seaborn/example_sns_count_plot.py +0 -0
  71. {maidr-1.7.2 → maidr-1.8.0}/example/dodged/matplotlib/example_mpl_dodged.py +0 -0
  72. {maidr-1.7.2 → maidr-1.8.0}/example/dodged/seaborn/example_sns_dodged.py +0 -0
  73. {maidr-1.7.2 → maidr-1.8.0}/example/facet-subplots/matplotlib/example_mpl_facet_bar_plot.py +0 -0
  74. {maidr-1.7.2 → maidr-1.8.0}/example/facet-subplots/matplotlib/example_mpl_facet_combined_plot.py +0 -0
  75. {maidr-1.7.2 → maidr-1.8.0}/example/facet-subplots/seaborn/example_sns_facet_bar_plot.py +0 -0
  76. {maidr-1.7.2 → maidr-1.8.0}/example/facet-subplots/seaborn/example_sns_facet_combined_plot.py +0 -0
  77. {maidr-1.7.2 → maidr-1.8.0}/example/heatmap/example_heatmap_plot.ipynb +0 -0
  78. {maidr-1.7.2 → maidr-1.8.0}/example/heatmap/matplotlib/example_mpl_heatmap.py +0 -0
  79. {maidr-1.7.2 → maidr-1.8.0}/example/heatmap/seaborn/example_sns_heatmap.py +0 -0
  80. {maidr-1.7.2 → maidr-1.8.0}/example/histogram/example_histogram_plot.ipynb +0 -0
  81. {maidr-1.7.2 → maidr-1.8.0}/example/histogram/matplotlib/example_mpl_hist.py +0 -0
  82. {maidr-1.7.2 → maidr-1.8.0}/example/histogram/matplotlib/histogram_with_kde_matplotlib.py +0 -0
  83. {maidr-1.7.2 → maidr-1.8.0}/example/histogram/seaborn/example_sns_hist.py +0 -0
  84. {maidr-1.7.2 → maidr-1.8.0}/example/histogram/seaborn/histogram_with_kde_seaborn.py +0 -0
  85. {maidr-1.7.2 → maidr-1.8.0}/example/kde/example_kde_plots.ipynb +0 -0
  86. {maidr-1.7.2 → maidr-1.8.0}/example/kde/matplotlib/multiple_kde_matplotlib.py +0 -0
  87. {maidr-1.7.2 → maidr-1.8.0}/example/kde/matplotlib/single_kde_matplotlib.py +0 -0
  88. {maidr-1.7.2 → maidr-1.8.0}/example/kde/seaborn/multiple_kde_seaborn.py +0 -0
  89. {maidr-1.7.2 → maidr-1.8.0}/example/kde/seaborn/single_kde_seaborn.py +0 -0
  90. {maidr-1.7.2 → maidr-1.8.0}/example/line/example_line_plot.ipynb +0 -0
  91. {maidr-1.7.2 → maidr-1.8.0}/example/line/matplotlib/example_mpl_line.py +0 -0
  92. {maidr-1.7.2 → maidr-1.8.0}/example/line/seaborn/example_sns_line.py +0 -0
  93. {maidr-1.7.2 → maidr-1.8.0}/example/multilayer/example_mpl_multilayer.py +0 -0
  94. {maidr-1.7.2 → maidr-1.8.0}/example/multilayer/example_multilayer_plot.ipynb +0 -0
  95. {maidr-1.7.2 → maidr-1.8.0}/example/multiline/example_multiline_plot.ipynb +0 -0
  96. {maidr-1.7.2 → maidr-1.8.0}/example/multiline/matplotlib/example_mpl_multiline.py +0 -0
  97. {maidr-1.7.2 → maidr-1.8.0}/example/multiline/seaborn/example_sns_multiline.py +0 -0
  98. {maidr-1.7.2 → maidr-1.8.0}/example/multipanel/example_multipanel_plot.ipynb +0 -0
  99. {maidr-1.7.2 → maidr-1.8.0}/example/multipanel/matplotlib/example_mpl_multipanel.py +0 -0
  100. {maidr-1.7.2 → maidr-1.8.0}/example/multipanel/seaborn/example_sns_multipanel.py +0 -0
  101. {maidr-1.7.2 → maidr-1.8.0}/example/quarto/demo.qmd +0 -0
  102. {maidr-1.7.2 → maidr-1.8.0}/example/reg/example_reg_plots.ipynb +0 -0
  103. {maidr-1.7.2 → maidr-1.8.0}/example/reg/matplotlib/example_matplotlib_smooth_plot.py +0 -0
  104. {maidr-1.7.2 → maidr-1.8.0}/example/reg/seaborn/example_sns_reg.py +0 -0
  105. {maidr-1.7.2 → maidr-1.8.0}/example/scatter/example_scatter_plot.ipynb +0 -0
  106. {maidr-1.7.2 → maidr-1.8.0}/example/scatter/matplotlib/example_mpl_scatter.py +0 -0
  107. {maidr-1.7.2 → maidr-1.8.0}/example/scatter/seaborn/example_sns_scatter.py +0 -0
  108. {maidr-1.7.2 → maidr-1.8.0}/example/shiny/example_shiny_scatter.py +0 -0
  109. {maidr-1.7.2 → maidr-1.8.0}/example/stacked/matplotlib/example_mpl_stacked.py +0 -0
  110. {maidr-1.7.2 → maidr-1.8.0}/example/stacked/seaborn/example_sns_stacked.py +0 -0
  111. {maidr-1.7.2 → maidr-1.8.0}/example/streamlit/example_streamlit_app.py +0 -0
  112. {maidr-1.7.2 → maidr-1.8.0}/maidr/core/__init__.py +0 -0
  113. {maidr-1.7.2 → maidr-1.8.0}/maidr/core/context_manager.py +0 -0
  114. {maidr-1.7.2 → maidr-1.8.0}/maidr/core/enum/__init__.py +0 -0
  115. {maidr-1.7.2 → maidr-1.8.0}/maidr/core/enum/library.py +0 -0
  116. {maidr-1.7.2 → maidr-1.8.0}/maidr/core/enum/maidr_key.py +0 -0
  117. {maidr-1.7.2 → maidr-1.8.0}/maidr/core/enum/plot_type.py +0 -0
  118. {maidr-1.7.2 → maidr-1.8.0}/maidr/core/enum/smooth_keywords.py +0 -0
  119. {maidr-1.7.2 → maidr-1.8.0}/maidr/core/plot/__init__.py +0 -0
  120. {maidr-1.7.2 → maidr-1.8.0}/maidr/core/plot/barplot.py +0 -0
  121. {maidr-1.7.2 → maidr-1.8.0}/maidr/core/plot/boxplot.py +0 -0
  122. {maidr-1.7.2 → maidr-1.8.0}/maidr/core/plot/grouped_barplot.py +0 -0
  123. {maidr-1.7.2 → maidr-1.8.0}/maidr/core/plot/heatmap.py +0 -0
  124. {maidr-1.7.2 → maidr-1.8.0}/maidr/core/plot/histogram.py +0 -0
  125. {maidr-1.7.2 → maidr-1.8.0}/maidr/core/plot/maidr_plot_factory.py +0 -0
  126. {maidr-1.7.2 → maidr-1.8.0}/maidr/core/plot/mplfinance_barplot.py +0 -0
  127. {maidr-1.7.2 → maidr-1.8.0}/maidr/core/plot/regplot.py +0 -0
  128. {maidr-1.7.2 → maidr-1.8.0}/maidr/core/plot/scatterplot.py +0 -0
  129. {maidr-1.7.2 → maidr-1.8.0}/maidr/exception/__init__.py +0 -0
  130. {maidr-1.7.2 → maidr-1.8.0}/maidr/exception/extraction_error.py +0 -0
  131. {maidr-1.7.2 → maidr-1.8.0}/maidr/patch/__init__.py +0 -0
  132. {maidr-1.7.2 → maidr-1.8.0}/maidr/patch/boxplot.py +0 -0
  133. {maidr-1.7.2 → maidr-1.8.0}/maidr/patch/candlestick.py +0 -0
  134. {maidr-1.7.2 → maidr-1.8.0}/maidr/patch/clear.py +0 -0
  135. {maidr-1.7.2 → maidr-1.8.0}/maidr/patch/common.py +0 -0
  136. {maidr-1.7.2 → maidr-1.8.0}/maidr/patch/heatmap.py +0 -0
  137. {maidr-1.7.2 → maidr-1.8.0}/maidr/patch/highlight.py +0 -0
  138. {maidr-1.7.2 → maidr-1.8.0}/maidr/patch/histogram.py +0 -0
  139. {maidr-1.7.2 → maidr-1.8.0}/maidr/patch/kdeplot.py +0 -0
  140. {maidr-1.7.2 → maidr-1.8.0}/maidr/patch/lineplot.py +0 -0
  141. {maidr-1.7.2 → maidr-1.8.0}/maidr/patch/regplot.py +0 -0
  142. {maidr-1.7.2 → maidr-1.8.0}/maidr/patch/scatterplot.py +0 -0
  143. {maidr-1.7.2 → maidr-1.8.0}/maidr/util/__init__.py +0 -0
  144. {maidr-1.7.2 → maidr-1.8.0}/maidr/util/dedup_utils.py +0 -0
  145. {maidr-1.7.2 → maidr-1.8.0}/maidr/util/mixin/__init__.py +0 -0
  146. {maidr-1.7.2 → maidr-1.8.0}/maidr/util/mixin/merger_mixin.py +0 -0
  147. {maidr-1.7.2 → maidr-1.8.0}/maidr/util/plot_detection.py +0 -0
  148. {maidr-1.7.2 → maidr-1.8.0}/maidr/util/regression_line_utils.py +0 -0
  149. {maidr-1.7.2 → maidr-1.8.0}/maidr/util/svg_utils.py +0 -0
  150. {maidr-1.7.2 → maidr-1.8.0}/maidr/widget/__init__.py +0 -0
  151. {maidr-1.7.2 → maidr-1.8.0}/maidr/widget/shiny.py +0 -0
  152. {maidr-1.7.2 → maidr-1.8.0}/tests/__init__.py +0 -0
  153. {maidr-1.7.2 → maidr-1.8.0}/tests/conftest.py +0 -0
  154. {maidr-1.7.2 → maidr-1.8.0}/tests/core/__init__.py +0 -0
  155. {maidr-1.7.2 → maidr-1.8.0}/tests/core/enum/__init__.py +0 -0
  156. {maidr-1.7.2 → maidr-1.8.0}/tests/core/plot/__init__.py +0 -0
  157. {maidr-1.7.2 → maidr-1.8.0}/tests/core/test_figure_manager.py +0 -0
  158. {maidr-1.7.2 → maidr-1.8.0}/tests/core/test_maidr_plot.py +0 -0
  159. {maidr-1.7.2 → maidr-1.8.0}/tests/core/test_maidr_plot_factory.py +0 -0
  160. {maidr-1.7.2 → maidr-1.8.0}/tests/fixture/__init__.py +0 -0
  161. {maidr-1.7.2 → maidr-1.8.0}/tests/fixture/library_factory.py +0 -0
  162. {maidr-1.7.2 → maidr-1.8.0}/tests/fixture/matplotlib_factory.py +0 -0
  163. {maidr-1.7.2 → maidr-1.8.0}/tests/fixture/seaborn_factory.py +0 -0
  164. {maidr-1.7.2 → maidr-1.8.0}/tox.ini +0 -0
@@ -1,6 +1,39 @@
1
1
  # CHANGELOG
2
2
 
3
3
 
4
+ ## v1.8.0 (2025-09-17)
5
+
6
+ ### Features
7
+
8
+ - Remove maidr.show params ([#244](https://github.com/xability/py-maidr/pull/244),
9
+ [`493cf57`](https://github.com/xability/py-maidr/commit/493cf5713068b1514b4836e46763c3619a51dd23))
10
+
11
+ ### Refactoring
12
+
13
+ - **maidr.api**: Improve lazy figure detection, eliminate code duplication, and resolve merge
14
+ conflicts ([#241](https://github.com/xability/py-maidr/pull/241),
15
+ [`15a966e`](https://github.com/xability/py-maidr/commit/15a966ea2ca9170ddf8bc28634705fb6233a1d58))
16
+
17
+ Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
18
+
19
+ Co-authored-by: jooyoungseo <19754711+jooyoungseo@users.noreply.github.com>
20
+
21
+
22
+ ## v1.7.3 (2025-09-15)
23
+
24
+ ### Bug Fixes
25
+
26
+ - Ensure all subplots are accessible and improve dodged plot detection
27
+ ([#242](https://github.com/xability/py-maidr/pull/242),
28
+ [`979b971`](https://github.com/xability/py-maidr/commit/979b9713d07be8bc9046056e7f1c0519336ddd22))
29
+
30
+ Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
31
+
32
+ Co-authored-by: jooyoungseo <19754711+jooyoungseo@users.noreply.github.com>
33
+
34
+ Co-authored-by: JooYoung Seo <jseo1005@illinois.edu>
35
+
36
+
4
37
  ## v1.7.2 (2025-09-12)
5
38
 
6
39
  ### Bug Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: maidr
3
- Version: 1.7.2
3
+ Version: 1.8.0
4
4
  Summary: Multimodal Access and Interactive Data Representations
5
5
  Project-URL: Homepage, https://xability.github.io/py-maidr
6
6
  Project-URL: Repository, https://github.com/xability/py-maidr
@@ -1,5 +1,6 @@
1
1
  import matplotlib
2
- matplotlib.use('Agg') # Use non-interactive backend for Flask
2
+
3
+ matplotlib.use("Agg") # Use non-interactive backend for Flask
3
4
 
4
5
  from flask import Flask
5
6
  import matplotlib.pyplot as plt
@@ -14,7 +15,8 @@ logger = logging.getLogger(__name__)
14
15
 
15
16
  app = Flask(__name__)
16
17
 
17
- @app.route('/')
18
+
19
+ @app.route("/")
18
20
  def index():
19
21
  # Log environment detection
20
22
  logger.info("=== Environment Detection ===")
@@ -34,8 +36,12 @@ def index():
34
36
  logger.info(f"flask_detected: {flask_detected}")
35
37
  logger.info(f"notebook_detected: {notebook_detected}")
36
38
  logger.info(f"shiny_detected: {shiny_detected}")
37
- logger.info("Condition: use_iframe and (flask_detected or notebook_detected or shiny_detected)")
38
- logger.info(f"Result: {use_iframe and (flask_detected or notebook_detected or shiny_detected)}")
39
+ logger.info(
40
+ "Condition: use_iframe and (flask_detected or notebook_detected or shiny_detected)"
41
+ )
42
+ logger.info(
43
+ f"Result: {use_iframe and (flask_detected or notebook_detected or shiny_detected)}"
44
+ )
39
45
 
40
46
  # Load the penguins dataset
41
47
  penguins = sns.load_dataset("penguins")
@@ -65,7 +71,8 @@ def index():
65
71
  # Return the maidr HTML directly
66
72
  return str(maidr_html)
67
73
 
68
- if __name__ == '__main__':
74
+
75
+ if __name__ == "__main__":
69
76
  logger.info("Starting Flask app with logging...")
70
77
  logger.info("Visit http://localhost:5002 to see the maidr plot in Flask")
71
- app.run(debug=False, host='0.0.0.0', port=5002)
78
+ app.run(debug=False, host="0.0.0.0", port=5002)
@@ -1,4 +1,4 @@
1
- __version__ = "1.7.2"
1
+ __version__ = "1.8.0"
2
2
 
3
3
  from .api import close, render, save_html, show, stacked
4
4
  from .core import Maidr
@@ -0,0 +1,155 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Literal
4
+
5
+ from htmltools import Tag
6
+ from matplotlib.axes import Axes
7
+ from matplotlib.container import BarContainer
8
+
9
+ from maidr.core import Maidr
10
+ from maidr.core.enum import PlotType
11
+ from maidr.core.figure_manager import FigureManager
12
+
13
+
14
+ def _get_plot_or_current(plot: Any | None) -> Any:
15
+ """
16
+ Get the plot object or current matplotlib figure if plot is None.
17
+
18
+ Parameters
19
+ ----------
20
+ plot : Any or None
21
+ The plot object. If None, returns the current matplotlib figure.
22
+
23
+ Returns
24
+ -------
25
+ Any
26
+ The plot object or current matplotlib figure.
27
+ """
28
+ if plot is None:
29
+ # Lazy import matplotlib.pyplot when needed
30
+ import matplotlib.pyplot as plt
31
+
32
+ return plt.gcf()
33
+ return plot
34
+
35
+
36
+ def render(plot: Any | None = None) -> Tag:
37
+ """
38
+ Render a MAIDR plot to HTML.
39
+
40
+ Parameters
41
+ ----------
42
+ plot : Any or None, optional
43
+ The plot object to render. If None, uses the current matplotlib figure.
44
+
45
+ Returns
46
+ -------
47
+ htmltools.Tag
48
+ The rendered HTML representation of the plot.
49
+ """
50
+ plot = _get_plot_or_current(plot)
51
+
52
+ ax = FigureManager.get_axes(plot)
53
+ if isinstance(ax, list):
54
+ for axes in ax:
55
+ maidr = FigureManager.get_maidr(axes.get_figure())
56
+ return maidr.render()
57
+ else:
58
+ maidr = FigureManager.get_maidr(ax.get_figure())
59
+ return maidr.render()
60
+
61
+
62
+ def show(
63
+ plot: Any | None = None,
64
+ renderer: Literal["auto", "ipython", "browser"] = "auto",
65
+ clear_fig: bool = True,
66
+ ) -> object:
67
+ """
68
+ Display a MAIDR plot.
69
+
70
+ Parameters
71
+ ----------
72
+ plot : Any or None, optional
73
+ The plot object to display. If None, uses the current matplotlib figure.
74
+ renderer : {"auto", "ipython", "browser"}, default "auto"
75
+ The renderer to use for display.
76
+ clear_fig : bool, default True
77
+ Whether to clear the figure after displaying.
78
+
79
+ Returns
80
+ -------
81
+ object
82
+ The display result.
83
+ """
84
+ plot = _get_plot_or_current(plot)
85
+
86
+ ax = FigureManager.get_axes(plot)
87
+ if isinstance(ax, list):
88
+ for axes in ax:
89
+ maidr = FigureManager.get_maidr(axes.get_figure())
90
+ return maidr.show(renderer)
91
+ else:
92
+ maidr = FigureManager.get_maidr(ax.get_figure())
93
+ return maidr.show(renderer, clear_fig=clear_fig)
94
+
95
+
96
+ def save_html(
97
+ plot: Any | None = None,
98
+ *,
99
+ file: str,
100
+ lib_dir: str | None = "lib",
101
+ include_version: bool = True
102
+ ) -> str:
103
+ """
104
+ Save a MAIDR plot as HTML file.
105
+
106
+ Parameters
107
+ ----------
108
+ plot : Any or None, optional
109
+ The plot object to save. If None, uses the current matplotlib figure.
110
+ file : str
111
+ The file path where to save the HTML.
112
+ lib_dir : str or None, default "lib"
113
+ Directory name for libraries.
114
+ include_version : bool, default True
115
+ Whether to include version information.
116
+
117
+ Returns
118
+ -------
119
+ str
120
+ The path to the saved HTML file.
121
+ """
122
+ plot = _get_plot_or_current(plot)
123
+
124
+ ax = FigureManager.get_axes(plot)
125
+ htmls = []
126
+ if isinstance(ax, list):
127
+ for axes in ax:
128
+ maidr = FigureManager.get_maidr(axes.get_figure())
129
+ htmls.append(maidr._create_html_doc(use_iframe=False))
130
+ return htmls[-1].save_html(
131
+ file, libdir=lib_dir, include_version=include_version
132
+ )
133
+ else:
134
+ maidr = FigureManager.get_maidr(ax.get_figure())
135
+ return maidr.save_html(file, lib_dir=lib_dir, include_version=include_version)
136
+
137
+
138
+ def stacked(plot: Axes | BarContainer) -> Maidr:
139
+ ax = FigureManager.get_axes(plot)
140
+ return FigureManager.create_maidr(ax, PlotType.STACKED)
141
+
142
+
143
+ def close(plot: Any | None = None) -> None:
144
+ """
145
+ Close a MAIDR plot and clean up resources.
146
+
147
+ Parameters
148
+ ----------
149
+ plot : Any or None, optional
150
+ The plot object to close. If None, uses the current matplotlib figure.
151
+ """
152
+ plot = _get_plot_or_current(plot)
153
+
154
+ ax = FigureManager.get_axes(plot)
155
+ FigureManager.destroy(ax.get_figure())
@@ -15,20 +15,24 @@ from maidr.core.plot import MaidrPlotFactory
15
15
 
16
16
  class FigureManager:
17
17
  """
18
- Manages creation and retrieval of Maidr instances associated with matplotlib figures.
18
+ Manages creation and retrieval of Maidr instances associated with figures.
19
19
 
20
- This class provides methods to manage Maidr objects which facilitate the organization and
21
- manipulation of plots within matplotlib figures.
20
+ This class provides methods to manage Maidr objects which facilitate the
21
+ organization and manipulation of plots within matplotlib figures.
22
22
 
23
23
  Attributes
24
24
  ----------
25
25
  figs : dict
26
- A dictionary that maps matplotlib Figure objects to their corresponding Maidr instances.
26
+ A dictionary that maps matplotlib Figure objects to their corresponding
27
+ Maidr instances.
28
+ PLOT_TYPE_PRIORITY : dict
29
+ Defines the priority order for plot types. Higher numbers take precedence.
27
30
 
28
31
  Methods
29
32
  -------
30
33
  create_maidr(ax, plot_type, **kwargs)
31
- Creates a Maidr instance for the given Axes and plot type, and adds a plot to it.
34
+ Creates a Maidr instance for the given Axes and plot type, and adds a
35
+ plot to it.
32
36
  _get_maidr(fig)
33
37
  Retrieves or creates a Maidr instance associated with the given Figure.
34
38
  get_axes(artist)
@@ -37,6 +41,21 @@ class FigureManager:
37
41
 
38
42
  figs = {}
39
43
 
44
+ # Define plot type priority order (higher numbers take precedence)
45
+ PLOT_TYPE_PRIORITY = {
46
+ PlotType.BAR: 1,
47
+ PlotType.STACKED: 2,
48
+ PlotType.DODGED: 2, # DODGED and STACKED have same priority
49
+ PlotType.LINE: 1,
50
+ PlotType.SCATTER: 1,
51
+ PlotType.HIST: 1,
52
+ PlotType.BOX: 1,
53
+ PlotType.HEAT: 1,
54
+ PlotType.COUNT: 1,
55
+ PlotType.SMOOTH: 1,
56
+ PlotType.CANDLESTICK: 1,
57
+ }
58
+
40
59
  _instance = None
41
60
  _lock = threading.Lock()
42
61
 
@@ -44,14 +63,15 @@ class FigureManager:
44
63
  if not cls._instance:
45
64
  with cls._lock:
46
65
  if not cls._instance:
47
- cls._instance = super(FigureManager, cls).__new__()
66
+ cls._instance = super(FigureManager, cls).__new__(cls)
48
67
  return cls._instance
49
68
 
50
69
  @classmethod
51
70
  def create_maidr(
52
71
  cls, axes: Axes | list[Axes], plot_type: PlotType, **kwargs
53
72
  ) -> Maidr:
54
- """Create a Maidr instance for the given Axes and plot type, and adds a plot to it."""
73
+ """Create a Maidr instance for the given Axes and plot type, and
74
+ adds a plot to it."""
55
75
  if axes is None:
56
76
  raise ValueError("No plot found.")
57
77
  if plot_type is None:
@@ -72,9 +92,34 @@ class FigureManager:
72
92
 
73
93
  @classmethod
74
94
  def _get_maidr(cls, fig: Figure, plot_type: PlotType) -> Maidr:
75
- """Retrieve or create a Maidr instance for the given Figure."""
95
+ """
96
+ Retrieve or create a Maidr instance for the given Figure.
97
+
98
+ If a Maidr instance already exists for the figure, update its plot type
99
+ if the new plot type has higher priority (DODGED/STACKED > BAR).
100
+
101
+ Parameters
102
+ ----------
103
+ fig : Figure
104
+ The matplotlib figure to get or create a Maidr instance for.
105
+ plot_type : PlotType
106
+ The plot type to set or update for the Maidr instance.
107
+
108
+ Returns
109
+ -------
110
+ Maidr
111
+ The Maidr instance associated with the figure.
112
+ """
76
113
  if fig not in cls.figs.keys():
77
114
  cls.figs[fig] = Maidr(fig, plot_type)
115
+ else:
116
+ # Update plot type if the new type has higher priority
117
+ maidr = cls.figs[fig]
118
+ current_priority = cls.PLOT_TYPE_PRIORITY.get(maidr.plot_type, 0)
119
+ new_priority = cls.PLOT_TYPE_PRIORITY.get(plot_type, 0)
120
+
121
+ if new_priority > current_priority:
122
+ maidr.plot_type = plot_type
78
123
  return cls.figs[fig]
79
124
 
80
125
  @classmethod
@@ -10,6 +10,7 @@ import webbrowser
10
10
  import subprocess
11
11
  from pathlib import Path
12
12
  from typing import Any, Literal, cast
13
+ from collections import defaultdict
13
14
 
14
15
  import matplotlib.pyplot as plt
15
16
  from htmltools import HTML, HTMLDocument, Tag, tags
@@ -159,10 +160,7 @@ class Maidr:
159
160
  if explorer_path:
160
161
  try:
161
162
  result = subprocess.run(
162
- [explorer_path, url],
163
- capture_output=True,
164
- text=True,
165
- timeout=10
163
+ [explorer_path, url], capture_output=True, text=True, timeout=10
166
164
  )
167
165
 
168
166
  if result.returncode == 0:
@@ -204,10 +202,49 @@ class Maidr:
204
202
  """Create an HTML document from Tag objects."""
205
203
  return HTMLDocument(self._create_html_tag(use_iframe), lang="en")
206
204
 
205
+ def _merge_plots_by_subplot_position(self) -> list[MaidrPlot]:
206
+ """
207
+ Merge plots by their subplot position, keeping only the first plot per position.
208
+
209
+ For DODGED and STACKED plot types, multiple plots on the same subplot
210
+ should be merged into a single plot since GroupedBarPlot extracts all
211
+ containers from the axes itself.
212
+
213
+ Returns
214
+ -------
215
+ list[MaidrPlot]
216
+ List of plots with one plot per unique subplot position.
217
+
218
+ Examples
219
+ --------
220
+ If we have plots at positions [(0,0), (0,0), (0,1), (1,0)],
221
+ this will return plots at positions [(0,0), (0,1), (1,0)].
222
+ """
223
+ # Group plots by their subplot position (row, col) using defaultdict
224
+ subplot_groups: dict[tuple[int, int], list[MaidrPlot]] = defaultdict(list)
225
+
226
+ for plot in self._plots:
227
+ # Get subplot position, defaulting to (0, 0) if not set
228
+ position = (getattr(plot, "row_index", 0), getattr(plot, "col_index", 0))
229
+ subplot_groups[position].append(plot)
230
+
231
+ # Keep only the first plot for each subplot position
232
+ # The GroupedBarPlot will extract all containers from that axes
233
+ merged_plots: list[MaidrPlot] = []
234
+ for position_plots in subplot_groups.values():
235
+ merged_plots.append(
236
+ position_plots[0]
237
+ ) # Each list is guaranteed to have at least one plot
238
+
239
+ return merged_plots
240
+
207
241
  def _flatten_maidr(self) -> dict | list[dict]:
208
242
  """Return a single plot schema or a list of schemas from the Maidr instance."""
243
+ # Handle DODGED/STACKED plots: only keep one plot per subplot position
244
+ # because GroupedBarPlot extracts all containers from the axes itself
209
245
  if self.plot_type in (PlotType.DODGED, PlotType.STACKED):
210
- self._plots = [self._plots[0]]
246
+ self._plots = self._merge_plots_by_subplot_position()
247
+
211
248
  # Deduplicate: if any SMOOTH plots exist, remove LINE plots
212
249
  self._plots = deduplicate_smooth_and_line(self._plots)
213
250
 
@@ -354,7 +391,11 @@ class Maidr:
354
391
  # Render the plot inside an iframe if in a Jupyter notebook, Google Colab
355
392
  # or VSCode notebook. No need for iframe if this is a Quarto document.
356
393
  # For TypeScript we will use iframe by default for now
357
- if use_iframe and (Environment.is_flask() or Environment.is_notebook() or Environment.is_shiny()):
394
+ if use_iframe and (
395
+ Environment.is_flask()
396
+ or Environment.is_notebook()
397
+ or Environment.is_shiny()
398
+ ):
358
399
  unique_id = "iframe_" + Maidr._unique_id()
359
400
 
360
401
  def generate_iframe_script(unique_id: str) -> str:
@@ -43,7 +43,9 @@ class CandlestickPlot(MaidrPlot):
43
43
  self._maidr_wick_collection = kwargs.get("_maidr_wick_collection", None)
44
44
  self._maidr_body_collection = kwargs.get("_maidr_body_collection", None)
45
45
  self._maidr_date_nums = kwargs.get("_maidr_date_nums", None)
46
- self._maidr_original_data = kwargs.get("_maidr_original_data", None) # Store original data
46
+ self._maidr_original_data = kwargs.get(
47
+ "_maidr_original_data", None
48
+ ) # Store original data
47
49
  self._maidr_datetime_converter = kwargs.get("_maidr_datetime_converter", None)
48
50
 
49
51
  # Store the GID for proper selector generation (legacy/shared)
@@ -122,7 +124,9 @@ class CandlestickPlot(MaidrPlot):
122
124
  for rect in body_rectangles:
123
125
  rect.set_gid(self._maidr_gid)
124
126
  # Keep a dedicated body gid for legacy dict selectors
125
- self._maidr_body_gid = getattr(self, "_maidr_body_gid", None) or self._maidr_gid
127
+ self._maidr_body_gid = (
128
+ getattr(self, "_maidr_body_gid", None) or self._maidr_gid
129
+ )
126
130
 
127
131
  # Assign a shared gid to wick Line2D (vertical 2-point lines) on the same axis
128
132
  wick_lines = []
@@ -132,7 +136,11 @@ class CandlestickPlot(MaidrPlot):
132
136
  if xydata is None:
133
137
  continue
134
138
  xy_arr = np.asarray(xydata)
135
- if xy_arr.ndim == 2 and xy_arr.shape[0] == 2 and xy_arr.shape[1] >= 2:
139
+ if (
140
+ xy_arr.ndim == 2
141
+ and xy_arr.shape[0] == 2
142
+ and xy_arr.shape[1] >= 2
143
+ ):
136
144
  x0 = float(xy_arr[0, 0])
137
145
  x1 = float(xy_arr[1, 0])
138
146
  if abs(x0 - x1) < 1e-10:
@@ -176,7 +184,12 @@ class CandlestickPlot(MaidrPlot):
176
184
  - Legacy path: return a dict with body and shared wick selectors (no open/close keys)
177
185
  """
178
186
  # Modern path: build structured selectors using separate gids
179
- if self._maidr_body_collection and self._maidr_wick_collection and self._maidr_body_gid and self._maidr_wick_gid:
187
+ if (
188
+ self._maidr_body_collection
189
+ and self._maidr_wick_collection
190
+ and self._maidr_body_gid
191
+ and self._maidr_wick_gid
192
+ ):
180
193
  # Determine candle count N
181
194
  N = None
182
195
  if self._maidr_original_data is not None:
@@ -115,7 +115,9 @@ class MultiLinePlot(MaidrPlot, LineExtractorMixin):
115
115
  line_type = label
116
116
 
117
117
  # Use the new method to extract data with categorical labels
118
- line_coords = LineExtractorMixin.extract_line_data_with_categorical_labels(self.ax, line)
118
+ line_coords = LineExtractorMixin.extract_line_data_with_categorical_labels(
119
+ self.ax, line
120
+ )
119
121
  if line_coords is None:
120
122
  continue
121
123
 
@@ -5,6 +5,7 @@ from abc import ABC, abstractmethod
5
5
  from matplotlib.axes import Axes
6
6
 
7
7
  from maidr.core.enum import MaidrKey, PlotType
8
+
8
9
  # uuid is used to generate unique identifiers for each plot layer in the MAIDR schema.
9
10
  import uuid
10
11
 
@@ -105,7 +105,10 @@ class MplfinanceLinePlot(MaidrPlot, LineExtractorMixin):
105
105
  continue
106
106
 
107
107
  # Use datetime converter for enhanced data extraction
108
- datetime_converter = getattr(line, "_maidr_datetime_converter", None) or self._maidr_datetime_converter
108
+ datetime_converter = (
109
+ getattr(line, "_maidr_datetime_converter", None)
110
+ or self._maidr_datetime_converter
111
+ )
109
112
  if datetime_converter is not None:
110
113
  # Convert x-coordinate (matplotlib index) to formatted datetime
111
114
  x_value = datetime_converter.get_formatted_datetime(int(round(x)))