diff --git a/app/routes.py b/app/routes.py
index c51255e109469f7b73207a6a3140b5a42db8d399..f4d4a59ab8b3eeb522f9337bcad0901c27026e5a 100644
--- a/app/routes.py
+++ b/app/routes.py
@@ -17,6 +17,7 @@ AHRS_PLOTS = [['yaw_calib'], ['pitch_calib'], ['roll_calib']]
 TRIGGER_PLOTS = [['trigger_rates'], ['trigger_rates_lin']]
 K40_PLOTS = [['intradom'], ['angular_k40rate_distribution']]
 RTTC_PLOTS = [['rttc']]
+RECO_PLOTS = [['track_reco']]
 COMPACT_PLOTS = [['dom_activity', 'dom_rates', 'pmt_rates', 'pmt_hrv'],
                  ['trigger_rates', 'trigger_rates_lin'],
                  ['ztplot', 'triggermap']]
@@ -83,6 +84,12 @@ def ahrs():
     return render_template('plots.html', plots=AHRS_PLOTS)
 
 
+@app.route('/reco.html')
+@requires_auth
+def reco():
+    return render_template('plots.html', plots=RECO_PLOTS)
+
+
 @app.route('/compact.html')
 @requires_auth
 def compact():
diff --git a/app/templates/base.html b/app/templates/base.html
index 9bec529909aedf3059c2e4b5825bbadc09c106e0..5d2f444d99234718c6020ddf794e2300c8008c2a 100644
--- a/app/templates/base.html
+++ b/app/templates/base.html
@@ -47,6 +47,9 @@
           <ul class="nav navbar-nav">
             <li class="active"><a href="k40.html">K40</a></li>
           </ul>
+          <ul class="nav navbar-nav">
+            <li class="active"><a href="reco.html">Reco</a></li>
+          </ul>
           <ul class="nav navbar-nav">
             <li class="active"><a href="compact.html">CMPCT</a></li>
           </ul>
diff --git a/scripts/online_reco.py b/scripts/online_reco.py
new file mode 100644
index 0000000000000000000000000000000000000000..1b1d89e4d523ff8afe398be28ef2eff35431b3bb
--- /dev/null
+++ b/scripts/online_reco.py
@@ -0,0 +1,95 @@
+#!/usr/bin/env python
+# coding=utf-8
+# Filename: online_reco.py
+# Author: Tamas Gal <tgal@km3net.de>
+# vim: ts=4 sw=4 et
+"""
+Visualisation routines for online reconstruction.
+
+Usage:
+    online_reco.py [options]
+    online_reco.py (-h | --help)
+
+Options:
+    -l LIGIER_IP    The IP of the ligier [default: 127.0.0.1].
+    -p LIGIER_PORT  The port of the ligier [default: 5553].
+    -o PLOT_DIR     The directory to save the plot [default: www/plots].
+    -h --help       Show this screen.
+
+"""
+from collections import deque
+import time
+import os
+import threading
+import numpy as np
+import matplotlib
+matplotlib.use("Agg")
+import matplotlib.pyplot as plt
+import km3pipe as kp
+import km3pipe.style
+
+km3pipe.style.use('km3pipe')
+
+
+class ZenithDistribution(kp.Module):
+    def configure(self):
+        self.plots_path = self.require('plots_path')
+        self.max_events = self.get('max_events', default=5000)
+        self.zeniths = deque(maxlen=self.max_events)
+        self.interval = 60
+        threading.Thread(target=self.plot).start()
+
+    def process(self, blob):
+        track = blob['RecoTrack']
+        zenith = np.cos(
+            kp.math.angle_between([0, 0, -1], [track.dx, track.dy, track.dz]))
+        self.zeniths.append(zenith)
+        return blob
+
+    def plot(self):
+        while True:
+            time.sleep(self.interval)
+            self.create_plot()
+
+    def create_plot(self):
+        n = len(self.zeniths)
+        n_ok = n - np.count_nonzero(np.isnan(self.zeniths))
+        fontsize = 16
+
+        fig, ax = plt.subplots(figsize=(16, 8))
+        ax.hist(self.zeniths, bins=180)
+        ax.set_title(
+            r"Zenith distribution of JGandalf track reconstructions"
+            "\nbased on %d reconstructed tracks out of %d events" % (n_ok, n))
+        ax.set_xlabel(r"cos(zenith)", fontsize=fontsize)
+        ax.set_ylabel("count", fontsize=fontsize)
+        ax.tick_params(labelsize=fontsize)
+        ax.set_yscale("log")
+        filename = os.path.join(self.plots_path, 'track_reco.png')
+        plt.savefig(filename, dpi=120, bbox_inches="tight")
+        plt.close('all')
+
+
+def main():
+    from docopt import docopt
+    args = docopt(__doc__)
+
+    plots_path = args['-o']
+    ligier_ip = args['-l']
+    ligier_port = int(args['-p'])
+
+    pipe = kp.Pipeline()
+    pipe.attach(
+        kp.io.ch.CHPump,
+        host=ligier_ip,
+        port=ligier_port,
+        tags='IO_OLINE',
+        timeout=60 * 60 * 24 * 7,
+        max_queue=2000)
+    pipe.attach(kp.io.daq.DAQProcessor)
+    pipe.attach(ZenithDistribution, plots_path=plots_path)
+    pipe.drain()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/scripts/ztplot.py b/scripts/ztplot.py
index 7b2936a44518b46c1e00dad9276e5da158ad10e2..e7ec5c530523f42578293c61f9bded41cab7bb43 100755
--- a/scripts/ztplot.py
+++ b/scripts/ztplot.py
@@ -64,6 +64,7 @@ class ZTPlot(Module):
         self.det_id = self.require('det_id')
         self.t0set = None
         self.calib = None
