import numpy as np
import matplotlib as mpl

from matplotlib import pyplot as plt
from matplotlib import rc
from matplotlib import colors
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
from matplotlib.colors import LightSource
from matplotlib.ticker import FormatStrFormatter
import matplotlib.font_manager as font_manager
import matplotlib.dates as mdates

from pylab import *

from numpy import sin, cos
from skimage import measure

from mock_data import MockData
from colormaps import ColorMaps

mpl.use('Qt5Agg')


class D1:

    def __init__(self,
                 fig=None,
                 ax=None,
                 shape_and_position=None,
                 font='serif',
                 light=None, dark=None,
                 usetex=False):

        rc('text', usetex=usetex)

        self.fig = fig
        self.ax = ax
        self.axes3d = ax
        self.shape_and_position = shape_and_position

        self.font = font
        self.light = light
        self.dark = dark

        # Style
        if light is None and dark is None:
            self.color = 'black'
            self.color2 = (193/256, 193/256, 193/256)
            self.style = None

        if self.light:
            self.color = 'black'
            self.color2 = (193/256, 193/256, 193/256)
            self.style = 'classic'
        elif self.dark:
            self.color = 'white'
            self.color2 = (89/256, 89/256, 89/256)
            self.style = 'dark_background'

        self.setup()

    def setup(self, figsize=None):

        if self.style is not None:
            plt.style.use(self.style)

        if isinstance(self.fig, type(None)):
            self.fig = plt.figure(figsize=figsize)

        return self.fig

    def scatter3d(self,
                  x=None, x_scale=1, x_pad=0,
                  y=None, y_scale=1, y_pad=0,
                  z=None, z_scale=1, z_pad=0,
                  pointsize=60,
                  edge_color='b', edges_to_RGBA=True, rstride=1, cstride=1, alpha=1, linewidth=0,
                  box_to_plot_pad=10,
                  grid=False, pane_fill=False, prune=None, lighting=False, antialiased=False, shade=False,
                  norm=None, cmap='RdBu_r', c=None,
                  color_bar=False, cb_title=None, cb_axis_labelpad=10, cb_nticks=10, shrink=0.75,
                  cb_outlinewidth=None, cb_title_rotation=None, cb_title_style='normal', cb_title_size=10,
                  cb_top_title_y=1, cb_ytitle_labelpad=10, cb_title_weight='normal', cb_top_title=False,
                  cb_y_title=False,
                  cb_vmin=None, cb_vmax=None,
                  plot_title='Drop - Wave function', title_bold=False, title_size=12, title_y=1,
                  x_label='x', xaxis_bold=False, xaxis_label_size=12, xaxis_labelpad=5,
                  xlabel_rotation=None, x_tick_number=10,
                  y_label='y', yaxis_bold=False, yaxis_label_size=12, yaxis_labelpad=5,
                  ylabel_rotation=None, y_tick_number=10,
                  z_label='z', zaxis_bold=False, zaxis_label_size=12, zaxis_labelpad=5,
                  zlabel_rotation=None, z_tick_number=10,
                  tick_color=None, tick_label_size=7, tick_label_pad=5, tick_ndecimals=1,
                  xtick_rotation=None, ytick_rotation=None, ztick_rotation=None,
                  custom_subplots=False, resize_axes=True, more_subplots_left=False,
                  filename=None, dpi=None,
                  cb_top_title_pad=None,
                  cb_top_title_x=0):

        if isinstance(self.fig, type(None)):
            self.setup()

        if isinstance(self.axes3d, type(None)):
            if isinstance(self.shape_and_position, type(None)):
                self.shape_and_position = 111
            self.axes3d = self.fig.add_subplot(self.shape_and_position, projection='3d')

        # Scaling
        max_scale = max(x_scale, y_scale, z_scale)
        x_scale = x_scale/max_scale
        y_scale = y_scale/max_scale
        z_scale = z_scale/max_scale

        # Reference:
        # https://stackoverflow.com/questions/30223161/matplotlib-mplot3d-how-to-increase-the-size-of-an-axis-stretch-in-a-3d-plo
        self.axes3d.get_proj = lambda: np.dot(Axes3D.get_proj(self.axes3d), np.diag([x_scale, y_scale, z_scale, 1]))

        # Distance from axes to limit of plot
        self.axes3d.dist = box_to_plot_pad

        # Mock plot
        if isinstance(x, type(None)) and isinstance(y, type(None)) and isinstance(z, type(None)):
            x, y, z = MockData().waterdrop3d()

        # Normalization
        if norm is not None:
            norm = self.normalize(array=z, norm=norm, variant='Power')

        # Plot
        graph = self.axes3d.scatter(x, y, z, cmap=cmap,
                                    edgecolors=edge_color, alpha=alpha,
                                    norm=norm, marker='s', c=c, linewidth=linewidth, s=pointsize)

        # Edges to RGBA
        if edges_to_RGBA is True:
            graph.set_edgecolors(graph.to_rgba(graph._A))

        # Color bar
        vmin = float(z.min())
        vmax = float(z.max())

        if color_bar is True:
            graph_for_color_bar = graph
            self.color_bar(graph_for_color_bar=graph_for_color_bar, cb_top_title_pad=cb_top_title_pad,
                           cb_axis_labelpad=cb_axis_labelpad, cb_nticks=cb_nticks, shrink=shrink, norm=norm,
                           cb_outlinewidth=cb_outlinewidth, cb_title=cb_title, cb_title_rotation=cb_title_rotation,
                           cb_title_size=cb_title_size, cb_title_style=cb_title_style,
                           cb_ytitle_labelpad=cb_ytitle_labelpad, cb_title_weight=cb_title_weight,
                           cb_y_title=cb_y_title,
                           cb_top_title=cb_top_title, cb_top_title_y=cb_top_title_y,
                           cb_vmin=cb_vmin, cb_vmax=cb_vmax,
                           tick_ndecimals=tick_ndecimals,
                           cb_top_title_x=cb_top_title_x)
        # Resize axes
        if resize_axes is True:
            z_pad = z_pad if z_pad > (abs(z.max()) + abs(z.min())) / 16 else (abs(z.max()) + abs(z.min())) / 16
            self.resize_axes3d(x_bounds=(x.min(), x.max()), y_bounds=(y.min(), y.max()), z_bounds=(z.min(), z.max()),
                               x_pad=x_pad, y_pad=y_pad, z_pad=z_pad)

        # Makeup
        self.makeup3d(plot_title=plot_title, title_bold=title_bold, title_size=title_size, title_y=title_y,
                      grid=grid, pane_fill=pane_fill,
                      x_label=x_label, xaxis_bold=xaxis_bold, xaxis_label_size=xaxis_label_size, xaxis_labelpad=xaxis_labelpad, xlabel_rotation=xlabel_rotation, x_tick_number=x_tick_number,
                      y_label=y_label, yaxis_bold=yaxis_bold, yaxis_label_size=yaxis_label_size, yaxis_labelpad=yaxis_labelpad, ylabel_rotation=ylabel_rotation, y_tick_number=y_tick_number,
                      z_label=z_label, zaxis_bold=zaxis_bold, zaxis_label_size=zaxis_label_size, zaxis_labelpad=zaxis_labelpad, zlabel_rotation=zlabel_rotation, z_tick_number=z_tick_number,
                      tick_color=tick_color, tick_label_size=tick_label_size, tick_label_pad=tick_label_pad,
                      xtick_rotation=xtick_rotation, ytick_rotation=ytick_rotation, ztick_rotation=ztick_rotation,
                      prune=prune, tick_ndecimals=tick_ndecimals)

        self.fig.tight_layout()

        # Save
        if not isinstance(filename, type(None)):
            plt.savefig(filename, dpi=dpi)

        # More subplots
        if more_subplots_left is not True:
            plt.show()
        else:
            print('Ready for next subplot')

        return self.axes3d

    def surface3d(self,
                  x=None, x_scale=1, x_pad=0,
                  y=None, y_scale=1, y_pad=0,
                  z=None, z_scale=1, z_pad=0,
                  edge_color='b', edges_to_RGBA=True, rstride=1, cstride=1, alpha=1, linewidth=0,
                  box_to_plot_pad=10,
                  grid=False, pane_fill=False, prune=None, lighting=True, antialiased=True, shade=True,
                  norm=None, cmap='RdBu_r',
                  color_bar=False, cb_title=None, cb_axis_labelpad=10, cb_nticks=10, shrink=0.75,
                  cb_outlinewidth=None, cb_title_rotation=None, cb_title_style='normal', cb_title_size=10,
                  cb_top_title_y=1, cb_ytitle_labelpad=10, cb_title_weight='normal', cb_top_title=False,
                  cb_y_title=False, cb_ticklabelsize=10,
                  cb_vmin=None, cb_vmax=None,
                  plot_title='Drop - Wave function', title_bold=False, title_size=12, title_y=1,
                  x_label='x', xaxis_bold=False, xaxis_label_size=12, xaxis_labelpad=5,
                  xlabel_rotation=None, x_tick_number=10,
                  y_label='y', yaxis_bold=False, yaxis_label_size=12, yaxis_labelpad=5,
                  ylabel_rotation=None, y_tick_number=10,
                  z_label='z', zaxis_bold=False, zaxis_label_size=12, zaxis_labelpad=5,
                  zlabel_rotation=None, z_tick_number=10,
                  tick_color=None, tick_label_size=7, tick_label_pad=5, tick_ndecimals=1,
                  xtick_rotation=None, ytick_rotation=None, ztick_rotation=None,
                  custom_subplots=False, resize_axes=True, more_subplots_left=False,
                  filename=None, dpi=None,
                  cb_top_title_pad=None,
                  cb_top_title_x=0):

        if isinstance(self.fig, type(None)):
            self.setup()

        if isinstance(self.axes3d, type(None)):
            if isinstance(self.shape_and_position, type(None)):
                self.shape_and_position = 111
            self.axes3d = self.fig.add_subplot(self.shape_and_position, projection='3d')

        # Scaling
        max_scale = max(x_scale, y_scale, z_scale)
        x_scale = x_scale/max_scale
        y_scale = y_scale/max_scale
        z_scale = z_scale/max_scale

        # Reference:
        # https://stackoverflow.com/questions/30223161/matplotlib-mplot3d-how-to-increase-the-size-of-an-axis-stretch-in-a-3d-plo
        self.axes3d.get_proj = lambda: np.dot(Axes3D.get_proj(self.axes3d), np.diag([x_scale, y_scale, z_scale, 1]))

        # Distance from axes to limit of plot
        self.axes3d.dist = box_to_plot_pad

        # Mock plot
        if isinstance(x, type(None)) and isinstance(y, type(None)) and isinstance(z, type(None)):
            x, y, z = MockData().waterdrop3d()

        # Normalization
        if norm is not None:
            norm = self.normalize(array=z, norm=norm, variant='Power')

        # Plot
        graph = self.axes3d.plot_surface(x, y, z, cmap=cmap,
                                         edgecolors=edge_color, alpha=alpha,
                                         rstride=rstride, cstride=cstride, linewidth=linewidth,
                                         norm=norm,
                                         facecolors=self.lighting(z, cmap) if lighting is True else None,
                                         antialiased=antialiased,
                                         shade=shade,
                                         vmax=cb_vmax, vmin=cb_vmin,
                                         )

        # Edges to RGBA
        if edges_to_RGBA is True:
            graph.set_edgecolors(graph.to_rgba(graph._A))

        # Color bar
        vmin = float(z.min())
        vmax = float(z.max())

        if color_bar is True:
            graph_for_color_bar = graph
            self.color_bar(graph_for_color_bar=graph_for_color_bar, cb_top_title_pad=cb_top_title_pad,
                           cb_axis_labelpad=cb_axis_labelpad, cb_nticks=cb_nticks, shrink=shrink, norm=norm,
                           cb_outlinewidth=cb_outlinewidth, cb_title=cb_title, cb_title_rotation=cb_title_rotation,
                           cb_title_size=cb_title_size, cb_title_style=cb_title_style,
                           cb_ytitle_labelpad=cb_ytitle_labelpad, cb_title_weight=cb_title_weight,
                           cb_y_title=cb_y_title,
                           cb_top_title=cb_top_title, cb_top_title_y=cb_top_title_y,
                           cb_vmin=cb_vmin, cb_vmax=cb_vmax,
                           tick_ndecimals=tick_ndecimals,
                           cb_top_title_x=cb_top_title_x,
                           cb_ticklabelsize=cb_ticklabelsize)
        # Resize axes
        if resize_axes is True:
            z_pad = z_pad if z_pad > (abs(z.max()) + abs(z.min())) / 16 else (abs(z.max()) + abs(z.min())) / 16
            self.resize_axes3d(x_bounds=(x.min(), x.max()), y_bounds=(y.min(), y.max()), z_bounds=(z.min(), z.max()),
                               x_pad=x_pad, y_pad=y_pad, z_pad=z_pad)

        # Makeup
        self.makeup3d(plot_title=plot_title, title_bold=title_bold, title_size=title_size, title_y=title_y,
                      grid=grid, pane_fill=pane_fill,
                      x_label=x_label, xaxis_bold=xaxis_bold, xaxis_label_size=xaxis_label_size, xaxis_labelpad=xaxis_labelpad, xlabel_rotation=xlabel_rotation, x_tick_number=x_tick_number,
                      y_label=y_label, yaxis_bold=yaxis_bold, yaxis_label_size=yaxis_label_size, yaxis_labelpad=yaxis_labelpad, ylabel_rotation=ylabel_rotation, y_tick_number=y_tick_number,
                      z_label=z_label, zaxis_bold=zaxis_bold, zaxis_label_size=zaxis_label_size, zaxis_labelpad=zaxis_labelpad, zlabel_rotation=zlabel_rotation, z_tick_number=z_tick_number,
                      tick_color=tick_color, tick_label_size=tick_label_size, tick_label_pad=tick_label_pad,
                      xtick_rotation=xtick_rotation, ytick_rotation=ytick_rotation, ztick_rotation=ztick_rotation,
                      prune=prune, tick_ndecimals=tick_ndecimals)

        self.fig.tight_layout()

        # Save
        if not isinstance(filename, type(None)):
            plt.savefig(filename, dpi=dpi)

        # More subplots
        if more_subplots_left is not True:
            plt.show()
        else:
            print('Ready for next subplot')

        return self.axes3d

    def tricontour(self,
                   x=None, x_scale=1, x_pad=0,
                   y=None, y_scale=1, y_pad=0,
                   z=None, z_scale=1, z_pad=0,
                   levels=5, alpha=1,
                   box_to_plot_pad=12,
                   grid=False, pane_fill=False, prune=None,
                   norm=None, cmap='RdBu_r',
                   color_bar=False, cb_title=None, cb_axis_labelpad=10, cb_nticks=10, shrink=0.75,
                   cb_outlinewidth=None, cb_title_rotation=None, cb_title_style='normal', cb_title_size=10,
                   cb_top_title_y=1, cb_ytitle_labelpad=10, cb_title_weight='normal', cb_top_title=False,
                   cb_y_title=False,
                   cb_vmin=None, cb_vmax=None,
                   plot_title='Drop - Wave function', title_bold=False, title_size=12, title_y=1,
                   x_label='x', xaxis_bold=False, xaxis_label_size=12, xaxis_labelpad=5, xlabel_rotation=None, x_tick_number=10,
                   y_label='y', yaxis_bold=False, yaxis_label_size=12, yaxis_labelpad=5, ylabel_rotation=None, y_tick_number=10,
                   z_label='z', zaxis_bold=False, zaxis_label_size=12, zaxis_labelpad=5, zlabel_rotation=None, z_tick_number=10,
                   tick_color=None, tick_label_size=7, tick_label_pad=5, tick_ndecimals=1,
                   xtick_rotation=None, ytick_rotation=None, ztick_rotation=None,
                   custom_subplots=False, resize_axes=True, more_subplots_left=False,
                   filename=None, dpi=None,
                   cb_top_title_pad=None,
                   cb_top_title_x=0):

        if isinstance(self.fig, type(None)):
            self.setup()

        if isinstance(self.axes3d, type(None)):
            if isinstance(self.shape_and_position, type(None)):
                self.shape_and_position = 111
            self.axes3d = self.fig.add_subplot(self.shape_and_position, projection='3d')

        # Scaling
        max_scale = max(x_scale, y_scale, z_scale)
        x_scale = x_scale / max_scale
        y_scale = y_scale / max_scale
        z_scale = z_scale / max_scale

        # Reference:
        # https://stackoverflow.com/questions/30223161/matplotlib-mplot3d-how-to-increase-the-size-of-an-axis-stretch-in-a-3d-plo
        self.axes3d.get_proj = lambda: np.dot(Axes3D.get_proj(self.axes3d), np.diag([x_scale, y_scale, z_scale, 1]))

        # Distance from axes to limit of plot
        self.axes3d.dist = box_to_plot_pad

        # Mock plot
        if isinstance(x, type(None)) and isinstance(y, type(None)) and isinstance(z, type(None)):
            x, y, z = MockData().random3d()

        # Normalization
        if norm is not None:
            norm = self.normalize(array=z, norm=norm, variant='Power')

        # Plot
        cf = self.axes3d.tricontourf(x, y, z, levels=levels, cmap=cmap, alpha=alpha, norm=norm)

        for c in cf.collections:
            c.set_edgecolor('None')

        # Color bar

        vmin = float(z.min())
        vmax = float(z.max())

        if color_bar is True:
            graph_for_color_bar = cf
            self.color_bar(graph_for_color_bar=graph_for_color_bar, cb_top_title_pad=cb_top_title_pad,
                           cb_axis_labelpad=cb_axis_labelpad, cb_nticks=cb_nticks, shrink=shrink, norm=norm,
                           cb_outlinewidth=cb_outlinewidth, cb_title=cb_title, cb_title_rotation=cb_title_rotation,
                           cb_title_size=cb_title_size, cb_title_style=cb_title_style,
                           cb_ytitle_labelpad=cb_ytitle_labelpad, cb_title_weight=cb_title_weight,
                           cb_y_title=cb_y_title,
                           cb_top_title=cb_top_title, cb_top_title_y=cb_top_title_y,
                           cb_vmin=cb_vmin, cb_vmax=cb_vmax,
                           tick_ndecimals=tick_ndecimals,
                           cb_top_title_x=cb_top_title_x)

        z_pad = z_pad if z_pad > (abs(z.max())+abs(z.min()))/4 else (abs(z.max())+abs(z.min()))/4

        # Resize axes
        if custom_subplots is True:
            resize_axes = False

        if resize_axes is True:
            z_pad = z_pad if z_pad > (abs(z.max()) + abs(z.min())) / 16 else (abs(z.max()) + abs(z.min())) / 16
            self.resize_axes3d(x_bounds=(x.min(), x.max()), y_bounds=(y.min(), y.max()), z_bounds=(z.min(), z.max()),
                               x_pad=x_pad, y_pad=y_pad, z_pad=z_pad)

        # Makeup
        self.makeup3d(plot_title=plot_title, title_bold=title_bold, title_size=title_size, title_y=title_y,
                      grid=grid, pane_fill=pane_fill, prune=prune,
                      x_label=x_label, xaxis_bold=xaxis_bold, xaxis_label_size=xaxis_label_size, xaxis_labelpad=xaxis_labelpad, xlabel_rotation=xlabel_rotation, x_tick_number=x_tick_number,
                      y_label=y_label, yaxis_bold=yaxis_bold, yaxis_label_size=yaxis_label_size, yaxis_labelpad=yaxis_labelpad, ylabel_rotation=ylabel_rotation, y_tick_number=y_tick_number,
                      z_label=z_label, zaxis_bold=zaxis_bold, zaxis_label_size=zaxis_label_size, zaxis_labelpad=zaxis_labelpad, zlabel_rotation=zlabel_rotation, z_tick_number=z_tick_number,
                      tick_color=tick_color, tick_label_size=tick_label_size, tick_label_pad=tick_label_pad,
                      xtick_rotation=xtick_rotation, ytick_rotation=ytick_rotation, ztick_rotation=ztick_rotation,
                      tick_ndecimals=tick_ndecimals)

        self.fig.tight_layout()

        # Save
        if not isinstance(filename, type(None)):
            plt.savefig(filename, dpi=dpi)

        # More subplots
        if more_subplots_left is not True:
            plt.show()
        else:
            print('Ready for next subplot')

        return self.axes3d

    def projections(self,
                    x=None, x_center=None, x_scale=1, x_pad=0,
                    y=None, y_center=None, y_scale=1, y_pad=0,
                    z=None, z_center=None, z_scale=1, z_pad=0, alpha=1,
                    switch=True,
                    box_to_plot_pad=12,
                    grid=False, pane_fill=False, prune=None,
                    norm=None, cmap='RdBu_r',
                    color_bar=False, cb_title=None, cb_axis_labelpad=10, cb_nticks=10, shrink=0.75,
                    cb_outlinewidth=None, cb_title_rotation=None, cb_title_style='normal', cb_title_size=10,
                    cb_top_title_y=1, cb_ytitle_labelpad=10, cb_title_weight='normal', cb_top_title=False,
                    cb_y_title=False,
                    cb_vmin=None, cb_vmax=None,
                    plot_title='Drop - Wave function', title_bold=False, title_size=12, title_y=1,
                    x_label='x', xaxis_bold=False, xaxis_label_size=12, xaxis_labelpad=5, xlabel_rotation=None, x_tick_number=10,
                    y_label='y', yaxis_bold=False, yaxis_label_size=12, yaxis_labelpad=5, ylabel_rotation=None, y_tick_number=10,
                    z_label='z', zaxis_bold=False, zaxis_label_size=12, zaxis_labelpad=5, zlabel_rotation=None, z_tick_number=10,
                    tick_color=None, tick_label_size=7, tick_label_pad=5, tick_ndecimals=1,
                    xtick_rotation=None, ytick_rotation=None, ztick_rotation=None,
                    custom_subplots=False, resize_axes=True, more_subplots_left=False,
                    filename=None, dpi=None,
                    cb_top_title_pad=None,
                    cb_top_title_x=0):

        if isinstance(self.fig, type(None)):
            self.setup()

        if isinstance(self.axes3d, type(None)):
            if isinstance(self.shape_and_position, type(None)):
                self.shape_and_position = 111
            self.axes3d = self.fig.add_subplot(self.shape_and_position, projection='3d')

        # Scaling
        max_scale = max(x_scale, y_scale, z_scale)
        x_scale = x_scale / max_scale
        y_scale = y_scale / max_scale
        z_scale = z_scale / max_scale

        # Reference:
        # https://stackoverflow.com/questions/30223161/matplotlib-mplot3d-how-to-increase-the-size-of-an-axis-stretch-in-a-3d-plo
        self.axes3d.get_proj = lambda: np.dot(Axes3D.get_proj(self.axes3d), np.diag([x_scale, y_scale, z_scale, 1]))

        # Distance from axes to limit of plot
        self.axes3d.dist = box_to_plot_pad

        # Mock plot
        if isinstance(x, type(None)) and isinstance(y, type(None)) and isinstance(z, type(None)):
            x, y, z = MockData().waterdrop3d()

        # Normalization
        if norm is not None:
            norm = self.normalize(array=z, norm=norm, variant='Power')

        # Resize axes
        z_pad = z_pad if z_pad > (abs(z.max()) + abs(z.min())) / 8 else (abs(z.max()) + abs(z.min())) / 8

        self.resize_axes3d(x_bounds=(x.min(), x.max()), y_bounds=(y.min(), y.max()), z_bounds=(z.min(), z.max()),
                           x_pad=x_pad, y_pad=y_pad, z_pad=z_pad)

        # Plot
        x_plane = self.axes3d.contourf(x, y, z, zdir='x', offset=self.axes3d.get_xlim()[0], cmap=cmap, alpha=alpha, norm=norm)
        y_plane = self.axes3d.contourf(x, y, z, zdir='y', offset=self.axes3d.get_ylim()[1], cmap=cmap, alpha=alpha, norm=norm)
        z_plane = self.axes3d.contourf(x, y, z, zdir='z', offset=self.axes3d.get_zlim()[0], cmap=cmap, alpha=alpha, norm=norm)

        for c in x_plane.collections:
            c.set_edgecolor('None')

        for c in y_plane.collections:
            c.set_edgecolor('None')

        for c in z_plane.collections:
            c.set_edgecolor('None')

        # Color bar

        vmin = min([float(x.min()), float(y.min()), float(z.min())])
        vmax = max([float(x.max()), float(y.max()), float(z.max())])

        if color_bar is True:
            graph_for_color_bar = y_plane
            self.color_bar(graph_for_color_bar=graph_for_color_bar, cb_top_title_pad=cb_top_title_pad,
                           cb_axis_labelpad=cb_axis_labelpad, cb_nticks=cb_nticks, shrink=shrink, norm=norm,
                           cb_outlinewidth=cb_outlinewidth, cb_title=cb_title, cb_title_rotation=cb_title_rotation,
                           cb_title_size=cb_title_size, cb_title_style=cb_title_style,
                           cb_ytitle_labelpad=cb_ytitle_labelpad, cb_title_weight=cb_title_weight,
                           cb_y_title=cb_y_title,
                           cb_top_title=cb_top_title, cb_top_title_y=cb_top_title_y,
                           cb_vmin=cb_vmin, cb_vmax=cb_vmax,
                           tick_ndecimals=tick_ndecimals,
                           cb_top_title_x=cb_top_title_x)
        # Resize axes
        if custom_subplots is True:
            resize_axes = False

        if resize_axes is True:
            z_pad = z_pad if z_pad > (abs(z.max()) + abs(z.min())) / 16 else (abs(z.max()) + abs(z.min())) / 16
            self.resize_axes3d(x_bounds=(x.min(), x.max()), y_bounds=(y.min(), y.max()), z_bounds=(z.min(), z.max()),
                               x_pad=x_pad, y_pad=y_pad, z_pad=z_pad)

        # Makeup
        self.makeup3d(plot_title=plot_title, title_bold=title_bold, title_size=title_size, title_y=title_y,
                      grid=grid, pane_fill=pane_fill, prune=prune,
                      x_label=x_label, xaxis_bold=xaxis_bold, xaxis_label_size=xaxis_label_size,
                      xaxis_labelpad=xaxis_labelpad, xlabel_rotation=xlabel_rotation, x_tick_number=x_tick_number,
                      y_label=y_label, yaxis_bold=yaxis_bold, yaxis_label_size=yaxis_label_size,
                      yaxis_labelpad=yaxis_labelpad, ylabel_rotation=ylabel_rotation, y_tick_number=y_tick_number,
                      z_label=z_label, zaxis_bold=zaxis_bold, zaxis_label_size=zaxis_label_size,
                      zaxis_labelpad=zaxis_labelpad, zlabel_rotation=zlabel_rotation, z_tick_number=z_tick_number,
                      tick_color=tick_color, tick_label_size=tick_label_size, tick_label_pad=tick_label_pad,
                      xtick_rotation=xtick_rotation, ytick_rotation=ytick_rotation, ztick_rotation=ztick_rotation,
                      tick_ndecimals=tick_ndecimals,
                           cb_top_title_x=cb_top_title_x)

        self.fig.tight_layout()

        # Save
        if not isinstance(filename, type(None)):
            plt.savefig(filename, dpi=dpi)

        # More subplots
        if more_subplots_left is not True:
            plt.show()
        else:
            print('Ready for next subplot')

        return self.axes3d

    def isosurface(self,
                   x=None, y=None, z=None, triangles=None,
                   x_center=None, x_scale=1, x_pad=0,
                   y_center=None, y_scale=1, y_pad=0,
                   z_center=None, z_scale=1, z_pad=0, alpha=1,
                   switch=True,
                   box_to_plot_pad=12,
                   grid=False, pane_fill=False, prune=None,
                   norm=None, cmap='RdBu_r',
                   color_bar=False, cb_title=None, cb_axis_labelpad=10, cb_nticks=10, shrink=0.75,
                   cb_outlinewidth=None, cb_title_rotation=None, cb_title_style='normal', cb_title_size=10,
                   cb_top_title_y=1, cb_ytitle_labelpad=10, cb_title_weight='normal', cb_top_title=False,
                   cb_y_title=False,
                   cb_vmin=None, cb_vmax=None,
                   plot_title='Drop - Wave function', title_bold=False, title_size=12, title_y=1,
                   x_label='x', xaxis_bold=False, xaxis_label_size=12, xaxis_labelpad=5, xlabel_rotation=None, x_tick_number=10,
                   y_label='y', yaxis_bold=False, yaxis_label_size=12, yaxis_labelpad=5, ylabel_rotation=None, y_tick_number=10,
                   z_label='z', zaxis_bold=False, zaxis_label_size=12, zaxis_labelpad=5, zlabel_rotation=None, z_tick_number=10,
                   tick_color=None, tick_label_size=7, tick_label_pad=5, tick_ndecimals=1,
                   xtick_rotation=None, ytick_rotation=None, ztick_rotation=None,
                   custom_subplots=False, resize_axes=True, more_subplots_left=False,
                   filename=None, dpi=None,
                   cb_top_title_pad=None,
                   cb_top_title_x=0):

        if isinstance(self.fig, type(None)):
            self.setup()

        if isinstance(self.axes3d, type(None)):
            if isinstance(self.shape_and_position, type(None)):
                self.shape_and_position = 111
            self.axes3d = self.fig.add_subplot(self.shape_and_position, projection='3d')

        # Scaling
        max_scale = max(x_scale, y_scale, z_scale)
        x_scale = x_scale / max_scale
        y_scale = y_scale / max_scale
        z_scale = z_scale / max_scale

        # Reference:
        # https://stackoverflow.com/questions/30223161/matplotlib-mplot3d-how-to-increase-the-size-of-an-axis-stretch-in-a-3d-plo
        self.axes3d.get_proj = lambda: np.dot(Axes3D.get_proj(self.axes3d), np.diag([x_scale, y_scale, z_scale, 1]))

        # Mock plot
        if isinstance(x, type(None)) and isinstance(y, type(None)) and isinstance(z, type(None)):
            def f(x, y, z):
                return cos(x) + cos(y) + cos(z)

            x, y, z = np.pi * np.mgrid[-2:2:31j, -2:2:31j, -2:2:31j]
            vol = f(x, y, z)
            verts, faces, _, _ = measure.marching_cubes_lewiner(vol, 0, spacing=(0.1, 0.1, 0.1))
            isosurface = self.axes3d.plot_trisurf(verts[:, 0], verts[:, 1], verts[:, 2], faces,
                                                  cmap='Spectral', lw=1)

        # Distance from axes to limit of plot
        self.axes3d.dist = box_to_plot_pad

        # Normalization
        if norm is not None:
            norm = self.normalize(array=z, norm=norm, variant='Power')

        # Plot
        isosurface = self.axes3d.plot_trisurf(x, y, z, triangles=triangles.triangles, cmap=cmap, lw=1)

        # Color bar
        if color_bar is True:
            graph_for_color_bar = isosurface
            vmin = z.min()
            vmax = z.max()
            self.color_bar(graph_for_color_bar=graph_for_color_bar, cb_top_title_pad=cb_top_title_pad,
                           cb_axis_labelpad=cb_axis_labelpad, cb_nticks=cb_nticks, shrink=shrink, norm=norm,
                           cb_outlinewidth=cb_outlinewidth, cb_title=cb_title, cb_title_rotation=cb_title_rotation,
                           cb_title_size=cb_title_size, cb_title_style=cb_title_style,
                           cb_ytitle_labelpad=cb_ytitle_labelpad, cb_title_weight=cb_title_weight,
                           cb_y_title=cb_y_title,
                           cb_top_title=cb_top_title, cb_top_title_y=cb_top_title_y,
                           cb_vmin=cb_vmin, cb_vmax=cb_vmax,
                           tick_ndecimals=tick_ndecimals,
                           cb_top_title_x=cb_top_title_x)

        # Resize axes
        if custom_subplots is True:
            resize_axes = False

        if resize_axes is True:
            z_pad = z_pad if z_pad > (abs(z.max()) + abs(z.min())) / 16 else (abs(z.max()) + abs(z.min())) / 16
            self.resize_axes3d(x_bounds=(x.min(), x.max()), y_bounds=(y.min(), y.max()),
                               z_bounds=(z.min(), z.max()),
                               x_pad=x_pad, y_pad=y_pad, z_pad=z_pad)

        # Makeup
        self.makeup3d(plot_title=plot_title, title_bold=title_bold, title_size=title_size, title_y=title_y,
                      grid=grid, pane_fill=pane_fill, prune=prune,
                      x_label=x_label, xaxis_bold=xaxis_bold, xaxis_label_size=xaxis_label_size,
                      xaxis_labelpad=xaxis_labelpad, xlabel_rotation=xlabel_rotation, x_tick_number=x_tick_number,
                      y_label=y_label, yaxis_bold=yaxis_bold, yaxis_label_size=yaxis_label_size,
                      yaxis_labelpad=yaxis_labelpad, ylabel_rotation=ylabel_rotation, y_tick_number=y_tick_number,
                      z_label=z_label, zaxis_bold=zaxis_bold, zaxis_label_size=zaxis_label_size,
                      zaxis_labelpad=zaxis_labelpad, zlabel_rotation=zlabel_rotation, z_tick_number=z_tick_number,
                      tick_color=tick_color, tick_label_size=tick_label_size, tick_label_pad=tick_label_pad,
                      xtick_rotation=xtick_rotation, ytick_rotation=ytick_rotation, ztick_rotation=ztick_rotation,
                      tick_ndecimals=tick_ndecimals,
                      cb_top_title_x=cb_top_title_x)

        self.fig.tight_layout()

        # Save
        if not isinstance(filename, type(None)):
            plt.savefig(filename, dpi=dpi)

        # More subplots
        if more_subplots_left is not True:
            plt.show()
        else:
            print('Ready for next subplot')

        return self.axes3d

    def vectors3D(self,
                  x=None, y=None, z=None, u=None, v=None, w=None, vectorlength=0.1, vectorwidth=1, rule=None,
                  x_scale=1, x_pad=0,
                  y_scale=1, y_pad=0,
                  z_scale=1, z_pad=0, alpha=1,
                  switch=True,
                  box_to_plot_pad=12,
                  grid=False, pane_fill=False, prune=None,
                  norm=None, cmap='RdBu_r',
                  color_bar=False, cb_title=None, cb_axis_labelpad=10, cb_nticks=10, shrink=0.75,
                  cb_outlinewidth=None, cb_title_rotation=None, cb_title_style='normal', cb_title_size=10,
                  cb_top_title_y=1, cb_ytitle_labelpad=10, cb_title_weight='normal', cb_top_title=False,
                  cb_y_title=False,
                  cb_vmin=None, cb_vmax=None,
                  plot_title='Drop - Wave function', title_bold=False, title_size=12, title_y=1,
                  x_label='x', xaxis_bold=False, xaxis_label_size=12, xaxis_labelpad=5, xlabel_rotation=None, x_tick_number=10,
                  y_label='y', yaxis_bold=False, yaxis_label_size=12, yaxis_labelpad=5, ylabel_rotation=None, y_tick_number=10,
                  z_label='z', zaxis_bold=False, zaxis_label_size=12, zaxis_labelpad=5, zlabel_rotation=None, z_tick_number=10,
                  tick_color=None, tick_label_size=7, tick_label_pad=5, tick_ndecimals=1,
                  xtick_rotation=None, ytick_rotation=None, ztick_rotation=None,
                  custom_subplots=False, resize_axes=True, more_subplots_left=False,
                  filename=None, dpi=None,
                  cb_top_title_pad=None,
                  cb_top_title_x=0):

        if isinstance(self.fig, type(None)):
            self.setup()

        if isinstance(self.axes3d, type(None)):
            if isinstance(self.shape_and_position, type(None)):
                self.shape_and_position = 111
            self.axes3d = self.fig.add_subplot(self.shape_and_position, projection='3d')

        # Scaling
        max_scale = max(x_scale, y_scale, z_scale)
        x_scale = x_scale / max_scale
        y_scale = y_scale / max_scale
        z_scale = z_scale / max_scale

        # Reference:
        # https://stackoverflow.com/questions/30223161/matplotlib-mplot3d-how-to-increase-the-size-of-an-axis-stretch-in-a-3d-plo
        self.axes3d.get_proj = lambda: np.dot(Axes3D.get_proj(self.axes3d), np.diag([x_scale, y_scale, z_scale, 1]))

        # Mock plot
        if isinstance(x, type(None)) and isinstance(y, type(None)) and isinstance(z, type(None)) and isinstance(u, type(None)) and isinstance(v, type(None)) and isinstance(w, type(None)):
            def f(x, y, z):
                return cos(x) + cos(y) + cos(z)

        # Distance from axes to limit of plot
        self.axes3d.dist = box_to_plot_pad

        # Normalization
        if norm is True:
            norm = colors.LogNorm(vmin=np.amin(np.array([u, v, w])), vmax=np.amax(np.array([u, v, w])))

        if isinstance(rule, type(None)):
            rule = lambda u, v, w: (u ** 2 + v ** 2 + w ** 2)

        rule = rule(u=u, v=v, w=w)

        # Color determined by rule function
        c = rule
        # Flatten and normalize
        c = (c.ravel() - c.min()) / c.ptp()
        # Repeat for each body line and two head lines
        c = np.concatenate((c, np.repeat(c, 2)))
        # Colormap
        cmap = mpl.cm.get_cmap(cmap)
        c = cmap(c)

        # Plot
        quiver = self.axes3d.quiver(x, y, z, u, v, w, length=vectorlength, linewidths=vectorwidth, pivot='tail', colors=c, cmap=cmap)

        # Color bar
        if color_bar is True:
            graph_for_color_bar = quiver
            vmin = rule.min()
            vmax = rule.max()
            self.color_bar(graph_for_color_bar=graph_for_color_bar, cb_top_title_pad=cb_top_title_pad,
                           cb_axis_labelpad=cb_axis_labelpad, cb_nticks=cb_nticks, shrink=shrink, norm=norm,
                           cb_outlinewidth=cb_outlinewidth, cb_title=cb_title, cb_title_rotation=cb_title_rotation,
                           cb_title_size=cb_title_size, cb_title_style=cb_title_style,
                           cb_ytitle_labelpad=cb_ytitle_labelpad, cb_title_weight=cb_title_weight,
                           cb_y_title=cb_y_title,
                           cb_top_title=cb_top_title, cb_top_title_y=cb_top_title_y,
                           cb_vmin=cb_vmin, cb_vmax=cb_vmax,
                           tick_ndecimals=tick_ndecimals,
                           cb_top_title_x=cb_top_title_x)

        # Resize axes
        if custom_subplots is True:
            resize_axes = False

        if resize_axes is True:
            z_pad = z_pad if z_pad > (abs(z.max()) + abs(z.min())) / 16 else (abs(z.max()) + abs(z.min())) / 16
            self.resize_axes3d(x_bounds=(x.min(), x.max()), y_bounds=(y.min(), y.max()),
                               z_bounds=(z.min(), z.max()),
                               x_pad=x_pad, y_pad=y_pad, z_pad=z_pad)

        # Makeup
        self.makeup3d(plot_title=plot_title, title_bold=title_bold, title_size=title_size, title_y=title_y,
                      grid=grid, pane_fill=pane_fill, prune=prune,
                      x_label=x_label, xaxis_bold=xaxis_bold, xaxis_label_size=xaxis_label_size,
                      xaxis_labelpad=xaxis_labelpad, xlabel_rotation=xlabel_rotation, x_tick_number=x_tick_number,
                      y_label=y_label, yaxis_bold=yaxis_bold, yaxis_label_size=yaxis_label_size,
                      yaxis_labelpad=yaxis_labelpad, ylabel_rotation=ylabel_rotation, y_tick_number=y_tick_number,
                      z_label=z_label, zaxis_bold=zaxis_bold, zaxis_label_size=zaxis_label_size,
                      zaxis_labelpad=zaxis_labelpad, zlabel_rotation=zlabel_rotation, z_tick_number=z_tick_number,
                      tick_color=tick_color, tick_label_size=tick_label_size, tick_label_pad=tick_label_pad,
                      xtick_rotation=xtick_rotation, ytick_rotation=ytick_rotation, ztick_rotation=ztick_rotation,
                      tick_ndecimals=tick_ndecimals,
                      cb_top_title_x=cb_top_title_x)

        self.fig.tight_layout()

        # Save
        if not isinstance(filename, type(None)):
            plt.savefig(filename, dpi=dpi)

        # More subplots
        if more_subplots_left is not True:
            plt.show()
        else:
            print('Ready for next subplot')

        return self.axes3d

    def makeup2d(self,
                 plot_title, title_bold, title_size, title_y,
                 x_label, xaxis_bold, xaxis_label_size, xaxis_labelpad, xlabel_rotation, x_tick_number, x_ticklabels,
                 y_label, yaxis_bold, yaxis_label_size, yaxis_labelpad, ylabel_rotation, y_tick_number, y_ticklabels,
                 tick_color, tick_label_size, tick_label_pad, tick_ndecimals,
                 xtick_rotation, ytick_rotation, x_ticklabel_size, y_ticklabel_size,
                 grid, prune, gridcolor, gridlines, custom_x_ticklabels, custom_y_ticklabels, date_xticklabels):

        # Background color
        self.ax.patch.set_alpha(1)

        # Title
        if not isinstance(plot_title, type(None)):
            if title_bold is True:
                weight = 'bold'
            else:
                weight = 'normal'
            self.ax.set_title(plot_title, fontname=self.font, weight=weight,
                              color=self.color, size=title_size)
            self.ax.title.set_position((0.5, title_y))

        # Axis labels
        if x_label is not None:
            if xaxis_bold is True:
                weight = 'bold'
            else:
                weight = 'normal'
            self.ax.set_xlabel(x_label, fontname=self.font, weight=weight,
                               color=self.color, size=xaxis_label_size, labelpad=xaxis_labelpad, rotation=xlabel_rotation)
        if y_label is not None:
            if yaxis_bold is True:
                weight = 'bold'
            else:
                weight = 'normal'
            self.ax.set_ylabel(y_label, fontname=self.font, weight=weight,
                               color=self.color, size=yaxis_label_size, labelpad=yaxis_labelpad, rotation=ylabel_rotation)
            self.ax.yaxis.set_label_coords(-0.275, 0.425)

        # Spines
        #   Color
        spine_color = self.color
        self.ax.spines['bottom'].set_color(spine_color)
        self.ax.spines['left'].set_color(spine_color)
        #   Remove top and right spines
        self.ax.spines['top'].set_visible(False)
        self.ax.spines['right'].set_visible(False)
        self.ax.tick_params(axis='both', which='both', top=False, right=False)

        # Ticks
        #   Tick-label distance
        self.ax.xaxis.set_tick_params(pad=0.1, direction='in')
        self.ax.yaxis.set_tick_params(pad=0.1, direction='in')
        #   Color
        if tick_color is not None:
            self.ax.tick_params(axis='both', color=tick_color)
        #   Label font and color
        for tick in self.ax.get_xticklabels():
            tick.set_fontname(self.font)
            tick.set_color(self.color)
        for tick in self.ax.get_yticklabels():
            tick.set_fontname(self.font)
            tick.set_color(self.color)
        #   Label size
        if not isinstance(x_ticklabel_size, type(None)):
            self.ax.tick_params(axis='x', labelsize=x_ticklabel_size)
        if not isinstance(y_ticklabel_size, type(None)):
            self.ax.tick_params(axis='y', labelsize=y_ticklabel_size)
        if not isinstance(tick_label_size, type(None)):
            self.ax.tick_params(axis='both', labelsize=tick_label_size)
        #   Number and custom position ---------------------------------------------------------------------------------
        if not isinstance(x_tick_number, type(None)):
            self.ax.set_xticks(np.linspace(x_ticklabels[0] if not isinstance(x_ticklabels, type(None)) else self.ax.get_xlim()[0],
                                           x_ticklabels[1] if not isinstance(x_ticklabels, type(None)) else self.ax.get_xlim()[1],
                                           x_tick_number))
        if not isinstance(y_tick_number, type(None)):
            self.ax.set_yticks(np.linspace(y_ticklabels[0] if not isinstance(y_ticklabels, type(None)) else self.ax.get_ylim()[0],
                                           y_ticklabels[1] if not isinstance(y_ticklabels, type(None)) else self.ax.get_ylim()[1],
                                           y_tick_number))
        #   Prune
        if not isinstance(prune, type(None)):
            self.ax.xaxis.set_major_locator(plt.MaxNLocator(prune=prune))
        if not isinstance(prune, type(None)):
            self.ax.yaxis.set_major_locator(plt.MaxNLocator(prune=prune))
        #   Float format
        float_format = '%.' + str(tick_ndecimals) + 'f'
        self.ax.xaxis.set_major_formatter(FormatStrFormatter(float_format))
        self.ax.yaxis.set_major_formatter(FormatStrFormatter(float_format))
        #   Custom tick labels
        if not isinstance(custom_x_ticklabels, type(None)):
            self.ax.set_xticklabels(np.round(np.linspace(custom_x_ticklabels[0],
                                                         custom_x_ticklabels[1],
                                                         x_tick_number),
                                             tick_ndecimals))
        if not isinstance(custom_y_ticklabels, type(None)):
            self.ax.set_yticklabels(np.round(np.linspace(custom_y_ticklabels[0],
                                                         custom_y_ticklabels[1],
                                                         y_tick_number),
                                             tick_ndecimals))
        if date_xticklabels is True:
            fmtd = []
            for date in list(plt.xticks()[0]):
                date = '{}/{}'.format(int(floor(date)), int(12 * (date % 1))) if date % 1 > 0 else '{}'.format(int(floor(date)))
                fmtd.append(date)
            self.ax.set_xticklabels(fmtd)
        #   Tick-label pad ---------------------------------------------------------------------------------------------
        if tick_label_pad is not None:
            self.ax.tick_params(axis='both', pad=tick_label_pad)
        #   Rotation
        if xtick_rotation is not None:
            self.ax.tick_params(axis='x', rotation=xtick_rotation)
        if ytick_rotation is not None:
            self.ax.tick_params(axis='y', rotation=ytick_rotation)

        # Grid
        if grid is not False:
            plt.grid(linestyle=gridlines, color=gridcolor)

    def makeup3d(self,
                 plot_title, title_bold, title_size, title_y,
                 grid, pane_fill, prune,
                 x_label, xaxis_bold, xaxis_label_size, xaxis_labelpad, xlabel_rotation, x_tick_number,
                 y_label, yaxis_bold, yaxis_label_size, yaxis_labelpad, ylabel_rotation, y_tick_number,
                 z_label, zaxis_bold, zaxis_label_size, zaxis_labelpad, zlabel_rotation, z_tick_number,
                 tick_color, tick_label_size, tick_label_pad, tick_ndecimals,
                 xtick_rotation, ytick_rotation, ztick_rotation):

        # Title
        if plot_title is not None:
            self.axes3d.set_title(plot_title, y=title_y, fontname=self.font, weight='bold' if title_bold else None,
                                  color=self.color, size=title_size)

        # Axis labels
        if x_label is not None:
            self.axes3d.set_xlabel(x_label, fontname=self.font, weight='bold' if xaxis_bold else None,
                                   color=self.color, size=xaxis_label_size, labelpad=xaxis_labelpad,
                                   rotation=xlabel_rotation)
        if y_label is not None:
            self.axes3d.set_ylabel(y_label, fontname=self.font, weight='bold' if yaxis_bold else None,
                                   color=self.color, size=yaxis_label_size, labelpad=yaxis_labelpad,
                                   rotation=ylabel_rotation)
        if z_label is not None:
            self.axes3d.set_zlabel(z_label, fontname=self.font, weight='bold' if zaxis_bold else None,
                                   color=self.color, size=zaxis_label_size, labelpad=zaxis_labelpad,
                                   rotation=zlabel_rotation)

        # Ticks
        #   Color
        if tick_color is not None:
            self.axes3d.tick_params(axis='both', color=tick_color)

            self.axes3d.w_xaxis.line.set_color(tick_color)
            self.axes3d.w_yaxis.line.set_color(tick_color)
            self.axes3d.w_zaxis.line.set_color(tick_color)
        #   Label size
        if tick_label_size is not None:
            self.axes3d.tick_params(axis='both', labelsize=tick_label_size)
        #   Numeral size
        for tick in self.axes3d.get_xticklabels():
            tick.set_fontname(self.font)
        for tick in self.axes3d.get_yticklabels():
            tick.set_fontname(self.font)
        for tick in self.axes3d.get_zticklabels():
            tick.set_fontname(self.font)
        #   Float format
        float_format = '%.'+str(tick_ndecimals)+'f'
        self.axes3d.xaxis.set_major_formatter(FormatStrFormatter(float_format))
        self.axes3d.yaxis.set_major_formatter(FormatStrFormatter(float_format))
        self.axes3d.zaxis.set_major_formatter(FormatStrFormatter(float_format))

        # Tick number
        if x_tick_number is not None:
            self.axes3d.xaxis.set_major_locator(plt.MaxNLocator(x_tick_number, prune=prune))
        if y_tick_number is not None:
            self.axes3d.yaxis.set_major_locator(plt.MaxNLocator(y_tick_number, prune=prune))
        if z_tick_number is not None:
            self.axes3d.zaxis.set_major_locator(plt.MaxNLocator(z_tick_number, prune=prune))

        # Tick label pad
        if tick_label_pad is not None:
            self.axes3d.tick_params(axis='both', pad=tick_label_pad)

        # Tick rotation
        if xtick_rotation is not None:
            self.axes3d.tick_params(axis='x', rotation=xtick_rotation)
        if ytick_rotation is not None:
            self.axes3d.tick_params(axis='y', rotation=ytick_rotation)
        if ytick_rotation is not None:
            self.axes3d.tick_params(axis='z', rotation=ztick_rotation)

        # Grid
        self.axes3d.grid(grid)

        # Pane fill and pane edge color
        self.axes3d.xaxis.pane.fill = pane_fill
        self.axes3d.yaxis.pane.fill = pane_fill
        self.axes3d.zaxis.pane.fill = pane_fill
        self.axes3d.xaxis.pane.set_edgecolor(tick_color)
        self.axes3d.yaxis.pane.set_edgecolor(tick_color)

    def resize_axes2d(self, aspect, x, y, x_bounds, y_bounds, xresize_pad, yresize_pad):
        if isinstance(x_bounds, type(None)):
            x_bounds = [x.min(), x.max()]
        else:
            xresize_pad = 0
        if isinstance(y_bounds, type(None)):
            y_bounds = [y.min(), y.max()]
        else:
            yresize_pad = 0

        self.ax.set_aspect(aspect)
        self.ax.set_xbound(lower=x_bounds[0] - xresize_pad, upper=x_bounds[1] + xresize_pad)
        self.ax.set_ybound(lower=y_bounds[0] - yresize_pad, upper=y_bounds[1] + yresize_pad)

        self.ax.set_xlim(x_bounds[0] - xresize_pad, x_bounds[1] + xresize_pad)
        self.ax.set_ylim(y_bounds[0] - yresize_pad, y_bounds[1] + yresize_pad)

    def resize_axes3d(self, x_bounds=None, y_bounds=None, z_bounds=None, x_pad=0, y_pad=0, z_pad=0):
        if x_bounds is not None:
            self.axes3d.set_xlim3d(x_bounds[0]-x_pad, x_bounds[1]+x_pad)
        if y_bounds is not None:
            self.axes3d.set_ylim3d(y_bounds[0]-y_pad, y_bounds[1]+y_pad)
        if z_bounds is not None:
            self.axes3d.set_zlim3d(z_bounds[0]-z_pad, z_bounds[1]+z_pad)

    def color_bar(self, graph_for_color_bar, cb_title, cb_title_rotation, cb_ytitle_labelpad, cb_nticks,
                  shrink, norm, cb_outlinewidth, cb_title_size, cb_title_style, cb_title_weight, cb_axis_labelpad,
                  cb_y_title, cb_top_title, cb_top_title_y, cb_vmin, cb_vmax, cb_top_title_pad, tick_ndecimals,
                  cb_top_title_x, cb_ticklabelsize):

        # Take limits from plot
        graph_for_color_bar.set_clim([cb_vmin, cb_vmax])

        # Normalization
        if norm is None:
            locator = np.linspace(cb_vmin, cb_vmax, cb_nticks, endpoint=True)
        else:
            locator = None

        # Colorbar
        cbar = self.fig.colorbar(graph_for_color_bar, spacing='proportional', ticks=locator, shrink=shrink,
                                 extend='max', norm=None, orientation='vertical',
                                 ax=self.axes3d if not isinstance(self.axes3d, type(None)) else self.ax,
                                 format='%.' + str(tick_ndecimals) + 'f')

        # Ticks
        #   Direction
        cbar.ax.tick_params(axis='y', direction='out')
        #   Tick label pad and size
        cbar.ax.yaxis.set_tick_params(pad=cb_axis_labelpad, labelsize=cb_ticklabelsize)

        # Title
        if not isinstance(cb_title, type(None)) and cb_y_title is False and cb_top_title is False:
            print('Input colorbar title location with booleans: cb_y_title=True or cb_top_title=True')
        if cb_y_title is True:
            cbar.ax.set_ylabel(cb_title, rotation=cb_title_rotation, labelpad=cb_ytitle_labelpad)
            text = cbar.ax.yaxis.label
            font = matplotlib.font_manager.FontProperties(family=self.font, style=cb_title_style, size=cb_title_size,
                                                          weight=cb_title_weight)
            text.set_font_properties(font)
        if cb_top_title is True:
            cbar.ax.set_title(cb_title, rotation=cb_title_rotation, fontdict={'verticalalignment': 'baseline',
                                                                              'horizontalalignment': 'left'},
                              pad=cb_top_title_pad)
            cbar.ax.title.set_position((cb_top_title_x, cb_top_title_y))
            text = cbar.ax.title
            font = matplotlib.font_manager.FontProperties(family=self.font, style=cb_title_style, weight=cb_title_weight,
                                                          size=cb_title_size)
            text.set_font_properties(font)

        # Outline
        cbar.outline.set_edgecolor(self.color2)
        cbar.outline.set_linewidth(cb_outlinewidth)

    def normalize(self, array, norm, variant):
        if variant == 'SymLog':
            vmin = float(array.min())
            temp = 0

            # Temp raise plot to normalize
            if vmin < 0:
                array = array - vmin
                temp = vmin

            vmax = float(array.max())
            vmin = norm * vmax

            # Revert to original position
            vmax = vmax + temp
            vmin = vmin + temp

            norm = colors.SymLogNorm(base=np.e, linthresh=0.03, linscale=0.03, vmin=vmin, vmax=vmax)

            return norm

        if variant == 'MidPoint':
            vmin = float(array.min())
            vmax = float(array.max())

            range = abs(vmax)+abs(vmin)

            vcenter = vmin + range*norm

            x, y = [vmin, 0.01, vmax], [0, 0.5, 1]
            norm = np.ma.masked_array(np.interp(1, x, y))

            return norm

        if variant == 'Power':
            return colors.PowerNorm(gamma=norm)

    def lighting(self, z, cmap):
        ls = LightSource(270, 45)
        rgb = ls.shade(z, cmap=cm.get_cmap(cmap), vert_exag=0.1, blend_mode='soft')
        return rgb