Exemplo n.º 1
0
    def save_graph_layout(self):
        """Save the graph layout in the project hdf5 file."""
        print("Saving the graph layout for well %s..." % self.wldset['Well'],
              end=" ")

        layout = {
            'WLmin': self.waterlvl_max.value(),
            'WLscale': self.waterlvl_scale.value(),
            'RAINscale': self.Ptot_scale.value(),
            'fwidth': self.page_setup_win.pageSize[0],
            'fheight': self.page_setup_win.pageSize[1],
            'va_ratio': self.page_setup_win.va_ratio,
            'NZGrid': self.NZGridWL_spinBox.value(),
            'bwidth_indx': self.qweather_bin.currentIndex(),
            'date_labels_pattern': self.dateDispFreq_spinBox.value(),
            'datemode': self.time_scale_label.currentText()
        }
        layout['wxdset'] = None if self.wxdset is None else self.wxdset.name

        year = self.date_start_widget.date().year()
        month = self.date_start_widget.date().month()
        layout['TIMEmin'] = xldate_from_date_tuple((year, month, 1), 0)

        year = self.date_end_widget.date().year()
        month = self.date_end_widget.date().month()
        layout['TIMEmax'] = xldate_from_date_tuple((year, month, 1), 0)

        if self.datum_widget.currentIndex() == 0:
            layout['WLdatum'] = 'mbgs'
        else:
            layout['WLdatum'] = 'masl'

        # ---- Page Setup

        layout['title_on'] = bool(self.page_setup_win.isGraphTitle)
        layout['legend_on'] = bool(self.page_setup_win.isLegend)
        layout['language'] = self.btn_language.language
        layout['trend_line'] = bool(self.page_setup_win.isTrendLine)
        layout['meteo_on'] = bool(self.page_setup_win.is_meteo_on)
        layout['glue_wl_on'] = bool(self.page_setup_win.is_glue_wl_on)
        layout['mrc_wl_on'] = bool(self.page_setup_win.is_mrc_wl_on)
        layout['figframe_lw'] = self.page_setup_win.figframe_lw

        # Save the colors :

        cdb = ColorsReader()
        cdb.load_colors_db()
        layout['colors'] = cdb.RGB

        # Save the layout :

        self.wldset.save_layout(layout)
        msg = 'Layout saved successfully for well %s.' % self.wldset['Well']
        self.ConsoleSignal.emit('<font color=black>%s</font>' % msg)
        print("done")
Exemplo n.º 2
0
    def save_graph_layout(self):
        """Save the graph layout in the project hdf5 file."""
        print("Saving the graph layout for well %s..." % self.wldset['Well'],
              end=" ")

        layout = {'WLmin': self.waterlvl_max.value(),
                  'WLscale': self.waterlvl_scale.value(),
                  'RAINscale': self.Ptot_scale.value(),
                  'fwidth': self.page_setup_win.pageSize[0],
                  'fheight': self.page_setup_win.pageSize[1],
                  'va_ratio': self.page_setup_win.va_ratio,
                  'NZGrid': self.NZGridWL_spinBox.value(),
                  'bwidth_indx': self.qweather_bin.currentIndex(),
                  'date_labels_pattern': self.dateDispFreq_spinBox.value(),
                  'datemode': self.time_scale_label.currentText()}
        layout['wxdset'] = None if self.wxdset is None else self.wxdset.name

        year = self.date_start_widget.date().year()
        month = self.date_start_widget.date().month()
        layout['TIMEmin'] = xldate_from_date_tuple((year, month, 1), 0)

        year = self.date_end_widget.date().year()
        month = self.date_end_widget.date().month()
        layout['TIMEmax'] = xldate_from_date_tuple((year, month, 1), 0)

        if self.datum_widget.currentIndex() == 0:
            layout['WLdatum'] = 'mbgs'
        else:
            layout['WLdatum'] = 'masl'

        # ---- Page Setup

        layout['title_on'] = bool(self.page_setup_win.isGraphTitle)
        layout['legend_on'] = bool(self.page_setup_win.isLegend)
        layout['language'] = self.btn_language.language
        layout['trend_line'] = bool(self.page_setup_win.isTrendLine)
        layout['meteo_on'] = bool(self.page_setup_win.is_meteo_on)
        layout['glue_wl_on'] = bool(self.page_setup_win.is_glue_wl_on)
        layout['mrc_wl_on'] = bool(self.page_setup_win.is_mrc_wl_on)
        layout['figframe_lw'] = self.page_setup_win.figframe_lw

        # Save the colors :

        cdb = ColorsReader()
        cdb.load_colors_db()
        layout['colors'] = cdb.RGB

        # Save the layout :

        self.wldset.save_layout(layout)
        msg = 'Layout saved successfully for well %s.' % self.wldset['Well']
        self.ConsoleSignal.emit('<font color=black>%s</font>' % msg)
        print("done")
Exemplo n.º 3
0
    def plot_legend(self):
        """Plot the legend of the figure."""
        ax = self.figure.axes[2]

        # bbox transform :

        padding = mpl.transforms.ScaledTranslation(5 / 72, -5 / 72,
                                                   self.figure.dpi_scale_trans)
        transform = ax.transAxes + padding

        # Define proxy artists :

        colors = ColorsReader()
        colors.load_colors_db()

        rec1 = mpl.patches.Rectangle((0, 0),
                                     1,
                                     1,
                                     fc=colors.rgb['Snow'],
                                     ec='none')
        rec2 = mpl.patches.Rectangle((0, 0),
                                     1,
                                     1,
                                     fc=colors.rgb['Rain'],
                                     ec='none')

        # Define the legend labels and markers :

        lines = [ax.lines[0], ax.lines[1], ax.lines[2], rec2, rec1]
        labels = [
            self.fig_labels.Tmax, self.fig_labels.Tavg, self.fig_labels.Tmin,
            self.fig_labels.rain, self.fig_labels.snow
        ]

        # Plot the legend :

        leg = ax.legend(lines,
                        labels,
                        numpoints=1,
                        fontsize=13,
                        borderaxespad=0,
                        loc='upper left',
                        borderpad=0,
                        bbox_to_anchor=(0, 1),
                        bbox_transform=transform)
        leg.draw_frame(False)
Exemplo n.º 4
0
    def plot_precip(self, PNORM, SNORM):

        # Define the vertices manually :

        Xmid = np.arange(0.5, 12.5, 1)
        n = 0.5  # Controls the width of the bins
        f = 0.75  # Controls the spacing between the bins

        Xpos = np.vstack((Xmid - n * f, Xmid - n * f, Xmid + n * f,
                          Xmid + n * f)).transpose().flatten()

        Ptot = np.vstack(
            (PNORM * 0, PNORM, PNORM, PNORM * 0)).transpose().flatten()

        Snow = np.vstack(
            (SNORM * 0, SNORM, SNORM, SNORM * 0)).transpose().flatten()

        # Plot the data :

        ax = self.figure.axes[1]
        for collection in reversed(ax.collections):
            collection.remove()

        colors = ColorsReader()
        colors.load_colors_db()

        ax.fill_between(Xpos,
                        0,
                        Ptot,
                        edgecolor='none',
                        color=colors.rgb['Rain'])
        ax.fill_between(Xpos,
                        0,
                        Snow,
                        edgecolor='none',
                        color=colors.rgb['Snow'])
Exemplo n.º 5
0
    def __init__(self, *args, **kargs):
        super(Hydrograph, self).__init__(*args, **kargs)

        # set canvas and renderer :

        self.set_canvas(FigureCanvas(self))
        self.canvas.get_renderer()

        self.__isHydrographExists = False

        # Fig Init :

        self.fwidth = 11
        self.fheight = 7
        self.NZGrid = 8  # Number of interval in the grid of the bottom part

        # Vertical height ratio between the top part  and the bottom part
        self.va_ratio = 0.2

        # Graph labels language :
        self.language = 'english'

        # Database :

        self.colorsDB = ColorsReader()
        self.colorsDB.load_colors_db()

        # Scales :

        self.WLmin = 0
        self.WLscale = 0

        self.RAINscale = 20

        self.TIMEmin = 36526
        self.TIMEmax = 36526

        # Legend  and Title :

        self.isLegend = 1
        self.isGraphTitle = 1

        # Layout Options :

        self.WLdatum = 0  # 0: mbgs;  1: masl
        self.trend_line = 0
        self.trend_MAW = 30
        # trend_MAW = width of the Moving Average Window used to
        #             smooth the water level data
        self.meteo_on = True
        self.glue_wl_on = False
        self.mrc_wl_on = False
        self.gridLines = 2  # 0 -> None, 1 -> "-" 2 -> ":"
        self.datemode = 'Month'  # 'month' or 'year'
        self.label_font_size = 14
        self.date_labels_pattern = 2
        self._figframe_lw = 0

        # Waterlvl & Meteo Obj :

        self.wldset = None
        self.wxdset = None
        self.gluedf = None

        # Daily Weather :

        self.dist = 0
        self.name_meteo = ''
        self.TIMEmeteo = np.array([])  # Time in Excel numeric format (days)
        self.TMAX = np.array([])  # Daily maximum temperature (deg C)
        self.PTOT = np.array([])
        self.RAIN = np.array([])

        # Bin Redistributed Weather :

        self.bTIME = np.array([])
        self.bTMAX = np.array([])
        self.bPTOT = np.array([])
        self.bRAIN = np.array([])

        self.bwidth_indx = 1
        #   0: 1 day;
        #   1: 1 week;
        #   2: 1 month;
        #   3: 1 year;

        self.NMissPtot = []
