maidr 1.8.1__tar.gz → 1.10.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 (161) hide show
  1. {maidr-1.8.1 → maidr-1.10.0}/CHANGELOG.md +17 -0
  2. {maidr-1.8.1 → maidr-1.10.0}/PKG-INFO +1 -1
  3. {maidr-1.8.1 → maidr-1.10.0}/docs/examples.qmd +1 -0
  4. {maidr-1.8.1 → maidr-1.10.0}/maidr/__init__.py +1 -1
  5. {maidr-1.8.1 → maidr-1.10.0}/maidr/api.py +17 -12
  6. {maidr-1.8.1 → maidr-1.10.0}/maidr/core/maidr.py +88 -37
  7. {maidr-1.8.1 → maidr-1.10.0}/maidr/core/plot/candlestick.py +59 -90
  8. {maidr-1.8.1 → maidr-1.10.0}/maidr/util/datetime_conversion.py +14 -27
  9. maidr-1.10.0/maidr/util/mplfinance_utils.py +117 -0
  10. {maidr-1.8.1 → maidr-1.10.0}/pyproject.toml +1 -1
  11. {maidr-1.8.1 → maidr-1.10.0}/uv.lock +7 -1
  12. maidr-1.8.1/maidr/util/mplfinance_utils.py +0 -415
  13. {maidr-1.8.1 → maidr-1.10.0}/.commitlintrc.cjs +0 -0
  14. {maidr-1.8.1 → maidr-1.10.0}/.editorconfig +0 -0
  15. {maidr-1.8.1 → maidr-1.10.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  16. {maidr-1.8.1 → maidr-1.10.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  17. {maidr-1.8.1 → maidr-1.10.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  18. {maidr-1.8.1 → maidr-1.10.0}/.github/copilot-instructions.md +0 -0
  19. {maidr-1.8.1 → maidr-1.10.0}/.github/workflows/ci.yml +0 -0
  20. {maidr-1.8.1 → maidr-1.10.0}/.github/workflows/docs.yml +0 -0
  21. {maidr-1.8.1 → maidr-1.10.0}/.github/workflows/release.yml +0 -0
  22. {maidr-1.8.1 → maidr-1.10.0}/.gitignore +0 -0
  23. {maidr-1.8.1 → maidr-1.10.0}/.pre-commit-config.yaml +0 -0
  24. {maidr-1.8.1 → maidr-1.10.0}/.vscode/extensions.json +0 -0
  25. {maidr-1.8.1 → maidr-1.10.0}/.vscode/settings.json +0 -0
  26. {maidr-1.8.1 → maidr-1.10.0}/CONDUCT.md +0 -0
  27. {maidr-1.8.1 → maidr-1.10.0}/CONTRIBUTING.md +0 -0
  28. {maidr-1.8.1 → maidr-1.10.0}/LICENSE +0 -0
  29. {maidr-1.8.1 → maidr-1.10.0}/README.md +0 -0
  30. {maidr-1.8.1 → maidr-1.10.0}/docs/.gitignore +0 -0
  31. {maidr-1.8.1 → maidr-1.10.0}/docs/CNAME +0 -0
  32. {maidr-1.8.1 → maidr-1.10.0}/docs/_environment +0 -0
  33. {maidr-1.8.1 → maidr-1.10.0}/docs/_extensions/machow/interlinks/.gitignore +0 -0
  34. {maidr-1.8.1 → maidr-1.10.0}/docs/_extensions/machow/interlinks/_extension.yml +0 -0
  35. {maidr-1.8.1 → maidr-1.10.0}/docs/_extensions/machow/interlinks/interlinks.lua +0 -0
  36. {maidr-1.8.1 → maidr-1.10.0}/docs/_extensions/shafayetShafee/line-highlight/_extension.yml +0 -0
  37. {maidr-1.8.1 → maidr-1.10.0}/docs/_extensions/shafayetShafee/line-highlight/line-highlight.lua +0 -0
  38. {maidr-1.8.1 → maidr-1.10.0}/docs/_extensions/shafayetShafee/line-highlight/resources/css/line-highlight.css +0 -0
  39. {maidr-1.8.1 → maidr-1.10.0}/docs/_extensions/shafayetShafee/line-highlight/resources/js/line-highlight.js +0 -0
  40. {maidr-1.8.1 → maidr-1.10.0}/docs/_quarto.yml +0 -0
  41. {maidr-1.8.1 → maidr-1.10.0}/docs/index.qmd +0 -0
  42. {maidr-1.8.1 → maidr-1.10.0}/docs/styles.css +0 -0
  43. {maidr-1.8.1 → maidr-1.10.0}/example/bar/example_bar_plot.ipynb +0 -0
  44. {maidr-1.8.1 → maidr-1.10.0}/example/bar/matplotlib/example_mpl_bar_plot.py +0 -0
  45. {maidr-1.8.1 → maidr-1.10.0}/example/bar/seaborn/example_sns_bar_plot.py +0 -0
  46. {maidr-1.8.1 → maidr-1.10.0}/example/box/example_box_plot.ipynb +0 -0
  47. {maidr-1.8.1 → maidr-1.10.0}/example/box/matplotlib/example_mpl_box.py +0 -0
  48. {maidr-1.8.1 → maidr-1.10.0}/example/box/seaborn/example_sns_box.py +0 -0
  49. {maidr-1.8.1 → maidr-1.10.0}/example/candle_stick/legacy_candlestick_example.py +0 -0
  50. {maidr-1.8.1 → maidr-1.10.0}/example/candle_stick/mplfinance_candlestick_example.py +0 -0
  51. {maidr-1.8.1 → maidr-1.10.0}/example/candle_stick/test_data_daily_current_year.csv +0 -0
  52. {maidr-1.8.1 → maidr-1.10.0}/example/candle_stick/test_data_daily_mixed_years.csv +0 -0
  53. {maidr-1.8.1 → maidr-1.10.0}/example/candle_stick/test_data_hourly.csv +0 -0
  54. {maidr-1.8.1 → maidr-1.10.0}/example/candle_stick/test_data_minute.csv +0 -0
  55. {maidr-1.8.1 → maidr-1.10.0}/example/candle_stick/test_data_weekly.csv +0 -0
  56. {maidr-1.8.1 → maidr-1.10.0}/example/candle_stick/volcandat.csv +0 -0
  57. {maidr-1.8.1 → maidr-1.10.0}/example/count/example_count_plot.ipynb +0 -0
  58. {maidr-1.8.1 → maidr-1.10.0}/example/count/seaborn/example_sns_count_plot.py +0 -0
  59. {maidr-1.8.1 → maidr-1.10.0}/example/dodged/matplotlib/example_mpl_dodged.py +0 -0
  60. {maidr-1.8.1 → maidr-1.10.0}/example/dodged/seaborn/example_sns_dodged.py +0 -0
  61. {maidr-1.8.1 → maidr-1.10.0}/example/facet-subplots/matplotlib/example_mpl_facet_bar_plot.py +0 -0
  62. {maidr-1.8.1 → maidr-1.10.0}/example/facet-subplots/matplotlib/example_mpl_facet_combined_plot.py +0 -0
  63. {maidr-1.8.1 → maidr-1.10.0}/example/facet-subplots/seaborn/example_sns_facet_bar_plot.py +0 -0
  64. {maidr-1.8.1 → maidr-1.10.0}/example/facet-subplots/seaborn/example_sns_facet_combined_plot.py +0 -0
  65. {maidr-1.8.1 → maidr-1.10.0}/example/flask/test_flask_app.py +0 -0
  66. {maidr-1.8.1 → maidr-1.10.0}/example/heatmap/example_heatmap_plot.ipynb +0 -0
  67. {maidr-1.8.1 → maidr-1.10.0}/example/heatmap/matplotlib/example_mpl_heatmap.py +0 -0
  68. {maidr-1.8.1 → maidr-1.10.0}/example/heatmap/seaborn/example_sns_heatmap.py +0 -0
  69. {maidr-1.8.1 → maidr-1.10.0}/example/histogram/example_histogram_plot.ipynb +0 -0
  70. {maidr-1.8.1 → maidr-1.10.0}/example/histogram/matplotlib/example_mpl_hist.py +0 -0
  71. {maidr-1.8.1 → maidr-1.10.0}/example/histogram/matplotlib/histogram_with_kde_matplotlib.py +0 -0
  72. {maidr-1.8.1 → maidr-1.10.0}/example/histogram/seaborn/example_sns_hist.py +0 -0
  73. {maidr-1.8.1 → maidr-1.10.0}/example/histogram/seaborn/histogram_with_kde_seaborn.py +0 -0
  74. {maidr-1.8.1 → maidr-1.10.0}/example/kde/example_kde_plots.ipynb +0 -0
  75. {maidr-1.8.1 → maidr-1.10.0}/example/kde/matplotlib/multiple_kde_matplotlib.py +0 -0
  76. {maidr-1.8.1 → maidr-1.10.0}/example/kde/matplotlib/single_kde_matplotlib.py +0 -0
  77. {maidr-1.8.1 → maidr-1.10.0}/example/kde/seaborn/multiple_kde_seaborn.py +0 -0
  78. {maidr-1.8.1 → maidr-1.10.0}/example/kde/seaborn/single_kde_seaborn.py +0 -0
  79. {maidr-1.8.1 → maidr-1.10.0}/example/line/example_line_plot.ipynb +0 -0
  80. {maidr-1.8.1 → maidr-1.10.0}/example/line/matplotlib/example_mpl_line.py +0 -0
  81. {maidr-1.8.1 → maidr-1.10.0}/example/line/seaborn/example_sns_line.py +0 -0
  82. {maidr-1.8.1 → maidr-1.10.0}/example/multilayer/example_mpl_multilayer.py +0 -0
  83. {maidr-1.8.1 → maidr-1.10.0}/example/multilayer/example_multilayer_plot.ipynb +0 -0
  84. {maidr-1.8.1 → maidr-1.10.0}/example/multiline/example_multiline_plot.ipynb +0 -0
  85. {maidr-1.8.1 → maidr-1.10.0}/example/multiline/matplotlib/example_mpl_multiline.py +0 -0
  86. {maidr-1.8.1 → maidr-1.10.0}/example/multiline/seaborn/example_sns_multiline.py +0 -0
  87. {maidr-1.8.1 → maidr-1.10.0}/example/multipanel/example_multipanel_plot.ipynb +0 -0
  88. {maidr-1.8.1 → maidr-1.10.0}/example/multipanel/matplotlib/example_mpl_multipanel.py +0 -0
  89. {maidr-1.8.1 → maidr-1.10.0}/example/multipanel/seaborn/example_sns_multipanel.py +0 -0
  90. {maidr-1.8.1 → maidr-1.10.0}/example/quarto/demo.qmd +0 -0
  91. {maidr-1.8.1 → maidr-1.10.0}/example/reg/example_reg_plots.ipynb +0 -0
  92. {maidr-1.8.1 → maidr-1.10.0}/example/reg/matplotlib/example_matplotlib_smooth_plot.py +0 -0
  93. {maidr-1.8.1 → maidr-1.10.0}/example/reg/seaborn/example_sns_reg.py +0 -0
  94. {maidr-1.8.1 → maidr-1.10.0}/example/scatter/example_scatter_plot.ipynb +0 -0
  95. {maidr-1.8.1 → maidr-1.10.0}/example/scatter/matplotlib/example_mpl_scatter.py +0 -0
  96. {maidr-1.8.1 → maidr-1.10.0}/example/scatter/seaborn/example_sns_scatter.py +0 -0
  97. {maidr-1.8.1 → maidr-1.10.0}/example/shiny/example_shiny_scatter.py +0 -0
  98. {maidr-1.8.1 → maidr-1.10.0}/example/stacked/matplotlib/example_mpl_stacked.py +0 -0
  99. {maidr-1.8.1 → maidr-1.10.0}/example/stacked/seaborn/example_sns_stacked.py +0 -0
  100. {maidr-1.8.1 → maidr-1.10.0}/example/streamlit/example_streamlit_app.py +0 -0
  101. {maidr-1.8.1 → maidr-1.10.0}/maidr/core/__init__.py +0 -0
  102. {maidr-1.8.1 → maidr-1.10.0}/maidr/core/context_manager.py +0 -0
  103. {maidr-1.8.1 → maidr-1.10.0}/maidr/core/enum/__init__.py +0 -0
  104. {maidr-1.8.1 → maidr-1.10.0}/maidr/core/enum/library.py +0 -0
  105. {maidr-1.8.1 → maidr-1.10.0}/maidr/core/enum/maidr_key.py +0 -0
  106. {maidr-1.8.1 → maidr-1.10.0}/maidr/core/enum/plot_type.py +0 -0
  107. {maidr-1.8.1 → maidr-1.10.0}/maidr/core/enum/smooth_keywords.py +0 -0
  108. {maidr-1.8.1 → maidr-1.10.0}/maidr/core/figure_manager.py +0 -0
  109. {maidr-1.8.1 → maidr-1.10.0}/maidr/core/plot/__init__.py +0 -0
  110. {maidr-1.8.1 → maidr-1.10.0}/maidr/core/plot/barplot.py +0 -0
  111. {maidr-1.8.1 → maidr-1.10.0}/maidr/core/plot/boxplot.py +0 -0
  112. {maidr-1.8.1 → maidr-1.10.0}/maidr/core/plot/grouped_barplot.py +0 -0
  113. {maidr-1.8.1 → maidr-1.10.0}/maidr/core/plot/heatmap.py +0 -0
  114. {maidr-1.8.1 → maidr-1.10.0}/maidr/core/plot/histogram.py +0 -0
  115. {maidr-1.8.1 → maidr-1.10.0}/maidr/core/plot/lineplot.py +0 -0
  116. {maidr-1.8.1 → maidr-1.10.0}/maidr/core/plot/maidr_plot.py +0 -0
  117. {maidr-1.8.1 → maidr-1.10.0}/maidr/core/plot/maidr_plot_factory.py +0 -0
  118. {maidr-1.8.1 → maidr-1.10.0}/maidr/core/plot/mplfinance_barplot.py +0 -0
  119. {maidr-1.8.1 → maidr-1.10.0}/maidr/core/plot/mplfinance_lineplot.py +0 -0
  120. {maidr-1.8.1 → maidr-1.10.0}/maidr/core/plot/regplot.py +0 -0
  121. {maidr-1.8.1 → maidr-1.10.0}/maidr/core/plot/scatterplot.py +0 -0
  122. {maidr-1.8.1 → maidr-1.10.0}/maidr/exception/__init__.py +0 -0
  123. {maidr-1.8.1 → maidr-1.10.0}/maidr/exception/extraction_error.py +0 -0
  124. {maidr-1.8.1 → maidr-1.10.0}/maidr/patch/__init__.py +0 -0
  125. {maidr-1.8.1 → maidr-1.10.0}/maidr/patch/barplot.py +0 -0
  126. {maidr-1.8.1 → maidr-1.10.0}/maidr/patch/boxplot.py +0 -0
  127. {maidr-1.8.1 → maidr-1.10.0}/maidr/patch/candlestick.py +0 -0
  128. {maidr-1.8.1 → maidr-1.10.0}/maidr/patch/clear.py +0 -0
  129. {maidr-1.8.1 → maidr-1.10.0}/maidr/patch/common.py +0 -0
  130. {maidr-1.8.1 → maidr-1.10.0}/maidr/patch/heatmap.py +0 -0
  131. {maidr-1.8.1 → maidr-1.10.0}/maidr/patch/highlight.py +0 -0
  132. {maidr-1.8.1 → maidr-1.10.0}/maidr/patch/histogram.py +0 -0
  133. {maidr-1.8.1 → maidr-1.10.0}/maidr/patch/kdeplot.py +0 -0
  134. {maidr-1.8.1 → maidr-1.10.0}/maidr/patch/lineplot.py +0 -0
  135. {maidr-1.8.1 → maidr-1.10.0}/maidr/patch/mplfinance.py +0 -0
  136. {maidr-1.8.1 → maidr-1.10.0}/maidr/patch/regplot.py +0 -0
  137. {maidr-1.8.1 → maidr-1.10.0}/maidr/patch/scatterplot.py +0 -0
  138. {maidr-1.8.1 → maidr-1.10.0}/maidr/util/__init__.py +0 -0
  139. {maidr-1.8.1 → maidr-1.10.0}/maidr/util/dedup_utils.py +0 -0
  140. {maidr-1.8.1 → maidr-1.10.0}/maidr/util/environment.py +0 -0
  141. {maidr-1.8.1 → maidr-1.10.0}/maidr/util/mixin/__init__.py +0 -0
  142. {maidr-1.8.1 → maidr-1.10.0}/maidr/util/mixin/extractor_mixin.py +0 -0
  143. {maidr-1.8.1 → maidr-1.10.0}/maidr/util/mixin/merger_mixin.py +0 -0
  144. {maidr-1.8.1 → maidr-1.10.0}/maidr/util/plot_detection.py +0 -0
  145. {maidr-1.8.1 → maidr-1.10.0}/maidr/util/regression_line_utils.py +0 -0
  146. {maidr-1.8.1 → maidr-1.10.0}/maidr/util/svg_utils.py +0 -0
  147. {maidr-1.8.1 → maidr-1.10.0}/maidr/widget/__init__.py +0 -0
  148. {maidr-1.8.1 → maidr-1.10.0}/maidr/widget/shiny.py +0 -0
  149. {maidr-1.8.1 → maidr-1.10.0}/tests/__init__.py +0 -0
  150. {maidr-1.8.1 → maidr-1.10.0}/tests/conftest.py +0 -0
  151. {maidr-1.8.1 → maidr-1.10.0}/tests/core/__init__.py +0 -0
  152. {maidr-1.8.1 → maidr-1.10.0}/tests/core/enum/__init__.py +0 -0
  153. {maidr-1.8.1 → maidr-1.10.0}/tests/core/plot/__init__.py +0 -0
  154. {maidr-1.8.1 → maidr-1.10.0}/tests/core/test_figure_manager.py +0 -0
  155. {maidr-1.8.1 → maidr-1.10.0}/tests/core/test_maidr_plot.py +0 -0
  156. {maidr-1.8.1 → maidr-1.10.0}/tests/core/test_maidr_plot_factory.py +0 -0
  157. {maidr-1.8.1 → maidr-1.10.0}/tests/fixture/__init__.py +0 -0
  158. {maidr-1.8.1 → maidr-1.10.0}/tests/fixture/library_factory.py +0 -0
  159. {maidr-1.8.1 → maidr-1.10.0}/tests/fixture/matplotlib_factory.py +0 -0
  160. {maidr-1.8.1 → maidr-1.10.0}/tests/fixture/seaborn_factory.py +0 -0
  161. {maidr-1.8.1 → maidr-1.10.0}/tox.ini +0 -0
@@ -1,6 +1,23 @@
1
1
  # CHANGELOG
2
2
 
3
3
 
4
+ ## v1.10.0 (2026-01-31)
5
+
6
+ ### Features
7
+
8
+ - Remove candlestick formatting and add ylabel to dodged plots
9
+ ([#260](https://github.com/xability/py-maidr/pull/260),
10
+ [`70b2626`](https://github.com/xability/py-maidr/commit/70b2626bb31e109e8cdb29051f809fe03bfb6275))
11
+
12
+
13
+ ## v1.9.0 (2025-10-31)
14
+
15
+ ### Features
16
+
17
+ - Add data_in_svg parameter for save_html ([#257](https://github.com/xability/py-maidr/pull/257),
18
+ [`17d65ee`](https://github.com/xability/py-maidr/commit/17d65eee06ac3209ef3a91ebd623a5a6d0b16e79))
19
+
20
+
4
21
  ## v1.8.1 (2025-10-16)
5
22
 
6
23
  ### Bug Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: maidr
3
- Version: 1.8.1
3
+ Version: 1.10.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
@@ -152,6 +152,7 @@ for offset, (category, counts) in zip(offsets, weight_counts.items()):
152
152
  ax.set_xticks(x)
153
153
  ax.set_xticklabels(species)
154
154
  ax.set_xlabel("Species")
155
+ ax.set_ylabel("Weight")
155
156
  ax.set_title("Dodged Bar Plot: Penguin Weight Counts")
156
157
  ax.legend(loc="upper right")
157
158
 
@@ -1,4 +1,4 @@
1
- __version__ = "1.8.1"
1
+ __version__ = "1.10.0"
2
2
 
3
3
  from .api import close, render, save_html, show, stacked
4
4
  from .core import Maidr
@@ -14,12 +14,12 @@ from maidr.core.figure_manager import FigureManager
14
14
  def _get_plot_or_current(plot: Any | None) -> Any:
15
15
  """
16
16
  Get the plot object or current matplotlib figure if plot is None.
17
-
17
+
18
18
  Parameters
19
19
  ----------
20
20
  plot : Any or None
21
21
  The plot object. If None, returns the current matplotlib figure.
22
-
22
+
23
23
  Returns
24
24
  -------
25
25
  Any
@@ -28,7 +28,7 @@ def _get_plot_or_current(plot: Any | None) -> Any:
28
28
  if plot is None:
29
29
  # Lazy import matplotlib.pyplot when needed
30
30
  import matplotlib.pyplot as plt
31
-
31
+
32
32
  return plt.gcf()
33
33
  return plot
34
34
 
@@ -48,7 +48,7 @@ def render(plot: Any | None = None) -> Tag:
48
48
  The rendered HTML representation of the plot.
49
49
  """
50
50
  plot = _get_plot_or_current(plot)
51
-
51
+
52
52
  ax = FigureManager.get_axes(plot)
53
53
  if isinstance(ax, list):
54
54
  for axes in ax:
@@ -82,7 +82,7 @@ def show(
82
82
  The display result.
83
83
  """
84
84
  plot = _get_plot_or_current(plot)
85
-
85
+
86
86
  ax = FigureManager.get_axes(plot)
87
87
  if isinstance(ax, list):
88
88
  for axes in ax:
@@ -95,10 +95,11 @@ def show(
95
95
 
96
96
  def save_html(
97
97
  plot: Any | None = None,
98
- *,
98
+ *,
99
99
  file: str,
100
- lib_dir: str | None = "lib",
101
- include_version: bool = True
100
+ lib_dir: str | None = "lib",
101
+ include_version: bool = True,
102
+ data_in_svg: bool = True,
102
103
  ) -> str:
103
104
  """
104
105
  Save a MAIDR plot as HTML file.
@@ -113,6 +114,8 @@ def save_html(
113
114
  Directory name for libraries.
114
115
  include_version : bool, default True
115
116
  Whether to include version information.
117
+ data_in_svg : bool, default True
118
+ Controls where the MAIDR JSON payload is placed in the HTML or SVG.
116
119
 
117
120
  Returns
118
121
  -------
@@ -120,19 +123,21 @@ def save_html(
120
123
  The path to the saved HTML file.
121
124
  """
122
125
  plot = _get_plot_or_current(plot)
123
-
126
+
124
127
  ax = FigureManager.get_axes(plot)
125
128
  htmls = []
126
129
  if isinstance(ax, list):
127
130
  for axes in ax:
128
131
  maidr = FigureManager.get_maidr(axes.get_figure())
129
- htmls.append(maidr._create_html_doc(use_iframe=False))
132
+ htmls.append(maidr._create_html_doc(use_iframe=False, data_in_svg=data_in_svg))
130
133
  return htmls[-1].save_html(
131
134
  file, libdir=lib_dir, include_version=include_version
132
135
  )
133
136
  else:
134
137
  maidr = FigureManager.get_maidr(ax.get_figure())
135
- return maidr.save_html(file, lib_dir=lib_dir, include_version=include_version)
138
+ return maidr.save_html(
139
+ file, lib_dir=lib_dir, include_version=include_version, data_in_svg=data_in_svg
140
+ )
136
141
 
137
142
 
138
143
  def stacked(plot: Axes | BarContainer) -> Maidr:
@@ -150,6 +155,6 @@ def close(plot: Any | None = None) -> None:
150
155
  The plot object to close. If None, uses the current matplotlib figure.
151
156
  """
152
157
  plot = _get_plot_or_current(plot)
153
-
158
+
154
159
  ax = FigureManager.get_axes(plot)
155
160
  FigureManager.destroy(ax.get_figure())
@@ -72,7 +72,12 @@ class Maidr:
72
72
  return self._create_html_tag(use_iframe=True)
73
73
 
74
74
  def save_html(
75
- self, file: str, *, lib_dir: str | None = "lib", include_version: bool = True
75
+ self,
76
+ file: str,
77
+ *,
78
+ lib_dir: str | None = "lib",
79
+ include_version: bool = True,
80
+ data_in_svg: bool = True,
76
81
  ) -> str:
77
82
  """
78
83
  Save the HTML representation of the figure with MAIDR to a file.
@@ -86,9 +91,11 @@ class Maidr:
86
91
  (relative to the file's directory).
87
92
  include_version : bool, default=True
88
93
  Whether to include the version number in the dependency folder name.
94
+ data_in_svg : bool, default=True
95
+ Controls where the MAIDR JSON payload is placed in the output HTML or SVG.
89
96
  """
90
97
  html = self._create_html_doc(
91
- use_iframe=False
98
+ use_iframe=False, data_in_svg=data_in_svg
92
99
  ) # Always use direct HTML for saving
93
100
  return html.save_html(file, libdir=lib_dir, include_version=include_version)
94
101
 
@@ -180,8 +187,17 @@ class Maidr:
180
187
  else:
181
188
  webbrowser.open(f"file://{html_file_path}")
182
189
 
183
- def _create_html_tag(self, use_iframe: bool = True) -> Tag:
184
- """Create the MAIDR HTML using HTML tags."""
190
+ def _create_html_tag(self, use_iframe: bool = True, data_in_svg: bool = True) -> Tag:
191
+ """Create the MAIDR HTML using HTML tags.
192
+
193
+ Parameters
194
+ ----------
195
+ use_iframe : bool, default=True
196
+ Whether to render the plot inside an iframe (for notebooks and similar envs).
197
+ data_in_svg : bool, default=True
198
+ If True, the MAIDR JSON is embedded in the root <svg> under attribute 'maidr'.
199
+ If False, a <script>var maidr = {...}</script> tag is injected instead.
200
+ """
185
201
  tagged_elements: list[Any] = [
186
202
  element for plot in self._plots for element in plot.elements
187
203
  ]
@@ -191,16 +207,31 @@ class Maidr:
191
207
  for _ in plot.elements:
192
208
  selector_ids.append(self.selector_ids[i])
193
209
 
210
+ # Build schema once so id stays consistent across SVG and global var
211
+ schema = self._flatten_maidr()
212
+
194
213
  with HighlightContextManager.set_maidr_elements(tagged_elements, selector_ids):
195
- svg = self._get_svg()
196
- maidr = f"\nlet maidr = {json.dumps(self._flatten_maidr(), indent=2)}\n"
214
+ svg = self._get_svg(embed_data=data_in_svg, schema=schema)
215
+
216
+ # Generate external payload if data is not embedded in SVG
217
+ maidr = None
218
+ if not data_in_svg:
219
+ maidr = f"\nvar maidr = {json.dumps(schema, indent=2)}\n"
197
220
 
198
221
  # Inject plot's svg and MAIDR structure into html tag.
199
222
  return Maidr._inject_plot(svg, maidr, self.maidr_id, use_iframe)
200
223
 
201
- def _create_html_doc(self, use_iframe: bool = True) -> HTMLDocument:
202
- """Create an HTML document from Tag objects."""
203
- return HTMLDocument(self._create_html_tag(use_iframe), lang="en")
224
+ def _create_html_doc(self, use_iframe: bool = True, data_in_svg: bool = True) -> HTMLDocument:
225
+ """Create an HTML document from Tag objects.
226
+
227
+ Parameters
228
+ ----------
229
+ use_iframe : bool, default=True
230
+ Whether to render the plot inside an iframe (for notebooks and similar envs).
231
+ data_in_svg : bool, default=True
232
+ See _create_html_tag for details on payload placement strategy.
233
+ """
234
+ return HTMLDocument(self._create_html_tag(use_iframe, data_in_svg), lang="en")
204
235
 
205
236
  def _merge_plots_by_subplot_position(self) -> list[MaidrPlot]:
206
237
  """
@@ -312,10 +343,23 @@ class Maidr:
312
343
  for cell in subplot_grid[i]
313
344
  ]
314
345
 
315
- return {"id": Maidr._unique_id(), "subplots": subplot_grid}
346
+ return {
347
+ "id": Maidr._unique_id(),
348
+ "subplots": subplot_grid,
349
+ }
316
350
 
317
- def _get_svg(self) -> HTML:
318
- """Extract the chart SVG from ``matplotlib.figure.Figure``."""
351
+ def _get_svg(self, embed_data: bool = True, schema: dict | None = None) -> HTML:
352
+ """Extract the chart SVG from ``matplotlib.figure.Figure``.
353
+
354
+ Parameters
355
+ ----------
356
+ embed_data : bool, default=True
357
+ If True, embed the MAIDR JSON schema as an attribute named 'maidr' on
358
+ the root <svg> element. If False, do not embed JSON in the SVG.
359
+ schema : dict | None, default=None
360
+ If provided, this schema will be used (ensuring a consistent id across
361
+ the page). If None, a new schema will be generated.
362
+ """
319
363
  svg_buffer = io.StringIO()
320
364
  self._fig.savefig(svg_buffer, format="svg")
321
365
  str_svg = svg_buffer.getvalue()
@@ -323,12 +367,14 @@ class Maidr:
323
367
  etree.register_namespace("svg", "http://www.w3.org/2000/svg")
324
368
  tree_svg = etree.fromstring(str_svg.encode(), parser=None)
325
369
  root_svg = None
326
- # Find the `svg` tag and set unique id if not present else use it.
370
+ # Find the `svg` tag and optionally embed MAIDR data.
327
371
  for element in tree_svg.iter(tag="{http://www.w3.org/2000/svg}svg"):
328
- if "maidr-data" not in element.attrib:
329
- element.attrib["maidr-data"] = json.dumps(
330
- self._flatten_maidr(), indent=2
331
- )
372
+ current_schema = schema if schema is not None else self._flatten_maidr()
373
+ # Ensure SVG id matches schema id in both modes
374
+ if isinstance(current_schema, dict) and "id" in current_schema:
375
+ element.attrib["id"] = str(current_schema["id"]) # ensure match
376
+ if embed_data:
377
+ element.attrib["maidr"] = json.dumps(current_schema, indent=2)
332
378
  root_svg = element
333
379
  break
334
380
 
@@ -355,36 +401,41 @@ class Maidr:
355
401
  return str(uuid.uuid4())
356
402
 
357
403
  @staticmethod
358
- def _inject_plot(plot: HTML, maidr: str, maidr_id, use_iframe: bool = True) -> Tag:
404
+ def _inject_plot(plot: HTML, maidr: str | None, maidr_id, use_iframe: bool = True) -> Tag:
359
405
  """Embed the plot and associated MAIDR scripts into the HTML structure."""
360
406
  # Get the latest version from npm registry
361
407
  MAIDR_TS_CDN_URL = "https://cdn.jsdelivr.net/npm/maidr@latest/dist/maidr.js"
362
408
 
363
409
  script = f"""
364
- if (!document.querySelector('script[src="{MAIDR_TS_CDN_URL}"]'))
365
- {{
366
- var script = document.createElement('script');
367
- script.type = 'module';
368
- script.src = '{MAIDR_TS_CDN_URL}';
369
- script.addEventListener('load', function() {{
370
- window.main();
371
- }});
372
- document.head.appendChild(script);
373
- }} else {{
374
- document.addEventListener('DOMContentLoaded', function (e) {{
375
- window.main();
376
- }});
377
- }}
410
+ (function() {{
411
+ var existing = document.querySelector('script[src="{MAIDR_TS_CDN_URL}"]');
412
+ if (!existing) {{
413
+ var s = document.createElement('script');
414
+ s.src = '{MAIDR_TS_CDN_URL}';
415
+ s.onload = function() {{ if (window.main) window.main(); }};
416
+ document.head.appendChild(s);
417
+ }} else {{
418
+ if (document.readyState === 'loading') {{
419
+ document.addEventListener('DOMContentLoaded', function() {{ if (window.main) window.main(); }});
420
+ }} else {{
421
+ if (window.main) window.main();
422
+ }}
423
+ }}
424
+ }})();
378
425
  """
379
426
 
380
- base_html = tags.div(
427
+ children = [
381
428
  tags.link(
382
429
  rel="stylesheet",
383
430
  href="https://cdn.jsdelivr.net/npm/maidr@latest/dist/maidr_style.css",
384
- ),
385
- tags.script(script, type="text/javascript"),
386
- tags.div(plot),
387
- )
431
+ )
432
+ ]
433
+ if maidr is not None:
434
+ children.append(tags.script(maidr, type="text/javascript"))
435
+ children.append(tags.script(script, type="text/javascript"))
436
+ children.append(tags.div(plot))
437
+
438
+ base_html = tags.div(*children)
388
439
 
389
440
  # is_quarto = os.getenv("IS_QUARTO") == "True"
390
441
 
@@ -1,24 +1,21 @@
1
1
  from __future__ import annotations
2
2
 
3
- import uuid
4
3
  from typing import Union, Dict
5
4
  from matplotlib.axes import Axes
6
- from matplotlib.patches import Rectangle
7
- import numpy as np
5
+ import pandas as pd
8
6
 
9
7
  from maidr.core.enum import PlotType
10
8
  from maidr.core.plot import MaidrPlot
11
9
  from maidr.core.enum.maidr_key import MaidrKey
12
10
  from maidr.exception import ExtractionError
13
- from maidr.util.mplfinance_utils import MplfinanceDataExtractor
14
11
 
15
12
 
16
13
  class CandlestickPlot(MaidrPlot):
17
14
  """
18
15
  Specialized candlestick plot class for mplfinance OHLC data.
19
16
 
20
- This class handles the extraction and processing of candlestick data from mplfinance
21
- plots, including proper date conversion and data validation.
17
+ This class extracts candlestick data directly from the original DataFrame
18
+ without any formatting or transformation.
22
19
  """
23
20
 
24
21
  def __init__(self, axes: list[Axes], **kwargs) -> None:
@@ -34,23 +31,17 @@ class CandlestickPlot(MaidrPlot):
34
31
  Additional keyword arguments.
35
32
  """
36
33
  self.axes = axes
37
- # Ensure there's at least one axis for the superclass init
38
34
  if not axes:
39
35
  raise ValueError("Axes list cannot be empty.")
40
36
  super().__init__(axes[0], PlotType.CANDLESTICK)
41
37
 
42
- # Store custom collections passed from mplfinance patch
38
+ # Store collections passed from mplfinance patch
43
39
  self._maidr_wick_collection = kwargs.get("_maidr_wick_collection", None)
44
40
  self._maidr_body_collection = kwargs.get("_maidr_body_collection", None)
45
- self._maidr_date_nums = kwargs.get("_maidr_date_nums", None)
46
- self._maidr_original_data = kwargs.get(
47
- "_maidr_original_data", None
48
- ) # Store original data
49
- self._maidr_datetime_converter = kwargs.get("_maidr_datetime_converter", None)
41
+ self._maidr_original_data = kwargs.get("_maidr_original_data", None)
50
42
 
51
- # Store the GID for proper selector generation (legacy/shared)
43
+ # Store the GID for selector generation
52
44
  self._maidr_gid = None
53
- # Modern-path separate gids for body and wick
54
45
  self._maidr_body_gid = None
55
46
  self._maidr_wick_gid = None
56
47
  if self._maidr_body_collection:
@@ -62,105 +53,83 @@ class CandlestickPlot(MaidrPlot):
62
53
 
63
54
  def _extract_plot_data(self) -> list[dict]:
64
55
  """
65
- Extract candlestick data from the plot.
66
-
67
- This method processes candlestick plots from both modern (mplfinance.plot) and
68
- legacy (original_flavor) pipelines, extracting OHLC data and setting up
69
- highlighting elements and GIDs.
56
+ Extract candlestick data directly from the original DataFrame.
70
57
 
71
58
  Returns
72
59
  -------
73
60
  list[dict]
74
61
  List of dictionaries containing candlestick data with keys:
75
- - 'value': Date string
62
+ - 'value': Date string (raw from DataFrame index)
76
63
  - 'open': Opening price (float)
77
64
  - 'high': High price (float)
78
65
  - 'low': Low price (float)
79
66
  - 'close': Closing price (float)
80
- - 'volume': Volume (float, typically 0 for candlestick-only plots)
67
+ - 'volume': Volume (float)
81
68
  """
82
-
83
- # Get the custom collections from kwargs
84
69
  body_collection = self._maidr_body_collection
85
70
  wick_collection = self._maidr_wick_collection
86
71
 
87
72
  if body_collection and wick_collection:
88
- # Store the GIDs from the collections (modern path)
73
+ # Store the GIDs from the collections
89
74
  self._maidr_body_gid = body_collection.get_gid()
90
75
  self._maidr_wick_gid = wick_collection.get_gid()
91
- # Keep legacy gid filled for backward compatibility
92
76
  self._maidr_gid = self._maidr_body_gid or self._maidr_wick_gid
93
77
 
94
78
  # Use the original collections for highlighting
95
79
  self._elements = [body_collection, wick_collection]
96
80
 
97
- # Use datetime converter for enhanced data extraction
98
- if self._maidr_datetime_converter is not None:
99
- data = self._maidr_datetime_converter.extract_candlestick_data(
100
- self.axes[0], wick_collection, body_collection
101
- )
102
- return data
103
-
104
- # Fallback to original detection method
105
- if not self.axes:
106
- return []
107
-
108
- ax_ohlc = self.axes[0]
109
-
110
- # Look for Rectangle patches (original_flavor candlestick)
111
- body_rectangles = []
112
- for patch in ax_ohlc.patches:
113
- if isinstance(patch, Rectangle):
114
- body_rectangles.append(patch)
115
-
116
- if body_rectangles:
117
- # Set elements for highlighting
118
- self._elements = body_rectangles
119
-
120
- # Generate a GID for highlighting if none exists
121
- if not self._maidr_gid:
122
- self._maidr_gid = f"maidr-{uuid.uuid4()}"
123
- # Set GID on all rectangles
124
- for rect in body_rectangles:
125
- rect.set_gid(self._maidr_gid)
126
- # Keep a dedicated body gid for legacy dict selectors
127
- self._maidr_body_gid = (
128
- getattr(self, "_maidr_body_gid", None) or self._maidr_gid
129
- )
130
-
131
- # Assign a shared gid to wick Line2D (vertical 2-point lines) on the same axis
132
- wick_lines = []
133
- for line in ax_ohlc.get_lines():
134
- try:
135
- xydata = line.get_xydata()
136
- if xydata is None:
137
- continue
138
- xy_arr = np.asarray(xydata)
139
- if (
140
- xy_arr.ndim == 2
141
- and xy_arr.shape[0] == 2
142
- and xy_arr.shape[1] >= 2
143
- ):
144
- x0 = float(xy_arr[0, 0])
145
- x1 = float(xy_arr[1, 0])
146
- if abs(x0 - x1) < 1e-10:
147
- wick_lines.append(line)
148
- except Exception:
149
- continue
150
- if wick_lines:
151
- if not getattr(self, "_maidr_wick_gid", None):
152
- self._maidr_wick_gid = f"maidr-{uuid.uuid4()}"
153
- for line in wick_lines:
154
- line.set_gid(self._maidr_wick_gid)
155
-
156
- # Use the utility class to extract data
157
- data = MplfinanceDataExtractor.extract_rectangle_candlestick_data(
158
- body_rectangles, self._maidr_date_nums, self._maidr_original_data
159
- )
160
- return data
81
+ # Extract data directly from DataFrame
82
+ if self._maidr_original_data is not None and isinstance(
83
+ self._maidr_original_data, pd.DataFrame
84
+ ):
85
+ return self._extract_from_dataframe(self._maidr_original_data)
161
86
 
162
87
  return []
163
88
 
89
+ def _extract_from_dataframe(self, df: pd.DataFrame) -> list[dict]:
90
+ """
91
+ Extract candlestick data directly from DataFrame without any formatting.
92
+
93
+ Parameters
94
+ ----------
95
+ df : pd.DataFrame
96
+ DataFrame with OHLC data and DatetimeIndex.
97
+
98
+ Returns
99
+ -------
100
+ list[dict]
101
+ List of candlestick data dictionaries with raw values.
102
+ """
103
+ candles = []
104
+
105
+ for i in range(len(df)):
106
+ try:
107
+ # Get date directly from index - raw representation
108
+ date_value = str(df.index[i])
109
+
110
+ # Get OHLC values directly from DataFrame columns
111
+ open_price = float(df.iloc[i]["Open"])
112
+ high_price = float(df.iloc[i]["High"])
113
+ low_price = float(df.iloc[i]["Low"])
114
+ close_price = float(df.iloc[i]["Close"])
115
+
116
+ # Get volume if available, otherwise 0
117
+ volume = float(df.iloc[i].get("Volume", 0.0))
118
+
119
+ candle_data = {
120
+ "value": date_value,
121
+ "open": open_price,
122
+ "high": high_price,
123
+ "low": low_price,
124
+ "close": close_price,
125
+ "volume": volume,
126
+ }
127
+ candles.append(candle_data)
128
+ except (KeyError, IndexError, ValueError, TypeError):
129
+ continue
130
+
131
+ return candles
132
+
164
133
  def _extract_axes_data(self) -> dict:
165
134
  """
166
135
  Extract the plot's axes data including labels.
@@ -6,18 +6,16 @@ from datetime import datetime
6
6
 
7
7
  class DatetimeConverter:
8
8
  """
9
- Enhanced datetime converter that automatically detects time periods
10
- and provides intelligent date/time formatting for mplfinance plots.
9
+ Datetime converter for mplfinance plots.
11
10
 
12
- This utility automatically detects the time period of financial data and formats
13
- datetime values consistently for screen reader accessibility and visual clarity.
11
+ This utility provides datetime value conversion for financial data visualization.
14
12
 
15
13
  Parameters
16
14
  ----------
17
15
  data : pd.DataFrame
18
16
  DataFrame with DatetimeIndex containing financial data.
19
17
  datetime_format : str, optional
20
- Custom datetime format string. If None, automatic format detection is used.
18
+ Custom datetime format string (currently unused, kept for compatibility).
21
19
 
22
20
  Attributes
23
21
  ----------
@@ -49,14 +47,14 @@ class DatetimeConverter:
49
47
  >>>
50
48
  >>> # Get formatted datetime
51
49
  >>> formatted = converter.get_formatted_datetime(0)
52
- >>> print(formatted) # Output: "Jan 15 2024"
50
+ >>> print(formatted) # Output: "2024-01-15 00:00:00"
53
51
  >>>
54
52
  >>> # For time-based data
55
53
  >>> hourly_dates = pd.date_range('2024-01-15 09:00:00', periods=3, freq='H')
56
54
  >>> df_hourly = pd.DataFrame({'Open': [3050, 3078, 3080]}, index=hourly_dates)
57
55
  >>> converter_hourly = create_datetime_converter(df_hourly)
58
56
  >>> formatted_hourly = converter_hourly.get_formatted_datetime(0)
59
- >>> print(formatted_hourly) # Output: "Jan 15 2024 09:00"
57
+ >>> print(formatted_hourly) # Output: "2024-01-15 09:00:00"
60
58
  """
61
59
 
62
60
  def __init__(
@@ -164,9 +162,7 @@ class DatetimeConverter:
164
162
 
165
163
  def get_formatted_datetime(self, index: int) -> Optional[str]:
166
164
  """
167
- Get formatted datetime string for given index using consistent formatting.
168
-
169
- Always includes year for screen reader accessibility.
165
+ Get datetime string for given index.
170
166
 
171
167
  Parameters
172
168
  ----------
@@ -176,13 +172,13 @@ class DatetimeConverter:
176
172
  Returns
177
173
  -------
178
174
  str or None
179
- Formatted datetime string or None if index is invalid.
175
+ Datetime string or None if index is invalid.
180
176
 
181
177
  Examples
182
178
  --------
183
179
  >>> converter = create_datetime_converter(df)
184
180
  >>> formatted = converter.get_formatted_datetime(0)
185
- >>> print(formatted) # "Jan 15 2024" for daily data
181
+ >>> print(formatted) # "2024-01-15 00:00:00"
186
182
  """
187
183
  if index not in self.date_mapping:
188
184
  return None
@@ -192,7 +188,7 @@ class DatetimeConverter:
192
188
 
193
189
  def _format_datetime_custom(self, dt: datetime) -> str:
194
190
  """
195
- Consistent datetime formatting with year always included.
191
+ Format datetime as-is using ISO format.
196
192
 
197
193
  Parameters
198
194
  ----------
@@ -202,24 +198,15 @@ class DatetimeConverter:
202
198
  Returns
203
199
  -------
204
200
  str
205
- Formatted datetime string with consistent pattern.
201
+ Formatted datetime string in ISO format.
206
202
 
207
203
  Notes
208
204
  -----
209
- Formatting rules:
210
- - Daily data: "Jan 15 2024"
211
- - Time-based data: "Jan 15 2024 09:00" or "Jan 15 2024 09:00:30"
212
- - Seconds are only shown when they are non-zero for cleaner display.
205
+ Returns the datetime as a string without smart formatting.
206
+ Output format is "YYYY-MM-DD HH:MM:SS" (e.g., "2024-01-15 00:00:00").
213
207
  """
214
- if self.time_period in ["minute", "intraday", "hour"]:
215
- # Time-based data: include time with optional seconds
216
- if dt.second == 0:
217
- return dt.strftime("%b %d %Y %H:%M")
218
- else:
219
- return dt.strftime("%b %d %Y %H:%M:%S")
220
- else:
221
- # Daily/weekly/monthly data: just date
222
- return dt.strftime("%b %d %Y")
208
+ # Return string representation of datetime
209
+ return str(dt)
223
210
 
224
211
  @property
225
212
  def date_nums(self) -> List[float]: