prevent camera resetting after plotting with Plotly-python - python-3.x

I am trying to plot some data for a 3d Quiver or Cone using dash and plotly and I want to update the Graph periodically through an interval Input!
So I managed to animate the graph but the problem is that the camera angle and zoom keep resetting after each update.
i have the following code:
import dash
import dash_core_components as dcc
import dash_html_components as html
import plotly.graph_objs as go
from dash.dependencies import Output, Input
import pickle
#reading initial data
with open("shared.pkl", "rb") as f:
quivDic = pickle.load(f)
quiver_3d = go.Cone(x = quivDic["X"], y = quivDic["Y"], z = quivDic["Z"],
u = quivDic["U"], v = quivDic["V"], w = quivDic["W"],
colorscale = 'Blues', name = "testScatter")
data = [quiver_3d]
layout = dict(title ="Test Quiver", showlegend=False, aspectratio=dict(x=1, y=1, z=0.8),
camera_eye=dict(x=1.2, y=1.2, z=0.6))
fig = dict(data=data, layout=layout)
app = dash.Dash()
app.layout = html.Div([
html.Div(html.H4("TEST CONE")),
html.Div(dcc.Graph(id = "testCone", figure=fig)),
dcc.Interval(
id='graph-update',
interval=1000,
n_intervals = 0
),
])
#app.callback(Output('testCone', 'figure'),
[Input('graph-update', 'n_intervals')])
def refresh(n):
#reading new data
with open("shared.pkl", "rb") as f:
quivDic = pickle.load(f)
quiver_3d.x = quivDic["X"]
quiver_3d.y = quivDic["Y"]
quiver_3d.z = quivDic["Z"]
quiver_3d.u = quivDic["U"]
quiver_3d.v = quivDic["V"]
quiver_3d.w = quivDic["W"]
data = [quiver_3d]
#creating new figure
fig = dict(data=data)
return fig
app.run_server(debug=True)
Does anyone know how to avoid this problem?
Ideally I'd like to update the data without redrawing the whole frame, something like "set_data" from matplotlib. Otherwise is there a way to keep track of the latest camera angle and update the layout through the callback?
and Thanks ^^

Yes, you can use the uirevision attribute, as detailed here: https://community.plot.ly/t/preserving-ui-state-like-zoom-in-dcc-graph-with-uirevision/15793

Related

Not able to update the plot on selection of a value of Select widget of Bokeh Library

