Using panel 0.12.6 I’m running into this following error. What I’d like to realize is a tabbed widget that can plot a list of x,y pairs and then dump them to disk if needed.
The problem I’m running into is with the FileDownload function. I’ve included an example below. One that works and one that creates the error. To illustrate the error, I get the recursion issue when the file download button is included. Hopefully, I’m missing something simple. My guess is that the datastream is being created immediately. I’ve tried using the embed=False and embed=True flags with no luck.
Help is appreciated.
import numpy as np
import pandas as pd
import plotly.graph_objects as go
from collections import OrderedDict
from io import StringIO
import panel as pn
pn.extension('plotly')
lowerBound = 10
upperBound = 100
def getPlotlyDataframe(plotlyDict):
'''
Accepts a ploty dictionary and preps for CSV dump
'''
dataDict = OrderedDict()
for i,subDict in enumerate(plotlyDict['data']):
curLabel = subDict['name']
xLabel = curLabel+"_x"
yLabel = curLabel+"_y"
curX = subDict['x']
curY = subDict['y']
dataDict[xLabel] = curX
dataDict[yLabel] = curY
df = pd.DataFrame(dataDict)
return df
class XYBOX(pn.Column):
'''
Adapted from: https://discourse.holoviz.org/t/how-to-create-a-self-contained-custom-panel/985/6
'''
def __init__(self, dataList, lowerBound = lowerBound, upperBound = upperBound,
xLabel = 'X', yLabel = 'Y', dataLabels = [], **params):
self._rename["column"] = None
super().__init__(**params)
self.dataList = dataList
self.dataLabels = []
self.xyFig = go.Figure()
self.xyFig.update_xaxes(title=xLabel)
self.xyFig.update_yaxes(title=yLabel)
self.addTraces()
self.xyPane = pn.pane.Plotly(self.xyFig, width = 800, height = 500)
self.lowerXInput = pn.widgets.FloatInput(name='Lower Bound', value=lowerBound, step=1e-1, start=0, end=1000000, width=150)
self.upperXInput = pn.widgets.FloatInput(name='Upper Bound', value=upperBound, step=1e-1, start=1, end=1000000, width=150)
self.xButton = pn.widgets.Button(name='Update x-axis', button_type='danger', width=150)
self.xButton.on_click(self.updateAxisButtonClick)
self.plotSet = pn.Column(pn.Row(pn.layout.HSpacer(), self.xyPane, pn.layout.HSpacer()),
pn.layout.Divider(margin=(-20, 0, 0, 0)),
pn.Row(pn.layout.HSpacer(),self.lowerXInput,
self.upperXInput,
self.xButton, pn.layout.HSpacer(),
))
self.savePlotButton = pn.widgets.Button(name='Save PNG', button_type='primary')
##This following 3 lines works when commenting out the FileDownload Section
self.saveVectorButton = pn.widgets.Button(name='Save CSV', button_type='warning')
self.saveVectorButton.on_click(self.saveCSVClicked)
self.saveRow = pn.Row(pn.layout.HSpacer(),self.saveVectorButton, self.savePlotButton, pn.layout.HSpacer())
##Comment out above 3 lines and then uncomment the next 3
## THIS DOES NOT WORK!
#fileName = 'xy.csv'
#self.fd = pn.widgets.FileDownload(callback = self.getDataStream, filename = fileName, auto = False)
#self.saveRow = pn.Row(pn.layout.HSpacer(),self.fd, self.savePlotButton, pn.layout.HSpacer())
self.tabs = pn.Tabs(('XY Plot', self.plotSet), ("Control Row", self.saveRow))
self[:] = [self.tabs]
def addTraces(self):
# fig = go.Figure(data=go.Scatter(x=XFT, y=slice1D_Mob, line=dict(color='red', width=2)))
for i, data in enumerate(self.dataList):
x,y = data
if i < len(self.dataLabels):
dataLabel = self.dataLabels[i]
else:
dataLabel = "Data Set %d"%(i+1)
self.xyFig.add_trace(go.Scatter(x=x, y=y, mode = 'lines', name = dataLabel))
self.xyFig.update_layout(legend=dict(orientation="h",
yanchor="bottom",
y=1.02,
xanchor="right",
x=1
))
def saveCSVClicked(self, event = None):
'''Direct stream of download failed due to an unknown recursion error'''
print("GO")
def getDataStream(self, event = None):
plotlyDict = self.xyFig.to_ordered_dict()
xyDF = getPlotlyDataframe(plotlyDict)
#https://panel.holoviz.org/reference/widgets/FileDownload.html
sio = StringIO()
xyDF.to_csv(sio)
sio.seek(0)
return sio
def updateAxisButtonClick(self, event=None):
l = self.lowerXInput.value
u = self.upperXInput.value
self.xyFig.update_xaxes(range=[l,u])
x = np.arange(20)
y = np.random.uniform(0,1, len(x))
xyb = XYBOX([[x,y], [x,-1*y]], xLabel = 'mz', yLabel = "Intensity (a.u.)")
xyb
Error Traceback:
---------------------------------------------------------------------------
RecursionError Traceback (most recent call last)
/opt/anaconda3/lib/python3.9/site-packages/IPython/core/formatters.py in __call__(self, obj)
700 type_pprinters=self.type_printers,
701 deferred_pprinters=self.deferred_printers)
--> 702 printer.pretty(obj)
703 printer.flush()
704 return stream.getvalue()
/opt/anaconda3/lib/python3.9/site-packages/IPython/lib/pretty.py in pretty(self, obj)
392 if cls is not object \
393 and callable(cls.__dict__.get('__repr__')):
--> 394 return _repr_pprint(obj, self, cycle)
395
396 return _default_pprint(obj, self, cycle)
/opt/anaconda3/lib/python3.9/site-packages/IPython/lib/pretty.py in _repr_pprint(obj, p, cycle)
698 """A pprint that just redirects to the normal repr function."""
699 # Find newlines and replace them with p.break_()
--> 700 output = repr(obj)
701 lines = output.splitlines()
702 with p.group():
/opt/anaconda3/lib/python3.9/site-packages/panel/layout/base.py in __repr__(self, depth, max_depth)
46 cls = type(self).__name__
47 params = param_reprs(self, ['objects'])
---> 48 objs = ['[%d] %s' % (i, obj.__repr__(depth+1)) for i, obj in enumerate(self)]
49 if not params and not objs:
50 return super().__repr__(depth+1)
/opt/anaconda3/lib/python3.9/site-packages/panel/layout/base.py in <listcomp>(.0)
46 cls = type(self).__name__
47 params = param_reprs(self, ['objects'])
---> 48 objs = ['[%d] %s' % (i, obj.__repr__(depth+1)) for i, obj in enumerate(self)]
49 if not params and not objs:
50 return super().__repr__(depth+1)
/opt/anaconda3/lib/python3.9/site-packages/panel/layout/base.py in __repr__(self, depth, max_depth)
46 cls = type(self).__name__
47 params = param_reprs(self, ['objects'])
---> 48 objs = ['[%d] %s' % (i, obj.__repr__(depth+1)) for i, obj in enumerate(self)]
49 if not params and not objs:
50 return super().__repr__(depth+1)
/opt/anaconda3/lib/python3.9/site-packages/panel/layout/base.py in <listcomp>(.0)
46 cls = type(self).__name__
47 params = param_reprs(self, ['objects'])
---> 48 objs = ['[%d] %s' % (i, obj.__repr__(depth+1)) for i, obj in enumerate(self)]
49 if not params and not objs:
50 return super().__repr__(depth+1)
/opt/anaconda3/lib/python3.9/site-packages/panel/layout/base.py in __repr__(self, depth, max_depth)
46 cls = type(self).__name__
47 params = param_reprs(self, ['objects'])
---> 48 objs = ['[%d] %s' % (i, obj.__repr__(depth+1)) for i, obj in enumerate(self)]
49 if not params and not objs:
50 return super().__repr__(depth+1)
/opt/anaconda3/lib/python3.9/site-packages/panel/layout/base.py in <listcomp>(.0)
46 cls = type(self).__name__
47 params = param_reprs(self, ['objects'])
---> 48 objs = ['[%d] %s' % (i, obj.__repr__(depth+1)) for i, obj in enumerate(self)]
49 if not params and not objs:
50 return super().__repr__(depth+1)
/opt/anaconda3/lib/python3.9/site-packages/panel/viewable.py in __repr__(self, depth)
550 def __repr__(self, depth=0):
551 return '{cls}({params})'.format(cls=type(self).__name__,
--> 552 params=', '.join(param_reprs(self)))
553
554 def __str__(self):
/opt/anaconda3/lib/python3.9/site-packages/panel/util.py in param_reprs(parameterized, skip)
208 elif isinstance(v, dict) and v == {}: continue
209 elif (skip and p in skip) or (p == 'name' and v.startswith(cls)): continue
--> 210 else: v = abbreviated_repr(v)
211 param_reprs.append('%s=%s' % (p, v))
212 return param_reprs
/opt/anaconda3/lib/python3.9/site-packages/panel/util.py in abbreviated_repr(value, max_length, natural_breaks)
157 vrepr = type(value).__name__
158 else:
--> 159 vrepr = repr(value)
160 if len(vrepr) > max_length:
161 # Attempt to find natural cutoff point
... last 9 frames repeated, from the frame below ...
/opt/anaconda3/lib/python3.9/site-packages/panel/layout/base.py in __repr__(self, depth, max_depth)
46 cls = type(self).__name__
47 params = param_reprs(self, ['objects'])
---> 48 objs = ['[%d] %s' % (i, obj.__repr__(depth+1)) for i, obj in enumerate(self)]
49 if not params and not objs:
50 return super().__repr__(depth+1)
RecursionError: maximum recursion depth exceeded