Python Matplotlib show the cursor when hovering on graph - python

I want to plot the close price of INS as below and it works. Then I want to add the cursor when hovering on the graph. I follow the demo from https://matplotlib.org/3.1.0/gallery/misc/cursor_demo_sgskip.html and that is what I want.
But when I added these lines into the code, it shows value error. Initially I use epoch time as x-axis and I thought that is the problem, so I convert epoch time to datetime but it is still not working and plot nothing.
snap_cursor = SnaptoCursor(ax, secs, df['close'])
fig.canvas.mpl_connect('motion_notify_event', snap_cursor.mouse_move)
Traceback (most recent call last): File
"c:\Users\Sam.vscode\extensions\ms-python.python-2020.6.89148\pythonFiles\ptvsd_launcher.py",
line 48, in
main(ptvsdArgs) File "c:\Users\Sam.vscode\extensions\ms-python.python-2020.6.89148\pythonFiles\lib\python\old_ptvsd\ptvsd_main_.py",
line 432, in main
run() File "c:\Users\Sam.vscode\extensions\ms-python.python-2020.6.89148\pythonFiles\lib\python\old_ptvsd\ptvsd_main_.py",
line 316, in run_file
runpy.run_path(target, run_name='main') File "C:\Users\Sam\AppData\Local\Programs\Python\Python36\lib\runpy.py",
line 263, in run_path
pkg_name=pkg_name, script_name=fname) File "C:\Users\Sam\AppData\Local\Programs\Python\Python36\lib\runpy.py",
line 96, in _run_module_code
mod_name, mod_spec, pkg_name, script_name) File "C:\Users\Sam\AppData\Local\Programs\Python\Python36\lib\runpy.py",
line 85, in _run_code
exec(code, run_globals) File "c:\Users\Sam\OneDrive\Project\stock\test.py", line 89, in
plt.gcf().autofmt_xdate() File "C:\Users\Sam\AppData\Local\Programs\Python\Python36\lib\site-packages\matplotlib\figure.py",
line 632, in autofmt_xdate
for label in self.axes[0].get_xticklabels(which=which): File "C:\Users\Sam\AppData\Local\Programs\Python\Python36\lib\site-packages\matplotlib\axes_base.py",
line 3355, in get_xticklabels
return self.xaxis.get_ticklabels(minor=minor, which=which) File "C:\Users\Sam\AppData\Local\Programs\Python\Python36\lib\site-packages\matplotlib\axis.py",
line 1320, in get_ticklabels
return self.get_majorticklabels() File "C:\Users\Sam\AppData\Local\Programs\Python\Python36\lib\site-packages\matplotlib\axis.py",
line 1276, in get_majorticklabels File
"C:\Users\Sam\AppData\Local\Programs\Python\Python36\lib\site-packages\matplotlib\axis.py",
line 1431, in get_major_ticks
numticks = len(self.get_majorticklocs()) File "C:\Users\Sam\AppData\Local\Programs\Python\Python36\lib\site-packages\matplotlib\axis.py",
line 1348, in get_majorticklocs
return self.major.locator() File "C:\Users\Sam\AppData\Local\Programs\Python\Python36\lib\site-packages\matplotlib\dates.py",
line 1338, in call
self.refresh() File "C:\Users\Sam\AppData\Local\Programs\Python\Python36\lib\site-packages\matplotlib\dates.py",
line 1364, in refresh
dmin, dmax = self.viewlim_to_dt() File "C:\Users\Sam\AppData\Local\Programs\Python\Python36\lib\site-packages\matplotlib\dates.py",
line 1098, in viewlim_to_dt
.format(vmin))
ValueError: view limit minimum -36879.777083333334 is less than 1 and
is an invalid Matplotlib date value. This often happens if you pass a
non-datetime value to an axis that has datetime units
class SnaptoCursor(object):
"""
Like Cursor but the crosshair snaps to the nearest x, y point.
For simplicity, this assumes that *x* is sorted.
"""
def __init__(self, ax, x, y):
self.ax = ax
self.lx = ax.axhline(color='k') # the horiz line
self.ly = ax.axvline(color='k') # the vert line
self.x = x
self.y = y
# text location in axes coords
self.txt = ax.text(0.7, 0.9, '', transform=ax.transAxes)
def mouse_move(self, event):
if not event.inaxes:
return
x, y = event.xdata, event.ydata
indx = min(np.searchsorted(self.x, x), len(self.x) - 1)
x = self.x[indx]
y = self.y[indx]
# update the line positions
self.lx.set_ydata(y)
self.ly.set_xdata(x)
self.txt.set_text('x=%1.2f, y=%1.2f' % (x, y))
print('x=%1.2f, y=%1.2f' % (x, y))
self.ax.figure.canvas.draw()
data = td.pricehistory("INS")
df = pd.DataFrame(data['candles'])
df['datetime'] = df.apply(lambda x: datetime.datetime.fromtimestamp(x['datetime']/1000),axis=1)
secs = df['datetime']
fig, ax = plt.subplots(1, 1)
ax.plot(secs,df['close'])
snap_cursor = SnaptoCursor(ax, secs, df['close'])
fig.canvas.mpl_connect('motion_notify_event', snap_cursor.mouse_move)
plt.gcf().autofmt_xdate()
myFmt = mdates.DateFormatter('%d-%m-%y %H:%M:%S')
plt.gca().xaxis.set_major_formatter(myFmt)
plt.show()