I am working on COVID19 analysis and am using a JSON data source. I have converted the json to dataframe. I am working on plotting a daily case, daily death and daily recovered bar chart over a datetime x-axis for each state and the state can be selected using a Select widget. I don't know Javascript so, I am trying to avoid using Javascript callbacks but have been using a function to update the select.value. I am not sure why is the plot not getting updated even when i am running the code on Bokeh server and there are no exceptions raised by the interpreter.
Can someone provide me with any direction or help with what might be causing the issue as I am new to Python and any help is appreciated? Or if there's any other alternative. This code is a derivation from a similar plot on [bokeh discourse][1]
#Creating DataFrame
cases_summary = requests.get('https://api.rootnet.in/covid19-in/stats/history')
json_data = cases_summary.json()
cases_summary=pd.json_normalize(json_data['data'], record_path='regional', meta='day')
cases_summary['day']=pd.to_datetime(cases_summary['day'])
cases_summary['daily deaths']=cases_summary['deaths'].groupby(cases_summary['loc']).diff(1)
cases_summary['daily confirmed']=cases_summary['totalConfirmed'].groupby(cases_summary['loc']).diff(1)
cases_summary['daily discharged']=cases_summary['discharged'].groupby(cases_summary['loc']).diff(1)
#Initializing the first default plot
cases=cases_summary[cases_summary['loc']=='Delhi']
source=ColumnDataSource(data=cases)
a = figure(plot_width=1200, plot_height=700, sizing_mode="scale_both", x_axis_type='datetime')
def make_plot(cases_val):
a.vbar('day', top='daily confirmed', width=timedelta(days=0.5),
legend_label='Daily Confirmed', color='#5e4fa2', source=cases_val)
a.vbar('day', bottom='daily discharged', width=timedelta(days=0.5),
legend_label='Daily Recovered', color='#66c2a5', source=cases_val)
a.vbar('day', bottom='daily deaths', width=timedelta(days=0.5),
legend_label='Daily Deaths', color='#3288bd', source=cases_val)
return a
def update_plot(attr,old,new):
location=select.value
data_loc = cases_summary[cases_summary['loc'] == location]
source = ColumnDataSource(data=dict()).from_df(data_loc)
layout.children[0]=make_plot(source)
select = Select(title="Select State:", value="Delhi", options=cases_summary['loc'].unique().tolist())
plot = make_plot(cases)
controls = column(select)
layout = row(a, controls)
select.on_change('value', update_plot)
curdoc().add_root(layout)
[1]: https://discourse.bokeh.org/t/how-to-update-the-bar-chart-that-has-dataframe-as-source-with-bokeh-select-widget/2031/8
This can be done more simply using a view and a filter. Here is an alternative approach:
import requests
import pandas as pd
from bokeh.plotting import figure
from bokeh.layouts import column, row
from bokeh.io import curdoc
from bokeh.models import *
from datetime import timedelta
cases_summary = requests.get("https://api.rootnet.in/covid19-in/stats/history")
json_data = cases_summary.json()
cases_summary = pd.json_normalize(json_data["data"], record_path="regional", meta="day")
cases_summary["day"] = pd.to_datetime(cases_summary["day"])
cases_summary["daily deaths"] = (
cases_summary["deaths"].groupby(cases_summary["loc"]).diff(1)
)
cases_summary["daily confirmed"] = (
cases_summary["totalConfirmed"].groupby(cases_summary["loc"]).diff(1)
)
cases_summary["daily discharged"] = (
cases_summary["discharged"].groupby(cases_summary["loc"]).diff(1)
)
source = ColumnDataSource(cases_summary)
filter = GroupFilter(column_name='loc',group='Delhi')
view = CDSView(source=source, filters = [filter])
a = figure(
plot_width=1200, plot_height=700, sizing_mode="scale_both", x_axis_type="datetime"
)
a.vbar(
"day",
top="daily confirmed",
width=timedelta(days=0.5),
legend_label="Daily Confirmed",
color="#5e4fa2",
source=source,
view = view
)
a.vbar(
"day",
bottom="daily discharged",
width=timedelta(days=0.5),
legend_label="Daily Recovered",
color="#66c2a5",
source=source,
view = view
)
a.vbar(
"day",
bottom="daily deaths",
width=timedelta(days=0.5),
legend_label="Daily Deaths",
color="#3288bd",
source=source,
view = view
)
def update_plot(attr, old, new):
view.filters = [GroupFilter(column_name='loc',group=select.value)]
select = Select(
title="Select State:", value="Delhi", options=cases_summary["loc"].unique().tolist()
)
controls = column(select)
layout = row(a, controls)
select.on_change("value", update_plot)
curdoc().add_root(layout)

How do I implement a start-stop button / play-resume button for an animation in bokeh?

I am trying to implement a star-stop button for a bokeh animation. I am using bokeh server in combination with the curdoc() function, but so far I haven't had much success.
I am wondering how someone with more experience would do that?
thanks
Here you go. Run with bokeh serve --show app.py (tested on Bokeh v1.0.4)
from bokeh.models import ColumnDataSource, Toggle, Column
from bokeh.plotting import figure, curdoc
from datetime import datetime
import random
source = ColumnDataSource(dict(time = [datetime.now()], value = [random.randint(5, 10)]))
plot = figure(plot_width = 1200, x_axis_type = 'datetime', tools = 'pan,box_select,crosshair,reset,save,wheel_zoom')
plot.line(x = 'time', y = 'value', line_color = 'black', source = source)
toggle = Toggle(label = "Toggle", button_type = "success")
def update():
if toggle.active:
source.stream(dict(time = [datetime.now()], value = [random.randint(5, 10)]))
curdoc().add_root(Column(plot, toggle))
curdoc().add_periodic_callback(update, 1000)
Result:

Why won't bokeh figure update with new data?

