Closed Huggies23 closed 3 years ago
I am also having issues with the use of tabs causing a loss of plot interactivity except on the lowest level tab. This issue occurs for Plotly and hvplot objects, however it does not occur for Holoviews and Geoviews objects.
Software Info:
os x Catalina
python 3.7.5
notebook 6.0.2
pandas 0.25.3
panel 0.7.0
plotly 4.3.0
plotly_express 0.4.1
holoviews 1.12.6
geoviews 1.6.5
hvplot 0.5.2
Description of the Problem:
I have created the following panel called "example":
This panel contains the following elements:
Tabs A,B,C.
Each tab contains an identical GridSpec which consists of a:
Bar plot produced using hvplot (fig1).
Histogram created using hvplot. I used pn.interact to create the app "app_numeric" which displays the appropriate histogram when a numeric attribute is chosen from the dropdown.
UK choropleth created using Geoviews and Holoviews (map_app_df).
Expected Behaviour: When using tabs, all elements 1. - 3. are fully interactive as they are when you plot just a single GridSpec.
Observed Behaviour: Elements 1. and 3. are fully interactive as expected for ALL 3 TABS. If you hover over a graph component in either element then you are presented with the appropriate hover data. For element 3., the map is fully interactive and zoomable. Tabs to show or hide heatmap all work fine.
For Element 2. (the histogram), if the user hovers over any graph component then the appropriate hover data is displayed in all 3 tabs. However if the user tries to interact with element 2., eg by selecting an attribute from the dropdown to display the appropriate histogram, then this only works for tab C.
Furthermore, I noticed that if I choose an attribute from the dropdown, sometimes the selection doesn't seem to take and the plot doesn't update - this following error is then displayed in my Jupyter notebook:
tornado.application - ERROR - Exception in callback functools.partial(<bound method IOLoop._discard_future_result of <tornado.platform.asyncio.AsyncIOMainLoop object at 0x10a97b890>>, <Future finished exception=AttributeError("'NoneType' object has no attribute 'values'")>)
Please note that this error does not trigger when choosing an attribute from the dropdown in the panel app "app_numeric" directly, however it does intermittently occur when "app_numeric" is added to the GridSpec.
I also created a second panel called "example2":
This panel is identical to "example" except that Element 2. was created using Plotly here. Result was identical to that of "example" except that:
For each tab, when you hover over a graph component, no hover data is displayed for the histogram in Element 2. except for in the lowest level tab C.
The tornado error seen above is never triggered when Element 2. was created using Plotly.
Code to Reproduce Issue: The following provides the code to generate the 2 Tabbed GridSpec panels "example" and "example2".
import pandas as pd
import numpy as np
import random
import copy
import feather
import plotly.graph_objects as go
import plotly.express as px
import panel as pn
import holoviews as hv
import geoviews as gv
import geoviews.feature as gf
import cartopy
import cartopy.feature as cf
from geoviews import opts
from cartopy import crs as ccrs
import hvplot.pandas
import colorcet as cc
from colorcet.plotting import swatch
gv.extension("bokeh")
cols = {"name":["Jim","Alice","Bob","Julia","Fern","Bill","Jordan","Pip","Shelly","Mimi"],
"age":[19,26,37,45,56,71,20,36,37,55],
"age_band":["18-24","25-34","35-44","45-54","55-64","65-74","18-24","35-44","35-44","55-64"],
"insurance_renew_month":[1,2,3,3,3,4,5,5,6,7],
"postcode_prefix":["EH","M","G","EH","EH","M","G","EH","M","EH"],
"postcode_order":[3,2,1,3,3,2,1,3,2,3],
"local_authority_district":["S12000036","E08000003","S12000049","S12000036","S12000036","E08000003","S12000036","E08000003","S12000049","S12000036"],
"blah1":[3,None,None,8,8,None,1,None,None,None],
"blah2":[None,None,None,33,5,None,66,3,22,3],
"blah3":["A",None,"A",None,"C",None,None,None,None,None],
"blah4":[None,None,None,None,None,None,None,None,None,1]}
df = pd.DataFrame.from_dict(cols)
df
Out[2]:
name age age_band insurance_renew_month ... blah1 blah2 blah3 blah4
0 Jim 19 18-24 1 ... 3.0 NaN A NaN
1 Alice 26 25-34 2 ... NaN NaN None NaN
2 Bob 37 35-44 3 ... NaN NaN A NaN
3 Julia 45 45-54 3 ... 8.0 33.0 None NaN
4 Fern 56 55-64 3 ... 8.0 5.0 C NaN
5 Bill 71 65-74 4 ... NaN NaN None NaN
6 Jordan 20 18-24 5 ... 1.0 66.0 None NaN
7 Pip 36 35-44 5 ... NaN 3.0 None NaN
8 Shelly 37 35-44 6 ... NaN 22.0 None NaN
9 Mimi 55 55-64 7 ... NaN 3.0 None 1.0
[10 rows x 11 columns]
swatch("CET_L17")
normal_cmap = cc.CET_L17
df_count = df.count().sort_values(ascending=False)
df_count = df_count.to_frame().reset_index().rename(columns={"index":"attribute",0:"count"})
# Plot Fig1
fig1 = df_count[::-1].hvplot.bar(x="attribute",y="count",color="count",cmap=normal_cmap,invert=True, width=500, height=300)
# Load Shapefile
shapefile = "/Users/maleko/Local_Authority_Districts_April_2019_Boundaries_UK_BUC/Local_Authority_Districts_April_2019_Boundaries_UK_BUC.shp"
gv.Shape.from_shapefile(shapefile, crs=ccrs.OSGB())
# Identify Attribute to Link Map & Data (It is "lad19cd"):
shapes = cartopy.io.shapereader.Reader(shapefile)
list(shapes.records())[0]
# Plot UK Choropleth:
swatch("CET_R3")
new_cmap = cc.CET_R3
heatmap = gv.Shape.from_records(shapes.records(), df, on={"lad19cd":"local_authority_district"},
value="postcode_order",index="postcode_prefix", crs=ccrs.OSGB()).opts(tools=["hover"],
cmap = new_cmap, colorbar=True, hover_color="white", hover_alpha=0, title="UK Postcode Heatmap", width=500, height=800)
non_heatmap = gv.Shape.from_records(shapes.records(), df, on={"lad19cd":"local_authority_district"},
crs=ccrs.OSGB()).opts(title="UK Dealership Map", width=500, height=800)
wikimap = hv.Tiles('https://maps.wikimedia.org/osm-intl/{Z}/{X}/{Y}@2x.png', name="Wikimedia").opts(width=500, height=800)
pins = gv.Points([(-1.67514,54.96003,"Gateshead"),(-1.51591,54.52271,"Darlington"),(-2.28369,53.46123,"Manchester"),(-0.05944,51.64624,"Enfield"),(-3.73886,56.15171,"Tillicoultry")]).opts(tools=["hover"],color="#6a0dad",size = 15,hover_color="green")
result1 = wikimap*heatmap*pins
result2 = wikimap*non_heatmap*pins
map_app_df = pn.Tabs(("Show Heatmap",result1),("Hide Heatmap",result2))
num_atts = ["age","insurance_renew_month"]
num_atts
def num_plot_df(numeric="age"):
if numeric =="age":
fig2= df.hvplot.hist(y="age",by=["age_band"],
bins=[18,25,35,45,55,65,74],
xticks=[(21.5,"18-24"),(30,"25-34"),(40,"35-44"),(50,"45-54"),(60,"55-64"),(69.5,"65-74")],
color="teal",legend=False,
line_width=4,line_color="w", width=500, height=300)
return fig2
elif numeric =="insurance_renew_month":
fig3 = df.hvplot.hist(y="insurance_renew_month",
bins=[1,2,3,4,5,6,7,8,9,10,11,12],
xticks=[(1.5,"JAN"),(2.5,"FEB"),(3.5,"MAR"),(4.5,"APR"),(5.5,"MAY"),(6.5,"JUN"),
(7.5,"JUL"),(8.5,"AUG"),(9.5,"SEP"),(10.5,"OCT"),(11.5,"NOV"),(12.5,"DEC")],
color="teal",legend=False,
line_width=4,line_color="w", width=500, height=300)
return fig3
app_numeric = pn.interact(num_plot_df,numeric=num_atts)
gspec = pn.GridSpec(sizing_mode="scale_both", max_height=1000)
gspec[0,0] = fig1
gspec[1,0] = app_numeric
gspec[0:2,1] = map_app_df
def num_plot_df_plotly(numeric="age",nbins=12):
if numeric in num_atts:
numplot = px.histogram(df,x=numeric,nbins=nbins, width=500, height=300)
numplot.update_traces(marker_line_color="rgb(255,255,255)",
marker_line_width=4)
return numplot
app_numeric2 = pn.interact(num_plot_df_plotly,numeric=num_atts,nbins=(1,20))
gspec2 = pn.GridSpec(sizing_mode="scale_both", max_height=1000)
gspec2[0,0] = fig1
gspec2[1,0] = app_numeric2
gspec2[0:2,1] = map_app_df
example = pn.Tabs(("A",gspec),("B",gspec),("C",gspec))
example.show()
example2 = pn.Tabs(("A",gspec2),("B",gspec2),("C",gspec2))
example2.show()
@jonmmease Do you have any idea what might be happening here?
Hi, is there any update on this issue? Thanks
No sorry and if there was we'd post it here. It's not clear to me whether this is an issue with Panel, Bokeh or Plotly tbh so it's hard to figure out what is happening.
Hi.
I also ran into the same issue. One thing I noticed was that when the graphs on the tabs don't overlap each other the functionality works perfectly fine. But if they overlap, even though we are seeing the graph on top, the interactive functions are in fact effecting the bottom graph.
This seems to suggest the issue is not with plotly
, but with how panel
"renders" the tabs. It seems like where the graphs overlap, the top graph is transparent to the interaction tools. I can't say anything more as I have no idea how both these libraries actually work.
I'm attaching a self sufficient example which you can run to see what I am talking about, if its not clear from my explanation. Hope this helps to understand the problem.
I have shifted the bottom graph by specifying a margin. So the graphs now overlap only the sides, a little. Notice that interactivity works fine on the left side of the top-tab graph, but if you try hovering or zooming on the right side of the top graph nothing happens, but it's the bottom graph that gets zoomed in on. (To see what I am talking about you can try using the Box Select
tool on the right side of the top tab and then switch to the bottom one to see.)
Red portion is where the graphs overlap. The blue portions are the parts of both the graphs that don't overlap. In the red region, the top tab interactivity doesn't work.
Code for the above example:
import numpy as np
import param
import plotly.graph_objects as go
import plotly.figure_factory as ff
import panel as pn
pn.extension('plotly')
x = np.arange(1000)
class PlotTabs(param.Parameterized):
log_x = param.Boolean(True, doc="X-axis log")
log_y = param.Boolean(True, doc="Y-axis log")
new_values = param.Action(lambda x: x.param.trigger('new_values'), label="New Sample")
def __init__(self, **params):
super(PlotTabs, self).__init__(**params)
self.y1 = np.random.normal(loc=np.random.randint(0,5), size=1000)
self.y2 = np.random.normal(loc=np.random.randint(0,5), size=1000)
self.figure1 = go.Figure()
self.figure2 = go.Figure()
@param.depends('new_values', watch=True)
def compute(self):
self.y1 = np.random.normal(loc=np.random.randint(0,5), size=1000)
self.y2 = np.random.normal(loc=np.random.randint(0,5), size=1000)
@param.depends('new_values', 'log_x', 'log_y', watch=True)
def view_timeseries(self):
self.figure1 = go.Figure() # Resetting
self.figure1.add_trace(go.Scatter(x=x, y=self.y1, mode='markers'))
self.figure1.add_trace(go.Scatter(x=x, y=self.y2, mode='markers'))
if self.log_x:
self.figure1.update_layout(xaxis_type="log")
if self.log_y:
self.figure1.update_layout(yaxis_type='log')
self.figure1.update_layout(autosize=True,legend_orientation='h', showlegend=True, \
margin=dict(l=200, r=20, t=20, b=20), width=500, height=300)
return self.figure1
@param.depends('new_values', watch=True)
def view_histogram(self):
self.figure2 = go.Figure()
hist_data = [self.y1, self.y2]
labels = ['y1', 'y2']
self.figure2 = ff.create_distplot(hist_data, labels, bin_size=0.1, show_rug=False, show_curve=True)
self.figure2.update_layout(autosize=True,legend_orientation='h', showlegend=True, \
margin=dict(l=20, r=20, t=20, b=20), width=300, height=300)
return self.figure2
plot = PlotTabs(name="Example")
pn.Row(pn.Column(plot.param),
pn.Tabs(('Histogram',plot.view_histogram),
('Time Series', plot.view_timeseries),))
As a temporary workaround, I move all the content of the non-active tabs far up on the page by adjusting the margins. This way none of the invisible plotly figures overlap with the figures on the active tab.
This workaround works well, even for nested tabs. But I have to warn you that the response time for switching tabs becomes rather slow when you have a lot of tabs.
import numpy as np
import panel as pn
import plotly.graph_objects as go
pn.extension('plotly')
def fix_plots_in_tabs(plots):
"""Unfortunately, the plotly interactivity doesn't work when the plots overlap in panel,
even if they are on different tabs. This is "fixed" by moving all the hidden plotly plots
up, so they don't overlap with the one which is visible.
issue thread: https://github.com/holoviz/panel/issues/804
"""
for p in plots:
if (isinstance(p, pn.layout.Tabs)):
fix_plots_in_tabs(p)
code = """
for (let i = 0; i < source.tabs.length; i++) {
var column = source.tabs[i.toString()].child
if(source.active == i){
column.margin = [0, 0, 0, 0]
} else {
column.margin = [-10000, 0, 0, 0]
}
}
"""
plots.jscallback(active=code)
for i in range(1, len(plots)):
plots[i].margin = [-10000, 0, 0, 0]
plots = pn.Tabs(tabs_location="left")
plots_normal = pn.Tabs(tabs_location="left")
plots_inverse = pn.Tabs(tabs_location="left")
plots.append(("normal", plots_normal))
plots.append(("inverse", plots_inverse))
x = np.arange(10)
pn1 = go.Scatter(x=x, y=x**2, marker={"color": "red"})
pn2 = go.Scatter(x=x, y=x**3, marker={"color": "green"})
pi1 = go.Scatter(x=x**2, y=x, marker={"color": "blue"})
pi2 = go.Scatter(x=x**3, y=x, marker={"color": "black"})
plots_normal.append(("quadratic", pn1))
plots_normal.append(("third power", pn2))
plots_inverse.append(("quadratic", pi1))
plots_inverse.append(("third power", pi2))
fix_plots_in_tabs(plots)
plots.show()
Hi @philippjfr , I also ran into the same issue. After a lot of searching and all, I ended up with analyzing the source code. Where I came to know that: Issue When we click on any tab a new class ''tab-active" is added to the corresponding div in html file, and "visibility: hidden" is removed from that tab's container's div, and rest all tabs will have class "visibility: hidden". According to documentation of "visibility", Hidden elements take up space on the page. So, this causes the issue illustrated by @Huggies23. Solution In place of "visibility: hidden", if we can replace it to "display: none", we can solve the issue. According to the documentation of "display", _None elements will not take space on the page.
From my side I tried to add JavaScript to replace "visibility: hidden" to "display: none", but was not successful because, the JavaScript was not called every time the tab changes.
Hi, any progress on this issue?
Hi! Since it's quite an old bug and there is no much progress here, maybe it's worth to write a warning in documentation that it's better to avoid use Tabs together with interactive plots in Panel, what do you think? Otherwise Panel users have to debug this issue, then google this page and finally have to redesign their application.
it's better to avoid use Tabs together with interactive plots in Panel,
The above examples seem specifically related to Plotly, right? I.e. it's not that interactive plots have trouble in tabs in Panel in general, but that specifically Plotly objects interact across tabs? If so such a general warning would not be appropriate; it's a very specific bug. It does sound like "display: none" is worth trying in Panel, though.
@jbednar agree, not general interactive plots, but it seems specifically Plotly objects together with Tabs create an issue.
I'll see if I can come up with a fix very soon.
Hoping for fix to this issue soon. Plotly itself has tabs implemented in Dash.
My first one of these so apologies if it's missing info / already flagged.
Software info:
python: 3.6.9 Panel = 0.6.2 plotly = 4.2.1 bokeh = 1.3.4 jupyter notebook server = 6.0.0 browser: Google chrome (and same behavior in embedded html output) OS: Windows 8.1
Description of expected behavior and the observed behavior
Below applies to within jupyter notebook, in browser window (.show()) and in html output (.save(embed = True))
Expected behavior: plotly objects within panel tabs to have same interactivity as when not in tabs.
observed behavior: Only plotly object in "bottom level" (last in list of tabs, "fig2" tab in example) retains full interactivity (pan, zoom, select, legend trace select etc.)). All other tab "levels" (tabs other than the last one in tab list, "fig1" tab inexample) retain only legend select interactivity. Interactions with the area bound by the axis (where a crosshair is seen) in "fig1" results in changes to the "fig2" plotly object.
Complete, minimal, self-contained example code that reproduces the issue
Screenshots of issue