from pyobsplot import Obsplot, Plot
= Obsplot() op
Usage
For a quick usage introduction, see getting started.
To create a plot we can either use Plot.plot()
to generate plots with the default settings, or create a plot generator object to be able to customize things further. This is the way we will use here.
So we start by importing the needed classes and create a plot generator object:
From JavaScript to Python
Converting a plot specification from JavaScript to Python should be straightforward most of the time:
- all dictionary keys must be quoted (so
x:
becomes"x":
) - JavaScript
true
andfalse
must be replaced byTrue
andFalse
- JavaScript
null
must be replaced byNone
So the following JavaScript code:
.plot(
Plot
{color: {legend: true},
grid: false,
marks: [Plot.dot(data, {x: "x", y: "y", fill: "type", r: 5})]
} )
Becomes:
op({"color": {"legend": True},
"grid": False,
"marks": [Plot.dot(data, {"x": "x", "y": "y", "fill": "type", "r": 5})],
})
It is possible to replace JavaScript methods from the Plot
, d3
and Math
modules with Python methods, but you must first import the corresponding classes.
from pyobsplot import Obsplot, Plot, d3
op({"x": {
"axis": None
},"marks": [
0], {"stroke": "steelblue"}),
Plot.ruleY(["length": 100 }, d3.randomNormal()))
Plot.lineY(d3.cumsum({
] })
If your specification includes JavaScript code (such as anonymous functions), you can pass it as a string by using the js
method (after importing it):
from pyobsplot import Obsplot, Plot, d3, js
import polars as pl
= pl.DataFrame({
data "x": [1, 5, 2, 4, 6, 2, 4],
"y": [2, 1, 3, 4, 5, 1, 2],
"type": ["T1", "T2", "T1", "T2", "T1", "T1", "T2"],
})
op({"grid": True,
"marks": [
Plot.dot(data, {"x": "x", "y": "y", "r": 5,
"stroke": "black", "fill": "steelblue",
"fillOpacity": js("d => d.type == 'T1' ? 0.7 : 0.1")
})
] })
Renderers
pyobsplot
provides two renderers : widget
and jsdom
. The widget
renderer outputs plots as Jupyter widgets. The jsdom
renderer uses an external npm package to generate plots as either SVG or HTML.
The following table lists the differences between the two renderers.
widget | jsdom | |
---|---|---|
Output | Jupyter Widget |
SVG (plots without legend) HTML (plots with legend) |
Additional installation |
None |
Needs a working node.js installation and an additional npm package
|
Quarto | Supported only in HTML format | Supported in HTML format, SVG output supported in other formats |
Output size | Big : includes the data and code needed to generate the plot | Moderate : size of the SVG or HTML output |
Plot interactions (tooltips…) |
Supported | Not supported. Only static plots are produced. |
Save plot to file | Plots can be saved as embeddable SVG or HTML files | Plots can be saved to a full HTML file |
Jupyter interactivity |
Basic | None |
Persistence between sessions |
Widget state is not saved between session in VSCode | Output is saved between sessions |
To use the jsdom
renderer, you need a working node.js installation and you must install the npm pyobsplot
package globally or locally:
# Install locally
npm install pyobsplot
# Install globally
npm install -g pyobsplot
After that, you can select which renderer to use when creating a plot generator object:
# Switch to widget renderer (the default one)
= Obsplot(renderer="widget")
opw # Switch to jsdom renderer
= Obsplot(renderer="jsdom") opj
Alternative syntaxes
As we’ve already seen, the most common syntax for a plot specification is to pass it as a dictionary :
op({"grid": True,
"color": {"legend": True}
"marks": [
"x": "x", "y": "y", "fill": "type"})
Plot.dot(data, {
], })
But it is also possible to pass top level arguments as kwargs
to the generator object, which would give the following:
op(= True,
grid = {"legend": True},
color = [
marks "x": "x", "y": "y", "fill": "type"})
Plot.dot(data, {
], )
Finally, for the simplest cases, you can also pass a mark method directly. The JavaScript plot()
method will be called automatically to display the plot:
import random
op(
Plot.tickX(0,1) for i in range(1000)],
[random.gauss("stroke": "steelblue", "opacity": 0.2}
{
) )
Default specification values
When creating a plot generator objects, it is possible to specify default specification values that will be applied to every plot created with this generator.
Only the top-level layout options can be specified as defaults. This can be useful to specify a default width
, colors, margins or even style
.
The complete list of available default attributes is :
['marginTop', 'marginRight', 'marginBottom', 'marginLeft', 'margin', 'width', 'height', 'aspectRatio', 'style']
So to create a plot generator object that creates by default 400px wide plots with a 50px margin and white on blue colors, you could use :
= Obsplot(default={
op_colors "width": 400,
"margin": 50,
"style": {"color": "white", "background-color": "#004"}
})
op_colors("x": "x", "y": "y"})
Plot.dot(data, { )
Themes
When using a plot generator object, it is possible to specify one of three output themes:
light
theme (default) creates plots with a white background and a black foreground colordark
theme creates plots with a black background and a white foregroundcurrent
theme creates plots with a transparent background and a currentColor foreground
You can specify a mode when creating the plot generator object by using the theme
argument:
= Obsplot(theme="dark") op_dark
You can see output examples in the themes gallery
DataFrames and Series
Pandas and polars DataFrames can be passed directly in a plot specification. They will be converted to JavaScript objects via Arrow IPC serialization, to ensure speed and data types conversion.
import polars as pl
from datetime import date
= pl.DataFrame({
df "Date": [date(2023, 1, 1), date(2023, 1, 2), date(2023, 1, 3), date(2023, 1, 4)],
"Value": [4.2, 3.8, 4.5, 4.7]
})
op({"x": {"grid": True},
"y": {"domain": [0, 5]},
"marks": [Plot.lineY(df, {"x": "Date", "y": "Value", "stroke": "steelblue"})]
})
If you pass a pandas or polars Series object, it will be automatically converted to a DataFrame with one column:
= df.get_column("Value")
value
op("x": "Value", "stroke": "darkviolet"})
Plot.tickX(value, { )
pyobsplot
implements a simple caching mechanism for some data objects (it currently works for DataFrames and for GeoJson data). Sometimes the same data object is used several times in a plot specification:
= pl.read_csv("data/penguins.csv")
penguins
op({"height": 600,
"grid": True,
"facet": {
"marginRight": 80
},"marks": [
"facet": False}),
Plot.frame({
Plot.dot(penguins, {"x": "culmen_depth_mm",
"y": "culmen_length_mm",
"r": 1.5,
"fill": "#ccc",
"facet": "exclude",
"fx": "sex",
"fy": "species",
}),
Plot.dot(penguins, {"x": "culmen_depth_mm",
"y": "culmen_length_mm",
"fx": "sex",
"fy": "species",
})
] })
In this case, caching ensures that the penguins
DataFrame is only serialized and transmitted once instead of twice.
datetime objects
datetime.date
and datetime.datetime
Python objects are automatically serialized and converted to JavaScript Date
objects.
That makes the following two specifications equivalent:
op({"x": {"domain": [js("new Date('2021-01-01')"), js("new Date('2022-01-01')")]},
"grid": True
})
from datetime import date
op({"x": {"domain": [date(2021,1,1), date(2022,1,1)]},
"grid": True
})
As well as the two following ones, using datetime
:
op({"x": {"domain": [js("new Date('2021-01-01T07:00:00')"), js("new Date('2021-01-01T08:00:00')")]},
"grid": True
})
from datetime import datetime
op({"x": {"domain": [datetime(2021,1,1,7,0,0), datetime(2021,1,1,8,0,0)]},
"grid": True
})
Quarto
pyobsplot
plots are compatible with quarto HTML formats. If you use the jsdom renderer, plots without legends are generated as SVG files and are also compatible with other formats such as PDF or docx.
If your source document is a jupyter notebook (and not a .qmd
file), then you have to use the --execute
argument to force plot computation and to make them visible in the output:
quarto render test.ipynb --execute --to html
Interactivity
When using the widget
renderer, the fact that plots are generated as Jupyter widgets allow for basic interactivity. More specifically, you can set the spec
attribute of an existing Obsplot
to another plot specification and it will update it.
This allows to do things like the following, where a plot is updated depending on the value of a Jupyter IntSlider
widget:
def generate_plot_spec(opacity):
return {
"grid": True,
"marks": [
"y": "count"}, {"x": "body_mass_g", "fill": "steelblue", "fillOpacity": opacity})),
Plot.rectY(penguins, Plot.binX({0])
Plot.ruleY([
]
}
= op(generate_plot_spec(1))
plot
def update_plot(change):
= change['new']
new = generate_plot_spec(new / 100)
plot.spec
= IntSlider(value = 100, min = 0, max = 100)
w ='value')
w.observe(update_plot, names
display(w) display(plot)
You can see a live version of this example in the following Colab notebook:
Saving plots to file
Generated plots can be saved to a file. To do this, just add a path
argument to your generator call:
= Obsplot(renderer="jsdom")
op 1,2,3,2]), path="path_to/file.svg") op(Plot.lineY([
jsdom renderer
When using the jsdom
renderer, charts can be saved either as SVG or HTML files : Plot generates charts as SVG, but if a legend, title, subtitle or caption is present, the SVG is wrapped in a <figure>
HTML tag. When using the path
option, pyobsplot
will warn you if you try to save an HTML output to a SVG file.
In any case, you can also use Plot’s figure
option to force the output to be wrapped in <figure>
:
= Obsplot(renderer="jsdom")
op
op({"marks": Plot.lineY([1, 2, 3, 2]),
"figure": True
}, ="/tmp/out.html"
path )
widget renderer
When using the widget
renderer, plots can only be saved to full HTML files. These files retain interactivity features such as tooltips. To embed widgets into an HTML website or document, Quarto documents can be more practical.