Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Added fplot to Axes subclass. #737

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 12 commits into from
Closed
5 changes: 5 additions & 0 deletions lib/matplotlib/axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import matplotlib.dates as mdates
from matplotlib import docstring
import matplotlib.font_manager as font_manager
import matplotlib.fplot as mfplot
import matplotlib.image as mimage
import matplotlib.legend as mlegend
import matplotlib.lines as mlines
Expand Down Expand Up @@ -6382,6 +6383,10 @@ def quiver(self, *args, **kw):
return q
quiver.__doc__ = mquiver.Quiver.quiver_doc

def fplot(self, f, limits, *args, **kwargs):
return mfplot.fplot(self, f, limits, *args, **kwargs)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function needs a doc string -- take a look at contour to see how we share docstrings between module helper code and the axes wrapper funcs.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I'll make sure to add that.

fplot.__doc__ = mfplot.fplot.__doc__

def streamplot(self, x, y, u, v, density=1, linewidth=None, color=None,
cmap=None, arrowsize=1, arrowstyle='-|>', minlength=0.1):
if not self._hold: self.cla()
Expand Down
125 changes: 125 additions & 0 deletions lib/matplotlib/fplot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
"""
1D Callable function plotting.

"""
import numpy as np
import matplotlib


__all__ = ['fplot']


def fplot(axes, f, limits, *args, **kwargs):
"""Plots a callable function f.

Parameters
----------
*f* : Python callable, the function that is to be plotted.
*limits* : 2-element array or list of limits: [xmin, xmax]. The function f
is to to be plotted between xmin and xmax.

Returns
-------
*lines* : `matplotlib.collections.LineCollection`
Line collection with that describes the function *f* between xmin
and xmax. all streamlines as a series of line segments.
"""

# TODO: Check f is callable. If not callable, support array of callables.
# TODO: Support y limits?

# Some small number, usually close to machine epsilon
eps = 1e-10

# If the gradient is bigger than this, we say it has a singularity
sing_tol = 1e6

# The scaling factor used to scale the step size
# as a function of the domain length
scale = max(1.0, abs(limits[1] - limits[0]))

# 0.2% absolute error
tol = kwargs.pop('tol', 2e-3)
n = kwargs.pop('tol', 50)

x = np.linspace(limits[0], limits[1], n)
f_vals = [f(xi) for xi in x]

# Bisect abscissa until the gradient error changes by less than tol
within_tol = False

while not within_tol:
within_tol = True
new_pts = []
new_f = []
for i in xrange(len(x)-1):
if np.isnan(f_vals[i]):
continue
# Make sure the step size is not pointlessly small.
# This is a numerical check to prevent silly roundoff errors.
#
# The temporary variable is to ensure the step size is
# represented properly in binary.
min_step = np.sqrt(eps) * x[i] * scale

tmp = x[i] + min_step

# The multiplation by two is just to be conservative.
min_step = 2*(tmp - x[i])

# Subdivide
x_new = (x[i+1] + x[i]) / 2.0

# If the absicissa points are too close, don't bisect
# since calculation of the gradient will produce mostly
# nonsense values due to roundoff error.
#
# If the function values are too close, the payoff is
# negligible, so skip them.
f_new = f(x_new) # Used later, so store it
if abs(x_new - x[i]) < min_step or abs(f_new - f_vals[i]) < min_step:
continue

# Compare gradients of actual f and linear approximation
# FIXME: What if f(x[i]) or f(x[i+1]) is nan?
dx = abs(x[i+1] - x[i])
f_interp = (f_vals[i+1] + f_vals[i])

# This line is the absolute error of the gradient
grad_error = np.abs(f_interp - 2.0 * f_new) / dx

# If the new gradient is not within the tolerance, store
# the subdivision point for merging later
if grad_error > sing_tol:
f_vals[i] = np.nan
f_vals[i+1] = np.nan
elif grad_error > tol:
within_tol = False
new_pts.append(x_new)
new_f.append(f_new)

if not within_tol:
# Not sure this is the best way to do this...
# Merge the subdivision points into the array of abscissae
x, f_vals = merge_pts(x, new_pts, f_vals, new_f)

return axes.plot(x, f_vals)

def merge_pts(xs, xs_sub, fs, fs_sub):
x = []
f = []
ia = 0
ib = 0
while ib < len(xs_sub):
if xs_sub[ib] < xs[ia]:
x.append(xs_sub[ib])
f.append(fs_sub[ib])
ib += 1
else:
x.append(xs[ia])
f.append(fs[ia])
ia += 1
if ia < len(xs):
return np.append(x, xs[ia::]), np.append(f, fs[ia::])
else:
return np.array(x), np.array(f)