maidr 1.7.1__tar.gz → 1.7.3__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 (163) hide show
  1. {maidr-1.7.1 → maidr-1.7.3}/CHANGELOG.md +23 -0
  2. {maidr-1.7.1 → maidr-1.7.3}/PKG-INFO +1 -1
  3. {maidr-1.7.1 → maidr-1.7.3}/example/flask/test_flask_app.py +13 -6
  4. {maidr-1.7.1 → maidr-1.7.3}/maidr/__init__.py +1 -1
  5. {maidr-1.7.1 → maidr-1.7.3}/maidr/core/figure_manager.py +53 -8
  6. {maidr-1.7.1 → maidr-1.7.3}/maidr/core/maidr.py +93 -19
  7. {maidr-1.7.1 → maidr-1.7.3}/maidr/core/plot/candlestick.py +17 -4
  8. {maidr-1.7.1 → maidr-1.7.3}/maidr/core/plot/lineplot.py +3 -1
  9. {maidr-1.7.1 → maidr-1.7.3}/maidr/core/plot/maidr_plot.py +1 -0
  10. {maidr-1.7.1 → maidr-1.7.3}/maidr/core/plot/mplfinance_lineplot.py +4 -1
  11. maidr-1.7.3/maidr/patch/barplot.py +218 -0
  12. {maidr-1.7.1 → maidr-1.7.3}/maidr/patch/mplfinance.py +2 -1
  13. {maidr-1.7.1 → maidr-1.7.3}/maidr/util/datetime_conversion.py +35 -20
  14. {maidr-1.7.1 → maidr-1.7.3}/maidr/util/environment.py +5 -9
  15. {maidr-1.7.1 → maidr-1.7.3}/maidr/util/mixin/extractor_mixin.py +3 -1
  16. {maidr-1.7.1 → maidr-1.7.3}/maidr/util/mplfinance_utils.py +8 -8
  17. {maidr-1.7.1 → maidr-1.7.3}/pyproject.toml +1 -1
  18. {maidr-1.7.1 → maidr-1.7.3}/uv.lock +2 -2
  19. maidr-1.7.1/example/stacked/matplotlib/example_mpl_stacked.html +0 -9
  20. maidr-1.7.1/example/stacked/seaborn/example_sns_stacked.html +0 -479
  21. maidr-1.7.1/maidr/patch/barplot.py +0 -77
  22. {maidr-1.7.1 → maidr-1.7.3}/.commitlintrc.cjs +0 -0
  23. {maidr-1.7.1 → maidr-1.7.3}/.editorconfig +0 -0
  24. {maidr-1.7.1 → maidr-1.7.3}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  25. {maidr-1.7.1 → maidr-1.7.3}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  26. {maidr-1.7.1 → maidr-1.7.3}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  27. {maidr-1.7.1 → maidr-1.7.3}/.github/copilot-instructions.md +0 -0
  28. {maidr-1.7.1 → maidr-1.7.3}/.github/workflows/ci.yml +0 -0
  29. {maidr-1.7.1 → maidr-1.7.3}/.github/workflows/docs.yml +0 -0
  30. {maidr-1.7.1 → maidr-1.7.3}/.github/workflows/release.yml +0 -0
  31. {maidr-1.7.1 → maidr-1.7.3}/.gitignore +0 -0
  32. {maidr-1.7.1 → maidr-1.7.3}/.pre-commit-config.yaml +0 -0
  33. {maidr-1.7.1 → maidr-1.7.3}/.vscode/extensions.json +0 -0
  34. {maidr-1.7.1 → maidr-1.7.3}/.vscode/settings.json +0 -0
  35. {maidr-1.7.1 → maidr-1.7.3}/CONDUCT.md +0 -0
  36. {maidr-1.7.1 → maidr-1.7.3}/CONTRIBUTING.md +0 -0
  37. {maidr-1.7.1 → maidr-1.7.3}/LICENSE +0 -0
  38. {maidr-1.7.1 → maidr-1.7.3}/README.md +0 -0
  39. {maidr-1.7.1 → maidr-1.7.3}/docs/.gitignore +0 -0
  40. {maidr-1.7.1 → maidr-1.7.3}/docs/CNAME +0 -0
  41. {maidr-1.7.1 → maidr-1.7.3}/docs/_environment +0 -0
  42. {maidr-1.7.1 → maidr-1.7.3}/docs/_extensions/machow/interlinks/.gitignore +0 -0
  43. {maidr-1.7.1 → maidr-1.7.3}/docs/_extensions/machow/interlinks/_extension.yml +0 -0
  44. {maidr-1.7.1 → maidr-1.7.3}/docs/_extensions/machow/interlinks/interlinks.lua +0 -0
  45. {maidr-1.7.1 → maidr-1.7.3}/docs/_extensions/shafayetShafee/line-highlight/_extension.yml +0 -0
  46. {maidr-1.7.1 → maidr-1.7.3}/docs/_extensions/shafayetShafee/line-highlight/line-highlight.lua +0 -0
  47. {maidr-1.7.1 → maidr-1.7.3}/docs/_extensions/shafayetShafee/line-highlight/resources/css/line-highlight.css +0 -0
  48. {maidr-1.7.1 → maidr-1.7.3}/docs/_extensions/shafayetShafee/line-highlight/resources/js/line-highlight.js +0 -0
  49. {maidr-1.7.1 → maidr-1.7.3}/docs/_quarto.yml +0 -0
  50. {maidr-1.7.1 → maidr-1.7.3}/docs/examples.qmd +0 -0
  51. {maidr-1.7.1 → maidr-1.7.3}/docs/index.qmd +0 -0
  52. {maidr-1.7.1 → maidr-1.7.3}/docs/styles.css +0 -0
  53. {maidr-1.7.1 → maidr-1.7.3}/example/bar/example_bar_plot.ipynb +0 -0
  54. {maidr-1.7.1 → maidr-1.7.3}/example/bar/matplotlib/example_mpl_bar_plot.py +0 -0
  55. {maidr-1.7.1 → maidr-1.7.3}/example/bar/seaborn/example_sns_bar_plot.py +0 -0
  56. {maidr-1.7.1 → maidr-1.7.3}/example/box/example_box_plot.ipynb +0 -0
  57. {maidr-1.7.1 → maidr-1.7.3}/example/box/matplotlib/example_mpl_box.py +0 -0
  58. {maidr-1.7.1 → maidr-1.7.3}/example/box/seaborn/example_sns_box.py +0 -0
  59. {maidr-1.7.1 → maidr-1.7.3}/example/candle_stick/legacy_candlestick_example.py +0 -0
  60. {maidr-1.7.1 → maidr-1.7.3}/example/candle_stick/mplfinance_candlestick_example.py +0 -0
  61. {maidr-1.7.1 → maidr-1.7.3}/example/candle_stick/test_data_daily_current_year.csv +0 -0
  62. {maidr-1.7.1 → maidr-1.7.3}/example/candle_stick/test_data_daily_mixed_years.csv +0 -0
  63. {maidr-1.7.1 → maidr-1.7.3}/example/candle_stick/test_data_hourly.csv +0 -0
  64. {maidr-1.7.1 → maidr-1.7.3}/example/candle_stick/test_data_minute.csv +0 -0
  65. {maidr-1.7.1 → maidr-1.7.3}/example/candle_stick/test_data_weekly.csv +0 -0
  66. {maidr-1.7.1 → maidr-1.7.3}/example/candle_stick/volcandat.csv +0 -0
  67. {maidr-1.7.1 → maidr-1.7.3}/example/count/example_count_plot.ipynb +0 -0
  68. {maidr-1.7.1 → maidr-1.7.3}/example/count/seaborn/example_sns_count_plot.py +0 -0
  69. {maidr-1.7.1 → maidr-1.7.3}/example/dodged/matplotlib/example_mpl_dodged.py +0 -0
  70. {maidr-1.7.1 → maidr-1.7.3}/example/dodged/seaborn/example_sns_dodged.py +0 -0
  71. {maidr-1.7.1 → maidr-1.7.3}/example/facet-subplots/matplotlib/example_mpl_facet_bar_plot.py +0 -0
  72. {maidr-1.7.1 → maidr-1.7.3}/example/facet-subplots/matplotlib/example_mpl_facet_combined_plot.py +0 -0
  73. {maidr-1.7.1 → maidr-1.7.3}/example/facet-subplots/seaborn/example_sns_facet_bar_plot.py +0 -0
  74. {maidr-1.7.1 → maidr-1.7.3}/example/facet-subplots/seaborn/example_sns_facet_combined_plot.py +0 -0
  75. {maidr-1.7.1 → maidr-1.7.3}/example/heatmap/example_heatmap_plot.ipynb +0 -0
  76. {maidr-1.7.1 → maidr-1.7.3}/example/heatmap/matplotlib/example_mpl_heatmap.py +0 -0
  77. {maidr-1.7.1 → maidr-1.7.3}/example/heatmap/seaborn/example_sns_heatmap.py +0 -0
  78. {maidr-1.7.1 → maidr-1.7.3}/example/histogram/example_histogram_plot.ipynb +0 -0
  79. {maidr-1.7.1 → maidr-1.7.3}/example/histogram/matplotlib/example_mpl_hist.py +0 -0
  80. {maidr-1.7.1 → maidr-1.7.3}/example/histogram/matplotlib/histogram_with_kde_matplotlib.py +0 -0
  81. {maidr-1.7.1 → maidr-1.7.3}/example/histogram/seaborn/example_sns_hist.py +0 -0
  82. {maidr-1.7.1 → maidr-1.7.3}/example/histogram/seaborn/histogram_with_kde_seaborn.py +0 -0
  83. {maidr-1.7.1 → maidr-1.7.3}/example/kde/example_kde_plots.ipynb +0 -0
  84. {maidr-1.7.1 → maidr-1.7.3}/example/kde/matplotlib/multiple_kde_matplotlib.py +0 -0
  85. {maidr-1.7.1 → maidr-1.7.3}/example/kde/matplotlib/single_kde_matplotlib.py +0 -0
  86. {maidr-1.7.1 → maidr-1.7.3}/example/kde/seaborn/multiple_kde_seaborn.py +0 -0
  87. {maidr-1.7.1 → maidr-1.7.3}/example/kde/seaborn/single_kde_seaborn.py +0 -0
  88. {maidr-1.7.1 → maidr-1.7.3}/example/line/example_line_plot.ipynb +0 -0
  89. {maidr-1.7.1 → maidr-1.7.3}/example/line/matplotlib/example_mpl_line.py +0 -0
  90. {maidr-1.7.1 → maidr-1.7.3}/example/line/seaborn/example_sns_line.py +0 -0
  91. {maidr-1.7.1 → maidr-1.7.3}/example/multilayer/example_mpl_multilayer.py +0 -0
  92. {maidr-1.7.1 → maidr-1.7.3}/example/multilayer/example_multilayer_plot.ipynb +0 -0
  93. {maidr-1.7.1 → maidr-1.7.3}/example/multiline/example_multiline_plot.ipynb +0 -0
  94. {maidr-1.7.1 → maidr-1.7.3}/example/multiline/matplotlib/example_mpl_multiline.py +0 -0
  95. {maidr-1.7.1 → maidr-1.7.3}/example/multiline/seaborn/example_sns_multiline.py +0 -0
  96. {maidr-1.7.1 → maidr-1.7.3}/example/multipanel/example_multipanel_plot.ipynb +0 -0
  97. {maidr-1.7.1 → maidr-1.7.3}/example/multipanel/matplotlib/example_mpl_multipanel.py +0 -0
  98. {maidr-1.7.1 → maidr-1.7.3}/example/multipanel/seaborn/example_sns_multipanel.py +0 -0
  99. {maidr-1.7.1 → maidr-1.7.3}/example/quarto/demo.qmd +0 -0
  100. {maidr-1.7.1 → maidr-1.7.3}/example/reg/example_reg_plots.ipynb +0 -0
  101. {maidr-1.7.1 → maidr-1.7.3}/example/reg/matplotlib/example_matplotlib_smooth_plot.py +0 -0
  102. {maidr-1.7.1 → maidr-1.7.3}/example/reg/seaborn/example_sns_reg.py +0 -0
  103. {maidr-1.7.1 → maidr-1.7.3}/example/scatter/example_scatter_plot.ipynb +0 -0
  104. {maidr-1.7.1 → maidr-1.7.3}/example/scatter/matplotlib/example_mpl_scatter.py +0 -0
  105. {maidr-1.7.1 → maidr-1.7.3}/example/scatter/seaborn/example_sns_scatter.py +0 -0
  106. {maidr-1.7.1 → maidr-1.7.3}/example/shiny/example_shiny_scatter.py +0 -0
  107. {maidr-1.7.1 → maidr-1.7.3}/example/stacked/matplotlib/example_mpl_stacked.py +0 -0
  108. {maidr-1.7.1 → maidr-1.7.3}/example/stacked/seaborn/example_sns_stacked.py +0 -0
  109. {maidr-1.7.1 → maidr-1.7.3}/example/streamlit/example_streamlit_app.py +0 -0
  110. {maidr-1.7.1 → maidr-1.7.3}/maidr/api.py +0 -0
  111. {maidr-1.7.1 → maidr-1.7.3}/maidr/core/__init__.py +0 -0
  112. {maidr-1.7.1 → maidr-1.7.3}/maidr/core/context_manager.py +0 -0
  113. {maidr-1.7.1 → maidr-1.7.3}/maidr/core/enum/__init__.py +0 -0
  114. {maidr-1.7.1 → maidr-1.7.3}/maidr/core/enum/library.py +0 -0
  115. {maidr-1.7.1 → maidr-1.7.3}/maidr/core/enum/maidr_key.py +0 -0
  116. {maidr-1.7.1 → maidr-1.7.3}/maidr/core/enum/plot_type.py +0 -0
  117. {maidr-1.7.1 → maidr-1.7.3}/maidr/core/enum/smooth_keywords.py +0 -0
  118. {maidr-1.7.1 → maidr-1.7.3}/maidr/core/plot/__init__.py +0 -0
  119. {maidr-1.7.1 → maidr-1.7.3}/maidr/core/plot/barplot.py +0 -0
  120. {maidr-1.7.1 → maidr-1.7.3}/maidr/core/plot/boxplot.py +0 -0
  121. {maidr-1.7.1 → maidr-1.7.3}/maidr/core/plot/grouped_barplot.py +0 -0
  122. {maidr-1.7.1 → maidr-1.7.3}/maidr/core/plot/heatmap.py +0 -0
  123. {maidr-1.7.1 → maidr-1.7.3}/maidr/core/plot/histogram.py +0 -0
  124. {maidr-1.7.1 → maidr-1.7.3}/maidr/core/plot/maidr_plot_factory.py +0 -0
  125. {maidr-1.7.1 → maidr-1.7.3}/maidr/core/plot/mplfinance_barplot.py +0 -0
  126. {maidr-1.7.1 → maidr-1.7.3}/maidr/core/plot/regplot.py +0 -0
  127. {maidr-1.7.1 → maidr-1.7.3}/maidr/core/plot/scatterplot.py +0 -0
  128. {maidr-1.7.1 → maidr-1.7.3}/maidr/exception/__init__.py +0 -0
  129. {maidr-1.7.1 → maidr-1.7.3}/maidr/exception/extraction_error.py +0 -0
  130. {maidr-1.7.1 → maidr-1.7.3}/maidr/patch/__init__.py +0 -0
  131. {maidr-1.7.1 → maidr-1.7.3}/maidr/patch/boxplot.py +0 -0
  132. {maidr-1.7.1 → maidr-1.7.3}/maidr/patch/candlestick.py +0 -0
  133. {maidr-1.7.1 → maidr-1.7.3}/maidr/patch/clear.py +0 -0
  134. {maidr-1.7.1 → maidr-1.7.3}/maidr/patch/common.py +0 -0
  135. {maidr-1.7.1 → maidr-1.7.3}/maidr/patch/heatmap.py +0 -0
  136. {maidr-1.7.1 → maidr-1.7.3}/maidr/patch/highlight.py +0 -0
  137. {maidr-1.7.1 → maidr-1.7.3}/maidr/patch/histogram.py +0 -0
  138. {maidr-1.7.1 → maidr-1.7.3}/maidr/patch/kdeplot.py +0 -0
  139. {maidr-1.7.1 → maidr-1.7.3}/maidr/patch/lineplot.py +0 -0
  140. {maidr-1.7.1 → maidr-1.7.3}/maidr/patch/regplot.py +0 -0
  141. {maidr-1.7.1 → maidr-1.7.3}/maidr/patch/scatterplot.py +0 -0
  142. {maidr-1.7.1 → maidr-1.7.3}/maidr/util/__init__.py +0 -0
  143. {maidr-1.7.1 → maidr-1.7.3}/maidr/util/dedup_utils.py +0 -0
  144. {maidr-1.7.1 → maidr-1.7.3}/maidr/util/mixin/__init__.py +0 -0
  145. {maidr-1.7.1 → maidr-1.7.3}/maidr/util/mixin/merger_mixin.py +0 -0
  146. {maidr-1.7.1 → maidr-1.7.3}/maidr/util/plot_detection.py +0 -0
  147. {maidr-1.7.1 → maidr-1.7.3}/maidr/util/regression_line_utils.py +0 -0
  148. {maidr-1.7.1 → maidr-1.7.3}/maidr/util/svg_utils.py +0 -0
  149. {maidr-1.7.1 → maidr-1.7.3}/maidr/widget/__init__.py +0 -0
  150. {maidr-1.7.1 → maidr-1.7.3}/maidr/widget/shiny.py +0 -0
  151. {maidr-1.7.1 → maidr-1.7.3}/tests/__init__.py +0 -0
  152. {maidr-1.7.1 → maidr-1.7.3}/tests/conftest.py +0 -0
  153. {maidr-1.7.1 → maidr-1.7.3}/tests/core/__init__.py +0 -0
  154. {maidr-1.7.1 → maidr-1.7.3}/tests/core/enum/__init__.py +0 -0
  155. {maidr-1.7.1 → maidr-1.7.3}/tests/core/plot/__init__.py +0 -0
  156. {maidr-1.7.1 → maidr-1.7.3}/tests/core/test_figure_manager.py +0 -0
  157. {maidr-1.7.1 → maidr-1.7.3}/tests/core/test_maidr_plot.py +0 -0
  158. {maidr-1.7.1 → maidr-1.7.3}/tests/core/test_maidr_plot_factory.py +0 -0
  159. {maidr-1.7.1 → maidr-1.7.3}/tests/fixture/__init__.py +0 -0
  160. {maidr-1.7.1 → maidr-1.7.3}/tests/fixture/library_factory.py +0 -0
  161. {maidr-1.7.1 → maidr-1.7.3}/tests/fixture/matplotlib_factory.py +0 -0
  162. {maidr-1.7.1 → maidr-1.7.3}/tests/fixture/seaborn_factory.py +0 -0
  163. {maidr-1.7.1 → maidr-1.7.3}/tox.ini +0 -0
