import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib.patches import *
import pdb

print("""
MatPlotLib Advanced Tutorial
----------------------------
This is a tutorial covering the features and usage of the matplotlib package
in more detail. In truth, no single tutorial can cover all the features that
exist in the matplotlib package since it is extremely expansive. This tutorial
will cover as much material as possible to let you know of the features that
are available to you when plotting data. 

Some Notes:
1) A few parts of this program uses pdb to pause the program and allow the
   user to try making things for themselves. Use c to continue with the
   program.

2) This program uses plt for the reference name of pyplot. All pyplot methods
   should be preceded with the qualifier plt such as plt.show().

3) For the best results, run this program with ipython. Regular python may
   dislike plotting during program execution.
""")

pause = input("Press [Enter] to continue...")
print('\n'*100)

print("""
###
## pyplot
###

Within the matplotlib package, the main module you want to be using is the
pyplot module. It is generally imported as

import matplotlib.pyplot as plt

The pyplot module has many useful functions that can be called and used and
we will go over them one by one. For reference, some useful methods are shown
below.

>>> plt.close()              # Closes the current figure. Optional arguments
                               include passing in a figure, figure number,
                               or the string 'all' which closes all figures.
>>> plt.draw()               # Forces the figure to be redrawn. Useful if it
                               was been updated after it was last shown or
                               drawn.
>>> plt.gca()                # Returns the currently active axes object
>>> plt.gcf()                # Returns the currently active figure object
>>> plt.show()               # Shows the latest figure. By default, matplotlib
                               pauses and waits for the window to be closed
                               before continuing. This feature can be turned
                               off with the keyword block = False.
>>> plt.savefig('title.png') # Saves the figure to a file. The file type is
                               automatically determined by the extension.
                               Supported formats include png, pdf, ps, eps,
                               and svg. This has the keywords dpi which
                               specifies the resolution of the output and
                               bbox_inches which, when set to 'tight' reduces
                               any extra white space in the saved file.
>>> plt.subplots_adjust()    # Allows for adjusting parameters of the layout
                               such as the horizontal space (hspace) or width
                               space (wspace) between plots, as well as the
                               left, right, top, and bottom padding.
""")

pause = input("Press [Enter] to continue...")
print('\n'*100)

print("""
###
## Components of a Plot
###

At it's core, matplotlib is nothing more than a graphics package. Every
component of a plot is just a particular "Artist", all drawn on top of
each other to make a nice looking plot.

The beauty of pyplot is the degree of customization that you can have. 
You have control over every individual component of this plot and you can
change each of them individually. To do this properly, we will focus on
using the object oriented feature of matplotlib.

Before we talk about how to work with all these features, we need to know
what they are. A window should have just popped up that you can examine.
This window shows all the various components of a figure and the names that
pyplot uses for them. This figure contains the following components

-> Figure    The main part of the plot which everything is shown on. This
             encompasses the entire area of the window, excluding the toolbar.
-> Axes      A single plot, added to the figure. This can have many sets of
             data added to it along with other components such as legends.
             Axes can even sit on top of other axes, but importantly, they
             are still a component of figure, not the axes they may sit inside
             of.
-> Axis      Note the difference here! This is an axIs not an axEs. This
             component is a single axis on the axes and defines how the data
             is plotted. An axes, by default has two axises, the x and y
             (unless you're plotting in 3D in which case it has a z). You can
             add more axises though. Each axis has various components such as
             the spine, tick labels, major ticks, and minor ticks.
-> Spine     Each axis has various components. One of them is the spine. This
             is the actual black line at the border of the plots that the
             tick marks are drawn on. Each default axis has 2 spines. For the
             x axis, it has the top and bottom spine and likewise the y axis
             has the right and left.
-> Legend    Each axes can have a legend added to it. The legend can have lines
             on it, one for each curve labeled.
""")

x = np.arange(0,4*np.pi+np.pi/8,np.pi/8)
y1 = np.sin(x)
y2 = np.cos(x)

fig, (ax1, ax2) = plt.subplots(2, figsize = (10,7))
fig.canvas.set_window_title('Pyplot Figure Components')

plt.subplots_adjust(hspace = 0.4)
plt.suptitle('Figure title', fontsize = 20)

