Skip to content

Commit

Permalink
Merge pull request #18 from lgienapp/dev
Browse files Browse the repository at this point in the history
Release 0.0.4
  • Loading branch information
lgienapp authored Aug 31, 2022
2 parents 92b7c05 + 31f143b commit 2687935
Show file tree
Hide file tree
Showing 28 changed files with 392 additions and 102 deletions.
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -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`
Expand Down Expand Up @@ -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.
103 changes: 99 additions & 4 deletions aquarel/theme.py
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down Expand Up @@ -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",
},
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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
"""
Expand All @@ -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(
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
"""
Expand All @@ -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',
Expand Down Expand Up @@ -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"
Expand All @@ -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,
},
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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(
Expand All @@ -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,
},
)
Expand All @@ -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"
Expand All @@ -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
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
"""
Expand All @@ -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
"""
Expand Down
4 changes: 3 additions & 1 deletion aquarel/themes/arctic_dark.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@
},
"overrides": {},
"transforms": {
"trim": {},
"trim": {
"axis": "both"
},
"offset": {
"distance": 10
}
Expand Down
4 changes: 3 additions & 1 deletion aquarel/themes/arctic_light.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@
},
"overrides": {},
"transforms": {
"trim": {},
"trim": {
"axis": "both"
},
"offset": {
"distance": 10
}
Expand Down
10 changes: 8 additions & 2 deletions aquarel/themes/boxy_dark.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
"top": true,
"left": true,
"right": true,
"xmargin": 0,
"ymargin": 0
"xmargin": 0.02,
"ymargin": 0.02
},
"ticks": {
"draw_minor": true,
Expand All @@ -29,6 +29,10 @@
"Arial"
]
},
"legend": {
"round": false,
"alpha": 1.0
},
"colors": {
"figure_background_color": "black",
"plot_background_color": "black",
Expand All @@ -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",
Expand Down
10 changes: 8 additions & 2 deletions aquarel/themes/boxy_light.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
"top": true,
"left": true,
"right": true,
"xmargin": 0,
"ymargin": 0
"xmargin": 0.02,
"ymargin": 0.02
},
"ticks": {
"draw_minor": true,
Expand All @@ -29,6 +29,10 @@
"Arial"
]
},
"legend": {
"round": false,
"alpha": 1.0
},
"colors": {
"figure_background_color": "white",
"plot_background_color": "white",
Expand All @@ -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",
Expand Down
Loading

0 comments on commit 2687935

Please sign in to comment.