3D surface plots using Bokeh in Python
Bokeh is a Python library used for creating interactive visualizations in a web browser. It provides powerful tools that offer flexibility, interactivity, and scalability for exploring various data insights.
How are 3D plots possible in Bokeh?
Bokeh directly does not offer 3D visuals; however, it allows integrating the external code in a code block that can use the bokeh modules, create 3D models and display them using the Bokeh functionalities.
We can use the vis.js library and write the code for the model using TypeScript. TypeScript code is wrapped in the Python Bokeh code and helps it to display the models to the user.
What is vis.js?
vis.js is a dynamic data visualization library solely based on the browser. It is user-friendly and allows users to handle lengthy dynamic data efficiently and also provides interactivity features. It consists of various components, including the Graph3d, which we will use in this code.
What is TypeScript?
TypeScript is a strongly typed programming language that builds on JavaScript that uses a type interface to offer better tooling without extra code. It incorporates additional syntax to JavaScript to improve the integration with the editor.
Summary
Used in code block | What is it? |
vis.js | A dynamic, browser based library for visuals. |
TypeScript | A strongly typed programming language that builds on JavaScript, |
Required imports
We import modules from the Bokeh library in the Python code and then import the required modules from them in the TypeScript code block.
import numpy as npfrom bokeh.io import output_file, save, showfrom bokeh.core.properties import Instance, Stringfrom bokeh.models import ColumnDataSource, LayoutDOMfrom bokeh.util.compiler import TypeScript
numpy:To generate data for visualizations and array manipulation.bokeh.io:To control the output and display of the plots. We specifically importoutput_file,saveandshowmethods.bokeh.core.properties:To assign properties of the model. We specifically importInstanceto specify the data source type andStringto specifyxandytypes and properties ofz.bokeh.models:To create highly customized visualizations in Bokeh. We specifically importColumnDataSourceandLayoutDOM.bokeh.util.compiler:To integrate custom code block in the Bokeh plot. We specifically importTypeScriptto define the TypeScript implementation in the code.
import {LayoutDOM, LayoutDOMView} from "models/layouts/layout_dom"import {ColumnDataSource} from "models/sources/column_data_source"import * as p from "core/properties"
LayoutDOM:Import from the Bokeh model class to define custom layout components through subclassing and adding additional properties.LayoutDOM:Import from the Bokeh model class to customize the rendering and interaction behaviors of the layout component.ColumnDataSource:Import from the Bokeh column data source model to store and provide the data for the 3D surface plot. It allows the plot to access and visualize thex,y, andzcoordinates of the surface.p:Import all from the Bokeh properties class to define the plot properties inside TypeScript.
Expected output
A 3D surface plot is created on the
x,y, andzaxis and represents three-dimensional coordinate points.
Allows an ariel view of the plot to interpret the coordinate densities on the three-dimensional grid.
Allow to zoom in on specific coordinates for a more precise visual knowledge of the three-dimensional grid.
Let's code this 3D surface block using the above-mentioned libraries and languages.
Example code
In this code, we use the Bokeh modules and wrap TypeScript code inside the Python code to create a 3D surface plot.
import numpy as np
from bokeh.io import output_file, save, show
from bokeh.core.properties import Instance, String
from bokeh.models import ColumnDataSource, LayoutDOM
from bokeh.util.compiler import TypeScript
CODE = """
import {LayoutDOM, LayoutDOMView} from "models/layouts/layout_dom"
import {ColumnDataSource} from "models/sources/column_data_source"
import * as p from "core/properties"
declare namespace vis {
class Graph3d {
constructor(el: HTMLElement | DocumentFragment, data: object, OPTIONS: object)
setData(data: vis.DataSet): void
}
class DataSet {
add(data: unknown): void
}
}
const OPTIONS = {
width: '650px',
height: '600px',
style: 'surface',
showPerspective: true,
showGrid: true,
keepAspectRatio: true,
verticalRatio: 1.0,
cameraPosition: {
horizontal: -0.35,
vertical: 0.22,
distance: 1.8,
},
}
export class Surface3dView extends LayoutDOMView {
declare model: Surface3d
private _graph: vis.Graph3d
initialize(): void {
super.initialize()
const url = "https://cdnjs.cloudflare.com/ajax/libs/vis/4.16.1/vis.min.js"
const script = document.createElement("script")
script.onload = () => this._init()
script.async = false
script.src = url
document.head.appendChild(script)
}
private _init(): void {
this._graph = new vis.Graph3d(this.shadow_el, this.get_data(), OPTIONS)
this.connect(this.model.data_source.change, () => {
this._graph.setData(this.get_data())
})
}
get_data(): vis.DataSet {
const data = new vis.DataSet()
const source = this.model.data_source
for (let i = 0; i < source.get_length()!; i++) {
data.add({
x: source.data[this.model.x][i],
y: source.data[this.model.y][i],
z: source.data[this.model.z][i],
})
}
return data
}
get child_models(): LayoutDOM[] {
return []
}
}
export namespace Surface3d {
export type Attrs = p.AttrsOf<Props>
export type Props = LayoutDOM.Props & {
x: p.Property<string>
y: p.Property<string>
z: p.Property<string>
data_source: p.Property<ColumnDataSource>
}
}
export interface Surface3d extends Surface3d.Attrs {}
export class Surface3d extends LayoutDOM {
declare properties: Surface3d.Props
declare __view_type__: Surface3dView
constructor(attrs?: Partial<Surface3d.Attrs>) {
super(attrs)
}
static __name__ = "Surface3d"
static {
this.prototype.default_view = Surface3dView
this.define<Surface3d.Props>(({String, Ref}) => ({
x:[ String ],
y:[ String ],
z:[ String ],
data_source:[Ref(ColumnDataSource)],
}))
}
}
"""
class Surface3d(LayoutDOM):
__implementation__ = TypeScript(CODE)
data_source = Instance(ColumnDataSource)
x = String()
y = String()
z = String()
x = np.arange(0, 300, 10)
y = np.arange(0, 300, 10)
xx, yy = np.meshgrid(x, y)
xx = xx.ravel()
yy = yy.ravel()
value = np.sin(xx / 50) * np.cos(yy / 50) * 50 + 50
source = ColumnDataSource(data=dict(x=xx, y=yy, z=value))
surface = Surface3d(x="x", y="y", z="z",
data_source=source,
width=680,
height=600)
#display output
output_file("output.html")
show(surface)Code explanation for Python code
Lines 1–5: Import all the necessary libraries and modules.
Line 7: Create a
CODEvariable that contains the TypeScript code required to create the interactive plot using thevis.jslibrary.
Note: The TypeScript code in the
CODEblock is explained below.
Line 119: Define the
Surface3dclass as a subclass ofLayoutDOMthat serves as the interface between Python and TypeScript implementation.Lines 121–123: Assign the TypeScript code defined in the
CODEvariable and Instance defined in theColumnDataSourceto the__implementation__anddata_sourceattributes of theSurface3dclass respectively.Lines 125–127: Assign the
String()instance tox,y, andz.Lines 130–131: Create
xandyarrays and usenumpyto specify the range for thexandycoordinates, respectively.Lines 133–135: Create
xxandyyarrays, usemeshgrid()fromnumpyto generate the surface plot grid for thexandyvalues and useravel()to convert them into 1D arrays.Lines 137: Compute the
valuearray using the values from thexxandyyarrays.
Lines 139: Assign the
ColumnDataSource, containing adatadictionary withx,y, andzarrays, to thesource.Lines 141–144: Create a
Surface3dclass object and pass thex,y, andzvalues,data_source,widthandheightas parameters.Lines 147–148: Set the output to
output.htmlto specify the endpoint where the display will appear and useshow()to display the 3D surface plot.
Code explanation for TypeScript code
Lines 8–10: Import the bokeh modules from the Python code required in the TypeScript code block.
Lines 12–19: Define a namespace named
visthat encapsulates theGraph3dclass containingsetDatathat takes a parameter of typevia.DataSet, and theDataSetclass containingaddmethod that takesdataas a parameter and add to the dataset.Lines 23–34: Create an
OPTIONSobject and set the properties of the plot that control the appearance to customize visuals as required.Lines 38–39: Define
Surface3dViewclass, which extends theLayoutDOMViewclass and declaremodelattribute in it to reference theSurface3dmodel.
Lines 41: Create a
_graphattribute that holdsGraph3dinstance, which represents the 3D surface plot rendered on the page.Lines 43–51: Override the
initialize()method, and inside it, save the CDN onlink link for CDN that points to 14.16.1 version of the vis.js library urlattribute and save the dynamically created script element onscriptattribute.
Lines 54–59: Define the
_init()method that initializes the_graphby creating avis.Graph3dobject and passing shadow, data, and OPTIONS as parameters, and set a listener using theconnect()method to process and set the new data when there is a change in the Bokeh data source.Lines 63–73: Define the
get_data()method that convertsmodel.data_sourceinto thevislibrary format and returnsdata. It createsvis.DataSetinstance iterates over the data, and useadd()to assign individual data points withx,y, andzcoordinates.Lines 76–77: Override the
child_modelsmethod that returns an empty array depicting theSurface3dViewdoes not have any child models.Line 81: Define a namespace named
Surface3dthat encapsulates the properties and attributes specific to theSurface3dplot.Lines 82–88: Use
LayoutDOM.Propsto assign the inherited properties from it toPropsand specify the attributes and additional properties forx,y,z, anddata_source.Line 92: Define the
Surface3dinterface to ensure the attributes conform to theSurface3d.Attrstype.Lines 94–96: Define
Surface3dclass, which extends theLayoutDOMclass and createpropertiesto specify the properties andview_typeto associate the model withSurface3dView.
Lines 98–102: Define a constructor to initialize the
Surface3dmodel with the provided attributes and set its nameSurface3dto__name__attribute.Lines 104–113: Define a
staticblock as a default view and usedefine(), withSurface3d.Propstype to define the model properties onx,yandzcoordinates.
Code output
It displays a 3D surface plot with a mesh surface created by integrating the TypeScript code block.
Common query
How can we run this TypeScript code block in our Python code?
Free Resources