maidr 1.6.0__py3-none-any.whl → 1.6.1__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 CHANGED
@@ -1,4 +1,4 @@
1
- __version__ = "1.6.0"
1
+ __version__ = "1.6.1"
2
2
 
3
3
  from .api import close, render, save_html, show, stacked
4
4
  from .core import Maidr
@@ -1,11 +1,15 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import uuid
4
+ from typing import Union, Dict
3
5
  from matplotlib.axes import Axes
4
6
  from matplotlib.patches import Rectangle
7
+ import numpy as np
5
8
 
6
9
  from maidr.core.enum import PlotType
7
10
  from maidr.core.plot import MaidrPlot
8
11
  from maidr.core.enum.maidr_key import MaidrKey
12
+ from maidr.exception import ExtractionError
9
13
  from maidr.util.mplfinance_utils import MplfinanceDataExtractor
10
14
 
11
15
 
@@ -41,23 +45,48 @@ class CandlestickPlot(MaidrPlot):
41
45
  self._maidr_date_nums = kwargs.get("_maidr_date_nums", None)
42
46
  self._maidr_original_data = kwargs.get("_maidr_original_data", None) # Store original data
43
47
 
44
- # Store the GID for proper selector generation
48
+ # Store the GID for proper selector generation (legacy/shared)
45
49
  self._maidr_gid = None
50
+ # Modern-path separate gids for body and wick
51
+ self._maidr_body_gid = None
52
+ self._maidr_wick_gid = None
46
53
  if self._maidr_body_collection:
47
54
  self._maidr_gid = self._maidr_body_collection.get_gid()
55
+ self._maidr_body_gid = self._maidr_gid
48
56
  elif self._maidr_wick_collection:
49
57
  self._maidr_gid = self._maidr_wick_collection.get_gid()
58
+ self._maidr_wick_gid = self._maidr_gid
50
59
 
51
60
  def _extract_plot_data(self) -> list[dict]:
52
- """Extract candlestick data from the plot."""
61
+ """
62
+ Extract candlestick data from the plot.
63
+
64
+ This method processes candlestick plots from both modern (mplfinance.plot) and
65
+ legacy (original_flavor) pipelines, extracting OHLC data and setting up
66
+ highlighting elements and GIDs.
67
+
68
+ Returns
69
+ -------
70
+ list[dict]
71
+ List of dictionaries containing candlestick data with keys:
72
+ - 'value': Date string
73
+ - 'open': Opening price (float)
74
+ - 'high': High price (float)
75
+ - 'low': Low price (float)
76
+ - 'close': Closing price (float)
77
+ - 'volume': Volume (float, typically 0 for candlestick-only plots)
78
+ """
53
79
 
54
80
  # Get the custom collections from kwargs
55
81
  body_collection = self._maidr_body_collection
56
82
  wick_collection = self._maidr_wick_collection
57
83
 
58
84
  if body_collection and wick_collection:
59
- # Store the GID from the body collection for highlighting
60
- self._maidr_gid = body_collection.get_gid()
85
+ # Store the GIDs from the collections (modern path)
86
+ self._maidr_body_gid = body_collection.get_gid()
87
+ self._maidr_wick_gid = wick_collection.get_gid()
88
+ # Keep legacy gid filled for backward compatibility
89
+ self._maidr_gid = self._maidr_body_gid or self._maidr_wick_gid
61
90
 
62
91
  # Use the original collections for highlighting
63
92
  self._elements = [body_collection, wick_collection]
@@ -86,12 +115,33 @@ class CandlestickPlot(MaidrPlot):
86
115
 
87
116
  # Generate a GID for highlighting if none exists
88
117
  if not self._maidr_gid:
89
- import uuid
90
-
91
118
  self._maidr_gid = f"maidr-{uuid.uuid4()}"
92
119
  # Set GID on all rectangles
93
120
  for rect in body_rectangles:
94
121
  rect.set_gid(self._maidr_gid)
