How to adjust height and formatting of rows in Bokeh DataTable - html

I'm trying to adjust the font-size of the title and cells and pop-up message of a bokeh DataTable.
When doing so using the HTMLTamplateFormatter I can get the font-size to increase, however, I do not know how to increase the height of a row.
Also I have no clue how to adjust the formatting/height of the top/title cell of the column.
I was able to make the title bold using the information provided at this issue. Is there a similar piece of code I could use to increase the font-size.
When hovering a cell I want to show some information, I would also like to format this, is that possible? And if so how?
This is a minimal example of what I currently have:
import pandas
from bokeh.models import (ColumnDataSource, TableColumn, DataTable)
from bokeh.models.widgets import HTMLTemplateFormatter
from bokeh.io import show
data = pandas.DataFrame({"x": [1, 2, 3, 4],
"y": [200, 3, 4, 5]})
source = ColumnDataSource(data=data)
columns = []
# column 1 with bold title: x and 200% font-size
template200 = """
<div title="<%= x %>" style="font-size: 200%">
<%= value %>
</div>
"""
htmltemplateformatter200 = HTMLTemplateFormatter(template=template200)
col = "x"
title = "<b>%s</b>" % col
columns.append(TableColumn(field=col, title=title,
width=50, editor=None,
formatter=htmltemplateformatter200))
# column 2 with normal title: y and 400% font-size
template400 = """
<div title="<%= x %>" style="font-size: 400%">
<%= value %>
</div>
"""
htmltemplateformatter400 = HTMLTemplateFormatter(template=template400)
col = "y"
columns.append(TableColumn(field=col, title=col,
width=50, editor=None,
formatter=htmltemplateformatter400))
data_table = DataTable(source=source, columns=columns, row_headers=False,
sortable=False)
show(data_table)
This code will make the text in the table cell bigger. However, when I change the size to 400% the text is larger than the height of the row can visualize.
Could anyone help me with this?
Cheers,
Dave
First edit:
To show what I want to be able to do:
In my case I think I would only need a single row height since I want all the font-sizes to be the same.
Second edit:
After the reply of Okonomiyaki I looked at my code again and came up with the following solution:
main.py:
import pandas
from bokeh.models import (ColumnDataSource, TableColumn, DataTable)
from bokeh.models.widgets import HTMLTemplateFormatter
from bokeh.io import curdoc
data = pandas.DataFrame({"x": [1, 2, 3, 4],
"y": [200, 3, 4, 5]})
source = ColumnDataSource(data=data)
columns = []
# column 1 with bold title: x and 20px font-size
template200 = """
<div title="<%= x %>" style="font-size: 20px;">
<%= value %>
</div>
"""
htmltemplateformatter200 = HTMLTemplateFormatter(template=template200)
col = "x"
title = "<b>%s</b>" % col
columns.append(TableColumn(field=col, title=title,
width=50, editor=None,
formatter=htmltemplateformatter200))
# column 2 with bold title: y and 20px font-size
template400 = """
<div title="<%= y %>" style="font-size: 20px;">
<%= value %>
</div>
"""
htmltemplateformatter400 = HTMLTemplateFormatter(template=template400)
col = "y"
columns.append(TableColumn(field=col, title=col,
width=50, editor=None,
formatter=htmltemplateformatter400))
data_table = DataTable(source=source, columns=columns, row_headers=False,
sortable=False, height=1000, fit_columns=True)
curdoc().add_root(data_table)
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script>
$(function() {
$( document ).tooltip();
});
</script>
<meta charset="utf-8">
{{ bokeh_css }}
{{ bokeh_js }}
<style>
{% include 'jquery-ui.css' %}
{% include 'styles.css' %}
</style>
</head>
<body>
<div class="content">
{{ plot_div|indent(8) }}
</div>
{{ plot_script|indent(8) }}
</body>
</html>
styles.css:
.ui-tooltip {
padding: 8px;
position: absolute;
z-index: 9999;
max-width: 500px;
-webkit-box-shadow: 0 0 5px #aaa;
box-shadow: 0 0 5px #aaa;
opacity: 1;
font-size: 20px;
}

