How to invert Y axis of a ScatterPlotItem in PyQtGraph - python-3.x

I want to display points in a scatter plot with indices as if it were a matrix, origin top left inverting the Y axis:
0,0 1,0
0,1 1,1
instead of default where the origin is the lower left.
0,1 1,1
0,0 1,0
I tried:
plot = pg.ScatterPlotItem(...)
plot.getViewBox().invertY(True) # view box is NoneType
as suggested here Inverting the Y axis in PyQtGraph with no luck.

Referring to the official doc, when you have data displayed within axes, you end up (directly or indirectly) using a PlotItem object as a container, as shown in the figure.
Assuming you are plotting something like the following:
plot_widget = PlotWidget()
plot_item = plot_widget.plotItem
plot = pg.ScatterPlotItem(x=[0, 0, 1, 1], y=[0, 1, 0, 1], size=20.0)
plot_widget.addItem(plot) # Or equivalently, plot_item.addItem(plot)
You can simply do:
plot_item.invertY(True)
And here is the result:

Related

Putting text from one corner to the opposite one

I'd like my plot to have a background text to be stretched from one corner (say lower left) to the opposite corner. The x and y dimensions are not isometric and it's not a square plot, so a fixed angle of 45 degrees will not work.
So far I have the text starting in the correct corner. How can the text be rotated and stretched so it spans the entire plot?
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
# plot command not shown
ax.text(ax.get_xlim()[0], ax.get_ylim()[0] , 'PRELIMINARY' , rotation=45 )
To position something with respect to the subplot (the ax), it helps to work in axes coordinates. These go from 0,0 in the lower left to 1,1 in the top right. Putting the text at 0.5,0.5 would set it nicely centered.
To calculate the angle, one could divide the subplot's height in pixels by its width, then take the arc tangent, and convert from radians to degrees.
The optimal size for the text is harder to calculate. One would need to render it, measure it, change the font size and render it again. Or just manually try a few sizes until it looks OK.
Note that when the window size gets changed interactively, the text will stay nicely in the center, but the rotation will get a bit off.
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.text(0.5, 0.5, 'PRELIMINARY', transform=ax.transAxes, size=50,
rotation=np.degrees(np.arctan(ax.get_window_extent().height / ax.get_window_extent().width)),
ha='center', va='center')
plt.show()

matplotlib: controlling position of y axis label with multiple twinx subplots

I wrote a Python script based on matplotlib that generates curves based on a common timeline. The number of curves sharing the same x axis in my plot can vary from 1 to 6 depending on user options.
Each of the data plotted use different y scales and require a different axis for drawing. As a result, I may need to draw up to 5 different Y axes on the right of my plot. I found the way in some other post to offset the position of the axes as I add new ones, but I still have two issues:
How to control the position of the multiple axes so that the tick labels don't overlap?
How to control the position of each axis label so that it is placed vertically at the bottom of each axis? And how to preserve this alignment as the display window is resized, zoomed-in etc...
I probably need to write some code that will first query the position of the axis and then a directive that will place the label relative to that position but I really have no idea how to do that.
I cannot share my entire code because it is too big, but I derived it from the code in this example. I modified that example by adding one extra plot and one extra axis to more closely match what intend to do in my script.
import matplotlib.pyplot as plt
def make_patch_spines_invisible(ax):
ax.set_frame_on(True)
ax.patch.set_visible(False)
for sp in ax.spines.values():
sp.set_visible(False)
fig, host = plt.subplots()
fig.subplots_adjust(right=0.75)
par1 = host.twinx()
par2 = host.twinx()
par3 = host.twinx()
# Offset the right spine of par2. The ticks and label have already been
# placed on the right by twinx above.
par2.spines["right"].set_position(("axes", 1.2))
# Having been created by twinx, par2 has its frame off, so the line of its
# detached spine is invisible. First, activate the frame but make the patch
# and spines invisible.
make_patch_spines_invisible(par2)
# Second, show the right spine.
par2.spines["right"].set_visible(True)
par3.spines["right"].set_position(("axes", 1.4))
make_patch_spines_invisible(par3)
par3.spines["right"].set_visible(True)
p1, = host.plot([0, 1, 2], [0, 1, 2], "b-", label="Density")
p2, = par1.plot([0, 1, 2], [0, 3, 2], "r-", label="Temperature")
p3, = par2.plot([0, 1, 2], [50, 30, 15], "g-", label="Velocity")
p4, = par3.plot([0,0.5,1,1.44,2],[100, 102, 104, 108, 110], "m-", label="Acceleration")
host.set_xlim(0, 2)
host.set_ylim(0, 2)
par1.set_ylim(0, 4)
par2.set_ylim(1, 65)
host.set_xlabel("Distance")
host.set_ylabel("Density")
par1.set_ylabel("Temperature")
par2.set_ylabel("Velocity")
par3.set_ylabel("Acceleration")
host.yaxis.label.set_color(p1.get_color())
par1.yaxis.label.set_color(p2.get_color())
par2.yaxis.label.set_color(p3.get_color())
par3.yaxis.label.set_color(p4.get_color())
tkw = dict(size=4, width=1.5)
host.tick_params(axis='y', colors=p1.get_color(), **tkw)
par1.tick_params(axis='y', colors=p2.get_color(), **tkw)
par2.tick_params(axis='y', colors=p3.get_color(), **tkw)
par3.tick_params(axis='y', colors=p4.get_color(), **tkw)
host.tick_params(axis='x', **tkw)
lines = [p1, p2, p3, p4]
host.legend(lines, [l.get_label() for l in lines])
# fourth y axis is not shown unless I add this line
plt.tight_layout()
plt.show()
When I run this, I obtain the following plot:
output from above script
In this image, question 2 above means that I would want the y-axis labels 'Temperature', 'Velocity', 'Acceleration' to be drawn directly below each of the corresponding axis.
Thanks in advance for any help.
Regards,
L.
What worked for me was ImportanceOfBeingErnest's suggestion of using text (with a line like
host.text(1.2, 0, "Velocity" , ha="left", va="top", rotation=90,
transform=host.transAxes))
instead of trying to control the label position.

