diff --git a/src/helpers.py b/src/helpers.py
index e977c9ae0da53c640405aa376a593281b485f46d..be73614319b39dc36043437c64379342a96ce00e 100644
--- a/src/helpers.py
+++ b/src/helpers.py
@@ -5,6 +5,7 @@ __date__ = '2019-10-21'
 
 
 import datetime as dt
+from functools import wraps
 import logging
 import math
 import os
@@ -43,6 +44,16 @@ def l_p_loss(power: int):
     return loss
 
 
+class TimeTrackingWrapper:
+
+    def __init__(self, func):
+        wraps(func)(self)
+
+    def __call__(self, *args, **kwargs):
+        with TimeTracking(name=self.__wrapped__.__name__):
+            return self.__wrapped__(*args, **kwargs)
+
+
 class TimeTracking(object):
     """
     Track time to measure execution time. Time tracking automatically starts on initialisation and ends by calling stop
diff --git a/src/plotting/postprocessing_plotting.py b/src/plotting/postprocessing_plotting.py
index 3338ce4c0d6f14aa8c0173779329a2fd81f0f57e..48606d4f8531812672391304b41885555608473b 100644
--- a/src/plotting/postprocessing_plotting.py
+++ b/src/plotting/postprocessing_plotting.py
@@ -18,13 +18,34 @@ import xarray as xr
 from matplotlib.backends.backend_pdf import PdfPages
 
 from src import helpers
-from src.helpers import TimeTracking
-from src.run_modules.run_environment import RunEnvironment
+from src.helpers import TimeTracking, TimeTrackingWrapper
+from src.data_handling.data_generator import DataGenerator
 
 logging.getLogger('matplotlib').setLevel(logging.WARNING)
 
 
-class PlotMonthlySummary(RunEnvironment):
+class AbstractPlotClass:
+
+    def __init__(self, plot_folder, plot_name, resolution=500):
+        self.plot_folder = plot_folder
+        self.plot_name = plot_name
+        self.resolution = resolution
+
+    def _plot(self, *args):
+        raise NotImplementedError
+    
+    def _save(self):
+        """
+        Standard save method to store plot locally. Name of and path to plot need to be set on initialisation
+        """
+        plot_name = os.path.join(os.path.abspath(self.plot_folder), f"{self.plot_name}.pdf")
+        logging.debug(f"... save plot to {plot_name}")
+        plt.savefig(plot_name, dpi=self.resolution)
+        plt.close('all')
+
+
+@TimeTrackingWrapper
+class PlotMonthlySummary(AbstractPlotClass):
     """
     Show a monthly summary over all stations for each lead time ("ahead") as box and whiskers plot. The plot is saved
     in data_path with name monthly_summary_box_plot.pdf and 500dpi resolution.
@@ -41,12 +62,13 @@ class PlotMonthlySummary(RunEnvironment):
             the maximum lead time from data is used. (default None -> use maximum lead time from data).
         :param plot_folder: path to save the plot (default: current directory)
         """
-        super().__init__()
+        super().__init__(plot_folder, "monthly_summary_box_plot")
         self._data_path = data_path
         self._data_name = name
         self._data = self._prepare_data(stations)
         self._window_lead_time = self._get_window_lead_time(window_lead_time)
-        self._plot(target_var, plot_folder)
+        self._plot(target_var)
+        self._save()
 
     def _prepare_data(self, stations: List) -> xr.DataArray:
         """
@@ -90,12 +112,11 @@ class PlotMonthlySummary(RunEnvironment):
             window_lead_time = ahead_steps
         return min(ahead_steps, window_lead_time)
 
-    def _plot(self, target_var: str, plot_folder: str):
+    def _plot(self, target_var: str):
         """
         Main plot function that creates a monthly grouped box plot over all stations but with separate boxes for each
         lead time step.
         :param target_var: display name of the target variable on plot's axis
-        :param plot_folder: path to save the plot
         """
         data = self._data.to_dataset(name='values').to_dask_dataframe()
         logging.debug("... start plotting")
@@ -105,21 +126,10 @@ class PlotMonthlySummary(RunEnvironment):
                          meanprops={'markersize': 1, 'markeredgecolor': 'k'})
         ax.set(xlabel='month', ylabel=f'{target_var}')
         plt.tight_layout()
