maidr 1.2.1__tar.gz → 1.3.0__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.
Files changed (53) hide show
  1. {maidr-1.2.1 → maidr-1.3.0}/PKG-INFO +1 -4
  2. {maidr-1.2.1 → maidr-1.3.0}/maidr/__init__.py +1 -1
  3. {maidr-1.2.1 → maidr-1.3.0}/maidr/core/maidr.py +12 -6
  4. {maidr-1.2.1 → maidr-1.3.0}/maidr/core/plot/lineplot.py +44 -30
  5. maidr-1.3.0/maidr/patch/lineplot.py +49 -0
  6. {maidr-1.2.1 → maidr-1.3.0}/maidr/util/environment.py +26 -7
  7. {maidr-1.2.1 → maidr-1.3.0}/pyproject.toml +5 -5
  8. maidr-1.2.1/maidr/patch/lineplot.py +0 -39
  9. {maidr-1.2.1 → maidr-1.3.0}/LICENSE +0 -0
  10. {maidr-1.2.1 → maidr-1.3.0}/README.md +0 -0
  11. {maidr-1.2.1 → maidr-1.3.0}/maidr/api.py +0 -0
  12. {maidr-1.2.1 → maidr-1.3.0}/maidr/core/__init__.py +0 -0
  13. {maidr-1.2.1 → maidr-1.3.0}/maidr/core/context_manager.py +0 -0
  14. {maidr-1.2.1 → maidr-1.3.0}/maidr/core/enum/__init__.py +0 -0
  15. {maidr-1.2.1 → maidr-1.3.0}/maidr/core/enum/library.py +0 -0
  16. {maidr-1.2.1 → maidr-1.3.0}/maidr/core/enum/maidr_key.py +0 -0
  17. {maidr-1.2.1 → maidr-1.3.0}/maidr/core/enum/plot_type.py +0 -0
  18. {maidr-1.2.1 → maidr-1.3.0}/maidr/core/enum/smooth_keywords.py +0 -0
  19. {maidr-1.2.1 → maidr-1.3.0}/maidr/core/figure_manager.py +0 -0
  20. {maidr-1.2.1 → maidr-1.3.0}/maidr/core/plot/__init__.py +0 -0
  21. {maidr-1.2.1 → maidr-1.3.0}/maidr/core/plot/barplot.py +0 -0
  22. {maidr-1.2.1 → maidr-1.3.0}/maidr/core/plot/boxplot.py +0 -0
  23. {maidr-1.2.1 → maidr-1.3.0}/maidr/core/plot/candlestick.py +0 -0
  24. {maidr-1.2.1 → maidr-1.3.0}/maidr/core/plot/grouped_barplot.py +0 -0
  25. {maidr-1.2.1 → maidr-1.3.0}/maidr/core/plot/heatmap.py +0 -0
  26. {maidr-1.2.1 → maidr-1.3.0}/maidr/core/plot/histogram.py +0 -0
  27. {maidr-1.2.1 → maidr-1.3.0}/maidr/core/plot/maidr_plot.py +0 -0
  28. {maidr-1.2.1 → maidr-1.3.0}/maidr/core/plot/maidr_plot_factory.py +0 -0
  29. {maidr-1.2.1 → maidr-1.3.0}/maidr/core/plot/regplot.py +0 -0
  30. {maidr-1.2.1 → maidr-1.3.0}/maidr/core/plot/scatterplot.py +0 -0
  31. {maidr-1.2.1 → maidr-1.3.0}/maidr/exception/__init__.py +0 -0
  32. {maidr-1.2.1 → maidr-1.3.0}/maidr/exception/extraction_error.py +0 -0
  33. {maidr-1.2.1 → maidr-1.3.0}/maidr/patch/__init__.py +0 -0
  34. {maidr-1.2.1 → maidr-1.3.0}/maidr/patch/barplot.py +0 -0
  35. {maidr-1.2.1 → maidr-1.3.0}/maidr/patch/boxplot.py +0 -0
  36. {maidr-1.2.1 → maidr-1.3.0}/maidr/patch/candlestick.py +0 -0
  37. {maidr-1.2.1 → maidr-1.3.0}/maidr/patch/clear.py +0 -0
  38. {maidr-1.2.1 → maidr-1.3.0}/maidr/patch/common.py +0 -0
  39. {maidr-1.2.1 → maidr-1.3.0}/maidr/patch/heatmap.py +0 -0
  40. {maidr-1.2.1 → maidr-1.3.0}/maidr/patch/highlight.py +0 -0
  41. {maidr-1.2.1 → maidr-1.3.0}/maidr/patch/histogram.py +0 -0
  42. {maidr-1.2.1 → maidr-1.3.0}/maidr/patch/kdeplot.py +0 -0
  43. {maidr-1.2.1 → maidr-1.3.0}/maidr/patch/regplot.py +0 -0
  44. {maidr-1.2.1 → maidr-1.3.0}/maidr/patch/scatterplot.py +0 -0
  45. {maidr-1.2.1 → maidr-1.3.0}/maidr/util/__init__.py +0 -0
  46. {maidr-1.2.1 → maidr-1.3.0}/maidr/util/dedup_utils.py +0 -0
  47. {maidr-1.2.1 → maidr-1.3.0}/maidr/util/mixin/__init__.py +0 -0
  48. {maidr-1.2.1 → maidr-1.3.0}/maidr/util/mixin/extractor_mixin.py +0 -0
  49. {maidr-1.2.1 → maidr-1.3.0}/maidr/util/mixin/merger_mixin.py +0 -0
  50. {maidr-1.2.1 → maidr-1.3.0}/maidr/util/regression_line_utils.py +0 -0
  51. {maidr-1.2.1 → maidr-1.3.0}/maidr/util/svg_utils.py +0 -0
  52. {maidr-1.2.1 → maidr-1.3.0}/maidr/widget/__init__.py +0 -0
  53. {maidr-1.2.1 → maidr-1.3.0}/maidr/widget/shiny.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: maidr
3
- Version: 1.2.1
3
+ Version: 1.3.0
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
@@ -26,14 +26,11 @@ Classifier: Topic :: Scientific/Engineering :: Visualization
26
26
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
27
27
  Classifier: Topic :: Text Processing :: Markup :: HTML
28
28
  Requires-Dist: htmltools (>=0.5)
29
- Requires-Dist: jupyter (>=1.0.0,<2.0.0)
30
29
  Requires-Dist: lxml (>=5.1.0)
31
30
  Requires-Dist: matplotlib (>=3.8)
32
31
  Requires-Dist: mplfinance (>=0.12.10b0,<0.13.0)
33
32
  Requires-Dist: numpy (>=1.26)
34
33
  Requires-Dist: seaborn (>=0.12)
35
- Requires-Dist: statsmodels (>=0.14.4,<0.15.0)
36
- Requires-Dist: virtualenv (>=20.26.6,<21.0.0)
37
34
  Requires-Dist: wrapt (>=1.16.0,<2.0.0)
38
35
  Project-URL: Homepage, https://xability.github.io/py-maidr
39
36
  Project-URL: Repository, https://github.com/xability/py-maidr
@@ -1,4 +1,4 @@
1
- __version__ = "1.2.1"
1
+ __version__ = "1.3.0"
2
2
 
3
3
  from .api import close, render, save_html, show, stacked
4
4
  from .core import Maidr
@@ -8,7 +8,7 @@ import os
8
8
  import tempfile
9
9
  import uuid
10
10
  import webbrowser
11
- from typing import Any, Literal
11
+ from typing import Any, Literal, cast
12
12
 
13
13
  import matplotlib.pyplot as plt
14
14
  from htmltools import HTML, HTMLDocument, Tag, tags
@@ -102,14 +102,20 @@ class Maidr:
102
102
  The renderer to use for the HTML preview.