Exemplo n.º 6
0
class Hydrograph(Figure):
    def __init__(self, *args, **kargs):
        super(Hydrograph, self).__init__(*args, **kargs)

        # set canvas and renderer :

        self.set_canvas(FigureCanvas(self))
        self.canvas.get_renderer()

        self.__isHydrographExists = False

        # Fig Init :

        self.fwidth = 11
        self.fheight = 7
        self.NZGrid = 8  # Number of interval in the grid of the bottom part

        # Vertical height ratio between the top part  and the bottom part
        self.va_ratio = 0.2

        # Graph labels language :
        self.language = 'english'

        # Database :

        self.colorsDB = ColorsReader()
        self.colorsDB.load_colors_db()

        # Scales :

        self.WLmin = 0
        self.WLscale = 0

        self.RAINscale = 20

        self.TIMEmin = 36526
        self.TIMEmax = 36526

        # Legend  and Title :

        self.isLegend = 1
        self.isGraphTitle = 1

        # Layout Options :

        self.WLdatum = 0  # 0: mbgs;  1: masl
        self.trend_line = 0
        self.trend_MAW = 30
        # trend_MAW = width of the Moving Average Window used to
        #             smooth the water level data
        self.meteo_on = True
        self.glue_wl_on = False
        self.mrc_wl_on = False
        self.gridLines = 2  # 0 -> None, 1 -> "-" 2 -> ":"
        self.datemode = 'Month'  # 'month' or 'year'
        self.label_font_size = 14
        self.date_labels_pattern = 2
        self._figframe_lw = 0

        # Waterlvl & Meteo Obj :

        self.wldset = None
        self.wxdset = None
        self.gluedf = None

        # Daily Weather :

        self.dist = 0
        self.name_meteo = ''
        self.TIMEmeteo = np.array([])  # Time in Excel numeric format (days)
        self.TMAX = np.array([])  # Daily maximum temperature (deg C)
        self.PTOT = np.array([])
        self.RAIN = np.array([])

        # Bin Redistributed Weather :

        self.bTIME = np.array([])
        self.bTMAX = np.array([])
        self.bPTOT = np.array([])
        self.bRAIN = np.array([])

        self.bwidth_indx = 1
        #   0: 1 day;
        #   1: 1 week;
        #   2: 1 month;
        #   3: 1 year;

        self.NMissPtot = []

    @property
    def meteo_on(self):
        """Controls whether meteo data are plotted or not."""
        return (self.__meteo_on and self.wxdset is not None)

    @meteo_on.setter
    def meteo_on(self, x):
        self.__meteo_on = bool(x)

    def set_meteo_on(self, x):
        """Set whether the meteo data are plotted or not."""
        self.meteo_on = x
        if self.__isHydrographExists:
            self.ax3.set_visible(self.meteo_on)
            self.ax4.set_visible(self.meteo_on)
            self.setup_waterlvl_scale()
            self.draw_weather()

    @property
    def glue_wl_on(self):
        """Controls whether glue water levels are plotted or not."""
        return (self.__glue_wl_on and self.gluedf is not None)

    @glue_wl_on.setter
    def glue_wl_on(self, x):
        self.__glue_wl_on = bool(x)

    def set_glue_wl_on(self, x):
        """Set whether the glue water levels data are plotted or not."""
        self.glue_wl_on = x
        if self.__isHydrographExists:
            self.draw_glue_wl()
            self.setup_legend()

    @property
    def mrc_wl_on(self):
        """Return whether the mrc water levels must be plotted or not."""
        return (self.__mrc_wl_on and self.wldset is not None and
                self.wldset.mrc_exists())

    @mrc_wl_on.setter
    def mrc_wl_on(self, x):
        """Set whether the mrc water levels data must plotted or not."""
        self.__mrc_wl_on = bool(x)

    def set_mrc_wl_on(self, x):
        """Set whether the mrc water levels data must plotted or not."""
        self.mrc_wl_on = x
        if self.__isHydrographExists:
            self.draw_mrc_wl()
            self.setup_legend()

    def set_figframe_lw(self, x):
        """
        Set the line thickness of the frame that encloses the entire figure.
        """
        self._figframe_lw = x
        self.setup_figure_frame()

    @property
    def language(self):
        return self.__language

    @language.setter
    def language(self, x):
        if x.lower() in ['english', 'french']:
            self.__language = x
        else:
            print('WARNING: Language not supported. '
                  'Setting language to "english".')
            self.__language = 'english'

    @property
    def isHydrographExists(self):
        return self.__isHydrographExists

    def set_wldset(self, wldset):
        self.wldset = wldset

    def set_wxdset(self, wxdset):
        self.wxdset = wxdset

    def set_gluedf(self, gluedf):
        """Set the namespace for the GLUE dataframe."""
        self.gluedf = gluedf
        self.draw_glue_wl()
        self.setup_legend()

    def clf(self, *args, **kargs):
        """Matplotlib override to set internal flag."""
        self.__isHydrographExists = False
        super(Hydrograph, self).clf(*args, **kargs)

    def savefig(self, fname):
        """Matplotlib override to set frameon when saving."""
        super(Hydrograph, self).savefig(fname, frameon=True, facecolor='white',
                                        edgecolor='black')

    def generate_hydrograph(self, wxdset=None, wldset=None):
        if wxdset is None:
            wxdset = self.wxdset
        else:
            self.wxdset = wxdset

        if wldset is None:
            wldset = self.wldset
        else:
            self.wldset = wldset

        # Reinit Figure :

        self.clf()
        self.set_size_inches(self.fwidth, self.fheight, forward=True)
        self.setup_figure_frame()

        # Assign Weather Data :

        if self.wxdset is None:
            self.name_meteo = ''
            self.TIMEmeteo = np.array([])
            self.TMAX = np.array([])
            self.PTOT = np.array([])
            self.RAIN = np.array([])
        else:
            self.name_meteo = wxdset['Station Name']
            self.TIMEmeteo = wxdset['Time']
            self.TMAX = wxdset['Tmax']
            self.PTOT = wxdset['Ptot']
            self.RAIN = wxdset['Rain']

        # Resample Data in Bins :

        self.resample_bin()

        # -------------------------------------------------- AXES CREATION ----

        # ---- Time (host) ----

        # Also holds the gridlines.

        self.ax1 = self.add_axes([0, 0, 1, 1], frameon=False)
        self.ax1.set_zorder(100)

        # ---- Frame ----

        # Only used to display the frame so it is always on top.

        self.ax0 = self.add_axes(self.ax1.get_position(), frameon=True)
        self.ax0.patch.set_visible(False)
        self.ax0.set_zorder(self.ax1.get_zorder() + 200)
        self.ax0.tick_params(bottom=False, top=False, left=False, right=False,
                             labelbottom=False, labelleft=False)

        # ---- Water Levels ----

        self.ax2 = self.add_axes(self.ax1.get_position(), frameon=False,
                                 label='axes2', sharex=self.ax1)
        self.ax2.set_zorder(self.ax1.get_zorder() + 100)
        self.ax2.yaxis.set_ticks_position('left')
        self.ax2.yaxis.set_label_position('left')
        self.ax2.tick_params(axis='y', direction='out', labelsize=10)

        # ---- Precipitation ----

        self.ax3 = self.add_axes(self.ax1.get_position(), frameon=False,
                                 label='axes3', sharex=self.ax1)
        self.ax3.set_zorder(self.ax1.get_zorder() + 150)
        self.ax3.set_navigate(False)

        # ---- Air Temperature ----

        self.ax4 = self.add_axes(self.ax1.get_position(), frameon=False,
                                 label='axes4', sharex=self.ax1)
        self.ax4.set_zorder(self.ax1.get_zorder() + 150)
        self.ax4.set_navigate(False)
        self.ax4.set_axisbelow(True)
        self.ax4.tick_params(bottom=False, top=False, left=False,
                             right=False, labelbottom=False, labelleft=False)

        if self.meteo_on is False:
            self.ax3.set_visible(False)
            self.ax4.set_visible(False)

        # ---- Bottom Graph Grid ----

        self.axLow = self.add_axes(self.ax1.get_position(), frameon=False,
                                   label='axLow', sharex=self.ax1)
        self.axLow.patch.set_visible(False)
        self.axLow.set_zorder(self.ax2.get_zorder() - 50)
        self.axLow.tick_params(bottom=False, top=False, left=False,
                               right=False, labelbottom=False, labelleft=False)

        self.setup_waterlvl_scale()

        # -------------------------------------------------- Remove Spines ----

        for axe in self.axes[2:]:
            for loc in axe.spines:
                axe.spines[loc].set_visible(False)

        # ------------------------------------------------- Update margins ----

        self.bottom_margin = 0.75
        self.set_margins()  # set margins for all the axes

        # --------------------------------------------------- FIGURE TITLE ----

        # Calculate horizontal distance between weather station and
        # observation well.
        if self.wxdset is not None:
            self.dist = calc_dist_from_coord(
                wldset['Latitude'], wldset['Longitude'],
                wxdset['Latitude'], wxdset['Longitude'])
        else:
            self.dist = 0

        # Weather Station name and distance to the well
        self.text1 = self.ax0.text(0, 1, '', va='bottom', ha='left',
                                   rotation=0, fontsize=10)

        # Well Name
        self.figTitle = self.ax0.text(0, 1, '', fontsize=18,
                                      ha='left', va='bottom')

        self.draw_figure_title()

        # ----------------------------------------------------------- TIME ----

        self.xlabels = []
        self.set_time_scale()

        self.ax1.xaxis.set_ticklabels([])
        self.ax1.xaxis.set_ticks_position('bottom')
        self.ax1.tick_params(axis='x', direction='out')
        self.ax1.tick_params(top=False, left=False, right=False,
                             labeltop=False, labelleft=False, labelright=False)

        self.set_gridLines()

        # ---- Init water level artists

        # Continuous Line Datalogger
        self.l1_ax2, = self.ax2.plot(
            [], [], '-', zorder=10, lw=1, color=self.colorsDB.rgb['WL solid'])

        # Data Point Datalogger
        self.l2_ax2, = self.ax2.plot(
            [], [], '.', color=self.colorsDB.rgb['WL data'], markersize=5)

        # Manual Mesures
        self.h_WLmes, = self.ax2.plot(
            [], [], 'o', zorder=15, label='Manual measures',
            markerfacecolor='none', markersize=5, markeredgewidth=1.5,
            mec=self.colorsDB.rgb['WL obs'])

        # Predicted Recession Curves
        self._mrc_plt, = self.ax2.plot(
            [], [], color='red', lw=1.5, dashes=[5, 3], zorder=100,
            alpha=0.85)

        # Predicted GLUE water levels
        self.glue_plt, = self.ax2.plot([], [])

        self.draw_waterlvl()
        self.draw_glue_wl()
        self.draw_mrc_wl()

        # ---- Init weather artists

        # ---- PRECIPITATION -----

        self.ax3.yaxis.set_ticks_position('right')
        self.ax3.yaxis.set_label_position('right')
        self.ax3.tick_params(axis='y', direction='out', labelsize=10)

        self.PTOT_bar, = self.ax3.plot([], [])
        self.RAIN_bar, = self.ax3.plot([], [])
        self.baseline, = self.ax3.plot(
            [self.TIMEmin, self.TIMEmax], [0, 0], 'k')

        # ---- AIR TEMPERATURE -----

        TEMPmin = -40
        TEMPscale = 20
        TEMPmax = 40

        self.ax4.axis(ymin=TEMPmin, ymax=TEMPmax)

        yticks_position = np.array([TEMPmin, 0, TEMPmax])
        yticks_position = np.arange(TEMPmin, TEMPmax + TEMPscale/2,
                                    TEMPscale)
        self.ax4.set_yticks(yticks_position)
        self.ax4.yaxis.set_ticks_position('left')
        self.ax4.tick_params(axis='y', direction='out', labelsize=10)
        self.ax4.yaxis.set_label_position('left')

        self.ax4.set_yticks([-20, 20], minor=True)
        self.ax4.tick_params(axis='y', which='minor', length=0)
        self.ax4.xaxis.set_ticklabels([], minor=True)

        self.l1_ax4, = self.ax4.plot([], [])  # fill shape
        self.l2_ax4, = self.ax4.plot([], [],  # contour line
                                     color='black', lw=1)

        # ---- MISSING VALUES MARKERS ----

        # Precipitation (v2):

        vshift = 5/72
        offset = ScaledTranslation(0, vshift, self.dpi_scale_trans)
        if self.wxdset is not None:
            t = self.wxdset['Missing Ptot']
            y = np.ones(len(t)) * self.ax4.get_ylim()[0]
        else:
            t, y = [], []
        self.lmiss_ax4, = self.ax4.plot(
            t, y, ls='-', solid_capstyle='projecting', lw=1, c='red',
            transform=self.ax4.transData + offset)

        # ---- Air Temperature (v2) ----

        offset = ScaledTranslation(0, -vshift, self.dpi_scale_trans)
        if self.wxdset is not None:
            t = self.wxdset['Missing Tmax']
            y = np.ones(len(t)) * self.ax4.get_ylim()[1]
        else:
            t, y = [], []
        self.ax4.plot(t, y, ls='-', solid_capstyle='projecting',
                      lw=1., c='red', transform=self.ax4.transData + offset)

        self.draw_weather()
        self.draw_ylabels()
        self.setup_legend()

        self.__isHydrographExists = True

    def setup_legend(self):
        """Setup the legend of the graph."""
        if self.isLegend == 1:
            labelDB = LabelDatabase(self.language).legend
            lg_handles = []
            lg_labels = []
            if self.meteo_on:
                colors = self.colorsDB.rgb
                # Snow
                lg_handles.append(Rectangle(
                    (0, 0), 1, 1, fc=colors['Snow'], ec=colors['Snow']))
                lg_labels.append(labelDB[0])

                # Rain
                lg_handles.append(Rectangle(
                    (0, 0), 1, 1, fc=colors['Rain'], ec=colors['Rain']))
                lg_labels.append(labelDB[1])

                # Air Temperature
                lg_handles.append(Rectangle(
                    (0, 0), 1, 1, fc=colors['Tair'], ec='black'))
                lg_labels.append(labelDB[2])

                # Missing Data Markers
                lg_handles.append(self.lmiss_ax4)
                lg_labels.append(labelDB[3])

            # Continuous Line Datalogger
            lg_handles.append(self.l1_ax2)
            if self.trend_line == 1:
                lg_labels.append(labelDB[4])
            else:
                lg_labels.append(labelDB[5])

            # Water Levels (data points)
            if self.trend_line == 1:
                lg_handles.append(self.l2_ax2)
                lg_labels.append(labelDB[6])

            # Manual Measures
            TIMEmes, WLmes = self.wldset.get_wlmeas()
            if len(TIMEmes) > 0:
                lg_handles.append(self.h_WLmes)
                lg_labels.append(labelDB[7])

            if self.mrc_wl_on:
                lg_labels.append(labelDB[8])
                lg_handles.append(self._mrc_plt)

            if self.glue_wl_on:
                lg_labels.append('GLUE 5/95')
                lg_handles.append(Rectangle(
                    (0, 0), 1, 1, fc='0.65', ec='0.65'))

            # Draw the legend
            # LOCS = ['right', 'center left', 'upper right', 'lower right',
            #         'center', 'lower left', 'center right', 'upper left',
            #         'upper center', 'lower center']
            # ncol = int(np.ceil(len(lg_handles)/2.))
            self.ax0.legend(lg_handles, lg_labels, bbox_to_anchor=[1, 1],
                            loc='lower right', ncol=3,
                            numpoints=1, fontsize=10, frameon=False)
            self.ax0.get_legend().set_zorder(100)
        else:
            if self.ax0.get_legend():
                self.ax0.get_legend().set_visible(False)

    def update_colors(self):
        """Update the color scheme of the figure."""
        if not self.__isHydrographExists:
            return

        self.colorsDB.load_colors_db()
        self.l1_ax2.set_color(self.colorsDB.rgb['WL solid'])
        self.l2_ax2.set_color(self.colorsDB.rgb['WL data'])
        self.h_WLmes.set_color(self.colorsDB.rgb['WL obs'])
        self.draw_weather()
        self.setup_legend()

    def update_fig_size(self):
        """Update the size of the figure."""
        self.set_size_inches(self.fwidth, self.fheight)
        self.set_margins()
        self.draw_ylabels()
        self.set_time_scale()

        self.canvas.draw()

    def set_margins(self):
        """Set the margins of the axes in inches."""
        fheight = self.fheight

        # --- MARGINS (Inches / Fig. Dimension) --- #

        left_margin = 0.85 / self.fwidth

        if self.meteo_on is False:
            right_margin = 0.15 / self.fwidth
        else:
            right_margin = 0.85 / self.fwidth

        bottom_margin = 0.6 / self.fheight

        top_margin = 0.25 / self.fheight
        if self.isGraphTitle == 1 or self.isLegend == 1:
            if self.meteo_on is False:
                top_margin += 0.2 / fheight
            else:
                top_margin += 0.45 / fheight

        # --- MARGINS (% of figure) --- #

        if self.meteo_on:
            va_ratio = self.va_ratio
        else:
            va_ratio = 0

        htot = 1 - (bottom_margin + top_margin)
        htop = htot * va_ratio
        hlow = htot * (1-va_ratio)
        wtot = 1 - (left_margin + right_margin)

        # Host, Frame, Water Levels, Precipitation, Air Temperature

        for i, axe in enumerate(self.axes):
            if i == 4:  # Air Temperature
                axe.set_position([left_margin, bottom_margin + hlow,
                                  wtot, htop])
            elif i in [0, 1]:  # Time, Frame
                axe.set_position([left_margin, bottom_margin, wtot, htot])

            else:
                axe.set_position([left_margin, bottom_margin, wtot, hlow])

    def draw_ylabels(self):

        labelDB = LabelDatabase(self.language)

        # ------------------------------------- Calculate LabelPadding ----

        left_margin = 0.85
        right_margin = 0.85
        if self.meteo_on is False:
            right_margin = 0.35

        axwidth = (self.fwidth - left_margin - right_margin)

        labPad = 0.3 / 2.54  # in Inches
        labPad /= axwidth   # relative coord.

        # --------------------------- YLABELS LEFT (Temp. & Waterlvl) ----

        if self.WLdatum == 0:
            lab_ax2 = labelDB.mbgs
        elif self.WLdatum == 1:
            lab_ax2 = labelDB.masl

        # ---- Water Level ---- #

        self.ax2.set_ylabel(lab_ax2, rotation=90,
                            fontsize=self.label_font_size,
                            va='bottom', ha='center')

        # Get bounding box dimensions of yaxis ticklabels for ax2
        renderer = self.canvas.get_renderer()
        self.canvas.draw()

        bbox2_left, _ = self.ax2.yaxis.get_ticklabel_extents(renderer)

        # bbox are structured in the the following way:   [[ Left , Bottom ],
        #                                                  [ Right, Top    ]]

        # Transform coordinates in ax2 coordinate system.
        bbox2_left = self.ax2.transAxes.inverted().transform(bbox2_left)

        # Calculate the labels positions in x and y.
        ylabel2_xpos = bbox2_left[0, 0] - labPad
        ylabel2_ypos = (bbox2_left[1, 1] + bbox2_left[0, 1]) / 2.

        if self.meteo_on is False:
            self.ax2.yaxis.set_label_coords(ylabel2_xpos, ylabel2_ypos)
            self.draw_figure_title()
            return

        # ------------------------------------------------ Temperature ----

        self.ax4.set_ylabel(labelDB.temperature, rotation=90, va='bottom',
                            ha='center', fontsize=self.label_font_size)

        # Get bounding box dimensions of yaxis ticklabels for ax4
        bbox4_left, _ = self.ax4.yaxis.get_ticklabel_extents(renderer)

        # Transform coordinates in ax4 coordinate system.
        bbox4_left = self.ax4.transAxes.inverted().transform(bbox4_left)

        # Calculate the labels positions in x and y.
        ylabel4_xpos = bbox4_left[0, 0] - labPad
        ylabel4_ypos = (bbox4_left[1, 1] + bbox4_left[0, 1]) / 2.

        # Take the position which is farthest from the left y axis in order
        # to have both labels on the left aligned.
        ylabel_xpos = min(ylabel2_xpos, ylabel4_xpos)

        self.ax2.yaxis.set_label_coords(ylabel_xpos, ylabel2_ypos)
        self.ax4.yaxis.set_label_coords(ylabel_xpos, ylabel4_ypos)

        # ---------------------------------------------- Precipitation ----

        label = labelDB.precip % labelDB.precip_units[self.bwidth_indx]
        self.ax3.set_ylabel(label, rotation=270, va='bottom',
                            ha='center', fontsize=self.label_font_size)

        # Get bounding box dimensions of yaxis ticklabels for ax3
        _, bbox = self.ax3.yaxis.get_ticklabel_extents(renderer)

        # Transform coordinates in ax3 coordinate system and
        # calculate the labels positions in x and y.
        bbox = self.ax3.transAxes.inverted().transform(bbox)

        ylabel3_xpos = bbox[1, 0] + labPad
        ylabel3_ypos = (bbox[1, 1] + bbox[0, 1]) / 2.

        self.ax3.yaxis.set_label_coords(ylabel3_xpos, ylabel3_ypos)

        self.draw_figure_title()

    def best_fit_waterlvl(self):
        WL = self.wldset['WL']
        if self.WLdatum == 1:  # masl
            WL = self.wldset['Elevation'] - WL

        WL = WL[~np.isnan(WL)]
        dWL = np.max(WL) - np.min(WL)
        ygrid = self.NZGrid - 5

        # --- WL Scale --- #

        SCALE = np.hstack((np.arange(0.05, 0.30, 0.05),
                           np.arange(0.3, 5.1, 0.1)))
        dSCALE = np.abs(SCALE - dWL / ygrid)
        indx = np.where(dSCALE == np.min(dSCALE))[0][0]

        self.WLscale = SCALE[indx]

        # ---- WL Min Value --- #

        if self.WLdatum == 0:  # mbgs
            N = np.ceil(np.max(WL)/self.WLscale)
        elif self.WLdatum == 1:  # masl
            # WL = self.WaterLvlObj.ALT - WL
            N = np.floor(np.min(WL) / self.WLscale)

        self.WLmin = self.WLscale * N

        return self.WLscale, self.WLmin

    def best_fit_time(self, TIME):  # =========================================

        # ----- Data Start -----

        date0 = xldate_as_tuple(TIME[0], 0)
        date0 = (date0[0], date0[1], 1)

        self.TIMEmin = xldate_from_date_tuple(date0, 0)

        # ----- Date End -----

        date1 = xldate_as_tuple(TIME[-1], 0)

        year = date1[0]
        month = date1[1] + 1
        if month > 12:
            month = 1
            year += 1

        date1 = (year, month, 1)

        self.TIMEmax = xldate_from_date_tuple(date1, 0)

        return date0, date1

    def resample_bin(self):  # ================================================

        # day; week; month; year
        self.bwidth = [1, 7, 30, 365][self.bwidth_indx]
        bwidth = self.bwidth

        if self.bwidth_indx == 0:  # daily

            self.bTIME = np.copy(self.TIMEmeteo)
            self.bTMAX = np.copy(self.TMAX)
            self.bPTOT = np.copy(self.PTOT)
            self.bRAIN = np.copy(self.RAIN)
        else:
            self.bTIME = self.bin_sum(self.TIMEmeteo, bwidth) / bwidth
            self.bTMAX = self.bin_sum(self.TMAX, bwidth) / bwidth
            self.bPTOT = self.bin_sum(self.PTOT, bwidth)
            self.bRAIN = self.bin_sum(self.RAIN, bwidth)

