Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ All plots are rendered to static PNG images inside the executed notebook:
- **Plotly**: `pio.renderers.default = "png"` with Kaleido (needs Chrome; `uv run kaleido_get_chrome`)
- **Altair**: `alt.renderers.enable("png")` with vl-convert-python (set in the notebook's first cell — do not remove)
- **R/ggplot2**: rpy2 against the system R installation (`%%R` cell magic)
- **Lets-Plot** and **hvPlot/Bokeh**: an `image/png` IPython formatter registered in the notebook's first cell. Bokeh has no headless renderer, so hvPlot figures are screenshotted through Selenium driving the same Chrome for Testing build Kaleido downloads; Selenium Manager fetches the matching chromedriver on first use (needs network).

## Adding New Plots

Expand Down
272 changes: 272 additions & 0 deletions Examples.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,73 @@
"\n",
"get_ipython().display_formatter.formatters[\"image/png\"].for_type(\n",
" lp.plot.core.PlotSpec, _lets_plot_png\n",
")\n",
"\n",
"import atexit\n",
"import io\n",
"import logging\n",
"\n",
"import holoviews as hv\n",
"import hvplot.pandas # registers the .hvplot accessor on pandas objects\n",
"from bokeh.io.export import get_screenshot_as_png\n",
"from bokeh.models import Plot\n",
"\n",
"hv.extension(\"bokeh\")\n",
"logging.getLogger(\"bokeh.io.export\").setLevel(logging.ERROR)\n",
"hv.plotting.bokeh.ElementPlot.toolbar = None\n",
"hv.plotting.bokeh.ElementPlot.fontscale = 1.6\n",
"\n",
"_bokeh_driver = None\n",
"\n",
"\n",
"def _bokeh_webdriver():\n",
" \"\"\"Headless Chrome for Bokeh's PNG export.\n",
"\n",
" Bokeh screenshots plots through Selenium. Reuse the Chrome for Testing\n",
" build Kaleido already downloads; Selenium Manager supplies the matching\n",
" chromedriver.\n",
" \"\"\"\n",
" global _bokeh_driver\n",
" if _bokeh_driver is None:\n",
" from selenium.webdriver.chrome.options import Options\n",
" from selenium.webdriver.chrome.webdriver import WebDriver as Chrome\n",
"\n",
" options = Options()\n",
" options.add_argument(\"--headless=new\")\n",
" options.add_argument(\"--hide-scrollbars\")\n",
" options.add_argument(\"--force-device-scale-factor=1\")\n",
" options.add_argument(\"--force-color-profile=srgb\")\n",
" options.add_argument(\"--no-sandbox\")\n",
" try:\n",
" from choreographer.browsers.chromium import Chromium\n",
"\n",
" options.binary_location = str(Chromium.find_browser(skip_local=False))\n",
" _bokeh_driver = Chrome(options=options)\n",
" except Exception: # fall back to whatever Chrome is on the system\n",
" options.binary_location = \"\"\n",
" _bokeh_driver = Chrome(options=options)\n",
" atexit.register(_bokeh_driver.quit)\n",
" return _bokeh_driver\n",
"\n",
"\n",
"def _hvplot_png(obj):\n",
" \"\"\"Render an hvPlot/HoloViews Bokeh figure to PNG for the cell output.\"\"\"\n",
" figure = hv.render(obj, backend=\"bokeh\")\n",
" models = figure.references()\n",
" for model in models:\n",
" if \"toolbar_location\" in model.properties():\n",
" model.toolbar_location = None\n",
" panes = [m for m in models if isinstance(m, Plot)]\n",
" if len(panes) == 1: # leave faceted grids at their per-panel size\n",
" panes[0].width, panes[0].height = 750, 750\n",
" image = get_screenshot_as_png(figure, driver=_bokeh_webdriver())\n",
" buffer = io.BytesIO()\n",
" image.save(buffer, format=\"png\")\n",
" return buffer.getvalue()\n",
"\n",
"\n",
"get_ipython().display_formatter.formatters[\"image/png\"].for_type(\n",
" hv.core.dimension.Dimensioned, _hvplot_png\n",
")"
]
},
Expand Down Expand Up @@ -397,6 +464,27 @@
" .label(title=\"Number of Cars by Make\"))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": [
"ex",
"name:bar-counts",
"package:hvplot"
]
},
"outputs": [],
"source": [
"\"\"\"hvPlot exposes Bokeh through a `.hvplot`\n",
"accessor that mirrors pandas' own `.plot`.\n",
"\"\"\"\n",
"(mpg[\"manufacturer\"]\n",
" .value_counts(sort=False)\n",
" .hvplot.barh(\n",
" title=\"Number of Cars by Make\"))"
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand Down Expand Up @@ -545,6 +633,21 @@
" .add(so.Bars(), so.Hist(binwidth=2)))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": [
"ex",
"name:simple-histogram",
"package:hvplot"
]
},
"outputs": [],
"source": [
"mpg.hvplot.hist(\"cty\", bins=12)"
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand Down Expand Up @@ -700,6 +803,26 @@
" title=\"Engine Displacement in Liters vs Highway MPG\"))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": [
"ex",
"name:scatter-plot",
"package:hvplot"
]
},
"outputs": [],
"source": [
"mpg.hvplot.scatter(\n",
" x=\"displ\", y=\"hwy\",\n",
" xlabel=\"Engine Displacement in Liters\",\n",
" ylabel=\"Highway MPG\",\n",
" title=\"Engine Displacement in Liters \"\n",
" \"vs Highway MPG\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand Down Expand Up @@ -1043,6 +1166,22 @@
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": [
"ex",
"name:scatter-plot-with-colors",
"package:hvplot"
]
},
"outputs": [],
"source": [
"mpg.hvplot.scatter(\n",
" x=\"displ\", y=\"hwy\", by=\"class\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand Down Expand Up @@ -1199,6 +1338,23 @@
" .label(x=\"City MPG\", y=\"Highway MPG\"))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": [
"ex",
"name:scatter-plot-with-size",
"package:hvplot"
]
},
"outputs": [],
"source": [
"mpg.hvplot.scatter(\n",
" x=\"cty\", y=\"hwy\",\n",
" s=\"cyl\", scale=4, alpha=0.5)"
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand Down Expand Up @@ -1338,6 +1494,25 @@
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": [
"ex",
"name:scatter-plot-with-facet",
"package:hvplot"
]
},
"outputs": [],
"source": [
"(mpg.hvplot.scatter(\n",
" x=\"displ\", y=\"hwy\", by=\"class\",\n",
" subplots=True, fontscale=0.65,\n",
" width=185, height=185)\n",
" .cols(4))"
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand Down Expand Up @@ -1482,6 +1657,27 @@
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": [
"ex",
"name:scatter-plot-with-facets",
"package:hvplot"
]
},
"outputs": [],
"source": [
"\"\"\"`row` and `col` build a HoloViews `GridSpace`,\n",
"which sorts its panels by the facet values.\n",
"\"\"\"\n",
"mpg.hvplot.scatter(\n",
" x=\"displ\", y=\"hwy\",\n",
" row=\"cyl\", col=\"drv\", subplots=True,\n",
" fontscale=0.8, width=230, height=180)"
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand Down Expand Up @@ -1778,6 +1974,26 @@
" .add(so.Bar(), so.Count(), so.Stack()))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": [
"ex",
"name:stacked-bar-chart",
"package:hvplot"
]
},
"outputs": [],
"source": [
"(diamonds\n",
" .groupby([\"cut\", \"clarity\"])\n",
" .size()\n",
" .unstack()\n",
" .hvplot.bar(stacked=True, rot=45,\n",
" legend=\"top_left\"))"
]
},
{
"cell_type": "code",
"execution_count": null,
Expand Down Expand Up @@ -1907,6 +2123,26 @@
" .add(so.Bar(), so.Count(), so.Dodge()))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": [
"ex",
"name:dodged-bar-chart",
"package:hvplot"
]
},
"outputs": [],
"source": [
"(diamonds\n",
" .groupby([\"cut\", \"clarity\"])\n",
" .size()\n",
" .unstack()\n",
" .hvplot.bar(stacked=False, rot=90,\n",
" fontscale=0.9))"
]
},
{
"cell_type": "code",
"execution_count": null,
Expand Down Expand Up @@ -2072,6 +2308,27 @@
"Image(fig.to_image(format=\"png\", width=750, height=750))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": [
"ex",
"name:stacked-kde",
"package:hvplot"
]
},
"outputs": [],
"source": [
"\"\"\"`xlim` only clips the axis. Unlike ggplot2's\n",
"`xlim()` it doesn't drop rows before the\n",
"densities are estimated.\n",
"\"\"\"\n",
"diamonds.hvplot.kde(\n",
" y=\"depth\", by=\"cut\",\n",
" alpha=0.1, xlim=(55, 70))"
]
},
{
"cell_type": "code",
"execution_count": null,
Expand Down Expand Up @@ -2186,6 +2443,21 @@
"(so.Plot(ts, x=\"date\", y=\"value\")\n",
" .add(so.Line()))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": [
"ex",
"name:timeseries",
"package:hvplot"
]
},
"outputs": [],
"source": [
"ts.hvplot.line(x=\"date\", y=\"value\")"
]
}
],
"metadata": {
Expand Down
2 changes: 1 addition & 1 deletion INTRO.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ There are several tools that can make the kinds of plots described here. At pres

"[Lets-Plot](https://lets-plot.org/ "Lets-Plot: an open-source plotting library for statistical data") is an open-source plotting library for statistical data," written by JetBrains and modeled on the grammar of graphics. Its Python API tracks ggplot2 closely enough that most of the examples below translate line for line. I provide Lets-Plot examples rendered as static images.

"[Bokeh](http://bokeh.pydata.org/en/latest/ "Python interactive visualization library") is a Python interactive visualization library that targets modern web browsers for presentation."
"[Bokeh](http://bokeh.pydata.org/en/latest/ "Python interactive visualization library") is a Python interactive visualization library that targets modern web browsers for presentation." The Bokeh examples below go through [hvPlot](https://hvplot.holoviz.org/), which adds an `.hvplot` accessor to data frames that deliberately echoes the pandas `.plot` API, so most of these plots are one call with a few keyword arguments. hvPlot has no regression line or loess smoother, so those two examples are missing. I provide hvPlot examples rendered as static images.

"[bqplot](https://github.com/bloomberg/bqplot) is a Grammar of Graphics-based interactive plotting framework for the Jupyter notebook."

Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@ dependencies = [
"plotly>=5.24",
"altair>=5.0",
"vl-convert-python>=1.0", # Required for Altair PNG export
"hvplot>=0.12", # Bokeh via the pandas .hvplot accessor

# Image rendering
"kaleido>=1.0", # v1+ requires Chrome
"pillow>=10.0",
"selenium>=4.11", # Required for Bokeh PNG export

# Statistical tools
"statsmodels>=0.14",
Expand Down
1 change: 1 addition & 0 deletions render.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"plotnine": "plotnine",
"lets-plot": "lets-plot",
"plotly": "plotly",
"hvplot": "hvplot (Bokeh)",
"altair": "Altair",
"ggplot": "ggplot2 (R)",
}
Expand Down
Loading
Loading