diff --git a/README.md b/README.md index cc9b8df..3b3e2f2 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,16 @@ # Aquarel 🎨 -![](https://img.shields.io/pypi/v/aquarel?style=for-the-badge) ![](https://img.shields.io/pypi/l/aquarel?style=for-the-badge) +[![PyPi](https://img.shields.io/pypi/v/aquarel)](https://pypi.org/project/aquarel/) +[![License](https://img.shields.io/github/license/lgienapp/aquarel)]() +[![Documentation Status](https://readthedocs.org/projects/aquarel/badge/?version=latest)](https://aquarel.readthedocs.io/en/latest/?badge=latest) + Aquarel is a lightweight templating engine and wrapper around Matplotlibs' `rcparams` to make styling plots simple. Aquarel templates can be defined programmatically and be serialized and shared in a JSON format. +Full documentation is available at [aquarel.readthedocs.io](https://aquarel.readthedocs.io/en/latest/?badge=latest). + + ## Installation Install via pip: `pip install aquarel` @@ -102,3 +108,12 @@ Add your own with a pull request! | `scientific` | Space-efficient and color-blind friendly theme for printing on paper | ![](https://github.com/lgienapp/aquarel/blob/main/assets/scientific.png?raw=true) | | `umbra_dark` | Balanced dark theme based on the [penumbra](https://github.com/nealmckee/penumbra) color scheme | ![](https://github.com/lgienapp/aquarel/blob/main/assets/umbra_dark.png?raw=true) | | `umbra_light` | Balanced light theme based on the [penumbra](https://github.com/nealmckee/penumbra) color scheme | ![](https://github.com/lgienapp/aquarel/blob/main/assets/umbra_light.png?raw=true) | + +## FAQ + +###### How is this different from matplotlib style sheets? + +`aquarel` is a wrapper around the stylesheets, so everything you can do with stylesheets can be achieved with `aquarel`. However there are some notable shortcomings of stylesheets that `aquarel` adresses: +1. **On-the-fly templating** – the stylesheets are applied once and are then used for every plot in the current plotting context (py-file, notebook, ipython session, ...). `aquarel` takes a different approach here and aims to provide per-plot styling with optional temporary changes. The style `aquarel` applies lasts throughout the context manager (`with aquarel.Theme:`), and switches back to whatever is the global default style outside of it. This allows you to do plot-level temporary changes. You have one plot in your notebook that needs no minor ticks? just `with theme.set_ticks():` for this plot only. +2. **Simplified templating**: matplotlib stylesheets have a lot of redundant keys for most applications. For example, you rarely want to have different colors for both axes; while possible with a stylefile, its cumbersome to change all the different keys to achieve a uniform look. `aquarel` simplifies this with e.x. a single `set_color(ticks="#eee")` call, which changes all related and relevant keys for ticks. Note that this simplifies the API, but does not restrict capabilities: the `set_overrides` method accepts every possible stylefile key if you want to access low-level styling. +3. **Transforms**: some style elements, like trimmed axes, are not achievable with stylesheets alone (see README for more informations). `aquarel` defines a few of these transforms (and hopefully many more in the future), and makes them persistable and shareable through aquarel themes. Instead of having to apply a seaborn despine after every plot, you can have a global style that specifies a trim, and have consistent styling throughout with minimal code repitition. \ No newline at end of file diff --git a/aquarel/theme.py b/aquarel/theme.py index 1925ada..69531b7 100644 --- a/aquarel/theme.py +++ b/aquarel/theme.py @@ -94,6 +94,20 @@ class Theme: "rotate_xlabel": rotate_xlabel, "rotate_ylabel": rotate_ylabel, } + _legend_location_options = [ + 'best', + 'upper right', + 'upper left', + 'lower left', + 'lower right', + 'right', + 'center left', + 'center right', + 'lower center', + 'upper center', + 'center' + ] + # Mapping from aquarel keys to matplotlib rcparams _rcparams_mapping = { "title": { @@ -144,6 +158,8 @@ class Theme: "grid_color": "grid.color", "tick_color": ["xtick.color", "ytick.color"], "tick_label_color": ["xtick.labelcolor", "ytick.labelcolor"], + "legend_background_color": "legend.facecolor", + "legend_border_color": "legend.edgecolor", "axis_label_color": "axes.labelcolor", "palette": "axes.prop_cycle", }, @@ -182,6 +198,18 @@ class Theme: "left": "ytick.labelleft", "right": "ytick.labelright", }, + "legend": { + "location": "legend.loc", + "round": "legend.fancybox", + "shadow": "legend.shadow", + "title_size": "legend.title_fontsize", + "text_size": "legend.fontsize", + "alpha": "legend.framealpha", + "marker_scale": "legend.markerscale", + "padding": "legend.borderpad", + "margin": "legend.borderaxespad", + "spacing": ["legend.handletextpad", "legend.labelspacing"] + } } def __init__(self, name: Optional[str] = None, description: Optional[str] = None): @@ -225,6 +253,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): def _update_params(self, param_key, value_dict): """ Updates the parameters of the theme. + :param param_key: the parameter key to modify :param value_dict: the value dict to update the parameter key with """ @@ -247,8 +276,8 @@ def _update_params(self, param_key, value_dict): def _update_transforms(self, value_dict): """ Updates the transforms of the theme. + :param value_dict: dictionary of transform names and args - :return: """ # Filter unset attributes and attributes not in the base transform template transforms = dict( @@ -263,6 +292,7 @@ def _update_transforms(self, value_dict): def save(self, path: str): """ Write the template to a JSON template file + :param path: file to write the template to """ with open(path, "w") as f: @@ -317,7 +347,8 @@ def set_transforms( ): """ Set the transforms - :param trim: if true, trim the axes to the nearest major tick + + :param trim: trims the axes to the nearest major tick, can be {"x", "y", "both"} :param offset: offset shift of the axes in pt. Applies to all axes :param rotate_xlabel: rotation of x-axis labels in degrees :param rotate_ylabel: rotation of y-axis labels in degrees @@ -326,7 +357,7 @@ def set_transforms( """ self._update_transforms( { - "trim": {} if trim else None, + "trim": {"axis": trim} if trim in self._axis_options else None, "offset": {"distance": offset} if offset is not None else None, "rotate_xlabel": {"degrees": rotate_xlabel} if rotate_xlabel is not None @@ -341,6 +372,7 @@ def set_transforms( def set_overrides(self, rc: dict): """ Set custom overrides of rcparam parameters directly + :param rc: Dict of valid matplotlib rcparams :return: self """ @@ -356,6 +388,7 @@ def set_title( ): """ Sets title styling options. + :param location: the location of the title, one of {left, right, center} default: 'center' :param pad: pad between axes and title in pt, default: 6.0 :param size: the font size of the title, float or one of {'xx-small', 'x-small', 'small', 'medium', 'large', @@ -389,6 +422,7 @@ def set_grid( ): """ Set grid styling options. + :param draw: True if grid should be drawn, False otherwise, default: False :param axis: axes along which the grid should be drawn, can be {"both", "x", "y"}, default: "both" :param ticks: which tick level to base the grid on, can be {"major", "minor"}, default: "major" @@ -403,7 +437,7 @@ def set_grid( "draw": draw, "axis": axis if axis in self._axis_options else None, "ticks": ticks if ticks in self._tick_options else None, - "alpha": alpha if 0 <= alpha <= 1 else None, + "alpha": alpha, "style": style if style in self._line_style_options else None, "width": width, }, @@ -423,6 +457,7 @@ def set_axes( ): """ Set axis styling options + :param width: axis line width, default: 1.0 :param top: display top axis, default: True :param bottom: display bottom axis, default: True @@ -460,8 +495,12 @@ def set_color( grid_color: Optional[str] = None, tick_color: Optional[str] = None, tick_label_color: Optional[str] = None, + legend_background_color: Optional[str] = None, + legend_border_color: Optional[str] = None, + ): """ + Sets color options. :param palette: The color palette to cycle through for plot elements, should be list of valid color arguments :param figure_background_color: the background color of the whole figure @@ -473,6 +512,8 @@ def set_color( :param grid_color: the color of the grid lines :param tick_color: the color of the ticks :param tick_label_color: the color of the tick labels + :param legend_border_color: color of the legend border + :param legend_background_color: color of the legend background :return: self """ self._update_params( @@ -487,6 +528,8 @@ def set_color( "tick_color": tick_color, "tick_label_color": tick_label_color, "axes_label_color": axes_label_color, + "legend_background_color": legend_background_color, + "legend_border_color": legend_border_color, "palette": palette, }, ) @@ -500,6 +543,7 @@ def set_axis_labels( ): """ Set axis label styling options. + :param pad: padding of the axis label :param size: font size of the axis label, can be {"xx-small", "x-small", "small", "medium", "large", "x-large", "xx-large"}, default: "normal" :param weight: font weight of the axis label, can be {"ultralight", "light", "normal", "regular", "book", "medium", "roman", "semibold", "demibold", "demi", "bold", "heavy", "extra bold", "black"}, default: "normal" @@ -526,6 +570,7 @@ def set_tick_labels( ): """ Set tick label styling options. + :param location: location of the tick labels, can be {"left", "right", "bottom", "top", "center"}, default: center :param size: size of the tick label, can be {"xx-small", "x-small", "small", "medium", "large", "x-large", "xx-large"}, default: "normal" :param left: whether to draw the tick labels to the left of the y-axis, default: True @@ -562,6 +607,7 @@ def set_ticks( ): """ Set styling options for ticks. + :param x_align: alignment of ticks along the horizontal axes, can be {"center", "right", "left"}, default: "center" :param y_align: alignment of ticks along the vertical axes, can be {"center", "top", "bottom", "baseline", "center_baseline"}, default: "center_baseline" :param direction: direction the ticks should be facing, can be {"in", "out", "inout"}, default: "out" @@ -600,6 +646,7 @@ def set_ticks( def set_lines(self, style: Optional[str] = None, width: Optional[float] = None): """ Set line styling options. + :param style: the style to draw lines with, can be {"-", "--", "-.", ":", ""}, default: "-" :param width: the width to draw lines with in pt, default: 1.5 :return: self @@ -629,6 +676,7 @@ def set_font( ): """ Set font styling options. + :param family: font family to use, can be {}, default: sans-serif :param cursive: which font(s) to use for cursive text :param fantasy: which font(s) to use for fantasy text @@ -660,10 +708,56 @@ def set_font( ) return self + def set_legend( + self, + location: Optional[str] = None, + round: Optional[bool] = None, + shadow: Optional[bool] = None, + title_size: Optional[str] = None, + text_size: Optional[str] = None, + alpha: Optional[float] = None, + marker_scale: Optional[float] = None, + padding: Optional[Union[int, float]] = None, + margin: Optional[Union[int, float]] = None, + spacing: Optional[Union[int, float]] = None + ): + """ + Set legend styling options. + + :param location: The location of the legend. Can be {'best', 'upper right', 'upper left', 'lower left', 'lower right', 'right', 'center left', 'center right', 'lower center', 'upper center', 'center'} or a 2-tuple giving the coordinates of the lower-left corner. Default: 'best' + :param round: indicates if legend corners should be rounded or rectangular. Default: True + :param shadow: indicates if the legend should cast a shadow. Default: False + :param title_size: font size of the legend title, can be {"xx-small", "x-small", "small", "medium", "large", "x-large", "xx-large"}, default: "medium" + :param text_size: font size of the legend title, can be {"xx-small", "x-small", "small", "medium", "large", "x-large", "xx-large"}, default: "medium" + :param alpha: transparency of the legend patch. + :param marker_scale: the relative size of legend markers. Default: 1.0 + :param padding: space between legend border and legend content in pt. Default: 0.4 + :param margin: space between legend border and axes in pt. Default: 0.5 + :param spacing: spacing of legend elements in pt. Default: 0.5 + :return: self + """ + self._update_params( + "legend", + { + "location": location if (location in self._legend_location_options) or type(location) == tuple else None, + "round": round, + "shadow": shadow, + "title_size": title_size if title_size in self._font_size_options else None, + "text_size": text_size if text_size in self._font_size_options else None, + "alpha": alpha, + "marker_scale": marker_scale, + "padding": padding, + "margin": margin, + "spacing": spacing + } + ) + return self + @classmethod def from_file(cls, filename: str): """ Initialize a theme from a theme file + :param filename: file to load theme dictionary from :return: cls """ @@ -675,6 +769,7 @@ def from_file(cls, filename: str): def from_dict(cls, data: dict): """ Initialize a theme from a dictionary + :param data: theme dictionary to initialize from :return: cls """ diff --git a/aquarel/themes/arctic_dark.json b/aquarel/themes/arctic_dark.json index 020f083..f52fe44 100644 --- a/aquarel/themes/arctic_dark.json +++ b/aquarel/themes/arctic_dark.json @@ -59,7 +59,9 @@ }, "overrides": {}, "transforms": { - "trim": {}, + "trim": { + "axis": "both" + }, "offset": { "distance": 10 } diff --git a/aquarel/themes/arctic_light.json b/aquarel/themes/arctic_light.json index d7eb7c0..eebf946 100644 --- a/aquarel/themes/arctic_light.json +++ b/aquarel/themes/arctic_light.json @@ -59,7 +59,9 @@ }, "overrides": {}, "transforms": { - "trim": {}, + "trim": { + "axis": "both" + }, "offset": { "distance": 10 } diff --git a/aquarel/themes/boxy_dark.json b/aquarel/themes/boxy_dark.json index 451b663..095c634 100644 --- a/aquarel/themes/boxy_dark.json +++ b/aquarel/themes/boxy_dark.json @@ -9,8 +9,8 @@ "top": true, "left": true, "right": true, - "xmargin": 0, - "ymargin": 0 + "xmargin": 0.02, + "ymargin": 0.02 }, "ticks": { "draw_minor": true, @@ -29,6 +29,10 @@ "Arial" ] }, + "legend": { + "round": false, + "alpha": 1.0 + }, "colors": { "figure_background_color": "black", "plot_background_color": "black", @@ -38,6 +42,8 @@ "grid_color": "#eeeeee", "tick_color": "white", "tick_label_color": "white", + "legend_border_color": "white", + "legend_background_color": "black", "palette": [ "#3c9797", "#fc6464", diff --git a/aquarel/themes/boxy_light.json b/aquarel/themes/boxy_light.json index 1bd1f3d..768d593 100644 --- a/aquarel/themes/boxy_light.json +++ b/aquarel/themes/boxy_light.json @@ -9,8 +9,8 @@ "top": true, "left": true, "right": true, - "xmargin": 0, - "ymargin": 0 + "xmargin": 0.02, + "ymargin": 0.02 }, "ticks": { "draw_minor": true, @@ -29,6 +29,10 @@ "Arial" ] }, + "legend": { + "round": false, + "alpha": 1.0 + }, "colors": { "figure_background_color": "white", "plot_background_color": "white", @@ -37,6 +41,8 @@ "grid_color": "#aaaaaa", "tick_color": "black", "tick_label_color": "black", + "legend_border_color": "black", + "legend_background_color": "white", "palette": [ "#3c9797", "#fc6464", diff --git a/aquarel/themes/scientific.json b/aquarel/themes/scientific.json index fbd7eb1..e288ab9 100644 --- a/aquarel/themes/scientific.json +++ b/aquarel/themes/scientific.json @@ -9,8 +9,8 @@ "top": false, "left": true, "right": false, - "xmargin": 0, - "ymargin": 0 + "xmargin": 0.02, + "ymargin": 0.02 }, "axis_labels": { "pad": -1, @@ -66,7 +66,9 @@ }, "overrides": {}, "transforms": { - "trim": {}, + "trim": { + "axis": "both" + }, "offset": { "distance": 10 } diff --git a/aquarel/themes/umbra_dark.json b/aquarel/themes/umbra_dark.json index 6e5d1c3..37d69eb 100644 --- a/aquarel/themes/umbra_dark.json +++ b/aquarel/themes/umbra_dark.json @@ -9,8 +9,8 @@ "top": false, "left": true, "right": false, - "xmargin": 0, - "ymargin": 0 + "xmargin": 0.02, + "ymargin": 0.02 }, "ticks": { "direction": "inout", diff --git a/aquarel/themes/umbra_light.json b/aquarel/themes/umbra_light.json index 1f9fbeb..cc0cc4c 100644 --- a/aquarel/themes/umbra_light.json +++ b/aquarel/themes/umbra_light.json @@ -9,8 +9,8 @@ "top": false, "left": true, "right": false, - "xmargin": 0, - "ymargin": 0 + "xmargin": 0.02, + "ymargin": 0.02 }, "ticks": { "direction": "inout", diff --git a/aquarel/transforms.py b/aquarel/transforms.py index 08ef8c3..aa9457a 100644 --- a/aquarel/transforms.py +++ b/aquarel/transforms.py @@ -5,6 +5,7 @@ def rotate_ylabel(degrees: int): """ Rotates the y-labels of the current plot. + :param degrees: rotation in degrees """ axes = plt.gcf().axes @@ -15,6 +16,7 @@ def rotate_ylabel(degrees: int): def rotate_xlabel(degrees: int): """ Rotates the x-labels of the current plot. + :param degrees: rotation in degrees """ axes = plt.gcf().axes @@ -26,8 +28,8 @@ def offset(distance: int): """ Offsets the plot spines. Code partly taken from https://github.com/mwaskom/seaborn/blob/563e96d3be1eaee8db8dfbccf7eed1f1c66dfd31/seaborn/utils.py#L292 + :param distance: offset distance int pt. - :return: """ axes = plt.gcf().axes for ax_i in axes: @@ -35,48 +37,50 @@ def offset(distance: int): ax_i.spines[side].set_position(("outward", distance)) -def trim(): +def trim(axis: str): """ Trims axes of a plot to first and last major tick. Code partly taken from https://github.com/mwaskom/seaborn/blob/563e96d3be1eaee8db8dfbccf7eed1f1c66dfd31/seaborn/utils.py#L292 - :return: + + :param axis: axes to apply the trim to. Can be {"x", "y", "both"}. """ - axes = plt.gcf().axes # Apply trim to all axes - for ax_i in axes: - # Trim x direction (bottom and top) - xticks_major = np.asarray(ax_i.get_xticks(minor=False)) - xticks = np.asarray(ax_i.get_xticks(minor=True)) - if xticks.size: - # Get first and last major ticks - firsttick = np.compress(xticks_major >= min(ax_i.get_xlim()), xticks_major)[ - 0 - ] - lasttick = np.compress(xticks_major <= max(ax_i.get_xlim()), xticks_major)[ - -1 - ] - # Trim spines to tick range - ax_i.spines["bottom"].set_bounds(firsttick, lasttick) - ax_i.spines["top"].set_bounds(firsttick, lasttick) - # Update tick values for both minor and major ticks - xticks = xticks.compress(xticks <= lasttick) - xticks = xticks.compress(xticks >= firsttick) - ax_i.set_xticks(xticks, minor=True) - # Trim y direction (left and right) - yticks_major = np.asarray(ax_i.get_yticks(minor=False)) - yticks = np.asarray(ax_i.get_yticks(minor=True)) - if yticks.size: - # Get first and last major ticks - firsttick = np.compress(yticks_major >= min(ax_i.get_ylim()), yticks_major)[ - 0 - ] - lasttick = np.compress(yticks_major <= max(ax_i.get_ylim()), yticks_major)[ - -1 - ] - # Trim spines to tick range - ax_i.spines["left"].set_bounds(firsttick, lasttick) - ax_i.spines["right"].set_bounds(firsttick, lasttick) - # Update tick values - newticks = yticks.compress(yticks <= lasttick) - newticks = newticks.compress(newticks >= firsttick) - ax_i.set_yticks(newticks, minor=True) + for ax_i in plt.gcf().axes: + if axis in ["x", "both"]: + # Trim x direction (bottom and top) + xticks_major = np.asarray(ax_i.get_xticks(minor=False)) + xticks = np.asarray(ax_i.get_xticks(minor=True)) + if xticks.size: + # Get first and last major ticks + firsttick = np.compress( + xticks_major >= min(ax_i.get_xlim()), xticks_major + )[0] + lasttick = np.compress( + xticks_major <= max(ax_i.get_xlim()), xticks_major + )[-1] + # Trim spines to tick range + ax_i.spines["bottom"].set_bounds(firsttick, lasttick) + ax_i.spines["top"].set_bounds(firsttick, lasttick) + # Update tick values for both minor and major ticks + xticks = xticks.compress(xticks <= lasttick) + xticks = xticks.compress(xticks >= firsttick) + ax_i.set_xticks(xticks, minor=True) + if axis in ["y", "both"]: + # Trim y direction (left and right) + yticks_major = np.asarray(ax_i.get_yticks(minor=False)) + yticks = np.asarray(ax_i.get_yticks(minor=True)) + if yticks.size: + # Get first and last major ticks + firsttick = np.compress( + yticks_major >= min(ax_i.get_ylim()), yticks_major + )[0] + lasttick = np.compress( + yticks_major <= max(ax_i.get_ylim()), yticks_major + )[-1] + # Trim spines to tick range + ax_i.spines["left"].set_bounds(firsttick, lasttick) + ax_i.spines["right"].set_bounds(firsttick, lasttick) + # Update tick values + newticks = yticks.compress(yticks <= lasttick) + newticks = newticks.compress(newticks >= firsttick) + ax_i.set_yticks(newticks, minor=True) diff --git a/aquarel/utils.py b/aquarel/utils.py index 374bd1d..0254715 100644 --- a/aquarel/utils.py +++ b/aquarel/utils.py @@ -1,11 +1,12 @@ import glob import os from .theme import Theme - +from typing import Union def load_theme(theme_name: str): """ Sets the chosen style and color palette globally. + :param theme_name: name of the theme to load :return: the specified Theme :raise ValueError: if a theme is not found @@ -20,6 +21,7 @@ def load_theme(theme_name: str): def list_themes(): """ Returns a list of available theme names. + :return: a list of available theme names """ return list(_get_themes().keys()) @@ -28,6 +30,7 @@ def list_themes(): def _get_themes(): """ Returns available themes from the theme directory + :return: a {name: path} dict of all available themes """ loc = os.path.dirname(os.path.abspath(__file__)) @@ -38,48 +41,62 @@ def _get_themes(): ) ) - -def make_samples(): +def _sample_plot(theme: Union[Theme, str], save_as: str = None): """ - Generates sample plots for all themes to be used in documentation + Generates a sample plot for a given theme. Optionally saves it to the specified location. + + :param theme: Theme instance or theme name to load. + :param save_as: path to save the generated plot to """ import seaborn as sns import matplotlib.pyplot as plt - for theme in list_themes(): - with load_theme(theme): - geysers = ( - sns.load_dataset("geyser") - .rename( - columns={ - "duration": "Duration", - "kind": "Kind", - "waiting": "Waiting", - } - ) - .replace({"long": "Long", "short": "Short"}) - ) - fig, ax = plt.subplots(1, 3, figsize=(15, 5)) - # Hacky patched boxplot since seaborn overrides color options otherwise - # sns.boxplot(x="Kind", y="Duration", data=tips, ax=ax[0]) - ax[0].boxplot( - [ - geysers.loc[geysers["Kind"] == "Long", "Duration"].tolist(), - geysers.loc[geysers["Kind"] == "Short", "Duration"].tolist(), - ], - vert=True, - patch_artist=True, - labels=geysers["Kind"].unique(), - ) - sns.kdeplot( - x="Waiting", y="Duration", hue="Kind", data=geysers, ax=ax[1], fill=True + if type(theme) == str: + theme = load_theme(theme) + + with theme: + geysers = ( + sns.load_dataset("geyser") + .rename( + columns={ + "duration": "Duration", + "kind": "Kind", + "waiting": "Waiting", + } ) - sns.lineplot(x="Waiting", y="Duration", hue="Kind", data=geysers, ax=ax[2]) - plt.suptitle("Geysers") - fig.savefig( - f"assets/{theme}.png", - dpi=75, - transparent=False, - facecolor=fig.get_facecolor(), - bbox_inches="tight", + .replace({"long": "Long", "short": "Short"}) ) + fig, ax = plt.subplots(1, 3, figsize=(15, 5)) + # Hacky patched boxplot since seaborn overrides color options otherwise + # sns.boxplot(x="Kind", y="Duration", data=tips, ax=ax[0]) + ax[0].boxplot( + [ + geysers.loc[geysers["Kind"] == "Long", "Duration"].tolist(), + geysers.loc[geysers["Kind"] == "Short", "Duration"].tolist(), + ], + vert=True, + patch_artist=True, + labels=geysers["Kind"].unique(), + ) + sns.kdeplot( + x="Waiting", y="Duration", hue="Kind", data=geysers, ax=ax[1], fill=True + ) + sns.lineplot(x="Waiting", y="Duration", hue="Kind", data=geysers, ax=ax[2]) + plt.suptitle("Geysers") + + if save_as is not None: + fig.savefig( + save_as, + dpi=75, + transparent=False, + facecolor=fig.get_facecolor(), + bbox_inches="tight", + ) + +def make_samples(): + """ + Generates sample plots for all themes to be used in documentation + """ + for theme in list_themes(): + _sample_plot(theme, f"assets/{theme}.png") + diff --git a/assets/arctic_dark.png b/assets/arctic_dark.png index d37e7fc..e8bebbe 100644 Binary files a/assets/arctic_dark.png and b/assets/arctic_dark.png differ diff --git a/assets/arctic_light.png b/assets/arctic_light.png index 02c6507..fc3c18a 100644 Binary files a/assets/arctic_light.png and b/assets/arctic_light.png differ diff --git a/assets/boxy_dark.png b/assets/boxy_dark.png index cb554e3..7511b9c 100644 Binary files a/assets/boxy_dark.png and b/assets/boxy_dark.png differ diff --git a/assets/boxy_light.png b/assets/boxy_light.png index 77ffde4..17a371a 100644 Binary files a/assets/boxy_light.png and b/assets/boxy_light.png differ diff --git a/assets/minimal_dark.png b/assets/minimal_dark.png index 4c5dfa1..41ae9f1 100644 Binary files a/assets/minimal_dark.png and b/assets/minimal_dark.png differ diff --git a/assets/minimal_light.png b/assets/minimal_light.png index 90089bf..2ebbcb5 100644 Binary files a/assets/minimal_light.png and b/assets/minimal_light.png differ diff --git a/assets/scientific.png b/assets/scientific.png index 7980b2a..2c5e72b 100644 Binary files a/assets/scientific.png and b/assets/scientific.png differ diff --git a/assets/umbra_dark.png b/assets/umbra_dark.png index b316173..d4f9bcf 100644 Binary files a/assets/umbra_dark.png and b/assets/umbra_dark.png differ diff --git a/assets/umbra_light.png b/assets/umbra_light.png index 290aadf..d549426 100644 Binary files a/assets/umbra_light.png and b/assets/umbra_light.png differ diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..d0c3cbf --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..dc1312a --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/source/aquarel.rst b/docs/source/aquarel.rst new file mode 100644 index 0000000..7a2b166 --- /dev/null +++ b/docs/source/aquarel.rst @@ -0,0 +1,25 @@ +Documentation +------------- + +Theme +===== + +.. automodule:: aquarel.theme + :members: + :undoc-members: + :show-inheritance: + +Transforms +========== + +.. automodule:: aquarel.transforms + :members: + :undoc-members: + :show-inheritance: + +Utils +===== +.. automodule:: aquarel.utils + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..3c413fe --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,34 @@ +import os +import sys +sys.path.insert(0, os.path.abspath('../../')) + +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = 'aquarel' +copyright = '2022, Lukas Gienapp' +author = 'Lukas Gienapp' +release = '0.0.4' + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo', 'sphinx.ext.mathjax', + 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode', 'sphinx.ext.githubpages', 'myst_parser'] + +templates_path = ['_templates'] +exclude_patterns = [] + + + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +autodoc_typehints = "description" +html_theme = 'sphinx_rtd_theme' +html_static_path = ['_static'] diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000..eb06e12 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,22 @@ +.. aquarel documentation master file, created by + sphinx-quickstart on Wed Aug 24 09:06:18 2022. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + +.. include:: ../../README.md + :parser: myst_parser.sphinx_ + +.. include:: aquarel.rst + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + + diff --git a/docs/source/requirements.txt b/docs/source/requirements.txt new file mode 100644 index 0000000..f458164 --- /dev/null +++ b/docs/source/requirements.txt @@ -0,0 +1,4 @@ +cycler +matplotlib>=3.4.0 +myst-parser +Sphinx \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index 224a779..b88034e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,2 @@ [metadata] -description-file = README.md \ No newline at end of file +description-file = README.md diff --git a/setup.py b/setup.py index 39aada4..53dc055 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setuptools.setup( name='aquarel', packages=['aquarel'], - version='0.0.3', + version='0.0.4', license='MIT', description='Lightweight templating engine for matplotlib', long_description=long_description, @@ -19,7 +19,8 @@ }, package_data={'aquarel': ['themes/*.json']}, include_package_data=True, - install_requires=['matplotlib', 'cycler'], + python_requires='>3.7', + install_requires=['matplotlib>=3.4.0', 'cycler'], keywords=["theme", "plotting", "visualization", "styling", "matplotlib"], classifiers=[ # https://pypi.org/classifiers 'Development Status :: 4 - Beta', @@ -30,5 +31,5 @@ 'License :: OSI Approved :: MIT License', 'Programming Language :: Python :: 3', ], - download_url="https://github.com/lgienapp/aquarel/archive/refs/tags/v0.0.3.tar.gz", + download_url="https://github.com/lgienapp/aquarel/archive/refs/tags/v0.0.4.tar.gz", ) \ No newline at end of file