#        elif self.bwidth_indx == 4 : # monthly
#            print('option not yet available, kept default of 1 day')
#
#        elif self.bwidth_indx == 5 : # yearly
#            print('option not yet available, kept default of 1 day')

    def bin_sum(self, x, bwidth):  # ==========================================
        """
        Sum data x over bins of width "bwidth" starting at indice 0 of x.
        If there is residual data at the end because of the last bin being not
        complete, data are rejected and removed from the reshaped series.
        """

        bwidth = int(bwidth)
        nbin = int(np.floor(len(x) / bwidth))

        bheight = x[:nbin*bwidth].reshape(nbin, bwidth)
        bheight = np.sum(bheight, axis=1)

        return bheight

    # ---- Drawing data methods

    def draw_glue_wl(self):
        """Draw the GLUE estimated water levels envelope."""
        if self.glue_wl_on is False:
            self.glue_plt.set_visible(False)
            return
        else:
            self.glue_plt.set_visible(True)

        xlstime = self.gluedf['water levels']['time']
        wl05 = self.gluedf['water levels']['predicted'][:, 0]/1000
        wl95 = self.gluedf['water levels']['predicted'][:, 2]/1000

        self.glue_plt.remove()
        self.glue_plt = self.ax2.fill_between(
            xlstime, wl95, wl05, facecolor='0.85', lw=1, edgecolor='0.65',
            zorder=0)

    def draw_mrc_wl(self):
        """Draw the water levels predicted with the MRC."""
        if self.mrc_wl_on is False:
            self._mrc_plt.set_visible(False)
        else:
            self._mrc_plt.set_visible(True)
            self._mrc_plt.set_data(
                self.wldset['mrc/time'], self.wldset['mrc/recess'])

    def draw_waterlvl(self):
        """
        This method is called the first time the graph is plotted and each
        time water level datum is changed.
        """

        # ---- Logger Measures

        time = self.wldset.xldates
        if self.WLdatum == 1:  # masl
            water_lvl = self.wldset['Elevation']-self.wldset['WL']
        else:  # mbgs -> yaxis is inverted
            water_lvl = self.wldset['WL']

        if self.trend_line == 1:
            tfilt, wlfilt = filt_data(time, water_lvl, self.trend_MAW)
            self.l1_ax2.set_data(tfilt, wlfilt)
            self.l2_ax2.set_data(time, water_lvl)

        else:
            self.l1_ax2.set_data(time, water_lvl)
            self.l2_ax2.set_data([], [])

        # ---- Manual Measures

        time_wl_meas, wl_meas = self.wldset.get_wlmeas()
        if len(wl_meas) > 0:
            if self.WLdatum == 1:
                # The datum is meter above see level.
                wl_meas = self.wldset['Elevation'] - wl_meas
            self.h_WLmes.set_data(time_wl_meas, wl_meas)

    def draw_weather(self):
        """
        This method is called the first time the graph is plotted and each
        time the time scale is changed.
        """
        if self.meteo_on is False:
            return

        # --------------------------------------------------- SUBSAMPLE DATA --

        # For performance purposes, only the data that fit within the limits
        # of the x axis limits are plotted.

        istart = np.where(self.bTIME > self.TIMEmin)[0]
        if len(istart) == 0:
            istart = 0
        else:
            istart = istart[0]
            if istart > 0:
                istart += -1

        iend = np.where(self.bTIME < self.TIMEmax)[0]
        if len(iend) == 0:
            iend = 0
        else:
            iend = iend[-1]
            if iend < len(self.bTIME):
                iend += 1

        time = self.bTIME[istart:iend]
        Tmax = self.bTMAX[istart:iend]
        Ptot = self.bPTOT[istart:iend]
        Rain = self.bRAIN[istart:iend]

        # ------------------------------------------------------ PLOT PRECIP --

        TIME2X = np.zeros(len(time) * 4)
        Ptot2X = np.zeros(len(time) * 4)
        Rain2X = np.zeros(len(time) * 4)

        n = self.bwidth / 2.
        f = 0.85  # Space between individual bar.

        TIME2X[0::4] = time - n * f
        TIME2X[1::4] = time - n * f
        TIME2X[2::4] = time + n * f
        TIME2X[3::4] = time + n * f

        Ptot2X[0::4] = 0
        Ptot2X[1::4] = Ptot
        Ptot2X[2::4] = Ptot
        Ptot2X[3::4] = 0

        Rain2X[0::4] = 0
        Rain2X[1::4] = Rain
        Rain2X[2::4] = Rain
        Rain2X[3::4] = 0

        self.PTOT_bar.remove()
        self.RAIN_bar.remove()

        self.PTOT_bar = self.ax3.fill_between(TIME2X, 0., Ptot2X,
                                              color=self.colorsDB.rgb['Snow'],
                                              linewidth=0.0)

        self.RAIN_bar = self.ax3.fill_between(TIME2X, 0., Rain2X,
                                              color=self.colorsDB.rgb['Rain'],
                                              linewidth=0.0)

        self.baseline.set_data([self.TIMEmin, self.TIMEmax], [0, 0])

        # ---------------------------------------------------- PLOT AIR TEMP --

        TIME2X = np.zeros(len(time)*2)
        Tmax2X = np.zeros(len(time)*2)

        n = self.bwidth / 2.
        TIME2X[0:2*len(time)-1:2] = time - n
        TIME2X[1:2*len(time):2] = time + n
        Tmax2X[0:2*len(time)-1:2] = Tmax
        Tmax2X[1:2*len(time):2] = Tmax

        self.l1_ax4.remove()
        self.l1_ax4 = self.ax4.fill_between(TIME2X, 0., Tmax2X,
                                            color=self.colorsDB.rgb['Tair'],
                                            edgecolor='None')

        self.l2_ax4.set_xdata(TIME2X)
        self.l2_ax4.set_ydata(Tmax2X)

        self.update_precip_scale()

    def set_time_scale(self):
        """Setup the time scale of the x-axis."""
        if self.datemode.lower() == 'year':
            year = xldate_as_tuple(self.TIMEmin, 0)[0]
            self.TIMEmin = xldate_from_date_tuple((year, 1, 1), 0)

            last_month = xldate_as_tuple(self.TIMEmax, 0)[1] == 1
            last_day = xldate_as_tuple(self.TIMEmax, 0)[2] == 1

            if last_month and last_day:
                pass
            else:
                year = xldate_as_tuple(self.TIMEmax, 0)[0] + 1
                self.TIMEmax = xldate_from_date_tuple((year, 1, 1), 0)

        self.setup_xticklabels()
        self.ax1.axis([self.TIMEmin, self.TIMEmax, 0, self.NZGrid])

    def setup_xticklabels(self):
        """Setup the xtick labels."""

        # Labels are placed manually because this is around 25% faster than
        # using the minor ticks.

        xticks_info = self.make_xticks_info()
        self.ax1.set_xticks(xticks_info[0])

        for i in range(len(self.xlabels)):
            self.xlabels[i].remove()

        padding = ScaledTranslation(0, -5/72, self.dpi_scale_trans)

        self.xlabels = []
        for i in range(len(xticks_info[1])):
            new_label = self.ax1.text(
                xticks_info[1][i], 0, xticks_info[2][i], rotation=45,
                va='top', ha='right', fontsize=10,
                transform=self.ax1.transData + padding)
            self.xlabels.append(new_label)

    def draw_figure_title(self):
        """Draw the title of the figure."""
        labelDB = LabelDatabase(self.language)
        if self.isGraphTitle:
            # Set the text and position of the title.
            if self.meteo_on:
                offset = ScaledTranslation(0, 7/72, self.dpi_scale_trans)
                self.text1.set_text(
                    labelDB.station_meteo % (self.name_meteo, self.dist))
                self.text1.set_transform(self.ax0.transAxes + offset)

            dy = 30 if self.meteo_on else 7
            offset = ScaledTranslation(0, dy/72, self.dpi_scale_trans)
            self.figTitle.set_text(labelDB.title % self.wldset['Well'])
            self.figTitle.set_transform(self.ax0.transAxes + offset)

        # Set whether the title is visible or not.
        self.text1.set_visible(self.meteo_on and self.isGraphTitle)
        self.figTitle.set_visible(self.isGraphTitle)

    def setup_waterlvl_scale(self):
        """Update the y scale of the water levels."""
        NZGrid = self.NZGrid if self.meteo_on else self.NZGrid - 2

        self.axLow.set_yticks(np.arange(1, self.NZGrid))
        self.axLow.axis(ymin=0, ymax=NZGrid)
        self.axLow.yaxis.set_ticklabels([])

        if self.WLdatum == 1:   # masl
            WLmin = self.WLmin
            WLscale = self.WLscale
            WLmax = WLmin + (NZGrid * WLscale)

            if self.meteo_on:
                self.ax2.set_yticks(np.arange(WLmin, WLmax - 1.9*WLscale,
                                              WLscale))
            else:
                self.ax2.set_yticks(np.arange(WLmin, WLmax + 0.1*WLscale,
                                              WLscale))

            self.ax2.axis(ymin=WLmin, ymax=WLmax)

        else:  # mbgs: Y axis is inverted
            WLmax = self.WLmin
            WLscale = self.WLscale
            WLmin = WLmax - (NZGrid * WLscale)

            if self.meteo_on:
                self.ax2.set_yticks(np.arange(WLmax, WLmin + 1.9*WLscale,
                                              -WLscale))
            else:
                self.ax2.set_yticks(np.arange(WLmax, WLmin - 0.1*WLscale,
                                              -WLscale))

            self.ax2.axis(ymin=WLmin, ymax=WLmax)
            self.ax2.invert_yaxis()

    def update_precip_scale(self):
        """Update the scale of the axe where precipitation are plotter."""
        if self.meteo_on is False:
            return

        ymax = self.NZGrid * self.RAINscale
        try:
            p = self.PTOT_bar.get_paths()[0]
            v = p.vertices
            y = v[:, 1]

            yticksmax = 0
            while True:
                if yticksmax > max(y):
                    break
                yticksmax += self.RAINscale
            yticksmax = min(ymax, yticksmax) + self.RAINscale/2
        except Exception:
            yticksmax = 3.9 * self.RAINscale

        self.ax3.axis(ymin=0, ymax=ymax)
        self.ax3.set_yticks(np.arange(0, yticksmax, self.RAINscale))
        self.ax3.invert_yaxis()

    def setup_figure_frame(self):
        """Draw a frame around the figure."""
        self.set_frameon(self._figframe_lw > 0)
        self.set_facecolor('white')
        self.set_edgecolor('black')
        self.patch.set_linewidth(self._figframe_lw)

    def set_gridLines(self):

        # 0 -> None, 1 -> "-" 2 -> ":"

        if self.gridLines == 0:
            for ax in self.axes:
                ax._gridOn = False

        elif self.gridLines == 1:
            self.ax4.grid(axis='y', color=[0.35, 0.35, 0.35], linestyle='-',
                          linewidth=0.5, which='minor')
            self.axLow.grid(axis='y', color=[0.35, 0.35, 0.35], linestyle='-',
                            linewidth=0.5)
            self.ax1.grid(axis='x', color=[0.35, 0.35, 0.35], linestyle='-',
                          linewidth=0.5)

        else:
            self.ax4.grid(axis='y', color=[0.35, 0.35, 0.35], linestyle=':',
                          linewidth=0.5, dashes=[0.5, 5], which='minor')
            self.axLow.grid(axis='y', color=[0.35, 0.35, 0.35], linestyle=':',
                            linewidth=0.5, dashes=[0.5, 5])
            self.ax1.grid(axis='x', color=[0.35, 0.35, 0.35], linestyle=':',
                          linewidth=0.5, dashes=[0.5, 5])

    def make_xticks_info(self):

        # ---------------------------------------- horizontal text alignment --

        # The strategy here is to:
        # 1. render some random text ;
        # 2. get the height of its bounding box ;
        # 3. get the horizontal translation of the top-right corner after a
        #    rotation of the bbox of 45 degrees ;
        # 4. sclale the length calculated in step 3 to the height to width
        #    ratio of the axe ;
        # 5. convert the lenght calculated in axes coord. to the data coord.
        #    system ;
        # 6. remove the random text from the figure.

        # Random text bbox height :

        dummytxt = self.ax1.text(0.5, 0.5, 'some_dummy_text', fontsize=10,
                                 ha='right', va='top',
                                 transform=self.ax1.transAxes)

        renderer = self.canvas.get_renderer()

        bbox = dummytxt.get_window_extent(renderer)
        bbox = bbox.transformed(self.ax1.transAxes.inverted())

        # Horiz. trans. of bbox top-right corner :

        dx = bbox.height * np.sin(np.radians(45))

        # Scale dx to axe dimension :

        bbox = self.ax1.get_window_extent(renderer)  # in pixels
        bbox = bbox.transformed(self.dpi_scale_trans.inverted())  # in inches

        sdx = dx * bbox.height / bbox.width
        sdx *= (self.TIMEmax - self.TIMEmin + 1)

        dummytxt.remove()

        # Transform to data coord :

        n = self.date_labels_pattern
        month_names = LabelDatabase(self.language).month_names

        xticks_labels_offset = sdx

        xticks_labels = []
        xticks_position = [self.TIMEmin]
        xticks_labels_position = []

        if self.datemode.lower() == 'month':

            i = 0
            while xticks_position[i] < self.TIMEmax:

                year = xldate_as_tuple(xticks_position[i], 0)[0]
                month = xldate_as_tuple(xticks_position[i], 0)[1]

                month_range = monthrange(year, month)[1]

                xticks_position.append(xticks_position[i] + month_range)

                if i % n == 0:

                    xticks_labels_position.append(xticks_position[i] +
                                                  0.5 * month_range +
                                                  xticks_labels_offset)

                    xticks_labels.append("%s '%s" % (month_names[month - 1],
                                                     str(year)[-2:]))

                i += 1

        elif self.datemode.lower() == 'year':

            i = 0
            year = xldate_as_tuple(xticks_position[i], 0)[0]
            while xticks_position[i] < self.TIMEmax:

                xticks_position.append(
                                     xldate_from_date_tuple((year+1, 1, 1), 0))
                year_range = xticks_position[i+1] - xticks_position[i]

                if i % n == 0:

                    xticks_labels_position.append(xticks_position[i] +
                                                  0.5 * year_range +
                                                  xticks_labels_offset)

                    xticks_labels.append("%d" % year)

                year += 1
                i += 1

        return xticks_position, xticks_labels_position, xticks_labels