103
103
  """
104
104
  html = self._create_html_tag(use_iframe=True) # Always use iframe for display
105
- _renderer = Environment.get_renderer()
106
- if _renderer == "browser" or (
107
- Environment.is_interactive_shell() and not Environment.is_notebook()
108
- ):
105
+
106
+ # Use the passed renderer parameter, fallback to auto-detection
107
+ if renderer == "auto":
108
+ _renderer = cast(Literal["ipython", "browser"], Environment.get_renderer())
109
+ else:
110
+ _renderer = renderer
111
+
112
+ # Only try browser opening if explicitly requested as browser and not in notebook
113
+ if _renderer == "browser" and not Environment.is_notebook():
109
114
  return self._open_plot_in_browser()
115
+
110
116
  if clear_fig:
111
117
  plt.close()
112
- return html.show(renderer)
118
+ return html.show(_renderer)
113
119
 
114
120
  def clear(self):
115
121
  self._plots = []
@@ -8,6 +8,7 @@ from maidr.core.enum.plot_type import PlotType
8
8
  from maidr.core.plot.maidr_plot import MaidrPlot
9
9
  from maidr.exception.extraction_error import ExtractionError
10
10
  from maidr.util.mixin.extractor_mixin import LineExtractorMixin
11
+ import uuid
11
12
 
12
13
 
13
14
  class MultiLinePlot(MaidrPlot, LineExtractorMixin):
@@ -42,62 +43,75 @@ class MultiLinePlot(MaidrPlot, LineExtractorMixin):
42
43
  super().__init__(ax, PlotType.LINE)
43
44
 
44
45
  def _get_selector(self) -> Union[str, List[str]]:
45
- return ["g[maidr='true'] > path"]
46
+ # Return selectors for all lines that have data
47
+ all_lines = self.ax.get_lines()
48
+ if not all_lines:
49
+ return ["g[maidr='true'] > path"]
50
+
51
+ selectors = []
52
+ for line in all_lines:
53
+ # Only create selectors for lines that have data (same logic as _extract_line_data)
54
+ xydata = line.get_xydata()
55
+ if xydata is None or not xydata.size: # type: ignore
56
+ continue
57
+ gid = line.get_gid()
58
+ if gid:
59
+ selectors.append(f"g[id='{gid}'] path")
60
+ else:
61
+ selectors.append("g[maidr='true'] > path")
62
+
63
+ if not selectors:
64
+ return ["g[maidr='true'] > path"]
65
+
66
+ return selectors
46
67
 
47
- def _extract_plot_data(self) -> list[dict]:
48
- plot = self.extract_lines(self.ax)
49
- data = self._extract_line_data(plot)
68
+ def _extract_plot_data(self) -> Union[List[List[dict]], None]:
69
+ data = self._extract_line_data()
50
70
 
51
71
  if data is None:
52
- raise ExtractionError(self.type, plot)
72
+ raise ExtractionError(self.type, None)
53
73
 
54
74
  return data
55
75
 
56
- def _extract_line_data(
57
- self, plot: Union[List[Line2D], None]
58
- ) -> Union[List[dict], None]:
76
+ def _extract_line_data(self) -> Union[List[List[dict]], None]:
59
77
  """
60
- Extract data from multiple line objects.
61
-
62
- Parameters
63
- ----------
64
- plot : list[Line2D] | None
65
- List of Line2D objects to extract data from.
78
+ Extract data from all line objects and return as separate arrays.
66
79
 
67
80
  Returns
68
81
  -------
