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
Uh oh!
There was an error while loading. Please reload this page.
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.templatepath in the figure specification.After a figure has been created, its template can be updated using the same
Plotly.relayoutoperation 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.templateobject 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.templateobject itself, templates also introduce a*defaultsproperty that corresponds to each object array property (e.g.layout.annotationsdefaultscorresponds tolayout.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.templatesconfiguration object for registering templates. A user can create a template object (an instance ofgo.layout.Template), configure it to their liking, then register it by name withplotly.io.templatesfor use in the future.If a string is assigned to the
fig.layout.templateproperty, plotly.py checks theplotly.io.templatesregistry 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.templatesconfiguration 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.templateproperty, 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_templatedfunction 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'sdata/layoutspecification into the figure'slayout.templateobject.The
layout.templateproperty 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
Construct a figure with large courier text
Convert to a figure with a template. Note how the 'font' properties have been moved into the template property.
Next create a new figure with this template
The default font in fig2 will now be size 20 Courier.
Next, register as a named template...
and specify this template by name when constructing a figure.
Finally, set this as the default template to be applied to all new figures
New built-in named templates
A new
templategenmodule is introduced that includes some helper function for us to use in generating templates to bundle with plotly.py. Thetemplategenmodule was purposely not added to setup.py because it should not be distributed with plotly.py, it's only for development (just like the existingcodegenmodule).Built-in templates are generated by running
templategen/__init__.pyand they are output to the newplotly/package_data/templatesdirectory. This directory has been added as apackage_datadirectory to setup.py as it needs to be distributed along with plotly.pyAt 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 themescreen 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
presentationtemplate 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