I'm not familiar with SnaptoCursor, but have you considered using plotly instead?
Saves many lines of code and is very user-friendly:
# install plotly library
!pip install plotly
# import express module
import plotly.express as px
# plot interactive line chart
fig = px.line(data=df, x="datetime", y="close")
fig.show()
It's very flexible too. Here is the documentation: https://plotly.com/python/line-charts/
You can interact with the plots using the cursor too, for example:
Hope this helps, though of course it's different to what you were trying to do!

Related

Intermittent error while plotting graph using matplotlib "RuntimeError: main thread is not in main loop"

I am trying to pull data from different end points using my python code and feeding the same data to plot a graph using matplotlib. I dont have any problem in reading the data but when i invoke the methods to plot graph by feeding the data i see intermittent error caused by matplot lib. below are the error details.
Traceback (most recent call last):
File "C:\Python35\lib\site-packages\slackbot\dispatcher.py", line 55, in _dispatch_msg_handler
func(Message(self._client, msg), *args)
File "C:\PycharmProjects\SlackBot\src\plugins\bot_response.py", line 248, in checkmarx
draw_chart.riskscore_bar(top_riskscore, project_name, "output_files", "riskscore_bar.png")
File "C:\PycharmProjects\SlackBot\src\drawchart.py", line 111, in riskscore_bar
fig, ax = plt.subplots()
File "C:\Python35\lib\site-packages\matplotlib\pyplot.py", line 1202, in subplots
fig = figure(**fig_kw)
File "C:\Python35\lib\site-packages\matplotlib\pyplot.py", line 535, in figure
**kwargs)
File "C:\Python35\lib\site-packages\matplotlib\backends\backend_tkagg.py", line 81, in new_figure_manager
return new_figure_manager_given_figure(num, figure)
File "C:\Python35\lib\site-packages\matplotlib\backends\backend_tkagg.py", line 98, in new_figure_manager_given_figure
icon_img = Tk.PhotoImage(file=icon_fname)
File "C:\Python35\lib\tkinter\__init__.py", line 3403, in __init__
Image.__init__(self, 'photo', name, cnf, master, **kw)
File "C:\Python35\lib\tkinter\__init__.py", line 3359, in __init__
self.tk.call(('image', 'create', imgtype, name,) + options)
RuntimeError: main thread is not in main loop
I have tried looking into other cases from stackoverflow with same error messages, but it didnt help me fix this. Below is my code snippet that invokes an error.
def riskscore_bar(self, top_riskscore, project_id, output_folder, output_filename):
logger.debug("Inside method plotgraph in drawchart.py.")
y_pos = np.arange(len(project_id))
width = .4
fig, ax = plt.subplots()
graph = ax.bar(y_pos+1, top_riskscore, width, color='#feb308')
ax.set_ylabel('Risk Score')
ax.set_title('Summary')
ax.set_xticks(y_pos + 1)
ax.set_xticklabels(project_id,fontsize=5, rotation=45 )
def autolabel(rects):
for rect in rects:
height = rect.get_height()
ax.text(rect.get_x() + rect.get_width()/2., 1.001*height,
'%d' % int(height),
ha='center', va='bottom')
autolabel(graph)
pylab.savefig(os.path.join(os.path.abspath(os.path.dirname(__file__)), '..', output_folder,output_filename))
The error seems to occur at "fig, ax = plt.subplots()". any ideas on how this can be fixed?
this can be solved by adding plt.switch_backend('agg') below right after you import matplotlib.pyplot as plt. As shown below
import matplotlib.pyplot as plt
plt.switch_backend('agg')

