K3D: WebGL 3D plots in a notebook cell, from NumPy arrays or VTK
WebGL 3D plotting for Jupyter: meshes, isosurfaces, volumes, voxels and point clouds from NumPy arrays, with a path-traced cinematic renderer.
At a glance
- What is it?
- K3D is a Jupyter widget library that renders 3D meshes, isosurfaces, volumes, voxels and point clouds with WebGL, aimed at being as easy to use as matplotlib while still interoperating with VTK. Since version 3.0.0 it runs on anywidget rather than a JupyterLab extension, and its third renderer, a path traced cinematic mode, is explicitly experimental.
- Who is it for?
- K3D suits a computational scientist or student who has data as a NumPy array and needs an interactive 3D view inside a notebook without exporting files or writing WebGL, because the plotting API is Python and the widget ships its own frontend module. It is the wrong tool if you need a publication-quality static image, since a WebGL view is a live view, and it is the wrong choice if your browser or GPU cannot provide WebGL2, which the cinematic renderer requires.
- Can I use it commercially?
- Yes. MIT 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 Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A matplotlib-shaped API over a WebGL canvas
K3D lets you create 3D plots backed by WebGL with a high-level API covering surfaces, isosurfaces, voxels, meshes, point clouds, VTK objects, volume renderers and colormaps.
The stated primary aim is to be easy to use as a standalone package like matplotlib, but also to allow interoperation with existing libraries as VTK. That double goal is the design brief, and it is where the project's real character sits.
A standalone plotting library has to be pleasant for someone who only has an array. It plots, it looks reasonable, it does not require the user to assemble a rendering pipeline. The VTK interoperation goal adds the other half: a researcher who already has a VTK pipeline should be able to push their objects into the widget rather than export and re-import.
The topics list names the data types it targets, including isosurface, point-cloud, voxel, volume-rendering, scientific-visualization, webgl and jupyter-widget, and the package metadata classifies it for science and research, education and developers.
The rendering surface runs in four places. A Jupyter Notebook or JupyterLab widget via anywidget, a Google Colab widget, VS Code notebooks, and standalone HTML/JS. That last one matters more than it sounds, because a notebook that renders an interactive 3D scene is a document, whereas an exported standalone page can be attached to a paper or shared with someone who will never open a notebook.
Three renderers, and the one marked experimental
Since 3.0.0 a plot also chooses how it is lit, through plot.renderer, and the README is unusually direct about which of the three to trust.
The simple renderer is the default rasteriser. The advanced renderer is image-based lighting with ambient occlusion. The cinematic renderer is progressive path tracing.
The cinematic renderer is experimental. It needs WebGL2 with renderable float textures. It does not cover every object: volume_slice is not drawn, mip stays outside the light simulation, and only the first volume is path traced. Its API and output may still change. The README states plainly that simple and advanced are the stable choices.
That is a well-handled disclosure. The showcase image gives the two renderers a fair showing. The advanced example is a million analytic sphere impostors where the occlusion in the crevices between strands does the sculpting. The cinematic example is a Draco-compressed glTF read with k3d.glb, where the stones refract with an index of 2.09 and throw light between their facets and the gold reflects the studio around it, at 256 samples denoised with Open Image Denoise, using a model by saber7711 under CC-BY.
The denoiser is Intel's Open Image Denoise, run in the browser by oidn-web, and the path tracing itself comes from three-gpu-pathtracer and three-mesh-bvh by Garrett Johnson, on top of Three.js. So the cinematic mode is a real renderer, not an approximation, and it is also the piece most likely to change.
anywidget removed the JupyterLab extension step
The single most consequential change in the project's recent history is the move to anywidget, and the README shows the difference concretely.
The Google Colab section says that since 3.0.0, the anywidget migration, no extra steps are needed, with a single pip install k3d. Before the migration, users had to import k3d, plot, and handle custom widget activation and the text protocol themselves. The source installation section repeats it: no separate JupyterLab extension step is needed, because the widget ships its own frontend module as an anywidget.
This matters more than it first appears. A prebuilt JupyterLab extension has to be built against a specific JupyterLab version, and it breaks when the environment moves. An anywidget is a Python object that ships its own JavaScript and renders inside the standard ipywidgets machinery, so it is version-independent of the notebook frontend.
The cost is that the frontend is no longer a separately installed and separately upgraded artefact. The JavaScript now lives in the pip package, and the build has to happen when the Python package is built. The repository layout shows the shape of that: a js/ directory with the frontend source, a package.json whose files field ships js/src, and a main pointing at js/src/standalone.js.
The pyproject.toml build system requires hatchling at 1.27.0 or newer, because that is the first version with PEP 639 support needed for the license and license-files fields, plus hatch-nodejs-version. The Python package version is supplied statically by hatch-nodejs-version, since package.json's homepage is a git clone URL.
Four install routes, and a dependency on Node for two of them
Installation is documented in four ways and the differences are real.
From PyPI:
$ pip install k3dFrom conda-forge:
$ conda install -c conda-forge k3dIn Google Colab, since 3.0.0 no extra steps are needed:
!pip install k3dFrom GitHub, which requires git and node.js plus npm to build:
$ pip install git+https://github.com/K3D-tools/K3D-jupyterThe same command with a @devel suffix gives the most up-to-date development version, and any historical version can be installed by replacing devel with a tag or commit hash.
For a source checkout, which requires npm and node.js:
$ git clone https://github.com/K3D-tools/K3D-jupyter.git
$ cd K3D-jupyter
$ pip install -e .So the two routes that skip Node are the ones that use a prebuilt wheel, and the two that need Node are the ones building the frontend from source. That is the practical dividing line for CI: a pip install from PyPI in a research environment needs nothing but Python, while anyone working on the widget itself needs a Node toolchain.
The Python requirements are short: anywidget at 0.9.13 or newer, ipywidgets at 7.6.0 but below 9.0.0, traittypes, traitlets, numpy, msgpack and deepcomparer.
The pyproject.toml comments explain two decisions in detail. The licence is an SPDX expression rather than the file form, because the file form puts the whole MIT text into the License metadata field which PyPI renders verbatim instead of as a licence chip. The version is declared static because a dynamic version with no hook is what left it null on PyPI.
The Docker test setup and the shared memory trap
The docker-compose.yml at the root is the most instructive file in the repository, because the comments explain the two non-obvious decisions in detail.
The first is shared memory. Docker gives a container 64 MB of /dev/shm, which one Chrome at 1280x720 just fits and two do not. The second renderer dies and its canvas comes back blank, which reads as every visual test failing rather than as running out of shared memory. The compose file therefore sets shm_size to 2gb, because pytest -n needs the room.
That is a real constraint that escapes into production-adjacent environments. If you run K3D inside a container with a small /dev/shm, a multi-renderer page will fail in a way that looks like a bug in the library.
The second is the port publishing. docker compose run publishes nothing unless --service-ports is passed, and Jupyter has to bind 0.0.0.0 to be reachable through the mapping. The comment gives the exact sequence:
docker compose run --rm --service-ports k3d-build bash
jupyter lab --ip=0.0.0.0 --port=8888 --allow-root --no-browser
docker compose stop k3d-buildThe ports 8888 and 9000 are published, and the deploy block reserves one nvidia GPU device with the nvidia driver, so the compose file assumes a machine with a GPU is available.
The volume mount maps the working tree to /opt/app/src with the delegated consistency flag, so edits on the host appear inside the container immediately. The service builds from the repository's own Dockerfile, and the image is tagged k3d-build:latest.
MIT licensed, with a European research origin
The licence is MIT, and the pyproject.toml carries a License :: OSI Approved :: MIT License classifier with the licence text in LICENSE.txt. The README's acknowledgements section records that the package was created as part of the Horizon 2020 European OpenDreamKit project, grant agreement #676541, and the acknowledgements page shows the European Research Infrastructure project flag.
The project follows the Python Software Foundation Code of Conduct, which is stated explicitly in the README.
The dependency on Three.js is acknowledged as the core of the rendering, along with Jupyter itself and the OpenDreamKit project. The cinematic renderer's path tracing and denoising credits are given separately, naming three-gpu-pathtracer, three-mesh-bvh, Open Image Denoise and oidn-web.
The release history shows v3.2.0 on 2026-10-05, v3.1.2 on 2026-10-02 and v3.1.1 on 2026-09-22, and the last push was on 2026-10-05, matching the newest release. The package version in pyproject.toml is 3.2.0 and the npm package version in package.json is the same, so the two halves of the distribution are kept in step.
The supported Python versions are listed in the classifiers from 3.9 to 3.12, and the comment next to that list says it stops at 3.12 because that is what the test workflow runs. So a user on 3.13 or 3.14 can install the package, since requires-python is 3.9 or newer, but the test matrix does not cover it, and the ipywidgets constraint of below 9.0.0 is another upper bound to be aware of when planning an environment.
Editorial conclusion
K3D suits a computational scientist or student who has data as a NumPy array and needs an interactive 3D view inside a notebook without exporting files or writing WebGL, because the plotting API is Python and the widget ships its own frontend module. It is the wrong tool if you need a publication-quality static image, since a WebGL view is a live view, and it is the wrong choice if your browser or GPU cannot provide WebGL2, which the cinematic renderer requires. Before you commit, decide which renderer you need and read the renderers page, because simple and advanced are called stable while cinematic is described as experimental, and check that your environment satisfies the shared memory requirement if you plan to run the test suite in Docker.
Frequently asked questions
How do I install K3D for Jupyter?
Run pip install k3d, or conda install -c conda-forge k3d. Since 3.0.0 no separate JupyterLab extension step is needed because the widget ships its own frontend module through anywidget, and in Google Colab a single pip install is enough.
What is the difference between the K3D simple, advanced and cinematic renderers?
simple is the default rasteriser, advanced is image-based lighting with ambient occlusion, and cinematic is progressive path tracing. The README marks cinematic as experimental, requiring WebGL2 with renderable float textures and not covering every object, and says simple and advanced are the stable choices.
Can I use K3D with VTK objects?
Yes. The stated primary aim is to be easy to use as a standalone package like matplotlib, while also allowing interoperation with existing libraries as VTK, and the API listing includes VTK objects among the supported plot types.
Does K3D need Node.js to install?
Only when building from source. Installing from PyPI or conda-forge needs only Python, while installing directly from GitHub or doing a development installation from a clone requires git and node.js with npm.
Why does K3D need a large /dev/shm when run in Docker?
The compose file comments explain that Docker gives a container 64 MB of /dev/shm, which one Chrome at 1280x720 just fits and two do not, so the second renderer dies and its canvas comes back blank, which is why shm_size is set to 2gb for pytest -n to have the room it needs.
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/k3d-tools-k3d-jupyter)Was this useful?