So few things, it turns out it is not possible to have variable row heights with a data_table. This is a restriction set by the underlying Js library - Slick Grid.
https://groups.google.com/a/continuum.io/forum/#!topic/bokeh/cg01WWfpdhw
Some discussion relevant to bokeh here.
You can however easily have variable styles on each row, and this can be set within python. Having the text/styles change is easier done with an external css sheet - although could be done within the HTML formatter, but quite messily.
Create the following app structure: (to run in command prompt type bokeh serve --show myapp)
myapp
|
+---main.py
|---templates
+---index.html
+---styles.css
main.py (contained within myapp directory) :
import pandas
from bokeh.models import (ColumnDataSource, TableColumn, DataTable)
from bokeh.models.widgets import HTMLTemplateFormatter
from bokeh.io import show, curdoc
x0 = [1,"8px", "red", "bold", "hover 1"]
x1 = [2,"10px", "blue", "italic", "hover 2"]
x2 = [3,"12px", "green", "bold", "hover 3"]
x3 = [4,"14px", "orange", "strong", "hover 4"]
x = [x0,x1,x2,x3]
data = pandas.DataFrame({"x": x,
"y": [200, 3, 4, 5]})
source = ColumnDataSource(data=data)
columns = []
# now in the formatter value[0] = original x value
# value[1] = desired font size
# value[2] = desired font color
# value[3] = desired font style
# value[4] = text to display on hover
# you could feed in any styles you want, or do it externally via css + js
template200 = """
<div title="<%= x %>" style="font-size: <%= value[1]%> ;
color: <%=value[2]%>; font-weight:<%=value[3] %>;" >
<div class = "parent_div">
<span class="nonhover"> <%= value[0] %> </span>
<span class= "cell_hover"><%= value[4]%></span> </div>
</div>
"""
htmltemplateformatter200 = HTMLTemplateFormatter(template=template200)
col = "x"
title = "<b>%s</b>" % col
columns.append(TableColumn(field=col, title=title,
width=50, editor=None,
formatter=htmltemplateformatter200))
# column 2 with normal title: y and 400% font-size
template400 = """
<div title="<%= x %>" style="font-size: 20px;">
<%= value %>
</div>
"""
htmltemplateformatter400 = HTMLTemplateFormatter(template=template400)
col = "y"
columns.append(TableColumn(field=col, title=col,
width=50, editor=None,
formatter=htmltemplateformatter400))
data_table = DataTable(source=source, columns=columns, row_headers=False,
sortable=False,height = 1000, fit_columns=True)
curdoc().add_root(data_table)
styles.css (contained within directory templates)
Basically you have two spans within a div. When you hover over them, one has the display property set to none - therefore not visible. The other span which is previously not visible then has its display set to inline, becoming visible. You can change the style of the hover over text through this.
Also note because both of the spans are contained within the very outer div which has the styles set through python, both the hover and default text have the same css style properties.
.nonhover{
display: inline;
}
.cell_hover{
display: none;
background: yellow;
}
.parent_div:hover .nonhover{
display: none;
}
.parent_div:hover .cell_hover{
display: inline;
background: yellow;
}
index.html (contained within directory templates)
<!DOCTYPE html>
<html lang="en">
<head>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/0.10.0/lodash.min.js"></script>
<meta charset="utf-8">
{{ bokeh_css }}
{{ bokeh_js }}
<style>
{% include 'styles.css' %}
</style>
</head>
<body>
<div class="content">
{{ plot_div|indent(8) }}
</div>
{{ plot_script|indent(8) }}
</body>
</html>
Hope that still helps.

Related

how to integrate python with html using jinja

