• Tidak ada hasil yang ditemukan

Drawing geometric shapes with Matplotlib’s Patches

Dalam dokumen DOING MATH WITH PYTHON (Halaman 169-177)

In matplotlib, patches allow us to draw geometric shapes, each of which we refer to as a patch. You can specify, for example, a circle’s radius and center in order to add the corresponding circle to your plot. This is quite different from how we’ve used matplotlib so far, which has been to supply the x- and y-coordinates of the points to plot. Before we can write a program to make use of the patches feature, however, we’ll need to understand a little bit more about how a matplotlib plot is created. Consider the following pro- gram, which plots the points (1, 1), (2, 2), and (3, 3) using matplotlib:

>>> import matplotlib.pyplot as plt

>>> x = [1, 2, 3]

>>> y = [1, 2, 3]

>>> plt.plot(x, y)

[<matplotlib.lines.Line2D object at 0x7fe822d67a20>]

>>> plt.show()

This program creates a matplotlib window that shows a line passing through the given points. Under the hood, when the plt.plot() function is called, a Figure object is created, within which the axes are created, and finally the data is plotted within the axes (see Figure 6-1).1

1.0 1.5 2.0 2.5 3.0

1.0 1.5 2.0 2.5

3.0 Figure

Line

Axes

Figure 6-1: Architecture of a matplotlib plot

The following program re-creates this plot, but we’ll also explicitly cre- ate the Figure object and add axes to it, instead of just calling the plot()

function and relying on it to create those:

>>> import matplotlib.pyplot as plt

>>> x = [1, 2, 3]

>>> y = [1, 2, 3]

u >>> fig = plt.figure() v >>> ax = plt.axes()

>>> plt.plot(x, y)

[<matplotlib.lines.Line2D object at 0x7f9bad1dcc18>]

>>> plt.show()

>>>

Here, we create the Figure object using the figure() function at u, and then we create the axes using the axes() function at v. The axes() function also adds the axes to the Figure object. The last two lines are the same as in the earlier program. This time, when we call the plot() function, it sees that a Figure object with an Axes object already exists and directly proceeds to plot the data supplied to it.

Besides manually creating Figure and Axes objects, you can use two different functions in the pyplot module to get a reference to the current

Figure and Axes objects. When you call the gcf() function, it returns a refer- ence to the current Figure, and when you call the gca() function, it returns a reference to the current Axes. An interesting feature of these functions is that each will create the respective object if it doesn’t already exist. How these functions work will become clearer as we make use of them later in this chapter.

Drawing a Circle

To draw a circle, you can add the Circle patch to the current Axes object, as demonstrated by the following example:

'''

Example of using matplotlib's Circle patch '''

import matplotlib.pyplot as plt def create_circle():

u circle = plt.Circle((0, 0), radius = 0.5) return circle

def show_shape(patch):

v ax = plt.gca() ax.add_patch(patch)

In this program, we’ve separated the creation of the Circle patch object and the addition of the patch to the figure into two functions:

create_circle() and show_shape(). In create_circle(), we make a circle with a center at (0, 0) and a radius of 0.5 by creating a Circle object with the coordinates of the center (0, 0) passed as a tuple and with the radius of 0.5 passed using the keyword argument of the same name at u. The function returns the created Circle object.

The show_shape() function is written such that it will work with any matplotlib patch. It first gets a reference to the current Axes object using the gca() function at v. Then, it adds the patch passed to it using the

add_patch() function and, finally, calls the show() function to display the figure. We call the axis() function here with the scaled parameter, which basically tells matplotlib to automatically adjust the axis limits. We’ll need to have this statement in all programs that use patches to automatically scale the axes. You can, of course, also specify fixed values for the limits, as we saw in Chapter 2.

At w, we call the create_circle() function using the label c to refer to the returned Circle object. Then, we call the show_shape() function, passing c

as an argument. When you run the program, you’ll see a matplotlib window showing the circle (see Figure 6-2).

Figure 6-2: A circle with a center of (0, 0) and radius of 0.5

The circle doesn’t quite look like a circle here, as you can see. This is due to the automatic aspect ratio, which determines the ratio of the length of the x- and y-axes. If you insert the statement ax.set_aspect('equal') after v, you will see that the circle does indeed look like a circle. The set_aspect() func- tion is used to set the aspect ratio of the graph; using the equal argument, we ask matplotlib to set the ratio of the length of the x- and y-axes to 1:1.

Both the edge color and the face color (fill color) of the patch can be changed using the ec and fc keyword arguments. For example, passing

fc='g' and ec='r' will create a circle with a green face color and red edge color.

Matplotlib supports a number of other patches, such as Ellipse, Polygon, and Rectangle.

Creating Animated Figures

Sometimes we may want to create figures with moving shapes. Matplotlib’s animation support will help us achieve this. At the end of this section, we’ll create an animated version of the projectile trajectory-drawing program.

First, let’s see a simpler example. We’ll draw a matplotlib figure with a circle that starts off small and grows to a certain radius indefinitely (unless the matplotlib window is closed):

'''

A growing circle '''

from matplotlib import pyplot as plt from matplotlib import animation def create_circle():

circle = plt.Circle((0, 0), 0.05) return circle

def update_radius(i, circle):

circle.radius = i*0.5 return circle, def create_animation():

u fig = plt.gcf()

ax = plt.axes(xlim=(-10, 10), ylim=(-10, 10)) ax.set_aspect('equal')

circle = create_circle() v ax.add_patch(circle)

w anim = animation.FuncAnimation(

fig, update_radius, fargs = (circle,), frames=30, interval=50) plt.title('Simple Circle Animation')

We start by importing the animation module from the matplotlib pack- age. The create_animation() function carries out the core functionality here.

It gets a reference to the current Figure object using the gcf() function at u and then creates the axes with limits of –10 and 10 for both the x- and y-axes.

After that, it creates a Circle object that represents a circle with a radius of 0.05 and a center at (0, 0) and adds this circle to the current axes at v.

Then, we create a FuncAnimation object w, which passes the following data about the animation we want to create:

fig This is the current Figure object.

update_radius This function will be responsible for drawing every frame. It takes two arguments—a frame number that is automatically passed to it when called and the patch object that we want to update every frame. This function also must return the object.

fargs This tuple consists of all the arguments to be passed to the

update_radius() function other than the frame number. If there are no such arguments to pass, this keyword argument need not be specified.

frames This is the number of frames in the animation. Our function

update_radius() is called this many times. Here, we’ve arbitrarily chosen 30 frames.

interval This is the time interval in milliseconds between two frames.

If your animation seems too slow, decrease this value; if it seems too fast, increase this value.

We then set a title using the title() function and, finally, show the fig- ure using the show() function.

As mentioned earlier, the update_radius() function is responsible for updating the property of the circle that will change each frame. Here, we set the radius to i*0.5, where i is the frame number. As a result, you see a circle that grows every frame for 30 frames—thus, the radius of the larg- est circle is 15. Because the axes’ limits are set at –10 and 10, this gives the effect of the circle exceeding the figure’s dimensions. When you run the program, you’ll see your first animated figure, as shown in Figure 6-3.

You’ll notice that the animation continues until you close the matplot- lib window. This is the default behavior, which you can change by setting the keyword argument to repeat=False when you create the FuncAnimation

object.

Figure 6-3: Simple circle animation

fUnCaniMation oBjECt anD PErSiStEnCE You probably noted in the animated circle program that we assigned the cre- ated FuncAnimation object to the label anim even though we don’t use it again elsewhere . This is because of an issue with matplotlib’s current behavior—it doesn’t store any reference to the FuncAnimation object, making it subject to garbage collection by Python . This means the animation will not be created . Creating a label referring to the object prevents this from happening .

For more on this issue, you may want to follow the discussions at https://

github.com/matplotlib/matplotlib/issues/1656/ .

Animating a Projectile’s Trajectory

In Chapter 2, we drew the trajectory for a ball in projectile motion. Here, we’ll build upon this drawing, making use of matplotlib’s animation sup- port to animate the trajectory so that it will come closer to demonstrating how you’d see a ball travel in real life:

'''

Animate the trajectory of an object in projectile motion '''

from matplotlib import pyplot as plt from matplotlib import animation import math

g = 9.8

def get_intervals(u, theta):

t_flight = 2*u*math.sin(theta)/g intervals = []

start = 0 interval = 0.005 while start < t_flight:

intervals.append(start) start = start + interval return intervals

def update_position(i, circle, intervals, u, theta):

t = intervals[i]

x = u*math.cos(theta)*t

y = u*math.sin(theta)*t - 0.5*g*t*t circle.center = x, y

return circle,

def create_animation(u, theta):

intervals = get_intervals(u, theta) xmin = 0

xmax = u*math.cos(theta)*intervals[-1]

ymin = 0

t_max = u*math.sin(theta)/g

u ymax = u*math.sin(theta)*t_max - 0.5*g*t_max**2 fig = plt.gcf()

v ax = plt.axes(xlim=(xmin, xmax), ylim=(ymin, ymax)) circle = plt.Circle((xmin, ymin), 1.0)

ax.add_patch(circle)

w anim = animation.FuncAnimation(fig, update_position, fargs=(circle, intervals, u, theta), frames=len(intervals), interval=1, repeat=False)

plt.title('Projectile Motion') plt.xlabel('X')

plt.ylabel('Y') plt.show()

if __name__ == '__main__':

try:

u = float(input('Enter the initial velocity (m/s): '))

theta = float(input('Enter the angle of projection (degrees): ')) except ValueError:

print('You entered an invalid input') else:

theta = math.radians(theta) create_animation(u, theta)

The create_animation() function accepts two arguments: u and theta. These arguments correspond to the initial velocity and the angle of projec- tion (θ), which were supplied as input to the program. The get_intervals()

function is used to find the time intervals at which to calculate the x- and y-coordinates. This function is implemented by making use of the same logic we used in Chapter 2, when we implemented a separate function,

frange(), to help us.

To set up the axis limits for the animation, we’ll need to find the minimum and maximum values of x and y. The minimum value for each is 0, which is the initial value for each. The maximum value of the x-coordinate is the value of the coordinate at the end of the flight of the ball, which is the last time interval in the list intervals. The maximum value of the y-coordinate is when the ball is at its highest point—that is, at u, where we calculate that point using the formula

.

Once we have the values, we create the axes at v, passing the appropri- ate axis limits. In the next two statements, we create a representation of the ball and add it to the figure’s Axes object by creating a circle of radius 1.0 at (xmin, ymin)—the minimum coordinates of the x- and y-axes, respectively.

We then create the FuncAnimation object w, supplying it with the current figure object and the following arguments:

update_position This function will change the center of the circle

intervals (see the description of frames in this list). We calculate the x- and y-coordinates of the ball at the time instant at the ith time interval, and we set the center of the circle to these values.

fargs The update_position() function needs to access the list of time intervals, intervals, initial velocity, and theta, which are specified using this keyword argument.

frames Because we’ll draw one frame per time interval, we set the number of frames to the size of the intervals list.

repeat As we discussed in the first animation example, animation repeats indefinitely by default. We don’t want that to happen in this case, so we set this keyword to False.

When you run the program, it asks for the initial inputs and then creates the animation, as shown in Figure 6-4.

Figure 6-4: Animation of the trajectory of a projectile

Dalam dokumen DOING MATH WITH PYTHON (Halaman 169-177)

Dokumen terkait