What are you trying to do?
Use a CustomJS callback to create an svg item and assign it to the text of a Div
model.
What have you tried that did NOT work as expected?
“Quasi” MRE below. You click the button, and the div updates with a dynamically generated svg inside it.
I’m using d3 to create the svg (simple d3 line plot) inside the callback and use my trust save_html_wJSResources
function to have access to d3 library there.
What’s happening is seemingly the size of the svg does not scale with the size of the Div
model, or something to that effect. For example I can hard set the Div
model to be width/height of 1000, but if I set the svg’s width/height properties similarly, I don’t get what I expect (see my comments in the JS code).
I don’t really know if this is a d3 usage/svg usage error, or if it’s a product of sticking the svgs inner/outerHTML into the div model that causes this. Wondering if anyone can weigh in/diagnose.
Thanks!
from bokeh.plotting import figure, show, save
from bokeh.layouts import column, row
from bokeh.models import CustomJS, Button, Div
from jinja2 import Template
from bokeh.embed import components
from bokeh.resources import Resources
#this is only dumped here for MRE: its used to have access to d3 in the callback
def save_html_wJSResources(bk_obj,fname,resources_list_dict,html_title='Bokeh Plot',theme=None
,icon_url='https://aquainsight.sharepoint.com/sites/AquaInsight/_api/siteiconmanager/getsitelogo?type=%271%27&hash=637675014792340093'):
'''function to save a bokeh figure/layout/widget but with additional JS resources imported at the top of the html
resources_list_dict is a dict input of where to import additional JS libs/scripts so they can be utilized into CustomJS etc in bokeh work
e.g. {'sources':['http://d3js.org/d3.v6.js'],'scripts':[var_storing_jsscript]}
theme arg is to pass a yaml-based theme -->use getTheme() function to retrieve dictionary of custom themes, and pass the yaml key
icon_url --> point to a url for an image for the icon url
'''
if theme is None:
script, div = components(bk_obj)
theme={'yaml':None,'css':''}
else:
script, div = components(bk_obj, theme=theme)
print(icon_url)
if icon_url is None:
template = Template('''<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>'''+html_title+'''</title>
{{ resources }}
{{ script }}
</head>
<body>
<div>
{{ div }}
</div>
</body>
</html>
''')
else:
template = Template('''<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="icon" href="'''+icon_url+'''">
<title>'''+html_title+'''</title>
{{ resources }}
{{ script }}
</head>
<body>
<div>
{{ div }}
</div>
</body>
</html>
''')
resources = Resources().render()
for r in resources_list_dict['sources']:
resources = resources +'''\n<script type='text/javascript' src="'''+r+'''"></script>'''
for r in resources_list_dict['scripts']:
resources = resources +'''\n<script type='text/javascript'> '''+r+'''</script>'''
html = template.render(resources=resources,
script=script,
div=div)
print('writing '+fname+'...')
with open(fname, mode="w", encoding="utf-8") as f:
f.write(html)
btn = Button(label='Sank')
#tried a bunch of sizing options here but don't think the problem is with the bokeh model div...
div = Div(text=''
# ,styles={'width':'100%','height':'100%'}
# ,sizing_mode="stretch_both"
,width=900,height=900
)
div.resizable=True
# div.min_height=1000
# div.min_width = 1000
cb = CustomJS(args=dict(div=div)
,code='''
//No matter what, the total extent of the plot sits within some predefined size.
//If I adjust svg Width and svgHeight, the size of the plot (inside the div stays the same but the "zoom" changes)
//e.g. change svgWidth and svgHeight to 200,150 and see what happens
var svgWidth = 900, svgHeight = 600;
div.width = svgWidth
div.height = svgHeight
var margin = {top: 10, right: 10, bottom: 30, left: 50};
var width = svgWidth - margin.left - margin.right;
var height = svgHeight - margin.top - margin.bottom;
// Create SVG element
var svg = d3.create('svg')
.attr("width", svgWidth)
.attr("height", svgHeight);
// Create chart group to provide margin
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Prepare data
var data = [
{x:1, y:6},
{x:2, y:7},
{x:3, y:2},
{x:4, y:4},
{x:5, y:5}
];
// Set up scales
var x = d3.scaleLinear()
.domain([0, d3.max(data, function(d) { return +d.x; })])
.range([0, width]);
var y = d3.scaleLinear()
.domain([0, d3.max(data, function(d) { return +d.y; })])
.range([height, 0]);
// Add X axis
g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add Y axis
g.append("g")
.call(d3.axisLeft(y));
// Add line
g.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
.attr("d", d3.line()
.x(function(d) { return x(d.x) })
.y(function(d) { return y(d.y) })
);
console.log(svg)
div.text= '<svg>'+svg._groups[0][0].outerHTML+'</svg>'
console.log(div)
''')
btn.js_on_click(cb)
save_html_wJSResources(bk_obj=column([btn,div])
, fname=r'test.html'
, resources_list_dict={'sources':['https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.0/d3.min.js'
,'https://cdnjs.cloudflare.com/ajax/libs/d3-sankey/0.12.3/d3-sankey.min.js']
,'scripts':[]}
,html_title='This thing'
)