Set matplotlib legend markersize to a constant

I'm making a diagram using matplotlib, and it has plt.Circles and plt.axvlines to represent different shapes. I need a legend to describe these shapes, but the problem is the legend marker (the image part), changes size depending on the input, which looks awful. How do I set the size to a constant?
fig = plt.figure(figsize=(6.4, 6), dpi=200, frameon=False)
ax = fig.gca()
# 3 Circles, they produce different sized legend markers
ax.add_patch(plt.Circle((0,0), radius=1, alpha=0.9, zorder=0, label="Circle"))
ax.add_patch(plt.Circle((-1,0), radius=0.05, color="y", label="Point on Circle"))
ax.add_patch(plt.Circle((1, 0), radius=0.05, color="k", label="Opposite Point on Circle"))
# A vertical line which produces a huge legend marker
ax.axvline(0, ymin=0.5-0.313, ymax=0.5+0.313, linewidth=12, zorder=1, c="g", label="Vertical Line")
ax.legend(loc=2)
ax.set_xlim(-2,1.2) # The figsize and limits are meant to preserve the circle's shape
ax.set_ylim(-1.5, 1.5)
fig.show()
I've seen solutions including legend.legendHandles[0]._size or various assortments of that, and it doesn't seem to change the size regardless of the value I set
The legend markers for the circles are different in size because the first circle has no edgecolor, while the two other ones have an edgecolor set via color. You may instead set the facecolor of the circle. Alternatively, you can set the linewidth of all 3 circles equal.
The legend marker for the line is so huge because it simply copies the attribute from the line in the plot. If you want to use a different linewidth, you can update it via the respective legend handler.
import matplotlib.pyplot as plt
from matplotlib.legend_handler import HandlerLine2D
def update_prop(handle, orig):
handle.update_from(orig)
handle.set_linewidth(2)
fig, ax = plt.subplots(figsize=(6.4, 6), dpi=200, frameon=False)
# 3 Circles, set the facecolor instead of edge- and face-color
ax.add_patch(plt.Circle((0,0), radius=1, alpha=0.9, zorder=0, label="Circle"))
ax.add_patch(plt.Circle((-1,0), radius=0.05, facecolor="y", label="Point on Circle"))
ax.add_patch(plt.Circle((1, 0), radius=0.05, facecolor="k", label="Opposite Point on Circle"))
# Line, update the linewidth via
ax.axvline(0, ymin=0.5-0.313, ymax=0.5+0.313, linewidth=12, zorder=1, c="g", label="Vertical Line")
ax.legend(loc=2, handler_map={plt.Line2D:HandlerLine2D(update_func=update_prop)})
ax.set_xlim(-2,1.2)
ax.set_ylim(-1.5, 1.5)
plt.show()

arrow in a matplotlib polar plot by defining length and angle

I am trying to plot arrows in a polar plot in matplotlib. I shall have 2 sets, one set points towards the center of the plot, I can get it easily; one set should tangent to the circle of radius r at the point considered. Both length should be a function of the angle theta.
I can get there by some cumbersome trigonometric consideration for the second set, but I was wondering if there would be a more elegant (and readable) way to do so. Especially as this is for a demo for students.
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
matplotlib.rcParams['figure.figsize'] = (8,8)
theta=np.linspace(0,2*np.pi,13)
rayon=np.linspace(R,R,13)
ax = plt.subplot(111, projection='polar')
ax.plot(theta, rayon,'.',markersize=10)
ax.set_rmax(0.6)
plt.arrow(theta[1], 0.5, 0, -0.25, width = 0.015, edgecolor = 'red', lw = 3,head_width=0.1, head_length=0.05)
l=.5
plt.arrow(theta[1], 0.5, np.arctan(l/.5), (np.sqrt(.5**2+l**2)-.5), width = 0.015,
edgecolor = 'green', lw = 3,head_width=0.1, head_length=0.05)
plt.show()

Polar contour plot in Maxima

How can I make a polar contour plot in Maxima? Given an expression such as
exp(-r) * cos(phi)
I'd like to plot contours in the x-y plane which have the same value of the expression at all points along the contour.
I've tried
draw3d(cylindrical(exp(-r) * cos(phi), r, 0, 5, phi, 0, 2*%pi), contour=map))
but cylindrical plots r as a function of z and phi, not z as a function of r and phi. It would be nice to not have to convert manually to Cartesian coordinates.
contour_plot(exp(-r)*cos(phi), [r,0,2], [phi, 0, 2*%pi], [transform_xy, polar_to_xy],
[gnuplot_preamble, "set cntrparam levels 10;"]);
The polar_to_xy option interprets the first two variables as distance from the z axis and azimuthal angle.
What is the problem using something like
draw3d(explicit(20*exp(-x^2-y^2)-10,x,0,2,y,-3,3),
contour_levels = 15,
contour = map,
surface_hide = true) ;
I think that in that case is straigthforward to do it.

Resources