2016-03-01 14:51:25 +01:00
|
|
|
"""
|
|
|
|
* Saner default config.
|
|
|
|
* Matplotlib API methods have an immediate effect on the figure. We do not want
|
|
|
|
it, then we write a buffer on top of matplotlib API.
|
|
|
|
"""
|
|
|
|
import matplotlib.pyplot as plt
|
|
|
|
import numpy as np
|
|
|
|
import seaborn
|
|
|
|
|
|
|
|
|
2016-03-01 15:12:59 +01:00
|
|
|
# Cancel seaborn modifications of matplotlib, we do not want to interfere in
|
|
|
|
# any ways with matplotlib
|
|
|
|
seaborn.reset_orig()
|
|
|
|
|
|
|
|
|
|
|
|
# TODO: Remove it, this is interfering with matplotlib
|
2016-03-01 14:51:25 +01:00
|
|
|
plt.rcParams['figure.figsize'] = (10.0, 8.0) # Larger figures by default
|
|
|
|
plt.rcParams['text.usetex'] = True # Use LaTeX rendering
|
|
|
|
|
|
|
|
|
|
|
|
class Figure():
|
|
|
|
def __init__(self,
|
|
|
|
xlabel="", ylabel="", title="", palette="hls",
|
|
|
|
legend=None):
|
2016-03-01 15:12:59 +01:00
|
|
|
# TODO: Constants
|
2016-03-01 14:51:25 +01:00
|
|
|
self.max_colors = 10
|
|
|
|
self.default_points_number = 1000
|
|
|
|
self.default_x_interval = np.linspace(-10, 10,
|
|
|
|
self.default_points_number)
|
|
|
|
|
2016-03-01 15:12:59 +01:00
|
|
|
# Set default values for attributes
|
2016-03-01 14:51:25 +01:00
|
|
|
self.xlabel = xlabel
|
|
|
|
self.ylabel = ylabel
|
|
|
|
self.title = title
|
|
|
|
self.palette = palette
|
|
|
|
self.legend = legend
|
|
|
|
self.plots = []
|
|
|
|
|
|
|
|
def __enter__(self):
|
|
|
|
return self
|
|
|
|
|
|
|
|
def __exit__(self, exception_type, exception_value, traceback):
|
|
|
|
self.show()
|
|
|
|
|
|
|
|
def show(self):
|
|
|
|
"""
|
2016-03-01 15:12:59 +01:00
|
|
|
Actually render and show the figure.
|
2016-03-01 14:51:25 +01:00
|
|
|
"""
|
2016-03-01 15:12:59 +01:00
|
|
|
# Tweak matplotlib to use seaborn
|
2016-03-01 14:51:25 +01:00
|
|
|
seaborn.set()
|
2016-03-01 15:12:59 +01:00
|
|
|
# Plot using specified color palette
|
2016-03-01 14:51:25 +01:00
|
|
|
with seaborn.color_palette(self.palette, self.max_colors):
|
|
|
|
# Create figure
|
|
|
|
figure, axes = plt.subplots()
|
|
|
|
# Add plots
|
|
|
|
for plot in self.plots:
|
|
|
|
axes.plot(*(plot[0]), **(plot[1]))
|
|
|
|
# Set properties
|
|
|
|
axes.set_xlabel(self.xlabel)
|
|
|
|
axes.set_ylabel(self.ylabel)
|
|
|
|
axes.set_title(self.title)
|
2016-03-01 15:12:59 +01:00
|
|
|
if self.legend is not None and self.legend is not False:
|
2016-03-01 14:51:25 +01:00
|
|
|
self._legend(axes, location=self.legend)
|
|
|
|
# Draw figure
|
|
|
|
figure.show()
|
2016-03-01 15:12:59 +01:00
|
|
|
# Do not forget to restore matplotlib state, in order not to interfere
|
|
|
|
# with it.
|
2016-03-01 14:51:25 +01:00
|
|
|
seaborn.reset_orig()
|
|
|
|
|
|
|
|
|
|
|
|
#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(np.sin)
|
|
|
|
>>> plot(np.sin, (-1, 1))
|
|
|
|
>>> plot(np.sin, [-1, -0.9, …, 1])
|
|
|
|
>>> plot([1, 2, 3], [4, 5, 6])
|
|
|
|
"""
|
|
|
|
if hasattr(data, "__call__"):
|
2016-03-01 15:12:59 +01:00
|
|
|
# We want to plot a function
|
2016-03-01 14:51:25 +01:00
|
|
|
self._plot_function(data, *args, **kwargs)
|
|
|
|
else:
|
2016-03-01 15:12:59 +01:00
|
|
|
# Else, it is a point series, and we just have to store it for
|
|
|
|
# later plotting.
|
2016-03-01 14:51:25 +01:00
|
|
|
self.plots.append(((data,) + args, kwargs))
|
|
|
|
|
2016-03-01 15:12:59 +01:00
|
|
|
# Automatically set the legend if label is found
|
|
|
|
# (only do it if legend is not explicitly suppressed)
|
|
|
|
if "label" in kwargs and self.legend is None:
|
|
|
|
self.legend = True
|
|
|
|
|
2016-03-01 14:51:25 +01:00
|
|
|
def _plot_function(self, data, *args, **kwargs):
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
# TODO: Better default interval and so on
|
|
|
|
if len(args) == 0:
|
|
|
|
# No interval specified, using default one
|
|
|
|
x_values = self.default_x_interval
|
|
|
|
elif isinstance(args[0], (list, np.ndarray)):
|
2016-03-01 15:12:59 +01:00
|
|
|
# List of points specified
|
2016-03-01 14:51:25 +01:00
|
|
|
x_values = args[0]
|
|
|
|
elif isinstance(args[0], tuple):
|
2016-03-01 15:12:59 +01:00
|
|
|
# Interval specified, generate a list of points
|
2016-03-01 14:51:25 +01:00
|
|
|
x_values = np.linspace(args[0][0], args[0][1],
|
|
|
|
self.default_points_number)
|
|
|
|
else:
|
|
|
|
# TODO: Error
|
|
|
|
assert False
|
|
|
|
y_values = [data(i) for i in x_values]
|
|
|
|
self.plots.append(((x_values, y_values) + args[1:], kwargs))
|
|
|
|
|
|
|
|
def _legend(self, axes, location="best"):
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
if location is True:
|
2016-03-01 15:12:59 +01:00
|
|
|
# If there should be a legend, but no location provided, put it at
|
|
|
|
# best location.
|
2016-03-01 14:51:25 +01:00
|
|
|
location = "best"
|
2016-03-01 15:12:59 +01:00
|
|
|
# Create aliases for "upper" / "top" and "lower" / "bottom"
|
2016-03-01 14:51:25 +01:00
|
|
|
location.replace("top ", "upper ")
|
|
|
|
location.replace("bottom ", "lower ")
|
2016-03-01 15:34:11 +01:00
|
|
|
# Avoid warning if no labels were given for plots
|
|
|
|
nb_labelled_plots = sum(["label" in plt[1] for plt in self.plots])
|
|
|
|
if nb_labelled_plots > 0:
|
|
|
|
# Add legend
|
|
|
|
axes.legend(loc=location)
|
|
|
|
|
|
|
|
|
|
|
|
def plot(data, **kwargs):
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
figure = Figure(**kwargs)
|
|
|
|
# TODO: Fix API, support every plot type
|
|
|
|
for plt in data:
|
|
|
|
figure.plot(plt)
|
|
|
|
return figure
|