Exemplo n.º 7
0
    def __init__(self, *args, **kargs):
        super(Hydrograph, self).__init__(*args, **kargs)

        # set canvas and renderer :

        self.set_canvas(FigureCanvas(self))
        self.canvas.get_renderer()

        self.__isHydrographExists = False

        # Fig Init :

        self.fwidth = 11
        self.fheight = 7
        self.NZGrid = 8  # Number of interval in the grid of the bottom part

        # Vertical height ratio between the top part  and the bottom part
        self.va_ratio = 0.2

        # Graph labels language :
        self.language = 'english'

        # Database :

        self.colorsDB = ColorsReader()
        self.colorsDB.load_colors_db()

        # Scales :

        self.WLmin = 0
        self.WLscale = 0

        self.RAINscale = 20

        self.TIMEmin = 36526
        self.TIMEmax = 36526

        # Legend  and Title :

        self.isLegend = 1
        self.isGraphTitle = 1

        # Layout Options :

        self.WLdatum = 0  # 0: mbgs;  1: masl
        self.trend_line = 0
        self.trend_MAW = 30
        # trend_MAW = width of the Moving Average Window used to
        #             smooth the water level data
        self.meteo_on = True
        self.glue_wl_on = False
        self.mrc_wl_on = False
        self.gridLines = 2  # 0 -> None, 1 -> "-" 2 -> ":"
        self.datemode = 'Month'  # 'month' or 'year'
        self.label_font_size = 14
        self.date_labels_pattern = 2
        self._figframe_lw = 0

        # Waterlvl & Meteo Obj :

        self.wldset = None
        self.wxdset = None
        self.gluedf = None

        # Daily Weather :

        self.dist = 0
        self.name_meteo = ''
        self.TIMEmeteo = np.array([])  # Time in Excel numeric format (days)
        self.TMAX = np.array([])  # Daily maximum temperature (deg C)
        self.PTOT = np.array([])
        self.RAIN = np.array([])

        # Bin Redistributed Weather :

        self.bTIME = np.array([])
        self.bTMAX = np.array([])
        self.bPTOT = np.array([])
        self.bRAIN = np.array([])

        self.bwidth_indx = 1
        #   0: 1 day;
        #   1: 1 week;
        #   2: 1 month;
        #   3: 1 year;

        self.NMissPtot = []
