Skip to content

Commit

Permalink
docs: add docstring (#34)
Browse files Browse the repository at this point in the history
  • Loading branch information
SaaiVenkat authored May 13, 2024
1 parent 8b0a838 commit 59f0ca1
Show file tree
Hide file tree
Showing 25 changed files with 614 additions and 192 deletions.
2 changes: 2 additions & 0 deletions maidr/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# __version__ will be automatically updated by python-semantic-release
__version__ = "0.0.1"

from .core import Maidr
from .core.enum import PlotType
from .maidr import bar, box, count, heat, hist, line, scatter, stacked

__all__ = [
Expand Down
1 change: 1 addition & 0 deletions maidr/core/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .maidr import Maidr
2 changes: 2 additions & 0 deletions maidr/core/enum/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .maidr_key import MaidrKey
from .plot_type import PlotType
2 changes: 2 additions & 0 deletions maidr/core/enum/plot_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@


class PlotType(str, Enum):
"""An enumeration of plot types supported by MAIDR."""

BAR = "bar"
BOX = "box"
DODGED = "dodged_bar"
Expand Down
39 changes: 30 additions & 9 deletions maidr/core/figure_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,55 @@
from matplotlib.container import BarContainer
from matplotlib.figure import Figure

from maidr.core.enum.plot_type import PlotType
from maidr.core.maidr import Maidr
from maidr.core.maidr_plot_factory import MaidrPlotFactory
from maidr.core import Maidr
from maidr.core.enum import PlotType
from maidr.core.plot import MaidrPlotFactory


class FigureManager:
"""
Manages creation and retrieval of Maidr instances associated with matplotlib figures.
This class provides methods to manage Maidr objects which facilitate the organization and
manipulation of plots within matplotlib figures.
Attributes
----------
figs : dict
A dictionary that maps matplotlib Figure objects to their corresponding Maidr instances.
Methods
-------
create_maidr(ax, plot_type, **kwargs)
Creates a Maidr instance for the given Axes and plot type, and adds a plot to it.
_get_maidr(fig)
Retrieves or creates a Maidr instance associated with the given Figure.
get_axes(artist)
Recursively extracts Axes objects from the input artist or container.
"""

figs = dict()

@classmethod
def create_maidr(cls, ax: Axes, plot_type: PlotType, **kwargs) -> Maidr:
"""Create a Maidr instance for the given Axes and plot type, and adds a plot to it."""
if ax is None:
raise ValueError("No plot found.")
if plot_type is None:
raise ValueError("No plot type found.")
if ax.get_figure() is None:
raise ValueError(f"No figure found for axis: {ax}.")

# convert the plot to MAIDR representation
maidr = cls._maidr(ax.get_figure())
# Add plot to the Maidr object associated with the plot's figure.
maidr = cls._get_maidr(ax.get_figure())
plot = MaidrPlotFactory.create(ax, plot_type, **kwargs)
maidr.plots.append(plot)

return maidr

@classmethod
def _maidr(cls, fig: Figure) -> Maidr:
def _get_maidr(cls, fig: Figure) -> Maidr:
"""Retrieve or create a Maidr instance for the given Figure."""
if fig not in cls.figs.keys():
cls.figs[fig] = Maidr(fig)
return cls.figs[fig]
Expand All @@ -40,9 +63,7 @@ def _maidr(cls, fig: Figure) -> Maidr:
def get_axes(
artist: Artist | Axes | BarContainer | dict | list | None,
) -> Any:
"""
Recursively extracts Axes objects from the input artist.
"""
"""Recursively extracts Axes objects from the input artist or container."""
if artist is None:
return None
elif isinstance(artist, Axes):
Expand Down
74 changes: 68 additions & 6 deletions maidr/core/maidr.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,68 +3,125 @@

import io
import json
from uuid import uuid4
import uuid

from htmltools import HTML, HTMLDocument, RenderedHTML, tags, Tag
from lxml import etree

from matplotlib.figure import Figure

from maidr.core.maidr_plot import MaidrPlot
from maidr.core.plot import MaidrPlot


class Maidr:
"""
A class to handle the rendering and interaction of matplotlib figures with additional metadata.
Attributes
----------
_fig : Figure
The matplotlib figure associated with this instance.
_plots : list[MaidrPlot]
A list of MaidrPlot objects which hold additional plot-specific configurations.
Methods
-------
render(lib_prefix=None, include_version=True)
Creates and returns a rendered HTML representation of the figure.
save_html(file, lib_dir=None, include_version=True)
Saves the rendered HTML representation to a file.
show(renderer='auto')
Displays the rendered HTML content in the specified rendering context.
"""

def __init__(self, fig: Figure) -> None:
"""Create a new Maidr for the given ``matplotlib.figure.Figure``."""
self._fig = fig
self._plots = list()

@property
def fig(self) -> Figure:
"""Return the ``matplotlib.figure.Figure`` associated with this object."""
return self._fig

@property
def plots(self) -> list[MaidrPlot]:
"""Return the list of plots extracted from the ``fig``."""
return self._plots

def render(
self, *, lib_prefix: str | None = "lib", include_version: bool = True
) -> RenderedHTML:
"""
Render the document.
Parameters
----------
lib_prefix : str, default="lib"
A prefix to add to relative paths to dependency files.
include_version : bool, default=True
Whether to include the version number in the dependency's folder name.
"""
html = self._create_html_doc()
return html.render(lib_prefix=lib_prefix, include_version=include_version)

def save_html(
self, file: str, *, lib_dir: str | None = "lib", include_version: bool = True
) -> str:
"""
Save the HTML representation of the figure with MAIDR to a file.
Parameters
----------
file : str
The file to save to.
lib_dir : str, default="lib"
The directory to save the dependencies to (relative to the file's directory).
include_version : bool, default=True
Whether to include the version number in the dependency folder name.
"""
html = self._create_html_doc()
return html.save_html(file, libdir=lib_dir, include_version=include_version)

def show(self, renderer: Literal["auto", "ipython", "browser"] = "auto") -> object:
"""
Preview the HTML content using the specified renderer.
Parameters
----------
renderer : Literal["auto", "ipython", "browser"], default="auto"
The renderer to use for the HTML preview.
"""
html = self._create_html_tag()
return html.show(renderer)

def _create_html_tag(self) -> Tag:
"""Create the MAIDR HTML using HTML tags."""
svg = self._get_svg()
maidr = f"\nlet maidr = {json.dumps(self._flatten_maidr(), indent=2)}\n"

# inject svg and maidr into html tag
# Inject plot's svg and MAIDR structure into html tag.
return Maidr._inject_plot(svg, maidr)

def _create_html_doc(self) -> HTMLDocument:
"""Create an HTML document from Tag objects."""
return HTMLDocument(self._create_html_tag(), lang="en")

def _flatten_maidr(self) -> dict | list[dict]:
"""Returns a single plot schema or a list of schemas from the Maidr instance."""
maidr = [plot.schema for plot in self._plots]
return maidr if len(maidr) != 1 else maidr[0]

def _get_svg(self) -> HTML:
"""Extract the chart SVG from ``matplotlib.figure.Figure``."""
svg_buffer = io.StringIO()
self._fig.savefig(svg_buffer, format="svg")
str_svg = svg_buffer.getvalue()

etree.register_namespace("svg", "http://www.w3.org/2000/svg")
tree_svg = etree.fromstring(str_svg.encode(), parser=None)
root_svg = None
# find the `svg` tag and set unique id if not present else use it
# Find the `svg` tag and set unique id if not present else use it.
for element in tree_svg.iter(tag="{http://www.w3.org/2000/svg}svg"):
if "id" not in element.attrib:
element.attrib["id"] = Maidr._unique_id()
Expand All @@ -74,21 +131,26 @@ def _get_svg(self) -> HTML:

svg_buffer = io.StringIO() # Reset the buffer
svg_buffer.write(
etree.tostring(root_svg, pretty_print=True, encoding="unicode") # type: ignore # noqa
etree.tostring(
root_svg, pretty_print=True, encoding="unicode" # type: ignore
)
)

return HTML(svg_buffer.getvalue())

def _set_maidr_id(self, maidr_id: str) -> None:
"""Set a unique identifier to each ``MaidrPlot``."""
for maidr in self._plots:
maidr.set_id(maidr_id)

@staticmethod
def _unique_id() -> str:
return str(uuid4())
"""Generate a unique identifier string using UUID4."""
return str(uuid.uuid4())

@staticmethod
def _inject_plot(plot: HTML, maidr: str) -> Tag:
"""Embed the plot and associated MAIDR scripts into the HTML structure."""
return tags.html(
tags.head(
tags.meta(charset="UTF-8"),
Expand Down
46 changes: 0 additions & 46 deletions maidr/core/maidr_plot.py

This file was deleted.

2 changes: 2 additions & 0 deletions maidr/core/plot/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .maidr_plot import MaidrPlot
from .maidr_plot_factory import MaidrPlotFactory
11 changes: 5 additions & 6 deletions maidr/core/plot/bar_plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@
from matplotlib.axes import Axes
from matplotlib.container import BarContainer

from maidr.core.enum.maidr_key import MaidrKey
from maidr.core.enum.plot_type import PlotType
from maidr.core.maidr_plot import MaidrPlot
from maidr.core.enum import MaidrKey, PlotType
from maidr.core.plot import MaidrPlot
from maidr.exception import ExtractionError
from maidr.utils.mixin import (
ContainerExtractorMixin,
DictMergerMixin,
LevelExtractorMixin,
)
from maidr.exception.extraction_error import ExtractionError


class BarPlot(MaidrPlot, ContainerExtractorMixin, LevelExtractorMixin, DictMergerMixin):
Expand All @@ -30,14 +29,14 @@ def _extract_axes_data(self) -> dict:

def _extract_plot_data(self) -> list:
plot = self.extract_container(self.ax, BarContainer, include_all=True)
data = self.__extract_bar_container_data(plot)
data = self._extract_bar_container_data(plot)

if data is None:
raise ExtractionError(self.type, plot)

return data

def __extract_bar_container_data(
def _extract_bar_container_data(
self, plot: list[BarContainer] | None
) -> list | None:
if plot is None:
Expand Down
15 changes: 7 additions & 8 deletions maidr/core/plot/box_plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@

from matplotlib.axes import Axes

from maidr.core.enum.maidr_key import MaidrKey
from maidr.core.enum.plot_type import PlotType
from maidr.core.maidr_plot import MaidrPlot
from maidr.core.enum import MaidrKey, PlotType
from maidr.core.plot import MaidrPlot
from maidr.exception import ExtractionError
from maidr.utils.mixin import (
ContainerExtractorMixin,
DictMergerMixin,
LevelExtractorMixin,
)
from maidr.exception.extraction_error import ExtractionError


class BoxPlotContainer:
Expand Down Expand Up @@ -129,14 +128,14 @@ def _extract_axes_data(self) -> dict:

def _extract_plot_data(self) -> list:
plot = self.extract_container(self.ax, self.__container_type)
data = self.__extract_box_container_data(plot)
data = self._extract_box_container_data(plot)

if data is None:
raise ExtractionError(self.type, plot)

return data

def __extract_bxp_maidr(self, bxpstats: dict) -> list[dict]:
def _extract_bxp_maidr(self, bxpstats: dict) -> list[dict]:
bxp_maidr = list()
whiskers = self.extract_whiskers(bxpstats["whiskers"])
caps = self.extract_caps(bxpstats["caps"])
Expand All @@ -158,12 +157,12 @@ def __extract_bxp_maidr(self, bxpstats: dict) -> list[dict]:

return bxp_maidr

def __extract_box_container_data(self, plot: BoxPlotContainer | None) -> list[dict]:
def _extract_box_container_data(self, plot: BoxPlotContainer | None) -> list[dict]:
bxpstats = {
"whiskers": plot.whiskers,
"medians": plot.medians,
"caps": plot.caps,
"fliers": plot.fliers,
}

return self.__extract_bxp_maidr(bxpstats)
return self._extract_bxp_maidr(bxpstats)
Loading

0 comments on commit 59f0ca1

Please sign in to comment.