-        self._save(plot_folder)
 
-    @staticmethod
-    def _save(plot_folder):
-        """
-        Standard save method to store plot locally. The name of this plot is static.
-        :param plot_folder: path to save the plot
-        """
-        plot_name = os.path.join(os.path.abspath(plot_folder), 'monthly_summary_box_plot.pdf')
-        logging.debug(f"... save plot to {plot_name}")
-        plt.savefig(plot_name, dpi=500)
-        plt.close('all')
 
-
-class PlotStationMap(RunEnvironment):
+@TimeTrackingWrapper
+class PlotStationMap(AbstractPlotClass):
     """
     Plot geographical overview of all used stations as squares. Different data sets can be colorised by its key in the
     input dictionary generators. The key represents the color to plot on the map. Currently, there is only a white
@@ -133,9 +143,10 @@ class PlotStationMap(RunEnvironment):
         as value.
         :param plot_folder: path to save the plot (default: current directory)
         """
-        super().__init__()
+        super().__init__(plot_folder, "station_map")
         self._ax = None
-        self._plot(generators, plot_folder)
+        self._plot(generators)
+        self._save()
 
     def _draw_background(self):
         """
@@ -163,32 +174,20 @@ class PlotStationMap(RunEnvironment):
                         station_coords.loc['station_lat'].values)
                     self._ax.plot(IDx, IDy, mfc=color, mec='k', marker='s', markersize=6, transform=ccrs.PlateCarree())
 
-    def _plot(self, generators: Dict, plot_folder: str):
+    def _plot(self, generators: Dict):
         """
         Main plot function to create the station map plot. Sets figure and calls all required sub-methods.
         :param generators: dictionary with the plot color of each data set as key and the generator containing all
             stations as value.
-        :param plot_folder: path to save the plot
         """
         fig = plt.figure(figsize=(10, 5))
         self._ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree())
         self._ax.set_extent([0, 20, 42, 58], crs=ccrs.PlateCarree())
         self._draw_background()
         self._plot_stations(generators)
-        self._save(plot_folder)
-
-    @staticmethod
-    def _save(plot_folder):
-        """
-        Standard save method to store plot locally. The name of this plot is static.
-        :param plot_folder: path to save the plot
-        """
-        plot_name = os.path.join(os.path.abspath(plot_folder), 'station_map.pdf')
-        logging.debug(f"... save plot to {plot_name}")
-        plt.savefig(plot_name, dpi=500)
-        plt.close('all')
 
 
+@TimeTrackingWrapper
 def plot_conditional_quantiles(stations: list, plot_folder: str = ".", rolling_window: int = 3, ref_name: str = 'obs',
                                pred_name: str = 'CNN', season: str = "", forecast_path: str = None,
                                plot_name_affix: str = "", units: str = "ppb"):
@@ -207,7 +206,7 @@ def plot_conditional_quantiles(stations: list, plot_folder: str = ".", rolling_w
     :param plot_name_affix: name to specify this plot (e.g. 'cali-ref', default: '')
     :param units: units of the forecasted values (default: ppb)
     """
-    time = TimeTracking()
+    # time = TimeTracking()
     logging.debug(f"started plot_conditional_quantiles()")
     # ignore warnings if nans appear in quantile grouping
     warnings.filterwarnings("ignore", message="All-NaN slice encountered")
