maidr 1.1.0__tar.gz → 1.2.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.
- {maidr-1.1.0 → maidr-1.2.1}/PKG-INFO +2 -1
- {maidr-1.1.0 → maidr-1.2.1}/maidr/__init__.py +2 -1
- {maidr-1.1.0 → maidr-1.2.1}/maidr/core/enum/plot_type.py +1 -0
- {maidr-1.1.0 → maidr-1.2.1}/maidr/core/figure_manager.py +9 -3
- maidr-1.2.1/maidr/core/plot/candlestick.py +239 -0
- {maidr-1.1.0 → maidr-1.2.1}/maidr/core/plot/maidr_plot_factory.py +26 -10
- {maidr-1.1.0 → maidr-1.2.1}/maidr/patch/boxplot.py +1 -2
- maidr-1.2.1/maidr/patch/candlestick.py +57 -0
- {maidr-1.1.0 → maidr-1.2.1}/pyproject.toml +3 -2
- {maidr-1.1.0 → maidr-1.2.1}/LICENSE +0 -0
- {maidr-1.1.0 → maidr-1.2.1}/README.md +0 -0
- {maidr-1.1.0 → maidr-1.2.1}/maidr/api.py +0 -0
- {maidr-1.1.0 → maidr-1.2.1}/maidr/core/__init__.py +0 -0
- {maidr-1.1.0 → maidr-1.2.1}/maidr/core/context_manager.py +0 -0
- {maidr-1.1.0 → maidr-1.2.1}/maidr/core/enum/__init__.py +0 -0
- {maidr-1.1.0 → maidr-1.2.1}/maidr/core/enum/library.py +0 -0
- {maidr-1.1.0 → maidr-1.2.1}/maidr/core/enum/maidr_key.py +0 -0
- {maidr-1.1.0 → maidr-1.2.1}/maidr/core/enum/smooth_keywords.py +0 -0
- {maidr-1.1.0 → maidr-1.2.1}/maidr/core/maidr.py +0 -0
- {maidr-1.1.0 → maidr-1.2.1}/maidr/core/plot/__init__.py +0 -0
- {maidr-1.1.0 → maidr-1.2.1}/maidr/core/plot/barplot.py +0 -0
- {maidr-1.1.0 → maidr-1.2.1}/maidr/core/plot/boxplot.py +0 -0
- {maidr-1.1.0 → maidr-1.2.1}/maidr/core/plot/grouped_barplot.py +0 -0
- {maidr-1.1.0 → maidr-1.2.1}/maidr/core/plot/heatmap.py +0 -0
- {maidr-1.1.0 → maidr-1.2.1}/maidr/core/plot/histogram.py +0 -0
- {maidr-1.1.0 → maidr-1.2.1}/maidr/core/plot/lineplot.py +0 -0
- {maidr-1.1.0 → maidr-1.2.1}/maidr/core/plot/maidr_plot.py +0 -0
- {maidr-1.1.0 → maidr-1.2.1}/maidr/core/plot/regplot.py +0 -0
- {maidr-1.1.0 → maidr-1.2.1}/maidr/core/plot/scatterplot.py +0 -0
- {maidr-1.1.0 → maidr-1.2.1}/maidr/exception/__init__.py +0 -0
- {maidr-1.1.0 → maidr-1.2.1}/maidr/exception/extraction_error.py +0 -0
- {maidr-1.1.0 → maidr-1.2.1}/maidr/patch/__init__.py +0 -0
- {maidr-1.1.0 → maidr-1.2.1}/maidr/patch/barplot.py +0 -0
- {maidr-1.1.0 → maidr-1.2.1}/maidr/patch/clear.py +0 -0
- {maidr-1.1.0 → maidr-1.2.1}/maidr/patch/common.py +0 -0
- {maidr-1.1.0 → maidr-1.2.1}/maidr/patch/heatmap.py +0 -0
- {maidr-1.1.0 → maidr-1.2.1}/maidr/patch/highlight.py +0 -0
- {maidr-1.1.0 → maidr-1.2.1}/maidr/patch/histogram.py +0 -0
- {maidr-1.1.0 → maidr-1.2.1}/maidr/patch/kdeplot.py +0 -0
- {maidr-1.1.0 → maidr-1.2.1}/maidr/patch/lineplot.py +0 -0
- {maidr-1.1.0 → maidr-1.2.1}/maidr/patch/regplot.py +0 -0
- {maidr-1.1.0 → maidr-1.2.1}/maidr/patch/scatterplot.py +0 -0
- {maidr-1.1.0 → maidr-1.2.1}/maidr/util/__init__.py +0 -0
- {maidr-1.1.0 → maidr-1.2.1}/maidr/util/dedup_utils.py +0 -0
- {maidr-1.1.0 → maidr-1.2.1}/maidr/util/environment.py +0 -0
- {maidr-1.1.0 → maidr-1.2.1}/maidr/util/mixin/__init__.py +0 -0
- {maidr-1.1.0 → maidr-1.2.1}/maidr/util/mixin/extractor_mixin.py +0 -0
- {maidr-1.1.0 → maidr-1.2.1}/maidr/util/mixin/merger_mixin.py +0 -0
- {maidr-1.1.0 → maidr-1.2.1}/maidr/util/regression_line_utils.py +0 -0
- {maidr-1.1.0 → maidr-1.2.1}/maidr/util/svg_utils.py +0 -0
- {maidr-1.1.0 → maidr-1.2.1}/maidr/widget/__init__.py +0 -0
- {maidr-1.1.0 → maidr-1.2.1}/maidr/widget/shiny.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: maidr
|
|
3
|
-
Version: 1.1
|
|
3
|
+
Version: 1.2.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
|
|
@@ -29,6 +29,7 @@ Requires-Dist: htmltools (>=0.5)
|
|
|
29
29
|
Requires-Dist: jupyter (>=1.0.0,<2.0.0)
|
|
30
30
|
Requires-Dist: lxml (>=5.1.0)
|
|
31
31
|
Requires-Dist: matplotlib (>=3.8)
|
|
32
|
+
Requires-Dist: mplfinance (>=0.12.10b0,<0.13.0)
|
|
32
33
|
Requires-Dist: numpy (>=1.26)
|
|
33
34
|
Requires-Dist: seaborn (>=0.12)
|
|
34
35
|
Requires-Dist: statsmodels (>=0.14.4,<0.15.0)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
__version__ = "1.1
|
|
1
|
+
__version__ = "1.2.1"
|
|
2
2
|
|
|
3
3
|
from .api import close, render, save_html, show, stacked
|
|
4
4
|
from .core import Maidr
|
|
@@ -6,6 +6,7 @@ from .core.enum import PlotType
|
|
|
6
6
|
from .patch import (
|
|
7
7
|
barplot,
|
|
8
8
|
boxplot,
|
|
9
|
+
candlestick,
|
|
9
10
|
clear,
|
|
10
11
|
heatmap,
|
|
11
12
|
highlight,
|
|
@@ -48,18 +48,24 @@ class FigureManager:
|
|
|
48
48
|
return cls._instance
|
|
49
49
|
|
|
50
50
|
@classmethod
|
|
51
|
-
def create_maidr(
|
|
51
|
+
def create_maidr(
|
|
52
|
+
cls, axes: Axes | list[Axes], plot_type: PlotType, **kwargs
|
|
53
|
+
) -> Maidr:
|
|
52
54
|
"""Create a Maidr instance for the given Axes and plot type, and adds a plot to it."""
|
|
53
|
-
if
|
|
55
|
+
if axes is None:
|
|
54
56
|
raise ValueError("No plot found.")
|
|
55
57
|
if plot_type is None:
|
|
56
58
|
raise ValueError("No plot type found.")
|
|
59
|
+
if isinstance(axes, list):
|
|
60
|
+
ax = axes[0]
|
|
61
|
+
else:
|
|
62
|
+
ax = axes
|
|
57
63
|
if ax.get_figure() is None:
|
|
58
64
|
raise ValueError(f"No figure found for axis: {ax}.")
|
|
59
65
|
|
|
60
66
|
# Add plot to the Maidr object associated with the plot's figure.
|
|
61
67
|
maidr = cls._get_maidr(ax.get_figure(), plot_type)
|
|
62
|
-
plot = MaidrPlotFactory.create(
|
|
68
|
+
plot = MaidrPlotFactory.create(axes, plot_type, **kwargs)
|
|
63
69
|
maidr.plots.append(plot)
|
|
64
70
|
maidr.selector_ids.append(Maidr._unique_id())
|
|
65
71
|
return maidr
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import matplotlib.dates as mdates
|
|
2
|
+
import numpy as np
|
|
3
|
+
from matplotlib.axes import Axes
|
|
4
|
+
from matplotlib.collections import LineCollection, PatchCollection
|
|
5
|
+
from matplotlib.patches import Rectangle
|
|
6
|
+
|
|
7
|
+
from maidr.core.enum.plot_type import PlotType
|
|
8
|
+
from maidr.core.plot.maidr_plot import MaidrPlot
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CandlestickPlot(MaidrPlot):
|
|
12
|
+
def __init__(self, axes: list[Axes], **kwargs) -> None:
|
|
13
|
+
"""
|
|
14
|
+
Initialize the CandlestickPlot.
|
|
15
|
+
|
|
16
|
+
Parameters
|
|
17
|
+
----------
|
|
18
|
+
axes : list[Axes]
|
|
19
|
+
A list of Matplotlib Axes objects. Expected to contain at least
|
|
20
|
+
one Axes for OHLC data, and optionally a second for volume data.
|
|
21
|
+
**kwargs : dict
|
|
22
|
+
Additional keyword arguments.
|
|
23
|
+
"""
|
|
24
|
+
self.axes = axes
|
|
25
|
+
# Ensure there's at least one axis for the superclass init
|
|
26
|
+
if not axes:
|
|
27
|
+
raise ValueError("Axes list cannot be empty.")
|
|
28
|
+
super().__init__(axes[0], PlotType.CANDLESTICK)
|
|
29
|
+
|
|
30
|
+
def _extract_plot_data(self) -> list[dict]:
|
|
31
|
+
"""
|
|
32
|
+
Extracts candlestick (OHLC) and volume data from the plot axes.
|
|
33
|
+
|
|
34
|
+
This method assumes that the candlestick chart is structured with
|
|
35
|
+
LineCollection for wicks and PatchCollection of Rectangles for bodies
|
|
36
|
+
on the first axis (self.axes[0]). Volume data is expected as a
|
|
37
|
+
PatchCollection of Rectangles on the second axis (self.axes[1]), if present.
|
|
38
|
+
Open and close prices are inferred from the body rectangle's color.
|
|
39
|
+
|
|
40
|
+
Returns
|
|
41
|
+
-------
|
|
42
|
+
list[dict]
|
|
43
|
+
A list of dictionaries, where each dictionary represents a data point
|
|
44
|
+
with 'value' (date string YYYY-MM-DD), 'open', 'high', 'low',
|
|
45
|
+
'close', and 'volume'. Fields that cannot be extracted will be None.
|
|
46
|
+
|
|
47
|
+
Examples
|
|
48
|
+
--------
|
|
49
|
+
Assuming a plot has been generated and `plot_instance.axes` is populated:
|
|
50
|
+
>>> data = plot_instance._extract_plot_data()
|
|
51
|
+
>>> print(data[0])
|
|
52
|
+
{
|
|
53
|
+
'value': '2021-01-01',
|
|
54
|
+
'open': 100.0,
|
|
55
|
+
'high': 100.9,
|
|
56
|
+
'low': 99.27,
|
|
57
|
+
'close': 100.75,
|
|
58
|
+
'volume': 171914,
|
|
59
|
+
}
|
|
60
|
+
"""
|
|
61
|
+
if not self.axes:
|
|
62
|
+
return []
|
|
63
|
+
|
|
64
|
+
plot_data: list[dict] = []
|
|
65
|
+
ax_ohlc: Axes = self.axes[0]
|
|
66
|
+
|
|
67
|
+
body_rectangles: list[Rectangle] = []
|
|
68
|
+
wick_collection: LineCollection | None = None
|
|
69
|
+
|
|
70
|
+
# Find candlestick body Rectangles from the OHLC axis
|
|
71
|
+
# Prefer PatchCollection containing Rectangles, fallback to individual Rectangles in ax.patches
|
|
72
|
+
for collection in ax_ohlc.collections:
|
|
73
|
+
if isinstance(collection, PatchCollection):
|
|
74
|
+
# Check if the collection's patches are Rectangles
|
|
75
|
+
try:
|
|
76
|
+
# Iterating a PatchCollection yields its constituent Patch objects
|
|
77
|
+
patches_are_rects = all(
|
|
78
|
+
isinstance(p, Rectangle) for p in collection
|
|
79
|
+
)
|
|
80
|
+
if (
|
|
81
|
+
patches_are_rects and len(collection.get_paths()) > 0
|
|
82
|
+
): # Ensure it has paths and they are Rectangles
|
|
83
|
+
for (
|
|
84
|
+
patch
|
|
85
|
+
) in collection: # Iterate to get actual Rectangle objects
|
|
86
|
+
if isinstance(patch, Rectangle):
|
|
87
|
+
body_rectangles.append(patch)
|
|
88
|
+
if (
|
|
89
|
+
body_rectangles
|
|
90
|
+
): # If we found rectangles this way, assume this is the primary body collection
|
|
91
|
+
break
|
|
92
|
+
except Exception:
|
|
93
|
+
# Could fail if collection is not iterable in the expected way or patches are not Rectangles
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
if not body_rectangles:
|
|
97
|
+
for patch in ax_ohlc.patches:
|
|
98
|
+
if isinstance(patch, Rectangle):
|
|
99
|
+
body_rectangles.append(patch)
|
|
100
|
+
|
|
101
|
+
if not body_rectangles:
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
ax_for_wicks: Axes | None = None
|
|
105
|
+
if len(self.axes) > 1:
|
|
106
|
+
ax_for_wicks = self.axes[1]
|
|
107
|
+
|
|
108
|
+
if ax_for_wicks:
|
|
109
|
+
# Attempt 1: Find wicks in ax_for_wicks.collections (as a LineCollection)
|
|
110
|
+
for collection in ax_for_wicks.collections:
|
|
111
|
+
if isinstance(collection, LineCollection):
|
|
112
|
+
segments = collection.get_segments()
|
|
113
|
+
# Check if the collection contains segments and the first segment looks like a vertical line
|
|
114
|
+
if segments is not None and len(segments) > 0:
|
|
115
|
+
first_segment = segments[0]
|
|
116
|
+
if (
|
|
117
|
+
len(first_segment) == 2 # Segment consists of two points
|
|
118
|
+
and len(first_segment[0]) == 2 # First point has (x, y)
|
|
119
|
+
and len(first_segment[1]) == 2 # Second point has (x, y)
|
|
120
|
+
and np.isclose(
|
|
121
|
+
first_segment[0][0], first_segment[1][0]
|
|
122
|
+
) # X-coordinates are close (vertical)
|
|
123
|
+
):
|
|
124
|
+
wick_collection = collection
|
|
125
|
+
break # Found a suitable LineCollection
|
|
126
|
+
|
|
127
|
+
# Attempt 2: If no LineCollection found, try to find wicks from individual Line2D objects in ax_for_wicks.get_lines()
|
|
128
|
+
if not wick_collection and hasattr(ax_for_wicks, "get_lines"):
|
|
129
|
+
potential_wick_segments = []
|
|
130
|
+
for line in ax_for_wicks.get_lines(): # Iterate over Line2D objects
|
|
131
|
+
x_data, y_data = line.get_data()
|
|
132
|
+
# A wick is typically a vertical line defined by two points.
|
|
133
|
+
if len(x_data) == 2 and len(y_data) == 2:
|
|
134
|
+
if np.isclose(x_data[0], x_data[1]): # Check for verticality
|
|
135
|
+
# Create a segment in the format [[x1, y1], [x2, y2]]
|
|
136
|
+
segment = [
|
|
137
|
+
[x_data[0], y_data[0]],
|
|
138
|
+
[x_data[1], y_data[1]],
|
|
139
|
+
]
|
|
140
|
+
potential_wick_segments.append(segment)
|
|
141
|
+
|
|
142
|
+
if potential_wick_segments:
|
|
143
|
+
# If wick segments were found from individual lines,
|
|
144
|
+
# create a new LineCollection to hold them.
|
|
145
|
+
# This allows the downstream processing logic
|
|
146
|
+
# for wicks to remain consistent.
|
|
147
|
+
# Basic properties are set; color/linestyle
|
|
148
|
+
# are defaults and may not match
|
|
149
|
+
# the original plot's styling if that
|
|
150
|
+
# were relevant for segment extraction.
|
|
151
|
+
wick_collection = LineCollection(
|
|
152
|
+
potential_wick_segments,
|
|
153
|
+
colors="k", # Default color for the temporary collection
|
|
154
|
+
linestyles="solid", # Default linestyle
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# Process wicks into a map: x_coordinate -> (low_price, high_price)
|
|
158
|
+
wick_segments_map: dict[float, tuple[float, float]] = {}
|
|
159
|
+
if wick_collection:
|
|
160
|
+
for seg in wick_collection.get_segments():
|
|
161
|
+
if len(seg) == 2 and len(seg[0]) == 2 and len(seg[1]) == 2:
|
|
162
|
+
# Ensure x-coordinates are (nearly) identical for a vertical wick line
|
|
163
|
+
if np.isclose(seg[0][0], seg[1][0]):
|
|
164
|
+
x_coord = seg[0][0] # Matplotlib date number
|
|
165
|
+
low_price = min(seg[0][1], seg[1][1])
|
|
166
|
+
high_price = max(seg[0][1], seg[1][1])
|
|
167
|
+
wick_segments_map[x_coord] = (low_price, high_price)
|
|
168
|
+
|
|
169
|
+
body_rectangles.sort(key=lambda r: r.get_x())
|
|
170
|
+
|
|
171
|
+
for rect in body_rectangles:
|
|
172
|
+
x_left = rect.get_x()
|
|
173
|
+
width = rect.get_width()
|
|
174
|
+
x_center_num = x_left + width / 2.0
|
|
175
|
+
|
|
176
|
+
try:
|
|
177
|
+
date_dt = mdates.num2date(x_center_num)
|
|
178
|
+
date_str = date_dt.strftime("%Y-%m-%d")
|
|
179
|
+
except ValueError:
|
|
180
|
+
date_str = f"raw_date_{x_center_num:.2f}"
|
|
181
|
+
|
|
182
|
+
y_bottom = rect.get_y()
|
|
183
|
+
height = rect.get_height()
|
|
184
|
+
face_color = rect.get_facecolor() # RGBA tuple
|
|
185
|
+
|
|
186
|
+
# Infer open and close prices
|
|
187
|
+
# Heuristic: Green component > Red component for an "up" candle (close > open)
|
|
188
|
+
# This assumes standard green for up, red for down.
|
|
189
|
+
# A more robust method would involve knowing the exact up/down colors used.
|
|
190
|
+
is_up_candle = (
|
|
191
|
+
face_color[1] > face_color[0]
|
|
192
|
+
) # Compare Green and Red components
|
|
193
|
+
|
|
194
|
+
if is_up_candle: # Typically green: price went up
|
|
195
|
+
open_price = y_bottom
|
|
196
|
+
close_price = y_bottom + height
|
|
197
|
+
else: # Typically red: price went down (or other color)
|
|
198
|
+
close_price = y_bottom
|
|
199
|
+
open_price = y_bottom + height
|
|
200
|
+
|
|
201
|
+
matched_wick_data = None
|
|
202
|
+
closest_wick_x = None
|
|
203
|
+
min_diff = float("inf")
|
|
204
|
+
|
|
205
|
+
for wick_x, prices in wick_segments_map.items():
|
|
206
|
+
diff = abs(wick_x - x_center_num)
|
|
207
|
+
if diff < min_diff:
|
|
208
|
+
min_diff = diff
|
|
209
|
+
closest_wick_x = wick_x
|
|
210
|
+
|
|
211
|
+
# Tolerance for matching wick x-coordinate (e.g., 10% of candle width)
|
|
212
|
+
if closest_wick_x is not None and min_diff < (width * 0.1):
|
|
213
|
+
matched_wick_data = wick_segments_map[closest_wick_x]
|
|
214
|
+
|
|
215
|
+
if matched_wick_data:
|
|
216
|
+
low_price, high_price = matched_wick_data
|
|
217
|
+
# Ensure high >= max(open,close) and low <= min(open,close)
|
|
218
|
+
high_price = max(high_price, open_price, close_price)
|
|
219
|
+
low_price = min(low_price, open_price, close_price)
|
|
220
|
+
else:
|
|
221
|
+
# Fallback if no wick found: high is max(open,close), low is min(open,close)
|
|
222
|
+
high_price = max(open_price, close_price)
|
|
223
|
+
low_price = min(open_price, close_price)
|
|
224
|
+
|
|
225
|
+
plot_data.append(
|
|
226
|
+
{
|
|
227
|
+
"value": date_str,
|
|
228
|
+
"open": open_price,
|
|
229
|
+
"high": high_price,
|
|
230
|
+
"low": low_price,
|
|
231
|
+
"close": close_price,
|
|
232
|
+
"volume": 0,
|
|
233
|
+
}
|
|
234
|
+
)
|
|
235
|
+
self._elements.extend(body_rectangles)
|
|
236
|
+
return plot_data
|
|
237
|
+
|
|
238
|
+
def _extract_axes_data(self) -> dict:
|
|
239
|
+
return {}
|
|
@@ -5,6 +5,7 @@ from matplotlib.axes import Axes
|
|
|
5
5
|
from maidr.core.enum import PlotType
|
|
6
6
|
from maidr.core.plot.barplot import BarPlot
|
|
7
7
|
from maidr.core.plot.boxplot import BoxPlot
|
|
8
|
+
from maidr.core.plot.candlestick import CandlestickPlot
|
|
8
9
|
from maidr.core.plot.grouped_barplot import GroupedBarPlot
|
|
9
10
|
from maidr.core.plot.heatmap import HeatPlot
|
|
10
11
|
from maidr.core.plot.histogram import HistPlot
|
|
@@ -30,22 +31,37 @@ class MaidrPlotFactory:
|
|
|
30
31
|
"""
|
|
31
32
|
|
|
32
33
|
@staticmethod
|
|
33
|
-
def create(ax: Axes, plot_type: PlotType, **kwargs) -> MaidrPlot:
|
|
34
|
-
if
|
|
35
|
-
|
|
34
|
+
def create(ax: Axes | list[Axes], plot_type: PlotType, **kwargs) -> MaidrPlot:
|
|
35
|
+
if isinstance(ax, list):
|
|
36
|
+
single_ax = ax[0]
|
|
37
|
+
else:
|
|
38
|
+
single_ax = ax
|
|
39
|
+
|
|
40
|
+
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
|
|
49
|
+
return CandlestickPlot(axes, **kwargs)
|
|
50
|
+
elif PlotType.BAR == plot_type or PlotType.COUNT == plot_type:
|
|
51
|
+
return BarPlot(single_ax)
|
|
36
52
|
elif PlotType.BOX == plot_type:
|
|
37
|
-
return BoxPlot(
|
|
53
|
+
return BoxPlot(single_ax, **kwargs)
|
|
38
54
|
elif PlotType.HEAT == plot_type:
|
|
39
|
-
return HeatPlot(
|
|
55
|
+
return HeatPlot(single_ax, **kwargs)
|
|
40
56
|
elif PlotType.HIST == plot_type:
|
|
41
|
-
return HistPlot(
|
|
57
|
+
return HistPlot(single_ax)
|
|
42
58
|
elif PlotType.LINE == plot_type:
|
|
43
|
-
return MultiLinePlot(
|
|
59
|
+
return MultiLinePlot(single_ax)
|
|
44
60
|
elif PlotType.SCATTER == plot_type:
|
|
45
|
-
return ScatterPlot(
|
|
61
|
+
return ScatterPlot(single_ax)
|
|
46
62
|
elif PlotType.DODGED == plot_type or PlotType.STACKED == plot_type:
|
|
47
|
-
return GroupedBarPlot(
|
|
63
|
+
return GroupedBarPlot(single_ax, plot_type, **kwargs)
|
|
48
64
|
elif PlotType.SMOOTH == plot_type:
|
|
49
|
-
return SmoothPlot(
|
|
65
|
+
return SmoothPlot(single_ax, **kwargs)
|
|
50
66
|
else:
|
|
51
67
|
raise TypeError(f"Unsupported plot type: {plot_type}.")
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import wrapt
|
|
4
|
-
|
|
5
4
|
from matplotlib.axes import Axes
|
|
6
5
|
|
|
7
|
-
from maidr.core.context_manager import
|
|
6
|
+
from maidr.core.context_manager import BoxplotContextManager, ContextManager
|
|
8
7
|
from maidr.core.enum import PlotType
|
|
9
8
|
from maidr.core.figure_manager import FigureManager
|
|
10
9
|
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import wrapt
|
|
2
|
+
from typing import Any, Callable, Dict, Tuple
|
|
3
|
+
from matplotlib.patches import Rectangle
|
|
4
|
+
from mplfinance import original_flavor
|
|
5
|
+
|
|
6
|
+
from maidr.core.context_manager import ContextManager
|
|
7
|
+
from maidr.core.enum.plot_type import PlotType
|
|
8
|
+
from maidr.core.figure_manager import FigureManager
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def candlestick(
|
|
12
|
+
wrapped: Callable[..., list[Rectangle]],
|
|
13
|
+
instance: Any,
|
|
14
|
+
args: Tuple[Any, ...],
|
|
15
|
+
kwargs: Dict[str, Any],
|
|
16
|
+
) -> list[Rectangle]:
|
|
17
|
+
"""
|
|
18
|
+
Patch function for candlestick plots.
|
|
19
|
+
|
|
20
|
+
This function patches the candlestick plotting function to extract
|
|
21
|
+
candlestick data and create MAIDR plot instances for visualization.
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
wrapped : Callable[..., list[Rectangle]]
|
|
26
|
+
The original candlestick function to be wrapped.
|
|
27
|
+
instance : Any
|
|
28
|
+
The instance of the class where the function is being patched.
|
|
29
|
+
args : Tuple[Any, ...]
|
|
30
|
+
Positional arguments passed to the original function.
|
|
31
|
+
kwargs : Dict[str, Any]
|
|
32
|
+
Keyword arguments passed to the original function.
|
|
33
|
+
|
|
34
|
+
Returns
|
|
35
|
+
-------
|
|
36
|
+
list[Rectangle]
|
|
37
|
+
The list of Rectangle objects returned by the original candlestick function.
|
|
38
|
+
|
|
39
|
+
Notes
|
|
40
|
+
-----
|
|
41
|
+
This wrapper function is used by the wrapt library to patch the mplfinance
|
|
42
|
+
candlestick function. It creates MAIDR plot instances for candlestick charts
|
|
43
|
+
while preserving the original functionality.
|
|
44
|
+
"""
|
|
45
|
+
with ContextManager.set_internal_context():
|
|
46
|
+
# Patch the plotting function.
|
|
47
|
+
plot = wrapped(*args, **kwargs)
|
|
48
|
+
|
|
49
|
+
axes = []
|
|
50
|
+
for ax in plot:
|
|
51
|
+
axes.append(FigureManager.get_axes(ax))
|
|
52
|
+
FigureManager.create_maidr(axes, PlotType.CANDLESTICK)
|
|
53
|
+
|
|
54
|
+
return plot
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
wrapt.wrap_function_wrapper(original_flavor, "_candlestick", candlestick)
|
|
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
|
|
|
4
4
|
|
|
5
5
|
[tool.poetry]
|
|
6
6
|
name = "maidr"
|
|
7
|
-
version = "1.1
|
|
7
|
+
version = "1.2.1"
|
|
8
8
|
description = "Multimodal Access and Interactive Data Representations"
|
|
9
9
|
authors = [
|
|
10
10
|
"JooYoung Seo <jseo1005@illinois.edu>",
|
|
@@ -43,6 +43,7 @@ htmltools = ">=0.5"
|
|
|
43
43
|
jupyter = "^1.0.0"
|
|
44
44
|
wrapt = "^1.16.0"
|
|
45
45
|
virtualenv = "^20.26.6"
|
|
46
|
+
mplfinance = "^0.12.10b0"
|
|
46
47
|
statsmodels = "^0.14.4"
|
|
47
48
|
|
|
48
49
|
[tool.poetry.group.dev.dependencies]
|
|
@@ -72,7 +73,7 @@ match = "(main|master)"
|
|
|
72
73
|
prerelease_token = "rc"
|
|
73
74
|
prerelease = false
|
|
74
75
|
|
|
75
|
-
[tool.semantic_release.changelog]
|
|
76
|
+
[tool.semantic_release.changelog.default_templates]
|
|
76
77
|
template_dir = "templates"
|
|
77
78
|
changelog_file = "CHANGELOG.md"
|
|
78
79
|
exclude_commit_patterns = [
|
|
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
|