Exemplo n.º 8
0
class Hydrograph(Figure):
    def __init__(self, *args, **kargs):
        super(Hydrograph, self).__init__(*args, **kargs)

        # set canvas and renderer :

        self.set_canvas(FigureCanvas(self))
        self.canvas.get_renderer()

        self.__isHydrographExists = False

        # Fig Init :

        self.fwidth = 11
        self.fheight = 7
        self.NZGrid = 8  # Number of interval in the grid of the bottom part

        # Vertical height ratio between the top part  and the bottom part
        self.va_ratio = 0.2

        # Graph labels language :
        self.language = 'english'

        # Database :

        self.colorsDB = ColorsReader()
        self.colorsDB.load_colors_db()

        # Scales :

        self.WLmin = 0
        self.WLscale = 0

        self.RAINscale = 20

        self.TIMEmin = 36526
        self.TIMEmax = 36526

        # Legend  and Title :

        self.isLegend = 1
        self.isGraphTitle = 1

        # Layout Options :

        self.WLdatum = 0  # 0: mbgs;  1: masl
        self.trend_line = 0
        self.trend_MAW = 30
        # trend_MAW = width of the Moving Average Window used to
        #             smooth the water level data
        self.meteo_on = True
        self.glue_wl_on = False
        self.mrc_wl_on = False
        self.gridLines = 2  # 0 -> None, 1 -> "-" 2 -> ":"
        self.datemode = 'Month'  # 'month' or 'year'
        self.label_font_size = 14
        self.date_labels_pattern = 2
        self._figframe_lw = 0

        # Waterlvl & Meteo Obj :

        self.wldset = None
        self.wxdset = None
        self.gluedf = None

        # Daily Weather :

        self.dist = 0
        self.name_meteo = ''
        self.TIMEmeteo = np.array([])  # Time in Excel numeric format (days)
        self.TMAX = np.array([])  # Daily maximum temperature (deg C)
        self.PTOT = np.array([])
        self.RAIN = np.array([])

        # Bin Redistributed Weather :

        self.bTIME = np.array([])
        self.bTMAX = np.array([])
        self.bPTOT = np.array([])
        self.bRAIN = np.array([])

        self.bwidth_indx = 1
        #   0: 1 day;
        #   1: 1 week;
        #   2: 1 month;
        #   3: 1 year;

        self.NMissPtot = []

    @property
    def meteo_on(self):
        """Controls whether meteo data are plotted or not."""
        return (self.__meteo_on and self.wxdset is not None)

    @meteo_on.setter
    def meteo_on(self, x):
        self.__meteo_on = bool(x)

    def set_meteo_on(self, x):
        """Set whether the meteo data are plotted or not."""
        self.meteo_on = x
        if self.__isHydrographExists:
            self.ax3.set_visible(self.meteo_on)
            self.ax4.set_visible(self.meteo_on)
            self.setup_waterlvl_scale()
            self.draw_weather()

    @property
    def glue_wl_on(self):
        """Controls whether glue water levels are plotted or not."""
        return (self.__glue_wl_on and self.gluedf is not None)

    @glue_wl_on.setter
    def glue_wl_on(self, x):
        self.__glue_wl_on = bool(x)

    def set_glue_wl_on(self, x):
        """Set whether the glue water levels data are plotted or not."""
        self.glue_wl_on = x
        if self.__isHydrographExists:
            self.draw_glue_wl()
            self.setup_legend()

    @property
    def mrc_wl_on(self):
        """Return whether the mrc water levels must be plotted or not."""
        return (self.__mrc_wl_on and self.wldset is not None and
                self.wldset.mrc_exists())

    @mrc_wl_on.setter
    def mrc_wl_on(self, x):
        """Set whether the mrc water levels data must plotted or not."""
        self.__mrc_wl_on = bool(x)

    def set_mrc_wl_on(self, x):
        """Set whether the mrc water levels data must plotted or not."""
        self.mrc_wl_on = x
        if self.__isHydrographExists:
            self.draw_mrc_wl()
            self.setup_legend()

    def set_figframe_lw(self, x):
        """
        Set the line thickness of the frame that encloses the entire figure.
        """
        self._figframe_lw = x
        self.setup_figure_frame()

    @property
    def language(self):
        return self.__language

    @language.setter
    def language(self, x):
        if x.lower() in ['english', 'french']:
            self.__language = x
        else:
            print('WARNING: Language not supported. '
                  'Setting language to "english".')
            self.__language = 'english'

    @property
    def isHydrographExists(self):
        return self.__isHydrographExists

    def set_wldset(self, wldset):
        self.wldset = wldset

    def set_wxdset(self, wxdset):
        self.wxdset = wxdset

    def set_gluedf(self, gluedf):
        """Set the namespace for the GLUE dataframe."""
        self.gluedf = gluedf
        self.draw_glue_wl()
        self.setup_legend()

    def clf(self, *args, **kargs):
        """Matplotlib override to set internal flag."""
        self.__isHydrographExists = False
        super(Hydrograph, self).clf(*args, **kargs)

    def savefig(self, fname):
        """Matplotlib override to set frameon when saving."""
        super(Hydrograph, self).savefig(fname, frameon=True, facecolor='white',
                                        edgecolor='black')

    def generate_hydrograph(self, wxdset=None, wldset=None):
        wxdset = self.wxdset if wxdset is None else wxdset
        wldset = self.wldset if wldset is None else wldset

        # Reinit the figure.
        self.clf()
        self.set_size_inches(self.fwidth, self.fheight, forward=True)
        self.setup_figure_frame()

        # Assign Weather Data.
        if self.wxdset is None:
            self.name_meteo = ''
            self.TIMEmeteo = np.array([])
            self.TMAX = np.array([])
            self.PTOT = np.array([])
            self.RAIN = np.array([])
        else:
            self.name_meteo = wxdset.metadata['Station Name']
            self.TIMEmeteo = datetimeindex_to_xldates(wxdset.data.index)
            self.TMAX = wxdset.data['Tmax'].values
            self.PTOT = wxdset.data['Ptot'].values
            self.RAIN = wxdset.data['Rain'].values

        # Resample Data in Bins :

        self.resample_bin()

        # -------------------------------------------------- AXES CREATION ----

        # ---- Time (host) ----

        # Also holds the gridlines.

        self.ax1 = self.add_axes([0, 0, 1, 1], frameon=False)
        self.ax1.set_zorder(100)

        # ---- Frame ----

        # Only used to display the frame so it is always on top.

        self.ax0 = self.add_axes(self.ax1.get_position(), frameon=True)
        self.ax0.patch.set_visible(False)
        self.ax0.set_zorder(self.ax1.get_zorder() + 200)
        self.ax0.tick_params(bottom=False, top=False, left=False, right=False,
                             labelbottom=False, labelleft=False)

        # ---- Water Levels ----

        self.ax2 = self.add_axes(self.ax1.get_position(), frameon=False,
                                 label='axes2', sharex=self.ax1)
        self.ax2.set_zorder(self.ax1.get_zorder() + 100)
        self.ax2.yaxis.set_ticks_position('left')
        self.ax2.yaxis.set_label_position('left')
        self.ax2.tick_params(axis='y', direction='out', labelsize=10)

        # ---- Precipitation ----

        self.ax3 = self.add_axes(self.ax1.get_position(), frameon=False,
                                 label='axes3', sharex=self.ax1)
        self.ax3.set_zorder(self.ax1.get_zorder() + 150)
        self.ax3.set_navigate(False)

        # ---- Air Temperature ----

        self.ax4 = self.add_axes(self.ax1.get_position(), frameon=False,
                                 label='axes4', sharex=self.ax1)
        self.ax4.set_zorder(self.ax1.get_zorder() + 150)
        self.ax4.set_navigate(False)
        self.ax4.set_axisbelow(True)
        self.ax4.tick_params(bottom=False, top=False, left=False,
                             right=False, labelbottom=False, labelleft=False)

        if self.meteo_on is False:
            self.ax3.set_visible(False)
            self.ax4.set_visible(False)

        # ---- Bottom Graph Grid ----

        self.axLow = self.add_axes(self.ax1.get_position(), frameon=False,
                                   label='axLow', sharex=self.ax1)
        self.axLow.patch.set_visible(False)
        self.axLow.set_zorder(self.ax2.get_zorder() - 50)
        self.axLow.tick_params(bottom=False, top=False, left=False,
                               right=False, labelbottom=False, labelleft=False)

        self.setup_waterlvl_scale()

        # -------------------------------------------------- Remove Spines ----

        for axe in self.axes[2:]:
            for loc in axe.spines:
                axe.spines[loc].set_visible(False)

        # ------------------------------------------------- Update margins ----

        self.bottom_margin = 0.75
        self.set_margins()  # set margins for all the axes

        # --------------------------------------------------- FIGURE TITLE ----

        # Calculate horizontal distance between weather station and
        # observation well.
        if self.wxdset is not None:
            self.dist = calc_dist_from_coord(
                wldset['Latitude'], wldset['Longitude'],
                wxdset.metadata['Latitude'], wxdset.metadata['Longitude'])
        else:
            self.dist = 0

        # Weather Station name and distance to the well
        self.text1 = self.ax0.text(0, 1, '', va='bottom', ha='left',
                                   rotation=0, fontsize=10)

        # Well Name
        self.figTitle = self.ax0.text(0, 1, '', fontsize=18,
                                      ha='left', va='bottom')

        self.draw_figure_title()

        # ----------------------------------------------------------- TIME ----

        self.xlabels = []
        self.set_time_scale()

        self.ax1.xaxis.set_ticklabels([])
        self.ax1.xaxis.set_ticks_position('bottom')
        self.ax1.tick_params(axis='x', direction='out')
        self.ax1.tick_params(top=False, left=False, right=False,
                             labeltop=False, labelleft=False, labelright=False)

        self.set_gridLines()

        # ---- Init water level artists

        # Continuous Line Datalogger
        self.l1_ax2, = self.ax2.plot(
            [], [], '-', zorder=10, lw=1, color=self.colorsDB.rgb['WL solid'])

        # Data Point Datalogger
        self.l2_ax2, = self.ax2.plot(
            [], [], '.', color=self.colorsDB.rgb['WL data'], markersize=5)

        # Manual Mesures
        self.h_WLmes, = self.ax2.plot(
            [], [], 'o', zorder=15, label='Manual measures',
            markerfacecolor='none', markersize=5, markeredgewidth=1.5,
            mec=self.colorsDB.rgb['WL obs'])

        # Predicted Recession Curves
        self._mrc_plt, = self.ax2.plot(
            [], [], color='red', lw=1.5, dashes=[5, 3], zorder=100,
            alpha=0.85)

        # Predicted GLUE water levels
        self.glue_plt, = self.ax2.plot([], [])

        self.draw_waterlvl()
        self.draw_glue_wl()
        self.draw_mrc_wl()

        # ---- Init weather artists

        # ---- PRECIPITATION -----

        self.ax3.yaxis.set_ticks_position('right')
        self.ax3.yaxis.set_label_position('right')
        self.ax3.tick_params(axis='y', direction='out', labelsize=10)

        self.PTOT_bar, = self.ax3.plot([], [])
        self.RAIN_bar, = self.ax3.plot([], [])
        self.baseline, = self.ax3.plot(
            [self.TIMEmin, self.TIMEmax], [0, 0], 'k')

        # ---- AIR TEMPERATURE -----

        TEMPmin = -40
        TEMPscale = 20
        TEMPmax = 40

        self.ax4.axis(ymin=TEMPmin, ymax=TEMPmax)

        yticks_position = np.array([TEMPmin, 0, TEMPmax])
        yticks_position = np.arange(
            TEMPmin, TEMPmax + TEMPscale / 2, TEMPscale)
        self.ax4.set_yticks(yticks_position)
        self.ax4.yaxis.set_ticks_position('left')
        self.ax4.tick_params(axis='y', direction='out', labelsize=10)
        self.ax4.yaxis.set_label_position('left')

        self.ax4.set_yticks([-20, 20], minor=True)
        self.ax4.tick_params(axis='y', which='minor', length=0)
        self.ax4.xaxis.set_ticklabels([], minor=True)

        self.l1_ax4, = self.ax4.plot([], [])  # fill shape
        self.l2_ax4, = self.ax4.plot(
            [], [], color='black', lw=1)  # contour line

        # ---- MISSING VALUES MARKERS

        # Precipitation.
        vshift = 5 / 72
        offset = ScaledTranslation(0, vshift, self.dpi_scale_trans)
        if self.wxdset is not None:
            t1 = pd.DataFrame(self.wxdset.missing_value_indexes['Ptot'],
                              index=self.wxdset.missing_value_indexes['Ptot'],
                              columns=['datetime'])
            t2 = pd.DataFrame(self.wxdset.missing_value_indexes['Ptot'] +
                              pd.Timedelta('1 days'),
                              self.wxdset.missing_value_indexes['Ptot'] +
                              pd.Timedelta('1 days'),
                              columns=['datetime'])
            time = datetimeindex_to_xldates(pd.DatetimeIndex(
                pd.concat([t1, t2], axis=0)
                .drop_duplicates()
                .resample('1D')
                .asfreq()
                ['datetime']
                ))
            y = np.ones(len(time)) * self.ax4.get_ylim()[0]
        else:
            time, y = [], []
        self.lmiss_ax4, = self.ax4.plot(
            time, y, ls='-', solid_capstyle='projecting', lw=1, c='red',
            transform=self.ax4.transData + offset)

        # Air Temperature.
        offset = ScaledTranslation(0, -vshift, self.dpi_scale_trans)
        if self.wxdset is not None:
            t1 = pd.DataFrame(self.wxdset.missing_value_indexes['Tmax'],
                              index=self.wxdset.missing_value_indexes['Tmax'],
                              columns=['datetime'])
            t2 = pd.DataFrame(self.wxdset.missing_value_indexes['Tmax'] +
                              pd.Timedelta('1 days'),
                              self.wxdset.missing_value_indexes['Tmax'] +
                              pd.Timedelta('1 days'),
                              columns=['datetime'])
            time = datetimeindex_to_xldates(pd.DatetimeIndex(
                pd.concat([t1, t2], axis=0)
                .drop_duplicates()
                .resample('1D')
                .asfreq()
                ['datetime']
                ))
            y = np.ones(len(time)) * self.ax4.get_ylim()[1]
        else:
            time, y = [], []
        self.ax4.plot(time, y, ls='-', solid_capstyle='projecting',
                      lw=1., c='red', transform=self.ax4.transData + offset)

        self.draw_weather()
        self.draw_ylabels()
        self.setup_legend()

        self.__isHydrographExists = True

    def setup_legend(self):
        """Setup the legend of the graph."""
        if self.isLegend == 1:
            labelDB = LabelDatabase(self.language).legend
            lg_handles = []
            lg_labels = []
            if self.meteo_on:
                colors = self.colorsDB.rgb
                # Snow
                lg_handles.append(Rectangle(
                    (0, 0), 1, 1, fc=colors['Snow'], ec=colors['Snow']))
                lg_labels.append(labelDB[0])

                # Rain
                lg_handles.append(Rectangle(
                    (0, 0), 1, 1, fc=colors['Rain'], ec=colors['Rain']))
                lg_labels.append(labelDB[1])

                # Air Temperature
                lg_handles.append(Rectangle(
                    (0, 0), 1, 1, fc=colors['Tair'], ec='black'))
                lg_labels.append(labelDB[2])

                # Missing Data Markers
                lg_handles.append(self.lmiss_ax4)
                lg_labels.append(labelDB[3])

            # Continuous Line Datalogger
            lg_handles.append(self.l1_ax2)
            if self.trend_line == 1:
                lg_labels.append(labelDB[4])
            else:
                lg_labels.append(labelDB[5])

            # Water Levels (data points)
            if self.trend_line == 1:
                lg_handles.append(self.l2_ax2)
                lg_labels.append(labelDB[6])

            # Manual Measures
            TIMEmes, WLmes = self.wldset.get_wlmeas()
            if len(TIMEmes) > 0:
                lg_handles.append(self.h_WLmes)
                lg_labels.append(labelDB[7])

            if self.mrc_wl_on:
                lg_labels.append(labelDB[8])
                lg_handles.append(self._mrc_plt)

            if self.glue_wl_on:
                lg_labels.append('GLUE 5/95')
                lg_handles.append(Rectangle(
                    (0, 0), 1, 1, fc='0.65', ec='0.65'))

            # Draw the legend
            # LOCS = ['right', 'center left', 'upper right', 'lower right',
            #         'center', 'lower left', 'center right', 'upper left',
            #         'upper center', 'lower center']
            # ncol = int(np.ceil(len(lg_handles)/2.))
            self.ax0.legend(lg_handles, lg_labels, bbox_to_anchor=[1, 1],
                            loc='lower right', ncol=3,
                            numpoints=1, fontsize=10, frameon=False)
            self.ax0.get_legend().set_zorder(100)
        else:
            if self.ax0.get_legend():
                self.ax0.get_legend().set_visible(False)

    def update_colors(self):
        """Update the color scheme of the figure."""
        if not self.__isHydrographExists:
            return

        self.colorsDB.load_colors_db()
        self.l1_ax2.set_color(self.colorsDB.rgb['WL solid'])
        self.l2_ax2.set_color(self.colorsDB.rgb['WL data'])
        self.h_WLmes.set_color(self.colorsDB.rgb['WL obs'])
        self.draw_weather()
        self.setup_legend()

    def update_fig_size(self):
        """Update the size of the figure."""
        self.set_size_inches(self.fwidth, self.fheight)
        self.set_margins()
        self.draw_ylabels()
        self.set_time_scale()

        self.canvas.draw()

    def set_margins(self):
        """Set the margins of the axes in inches."""
        fheight = self.fheight

        # --- MARGINS (Inches / Fig. Dimension) --- #

        left_margin = 0.85 / self.fwidth

        if self.meteo_on is False:
            right_margin = 0.15 / self.fwidth
        else:
            right_margin = 0.85 / self.fwidth

        bottom_margin = 0.6 / self.fheight

        top_margin = 0.25 / self.fheight
        if self.isGraphTitle == 1 or self.isLegend == 1:
            if self.meteo_on is False:
                top_margin += 0.2 / fheight
            else:
                top_margin += 0.45 / fheight

        # --- MARGINS (% of figure) --- #

        if self.meteo_on:
            va_ratio = self.va_ratio
        else:
            va_ratio = 0

        htot = 1 - (bottom_margin + top_margin)
        htop = htot * va_ratio
        hlow = htot * (1-va_ratio)
        wtot = 1 - (left_margin + right_margin)

        # Host, Frame, Water Levels, Precipitation, Air Temperature

        for i, axe in enumerate(self.axes):
            if i == 4:  # Air Temperature
                axe.set_position([left_margin, bottom_margin + hlow,
                                  wtot, htop])
            elif i in [0, 1]:  # Time, Frame
                axe.set_position([left_margin, bottom_margin, wtot, htot])

            else:
                axe.set_position([left_margin, bottom_margin, wtot, hlow])

    def draw_ylabels(self):

        labelDB = LabelDatabase(self.language)

        # ------------------------------------- Calculate LabelPadding ----

        left_margin = 0.85
        right_margin = 0.85
        if self.meteo_on is False:
            right_margin = 0.35

        axwidth = (self.fwidth - left_margin - right_margin)

        labPad = 0.3 / 2.54  # in Inches
        labPad /= axwidth   # relative coord.

        # --------------------------- YLABELS LEFT (Temp. & Waterlvl) ----

        if self.WLdatum == 0:
            lab_ax2 = labelDB.mbgs
        elif self.WLdatum == 1:
            lab_ax2 = labelDB.masl

        # ---- Water Level ---- #

        self.ax2.set_ylabel(lab_ax2, rotation=90,
                            fontsize=self.label_font_size,
                            va='bottom', ha='center')

        # Get bounding box dimensions of yaxis ticklabels for ax2
        renderer = self.canvas.get_renderer()
        self.canvas.draw()

        bbox2_left, _ = self.ax2.yaxis.get_ticklabel_extents(renderer)

        # bbox are structured in the the following way:   [[ Left , Bottom ],
        #                                                  [ Right, Top    ]]

        # Transform coordinates in ax2 coordinate system.
        bbox2_left = self.ax2.transAxes.inverted().transform(bbox2_left)

        # Calculate the labels positions in x and y.
        ylabel2_xpos = bbox2_left[0, 0] - labPad
        ylabel2_ypos = (bbox2_left[1, 1] + bbox2_left[0, 1]) / 2.

        if self.meteo_on is False:
            self.ax2.yaxis.set_label_coords(ylabel2_xpos, ylabel2_ypos)
            self.draw_figure_title()
            return

        # ------------------------------------------------ Temperature ----

        self.ax4.set_ylabel(labelDB.temperature, rotation=90, va='bottom',
                            ha='center', fontsize=self.label_font_size)

        # Get bounding box dimensions of yaxis ticklabels for ax4
        bbox4_left, _ = self.ax4.yaxis.get_ticklabel_extents(renderer)

        # Transform coordinates in ax4 coordinate system.
        bbox4_left = self.ax4.transAxes.inverted().transform(bbox4_left)

        # Calculate the labels positions in x and y.
        ylabel4_xpos = bbox4_left[0, 0] - labPad
        ylabel4_ypos = (bbox4_left[1, 1] + bbox4_left[0, 1]) / 2.

        # Take the position which is farthest from the left y axis in order
        # to have both labels on the left aligned.
        ylabel_xpos = min(ylabel2_xpos, ylabel4_xpos)

        self.ax2.yaxis.set_label_coords(ylabel_xpos, ylabel2_ypos)
        self.ax4.yaxis.set_label_coords(ylabel_xpos, ylabel4_ypos)

        # ---------------------------------------------- Precipitation ----

        label = labelDB.precip % labelDB.precip_units[self.bwidth_indx]
        self.ax3.set_ylabel(label, rotation=270, va='bottom',
                            ha='center', fontsize=self.label_font_size)

        # Get bounding box dimensions of yaxis ticklabels for ax3
        _, bbox = self.ax3.yaxis.get_ticklabel_extents(renderer)

        # Transform coordinates in ax3 coordinate system and
        # calculate the labels positions in x and y.
        bbox = self.ax3.transAxes.inverted().transform(bbox)

        ylabel3_xpos = bbox[1, 0] + labPad
        ylabel3_ypos = (bbox[1, 1] + bbox[0, 1]) / 2.

        self.ax3.yaxis.set_label_coords(ylabel3_xpos, ylabel3_ypos)

        self.draw_figure_title()

    def best_fit_waterlvl(self):
        WL = self.wldset['WL']
        if self.WLdatum == 1:  # masl
            WL = self.wldset['Elevation'] - WL

        WL = WL[~np.isnan(WL)]
        dWL = np.max(WL) - np.min(WL)
        ygrid = self.NZGrid - 5

        # --- WL Scale --- #

        SCALE = np.hstack((np.arange(0.05, 0.30, 0.05),
                           np.arange(0.3, 5.1, 0.1)))
        dSCALE = np.abs(SCALE - dWL / ygrid)
        indx = np.where(dSCALE == np.min(dSCALE))[0][0]

        self.WLscale = SCALE[indx]

        # ---- WL Min Value --- #

        if self.WLdatum == 0:  # mbgs
            N = np.ceil(np.max(WL)/self.WLscale)
        elif self.WLdatum == 1:  # masl
            # WL = self.WaterLvlObj.ALT - WL
            N = np.floor(np.min(WL) / self.WLscale)

        self.WLmin = self.WLscale * N

        return self.WLscale, self.WLmin

    def best_fit_time(self, TIME):  # =========================================

        # ----- Data Start -----

        date0 = xldate_as_tuple(TIME[0], 0)
        date0 = (date0[0], date0[1], 1)

        self.TIMEmin = xldate_from_date_tuple(date0, 0)

        # ----- Date End -----

        date1 = xldate_as_tuple(TIME[-1], 0)

        year = date1[0]
        month = date1[1] + 1
        if month > 12:
            month = 1
            year += 1

        date1 = (year, month, 1)

        self.TIMEmax = xldate_from_date_tuple(date1, 0)

        return date0, date1

    def resample_bin(self):  # ================================================

        # day; week; month; year
        self.bwidth = [1, 7, 30, 365][self.bwidth_indx]
        bwidth = self.bwidth

        if self.bwidth_indx == 0:  # daily

            self.bTIME = np.copy(self.TIMEmeteo)
            self.bTMAX = np.copy(self.TMAX)
            self.bPTOT = np.copy(self.PTOT)
            self.bRAIN = np.copy(self.RAIN)
        else:
            self.bTIME = self.bin_sum(self.TIMEmeteo, bwidth) / bwidth
            self.bTMAX = self.bin_sum(self.TMAX, bwidth) / bwidth
            self.bPTOT = self.bin_sum(self.PTOT, bwidth)
            self.bRAIN = self.bin_sum(self.RAIN, bwidth)