@@ -321,10 +320,11 @@ def plot_conditional_quantiles(stations: list, plot_folder: str = ".", rolling_w
     # close all open figures / plots
     pdf_pages.close()
     plt.close('all')
-    logging.info(f"plot_conditional_quantiles() finished after {time}")
+    #logging.info(f"plot_conditional_quantiles() finished after {time}")
 
 
-class PlotClimatologicalSkillScore(RunEnvironment):
+@TimeTrackingWrapper
+class PlotClimatologicalSkillScore(AbstractPlotClass):
     """
     Create plot of climatological skill score after Murphy (1988) as box plot over all stations. A forecast time step
     (called "ahead") is separately shown to highlight the differences for each prediction time step. Either each single
@@ -342,10 +342,11 @@ class PlotClimatologicalSkillScore(RunEnvironment):
         :param extra_name_tag: additional tag that can be included in the plot name (default "")
         :param model_setup: architecture type to specify plot name (default "CNN")
         """
-        super().__init__()
+        super().__init__(plot_folder, f"skill_score_clim_{extra_name_tag}{model_setup}")
         self._labels = None
         self._data = self._prepare_data(data, score_only)
-        self._plot(plot_folder, score_only, extra_name_tag, model_setup)
+        self._plot(score_only)
+        self._save()
 
     def _prepare_data(self, data: Dict, score_only: bool) -> pd.DataFrame:
         """
@@ -369,13 +370,10 @@ class PlotClimatologicalSkillScore(RunEnvironment):
         """
         return "" if score_only else "terms and "
 
-    def _plot(self, plot_folder, score_only, extra_name_tag, model_setup):
+    def _plot(self, score_only):
         """
         Main plot function to plot climatological skill score.
-        :param plot_folder: path to save the plot
         :param score_only: if true plot only scores of CASE I to IV, otherwise plot all single terms
-        :param extra_name_tag: additional tag that can be included in the plot name
-        :param model_setup: architecture type to specify plot name
         """
         fig, ax = plt.subplots()
         if not score_only:
@@ -387,24 +385,10 @@ class PlotClimatologicalSkillScore(RunEnvironment):
         handles, _ = ax.get_legend_handles_labels()
         ax.legend(handles, self._labels)
         plt.tight_layout()
-        self._save(plot_folder, extra_name_tag, model_setup)
 
-    @staticmethod
-    def _save(plot_folder, extra_name_tag, model_setup):
-        """
-        Standard save method to store plot locally. The name of this plot is dynamic. It includes the model setup like
-        'CNN' and can additionally be adjusted using an extra name tag.
-        :param plot_folder: path to save the plot
-        :param extra_name_tag: additional tag that can be included in the plot name
-        :param model_setup: architecture type to specify plot name
-        """
-        plot_name = os.path.join(plot_folder, f"skill_score_clim_{extra_name_tag}{model_setup}.pdf")
-        logging.debug(f"... save plot to {plot_name}")
-        plt.savefig(plot_name, dpi=500)
-        plt.close('all')
 
-
-class PlotCompetitiveSkillScore(RunEnvironment):
+@TimeTrackingWrapper
+class PlotCompetitiveSkillScore(AbstractPlotClass):
     """
     Create competitive skill score for the given model setup and the reference models ordinary least squared ("ols") and
     the persistence forecast ("persi") for all lead times ("ahead"). The plot is saved under plot_folder with the name
@@ -417,10 +401,11 @@ class PlotCompetitiveSkillScore(RunEnvironment):
         :param plot_folder: path to save the plot (default: current directory)
         :param model_setup: architecture type (default "CNN")
         """
-        super().__init__()
+        super().__init__(plot_folder, f"skill_score_competitive_{model_setup}")
         self._labels = None
         self._data = self._prepare_data(data)
-        self._plot(plot_folder, model_setup)
+        self._plot()
+        self._save()
 
     def _prepare_data(self, data: pd.DataFrame) -> pd.DataFrame:
         """
@@ -437,12 +422,9 @@ class PlotCompetitiveSkillScore(RunEnvironment):
         self._labels = [str(i) + "d" for i in data.index.levels[1].values]
         return data.stack(level=0).reset_index(level=2, drop=True).reset_index(name="data")
 
-    def _plot(self, plot_folder, model_setup):
+    def _plot(self):
         """
         Main plot function to plot skill scores of the comparisons cnn-persi, ols-persi and cnn-ols.
-        :param plot_folder: path to save the plot
-        :param model_setup:
-        :return: architecture type to specify plot name
         """
         fig, ax = plt.subplots()
         sns.boxplot(x="comparison", y="data", hue="ahead", data=self._data, whis=1., ax=ax, palette="Blues_d",
@@ -454,7 +436,6 @@ class PlotCompetitiveSkillScore(RunEnvironment):
         handles, _ = ax.get_legend_handles_labels()
         ax.legend(handles, self._labels)
         plt.tight_layout()
-        self._save(plot_folder, model_setup)
 
     def _ylim(self) -> Tuple[float, float]:
         """
@@ -466,20 +447,9 @@ class PlotCompetitiveSkillScore(RunEnvironment):
         upper = helpers.float_round(self._data.max()[2], 2) + 0.1
         return lower, upper
 
-    @staticmethod
-    def _save(plot_folder, model_setup):
-        """
-        Standard save method to store plot locally. The name of this plot is dynamic by including the model setup.
-        :param plot_folder: path to save the plot
-        :param model_setup: architecture type to specify plot name
-        """
-        plot_name = os.path.join(plot_folder, f"skill_score_competitive_{model_setup}.pdf")
-        logging.debug(f"... save plot to {plot_name}")
-        plt.savefig(plot_name, dpi=500)
-        plt.close()
-
 
-class PlotBootstrapSkillScore(RunEnvironment):
+@TimeTrackingWrapper
+class PlotBootstrapSkillScore(AbstractPlotClass):
     """
     Create plot of climatological skill score after Murphy (1988) as box plot over all stations. A forecast time step
     (called "ahead") is separately shown to highlight the differences for each prediction time step. Either each single
@@ -495,11 +465,12 @@ class PlotBootstrapSkillScore(RunEnvironment):
         :param plot_folder: path to save the plot (default: current directory)
         :param model_setup: architecture type to specify plot name (default "CNN")
         """
-        super().__init__()
+        super().__init__(plot_folder, f"skill_score_bootstrap_{model_setup}")
         self._labels = None
         self._x_name = "boot_var"
         self._data = self._prepare_data(data)
-        self._plot(plot_folder, model_setup)
+        self._plot()
+        self._save()
 
     def _prepare_data(self, data: Dict) -> pd.DataFrame:
         """
@@ -520,11 +491,9 @@ class PlotBootstrapSkillScore(RunEnvironment):
         """
         return "" if score_only else "terms and "
 
-    def _plot(self, plot_folder,  model_setup):
+    def _plot(self):
         """
         Main plot function to plot climatological skill score.
-        :param plot_folder: path to save the plot
-        :param model_setup: architecture type to specify plot name
         """
         fig, ax = plt.subplots()
         sns.boxplot(x=self._x_name, y="data", hue="ahead", data=self._data, ax=ax, whis=1., palette="Blues_d",
@@ -534,27 +503,13 @@ class PlotBootstrapSkillScore(RunEnvironment):
         handles, _ = ax.get_legend_handles_labels()
         ax.legend(handles, self._labels)
         plt.tight_layout()
-        self._save(plot_folder, model_setup)
-
-    @staticmethod
-    def _save(plot_folder, model_setup):
-        """
-        Standard save method to store plot locally. The name of this plot is dynamic. It includes the model setup like
-        'CNN' and can additionally be adjusted using an extra name tag.
-        :param plot_folder: path to save the plot
-        :param model_setup: architecture type to specify plot name
-        """
-        plot_name = os.path.join(plot_folder, f"skill_score_bootstrap_{model_setup}.pdf")
-        logging.debug(f"... save plot to {plot_name}")
-        plt.savefig(plot_name, dpi=500)
-        plt.close('all')
 
 
-class PlotTimeSeries(RunEnvironment):
+@TimeTrackingWrapper
+class PlotTimeSeries:
 
     def __init__(self, stations: List, data_path: str, name: str, window_lead_time: int = None, plot_folder: str = ".",
                  sampling="daily"):
-        super().__init__()
         self._data_path = data_path
         self._data_name = name
         self._stations = stations
@@ -666,3 +621,64 @@ class PlotTimeSeries(RunEnvironment):
         plot_name = os.path.join(os.path.abspath(plot_folder), 'timeseries_plot.pdf')
         logging.debug(f"... save plot to {plot_name}")
         return matplotlib.backends.backend_pdf.PdfPages(plot_name)
+
+
+@TimeTrackingWrapper
+class PlotAvailability(AbstractPlotClass):
+
+    def __init__(self, generators: Dict[str, DataGenerator], plot_folder: str = ".", sampling="daily"):
+        super().__init__(plot_folder, "data_availability")
+        self.sampling = self._get_sampling(sampling)
+        plot_dict = self._prepare_data(generators)
+        self._plot(plot_dict)
+        self._save()
+
+    @staticmethod
+    def _get_sampling(sampling):
+        if sampling == "daily":
+            return "D"
+        elif sampling == "hourly":
+            return "h"
+
+    def _prepare_data(self, generators: Dict[str, DataGenerator]):
+        plt_dict = {}
+        for subset, generator in generators.items():
+            stations = generator.stations
+            for station in stations:
+                station_data = generator.get_data_generator(station)
+                labels = station_data.get_transposed_label().resample(datetime=self.sampling, skipna=True).mean()
+                labels_bool = labels.sel(window=1).notnull()
+                group = (labels_bool != labels_bool.shift(datetime=1)).cumsum()
+                plot_data = pd.DataFrame({"avail": labels_bool.values, "group": group.values}, index=labels.datetime.values)
+                t = plot_data.groupby("group").apply(lambda x: (x["avail"].head(1)[0], x.index[0], x.shape[0]))
+                t2 = [i[1:] for i in t if i[0]]
+
+                if plt_dict.get(station) is None:
+                    plt_dict[station] = {subset: t2}
+                else:
+                    plt_dict[station].update({subset: t2})
+        return plt_dict
+
+    def _plot(self, plt_dict):
+        # colors = {"train": "orange", "val": "skyblue", "test": "blueishgreen"}
+        colors = {"train": "#e69f00", "val": "#56b4e9", "test": "#009e73"}
+        # colors = {"train": (230, 159, 0), "val": (86, 180, 233), "test": (0, 158, 115)}
+        pos = 0
+        count = 0
+        height = 0.8  # should be <= 1
+        yticklabels = []
+        number_of_stations = len(plt_dict.keys())
+        fig, ax = plt.subplots(figsize=(10, number_of_stations/3))
+        for station, d in sorted(plt_dict.items(), reverse=True):
+            pos += 1
+            for subset, color in colors.items():
+                plt_data = d.get(subset)
+                if plt_data is None:
+                    continue
+                ax.broken_barh(plt_data, (pos, height), color=color, edgecolor="white")
+            yticklabels.append(station)
+
+        ax.set_ylim([height, number_of_stations + 1])
+        ax.set_yticks(np.arange(len(plt_dict.keys()))+1+height/2)
+        ax.set_yticklabels(yticklabels)
+        plt.tight_layout()
diff --git a/src/run_modules/post_processing.py b/src/run_modules/post_processing.py
index 158b29c6e25c8d1181872d700cb2a36114fabf6a..1361dab63e93ea813c3b92394822fb683c7621c1 100644
--- a/src/run_modules/post_processing.py
+++ b/src/run_modules/post_processing.py
@@ -20,7 +20,7 @@ from src.helpers import TimeTracking
 from src.model_modules.linear_model import OrdinaryLeastSquaredModel
 from src.model_modules.model_class import AbstractModelClass
 from src.plotting.postprocessing_plotting import PlotMonthlySummary, PlotStationMap, PlotClimatologicalSkillScore, \
-    PlotCompetitiveSkillScore, PlotTimeSeries, PlotBootstrapSkillScore
+    PlotCompetitiveSkillScore, PlotTimeSeries, PlotBootstrapSkillScore, PlotAvailability
 from src.plotting.postprocessing_plotting import plot_conditional_quantiles
 from src.run_modules.run_environment import RunEnvironment
 
@@ -37,6 +37,7 @@ class PostProcessing(RunEnvironment):
         self.test_data: DataGenerator = self.data_store.get("generator", "test")
         self.test_data_distributed = Distributor(self.test_data, self.model, self.batch_size)
         self.train_data: DataGenerator = self.data_store.get("generator", "train")
+        self.val_data: DataGenerator = self.data_store.get("generator", "val")
         self.train_val_data: DataGenerator = self.data_store.get("generator", "train_val")
         self.plot_path: str = self.data_store.get("plot_path")
         self.target_var = self.data_store.get("target_var")
@@ -213,6 +214,8 @@ class PostProcessing(RunEnvironment):
         if "PlotTimeSeries" in plot_list:
             PlotTimeSeries(self.test_data.stations, path, r"forecasts_%s_test.nc", plot_folder=self.plot_path,
                            sampling=self._sampling)
+        avail_data = {"train": self.train_data, "val": self.val_data, "test": self.test_data}
+        PlotAvailability(avail_data, plot_folder=self.plot_path)
 
     def calculate_test_score(self):
         test_score = self.model.evaluate_generator(generator=self.test_data_distributed.distribute_on_batches(),