@@ -1,6 +1,29 @@
1
1
  # CHANGELOG
2
2
 
3
3
 
4
+ ## v1.7.3 (2025-09-15)
5
+
6
+ ### Bug Fixes
7
+
8
+ - Ensure all subplots are accessible and improve dodged plot detection
9
+ ([#242](https://github.com/xability/py-maidr/pull/242),
10
+ [`979b971`](https://github.com/xability/py-maidr/commit/979b9713d07be8bc9046056e7f1c0519336ddd22))
11
+
12
+ Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
13
+
14
+ Co-authored-by: jooyoungseo <19754711+jooyoungseo@users.noreply.github.com>
15
+
16
+ Co-authored-by: JooYoung Seo <jseo1005@illinois.edu>
17
+
18
+
19
+ ## v1.7.2 (2025-09-12)
20
+
21
+ ### Bug Fixes
22
+
23
+ - Prevent iframe resizing when modal is open ([#237](https://github.com/xability/py-maidr/pull/237),
24
+ [`89a4253`](https://github.com/xability/py-maidr/commit/89a42537a32025b286b8089b7f4b21b7c409c4e4))
25
+
26
+
4
27
  ## v1.7.1 (2025-09-03)
5
28
 
6
29
  ### Bug Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: maidr
3
- Version: 1.7.1
3
+ Version: 1.7.3
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.1"
1
+ __version__ = "1.7.3"
2
2
 
3
3
  from .api import close, render, save_html, show, stacked
4
4
  from .core import Maidr
@@ -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:
@@ -366,16 +407,29 @@ class Maidr:
366
407
  iframe.contentWindow.document
367
408
  ) {{
368
409
  let iframeDocument = iframe.contentWindow.document;
369
- let brailleContainer =
370
- iframeDocument.getElementById('braille-input');
410
+ // Detect braille textarea by dynamic id prefix
411
+ let brailleContainer = iframeDocument.querySelector('[id^="maidr-braille-textarea"]');
412
+ // Detect review input container by class name
413
+ let reviewInputContainer = iframeDocument.querySelector('.maidr-review-input');
371
414
  iframe.style.height = 'auto';
372
415
  let height = iframeDocument.body.scrollHeight;
373
- if (brailleContainer &&
374
- brailleContainer === iframeDocument.activeElement
375
- ) {{
416
+ // Consider braille active if it or any descendant has focus
417
+ let isBrailleActive = brailleContainer && (
418
+ brailleContainer === iframeDocument.activeElement ||
419
+ (typeof brailleContainer.contains === 'function' && brailleContainer.contains(iframeDocument.activeElement))
420
+ );
421
+ // Consider review input active if it or any descendant has focus
422
+ let isReviewInputActive = reviewInputContainer && (
423
+ reviewInputContainer === iframeDocument.activeElement ||
424
+ (typeof reviewInputContainer.contains === 'function' && reviewInputContainer.contains(iframeDocument.activeElement))
425
+ );
426
+ // (logs removed)
427
+ if (isBrailleActive) {{
376
428
  height += 100;
377
- }}else{{
378
- height += 50
429
+ }} else if (isReviewInputActive) {{
430
+ height += 50;
431
+ }} else {{
432
+ height += 50;
379
433
  }}
380
434
  iframe.style.height = (height) + 'px';
381
435
  iframe.style.width = iframeDocument.body.scrollWidth + 'px';
@@ -387,12 +441,32 @@ class Maidr:
387
441
  resizeIframe();
388
442
  iframe.contentWindow.addEventListener('resize', resizeIframe);
389
443
  }};
390
- iframe.contentWindow.document.addEventListener('focusin', () => {{
391
- resizeIframe();
392
- }});
393
- iframe.contentWindow.document.addEventListener('focusout', () => {{
394
- resizeIframe();
395
- }});
444
+ // Delegate focus events for braille textarea (by id prefix)
445
+ iframe.contentWindow.document.addEventListener('focusin', (e) => {{
446
+ try {{
447
+ const t = e && e.target ? e.target : null;
448
+ if (t && typeof t.id === 'string' && t.id.startsWith('maidr-braille-textarea')) resizeIframe();
449
+ }} catch (_) {{ resizeIframe(); }}
450
+ }}, true);
451
+ iframe.contentWindow.document.addEventListener('focusout', (e) => {{
452
+ try {{
453
+ const t = e && e.target ? e.target : null;
454
+ if (t && typeof t.id === 'string' && t.id.startsWith('maidr-braille-textarea')) resizeIframe();
455
+ }} catch (_) {{ resizeIframe(); }}
456
+ }}, true);
457
+ // Delegate focus events for review input container (by class name)
458
+ iframe.contentWindow.document.addEventListener('focusin', (e) => {{
459
+ try {{
460
+ const t = e && e.target ? e.target : null;
461
+ if (t && t.classList && t.classList.contains('maidr-review-input')) resizeIframe();
462
+ }} catch (_) {{ resizeIframe(); }}
463
+ }}, true);
464
+ iframe.contentWindow.document.addEventListener('focusout', (e) => {{
465
+ try {{
466
+ const t = e && e.target ? e.target : null;
467
+ if (t && t.classList && t.classList.contains('maidr-review-input')) resizeIframe();
468
+ }} catch (_) {{ resizeIframe(); }}
469
+ }}, true);
396
470
  """
397
471
  return resizing_script
398
472
 
@@ -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)))
@@ -0,0 +1,218 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Callable, Dict, Tuple, Union
4
+
5
+ import wrapt
6
+ from matplotlib.axes import Axes
7
+ from matplotlib.container import BarContainer
8
+
9
+ from maidr.core.enum import PlotType
10
+ from maidr.patch.common import common
11
+
12
+
13
+ def bar(
14
+ wrapped: Callable, instance: Any, args: Tuple[Any, ...], kwargs: Dict[str, Any]
15
+ ) -> Union[Axes, BarContainer]:
16
+ """
17
+ Patch function for bar plots.
18
+
19
+ This function patches the bar plotting functions to identify whether the
20
+ plot should be rendered as a normal, stacked, or dodged bar plot.
21
+ It uses the 'bottom' keyword to identify stacked bar plots. For dodged plots,
22
+ it first checks for seaborn-specific indicators (hue parameter with dodge=True),
23
+ then uses robust detection logic that considers both width and context
24
+ to avoid misclassifying simple bar plots with narrow widths as dodged plots.
25
+
26
+ Parameters
27
+ ----------
28
+ wrapped : Callable
29
+ The original function to be wrapped.
30
+ instance : Any
31
+ The instance of the class where the function is being patched.
32
+ args : tuple
33
+ Positional arguments passed to the original function.
34
+ For a dodged plot, the first argument (x positions) should be numeric.
35
+ kwargs : dict
36
+ Keyword arguments passed to the original function.
37
+ For seaborn plots, may contain 'hue' and 'dodge' parameters.
38
+
39
+ Returns
40
+ -------
41
+ Union[Axes, BarContainer]
42
+ The axes or bar container returned by the original function.
43
+
44
+ Examples
45
+ --------
46
+ >>> # For a seaborn dodged (grouped) bar plot:
47
+ >>> sns.barplot(data=df, x='category', y='value', hue='group', dodge=True)
48
+
49
+ >>> # For a manual dodged (grouped) bar plot, pass numeric x positions:
50
+ >>> x_positions = np.arange(3)
51
+ >>> ax.bar(x_positions, heights, width, label='Group') # Dodged bar plot.
52
+ """
53
+ plot_type = PlotType.BAR
54
+
55
+ # Check for stacked plots first (explicit bottom parameter)
56
+ if "bottom" in kwargs:
57
+ bottom = kwargs.get("bottom")
58
+ if bottom is not None:
59
+ plot_type = PlotType.STACKED
60
+ else:
61
+ # Check for seaborn-specific dodged plot indicators first
62
+ # This handles seaborn.barplot with hue and dodge=True
63
+ if "hue" in kwargs and kwargs.get("dodge"):
64
+ plot_type = PlotType.DODGED
65
+ else:
66
+ # Extract width and align parameters
67
+ if len(args) >= 3:
68
+ real_width = args[2]
69
+ else:
70
+ real_width = kwargs.get("width", 0.8)
71
+
72
+ align = kwargs.get("align", "center")
73
+
74
+ # More robust dodged plot detection: consider multiple factors
75
+ # Only classify as DODGED if there are strong indicators of grouping
76
+ should_be_dodged = _should_classify_as_dodged(
77
+ instance, real_width, align, args, kwargs
78
+ )
79
+
80
+ if should_be_dodged:
81
+ plot_type = PlotType.DODGED
82
+
83
+ return common(plot_type, wrapped, instance, args, kwargs)
84
+
85
+
86
+ def _should_classify_as_dodged(
87
+ ax: Any, width: Any, align: str, args: Tuple[Any, ...], kwargs: Dict[str, Any]
88
+ ) -> bool:
89
+ """
90
+ Determine if a bar plot should be classified as dodged based on context.
91
+
92
+ This function uses more sophisticated logic than just checking width < 0.8,
93
+ as simple bar plots with narrow widths should not be considered dodged.
94
+
95
+ Parameters
96
+ ----------
97
+ ax : Any
98
+ The axes instance where the plot is being created.
99
+ width : Any
100
+ The width parameter for the bar plot.
101
+ align : str
102
+ The alignment parameter for the bar plot.
103
+ args : tuple
104
+ Positional arguments passed to the bar function.
105
+ kwargs : dict
106
+ Keyword arguments passed to the bar function.
107
+
108
+ Returns
109
+ -------
110
+ bool
111
+ True if the plot should be classified as DODGED, False otherwise.
112
+
113
+ Examples
114
+ --------
115
+ >>> # These should be DODGED:
116
+ >>> ax.bar([0.1, 1.1, 2.1], [1, 2, 3], width=0.4, label='Group A')
117
+ >>> ax.bar([0.4, 1.4, 2.4], [4, 5, 6], width=0.4, label='Group B')
118
+
119
+ >>> # These should remain BAR:
120
+ >>> ax.bar(['A', 'B', 'C'], [1, 2, 3], width=0.6) # Simple categorical bar plot
121
+ """
122
+ # If align is 'edge', it's likely a dodged plot
123
+ if align == "edge":
124
+ return True
125
+
126
+ # If width is specified and very narrow (< 0.5), more likely to be dodged
127
+ # But only if there are other indicators
128
+ if isinstance(width, (int, float)) and float(width) < 0.5:
129
+ # Check if x positions suggest grouping (numeric positions with fractional parts)
130
+ if len(args) > 0:
131
+ x_positions = args[0]
132
+ if _has_numeric_grouping_pattern(x_positions):
133
+ return True
134
+
135
+ # Check if there are already multiple bar containers on the axes
136
+ # This suggests that this might be part of a grouped bar plot
137
+ if hasattr(ax, "containers") and len(ax.containers) > 0:
138
+ # If there are existing containers, this might be adding to a group
139
+ if isinstance(width, (int, float)) and float(width) < 0.8:
140
+ return True
141
+
142
+ # Check for explicit grouping indicators in kwargs
143
+ if "label" in kwargs and isinstance(width, (int, float)) and float(width) < 0.8:
144
+ # If there's a label and narrow width, it might be part of a group
145
+ # But we need to be conservative here to avoid false positives
146
+ if _has_numeric_grouping_pattern(args[0] if len(args) > 0 else None):
147
+ return True
148
+
149
+ # Default to False - prefer BAR over DODGED for ambiguous cases
150
+ return False
151
+
152
+
153
+ def _has_numeric_grouping_pattern(x_positions: Any) -> bool:
154
+ """
155
+ Check if x positions suggest a grouping pattern typical of dodged plots.
156
+
157
+ Parameters
158
+ ----------
159
+ x_positions : Any
160
+ The x positions for the bar plot.
161
+
162
+ Returns
163
+ -------
164
+ bool
165
+ True if the positions suggest grouping, False otherwise.
166
+
167
+ Examples
168
+ --------
169
+ >>> _has_numeric_grouping_pattern([0.1, 1.1, 2.1]) # True - fractional offsets
170
+ >>> _has_numeric_grouping_pattern(['A', 'B', 'C']) # False - categorical
171
+ >>> _has_numeric_grouping_pattern([0, 1, 2]) # False - simple numeric
172
+ """
173
+ try:
174
+ # Convert to list if possible (duck typing)
175
+ try:
176
+ positions = list(x_positions)
177
+ except TypeError:
178
+ return False
179
+
180
+ # If all positions are strings, it's categorical (not dodged)
181
+ if all(isinstance(pos, str) for pos in positions):
182
+ return False
183
+
184
+ # If positions are numeric, check for fractional offsets
185
+ # that suggest manual positioning for grouping
186
+ numeric_positions = []
187
+ for pos in positions:
188
+ try:
189
+ numeric_positions.append(float(pos))
190
+ except (ValueError, TypeError):
191
+ return False
192
+
193
+ if len(numeric_positions) < 2:
194
+ return False
195
+
196
+ # Check if positions have fractional parts that suggest manual offset
197
+ # for grouping (e.g., [0.1, 1.1, 2.1] or [0.8, 1.8, 2.8])
198
+ fractional_parts = [pos % 1 for pos in numeric_positions]
199
+
200
+ # If all have the same non-zero fractional part, it suggests grouping
201
+ if all(abs(frac - fractional_parts[0]) < 0.01 for frac in fractional_parts):
202
+ if fractional_parts[0] > 0.01: # Non-zero fractional part
203
+ return True
204
+
205
+ return False
206
+
207
+ except Exception:
208
+ # If anything goes wrong in analysis, default to False
209
+ return False
210
+
211
+
212
+ # Patch matplotlib functions.
213
+ wrapt.wrap_function_wrapper(Axes, "bar", bar)
214
+ wrapt.wrap_function_wrapper(Axes, "barh", bar)
215
+
216
+ # Patch seaborn functions.
217
+ wrapt.wrap_function_wrapper("seaborn", "barplot", bar)
218
+ wrapt.wrap_function_wrapper("seaborn", "countplot", bar)
@@ -72,6 +72,7 @@ def mplfinance_plot_patch(wrapped, instance, args, kwargs):
72
72
  # fallback: use index if it's a DatetimeIndex
73
73
  try:
74
74
  import matplotlib.dates as mdates
75
+
75
76
  date_nums = [mdates.date2num(d) for d in data.index]
76
77
  except Exception:
77
78
  pass
@@ -82,7 +83,7 @@ def mplfinance_plot_patch(wrapped, instance, args, kwargs):
82
83
  datetime_converter = create_datetime_converter(data)
83
84
 
84
85
  # Use enhanced converter's date_nums for mplfinance compatibility
85
- if date_nums is None and hasattr(datetime_converter, 'date_nums'):
86
+ if date_nums is None and hasattr(datetime_converter, "date_nums"):
86
87
  date_nums = datetime_converter.date_nums
87
88
 
88
89
  # Process and register the Candlestick plot