matplotlib contourf() raises exception "ValueError: array is too big."

I'm trying to plot a data set (777 x 576) with 3D contours. This appears to be working fine until I try to place the contour under the 3d plot.
from mpl_toolkits.mplot3d import axes3d
from matplotlib import cm, pyplot
import numpy
X,Y = numpy.mgrid[:len(data), :len(data[0])]
fig = pyplot.figure('''figsize=(20, 10), dpi=800''')
ax = fig.gca(projection='3d')
ax.plot_surface(X,
Y,
data,
rstride=100,
cstride=100,
alpha=0.3,
linewidths=(.5,),
antialiased=True,
)
# cset = ax.contourf(X, Y, data, zdir='z', offset=130, cmap=cm.coolwarm)
ax.set_xlim(800, 0)
ax.set_ylim(0, 600)
ax.set_zlim(130, 170)
plt.show()
That is to say that uncommenting the ax.contourf(...) line causes the following exception:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Python27\lib\lib-tk\Tkinter.py", line 1470, in __call__
return self.func(*args)
File "C:\Python27\lib\site-packages\matplotlib\backends\backend_tkagg.py", line 276, in resize
self.show()
File "C:\Python27\lib\site-packages\matplotlib\backends\backend_tkagg.py", line 348, in draw
FigureCanvasAgg.draw(self)
File "C:\Python27\lib\site-packages\matplotlib\backends\backend_agg.py", line 451, in draw
self.figure.draw(self.renderer)
File "C:\Python27\lib\site-packages\matplotlib\artist.py", line 55, in draw_wrapper
draw(artist, renderer, *args, **kwargs)
File "C:\Python27\lib\site-packages\matplotlib\figure.py", line 1034, in draw
func(*args)
File "C:\Python27\lib\site-packages\mpl_toolkits\mplot3d\axes3d.py", line 248, in draw
for col in self.collections]
File "C:\Python27\lib\site-packages\mpl_toolkits\mplot3d\art3d.py", line 456, in do_3d_projection
cedge = cedge.repeat(len(xyzlist), axis=0)
ValueError: array is too big.
In case this means anything:
len(cedge) == 35656
len(xyzlist) == 8914
35656 * 8914 = 317837584
Is there something I need to set to accommodate my data set?

python cluster animation using matplotlib and different colors