122
+ # Keep a dedicated body gid for legacy dict selectors
123
+ self._maidr_body_gid = getattr(self, "_maidr_body_gid", None) or self._maidr_gid
124
+
125
+ # Assign a shared gid to wick Line2D (vertical 2-point lines) on the same axis
126
+ wick_lines = []
127
+ for line in ax_ohlc.get_lines():
128
+ try:
129
+ xydata = line.get_xydata()
130
+ if xydata is None:
131
+ continue
132
+ xy_arr = np.asarray(xydata)
133
+ if xy_arr.ndim == 2 and xy_arr.shape[0] == 2 and xy_arr.shape[1] >= 2:
134
+ x0 = float(xy_arr[0, 0])
135
+ x1 = float(xy_arr[1, 0])
136
+ if abs(x0 - x1) < 1e-10:
137
+ wick_lines.append(line)
138
+ except Exception:
139
+ continue
140
+ if wick_lines:
141
+ if not getattr(self, "_maidr_wick_gid", None):
142
+ self._maidr_wick_gid = f"maidr-{uuid.uuid4()}"
143
+ for line in wick_lines:
144
+ line.set_gid(self._maidr_wick_gid)
95
145
 
96
146
  # Use the utility class to extract data
97
147
  data = MplfinanceDataExtractor.extract_rectangle_candlestick_data(
@@ -117,17 +167,57 @@ class CandlestickPlot(MaidrPlot):
117
167
  x_labels = "X"
118
168
  return {MaidrKey.X: x_labels, MaidrKey.Y: self.ax.get_ylabel()}
119
169
 
120
- def _get_selector(self) -> str:
121
- """Return the CSS selector for highlighting candlestick elements in the SVG output."""
122
- # Use the stored GID if available, otherwise fall back to generic selector
123
- if self._maidr_gid:
124
- # Use the full GID as the id attribute (since that's what's in the SVG)
125
- selector = (
126
- f"g[id='{self._maidr_gid}'] > path, g[id='{self._maidr_gid}'] > rect"
127
- )
128
- else:
129
- selector = "g[maidr='true'] > path, g[maidr='true'] > rect"
130
- return selector
170
+ def _get_selector(self) -> Union[str, Dict[str, str]]:
171
+ """Return selectors for highlighting candlestick elements.
172
+
173
+ - Modern path (collections present): return a dict with separate selectors for body, wickLow, wickHigh
174
+ - Legacy path: return a dict with body and shared wick selectors (no open/close keys)
175
+ """
176
+ # Modern path: build structured selectors using separate gids
177
+ if self._maidr_body_collection and self._maidr_wick_collection and self._maidr_body_gid and self._maidr_wick_gid:
178
+ # Determine candle count N
179
+ N = None
180
+ if self._maidr_original_data is not None:
181
+ try:
182
+ N = len(self._maidr_original_data)
183
+ except Exception:
184
+ N = None
185
+ if N is None and hasattr(self._maidr_wick_collection, "get_paths"):
186
+ try:
187
+ wick_paths = len(list(self._maidr_wick_collection.get_paths()))
188
+ if wick_paths % 2 == 0 and wick_paths > 0:
189
+ N = wick_paths // 2
190
+ except Exception:
191
+ pass
192
+ if N is None and hasattr(self._maidr_body_collection, "get_paths"):
193
+ try:
194
+ body_paths = len(list(self._maidr_body_collection.get_paths()))
195
+ if body_paths > 0:
196
+ N = body_paths
197
+ except Exception:
198
+ pass
199
+ if N is None:
200
+ raise ExtractionError(PlotType.CANDLESTICK, self._maidr_wick_collection)
201
+
202
+ selectors = {
203
+ "body": f"g[id='{self._maidr_body_gid}'] > path",
204
+ "wickLow": f"g[id='{self._maidr_wick_gid}'] > path:nth-child(-n+{N})",
205
+ "wickHigh": f"g[id='{self._maidr_wick_gid}'] > path:nth-child(n+{N + 1})",
206
+ }
207
+ return selectors
208
+
209
+ # Legacy path: build shared-id selectors; omit open/close
210
+ legacy_selectors = {}
211
+ if getattr(self, "_maidr_body_gid", None) or self._maidr_gid:
212
+ body_gid = getattr(self, "_maidr_body_gid", None) or self._maidr_gid
213
+ legacy_selectors["body"] = f"g[id='{body_gid}'] > path"
214
+ if getattr(self, "_maidr_wick_gid", None):
215
+ legacy_selectors["wick"] = f"g[id='{self._maidr_wick_gid}'] > path"
216
+ if legacy_selectors:
217
+ return legacy_selectors
218
+
219
+ # Fallback
220
+ return "g[maidr='true'] > path, g[maidr='true'] > rect"
131
221
 
132
222
  def render(self) -> dict:
133
223
  """Initialize the MAIDR schema dictionary with basic plot information."""
@@ -2,6 +2,7 @@ import wrapt
2
2
  from typing import Any, Callable, Dict, Tuple
3
3
  from matplotlib.patches import Rectangle
4
4
  from mplfinance import original_flavor
5
+ import numpy as np
5
6
 
6
7
  from maidr.core.context_manager import ContextManager
7
8
  from maidr.core.enum.plot_type import PlotType
@@ -46,10 +47,35 @@ def candlestick(
46
47
  # Patch the plotting function.
47
48
  plot = wrapped(*args, **kwargs)
48
49
 
50
+ original_data = None
51
+ date_nums = None
52
+ if len(args) >= 2:
53
+ try:
54
+ quotes = args[1]
55
+ if quotes is not None:
56
+ arr = np.asarray(quotes)
57
+ if arr.ndim == 2 and arr.shape[1] >= 5 and arr.size > 0:
58
+ date_nums = arr[:, 0].tolist()
59
+ original_data = {
60
+ "Open": arr[:, 1].tolist(),
61
+ "High": arr[:, 2].tolist(),
62
+ "Low": arr[:, 3].tolist(),
63
+ "Close": arr[:, 4].tolist(),
64
+ }
65
+ except Exception:
66
+ pass
67
+
49
68
  axes = []
50
69
  for ax in plot:
51
70
  axes.append(FigureManager.get_axes(ax))
52
- FigureManager.create_maidr(axes, PlotType.CANDLESTICK)
71
+
72
+ extra_kwargs: Dict[str, Any] = {}
73
+ if original_data is not None:
74
+ extra_kwargs["_maidr_original_data"] = original_data
75
+ if date_nums is not None:
76
+ extra_kwargs["_maidr_date_nums"] = date_nums
77
+
78
+ FigureManager.create_maidr(axes, PlotType.CANDLESTICK, **extra_kwargs)
53
79
 
54
80
  return plot
55
81
 
maidr/patch/mplfinance.py CHANGED
@@ -83,9 +83,10 @@ def mplfinance_plot_patch(wrapped, instance, args, kwargs):
83
83
  )
84
84
 
85
85
  if wick_collection and body_collection:
86
- gid = f"maidr-{uuid.uuid4()}"
87
- wick_collection.set_gid(gid)
88
- body_collection.set_gid(gid)
86
+ wick_gid = f"maidr-{uuid.uuid4()}"
87
+ body_gid = f"maidr-{uuid.uuid4()}"
88
+ wick_collection.set_gid(wick_gid)
89
+ body_collection.set_gid(body_gid)
89
90
 
90
91
  candlestick_kwargs = dict(
91
92
  kwargs,
@@ -93,6 +94,8 @@ def mplfinance_plot_patch(wrapped, instance, args, kwargs):
93
94
  _maidr_body_collection=body_collection,
94
95
  _maidr_date_nums=date_nums,
95
96
  _maidr_original_data=data,
97
+ _maidr_wick_gid=wick_gid,
98
+ _maidr_body_gid=body_gid,
96
99
  )
97
100
  common(
98
101
  PlotType.CANDLESTICK,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: maidr
3
- Version: 1.6.0
3
+ Version: 1.6.1
4
4
  Summary: Multimodal Access and Interactive Data Representations
5
5
  Project-URL: Homepage, https://xability.github.io/py-maidr
6
6
  Project-URL: Repository, https://github.com/xability/py-maidr
@@ -28,7 +28,6 @@ Requires-Dist: htmltools>=0.5
28
28
  Requires-Dist: lxml>=5.1.0
29
29
  Requires-Dist: mplfinance>=0.12.10b0
30
30
  Requires-Dist: numpy>=1.26
31
- Requires-Dist: virtualenv<21,>=20.26.6
32
31
  Requires-Dist: wrapt<2,>=1.16.0
33
32
  Provides-Extra: jupyter
34
33
  Requires-Dist: ipykernel>=6.0.0; extra == 'jupyter'
@@ -1,4 +1,4 @@
1
- maidr/__init__.py,sha256=bvyrTCLC6rRb_Iu5wCMtnDYCY0tbaZ--c21hFuNMICE,415
1
+ maidr/__init__.py,sha256=n8luuuD48JSxB6D5jXxE2QsGGoeQI-3S9lM4qhb4RPI,415
2
2
  maidr/api.py,sha256=gRNLXqUWpFGdD-I7Nu6J0_LeEni9KRAr0TBHwHaDAsc,1928
3
3
  maidr/core/__init__.py,sha256=WgxLpSEYMc4k3OyEOf1shOxfEq0ASzppEIZYmE91ThQ,25
4
4
  maidr/core/context_manager.py,sha256=6cT7ZGOApSpC-SLD2XZWWU_H08i-nfv-JUlzXOtvWYw,3374
@@ -12,7 +12,7 @@ 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=dk1MCU5zqGA2CFLT1_1tGrsB6kYNhsUFLPLNaEi9e04,5363
15
+ maidr/core/plot/candlestick.py,sha256=pDuO7ljGiwpqLUWpMgPYARTZGfLHdEoPVbAoNkv3lV0,9559
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
@@ -28,7 +28,7 @@ maidr/exception/extraction_error.py,sha256=rd37Oxa9gn2OWFWt9AOH5fv0hNd3sAWGvpDMF
28
28
  maidr/patch/__init__.py,sha256=FnkoUQJC2ODhLO37GwgRVSitBCRax42Ti0e4NIAgdO0,236
29
29
  maidr/patch/barplot.py,sha256=qdUy5Y8zkMg4dH3fFh93OYzLdar4nl1u5O2Jcw4d2zI,2433
30
30
  maidr/patch/boxplot.py,sha256=l7wDD4pDi4ZbsL5EX5XDhPRxgtSIFSrFguMOZ7IC2eg,2845
31
- maidr/patch/candlestick.py,sha256=NFkzwpxmLBpWmb5s05pjk6obNMQee-xIEZTqGkbhhqM,1776
31
+ maidr/patch/candlestick.py,sha256=R2MgPX5ih9W-RBluvF6jFNJBxETH0eMt7Tzn0Ej9LoU,2652
32
32
  maidr/patch/clear.py,sha256=2Sc4CIt5jRGkew3TxFsBZm-uowC9yDSxtraEcXZjmGw,396
33
33
  maidr/patch/common.py,sha256=RV2NayjPmcWJVhZJV7nmBCjcH7MPDhW_fIluTOPAATk,880
34
34
  maidr/patch/heatmap.py,sha256=uxLLg530Ql9KVC5rxk8vnwPHXBWWHwYgJRkyHY-tJzs,1048
@@ -36,7 +36,7 @@ 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=73aYyFYnAX-AXqPid8ScfexEU2Y8fC9L_tEidzPcKZo,8008
39
+ maidr/patch/mplfinance.py,sha256=FJYM5xRDMpTwaKZUzT6IjpgsR1UiVnvATZsDTyDxqmY,8154
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
@@ -51,7 +51,7 @@ maidr/util/mixin/extractor_mixin.py,sha256=oHtwpmS5kARvaLrSO3DKTPQxyFUw9nOcKN7rz
51
51
  maidr/util/mixin/merger_mixin.py,sha256=V0qLw_6DUB7X6CQ3BCMpsCQX_ZuwAhoSTm_E4xAJFKM,712
52
52
  maidr/widget/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
53
53
  maidr/widget/shiny.py,sha256=wrrw2KAIpE_A6CNQGBtNHauR1DjenA_n47qlFXX9_rk,745
54
- maidr-1.6.0.dist-info/METADATA,sha256=e3l2lyk61dpyniYkOGEIO2Ycyt10LWgPfM-z6OR8fYU,3193
55
- maidr-1.6.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
56
- maidr-1.6.0.dist-info/licenses/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
57
- maidr-1.6.0.dist-info/RECORD,,
54
+ maidr-1.6.1.dist-info/METADATA,sha256=WygnnC777b2MAHatLuMFLRjtoRZIW9gc5Ft1RKHE06I,3154
55
+ maidr-1.6.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
56
+ maidr-1.6.1.dist-info/licenses/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
57
+ maidr-1.6.1.dist-info/RECORD,,
File without changes