I'm creating a bokeh application that pulls data from Quandl stock prices and changes the plot based the stock symbol the user inputs. I used an example from this bokeh tuorial as a model.
Everything is working except the plot won't update when I input a new symbol.
I've tried passing the new data as a dictionary (before I was just passing a DataFrame to ColumnDataSource(), but no luck.
import pandas as pd
import numpy as np
from bokeh.models.widgets import TextInput, Select
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure
from bokeh.layouts import column, row
from bokeh.io import show, output_notebook
import quandl
This is the function to get the data:
def get_data(symbol):
dictionary = {}
data = quandl.get('WIKI/' + symbol, collapse = 'annual', returns='numpy')
df = pd.DataFrame(data)
dictionary['date'] = list(df.Date.values)
dictionary['high'] = list(df.High.values)
return dictionary
And this is a function for the plot:
def modify_doc(doc):
symbol = 'AAWW'
source = ColumnDataSource(data = get_data(symbol))
p = figure(x_axis_type='datetime', title='Stock Price', plot_height=350, plot_width=800)
p.xgrid.grid_line_color=None
p.ygrid.grid_line_alpha=0.5
p.xaxis.axis_label = 'year'
p.yaxis.axis_label = 'close'
r = p.line(source.data['date'],
source.data['high'],
line_color = 'navy')
select = Select(title="Color", value="navy", options=COLORS)
input = TextInput(title="Ticker Symbol", value=symbol)
def update_symbol(attrname, old, new):
source.data = get_data(input.value)
input.on_change('value', update_symbol)
layout = column(row(input, width=400), row(p))
doc.add_root(layout)
show(modify_doc)
I would think that the plot would update when the new symbol is entered, but it just stays the same.
Any thoughts?
Your code looks like Bokeh server application but you use show() what doesn't look good to me. You are also trying to update the figure by assigning new data to the source but you did not pass your source to the figure object so it won't have any effect. Could you try if this code works for you? (should work for Bokeh v1.0.4)
import random
import pandas as pd
from tornado.ioloop import IOLoop
from bokeh.server.server import Server
from bokeh.application import Application
from bokeh.application.handlers.function import FunctionHandler
from bokeh.plotting import figure, ColumnDataSource
from bokeh.models.widgets import TextInput
from bokeh.layouts import column, row
def make_document(doc):
symbol = 'AAWW'
def get_data(symbol):
dictionary = {}
data = quandl.get('WIKI/' + symbol, collapse = 'annual', returns = 'numpy')
df = pd.DataFrame(data)
dictionary['date'] = list(df.Date.values)
dictionary['high'] = list(df.High.values)
return dictionary
source = ColumnDataSource(data = get_data(symbol))
p = figure(x_axis_type = 'datetime', title = 'Stock Price', plot_height = 350, plot_width = 800)
p.xgrid.grid_line_color = None
p.ygrid.grid_line_alpha = 0.5
p.xaxis.axis_label = 'year'
p.yaxis.axis_label = 'close'
r = p.line(x = 'date',
y = 'high',
source = source,
line_color = 'navy')
input = TextInput(title = "Ticker Symbol", value = symbol)
def update_symbol(attrname, old, new):
source.data = get_data(input.value)
input.on_change('value', update_symbol)
layout = column(row(input, width = 400), row(p))
doc.add_root(layout)
io_loop = IOLoop.current()
server = Server({'/myapp': Application(FunctionHandler(make_document))}, port = 5001, io_loop = io_loop)
server.start()
server.show('/myapp')
io_loop.start()
Basically the main change is here:
r = p.line(x = 'date',
y = 'high',
source = source,
line_color = 'navy')
Based on the answer I got from Tony, I just had to change one line of code:
r = p.line(x = 'date',
y = 'high',
source = source,
line_color = 'navy')

How can I handle all my subplot graphs just using a single slider? [Python-Dash]

