maidr 1.7.2__py3-none-any.whl → 1.8.0__py3-none-any.whl
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/__init__.py +1 -1
- maidr/api.py +95 -4
- maidr/core/figure_manager.py +53 -8
- maidr/core/maidr.py +47 -6
- maidr/core/plot/candlestick.py +17 -4
- maidr/core/plot/lineplot.py +3 -1
- maidr/core/plot/maidr_plot.py +1 -0
- maidr/core/plot/mplfinance_lineplot.py +4 -1
- maidr/patch/barplot.py +155 -14
- maidr/patch/mplfinance.py +2 -1
- maidr/util/datetime_conversion.py +35 -20
- maidr/util/environment.py +5 -9
- maidr/util/mixin/extractor_mixin.py +3 -1
- maidr/util/mplfinance_utils.py +8 -8
- {maidr-1.7.2.dist-info → maidr-1.8.0.dist-info}/METADATA +1 -1
- {maidr-1.7.2.dist-info → maidr-1.8.0.dist-info}/RECORD +18 -18
- {maidr-1.7.2.dist-info → maidr-1.8.0.dist-info}/WHEEL +0 -0
- {maidr-1.7.2.dist-info → maidr-1.8.0.dist-info}/licenses/LICENSE +0 -0
maidr/__init__.py
CHANGED
maidr/api.py
CHANGED
|
@@ -11,7 +11,44 @@ from maidr.core.enum import PlotType
|
|
|
11
11
|
from maidr.core.figure_manager import FigureManager
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
def
|
|
14
|
+
def _get_plot_or_current(plot: Any | None) -> Any:
|
|
15
|
+
"""
|
|
16
|
+
Get the plot object or current matplotlib figure if plot is None.
|
|
17
|
+
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
plot : Any or None
|
|
21
|
+
The plot object. If None, returns the current matplotlib figure.
|
|
22
|
+
|
|
23
|
+
Returns
|
|
24
|
+
-------
|
|
25
|
+
Any
|
|
26
|
+
The plot object or current matplotlib figure.
|
|
27
|
+
"""
|
|
28
|
+
if plot is None:
|
|
29
|
+
# Lazy import matplotlib.pyplot when needed
|
|
30
|
+
import matplotlib.pyplot as plt
|
|
31
|
+
|
|
32
|
+
return plt.gcf()
|
|
33
|
+
return plot
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def render(plot: Any | None = None) -> Tag:
|
|
37
|
+
"""
|
|
38
|
+
Render a MAIDR plot to HTML.
|
|
39
|
+
|
|
40
|
+
Parameters
|
|
41
|
+
----------
|
|
42
|
+
plot : Any or None, optional
|
|
43
|
+
The plot object to render. If None, uses the current matplotlib figure.
|
|
44
|
+
|
|
45
|
+
Returns
|
|
46
|
+
-------
|
|
47
|
+
htmltools.Tag
|
|
48
|
+
The rendered HTML representation of the plot.
|
|
49
|
+
"""
|
|
50
|
+
plot = _get_plot_or_current(plot)
|
|
51
|
+
|
|
15
52
|
ax = FigureManager.get_axes(plot)
|
|
16
53
|
if isinstance(ax, list):
|
|
17
54
|
for axes in ax:
|
|
@@ -23,10 +60,29 @@ def render(plot: Any) -> Tag:
|
|
|
23
60
|
|
|
24
61
|
|
|
25
62
|
def show(
|
|
26
|
-
plot: Any,
|
|
63
|
+
plot: Any | None = None,
|
|
27
64
|
renderer: Literal["auto", "ipython", "browser"] = "auto",
|
|
28
65
|
clear_fig: bool = True,
|
|
29
66
|
) -> object:
|
|
67
|
+
"""
|
|
68
|
+
Display a MAIDR plot.
|
|
69
|
+
|
|
70
|
+
Parameters
|
|
71
|
+
----------
|
|
72
|
+
plot : Any or None, optional
|
|
73
|
+
The plot object to display. If None, uses the current matplotlib figure.
|
|
74
|
+
renderer : {"auto", "ipython", "browser"}, default "auto"
|
|
75
|
+
The renderer to use for display.
|
|
76
|
+
clear_fig : bool, default True
|
|
77
|
+
Whether to clear the figure after displaying.
|
|
78
|
+
|
|
79
|
+
Returns
|
|
80
|
+
-------
|
|
81
|
+
object
|
|
82
|
+
The display result.
|
|
83
|
+
"""
|
|
84
|
+
plot = _get_plot_or_current(plot)
|
|
85
|
+
|
|
30
86
|
ax = FigureManager.get_axes(plot)
|
|
31
87
|
if isinstance(ax, list):
|
|
32
88
|
for axes in ax:
|
|
@@ -38,8 +94,33 @@ def show(
|
|
|
38
94
|
|
|
39
95
|
|
|
40
96
|
def save_html(
|
|
41
|
-
plot: Any
|
|
97
|
+
plot: Any | None = None,
|
|
98
|
+
*,
|
|
99
|
+
file: str,
|
|
100
|
+
lib_dir: str | None = "lib",
|
|
101
|
+
include_version: bool = True
|
|
42
102
|
) -> str:
|
|
103
|
+
"""
|
|
104
|
+
Save a MAIDR plot as HTML file.
|
|
105
|
+
|
|
106
|
+
Parameters
|
|
107
|
+
----------
|
|
108
|
+
plot : Any or None, optional
|
|
109
|
+
The plot object to save. If None, uses the current matplotlib figure.
|
|
110
|
+
file : str
|
|
111
|
+
The file path where to save the HTML.
|
|
112
|
+
lib_dir : str or None, default "lib"
|
|
113
|
+
Directory name for libraries.
|
|
114
|
+
include_version : bool, default True
|
|
115
|
+
Whether to include version information.
|
|
116
|
+
|
|
117
|
+
Returns
|
|
118
|
+
-------
|
|
119
|
+
str
|
|
120
|
+
The path to the saved HTML file.
|
|
121
|
+
"""
|
|
122
|
+
plot = _get_plot_or_current(plot)
|
|
123
|
+
|
|
43
124
|
ax = FigureManager.get_axes(plot)
|
|
44
125
|
htmls = []
|
|
45
126
|
if isinstance(ax, list):
|
|
@@ -59,6 +140,16 @@ def stacked(plot: Axes | BarContainer) -> Maidr:
|
|
|
59
140
|
return FigureManager.create_maidr(ax, PlotType.STACKED)
|
|
60
141
|
|
|
61
142
|
|
|
62
|
-
def close(plot: Any) -> None:
|
|
143
|
+
def close(plot: Any | None = None) -> None:
|
|
144
|
+
"""
|
|
145
|
+
Close a MAIDR plot and clean up resources.
|
|
146
|
+
|
|
147
|
+
Parameters
|
|
148
|
+
----------
|
|
149
|
+
plot : Any or None, optional
|
|
150
|
+
The plot object to close. If None, uses the current matplotlib figure.
|
|
151
|
+
"""
|
|
152
|
+
plot = _get_plot_or_current(plot)
|
|
153
|
+
|
|
63
154
|
ax = FigureManager.get_axes(plot)
|
|
64
155
|
FigureManager.destroy(ax.get_figure())
|
maidr/core/figure_manager.py
CHANGED
|
@@ -15,20 +15,24 @@ from maidr.core.plot import MaidrPlotFactory
|
|
|
15
15
|
|
|
16
16
|
class FigureManager:
|
|
17
17
|
"""
|
|
18
|
-
Manages creation and retrieval of Maidr instances associated with
|
|
18
|
+
Manages creation and retrieval of Maidr instances associated with figures.
|
|
19
19
|
|
|
20
|
-
This class provides methods to manage Maidr objects which facilitate the
|
|
21
|
-
manipulation of plots within matplotlib figures.
|
|
20
|
+
This class provides methods to manage Maidr objects which facilitate the
|
|
21
|
+
organization and manipulation of plots within matplotlib figures.
|
|
22
22
|
|
|
23
23
|
Attributes
|
|
24
24
|
----------
|
|
25
25
|
figs : dict
|
|
26
|
-
A dictionary that maps matplotlib Figure objects to their corresponding
|
|
26
|
+
A dictionary that maps matplotlib Figure objects to their corresponding
|
|
27
|
+
Maidr instances.
|
|
28
|
+
PLOT_TYPE_PRIORITY : dict
|
|
29
|
+
Defines the priority order for plot types. Higher numbers take precedence.
|
|
27
30
|
|
|
28
31
|
Methods
|
|
29
32
|
-------
|
|
30
33
|
create_maidr(ax, plot_type, **kwargs)
|
|
31
|
-
Creates a Maidr instance for the given Axes and plot type, and adds a
|
|
34
|
+
Creates a Maidr instance for the given Axes and plot type, and adds a
|
|
35
|
+
plot to it.
|
|
32
36
|
_get_maidr(fig)
|
|
33
37
|
Retrieves or creates a Maidr instance associated with the given Figure.
|
|
34
38
|
get_axes(artist)
|
|
@@ -37,6 +41,21 @@ class FigureManager:
|
|
|
37
41
|
|
|
38
42
|
figs = {}
|
|
39
43
|
|
|
44
|
+
# Define plot type priority order (higher numbers take precedence)
|
|
45
|
+
PLOT_TYPE_PRIORITY = {
|
|
46
|
+
PlotType.BAR: 1,
|
|
47
|
+
PlotType.STACKED: 2,
|
|
48
|
+
PlotType.DODGED: 2, # DODGED and STACKED have same priority
|
|
49
|
+
PlotType.LINE: 1,
|
|
50
|
+
PlotType.SCATTER: 1,
|
|
51
|
+
PlotType.HIST: 1,
|
|
52
|
+
PlotType.BOX: 1,
|
|
53
|
+
PlotType.HEAT: 1,
|
|
54
|
+
PlotType.COUNT: 1,
|
|
55
|
+
PlotType.SMOOTH: 1,
|
|
56
|
+
PlotType.CANDLESTICK: 1,
|
|
57
|
+
}
|
|
58
|
+
|
|
40
59
|
_instance = None
|
|
41
60
|
_lock = threading.Lock()
|
|
42
61
|
|
|
@@ -44,14 +63,15 @@ class FigureManager:
|
|
|
44
63
|
if not cls._instance:
|
|
45
64
|
with cls._lock:
|
|
46
65
|
if not cls._instance:
|
|
47
|
-
cls._instance = super(FigureManager, cls).__new__()
|
|
66
|
+
cls._instance = super(FigureManager, cls).__new__(cls)
|
|
48
67
|
return cls._instance
|
|
49
68
|
|
|
50
69
|
@classmethod
|
|
51
70
|
def create_maidr(
|
|
52
71
|
cls, axes: Axes | list[Axes], plot_type: PlotType, **kwargs
|
|
53
72
|
) -> Maidr:
|
|
54
|
-
"""Create a Maidr instance for the given Axes and plot type, and
|
|
73
|
+
"""Create a Maidr instance for the given Axes and plot type, and
|
|
74
|
+
adds a plot to it."""
|
|
55
75
|
if axes is None:
|
|
56
76
|
raise ValueError("No plot found.")
|
|
57
77
|
if plot_type is None:
|
|
@@ -72,9 +92,34 @@ class FigureManager:
|
|
|
72
92
|
|
|
73
93
|
@classmethod
|
|
74
94
|
def _get_maidr(cls, fig: Figure, plot_type: PlotType) -> Maidr:
|
|
75
|
-
"""
|
|
95
|
+
"""
|
|
96
|
+
Retrieve or create a Maidr instance for the given Figure.
|
|
97
|
+
|
|
98
|
+
If a Maidr instance already exists for the figure, update its plot type
|
|
99
|
+
if the new plot type has higher priority (DODGED/STACKED > BAR).
|
|
100
|
+
|
|
101
|
+
Parameters
|
|
102
|
+
----------
|
|
103
|
+
fig : Figure
|
|
104
|
+
The matplotlib figure to get or create a Maidr instance for.
|
|
105
|
+
plot_type : PlotType
|
|
106
|
+
The plot type to set or update for the Maidr instance.
|
|
107
|
+
|
|
108
|
+
Returns
|
|
109
|
+
-------
|
|
110
|
+
Maidr
|
|
111
|
+
The Maidr instance associated with the figure.
|
|
112
|
+
"""
|
|
76
113
|
if fig not in cls.figs.keys():
|
|
77
114
|
cls.figs[fig] = Maidr(fig, plot_type)
|
|
115
|
+
else:
|
|
116
|
+
# Update plot type if the new type has higher priority
|
|
117
|
+
maidr = cls.figs[fig]
|
|
118
|
+
current_priority = cls.PLOT_TYPE_PRIORITY.get(maidr.plot_type, 0)
|
|
119
|
+
new_priority = cls.PLOT_TYPE_PRIORITY.get(plot_type, 0)
|
|
120
|
+
|
|
121
|
+
if new_priority > current_priority:
|
|
122
|
+
maidr.plot_type = plot_type
|
|
78
123
|
return cls.figs[fig]
|
|
79
124
|
|
|
80
125
|
@classmethod
|
maidr/core/maidr.py
CHANGED
|
@@ -10,6 +10,7 @@ import webbrowser
|
|
|
10
10
|
import subprocess
|
|
11
11
|
from pathlib import Path
|
|
12
12
|
from typing import Any, Literal, cast
|
|
13
|
+
from collections import defaultdict
|
|
13
14
|
|
|
14
15
|
import matplotlib.pyplot as plt
|
|
15
16
|
from htmltools import HTML, HTMLDocument, Tag, tags
|
|
@@ -159,10 +160,7 @@ class Maidr:
|
|
|
159
160
|
if explorer_path:
|
|
160
161
|
try:
|
|
161
162
|
result = subprocess.run(
|
|
162
|
-
[explorer_path, url],
|
|
163
|
-
capture_output=True,
|
|
164
|
-
text=True,
|
|
165
|
-
timeout=10
|
|
163
|
+
[explorer_path, url], capture_output=True, text=True, timeout=10
|
|
166
164
|
)
|
|
167
165
|
|
|
168
166
|
if result.returncode == 0:
|
|
@@ -204,10 +202,49 @@ class Maidr:
|
|
|
204
202
|
"""Create an HTML document from Tag objects."""
|
|
205
203
|
return HTMLDocument(self._create_html_tag(use_iframe), lang="en")
|
|
206
204
|
|
|
205
|
+
def _merge_plots_by_subplot_position(self) -> list[MaidrPlot]:
|
|
206
|
+
"""
|
|
207
|
+
Merge plots by their subplot position, keeping only the first plot per position.
|
|
208
|
+
|
|
209
|
+
For DODGED and STACKED plot types, multiple plots on the same subplot
|
|
210
|
+
should be merged into a single plot since GroupedBarPlot extracts all
|
|
211
|
+
containers from the axes itself.
|
|
212
|
+
|
|
213
|
+
Returns
|
|
214
|
+
-------
|
|
215
|
+
list[MaidrPlot]
|
|
216
|
+
List of plots with one plot per unique subplot position.
|
|
217
|
+
|
|
218
|
+
Examples
|
|
219
|
+
--------
|
|
220
|
+
If we have plots at positions [(0,0), (0,0), (0,1), (1,0)],
|
|
221
|
+
this will return plots at positions [(0,0), (0,1), (1,0)].
|
|
222
|
+
"""
|
|
223
|
+
# Group plots by their subplot position (row, col) using defaultdict
|
|
224
|
+
subplot_groups: dict[tuple[int, int], list[MaidrPlot]] = defaultdict(list)
|
|
225
|
+
|
|
226
|
+
for plot in self._plots:
|
|
227
|
+
# Get subplot position, defaulting to (0, 0) if not set
|
|
228
|
+
position = (getattr(plot, "row_index", 0), getattr(plot, "col_index", 0))
|
|
229
|
+
subplot_groups[position].append(plot)
|
|
230
|
+
|
|
231
|
+
# Keep only the first plot for each subplot position
|
|
232
|
+
# The GroupedBarPlot will extract all containers from that axes
|
|
233
|
+
merged_plots: list[MaidrPlot] = []
|
|
234
|
+
for position_plots in subplot_groups.values():
|
|
235
|
+
merged_plots.append(
|
|
236
|
+
position_plots[0]
|
|
237
|
+
) # Each list is guaranteed to have at least one plot
|
|
238
|
+
|
|
239
|
+
return merged_plots
|
|
240
|
+
|
|
207
241
|
def _flatten_maidr(self) -> dict | list[dict]:
|
|
208
242
|
"""Return a single plot schema or a list of schemas from the Maidr instance."""
|
|
243
|
+
# Handle DODGED/STACKED plots: only keep one plot per subplot position
|
|
244
|
+
# because GroupedBarPlot extracts all containers from the axes itself
|
|
209
245
|
if self.plot_type in (PlotType.DODGED, PlotType.STACKED):
|
|
210
|
-
self._plots =
|
|
246
|
+
self._plots = self._merge_plots_by_subplot_position()
|
|
247
|
+
|
|
211
248
|
# Deduplicate: if any SMOOTH plots exist, remove LINE plots
|
|
212
249
|
self._plots = deduplicate_smooth_and_line(self._plots)
|
|
213
250
|
|
|
@@ -354,7 +391,11 @@ class Maidr:
|
|
|
354
391
|
# Render the plot inside an iframe if in a Jupyter notebook, Google Colab
|
|
355
392
|
# or VSCode notebook. No need for iframe if this is a Quarto document.
|
|
356
393
|
# For TypeScript we will use iframe by default for now
|
|
357
|
-
if use_iframe and (
|
|
394
|
+
if use_iframe and (
|
|
395
|
+
Environment.is_flask()
|
|
396
|
+
or Environment.is_notebook()
|
|
397
|
+
or Environment.is_shiny()
|
|
398
|
+
):
|
|
358
399
|
unique_id = "iframe_" + Maidr._unique_id()
|
|
359
400
|
|
|
360
401
|
def generate_iframe_script(unique_id: str) -> str:
|
maidr/core/plot/candlestick.py
CHANGED
|
@@ -43,7 +43,9 @@ class CandlestickPlot(MaidrPlot):
|
|
|
43
43
|
self._maidr_wick_collection = kwargs.get("_maidr_wick_collection", None)
|
|
44
44
|
self._maidr_body_collection = kwargs.get("_maidr_body_collection", None)
|
|
45
45
|
self._maidr_date_nums = kwargs.get("_maidr_date_nums", None)
|
|
46
|
-
self._maidr_original_data = kwargs.get(
|
|
46
|
+
self._maidr_original_data = kwargs.get(
|
|
47
|
+
"_maidr_original_data", None
|
|
48
|
+
) # Store original data
|
|
47
49
|
self._maidr_datetime_converter = kwargs.get("_maidr_datetime_converter", None)
|
|
48
50
|
|
|
49
51
|
# Store the GID for proper selector generation (legacy/shared)
|
|
@@ -122,7 +124,9 @@ class CandlestickPlot(MaidrPlot):
|
|
|
122
124
|
for rect in body_rectangles:
|
|
123
125
|
rect.set_gid(self._maidr_gid)
|
|
124
126
|
# Keep a dedicated body gid for legacy dict selectors
|
|
125
|
-
self._maidr_body_gid =
|
|
127
|
+
self._maidr_body_gid = (
|
|
128
|
+
getattr(self, "_maidr_body_gid", None) or self._maidr_gid
|
|
129
|
+
)
|
|
126
130
|
|
|
127
131
|
# Assign a shared gid to wick Line2D (vertical 2-point lines) on the same axis
|
|
128
132
|
wick_lines = []
|
|
@@ -132,7 +136,11 @@ class CandlestickPlot(MaidrPlot):
|
|
|
132
136
|
if xydata is None:
|
|
133
137
|
continue
|
|
134
138
|
xy_arr = np.asarray(xydata)
|
|
135
|
-
if
|
|
139
|
+
if (
|
|
140
|
+
xy_arr.ndim == 2
|
|
141
|
+
and xy_arr.shape[0] == 2
|
|
142
|
+
and xy_arr.shape[1] >= 2
|
|
143
|
+
):
|
|
136
144
|
x0 = float(xy_arr[0, 0])
|
|
137
145
|
x1 = float(xy_arr[1, 0])
|
|
138
146
|
if abs(x0 - x1) < 1e-10:
|
|
@@ -176,7 +184,12 @@ class CandlestickPlot(MaidrPlot):
|
|
|
176
184
|
- Legacy path: return a dict with body and shared wick selectors (no open/close keys)
|
|
177
185
|
"""
|
|
178
186
|
# Modern path: build structured selectors using separate gids
|
|
179
|
-
if
|
|
187
|
+
if (
|
|
188
|
+
self._maidr_body_collection
|
|
189
|
+
and self._maidr_wick_collection
|
|
190
|
+
and self._maidr_body_gid
|
|
191
|
+
and self._maidr_wick_gid
|
|
192
|
+
):
|
|
180
193
|
# Determine candle count N
|
|
181
194
|
N = None
|
|
182
195
|
if self._maidr_original_data is not None:
|
maidr/core/plot/lineplot.py
CHANGED
|
@@ -115,7 +115,9 @@ class MultiLinePlot(MaidrPlot, LineExtractorMixin):
|
|
|
115
115
|
line_type = label
|
|
116
116
|
|
|
117
117
|
# Use the new method to extract data with categorical labels
|
|
118
|
-
line_coords = LineExtractorMixin.extract_line_data_with_categorical_labels(
|
|
118
|
+
line_coords = LineExtractorMixin.extract_line_data_with_categorical_labels(
|
|
119
|
+
self.ax, line
|
|
120
|
+
)
|
|
119
121
|
if line_coords is None:
|
|
120
122
|
continue
|
|
121
123
|
|
maidr/core/plot/maidr_plot.py
CHANGED
|
@@ -105,7 +105,10 @@ class MplfinanceLinePlot(MaidrPlot, LineExtractorMixin):
|
|
|
105
105
|
continue
|
|
106
106
|
|
|
107
107
|
# Use datetime converter for enhanced data extraction
|
|
108
|
-
datetime_converter =
|
|
108
|
+
datetime_converter = (
|
|
109
|
+
getattr(line, "_maidr_datetime_converter", None)
|
|
110
|
+
or self._maidr_datetime_converter
|
|
111
|
+
)
|
|
109
112
|
if datetime_converter is not None:
|
|
110
113
|
# Convert x-coordinate (matplotlib index) to formatted datetime
|
|
111
114
|
x_value = datetime_converter.get_formatted_datetime(int(round(x)))
|
maidr/patch/barplot.py
CHANGED
|
@@ -18,9 +18,10 @@ def bar(
|
|
|
18
18
|
|
|
19
19
|
This function patches the bar plotting functions to identify whether the
|
|
20
20
|
plot should be rendered as a normal, stacked, or dodged bar plot.
|
|
21
|
-
It uses the 'bottom' keyword to identify stacked bar plots.
|
|
22
|
-
|
|
23
|
-
then
|
|
21
|
+
It uses the 'bottom' keyword to identify stacked bar plots. For dodged plots,
|
|
22
|
+
it first checks for seaborn-specific indicators (hue parameter with dodge=True),
|
|
23
|
+
then uses robust detection logic that considers both width and context
|
|
24
|
+
to avoid misclassifying simple bar plots with narrow widths as dodged plots.
|
|
24
25
|
|
|
25
26
|
Parameters
|
|
26
27
|
----------
|
|
@@ -33,6 +34,7 @@ def bar(
|
|
|
33
34
|
For a dodged plot, the first argument (x positions) should be numeric.
|
|
34
35
|
kwargs : dict
|
|
35
36
|
Keyword arguments passed to the original function.
|
|
37
|
+
For seaborn plots, may contain 'hue' and 'dodge' parameters.
|
|
36
38
|
|
|
37
39
|
Returns
|
|
38
40
|
-------
|
|
@@ -41,33 +43,172 @@ def bar(
|
|
|
41
43
|
|
|
42
44
|
Examples
|
|
43
45
|
--------
|
|
44
|
-
>>> # For a dodged (grouped) bar plot
|
|
46
|
+
>>> # For a seaborn dodged (grouped) bar plot:
|
|
47
|
+
>>> sns.barplot(data=df, x='category', y='value', hue='group', dodge=True)
|
|
48
|
+
|
|
49
|
+
>>> # For a manual dodged (grouped) bar plot, pass numeric x positions:
|
|
45
50
|
>>> x_positions = np.arange(3)
|
|
46
51
|
>>> ax.bar(x_positions, heights, width, label='Group') # Dodged bar plot.
|
|
47
52
|
"""
|
|
48
53
|
plot_type = PlotType.BAR
|
|
54
|
+
|
|
55
|
+
# Check for stacked plots first (explicit bottom parameter)
|
|
49
56
|
if "bottom" in kwargs:
|
|
50
57
|
bottom = kwargs.get("bottom")
|
|
51
58
|
if bottom is not None:
|
|
52
59
|
plot_type = PlotType.STACKED
|
|
53
60
|
else:
|
|
54
|
-
|
|
55
|
-
|
|
61
|
+
# Check for seaborn-specific dodged plot indicators first
|
|
62
|
+
# This handles seaborn.barplot with hue and dodge=True
|
|
63
|
+
if "hue" in kwargs and kwargs.get("dodge"):
|
|
64
|
+
plot_type = PlotType.DODGED
|
|
56
65
|
else:
|
|
57
|
-
|
|
66
|
+
# Extract width and align parameters
|
|
67
|
+
if len(args) >= 3:
|
|
68
|
+
real_width = args[2]
|
|
69
|
+
else:
|
|
70
|
+
real_width = kwargs.get("width", 0.8)
|
|
58
71
|
|
|
59
|
-
|
|
72
|
+
align = kwargs.get("align", "center")
|
|
60
73
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
74
|
+
# More robust dodged plot detection: consider multiple factors
|
|
75
|
+
# Only classify as DODGED if there are strong indicators of grouping
|
|
76
|
+
should_be_dodged = _should_classify_as_dodged(
|
|
77
|
+
instance, real_width, align, args, kwargs
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
if should_be_dodged:
|
|
81
|
+
plot_type = PlotType.DODGED
|
|
67
82
|
|
|
68
83
|
return common(plot_type, wrapped, instance, args, kwargs)
|
|
69
84
|
|
|
70
85
|
|
|
86
|
+
def _should_classify_as_dodged(
|
|
87
|
+
ax: Any, width: Any, align: str, args: Tuple[Any, ...], kwargs: Dict[str, Any]
|
|
88
|
+
) -> bool:
|
|
89
|
+
"""
|
|
90
|
+
Determine if a bar plot should be classified as dodged based on context.
|
|
91
|
+
|
|
92
|
+
This function uses more sophisticated logic than just checking width < 0.8,
|
|
93
|
+
as simple bar plots with narrow widths should not be considered dodged.
|
|
94
|
+
|
|
95
|
+
Parameters
|
|
96
|
+
----------
|
|
97
|
+
ax : Any
|
|
98
|
+
The axes instance where the plot is being created.
|
|
99
|
+
width : Any
|
|
100
|
+
The width parameter for the bar plot.
|
|
101
|
+
align : str
|
|
102
|
+
The alignment parameter for the bar plot.
|
|
103
|
+
args : tuple
|
|
104
|
+
Positional arguments passed to the bar function.
|
|
105
|
+
kwargs : dict
|
|
106
|
+
Keyword arguments passed to the bar function.
|
|
107
|
+
|
|
108
|
+
Returns
|
|
109
|
+
-------
|
|
110
|
+
bool
|
|
111
|
+
True if the plot should be classified as DODGED, False otherwise.
|
|
112
|
+
|
|
113
|
+
Examples
|
|
114
|
+
--------
|
|
115
|
+
>>> # These should be DODGED:
|
|
116
|
+
>>> ax.bar([0.1, 1.1, 2.1], [1, 2, 3], width=0.4, label='Group A')
|
|
117
|
+
>>> ax.bar([0.4, 1.4, 2.4], [4, 5, 6], width=0.4, label='Group B')
|
|
118
|
+
|
|
119
|
+
>>> # These should remain BAR:
|
|
120
|
+
>>> ax.bar(['A', 'B', 'C'], [1, 2, 3], width=0.6) # Simple categorical bar plot
|
|
121
|
+
"""
|
|
122
|
+
# If align is 'edge', it's likely a dodged plot
|
|
123
|
+
if align == "edge":
|
|
124
|
+
return True
|
|
125
|
+
|
|
126
|
+
# If width is specified and very narrow (< 0.5), more likely to be dodged
|
|
127
|
+
# But only if there are other indicators
|
|
128
|
+
if isinstance(width, (int, float)) and float(width) < 0.5:
|
|
129
|
+
# Check if x positions suggest grouping (numeric positions with fractional parts)
|
|
130
|
+
if len(args) > 0:
|
|
131
|
+
x_positions = args[0]
|
|
132
|
+
if _has_numeric_grouping_pattern(x_positions):
|
|
133
|
+
return True
|
|
134
|
+
|
|
135
|
+
# Check if there are already multiple bar containers on the axes
|
|
136
|
+
# This suggests that this might be part of a grouped bar plot
|
|
137
|
+
if hasattr(ax, "containers") and len(ax.containers) > 0:
|
|
138
|
+
# If there are existing containers, this might be adding to a group
|
|
139
|
+
if isinstance(width, (int, float)) and float(width) < 0.8:
|
|
140
|
+
return True
|
|
141
|
+
|
|
142
|
+
# Check for explicit grouping indicators in kwargs
|
|
143
|
+
if "label" in kwargs and isinstance(width, (int, float)) and float(width) < 0.8:
|
|
144
|
+
# If there's a label and narrow width, it might be part of a group
|
|
145
|
+
# But we need to be conservative here to avoid false positives
|
|
146
|
+
if _has_numeric_grouping_pattern(args[0] if len(args) > 0 else None):
|
|
147
|
+
return True
|
|
148
|
+
|
|
149
|
+
# Default to False - prefer BAR over DODGED for ambiguous cases
|
|
150
|
+
return False
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _has_numeric_grouping_pattern(x_positions: Any) -> bool:
|
|
154
|
+
"""
|
|
155
|
+
Check if x positions suggest a grouping pattern typical of dodged plots.
|
|
156
|
+
|
|
157
|
+
Parameters
|
|
158
|
+
----------
|
|
159
|
+
x_positions : Any
|
|
160
|
+
The x positions for the bar plot.
|
|
161
|
+
|
|
162
|
+
Returns
|
|
163
|
+
-------
|
|
164
|
+
bool
|
|
165
|
+
True if the positions suggest grouping, False otherwise.
|
|
166
|
+
|
|
167
|
+
Examples
|
|
168
|
+
--------
|
|
169
|
+
>>> _has_numeric_grouping_pattern([0.1, 1.1, 2.1]) # True - fractional offsets
|
|
170
|
+
>>> _has_numeric_grouping_pattern(['A', 'B', 'C']) # False - categorical
|
|
171
|
+
>>> _has_numeric_grouping_pattern([0, 1, 2]) # False - simple numeric
|
|
172
|
+
"""
|
|
173
|
+
try:
|
|
174
|
+
# Convert to list if possible (duck typing)
|
|
175
|
+
try:
|
|
176
|
+
positions = list(x_positions)
|
|
177
|
+
except TypeError:
|
|
178
|
+
return False
|
|
179
|
+
|
|
180
|
+
# If all positions are strings, it's categorical (not dodged)
|
|
181
|
+
if all(isinstance(pos, str) for pos in positions):
|
|
182
|
+
return False
|
|
183
|
+
|
|
184
|
+
# If positions are numeric, check for fractional offsets
|
|
185
|
+
# that suggest manual positioning for grouping
|
|
186
|
+
numeric_positions = []
|
|
187
|
+
for pos in positions:
|
|
188
|
+
try:
|
|
189
|
+
numeric_positions.append(float(pos))
|
|
190
|
+
except (ValueError, TypeError):
|
|
191
|
+
return False
|
|
192
|
+
|
|
193
|
+
if len(numeric_positions) < 2:
|
|
194
|
+
return False
|
|
195
|
+
|
|
196
|
+
# Check if positions have fractional parts that suggest manual offset
|
|
197
|
+
# for grouping (e.g., [0.1, 1.1, 2.1] or [0.8, 1.8, 2.8])
|
|
198
|
+
fractional_parts = [pos % 1 for pos in numeric_positions]
|
|
199
|
+
|
|
200
|
+
# If all have the same non-zero fractional part, it suggests grouping
|
|
201
|
+
if all(abs(frac - fractional_parts[0]) < 0.01 for frac in fractional_parts):
|
|
202
|
+
if fractional_parts[0] > 0.01: # Non-zero fractional part
|
|
203
|
+
return True
|
|
204
|
+
|
|
205
|
+
return False
|
|
206
|
+
|
|
207
|
+
except Exception:
|
|
208
|
+
# If anything goes wrong in analysis, default to False
|
|
209
|
+
return False
|
|
210
|
+
|
|
211
|
+
|
|
71
212
|
# Patch matplotlib functions.
|
|
72
213
|
wrapt.wrap_function_wrapper(Axes, "bar", bar)
|
|
73
214
|
wrapt.wrap_function_wrapper(Axes, "barh", bar)
|
maidr/patch/mplfinance.py
CHANGED
|
@@ -72,6 +72,7 @@ def mplfinance_plot_patch(wrapped, instance, args, kwargs):
|
|
|
72
72
|
# fallback: use index if it's a DatetimeIndex
|
|
73
73
|
try:
|
|
74
74
|
import matplotlib.dates as mdates
|
|
75
|
+
|
|
75
76
|
date_nums = [mdates.date2num(d) for d in data.index]
|
|
76
77
|
except Exception:
|
|
77
78
|
pass
|
|
@@ -82,7 +83,7 @@ def mplfinance_plot_patch(wrapped, instance, args, kwargs):
|
|
|
82
83
|
datetime_converter = create_datetime_converter(data)
|
|
83
84
|
|
|
84
85
|
# Use enhanced converter's date_nums for mplfinance compatibility
|
|
85
|
-
if date_nums is None and hasattr(datetime_converter,
|
|
86
|
+
if date_nums is None and hasattr(datetime_converter, "date_nums"):
|
|
86
87
|
date_nums = datetime_converter.date_nums
|
|
87
88
|
|
|
88
89
|
# Process and register the Candlestick plot
|
|
@@ -3,6 +3,7 @@ import numpy as np
|
|
|
3
3
|
from typing import Optional, Dict, Any, List, Tuple
|
|
4
4
|
from datetime import datetime
|
|
5
5
|
|
|
6
|
+
|
|
6
7
|
class DatetimeConverter:
|
|
7
8
|
"""
|
|
8
9
|
Enhanced datetime converter that automatically detects time periods
|
|
@@ -58,7 +59,9 @@ class DatetimeConverter:
|
|
|
58
59
|
>>> print(formatted_hourly) # Output: "Jan 15 2024 09:00"
|
|
59
60
|
"""
|
|
60
61
|
|
|
61
|
-
def __init__(
|
|
62
|
+
def __init__(
|
|
63
|
+
self, data: pd.DataFrame, datetime_format: Optional[str] = None
|
|
64
|
+
) -> None:
|
|
62
65
|
"""
|
|
63
66
|
Initialize the DatetimeConverter.
|
|
64
67
|
|
|
@@ -120,7 +123,7 @@ class DatetimeConverter:
|
|
|
120
123
|
# Calculate average time difference between consecutive data points
|
|
121
124
|
time_diffs = []
|
|
122
125
|
for i in range(1, len(self.data)):
|
|
123
|
-
diff = self.data.index[i] - self.data.index[i-1]
|
|
126
|
+
diff = self.data.index[i] - self.data.index[i - 1]
|
|
124
127
|
time_diffs.append(diff.total_seconds())
|
|
125
128
|
|
|
126
129
|
avg_diff_seconds = np.mean(time_diffs)
|
|
@@ -155,7 +158,7 @@ class DatetimeConverter:
|
|
|
155
158
|
"day": "Daily data",
|
|
156
159
|
"week": "Weekly data",
|
|
157
160
|
"month": "Monthly data",
|
|
158
|
-
"unknown": "Unknown time period"
|
|
161
|
+
"unknown": "Unknown time period",
|
|
159
162
|
}
|
|
160
163
|
return period_descriptions.get(self.time_period, "Unknown time period")
|
|
161
164
|
|
|
@@ -236,6 +239,7 @@ class DatetimeConverter:
|
|
|
236
239
|
"""
|
|
237
240
|
try:
|
|
238
241
|
import matplotlib.dates as mdates
|
|
242
|
+
|
|
239
243
|
date_nums = []
|
|
240
244
|
for d in self.data.index:
|
|
241
245
|
try:
|
|
@@ -247,7 +251,9 @@ class DatetimeConverter:
|
|
|
247
251
|
except Exception:
|
|
248
252
|
return []
|
|
249
253
|
|
|
250
|
-
def extract_candlestick_data(
|
|
254
|
+
def extract_candlestick_data(
|
|
255
|
+
self, ax, wick_collection=None, body_collection=None
|
|
256
|
+
) -> List[Dict[str, Any]]:
|
|
251
257
|
"""
|
|
252
258
|
Extract candlestick data with proper datetime formatting using original DataFrame.
|
|
253
259
|
|
|
@@ -273,33 +279,40 @@ class DatetimeConverter:
|
|
|
273
279
|
datetime values using the enhanced datetime conversion logic.
|
|
274
280
|
"""
|
|
275
281
|
candles = []
|
|
276
|
-
if
|
|
282
|
+
if (
|
|
283
|
+
not hasattr(self.data, "Open")
|
|
284
|
+
or not hasattr(self.data, "High")
|
|
285
|
+
or not hasattr(self.data, "Low")
|
|
286
|
+
or not hasattr(self.data, "Close")
|
|
287
|
+
):
|
|
277
288
|
return candles
|
|
278
289
|
|
|
279
290
|
for i in range(len(self.data)):
|
|
280
291
|
try:
|
|
281
|
-
open_price = self.data.iloc[i][
|
|
282
|
-
high_price = self.data.iloc[i][
|
|
283
|
-
low_price = self.data.iloc[i][
|
|
284
|
-
close_price = self.data.iloc[i][
|
|
285
|
-
volume = self.data.iloc[i].get(
|
|
292
|
+
open_price = self.data.iloc[i]["Open"]
|
|
293
|
+
high_price = self.data.iloc[i]["High"]
|
|
294
|
+
low_price = self.data.iloc[i]["Low"]
|
|
295
|
+
close_price = self.data.iloc[i]["Close"]
|
|
296
|
+
volume = self.data.iloc[i].get("Volume", 0.0)
|
|
286
297
|
|
|
287
298
|
formatted_datetime = self.get_formatted_datetime(i)
|
|
288
299
|
|
|
289
300
|
candle_data = {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
301
|
+
"value": formatted_datetime or f"datetime_{i:03d}",
|
|
302
|
+
"open": float(open_price),
|
|
303
|
+
"high": float(high_price),
|
|
304
|
+
"low": float(low_price),
|
|
305
|
+
"close": float(close_price),
|
|
306
|
+
"volume": float(volume),
|
|
296
307
|
}
|
|
297
308
|
candles.append(candle_data)
|
|
298
309
|
except (KeyError, IndexError, ValueError):
|
|
299
310
|
continue
|
|
300
311
|
return candles
|
|
301
312
|
|
|
302
|
-
def extract_moving_average_data(
|
|
313
|
+
def extract_moving_average_data(
|
|
314
|
+
self, ax, line_index: int = 0
|
|
315
|
+
) -> List[Tuple[str, float]]:
|
|
303
316
|
"""
|
|
304
317
|
Extract moving average data with proper datetime formatting and NaN filtering.
|
|
305
318
|
|
|
@@ -364,10 +377,10 @@ class DatetimeConverter:
|
|
|
364
377
|
datetime values using the enhanced datetime conversion logic.
|
|
365
378
|
"""
|
|
366
379
|
volume_data = []
|
|
367
|
-
if hasattr(self.data,
|
|
380
|
+
if hasattr(self.data, "Volume"):
|
|
368
381
|
for i in range(len(self.data)):
|
|
369
382
|
try:
|
|
370
|
-
volume = self.data.iloc[i][
|
|
383
|
+
volume = self.data.iloc[i]["Volume"]
|
|
371
384
|
if pd.isna(volume) or volume <= 0:
|
|
372
385
|
continue
|
|
373
386
|
formatted_datetime = self.get_formatted_datetime(i)
|
|
@@ -377,7 +390,9 @@ class DatetimeConverter:
|
|
|
377
390
|
return volume_data
|
|
378
391
|
|
|
379
392
|
|
|
380
|
-
def create_datetime_converter(
|
|
393
|
+
def create_datetime_converter(
|
|
394
|
+
data: pd.DataFrame, datetime_format: Optional[str] = None
|
|
395
|
+
) -> DatetimeConverter:
|
|
381
396
|
"""
|
|
382
397
|
Factory function to create a DatetimeConverter instance.
|
|
383
398
|
|
maidr/util/environment.py
CHANGED
|
@@ -101,6 +101,7 @@ class Environment:
|
|
|
101
101
|
return False
|
|
102
102
|
except ImportError:
|
|
103
103
|
return False
|
|
104
|
+
|
|
104
105
|
@staticmethod
|
|
105
106
|
def is_wsl() -> bool:
|
|
106
107
|
"""
|
|
@@ -122,9 +123,9 @@ class Environment:
|
|
|
122
123
|
False # When not in WSL
|
|
123
124
|
"""
|
|
124
125
|
try:
|
|
125
|
-
with open(
|
|
126
|
+
with open("/proc/version", "r") as f:
|
|
126
127
|
version_info = f.read().lower()
|
|
127
|
-
if
|
|
128
|
+
if "microsoft" in version_info or "wsl" in version_info:
|
|
128
129
|
return True
|
|
129
130
|
except FileNotFoundError:
|
|
130
131
|
pass
|
|
@@ -151,7 +152,7 @@ class Environment:
|
|
|
151
152
|
>>> Environment.get_wsl_distro_name()
|
|
152
153
|
'' # When not in WSL or WSL_DISTRO_NAME not set
|
|
153
154
|
"""
|
|
154
|
-
return os.environ.get(
|
|
155
|
+
return os.environ.get("WSL_DISTRO_NAME", "")
|
|
155
156
|
|
|
156
157
|
@staticmethod
|
|
157
158
|
def find_explorer_path() -> Union[str, None]:
|
|
@@ -175,10 +176,7 @@ class Environment:
|
|
|
175
176
|
# Check if explorer.exe is in PATH
|
|
176
177
|
try:
|
|
177
178
|
result = subprocess.run(
|
|
178
|
-
[
|
|
179
|
-
capture_output=True,
|
|
180
|
-
text=True,
|
|
181
|
-
timeout=5
|
|
179
|
+
["which", "explorer.exe"], capture_output=True, text=True, timeout=5
|
|
182
180
|
)
|
|
183
181
|
if result.returncode == 0:
|
|
184
182
|
return result.stdout.strip()
|
|
@@ -187,8 +185,6 @@ class Environment:
|
|
|
187
185
|
|
|
188
186
|
return None
|
|
189
187
|
|
|
190
|
-
|
|
191
|
-
|
|
192
188
|
@staticmethod
|
|
193
189
|
def get_renderer() -> str:
|
|
194
190
|
"""Return renderer which can be ipython or browser."""
|
|
@@ -114,7 +114,9 @@ class LineExtractorMixin:
|
|
|
114
114
|
return ax.get_lines()
|
|
115
115
|
|
|
116
116
|
@staticmethod
|
|
117
|
-
def extract_line_data_with_categorical_labels(
|
|
117
|
+
def extract_line_data_with_categorical_labels(
|
|
118
|
+
ax: Axes, line: Line2D
|
|
119
|
+
) -> Optional[List[Tuple[Union[str, float], float]]]:
|
|
118
120
|
"""
|
|
119
121
|
Extract line data with proper handling of categorical x-axis labels.
|
|
120
122
|
|
maidr/util/mplfinance_utils.py
CHANGED
|
@@ -231,7 +231,7 @@ class MplfinanceDataExtractor:
|
|
|
231
231
|
def _determine_bull_bear_from_data(
|
|
232
232
|
original_data: Optional[Union[pd.DataFrame, pd.Series, dict]],
|
|
233
233
|
index: int,
|
|
234
|
-
date_str: str
|
|
234
|
+
date_str: str,
|
|
235
235
|
) -> bool:
|
|
236
236
|
"""
|
|
237
237
|
Determine if a candle is bullish (up) or bearish (down) using original OHLC data.
|
|
@@ -258,19 +258,19 @@ class MplfinanceDataExtractor:
|
|
|
258
258
|
|
|
259
259
|
try:
|
|
260
260
|
# Try to access the original data
|
|
261
|
-
if hasattr(original_data,
|
|
261
|
+
if hasattr(original_data, "iloc"):
|
|
262
262
|
# It's a pandas DataFrame/Series
|
|
263
263
|
if index < len(original_data):
|
|
264
264
|
row = original_data.iloc[index]
|
|
265
|
-
if
|
|
266
|
-
is_bullish = row[
|
|
265
|
+
if "Close" in row and "Open" in row:
|
|
266
|
+
is_bullish = row["Close"] > row["Open"]
|
|
267
267
|
return is_bullish
|
|
268
268
|
|
|
269
|
-
elif hasattr(original_data,
|
|
269
|
+
elif hasattr(original_data, "__getitem__"):
|
|
270
270
|
# It's a dictionary or similar
|
|
271
|
-
if
|
|
272
|
-
closes = original_data[
|
|
273
|
-
opens = original_data[
|
|
271
|
+
if "Close" in original_data and "Open" in original_data:
|
|
272
|
+
closes = original_data["Close"]
|
|
273
|
+
opens = original_data["Open"]
|
|
274
274
|
if index < len(closes) and index < len(opens):
|
|
275
275
|
is_bullish = closes[index] > opens[index]
|
|
276
276
|
return is_bullish
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
maidr/__init__.py,sha256=
|
|
2
|
-
maidr/api.py,sha256=
|
|
1
|
+
maidr/__init__.py,sha256=WHwg8BcJQIcf4dsUFOdKS_frxDfuHewDEGnBBg9j3WE,415
|
|
2
|
+
maidr/api.py,sha256=od539V0CKcs15FEXQo0hX_hK_A0U3noHE54obdHcTqY,4138
|
|
3
3
|
maidr/core/__init__.py,sha256=WgxLpSEYMc4k3OyEOf1shOxfEq0ASzppEIZYmE91ThQ,25
|
|
4
4
|
maidr/core/context_manager.py,sha256=6cT7ZGOApSpC-SLD2XZWWU_H08i-nfv-JUlzXOtvWYw,3374
|
|
5
|
-
maidr/core/figure_manager.py,sha256=
|
|
6
|
-
maidr/core/maidr.py,sha256=
|
|
5
|
+
maidr/core/figure_manager.py,sha256=t-lhe4jj2gsF5-8VUBUZOPlDutKjm_AZ8xXWJU2pFRc,5555
|
|
6
|
+
maidr/core/maidr.py,sha256=0MuyPgc1CFomA87jq70GG-MSxYaMI2Z7ct2FAOGCxQM,20067
|
|
7
7
|
maidr/core/enum/__init__.py,sha256=9ee78L0dlxEx4ulUGVlD-J23UcUZmrGu0rXms54up3c,93
|
|
8
8
|
maidr/core/enum/library.py,sha256=e8ujT_L-McJWfoVJd1ty9K_2bwITnf1j0GPLsnAcHes,104
|
|
9
9
|
maidr/core/enum/maidr_key.py,sha256=ljG0omqzd8K8Yk213N7i7PXGvG-IOlnE5v7o6RoGJzc,795
|
|
@@ -12,21 +12,21 @@ maidr/core/enum/smooth_keywords.py,sha256=z2kVZZ-mETWWh5reWu_hj9WkJD6WFj7_2-6s1e
|
|
|
12
12
|
maidr/core/plot/__init__.py,sha256=xDIpRGM-4DfaSSL3nKcXrjdMecCHJ6en4K4nA_fPefQ,83
|
|
13
13
|
maidr/core/plot/barplot.py,sha256=0hBgp__putezvxXc9G3qmaktmAzze3cN8pQMD9iqktE,2116
|
|
14
14
|
maidr/core/plot/boxplot.py,sha256=i11GdNuz_c-hilmhydu3ah-bzyVdFoBkNvRi5lpMrrY,9946
|
|
15
|
-
maidr/core/plot/candlestick.py,sha256=
|
|
15
|
+
maidr/core/plot/candlestick.py,sha256=ofvlUwtzaaopvv6VjNDf1IZODbu1UkMHsi1zdvcG-Yo,10120
|
|
16
16
|
maidr/core/plot/grouped_barplot.py,sha256=_zn4XMeEnSiDHtf6t4-z9ErBqg_CijhAS2CCtlHgYIQ,2077
|
|
17
17
|
maidr/core/plot/heatmap.py,sha256=yMS-31tS2GW4peds9LtZesMxmmTV_YfqYO5M_t5KasQ,2521
|
|
18
18
|
maidr/core/plot/histogram.py,sha256=QV5W-6ZJQQcZsrM91JJBX-ONktJzH7yg_et5_bBPfQQ,1525
|
|
19
|
-
maidr/core/plot/lineplot.py,sha256=
|
|
20
|
-
maidr/core/plot/maidr_plot.py,sha256=
|
|
19
|
+
maidr/core/plot/lineplot.py,sha256=uoJpGkJB3IJSDJTwH6ECxLyXGdarsVQNULELp5NncWg,4522
|
|
20
|
+
maidr/core/plot/maidr_plot.py,sha256=heotWue1IzMOXnpoHVCqKSW88Sfep1IuuP4MBarpSek,4231
|
|
21
21
|
maidr/core/plot/maidr_plot_factory.py,sha256=NW2iFScswgXbAC9rAOo4iMkAFsjY43DAvFioGr0yzx4,2732
|
|
22
22
|
maidr/core/plot/mplfinance_barplot.py,sha256=zhTp2i6BH0xn7vQvGTotKgu2HbzlKT4p6zA5CVUUHHc,5673
|
|
23
|
-
maidr/core/plot/mplfinance_lineplot.py,sha256=
|
|
23
|
+
maidr/core/plot/mplfinance_lineplot.py,sha256=pIbsusnQg1_GrstVVHfMw-t9yipWJowa9ZvGsiVV6l8,7329
|
|
24
24
|
maidr/core/plot/regplot.py,sha256=b7u6bGTz1IxKahplNUrfwIr_OGSwMJ2BuLgFAVjL0s0,2744
|
|
25
25
|
maidr/core/plot/scatterplot.py,sha256=o0i0uS-wXK9ZrENxneoHbh3-u-2goRONp19Yu9QLsaY,1257
|
|
26
26
|
maidr/exception/__init__.py,sha256=PzaXoYBhyZxMDcJkuxJugDx7jZeseI0El6LpxIwXyG4,46
|
|
27
27
|
maidr/exception/extraction_error.py,sha256=rd37Oxa9gn2OWFWt9AOH5fv0hNd3sAWGvpDMFBuJY2I,607
|
|
28
28
|
maidr/patch/__init__.py,sha256=FnkoUQJC2ODhLO37GwgRVSitBCRax42Ti0e4NIAgdO0,236
|
|
29
|
-
maidr/patch/barplot.py,sha256=
|
|
29
|
+
maidr/patch/barplot.py,sha256=QncV4Wv0B5bfY3OekA_ga8tgRLNmRDJf89mnx7xeQzs,7762
|
|
30
30
|
maidr/patch/boxplot.py,sha256=l7wDD4pDi4ZbsL5EX5XDhPRxgtSIFSrFguMOZ7IC2eg,2845
|
|
31
31
|
maidr/patch/candlestick.py,sha256=R2MgPX5ih9W-RBluvF6jFNJBxETH0eMt7Tzn0Ej9LoU,2652
|
|
32
32
|
maidr/patch/clear.py,sha256=2Sc4CIt5jRGkew3TxFsBZm-uowC9yDSxtraEcXZjmGw,396
|
|
@@ -36,23 +36,23 @@ maidr/patch/highlight.py,sha256=I1dGFHJAnVd0AHVnMJzk_TE8BC8Uv-I6fTzSrJLU5QM,1155
|
|
|
36
36
|
maidr/patch/histogram.py,sha256=k3N0RUf1SQ2402pwbaY5QyS98KnLWvr9glCHQw9NTko,2378
|
|
37
37
|
maidr/patch/kdeplot.py,sha256=qv-OKzuop2aTrkZgUe2OnLxvV-KMyeXt1Td0_uZeHzE,2338
|
|
38
38
|
maidr/patch/lineplot.py,sha256=kvTAiOddy1g5GMP456Awk21NUEZfn-vWaYXy5GTVtuA,1841
|
|
39
|
-
maidr/patch/mplfinance.py,sha256=
|
|
39
|
+
maidr/patch/mplfinance.py,sha256=ySD32onanoMgdQkV6XlSAbVd_BQuLWuEQtpkYSEDSzA,9491
|
|
40
40
|
maidr/patch/regplot.py,sha256=k86ekd0E4XJ_L1u85zObuDnxuXlM83z7tKtyXRTj2rI,3240
|
|
41
41
|
maidr/patch/scatterplot.py,sha256=kln6zZwjVsdQzICalo-RnBOJrx1BnIB2xYUwItHvSNY,525
|
|
42
42
|
maidr/util/__init__.py,sha256=eRJZfRpDX-n7UoV3JXw_9Lbfu_qNl_D0W1UTvLL-Iv4,81
|
|
43
|
-
maidr/util/datetime_conversion.py,sha256=
|
|
43
|
+
maidr/util/datetime_conversion.py,sha256=AQ8qShbEkLVo13TUkOOmtOLnOvaI05Vh5oWhgchvXSA,14478
|
|
44
44
|
maidr/util/dedup_utils.py,sha256=RpgPL5p-3oULUHaTCZJaQKhPHfyPkvBLHMt8lAGpJ5A,438
|
|
45
|
-
maidr/util/environment.py,sha256=
|
|
46
|
-
maidr/util/mplfinance_utils.py,sha256=
|
|
45
|
+
maidr/util/environment.py,sha256=C4VMyB16mqzrFxpJdxFdm40M0IZojxh60UX80680jgo,9403
|
|
46
|
+
maidr/util/mplfinance_utils.py,sha256=OZe5Y7gzjdjve9DViioQvGZYTdZvz8obvN3oHElQFZw,14418
|
|
47
47
|
maidr/util/plot_detection.py,sha256=bgLHoDcHSRwOiyKzUK3EqGwdAIhF44ocHW5ox6xYGZw,3883
|
|
48
48
|
maidr/util/regression_line_utils.py,sha256=yFKr-H0whT_su2YVZwNksBLp5EC5s77sr6HUFgNcsyY,2329
|
|
49
49
|
maidr/util/svg_utils.py,sha256=2gyzBtNKFHs0utrw1iOlxTmznzivOWQMV2aW8zu2c8E,1442
|
|
50
50
|
maidr/util/mixin/__init__.py,sha256=aGJZNhtWh77yIVPc7ipIZm1OajigjMtCWYKPuDWTC-c,217
|
|
51
|
-
maidr/util/mixin/extractor_mixin.py,sha256=
|
|
51
|
+
maidr/util/mixin/extractor_mixin.py,sha256=j2Rv2vh_gqqcxLV1ka3xsPaPAfWsX94CtKIW2FgPLnI,6937
|
|
52
52
|
maidr/util/mixin/merger_mixin.py,sha256=V0qLw_6DUB7X6CQ3BCMpsCQX_ZuwAhoSTm_E4xAJFKM,712
|
|
53
53
|
maidr/widget/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
54
54
|
maidr/widget/shiny.py,sha256=wrrw2KAIpE_A6CNQGBtNHauR1DjenA_n47qlFXX9_rk,745
|
|
55
|
-
maidr-1.
|
|
56
|
-
maidr-1.
|
|
57
|
-
maidr-1.
|
|
58
|
-
maidr-1.
|
|
55
|
+
maidr-1.8.0.dist-info/METADATA,sha256=JSCtmOqsD8Mka8aDihHcqHs4wJ9Yc4eBYV3by-pQSl0,3154
|
|
56
|
+
maidr-1.8.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
57
|
+
maidr-1.8.0.dist-info/licenses/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
|
|
58
|
+
maidr-1.8.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|