Skip to content

Commit 14e872d

Browse files
Merge pull request #389 from tpvasconcelos/template-param
Add a new template parameter to ridgeplot()
2 parents bfb387f + 5bdfdab commit 14e872d

16 files changed

Lines changed: 267 additions & 10 deletions

File tree

cicd_utils/ridgeplot_examples/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ def load_basic() -> go.Figure:
1919
return main()
2020

2121

22+
def load_basic_dark() -> go.Figure:
23+
from ._basic_dark import main
24+
25+
return main()
26+
27+
2228
def load_basic_hist() -> go.Figure:
2329
from ._basic_hist import main
2430

@@ -45,6 +51,7 @@ def load_probly() -> go.Figure:
4551

4652
ALL_EXAMPLES: list[Example] = [
4753
Example("basic", load_basic),
54+
Example("basic_dark", load_basic_dark),
4855
Example("basic_hist", load_basic_hist),
4956
Example("lincoln_weather", load_lincoln_weather),
5057
Example("lincoln_weather_red_blue", load_lincoln_weather_red_blue),

cicd_utils/ridgeplot_examples/_basic.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,19 @@
55
if TYPE_CHECKING:
66
import plotly.graph_objects as go
77

8+
from ridgeplot._types import PlotlyTemplate
89

9-
def main() -> go.Figure:
10+
11+
def main(
12+
template: PlotlyTemplate | None = None,
13+
) -> go.Figure:
1014
import numpy as np
1115

1216
from ridgeplot import ridgeplot
1317

1418
rng = np.random.default_rng(42)
1519
my_samples = [rng.normal(n, size=900) for n in range(6, 0, -2)]
16-
fig = ridgeplot(samples=my_samples)
20+
fig = ridgeplot(samples=my_samples, template=template)
1721
fig.update_layout(height=400, width=800)
1822

1923
return fig
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from __future__ import annotations
2+
3+
from typing import TYPE_CHECKING
4+
5+
from ridgeplot_examples._basic import main as basic
6+
7+
if TYPE_CHECKING:
8+
import plotly.graph_objects as go
9+
10+
11+
def main() -> go.Figure:
12+
fig = basic(template="plotly_dark")
13+
return fig
14+
15+
16+
if __name__ == "__main__":
17+
fig = main()
18+
fig.show()
25.7 KB
Loading

docs/getting_started/getting_started.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,3 +287,20 @@ fig = ridgeplot(
287287
```{raw} html
288288
:file: ../_static/charts/lincoln_weather_red_blue.html
289289
```
290+
291+
## Theming with Plotly templates
292+
293+
Since `ridgeplot` is built on top of Plotly, you can also theme your ridgeline plots using [Plotly's figure templates](https://plotly.com/python/templates/). Simply pass the name of a registered template (or any other valid template representation) to the {py:paramref}`~ridgeplot.ridgeplot.template` parameter. For instance, taking the basic example from the top of this page and applying the `"plotly_dark"` template:
294+
295+
```python
296+
fig = ridgeplot(samples=my_samples, template="plotly_dark")
297+
fig.show()
298+
```
299+
300+
```{raw} html
301+
:file: ../_static/charts/basic_dark.html
302+
```
303+
304+
:::{note}
305+
Unless a custom {py:paramref}`~ridgeplot.ridgeplot.colorscale` is specified, the default colorscale will also be inferred from the specified template.
306+
:::

docs/reference/changelog.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@ This document outlines the list of changes to ridgeplot between each release. Fo
55
Unreleased changes
66
------------------
77

8+
### Features
9+
10+
- Implement a new `template` parameter to allow users to specify a Plotly figure template ({gh-pr}`389`)
11+
12+
### Documentation
13+
14+
- Add a section on theming with Plotly templates to the getting started guide ({gh-pr}`389`)
15+
816
### CI/CD
917

1018
- Review the coverage configuration in light of `covdefaults`, adopting its `assert_never` exclusion and `skip_covered` report setting, and raising all package coverage gates to 100% ({gh-pr}`390`)

src/ridgeplot/_color/colorscale.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
if TYPE_CHECKING:
1515
from collections.abc import Collection
1616

17+
import plotly.graph_objects as go
18+
1719
from ridgeplot._types import Color, ColorScale
1820

1921

@@ -43,19 +45,29 @@ def validate_coerce(self, v: Any) -> ColorScale:
4345
return cast("ColorScale", coerced)
4446

4547

46-
def infer_default_colorscale() -> ColorScale | Collection[Color] | str:
48+
def infer_default_colorscale(
49+
template: go.layout.Template | None = None,
50+
) -> ColorScale | Collection[Color] | str:
51+
if template is None:
52+
template = default_plotly_template()
4753
return validate_coerce_colorscale(
48-
default_plotly_template().layout.colorscale.sequential or px.colors.sequential.Viridis
54+
template.layout.colorscale.sequential or px.colors.sequential.Viridis
4955
)
5056

5157

5258
def validate_coerce_colorscale(
5359
colorscale: ColorScale | Collection[Color] | str | None,
60+
template: go.layout.Template | None = None,
5461
) -> ColorScale:
5562
"""Convert mixed colorscale representations to the canonical
56-
:data:`ColorScale` format."""
63+
:data:`ColorScale` format.
64+
65+
If ``colorscale`` is None, the default colorscale is inferred from the
66+
given ``template`` (or from the current default Plotly template, if no
67+
template is specified).
68+
"""
5769
if colorscale is None:
58-
colorscale = infer_default_colorscale()
70+
colorscale = infer_default_colorscale(template=template)
5971
return ColorscaleValidator().validate_coerce(colorscale)
6072

6173

src/ridgeplot/_figure_factory.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
)
1515
from ridgeplot._obj.traces import get_trace_cls
1616
from ridgeplot._obj.traces.base import ColoringContext
17+
from ridgeplot._template import validate_coerce_template
1718
from ridgeplot._types import (
1819
Color,
1920
ColorScale,
@@ -37,7 +38,7 @@
3738
if TYPE_CHECKING:
3839
from collections.abc import Collection
3940

40-
from ridgeplot._types import Densities
41+
from ridgeplot._types import Densities, PlotlyTemplate
4142

4243

4344
def normalise_trace_types(
@@ -80,8 +81,11 @@ def _update_layout(
8081
xpad: float,
8182
x_max: float,
8283
x_min: float,
84+
template: go.layout.Template | None,
8385
) -> go.Figure:
8486
"""Update figure's layout."""
87+
if template is not None:
88+
fig.update_layout(template=template)
8589
fig.update_layout(
8690
legend=dict(traceorder="normal"),
8791
)
@@ -130,6 +134,7 @@ def create_ridgeplot(
130134
line_width: float | None,
131135
spacing: float,
132136
xpad: float,
137+
template: PlotlyTemplate | None,
133138
) -> go.Figure:
134139
# ==============================================================
135140
# --- Get clean and validated input arguments
@@ -169,7 +174,8 @@ def create_ridgeplot(
169174
line_width = float(line_width) if line_width is not None else None
170175
spacing = float(spacing)
171176
xpad = float(xpad)
172-
colorscale = validate_coerce_colorscale(colorscale)
177+
template = validate_coerce_template(template)
178+
colorscale = validate_coerce_colorscale(colorscale, template=template)
173179

174180
# ==============================================================
175181
# --- Build the figure
@@ -233,5 +239,6 @@ def create_ridgeplot(
233239
xpad=xpad,
234240
x_max=x_max,
235241
x_min=x_min,
242+
template=template,
236243
)
237244
return fig

src/ridgeplot/_ridgeplot.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
ColorScale,
3232
LabelsArray,
3333
NormalisationOption,
34+
PlotlyTemplate,
3435
SampleWeights,
3536
SampleWeightsArray,
3637
ShallowDensities,
@@ -120,6 +121,7 @@ def ridgeplot(
120121
line_width: float | None = None,
121122
spacing: float = 0.5,
122123
xpad: float = 0.05,
124+
template: PlotlyTemplate | None = None,
123125
# Deprecated parameters
124126
coloralpha: float | None | MissingType = MISSING,
125127
linewidth: float | MissingType = MISSING,
@@ -140,6 +142,8 @@ def ridgeplot(
140142
https://www.statsmodels.org/stable/_modules/statsmodels/nonparametric/bandwidths.html
141143
.. _Plotly's built-in color-scales:
142144
https://plotly.com/python/builtin-colorscales/
145+
.. _Plotly's theming and templates guide:
146+
https://plotly.com/python/templates/
143147
.. _ragged:
144148
https://en.wikipedia.org/wiki/Jagged_array
145149
@@ -290,8 +294,8 @@ def ridgeplot(
290294
``["rgb(255, 0, 0)", "blue", "hsl(120, 100%, 50%)"]``). The list will
291295
ultimately be converted into a :data:`~ridgeplot._types.ColorScale`
292296
object, assuming the colors provided are evenly spaced. If not specified
293-
(default), the color scale will be inferred from current Plotly
294-
template.
297+
(default), the color scale will be inferred from the current Plotly
298+
template (see the :paramref:`.template` parameter).
295299
296300
colormode : "fillgradient" or SolidColormode
297301
This parameter controls the logic used for the coloring of each
@@ -395,6 +399,20 @@ def ridgeplot(
395399
units of the range between the minimum and maximum x-values from all
396400
distributions.
397401
402+
template : PlotlyTemplate or None
403+
The Plotly template to use in this figure. This can be the name of a
404+
registered template (e.g., ``"plotly_dark"``), a
405+
:class:`plotly.graph_objects.layout.Template
406+
<plotly.graph_objs.layout.Template>` instance, or a dictionary with a
407+
template's properties. See `Plotly's theming and templates guide`_
408+
for more details. If not specified (default), the plot will be
409+
rendered using Plotly's current default template (i.e.,
410+
``plotly.io.templates.default``). Note that, unless a custom
411+
:paramref:`.colorscale` is specified, the default colorscale will
412+
also be inferred from this template.
413+
414+
.. versionadded:: 0.7.0
415+
398416
coloralpha : float
399417
400418
.. deprecated:: 0.2.0
@@ -511,5 +529,6 @@ def ridgeplot(
511529
line_width=line_width,
512530
spacing=spacing,
513531
xpad=xpad,
532+
template=template,
514533
)
515534
return fig

src/ridgeplot/_template.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
"""Plotly figure template utilities."""
2+
3+
from __future__ import annotations
4+
5+
from typing import TYPE_CHECKING, cast
6+
7+
from _plotly_utils.basevalidators import BaseTemplateValidator
8+
9+
if TYPE_CHECKING:
10+
import plotly.graph_objects as go
11+
12+
from ridgeplot._types import PlotlyTemplate
13+
14+
15+
def validate_coerce_template(template: PlotlyTemplate | None) -> go.layout.Template | None:
16+
"""Convert mixed template representations into a
17+
:class:`plotly.graph_objects.layout.Template
18+
<plotly.graph_objs.layout.Template>` instance.
19+
20+
``None`` is passed through as-is, meaning that no template has been
21+
specified and that Plotly's current default template should be used.
22+
"""
23+
if template is None:
24+
return None
25+
validator = BaseTemplateValidator(
26+
plotly_name="template",
27+
parent_name="layout",
28+
data_class_str="Template",
29+
data_docs="",
30+
)
31+
return cast("go.layout.Template", validator.validate_coerce(template))

0 commit comments

Comments
 (0)