op({
"color": {"legend": True},
"grid": False,
"marks": [Plot.dot(data, {"x": "x", "y": "y", "fill": "type", "r": 5})],
})Usage
For a quick usage introduction, see getting started.
Plot specification
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
trueandfalsemust be replaced byTrueandFalse - JavaScript
nullmust 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:
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 Plot, d3
Plot.plot({
"x": {
"axis": None
},
"marks": [
Plot.ruleY([0], {"stroke": "steelblue"}),
Plot.lineY(d3.cumsum({ "length": 100 }, d3.randomNormal()))
]
})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 Plot, d3, js
import polars as pl
data = pl.DataFrame({
"x": [1, 5, 2, 4, 6, 2, 4],
"y": [2, 1, 3, 4, 5, 1, 2],
"type": ["T1", "T2", "T1", "T2", "T1", "T1", "T2"],
})
Plot.plot({
"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")
})
]
})Alternative syntaxes
For the simplest cases, you can also pass a mark method directly as plot specification. The JavaScript plot() method will be called automatically to display the plot:
import random
v = [random.gauss(0,1) for i in range(1000)]
Plot.plot(
Plot.tickX(
v,
{"stroke": "steelblue", "opacity": 0.2}
)
)You can call also call the plot() method directly on a Plot mark method:
Plot.tickX(
v,
{"stroke": "steelblue", "opacity": 0.2}
).plot()Output formats
pyobsplot allows to output plots as Jupyter widgets, but also as static HTML, SVG or PNG.
The output format is determined by the format argument passed to a plot generator object or during plot creation.
| Format value | Output type | Renderer |
|---|---|---|
| “widget” (default) | Jupyter Widget | widget |
| “html” | Static HTML | jsdom |
| “svg” | SVG image | jsdom or jsdom+typst |
| “png” | PNG image | jsdom+typst |
The following table lists the differences between the widget output and the other static formats (HTML, SVG and PNG).
| Widget output | Other outputs | |
|---|---|---|
| Output | Jupyter Widget | Static HTML, SVG, PNG. PDF is available when saving to a file. |
|
Additional installation |
None |
Needs a working node.js installation and an additional npm package
|
| Quarto | Supported only in HTML format | HTML output supported in HTML format, SVG and PNG outputs supported in other formats |
| Output size | Big : includes the data and code needed to generate the plot | Moderate : size of the output file |
|
Plot interactions (tooltips…) |
Supported | Not supported. Only static plots are produced. |
| Save plot to file | Plots can be saved as embeddable HTML file | Plots can be saved to static HTML, SVG, PNG or PDF files. |
|
Jupyter interactivity |
Basic | None |
|
Persistence between sessions |
Widget state is not saved between sessions (for now) | Output is saved between sessions |
To use a static output format like HTML, SVG or PNG, 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 pyobsplotAfter that, you can specifiy a format when creating the plot by adding a format argument:
# Specify format to Plot.plot()
Plot.plot(Plot.auto(penguins, {"x": "flipper_length_mm"}), format="png")Saving plots to file
Plots can be saved to a file. To do this, just add a path argument to your Plot.plot call:
Plot.plot(Plot.lineY([1,2,3,2]), path="path_to/file.svg")Widget format
When using the widget format, plots can only be saved to HTML files. These files retain interactive features such as tooltips.
Plot.plot(Plot.lineY([1,2,3,2]), path="path_to/file.html")To embed widgets into an HTML website or document, Quarto documents can be more practical.
Other output formats
Plots can also be saved as SVG, PNG, PDF or static HTML files. The output format is determined by the path file extension.
Plot.plot(Plot.lineY([1,2,3,2]), path="path_to/file.png")PDF format is only available when saving to a file, as PDF output cannot be embedded and displayed in a Jupyter notebook.
When saving a plot to an HTML file, the result will depend on the format value. If format="widget" (the default), the HTML file will be a Jupyter widget, but if format="html", then a static HTML version will be saved.
Plot generates charts as SVG, but if a legend, title, subtitle or caption is present, the SVG is wrapped in a <figure> HTML tag. In this case, when saving to an SVG file, the plot will be converted using typst.
It is also possible to pass an io.StringIO object as path argument if you want to get the generated plot file as a Python object.
Themes
When using a plot generator object, it is possible to specify one of three color themes:
lighttheme (default) creates plots with a white background and a black foreground colordarktheme creates plots with a black background and a white foregroundcurrenttheme creates plots with a transparent background and acurrentColorforeground
You can specify a mode when creating the plot generator object by using the theme argument:
Plot.plot(Plot.lineY([1,2,3,2]), theme="dark")You can see output examples in the themes gallery
The current theme is not available when exporting to PNG, PDF, or SVG via typst.
Plot generator
Calling Plot.plot() is the fastest way to generate a plot with the default settings, but for further customization you can import the Obsplot class and create a plot generator:
from pyobsplot import Obsplot, Plot
op = Obsplot()By default plot generators output plots as widget format, but you can specify another one:
op = Obsplot(format="png")You can then create plots by calling this generator with your plot specification:
op(
{
"grid": True,
"marks": [Plot.dot(data, {"x": "x", "y": "y", "fill": "type", "r": 5})],
}
)For the simplest cases, you can also create your plot directly by passing a Plot mark method to the generator:
penguins = pl.read_csv("data/penguins.csv")
op(Plot.auto(penguins, {"x": "flipper_length_mm"}))You can also override the default format output or save the plot to a file by adding a format or path argument when calling the generator:
# Switch to SVG output for this plot
op(Plot.auto(penguins, {"x": "flipper_length_mm"}), format="svg")
# Save widget to HTML file
op(Plot.auto(penguins, {"x": "flipper_length_mm"}), path="plot.html")Default specification values
When creating a plot generator, 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', 'grid', 'aspectRatio', 'style']
So to create a plot generator object that creates by default 400px wide plots with a 50px margin and a dark theme with blue color, you could use :
op_colors = Obsplot(
format = "html",
theme = "dark",
default={
"width": 400,
"margin": 50,
"style": {"color": "#54A4C4"}
}
)op_colors(
Plot.dot(data, {"x": "x", "y": "y"})
)Data handling
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
df = pl.DataFrame({
"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]
})
Plot.plot({
"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:
value = df.get_column("Value")
Plot.plot(
Plot.tickX(value, {"x": "Value", "stroke": "darkviolet"})
)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:
penguins = pl.read_csv("data/penguins.csv")
Plot.plot({
"height": 600,
"grid": True,
"facet": {
"marginRight": 80
},
"marks": [
Plot.frame({"facet": False}),
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:
Plot.plot({
"x": {"domain": [js("new Date('2021-01-01')"), js("new Date('2022-01-01')")]},
"grid": True
})from datetime import date
Plot.plot({
"x": {"domain": [date(2021,1,1), date(2022,1,1)]},
"grid": True
})As well as the two following ones, using datetime:
Plot.plot({
"x": {"domain": [js("new Date('2021-01-01T07:00:00')"), js("new Date('2021-01-01T08:00:00')")]},
"grid": True
})from datetime import datetime
Plot.plot({
"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 static output formats such as SVG, PNG or PDF, they may work in PDF or docx documents as well.
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 htmlJupyter Interactivity
When using the default widget format, 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": [
Plot.rectY(penguins, Plot.binX({"y": "count"}, {"x": "body_mass_g", "fill": "steelblue", "fillOpacity": opacity})),
Plot.ruleY([0])
]
}
plot = Plot.plot(generate_plot_spec(1))
def update_plot(change):
new = change['new']
plot.spec = generate_plot_spec(new / 100)
w = IntSlider(value = 100, min = 0, max = 100)
w.observe(update_plot, names='value')
display(w)
display(plot)You can see a live version of this example in the following Colab notebook: