Create a TextBox in Plotly Python - python

Can I somehow make a report in Plotly where the left side will be a text, which changes on a slider pick, and the right side is my chart? The way without using dash and too much javascript is preferred.
It should look like this:

For people still looking for an answer:
Some plotly.graph_objects basics first:
Plotly figure is made up of 3 parts: Data (trace to be plotted), Layout (on which data is to be plotted), Frame (Data + Layout to be used for looping)
To answer your question to include text on the left side you will have to write the text as a annotation (layout). To include two charts on right use subplots (data). To update them with slider pick you will have to update them with Frame. Each frame will have Data+Layout which corresponds to that slider position.
Generic code flow:
Let's assume:
Dataframe df has 4 columns X,Y1,Y2,Dates
Your slider represents different dates
#Import plotly graph object
import plotly.graph_objects as go
#Getting different dates
dates = df['Dates'].unique().tolist()
#Plotting first instance
data=[]
#Fig 1
data_dict1 = {{
"x": list(df["X"]),
"y": list(df["Y1"]),
"type":"scatter"},
"ncol":2,"nrow":1 #Subplot location
}
#Fig 2
data_dict2 = {
"x": list(df["X"]),
"y": list(df["Y2"]),
"type":"scatter",
"ncol":2,"nrow":2 #Subplot location
}
data = [data_dict1,data_dict2] #combining both plots
#Layout and slider
layout = {
"sliders" : [{
#Enter required slider layout
,"steps": [] #should include this line in slider
}],
"annotations":[ {
#Input required annotations
}]
} # layout ends
#making Frames
frames =[]
for date in dates:
frame = {"data": [], "name": str(date),"layout":{"annotations":[]}} # Saving each data frame
dataset = df[df[Dates]==date] #subsetting data for a date
# Create charts for this subset data
#Fig 1
data_dict1 = {{
"x": list(dataset["X"]),
"y": list(dataset["Y1"]),
"type":"scatter"},
"ncol":2,"nrow":1 #Subplot location
}
#Fig 2
data_dict2 = {
"x": list(dataset["X"]),
"y": list(dataset["Y2"]),
"type":"scatter",
"ncol":2,"nrow":2 #Subplot location
}
#Adding this data to frame
frame["data"] = [data_dict1,data_dict2]
# Creating lines to be displayed using annotations
annote1 = {
#Add data for line 1
}
annote2 = {
#Add data for line 2
}
annote3 = {
#Add data for line 3
}
annote4 = {
#Add data for line 4
}
#Adding all annotations lines to frame
frame["layout"]["annotations"] = [annote1,annote2,annote3,annote4]
#Combinig all frames
frames.append(frame)
#Updating slider step
slider_step = {
"label": date,
"method": "update"}
#Adding step to layout slider
layout["sliders"][0]["steps"].append(slider_step)
#Combine everything
fig = go.Figure(data =data,layout = layout, frames =frames)
#View in browser
plot(fig, auto_open=True)
Hope the logic flow is clear!

Related

Python-Bokeh application:Unable to export updated data from Webapp to local system by clicking on Bokeh Button widget

I am currently working on creating a Python-Bokeh based webapp Application running on server.In this application,user can preview the data from a pandas dataframe(shown using BOKEH DATATABLE) and can modify the data as per business needs. After refreshing the dataframe,the user will need to export the dataframe to his local system(in csv format) by clicking a BOKEH BUTTON widget.
Now i was able to create the webapp application but it is causing issues while exporting the data to the local system. When i click on the button to download, a csv file gets downloaded having the intial default data present in the dataframe.After that even though I update the dataframe and again click on the Download BUTTON,same old default data is getting downloaded instead of the updated dataframe.
Below is the code which I tried at my end. Please suggest, what changes need to be done to the below snippet so that everytime the data gets refreshed and download button is clicked it will export the latest data showing in the datatable.
from bokeh.io import curdoc
from bokeh.models import ColumnDataSource, CustomJS
from bokeh.models.widgets import DataTable, NumberFormatter, TableColumn, Button
from bokeh.layouts import column,row
import pandas as pd
import numpy as np
# Create Default data
data = {'COL_1_DEFAULT': np.random.randint(200, size=100),
'COL_2_DEFAULT': np.random.randint(200, size=100),
'COL_3_DEFAULT': np.random.randint(200, size=100),
'COL_4_DEFAULT': np.random.randint(200, size=100)}
TABLE1_DATA = pd.DataFrame(data)
source_new = ColumnDataSource(TABLE1_DATA)
Columns_tab1 = [TableColumn(field=Ci, title=Ci) for Ci in TABLE1_DATA.columns] # bokeh columns
data_table1 = DataTable(columns=Columns_tab1, source=source_new,height = 200)
# Javascript for exporting data
js_code="""
var data = source.data;
var columns = Object.keys(source.data);
var filetext = [columns.join(',')].shift().concat('\\n');
var nrows = source.get_length();
for (let i=0; i < nrows; i++) {
let currRow = [];
for (let j = 0; j < columns.length; j++) {
var column = columns[j]
currRow.push(source.data[column][i].toString())
}
currRow = currRow.concat('\\n')
var joined = currRow.join();
filetext = filetext.concat(joined);
}
var filename = 'data_output.csv';
var blob = new Blob([filetext], { type: 'text/csv;charset=utf-8;' });
//addresses IE
if (navigator.msSaveBlob) {
navigator.msSaveBlob(blob, filename);
}
else {
var link = document.createElement("a");
link = document.createElement('a')
link.href = URL.createObjectURL(blob);
link.download = filename
link.target = "_blank";
link.style.visibility = 'hidden';
link.click();
URL.revokeObjectURL(link.href);
}
"""
# Created Button for Export
Exportbutton = Button(label="Download", button_type="success")
Exportbutton.js_on_click(CustomJS(args=dict(source=source_new),code = js_code ))
def refresh_table():
global source_new,Columns_tab1,data_table1,button
global TABLE1_DATA
df = []
for ii in range(1, 11):
df.append({'x': ii,
'y': 1000 * np.random.rand(),
'z' : 11 * np.random.rand()
})
df = pd.DataFrame(df)
source_new = ColumnDataSource(df)
Columns_tab1 = [TableColumn(field='x', title='Col 1'),TableColumn(field='y', title='Col 2',formatter=NumberFormatter(format='$0,0.00',text_align='right')),TableColumn(field='z', title='Col 3')]
data_table1.update(source=source_new, columns=Columns_tab1, width=500, height=200)
Exportbutton.js_on_click(CustomJS(args=dict(source=ColumnDataSource(df)),code = js_code ))
# Created Button for Refreshing dataframe data
table_Refresh_Button = Button(label='Refresh Table',height = 30,width = 200,button_type="success")
table_Refresh_Button.on_click(refresh_table)
layout = column(data_table1,table_Refresh_Button, Exportbutton)
curdoc().add_root(layout)
It's a bug. I just created https://github.com/bokeh/bokeh/issues/10146
As a workaround, replace
Exportbutton.js_on_click(CustomJS(args=dict(source=ColumnDataSource(df)),code = js_code ))
with
Exportbutton.js_event_callbacks['button_click'] = [CustomJS(args=dict(source=ColumnDataSource(df)), code=js_code)]

python bokeh dynamically update categorical x_range

I want to create a candlestick plot in bokeh and dynamically update the x-axis according to user input through a MultiSelect widget. Basically the user will choose a few items in the MultiSelect and then I would like those to become the values of the x axis. I already have set up the MultiSelect widget and confirmed that it is working by attaching a DataTable to the MultiSelect and having it update accordingly (which it does). I just need help retrieving the values from the MultiSelect widget and setting them as my plot.x_range. Based on a few github/issues posts (like this one: https://github.com/bokeh/bokeh/issues/4022) I tried using a FactorRange, but it isn't working. Currently the behavior is the x axis labels stay set to the values that are set during the initial configuration of the MultiSelect ('aaa' and 'bbb'), and don't change when I choose different values in the MultiSelect widget.
Here's a code sample:
### SET UP SOURCE DF INFO ###
tab2_source = ColumnDataSource(df)
tab2_original_source = ColumnDataSource(df)
columns_t2 = [TableColumn(field='Gene', title='Gene'), TableColumn(field='row_min', title='min'), TableColumn(field='row_max', title='max'),
TableColumn(field='quantile_25', title='25th quantile'), TableColumn(field='quantile_50', title='50th quantile'), TableColumn(field='quantile_75', title='75th quantile')]
data_table_t2 = DataTable(source=tab2_source, columns=columns_t2, reorderable=False)
### STUFF FOR THE WIDGETS ###
# customJS stuff
tab2_callback_code = """
var t2_data = tab2_source.data;
var t2_original_data = tab2_original_source.data;
var gene_t2 = gene_select_obj_t2.value;
console.log("gene: " + gene_t2);
for (var key in t2_original_data) {
t2_data[key] = [];
for (var i = 0; i < t2_original_data['Gene'].length; ++i) {
if (t2_original_data['Gene'][i] === gene_t2[0] || t2_original_data['Gene'][i] === gene_t2[1]) {
t2_data[key].push(t2_original_data[key][i]);
}
}
}
tab2_source.change.emit();
target_obj_t2.change.emit();
"""
# make drop-down selectors
select_gene_t2 = MultiSelect(title = "Select up to 2 Genes to View:", value=['aaa', 'bbb'], options=list(df.Gene.unique()))
# define the callback objects now that the select widgets exist
tab2_callback = CustomJS(
args=dict(tab2_source=tab2_source,
tab2_original_source=tab2_original_source,
gene_select_obj_t2=select_gene_t2,
target_obj_t2=data_table_t2),
code=tab2_callback_code)
# connect the callbacks to the filter widgets
select_gene_t2.js_on_change('value', tab2_callback)
## PLOTTING ##
p2 = figure(plot_width=500, plot_height=400, title='Avg Gene Exp', y_range=(-2,12), x_range=FactorRange())
p2.x_range.factors = list(select_gene_t2.value)
p2.vbar(x='Gene', top='quantile_75', bottom='quantile_25', source=tab2_source, width=0.5, fill_color="#D5E1DD", line_color="black")
tab2 = Panel(child=column(select_gene_t2, p2, data_table_t2), title="Exp by Gene")
tabs = Tabs(tabs=[tab2])
save(tabs)
For anyone who is interested, this is the best solution I found:
Add this line to the tab2_callback_code (plot_xr references my figure p2 - you will also need to add these references in the CustomJS args dict):
plot_xr.x_range.factors = gene_t2;
Also it is important to instantiate the figure before the tab2_callback_code is called. This worked for me and now my x_range values change as I select different options in the MultiSelect.

How to print data when a Selectable GraphicsItem is clicked?

I have a JSON file which contains coordinate list and some other information.
My JSON structure look like this;
"annotations": [
{
"type": "Box",
"color": "red",
"box_top": 406.0,
"box_left": 656.0,
"box_height": 73.0,
"box_width": 40.0
}
],
"annotations": [
{
"type": "Box",
"color": "green",
"box_top": 450.0,
"box_left": 700.0,
"box_height": 95.0,
"box_width": 47.0
}
]
By taking the box values (box_top,box_left,box_height,box_width) I have drawn Rectangle using QGraphicsView and QGraphicsScene. The code is given below;
def load_image(self,image_item):
self.scene = QGraphicsScene(self.centralWidget) # Created a QGraphicsScene
self.pic = QPixmap(str(image_item.text())) # Loaded Image
self.brush = QBrush()
self.pen = QPen(Qt.red)
self.pen.setWidth(2)
self.pixItem = QGraphicsPixmapItem(self.pic)
self.load_view = self.scene.addItem(self.pixItem) # Image Added to Scene
# Opening JSON and fetching data
# …
for rect in json_file['annotations']:
# Taken type and color and stored in variable
self.box_type = rect['type']
self.box_color = rect['color']
# Taken box_top,box_left,box_height,box_width values
self.rect_item = self.scene.addRect(rect['box_top'],rect['box_left'],rect['box_width'],rect['box_length'],self.pen,self.brush) # x,y,w,h
self.rect_item.setFlag(QGraphicsItem.ItemIsSelectable) #Item is Selectable
#self.rect_item.setFlag(QGraphicsItem.ItemIsMovable) # Item is Movable
self.fit_view = self.gView.setScene(self.scene)
self.gView.fitInView(self.pixItem,Qt.KeepAspectRatio)
self.gView.setRenderHint(QPainter.Antialiasing)
self.gView.show()
Now What I want is when I click one box (say which has color red) from the GraphicsScene, I want to print its corresponding type and color. In a simple way, I want to print all data related to that box. A sample images also attached for reference. Note : Image is the output of this program.
Thank you.
Use the code provided in the other answer I gave you (the second and last method are probably better) and get the data you need from the item. The "type" is the object class, the color is the pen() color.

Bokeh - Update func for Slider

I have some problem with updating the plot values with Bokeh. Select and Slider don't change the plot. The code is supposed to plot 'budget' along with 'vote_average' in different years. Slider is for showing data (release_date) from 1970 to 2016 years. I'm working in the Jupyter notebook. Code is below:
source = ColumnDataSource(data = {
'x': movies.budget,
'y': movies.vote_average,
'revenue': movies.revenue,
'profit': movies.profit,
'original_title': movies.original_title,
'release_date': movies.release_date
})
p = figure(x_axis_label='Budget in millions $', y_axis_label='Rank',
tools = [HoverTool(tooltips = '#original_title')])
p.circle(x = 'x', y = 'y', source=source)
def update_plot(attr, old, new):
yr = slider.value
# Set new_data
new_data = {
'x' : data.budget.loc[data.release_date == str(yr)].values,
'y' : data.vote_average.loc[data.release_date == str(yr).values
}
# Assign new_data to source.data
source.data = new_data
slider = Slider(start=1970, end=2016, step=1, value=1970, title='Year')
slider.on_change('value', update_plot)
layout = row(widgetbox(slider), p)
show(layout)
What's supposed to be in 'update plot' function? It seems that this func just doesn't work.
Binding widgets in Jupyter notebook requires custom Javascript callbacks as far as I know. Your example would only work on a bokeh serve app. Check out this notebook to see how.

Place a chart in plotly popup

I'm using plotly for R, although I'm open to using the Python version, as well. When I hover over a datapoint, is there a way to make the popup contain another chart? Ideally the chart would be created from the data, although I can use a static image as a fallback.
I'm unsure where to start on this, and apologize in advance for not having an MWE.
Solution 1: Stick to R
Thanks to #MLavoie. The following example use pure R to create two plot, the "mainplot" and the "hover" which reacts to the hover event of the first one.
library(shiny)
library(plotly)
ui <- fluidPage(
plotlyOutput("mainplot"),
plotlyOutput("hover")
)
server <- function(input, output) {
output$mainplot <- renderPlotly({
# https://plot.ly/r/
d <- diamonds[sample(nrow(diamonds), 1000), ]
plot_ly(d, x = carat, y = price, text = paste("Clarity: ", clarity), mode = "markers", color = carat, size = carat, source="main")
})
output$hover <- renderPlotly({
eventdat <- event_data('plotly_hover', source="main") # get event data from source main
if(is.null(eventdat) == T) return(NULL) # If NULL dont do anything
point <- as.numeric(eventdat[['pointNumber']]) # Index of the data point being charted
# draw plot according to the point number on hover
plot_ly( x = c(1,2,3), y = c(point, point*2, point*3), mode = "scatter")
})
}
shinyApp(ui, server)
This example use the shiny binds for plotly. For every hover event, a POST request is sent to the server, then the server will update the popup-chart. It's very inefficient thus may not work well on slow connections.
The above code is just for demo, and not yet tested. See a working and much more complicated example here (with source).
Solution 2: Javascript
Yes, you can do it using the plotly Javascript API.
Short answer
Create your graph using R or Python or any other supported language.
Insert the graph into a new HTML page and add a callback function as shown in the example below. If you have good knowledge about DOM, you can also add the JS to the original HTML instead of creating a new one.
Draw the popup graph inside the callback function which accepts parameters containing the data of the datapoint on-hover.
Details
As #MLavoie mentioned, a good example is shown in plotly.hover-events
Let's dig into the code. In the JS file, there is a simple callback function attached to Plot:
Plot.onHover = function(message) {
var artist = message.points[0].x.toLowerCase().replace(/ /g, '-');
var imgSrc = blankImg;
if(artistToUrl[artist] !== undefined) imgSrc = artistToUrl[artist];
Plot.hoverImg.src = imgSrc;
};
Above, artistToUrl is a huge object filled with base64 string which I will not paste here to overflow the post. But you can see it under the JS tab of the example page. It has such structure:
var artistToUrl = { 'bob-dylan': 'data:image/jpeg;base64,/...',...}
Working example:
For demonstration, I prepare a simple example here (click to try):
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
</head>
<body>
<iframe id="plot" style="width: 900px; height: 600px;" src="https://plot.ly/~jackp/10816.embed" seamless></iframe>
<div id="myDiv"></div>
<script>
(function main() {
var Plot = { id: 'plot', domain: 'https://plot.ly' };
Plot.onHover = function(message) {
var y = message.points[0].y; /*** y value of the data point(bar) under hover ***/
var line1 = {
x: [0.25,0.5,1], /*** dummy x array in popup-chart ***/
y: [1/y, 2, y], /*** dummy y array in popup-chart ***/
mode: 'lines+markers'
};
var layout = {
title:'Popup graph on hover',
height: 400,
width: 480
};
Plotly.newPlot('myDiv', [ line1 ], layout); // this finally draws your popup-chart
};
Plot.init = function init() {
var pinger = setInterval(function() {
Plot.post({task: 'ping'});
}, 500);
function messageListener(e) {
var message = e.data;
if(message.pong) {
console.log('Initial pong, frame is ready to receive');
clearInterval(pinger);
Plot.post({
'task': 'listen',
'events': ['hover']
});
}
else if(message.type === 'hover') {
Plot.onHover(message);
}
}
window.removeEventListener('message', messageListener);
window.addEventListener('message', messageListener);
};
Plot.post = function post(o) {
document.getElementById(Plot.id).contentWindow.postMessage(o, Plot.domain);
};
Plot.init();
})();
</script>
</body>
</html>
This is modified from the poltly.hover-events example for python. Instead of poping up an image, I change the onhover callback to plot a curve based on the y value of the each bar.
The main chart is generated by python and inserted here as iframe. You can make your own by any language including R. In this page we add a <div id="myDiv"></div> and use the plotly.js to draw the popup-chart whithin it.
Export R data frame to JS enviornment
Shiny uses jsonlite to convert R objects to json and send them to the client. We can use the same mechanism to pack and send our data frame so that the JS callback can use the data to render the popup chart.
server.r
output$json <- reactive({
paste('<script>data =', RJSONIO::toJSON(your_data_frame, byrow=T, colNames=T),'</script>')
ui.r
fluidPage(..., htmlOutput("json"), ...)
In the JS callback function, you can use data as any other JS objects.
More detail goes here and here.
If you want to stick with R you could use Shiny to get almost the result you want. When you hover each point an image will be render under the main plot. For the example below, I used the first three rows of the mtcars datasets. To run the code, you only need 3 logos/images corresponding to the name of the first three rows (under mtcars$name, Mazda RX4, Mazda RX4 Wag, Datsun 710 in this example).
library(shiny)
library(plotly)
datatest <- diamonds %>% count(cut)
datatest$ImageNumber <- c(0, 1, 2, 3, 4)
datatest$name <- c("Image0", "Image1", "Image2", "Image3", "Image4")
ui <- fluidPage(
plotlyOutput("plot"),
# verbatimTextOutput("hover2"),
#imageOutput("hover"),
plotlyOutput("hover3")
)
server <- function(input, output, session) {
output$plot <- renderPlotly({
plot_ly(datatest, x = cut, y = n, type = "bar", marker = list(color = toRGB("black")))
})
selected_image <- reactive({
eventdat <- event_data('plotly_hover', source = 'A')
ImagePick <- as.numeric(eventdat[['pointNumber']])
sub <- datatest[datatest$ImageNumber %in% ImagePick, ]
return(sub)
})
# output$hover2 <- renderPrint({
#d <- event_data("plotly_hover")
#if (is.null(d)) "Hover events appear here (unhover to clear)" else d
#})
# output$hover <- renderImage({
# datag <- selected_image()
#filename <- normalizePath(file.path('/Users/drisk/Desktop/temp',
# paste(datag$name, '.png', sep='')))
# Return a list containing the filename and alt text
# list(src = filename,
# alt = paste("Image number", datag$name))
# }, deleteFile = FALSE)
output$hover3 <- renderPlotly({
datag <- selected_image()
# draw plot according to the point number on hover
plot_ly(data=datag, x = ImageNumber, y = n, mode = "scatter")
})
}
shinyApp(ui, server)
Seems the answers posted aren't working for you #Adam_G. I have been exploring similar libraries for my own work and determined that Plot.ly is not always the right path when you want advanced features. Have you seen bokeh? It is basically designed for this type of task and much easier to implement (also a D3.js library like Plot.ly). Here is a copy of an example they posted where you can move a slider to change a graph of data (similar to the example posted by #gdlmx for Plot.ly but you can use it without hosting it on a website). I added the flexx package so you can use this writing pure Python (no JavaScript - it can translate Python functions to JavaScript (CustomJS.from_py_func(callback)) https://github.com/zoofIO/flexx-notebooks/blob/master/flexx_tutorial_pyscript.ipynb):
from bokeh.io import vform
from bokeh.models import CustomJS, ColumnDataSource, Slider
from bokeh.plotting import figure, output_file, show
import flexx
output_file("callback.html")
x = [x*0.005 for x in range(0, 200)]
y = x
source = ColumnDataSource(data=dict(x=x, y=y))
plot = figure(plot_width=400, plot_height=400)
plot.line('x', 'y', source=source, line_width=3, line_alpha=0.6)
def callback(source=source):
data = source.get('data')
f = cb_obj.get('value') #this is the bokeh callback object, linked to the slider below
x, y = data['x'], data['y']
for i in range(len(x)):
y[i] = x[i]**f #the slider value passed to this function as f will alter chart as a function of x and y
source.trigger('change') #as the slider moves, the chart will change
slider = Slider(start=0.1, end=4, value=1, step=.1, title="power", callback=CustomJS.from_py_func(callback))
layout = vform(slider, plot)
show(layout)
See here for the actual example in action: http://docs.bokeh.org/en/0.10.0/docs/user_guide/interaction.html#customjs-for-widgets
To integrate with hover events see here ( from bokeh.models import HoverTool):
http://docs.bokeh.org/en/0.10.0/docs/user_guide/interaction.html#customjs-for-hover
Hover example:
from bokeh.plotting import figure, output_file, show, ColumnDataSource
from bokeh.models import HoverTool
output_file("toolbar.html")
source = ColumnDataSource(
data=dict(
x=[1, 2, 3, 4, 5],
y=[2, 5, 8, 2, 7],
desc=['A', 'b', 'C', 'd', 'E'],
)
)
hover = HoverTool(
tooltips=[
("index", "$index"),
("(x,y)", "($x, $y)"),
("desc", "#desc"),
]
)
p = figure(plot_width=400, plot_height=400, tools=[hover], title="Mouse over the dots")
p.circle('x', 'y', size=20, source=source)
show(p)
Looking at the 1st code you could put whatever formula you want under the def callback function - some playing around required. You can get the hover to alter a graph next to it (hform(leftchart, rightchart) or above / below it (vform(topchart, bottomchart)). This is passed as CustomJS which bokeh uses to allow extendability and flexx allows you to write it in Python.
The alternative is to put whatever you want customized on the hover tooltips using HTML (although this example is placing images in dictionaries instead of new plots from the underlying data): http://docs.bokeh.org/en/0.10.0/docs/user_guide/tools.html#custom-tooltip

Categories

Resources