I am using a subplot structure for my plots and I want to range in the x-axis by using a Slider. I try to range through my graphs simultaneously, but it doesn't work. The callback routine destroys the subplot structure (created with plotly.tools.make_subplots).
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
from plotly import tools
from plotly import graph_objs as go
fig = tools.make_subplots(rows=2, shared_xaxes=True)
fig.add_scatter(x=[1,2,3], y=[2,1,2])
fig.add_scatter(x=[1,2,3], y=[5,3,3], yaxis='y2')
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.layout = html.Div(children=[
dcc.Graph(
id='graph1',
figure = fig
),
dcc.Slider(
id='temp-slider',
min=0,
max=10,
step=0.2,
value=0
)
])
#app.callback(
Output('graph1','figure'), [Input('temp-slider','value')]
)
def update_graph(value):
out = dict(
data = fig.data,
layout = dict(
xaxis = dict(
range = [value,value+1]
)
)
)
return out
if __name__ == '__main__':
app.run_server(debug=True)
I need a subplot that ranges both graphs by using the same slider
Since you are recreating the graph again, the subplot layout is lost, please just update the specific range property of the existing figure instead. Please refer the below example code and let me know if this fixes your issue.
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
from plotly import tools
from plotly import graph_objs as go
fig = tools.make_subplots(rows=2, shared_xaxes=True)
fig.add_scatter(x=[1,2,3], y=[2,1,2])
fig.add_scatter(x=[1,2,3], y=[5,3,3], yaxis='y2')
fig.layout.xaxis.dtick=0.5
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.layout = html.Div(children=[
dcc.Graph(
id='graph1',
figure = fig
),
dcc.Slider(
id='temp-slider',
min=0,
max=10,
step=0.2,
value=0
)
])
#app.callback(
Output('graph1','figure'), [Input('temp-slider','value')]
)
def update_graph(value):
fig.layout.xaxis.range = [value,value+1]
return fig
if __name__ == '__main__':
app.run_server(debug=True)

Bokeh World Map and Coloring Waterbodies

Is there an equivalent way in Bokeh to Basemap's drawmapboundary where you can specify certain colors? See the first example here:
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
# setup Lambert Conformal basemap.
m = Basemap(width=12000000,height=9000000,projection='lcc',
resolution='c',lat_1=45.,lat_2=55,lat_0=50,lon_0=-107.)
# draw coastlines.
m.drawcoastlines()
# draw a boundary around the map, fill the background.
# this background will end up being the ocean color, since
# the continents will be drawn on top.
m.drawmapboundary(fill_color='aqua')
# fill continents, set lake color same as ocean color.
m.fillcontinents(color='coral',lake_color='aqua')
plt.show()
I would like to fill waterbodies (e.g., oceans) with the color "aqua". I'm able to generate a black-and-white world map, but how do I color oceans specifically?
I'm using the JSON file for countries from here, and then loading it with GeoJSONDataSource.
import bokeh.plotting as bkp
import bokeh.models as bkm
filename = "test.html"
tools = "pan,wheel_zoom,box_zoom,reset,previewsave"
with open("./countries.geo.json", "r") as f:
countries = bkm.GeoJSONDataSource(geojson=f.read())
p = bkp.figure(width=1000, height=600, tools=tools, title='World Countries', x_axis_label='Longitude', y_axis_label='Latitude')
p.x_range = bkm.Range1d(start=-180, end=180)
p.y_range = bkm.Range1d(start=-90, end=90)
p.patches("xs", "ys", color="white", line_color="black", source=countries)
bkp.output_file(filename)
bkp.save(p, filename)
Figured out by looking at what drawmapboundary does. Just need to set the background color. :)
import bokeh.plotting as bkp
import bokeh.models as bkm
filename = "test.html"
tools = "pan,wheel_zoom,box_zoom,reset,previewsave"
with open("./countries.geo.json", "r") as f:
countries = bkm.GeoJSONDataSource(geojson=f.read())
p = bkp.figure(width=1000, height=600, tools=tools, title='World Countries', x_axis_label='Longitude', y_axis_label='Latitude')
p.background_fill_color = "aqua"
p.x_range = bkm.Range1d(start=-180, end=180)
p.y_range = bkm.Range1d(start=-90, end=90)
p.patches("xs", "ys", color="white", line_color="black", source=countries)
bkp.output_file(filename)
bkp.save(p, filename)

Resources