I am making an application where the users inputs data and the results are displayed on a chart. I want the plot to update each time new data is input, but updating the plot is not happening.
The plot is produce using the matlib plot library and the interface is created using the tkinter package. The way it is done is uses a function to create the plot as I want to call this several times. What I think could be the problem is the way the function returns the variable, but I can't put my finger on what or why this is wrong.
I have looked on here for solutions and none of the ones I have read seem to work. I have tried to update my axes, used patlibplot.figure instead of matlibplot.plot , adding canvas.daw() to the end and even tried to get a new way of producing the plot.
This is the function that creates the plot:
import matplotlib.pyplot as plt
def PlotConsumption(fuelEntry, plotConfig):
'''
Produce a line plot of the consumption
:params object fuelEntry: all of the fuel entry information
:params object plotConfig: the config object for the plot
'''
# create a data frame for plotting
df = pd.DataFrame(fuelEntry)
df = df.sort_values(["sort"]) # ensure the order is correct
df["dateTime"] = pd.to_datetime(df["date"], format="%d/%m/%Y") # create a date-time version of the date string
if plotConfig.xName == "date": # change the indexing for dates
ax = df.plot(x = "dateTime", y = plotConfig.yName, marker = '.', rot = 90, title = plotConfig.title)
else:
ax = df.plot(x = plotConfig.xName, y = plotConfig.yName, marker = '.', title = plotConfig.title)
# calculate the moving average if applicable
if plotConfig.moveCalc == True and plotConfig.move > 1 and len(df) > plotConfig.move:
newName = plotConfig.yName + "_ma"
df[newName] = df[plotConfig.yName].rolling(window = plotConfig.move).mean()
if plotConfig.xName == "date":
df.plot(x = "dateTime", y = newName, marker = "*", ax = ax)
else:
df.plot(x = plotConfig.xName, y = newName, marker = "*", ax = ax)
# tidy up the plot
ax.set(xlabel = plotConfig.xLabel, ylabel = plotConfig.yLabel)
plt.setp(ax.xaxis.get_majorticklabels(), rotation=90)
L=plt.legend()
L.get_texts()[0].set_text(plotConfig.yLegend)
if len(L.get_texts()) == 2: # only process if there is a moving average plot
L.get_texts()[1].set_text(plotConfig.moveLegend)
return plt # also tried with returning ax
And this is where it is used:
self.plot = displayCharts.PlotConsumption(self.data.carEntry["fuelEntry"], self.plotConfig)
#fig = plt.figure(1) # also tried creating fig this way
fig = self.plot.figure(1)
self.plotCanvas = FigureCanvasTkAgg(fig, master = self.master)
self.plotWidget = self.plotCanvas.get_tk_widget()
self.plotWidget.grid(row = 0, column = 1, rowspan = 3, sticky = tk.N)
fig.canvas.draw()
I expect the plot to update automatically but instead it does nothing. I know the data is read and processed as if you close the application, start it up again and then load the same data file the plot produced is correct.
I revisited some of the answers I initially looked at and picked my way through them and this one is the best I found and it actually does what I want!
There were a few sublte differences that I needed to change:
Create the figure and canvas to start with
Use these variables to create the plot
I copied the linked solution to see how it works and managed to get it to work. Now the next thing is to get it to plot with my data! But now that I have got it updating it should be easier to finish off.
Related
I have the following plotly code (below) which I have tried to turn into a function. I want to create 20 plots from 20 different dataframes (all the same structure), without repeating this plotly code each time.
For each new plot, I want to change the data source (data_frame = nema), and the name of each new plot (I want plots called fig1, fig2, fig3, fig4 etc.)
I know how to do this in R GGPLOT (https://thomasadventure.blog/posts/turning-your-ggplot2-code-into-a-function/), and I am hoping there is a similar solution in Python.
def nemaheatmap():
fig1 = px.scatter(data_frame = nema, x = "Longitude", y = "Latitude",
hover_name="Q0",
color="Q0",
color_continuous_scale= 'hot_r')
fig1.update_xaxes(zeroline=False)
fig1.update_yaxes(zeroline=False)
fig1.update_layout (title = 'Q0 values',
plot_bgcolor = 'rgb(173,216,230)',
xaxis_showgrid=False,
yaxis_showgrid=False)
pio.renderers.default = 'browser'
fig1.show()
nemaheatmap()
I know this question is probably asked frequently but I've been unable to find an answer. I have created a plot by using the following code:
matplotlib.pyplot.loglog(a,b, basex = 2, label = 'mergesort')
matplotlib.pyplot.ylabel('Time Taken')
matplotlib.pyplot.title('Time vs 2^i')
matplotlib.pyplot.legend()
This has created a graph in a new window with a suitable plot. I am now looking to create an entirely different plot which will pop up in a seperate window for another set of data. I have tried writing this code:
newfig = matplotlib.pyplot.loglog(e,f, basex = 2, label = "Timsort")
However when I try this, it just adds another label onto the previous graph. Can anyone help me figure out how to avoid this?
This is what "matplotlib.pyplot.show()" is for.
from matplotlib import pyplot as plt
import numpy as np
if __name__ == "__main__":
a = np.random.rand(100,1)
b = np.random.rand(100,1)
e = np.random.rand(100,1)
f = np.random.rand(100,1)
plt.loglog(a,b, basex = 2, label = 'mergesort')
plt.ylabel('Time Taken')
plt.title('Time vs 2^i')
plt.legend()
plt.show()
plt.loglog(e,f, basex = 2, label = "Timsort")
plt.show()
This will generate 2 separate figures/plots
You can use something like this:
fig,ax = plt.subplots(1,2)
and when plotting you can call them by their index. In this case:
ax[0].plot(your_data)
ax[1].plot(your_data_2)
The first argument of plt.subplots specifies the number of rows and second argument specifies the number of columns
I am learning to make color bars, and thus learning to make good use of plt.Normalize , I succeeded to make it work with scipy.stats.norm, but when tryin to use plt.norm, I found out that I have to do two things to make it work well :
defining vmin and vmax to -1.96 and 1.96 respectively,I guess that it's because they are the z value for 95% confidence interval, but I still don't precisely know why they have we have to set vmin and vmax to those values
dividing the standard deviation by sqrt( number of elements )
I don't understand why are those two points important for using the Norm. Any help is welcome ! thank you in advance
# Use the following data for this assignment:
%matplotlib notebook
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import scipy.stats as st
df = pd.DataFrame([np.random.normal(33500,150000,3650),
np.random.normal(41000,90000,3650),
np.random.normal(41000,120000,3650),
np.random.normal(48000,55000,3650)],
index=[1992,1993,1994,1995])
new_df = pd.DataFrame()
new_df['mean'] = df.mean(axis =1)
new_df['std'] = df.std(axis =1)
new_df['se'] = df.sem(axis= 1)
new_df['C_low'] = new_df['mean'] - 1.96 * new_df['se']
new_df['C_high'] = new_df['mean'] + 1.96 * new_df['se']
from scipy.stats import norm
import numpy as np
# First, Define a figure
fig = plt.figure()
# next define its the axis and create a plot
ax = fig.add_subplot(1,1,1)
# change the ticks
xticks = np.array(new_df.index,dtype= 'str')
# remove the top and right borders
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
# draw the bars in the axis
bars = ax.bar(xticks,new_df['mean'].values,
yerr = (1.96*new_df['se'],1.96*new_df['se']),
capsize= 10)
# define labels
plt.xlabel('YEARS',size = 14)
plt.ylabel('FREQUENCY',size = 14)
# Define color map
cmap = plt.cm.get_cmap('coolwarm')
# define scalar mappable
sm = plt.cm.ScalarMappable(cmap = cmap)
# draw the color bar
cbar = plt.colorbar(cmap = cmap, mappable =sm)
# define norm (will be used later to turn y to a value from 0 to 1 )
# define the events
class Cursor(object):
def __init__(self,ax):
self.ax = ax
self.lx = ax.axhline(color = 'c')
self.txt = ax.text(1,50000,'')
def mouse_movemnt(self,event):
#behaviour outside of the plot
if not event.inaxes:
return
#behavior inside the plot
y = event.ydata
self.lx.set_ydata(y)
for idx,bar in zip(new_df.index, bars):
norm = plt.Normalize(vmin =-1.96,vmax = 1.96)
mean = new_df.loc[idx,'mean']
err = new_df.loc[idx, 'se']
std = new_df.loc[idx,'std']/ np.sqrt(df.shape[1]) # not sure why we re dividing by np.sqrt(df.shape[1])
self.txt.set_text(f'Y = {round(y,2)} \n')
color_prob = norm( (mean - y)/std)
#color_prob = norm.cdf(y,loc = mean, scale = err) # you can also use this
bar.set_color( cmap(color_prob))
# connect the events to the plot
cursor = Cursor(ax)
plt.connect('motion_notify_event', cursor.mouse_movemnt)
None
After few hours of thinking, an explanation barged into my head and I was able to answer all of my inquiries,
first before answering the first point, I will answer the second one, the standard deviation was divided by the sqrt(nbr of element) because the resulting value is the standard error.
I will now move on to answering the first part:
(I can't embed images for now and I can't use latex either so I have to put links of the image instead). But here is the conclusion in advance, for all values within that confidence interval, the function (y-mean)/se will spit out a value within the range [−1.96,1.96]
answer of first part
Please, if I left something out or you have a better answer, share it with me.
I am trying to use interactive plotting with matplotlib to manually correct automatically-generated scatterplot data. (The program attempts to detect peaks in timeseries data and place a point on each one, but there are some artifacts in the timeseries data where peaks are inappropriately detected.)
I have successfully written code that plots the timeseries and overlays the scatterplot data, and then the user can add or remove points by clicking. However, I would like the script to save the edited point series as a new array and pass it to other functions etc. I can't really call the function without mouseclick events, so I don't know how to return the data.
Code is adapted from here: Interactively add and remove scatter points in matplotlib
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots(figsize=(16,4))
a_input = np.sin(range(100))*np.random.normal(20,10,100)
b_input = [ 5, 15, 25, 30, 40, 50, 75, 85]
a = plt.plot(range(len(a_input)),a_input,color='red')[0]
b = plt.scatter(b_input,a_input[b_input],color='grey',s=50,picker=5)
newpeaks = np.array([])
def add_or_remove_point(event):
global a
xydata_a = np.stack(a.get_data(),axis=1)
xdata_a = a.get_xdata()
ydata_a = a.get_ydata()
global b
xydata_b = b.get_offsets()
xdata_b = b.get_offsets()[:,0]
ydata_b = b.get_offsets()[:,1]
global newpeaks
#click x-value
xdata_click = event.xdata
#index of nearest x-value in a
xdata_nearest_index_a = (np.abs(xdata_a-xdata_click)).argmin()
#new scatter point x-value
new_xdata_point_b = xdata_a[xdata_nearest_index_a]
#new scatter point [x-value, y-value]
new_xydata_point_b = xydata_a[new_xdata_point_b,:]
if event.button == 1:
if new_xdata_point_b not in xdata_b:
#insert new scatter point into b
new_xydata_b = np.insert(xydata_b,0,new_xydata_point_b,axis=0)
#sort b based on x-axis values
new_xydata_b = new_xydata_b[np.argsort(new_xydata_b[:,0])]
#update b
b.set_offsets(new_xydata_b)
newpeaks = b.get_offsets()[:,0]
plt.draw()
elif event.button == 3:
if new_xdata_point_b in xdata_b:
#remove xdata point b
new_xydata_b = np.delete(xydata_b,np.where(xdata_b==new_xdata_point_b),axis=0)
#print(new_xdata_point_b)
#update b
b.set_offsets(new_xydata_b)
newpeaks = b.get_offsets()[:,0]
plt.draw()
print(len(newpeaks))
return newpeaks
fig.canvas.mpl_connect('button_press_event',add_or_remove_point)
plt.show()
Interestingly, b.get_offsets() does remain updated outside of the function, so if I reassign newpeaks manaully in the commandline after the script completes, I get the right updated data. But if I place the assignment
newpeaks = b.get_offsets()[:,0]
after the function in the script, it gets run first and does not update when the plot is closed. I would like it to either update automatically as soon as the plot is closed or update the global variable somehow instead of having to do it manually. Any advice is welcome!
I'm trying to go away from matlab and use python + matplotlib instead. However, I haven't really figured out what the matplotlib equivalent of matlab 'handles' is. So here's some matlab code where I return the handles so that I can change certain properties. What is the exact equivalent of this code using matplotlib? I very often use the 'Tag' property of handles in matlab and use 'findobj' with it. Can this be done with matplotlib as well?
% create figure and return figure handle
h = figure();
% add a plot and tag it so we can find the handle later
plot(1:10, 1:10, 'Tag', 'dummy')
% add a legend
my_legend = legend('a line')
% change figure name
set(h, 'name', 'myfigure')
% find current axes
my_axis = gca();
% change xlimits
set(my_axis, 'XLim', [0 5])
% find the plot object generated above and modify YData
set(findobj('Tag', 'dummy'), 'YData', repmat(10, 1, 10))
There is a findobj method is matplotlib too:
import matplotlib.pyplot as plt
import numpy as np
h = plt.figure()
plt.plot(range(1,11), range(1,11), gid='dummy')
my_legend = plt.legend(['a line'])
plt.title('myfigure') # not sure if this is the same as set(h, 'name', 'myfigure')
my_axis = plt.gca()
my_axis.set_xlim(0,5)
for p in set(h.findobj(lambda x: x.get_gid()=='dummy')):
p.set_ydata(np.ones(10)*10.0)
plt.show()
Note that the gid parameter in plt.plot is usually used by matplotlib (only) when the backend is set to 'svg'. It use the gid as the id attribute to some grouping elements (like line2d, patch, text).
I have not used matlab but I think this is what you want
import matplotlib
import matplotlib.pyplot as plt
x = [1,3,4,5,6]
y = [1,9,16,25,36]
fig = plt.figure()
ax = fig.add_subplot(111) # add a plot
ax.set_title('y = x^2')
line1, = ax.plot(x, y, 'o-') #x1,y1 are lists(equal size)
line1.set_ydata(y2) #Use this to modify Ydata
plt.show()
Of course, this is just a basic plot, there is more to it.Go though this to find the graph you want and view its source code.
# create figure and return figure handle
h = figure()
# add a plot but tagging like matlab is not available here. But you can
# set one of the attributes to find it later. url seems harmless to modify.
# plot() returns a list of Line2D instances which you can store in a variable
p = plot(arange(1,11), arange(1,11), url='my_tag')
# add a legend
my_legend = legend(p,('a line',))
# you could also do
# p = plot(arange(1,11), arange(1,11), label='a line', url='my_tag')
# legend()
# or
# p[0].set_label('a line')
# legend()
# change figure name: not sure what this is for.
# set(h, 'name', 'myfigure')
# find current axes
my_axis = gca()
# change xlimits
my_axis.set_xlim(0, 5)
# You could compress the above two lines of code into:
# xlim(start, end)
# find the plot object generated above and modify YData
# findobj in matplotlib needs you to write a boolean function to
# match selection criteria.
# Here we use a lambda function to return only Line2D objects
# with the url property set to 'my_tag'
q = h.findobj(lambda x: isinstance(x, Line2D) and x.get_url() == 'my_tag')
# findobj returns duplicate objects in the list. We can take the first entry.
q[0].set_ydata(ones(10)*10.0)
# now refresh the figure
draw()