I would like to plot implicit equations (of the form f(x, y)=g(x, y) eg. X^y=y^x) in Matplotlib. Is this possible?
Since you've tagged this question with sympy, I will give such an example.
From the documentation: http://docs.sympy.org/latest/modules/plotting.html.
from sympy import var, plot_implicit
var('x y')
plot_implicit(x*y**3 - y*x**3)
I don't believe there's very good support for this, but you could try something like
import matplotlib.pyplot
from numpy import arange
from numpy import meshgrid
delta = 0.025
xrange = arange(-5.0, 20.0, delta)
yrange = arange(-5.0, 20.0, delta)
X, Y = meshgrid(xrange,yrange)
# F is one side of the equation, G is the other
F = Y**X
G = X**Y
matplotlib.pyplot.contour(X, Y, (F - G), [0])
matplotlib.pyplot.show()
See the API docs for contour: if the fourth argument is a sequence then it specifies which contour lines to plot. But the plot will only be as good as the resolution of your ranges, and there are certain features it may never get right, often at self-intersection points.
matplotlib does not plot equations; it plots serieses of points. You can use a tool like scipy.optimize to numerically calculate y points from x values (or vice versa) of implicit equations numerically or any number of other tools as appropriate.
For example, here is an example where I plot the implicit equation x ** 2 + x * y + y ** 2 = 10 in a certain region.
from functools import partial
import numpy
import scipy.optimize
import matplotlib.pyplot as pp
def z(x, y):
return x ** 2 + x * y + y ** 2 - 10
x_window = 0, 5
y_window = 0, 5
xs = []
ys = []
for x in numpy.linspace(*x_window, num=200):
try:
# A more efficient technique would use the last-found-y-value as a
# starting point
y = scipy.optimize.brentq(partial(z, x), *y_window)
except ValueError:
# Should we not be able to find a solution in this window.
pass
else:
xs.append(x)
ys.append(y)
pp.plot(xs, ys)
pp.xlim(*x_window)
pp.ylim(*y_window)
pp.show()
There is an implicit equation (and inequality) plotter in sympy. It is created as a part of GSoC and it produces the plots as matplotlib figure instances.
Docs at http://docs.sympy.org/latest/modules/plotting.html#sympy.plotting.plot_implicit.plot_implicit
Since sympy version 0.7.2 it is available as:
>>> from sympy.plotting import plot_implicit
>>> p = plot_implicit(x < sin(x)) # also creates a window with the plot
>>> the_matplotlib_axes_instance = p._backend._ax
Edit: If you plot a hyperbola using plt.plot() then you will get the undesired branching effect. plt.scatter() in its place should still work. Then there is no need to reverse the order of negative or positive values, but if you wanted to use this code for some reason (instead of using contour plot from scipy) it will work anyways with plt.scatter()
An implicit function in two dimensions in general can be written as:
f(x,y)=0
Since we cannot write this as f(x) = y, then we cannot compute y from an easily programmable set of discrete x. It is possible, however, to see how close a point generated from a grid is from the true function.
So make a grid of x and y to a custom point density and see how close each point is to satisfying the equation.
In other words, if we can't get f(x,y) =0, perhaps we can get close to 0. Instead of looking for f(x,y) =0 look for f(x,y) > -\epsilon and f(x,y) < \epsilon.
\epsilon is your tolerance and if this condition fits within your tolerance of 0 and tuning the grid appropriately you can get your function plotted.
The code below does just that for a circle of radius 1 (f(x,y)= x^2 + y^2 -1 = 0). I used the symbol dr for \epsilon.
Also, to make sure the plt.plot function connects the lines in the correct order, I use a reversed version of the x values for the negative y values. That way, the evaluation of f(x,y) is done in a clockwise loop so that the nearest values are one after another. Without this, lines from opposite sides of the function would connect and it would appear slightly filled in.
import numpy as np
import matplotlib.pyplot as plt
r = 1 #arbitrary radius to set up the span of points
points = 250
dr = r/points #epsilon window
x=list(np.linspace(-5*r,5*r,5*points+1)) #setting up the x,y grid
y=x
xreversed = reversed(x) #reversing the array
x_0=[] #placeholder arrays
y_0=[]
for i in x:
for j in y:
if i**2 + j**2 -1 < dr and i**2+j**2 -1 > -dr and j >= 0: #positive values of y
x_0.append(i)
y_0.append(j)
for i in xreversed:
for j in y:
if i**2+j**2 -1 < dr and i**2+j**2 -1 > -dr and j < 0: #negative values of y, using x reversed
x_0.append(i)
y_0.append(j)
plt.plot(x_0,y_0)
plt.show()
Many thanks Steve, Mike, Alex. I have gone along with Steve's solution (please see code below). My only remaining issue is that the contour plot appears behind my gridlines, as opposed to a regular plot, which I can force to the front with zorder. Any more halp greatly appreciated.
Cheers,
Geddes
import matplotlib.pyplot as plt
from matplotlib.ticker import MultipleLocator, FormatStrFormatter
import numpy as np
fig = plt.figure(1)
ax = fig.add_subplot(111)
# set up axis
ax.spines['left'].set_position('zero')
ax.spines['right'].set_color('none')
ax.spines['bottom'].set_position('zero')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
# setup x and y ranges and precision
x = np.arange(-0.5,5.5,0.01)
y = np.arange(-0.5,5.5,0.01)
# draw a curve
line, = ax.plot(x, x**2,zorder=100)
# draw a contour
X,Y=np.meshgrid(x,y)
F=X**Y
G=Y**X
ax.contour(X,Y,(F-G),[0],zorder=100)
#set bounds
ax.set_xbound(-1,7)
ax.set_ybound(-1,7)
#produce gridlines of different colors/widths
ax.xaxis.set_minor_locator(MultipleLocator(0.2))
ax.yaxis.set_minor_locator(MultipleLocator(0.2))
ax.xaxis.grid(True,'minor',linestyle='-')
ax.yaxis.grid(True,'minor',linestyle='-')
minor_grid_lines = [tick.gridline for tick in ax.xaxis.get_minor_ticks()]
for idx,loc in enumerate(ax.xaxis.get_minorticklocs()):
if loc % 2.0 == 0:
minor_grid_lines[idx].set_color('0.3')
minor_grid_lines[idx].set_linewidth(2)
elif loc % 1.0 == 0:
minor_grid_lines[idx].set_c('0.5')
minor_grid_lines[idx].set_linewidth(1)
else:
minor_grid_lines[idx].set_c('0.7')
minor_grid_lines[idx].set_linewidth(1)
minor_grid_lines = [tick.gridline for tick in ax.yaxis.get_minor_ticks()]
for idx,loc in enumerate(ax.yaxis.get_minorticklocs()):
if loc % 2.0 == 0:
minor_grid_lines[idx].set_color('0.3')
minor_grid_lines[idx].set_linewidth(2)
elif loc % 1.0 == 0:
minor_grid_lines[idx].set_c('0.5')
minor_grid_lines[idx].set_linewidth(1)
else:
minor_grid_lines[idx].set_c('0.7')
minor_grid_lines[idx].set_linewidth(1)
plt.show()
Related
I have a plot like this, plotting a semicircle with x and y
I want to add arrows at each point like so (ignore the horrible paint job):
Is there an easy way to add arrows perpendicular to the plot?
Current code:
import numpy as np
import matplotlib.pyplot as plt
r = 2
h = 0
k = 0
x0 = h-r
x1 = h+r
x = np.linspace(x0,x1,9)
y = k + np.sqrt(r**2 - (x-h)**2)
plt.scatter(x,y)
plt.xlim(-4,4)
plt.ylim(-4,4)
PERPENDICULAR TO THE TANGENT OF THE CURVE I'M SORRY I FORGOT TO ADD THIS
A point in space has no idea what "perpendicular" means, but assuming your y is some function of x that has a derivate, you can think of the derivate of the function at some point to be the tangent of the curve at that point, and to get a perpendicular vector you just need to rotate the vector counter-clockwise 90 degrees:
x1, y1 = -y0, x0
We know that these points come from a circle. So given three points we can easily find the center using basic geometry notions. If you need a refresher, take a look here.
For this particular case, the center is at the origin. Knowing the center coordinates, the normal at each point is just the vector from the center to the point itself. Since the center is the origin, the normals' components are just given by the coordinates of the points themselves.
import numpy as np
import matplotlib.pyplot as plt
r = 2
h = 0
k = 0
x0 = h-r
x1 = h+r
x = np.linspace(x0, x1, 9)
y = k + np.sqrt(r**2 - (x-h)**2)
center = np.array([0.0, 0.0])
plt.scatter(x, y)
plt.quiver(x, y, x, y, width=0.005)
plt.xlim(-4, 4)
plt.ylim(-4, 4)
plt.show()
If you are in a hurry and you do not have time to implement equations, you could use the scikit-spatial library in the following way:
from skspatial.objects import Circle, Vector, Points
import numpy as np
import matplotlib.pyplot as plt
r = 2
h = 0
k = 0
x0 = h-r
x1 = h+r
x = np.linspace(x0, x1, 9)
y = k + np.sqrt(r**2 - (x-h)**2)
points = Points(np.vstack((x, y)).T)
circle = Circle.best_fit(np.vstack((x, y)).T)
center = circle.point
normals = np.array([Vector.from_points(center, point) for point in points])
plt.scatter(x, y)
plt.quiver(x, y, normals[:, 0], normals[:, 1], width=0.005)
plt.xlim(-4, 4)
plt.ylim(-4, 4)
plt.show()
Postulate of blunova's and simon's answers is correct, generally speaking: points have no normal, but curve have; so you need to rely on what you know your curve is. Either, as blunova described it, by the knowledge that it is a circle, and computing those normal with ad-hoc computation from that knowledge.
Or, as I am about to describe, using the function f such as y=f(x). and using knowledge on what is the normal to such a (x,f(x)) chart.
Here is your code, written with such a function f
import numpy as np
import matplotlib.pyplot as plt
r = 2
h = 0
k = 0
x0 = h-r
x1 = h+r
x = np.linspace(x0,x1,9)
def f(x):
return k + np.sqrt(r**2 - (x-h)**2)
y=f(x)
plt.scatter(x,y)
plt.xlim(-4,4)
plt.ylim(-4,4)
So, all I did here is rewriting your line y=... in the form of a function.
From there, it is possible to compute the normal to each point of the chart (x,f(x)).
The tangent to a point (x,f(x)) is well known: it is vector (1,f'(x)), where f'(x) is the derivative of f. So, normal to that is (-f'(x), 1).
Divided by √(f'(x)²+1) to normalize this vector.
So, just use that as entry to quiver.
First compute a derivative of your function
dx=0.001
def fprime(x):
return (f(x+dx)-f(x-dx))/(2*dx)
Then, just
plt.quiver(x, f(x), -fprime(x), 1)
Or, to have all vector normalized
plt.quiver(x, f(x), -fprime(x)/np.sqrt(fprime(x)**2+1), 1/np.sqrt(fprime(x)**2+1))
(note that fprime and the normalization part are all vectorizable operation, so it works with x being a arange)
All together
import numpy as np
import matplotlib.pyplot as plt
r = 2
h = 0
k = 0
x0 = h-r
x1 = h+r
def f(x):
return k+ np.sqrt(r**2 - (x-h)**2)
dx=0.001
x = np.linspace(x0+dx,x1-dx,9)
y = f(x)
def fprime(x):
return (f(x+dx)-f(x-dx))/(2*dx)
plt.scatter(x,y)
plt.quiver(x,f(x), -fprime(x)/np.sqrt(fprime(x)**2+1), 1/np.sqrt(fprime(x)**2+1))
plt.xlim(-4,4)
plt.ylim(-4,4)
plt.show()
That is almost an exact copy of your code, but for the quiver line, and with the addition of fprime.
One other slight change, specific to your curve, is that I changed x range to ensure the computability of fprime (if first x is x0, then fprime need f(x0-dx) which does not exist because of sqrt. Likewise for x1. So, first x is x0+dx, and last is x1-dx, which is visually the same)
That is the main advantage of this solution over blunova's: it is your code, essentially. And would work if you change f, without assuming that f is a circle. All that is assume is that f is derivable (and if it were not, you could not define what those normal are anyway).
For example, if you want to do the same with a parabola instead, just change f
import numpy as np
import matplotlib.pyplot as plt
r = 2
h = 0
k = 0
x0 = h-r
x1 = h+r
def f(x):
return x**2
dx=0.001
x = np.linspace(x0+dx,x1-dx,9)
y = f(x)
def fprime(x):
return (f(x+dx)-f(x-dx))/(2*dx)
plt.scatter(x,y)
plt.quiver(x,f(x), -fprime(x)/np.sqrt(fprime(x)**2+1), 1/np.sqrt(fprime(x)**2+1))
plt.xlim(-4,4)
plt.ylim(-2,5)
plt.show()
All I changed here is the f formula. Not need for a new reasoning to compute the normal.
Last remark: an even more accurate version (not forcing the approximate computation of fprime with a dx) would be to use sympy to define f, and then compute the real, symbolic, derivative of f. But that doesn't seem necessary for your case.
I am trying to plot 3 different functions on a log-log scale in python for three intervals of the x-range. Attaching the image of the kind of plot that I want to create and which functions, y, for what intervals of x.
My code attempt is as follows. Maybe I am overcomplicating it.
import math as m
import numpy as np
import pylab as pl
import matplotlib.pyplot as plt
x = np.arange(100) # this gives `array([0, 1, 2, ..., 9])`
y = np.arange(100)
for i in range (-50,20):
if x[i] < -43:
y[i] = m.log10((10**x[i])/(10**-43))**(1/2)
if x[i] > -43 and x[i] < -40:
y[i] = m.log10(np.exp((10**36)((10**x[i])-(10**-43))))
if x[i] >-40:
y[i] = m.log10((np.exp((10**36)((10**-40) - (10**-43)))(((10**x[i])/(10**-43))**(1/2)))
#i+=1
pl.plot(x,y)
#pl.xlim([-100.,100.])
#pl.ylim([-100.,100.])
pl.xlabel('log x')
pl.ylabel('log y')
pl.show()
PLEASE NOTE:
updated code with help from #Sembei which works but there's further question on colours below:
import matplotlib.pyplot as plt
x = np.linspace(-50,23,500)
y = []
for xval in x:
if xval < -36:
y.append(m.log10(((10**xval)/(10**-36))**(1/2)))
elif -36 <= xval <= -34:
y.append(m.log10(np.exp((10**36)*((10**xval)-(10**-36)))))
else:
y.append(m.log10((np.exp((10**36)*((10**-34) - (10**-36)))*(((10**xval)/(10**-36))**(1/2)))))
plt.plot(x,y)
pl.xlim([-44.,-30.])
#pl.ylim([-10.,20.])
pl.xlabel('log x')
pl.ylabel('log y')
plt.show()
FURTHER QUESTION:
how to set 3 different colours for the different y functions for the 3 x-intervals?
Any help is appreciated. Thanks!
you can do something like this:
x = range(-50,23)
y = []
for xval in x:
if xval < -43:
y.append(-43) #your function in this interval
elif -43 <= xval <= -40:
y.append(xval) #your function in this interval)
else:
y.append(-40) #your function in this interval)
plt.plot(x,y, '.-')
plt.xlabel('log x')
plt.ylabel('log y')
plt.show()
You just need to fill the #your function in this interval with a correct syntax (note that in your syntax you are missing product operators *)
Here I have used y as a list and I'm appending values. You can also initialize y to all zeros and assign values based on indexes. For that you will need to include an enumerate in the loop that will give you the index of y where you have to put the value.
Note: here, range steps one by one. if you want more resolution you might want to use np.linspace so you can control the resolution of your function.
Edit: I put some toy definitions of the function so you can see how it works. Now just change my function definitions for your own
For the following code whose job is to perform Monte Carlo integration for a function f, I was wondering what would happen if I define f as y = sqrt(1-x^2), which is the equation for a unit quarter circle, and specify an endpoint that is greater than 1, since we know that f is only defined for 0<x<1.
import numpy as np
import matplotlib.pyplot as plt
def definite_integral_show(f, x0, x1, N):
"""Approximate the definite integral of f(x)dx between x0 and x1 using
N random points
Arguments:
f -- a function of one real variable, must be nonnegative on [x0, x1]
N -- the number of random points to use
"""
#First, let's compute fmax. We do that by evaluating f(x) on a grid
#of points between x0 and x1
#This assumes that f is generally smooth. If it's not, we're in trouble!
x = np.arange(x0, x1, 0.01)
y = f(x)
print(y)
f_max = max(y)
#Now, let's generate the random points. The x's should be between
#x0 and x1, so we first create points beterrm 0 and (x1-x0), and
#then add x0
#The y's should be between 0 and fmax
#
# 0...(x1-x0)
x_rand = x0 + np.random.random(N)*(x1-x0)
print(x_rand)
y_rand = 0 + np.random.random(N)*f_max
#Now, let's find the indices of the poitns above and below
#the curve. That is, for points below the curve, let's find
# i s.t. y_rand[i] < f(x_rand)[i]
#And for points above the curve, find
# i s.t. y_rand[i] >= f(x_rand)[i]
ind_below = np.where(y_rand < f(x_rand))
ind_above = np.where(y_rand >= f(x_rand))
#Finally, let's display the results
plt.plot(x, y, color = "red")
pts_below = plt.scatter(x_rand[ind_below[0]], y_rand[ind_below[0]], color = "green")
pts_above = plt.scatter(x_rand[ind_above[0]], y_rand[ind_above[0]], color = "blue")
plt.legend((pts_below, pts_above),
('Pts below the curve', 'Pts above the curve'),
loc='lower left',
ncol=3,
fontsize=8)
def f1(x):
return np.sqrt(1-x**2)
definite_integral_show(f1, 0, 6, 200)
To my surprise, the program still works and gives me the following picture.
I suspect that it works because in NumPy, nan's in an array are just ignored when performing operations on the array. However, I don't understand why the picture only contains points whose x and y coordinates are both between 0 to 1. Where are the points that aren't within this range, but whose values are computed by
x_rand = x0 + np.random.random(N)*(x1-x0)
y_rand = 0 + np.random.random(N)*f_max
You can just print out the arrays (for example by generating only one random point) and see that they go into neither ind_below nor ind_above...
That's because all comparisons that involves nan returns False. (See also: What is the rationale for all comparisons returning false for IEEE754 NaN values?). (so y_rand < nan and y_rand >= nan both evaluates to False)
The easiest way to change the code is
ind_below = np.where(y_rand < f(x_rand))
ind_above = np.where(~(y_rand < f(x_rand)))
(optionally only compute the array once)
I would like to plot implicit equations (of the form f(x, y)=g(x, y) eg. X^y=y^x) in Matplotlib. Is this possible?
Since you've tagged this question with sympy, I will give such an example.
From the documentation: http://docs.sympy.org/latest/modules/plotting.html.
from sympy import var, plot_implicit
var('x y')
plot_implicit(x*y**3 - y*x**3)
I don't believe there's very good support for this, but you could try something like
import matplotlib.pyplot
from numpy import arange
from numpy import meshgrid
delta = 0.025
xrange = arange(-5.0, 20.0, delta)
yrange = arange(-5.0, 20.0, delta)
X, Y = meshgrid(xrange,yrange)
# F is one side of the equation, G is the other
F = Y**X
G = X**Y
matplotlib.pyplot.contour(X, Y, (F - G), [0])
matplotlib.pyplot.show()
See the API docs for contour: if the fourth argument is a sequence then it specifies which contour lines to plot. But the plot will only be as good as the resolution of your ranges, and there are certain features it may never get right, often at self-intersection points.
matplotlib does not plot equations; it plots serieses of points. You can use a tool like scipy.optimize to numerically calculate y points from x values (or vice versa) of implicit equations numerically or any number of other tools as appropriate.
For example, here is an example where I plot the implicit equation x ** 2 + x * y + y ** 2 = 10 in a certain region.
from functools import partial
import numpy
import scipy.optimize
import matplotlib.pyplot as pp
def z(x, y):
return x ** 2 + x * y + y ** 2 - 10
x_window = 0, 5
y_window = 0, 5
xs = []
ys = []
for x in numpy.linspace(*x_window, num=200):
try:
# A more efficient technique would use the last-found-y-value as a
# starting point
y = scipy.optimize.brentq(partial(z, x), *y_window)
except ValueError:
# Should we not be able to find a solution in this window.
pass
else:
xs.append(x)
ys.append(y)
pp.plot(xs, ys)
pp.xlim(*x_window)
pp.ylim(*y_window)
pp.show()
There is an implicit equation (and inequality) plotter in sympy. It is created as a part of GSoC and it produces the plots as matplotlib figure instances.
Docs at http://docs.sympy.org/latest/modules/plotting.html#sympy.plotting.plot_implicit.plot_implicit
Since sympy version 0.7.2 it is available as:
>>> from sympy.plotting import plot_implicit
>>> p = plot_implicit(x < sin(x)) # also creates a window with the plot
>>> the_matplotlib_axes_instance = p._backend._ax
Edit: If you plot a hyperbola using plt.plot() then you will get the undesired branching effect. plt.scatter() in its place should still work. Then there is no need to reverse the order of negative or positive values, but if you wanted to use this code for some reason (instead of using contour plot from scipy) it will work anyways with plt.scatter()
An implicit function in two dimensions in general can be written as:
f(x,y)=0
Since we cannot write this as f(x) = y, then we cannot compute y from an easily programmable set of discrete x. It is possible, however, to see how close a point generated from a grid is from the true function.
So make a grid of x and y to a custom point density and see how close each point is to satisfying the equation.
In other words, if we can't get f(x,y) =0, perhaps we can get close to 0. Instead of looking for f(x,y) =0 look for f(x,y) > -\epsilon and f(x,y) < \epsilon.
\epsilon is your tolerance and if this condition fits within your tolerance of 0 and tuning the grid appropriately you can get your function plotted.
The code below does just that for a circle of radius 1 (f(x,y)= x^2 + y^2 -1 = 0). I used the symbol dr for \epsilon.
Also, to make sure the plt.plot function connects the lines in the correct order, I use a reversed version of the x values for the negative y values. That way, the evaluation of f(x,y) is done in a clockwise loop so that the nearest values are one after another. Without this, lines from opposite sides of the function would connect and it would appear slightly filled in.
import numpy as np
import matplotlib.pyplot as plt
r = 1 #arbitrary radius to set up the span of points
points = 250
dr = r/points #epsilon window
x=list(np.linspace(-5*r,5*r,5*points+1)) #setting up the x,y grid
y=x
xreversed = reversed(x) #reversing the array
x_0=[] #placeholder arrays
y_0=[]
for i in x:
for j in y:
if i**2 + j**2 -1 < dr and i**2+j**2 -1 > -dr and j >= 0: #positive values of y
x_0.append(i)
y_0.append(j)
for i in xreversed:
for j in y:
if i**2+j**2 -1 < dr and i**2+j**2 -1 > -dr and j < 0: #negative values of y, using x reversed
x_0.append(i)
y_0.append(j)
plt.plot(x_0,y_0)
plt.show()
Many thanks Steve, Mike, Alex. I have gone along with Steve's solution (please see code below). My only remaining issue is that the contour plot appears behind my gridlines, as opposed to a regular plot, which I can force to the front with zorder. Any more halp greatly appreciated.
Cheers,
Geddes
import matplotlib.pyplot as plt
from matplotlib.ticker import MultipleLocator, FormatStrFormatter
import numpy as np
fig = plt.figure(1)
ax = fig.add_subplot(111)
# set up axis
ax.spines['left'].set_position('zero')
ax.spines['right'].set_color('none')
ax.spines['bottom'].set_position('zero')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
# setup x and y ranges and precision
x = np.arange(-0.5,5.5,0.01)
y = np.arange(-0.5,5.5,0.01)
# draw a curve
line, = ax.plot(x, x**2,zorder=100)
# draw a contour
X,Y=np.meshgrid(x,y)
F=X**Y
G=Y**X
ax.contour(X,Y,(F-G),[0],zorder=100)
#set bounds
ax.set_xbound(-1,7)
ax.set_ybound(-1,7)
#produce gridlines of different colors/widths
ax.xaxis.set_minor_locator(MultipleLocator(0.2))
ax.yaxis.set_minor_locator(MultipleLocator(0.2))
ax.xaxis.grid(True,'minor',linestyle='-')
ax.yaxis.grid(True,'minor',linestyle='-')
minor_grid_lines = [tick.gridline for tick in ax.xaxis.get_minor_ticks()]
for idx,loc in enumerate(ax.xaxis.get_minorticklocs()):
if loc % 2.0 == 0:
minor_grid_lines[idx].set_color('0.3')
minor_grid_lines[idx].set_linewidth(2)
elif loc % 1.0 == 0:
minor_grid_lines[idx].set_c('0.5')
minor_grid_lines[idx].set_linewidth(1)
else:
minor_grid_lines[idx].set_c('0.7')
minor_grid_lines[idx].set_linewidth(1)
minor_grid_lines = [tick.gridline for tick in ax.yaxis.get_minor_ticks()]
for idx,loc in enumerate(ax.yaxis.get_minorticklocs()):
if loc % 2.0 == 0:
minor_grid_lines[idx].set_color('0.3')
minor_grid_lines[idx].set_linewidth(2)
elif loc % 1.0 == 0:
minor_grid_lines[idx].set_c('0.5')
minor_grid_lines[idx].set_linewidth(1)
else:
minor_grid_lines[idx].set_c('0.7')
minor_grid_lines[idx].set_linewidth(1)
plt.show()
I want to know how the contours levels are chosen in pyplot.contour. What I mean by this is, given a function f(x, y), the level curves are usually chosen by evaluating the points where f(x, y) = c, c=0,1,2,... etc. However if f(x, y) is an array A of nxn points, how do the level points get chosen? I don't mean how do the points get connected, just simply the points that correspond to A = c
Looking at the source code (contour.py), it's something like this
import numpy as np
import matplotlib.pylab as pl
import matplotlib.ticker as ticker
x = np.arange(10)
y = np.arange(10)
z = np.random.random((10,10))
pl.figure()
pl.contour(x, y, z)
pl.colorbar()
# manually calculate levels:
zmin = np.amin(z)
zmax = np.amax(z)
N = 7 # default
locator = ticker.MaxNLocator(N+1)
lev = locator.tick_values(zmin, zmax)
# Clip
levels = lev[(lev > zmin) & (lev < zmax)]
print(levels)
[ 0.15 0.3 0.45 0.6 0.75 0.9 ]
The function is evaluated at every grid node, and compared to the iso-level. When there is a change of sign along a cell edge, a point is computed by linear interpolation between the two nodes. Points are joined in pairs by line segments. This is an acceptable approximation when the grid is dense enough.