Created
February 16, 2023 13:56
-
-
Save gmerritt123/55356ea10be37a1f73c144801d12be56 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- 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