maidr 1.4.0__tar.gz → 1.4.2__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.
- {maidr-1.4.0 → maidr-1.4.2}/PKG-INFO +1 -1
- {maidr-1.4.0 → maidr-1.4.2}/maidr/__init__.py +1 -1
- {maidr-1.4.0 → maidr-1.4.2}/maidr/core/enum/smooth_keywords.py +3 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/core/maidr.py +74 -7
- {maidr-1.4.0 → maidr-1.4.2}/maidr/core/plot/candlestick.py +28 -4
- {maidr-1.4.0 → maidr-1.4.2}/maidr/core/plot/mplfinance_barplot.py +24 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/core/plot/mplfinance_lineplot.py +129 -2
- {maidr-1.4.0 → maidr-1.4.2}/maidr/patch/mplfinance.py +3 -1
- {maidr-1.4.0 → maidr-1.4.2}/maidr/patch/regplot.py +29 -21
- maidr-1.4.2/maidr/util/regression_line_utils.py +69 -0
- {maidr-1.4.0 → maidr-1.4.2}/pyproject.toml +1 -1
- maidr-1.4.0/maidr/util/regression_line_utils.py +0 -19
- {maidr-1.4.0 → maidr-1.4.2}/LICENSE +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/README.md +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/api.py +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/core/__init__.py +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/core/context_manager.py +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/core/enum/__init__.py +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/core/enum/library.py +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/core/enum/maidr_key.py +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/core/enum/plot_type.py +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/core/figure_manager.py +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/core/plot/__init__.py +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/core/plot/barplot.py +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/core/plot/boxplot.py +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/core/plot/grouped_barplot.py +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/core/plot/heatmap.py +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/core/plot/histogram.py +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/core/plot/lineplot.py +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/core/plot/maidr_plot.py +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/core/plot/maidr_plot_factory.py +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/core/plot/regplot.py +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/core/plot/scatterplot.py +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/exception/__init__.py +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/exception/extraction_error.py +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/patch/__init__.py +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/patch/barplot.py +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/patch/boxplot.py +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/patch/candlestick.py +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/patch/clear.py +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/patch/common.py +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/patch/heatmap.py +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/patch/highlight.py +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/patch/histogram.py +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/patch/kdeplot.py +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/patch/lineplot.py +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/patch/scatterplot.py +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/util/__init__.py +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/util/dedup_utils.py +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/util/environment.py +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/util/mixin/__init__.py +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/util/mixin/extractor_mixin.py +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/util/mixin/merger_mixin.py +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/util/mplfinance_utils.py +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/util/plot_detection.py +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/util/svg_utils.py +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/widget/__init__.py +0 -0
- {maidr-1.4.0 → maidr-1.4.2}/maidr/widget/shiny.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: maidr
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.2
|
|
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,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
|
-
#
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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}
|
|
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}
|
|
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),
|
|
@@ -1,15 +1,24 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import matplotlib.dates as mdates
|
|
2
4
|
import numpy as np
|
|
3
5
|
from matplotlib.axes import Axes
|
|
4
6
|
from matplotlib.patches import Rectangle
|
|
5
7
|
|
|
6
|
-
from maidr.core.enum
|
|
7
|
-
from maidr.core.plot
|
|
8
|
+
from maidr.core.enum import PlotType
|
|
9
|
+
from maidr.core.plot import MaidrPlot
|
|
8
10
|
from maidr.core.enum.maidr_key import MaidrKey
|
|
9
11
|
from maidr.util.mplfinance_utils import MplfinanceDataExtractor
|
|
10
12
|
|
|
11
13
|
|
|
12
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
|
+
|
|
13
22
|
def __init__(self, axes: list[Axes], **kwargs) -> None:
|
|
14
23
|
"""
|
|
15
24
|
Initialize the CandlestickPlot.
|
|
@@ -93,7 +102,20 @@ class CandlestickPlot(MaidrPlot):
|
|
|
93
102
|
return []
|
|
94
103
|
|
|
95
104
|
def _extract_axes_data(self) -> dict:
|
|
96
|
-
|
|
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()}
|
|
97
119
|
|
|
98
120
|
def _get_selector(self) -> str:
|
|
99
121
|
"""Return the CSS selector for highlighting candlestick elements in the SVG output."""
|
|
@@ -109,9 +131,11 @@ class CandlestickPlot(MaidrPlot):
|
|
|
109
131
|
|
|
110
132
|
def render(self) -> dict:
|
|
111
133
|
"""Initialize the MAIDR schema dictionary with basic plot information."""
|
|
134
|
+
title = "Candlestick Chart"
|
|
135
|
+
|
|
112
136
|
maidr_schema = {
|
|
113
137
|
MaidrKey.TYPE: self.type,
|
|
114
|
-
MaidrKey.TITLE:
|
|
138
|
+
MaidrKey.TITLE: title,
|
|
115
139
|
MaidrKey.AXES: self._extract_axes_data(),
|
|
116
140
|
MaidrKey.DATA: self._extract_plot_data(),
|
|
117
141
|
}
|
|
@@ -30,6 +30,11 @@ class MplfinanceBarPlot(
|
|
|
30
30
|
self._custom_patches = kwargs.get("_maidr_patches", None)
|
|
31
31
|
# Store date numbers for volume bars (from mplfinance)
|
|
32
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
|
|
33
38
|
|
|
34
39
|
def _extract_plot_data(self) -> list:
|
|
35
40
|
"""Extract data from mplfinance volume patches."""
|
|
@@ -113,3 +118,22 @@ class MplfinanceBarPlot(
|
|
|
113
118
|
# Use the standard working selector that gets replaced with UUID by Maidr class
|
|
114
119
|
# This works for both original bar plots and mplfinance volume bars
|
|
115
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
|
|
@@ -23,6 +23,7 @@ class MplfinanceLinePlot(MaidrPlot, LineExtractorMixin):
|
|
|
23
23
|
|
|
24
24
|
def __init__(self, ax: Axes, **kwargs):
|
|
25
25
|
super().__init__(ax, PlotType.LINE)
|
|
26
|
+
self._line_titles = [] # Store line titles separately
|
|
26
27
|
|
|
27
28
|
def _get_selector(self) -> Union[str, List[str]]:
|
|
28
29
|
"""Return selectors for all lines that have data."""
|
|
@@ -47,6 +48,65 @@ class MplfinanceLinePlot(MaidrPlot, LineExtractorMixin):
|
|
|
47
48
|
|
|
48
49
|
return selectors
|
|
49
50
|
|
|
51
|
+
def _extract_axes_data(self) -> dict:
|
|
52
|
+
"""
|
|
53
|
+
Extract axis labels for the plot.
|
|
54
|
+
|
|
55
|
+
Returns
|
|
56
|
+
-------
|
|
57
|
+
dict
|
|
58
|
+
Dictionary containing x and y axis labels with custom y-label for moving averages.
|
|
59
|
+
"""
|
|
60
|
+
x_labels = self.ax.get_xlabel()
|
|
61
|
+
if not x_labels:
|
|
62
|
+
x_labels = self.extract_shared_xlabel(self.ax)
|
|
63
|
+
if not x_labels:
|
|
64
|
+
x_labels = "Date"
|
|
65
|
+
|
|
66
|
+
# Get the period from the first line for y-axis label
|
|
67
|
+
ma_period = self._extract_moving_average_period()
|
|
68
|
+
y_label = (
|
|
69
|
+
f"{ma_period}-day mav price ($)"
|
|
70
|
+
if ma_period
|
|
71
|
+
else "Moving Average Price ($)"
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
return {MaidrKey.X: x_labels, MaidrKey.Y: y_label}
|
|
75
|
+
|
|
76
|
+
def _extract_moving_average_periods(self) -> List[str]:
|
|
77
|
+
"""
|
|
78
|
+
Extract all moving average periods from the _maidr_ma_period attributes set by the mplfinance patch.
|
|
79
|
+
|
|
80
|
+
Returns
|
|
81
|
+
-------
|
|
82
|
+
List[str]
|
|
83
|
+
List of moving average periods (e.g., ["3", "6", "30"]).
|
|
84
|
+
"""
|
|
85
|
+
all_lines = self.ax.get_lines()
|
|
86
|
+
periods = []
|
|
87
|
+
for line in all_lines:
|
|
88
|
+
# Get the period that was stored by the mplfinance patch
|
|
89
|
+
ma_period = getattr(line, "_maidr_ma_period", None)
|
|
90
|
+
if ma_period is not None:
|
|
91
|
+
periods.append(str(ma_period))
|
|
92
|
+
|
|
93
|
+
# Remove duplicates and sort
|
|
94
|
+
periods = sorted(list(set(periods)))
|
|
95
|
+
|
|
96
|
+
return periods
|
|
97
|
+
|
|
98
|
+
def _extract_moving_average_period(self) -> str:
|
|
99
|
+
"""
|
|
100
|
+
Extract the moving average period from the _maidr_ma_period attribute set by the mplfinance patch.
|
|
101
|
+
|
|
102
|
+
Returns
|
|
103
|
+
-------
|
|
104
|
+
str
|
|
105
|
+
The moving average period (e.g., "3", "6", "30") or empty string if no period found.
|
|
106
|
+
"""
|
|
107
|
+
periods = self._extract_moving_average_periods()
|
|
108
|
+
return periods[0] if periods else ""
|
|
109
|
+
|
|
50
110
|
def _extract_plot_data(self) -> Union[List[List[dict]], None]:
|
|
51
111
|
"""Extract data from mplfinance moving average lines."""
|
|
52
112
|
data = self._extract_line_data()
|
|
@@ -91,6 +151,17 @@ class MplfinanceLinePlot(MaidrPlot, LineExtractorMixin):
|
|
|
91
151
|
line.set_gid(unique_gid)
|
|
92
152
|
|
|
93
153
|
label: str = line.get_label() # type: ignore
|
|
154
|
+
|
|
155
|
+
# Get the period for this specific line
|
|
156
|
+
ma_period = getattr(line, "_maidr_ma_period", None)
|
|
157
|
+
|
|
158
|
+
# Create title for this line
|
|
159
|
+
line_title = (
|
|
160
|
+
f"{ma_period}-Day Moving Average Line Plot"
|
|
161
|
+
if ma_period
|
|
162
|
+
else "Moving Average Line Plot"
|
|
163
|
+
)
|
|
164
|
+
|
|
94
165
|
line_data = []
|
|
95
166
|
|
|
96
167
|
# Check if this line has date numbers from mplfinance
|
|
@@ -117,12 +188,22 @@ class MplfinanceLinePlot(MaidrPlot, LineExtractorMixin):
|
|
|
117
188
|
point_data = {
|
|
118
189
|
MaidrKey.X: x_value,
|
|
119
190
|
MaidrKey.Y: float(y),
|
|
120
|
-
MaidrKey.FILL: (label if not label.startswith("_child") else ""),
|
|
121
191
|
}
|
|
122
192
|
line_data.append(point_data)
|
|
123
193
|
|
|
124
194
|
if line_data:
|
|
125
|
-
|
|
195
|
+
# Create line data with title, axes, and points structure
|
|
196
|
+
line_with_metadata = {
|
|
197
|
+
"title": line_title,
|
|
198
|
+
"axes": {
|
|
199
|
+
"x": "Date",
|
|
200
|
+
"y": f"{ma_period}-day mav price ($)"
|
|
201
|
+
if ma_period
|
|
202
|
+
else "Moving Average Price ($)",
|
|
203
|
+
},
|
|
204
|
+
"points": line_data,
|
|
205
|
+
}
|
|
206
|
+
all_lines_data.append(line_with_metadata)
|
|
126
207
|
|
|
127
208
|
return all_lines_data if all_lines_data else None
|
|
128
209
|
|
|
@@ -144,3 +225,49 @@ class MplfinanceLinePlot(MaidrPlot, LineExtractorMixin):
|
|
|
144
225
|
Date string in YYYY-MM-DD format
|
|
145
226
|
"""
|
|
146
227
|
return MplfinanceDataExtractor._convert_date_num_to_string(x_value)
|
|
228
|
+
|
|
229
|
+
def _extract_line_titles(self) -> List[str]:
|
|
230
|
+
"""
|
|
231
|
+
Extract titles for all moving average lines.
|
|
232
|
+
|
|
233
|
+
Returns
|
|
234
|
+
-------
|
|
235
|
+
List[str]
|
|
236
|
+
List of titles for each line.
|
|
237
|
+
"""
|
|
238
|
+
all_lines = self.ax.get_lines()
|
|
239
|
+
titles = []
|
|
240
|
+
|
|
241
|
+
for line in all_lines:
|
|
242
|
+
ma_period = getattr(line, "_maidr_ma_period", None)
|
|
243
|
+
title = (
|
|
244
|
+
f"{ma_period}-Day Moving Average Line Plot"
|
|
245
|
+
if ma_period
|
|
246
|
+
else "Moving Average Line Plot"
|
|
247
|
+
)
|
|
248
|
+
titles.append(title)
|
|
249
|
+
|
|
250
|
+
return titles
|
|
251
|
+
|
|
252
|
+
def render(self) -> dict:
|
|
253
|
+
"""Initialize the MAIDR schema dictionary with basic plot information."""
|
|
254
|
+
# Use the first line's period for the main title
|
|
255
|
+
ma_period = self._extract_moving_average_period()
|
|
256
|
+
title = (
|
|
257
|
+
f"{ma_period}-Day Moving Averages Line Plot"
|
|
258
|
+
if ma_period
|
|
259
|
+
else "Moving Averages Line Plot"
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
maidr_schema = {
|
|
263
|
+
MaidrKey.TYPE: self.type,
|
|
264
|
+
MaidrKey.TITLE: title,
|
|
265
|
+
MaidrKey.AXES: self._extract_axes_data(),
|
|
266
|
+
MaidrKey.DATA: self._extract_plot_data(),
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
# Include selector only if the plot supports highlighting.
|
|
270
|
+
if self._support_highlighting:
|
|
271
|
+
maidr_schema[MaidrKey.SELECTOR] = self._get_selector()
|
|
272
|
+
|
|
273
|
+
return maidr_schema
|
|
@@ -8,7 +8,6 @@ from matplotlib.lines import Line2D
|
|
|
8
8
|
from maidr.core.enum import PlotType
|
|
9
9
|
from maidr.patch.common import common
|
|
10
10
|
from maidr.core.context_manager import ContextManager
|
|
11
|
-
from maidr.util.mplfinance_utils import MplfinanceDataExtractor
|
|
12
11
|
|
|
13
12
|
|
|
14
13
|
def mplfinance_plot_patch(wrapped, instance, args, kwargs):
|
|
@@ -150,6 +149,9 @@ def mplfinance_plot_patch(wrapped, instance, args, kwargs):
|
|
|
150
149
|
# Map NaN count to likely moving average period
|
|
151
150
|
estimated_period = nan_count + 1
|
|
152
151
|
|
|
152
|
+
# Store the period directly on the line for easy access
|
|
153
|
+
setattr(line, "_maidr_ma_period", estimated_period)
|
|
154
|
+
|
|
153
155
|
# Create a better label for the line
|
|
154
156
|
label = str(line.get_label())
|
|
155
157
|
if label.startswith("_child"):
|
|
@@ -5,11 +5,10 @@ from matplotlib.axes import Axes
|
|
|
5
5
|
from matplotlib.lines import Line2D
|
|
6
6
|
from maidr.core.enum import PlotType
|
|
7
7
|
from maidr.patch.common import common
|
|
8
|
-
import numpy as np
|
|
9
8
|
from maidr.core.context_manager import ContextManager
|
|
10
9
|
import uuid
|
|
11
10
|
from maidr.core.enum.smooth_keywords import SMOOTH_KEYWORDS
|
|
12
|
-
from maidr.util.regression_line_utils import
|
|
11
|
+
from maidr.util.regression_line_utils import find_smooth_lines_by_label
|
|
13
12
|
|
|
14
13
|
|
|
15
14
|
def regplot(wrapped, instance, args, kwargs) -> Axes:
|
|
@@ -23,27 +22,34 @@ def regplot(wrapped, instance, args, kwargs) -> Axes:
|
|
|
23
22
|
# Prevent any MAIDR layer registration during plotting when scatter=False
|
|
24
23
|
with ContextManager.set_internal_context():
|
|
25
24
|
ax = wrapped(*args, **kwargs)
|
|
25
|
+
|
|
26
26
|
axes = ax if isinstance(ax, Axes) else ax.axes if hasattr(ax, "axes") else None
|
|
27
27
|
if axes is not None:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
#
|
|
32
|
-
|
|
33
|
-
# and only the regression line should be uniquely selectable for accessibility and highlighting.
|
|
34
|
-
# By setting a unique gid, we ensure the backend and frontend can generate a reliable selector
|
|
35
|
-
# (e.g., g[id='maidr-...'] path) that matches only the intended regression line.
|
|
36
|
-
# ---
|
|
37
|
-
if regression_line.get_gid() is None:
|
|
28
|
+
# Find and register all smooth lines
|
|
29
|
+
smooth_lines = find_smooth_lines_by_label(axes)
|
|
30
|
+
for line in smooth_lines:
|
|
31
|
+
# If line doesn't have a gid yet, assign one and register
|
|
32
|
+
if line.get_gid() is None:
|
|
38
33
|
new_gid = f"maidr-{uuid.uuid4()}"
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
34
|
+
line.set_gid(new_gid)
|
|
35
|
+
common(
|
|
36
|
+
PlotType.SMOOTH,
|
|
37
|
+
lambda *a, **k: ax,
|
|
38
|
+
instance,
|
|
39
|
+
args,
|
|
40
|
+
dict(kwargs, regression_line=line),
|
|
41
|
+
)
|
|
42
|
+
else:
|
|
43
|
+
# Even if it has a gid, register it as a smooth layer
|
|
44
|
+
# This handles the case where patched_plot already assigned a gid
|
|
45
|
+
common(
|
|
46
|
+
PlotType.SMOOTH,
|
|
47
|
+
lambda *a, **k: ax,
|
|
48
|
+
instance,
|
|
49
|
+
args,
|
|
50
|
+
dict(kwargs, regression_line=line),
|
|
51
|
+
)
|
|
52
|
+
|
|
47
53
|
return ax
|
|
48
54
|
|
|
49
55
|
|
|
@@ -53,7 +59,8 @@ def patched_plot(wrapped, instance, args, kwargs):
|
|
|
53
59
|
"""
|
|
54
60
|
# Call the original plot function
|
|
55
61
|
lines = wrapped(*args, **kwargs)
|
|
56
|
-
|
|
62
|
+
|
|
63
|
+
# Check each line for smooth keywords and register if found
|
|
57
64
|
for line in lines:
|
|
58
65
|
if isinstance(line, Line2D):
|
|
59
66
|
label = line.get_label() or ""
|
|
@@ -72,6 +79,7 @@ def patched_plot(wrapped, instance, args, kwargs):
|
|
|
72
79
|
args,
|
|
73
80
|
dict(kwargs, regression_line=line),
|
|
74
81
|
)
|
|
82
|
+
|
|
75
83
|
return lines
|
|
76
84
|
|
|
77
85
|
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from matplotlib.lines import Line2D
|
|
2
|
+
from matplotlib.axes import Axes
|
|
3
|
+
import numpy as np
|
|
4
|
+
from typing import List, Union
|
|
5
|
+
from maidr.core.enum.smooth_keywords import SMOOTH_KEYWORDS
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def find_regression_line(axes: Axes) -> Union[Line2D, None]:
|
|
9
|
+
"""
|
|
10
|
+
Helper to find the regression line (Line2D) in the given axes.
|
|
11
|
+
"""
|
|
12
|
+
return next(
|
|
13
|
+
(
|
|
14
|
+
artist
|
|
15
|
+
for artist in axes.get_children()
|
|
16
|
+
if isinstance(artist, Line2D)
|
|
17
|
+
and artist.get_label() not in (None, "", "_nolegend_")
|
|
18
|
+
and artist.get_xydata() is not None
|
|
19
|
+
and np.asarray(artist.get_xydata()).size > 0
|
|
20
|
+
),
|
|
21
|
+
None,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def find_smooth_lines_by_label(axes: Axes) -> List[Line2D]:
|
|
26
|
+
"""
|
|
27
|
+
Helper to find all smooth/regression lines (Line2D) in the given axes by checking their labels.
|
|
28
|
+
|
|
29
|
+
This function detects smooth lines by examining their labels for smooth-related keywords
|
|
30
|
+
or generic '_child' labels that are commonly created by seaborn's regplot function.
|
|
31
|
+
|
|
32
|
+
Parameters
|
|
33
|
+
----------
|
|
34
|
+
axes : matplotlib.axes.Axes
|
|
35
|
+
The matplotlib axes object to search for smooth lines.
|
|
36
|
+
|
|
37
|
+
Returns
|
|
38
|
+
-------
|
|
39
|
+
List[Line2D]
|
|
40
|
+
List of Line2D objects that have labels matching smooth keywords or generic '_child' labels.
|
|
41
|
+
|
|
42
|
+
Examples
|
|
43
|
+
--------
|
|
44
|
+
>>> import matplotlib.pyplot as plt
|
|
45
|
+
>>> import seaborn as sns
|
|
46
|
+
>>>
|
|
47
|
+
>>> fig, ax = plt.subplots()
|
|
48
|
+
>>> # Create a regplot with smooth line
|
|
49
|
+
>>> sns.regplot(x=[1, 2, 3], y=[1, 2, 3], ax=ax, lowess=True)
|
|
50
|
+
>>>
|
|
51
|
+
>>> # Find smooth lines
|
|
52
|
+
>>> smooth_lines = find_smooth_lines_by_label(ax)
|
|
53
|
+
>>> print(f"Found {len(smooth_lines)} smooth lines")
|
|
54
|
+
"""
|
|
55
|
+
smooth_lines = []
|
|
56
|
+
for line in axes.get_lines():
|
|
57
|
+
if isinstance(line, Line2D):
|
|
58
|
+
label = line.get_label() or ""
|
|
59
|
+
label_str = str(label)
|
|
60
|
+
|
|
61
|
+
# Check if label matches smooth keywords
|
|
62
|
+
if any(key in label_str.lower() for key in SMOOTH_KEYWORDS):
|
|
63
|
+
smooth_lines.append(line)
|
|
64
|
+
# Also check for seaborn regplot lines with generic labels (like '_child0', '_child1')
|
|
65
|
+
elif label_str.startswith("_child"):
|
|
66
|
+
# Lines with _child labels are likely smooth lines from seaborn regplot
|
|
67
|
+
smooth_lines.append(line)
|
|
68
|
+
|
|
69
|
+
return smooth_lines
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
from matplotlib.lines import Line2D
|
|
2
|
-
import numpy as np
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
def find_regression_line(axes):
|
|
6
|
-
"""
|
|
7
|
-
Helper to find the regression line (Line2D) in the given axes.
|
|
8
|
-
"""
|
|
9
|
-
return next(
|
|
10
|
-
(
|
|
11
|
-
artist
|
|
12
|
-
for artist in axes.get_children()
|
|
13
|
-
if isinstance(artist, Line2D)
|
|
14
|
-
and artist.get_label() not in (None, "", "_nolegend_")
|
|
15
|
-
and artist.get_xydata() is not None
|
|
16
|
-
and np.asarray(artist.get_xydata()).size > 0
|
|
17
|
-
),
|
|
18
|
-
None,
|
|
19
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|