I'd like to have a custom formatter for one my dataTable cells.
Say, for example I'd like to have the title column font style in bold.
from bokeh.plotting import show
from bokeh.models.sources import ColumnDataSource
from bokeh.models.widgets import DataTable, TableColumn
if __name__ == '__main__':
source = ColumnDataSource(data = dict(hello = ["one", "two", "three"]))
c0 = TableColumn(field="hello",
title= "TITLE IN BOLD?")
ds = DataTable(source=source,
columns = [c0])
show(ds)
Is there a simple way to do this in BokehJS?
Thanks
Using the HTMLTemplateFormatter can get the job done. This allows all cells in the column to be formatted as code so you can add in bold tags or what ever else you want in any of the cells.
Note: if you are using bokeh version 0.12.5 Unfortunately they have removed underscore js so to get around it (first block of ugly code), you can use the work around from this github issue: https://github.com/bokeh/bokeh/issues/6207. Or alternatively include underscorejs/lodash in the output html file, i.e. <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/0.10.0/lodash.min.js"></script>
from bokeh.models.widgets import HTMLTemplateFormatter
from bokeh.plotting import show
from bokeh.models.sources import ColumnDataSource
from bokeh.models.widgets import DataTable, TableColumn
class MyHTMLFormatter(HTMLTemplateFormatter):
__javascript__ = ["https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"]
__implementation__ = """
import {HTMLTemplateFormatter} from "models/widgets/cell_formatters"
export class MyHTMLFormatter extends HTMLTemplateFormatter
type: 'MyHTMLFormatter'
"""
if __name__ == '__main__':
source = ColumnDataSource(data = dict(hello = ["<b>one</b>", "two", "three"]))
formatter = HTMLTemplateFormatter(template='<code><%= value %></code>')
c0 = TableColumn(field="hello",
title= "<b>TITLE IN BOLD?</b>", formatter=formatter)
ds = DataTable(source=source,
columns = [c0])
show(ds)
Related
I have a QTextTable, I do some actions in the table and I want two things:
Return QTextCursor to cell (0,0)
Or move QTextCursor to cell (row, columnn)
How can I do?
For the given table object of type QTextTable get desired cursor:
auto tableCell = table->cellAt(row, column);
auto cursor = tableCell.firstCursorPosition();
then setTextCursor within edited QTextEdit object.
Following #ekhumoro and #retmas suggestion I do this:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtGui import QTextCursor
#### EKHUMORO suggestion
def move_cursor(table_text,cursor,row_destiny,col_destiny):
row=table_text.cellAt(cursor).row()
while not row==row_destiny:
if row<row_destiny:
cursor.movePosition(QTextCursor.NextRow)
else:
cursor.movePosition(QTextCursor.PreviousRow)
row=table_text.cellAt(cursor).row()
col=table_text.cellAt(cursor).column()
while not col==col_destiny:
if col<col_destiny:
cursor.movePosition(QTextCursor.NextCell)
else:
cursor.movePosition(QTextCursor.PreviousCell)
col=table_text.cellAt(cursor).column()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
widget = QtWidgets.QTextEdit()
cursor=widget.textCursor()
table_text=cursor.insertTable(10,10)
# Format Table
fmt = table_text.format()
fmt.setWidth(QtGui.QTextLength(QtGui.QTextLength.PercentageLength, 100))
table_text.setFormat(fmt)
row=2
col=5
move_cursor(table_text,cursor,row,col)
cursor.insertText('Cell {} {}'.format(row,col))
#### RETMAS SUGGESTION
row=3
column=6
tableCell = table_text.cellAt(row, column)
cursor = tableCell.firstCursorPosition()
widget.setTextCursor(cursor)
cursor.insertText('Cell {} {}'.format(row,column))
widget.show()
sys.exit(app.exec_())
This works fine.
I would like to work in Jupyter Notebook though the project with one plot. Update it with a new data or new layout, maybe add/delete additional plot nearby it and so on… In Bokeh I could use smth similar to:
target = show(layout, notebook_handle=True)
push_notebook(handle=target)
In Holoviews I found how to feed new data to the existing plot:
pipe = Pipe(data=[])
Image = hv.DynamicMap(hv.Image, streams=[pipe1])
pipe.send(np.random.rand(3,2)) #data change
But is there any solution to manage live layout update in Holoviews? Is it possible to update existed plot by .opts() construction? In this example I will get a new plot:
pipe = Pipe(data=[])
Image = hv.DynamicMap(hv.Image, streams=[pipe])
Image.opts(width=1000,height=1000)
#######new cell in jupyter notebook############
Image.opts(width=100,height=100)
Here is a brilliant answer I have got on my question:
import param
import panel as pn
import numpy as np
import holoviews as hv
from holoviews.streams import Pipe
pn.extension()
pipe = Pipe(data=[])
class Layout(param.Parameterized):
colormap = param.ObjectSelector(default='viridis', objects=['viridis', 'fire'])
width = param.Integer(default=500)
update_data = param.Action(lambda x: x.param.trigger('update_data'), label='Update data!')
#param.depends("update_data", watch=True)
def _update_data(self):
pipe.send(np.random.rand(3,2))
layout = Layout()
Image = hv.DynamicMap(hv.Image, streams=[pipe]).apply.opts(cmap=layout.param.colormap, width=layout.width)
pdmap = pn.panel(Image)
playout = pn.panel(layout)
def update_width(*events):
for event in events:
if event.what == "value":
pdmap.width = event.new
layout.param.watch(update_width, parameter_names=['width'], onlychanged=False)
pn.Row(playout, pdmap)
https://discourse.holoviz.org/t/how-to-update-holoviews-plots-width-height/1947
My app has become fairly complex, about 1500 lines across several files. There is a button that creates tabs and adds them to an existing panel. Here is a minimal working example of how the button works:
def create_new_tab():
paragraph = Paragraph(text="Hello!")
tab = Panel(child=paragraph, title="tab")
tab.closable = True
return tab
def append_new_tab():
new_tab = create_new_tab()
curdoc().select_one({'name': 'tabs'}).tabs.append(new_tab)
button = Button(label='append new tab')
button.on_click(append_new_tab)
tab1 = Panel(child=button, title='button tab')
tabs = Tabs(tabs = [tab1], name='tabs')
curdoc().add_root(tabs)
In my actual program there is a bug somewhere, because when I click the button I get the message that some set changed during iteration:
error handling message Message 'EVENT' (revision 1) content: '{"event_name":"button_click","event_values":{"model_id":"1002"}}': RuntimeError('Set changed size during iteration',)
I have spent some time trying to debug, without success. I have tried to create a minimal not working example, but all my minimal examples work. I keep staring at the error message and wonder what else can I do to find out what is going wrong?
Remark: I am already using bokeh serve myapp/ --dev
You can debug your app using this code (Bokehh v1.1.0):
from bokeh.models import Panel, Tabs, Button, Paragraph
from tornado.ioloop import IOLoop
from bokeh.server.server import Server
from bokeh.application import Application
from bokeh.application.handlers.function import FunctionHandler
def modify_doc(doc):
def create_new_tab():
paragraph = Paragraph(text="Hello!")
tab = Panel(child=paragraph, title="tab")
tab.closable = True
return tab
def append_new_tab():
new_tab = create_new_tab()
doc.select_one({'name': 'tabs'}).tabs.append(new_tab)
button = Button(label='append new tab')
button.on_click(append_new_tab)
tab1 = Panel(child=button, title='button tab')
tabs = Tabs(tabs = [tab1], name='tabs')
doc.add_root(tabs)
io_loop = IOLoop.current()
server = Server(applications = {'/app': Application(FunctionHandler(modify_doc))}, io_loop = io_loop, port = 5001)
server.start()
server.show('/app')
io_loop.start()
I'm making a scatter plot of some data using Bokeh from a pandas dataframe (df). The dataframe is university data, and has columns "faculty_salary", "tuition", "sector" and "institution_name". The plot is faculty_salary vs tuition, and I'm using the sector to color the data, as shown below.
I currently have the tap tool which greys-out all other data points except for the one you clicked, as shown below.
What I want to do is use the AutocompleteInput widget to select an institution_name from the autocomplete and have it highlight that data point by graying out all the others, just like the tap tool. The AutocompleteInput widget is working fine, it's just the code to make it highlight that data point that I'm struggling to figure out. Here's where I'm at:
source = ColumnDataSource(df)
autocomp = AutocompleteInput(completions=df['name'].tolist())
callback = CustomJS(args=dict(source=source), code="""
what do I put here?? It doesn't seem to have a cb_obj
""")
autocomp.js_on_change('value',callback)
Please let me know how I can achieve the functionality I described: use the selected value in the AutocompleteInput to highlight the datapoint it corresponds to. I should note that for my application I would prefer a CustomJS solution, and not a Bokeh server solution.
Edit: It turns out that my browser (Chrome in Linux) doesn't recognize that an autocompletion value has been selected, but Firefox for Linux does. I think this must be because Jquery UI says:
This widget manipulates its element's value programmatically, therefore a native change event may not be fired when the element's value changes.
Suggestions for how to get around this in Bokeh are welcome.
Here is the Bokeh server solution :
from bokeh.layouts import layout
from bokeh.io import curdoc
from bokeh.models.widgets import AutocompleteInput
from bokeh.models.widgets import (Panel, Tabs, DataTable, TableColumn,
Paragraph, Slider, Div, Button, Select)
from bokeh.plotting import figure, ColumnDataSource
from bokeh.models import HoverTool,TapTool
def update_selected(wttr,old,new):
a_val = autocomp.value
names = source.data['names']
ind = [i for i,x in enumerate(names) if x == a_val]
source.selected={'0d': {'glyph': None, 'indices': []}, '1d': {'indices': ind}, '2d': {}}
data_dict = {'names':['test2','test3','hello','goodbye'],
'x':[0,1,2,3], 'y':[10,20,30,40]}
source = ColumnDataSource(data_dict)
autocomp = AutocompleteInput(completions=['test2','test3','hello','goodbye'])
autocomp.on_change('value',update_selected )
fig = figure(plot_width=400,
plot_height=400,
x_axis_label='x',
y_axis_label='y',
tools=["pan","hover","box_zoom","reset,save"])
fig.scatter('x','y',source=source)
layout = layout([[fig, autocomp]])
curdoc().add_root(layout)
EDIT : here is the solution using customJS
from bokeh.layouts import layout
from bokeh.io import curdoc
from bokeh.models.widgets import AutocompleteInput
from bokeh.plotting import figure, ColumnDataSource
from bokeh.models.callbacks import CustomJS
data_dict = {'names':['test2','test3','hello','test3'],
'x':[0,1,2,3], 'y':[10,20,30,40]}
source = ColumnDataSource(data_dict)
fig = figure(plot_width=400,
plot_height=400,
x_axis_label='x',
y_axis_label='y',
tools=["pan","hover","box_zoom","reset,save"])
fig.scatter('x','y',source=source)
autocomp = AutocompleteInput(completions=['test2','test3','hello','goodbye'])
code = """
var autocomplete = cb_obj.value
var names = source.data.names
function getAllIndexes(arr, val) {
var indexes = [], i;
for(i = 0; i < arr.length; i++)
if (arr[i] === val)
indexes.push(i);
return indexes;
}
var index = getAllIndexes(names,autocomplete)
source.selected = {'0d':{indices: [0]}, '1d':{indices: index},'2d':{indices: [0]}}
"""
callback = CustomJS(args={'source':source}, code=code)
autocomp.callback = callback
layout = layout([[fig, autocomp]])
curdoc().add_root(layout)
I'm trying to pull some info off of Craigslist and store it in a JSON file, but the info is getting stored a bit wrong. Instead of having an array of [title, link, location, time], I'm getting an array with all the titles, one with all the links, etc. Are my titles wrong or is the for loop itself wrong?
from scrapy.spiders import BaseSpider
from scrapy.selector import HtmlXPathSelector
from scrapy.selector import Selector
from craigslist_sample.items import CraigslistSampleItem
class MySpider(BaseSpider):
name = "craig"
allowed_domains = ["craigslist.org"]
start_urls = ["https://pittsburgh.craigslist.org/search/ccc"]
def parse(self, response):
titles = response.selector.xpath("//p[#class='row']")
items = []
for titles in titles:
item = CraigslistSampleItem()
item["title"] = titles.xpath("//span[#id='titletextonly']").extract()
item["link"] = titles.xpath("a/#href").extract()
item["location"] = titles.xpath("//small").extract()
item["time"] = titles.xpath('//time').extract()
items.append(item)
return items
That's because your inner xpaths match the elements starting from the root of the tree. Instead, you need to force them to work in the context of each item by prepending a dot:
for title in titles:
item = CraigslistSampleItem()
item["title"] = title.xpath(".//span[#id='titletextonly']").extract()
item["link"] = title.xpath("a/#href").extract()
item["location"] = title.xpath(".//small").extract()
item["time"] = title.xpath('.//time').extract()
yield item