+        self.max_z = None
 
         self.sds = kp.db.StreamDS()
 
@@ -81,6 +82,7 @@ class ZTPlot(Module):
         self.print("Updating calibration")
         self.t0set = self.sds.t0sets(detid=self.det_id).iloc[-1]['CALIBSETID']
         self.calib = kp.calib.Calibration(det_id=self.det_id, t0set=self.t0set)
+        self.max_z = round(np.max(self.calib.detector.pmts.pos_z) + 10, -1)
 
     def process(self, blob):
         if 'Hits' not in blob:
@@ -118,8 +120,12 @@ class ZTPlot(Module):
     def create_plot(self, event_info, hits):
         print(self.__class__.__name__ + ": updating plot.")
         dus = set(hits.du)
+        doms = set(hits.dom_id)
         fontsize = 16
 
+        time_offset = np.min(hits[hits.triggered == True].time)
+        hits.time -= time_offset
+
         n_plots = len(dus)
         n_cols = int(np.ceil(np.sqrt(n_plots)))
         n_rows = int(n_plots / n_cols) + (n_plots % n_cols > 0)
@@ -132,7 +138,13 @@ class ZTPlot(Module):
 
         axes = [axes] if n_plots == 1 else axes.flatten()
 
+        dom_zs = self.calib.detector.pmts.pos_z[
+            (self.calib.detector.pmts.du == min(dus))
+            & (self.calib.detector.pmts.channel_id == 0)]
+
         for ax, du in zip(axes, dus):
+            for z in dom_zs:
+                ax.axhline(z, lw=1, color='b', ls='--', alpha=0.15)
             du_hits = hits[hits.du == du]
             trig_hits = du_hits[du_hits.triggered == True]
 
@@ -146,6 +158,7 @@ class ZTPlot(Module):
                 'DU{0}'.format(int(du)), fontsize=fontsize, fontweight='bold')
 
         for idx, ax in enumerate(axes):
+            ax.set_ylim(0, self.max_z)
             ax.tick_params(labelsize=fontsize)
             ax.yaxis.set_major_locator(
                 ticker.MultipleLocator(self.ytick_distance))
@@ -161,10 +174,11 @@ class ZTPlot(Module):
         print
         plt.suptitle(
             "z-t-Plot for DetID-{0} (t0set: {1}), Run {2}, FrameIndex {3}, "
-            "TriggerCounter {4}, Overlays {5}\n{6} UTC".format(
+            "TriggerCounter {4}, Overlays {5}, time offset {6} ns"
+            "\n{7} UTC".format(
                 event_info.det_id[0], self.t0set, event_info.run_id[0],
                 event_info.frame_index[0], event_info.trigger_counter[0],
-                event_info.overlays[0],
+                event_info.overlays[0], time_offset,
                 datetime.utcfromtimestamp(event_info.utc_seconds)),
             fontsize=fontsize,
             y=1.05)
@@ -173,6 +187,8 @@ class ZTPlot(Module):
         f = os.path.join(self.plots_path, filename + '.png')
         f_tmp = os.path.join(self.plots_path, filename + '_tmp.png')
         plt.savefig(f_tmp, dpi=120, bbox_inches="tight")
+        if len(doms) > 4:
+            plt.savefig(os.path.join(self.plots_path, filename + '_5doms.png'))
         plt.close('all')
         shutil.move(f_tmp, f)