Here I am using jinja template to integrate python with html. Backend code has been written to fetch images one after another from folder and count the number of objects in an image and display it on front end.There is one container for live image, another container for preview image and the total count box it should display on dashboard.
mian.py
app=Flask(__name__)
#app.route('/')
def welcome():
return render_template("index.html")
#app.route('/')
def count(photo):
image = cv2.imread(f"C:/Users/suhas/Documents/New folder/templates/static/photos/{photo}")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
plt.imshow(gray, cmap='gray')
#plt.show()
blur = cv2.GaussianBlur(gray, (11, 11), 0)
plt.imshow(blur, cmap='gray')
plt.show()
canny = cv2.Canny(blur, 25, 205, 2)
plt.imshow(canny, cmap='gray')
dilated = cv2.dilate(canny, (1, 1), iterations=2)
plt.imshow(dilated, cmap='gray')
(cnt, hierarchy) = cv2.findContours(
dilated.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
cv2.drawContours(rgb, cnt, -1, (0, 255, 0), 2)
bgr = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
imS = cv2.resize(rgb, (540, 340))
text = "Total count:"+str(len(cnt))
# font
font = cv2.FONT_HERSHEY_SIMPLEX
# org
org = (220, 325)
# fontScale
fontScale = 1
# Red color in BGR
color = (0, 0, 255)
# Line thickness of 2 px
thickness = 2
# Using cv2.putText() method
imag = cv2.putText(imS, text, org, font, fontScale,
color, thickness, cv2.LINE_AA, False)
cv2.imshow('output', imag)
img=print(f"Total numbe:{photo}", len(cnt))
cv2.waitKey(1)
time.sleep(10)
cv2.destroyAllWindows()
cnt = 0
# return 1
while 1:
photo=os.listdir("C:/Users/suhas/Documents/New folder/templates/static/photos/")
# if len(photo)==0:
# break
for i in photo:
count(i)
shutil.move("C:/Users/suhas/Documents/New folder/templates/static/photos/%s"%(i),"C:/Users/suhas/Documents/New folder/templates/static/move")
print("------")
time.sleep(10)
return render_template("index.html",live='photo',preview='img',TotalCount='text')
if __name__=="__main__":
app.run()
HTML
</div>
<div class="videoSection">
<div class="liveVideoContainer">
<h3>Live</h3>
{% for img in live %}
<img src=" {{url_for('static',filename= img )}} " width="80" height="142">
{% endfor %}
<div class="previewVideoContainer">
<h3>Preview</h3>
<img src="{{url_for('static',filename= preview)}} " width="80" height="142">
' </div>
<footer>
<div class="count">
<h3>{{Totalcount}}</h3>
</div>
</footer>
</div>

django response method POST don't render to the html template

I have two request function in views one is with .get method and the other one with .post. Both of the function works properly because in the the terminal the code is 200.
[01/Apr/2021 08:04:39] "GET /search/search HTTP/1.1" 200 4164
[01/Apr/2021 08:04:57] "POST /search/search HTTP/1.1" 200 4164
The problem comes when i try to render the function with .post method to the html template nothing appear on the html page.
def wind_search(request):
if request.method == 'post':
city = request.post['city']
weather_city_url = urllib.request.urlopen('api.openweathermap.org/data/2.5/weather?q=' + city + '&appid=1a7c2a40a0734d1dc18141fc6b6241bb').read()
list_of_data = json.loads(waether_city_url)
# main wind information
wind_speed = list_of_data['wind']['speed']
# wind_gust = wea['current']['wind_gust']
wind_deg = list_of_data['wind']['deg']
# wind conversiont m/s to knots
def wind_converter(w):
knots = 2
kt = (float(w)) * knots
return kt
wind_response = wind_converter(wind_speed)
#convert wind degree in cardinal direction.
def degrees_to_cardinal(d):
dirs = ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW']
ix = round(d / (360. / len(dirs)))
return dirs[ix % len(dirs)]
direction = degrees_to_cardinal(wind_deg)
wind_data = {
"wind_response":wind_response,
"wind_direction":direction,
}
else:
wind_data={}
context = {"wind_data":wind_data}
return render(request, 'API/wind_search.html',context)
This is the html template:
{% extends "API/base.html" %}
{% block content %}
<!--Jumbotron -->
<div class="jumbotron jumbotron-fluid">
<div class="container">
<h1 class="display-4">Wind search</h1>
<p class="lead">Write the city and check th wind condition. </p>
<!-- form input search tool -->
<nav class="navbar navbar-expand-lg navbar-dark">
<form method="post" class="col-md"">
{% csrf_token %}
<div class=" input-group">
<input type="text" class="form-control" name="city" placeholder="Choose Your City ...">
<div class="input-group-btn">
<button type="submit" class="btn btn-primary">Search</button>
</div>
</div>
</form>
</nav>
<div class="row">
{% if wind_response and wind_direction %}
<h4><span>Wind Speed :</span> {{wind_data.wind_speed}}</h4>
<h4><span>Wind_Direction :</span> {{wind_data.wind_direction}}</h4>
</div>
{% endif %}
</div>
{% endblock content %}
I think the problem is with the html because basically the view don't show any error message, so i tried to change couple of time the html code but without success. Any help/ explenation is nice.
Thank you very much.
Please set the render and redirect template,
def wind_search(request):
if request.method == 'POST':
#this block of code manages if there is a POST request...
city = request.POST.get('city')
weather_city_url = urllib.request.urlopen('api.openweathermap.org/data/2.5/weather?q=' + city + '&appid=1a7c2a40a0734d1dc18141fc6b6241bb').read()
list_of_data = json.loads(waether_city_url)
# main wind information
wind_speed = list_of_data['wind']['speed']
# wind_gust = wea['current']['wind_gust']
wind_deg = list_of_data['wind']['deg']
# wind conversiont m/s to knots
def wind_converter(w):
knots = 2
kt = (float(w)) * knots
return kt
wind_response = wind_converter(wind_speed)
#convert wind degree in cardinal direction.
def degrees_to_cardinal(d):
dirs = ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW']
ix = round(d / (360. / len(dirs)))
return dirs[ix % len(dirs)]
direction = degrees_to_cardinal(wind_deg)
wind_data = {
"wind_response":wind_response,
"wind_direction":direction,
}
# the page that you want to load after submitting your POST request <-----------------------------------------------------
return redirect( 'redirect to a view ' )
#the below block of code will cater for the GET method request
else:
wind_data={
'foo' : 'foo'
}
#the page you want to render on a Get Request <-----------------------------------------------------
return render(request,'render the required html for GET request' , wind_data)

Populating multiple plotly offline plots<div> of HTML page using flask

I am trying to embed multiple plotly offline plots into an HTML page at different locations on the page. I am using flask at the back-end. Currently I am unable to change the properties of the returned by plotly offline plot to change the location, width, height on the HTML page. I am new to the front-end development concept so pardon my ignorance.
I am using the below test code to create div plot.
from plotly.offline import plot
import plotly.graph_objs as go
def plot_test():
months = ['Jan', 'Feb', 'Mar']
user = [10,50,30]
t20h = [17, 46, 39]
trace1 = go.Scatter(x= months, y=user, mode='lines+markers', name="OHR")
trace2 = go.Scatter(x= months, y=t20h, mode='lines+markers', name="T20H")
data = [trace1, trace2]
layout = go.Layout(title='Test', xaxis=dict(title='Months'), yaxis=dict(title='Test'))
fig = go.Figure(data=data, layout=layout)
div_output = plot(fig, output_type='div', include_plotlyjs=False)
return div
And below code to render the results in my HTML
from plot_test import plot_test
#app.route('/')
def home():
plotly_graph = plot_test()
return render_template("index.html", plotly_graph=Markup(plotly_graph))
And below is my HTML. Currently the plot gets crated but at the top of everything else. All my other buttons.
<!DOCTYPE html>
<html>
<head>
<title>Intro</title>
<link href="static/index_style.css" rel="stylesheet">
</head>
<body>
<div id="home">
<form action="/" method="GET">
<input type="submit" class="btn btn-default>" value="HOME" id="home-btn">
</form>
</div>
<div id="logout">
<form action="/logout" method="GET">
<input type="submit" class="btn btn-default>" value="LOGOUT" id="logout-btn">
</form>
</div>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
{{ plotly_graph }}
<!-- errors -->
<!-- {% if error %}
<p class="error"><strong>Error:</strong> {{ error }}</p>
{% endif %}
-->
<!-- messages -->
<!-- {% for message in get_flashed_messages() %}
{{ message }}
{% endfor %} -->
</body>
</html>
I used a slightly different approach :
(simplified)
In my python code, I returned fig instead of the plot itself
In the HTML, I created a div with id( 'div_name'
In Javascript, through the API call received the data to recreate the plot
and then I only had to add Javascript: Plotly.plot("div_name",fig[("data],fig["layout"].
This way you have more flexibility trying out different sizing methods for either your div or the width-height arguments of the layout itself.

Embedding multiple bokeh HTML plots into flask

I've searched for the past 3 hours on the bokeh website and stack overflow but none of it is really what i was looking for.
I've generated my plots already, and have them in html files. All i want to do is embed the plots into my dashboard in a multi grid like formation in the white area in the pic below. However, adding just 2 plots cause them to overlay and be really weird.
I used the {{ include }} method to include the graphs this way:
Anyone can give me pointers on how to align them well? Ideally i want 6 small plots in that space. I didnt want to regenerate the plots everytime i loaded the dashboard so i didnt want the embed way.
Please help :( Thank you so much!
EDIT: following big's suggestion, using responsive = True works, but i am unable to control the css styling and the sizes of the charts. I suspect its to do with using the include tag. can anyone help? :)
Why you dont try to make it with the horizontal layout
horizontal-layout
Whith your way ( {% include %} ), i don't find a solution so probably sou must use the standart flask way. Python file:
#Your imports
from flask import Flask, render_template
from bokeh.embed import components
from bokeh.plotting import figure
#app.route('/')
def homepage():
title = 'home'
from bokeh.plotting import figure
#First Plot
p = figure(plot_width=400, plot_height=400, responsive = True)
p.circle([1, 2, 3, 4, 5], [6, 7, 2, 4, 5], size=20, color="navy", alpha=0.5)
#Second Plot
p2 = figure(plot_width=400, plot_height=400,responsive = True)
p2.square([1, 2, 3, 4, 5], [6, 7, 2, 4, 5], size=20, color="olive", alpha=0.5)
script, div = components(p)
script2, div2 = components(p)
return render_template('index.html', title = title, script = script,
div = div, script2 = script2, div2 = div2)
Your HTML file:
<!DOCTYPE html>
<html lang="en">
<head>
<link
href="http://cdn.bokeh.org/bokeh/release/bokeh-0.11.1.min.css"
rel="stylesheet" type="text/css">
<script src="http://cdn.bokeh.org/bokeh/release/bokeh-0.11.1.min.js"></script>
<meta charset="UTF-8">
<title>{{title}}</title>
</head>
<body>
<div style="width: 20%; display: inline-block;">
{{ div | safe }}
{{ script | safe }}
</div>
<div style="width: 20%; display: inline-block;">
{{ div2 | safe }}
{{ script2 | safe }}
</div>
</body>
</html>
And one other tip is to make a python file like my_plots.py
and add your plots there, and then import to you main.py it will make your code cleaner. (i dont know 100% if this will impact your speed, but i don't seen any isues until now ) For example.
my_plots.py:
from bokeh.plotting import figure
def first_plot():
p = figure(plot_width=400, plot_height=400, responsive = True)
p.circle([1, 2, 3, 4, 5], [6, 7, 2, 4, 5], size=20, color="navy", alpha=0.5)
return p
def second_plot():
p2 = figure(plot_width=400, plot_height=400, responsive = True)
p2.square([1, 2, 3, 4, 5], [6, 7, 2, 4, 5], size=20, color="olive", alpha=0.5)
return p2
main.py:
#app.route('/')
def homepage():
title = 'home'
#First Plot
from my_plots import first_plot
p = first_plot()
#Second Plot
from my_plots import second_plot
p2 = second_plot()
script, div = components(p)
script2, div2 = components(p)
return render_template('index.html', title = title, script = script,
div = div, script2 = script2, div2 = div2)
Hope i was helpful, Good Luck!
Updating Leo's answer as it is for the deprecated version of Bokeh.
Bokeh v3.0.1
Flask v2.2.2
Flask App
from flask import Flask, render_template
from bokeh.embed import components
from bokeh.plotting import figure
app = Flask(__name__)
#app.route('/')
def homepage():
title = 'home'
from bokeh.plotting import figure
### First Plot ###
p1 = figure(height = 400, sizing_mode = "stretch_width")
p1.circle([1, 2, 3, 4, 5], [6, 7, 2, 4, 5], size=20, color="navy", alpha=0.5)
### Second Plot ###
p2 = figure(height = 400, sizing_mode = "stretch_width")
p2.square([1, 2, 3, 4, 5], [6, 7, 2, 4, 5], size=20, color="olive", alpha=0.5)
script1, div1 = components(p1)
script2, div2 = components(p2)
return render_template(
'index.html',
title = title,
script = script1,
div = div1,
script2 = script2,
div2 = div2
)
if __name__ == '__main__':
app.run(debug=True)
HTML Template
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://cdnjs.cloudflare.com/ajax/libs/bokeh/3.0.1/bokeh.min.js"
integrity="sha512-p7EUyPmeDeOwHiu7fIZNboAcQLxei3sWtXoHoShWWiPNUSRng/Xs5JPcaFPRa4dKy9IuHjyIQuLE4caGCwuewA=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<title>Bokeh Charts</title>
</head>
<body>
<div style="width: 40%; display: inline-block;">
{{ div1 | safe }}
{{ script1 | safe }}
</div>
<div style="width: 40%; display: inline-block;">
{{ div2 | safe }}
{{ script2 | safe }}
</div>
</body>
</html>

Creating a new table row with ng-repeat and <td> if mod N columns

I'm trying to use ng-repeat to create two columns. I have a list of items i.e. ['green', 'red', 'blue', 'yellow'] and want to create two columns with two items in each. However, I'm not sure how to start a new row if the current index is mod 2.
green red
blue yellow
Is there a way of doing this?
I would just prepare the data before passing it to the DOM, you can find lots of "chunk" implementations online
['green', 'red', 'blue', 'yellow']
split into suitable chunks:
[ [ "green", "red" ], [ "blue", "yellow" ] ]
code:
<div data-ng-controller="MyCtrl">
<table>
<tr data-ng-repeat="row in tableData">
<td data-ng-repeat="color in row">
{{ color }}
</td>
</tr>
</table>
</div>
<script>
var myApp = angular.module('myApp',[]);
function MyCtrl($scope) {
$scope.colors = ['green', 'red', 'blue', 'yellow'];
$scope.tableData = chunk($scope.colors, 2);
}
function chunk (arr, len) {
var chunks = [],
i = 0,
n = arr.length;
while (i < n) {
chunks.push(arr.slice(i, i += len));
}
return chunks;
}
</script>
Output
green red
blue yellow
http://jsfiddle.net/oyz0f07n/
You can control it via css
If you are using bootstrap
<div class="row">
<div data-ng-repeat="color in colors" class="col-md-6"> {{ color }} </div>
</div>
If you are not using bootstrap, you can set a class in your css with 50% width and add that class in the div. Something like this
CSS
.width-50 {
width: 50%;
}
HTML
<div data-ng-repeat="color in colors" class="width-50"> {{ color }} </div>