#Create subplot 1
ax1.plot(x, y1, '-dr', label = '$sin(x)$')
ax1.plot(x, np.array([0]*len(x)), '--k')
ax1.set_xlim([0,4*np.pi])
ax1.set_title('Axes 1 Title')
ax1.set_xlabel('Axes 1 x-axis label')
ax1.set_ylabel('Axes 1 y-axis label')
ax1.legend(loc = 'best')

#Create subplot 2
ax2.plot(x, y2, ':og', label = '$cos(x)$')
ax2.plot(x, np.array([0]*len(x)), '-k')
ax2.set_xlim([0,4*np.pi])
ax2.set_title('Axes 2 Title')
ax2.set_xlabel('Axes 2 x-axis label')
ax2.set_ylabel('Axes 2 y-axis label')
ax2.legend(loc = 'best')

#Add artists
ax = fig.add_axes([0,0,1,1])
ax.xaxis.set_visible(False)
ax.yaxis.set_visible(False)
ax.set_zorder(0)
ax.set_axis_bgcolor((0, 0, 0, 0))

ax.add_patch(Rectangle((0.01,0.01),0.98,0.98, fill = False, lw = 2, ec = 'b', transform=ax.transAxes))
ax.annotate('Figure', (0.02,0.02), textcoords = 'axes fraction',
            fontsize = 20, color = 'b', transform=ax.transAxes)

ax.add_patch(Rectangle((0.04,0.5),0.9,0.44, fill = False, lw = 2, ec = 'g', transform=ax.transAxes))
ax.annotate('Axes', (0.05,0.52), textcoords = 'axes fraction',
            fontsize = 20, color = 'g', transform=ax.transAxes)

ax.add_patch(Rectangle((0.11,0.08),0.03,0.38, fill = False, lw = 2, ec = 'r', transform=ax.transAxes))
ax.annotate('Axis', (0.045,0.4), textcoords = 'axes fraction',
            fontsize = 20, color = 'r', transform=ax.transAxes)

ax.add_patch(Rectangle((0.11,0.08),0.8,0.04, fill = False, lw = 2, ec = 'r', transform=ax.transAxes))
ax.annotate('Axis', (0.85,0.04), textcoords = 'axes fraction',
            fontsize = 20, color = 'r')

ax.annotate('Spine', (0.8,0.43), xytext = (0.8,0.35), xycoords = 'axes fraction',
            color = (1,0.5,0), fontsize = 20, 
            textcoords = 'axes fraction', horizontalalignment='left',
            arrowprops=dict(arrowstyle = '-|>', fc=(1,0.5,0)))

ax.annotate('', (0.9,0.32), xytext = (0.84,0.34), xycoords = 'axes fraction',
            arrowprops=dict(arrowstyle = '-|>', fc=(1,0.5,0)))

plt.show(block = False)
plt.pause(0.01)

pause = input('Press [Enter] to continue...')
print('\n'*100)

print("""
###
## Objects in matplotlib
###

The above mentioned components of a figure (along with a few others) are
all representable as objects. These objects are stored in a variable which
maintains the state of that object and also has functions the object can
call to change its state. Let's look at how we can use these objects to
create a new figure.
""")

pause = input('Press [Enter] to continue...')
print('\n'*100)

print("""
###
## Creating a New Figure
###

There multiple ways to create a new figure. Probably the simplest is

>>> fig = figure(1, figsize = (5,5), tight_layout = True)

The 1 in this case is an ID for the figure (much like the logical unit
number in IDL). The keywords figsize and tight_layout are optional. The
former sets the physical size of the figure and the second tells the layout
manager to make the plots as close as possible. The state of the figure is
stored in the fig variable which knows entirely about this new figure.
Calling this figure method tells matplotlib that any subsequent plotting
commands should apply to this figure. We can switch to plotting on a new
figure by calling the figure command for another figure (or even switch
back to an old figure). Another method for creating figures is the following

>>> fig = plt.subplots()

This method is much more powerful, but these features will be discussed in
the next section. For reference here are a set of methods and their
functionality that the figure object can call

>>> fig.add_subplot(111)   # Adds a subplot at the specified position
>>> fig.clear()            # Clears the figure's axes
>>> fig.suptitle('Title')  # Adds a title to the figure

Many of the methods listed above as pyplot methods, such as subplots_adjust or
draw, can be applied to a specific figure as well.
""")