I'm trying to create an animation of my clustering where each cluster is a different color on a scatter plot and there are several clusters per plot. The next plot to show has the same clusters and colors but a different location.
I've searched around and so far have this (timePeriodData refers to a list of cluster objects. Each list contains the clusters for that time/plot and the cluster object has the x, y, and color data.):
plt.ion()
fig,ax = subplots(1,1)
ax.set_aspect('equal')
fig.canvas.draw()
background = fig.canvas.copy_from_bbox(ax.bbox)
for timeData in timePeriodData:
plt.clf()
#print timeperiod
for cluster in timeData:
#ax.scatter(cluster.dataX,cluster.dataY, color = cluster.color)
# restore background
plt.scatter(cluster.dataX,cluster.dataY, color = cluster.color)
#plt.show()
#fig.canvas.restore_region(background)
# redraw just the points
#ax.draw_artist()
# fill in the axes rectangle
#fig.canvas.blit(ax.bbox)
plt.draw()
time.sleep(1)
But the plot window eventually hangs and doesn't continue plotting after about 5 plots. You can see in the comments I also tried to save the background and update that, but I am doing something wrong there. What exactly needs to be updated confuses me and I can't find good documentation on what needs to be passed to draw_artist.
I've also tried to use the animation feature of matplotlib, but can't seem to get it working with what I am trying to do.
timeperiods = 4
fig, ax = plt.subplots()
#fig = plt.figure()
def update(timePeriod, *args):
clusterNum = 0
for cluster in args[timePeriod]:
scat = ax.scatter(cluster.dataX,cluster.dataY, color = cluster.color)
clusterNum +=1
#scat = ax.scatter(dataX, dataY, c = scalarMap.to_rgba(values[clusterNum]))
return scat
ani = animation.FuncAnimation(fig, update, frames=xrange(timeperiods),
fargs=(timePeriodData), blit = True)
plt.show()
It says something is not iterable, but I'm sure what part it is referring to. I'm also not sure if I can continually assign scat to ax.scatter and it will add the points or not. but I assume so. Error message is:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Python27\lib\lib-tk\Tkinter.py", line 1470, in __call__
return self.func(*args)
File "C:\Python27\lib\site-packages\matplotlib\backends\backend_tkagg.py", line 276, in resize
self.show()
File "C:\Python27\lib\site-packages\matplotlib\backends\backend_tkagg.py", line 348, in draw
FigureCanvasAgg.draw(self)
File "C:\Python27\lib\site-packages\matplotlib\backends\backend_agg.py", line 451, in draw
self.figure.draw(self.renderer)
File "C:\Python27\lib\site-packages\matplotlib\artist.py", line 55, in draw_wrapper
draw(artist, renderer, *args, **kwargs)
File "C:\Python27\lib\site-packages\matplotlib\figure.py", line 1040, in draw
self.canvas.draw_event(renderer)
File "C:\Python27\lib\site-packages\matplotlib\backend_bases.py", line 1693, in draw_event
self.callbacks.process(s, event)
File "C:\Python27\lib\site-packages\matplotlib\cbook.py", line 527, in process
proxy(*args, **kwargs)
File "C:\Python27\lib\site-packages\matplotlib\cbook.py", line 405, in __call__
return mtd(*args, **kwargs)
File "C:\Python27\lib\site-packages\matplotlib\animation.py", line 832, in _end_redraw
self._post_draw(None, self._blit)
File "C:\Python27\lib\site-packages\matplotlib\animation.py", line 778, in _post_draw
self._blit_draw(self._drawn_artists, self._blit_cache)
File "C:\Python27\lib\site-packages\matplotlib\animation.py", line 787, in _blit_draw
for a in artists:
TypeError: 'PathCollection' object is not iterable
Any help getting a working system would be great!

Matplotlib animating multiple lines and text

Thanks for taking time to read my question, first time I have posted on SO so here goes...
I am plotting time series data in an animation using matplotlib.animation.FuncAnimation
I am plotting more than one line by looping over a list and slicing data from a numpy array.
This works fine, but I also want to add text to the plot which is animated and describes the frame number.
I have included sample code below.
I am trying returning a list of line objects and a text object from the animate function.
I receive an attribute error when I try to do this:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Python27\lib\lib-tk\Tkinter.py", line 1470, in __call__
return self.func(*args)
File "C:\Python27\lib\lib-tk\Tkinter.py", line 531, in callit
func(*args)
File "C:\Python27\lib\site-packages\matplotlib\backends\backend_tkagg.py", line 141, in _on_timer
TimerBase._on_timer(self)
File "C:\Python27\lib\site-packages\matplotlib\backend_bases.py", line 1117, in _on_timer
ret = func(*args, **kwargs)
File "C:\Python27\lib\site-packages\matplotlib\animation.py", line 773, in _step
still_going = Animation._step(self, *args)
File "C:\Python27\lib\site-packages\matplotlib\animation.py", line 632, in _step
self._draw_next_frame(framedata, self._blit)
File "C:\Python27\lib\site-packages\matplotlib\animation.py", line 652, in _draw_next_frame
self._post_draw(framedata, blit)
File "C:\Python27\lib\site-packages\matplotlib\animation.py", line 675, in _post_draw
self._blit_draw(self._drawn_artists, self._blit_cache)
File "C:\Python27\lib\site-packages\matplotlib\animation.py", line 688, in _blit_draw
if a.axes not in bg_cache:
AttributeError: 'list' object has no attribute 'axes'
But, say that I have a list of two line objects, if I return the objects individually, e.g.
return lines[0],lines[1], timetext
I receive no errors.
Any ideas?
Cheers
Vanessa
import numpy
import matplotlib.pyplot as plt
import matplotlib.animation as animation
npdata = numpy.random.randint(100, size=(5,6,10))
plotlays, plotcols = [2,5], ["black","red"]
fig = plt.figure()
ax = plt.axes(xlim=(0, numpy.shape(npdata)[0]), ylim=(0, numpy.max(npdata)))
timetext = ax.text(0.5,50,'')
lines = []
for index,lay in enumerate(plotlays):
lobj = ax.plot([],[],lw=2,color=plotcols[index])[0]
lines.append(lobj)
def init():
for line in lines:
line.set_data([],[])
return lines
def animate(i):
timetext.set_text(i)
x = numpy.array(range(1,npdata.shape[0]+1))
for lnum,line in enumerate(lines):
line.set_data(x,npdata[:,plotlays[lnum]-1,i])
return lines, timetext
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames=numpy.shape(npdata)[1], interval=100, blit=True)
plt.show()
def animate(i):
timetext.set_text(i)
x = numpy.array(range(1,npdata.shape[0]+1))
for lnum,line in enumerate(lines):
line.set_data(x,npdata[:,plotlays[lnum]-1,i])
return lines, timetext # <- returns a tuple of form (list, artist)
change this to
return tuple(lines) + (timetext,)
or something similar so that you return an iterable of artists from animate.

