I have an embedded widget in a QT5 gui connected via PlotWidget.
I am trying to plot 2 streams of live data Voltage (self.p1) & Current (self.p2). Voltage on the left axis & Current on the right. So far I have each data stream associated with its relevant axis.
However my problem is that the Current plot (self.p2) is not in the correct area of the display. This particular plot appears in the upper left hand corner of the widget, it appears before the LHD axis. Its best to view the image to view the problem.
View Me
.
I know the problem lies in the setup function & self.p2 (Current) is being placed in the wrong location but my searching hasn't produced an answer.
Could someone please help?
Code used to generate graph, is called once on start up:
def pg_plot_setup(self): # not working still on left axis
self.p1 = self.graphicsView.plotItem
# x axis
self.p1.setLabel('bottom', 'Time', units='s', color='g', **{'font-size':'12pt'})
self.p1.getAxis('bottom').setPen(pg.mkPen(color='g', width=3))
# Y1 axis
self.p1.setLabel('left', 'Voltage', units='V', color='r', **{'font-size':'12pt'})
self.p1.getAxis('left').setPen(pg.mkPen(color='r', width=3))
self.p2 = pg.ViewBox()
self.p1.showAxis('right')
self.p1.scene().addItem(self.p2)
self.p1.getAxis('right').linkToView(self.p2)
self.p2.setXLink(self.p1)
# Y2 axis
self.p1.setLabel('right', 'Current', units="A", color='c', **{'font-size':'12pt'}) #<font>Ω</font>
self.p1.getAxis('right').setPen(pg.mkPen(color='c', width=3))
and code used to update display, is called via QTimer:
def update_graph_plot(self):
start = time.time()
X = np.asarray(self.graph_X, dtype=np.float32)
Y1 = np.asarray(self.graph_Y1, dtype=np.float32)
Y2 = np.asarray(self.graph_Y2, dtype=np.float32)
pen1=pg.mkPen(color='r',width=1.0)
pen2=pg.mkPen(color='c',width=1.0)
self.p1.plot(X,Y1,pen=pen1, name="V", clear=True)
self.p2.addItem(pg.PlotCurveItem(X,Y2,pen=pen2, name="I"))
I found the answer buried in here MultiplePlotAxes.py
adding this self.p2.setGeometry(self.p1.vb.sceneBoundingRect()) to function 'update_graph_plot' adjusts the size of viewbox each time the scene is updated, but it has to be in the update loop.
or add this self.p1.vb.sigResized.connect(self.updateViews) to function 'pg_plot_setup' as part of the setup, which shall then automatically call this function
def updateViews(self):
self.p2.setGeometry(self.p1.vb.sceneBoundingRect())
to resize viewbox (self.p2) each time self.p1 updates.
Related
I am currently making the Panel widgets that could manipulate 3D shaped with the use of PyVista / VTK extension, but I could not find a way to modify the camera viewpoint of another subplot.
Given an example in VTKInterative. It was found that Panel encapsulates pl.ren_win as vtkpan, and its camera position could access and edited through vtkpan.camera following the param.Parameterized manner. But when using two or more subplots. vtkpan.camera is sticking to the first subplot.
pl = pv.Plotter(shape=(1, 2))
pyramid = pv.Pyramid()
pl.subplot(0, 0) # A
actor_A = pl.add_mesh(pyramid, color='r')
pl.camera_position = np.random.random((3,3))
pl.set_scale(0.8, 0.8, 0.8)
pl.subplot(0, 1) # B
actor_B = pl.add_mesh(pyramid, color='b')
pl.camera_position = np.random.random((3,3))
pl.set_scale(0.8, 0.8, 0.8)
vtkpan = pn.panel(pl.ren_win, sizing_mode='fixed', width=400)
vtkpan
vtkpan.camera = {'position': np.array(vtkpan.camera['position']) * [-1],
'viewUp': np.array(vtkpan.camera['viewUp']) * [-1]}
actor_B.GetProperty().SetColor(np.random.random((3)))
vtkpan.synchronize() # change actor_B properties
By modifying vtkpan.camera params, only the left camera viewpoint was changed. Even though modifying VTK camera directly with list(pl.ren_win.GetRenderers())[0].GetActiveCamera().SetViewUp([...]) nothing has happened, suggesting that synchronize() only syncs the renderer's actors. I know that there is a workaround to separate it into 2 vtkpan. However, if there is a way, please tell me.
I have a plot that I have made which has two different categories that is subdvided into three different groups. I have made calculations of the mean and median for each of these groups, but when I try to add annotate the figures with these numbers, they end up printing on top of each other, when I want each figure within the plot to be annotated with its respective mean and median.
So my code to make this plot currently looks like this:
fig = px.violin(CVs,
y="cv %",
x="group",
color="method",
box=True,
points=False,
hover_data=CVs.columns)
for i in CVs['method'].unique():
for j in CVs['group'].unique():
mean, median = np.round(CVs.loc[CVs['method']==i].agg({'cv %':['mean', 'median']}), 2)['cv %'].values
fig.add_annotation(x=j, y=0,
yshift=-65,
text="Mean: {}%".format(mean),
font=dict(size=10),
showarrow=False)
fig.add_annotation(x=j, y=0,
yshift=-75,
text="Median: {}%".format(median),
font=dict(size=10),
showarrow=False)
fig.update_traces(meanline_visible=True)
fig.update_layout(template='plotly_white', yaxis_zeroline=False, height=fig_height, width=fig_width)
iplot(fig)
From what I have read in the documentation (https://plotly.com/python/text-and-annotations/), it seems like you need indicate the coordinates of the added annotation using the parameters x and y.
I have tried to adhere to these parameters by setting y to 0 (since the y axis is numerical), and setting x to the pertinent group along the x axis (which is a categorical). However, as one can tell from the plot above, this doesn't seem to work. I have also tried setting x to a value that increments with each iteration of the for loop, but all the values I have tried (e.g. 1, 10, 0.1) haven't worked, the annotations keep printing on top of each other, just at different places along the x axis.
I want to have one set of annotations under each figure. Does anyone know how I can set this up?
Based on what you used (yshift) to adjust the annotation, I have done the same using xshift to move each of the labels below their respective plot. Note that you have fig_height and fig_width which was not provided, so I let plotly choose the size. You may need to adjust the offset a bit if figure is different. Hope this works.
CVs = px.data.tips() ##Used tips db
CVs.rename(columns={'sex': 'group', 'day':'method', 'total_bill': 'cv %'}, inplace=True) ##Replaced to names you have
CVs = CVs[CVs.method != 'Thur'] ##Removed one as there were 4 days in tips
fig = px.violin(CVs,
y="cv %",
x="group",
color="method",
box=True,
points=False,
hover_data=CVs.columns)
x_shift = -100 ##Start at -100 to the left of the j location
for i in CVs['method'].unique():
for j in CVs['group'].unique():
mean, median = np.round(CVs.loc[CVs['method']==i].agg({'cv %':['mean', 'median']}), 2)['cv %'].values
fig.add_annotation(x=j, y=0,
yshift=-65, xshift = x_shift,
text="Mean: {}%".format(mean),
font=dict(size=10),
showarrow=False)
fig.add_annotation(x=j, y=0,
yshift=-75, xshift = x_shift,
text="Median: {}%".format(median),
font=dict(size=10),
showarrow=False)
x_shift = x_shift + 100 ##After each entry (healthy/sick in your case), add 100
fig.update_traces(meanline_visible=True)
fig.update_layout(template='plotly_white', yaxis_zeroline=False)#, height=fig_height, width=fig_width)
#iplot(fig)
Plot
I am plotting a fits image of a field of stars. I am then displaying circular apertures across the image on sufficiently bright stars. Next, I am trying to click on the star that I am interested in, and getting the brightness measurement out of the nearest circle. Finally, I want to use that brightness measurement for other calculations.
My problem is that the variable I declare to store the x,y coordinates of my click ("coords") seems to be getting called before I actually click, resulting in an empty array and errors.
A co-worker sent it to me many months ago, and once upon a time it worked flawlessly. But it seems that it's stopped working. It may be a result of updating some libraries/modules, but I can't be sure.
I have tried various combinations, including declaring "coords" in other function, in other locations, etc. I have tried changing the order of certain lines to perhaps get "coords" to be called later. I've also tried writing the code from the ground up, and have had many of the same errors. Because this specific code once worked, I attach it instead of my attempts.
To be honest, I don't fully understand the code, as I didn't write it. As a result, I don't understand what is being called when, or what any changes I've made actually do.
def plot_image(file, vmin=5, vmax=99, threshold=5, radius=25):
hdu=fits.open(file)
image = hdu[0].data
exptime = hdu[0].header['EXPTIME']
band = hdu[0].header['FILTERS']
airmass = hdu[0].header['AIRMASS']
readnoise = hdu[0].header['RN_01']
gain = hdu[0].header['GAIN_01']
obj = hdu[0].header['OBJECT']
sub = image[1500:2000,1500:2000]
bkg_sigma = mad_std(sub)
mean, median, std = sigma_clipped_stats(sub, sigma=3.0, maxiters=5)
daofind = photutils.DAOStarFinder(fwhm=2., threshold=threshold*bkg_sigma)
sources = daofind(sub - median)
positions = (sources['xcentroid'], sources['ycentroid'])
apertures = photutils.CircularAperture(positions, r=radius)
phot_table = photutils.aperture_photometry(sub - median, apertures)
pix = select(image, apertures)
print(pix)
if len(pix) == 2 or len(coords) == 2:
distance = np.sqrt((np.array(phot_table['xcenter'])-pix[0])**2 + (np.array(phot_table['ycenter'])-pix[1])**2)
star = np.argmin(dist)
counts = phot_table[star]['aperture_sum']
fluxfile = open('testfile.txt')
signal = (counts * gain) / exptime
err = np.sqrt(counts*gain + (readnoise**2*np.pi*radius**2))
else:
print('Pix length = 0')
def select(image, apertures, vmin = 5, vmax = 99):
global coords
coords = []
fig = plt.figure(figsize = (9,9))
ax = fig.add_subplot(111)
ax.imshow(image, cmap = 'gist_gray_r', origin='lower', vmin = np.percentile(image, vmin), vmax = np.percentile(image, vmax), interpolation='none')
apertures.plot(color='blue', lw=1.5, alpha=0.5, ax = ax)
ax.set_title('Hello')#label='Object: '+obj+'\nFilter: '+band)
cid = fig.canvas.mpl_connect('button_press_event', onclick)
plt.show()
fig.canvas.mpl_disconnect(cid)
if None in coords:
return [np.nan,np.nan]
else:
return np.round(coords)
def onclick(event):
x = event.xdata
y = event.ydata
global coords
coords = [x, y]
plt.close()
return
def closeonclick(event):
print('Close On Click')
plt.close()
return
Expected Result: The image is displayed with blue apertures overlaid. Then, I click on the desired star and the coordinates I click are stored to "coords" and printed to the console. The window displaying the image is closed alongside the previous step, as well. Finally, using those coordinates, it finds the nearest aperture and does some science with the resulting brightness.
Actual Result: "coords" is immediately printed (an empty list). Immediately after, the image is displayed. Clicking it does nothing. It doesn't change the value of "coords", nothing else is printed, nor does the window close.
I'll come back and delete this if it isn't right (I'd comment if I had the reputation), but it looks like you have to define a global variable outside any functions first, then use the keyword before the variable name inside a function to change its scope. Try moving "coords = []" outside your functions (the list will no longer be empty after the first call to "onclick", but each new click should replace the coordinates so it shouldn't be an issue).
Ref: https://www.programiz.com/python-programming/global-keyword
I am trying to create a widget callback function that resets the entire plot to its initialized state but it is not working. I expect the users to click Sample as many times as they want then be able to reset the vbar plot to its initialized state.
I have already created the python callback function and used some print functions to debug a bit but the plot is not resetting.
plot2 = figure(plot_height=400, plot_width=int(1.618*600), title="Block Party",
tools="crosshair,reset,save",
x_range=[0, 11], y_range=[0, max(counts)])
plot2.vbar(x='x', top='y', source=source2, width=0.8)
"""
Set up widgets
"""
title2 = TextInput(title="Plot Title", value='Blocks')
sample = Button(label="Sample", button_type="success")
reset = Button(label="Reset", button_type="success")
# Callback
def reset_window_2():
global source2
print("I was clicked")
np.random.seed(42)
unique, counts = np.unique(np.random.randint(low=1, high=11, size=100), return_counts=True)
source2 = ColumnDataSource(data=dict(x=unique, y=counts))
plot2 = figure(plot_height=400, plot_width=int(1.618 * 600), title="Block Party",
tools="crosshair,reset,save",
x_range=[0, 11], y_range=[0, max(counts)])
plot2.vbar(x='x', top='y', source=source2, width=0.618)
reset.js_on_click(CustomJS(args=dict(p=plot2), code="""
plots2.reset.emit()
"""))
print("Check 2")
reset.on_click(reset_window_2)
# Set up layouts and add to document
inputs1 = column(title1, sigma, mu)
inputs2 = column(title2, sample, reset)
tab1 = row(inputs1, plot1, width=int(phi*400))
tab2 = row(inputs2, plot2, width=int(phi*400))
tab1 = Panel(child=tab1, title="Like a Gauss")
tab2 = Panel(child=tab2, title="Sampling")
tabs = Tabs(tabs=[tab1, tab2])
curdoc().add_root(tabs)
curdoc().title = "Sample Dash"
The print functions occur but the reset does not. Any ideas on how to reset the entire plot to init?
Bokeh plots don't show up merely by virtue of being created. In Bokeh server apps, they have to be put in a layout and added to curdoc. You presumably did this:
curdoc.add_root(plot2)
If you want to replace plot2 in the browser, it has to be replaced in curdoc. The plot2 you create in your callback is just a local variable in a function. It pops into existence for the duration of the function, only exists inside the function, then gets thrown away when the function ends. You haven't actually done anything with it. To actually replace in curdoc, it will be easier to store the plot in an explicit layout:
lauyot = row(plot)
curdoc().add_root(layout)
Then in your callback, you can replace what is in the layout:
layout.children[0] = new_plot
All that said, I would actually advise against doing things this way. The general, always-applicable best-practice for Bokeh is:
Always make the smallest change possible.
A Bokeh plot has dozen of sub-components (ranges, axes, glyphs, data sources, tools, ...) Swapping out an entire plot is a very heavyweight operation Instead, what you should do, is just update the data source for the plot you already have, to restore the data it started with:
source2.data = original_data_dict # NOTE: set from plain python dict
That will restore the bars to their original state, making the smallest change possible. This is the usage Bokeh has been optimized for, both in terms of efficient internal implementation, as well as efficient APIs for coding.
To summarise the important bit:
I have a function which plots a circle on a matplotlib graph. Every time I recall the function I simply resize the circle (using set_radius), as It always needs to be in the same positon on the graph (in the centre). This way It doesn't get too messy
I want to do the same thing with an ellipse patch but this time be able to change the height, width and angle it is at. However I can't find any equivalent of set_radius
def Moment_Of_Inertia(self):
"""Plot the moment of Inertia ellipse, with the ratio factor """
# my code to get ellipse/circle properties
self.limitradius = findSBradius(self.RawImage,self.SBLimit)[0]
MoIcall = mOinertia(self.RawImage,self.limitradius)
self.ratio=MoIcall[0] # get the axes ratio
self.height=1
Eigenvector = MoIcall[1]
self.EllipseAngle np.degrees(np.arctanh((Eigenvector[1]/Eigenvector[0])))
# This is the part I am not sure how to do
self.MoIellipse.set(width=self.ratio*15)
self.MoIellipse.set(height=self.height*15)
self.MoIellipse.set(angle= self.EllipseAngle)
# It works with a circle patch
self.circleLimit.set_radius(self.limitradius)
self.circleLimit.set_visible(True)
self.MoIellipse.set_visible(True)
self.canvas.draw()
If my code is a bit out of context I am happy to explain more, I am trying to embed a matplotlib graph in a tkinter window. both patches are already initialized in the constructor and I just want to resize them.
This answer assumes that the question is about the Ellipse from matplotlib.patches.Ellipse.
This has attributes width, height and angle. You can set those attributes as
ellipse = matplotlib.patches.Ellipse((0,0),.5,.5)
ellipse.width = 1
ellipse.height = 2
ellipse.angle = 60
As for any other python object, you can also use setattr, like
setattr(ellipse,"width", 2)
Some complete example:
import matplotlib.pyplot as plt
import matplotlib.widgets
class sliderellipse(matplotlib.widgets.Slider):
def __init__(self,*args,**kwargs):
self.ellipse = kwargs.pop("ellipse", None)
self.attr = kwargs.pop("attr", "width")
matplotlib.widgets.Slider.__init__(self,*args,**kwargs)
self.on_changed(self.update_me)
def update_me(self,val=None):
setattr(self.ellipse,self.attr, val)
self.ax.figure.canvas.draw_idle()
fig, axes = plt.subplots(nrows=4,
gridspec_kw={"height_ratios" : [1,.05,.05,.05],
"hspace" : 0.5})
axes[0].axis([-1,1,-1,1])
axes[0].set_aspect("equal")
ellipse = matplotlib.patches.Ellipse((0,0),.5,.5)
axes[0].add_patch(ellipse)
labels = ["width", "height","angle"]
maxs = [2,2,360]
sl = []
for ax,lab,m in zip(axes[1:],labels,maxs):
sl.append(sliderellipse(ax,lab,0,m,ellipse=ellipse,attr=lab))
plt.show()