pause = input('Press [Enter] to continue...')
print('\n'*100)

print("""
###
## Creating a New Axes
###

Once you have a figure, it's time to add some Axeses to it. As mentioned
before, matplotlib supports using objects. If you've properly created your
figure, it will have been stored into an object. You can now call the method
add_subplot.

>>> ax = fig.add_subplot(1,1,1)

The order of these parameters is add_subplot(rows, columns, plotNo), where
plotNo is the number of the plot, starting at 1 in the upper left and counting
left to right then top to bottom. If all values are less than 10, an equivalent
procedure is to do

>>> ax = fig.add_subplot(111)

Note how this function has created and returned an axes object which we have
stored into the variable ax. There is another method which creates the figure
an axes at the same time

>>> fig, (ax1, ax2) = plt.subplots(nrows = 2, ncols = 1, figsize = (8,8))

The figure is stored into the first variable and the axes are stored into
the second variable with is a tuple of axes.

You can also call the plt.subplot() which acts like add_subplot() but adds
an axes to the currently active figure (determined by the last one referenced).

For more control over your axes positioning, you can specify the exact position
and extent of an axes with the subplot2grid function.

>>> ax = plt.subplot2grid((2,3),(1,0), colspan = 2, rowspan = 1)

This tells figure that there will be a grid of 2 x 3 plots (2 rows, 3 cols) and
this creates a new plot at the position (1,0) (second row, first column) with a
column span of 2 and a row span of 1. If you really want to specify the exact
location, try the add_axes method.

>>> ax = fig.add_axes([0.5, 0.5, 0.3, 0.3])

This tells the figure to put the lower left corner of the axes at the position
(0.5, 0.5) (as fractions of the figure size) and have it span a width and height
of (0.3, 0.3). This is useful to putting plots inside plots. Try this out for
yourself!
""")

pdb.set_trace()
print('\n'*100)

print("""
###
## Plotting to Axes
###

There are many types of plots that can be put on an axes. Below are some simple
examples.

>>> ax.plot()     # Simple scatter/line plot
>>> ax.bar()      # Vertical bar plot
>>> ax.barh()     # Horizonatal bar plot
>>> ax.boxplot()  # Box and wisker plot
>>> ax.contour()  # Contour plot of lines
>>> ax.contourf() # Filled contour plot
>>> ax.errorbar() # Scatter/line plot with errorbars
>>> ax.fill()     # Scatter/line plot which is filled below the curve
>>> ax.hist()     # A histogram of the input data
>>> ax.loglog()   # Scatter/line plot that is logarithmic on both axes
>>> ax.pie()      # Pie chart
>>> ax.polar()    # Polar plot
>>> ax.quiver()   # 2D field of arrows
>>> ax.semilogx() # Scatter/line plot with logarithmic x and linear y.
>>> ax.semilogy() # Equivalent to semilogx, but now y is logarithmic
>>> ax.steamplot()# A streamline of a vector flow
>>> ax.step()     # A step plot

Feel free to try out some of these. You may have to look up the proper
procedures online.
""")

pdb.set_trace()
print('\n'*100)

print("""
###
## Axes Methods
###

Aside from the many plots, there are many useful methods to adjust the
properties of of the axes

>>> ax.add_patch()     # Adds a 'patch' which is an artist like arrows or circles
>>> ax.annotate()      # Adds a string with an arrow to the axes
>>> ax.axhspan()       # Adds a horizontal bar across the plot
>>> ax.axvspan()       # Adds a vertical bar across the plot
>>> ax.arrow()         # Adds an arrow
>>> ax.cla()           # Clears the axes
>>> ax.colorbar()      # Colorbar added to the plot
>>> ax.grid()          # Turns on grid lines, keywords include which (major or
                         minor) and axis (both, x, or y).
>>> ax.legend()        # Legend added to the plot
>>> ax.minorticks_on() # Turns on minor tick marks
>>> ax.set_cmap()      # Sets the color map of the axes
>>> ax.set_title()     # Sets the title of the axes
>>> ax.set_xlabel()    # Sets the x label of the axes
>>> ax.set_xlim()      # Sets the x limits of the axes
>>> ax.set_xscale()    # Sets the scale, either linear, log, or symlog
>>> ax.set_xticklabels()#A list of strings to use for the tick labels
>>> ax.set_xticks()    # Set's values of tick marks with list
## The above x axis specific functions have analagous y axis functions
>>> ax.text()          # Adds a string to the axes
>>> ax.tick_params()   # Changes tick and tick label appearances

Try playing with these various features after creating a figure and axes.
""")