Memory full Python plotting 3d

I'm getting some memory problems from plotting images in a loop. How do I delete the old ones?
Error:
Traceback (most recent call last):
File "C:\Users\Alex\Dropbox\code stuff\solarsystem.py", line 69, in <module>
fig = plt.figure()
File "C:\Python27\lib\site-packages\matplotlib\pyplot.py", line 343, in figure
**kwargs)
File "C:\Python27\lib\site-packages\matplotlib\backends\backend_tkagg.py", line 79, in new_figure_manager
return new_figure_manager_given_figure(num, figure)
File "C:\Python27\lib\site-packages\matplotlib\backends\backend_tkagg.py", line 106, in new_figure_manager_given_figure
canvas = FigureCanvasTkAgg(figure, master=window)
File "C:\Python27\lib\site-packages\matplotlib\backends\backend_tkagg.py", line 225, in __init__
master=self._tkcanvas, width=w, height=h)
File "C:\Python27\lib\lib-tk\Tkinter.py", line 3306, in __init__
Image.__init__(self, 'photo', name, cnf, master, **kw)
File "C:\Python27\lib\lib-tk\Tkinter.py", line 3262, in __init__
self.tk.call(('image', 'create', imgtype, name,) + options)
TclError: not enough free memory for image buffer
Script:
count = 0
xy = bodies[0][2]
x = [[a[0] for a in bodies[0][2]]]
y = [[a[1] for a in bodies[0][2]]]
z = [[a[2] for a in bodies[0][2]]]
for i in range(1,nbodies):
x.append([a[0] for a in bodies[i][2]])
y.append([a[1] for a in bodies[i][2]])
z.append([a[2] for a in bodies[i][2]])
for j in range(0,len(bodies[0][2])-1,10):
Xc = [[x[0][j]]]
Yc = [[y[0][j]]]
Zc = [[z[0][j]]]
for k in range(1,nbodies):
Xc.append([x[k][j]])
Yc.append([y[k][j]])
Zc.append([z[k][j]])
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
for l in range(len(Xc)):
ax.scatter( Xc[l], Yc[l], Zc[l], c=(i/nbodies,i/nbodies,i/nbodies))
ax.axis([-400, 400, -400, 400])
ax.set_zlim(-400, 400)
pylab.savefig('images/img'+str(count))
pylab.clf()
count += 1
percent = (j/(len(bodies[0][2])-1.))*100
if percent % 10 ==0:
print percent
If you're generating a number of pictures, you need to not only call the
plt.clf()
function between figures, but also the
plt.close()
function between pictures (i.e. after the for j in range(0,len(bodies[0][2])-1,10):
loop completes)
You can delete the fig instance (when you're done with it) using
del fig
and you can delete the files with os.unlink
os.unlink('images/img'+str(count))

Categories