maidr 1.3.0__tar.gz → 1.4.1__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 (60) hide show
  1. {maidr-1.3.0 → maidr-1.4.1}/PKG-INFO +1 -1
  2. {maidr-1.3.0 → maidr-1.4.1}/maidr/__init__.py +2 -1
  3. {maidr-1.3.0 → maidr-1.4.1}/maidr/core/maidr.py +74 -7
  4. maidr-1.4.1/maidr/core/plot/candlestick.py +147 -0
  5. {maidr-1.3.0 → maidr-1.4.1}/maidr/core/plot/maidr_plot.py +7 -2
  6. {maidr-1.3.0 → maidr-1.4.1}/maidr/core/plot/maidr_plot_factory.py +12 -11
  7. maidr-1.4.1/maidr/core/plot/mplfinance_barplot.py +139 -0
  8. maidr-1.4.1/maidr/core/plot/mplfinance_lineplot.py +273 -0
  9. maidr-1.4.1/maidr/patch/__init__.py +15 -0
  10. maidr-1.4.1/maidr/patch/mplfinance.py +215 -0
  11. maidr-1.4.1/maidr/util/__init__.py +3 -0
  12. maidr-1.4.1/maidr/util/mplfinance_utils.py +409 -0
  13. maidr-1.4.1/maidr/util/plot_detection.py +136 -0
  14. {maidr-1.3.0 → maidr-1.4.1}/pyproject.toml +1 -1
  15. maidr-1.3.0/maidr/core/plot/candlestick.py +0 -239
  16. maidr-1.3.0/maidr/util/__init__.py +0 -0
  17. maidr-1.3.0/maidr/widget/__init__.py +0 -0
  18. {maidr-1.3.0 → maidr-1.4.1}/LICENSE +0 -0
  19. {maidr-1.3.0 → maidr-1.4.1}/README.md +0 -0
  20. {maidr-1.3.0 → maidr-1.4.1}/maidr/api.py +0 -0
  21. {maidr-1.3.0 → maidr-1.4.1}/maidr/core/__init__.py +0 -0
  22. {maidr-1.3.0 → maidr-1.4.1}/maidr/core/context_manager.py +0 -0
  23. {maidr-1.3.0 → maidr-1.4.1}/maidr/core/enum/__init__.py +0 -0
  24. {maidr-1.3.0 → maidr-1.4.1}/maidr/core/enum/library.py +0 -0
  25. {maidr-1.3.0 → maidr-1.4.1}/maidr/core/enum/maidr_key.py +0 -0
  26. {maidr-1.3.0 → maidr-1.4.1}/maidr/core/enum/plot_type.py +0 -0
  27. {maidr-1.3.0 → maidr-1.4.1}/maidr/core/enum/smooth_keywords.py +0 -0
  28. {maidr-1.3.0 → maidr-1.4.1}/maidr/core/figure_manager.py +0 -0
  29. {maidr-1.3.0 → maidr-1.4.1}/maidr/core/plot/__init__.py +0 -0
  30. {maidr-1.3.0 → maidr-1.4.1}/maidr/core/plot/barplot.py +0 -0
  31. {maidr-1.3.0 → maidr-1.4.1}/maidr/core/plot/boxplot.py +0 -0
  32. {maidr-1.3.0 → maidr-1.4.1}/maidr/core/plot/grouped_barplot.py +0 -0
  33. {maidr-1.3.0 → maidr-1.4.1}/maidr/core/plot/heatmap.py +0 -0
  34. {maidr-1.3.0 → maidr-1.4.1}/maidr/core/plot/histogram.py +0 -0
  35. {maidr-1.3.0 → maidr-1.4.1}/maidr/core/plot/lineplot.py +0 -0
  36. {maidr-1.3.0 → maidr-1.4.1}/maidr/core/plot/regplot.py +0 -0
  37. {maidr-1.3.0 → maidr-1.4.1}/maidr/core/plot/scatterplot.py +0 -0
  38. {maidr-1.3.0 → maidr-1.4.1}/maidr/exception/__init__.py +0 -0
  39. {maidr-1.3.0 → maidr-1.4.1}/maidr/exception/extraction_error.py +0 -0
  40. {maidr-1.3.0 → maidr-1.4.1}/maidr/patch/barplot.py +0 -0
  41. {maidr-1.3.0 → maidr-1.4.1}/maidr/patch/boxplot.py +0 -0
  42. {maidr-1.3.0 → maidr-1.4.1}/maidr/patch/candlestick.py +0 -0
  43. {maidr-1.3.0 → maidr-1.4.1}/maidr/patch/clear.py +0 -0
  44. {maidr-1.3.0 → maidr-1.4.1}/maidr/patch/common.py +0 -0
  45. {maidr-1.3.0 → maidr-1.4.1}/maidr/patch/heatmap.py +0 -0
  46. {maidr-1.3.0 → maidr-1.4.1}/maidr/patch/highlight.py +0 -0
  47. {maidr-1.3.0 → maidr-1.4.1}/maidr/patch/histogram.py +0 -0
  48. {maidr-1.3.0 → maidr-1.4.1}/maidr/patch/kdeplot.py +0 -0
  49. {maidr-1.3.0 → maidr-1.4.1}/maidr/patch/lineplot.py +0 -0
  50. {maidr-1.3.0 → maidr-1.4.1}/maidr/patch/regplot.py +0 -0
  51. {maidr-1.3.0 → maidr-1.4.1}/maidr/patch/scatterplot.py +0 -0
  52. {maidr-1.3.0 → maidr-1.4.1}/maidr/util/dedup_utils.py +0 -0
  53. {maidr-1.3.0 → maidr-1.4.1}/maidr/util/environment.py +0 -0
  54. {maidr-1.3.0 → maidr-1.4.1}/maidr/util/mixin/__init__.py +0 -0
  55. {maidr-1.3.0 → maidr-1.4.1}/maidr/util/mixin/extractor_mixin.py +0 -0
  56. {maidr-1.3.0 → maidr-1.4.1}/maidr/util/mixin/merger_mixin.py +0 -0
  57. {maidr-1.3.0 → maidr-1.4.1}/maidr/util/regression_line_utils.py +0 -0
  58. {maidr-1.3.0 → maidr-1.4.1}/maidr/util/svg_utils.py +0 -0
  59. {maidr-1.3.0/maidr/patch → maidr-1.4.1/maidr/widget}/__init__.py +0 -0
  60. {maidr-1.3.0 → maidr-1.4.1}/maidr/widget/shiny.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: maidr
3
- Version: 1.3.0
3
+ Version: 1.4.1
4
4
  Summary: Multimodal Access and Interactive Data Representations
5
5
  License: GPL-3.0-or-later
6
6
  Keywords: accessibility,visualization,sonification,braille,tactile,multimodal,data representation,blind,low vision,visual impairments
@@ -1,4 +1,4 @@
1
- __version__ = "1.3.0"
1
+ __version__ = "1.4.1"
2
2
 
3
3
  from .api import close, render, save_html, show, stacked
4
4
  from .core import Maidr
@@ -15,6 +15,7 @@ from .patch import (
15
15
  scatterplot,
16
16
  regplot,
17
17
  kdeplot,
18
+ mplfinance,
18
19
  )
19
20
 
20
21
  __all__ = [
@@ -1,6 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from datetime import datetime
4
+ import urllib.request
5
+ import urllib.error
6
+ import json
4
7
 
5
8
  import io
6
9
  import json
@@ -22,6 +25,11 @@ from maidr.core.plot import MaidrPlot
22
25
  from maidr.util.environment import Environment
23
26
  from maidr.util.dedup_utils import deduplicate_smooth_and_line
24
27
 
28
+ # Module-level cache for version to avoid repeated API calls
29
+ _MAIDR_VERSION_CACHE: str | None = None
30
+ _MAIDR_VERSION_CACHE_TIME: float = 0.0
31
+ _MAIDR_CACHE_DURATION = 3600 # Cache for 1 hour
32
+
25
33
 
26
34
  class Maidr:
27
35
  """
@@ -34,6 +42,8 @@ class Maidr:
34
42
  The matplotlib figure associated with this instance.
35
43
  _plots : list[MaidrPlot]
36
44
  A list of MaidrPlot objects which hold additional plot-specific configurations.
45
+ _cached_version : str | None
46
+ Cached version of maidr from npm registry to avoid repeated API calls.
37
47
 
38
48
  Methods
39
49
  -------
@@ -271,20 +281,77 @@ class Maidr:
271
281
  """Generate a unique identifier string using UUID4."""
272
282
  return str(uuid.uuid4())
273
283
 
284
+ @staticmethod
285
+ def _get_latest_maidr_version() -> str:
286
+ """
287
+ Query the npm registry API to get the latest version of maidr with caching.
288
+
289
+ Returns
290
+ -------
291
+ str
292
+ The latest version of maidr from npm registry, or 'latest' as fallback.
293
+ """
294
+ import time
295
+
296
+ global _MAIDR_VERSION_CACHE, _MAIDR_VERSION_CACHE_TIME
297
+
298
+ # Check if version fetching is disabled via environment variable
299
+ if os.getenv("MAIDR_DISABLE_VERSION_FETCH", "").lower() in ("true", "1", "yes"):
300
+ return "latest"
301
+
302
+ current_time = time.time()
303
+
304
+ # Check if we have a valid cached version
305
+ if (
306
+ _MAIDR_VERSION_CACHE is not None
307
+ and current_time - _MAIDR_VERSION_CACHE_TIME < _MAIDR_CACHE_DURATION
308
+ ):
309
+ return _MAIDR_VERSION_CACHE
310
+
311
+ try:
312
+ # Query npm registry API for maidr package
313
+ with urllib.request.urlopen(
314
+ "https://registry.npmjs.org/maidr/latest", timeout=5 # 5 second timeout
315
+ ) as response:
316
+ if response.status == 200:
317
+ data = json.loads(response.read().decode("utf-8"))
318
+ version = data.get("version", "latest")
319
+
320
+ # Cache the successful result
321
+ _MAIDR_VERSION_CACHE = version
322
+ _MAIDR_VERSION_CACHE_TIME = current_time
323
+
324
+ return version
325
+
326
+ except Exception:
327
+ # Any error - just use latest
328
+ pass
329
+
330
+ # Fallback to 'latest' if API call fails
331
+ return "latest"
332
+
333
+ @staticmethod
334
+ def clear_version_cache() -> None:
335
+ """Clear the cached version to force a fresh API call on next request."""
336
+ global _MAIDR_VERSION_CACHE, _MAIDR_VERSION_CACHE_TIME
337
+ _MAIDR_VERSION_CACHE = None
338
+ _MAIDR_VERSION_CACHE_TIME = 0.0
339
+
274
340
  @staticmethod
275
341
  def _inject_plot(plot: HTML, maidr: str, maidr_id, use_iframe: bool = True) -> Tag:
276
342
  """Embed the plot and associated MAIDR scripts into the HTML structure."""
277
- # MAIDR_TS_CDN_URL = "http://localhost:8888/tree/maidr/core/maidr.js" # DEMO URL
278
- MAIDR_TS_CDN_URL = "https://cdn.jsdelivr.net/npm/maidr@latest/dist/maidr.js"
279
- # Append a query parameter (using TIMESTAMP) to bust the cache (so that the latest (non-cached) version is always loaded).
280
- TIMESTAMP = datetime.now().strftime("%Y%m%d%H%M%S")
343
+ # Get the latest version from npm registry
344
+ latest_version = Maidr._get_latest_maidr_version()
345
+ MAIDR_TS_CDN_URL = (
346
+ f"https://cdn.jsdelivr.net/npm/maidr@{latest_version}/dist/maidr.js"
347
+ )
281
348
 
282
349
  script = f"""
283
- if (!document.querySelector('script[src="{MAIDR_TS_CDN_URL}?v={TIMESTAMP}"]'))
350
+ if (!document.querySelector('script[src="{MAIDR_TS_CDN_URL}"]'))
284
351
  {{
285
352
  var script = document.createElement('script');
286
353
  script.type = 'module';
287
- script.src = '{MAIDR_TS_CDN_URL}?v={TIMESTAMP}';
354
+ script.src = '{MAIDR_TS_CDN_URL}';
288
355
  script.addEventListener('load', function() {{
289
356
  window.main();
290
357
  }});
@@ -299,7 +366,7 @@ class Maidr:
299
366
  base_html = tags.div(
300
367
  tags.link(
301
368
  rel="stylesheet",
302
- href="https://cdn.jsdelivr.net/npm/maidr/dist/maidr_style.css",
369
+ href=f"https://cdn.jsdelivr.net/npm/maidr@{latest_version}/dist/maidr_style.css",
303
370
  ),
304
371
  tags.script(script, type="text/javascript"),
305
372
  tags.div(plot),
@@ -0,0 +1,147 @@
1
+ from __future__ import annotations
2
+
3
+ import matplotlib.dates as mdates
4
+ import numpy as np
5
+ from matplotlib.axes import Axes
6
+ from matplotlib.patches import Rectangle
7
+
8
+ from maidr.core.enum import PlotType
9
+ from maidr.core.plot import MaidrPlot
10
+ from maidr.core.enum.maidr_key import MaidrKey
11
+ from maidr.util.mplfinance_utils import MplfinanceDataExtractor
12
+
13
+
14
+ class CandlestickPlot(MaidrPlot):
15
+ """
16
+ Specialized candlestick plot class for mplfinance OHLC data.
17
+
18
+ This class handles the extraction and processing of candlestick data from mplfinance
19
+ plots, including proper date conversion and data validation.
20
+ """
21
+
22
+ def __init__(self, axes: list[Axes], **kwargs) -> None:
23
+ """
24
+ Initialize the CandlestickPlot.
25
+
26
+ Parameters
27
+ ----------
28
+ axes : list[Axes]
29
+ A list of Matplotlib Axes objects. Expected to contain at least
30
+ one Axes for OHLC data, and optionally a second for volume data.
31
+ **kwargs : dict
32
+ Additional keyword arguments.
33
+ """
34
+ self.axes = axes
35
+ # Ensure there's at least one axis for the superclass init
36
+ if not axes:
37
+ raise ValueError("Axes list cannot be empty.")
38
+ super().__init__(axes[0], PlotType.CANDLESTICK)
39
+
40
+ # Store custom collections passed from mplfinance patch
41
+ self._maidr_wick_collection = kwargs.get("_maidr_wick_collection", None)
42
+ self._maidr_body_collection = kwargs.get("_maidr_body_collection", None)
43
+ self._maidr_date_nums = kwargs.get("_maidr_date_nums", None)
44
+
45
+ # Store the GID for proper selector generation
46
+ self._maidr_gid = None
47
+ if self._maidr_body_collection:
48
+ self._maidr_gid = self._maidr_body_collection.get_gid()
49
+ elif self._maidr_wick_collection:
50
+ self._maidr_gid = self._maidr_wick_collection.get_gid()
51
+
52
+ def _extract_plot_data(self) -> list[dict]:
53
+ """Extract candlestick data from the plot."""
54
+
55
+ # Get the custom collections from kwargs
56
+ body_collection = self._maidr_body_collection
57
+ wick_collection = self._maidr_wick_collection
58
+
59
+ if body_collection and wick_collection:
60
+ # Store the GID from the body collection for highlighting
61
+ self._maidr_gid = body_collection.get_gid()
62
+
63
+ # Use the original collections for highlighting
64
+ self._elements = [body_collection, wick_collection]
65
+
66
+ # Use the utility class to extract data
67
+ return MplfinanceDataExtractor.extract_candlestick_data(
68
+ body_collection, wick_collection, self._maidr_date_nums
69
+ )
70
+
71
+ # Fallback to original detection method
72
+ if not self.axes:
73
+ return []
74
+
75
+ ax_ohlc = self.axes[0]
76
+ candles = []
77
+
78
+ # Look for Rectangle patches (original_flavor candlestick)
79
+ body_rectangles = []
80
+ for patch in ax_ohlc.patches:
81
+ if isinstance(patch, Rectangle):
82
+ body_rectangles.append(patch)
83
+
84
+ if body_rectangles:
85
+ # Set elements for highlighting
86
+ self._elements = body_rectangles
87
+
88
+ # Generate a GID for highlighting if none exists
89
+ if not self._maidr_gid:
90
+ import uuid
91
+
92
+ self._maidr_gid = f"maidr-{uuid.uuid4()}"
93
+ # Set GID on all rectangles
94
+ for rect in body_rectangles:
95
+ rect.set_gid(self._maidr_gid)
96
+
97
+ # Use the utility class to extract data
98
+ return MplfinanceDataExtractor.extract_rectangle_candlestick_data(
99
+ body_rectangles, self._maidr_date_nums
100
+ )
101
+
102
+ return []
103
+
104
+ def _extract_axes_data(self) -> dict:
105
+ """
106
+ Extract the plot's axes data including labels.
107
+
108
+ Returns
109
+ -------
110
+ dict
111
+ Dictionary containing x and y axis labels.
112
+ """
113
+ x_labels = self.ax.get_xlabel()
114
+ if not x_labels:
115
+ x_labels = self.extract_shared_xlabel(self.ax)
116
+ if not x_labels:
117
+ x_labels = "X"
118
+ return {MaidrKey.X: x_labels, MaidrKey.Y: self.ax.get_ylabel()}
119
+
120
+ def _get_selector(self) -> str:
121
+ """Return the CSS selector for highlighting candlestick elements in the SVG output."""
122
+ # Use the stored GID if available, otherwise fall back to generic selector
123
+ if self._maidr_gid:
124
+ # Use the full GID as the id attribute (since that's what's in the SVG)
125
+ selector = (
126
+ f"g[id='{self._maidr_gid}'] > path, g[id='{self._maidr_gid}'] > rect"
127
+ )
128
+ else:
129
+ selector = "g[maidr='true'] > path, g[maidr='true'] > rect"
130
+ return selector
131
+
132
+ def render(self) -> dict:
133
+ """Initialize the MAIDR schema dictionary with basic plot information."""
134
+ title = "Candlestick Chart"
135
+
136
+ maidr_schema = {
137
+ MaidrKey.TYPE: self.type,
138
+ MaidrKey.TITLE: title,
139
+ MaidrKey.AXES: self._extract_axes_data(),
140
+ MaidrKey.DATA: self._extract_plot_data(),
141
+ }
142
+
143
+ # Include selector only if the plot supports highlighting.
144
+ if self._support_highlighting:
145
+ maidr_schema[MaidrKey.SELECTOR] = self._get_selector()
146
+
147
+ return maidr_schema
@@ -42,8 +42,13 @@ class MaidrPlot(ABC):
42
42
  self._support_highlighting = True
43
43
  self._elements = []
44
44
  ss = self.ax.get_subplotspec()
45
- self.row_index = ss.rowspan.start
46
- self.col_index = ss.colspan.start
45
+ # Handle cases where subplotspec is None (dynamically created axes)
46
+ if ss is not None:
47
+ self.row_index = ss.rowspan.start
48
+ self.col_index = ss.colspan.start
49
+ else:
50
+ self.row_index = 0
51
+ self.col_index = 0
47
52
 
48
53
  # MAIDR data
49
54
  self.type = plot_type
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from matplotlib.axes import Axes
4
-
5
4
  from maidr.core.enum import PlotType
6
5
  from maidr.core.plot.barplot import BarPlot
7
6
  from maidr.core.plot.boxplot import BoxPlot
@@ -13,6 +12,9 @@ from maidr.core.plot.lineplot import MultiLinePlot
13
12
  from maidr.core.plot.maidr_plot import MaidrPlot
14
13
  from maidr.core.plot.scatterplot import ScatterPlot
15
14
  from maidr.core.plot.regplot import SmoothPlot
15
+ from maidr.core.plot.mplfinance_barplot import MplfinanceBarPlot
16
+ from maidr.core.plot.mplfinance_lineplot import MplfinanceLinePlot
17
+ from maidr.util.plot_detection import PlotDetectionUtils
16
18
 
17
19
 
18
20
  class MaidrPlotFactory:
@@ -38,17 +40,13 @@ class MaidrPlotFactory:
38
40
  single_ax = ax
39
41
 
40
42
  if plot_type == PlotType.CANDLESTICK:
41
- if isinstance(ax, list):
42
- # If ax is a list of lists, flatten it
43
- if ax and isinstance(ax[0], list):
44
- axes = ax[0] # Take the first inner list
45
- else:
46
- axes = ax # Use the list as-is
47
- else:
48
- axes = [ax] # Wrap single axes in list
43
+ axes = PlotDetectionUtils.get_candlestick_axes(ax)
49
44
  return CandlestickPlot(axes, **kwargs)
50
45
  elif PlotType.BAR == plot_type or PlotType.COUNT == plot_type:
51
- return BarPlot(single_ax)
46
+ if PlotDetectionUtils.is_mplfinance_bar_plot(**kwargs):
47
+ return MplfinanceBarPlot(single_ax, **kwargs)
48
+ else:
49
+ return BarPlot(single_ax)
52
50
  elif PlotType.BOX == plot_type:
53
51
  return BoxPlot(single_ax, **kwargs)
54
52
  elif PlotType.HEAT == plot_type:
@@ -56,7 +54,10 @@ class MaidrPlotFactory:
56
54
  elif PlotType.HIST == plot_type:
57
55
  return HistPlot(single_ax)
58
56
  elif PlotType.LINE == plot_type:
59
- return MultiLinePlot(single_ax)
57
+ if PlotDetectionUtils.is_mplfinance_line_plot(single_ax, **kwargs):
58
+ return MplfinanceLinePlot(single_ax, **kwargs)
59
+ else:
60
+ return MultiLinePlot(single_ax, **kwargs)
60
61
  elif PlotType.SCATTER == plot_type:
61
62
  return ScatterPlot(single_ax)
62
63
  elif PlotType.DODGED == plot_type or PlotType.STACKED == plot_type:
@@ -0,0 +1,139 @@
1
+ from __future__ import annotations
2
+
3
+ from matplotlib.axes import Axes
4
+ from matplotlib.patches import Rectangle
5
+
6
+ from maidr.core.enum import PlotType
7
+ from maidr.core.plot import MaidrPlot
8
+ from maidr.exception import ExtractionError
9
+ from maidr.util.mixin import (
10
+ ContainerExtractorMixin,
11
+ DictMergerMixin,
12
+ LevelExtractorMixin,
13
+ )
14
+ from maidr.util.mplfinance_utils import MplfinanceDataExtractor
15
+
16
+
17
+ class MplfinanceBarPlot(
18
+ MaidrPlot, ContainerExtractorMixin, LevelExtractorMixin, DictMergerMixin
19
+ ):
20
+ """
21
+ Specialized bar plot class for mplfinance volume bars.
22
+
23
+ This class handles the extraction and processing of volume data from mplfinance
24
+ plots, including proper date conversion and data validation.
25
+ """
26
+
27
+ def __init__(self, ax: Axes, **kwargs) -> None:
28
+ super().__init__(ax, PlotType.BAR)
29
+ # Store custom patches passed from mplfinance patch
30
+ self._custom_patches = kwargs.get("_maidr_patches", None)
31
+ # Store date numbers for volume bars (from mplfinance)
32
+ self._maidr_date_nums = kwargs.get("_maidr_date_nums", None)
33
+ # Store custom title
34
+
35
+ def set_title(self, title: str) -> None:
36
+ """Set a custom title for this volume bar plot."""
37
+ self._title = title
38
+
39
+ def _extract_plot_data(self) -> list:
40
+ """Extract data from mplfinance volume patches."""
41
+ if self._custom_patches:
42
+ return self._extract_volume_patches_data(self._custom_patches)
43
+
44
+ # Fallback to original bar plot logic if no custom patches
45
+ plot = self.extract_container(
46
+ self.ax, ContainerExtractorMixin, include_all=True
47
+ )
48
+ data = self._extract_bar_container_data(plot)
49
+ levels = self.extract_level(self.ax)
50
+ formatted_data = []
51
+ combined_data = list(
52
+ zip(levels, data) if plot[0].orientation == "vertical" else zip(data, levels) # type: ignore
53
+ )
54
+ if combined_data: # type: ignore
55
+ for x, y in combined_data: # type: ignore
56
+ formatted_data.append({"x": x, "y": y})
57
+ return formatted_data
58
+ if len(formatted_data) == 0:
59
+ raise ExtractionError(self.type, plot)
60
+ if data is None:
61
+ raise ExtractionError(self.type, plot)
62
+
63
+ return data
64
+
65
+ def _extract_volume_patches_data(self, volume_patches: list[Rectangle]) -> list:
66
+ """
67
+ Extract data from volume Rectangle patches (used by mplfinance volume bars).
68
+ """
69
+ if not volume_patches:
70
+ return []
71
+
72
+ # Sort patches by x-coordinate to maintain order
73
+ sorted_patches = sorted(volume_patches, key=lambda p: p.get_x())
74
+
75
+ # Set elements for highlighting (use the patches directly)
76
+ self._elements = sorted_patches
77
+
78
+ # Use the utility class to extract data
79
+ return MplfinanceDataExtractor.extract_volume_data(
80
+ sorted_patches, self._maidr_date_nums
81
+ )
82
+
83
+ def _extract_bar_container_data(self, plot: list | None) -> list | None:
84
+ """Fallback method for regular bar containers."""
85
+ if plot is None:
86
+ return None
87
+
88
+ # Since v0.13, Seaborn has transitioned from using `list[Patch]` to
89
+ # `list[BarContainers] for plotting bar plots.
90
+ # So, extract data correspondingly based on the level.
91
+ # Flatten all the `list[BarContainer]` to `list[Patch]`.
92
+ patches = [patch for container in plot for patch in container.patches]
93
+ level = self.extract_level(self.ax)
94
+ if level is None or len(level) == 0: # type: ignore
95
+ level = ["" for _ in range(len(patches))] # type: ignore
96
+
97
+ if len(patches) != len(level):
98
+ return None
99
+
100
+ self._elements.extend(patches)
101
+
102
+ return [float(patch.get_height()) for patch in patches]
103
+
104
+ def _extract_axes_data(self) -> dict:
105
+ """Extract axes data with mplfinance-specific cleaning."""
106
+ ax_data = super()._extract_axes_data()
107
+
108
+ # For mplfinance volume plots, clean up the y-axis label
109
+ if self._custom_patches:
110
+ y_label = ax_data.get("y")
111
+ if y_label:
112
+ ax_data["y"] = MplfinanceDataExtractor.clean_axis_label(y_label)
113
+
114
+ return ax_data
115
+
116
+ def _get_selector(self) -> str:
117
+ """Return the CSS selector for highlighting bar elements in the SVG output."""
118
+ # Use the standard working selector that gets replaced with UUID by Maidr class
119
+ # This works for both original bar plots and mplfinance volume bars
120
+ return "g[maidr='true'] > path"
121
+
122
+ def render(self) -> dict:
123
+ """Initialize the MAIDR schema dictionary with basic plot information."""
124
+ from maidr.core.enum.maidr_key import MaidrKey
125
+
126
+ title = "Volume Bar Plot"
127
+
128
+ maidr_schema = {
129
+ MaidrKey.TYPE: self.type,
130
+ MaidrKey.TITLE: title,
131
+ MaidrKey.AXES: self._extract_axes_data(),
132
+ MaidrKey.DATA: self._extract_plot_data(),
133
+ }
134
+
135
+ # Include selector only if the plot supports highlighting.
136
+ if self._support_highlighting:
137
+ maidr_schema[MaidrKey.SELECTOR] = self._get_selector()
138
+
139
+ return maidr_schema