maidr 1.7.2__tar.gz → 1.7.3__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.7.2 → maidr-1.7.3}/CHANGELOG.md +15 -0
- {maidr-1.7.2 → maidr-1.7.3}/PKG-INFO +1 -1
- {maidr-1.7.2 → maidr-1.7.3}/example/flask/test_flask_app.py +13 -6
- {maidr-1.7.2 → maidr-1.7.3}/maidr/__init__.py +1 -1
- {maidr-1.7.2 → maidr-1.7.3}/maidr/core/figure_manager.py +53 -8
- {maidr-1.7.2 → maidr-1.7.3}/maidr/core/maidr.py +47 -6
- {maidr-1.7.2 → maidr-1.7.3}/maidr/core/plot/candlestick.py +17 -4
- {maidr-1.7.2 → maidr-1.7.3}/maidr/core/plot/lineplot.py +3 -1
- {maidr-1.7.2 → maidr-1.7.3}/maidr/core/plot/maidr_plot.py +1 -0
- {maidr-1.7.2 → maidr-1.7.3}/maidr/core/plot/mplfinance_lineplot.py +4 -1
- maidr-1.7.3/maidr/patch/barplot.py +218 -0
- {maidr-1.7.2 → maidr-1.7.3}/maidr/patch/mplfinance.py +2 -1
- {maidr-1.7.2 → maidr-1.7.3}/maidr/util/datetime_conversion.py +35 -20
- {maidr-1.7.2 → maidr-1.7.3}/maidr/util/environment.py +5 -9
- {maidr-1.7.2 → maidr-1.7.3}/maidr/util/mixin/extractor_mixin.py +3 -1
- {maidr-1.7.2 → maidr-1.7.3}/maidr/util/mplfinance_utils.py +8 -8
- {maidr-1.7.2 → maidr-1.7.3}/pyproject.toml +1 -1
- {maidr-1.7.2 → maidr-1.7.3}/uv.lock +2 -2
- maidr-1.7.2/example/stacked/matplotlib/example_mpl_stacked.html +0 -9
- maidr-1.7.2/example/stacked/seaborn/example_sns_stacked.html +0 -479
- maidr-1.7.2/maidr/patch/barplot.py +0 -77
- {maidr-1.7.2 → maidr-1.7.3}/.commitlintrc.cjs +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/.editorconfig +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/.github/copilot-instructions.md +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/.github/workflows/ci.yml +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/.github/workflows/docs.yml +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/.github/workflows/release.yml +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/.gitignore +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/.pre-commit-config.yaml +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/.vscode/extensions.json +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/.vscode/settings.json +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/CONDUCT.md +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/CONTRIBUTING.md +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/LICENSE +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/README.md +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/docs/.gitignore +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/docs/CNAME +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/docs/_environment +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/docs/_extensions/machow/interlinks/.gitignore +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/docs/_extensions/machow/interlinks/_extension.yml +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/docs/_extensions/machow/interlinks/interlinks.lua +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/docs/_extensions/shafayetShafee/line-highlight/_extension.yml +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/docs/_extensions/shafayetShafee/line-highlight/line-highlight.lua +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/docs/_extensions/shafayetShafee/line-highlight/resources/css/line-highlight.css +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/docs/_extensions/shafayetShafee/line-highlight/resources/js/line-highlight.js +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/docs/_quarto.yml +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/docs/examples.qmd +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/docs/index.qmd +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/docs/styles.css +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/bar/example_bar_plot.ipynb +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/bar/matplotlib/example_mpl_bar_plot.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/bar/seaborn/example_sns_bar_plot.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/box/example_box_plot.ipynb +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/box/matplotlib/example_mpl_box.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/box/seaborn/example_sns_box.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/candle_stick/legacy_candlestick_example.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/candle_stick/mplfinance_candlestick_example.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/candle_stick/test_data_daily_current_year.csv +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/candle_stick/test_data_daily_mixed_years.csv +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/candle_stick/test_data_hourly.csv +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/candle_stick/test_data_minute.csv +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/candle_stick/test_data_weekly.csv +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/candle_stick/volcandat.csv +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/count/example_count_plot.ipynb +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/count/seaborn/example_sns_count_plot.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/dodged/matplotlib/example_mpl_dodged.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/dodged/seaborn/example_sns_dodged.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/facet-subplots/matplotlib/example_mpl_facet_bar_plot.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/facet-subplots/matplotlib/example_mpl_facet_combined_plot.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/facet-subplots/seaborn/example_sns_facet_bar_plot.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/facet-subplots/seaborn/example_sns_facet_combined_plot.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/heatmap/example_heatmap_plot.ipynb +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/heatmap/matplotlib/example_mpl_heatmap.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/heatmap/seaborn/example_sns_heatmap.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/histogram/example_histogram_plot.ipynb +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/histogram/matplotlib/example_mpl_hist.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/histogram/matplotlib/histogram_with_kde_matplotlib.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/histogram/seaborn/example_sns_hist.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/histogram/seaborn/histogram_with_kde_seaborn.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/kde/example_kde_plots.ipynb +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/kde/matplotlib/multiple_kde_matplotlib.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/kde/matplotlib/single_kde_matplotlib.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/kde/seaborn/multiple_kde_seaborn.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/kde/seaborn/single_kde_seaborn.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/line/example_line_plot.ipynb +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/line/matplotlib/example_mpl_line.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/line/seaborn/example_sns_line.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/multilayer/example_mpl_multilayer.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/multilayer/example_multilayer_plot.ipynb +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/multiline/example_multiline_plot.ipynb +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/multiline/matplotlib/example_mpl_multiline.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/multiline/seaborn/example_sns_multiline.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/multipanel/example_multipanel_plot.ipynb +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/multipanel/matplotlib/example_mpl_multipanel.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/multipanel/seaborn/example_sns_multipanel.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/quarto/demo.qmd +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/reg/example_reg_plots.ipynb +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/reg/matplotlib/example_matplotlib_smooth_plot.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/reg/seaborn/example_sns_reg.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/scatter/example_scatter_plot.ipynb +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/scatter/matplotlib/example_mpl_scatter.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/scatter/seaborn/example_sns_scatter.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/shiny/example_shiny_scatter.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/stacked/matplotlib/example_mpl_stacked.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/stacked/seaborn/example_sns_stacked.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/example/streamlit/example_streamlit_app.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/maidr/api.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/maidr/core/__init__.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/maidr/core/context_manager.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/maidr/core/enum/__init__.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/maidr/core/enum/library.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/maidr/core/enum/maidr_key.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/maidr/core/enum/plot_type.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/maidr/core/enum/smooth_keywords.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/maidr/core/plot/__init__.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/maidr/core/plot/barplot.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/maidr/core/plot/boxplot.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/maidr/core/plot/grouped_barplot.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/maidr/core/plot/heatmap.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/maidr/core/plot/histogram.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/maidr/core/plot/maidr_plot_factory.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/maidr/core/plot/mplfinance_barplot.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/maidr/core/plot/regplot.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/maidr/core/plot/scatterplot.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/maidr/exception/__init__.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/maidr/exception/extraction_error.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/maidr/patch/__init__.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/maidr/patch/boxplot.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/maidr/patch/candlestick.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/maidr/patch/clear.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/maidr/patch/common.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/maidr/patch/heatmap.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/maidr/patch/highlight.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/maidr/patch/histogram.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/maidr/patch/kdeplot.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/maidr/patch/lineplot.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/maidr/patch/regplot.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/maidr/patch/scatterplot.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/maidr/util/__init__.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/maidr/util/dedup_utils.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/maidr/util/mixin/__init__.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/maidr/util/mixin/merger_mixin.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/maidr/util/plot_detection.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/maidr/util/regression_line_utils.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/maidr/util/svg_utils.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/maidr/widget/__init__.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/maidr/widget/shiny.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/tests/__init__.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/tests/conftest.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/tests/core/__init__.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/tests/core/enum/__init__.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/tests/core/plot/__init__.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/tests/core/test_figure_manager.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/tests/core/test_maidr_plot.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/tests/core/test_maidr_plot_factory.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/tests/fixture/__init__.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/tests/fixture/library_factory.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/tests/fixture/matplotlib_factory.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/tests/fixture/seaborn_factory.py +0 -0
- {maidr-1.7.2 → maidr-1.7.3}/tox.ini +0 -0
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## v1.7.3 (2025-09-15)
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
- Ensure all subplots are accessible and improve dodged plot detection
|
|
9
|
+
([#242](https://github.com/xability/py-maidr/pull/242),
|
|
10
|
+
[`979b971`](https://github.com/xability/py-maidr/commit/979b9713d07be8bc9046056e7f1c0519336ddd22))
|
|
11
|
+
|
|
12
|
+
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
|
|
13
|
+
|
|
14
|
+
Co-authored-by: jooyoungseo <19754711+jooyoungseo@users.noreply.github.com>
|
|
15
|
+
|
|
16
|
+
Co-authored-by: JooYoung Seo <jseo1005@illinois.edu>
|
|
17
|
+
|
|
18
|
+
|
|
4
19
|
## v1.7.2 (2025-09-12)
|
|
5
20
|
|
|
6
21
|
### Bug Fixes
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import matplotlib
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
matplotlib.use("Agg") # Use non-interactive backend for Flask
|
|
3
4
|
|
|
4
5
|
from flask import Flask
|
|
5
6
|
import matplotlib.pyplot as plt
|
|
@@ -14,7 +15,8 @@ logger = logging.getLogger(__name__)
|
|
|
14
15
|
|
|
15
16
|
app = Flask(__name__)
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
|
|
19
|
+
@app.route("/")
|
|
18
20
|
def index():
|
|
19
21
|
# Log environment detection
|
|
20
22
|
logger.info("=== Environment Detection ===")
|
|
@@ -34,8 +36,12 @@ def index():
|
|
|
34
36
|
logger.info(f"flask_detected: {flask_detected}")
|
|
35
37
|
logger.info(f"notebook_detected: {notebook_detected}")
|
|
36
38
|
logger.info(f"shiny_detected: {shiny_detected}")
|
|
37
|
-
logger.info(
|
|
38
|
-
|
|
39
|
+
logger.info(
|
|
40
|
+
"Condition: use_iframe and (flask_detected or notebook_detected or shiny_detected)"
|
|
41
|
+
)
|
|
42
|
+
logger.info(
|
|
43
|
+
f"Result: {use_iframe and (flask_detected or notebook_detected or shiny_detected)}"
|
|
44
|
+
)
|
|
39
45
|
|
|
40
46
|
# Load the penguins dataset
|
|
41
47
|
penguins = sns.load_dataset("penguins")
|
|
@@ -65,7 +71,8 @@ def index():
|
|
|
65
71
|
# Return the maidr HTML directly
|
|
66
72
|
return str(maidr_html)
|
|
67
73
|
|
|
68
|
-
|
|
74
|
+
|
|
75
|
+
if __name__ == "__main__":
|
|
69
76
|
logger.info("Starting Flask app with logging...")
|
|
70
77
|
logger.info("Visit http://localhost:5002 to see the maidr plot in Flask")
|
|
71
|
-
app.run(debug=False, host=
|
|
78
|
+
app.run(debug=False, host="0.0.0.0", port=5002)
|
|
@@ -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
|
|
@@ -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:
|
|
@@ -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:
|
|
@@ -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
|
|
|
@@ -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)))
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, Dict, Tuple, Union
|
|
4
|
+
|
|
5
|
+
import wrapt
|
|
6
|
+
from matplotlib.axes import Axes
|
|
7
|
+
from matplotlib.container import BarContainer
|
|
8
|
+
|
|
9
|
+
from maidr.core.enum import PlotType
|
|
10
|
+
from maidr.patch.common import common
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def bar(
|
|
14
|
+
wrapped: Callable, instance: Any, args: Tuple[Any, ...], kwargs: Dict[str, Any]
|
|
15
|
+
) -> Union[Axes, BarContainer]:
|
|
16
|
+
"""
|
|
17
|
+
Patch function for bar plots.
|
|
18
|
+
|
|
19
|
+
This function patches the bar plotting functions to identify whether the
|
|
20
|
+
plot should be rendered as a normal, stacked, or dodged bar plot.
|
|
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.
|
|
25
|
+
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
wrapped : Callable
|
|
29
|
+
The original function to be wrapped.
|
|
30
|
+
instance : Any
|
|
31
|
+
The instance of the class where the function is being patched.
|
|
32
|
+
args : tuple
|
|
33
|
+
Positional arguments passed to the original function.
|
|
34
|
+
For a dodged plot, the first argument (x positions) should be numeric.
|
|
35
|
+
kwargs : dict
|
|
36
|
+
Keyword arguments passed to the original function.
|
|
37
|
+
For seaborn plots, may contain 'hue' and 'dodge' parameters.
|
|
38
|
+
|
|
39
|
+
Returns
|
|
40
|
+
-------
|
|
41
|
+
Union[Axes, BarContainer]
|
|
42
|
+
The axes or bar container returned by the original function.
|
|
43
|
+
|
|
44
|
+
Examples
|
|
45
|
+
--------
|
|
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:
|
|
50
|
+
>>> x_positions = np.arange(3)
|
|
51
|
+
>>> ax.bar(x_positions, heights, width, label='Group') # Dodged bar plot.
|
|
52
|
+
"""
|
|
53
|
+
plot_type = PlotType.BAR
|
|
54
|
+
|
|
55
|
+
# Check for stacked plots first (explicit bottom parameter)
|
|
56
|
+
if "bottom" in kwargs:
|
|
57
|
+
bottom = kwargs.get("bottom")
|
|
58
|
+
if bottom is not None:
|
|
59
|
+
plot_type = PlotType.STACKED
|
|
60
|
+
else:
|
|
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
|
|
65
|
+
else:
|
|
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)
|
|
71
|
+
|
|
72
|
+
align = kwargs.get("align", "center")
|
|
73
|
+
|
|
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
|
|
82
|
+
|
|
83
|
+
return common(plot_type, wrapped, instance, args, kwargs)
|
|
84
|
+
|
|
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
|
+
|
|
212
|
+
# Patch matplotlib functions.
|
|
213
|
+
wrapt.wrap_function_wrapper(Axes, "bar", bar)
|
|
214
|
+
wrapt.wrap_function_wrapper(Axes, "barh", bar)
|
|
215
|
+
|
|
216
|
+
# Patch seaborn functions.
|
|
217
|
+
wrapt.wrap_function_wrapper("seaborn", "barplot", bar)
|
|
218
|
+
wrapt.wrap_function_wrapper("seaborn", "countplot", bar)
|
|
@@ -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
|