NOTE: This question concerns the "first generation" Bokeh server, which has been deprecated and removed for several years. Nothing in this question or its answers is relevant to any version of Bokeh >= 0.11
For detailed information about using the modern, supported Bokeh Server, see the Running a Bokeh Server chapter of the User's Guide.
I'm trying to understand Bokeh for an interactive app that I'm building. I'm looking at the Bokeh examples, and I see that most of the examples are written all in the global namespace, but the ones in the "app" subdirectory are written in a nice, object-oriented style, where the main class inhereits from a Property class like HBox.
This is going to be a mish-mash of questions because I don't think this way of programming Bokeh was very well-documented. The first thing I encountered was that the plot didn't draw unless I included extra_generated_classes.
What does extra_generated_classes do?
Secondly, it looks like the event loop setup_events is called on startup before create and subsequently every time the plot triggers an event.
Why does setup_events need to register callbacks each time an event is triggered? And why doesn't it wait for create to finish before attempting to register them the first time?
The last thing I'm unsure about is how to force a redraw of a Glyph here. The slider demo works for me, and I'm trying to do basically the same thing, except with a scatterplot instead of a line.
I set a pdb trace at the very end of my update_data, and I can guarantee that self.source matches self.plot.renderers[-1].data_source and that both of them have been tweaked from the start. However, self.plot itself doesn't change.
What is the object-oriented approach's equivalent to calling store_objects to update the plot?
I'm especially confused by this third one, because it doesn't look like the sliders_app example needs anything like that. For clarification, I'm trying to make a variable number of widgets/sliders, so this is what my code looks like:
class attributes:
extra_generated_classes = [['ScatterBias', 'ScatterBias', 'HBox']]
maxval = 100.0
inputs = Instance(bkw.VBoxForm)
outputs = Instance(bkw.VBoxForm)
plots = Dict(String, Instance(Plot))
source = Instance(ColumnDataSource)
cols = Dict(String, String)
widgets = Dict(String, Instance(bkw.Slider))
# unmodified source
df0 = Instance(ColumnDataSource)
initialize method
#classmethod
def create(cls):
obj = cls()
##############################
## load DataFrame
##############################
df = pd.read_csv('data/crime2013_tagged_clean.csv', index_col='full_name')
obj.cols = {'x': 'Robbery',
'y': 'Violent crime total',
'pop': 'Population'
}
cols = obj.cols
# only keep interested values
df2= df.ix[:, cols.values()]
# drop empty rows
df2.dropna(axis=0, inplace=True)
df0 = df2.copy()
df0.reset_index(inplace=True)
# keep copy of original data
obj.source = ColumnDataSource(df2)
obj.df0 = ColumnDataSource(df0)
##############################
## draw scatterplot
##############################
obj.plots = {
'robbery': scatter(x=cols['x'],
y=cols['y'],
source=obj.source,
x_axis_label=cols['x'],
y_axis_label=cols['y']),
'pop': scatter(x=cols['pop'],
y=cols['y'],
source=obj.source,
x_axis_label=cols['pop'],
y_axis_label=cols['y'],
title='%s by %s, Adjusted by by %s'%(cols['y'],
cols['pop'], cols['pop'])),
}
obj.update_data()
##############################
## draw inputs
##############################
# bokeh.plotting.scatter
## TODO: refactor so that any number of control variables are created
# automatically. This involves subsuming c['pop'] into c['ctrls'], which
# would be a dictionary mapping column names to their widget titles
pop_slider = obj.make_widget(bkw.Slider, dict(
start=-obj.maxval,
end=obj.maxval,
value=0,
step=1,
title='Population'),
cols['pop'])
##############################
## make layout
##############################
obj.inputs = bkw.VBoxForm(
children=[pop_slider]
)
obj.outputs = bkw.VBoxForm(
children=[obj.plots['robbery']]
)
obj.children.append(obj.inputs)
obj.children.append(obj.outputs)
return obj
update_data
def update_data(self):
"""Update y by the amount designated by each slider"""
logging.debug('update_data')
c = self.cols
## TODO:: make this check for bad input; especially with text boxes
betas = {
varname: getattr(widget, 'value')/self.maxval
for varname, widget in self.widgets.iteritems()
}
df0 = pd.DataFrame(self.df0.data)
adj_y = []
for ix, row in df0.iterrows():
## perform calculations and generate new y's
adj_y.append(self.debias(row))
self.source.data[c['y']] = adj_y
assert len(adj_y) == len(self.source.data[c['x']])
logging.debug('self.source["y"] now contains debiased data')
import pdb; pdb.set_trace()
Note that I am sure that the event handler gets setup and triggered correctly. I just don't know how to make the changed source data reflect in the scatterplot.
I'm searching for the same answers (lack of documentation makes it difficult).
In answer, to question #1, what is the utility of "extra_generated_classes":
tl;dr extra_generated_classes defines a modulename, classname, and parentname used in template generating js/html code, and extends the parent class passed into the app class (usually HBox or VBox in the examples).
Longer answer. Look at the source code in bokeh/server/utils/plugins.py, this is the code that is run on code passed to bokeh-server using the --script command line argument. At the end of plugins.py, you can see that extra_generated_classes is passed to the flask method render_template, which renders a Jinja2 template. Looking inside the template, oneobj.html, extra_generated_classes is an array of arrays of three things: modulename, classname, and parentname, which are passed into bokeh.server.generatejs:
{% block extra_scripts %}
{% for modulename, classname, parentname in extra_generated_classes %}
<script
src="{{ url_for('bokeh.server.generatejs', modulename=modulename, classname=classname, parentname=parentname) }}"
></script>
{% endfor %}
{% endblock %}
bokeh.server.generatejs is a Python code in bokeh/server/views/plugins.py, and only calls render_template for a template app.js, which you can find in bokeh/server/templates. This template takes the modulename, classname, and parentname, and basically creates js code which extends the parentname (e.g. HBox or VBox) to the classname (your app).
Related
Assume I have a code with a button coded in ipyvuetify
v_btn_load = vue.Btn(class_='mx-2 light-red darken-1',
children=[vue.Icon(left=True, children=['get_app']),'Load data'])
def on_click_load(widget, event, data):
#pseudo code: load file
print("button run")
v_btn_load.on_event('click', on_click_load)
How do I run (click) programmatically the v_btn_load button?
v_btn_load.click() does not work
Thanks
the "on_click_load" is still a local python function, so you can simple access it in your script. Just fill out the variables you do not need with dummies (probably widget and event) and fill the data variable according to your needs.
If you need some input from the client, than it is more difficult. I know no way to remote control the client side. The only thing I got working so far is to extend VuetifyTemplate with a private class and specify some JS code to be run when 'Mounted'. This will run the code on display, but is not the same as triggering a click act:
Here is a simple example which directly copies the content of a variable to the local clipboard without any display element:
import ipyvuetify as v
from traitlets import Unicode, observe
class toClipboard(v.VuetifyTemplate):
"""Copies a given string directly to the users clipboard.
Parameters:
clipboardValue - The value to offer for copying to the clipboard
Example:
tunnel = toClipboard(clipboardValue='VALUE_TO_COPY')
Upon change of the variable 'clipboardValue', it's content will be automatically pushed to the clipboard
"""
clipboardValue = Unicode('').tag(sync=True)
template = Unicode('''
<script>
export default {
mounted () {
var tmpElement = $('<textarea>').val(this.clipboardValue).appendTo('body').select();
document.execCommand('copy');
$(tmpElement).remove();
},
}
</script>''').tag(sync=True)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.observe(self.pushToClipboard, names=('clipboardValue'))
display(self)
def pushToClipboard(self, change):
display(self)
As an additional bonus this example uses the observe function of traitlets to redisplay the JS as soon as the value of the variable changes. This is a cheap workaround to create a bit similar behaviour.
I use the example above not in real GUIs, but as a lazy way in a Jupyther Notebook to automatically copy the result of a calculation to my local clipboard.
looking at the description of the v.Btn class I found this :
| ----------------------------------------------------------------------
| Methods inherited from ipyvue.VueWidget.Events:
|
| fire_event(self, event, data)
|
| on_event(self, event_and_modifiers, callback, remove=False)
I then assume that
v_btn_load.fire_event('click', None)
should do trick
I'm having an issue with a page running on Django 2.1. My issue is that when a function is called from views.py to populate a template, an instance of a class seems to be cached. Every subsequent call to that function/every refresh of that page shows old data from the previous time that page/function was called.
The code in question:
projectroot/myapp/templates/myapp/bar.html
#this is bar.html
{{ MyTable.DataColumns }}
<br>
{{ MyTable.DataRows }}
projectroot/myapp/views.py
#this is a relevant snippet from views.py
#There isn't anything outside of the functions,
#with imports being the exception to this rule
import myownpackage.DataTable import DataTable
def list_page(request):
template = 'foo/bar.html'
connection = a_connection_here
query_results = connection.get_query_results(sql_query_here)
list_of_rows = [x for x in query_results]
dt = DataTable()
dt.DataColumns = [list_of_columns_here]
for each_row in list_of_rows:
dt.add(each_row)
return_dict = {"MyTable": dt}
return render(request, template, return_dict)
projectroot/myownpackage/DataTable.py
class DataTable:
DataColumns = []
DataRows = []
def add(self, data_row):
if len(self.DataColumns) != len(data_row):
raise IndexError("Target column count does not match DataTable's DataColumn count.")
else:
self.DataRows.append(data_row)
def remove(self, index):
self.DataRows.remove(index)
The first time this page is loaded, foo/bar.html appears to be just what I want. I can see the columns printed to the page, and the few rows in that table.
The problem is visible if you refresh the page. It's as if dt is being cached after the function has returned the render().
When you refresh the page, you'll see the columns, and the rows from the first request duplicated! If we refresh a third time, we'll see a third set of those same rows! This appending of the DataTable goes on for as many times as you refresh. Clearly, dt is being stored outside of this function after being instantiated within the function, and I'm not sure why, or how, this is happening.
NOTE: Only the rows are being added over and over again. The columns remain the same. This is because the columns are being set on the table, while the rows are being added.
Things I've tried:
After dt is assigned to that return_dict, I've tried dt = None and del dt. Neither of these worked, and the same results were shown when refreshing the page.
My questions to Stack Overflow:
Why isn't the instance of DataTable called dt being thrown away after the function returns?
If this (for whatever the reason may be) is normal, what can I do to destroy that instance of DataTable when I'm done with it?
It turns out, the issue was that the DataRows were static in the DataTable class. That class should look like this:
class DataTable:
def __init__(self):
self.DataColumns = []
self.DataRows = []
This working code brings up a QFileDialog prompting the user to select a .csv file:
def load(self,fileName=None):
if not fileName:
fileName=fileDialog.getOpenFileName(caption="Load Existing Radio Log",filter="csv (*.csv)")[0]
...
...
Now, I'd like to change that filter to be more selective. The program saves each project as a set of three .csv files (project.csv, project_fleetsync.csv, project_clueLog.csv) but I only want the file dialog to display the first one (project.csv) in order to avoid presenting the user with too many choices when only a third of them can be handled by the rest of the load() function.
According to this post, it looks like the solution is to use a proxy model. So, I changed the code to the following (all of the commented lines in load() are things I've tried in various combinations):
def load(self,fileName=None):
if not fileName:
fileDialog=QFileDialog()
fileDialog.setProxyModel(CSVFileSortFilterProxyModel(self))
# fileDialog.setNameFilter("CSV (*.csv)")
# fileDialog.setOption(QFileDialog.DontUseNativeDialog)
# fileName=fileDialog.getOpenFileName(caption="Load Existing Radio Log",filter="csv (*.csv)")[0]
# fileName=fileDialog.getOpenFileName(caption="Load Existing Radio Log")[0]
# fileDialog.exec_()
...
...
# code for CSVFileSortFilterProxyModel partially taken from
# https://github.com/ZhuangLab/storm-control/blob/master/steve/qtRegexFileDialog.py
class CSVFileSortFilterProxyModel(QSortFilterProxyModel):
def __init__(self,parent=None):
print("initializing CSVFileSortFilterProxyModel")
super(CSVFileSortFilterProxyModel,self).__init__(parent)
# filterAcceptsRow - return True if row should be included in the model, False otherwise
#
# do not list files named *_fleetsync.csv or *_clueLog.csv
# do a case-insensitive comparison just in case
def filterAcceptsRow(self,source_row,source_parent):
print("CSV filterAcceptsRow called")
source_model=self.sourceModel()
index0=source_model.index(source_row,0,source_parent)
# Always show directories
if source_model.isDir(index0):
return True
# filter files
filename=source_model.fileName(index0)
# filename=self.sourceModel().index(row,0,parent).data().lower()
print("testing lowercased filename:"+filename)
if filename.count("_fleetsync.csv")+filename.count("_clueLog.csv")==0:
return True
else:
return False
When I call the load() function, I do get the "initializing CSVFileSortFilterProxyModel" output, but apparently filterAcceptsRow is not getting called: there is no "CSV filterAcceptsRow called" output, and, the _fleetsync.csv and _clueLog.csv files are still listed in the dialog. Clearly I'm doing something wrong...?
Found the solution at another stackoverflow question here.
From that solution:
The main thing to watch out for is to call
dialog.setOption(QFileDialog::DontUseNativeDialog) before
dialog.setProxyModel.
Also it looks like you then have to use fileDialog.exec_() rather than fileDialog.getOpenFileName. The value you set to setNameFilter does show up in the filter cyclic field of the non-native dialog, but is effectively just for decoration since the proxymodel filter overrides it. In my opinion that is a good thing since you can put wording in the filter cyclic that would indicate something useful to the user as to what type of filtering is going on.
Thanks to users Frank and ariwez.
UPDATE: to clarify, here's the full final code I'm using:
def load(self,fileName=None):
if not fileName:
fileDialog=QFileDialog()
fileDialog.setOption(QFileDialog.DontUseNativeDialog)
fileDialog.setProxyModel(CSVFileSortFilterProxyModel(self))
fileDialog.setNameFilter("CSV Radio Log Data Files (*.csv)")
fileDialog.setDirectory(self.firstWorkingDir)
if fileDialog.exec_():
fileName=fileDialog.selectedFiles()[0]
else: # user pressed cancel on the file browser dialog
return
... (the rest of the load function processes the selected file)
...
# code for CSVFileSortFilterProxyModel partially taken from
# https://github.com/ZhuangLab/storm-control/blob/master/steve/qtRegexFileDialog.py
class CSVFileSortFilterProxyModel(QSortFilterProxyModel):
def __init__(self,parent=None):
# print("initializing CSVFileSortFilterProxyModel")
super(CSVFileSortFilterProxyModel,self).__init__(parent)
# filterAcceptsRow - return True if row should be included in the model, False otherwise
#
# do not list files named *_fleetsync.csv or *_clueLog.csv
# do a case-insensitive comparison just in case
def filterAcceptsRow(self,source_row,source_parent):
# print("CSV filterAcceptsRow called")
source_model=self.sourceModel()
index0=source_model.index(source_row,0,source_parent)
# Always show directories
if source_model.isDir(index0):
return True
# filter files
filename=source_model.fileName(index0).lower()
# print("testing lowercased filename:"+filename)
# never show non- .csv files
if filename.count(".csv")<1:
return False
if filename.count("_fleetsync.csv")+filename.count("_cluelog.csv")==0:
return True
else:
return False
I have a main page that has a GET and a POST function. The POST function gets data from a search screen and should pass this information, via an ajax call, to the worldMarkers class. This is separate because it will be needed for other aspects of the application.
The goal of this, is to have a user press submit on index and during the POST call, it is able to limit the results retrieved. This logic exists in the worldMarkers class.
class index(object):
def GET(self):
# do things to generate the page
return html
def POST(self):
continents = web.input(search_continents=[])
countries = web.input(search_countries=[])
searchDict = {}
if continents['search_continents']:
searchDict['continents'] = continents['search_continents']
if countries['search_countries']:
searchDict['countries'] = countries['search_countries']
markers = worldMarkers()
# Yes, this just spits out the results, nothing fancy right now
return markers.GET()
#alternatively,
#return markers.GET(searchDict)
class worldMarkers(object):
def __init__(self, **kargs):
self.searchDict = None
if 'searchDict' in kargs:
self.searchDict = kargs['searchDict']
def GET(self):
print "SearchDict: %s" % (self.searchDict)
# No searchDict data exists
The first option, with no parameters to markers.GET() means that none of my search criteria has been passed. If I do markers.GET(searchDict), I receive this error:
<type 'exceptions.TypeError'> at /
GET() takes exactly 1 argument (2 given)
How can I pass my search parameters to the worldMarkers class?
It looks like you should actually create an instance of worldMarkers as follows in order for your searchDict to exist:
markers = worldMarkers(searchDict=searchDict)
Right now, you're creating it without the argument:
markers = worldMarkers()
and in that case, the condition if 'searchDict' in kargs is false and self.searchDict = kargs['searchDict'] is not run.
And, as #TyrantWave points out, your GET is not really prepared to take any arguments since it is only declared as def GET(self). See the last sample code of this section of the docs.
I just started with envisage framework. In the 4.x version I saw a few example, but I need a good documentation: link.
How can I add custom buttons to the envisage workbench, or how can I create a similar one?
The best place look for documentation is the Acmelab example in the Envisage source tree.
I'm assuming when you talk about custom buttons you mean buttons on a toolbar. First you need to create a WorkbenchActionSet, add your toolbar there, and then define your actions and assign them a button image. Here is the (slightly modified) Acmelab example with non-relevant parts taken out:
test_action_set.py
# Enthought library imports.
from envisage.ui.action.api import Action, Group, Menu, ToolBar
from envisage.ui.workbench.api import WorkbenchActionSet
class TestActionSet(WorkbenchActionSet):
""" An action test useful for testing. """
#### 'ActionSet' interface ################################################
tool_bars = [
ToolBar(name='Fred', groups=['AToolBarGroup']),
ToolBar(name='Wilma'),
ToolBar(name='Barney')
]
actions = [
Action(
path='ToolBar',
class_name='acme.workbench.action.new_view_action:NewViewAction'
),]
new_view_action.py
""" An action that dynamically creates and adds a view. """
# Enthought library imports.
from pyface.api import ImageResource
from pyface.action.api import Action
from pyface.workbench.api import View
class NewViewAction(Action):
""" An action that dynamically creates and adds a view. """
#### 'Action' interface ###################################################
# A longer description of the action.
description = 'Create and add a new view'
# The action's name (displayed on menus/tool bar tools etc).
name = 'New View'
# A short description of the action used for tooltip text etc.
tooltip = 'Create and add a new view'
image = ImageResource(Your Image File Name Goes Here)
###########################################################################
# 'Action' interface.
###########################################################################
def perform(self, event):
""" Perform the action. """
# You can give the view a position... (it default to 'left')...
view = View(id='my.view.fred', name='Fred', position='right')
self.window.add_view(view)
# or you can specify it on the call to 'add_view'...
view = View(id='my.view.wilma', name='Wilma')
self.window.add_view(view, position='top')
return
#### EOF ######################################################################