#        elif self.bwidth_indx == 4 : # monthly
#            print('option not yet available, kept default of 1 day')
#
#        elif self.bwidth_indx == 5 : # yearly
#            print('option not yet available, kept default of 1 day')

    def bin_sum(self, x, bwidth):  # ==========================================
        """
        Sum data x over bins of width "bwidth" starting at indice 0 of x.
        If there is residual data at the end because of the last bin being not
        complete, data are rejected and removed from the reshaped series.
        """

        bwidth = int(bwidth)
        nbin = int(np.floor(len(x) / bwidth))

        bheight = x[:nbin*bwidth].reshape(nbin, bwidth)
        bheight = np.sum(bheight, axis=1)

        return bheight

    # ---- Drawing data methods

    def draw_glue_wl(self):
        """Draw the GLUE estimated water levels envelope."""
        if self.glue_wl_on is False:
            self.glue_plt.set_visible(False)
            return
        else:
            self.glue_plt.set_visible(True)

        xlstime = self.gluedf['water levels']['time']
        wl05 = self.gluedf['water levels']['predicted'][:, 0]/1000
        wl95 = self.gluedf['water levels']['predicted'][:, 2]/1000

        self.glue_plt.remove()
        self.glue_plt = self.ax2.fill_between(
            xlstime, wl95, wl05, facecolor='0.85', lw=1, edgecolor='0.65',
            zorder=0)

    def draw_mrc_wl(self):
        """Draw the water levels predicted with the MRC."""
        if self.mrc_wl_on is False:
            self._mrc_plt.set_visible(False)
        else:
            self._mrc_plt.set_visible(True)
            self._mrc_plt.set_data(
                self.wldset['mrc/time'], self.wldset['mrc/recess'])

    def draw_waterlvl(self):
        """
        This method is called the first time the graph is plotted and each
        time water level datum is changed.
        """

        # ---- Logger Measures

        time = self.wldset.xldates
        if self.WLdatum == 1:  # masl
            water_lvl = self.wldset['Elevation']-self.wldset['WL']
        else:  # mbgs -> yaxis is inverted
            water_lvl = self.wldset['WL']

        if self.trend_line == 1:
            tfilt, wlfilt = filt_data(time, water_lvl, self.trend_MAW)
            self.l1_ax2.set_data(tfilt, wlfilt)
            self.l2_ax2.set_data(time, water_lvl)

        else:
            self.l1_ax2.set_data(time, water_lvl)
            self.l2_ax2.set_data([], [])

        # ---- Manual Measures

        time_wl_meas, wl_meas = self.wldset.get_wlmeas()
        if len(wl_meas) > 0:
            if self.WLdatum == 1:
                # The datum is meter above see level.
                wl_meas = self.wldset['Elevation'] - wl_meas
            self.h_WLmes.set_data(time_wl_meas, wl_meas)

    def draw_weather(self):
        """
        This method is called the first time the graph is plotted and each
        time the time scale is changed.
        """
        if self.meteo_on is False:
            return

        # --------------------------------------------------- SUBSAMPLE DATA --

        # For performance purposes, only the data that fit within the limits
        # of the x axis limits are plotted.

        istart = np.where(self.bTIME > self.TIMEmin)[0]
        if len(istart) == 0:
            istart = 0
        else:
            istart = istart[0]
            if istart > 0:
                istart += -1

        iend = np.where(self.bTIME < self.TIMEmax)[0]
        if len(iend) == 0:
            iend = 0
        else:
            iend = iend[-1]
            if iend < len(self.bTIME):
                iend += 1

        time = self.bTIME[istart:iend]
        Tmax = self.bTMAX[istart:iend]
        Ptot = self.bPTOT[istart:iend]
        Rain = self.bRAIN[istart:iend]

        # ------------------------------------------------------ PLOT PRECIP --

        TIME2X = np.zeros(len(time) * 4)
        Ptot2X = np.zeros(len(time) * 4)
        Rain2X = np.zeros(len(time) * 4)

        n = self.bwidth / 2.
        f = 0.85  # Space between individual bar.

        TIME2X[0::4] = time - n * f
        TIME2X[1::4] = time - n * f
        TIME2X[2::4] = time + n * f
        TIME2X[3::4] = time + n * f

        Ptot2X[0::4] = 0
        Ptot2X[1::4] = Ptot
        Ptot2X[2::4] = Ptot
        Ptot2X[3::4] = 0

        Rain2X[0::4] = 0
        Rain2X[1::4] = Rain
        Rain2X[2::4] = Rain
        Rain2X[3::4] = 0

        self.PTOT_bar.remove()
        self.RAIN_bar.remove()

        self.PTOT_bar = self.ax3.fill_between(TIME2X, 0., Ptot2X,
                                              color=self.colorsDB.rgb['Snow'],
                                              linewidth=0.0)

        self.RAIN_bar = self.ax3.fill_between(TIME2X, 0., Rain2X,
                                              color=self.colorsDB.rgb['Rain'],
                                              linewidth=0.0)

        self.baseline.set_data([self.TIMEmin, self.TIMEmax], [0, 0])

        # ---------------------------------------------------- PLOT AIR TEMP --

        TIME2X = np.zeros(len(time)*2)
        Tmax2X = np.zeros(len(time)*2)

        n = self.bwidth / 2.
        TIME2X[0:2*len(time)-1:2] = time - n
        TIME2X[1:2*len(time):2] = time + n
        Tmax2X[0:2*len(time)-1:2] = Tmax
        Tmax2X[1:2*len(time):2] = Tmax

        self.l1_ax4.remove()
        self.l1_ax4 = self.ax4.fill_between(TIME2X, 0., Tmax2X,
                                            color=self.colorsDB.rgb['Tair'],
                                            edgecolor='None')

        self.l2_ax4.set_xdata(TIME2X)
        self.l2_ax4.set_ydata(Tmax2X)

        self.update_precip_scale()

    def set_time_scale(self):
        """Setup the time scale of the x-axis."""
        if self.datemode.lower() == 'year':
            year = xldate_as_tuple(self.TIMEmin, 0)[0]
            self.TIMEmin = xldate_from_date_tuple((year, 1, 1), 0)

            last_month = xldate_as_tuple(self.TIMEmax, 0)[1] == 1
            last_day = xldate_as_tuple(self.TIMEmax, 0)[2] == 1

            if last_month and last_day:
                pass
            else:
                year = xldate_as_tuple(self.TIMEmax, 0)[0] + 1
                self.TIMEmax = xldate_from_date_tuple((year, 1, 1), 0)

        self.setup_xticklabels()
        self.ax1.axis([self.TIMEmin, self.TIMEmax, 0, self.NZGrid])

    def setup_xticklabels(self):
        """Setup the xtick labels."""

        # Labels are placed manually because this is around 25% faster than
        # using the minor ticks.

        xticks_info = self.make_xticks_info()
        self.ax1.set_xticks(xticks_info[0])

        for i in range(len(self.xlabels)):
            self.xlabels[i].remove()

        padding = ScaledTranslation(0, -5/72, self.dpi_scale_trans)

        self.xlabels = []
        for i in range(len(xticks_info[1])):
            new_label = self.ax1.text(
                xticks_info[1][i], 0, xticks_info[2][i], rotation=45,
                va='top', ha='right', fontsize=10,
                transform=self.ax1.transData + padding)
            self.xlabels.append(new_label)

    def draw_figure_title(self):
        """Draw the title of the figure."""
        labelDB = LabelDatabase(self.language)
        if self.isGraphTitle:
            # Set the text and position of the title.
            if self.meteo_on:
                offset = ScaledTranslation(0, 7/72, self.dpi_scale_trans)
                self.text1.set_text(
                    labelDB.station_meteo % (self.name_meteo, self.dist))
                self.text1.set_transform(self.ax0.transAxes + offset)

            dy = 30 if self.meteo_on else 7
            offset = ScaledTranslation(0, dy/72, self.dpi_scale_trans)
            self.figTitle.set_text(labelDB.title % self.wldset['Well'])
            self.figTitle.set_transform(self.ax0.transAxes + offset)

        # Set whether the title is visible or not.
        self.text1.set_visible(self.meteo_on and self.isGraphTitle)
        self.figTitle.set_visible(self.isGraphTitle)

    def setup_waterlvl_scale(self):
        """Update the y scale of the water levels."""
        NZGrid = self.NZGrid if self.meteo_on else self.NZGrid - 2

        self.axLow.set_yticks(np.arange(1, self.NZGrid))
        self.axLow.axis(ymin=0, ymax=NZGrid)
        self.axLow.yaxis.set_ticklabels([])

        if self.WLdatum == 1:   # masl
            WLmin = self.WLmin
            WLscale = self.WLscale
            WLmax = WLmin + (NZGrid * WLscale)

            if self.meteo_on:
                self.ax2.set_yticks(np.arange(WLmin, WLmax - 1.9*WLscale,
                                              WLscale))
            else:
                self.ax2.set_yticks(np.arange(WLmin, WLmax + 0.1*WLscale,
                                              WLscale))

            self.ax2.axis(ymin=WLmin, ymax=WLmax)

        else:  # mbgs: Y axis is inverted
            WLmax = self.WLmin
            WLscale = self.WLscale
            WLmin = WLmax - (NZGrid * WLscale)

            if self.meteo_on:
                self.ax2.set_yticks(np.arange(WLmax, WLmin + 1.9*WLscale,
                                              -WLscale))
            else:
                self.ax2.set_yticks(np.arange(WLmax, WLmin - 0.1*WLscale,
                                              -WLscale))

            self.ax2.axis(ymin=WLmin, ymax=WLmax)
            self.ax2.invert_yaxis()

    def update_precip_scale(self):
        """Update the scale of the axe where precipitation are plotter."""
        if self.meteo_on is False:
            return

        ymax = self.NZGrid * self.RAINscale
        try:
            p = self.PTOT_bar.get_paths()[0]
            v = p.vertices
            y = v[:, 1]

            yticksmax = 0
            while True:
                if yticksmax > max(y):
                    break
                yticksmax += self.RAINscale
            yticksmax = min(ymax, yticksmax) + self.RAINscale/2
        except Exception:
            yticksmax = 3.9 * self.RAINscale

        self.ax3.axis(ymin=0, ymax=ymax)
        self.ax3.set_yticks(np.arange(0, yticksmax, self.RAINscale))
        self.ax3.invert_yaxis()

    def setup_figure_frame(self):
        """Draw a frame around the figure."""
        self.set_frameon(self._figframe_lw > 0)
        self.set_facecolor('white')
        self.set_edgecolor('black')
        self.patch.set_linewidth(self._figframe_lw)

    def set_gridLines(self):

        # 0 -> None, 1 -> "-" 2 -> ":"

        if self.gridLines == 0:
            for ax in self.axes:
                ax._gridOn = False

        elif self.gridLines == 1:
            self.ax4.grid(axis='y', color=[0.35, 0.35, 0.35], linestyle='-',
                          linewidth=0.5, which='minor')
            self.axLow.grid(axis='y', color=[0.35, 0.35, 0.35], linestyle='-',
                            linewidth=0.5)
            self.ax1.grid(axis='x', color=[0.35, 0.35, 0.35], linestyle='-',
                          linewidth=0.5)

        else:
            self.ax4.grid(axis='y', color=[0.35, 0.35, 0.35], linestyle=':',
                          linewidth=0.5, dashes=[0.5, 5], which='minor')
            self.axLow.grid(axis='y', color=[0.35, 0.35, 0.35], linestyle=':',
                            linewidth=0.5, dashes=[0.5, 5])
            self.ax1.grid(axis='x', color=[0.35, 0.35, 0.35], linestyle=':',
                          linewidth=0.5, dashes=[0.5, 5])

    def make_xticks_info(self):

        # ---------------------------------------- horizontal text alignment --

        # The strategy here is to:
        # 1. render some random text ;
        # 2. get the height of its bounding box ;
        # 3. get the horizontal translation of the top-right corner after a
        #    rotation of the bbox of 45 degrees ;
        # 4. sclale the length calculated in step 3 to the height to width
        #    ratio of the axe ;
        # 5. convert the lenght calculated in axes coord. to the data coord.
        #    system ;
        # 6. remove the random text from the figure.

        # Random text bbox height :

        dummytxt = self.ax1.text(0.5, 0.5, 'some_dummy_text', fontsize=10,
                                 ha='right', va='top',
                                 transform=self.ax1.transAxes)

        renderer = self.canvas.get_renderer()

        bbox = dummytxt.get_window_extent(renderer)
        bbox = bbox.transformed(self.ax1.transAxes.inverted())

        # Horiz. trans. of bbox top-right corner :

        dx = bbox.height * np.sin(np.radians(45))

        # Scale dx to axe dimension :

        bbox = self.ax1.get_window_extent(renderer)  # in pixels
        bbox = bbox.transformed(self.dpi_scale_trans.inverted())  # in inches

        sdx = dx * bbox.height / bbox.width
        sdx *= (self.TIMEmax - self.TIMEmin + 1)

        dummytxt.remove()

        # Transform to data coord :

        n = self.date_labels_pattern
        month_names = LabelDatabase(self.language).month_names

        xticks_labels_offset = sdx

        xticks_labels = []
        xticks_position = [self.TIMEmin]
        xticks_labels_position = []

        if self.datemode.lower() == 'month':

            i = 0
            while xticks_position[i] < self.TIMEmax:

                year = xldate_as_tuple(xticks_position[i], 0)[0]
                month = xldate_as_tuple(xticks_position[i], 0)[1]

                month_range = monthrange(year, month)[1]

                xticks_position.append(xticks_position[i] + month_range)

                if i % n == 0:

                    xticks_labels_position.append(xticks_position[i] +
                                                  0.5 * month_range +
                                                  xticks_labels_offset)

                    xticks_labels.append("%s '%s" % (month_names[month - 1],
                                                     str(year)[-2:]))

                i += 1

        elif self.datemode.lower() == 'year':

            i = 0
            year = xldate_as_tuple(xticks_position[i], 0)[0]
            while xticks_position[i] < self.TIMEmax:

                xticks_position.append(
                                     xldate_from_date_tuple((year+1, 1, 1), 0))
                year_range = xticks_position[i+1] - xticks_position[i]

                if i % n == 0:

                    xticks_labels_position.append(xticks_position[i] +
                                                  0.5 * year_range +
                                                  xticks_labels_offset)

                    xticks_labels.append("%d" % year)

                year += 1
                i += 1

        return xticks_position, xticks_labels_position, xticks_labels