Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Templates (themes) integration #1224

Merged
merged 34 commits into from
Oct 23, 2018
Merged

Templates (themes) integration #1224

merged 34 commits into from
Oct 23, 2018

Conversation

jonmmease
Copy link
Contributor

@jonmmease jonmmease commented Oct 14, 2018

Overview

This PR introduces figure templates (themes) to plotly.py

Background

Earlier this year the plotly.js team discussed (plotly/plotly.js#2469) and then implemented (plotly/plotly.js#2761) a powerful templating approach that allows plotly.js users to specify default values for all trace and layout properties that make up a figure. This makes it possible to create custom figure themes that can be applied repeatedly to new figures.

In addition to default property values, themes can also include default object array elements like annotations, shapes, and images. This way, templates can be used to create figures that include default labels (e.g. "CONFIDENTIAL") and/or default images (e.g. a company/project logo).

Plotly.js Template Representation

Templates in Plotly.js are specified as a part of the declarative structure of the figure itself. A template is a specification object located at the layout.template path in the figure specification.

After a figure has been created, its template can be updated using the same Plotly.relayout operation that can be used to update the rest of the figure's layout properties.

plotly.py integration

This section describes the high-level changes made in this PR to support templates in plotly.py

Code generation / validation

The specification of the layout.template object can't be fully captured in the schema, so a bunch of code generation work was needed in order to provide the same level of validation that plotly.py users have come to expect from the rest of the figure property hierarchy.

In addition to the layout.template object itself, templates also introduce a *defaults property that corresponds to each object array property (e.g. layout.annotationsdefaults corresponds to layout.annotations). These are now included in the code generation / validation process.

These changes are implemented in approximately commits 106426e through f84c8a5

plotly.io.templates configuration object for registering named templates

Next, this PR adds a new plotly.io.templates configuration object for registering templates. A user can create a template object (an instance of go.layout.Template), configure it to their liking, then register it by name with plotly.io.templates for use in the future.

If a string is assigned to the fig.layout.template property, plotly.py checks the plotly.io.templates registry to see if there is a template registered with this name. If there is, then this template is applied to the figure.

Default template

The plotly.io.templates configuration object can also be used to specify a global default template. The global default template will be applied automatically when a figure is constructed (unless an explicit template was provided to the figure constructor).

Merging templates

A string assigned to the fig.layout.template property, or registered as the default template, may contain multiple registered template names joined on '+' characters (borrowing the flaglist syntax). In this case the templates are merged together (applied to the figure from left to right).

Extracting a template from a figure

A new plotly.io.to_templated function is introduced that inputs a figure and then returns a copy of the figure where all of the "styling" properties have been moved from the figure's data/layout specification into the figure's layout.template object.

The layout.template property of the resulting figure can then be applied to another figure, or it can be registered as a named template.

Custom Theme API example

Imports

    >>> import plotly.graph_objs as go
    >>> import plotly.io as pio

Construct a figure with large courier text

    >>> fig = go.Figure(layout={'title': 'Figure Title',
    ...                         'font': {'size': 20, 'family': 'Courier'}})
    >>> fig
    Figure({
        'data': [],
        'layout': {'title': 'Figure Title',
                   'font': {'family': 'Courier', 'size': 20}}
    })

Convert to a figure with a template. Note how the 'font' properties have been moved into the template property.

    >>> templated_fig = pio.to_templated(fig)
    >>> templated_fig
    Figure({
        'data': [],
        'layout': {'title': 'Figure Title',
                   'template': {'layout': {'font': {'family': 'Courier',
                                                    'size': 20}}}}
    })

Next create a new figure with this template

    >>> fig2 = go.Figure(layout={
    ...     'title': 'Figure 2 Title',
    ...     'template': templated_fig.layout.template})
    >>> fig2
    Figure({
        'data': [],
        'layout': {'title': 'Figure 2 Title',
                   'template': {'layout': {'font': {'family': 'Courier',
                                                    'size': 20}}}}
    })

The default font in fig2 will now be size 20 Courier.

Next, register as a named template...

    >>> pio.templates['large_courier'] = templated_fig.layout.template

and specify this template by name when constructing a figure.

    >>> go.Figure(layout={
    ...     'title': 'Figure 3 Title',
    ...     'template': 'large_courier'})
    Figure({
        'data': [],
        'layout': {'title': 'Figure 3 Title',
                   'template': {'layout': {'font': {'family': 'Courier',
                                                    'size': 20}}}}
    })

Finally, set this as the default template to be applied to all new figures

    >>> pio.templates.default = 'large_courier'
    >>> go.Figure(layout={'title': 'Figure 4 Title'})
    Figure({
        'data': [],
        'layout': {'title': 'Figure 4 Title',
                   'template': {'layout': {'font': {'family': 'Courier',
                                                    'size': 20}}}}
    })

New built-in named templates

A new templategen module is introduced that includes some helper function for us to use in generating templates to bundle with plotly.py. The templategen module was purposely not added to setup.py because it should not be distributed with plotly.py, it's only for development (just like the existing codegen module).

Built-in templates are generated by running templategen/__init__.py and they are output to the new plotly/package_data/templates directory. This directory has been added as a package_data directory to setup.py as it needs to be distributed along with plotly.py

At this point this PR includes templates for 2 common themes, 3 original themes, and 2 "add-on" templates.

Common themes

This PR includes pre-registered templates that imitate (in the most flattering way possible 🙂) the default ggplot2 and seaborn themes.

Current default

First, here is the sample figure with no theme applied
newplot 17

ggplot

newplot 18

seaborn

newplot 19

Original themes

This PR currently includes three original themes, including one dark theme that's well suited for use alongside JupyterLab's dark theme.

plotly

newplot 20

plotly_white

newplot 21

plotly_dark

Here's a screeshot of the plotly_dark, viewed in JupyterLab with JupyterLab dark theme
screen shot 2018-10-15 at 6 42 56 pm

Add-on templates

In addition to these theme templates, I've added a couple of "add-on" templates. These are templates that can be used either on their own, or merged with one of the themes above.

presentation

The presentation template increases the font size, marker size, and line width by about 1.5x The idea is that this is a templates that can be selectively activated during a presentation, along with JupyterLab's "View->Presentation Mode" option, to make figure more readable from a distance.

Here is an example of 'plotly+presentation'

newplot 15

xgridoff

The "xgridoff" template simply disables x-grid axis lines by default. It can be

Here's an example of 'plotly_dark+presentation+xgridoff'

newplot 16

Interactive theme switching

Thanks to the design of templates in Plotly.js and the design of the ipywidgets plotly.py it's possible to interactively apply different templates to a figure after it has already been created and displayed. Here's what that looks like...

template_switching

cc @jackparmer @chriddyp @nicolaskruchten @sglyon

e.g. `layout.template.layout.annotationdefaults`
Supports registering/unregistering templates and setting default template

layout.template can now be specified as the name of a template, and
if layout.template is not specified, then a registered default is applied
during figure construction.
This inputs a figure and outputs a new figure where all eligible
properties have been moved into the new figure's template definition

By default properties named 'text' and 'title' are note moved into the
template, but this can be customized using the `skip` argument.
and logic to lazily load theme from file on first use
decimate bmw colorscale in plotly templates to save space and reduce
plotting latency.
colorscale is used across trace types.

Also crop bwm colorscale slightly so that it has better contrast on both
light and dark themes. No it doesn't go quite as close to black or as
close to white.
of text and lines/markers for several trace types.
There is still some non-determinism with raster image formats, and
eps is enough to execute the full orca pipeline
 - Slightly lighten background grid in plotly template
 - Slightly darken grid lines in plotly_dark template
 - Don't explicitly enable xgrid/ygrid in plotly* templates, let
   plotly.js decide.
@jonmmease
Copy link
Contributor Author

Small refinements:

Slightly lighten background grey in plotly template. Now it's an interpolated "Rhino Light 1.5".
newplot 27

Slightly darken the 2D cartesian grid lines in plotly_dark. Now they are a color interpolated half way from "Rhino Dark" to black. This provides a more subtle contrast that, to my eye, matches the gray-on-white contrast of the grid lines in plotly_white.
newplot 28

newplot 29

In all plotly* templates. No longer explicitly enable x/y grid lines. Let plotly.js decide.

@jonmmease jonmmease added this to the v3.4.0 milestone Oct 22, 2018
This way fig.layout.template won't be None after Figure is constructed
…* templates

This makes zero lines less distracting when not helpful, while still
making it fairly easy to focus on them when desired.
@jonmmease
Copy link
Contributor Author

Alright, time to merge this! We'll review the built-in templates once more after building this against plotly.js 1.42.0 as there are a few template fixes in that version.

Here are final versions of each template at the time of the merge

None (still the default):

newplot 30
newplot 31
newplot 32

'ggplot2':

newplot 33
newplot 34
newplot 35

'seaborn':

newplot 36
newplot 37
newplot 38

'plotly':

newplot 39
newplot 40
newplot 41

'plotly_white':

newplot 42
newplot 43
newplot 44

'plotly_dark':

newplot 45
newplot 46
newplot 47

in JupyerLab with Dark Theme:
screen shot 2018-10-23 at 8 13 02 am

'plotly+presentation':

newplot 48
newplot 49
newplot 50

'plotly_white+presentation+xgridoff':

newplot 51
newplot 52
newplot 53

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant