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

Skip to content
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)