bqplot: a widget-native plotting library caught mid-split
Plotting library for IPython/Jupyter notebooks
At a glance
- What is it?
- The 2-D plotting system for Project Jupyter is now three packages instead of one, and the fix for the regression that split introduced shipped three days later. The Python and JavaScript halves have to be installed at matching versions, which the README documents with a 23-row table.
- Who is it for?
- The interesting thing about bqplot right now is that two decisions happened within three days of each other in May 2026. Version 0.12.47 shipped pandas>=3 support as a backport to the 0.12.x line, and 0.13.0 shipped the same day to split the project into bqplot, bqplot-gl and bqscales.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 7, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Two APIs, and the choice between them is the real design decision
bqplot ships two front doors and the README points at a different example notebook for each. The pyplot API is the familiar one, matplotlib-shaped, and the screenshot in the README is pyplot.png, which is the visual shorthand for plotting code that reads like the library you already know. The Object Model API is the other one, screenshot bqplot.png, and it is where the actual design of the project lives.
The reason for that split is stated plainly in the introduction: every component of a plot is an interactive widget. That single constraint explains most of what makes bqplot different from a static matplotlib figure. A mark is not a list of drawn points, it is an object with traits. A scale is not an axis range, it is an object that other marks hold a reference to and that can be rebound later. Because all of it is built on ipywidgets, a scale can drive a slider, a dropdown can swap the mark set, and a colour scale can be reconfigured from another widget without redrawing anything in Python.
That is also where the complexity lives, and it is why the examples tree has an Interactions directory and a Plotting Widgets directory alongside the more familiar Basic Plotting and Marks directories. The Grammar of Graphics lineage is explicit in the introduction, which credits the constructs of Wilkinson grammar rather than mimicking a particular plotting library, and the effect is that composition works by wiring objects to objects. The Learning curve is the cost, and the payoff is that a figure can become a control panel without leaving the notebook. If you are coming from matplotlib, read the pyplot examples first to get moving, then move to Object Model for anything interactive, because pyplot will not teach you the trait wiring that the library is actually built on.
Version 0.13.0 turned one package into three
The 0.13.0 release notes open with a Highlights section, and the first line is the one that matters: bqplot is now split in three packages, namely the main bqplot package, bqplot-gl for WebGL-based marks, and bqscales. The second highlight is kiwi-based aspect ratio optimization.
The rest of the release is a long tail of maintenance that reads like an accurate picture of a mature library absorbing ecosystem churn. There is a default toolbar for figure, contributed by trungleduc. There is a fix for changing an interaction causing it to remove the old view twice, from maartenbreddels. There is a fix for jupyter_client >= 7, and a line about removing trailing logs, both from martinRenou. Use bqscales is the mechanical counterpart to the split itself, and it too is from martinRenou.
For an existing user the split is the part that requires work. Anything that imported a scale from bqplot may now need to come from bqscales, and anyone who was reaching for WebGL marks may find they can move that work to bqplot-gl and drop a heavy dependency from the main install. That is the correct shape for this kind of code, since the WebGL path is useful for large point counts and irrelevant for a bar chart, and it should not be in the default install. It is also a breaking change, and the release notes do not flag it with a heading the way a library with a formal deprecation policy would.
The 0.12.x line stayed alive on purpose
Version 0.12.47 was published on 2026-05-04 at 09:18 UTC, and 0.13.0 followed the same day at 11:58 UTC. That ordering is the interesting part. The 0.12.47 release contains a single change, described in the notes as support for pandas>=3, with the parenthetical Backport 0.12.x for real, by martinRenou.
Backporting a dependency compatibility fix to the previous minor line on the same day the next minor line ships is deliberate maintenance discipline. It says the maintainers know a large share of production installs are still on 0.12.x, probably because they upgraded at some earlier point and never had a reason to move again, and pandas 3 breaking their plotting calls is exactly the kind of change that would otherwise strand them. The wording suggests this had already been attempted once and had to be redone properly.
The result is a practical two-track situation. If your environment is pinned and stable, 0.12.47 is a safe place to sit and you get pandas 3 compatibility without absorbing the three-package split. If you want the new layout, 0.13.x is where it is. Either way you should assume the 0.12.x line receives fixes only, and that feature work goes to 0.13.x. Version 0.13.1, published 2026-05-07, is what that feature line looks like in practice: a single pull request, number 1705, from a first-time contributor, fixing MarketMap not rendering and axis positioning in 0.13.0.
That MarketMap fix is worth reading twice. MarketMap is the mark type that renders rectangles positioned and sized by data, which is what choropleth and range charts use. Shipping a minor release where that mark does not render, then patching it three days later with an outside contribution, is a normal and slightly humbling sequence. It is also evidence that the 0.13.x split has not yet been exercised by a wide user base, so if you adopt it early you should expect to find rough edges that 0.12.x users have already smoothed over.
Installing it means installing two halves that have to agree
The plain install is short, and there are two supported paths:
pip install bqplotconda install -c conda-forge bqplotThe complication is that bqplot is not only a Python package. Its declared language in this repository is TypeScript, which is accurate, because the plotting happens in the browser and the Python side is a widget wrapper. The dependency list in the README makes the coupling explicit: ipywidgets at version >=7.0.0, <8.0, traitlets at >=4.3.0, <5.0, traittypes at >=0.2.1, <0.3, plus numpy and pandas. Those upper bounds are real constraints rather than caution, and an environment holding ipywidgets 8 is a different problem from a notebook that will not draw anything.
For anyone on JupyterLab <= 2 there is an extra step, and the README is unusually direct that it applies only to that older range:
jupyter labextension install @jupyter-widgets/jupyterlab-manager bqplotThe reason versions have to be matched at all is spelled out in a section titled Install a previous bqplot version (only for JupyterLab <= 2). You need to know which front-end version matches the back-end version, because the two ship independently. The worked example is 0.11.9 in Python paired with labextension 0.4.9, and the lookup table that follows runs from 0.12.14 down to 0.11.0. The pattern across the table is a consistent offset of seven minor versions, which is easy to remember and presumably easy to get wrong by hand at three in the morning.
One inconsistency is worth knowing about. The table maps back-end 0.11.3 to front-end 0.4.4, but maps 0.11.4 to 0.4.5, skipping 0.4.2 entirely and starting the front-end series at 0.4.0 for 0.11.0. Small discontinuities like that are exactly why the table exists as a table instead of a formula, and why checking the pair is faster than deriving it.
The build wires npm into setuptools, which is where the friction lives
setup.py is short enough to read and it explains the whole packaging story. It imports create_cmdclass, install_npm, ensure_targets, combine_commands, get_version and skip_if_exists from jupyter_packaging, reads the version out of bqplot/_version.py, and then declares what a successful build must have produced. Those targets are share/jupyter/nbextensions/bqplot/index.js and share/jupyter/labextensions/bqplot/package.json. If those files are not there after the build, ensure_targets fails the install rather than shipping a Python package whose front end is missing.
The actual build command is install_npm pointed at the js directory, using jlpm rather than npm, with a build directory of share/jupyter/, a source directory of js/src, and a build command of build. jlpm is the Yarn that ships with JupyterLab, and .yarnrc.yml in the repository root is the matching configuration, so the JavaScript side is built with the package manager JupyterLab expects rather than whatever npm version happens to be on the machine. data_files_spec then lists the nbextension and labextension paths plus etc/jupyter/nbconfig/notebook.d/bqplot.json, and a comment in the file notes that map_data is being added to package_data manually.
pyproject.toml confirms the toolchain at the top level, requiring jupyter_packaging~=0.12.2, jupyterlab==4.*, setuptools>=40.8.0 and wheel, with setuptools.build_meta as the build backend. The jupyterlab==4.* pin alongside a README section that repeatedly says JupyterLab <= 2 is a mild tension, and it is explained by the split: modern JupyterLab uses the prebuilt extension mechanism and needs no manual labextension install, while the version table exists for the older path.
The header comment in setup.py carries Copyright 2015 Bloomberg Finance L.P., which dates the origin of the project and its institutional backing. Around that, the repository carries the usual set of development scaffolding: environment.yml and test-environment.yml for conda-based setup, pytest.ini for Python tests, a tests directory, a ui-tests directory for browser-level checks, RELEASE.md and CONTRIBUTING.md, mkdocs.yml with docs, custom_theme, install.json, and examples including data_files.
Examples as curriculum, and what 279 open issues suggest
The examples directory is organised well enough to be a course. Index.ipynb is the entry point, Introduction.ipynb and Tutorials.ipynb sit beside a Tutorials subdirectory, and then there are Advanced Plotting, Applications, Basic Plotting, Interactions, Marks, Plotting Widgets and Scales. Applications includes the Wealth of Nations bubble chart notebook, which is the gif in the README and the best single demonstration of what this library is for: many marks, a colour scale, and a bubble size channel all driven at once.
Two details in the tree are worth flagging for anyone reading the repository. The .nblink directory suggests documentation notebooks are linked rather than copied, so the examples that appear on the site and the ones in the repository cannot drift apart. And custom_theme at the top level, alongside bqplot.png and pyplot.png as separate screenshot assets, points to a styling story that is maintained in the repo rather than left to each notebook author.
Then there is the number: 279 open issues against 3692 stars, with 477 forks. That ratio is high for a library of this age and it lines up with the split history. A package that ships Python and JavaScript halves at independently versioned numbers, wraps widget traits with upper-bounded dependency ranges, and just reorganised itself into three distributions has a long tail of environment-specific reports by construction. Add a dependency ecosystem moving through ipywidgets major versions, jupyter_client majors, and pandas 3, and most of those issues are probably about pinning rather than about plotting.
The practical reading is that bqplot rewards a deliberate installation and a version choice made on purpose. Install it with conda-forge if your environment is conda-based, because the front end is built with jlpm and that pairing is the tested one. Check your ipywidgets version before you install rather than after. Decide between 0.12.x and 0.13.x deliberately. And if something does not render, the version pair is the first thing to verify, ahead of your code, because the library will not tell you which half is stale.
Editorial conclusion
The interesting thing about bqplot right now is that two decisions happened within three days of each other in May 2026. Version 0.12.47 shipped pandas>=3 support as a backport to the 0.12.x line, and 0.13.0 shipped the same day to split the project into bqplot, bqplot-gl and bqscales. Then 0.13.1 landed on 2026-05-07 to fix a MarketMap that stopped rendering, which is a plain statement that the headline release had a visible defect in one of its most distinctive mark types. For your own decision, three facts are enough. Pick the 0.12.x line if you cannot absorb a dependency split, and you will still get pandas 3. Pick 0.13.x if you want WebGL marks or want scales reusable outside a figure, and read the bqscales migration first. In either case, remember that a bqplot import is only half a component: setup.py builds the front end with jlpm into share/jupyter/labextensions/bqplot and verifies index.js exists before the Python package installs, so a working notebook needs both halves at matching versions.
Frequently asked questions
Which Python library is best for plotting?
That depends on whether the figure has to be interactive. For static output inside a notebook, matplotlib through the inline backend remains the shortest path. bqplot is the better answer when the plot itself has to respond to input, because every mark and scale in it is an ipywidgets widget that another control can drive, so a figure can behave like a control panel rather than a picture.
What is the purpose of JupyterLab?
JupyterLab is the browser-based workbench for notebooks, terminals and file browsing, and it matters here because it is what loads the labextension half of bqplot. On JupyterLab 4 or later that extension is installed with the Python package. On versions at or below 2 you need the manual jupyter labextension install step, and you need the Python and JavaScript versions to match.
What is ggplot used for?
ggplot popularised the idea of building a plot from composable layers: data, scales, coordinate system and marks, rather than a sequence of imperative drawing calls. bqplot takes that idea and puts it inside Jupyter widgets, so scales and marks are objects with traits that can be rewired at runtime instead of fixed at draw time.
Why isn't my Jupyter Notebook plotting?
For bqplot specifically, check the version pairing before your own code. The Python package and the labextension are versioned independently, and setup.py verifies that the front-end build produced index.js and the labextension package.json before it will install. A stale or missing extension shows up as a blank output rather than as an error.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/bqplot-bqplot)