Skip to content

Instantly share code, notes, and snippets.

@gmerritt123
Created February 16, 2023 13:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gmerritt123/55356ea10be37a1f73c144801d12be56 to your computer and use it in GitHub Desktop.
Save gmerritt123/55356ea10be37a1f73c144801d12be56 to your computer and use it in GitHub Desktop.
# -*- coding: utf-8 -*-
"""
Created on Wed Feb 15 19:24:01 2023
Example showing how to use d3 to draw JS-side voronoi polygons on a bokeh plot
@author: Gaelen Merritt
"""
import numpy as np
from bokeh.plotting import figure
from bokeh.layouts import column
from bokeh.models import CustomJS, ColumnDataSource, PointDrawTool, Slider, ColorBar
from bokeh.palettes import Sunset8
from bokeh.transform import linear_cmap
from bokeh.embed import components
from bokeh.resources import Resources
x = np.random.random(100)*3
y = np.random.random(100)*2
z = 1.3*np.exp(-2.5*((x-1.3)**2 + (y-0.8)**2)) - 1.2*np.exp(-2*((x-1.8)**2 + (y-1.3)**2))
src = ColumnDataSource({'x':x,'y':y,'z':z,'pxs':[[] for x in range(100)],'pys':[[] for y in range(100)]})
p = figure(width=800, height=400, x_range=(-.5, 3.5), y_range=(-0.5, 2.5)
,title='Move some points!')
cmap = linear_cmap(field_name='z',palette=Sunset8,low=-1,high=1)
r = p.scatter(x='x',y='y'
,fill_color=cmap
,line_alpha=cmap
,size=8
,source=src)
color_bar = ColorBar(color_mapper=cmap.transform, label_standoff=12)
p.add_layout(color_bar, "right")
et = PointDrawTool(renderers=[r],num_objects=50)
p.add_tools(et)
p.toolbar.active_tap = et
vr = p.patches(xs='pxs',ys='pys',fill_color=cmap,line_color='black',fill_alpha=0.5,source=src)
sl = Slider(title='Buffer extent',value=0,start=0,end=1,step=0.1)
cb = CustomJS(args=dict(src=src,sl=sl)
,code='''
var px = src.data['x']
var py = src.data['y']
var ext = [d3.min(px)-sl.value,d3.min(py)-sl.value,d3.max(px)+sl.value,d3.max(py)+sl.value]
//create the delaunay/voronoi object using d3, and iterate through its polygons
//populating arrays storing polygon geometries as we go
var delaun = d3.Delaunay.from(d3.transpose([px,py]))
var vor = delaun.voronoi(ext)
var polyxs = []
var polyys = []
var inds = []
for (var vp of vor.cellPolygons()){
//it's possible that a polygon with less than 3 points will be produced
//in that case, want to assign an empty geometry instead
if (vp.length>3){
inds.push(vp.index)
var tp = d3.transpose(vp)
polyxs.push(tp[0])
polyys.push(tp[1])
}
else {
polyxs.push([])
polyys.push([])
}
}
src.data['pxs'] = polyxs
src.data['pys'] = polyys
src.change.emit()
''')
src.js_on_change('data',cb)
sl.js_on_change('value',cb)
def save_html_wJSResources(bk_obj,fname,resources_list,html_title='Bokeh Plot'):
'''function to save a bokeh figure/layout/widget but with additional JS resources imported at the top of the html
resources_list is a list input of where to import additional JS libs so they can be utilized into CustomJS etc in bokeh work
e.g. ['http://d3js.org/d3.v6.js']
'''
script, div = components(bk_obj)
resources = Resources()
# print(resources)
tpl = '''<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>'''+html_title+'''</title>
'''
tpl = tpl+resources.render_js()
for r in resources_list:
tpl = tpl +'''\n<script src="'''+r+'''"></script>'''
tpl = tpl+script+\
'''
</head>
<body>'''\
+div+\
'''</body>
</html>'''
with open(fname,'w') as f:
f.write(tpl)
save_html_wJSResources(bk_obj=column([p,sl]),fname='d3Vor.html'
,resources_list=['https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.0/d3.min.js'
,'https://cdn.jsdelivr.net/npm/d3-delaunay@6']
,html_title='d3 Voronoi')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment