Add some doc

This commit is contained in:
Lucas Verney 2016-03-02 14:04:28 +01:00
parent 94b3cb53c4
commit fa104cf48f
3 changed files with 3338 additions and 71 deletions

File diff suppressed because one or more lines are too long

View File

@ -1,15 +1,21 @@
""" """
* Saner default config. The :mod:`replot` module is a (sane) Python plotting module, abstracting on top
* Matplotlib API methods have an immediate effect on the figure. We do not want of Matplotlib.
it, then we write a buffer on top of matplotlib API.
""" """
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import numpy as np import numpy as np
import seaborn.apionly as sns import seaborn.apionly as sns
from replot import exceptions as exc
__VERSION__ = "0.0.1" __VERSION__ = "0.0.1"
# Constants
DEFAULT_NB_SAMPLES = 1000
DEFAULT_X_INTERVAL = np.linspace(-10, 10,
DEFAULT_NB_SAMPLES)
# TODO: Remove it, this is interfering with matplotlib # TODO: Remove it, this is interfering with matplotlib
plt.rcParams['figure.figsize'] = (10.0, 8.0) # Larger figures by default plt.rcParams['figure.figsize'] = (10.0, 8.0) # Larger figures by default
@ -17,20 +23,39 @@ plt.rcParams['text.usetex'] = True # Use LaTeX rendering
class Figure(): class Figure():
"""
The main class from :mod:`replot`, representing a figure. Can be used \
directly or in a ``with`` statement.
"""
def __init__(self, def __init__(self,
xlabel="", ylabel="", title="", palette="hls", xlabel="", ylabel="", title="",
palette="hls", max_colors=10,
legend=None): legend=None):
# TODO: Constants """
self.max_colors = 10 Build a :class:`Figure` object.
self.default_points_number = 1000
self.default_x_interval = np.linspace(-10, 10,
self.default_points_number)
:param xlabel: Label for the X axis (optional).
:param ylabel: Label for the Z axis (optional).
:param title: Title of the figure (optional).
:param palette: Color palette to use (optional). Defaults to a safe \
palette with compatibility with colorblindness and black and \
white printing.
:type palette: Either a palette name (``str``) or a built palette.
:param max_colors: Number of colors to use in the palette (optional). \
Defaults to 10.
:param legend: Whether to use a legend or not (optional). Defaults to \
no legend, except if labels are found on provided plots. \
``False`` to disable completely. ``None`` for default \
behavior. A string indicating position (:mod:`matplotlib` \
format) to put a legend. ``True`` is a synonym for ``best`` \
position.
"""
# Set default values for attributes # Set default values for attributes
self.xlabel = xlabel self.xlabel = xlabel
self.ylabel = ylabel self.ylabel = ylabel
self.title = title self.title = title
self.palette = palette self.palette = palette
self.max_colors = max_colors
self.legend = legend self.legend = legend
self.plots = [] self.plots = []
@ -42,17 +67,17 @@ class Figure():
def show(self): def show(self):
""" """
Actually render and show the figure. Actually render and show the :class:`Figure` object.
""" """
# Tweak matplotlib to use seaborn # Tweak matplotlib to use seaborn
sns.set() sns.set()
# Plot using specified color palette # Plot using specified color palette
with sns.color_palette(self.palette, self.max_colors): with sns.color_palette(palette=self.palette, n_colors=self.max_colors):
# Create figure # Create figure
figure, axes = plt.subplots() figure, axes = plt.subplots()
# Add plots # Add plots
for plot in self.plots: for plot_ in self.plots:
tmp_plots = axes.plot(*(plot[0]), **(plot[1])) tmp_plots = axes.plot(*(plot_[0]), **(plot_[1]))
# Do not clip line at the axes boundaries to prevent extremas # Do not clip line at the axes boundaries to prevent extremas
# from being cropped. # from being cropped.
for tmp_plot in tmp_plots: for tmp_plot in tmp_plots:
@ -61,40 +86,49 @@ class Figure():
axes.set_xlabel(self.xlabel) axes.set_xlabel(self.xlabel)
axes.set_ylabel(self.ylabel) axes.set_ylabel(self.ylabel)
axes.set_title(self.title) axes.set_title(self.title)
if self.legend is not None and self.legend is not False: self._legend(axes)
self._legend(axes, location=self.legend)
# Draw figure # Draw figure
figure.show() figure.show()
# Do not forget to restore matplotlib state, in order not to interfere # Do not forget to restore matplotlib state, in order not to interfere
# with it. # with it.
sns.reset_orig() sns.reset_orig()
def plot(self, *args, **kwargs):
#def palette(self, palette):
# """
# """
# if isinstance(palette, str):
# self.current_palette = palette
# with seaborn.color_palette(self.current_palette, self.max_colors):
# # TODO
# pass
def plot(self, data, *args, **kwargs):
""" """
Plot something on the figure. Plot something on the :class:`Figure` object.
>>> plot(np.sin) .. note:: This function expects ``args`` and ``kwargs`` to support
>>> plot(np.sin, (-1, 1)) every possible case. You can either pass it (see examples):
>>> plot(np.sin, [-1, -0.9, , 1])
>>> plot([1, 2, 3], [4, 5, 6]) - A single argument, being a series of points or a function.
- Two series of points representing X values and Y values \
(standard :mod:`matplotlib` behavior).
- Two arguments being a function and a list of points at which \
it should be evaluated (X values).
- Two arguments being a function and an interval represented by \
a tuple of its bounds.
.. note:: ``kwargs`` arguments are directly passed to \
``matplotlib.pyplot.plot``.
>>> with replot.figure() as fig: fig.plot(np.sin)
>>> with replot.figure() as fig: fig.plot(np.sin, (-1, 1))
>>> with replot.figure() as fig: fig.plot(np.sin, [-1, -0.9, , 1])
>>> with replot.figure() as fig: fig.plot([1, 2, 3], [4, 5, 6])
>>> with replot.figure() as fig: fig.plot([1, 2, 3],
[4, 5, 6], linewidth=2.0)
""" """
if hasattr(data, "__call__"): if len(args) == 0:
raise exc.InvalidParameterError(
"You should pass at least one argument to this function.")
if hasattr(args[0], "__call__"):
# We want to plot a function # We want to plot a function
self._plot_function(data, *args, **kwargs) self._plot_function(args[0], *(args[1:]), **kwargs)
else: else:
# Else, it is a point series, and we just have to store it for # Else, it is a point series, and we just have to store it for
# later plotting. # later plotting.
self.plots.append(((data,) + args, kwargs)) self.plots.append((args, kwargs))
# Automatically set the legend if label is found # Automatically set the legend if label is found
# (only do it if legend is not explicitly suppressed) # (only do it if legend is not explicitly suppressed)
@ -103,31 +137,52 @@ class Figure():
def _plot_function(self, data, *args, **kwargs): def _plot_function(self, data, *args, **kwargs):
""" """
Helper function to handle plotting of unevaluated functions (trying \
to evaluate it nicely and rendering the plot).
:param data: The function to plot.
.. seealso:: The documentation of the ``replot.Figure.plot`` method.
.. note:: ``args`` is used to handle the interval or point series on \
which the function should be evaluated. ``kwargs`` are passed \
directly to ``matplotlib.pyplot.plot`.
""" """
# TODO: Better default interval and so on # TODO: Better default interval and so on, adaptive plotting
if len(args) == 0: if len(args) == 0:
# No interval specified, using default one # No interval specified, using default one
x_values = self.default_x_interval x_values = DEFAULT_X_INTERVAL
elif isinstance(args[0], (list, np.ndarray)): elif isinstance(args[0], (list, np.ndarray)):
# List of points specified # List of points specified
x_values = args[0] x_values = args[0]
elif isinstance(args[0], tuple): elif isinstance(args[0], tuple):
# Interval specified, generate a list of points # Interval specified, generate a list of points
x_values = np.linspace(args[0][0], args[0][1], x_values = np.linspace(args[0][0], args[0][1],
self.default_points_number) DEFAULT_NB_SAMPLES)
else: else:
# TODO: Error raise exc.InvalidParameterError(
assert False "Second parameter in plot command should be a tuple " +
"specifying plotting interval.")
y_values = [data(i) for i in x_values] y_values = [data(i) for i in x_values]
self.plots.append(((x_values, y_values) + args[1:], kwargs)) self.plots.append(((x_values, y_values) + args[1:], kwargs))
def _legend(self, axes, location="best"): def _legend(self, axes):
""" """
Helper function to handle ``legend`` attribute. It places the legend \
correctly depending on attributes and required plots.
:param axes: The :mod:`matplotlib` axes to put the legend on.
""" """
if location is True: # If no legend is required, just pass
if self.legend is None or self.legend is False:
return
if self.legend is True:
# If there should be a legend, but no location provided, put it at # If there should be a legend, but no location provided, put it at
# best location. # best location.
location = "best" location = "best"
else:
location = self.legend
# Create aliases for "upper" / "top" and "lower" / "bottom" # Create aliases for "upper" / "top" and "lower" / "bottom"
location.replace("top ", "upper ") location.replace("top ", "upper ")
location.replace("bottom ", "lower ") location.replace("bottom ", "lower ")
@ -140,29 +195,42 @@ class Figure():
def plot(data, **kwargs): def plot(data, **kwargs):
""" """
Helper function to make one-liner plots. Typical use case is:
>>> replot.plot([range(10),
(np.sin, (-5, 5)),
np.cos,
(lambda x: np.sin(x) + 4, (-10, 10), {"linewidth": 10}),
(lambda x: np.sin(x) - 4, {"linewidth": 10}),
([-i for i in range(5)], {"linewidth": 10})],
xlabel="some x label",
ylabel="some y label",
title="A title for the figure",
legend="best",
palette=replot.sns.color_palette("husl", 2))
""" """
# Init new figure # Init new figure
figure = Figure(**kwargs) figure = Figure(**kwargs)
# data is a list of graphs commands # data is a list of plotting commands
for graph in data: for plot_ in data:
# If we provide a tuple, handle it # If we provide a tuple, handle it
if isinstance(graph, tuple): if isinstance(plot_, tuple):
args = () args = ()
kwargs = {} kwargs = {}
# First case, only two items provided # First case, only two items provided
if len(graph) == 2: if len(plot_) == 2:
# Parse args and kwargs according to type of items # Parse args and kwargs according to type of items
if isinstance(graph[1], tuple): if isinstance(plot_[1], tuple):
args = (graph[1],) args = (plot_[1],)
elif isinstance(graph[1], dict): elif isinstance(plot_[1], dict):
kwargs = graph[1] kwargs = plot_[1]
# Second case, at least 3 items provided # Second case, at least 3 items provided
elif len(graph) > 2: elif len(plot_) > 2:
# Then, args and kwargs are well defined # Then, args and kwargs are well defined
args = (graph[1],) args = (plot_[1],)
kwargs = graph[2] kwargs = plot_[2]
# Pass the correct argument to plot function # Pass the correct argument to plot function
figure.plot(graph[0], *args, **kwargs) figure.plot(plot_[0], *args, **kwargs)
else: else:
figure.plot(graph) figure.plot(plot_)
figure.show() figure.show()

17
replot/exceptions.py Normal file
View File

@ -0,0 +1,17 @@
"""
TODO
"""
class BaseException(Exception):
"""
Base exception for all replot exceptions.
"""
pass
class InvalidParameterError(BaseException):
"""
Exception raised when an invalid parameter is provided.
"""
pass