69
- list[dict] | None
70
- List of dictionaries containing x,y coordinates and line identifiers,
71
- or None if the plot data is invalid.
82
+ list[list[dict]] | None
83
+ List of lists, where each inner list contains dictionaries with x,y coordinates
84
+ and line identifiers for one line, or None if the plot data is invalid.
72
85
  """
73
- if plot is None or len(plot) == 0:
86
+ all_lines = self.ax.get_lines()
87
+ if not all_lines:
74
88
  return None
75
89
 
76
- all_line_data = []
90
+ all_lines_data = []
77
91
 
78
- # Process each line in the plot
79
- for i, line in enumerate(plot):
80
- if line.get_xydata() is None:
92
+ for line in all_lines:
93
+ xydata = line.get_xydata()
94
+ if xydata is None or not xydata.size: # type: ignore
81
95
  continue
82
96
 
83
- # Tag the element for highlighting
84
97
  self._elements.append(line)
85
98
 
86
- # Extract data from this line
99
+ # Assign unique GID to each line if not already set
100
+ if line.get_gid() is None:
101
+ unique_gid = f"maidr-{uuid.uuid4()}"
102
+ line.set_gid(unique_gid)
87
103
 
88
104
  label: str = line.get_label() # type: ignore
89
105
  line_data = [
90
106
  {
91
107
  MaidrKey.X: float(x),
92
108
  MaidrKey.Y: float(y),
93
- # Replace labels starting with '_child'
94
- # with an empty string to exclude
95
- # internal or non-relevant labels from being used as identifiers.
96
109
  MaidrKey.FILL: (label if not label.startswith("_child") else ""),
97
110
  }
98
111
  for x, y in line.get_xydata() # type: ignore
99
112
  ]
100
- if len(line_data) > 0:
101
- all_line_data.append(line_data)
102
113
 
103
- return all_line_data if all_line_data else None
114
+ if line_data:
115
+ all_lines_data.append(line_data)
116
+
117
+ return all_lines_data if all_lines_data else None
@@ -0,0 +1,49 @@
1
+ from __future__ import annotations
2
+
3
+ import wrapt
4
+ from matplotlib.axes import Axes
5
+ from matplotlib.lines import Line2D
6
+
7
+ from maidr.core.enum import PlotType
8
+ from maidr.patch.common import common
9
+ from maidr.core.enum.smooth_keywords import SMOOTH_KEYWORDS
10
+ from maidr.core.context_manager import ContextManager
11
+ from maidr.core.figure_manager import FigureManager
12
+
13
+
14
+ def line(wrapped, instance, args, kwargs) -> Axes | list[Line2D]:
15
+ """
16
+ Wrapper for line plotting functions that creates a single MAIDR plot per axes to handle
17
+ multiline plots (matplotlib) and single-call plots (seaborn) correctly by preventing
18
+ multiple MAIDR layers and using internal context to avoid cyclic processing.
19
+ """
20
+ # Don't proceed if the call is made internally by the patched function.
21
+ if ContextManager.is_internal_context():
22
+ return wrapped(*args, **kwargs)
23
+
24
+ # Set the internal context to avoid cyclic processing.
25
+ with ContextManager.set_internal_context():
26
+ # Patch the plotting function.
27
+ plot = wrapped(*args, **kwargs)
28
+
29
+ # Get the axes from the plot result (works for both matplotlib and seaborn)
30
+ ax = FigureManager.get_axes(plot)
31
+ if ax is None:
32
+ # If we can't get axes from plot, try from instance
33
+ ax = instance if isinstance(instance, Axes) else getattr(instance, "axes", None)
34
+
35
+ # Check if a MAIDR plot already exists for this axes
36
+ if ax is not None and not hasattr(ax, "_maidr_plot_created"):
37
+ # Create MAIDR plot only once for this axes using common()
38
+ common(PlotType.LINE, lambda *a, **k: plot, instance, args, kwargs)
39
+ # Mark that a MAIDR plot has been created for this axes
40
+ setattr(ax, "_maidr_plot_created", True)
41
+
42
+ return plot
43
+
44
+
45
+ # Patch matplotlib function.
46
+ wrapt.wrap_function_wrapper(Axes, "plot", line)
47
+
48
+ # Patch seaborn function.
49
+ wrapt.wrap_function_wrapper("seaborn", "lineplot", line)
@@ -1,5 +1,6 @@
1
1
  import json
2
2
  import os
3
+ import sys
3
4
 
4
5
 
5
6
  class Environment:
@@ -24,10 +25,19 @@ class Environment:
24
25
  try:
25
26
  from IPython import get_ipython # type: ignore
26
27
 
27
- return get_ipython() is not None and (
28
- "ipykernel" in str(get_ipython())
29
- or "google.colab" in str(get_ipython())
30
- )
28
+ ipy = get_ipython()
29
+ if ipy is not None:
30
+ # Check for Pyodide/JupyterLite specific indicators
31
+ ipy_str = str(ipy).lower()
32
+ if "pyodide" in ipy_str or "jupyterlite" in ipy_str:
33
+ return True
34
+ # Check for other notebook indicators
35
+ if "ipykernel" in str(ipy) or "google.colab" in str(ipy):
36
+ return True
37
+ # Check for Pyodide platform
38
+ if sys.platform == "emscripten":
39
+ return True
40
+ return False
31
41
  except ImportError:
32
42
  return False
33
43
 
@@ -61,10 +71,19 @@ class Environment:
61
71
  ipy = ( # pyright: ignore[reportUnknownVariableType]
62
72
  IPython.get_ipython() # pyright: ignore[reportUnknownMemberType, reportPrivateImportUsage]
63
73
  )
64
- renderer = "ipython" if ipy else "browser"
74
+ if ipy is not None:
75
+ # Check for Pyodide/JupyterLite
76
+ ipy_str = str(ipy).lower()
77
+ if "pyodide" in ipy_str or "jupyterlite" in ipy_str:
78
+ return "ipython"
79
+ # Check for Pyodide platform
80
+ if sys.platform == "emscripten":
81
+ return "ipython"
82
+ return "ipython"
83
+ else:
84
+ return "browser"
65
85
  except ImportError:
66
- renderer = "browser"
67
- return renderer
86
+ return "browser"
68
87
 
69
88
  @staticmethod
70
89
  def initialize_llm_secrets(unique_id: str) -> str:
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
4
4
 
5
5
  [tool.poetry]
6
6
  name = "maidr"
7
- version = "1.2.1"
7
+ version = "1.3.0"
8
8
  description = "Multimodal Access and Interactive Data Representations"
9
9
  authors = [
10
10
  "JooYoung Seo <jseo1005@illinois.edu>",
@@ -40,20 +40,20 @@ numpy = ">=1.26"
40
40
  seaborn = ">=0.12"
41
41
  lxml = ">=5.1.0"
42
42
  htmltools = ">=0.5"
43
- jupyter = "^1.0.0"
44
43
  wrapt = "^1.16.0"
45
- virtualenv = "^20.26.6"
46
44
  mplfinance = "^0.12.10b0"
47
- statsmodels = "^0.14.4"
48
45
 
49
46
  [tool.poetry.group.dev.dependencies]
50
47
  black = "23.3.0"
51
48
  pre-commit = "^3.3.2"
52
49
  pytest = "^7.3.2"
53
- python-semantic-release = "^8.7.0"
50
+ python-semantic-release = "9.21.0"
54
51
  pytest-mock = "^3.12.0"
55
52
  tox = "^4.13.0"
56
53
  quartodoc = "^0.7.5"
54
+ jupyter = "^1.0.0"
55
+ virtualenv = "^20.26.6"
56
+ statsmodels = "^0.14.4"
57
57
 
58
58
  [tool.black]
59
59
  line-length = 88
@@ -1,39 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import wrapt
4
- from matplotlib.axes import Axes
5
- from matplotlib.lines import Line2D
6
-
7
- from maidr.core.enum import PlotType
8
- from maidr.patch.common import common
9
- from maidr.core.enum.smooth_keywords import SMOOTH_KEYWORDS
10
-
11
-
12
- def line(wrapped, instance, args, kwargs) -> Axes | list[Line2D]:
13
- """
14
- Wrapper function for line plotting functions in matplotlib and seaborn.
15
-
16
- Parameters
17
- ----------
18
- wrapped : callable
19
- The wrapped function (plot or lineplot)
20
- instance : object
21
- The object to which the wrapped function belongs
22
- args : tuple
23
- Positional arguments passed to the wrapped function
24
- kwargs : dict
25
- Keyword arguments passed to the wrapped function
26
-
27
- Returns
28
- -------
29
- Axes | list[Line2D]
30
- The result of the wrapped function after processing
31
- """
32
- return common(PlotType.LINE, wrapped, instance, args, kwargs)
33
-
34
-
35
- # Patch matplotlib function.
36
- wrapt.wrap_function_wrapper(Axes, "plot", line)
37
-
38
- # Patch seaborn function.
39
- wrapt.wrap_function_wrapper("seaborn", "lineplot", line)
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