I am trying to plot a circle on a grid. The code that I have written is as follows:
import pyplot as plt
from pyplot import Figure, subplot
fig=plt.figure(1)
plt.axis([0,400,0,400])
ax=fig.add_subplot(1,1,1)
circ=plt.Circle((200,200), radius=10, color='g', fill=False)
ax.add_patch(circ)
plt.show()
Now, I want the center of the circle to be the center of the graph, that is, (200,200) in this example. In case of other cases I want it to automatically choose the centre depending on the size that us set. Can this be in some way?
To make it clearer I want to get the x-axis and the y-axis range so as to find the mid point of the grid. How do I proceed?
Your x-axis and y-axis ranges are in your code right here:
plt.axis([0,400,0,400])
So all you would need is leverage on this a bit like so:
x_min = 0
x_max = 400
y_min = 0
y_max = 400
circle_x = (x_max-x_min)/2.
circle_y = (y_max-y_min)/2.
circ=plt.Circle((circle_x,circle_y), radius=10, color='g', fill=False)
If you want to catch x_min etc. from the command prompt then read out sys.argv.
What you want may be ax.transAxes, here's the tutorial for coordinates transformation.
ax.transAxes means the coordinate system of the Axes; (0,0) is bottom left of the axes, and (1,1) is top right of the axes.
fig=plt.figure(1)
plt.axis([0,400,0,400])
ax=fig.add_subplot(1,1,1)
circ=plt.Circle((0.5,0.5), radius=0.2, color='g', fill=False,transform=ax.transAxes)
ax.add_patch(circ)
plt.show()
Note that the radius is also transformed into Axes coordinate. If you specify a radius larger than sqrt(2)/2 (about 0.7) you will see nothing in the plot.
If you want to plot a set of circles, it would be much simpler if you use the function circles here. For this problem,
fig=plt.figure(1)
plt.axis([0,400,0,400])
ax=fig.add_subplot(1,1,1)
circles(0.5, 0.5, 0.2, c='g', ax=ax, facecolor='none', transform=ax.transAxes)
plt.show()
A bit more, if you want see a real circle (instead of an ellipse) in your figure, you should use
ax=fig.add_subplot(1,1,1, aspect='equal')
or something like that.
Related
Good evening. I am trying to plot a fuzzy number so an element "140" is connected with it's membership function. I tried to plot one more curve above data curve, but that seemed not a good idea. Any advices are appreciated. Thanks for your time. Here's the code and the desired plot:
import numpy as np
import fuzzylab
x = np.linspace(130, 150, 1000)
y = fuzzylab.trimf(x, [133.3, 140, 147])
plt.plot(x, y)
plt.show()
desired plot
You can use the axvline and axhline to draw lines at specific coordinates.
# Manual way
max_y = y.max()
max_x = x[y.argmax()]
plt.plot(x, y)
plt.axvline(x=max_x, ymin=0, ymax=0.95, c='r', lw=3)
plt.axhline(y=max_y, xmin=0, xmax=0.5, c='r', lw=3)
plt.show()
Note that ymin, ymax, xmin and xmax are in Axes coordinates (from 0 to 1, see the documentation). Your example is not too bad, you can just fiddle with the numbers until you get something you like.
However, if you want this to be more generic, you'll need to use transforms, from data coordinates to Axes coordinates. You can do that like this:
# Generic way
max_y = y.max()
max_x = x[y.argmax()]
fig, ax = plt.subplots()
ax.plot(x, y)
# For some reason this was necessary, otherwise the limits would behave badly.
ax.set_xlim(ax.get_xlim())
x_prop, y_prop = ax.transLimits.transform((max_x, max_y))
ax.axvline(x=max_x, ymin=0, ymax=y_prop, c='r', lw=3)
ax.axhline(y=max_y, xmin=0, xmax=x_prop, c='r', lw=3)
Here, transLimits transforms your data coordinates to axes coordinates. If you modify your y variable to be a bit skewed, the red lines will match the position automatically.
Output plot:
I looked into the matplotlib.transforms module but haven't figured out how to achieve all the following targets at the same time.
plot a curve with x and y coordinates in list x and y.
plot a rectangle (matplotlib.patches.Rectangle) that sticks to the bottom left corner
width of the rectangle is 10% of horizontal axis (xmax-xmin).
height is 1, meaning a unit height rectangle
the rectangle sticks to the bottom left corner, when plot is moved/zoomed.
the rectangle's width remains 10% of horizontal axis 0.1*(xmax-xmin) when zoomed
the rectangle's height changes consistently with y-axis when zoomed.
It seems that along the horizontal direction, scale and position are both in axes.transAxes, which is easy to do. However, along the vertical direction, I need scale to be in axes.transData, but position in axes.transAxes. If I give up item 5, I can use the transforms.blended_transform_factory(), to have axes.transAxes on horizontal and axes.transData on vertical direction, in which case if I zoom, the height of the rectangle changes correctly, but it might go out-of-sight, either partially or completely, in the vertical direction.
Does anyone know if this is doable without hacking too much into matplotlib?
You can subclass AnchoredOffsetbox to have an artist stay "anchored" at a specific position of the axes. See Anchored Artists demo
demonstration code:
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
from matplotlib.offsetbox import OffsetBox, AnchoredOffsetbox, AuxTransformBox
class AnchoredRectangle(AnchoredOffsetbox):
def __init__(self, transform, width, height, loc, **rect_kwds):
self._box = AuxTransformBox(transform)
self.rectangle = Rectangle((0, 0), width, height, **rect_kwds)
self._box.add_artist(self.rectangle)
super().__init__(loc, pad=0, borderpad=0,
child=self._box, frameon=False)
fig, ax = plt.subplots()
r = AnchoredRectangle(ax.get_yaxis_transform(), width=0.1, height=1,
loc='lower left', color='green', alpha=0.5)
ax.add_artist(r)
ax.set_xlim(0,1)
ax.set_ylim(0,2)
plt.show()
I have set of data and I made a graph by using them. The problem is the data does not look like scaled properly since y axis ranges from 0 to 30000 while x axis from -2 to 30. How can I solve this problem ? Thanks
Here is my code,
import numpy as np
import matplotlib.pyplot as plt
voltage365nm = [-1.877,-2.0,-1.5,-1.0,0.0,5.0,10.0,20.0,30.0]
voltage405nm = [-1.437,-2.0,-1.5,-1.0,0.0,5.0,10.0,20.0,30.0]
voltage546nm = [-0.768,-2.0,-1.5,-1.0,0.0,5.0,10.0,20.0,30.0]
current365nm = [0.0,5.6,151.1,428,1164,5760,9870,1626,20700]
current405nm = [0.0,-8.2,-2.6,70.2,278,1954,2460,3970,5021]
current546nm = [0.0,-6.5,-6.1,-5.4,248,1435,2240,3250,3750] plt.plot(voltage365nm,current405nm,"r--",marker="s",label="$\lambda$=365 nm")
plt.plot(voltage405nm,current405nm,"b-.",marker="o",label="$\lambda$=405nm")
plt.plot(voltage546nm,current546nm,"g-",marker="^",label="$\lambda$=546nm")
plt.legend(loc='best')
plt.xlabel("Voltage (V)")
plt.ylabel("Current (I x $10^{-13}A}$)")
plt.title("Current vs Voltage")
plt.grid(b=True, which='major', color='g', linestyle='--')
plt.grid(b=True, which='minor', color='r', linestyle='--', alpha=0.2)
plt.show()
You could use plt.xlim([-10,0]) and plt.ylim([-10,0]) to specify the minimum and maximum values of your axes.
I ran your code, and got the following chart:
Is your concern that the data points along the lower end of your y axis are "scrunched up"? If that's the case, perhaps plotting on the log axis might help. Use the set_yscale('log') method like so:
ax = plt.gca()
ax.set_yscale('log')
With that, I get the following chart:
The issue with this of course is that some of the y-axis values are negative, and thus can't be directly plotted on a log scale. The complete solution would involve adding a constant to all currents such that they're positive.
PS -- I think there's a bug in one of your plt.plot commands:
plt.plot(voltage365nm,current405nm,"r--",marker="s",label="$\lambda$=365 nm")
should be
plt.plot(voltage365nm, current365nm,"r--",marker="s",label="$\lambda$=365 nm")
I would additionally put the lower values in an inset as a zoom in
# your existing code before plt.show()
left, bottom, width, height = [0.32, 0.55, 0.4, 0.3]
ax2 = fig.add_axes([left, bottom, width, height])
ax2.plot(voltage365nm,current365nm,"r--",marker="s",label="$\lambda$=365 nm")
ax2.plot(voltage405nm,current405nm,"b-.",marker="o",label="$\lambda$=405nm")
ax2.plot(voltage546nm,current546nm,"g-",marker="^",label="$\lambda$=546nm")
ax2.set_xlim(-2.1, 1)
ax2.set_ylim(-100, 1500)
plt.grid(b=True, which='major', color='g', linestyle='--')
plt.show()
I'm adding a text inside a shape by:
ax.text(x,y,'text', ha='center', va='center',bbox=dict(boxstyle='circle', fc="w", ec="k"),fontsize=10) (ax is AxesSubplot)
The problem is that I couldn't make the circle size constant while changing the string length. I want the text size adjust to the circle size and not the other way around.
The circle is even completely gone if the string is an empty one.
The only bypass to the problem I had found is dynamically to set the fontsize param according to the len of the string, but that's too ugly and not still the circle size is not completely constant.
EDIT (adding a MVCE):
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_axes([0,0,1,1])
ax.text(0.5,0.5,'long_text', ha='center', va='center',bbox=dict(boxstyle='circle', fc="w", ec="k"),fontsize=10)
ax.text(0.3,0.7,'short', ha='center', va='center',bbox=dict(boxstyle='circle', fc="w", ec="k"),fontsize=10)
plt.show()
Trying to make both circles the same size although the string len is different. Currently looks like this:
I have a very dirty and hard-core solution which requires quite deep knowledge of matplotlib. It is not perfect but might give you some ideas how to start.
import matplotlib.pyplot as plt
from matplotlib.patches import Circle
import numpy as np
plt.close('all')
fig, ax = plt.subplots(1, 1, figsize=(8, 8))
t1 = ax.text(0.5,0.5,'long_text', ha='center', va='center',fontsize=10)
t2 = ax.text(0.3,0.7,'short', ha='center', va='center', fontsize=10)
t3 = ax.text(0.1,0.7,'super-long-text-that-is-long', ha='center', va='center', fontsize=10)
fig.show()
def text_with_circle(text_obj, axis, color, border=1.5):
# Get the box containing the text
box1 = text_obj.get_window_extent()
# It turned out that what you get from the box is
# in screen pixels, so we need to transform them
# to "data"-coordinates. This is done with the
# transformer-function below
transformer = axis.transData.inverted().transform
# Now transform the corner coordinates of the box
# to data-coordinates
[x0, y0] = transformer([box1.x0, box1.y0])
[x1, y1] = transformer([box1.x1, box1.y1])
# Find the x and y center coordinate
x_center = (x0+x1)/2.
y_center = (y0+y1)/2.
# Find the radius, add some extra to make a nice border around it
r = np.max([(x1-x0)/2., (y1-y0)/2.])*border
# Plot the a circle at the center of the text, with radius r.
circle = Circle((x_center, y_center), r, color=color)
# Add the circle to the axis.
# Redraw the canvas.
return circle
circle1 = text_with_circle(t1, ax, 'g')
ax.add_artist(circle1)
circle2 = text_with_circle(t2, ax, 'r', 5)
ax.add_artist(circle2)
circle3 = text_with_circle(t3, ax, 'y', 1.1)
ax.add_artist(circle3)
fig.canvas.draw()
At the moment you have to run this in ipython, because the figure has to be drawn BEFORE you get_window_extent(). Therefore the fig.show() has to be called AFTER the text is added, but BEFORE the circle can be drawn! Then we can get the coordinates of the text, figures out where the middle is and add a circle around the text with a certain radius. When this is done we redraw the canvas to update with the new circle. Ofcourse you can customize the circle a lot more (edge color, face color, line width, etc), look into the Circle class.
Example of output plot:
I would like to plot concentric circles at a given set of distances away from a source. The first thing I tried to do was draw an arc on polar plot, as this seemed like a logical substep:
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111, polar=True)
ax.plot([1.0,1.5], [5,5], color='r', linestyle='-')
plt.show()
The first problem I'm having is that this draws a straight line rather than an arc:
So the subquestion might be how do I draw an arc, in this case a 360 degree arc, at a given radius on a polar plot?. On the other hand, there might be a better solution altogether, perhaps one that doesn't involve a polar plot. Ultimately, as per the title, my objective is to draw concentric circles at a set of radii around a centre source.
easy, use it to make shooting targets all the time.:
ax.plot(np.linspace(0, 2*np.pi, 100), np.ones(100)*5, color='r', linestyle='-')
Just think of how you define a circle in a polar axis? Need two things, angle and radius. Those are np.linspace(0, 2*np.pi, 100) and np.ones(100)*5 here. If you just need a arc, change the first argument to something less than 0 to 2pi. And change the 2nd argument accordingly.
There are other ways to do this. plot() creates .lines.Line2D object objects, if you want .collections.PathCollection object instead of Line2D:
ax.scatter(1, 0, s=100000, facecolors='none')
Or you want to make patches:
ax.bar(0, 5, 2*np.pi, bottom=0.0, facecolor='None') #need to modified the edge lines or won't look right