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.
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.
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.
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.
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, |
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 import output_file
, save
and show
methods.
bokeh.core.properties:
To assign properties of the model. We specifically import Instance
to specify the data source type and String
to specify x
and y
types and properties of z
.
bokeh.models:
To create highly customized visualizations in Bokeh. We specifically import ColumnDataSource
and LayoutDOM
.
bokeh.util.compiler:
To integrate custom code block in the Bokeh plot. We specifically import TypeScript
to 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 the x
, y
, and z
coordinates of the surface.
p:
Import all from the Bokeh properties class to define the plot properties inside TypeScript.
A 3D surface plot is created on the x
, y
, and z
axis 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.
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)
Lines 1–5: Import all the necessary libraries and modules.
Line 7: Create a CODE
variable that contains the TypeScript code required to create the interactive plot using the vis.js
library.
Note: The TypeScript code in the
CODE
block is explained below.
Line 119: Define the Surface3d
class as a subclass of LayoutDOM
that serves as the interface between Python and TypeScript implementation.
Lines 121–123: Assign the TypeScript code defined in the CODE
variable and Instance defined in the ColumnDataSource
to the __implementation__
and data_source
attributes of the Surface3d
class respectively.
Lines 125–127: Assign the String()
instance to x
, y
, and z
.
Lines 130–131: Create x
and y
arrays and use numpy
to specify the range for the x
and y
coordinates, respectively.
Lines 133–135: Create xx
and yy
arrays, use meshgrid()
from numpy
to generate the surface plot grid for the x
and y
values and use ravel()
to convert them into 1D arrays.
Lines 137: Compute the value
array using the values from the xx
and yy
arrays.
Lines 139: Assign the ColumnDataSource
, containing a data
dictionary with x
, y
, and z
arrays, to the source
.
Lines 141–144: Create a Surface3d
class object and pass the x
, y
, and z
values, data_source
, width
and height
as parameters.
Lines 147–148: Set the output to output.html
to specify the endpoint where the display will appear and use show()
to display the 3D surface plot.
Lines 8–10: Import the bokeh modules from the Python code required in the TypeScript code block.
Lines 12–19: Define a namespace named vis
that encapsulates the Graph3d
class containing setData
that takes a parameter of type via.DataSet
, and the DataSet
class containing add
method that takes data
as a parameter and add to the dataset.
Lines 23–34: Create an OPTIONS
object and set the properties of the plot that control the appearance to customize visuals as required.
Lines 38–39: Define Surface3dView
class, which extends the LayoutDOMView
class and declare model
attribute in it to reference the Surface3d
model.
Lines 41: Create a _graph
attribute that holds Graph3d
instance, which represents the 3D surface plot rendered on the page.
Lines 43–51: Override the initialize()
method, and inside it, save the CDN url
attribute and save the dynamically created script element on script
attribute.
Lines 54–59: Define the _init()
method that initializes the _graph
by creating a vis.Graph3d
object and passing shadow, data, and OPTIONS as parameters, and set a listener using the connect()
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 converts model.data_source
into the vis
library format and returns data
. It creates vis.DataSet
instance iterates over the data, and use add()
to assign individual data points with x
, y
, and z
coordinates.
Lines 76–77: Override the child_models
method that returns an empty array depicting the Surface3dView
does not have any child models.
Line 81: Define a namespace named Surface3d
that encapsulates the properties and attributes specific to the Surface3d
plot.
Lines 82–88: Use LayoutDOM.Props
to assign the inherited properties from it to Props
and specify the attributes and additional properties for x
, y
, z
, and data_source
.
Line 92: Define the Surface3d
interface to ensure the attributes conform to the Surface3d.Attrs
type.
Lines 94–96: Define Surface3d
class, which extends the LayoutDOM
class and create properties
to specify the properties and view_type
to associate the model with Surface3dView
.
Lines 98–102: Define a constructor to initialize the Surface3d
model with the provided attributes and set its name Surface3d
to __name__
attribute.
Lines 104–113: Define a static
block as a default view and use define()
, with Surface3d.Props
type to define the model properties on x
, y
and z
coordinates.
It displays a 3D surface plot with a mesh surface created by integrating the TypeScript code block.
How can we run this TypeScript code block in our Python code?