pdb.set_trace()
print('\n'*100)

print("""
###
## Axis and Spines
###

Just as you can work with the specific axes on a figure, you can also work with
specific axis and spines on your axes. These can be extracted and stored in
their own variables, but it is generally easier to refer to them as the
components of the axes object. They are accessed in the following way.

>>> ax.xaxis
>>> ax.yaxis
>>> ax.spine['top']    # Spine is a dict with components, 'top', 'bottom',
                         'left', and 'right'.

These components of the axes have the following useful methods.

>>> ax.xaxis.set_major_formatter()# Set's how the tick marks are formatted
>>> ax.xaxis.set_major_locator()  # Sets location of tick marks (see locators)
## The above major methods have analagous minor methods
>>> ax.xaxis.set_ticklabels()     # Set to empty list to turn off labels
>>> ax.xaxis.set_ticks_position() # Change tick position to only 'top', 'left, etc.
## The above xaxis methods have analagous yaxis methods
>>> ax.spines['top'].set_color()  # Sets the color of the spine
>>> ax.spines['top'].set_position()# Changes position of the spine
>>> ax.spines['top'].set_visible()# Turns off the spine
## The above spine methods have analagous methods for 'bottom', 'left', and
   'right'

Feel free to play with these properties as well.
""")

pdb.set_trace()
print('\n'*100)

print("""
###
## Higher Degrees of Customization
###

We could choose to go even further down the ladder than axis and spines. It is
possible to get the tickmark objects from an axis (via get_major_ticks()) and
change properties on a tickmark by tickmark basis. However, it is no longer
instructive to continue showing methods and ways of doing this as it can always
be looked up. For extreme control over every component of plotting, it is sometimes
useful to use the rcParams variable. This should be imported as

from matplotlib import rcParams

You can then refer to any component of the figure by referencing the dict's
keyword, and setting the value. Common examples include

>>> rcParams['lines.linewidth'] = 2   # Sets linewidths to be 2 by default
>>> rcParams['lines.color'] = 'r'     # Sets line colors to be red by default

There are hundreds of parameters that can be set, all of which can be seen by
going here http://matplotlib.org/users/customizing.html.
""")

pause = input('Press [Enter] to continue...')
print('\n'*100)

print("""
###
## Animations
###

This will only introduce the idea of animations. To actually produce saved
animations in the form of mp4 or some similar format requires installing third
party programs such as ffmpeg. However, matplotlib comes with an animation
package imported as matplotlib.animation. It has tools to allow you to
continually update a plot such that it is animated. There are abilities to
save the animation as well. Below is the code for a very simple animation plot.

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation

fig, ax = plt.subplots()
ax.set_xlim([0,2*np.pi])

x = np.arange(0, 2*np.pi, 0.01)   # x-array
line, = ax.plot(x, np.sin(x))     # The comma after line makes it a tuple

#Init only required for blitting to give a clean slate.
def init():
    line.set_ydata(np.ma.array(x, mask=True))
    return line,

def animate(i):
    line.set_ydata(np.sin(x+i/10.0))  # update the data
    return line,

#blit=True means only redraw the components which have updated. This is
#faster than redrawing everything.
ani = animation.FuncAnimation(fig, animate, init_func=init,
                              interval=25, blit=True)
plt.show()
""")

fig, ax = plt.subplots()
ax.set_xlim([0,2*np.pi])

x = np.arange(0, 2*np.pi, 0.01)   # x-array
line, = ax.plot(x, np.sin(x))     # The comma after line makes it a tuple

#Init only required for blitting to give a clean slate.
def init():
    line.set_ydata(np.ma.array(x, mask=True))
    return line,

def animate(i):
    line.set_ydata(np.sin(x+i/10.0))  # update the data
    return line,

#blit=True means only redraw the components which have updated. This is
#faster than redrawing everything.
ani = animation.FuncAnimation(fig, animate, init_func=init,
                              interval=25, blit=True)
plt.show(block = False)

print('Done...')
