diff --git a/Gruntfile.js b/Gruntfile.js
index 2d5f36f076d2cf9d29d6310250626b73304e724d..b19343abd76f1b7cc054cff8eaf7794a6be3c5f1 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -263,6 +263,29 @@ module.exports = function(grunt) {
                         src: './*',
                         dest: '<%= project_paths.web_static_dir %>vendor/bootstrap/js/'
                     },
+                    // ----- D3
+                    {
+                        expand: true,
+                        flatten: true,
+                        cwd: 'node_modules/d3/',
+                        src: './d3*.js',
+                        dest: '<%= project_paths.web_static_dir %>vendor/d3/js/'
+                    },
+                    // ----- NVD3
+                    {
+                        expand: true,
+                        flatten: true,
+                        cwd: 'node_modules/nvd3/build/',
+                        src: './*.js*',
+                        dest: '<%= project_paths.web_static_dir %>vendor/nvd3/js/'
+                    },
+                    {
+                        expand: true,
+                        flatten: true,
+                        cwd: 'node_modules/nvd3/build/',
+                        src: './*.css*',
+                        dest: '<%= project_paths.web_static_dir %>vendor/nvd3/css/'
+                    },
                     // ----- moment
                     {
                         expand: true,
diff --git a/conf/core/statistics.json.conf b/conf/core/statistics.json.conf
new file mode 100644
index 0000000000000000000000000000000000000000..d9fe0201f27f3dcc4976061653e00bbaa7c1d172
--- /dev/null
+++ b/conf/core/statistics.json.conf
@@ -0,0 +1,9 @@
+{
+    #
+    # Definitions of core configurations for event statistics.
+    #
+    "__core__statistics": {
+        "rrds_dir":    "/var/mentat/rrds",
+        "reports_dir": "/var/mentat/reports/statistician"
+    }
+}
diff --git a/lib/hawat/app.py b/lib/hawat/app.py
index 2a949ffe447222f989cbeac39446ac8c32a84a2b..48c5c1a8e06803c91ac067d2a2a52cf6752d8ec9 100644
--- a/lib/hawat/app.py
+++ b/lib/hawat/app.py
@@ -385,7 +385,8 @@ def _setup_app_babel(app):
 
         return dict(
             babel_format_datetime  = flask_babel.format_datetime,
-            babel_format_timedelta = flask_babel.format_timedelta
+            babel_format_timedelta = flask_babel.format_timedelta,
+            babel_format_decimal   = flask_babel.format_decimal
         )
 
     return app
diff --git a/lib/hawat/base.py b/lib/hawat/base.py
index 0605adc0b03179c88e9e91a3b6c31af236dbf5ac..345f07a71ca86837b0fccad1151eed1a96d197b0 100644
--- a/lib/hawat/base.py
+++ b/lib/hawat/base.py
@@ -205,14 +205,18 @@ class HawatBaseView(flask.views.View):
 
 class HawatFileView(HawatBaseView):
     """
-    Base class for simple views. These are the most, well, simple views, that are
-    rendering single template file. In most use cases, it should be enough to just
-    enhance the default implementation :py:func:`hawat.base.HawatBaseView.get_template_context`
-    to inject additional variables into the template.
+    Base class for indirrect file access views. These views can be used to access
+    and serve files from arbitrary filesystem directories (that are accessible to
+    application process). This can be very usefull for serving files like charts,
+    that are periodically generated into configurable and changeable location.
     """
 
     @staticmethod
     def get_directory_path():
+        """
+        *Hook method*. Must return full path to the directory, that will be used
+        as a base path for serving files.
+        """
         raise NotImplementedError()
 
     def dispatch_request(self, filename):
@@ -281,6 +285,47 @@ class HawatDbmodelView(HawatBaseView):
         return
 
 
+class HawatSearchView(HawatDbmodelView):
+    """
+    Base class for search views.
+    """
+    @staticmethod
+    def get_search_form(args):
+        """
+        *Hook method*. Must return instance of :py:mod:`flask_wtf.FlaskForm`
+        appropriate for given search type.
+        """
+        raise NotImplementedError()
+
+    def search(self, query, model, args, context):
+        """
+        *Hook method*.
+        """
+        raise NotImplementedError()
+
+    def dispatch_request(self):
+        """
+        Mandatory interface required by the :py:func:`flask.views.View.dispatch_request`.
+        Will be called by the *Flask* framework to service the request.
+        """
+        context = self.get_template_context()
+
+        form = self.get_search_form(flask.request.args)
+
+        if hawat.const.HAWAT_FORM_ACTION_SUBMIT in flask.request.args:
+            if form.validate():
+                items = self.search(self.dbquery, self.dbmodel, flask.request.args, context)
+                context.update(
+                    items = items,
+                    items_count = len(items),
+                    args  = flask.request.args
+                )
+                self.do_before_render(items, context)
+
+        context.update(search_form = form)
+        return flask.render_template(self.get_view_template(), **context)
+
+
 class HawatItemListView(HawatDbmodelView):
     """
     Base class for item *list* views. These views provide quick and simple access
diff --git a/lib/hawat/blueprints/dashboards/__init__.py b/lib/hawat/blueprints/dashboards/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..b0761e018c8814859f6c73ae9a1124c14509f6e2
--- /dev/null
+++ b/lib/hawat/blueprints/dashboards/__init__.py
@@ -0,0 +1,121 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#-------------------------------------------------------------------------------
+# This file is part of Mentat system (https://mentat.cesnet.cz/).
+#
+# Copyright (C) since 2011 CESNET, z.s.p.o (http://www.ces.net/)
+# Use of this source is governed by the MIT license, see LICENSE file.
+#-------------------------------------------------------------------------------
+
+
+"""
+Hawat pluggable module: *dashboards*
+
+Description
+^^^^^^^^^^^
+
+This pluggable module provides access to event statistic dashboards.
+
+"""
+
+
+__author__ = "Jan Mach <jan.mach@cesnet.cz>"
+__credits__ = "Pavel Kácha <pavel.kacha@cesnet.cz>, Andrea Kropáčová <andrea.kropacova@cesnet.cz>"
+
+
+import collections
+
+import flask_login
+from flask_babel import lazy_gettext
+
+import mentat.stats.idea
+import hawat.base
+
+from mentat.datatype.sqldb import EventStatisticsModel
+from hawat.blueprints.dashboards.forms import OverallDashboardForm
+
+
+class OverallView(hawat.base.HawatSearchView):
+
+    decorators = [flask_login.login_required]
+
+    @staticmethod
+    def get_view_name():
+        return 'overall'
+
+    @staticmethod
+    def get_view_title():
+        return lazy_gettext('Overall event dashboards')
+
+    @staticmethod
+    def get_view_template():
+        return 'dashboards/overall.html'
+
+    @staticmethod
+    def get_menu_icon():
+        return 'dashboards'
+
+    @staticmethod
+    def get_menu_title():
+        return lazy_gettext('Overall')
+
+    @property
+    def dbmodel(self):
+        """
+        *Hook property*. Implementation of :py:func:`hawat.base.HawatDbmodelView.dbmodel` interface.
+        """
+        return EventStatisticsModel
+
+    @staticmethod
+    def get_search_form(args):
+        return OverallDashboardForm(args, meta = {'csrf': False})
+
+    def search(self, query, model, args, context):
+        if 'dt_from' in args and args['dt_from']:
+            query = query.filter(model.dt_from >= args['dt_from'])
+        if 'dt_to' in args and args['dt_to']:
+            query = query.filter(model.dt_to <= args['dt_to'])
+        return query.order_by(model.interval).all()
+
+    def do_before_render(self, items, context):
+        """
+        *Hook method*. Will be called before rendering the template.
+        """
+        context.update(
+            statistics = mentat.stats.idea.truncate_evaluations(
+                mentat.stats.idea.aggregate_stat_groups(items)
+            )
+        )
+
+
+#-------------------------------------------------------------------------------
+
+
+class DashboardsBlueprint(hawat.base.HawatBlueprint):
+
+    @staticmethod
+    def get_module_title():
+        return lazy_gettext('Event statistic dashboards')
+
+    def register_app(self, app):
+        app.menu_main.add_entry('dashboards.overall', content = self.hawat_view_list['overall'], position = 10)
+
+
+#-------------------------------------------------------------------------------
+
+
+def get_blueprint():
+    """
+    Mandatory interface and factory function. This function must return a valid
+    instance of :py:class:`hawat.base.HawatBlueprint` or :py:class:`flask.Blueprint`.
+    """
+
+    bp = DashboardsBlueprint(
+        'dashboards',
+        __name__,
+        template_folder = 'templates',
+        url_prefix = '/dashboards')
+
+    bp.register_view_class(OverallView, '/overall')
+
+    return bp
diff --git a/lib/hawat/blueprints/dashboards/forms.py b/lib/hawat/blueprints/dashboards/forms.py
new file mode 100644
index 0000000000000000000000000000000000000000..2db6ca0011e329a23126a1b237b3605af257a317
--- /dev/null
+++ b/lib/hawat/blueprints/dashboards/forms.py
@@ -0,0 +1,78 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#-------------------------------------------------------------------------------
+# This file is part of Mentat system (https://mentat.cesnet.cz/).
+#
+# Copyright (C) since 2011 CESNET, z.s.p.o (http://www.ces.net/)
+# Use of this source is governed by the MIT license, see LICENSE file.
+#-------------------------------------------------------------------------------
+
+
+"""
+This module contains custom internal geoip search form for Hawat.
+"""
+
+
+__author__ = "Jan Mach <jan.mach@cesnet.cz>"
+__credits__ = "Pavel Kácha <pavel.kacha@cesnet.cz>, Andrea Kropáčová <andrea.kropacova@cesnet.cz>"
+
+
+import time
+import datetime
+import ipranges
+import wtforms
+
+import flask_wtf
+from flask_babel import lazy_gettext, gettext
+
+import hawat.forms
+
+class SimpleOverallDashboardForm(flask_wtf.FlaskForm):
+    """
+    Class representing simple overall dashboard search form.
+    """
+    dt_from = hawat.forms.DateTimeLocalField(
+        lazy_gettext('From:'),
+        validators = [
+            wtforms.validators.Optional()
+        ],
+        format = '%Y-%m-%d'
+    )
+    interval = wtforms.SelectField(
+        lazy_gettext('Interval:'),
+        validators = [
+            wtforms.validators.Optional()
+        ],
+        choices = [
+            ('1day',   lazy_gettext('one day')),
+            ('1week',  lazy_gettext('one week')),
+            ('4weeks', lazy_gettext('four weeks'))
+        ],
+        filters = [lambda x: x or None]
+    )
+    submit = wtforms.SubmitField(
+        lazy_gettext('Calculate')
+    )
+
+class OverallDashboardForm(flask_wtf.FlaskForm):
+    """
+    Class representing overall dashboard search form.
+    """
+    dt_from = hawat.forms.DateTimeLocalField(
+        lazy_gettext('From:'),
+        validators = [
+            wtforms.validators.Optional()
+        ],
+        format = '%Y-%m-%d %H:%M',
+        default = datetime.datetime.fromtimestamp(int(time.time() - (time.time() % 86400)))
+    )
+    dt_to = hawat.forms.DateTimeLocalField(
+        lazy_gettext('To:'),
+        validators = [
+            wtforms.validators.Optional()
+        ],
+        format = '%Y-%m-%d %H:%M'
+    )
+    submit = wtforms.SubmitField(
+        lazy_gettext('Calculate')
+    )
diff --git a/lib/hawat/blueprints/dashboards/templates/dashboards/overall.html b/lib/hawat/blueprints/dashboards/templates/dashboards/overall.html
new file mode 100644
index 0000000000000000000000000000000000000000..d819cd1632bc22da8545feb5605a5e30bcd207b3
--- /dev/null
+++ b/lib/hawat/blueprints/dashboards/templates/dashboards/overall.html
@@ -0,0 +1,209 @@
+{% extends "_layout.html" %}
+
+{% block content %}
+
+            <div class="row">
+                <div class="col-lg-12">
+
+                    <div class="jumbotron" style="margin-top: 1em;">
+                        <h2>{{ hawat_view_title }}</h2>
+                        <hr>
+                        <form method="GET" class="form-inline" action="{{ url_for('dashboards.overall') }}">
+                            {{ macros_site.render_form_item_datetime(search_form.dt_from, 'datetimepicker-hm-from', False) }}
+
+                            {{ macros_site.render_form_item_datetime(search_form.dt_to, 'datetimepicker-hm-to', False) }}
+
+                            {{ search_form.submit(class_='btn btn-primary') }}
+                        </form>
+                        {%- if search_form.dt_from.errors %}
+                        <div>
+                            {{ macros_site.form_errors(search_form.dt_from.errors) }}
+                        </div>
+                        {%- endif %}
+                        {%- if search_form.dt_to.errors %}
+                        <div>
+                            {{ macros_site.form_errors(search_form.dt_to.errors) }}
+                        </div>
+                        {%- endif %}
+                    </div> <!-- /.jumbotron -->
+
+                </div> <!-- /.col-lg-12 -->
+            </div> <!-- /.row -->
+
+    {%- if items %}
+
+            <div class="row">
+                <div class="col-lg-12">
+
+                    <div class="panel panel-default">
+                        <div class="panel-heading">
+                            <h3 class="panel-title">{{ gettext('Result summary') }}</h3>
+                        </div>
+                        <table class="table">
+                            <tr>
+                                <th>{{ gettext('Number of statistical records') }}:</th>
+                                <td>{{ babel_format_decimal(items_count) }}</td>
+                            </tr>
+                            <tr>
+                                <th>{{ gettext('Number of events') }}:</th>
+                                <td>{{ babel_format_decimal(statistics['count']) }}</td>
+                            </tr>
+                            <tr>
+                                <th>{{ gettext('Time interval') }}:</th>
+                                <td>{{ babel_format_datetime(statistics['dt_from']) }} - {{ babel_format_datetime(statistics['dt_to']) }}</td>
+                            </tr>
+                            <tr>
+                                <th>{{ gettext('Time interval delta') }}:</th>
+                                <td>{{ babel_format_timedelta(statistics['dt_to'] - statistics['dt_from']) }}</td>
+                            </tr>
+                        </table>
+                    </div>
+
+                </div> <!-- /.col-lg-12 -->
+            </div> <!-- /.row -->
+
+            <!-- Nav tabs -->
+            <ul class="nav nav-tabs" role="tablist">
+                <li role="presentation" class="active">
+                    <a href="#tab-stats-overall" aria-controls="tab-stats-overall" role="tab" data-toggle="tab">
+                        <strong>{{ gettext('Overall processing statistics') }}</strong>
+                    </a>
+                </li>
+                <li role="presentation">
+                    <a href="#tab-stats-internal" aria-controls="tab-stats-internal" role="tab" data-toggle="tab">
+                        <strong>{{ gettext('Internal processing statistics') }}</strong>
+                    </a>
+                </li>
+                <li role="presentation">
+                    <a href="#tab-stats-external" aria-controls="tab-stats-external" role="tab" data-toggle="tab">
+                        <strong>{{ gettext('External processing statistics') }}</strong>
+                    </a>
+                </li>
+            </ul>
+
+            <!-- Tab panes -->
+            <div class="tab-content">
+
+                <div role="tabpanel" class="tab-pane fade in active" id="tab-stats-overall">
+                    <ul class="nav nav-tabs nav-tabs-tooltipped" role="tablist">
+                        {%- for chsection in ('abuses', 'analyzers', 'asns', 'categories', 'category_sets', 'countries', 'detectors', 'detectorsws', 'ips') %}
+                        <li role="presentation" class="text-center{% if loop.first %} active{% endif %}">
+                            <a role="tab" data-toggle="tab" href="#tab-stats-overall-{{ chsection }}" class="chart-tab">
+                                # {{ chsection }}
+                            </a>
+                        </li>
+                        {%- endfor %}
+                    </ul>
+                    <div class="tab-content">
+                        {%- for chsection in ('abuses', 'analyzers', 'asns', 'categories', 'category_sets', 'countries', 'detectors', 'detectorsws', 'ips') %}
+                        <div role="tabpanel" class="tab-pane fade{% if loop.first %} in active{% endif %}" id="tab-stats-overall-{{ chsection }}">
+                            {{ macros_site.render_chart_pie(
+                                    'stats_overall',
+                                    statistics['stats_overall'],
+                                    chsection,
+                                    'Number of events per ' + chsection,
+                                )
+                            }}
+                        </div>
+                        {%- endfor %}
+                    </div>
+                </div>
+
+                <div role="tabpanel" class="tab-pane fade" id="tab-stats-internal">
+                    <ul class="nav nav-tabs nav-tabs-tooltipped" role="tablist">
+                        {%- for chsection in ('abuses', 'analyzers', 'asns', 'categories', 'category_sets', 'countries', 'detectors', 'detectorsws', 'ips') %}
+                        <li role="presentation" class="text-center{% if loop.first %} active{% endif %}">
+                            <a role="tab" data-toggle="tab" href="#tab-stats-internal-{{ chsection }}" class="chart-tab">
+                                # {{ chsection }}
+                            </a>
+                        </li>
+                        {%- endfor %}
+                    </ul>
+                    <div class="tab-content">
+                        {%- for chsection in ('abuses', 'analyzers', 'asns', 'categories', 'category_sets', 'countries', 'detectors', 'detectorsws', 'ips') %}
+                        <div role="tabpanel" class="tab-pane fade{% if loop.first %} in active{% endif %}" id="tab-stats-internal-{{ chsection }}">
+                            {{ macros_site.render_chart_pie(
+                                    'stats_internal',
+                                    statistics['stats_internal'],
+                                    chsection,
+                                    'Number of events per ' + chsection,
+                                )
+                            }}
+                        </div>
+                        {%- endfor %}
+                    </div>
+                </div>
+
+                <div role="tabpanel" class="tab-pane fade" id="tab-stats-external">
+                    <ul class="nav nav-tabs nav-tabs-tooltipped" role="tablist">
+                        {%- for chsection in ('analyzers', 'asns', 'categories', 'category_sets', 'countries', 'detectors', 'detectorsws', 'ips') %}
+                        <li role="presentation" class="text-center{% if loop.first %} active{% endif %}">
+                            <a role="tab" data-toggle="tab" href="#tab-stats-external-{{ chsection }}" class="chart-tab">
+                                # {{ chsection }}
+                            </a>
+                        </li>
+                        {%- endfor %}
+                    </ul>
+                    <div class="tab-content">
+                        {%- for chsection in ('abuses', 'analyzers', 'asns', 'categories', 'category_sets', 'countries', 'detectors', 'detectorsws', 'ips') %}
+                        <div role="tabpanel" class="tab-pane fade{% if loop.first %} in active{% endif %}" id="tab-stats-external-{{ chsection }}">
+                            {{ macros_site.render_chart_pie(
+                                    'stats_external',
+                                    statistics['stats_external'],
+                                    chsection,
+                                    'Number of events per ' + chsection,
+                                )
+                            }}
+                        </div>
+                        {%- endfor %}
+                    </div>
+                </div>
+
+            </div>
+
+        {%- if permission_can('developer') %}
+            {%- if item %}
+
+            <hr>
+
+{{ macros_site.render_raw_item_view(item) }}
+
+            {%- endif %}
+        {%- endif %}
+
+    {%- else %}
+            <div class="alert alert-info">{{ gettext('No data matches your search criteria.') }}</div>
+    {%- endif %}
+
+{%- endblock content %}
+
+
+{%- block css %}
+{{ super() }}
+        <link rel="stylesheet" href="{{ url_for('design.static', filename='vendor/nvd3/css/nv.d3.min.css') }}">
+{%- endblock css %}
+
+
+{%- block headjs %}
+{{ super() }}
+        <script src="{{ url_for('design.static', filename='vendor/d3/js/d3.min.js') }}"></script>
+        <script src="{{ url_for('design.static', filename='vendor/nvd3/js/nv.d3.min.js') }}"></script>
+{%- endblock headjs %}
+
+
+{%- block js %}
+{{ super() }}
+        <script src="{{ url_for('design.static', filename='vendor/datatables/js/jquery.dataTables.js') }}"></script>
+        <script src="{{ url_for('design.static', filename='vendor/datatables/js/dataTables.bootstrap.js') }}"></script>
+
+        <script>
+            $(document).ready(function() {
+                $('.hawat-datatable-nopaging').DataTable({
+                    "responsive": true,
+                    "pageLength": 50,
+                    "paging": false,
+                    "ordering": true
+                });
+            });
+        </script>
+{%- endblock js %}
diff --git a/lib/hawat/blueprints/design/static/css/hawat.css b/lib/hawat/blueprints/design/static/css/hawat.css
index 6c35acd94ba9bb49f38fe0bb8ec27f0671b25889..2342a7d5b53f10f98625b267843f6ef5a6032d88 100644
--- a/lib/hawat/blueprints/design/static/css/hawat.css
+++ b/lib/hawat/blueprints/design/static/css/hawat.css
@@ -75,3 +75,10 @@ body {
 .rrd-button-group {
     padding-left: 1em !important;
 }
+
+/*
+ * Fix for wrong indentation of search field in datatables.
+ * */
+#DataTables_Table_0_filter {
+    text-align: right;
+}
diff --git a/lib/hawat/blueprints/design/static/js/hawat-common.js b/lib/hawat/blueprints/design/static/js/hawat-common.js
index 80feb22bd9a6b07908c0518b2d924ffa262ab13d..6bb56b3ce89bb5b8b19aaf01f4b84b751a255d02 100644
--- a/lib/hawat/blueprints/design/static/js/hawat-common.js
+++ b/lib/hawat/blueprints/design/static/js/hawat-common.js
@@ -39,6 +39,35 @@ $(function() {
         $('#datetimepicker-from').data("DateTimePicker").maxDate(e.date);
     });
 
+    // Initialize linked date and datetime picker components.
+    $('#datetimepicker-hm-from').datetimepicker({
+        locale: GLOBAL_LOCALE,
+        format: 'YYYY-MM-DD HH:mm',
+        icons: {
+            time: "fa fa-fw fa-clock-o",
+            date: "fa fa-fw fa-calendar",
+            up:   "fa fa-fw fa-arrow-up",
+            down: "fa fa-fw fa-arrow-down"
+        }
+    });
+    $('#datetimepicker-hm-to').datetimepicker({
+        useCurrent: false, //Important! See issue #1075
+        locale: GLOBAL_LOCALE,
+        format: 'YYYY-MM-DD HH:mm',
+        icons: {
+            time: "fa fa-fw fa-clock-o",
+            date: "fa fa-fw fa-calendar",
+            up:   "fa fa-fw fa-arrow-up",
+            down: "fa fa-fw fa-arrow-down"
+        }
+    });
+    $("#datetimepicker-hm-from").on("dp.change", function (e) {
+        $('#datetimepicker-hm-to').data("DateTimePicker").minDate(e.date);
+    });
+    $("#datetimepicker-hm-to").on("dp.change", function (e) {
+        $('#datetimepicker-hm-from').data("DateTimePicker").maxDate(e.date);
+    });
+
     // Initialize select pickers.
     //$('.selectpicker').selectpicker();
 
diff --git a/lib/hawat/blueprints/design/templates/_layout.html b/lib/hawat/blueprints/design/templates/_layout.html
index 15257653914a5fa0749746302b31d7dbdfff7f18..5430088ae0c70b7de2e2ebd4bd8eab85cbad7041 100644
--- a/lib/hawat/blueprints/design/templates/_layout.html
+++ b/lib/hawat/blueprints/design/templates/_layout.html
@@ -20,6 +20,10 @@
         <link rel="stylesheet" href="{{ url_for('design.static', filename='vendor/bootstrap-datetimepicker/css/bootstrap-datetimepicker.min.css') }}">
         <link rel="stylesheet" href="{{ url_for('design.static', filename='css/hawat.css') }}">
     {%- endblock css %}
+    {%- block headjs %}
+
+        <!-- Head JS -->
+    {%- endblock headjs %}
 
         <!-- Favicon -->
         <link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
diff --git a/lib/hawat/blueprints/design/templates/_macros_site.html b/lib/hawat/blueprints/design/templates/_macros_site.html
index d82d22d07a73c006c06066224a9647c4a9589d43..ee21c782b20785cc7eca8b83e6b9d15643051db2 100644
--- a/lib/hawat/blueprints/design/templates/_macros_site.html
+++ b/lib/hawat/blueprints/design/templates/_macros_site.html
@@ -1,4 +1,4 @@
-{#- ----------------------------------------------------------------------------
+;{#- ----------------------------------------------------------------------------
 
     Macro for rendering the main menu of the application.
 
@@ -166,13 +166,15 @@
 
 ----------------------------------------------------------------------------- #}
 
-{%- macro render_form_item_default(form_item) %}
+{%- macro render_form_item_default(form_item, with_errors = True) %}
                                 <div class="form-group{% if form_item.errors %}{{ ' has-error' }}{% endif %}">
                                     {{ form_item.label }}
                                     {{ form_item(class_='form-control') }}
-    {%- for err in form_item.errors %}
+    {%- if with_errors %}
+        {%- for err in form_item.errors %}
                                     <span class="help-block form-error">{{ get_fa_icon('form-error') }} {{ err }}</span>{%- if not loop.last %}<br>{%- endif %}
-    {%- endfor %}
+        {%- endfor %}
+    {%- endif %}
                                 </div>
 {%- endmacro %}
 
@@ -183,13 +185,15 @@
 
 ----------------------------------------------------------------------------- #}
 
-{%- macro render_form_item_select(form_item) %}
+{%- macro render_form_item_select(form_item, with_errors = True) %}
                                 <div class="form-group{% if form_item.errors %}{{ ' has-error' }}{% endif %}">
                                     {{ form_item.label }}
                                     {{ form_item(class_='form-control selectpicker',**{'data-live-search':'true', 'data-size': '10', 'data-selected-text-format': 'count > 3'}) }}
-    {%- for err in form_item.errors %}
+    {%- if with_errors %}
+        {%- for err in form_item.errors %}
                                     <span class="help-block form-error">{{ get_fa_icon('form-error') }} {{ err }}</span>{%- if not loop.last %}<br>{%- endif %}
-    {%- endfor %}
+        {%- endfor %}
+    {%- endif %}
                                 </div>
 {%- endmacro %}
 
@@ -200,7 +204,7 @@
 
 ----------------------------------------------------------------------------- #}
 
-{%- macro render_form_item_datetime(form_item, ident) %}
+{%- macro render_form_item_datetime(form_item, ident, with_errors = True) %}
                                 <div class="form-group{% if form_item.errors %}{{ ' has-error' }}{% endif %}">
                                     {{ form_item.label }}
                                     <div id="{{ ident }}" class="input-group date">
@@ -209,9 +213,11 @@
                                             <i class="fa fa-fw fa-calendar"></i>
                                         </span>
                                     </div>
-    {%- for err in form_item.errors %}
+    {%- if with_errors %}
+        {%- for err in form_item.errors %}
                                     <span class="help-block form-error">{{ get_fa_icon('form-error') }} {{ err }}</span>{%- if not loop.last %}<br>{%- endif %}
-    {%- endfor %}
+        {%- endfor %}
+    {%- endif %}
                                 </div>
 {%- endmacro %}
 
@@ -236,7 +242,7 @@
 
 ----------------------------------------------------------------------------- #}
 
-{%- macro render_form_item_radiobutton(form_item) %}
+{%- macro render_form_item_radiobutton(form_item, with_errors = True) %}
                                 <div class="form-group{% if form_item.errors %}{{ ' has-error' }}{% endif %}">
                                     <div>
                                         {{ form_item.label }}
@@ -247,10 +253,13 @@
                                             {{ subfield }} {{ subfield.label }}
                                         </span>
     {%- endfor %}
+
                                     </div>
-    {%- for err in form_item.errors %}
+    {%- if with_errors %}
+        {%- for err in form_item.errors %}
                                     <span class="help-block form-error">{{ get_fa_icon('form-error') }} {{ err }}</span>{%- if not loop.last %}<br>{%- endif %}
-    {%- endfor %}
+        {%- endfor %}
+    {%- endif %}
                                 </div>
 {%- endmacro %}
 
@@ -297,3 +306,119 @@
                         <pre>{{ var | pprint }}</pre>
                     </div> <!-- /.well -->
 {%- endmacro %}
+
+{#- ----------------------------------------------------------------------------
+
+    Macros for rendering NVD3 charts.
+
+----------------------------------------------------------------------------- #}
+
+{%- macro render_chart_pie(chidprefix, chstats, chstatkey, chtitle) %}
+{%- set chid   = '' + chidprefix + '_' + chstatkey %}
+{%- set chdata = chstats[chstatkey] %}
+{%- set chkeys = chstats['list_' + chstatkey] %}
+            <h4>{{ chtitle }}</h4>
+            <div class="row">
+                <div class="col-md-6">
+
+                    <div id="nvd3_chart_pie_{{ chid }}" class="span4">
+                        <svg style='height:600px;width:600px' class='center-block'></svg>
+                    </div>
+
+                    <script>
+                        nv.addGraph(function() {
+                            var nvd3_data_pie_{{ chid }} = [
+                                {%- for chitemkey in chkeys %}
+                                { label: "{{ chitemkey }}", value: {{ chdata[chitemkey] }} },
+                                {%- endfor %}
+                            ];
+
+                            var chart = nv.models.pieChart()
+                                .x(function(d) { return d.label })
+                                .y(function(d) { return d.value })
+                                .showLabels(true)
+                                .showLegend(true)
+                                .labelThreshold(.05)  //Configure the minimum slice size for labels to show up
+                                .labelType("key")     //Configure what type of data to show in the label. Can be "key", "value" or "percent"
+                                .donut(true)          //Turn on Donut mode. Makes pie chart look tasty!
+                                .donutRatio(0.25)     //Configure how big you want the donut hole size to be.
+                              ;
+
+                            d3.select("#nvd3_chart_pie_{{ chid }} svg")
+                                .datum(nvd3_data_pie_{{ chid }})
+                                .transition().duration(350)
+                                .call(chart);
+
+                            return chart;
+                        });
+                    </script>
+
+                </div>
+                <div class="col-md-6">
+
+                    <table class="table table-bordered table-striped table-condensed hawat-datatable-nopaging">
+                        <thead>
+                            <tr>
+                                <th>&nbsp;</th>
+                                <th>Name</th>
+                                <th>#</th>
+                                <th>%</th>
+                            </tr>
+                         </thead>
+                         <tbody>
+                            {%- for chitemkey in chkeys %}
+                            <tr>
+                                <td style="text-align: right; font-weight: bold; background-color: white;">
+                                    {{ loop.index }}
+                                </td>
+                                <td>
+                                    {{ chitemkey }}
+                                </td>
+                                <td class="col-value">
+                                    {{ babel_format_decimal(chdata[chitemkey]) }}
+                                </td>
+                                <td>
+
+                                </td>
+                            </tr>
+                            {%- endfor %}
+                         </tbody>
+                         <tfoot>
+                            <tr>
+                                <th>&nbsp;</th>
+                                <th>Sum</th>
+                                <th>{{ babel_format_decimal(chstats['sum_' + chstatkey]) }}</th>
+                                <th></th>
+                            </tr>
+                         </tfoot>
+                    </table>
+
+                    <hr>
+
+                    <table class="table table-bordered table-striped table-condensed">
+                        <tr>
+                            <th>{{ get_fa_icon('min') }} Min</th>
+                            <td class="col-value">{{ babel_format_decimal(chstats['min_' + chstatkey]) }}</td>
+                        </tr>
+                        <tr>
+                            <th>{{ get_fa_icon('max') }} Max</th>
+                            <td class="col-value">{{ babel_format_decimal(chstats['max_' + chstatkey]) }}</td>
+                        </tr>
+                        <tr>
+                            <th>{{ get_fa_icon('sum') }} Sum</th>
+                            <td class="col-value">{{ babel_format_decimal(chstats['sum_' + chstatkey]) }}</td>
+                        </tr>
+                        <tr>
+                            <th>{{ get_fa_icon('cnt') }} Cnt</th>
+                            <td class="col-value">{{ babel_format_decimal(chstats['cnt_' + chstatkey]) }}</td>
+                        </tr>
+                        <tr>
+                            <th>{{ get_fa_icon('avg') }} Avg</th>
+                            <td class="col-value">{{ babel_format_decimal(chstats['avg_' + chstatkey]) }}</td>
+                        </tr>
+                    </table>
+
+                </div>
+            </div>
+
+{%- endmacro %}
diff --git a/lib/hawat/config.py b/lib/hawat/config.py
index 457c099e5cdccf38b2c4a4198ee03821eb304611..cd80aff078c185db4b20a84c2c8a0007f54f3882 100644
--- a/lib/hawat/config.py
+++ b/lib/hawat/config.py
@@ -112,6 +112,7 @@ class Config:
     ENABLED_BLUEPRINTS = [
         'hawat.blueprints.auth_env',
         'hawat.blueprints.design',
+        'hawat.blueprints.dashboards',
         'hawat.blueprints.reports',
         'hawat.blueprints.events',
         'hawat.blueprints.geoip',
@@ -132,7 +133,7 @@ class Config:
 
     HAWAT_MENU_SKELETON = [
         {
-            'ident':    'boards',
+            'ident':    'dashboards',
             'content':  {'type': 'submenu', 'title': lazy_gettext('Dashboards'), 'icon': 'dashboards', 'tooltip': lazy_gettext('Various dashboards')},
             'position': 100
         },
@@ -197,6 +198,7 @@ class DevelopmentConfig(Config):
         'hawat.blueprints.auth_env',
         'hawat.blueprints.auth_dev',
         'hawat.blueprints.design',
+        'hawat.blueprints.dashboards',
         'hawat.blueprints.reports',
         'hawat.blueprints.events',
         'hawat.blueprints.geoip',
diff --git a/lib/mentat/module/statistician.py b/lib/mentat/module/statistician.py
index 736e15bc83ba59db7334b73f1d9fa11f2a16c69b..53efb2b76f3d080ad4d55fb2eb8fe1f57288ad7f 100644
--- a/lib/mentat/module/statistician.py
+++ b/lib/mentat/module/statistician.py
@@ -41,33 +41,6 @@ Available script commands
     Calculate statistics for messages stored into database within configured
     time interval thresholds.
 
-
-Custom configuration
---------------------
-
-Custom command line options
-^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-``--database-stats db-name``
-    Name of the database for storing IDEA message statistics.
-
-    *Type:* ``string``
-
-``--collection-stats col-name``
-    Name of the collection for storing IDEA message statistics.
-
-    *Type:* ``string``
-
-``--rrds-dir dir-name``
-    Name of the directory for the RRD database files.
-
-    *Type:* ``string``, *default:* ``/var/mentat/rrds``
-
-``--reports-dir dir-name``
-    Name of the directory for RRD generated charts.
-
-    *Type:* ``string``, *default:* ``/var/mentat/reports/statistician``
-
 """
 
 
@@ -84,6 +57,8 @@ import mentat.script.fetcher
 import mentat.const
 import mentat.stats.idea
 import mentat.stats.rrd
+import mentat.plugin.app.sqlstorage
+import mentat.datatype.sqldb
 
 
 # Current time (second precission)
@@ -101,9 +76,7 @@ class MentatStatisticianScript(mentat.script.fetcher.FetcherScript):
     #
 
     # List of configuration keys.
-    CONFIG_DATABASE_STATS   = 'database_stats'
-    CONFIG_COLLECTION_STATS = 'collection_stats'
-
+    CORECFG_STATISTICS = '__core__statistics'
     CONFIG_RRDS_DIR    = 'rrds_dir'
     CONFIG_REPORTS_DIR = 'reports_dir'
 
@@ -115,6 +88,9 @@ class MentatStatisticianScript(mentat.script.fetcher.FetcherScript):
         and it aims to even more simplify the script object creation by providing
         configuration values for parent contructor.
         """
+        # Declare private attributes.
+        self.sqlservice = None
+
         super().__init__(
 
             description = 'mentat-statistician.py - Mentat system statistical script',
@@ -131,78 +107,15 @@ class MentatStatisticianScript(mentat.script.fetcher.FetcherScript):
             #
             # Override default configurations.
             #
-            default_config_dir = '/etc/mentat/core'
-        )
-
-    def _init_argparser(self, **kwargs):
-        """
-        Initialize script command line argument parser. This method overrides the
-        base implementation in :py:func:`pyzenkit.zenscript.ZenScript._init_argparser`
-        and it must return valid :py:class:`argparse.ArgumentParser` object. It
-        appends additional command line options custom for this script object.
-
-        This method is called from the main constructor in :py:func:`pyzenkit.baseapp.BaseApp.__init__`
-        as a part of the **__init__** stage of application`s life cycle.
-
-        :param kwargs: Various additional parameters passed down from object constructor.
-        :return: Valid argument parser object.
-        :rtype: argparse.ArgumentParser
-        """
-        argparser = super()._init_argparser(**kwargs)
+            default_config_dir = '/etc/mentat/core',
 
-        #
-        # Create and populate options group for custom script arguments.
-        #
-        arggroup_script = argparser.add_argument_group('custom script arguments')
-
-        arggroup_script.add_argument('--database-stats',   type = str, default = None, help = 'name of the database for storing IDEA message statistics')
-        arggroup_script.add_argument('--collection-stats', type = str, default = None, help = 'name of the collection for storing IDEA message statistics')
-        arggroup_script.add_argument('--rrds-dir',         type = str, default = None, help = 'name of the directory for the RRD database files')
-        arggroup_script.add_argument('--reports-dir',      type = str, default = None, help = 'name of the directory for RRD generated charts')
-
-        return argparser
-
-    def _init_config(self, cfgs, **kwargs):
-        """
-        Initialize default script configurations. This method overrides the base
-        implementation in :py:func:`pyzenkit.zenscript.ZenScript._init_config`
-        and it appends additional configurations via ``cfgs`` parameter.
-
-        This method is called from the main constructor in :py:func:`pyzenkit.baseapp.BaseApp.__init__`
-        as a part of the **__init__** stage of application`s life cycle.
-
-        :param list cfgs: Additional set of configurations.
-        :param kwargs: Various additional parameters passed down from constructor.
-        :return: Default configuration structure.
-        :rtype: dict
-        """
-        cfgs = (
-            (self.CONFIG_DATABASE_STATS,   None),
-            (self.CONFIG_COLLECTION_STATS, None),
-            (self.CONFIG_RRDS_DIR,         '/var/mentat/rrds'),
-            (self.CONFIG_REPORTS_DIR,      '/var/mentat/reports/statistician')
-        ) + cfgs
-        return super()._init_config(cfgs, **kwargs)
-
-    def _configure_postprocess(self):
-        """
-        Perform postprocessing of configuration values and calculate *core* configurations.
-
-        This method is called from the :py:func:`pyzenkit.baseapp.BaseApp._stage_setup_configuration`
-        as a port of the **setup** stage of application`s life cycle.
-        """
-        super()._configure_postprocess()
-
-        # Prepare shortcuts for core database settings.
-        db_settings = self.c(mentat.const.CKEY_CORE_DATABASE)
-        db_config   = db_settings[mentat.const.CKEY_CORE_DATABASE_CONFIG]
-
-        # Configure undefined database settings from core settings.
-        for cfg in (
-                (self.CONFIG_DATABASE_STATS,   db_config['db_stats']),
-                (self.CONFIG_COLLECTION_STATS, db_config['col_stats_alerts'])):
-            if self.config[cfg[0]] is None:
-                self.config[cfg[0]] = cfg[1]
+            #
+            # Load additional application-level plugins.
+            #
+            plugins = [
+                mentat.plugin.app.sqlstorage.SQLStoragePlugin()
+            ]
+        )
 
     def _sub_stage_init(self, **kwargs):
         """
@@ -226,14 +139,10 @@ class MentatStatisticianScript(mentat.script.fetcher.FetcherScript):
         This method is called from the main setup method :py:func:`pyzenkit.baseapp.BaseApp._stage_setup`
         as a part of the **setup** stage of application`s life cycle.
         """
-        self.database_stats_name   = self.c(self.CONFIG_DATABASE_STATS)
-        self.collection_stats_name = self.c(self.CONFIG_COLLECTION_STATS)
-
-        self.database_stats   = self.storage.database(self.database_stats_name)
-        self.collection_stats = self.database_stats.collection(self.collection_stats_name)
-        self.logger.info("Connected to stats database collection '%s':'%s'", self.database_stats_name, self.collection_stats_name)
-
-        self.stats_rrd = mentat.stats.rrd.RrdStats(rrds_dir = self.c(self.CONFIG_RRDS_DIR), reports_dir = self.c(self.CONFIG_REPORTS_DIR))
+        self.stats_rrd = mentat.stats.rrd.RrdStats(
+            rrds_dir    = self.config[self.CORECFG_STATISTICS][self.CONFIG_RRDS_DIR],
+            reports_dir = self.config[self.CORECFG_STATISTICS][self.CONFIG_REPORTS_DIR]
+        )
 
 
     #---------------------------------------------------------------------------
@@ -264,27 +173,26 @@ class MentatStatisticianScript(mentat.script.fetcher.FetcherScript):
             interval  = self.c(self.CONFIG_INTERVAL),
             adjust    = self.c(self.CONFIG_REGULAR)
         )
-        self.logger.info("Lower statistics calculation time interval threshold: %s (%s)", time_low.strftime('%Y-%m-%d %H:%M'), time_low.timestamp())
-        self.logger.info("Upper statistics calculation time interval threshold: %s (%s)", time_high.strftime('%Y-%m-%d %H:%M'), time_high.timestamp())
+        self.logger.info("Lower statistics calculation time interval threshold: %s (%s)", time_low.strftime('%FT%T'), time_low.timestamp())
+        self.logger.info("Upper statistics calculation time interval threshold: %s (%s)", time_high.strftime('%FT%T'), time_high.timestamp())
 
-        result['ts_from_s'] = str(time_low)
-        result['ts_to_s']   = str(time_high)
+        result['ts_from_s'] = time_low.strftime('%FT%T')
+        result['ts_to_s']   = time_high.strftime('%FT%T')
         result['ts_from']   = int(time_low.timestamp())
         result['ts_to']     = int(time_high.timestamp())
-        result['_id']       = '{}_{}'.format(result['ts_from'], result['ts_to'])
+        result['interval']  = '{}_{}'.format(result['ts_from_s'], result['ts_to_s'])
 
         messages = self.fetch_messages(time_low, time_high)
 
-        result = mentat.stats.idea.evaluate_messages_full(messages, result)
+        result = mentat.stats.idea.evaluate_event_groups(messages, result)
 
         self._update_rrds(result, time_high)
 
         self._generate_charts(time_high)
 
-        result = mentat.stats.idea.brief_stats_full(result)
-        result = mentat.stats.idea.escape_stats_full(result)
+        result = mentat.stats.idea.truncate_evaluations(result)
 
-        result = self._update_db(result)
+        result = self._update_db(result, time_low, time_high)
 
         return result
 
@@ -361,19 +269,32 @@ class MentatStatisticianScript(mentat.script.fetcher.FetcherScript):
 
         self.runlog['generated_files'] = result
 
-    def _update_db(self, stats):
+    def _update_db(self, stats, time_low, time_high):
         """
-        Store given message statistics into database.
+        Store given event statistics into database.
 
-        :param dict stats: Message statistics to store.
+        :param dict stats: Event statistics to store.
         """
         stats['ts'] = int(time.time())
 
         try:
-            db_id = self.collection_stats.insert(stats)
-            self.logger.info("Stored statistics log to database document '%s'", db_id)
+            sql_stats = mentat.datatype.sqldb.EventStatisticsModel(
+                dt_from        = time_low,
+                dt_to          = time_high,
+                count          = stats[mentat.stats.idea.ST_SKEY_COUNT],
+                stats_overall  = stats.get(mentat.stats.idea.ST_OVERALL, {}),
+                stats_internal = stats.get(mentat.stats.idea.ST_INTERNAL, {}),
+                stats_external = stats.get(mentat.stats.idea.ST_EXTERNAL, {})
+            )
+            sql_stats.calculate_interval()
+            sql_stats.calculate_delta()
+
+            self.sqlservice.session.add(sql_stats)
+            self.sqlservice.session.commit()
+
+            self.logger.info("Stored event statistics log to database document '%s'", sql_stats.id)
             stats['flag_stored'] = True
-            stats['db_id']       = db_id
+            stats['db_id']       = sql_stats.id
 
         except Exception as exc:
             self.logger.error(str(exc))
diff --git a/lib/mentat/stats/idea.py b/lib/mentat/stats/idea.py
index 8c98f0ae71e64a1cecb24ed9f92dc2795e246f11..7e870aff4c34a309601c6d36081a8cf4ebd10696 100644
--- a/lib/mentat/stats/idea.py
+++ b/lib/mentat/stats/idea.py
@@ -17,18 +17,18 @@ __author__  = "Jan Mach <jan.mach@cesnet.cz>"
 __credits__ = "Pavel Kácha <pavel.kacha@cesnet.cz>, Andrea Kropáčová <andrea.kropacova@cesnet.cz>"
 
 
-from mentat.stats import escape_dict, unescape_dict
 from pynspect.jpath import jpath_values
-
+from mentat.stats import escape_dict, unescape_dict
+from mentat.datatype.sqldb import EventStatisticsModel
 
 KEY_UNKNOWN = '__unknown__'
 
 #
 # Literals for keywords of statistical categories
 #
-ST_INTERNAL = 'internal'
-ST_EXTERNAL = 'external'
-ST_OVERALL  = 'overall'
+ST_INTERNAL = 'stats_internal'
+ST_EXTERNAL = 'stats_external'
+ST_OVERALL  = 'stats_overall'
 
 #
 # Literals for keywords of calculated statistics
@@ -44,17 +44,11 @@ ST_SKEY_DETECTORSWS = 'detectorsws'
 ST_SKEY_ABUSES      = 'abuses'
 ST_SKEY_ASNS        = 'asns'
 ST_SKEY_COUNTRIES   = 'countries'
-
-ST_SKEY_REST = '__REST__'
-
+ST_SKEY_LIST_IDS    = 'list_ids'
 ST_SKEY_CNT_ALERTS  = 'cnt_alerts'
-
-ST_LIST_IDS       = 'list_ids'
-ST_LIST_IPS       = 'list_ips'
-ST_LIST_IP4S      = 'list_ip4s'
-ST_LIST_IP6S      = 'list_ip6s'
-ST_LIST_ASNS      = 'list_asns'
-ST_LIST_COUNTRIES = 'list_countries'
+ST_SKEY_COUNT       = 'count'
+ST_SKEY_HISTOGRAM   = 'histogram'
+ST_SKEY_REST        = '__REST__'
 
 LIST_CALCSTAT_KEYS = (
     ST_SKEY_IPS,
@@ -88,43 +82,6 @@ LIST_AGGREGATIONS = (
 
 #-------------------------------------------------------------------------------
 
-def _counter_inc(stats, stat, key, increment = 1):
-    """
-    Helper for incrementing given statistical parameter within given statistical
-    bundle.
-
-    :param dict stats: Structure containing all statistics.
-    :param str stat: Name of the statistic category.
-    :param str key: Name of the statistic key.
-    :param int increment: Counter increment.
-    :return: Updated structure containing statistics.
-    :rtype: dict
-    """
-    if not stat in stats:
-        stats[stat] = {}
-    stats[stat][str(key)] = stats[stat].get(str(key), 0) + increment
-    return stats
-
-
-def escape_stats(stats):
-    """
-    Escape dots ``.`` in statistic keyword names with ``(dot)`` (because ``.`` is reserved
-    in MongoDB).
-
-    .. todo::
-
-        Perhaps move this feature into :py:mod:`mentat.datatype.mongodb` library.
-
-    :param dict stats: Structure containing single statistic category.
-    :return: Updated structure containing statistics.
-    :rtype: dict
-    """
-    for key in LIST_CALCSTAT_KEYS:
-        if key in stats:
-            stats[key] = escape_dict(stats[key])
-    return stats
-
-
 def unescape_stats(stats):
     """
     Unescape ``(dot)``s in statistic keyword names with ``.`` (because ``.`` is reserved
@@ -143,25 +100,6 @@ def unescape_stats(stats):
             stats[key] = unescape_dict(stats[key])
     return stats
 
-
-def escape_stats_full(stats):
-    """
-    Escape all statistic categories with :py:func:`escape_stats`.
-
-    .. todo::
-
-        Perhaps move this feature into :py:mod:`mentat.datatype.mongodb` library.
-
-    :param dict stats: Structure containing single statistic category.
-    :return: Updated structure containing statistics.
-    :rtype: dict
-    """
-    for key in LIST_STAT_GROUPS:
-        if key in stats:
-            stats[key] = escape_stats(stats[key])
-    return stats
-
-
 def unescape_stats_full(stats):
     """
     Unescape all statistic categories with :py:func:`unescape_stats`.
@@ -174,13 +112,12 @@ def unescape_stats_full(stats):
     :return: Updated structure containing statistics.
     :rtype: dict
     """
-    for key in LIST_STAT_GROUPS:
+    for key in ('internal', 'external', 'overall'):
         if key in stats:
             stats[key] = unescape_stats(stats[key])
     return stats
 
-
-def brief_stats(stats, top_threshold = 20):
+def truncate_stats(stats, top_threshold = 20):
     """
     Make statistics more brief.
 
@@ -190,18 +127,19 @@ def brief_stats(stats, top_threshold = 20):
     :rtype: dict
     """
     if stats[ST_SKEY_CNT_ALERTS] > 0:
-        del stats[ST_LIST_IDS]
+        if ST_SKEY_LIST_IDS in stats:
+            del stats[ST_SKEY_LIST_IDS]
 
-        stats = _make_toplist(stats, ST_SKEY_IPS,       ST_LIST_IPS,       top_threshold)
-        stats = _make_toplist(stats, ST_SKEY_ASNS,      ST_LIST_ASNS,      top_threshold)
-        stats = _make_toplist(stats, ST_SKEY_COUNTRIES, ST_LIST_COUNTRIES, top_threshold)
+        for key in LIST_CALCSTAT_KEYS:
+            if key in stats:
+                stats = _make_toplist(stats, key, top_threshold)
 
     return stats
 
 
-def brief_stats_full(stats, top_threshold = 20):
+def truncate_evaluations(stats, top_threshold = 20):
     """
-    Make all statistic categories more brief with :py:func:`brief_stats`.
+    Make all statistic categories more brief with :py:func:`truncate_stats`.
 
     :param dict stats: Structure containing single statistic category.
     :param int top_threshold: Toplist threshold size.
@@ -209,22 +147,23 @@ def brief_stats_full(stats, top_threshold = 20):
     :rtype: dict
     """
     for key in LIST_STAT_GROUPS:
-        stats[key] = brief_stats(stats[key], top_threshold)
+        if key in stats:
+            stats[key] = truncate_stats(stats[key], top_threshold)
     return stats
 
 
-def group_messages(messages):
+def group_events(events):
     """
     Group mesages according to the presence of ``_CESNET.ResolvedAbuses`` key.
     Each message will be added to group ``overall`` and then to either ``internal``,
     or ``external`` based on the presence of the key mentioned above.
 
-    :param list messages: List of messages to be grouped.
-    :return: Structure containing message groups ``overall``, ``internal`` and ``external``.
+    :param list events: List of IDEA events to be grouped.
+    :return: Structure containing message groups ``stats_overall``, ``stats_internal`` and ``stats_external``.
     :rtype: dict
     """
     result = {ST_OVERALL: [], ST_INTERNAL: [], ST_EXTERNAL: []}
-    for msg in messages:
+    for msg in events:
         result[ST_OVERALL].append(msg)
         values = jpath_values(msg, '_CESNET.ResolvedAbuses')
         if values:
@@ -237,11 +176,11 @@ def group_messages(messages):
 #-------------------------------------------------------------------------------
 
 
-def evaluate_messages(messages, stats = None):
+def evaluate_events(events, stats = None):
     """
-    Evaluate statistics for given list of IDEA messages.
+    Evaluate statistics for given list of IDEA events.
 
-    :param list messages: List of messages to be evaluated.
+    :param list events: List of IDEA events to be evaluated.
     :param dict stats: Data structure to which to append calculated statistics.
     :return: Structure containing evaluated message statistics.
     :rtype: dict
@@ -249,18 +188,18 @@ def evaluate_messages(messages, stats = None):
     if stats is None:
         stats = dict()
 
-    stats[ST_SKEY_CNT_ALERTS] = len(messages)
+    stats[ST_SKEY_CNT_ALERTS] = len(events)
 
     # Do not calculate anything for empty message list.
     if not stats[ST_SKEY_CNT_ALERTS]:
         return stats
 
     # Prepare structure for storing IDEA message identifiers.
-    if ST_LIST_IDS not in stats:
-        stats[ST_LIST_IDS] = []
+    if ST_SKEY_LIST_IDS not in stats:
+        stats[ST_SKEY_LIST_IDS] = []
 
-    for msg in messages:
-        stats[ST_LIST_IDS].append(msg['ID'])
+    for msg in events:
+        stats[ST_SKEY_LIST_IDS].append(msg['ID'])
 
         reg = {}
 
@@ -293,53 +232,163 @@ def evaluate_messages(messages, stats = None):
     # Calculate secondary statistics (cnt, min, max, sum, avg).
     for key in LIST_CALCSTAT_KEYS:
         if key in stats:
-            stats['cnt_{}'.format(key)]  = len(stats[key])
-            stats['sum_{}'.format(key)]  = sum(stats[key].values())
-            stats['min_{}'.format(key)]  = min(stats[key].values())
-            stats['max_{}'.format(key)]  = max(stats[key].values())
-            stats['avg_{}'.format(key)]  = stats['sum_{}'.format(key)]/stats['cnt_{}'.format(key)]
-            stats['list_{}'.format(key)] = list(sorted(stats[key].keys()))
+            stats = _calculate_substats(stats, key)
 
     return stats
 
 
-def evaluate_messages_full(messages, stats = None):
+def evaluate_event_groups(events, stats = None):
     """
-    Evaluate full statistics for given list of IDEA messages. Messages will be
-    gruped using :py:func:`group_messages` first and the statistics will be
-    evaluated separatelly for each of message groups ``overall``, ``internal``
-    and ``external``.
-
-    :param list messages: List of messages to be evaluated.
-    :param dict stats: Data structure to which to append calculated statistics.
-    :return: Structure containing evaluated message statistics.
+    Evaluate full statistics for given list of IDEA events. Events will be
+    grouped using :py:func:`group_events` first and the statistics will be
+    evaluated separatelly for each of message groups ``stats_overall``,
+    ``stats_internal`` and ``external``.
+
+    :param list events: List of IDEA events to be evaluated.
+    :param dict stats: Optional dictionary structure to populate with statistics.
+    :return: Structure containing evaluated event statistics.
     :rtype: dict
     """
     if stats is None:
         stats = dict()
+    stats[ST_SKEY_COUNT] = len(events)
 
-    msg_groups = group_messages(messages)
+    msg_groups = group_events(events)
 
     for grp_key in LIST_STAT_GROUPS:
-        stats[grp_key] = evaluate_messages(msg_groups.get(grp_key, []))
-
+        stats[grp_key] = evaluate_events(msg_groups.get(grp_key, []))
     return stats
 
 
+def aggregate_stats(stats, interval, dt_from, result = None):
+    """
+    :param dict stats: Optional dictionary structure to populate with statistics.
+    :param str interval: String label describing time interval.
+    :param datetime.Datetime dt_from: Timestamp of the time interval beginning.
+    :param dict result: Structure for aggregated result.
+    :return: Structure containing aggregated event statistics.
+    :rtype: dict
+    """
+    if not result:
+        result = {
+            ST_SKEY_CNT_ALERTS: 0,
+            ST_SKEY_HISTOGRAM: []
+        }
+
+    result[ST_SKEY_CNT_ALERTS] += stats[ST_SKEY_CNT_ALERTS]
+    result[ST_SKEY_HISTOGRAM].append((
+            interval,
+            dt_from,
+            stats[ST_SKEY_CNT_ALERTS]
+        ))
+
+    for key in LIST_CALCSTAT_KEYS:
+        if key in stats:
+            for subkey, subval in stats[key].items():
+                result = _counter_inc(result, key, subkey, subval)
+
+    for key in LIST_CALCSTAT_KEYS:
+        if key in result:
+            result = _calculate_substats(result, key)
+
+    return result
+
+def aggregate_stat_groups(stats_list, result = None):
+    """
+
+    """
+    if result is None:
+        result = dict()
+    result[ST_SKEY_COUNT] = 0
+    result[ST_SKEY_HISTOGRAM]  = []
+
+    for stat in stats_list:
+        result[ST_SKEY_COUNT] += stat.count
+        result[ST_SKEY_HISTOGRAM].append((
+            stat.interval,
+            stat.dt_from,
+            stat.count
+        ))
+
+        if 'dt_from' in result:
+            result['dt_from'] = min(result['dt_from'], stat.dt_from)
+        else:
+            result['dt_from'] = stat.dt_from
+        if 'dt_to' in result:
+            result['dt_to'] = max(result['dt_to'], stat.dt_to)
+        else:
+            result['dt_to'] = stat.dt_to
+
+        if not stat.count:
+            continue
+
+        for grp_key in LIST_STAT_GROUPS:
+            result[grp_key] = aggregate_stats(
+                getattr(stat, grp_key),
+                stat.interval,
+                stat.dt_from,
+                result.get(grp_key, {})
+            )
+
+    return result
+
+
 #-------------------------------------------------------------------------------
 
 
-def _make_toplist(stats, dict_key, list_key, top_threshold):
+def _counter_inc(stats, stat, key, increment = 1):
+    """
+    Helper for incrementing given statistical parameter within given statistical
+    bundle.
+
+    :param dict stats: Structure containing all statistics.
+    :param str stat: Name of the statistic category.
+    :param str key: Name of the statistic key.
+    :param int increment: Counter increment.
+    :return: Updated structure containing statistics.
+    :rtype: dict
+    """
+    if not stat in stats:
+        stats[stat] = {}
+    stats[stat][str(key)] = stats[stat].get(str(key), 0) + increment
+    return stats
+
+def _calculate_substats(stats, key):
+    """
+    Calculate substatistics for given key within given statistics.
+
+    :param dict stats: Structure containing event statistics.
+    :param str key: Designated key within structure.
+    :return: Enriched event statistics.
+    :rtype: dict
+    """
+    stats['cnt_{}'.format(key)]  = len(stats[key])
+    stats['sum_{}'.format(key)]  = sum(stats[key].values())
+    stats['min_{}'.format(key)]  = min(stats[key].values())
+    stats['max_{}'.format(key)]  = max(stats[key].values())
+    stats['avg_{}'.format(key)]  = stats['sum_{}'.format(key)]/stats['cnt_{}'.format(key)]
+    stats['list_{}'.format(key)] = list(sorted(stats[key].keys()))
+    return stats
+
+def _make_toplist(stats, dict_key, top_threshold):
     """
     Produce only toplist of given statistical keys.
 
     :param dict stats: Calculated statistics.
     :param str dict_key: Name of the dictionary key within statistics containing values.
-    :param str dict_key: Name of the list key within statistics containing list of keys.
     :param int top_threshold: Number of desired items in toplist.
     :return: Updated statistics structure.
     :rtype: dict
     """
+    list_key = 'list_{}'.format(dict_key)
+
+    top_threshold -= 1
+
+    rest = None
+    if ST_SKEY_REST in stats[dict_key]:
+        rest = stats[dict_key][ST_SKEY_REST]
+        del stats[dict_key][ST_SKEY_REST]
+
     # Produce list of dictionary keys sorted in reverse order by their values.
     sorted_key_list = sorted(sorted(stats[dict_key].keys()), key=lambda x: stats[dict_key][x], reverse=True)
     sorted_key_list_keep  = sorted_key_list[:top_threshold]
@@ -353,8 +402,14 @@ def _make_toplist(stats, dict_key, list_key, top_threshold):
     if sorted_key_list_throw:
         tmp[ST_SKEY_REST] = sum([stats[dict_key][key] for key in sorted_key_list_throw])
 
+    if rest:
+        tmp[ST_SKEY_REST] = tmp.get(ST_SKEY_REST, 0) + rest
+
     # Put everything back into original statistics.
     stats[dict_key] = tmp
-    stats[list_key] = sorted(sorted_key_list_keep)
+    stats[list_key] = sorted_key_list_keep
+
+    if ST_SKEY_REST in stats[dict_key]:
+        stats[list_key].append(ST_SKEY_REST)
 
     return stats
diff --git a/lib/mentat/stats/test_idea.py b/lib/mentat/stats/test_idea.py
index 9e730729baae349260f42ac484ebfb9a740938bc..44948893cc0ad18d1df0ccc023ab83647c42562e 100644
--- a/lib/mentat/stats/test_idea.py
+++ b/lib/mentat/stats/test_idea.py
@@ -15,12 +15,10 @@ import os
 import sys
 import time
 import shutil
-
-# Generate the path to custom 'lib' directory
-lib = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../lib'))
-sys.path.insert(0, lib)
+import datetime
 
 import mentat.stats.idea
+import mentat.datatype.sqldb
 
 class TestMentatStatsIdea(unittest.TestCase):
 
@@ -186,7 +184,7 @@ class TestMentatStatsIdea(unittest.TestCase):
 
     def test_01_counter_inc(self):
         """
-        Perform the basic operativity tests.
+        Test counter incrementation utility.
         """
         self.maxDiff = None
 
@@ -197,213 +195,125 @@ class TestMentatStatsIdea(unittest.TestCase):
         self.assertEqual(mentat.stats.idea._counter_inc(test, 'x', 'a'),    {'x': {'a': 3}})
         self.assertEqual(mentat.stats.idea._counter_inc(test, 'x', 'a', 5), {'x': {'a': 8}})
 
-    def test_02_evaluate_messages(self):
+    def test_02_make_toplist(self):
         """
-        Perform the message evaluation tests.
+        Test toplist creation utility.
         """
         self.maxDiff = None
 
-        self.assertEqual(mentat.stats.idea.evaluate_messages(self.ideas_raw), {
-            'abuses': {'__unknown__': 2, 'abuse@cesnet.cz': 4},
-            'analyzers': {'Beekeeper': 1, 'Dionaea': 1, 'Kippo': 3, 'LaBrea': 1},
-            'asns': {'__unknown__': 6},
-            'avg_abuses': 3.0,
-            'avg_analyzers': 1.5,
-            'avg_asns': 6.0,
-            'avg_categories': 2.0,
-            'avg_category_sets': 2.0,
-            'avg_countries': 6.0,
-            'avg_detectors': 1.5,
-            'avg_detectorsws': 1.2,
-            'avg_ips': 1.5,
-            'categories': {'Exploit': 2, 'Fraud.Phishing': 3, 'Spam': 1},
-            'category_sets': {'Exploit': 2, 'Fraud.Phishing': 3, 'Spam': 1},
-            'cnt_abuses': 2,
-            'cnt_alerts': 6,
-            'cnt_analyzers': 4,
-            'cnt_asns': 1,
-            'cnt_categories': 3,
-            'cnt_category_sets': 3,
-            'cnt_countries': 1,
-            'cnt_detectors': 4,
-            'cnt_detectorsws': 5,
-            'cnt_ips': 8,
-            'countries': {'__unknown__': 6},
-            'detectors': {'cz.cesnet.holly': 1,
-                          'org.example.dionaea': 2,
-                          'org.example.kippo': 2,
-                          'org.example.labrea': 1},
-            'detectorsws': {'cz.cesnet.holly/Beekeeper': 1,
-                            'org.example.dionaea/Dionaea': 1,
-                            'org.example.dionaea/Kippo': 1,
-                            'org.example.kippo/Kippo': 2,
-                            'org.example.labrea/LaBrea': 1},
-            'ips': {'192.168.0.0/25': 3,
-                    '192.168.0.100': 1,
-                    '192.168.0.105': 1,
-                    '192.168.0.109': 1,
-                    '192.168.0.2-192.168.0.5': 3,
-                    '192.168.0.200': 1,
-                    '192.172.0.109': 1,
-                    '192.172.0.200': 1},
-            'list_abuses': ['__unknown__', 'abuse@cesnet.cz'],
-            'list_analyzers': ['Beekeeper', 'Dionaea', 'Kippo', 'LaBrea'],
-            'list_asns': ['__unknown__'],
-            'list_categories': ['Exploit', 'Fraud.Phishing', 'Spam'],
-            'list_category_sets': ['Exploit', 'Fraud.Phishing', 'Spam'],
-            'list_countries': ['__unknown__'],
-            'list_detectors': ['cz.cesnet.holly',
-                               'org.example.dionaea',
-                               'org.example.kippo',
-                               'org.example.labrea'],
-            'list_detectorsws': ['cz.cesnet.holly/Beekeeper',
-                                 'org.example.dionaea/Dionaea',
-                                 'org.example.dionaea/Kippo',
-                                 'org.example.kippo/Kippo',
-                                 'org.example.labrea/LaBrea'],
-            'list_ids': ['msg01', 'msg02', 'msg03', 'msg04', 'msg05', 'msg06'],
-            'list_ips': ['192.168.0.0/25',
-                         '192.168.0.100',
-                         '192.168.0.105',
-                         '192.168.0.109',
-                         '192.168.0.2-192.168.0.5',
-                         '192.168.0.200',
-                         '192.172.0.109',
-                         '192.172.0.200'],
-            'max_abuses': 4,
-            'max_analyzers': 3,
-            'max_asns': 6,
-            'max_categories': 3,
-            'max_category_sets': 3,
-            'max_countries': 6,
-            'max_detectors': 2,
-            'max_detectorsws': 2,
-            'max_ips': 3,
-            'min_abuses': 2,
-            'min_analyzers': 1,
-            'min_asns': 6,
-            'min_categories': 1,
-            'min_category_sets': 1,
-            'min_countries': 6,
-            'min_detectors': 1,
-            'min_detectorsws': 1,
-            'min_ips': 1,
-            'sum_abuses': 6,
-            'sum_analyzers': 6,
-            'sum_asns': 6,
-            'sum_categories': 6,
-            'sum_category_sets': 6,
-            'sum_countries': 6,
-            'sum_detectors': 6,
-            'sum_detectorsws': 6,
-            'sum_ips': 12
+        test1 = {
+            'detectors': {
+                'org.example.holly': 1,
+                'org.example.rimmer': 5,
+                'org.example.kryten': 10,
+                'org.example.queeg': 20,
+                'org.example.dionaea': 5,
+                'org.example.kippo': 3,
+                'org.example.labrea': 2
+            },
+            'list_detectors': [
+                'org.example.holly',
+                'org.example.rimmer',
+                'org.example.kryten',
+                'org.example.queeg',
+                'org.example.dionaea',
+                'org.example.kippo',
+                'org.example.labrea'
+            ]
+        }
+
+        self.assertEqual(mentat.stats.idea._make_toplist(test1, 'detectors', 5), {
+            'detectors': {
+                '__REST__': 6,
+                'org.example.dionaea': 5,
+                'org.example.kryten': 10,
+                'org.example.queeg': 20,
+                'org.example.rimmer': 5
+            },
+            'list_detectors': [
+                'org.example.queeg',
+                'org.example.kryten',
+                'org.example.dionaea',
+                'org.example.rimmer',
+                '__REST__'
+            ]
         })
 
-    def test_03_escape_stats(self):
+        test2 = {
+            'detectors': {
+                '__REST__': 50,
+                'org.example.rimmer': 5,
+                'org.example.kryten': 10,
+                'org.example.queeg': 20,
+                'org.example.dionaea': 5,
+                'org.example.kippo': 3,
+                'org.example.labrea': 2
+            },
+            'list_detectors': [
+                '__REST__',
+                'org.example.rimmer',
+                'org.example.kryten',
+                'org.example.queeg',
+                'org.example.dionaea',
+                'org.example.kippo',
+                'org.example.labrea'
+            ]
+        }
+
+        self.assertEqual(mentat.stats.idea._make_toplist(test2, 'detectors', 5), {
+            'detectors': {
+                '__REST__': 55,
+                'org.example.dionaea': 5,
+                'org.example.kryten': 10,
+                'org.example.queeg': 20,
+                'org.example.rimmer': 5
+            },
+            'list_detectors': [
+                'org.example.queeg',
+                'org.example.kryten',
+                'org.example.dionaea',
+                'org.example.rimmer',
+                '__REST__'
+            ]
+        })
+
+        test3 = {
+            'detectors': {
+                '__REST__': 50,
+                'org.example.rimmer': 5,
+                'org.example.kryten': 10,
+                'org.example.queeg': 20,
+            },
+            'list_detectors': [
+                '__REST__',
+                'org.example.rimmer',
+                'org.example.kryten',
+                'org.example.queeg'
+            ]
+        }
+
+        self.assertEqual(mentat.stats.idea._make_toplist(test3, 'detectors', 5), {
+            'detectors': {
+                '__REST__': 50,
+                'org.example.kryten': 10,
+                'org.example.queeg': 20,
+                'org.example.rimmer': 5
+            },
+            'list_detectors': [
+                'org.example.queeg',
+                'org.example.kryten',
+                'org.example.rimmer',
+                '__REST__'
+            ]
+        })
+
+    def test_03_evaluate_events(self):
         """
-        Perform the basic operativity tests.
+        Perform the message evaluation tests.
         """
         self.maxDiff = None
 
-        result = mentat.stats.idea.escape_stats(mentat.stats.idea.evaluate_messages(self.ideas_raw))
-        self.assertEqual(result, {
-            'abuses': {'__unknown__': 2, 'abuse@cesnet(dot)cz': 4},
-            'analyzers': {'Beekeeper': 1, 'Dionaea': 1, 'Kippo': 3, 'LaBrea': 1},
-            'asns': {'__unknown__': 6},
-            'avg_abuses': 3.0,
-            'avg_analyzers': 1.5,
-            'avg_asns': 6.0,
-            'avg_categories': 2.0,
-            'avg_category_sets': 2.0,
-            'avg_countries': 6.0,
-            'avg_detectors': 1.5,
-            'avg_detectorsws': 1.2,
-            'avg_ips': 1.5,
-            'categories': {'Exploit': 2, 'Fraud(dot)Phishing': 3, 'Spam': 1},
-            'category_sets': {'Exploit': 2, 'Fraud(dot)Phishing': 3, 'Spam': 1},
-            'cnt_abuses': 2,
-            'cnt_alerts': 6,
-            'cnt_analyzers': 4,
-            'cnt_asns': 1,
-            'cnt_categories': 3,
-            'cnt_category_sets': 3,
-            'cnt_countries': 1,
-            'cnt_detectors': 4,
-            'cnt_detectorsws': 5,
-            'cnt_ips': 8,
-            'countries': {'__unknown__': 6},
-            'detectors': {'cz(dot)cesnet(dot)holly': 1,
-                          'org(dot)example(dot)dionaea': 2,
-                          'org(dot)example(dot)kippo': 2,
-                          'org(dot)example(dot)labrea': 1},
-            'detectorsws': {'cz(dot)cesnet(dot)holly/Beekeeper': 1,
-                            'org(dot)example(dot)dionaea/Dionaea': 1,
-                            'org(dot)example(dot)dionaea/Kippo': 1,
-                            'org(dot)example(dot)kippo/Kippo': 2,
-                            'org(dot)example(dot)labrea/LaBrea': 1},
-            'ips': {'192(dot)168(dot)0(dot)0/25': 3,
-                    '192(dot)168(dot)0(dot)100': 1,
-                    '192(dot)168(dot)0(dot)105': 1,
-                    '192(dot)168(dot)0(dot)109': 1,
-                    '192(dot)168(dot)0(dot)2-192(dot)168(dot)0(dot)5': 3,
-                    '192(dot)168(dot)0(dot)200': 1,
-                    '192(dot)172(dot)0(dot)109': 1,
-                    '192(dot)172(dot)0(dot)200': 1},
-            'list_abuses': ['__unknown__', 'abuse@cesnet.cz'],
-            'list_analyzers': ['Beekeeper', 'Dionaea', 'Kippo', 'LaBrea'],
-            'list_asns': ['__unknown__'],
-            'list_categories': ['Exploit', 'Fraud.Phishing', 'Spam'],
-            'list_category_sets': ['Exploit', 'Fraud.Phishing', 'Spam'],
-            'list_countries': ['__unknown__'],
-            'list_detectors': ['cz.cesnet.holly',
-                               'org.example.dionaea',
-                               'org.example.kippo',
-                               'org.example.labrea'],
-            'list_detectorsws': ['cz.cesnet.holly/Beekeeper',
-                                 'org.example.dionaea/Dionaea',
-                                 'org.example.dionaea/Kippo',
-                                 'org.example.kippo/Kippo',
-                                 'org.example.labrea/LaBrea'],
-            'list_ids': ['msg01', 'msg02', 'msg03', 'msg04', 'msg05', 'msg06'],
-            'list_ips': ['192.168.0.0/25',
-                         '192.168.0.100',
-                         '192.168.0.105',
-                         '192.168.0.109',
-                         '192.168.0.2-192.168.0.5',
-                         '192.168.0.200',
-                         '192.172.0.109',
-                         '192.172.0.200'],
-            'max_abuses': 4,
-            'max_analyzers': 3,
-            'max_asns': 6,
-            'max_categories': 3,
-            'max_category_sets': 3,
-            'max_countries': 6,
-            'max_detectors': 2,
-            'max_detectorsws': 2,
-            'max_ips': 3,
-            'min_abuses': 2,
-            'min_analyzers': 1,
-            'min_asns': 6,
-            'min_categories': 1,
-            'min_category_sets': 1,
-            'min_countries': 6,
-            'min_detectors': 1,
-            'min_detectorsws': 1,
-            'min_ips': 1,
-            'sum_abuses': 6,
-            'sum_analyzers': 6,
-            'sum_asns': 6,
-            'sum_categories': 6,
-            'sum_category_sets': 6,
-            'sum_countries': 6,
-            'sum_detectors': 6,
-            'sum_detectorsws': 6,
-            'sum_ips': 12
-        })
-
-        result = mentat.stats.idea.unescape_stats(result)
-        self.assertEqual(result, {
+        self.assertEqual(mentat.stats.idea.evaluate_events(self.ideas_raw), {
             'abuses': {'__unknown__': 2, 'abuse@cesnet.cz': 4},
             'analyzers': {'Beekeeper': 1, 'Dionaea': 1, 'Kippo': 3, 'LaBrea': 1},
             'asns': {'__unknown__': 6},
@@ -499,15 +409,14 @@ class TestMentatStatsIdea(unittest.TestCase):
             'sum_ips': 12
         })
 
-    def test_04_brief_stats(self):
+    def test_04_truncate_stats(self):
         """
         Perform the basic operativity tests.
         """
         self.maxDiff = None
 
-        self.assertEqual(mentat.stats.idea.brief_stats(mentat.stats.idea.evaluate_messages(self.ideas_raw), 3), {
-            'abuses': {'__unknown__': 2, 'abuse@cesnet.cz': 4},
-            'analyzers': {'Beekeeper': 1, 'Dionaea': 1, 'Kippo': 3, 'LaBrea': 1},
+        self.assertEqual(mentat.stats.idea.truncate_stats(mentat.stats.idea.evaluate_events(self.ideas_raw), 3), {'abuses': {'__unknown__': 2, 'abuse@cesnet.cz': 4},
+            'analyzers': {'Beekeeper': 1, 'Kippo': 3, '__REST__': 2},
             'asns': {'__unknown__': 6},
             'avg_abuses': 3.0,
             'avg_analyzers': 1.5,
@@ -518,8 +427,8 @@ class TestMentatStatsIdea(unittest.TestCase):
             'avg_detectors': 1.5,
             'avg_detectorsws': 1.2,
             'avg_ips': 1.5,
-            'categories': {'Exploit': 2, 'Fraud.Phishing': 3, 'Spam': 1},
-            'category_sets': {'Exploit': 2, 'Fraud.Phishing': 3, 'Spam': 1},
+            'categories': {'Exploit': 2, 'Fraud.Phishing': 3, '__REST__': 1},
+            'category_sets': {'Exploit': 2, 'Fraud.Phishing': 3, '__REST__': 1},
             'cnt_abuses': 2,
             'cnt_alerts': 6,
             'cnt_analyzers': 4,
@@ -531,32 +440,22 @@ class TestMentatStatsIdea(unittest.TestCase):
             'cnt_detectorsws': 5,
             'cnt_ips': 8,
             'countries': {'__unknown__': 6},
-            'detectors': {'cz.cesnet.holly': 1,
-                          'org.example.dionaea': 2,
-                          'org.example.kippo': 2,
-                          'org.example.labrea': 1},
-            'detectorsws': {'cz.cesnet.holly/Beekeeper': 1,
-                            'org.example.dionaea/Dionaea': 1,
-                            'org.example.dionaea/Kippo': 1,
-                            'org.example.kippo/Kippo': 2,
-                            'org.example.labrea/LaBrea': 1},
-            'ips': {'192.168.0.0/25': 3, '192.168.0.100': 1, '192.168.0.2-192.168.0.5': 3, '__REST__': 5},
-            'list_abuses': ['__unknown__', 'abuse@cesnet.cz'],
-            'list_analyzers': ['Beekeeper', 'Dionaea', 'Kippo', 'LaBrea'],
+            'detectors': {'__REST__': 2, 'org.example.dionaea': 2, 'org.example.kippo': 2},
+            'detectorsws': {'__REST__': 3,
+                            'cz.cesnet.holly/Beekeeper': 1,
+                            'org.example.kippo/Kippo': 2},
+            'ips': {'192.168.0.0/25': 3, '192.168.0.2-192.168.0.5': 3, '__REST__': 6},
+            'list_abuses': ['abuse@cesnet.cz', '__unknown__'],
+            'list_analyzers': ['Kippo', 'Beekeeper', '__REST__'],
             'list_asns': ['__unknown__'],
-            'list_categories': ['Exploit', 'Fraud.Phishing', 'Spam'],
-            'list_category_sets': ['Exploit', 'Fraud.Phishing', 'Spam'],
+            'list_categories': ['Fraud.Phishing', 'Exploit', '__REST__'],
+            'list_category_sets': ['Fraud.Phishing', 'Exploit', '__REST__'],
             'list_countries': ['__unknown__'],
-            'list_detectors': ['cz.cesnet.holly',
-                               'org.example.dionaea',
-                               'org.example.kippo',
-                               'org.example.labrea'],
-            'list_detectorsws': ['cz.cesnet.holly/Beekeeper',
-                                 'org.example.dionaea/Dionaea',
-                                 'org.example.dionaea/Kippo',
-                                 'org.example.kippo/Kippo',
-                                 'org.example.labrea/LaBrea'],
-            'list_ips': ['192.168.0.0/25', '192.168.0.100', '192.168.0.2-192.168.0.5'],
+            'list_detectors': ['org.example.dionaea', 'org.example.kippo', '__REST__'],
+            'list_detectorsws': ['org.example.kippo/Kippo',
+                                'cz.cesnet.holly/Beekeeper',
+                                '__REST__'],
+            'list_ips': ['192.168.0.0/25', '192.168.0.2-192.168.0.5', '__REST__'],
             'max_abuses': 4,
             'max_analyzers': 3,
             'max_asns': 6,
@@ -586,14 +485,14 @@ class TestMentatStatsIdea(unittest.TestCase):
             'sum_ips': 12
         })
 
-    def test_05_group_messages(self):
+    def test_05_group_events(self):
         """
         Perform the basic operativity tests.
         """
         self.maxDiff = None
 
-        self.assertEqual(mentat.stats.idea.group_messages(self.ideas_raw), {
-            'external': [{'Category': ['Spam'],
+        self.assertEqual(mentat.stats.idea.group_events(self.ideas_raw), {
+            'stats_external': [{'Category': ['Spam'],
                           'CreateTime': '2012-11-03T15:00:02Z',
                           'DetectTime': '2012-11-03T15:00:07Z',
                           'Format': 'IDEA0',
@@ -614,7 +513,7 @@ class TestMentatStatsIdea(unittest.TestCase):
                                    {'Name': 'cz.cesnet.holly', 'SW': ['Beekeeper']}],
                           'Source': [{'IP4': ['192.172.0.109', '192.172.0.200'],
                                       'Type': ['Exploit']}]}],
-            'internal': [{'Category': ['Fraud.Phishing'],
+            'stats_internal': [{'Category': ['Fraud.Phishing'],
                           'CreateTime': '2012-11-03T10:00:02Z',
                           'DetectTime': '2012-11-03T10:00:07Z',
                           'Format': 'IDEA0',
@@ -661,7 +560,7 @@ class TestMentatStatsIdea(unittest.TestCase):
                           'Source': [{'IP4': ['192.168.0.109', '192.168.0.200'],
                                       'Type': ['Exploit']}],
                           '_CESNET': {'ResolvedAbuses': ['abuse@cesnet.cz']}}],
-            'overall': [{'Category': ['Fraud.Phishing'],
+            'stats_overall': [{'Category': ['Fraud.Phishing'],
                          'CreateTime': '2012-11-03T10:00:02Z',
                          'DetectTime': '2012-11-03T10:00:07Z',
                          'Format': 'IDEA0',
@@ -731,27 +630,187 @@ class TestMentatStatsIdea(unittest.TestCase):
                                      'Type': ['Exploit']}]}]
         })
 
-    def test_06_evaluate_messages_full(self):
+    def test_06_evaluate_event_groups(self):
         """
         Perform the basic operativity tests.
         """
-        result = mentat.stats.idea.evaluate_messages_full(self.ideas_raw)
+        result = mentat.stats.idea.evaluate_event_groups(self.ideas_raw)
         if self.verbose:
-            print('*** result = mentat.stats.idea.evaluate_messages_full(self.ideas_raw) ***')
+            print('*** result = mentat.stats.idea.evaluate_event_groups(self.ideas_raw) ***')
             pprint(result)
         self.assertTrue(result)
 
-        result = mentat.stats.idea.escape_stats_full(mentat.stats.idea.brief_stats_full(result, 3))
+        result = mentat.stats.idea.truncate_evaluations(result, 3)
         if self.verbose:
-            print('*** result = mentat.stats.idea.escape_stats_full(mentat.stats.idea.brief_stats_full(result, 3)) ***')
+            print('*** result = mentat.stats.idea.truncate_evaluations(result, 3) ***')
             pprint(result)
         self.assertTrue(result)
 
-        result = mentat.stats.idea.unescape_stats_full(result)
-        if self.verbose:
-            print('*** result = mentat.stats.idea.unescape_stats_full(result) ***')
-            pprint(result)
+    def test_07_aggregate_stats(self):
+        """
+        Perform the statistics aggregation tests.
+        """
+        self.maxDiff = None
+
+        sts1 = mentat.stats.idea.evaluate_events(self.ideas_raw)
+        sts2 = mentat.stats.idea.evaluate_events(self.ideas_raw)
+        sts3 = mentat.stats.idea.evaluate_events(self.ideas_raw)
+
+        result = mentat.stats.idea.aggregate_stats(sts1, 'interval1', 123456)
+        result = mentat.stats.idea.aggregate_stats(sts2, 'interval2', 123456, result)
+        result = mentat.stats.idea.aggregate_stats(sts3, 'interval3', 123456, result)
+
+        self.assertEqual(result, {'abuses': {'__unknown__': 6, 'abuse@cesnet.cz': 12},
+            'analyzers': {'Beekeeper': 3, 'Dionaea': 3, 'Kippo': 9, 'LaBrea': 3},
+            'asns': {'__unknown__': 18},
+            'avg_abuses': 9.0,
+            'avg_analyzers': 4.5,
+            'avg_asns': 18.0,
+            'avg_categories': 6.0,
+            'avg_category_sets': 6.0,
+            'avg_countries': 18.0,
+            'avg_detectors': 4.5,
+            'avg_detectorsws': 3.6,
+            'avg_ips': 4.5,
+            'categories': {'Exploit': 6, 'Fraud.Phishing': 9, 'Spam': 3},
+            'category_sets': {'Exploit': 6, 'Fraud.Phishing': 9, 'Spam': 3},
+            'cnt_abuses': 2,
+            'cnt_alerts': 18,
+            'cnt_analyzers': 4,
+            'cnt_asns': 1,
+            'cnt_categories': 3,
+            'cnt_category_sets': 3,
+            'cnt_countries': 1,
+            'cnt_detectors': 4,
+            'cnt_detectorsws': 5,
+            'cnt_ips': 8,
+            'countries': {'__unknown__': 18},
+            'detectors': {'cz.cesnet.holly': 3,
+                          'org.example.dionaea': 6,
+                          'org.example.kippo': 6,
+                          'org.example.labrea': 3},
+            'detectorsws': {'cz.cesnet.holly/Beekeeper': 3,
+                            'org.example.dionaea/Dionaea': 3,
+                            'org.example.dionaea/Kippo': 3,
+                            'org.example.kippo/Kippo': 6,
+                            'org.example.labrea/LaBrea': 3},
+            'histogram': [('interval1', 123456, 6),
+                          ('interval2', 123456, 6),
+                          ('interval3', 123456, 6)],
+            'ips': {'192.168.0.0/25': 9,
+                    '192.168.0.100': 3,
+                    '192.168.0.105': 3,
+                    '192.168.0.109': 3,
+                    '192.168.0.2-192.168.0.5': 9,
+                    '192.168.0.200': 3,
+                    '192.172.0.109': 3,
+                    '192.172.0.200': 3},
+            'list_abuses': ['__unknown__', 'abuse@cesnet.cz'],
+            'list_analyzers': ['Beekeeper', 'Dionaea', 'Kippo', 'LaBrea'],
+            'list_asns': ['__unknown__'],
+            'list_categories': ['Exploit', 'Fraud.Phishing', 'Spam'],
+            'list_category_sets': ['Exploit', 'Fraud.Phishing', 'Spam'],
+            'list_countries': ['__unknown__'],
+            'list_detectors': ['cz.cesnet.holly',
+                               'org.example.dionaea',
+                               'org.example.kippo',
+                               'org.example.labrea'],
+            'list_detectorsws': ['cz.cesnet.holly/Beekeeper',
+                                 'org.example.dionaea/Dionaea',
+                                 'org.example.dionaea/Kippo',
+                                 'org.example.kippo/Kippo',
+                                 'org.example.labrea/LaBrea'],
+            'list_ips': ['192.168.0.0/25',
+                         '192.168.0.100',
+                         '192.168.0.105',
+                         '192.168.0.109',
+                         '192.168.0.2-192.168.0.5',
+                         '192.168.0.200',
+                         '192.172.0.109',
+                         '192.172.0.200'],
+            'max_abuses': 12,
+            'max_analyzers': 9,
+            'max_asns': 18,
+            'max_categories': 9,
+            'max_category_sets': 9,
+            'max_countries': 18,
+            'max_detectors': 6,
+            'max_detectorsws': 6,
+            'max_ips': 9,
+            'min_abuses': 6,
+            'min_analyzers': 3,
+            'min_asns': 18,
+            'min_categories': 3,
+            'min_category_sets': 3,
+            'min_countries': 18,
+            'min_detectors': 3,
+            'min_detectorsws': 3,
+            'min_ips': 3,
+            'sum_abuses': 18,
+            'sum_analyzers': 18,
+            'sum_asns': 18,
+            'sum_categories': 18,
+            'sum_category_sets': 18,
+            'sum_countries': 18,
+            'sum_detectors': 18,
+            'sum_detectorsws': 18,
+            'sum_ips': 36
+        })
+
+    def test_08_aggregate_stat_groups(self):
+        """
+        Perform the statistic group aggregation tests.
+        """
+        self.maxDiff = None
+
+        timestamp = 1485993600
+
+        stse1 = mentat.stats.idea.evaluate_events(self.ideas_raw)
+        stse2 = mentat.stats.idea.evaluate_events(self.ideas_raw)
+        stse3 = mentat.stats.idea.evaluate_events(self.ideas_raw)
+
+        stso1 = mentat.stats.idea.evaluate_events(self.ideas_raw)
+        stso2 = mentat.stats.idea.evaluate_events(self.ideas_raw)
+        stso3 = mentat.stats.idea.evaluate_events(self.ideas_raw)
+
+        stsi1 = mentat.stats.idea.evaluate_events(self.ideas_raw)
+        stsi2 = mentat.stats.idea.evaluate_events(self.ideas_raw)
+        stsi3 = mentat.stats.idea.evaluate_events(self.ideas_raw)
+
+        sts1 = mentat.datatype.sqldb.EventStatisticsModel(
+            interval       = 'interval1',
+            dt_from        = datetime.datetime.fromtimestamp(timestamp),
+            dt_to          = datetime.datetime.fromtimestamp(timestamp+300),
+            count          = stso1[mentat.stats.idea.ST_SKEY_CNT_ALERTS],
+            stats_overall  = stso1,
+            stats_internal = stsi1,
+            stats_external = stse1
+        )
+        sts2 = mentat.datatype.sqldb.EventStatisticsModel(
+            interval       = 'interval2',
+            dt_from        = datetime.datetime.fromtimestamp(timestamp+300),
+            dt_to          = datetime.datetime.fromtimestamp(timestamp+600),
+            count          = stso2[mentat.stats.idea.ST_SKEY_CNT_ALERTS],
+            stats_overall  = stso2,
+            stats_internal = stsi2,
+            stats_external = stse2
+        )
+        sts3 = mentat.datatype.sqldb.EventStatisticsModel(
+            interval       = 'interval3',
+            dt_from        = datetime.datetime.fromtimestamp(timestamp+600),
+            dt_to          = datetime.datetime.fromtimestamp(timestamp+900),
+            count          = stso3[mentat.stats.idea.ST_SKEY_CNT_ALERTS],
+            stats_overall  = stso3,
+            stats_internal = stsi3,
+            stats_external = stse3
+        )
+
+        result = mentat.stats.idea.aggregate_stat_groups([sts1, sts2, sts3])
+
         self.assertTrue(result)
+        self.assertEqual(result['dt_from'], datetime.datetime.fromtimestamp(timestamp))
+        self.assertEqual(result['dt_to'], datetime.datetime.fromtimestamp(timestamp+900))
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/package.json b/package.json
index c6ac83c3add2412cd3f04f36af5a5710f5fda3e7..2a5c730d98a8f449d7a74467d35ef8b6c45070e9 100644
--- a/package.json
+++ b/package.json
@@ -26,12 +26,14 @@
         "bootstrap-datepicker": "^1.7.1",
         "bootstrap-select": "^1.12.4",
         "bootswatch": "^3.3.7",
-        "d3": "^4.10.2",
+        "d3": "3.5.17",
         "datatables.net": "^1.10.16",
         "datatables.net-plugins": "^1.10.15",
         "eonasdan-bootstrap-datetimepicker": "^4.17.47",
         "font-awesome": "^4.7.0",
         "jquery": "^3.2.1",
+        "moment": "^2.19.4",
+        "moment-timezone": "^0.5.14",
         "nvd3": "^1.8.6"
     }
 }
diff --git a/yarn.lock b/yarn.lock
index 83491598c479ef9696742d84f1285eff51e7cdef..d26c07fff3793067c925e8e1a5db51d7d3b3c39b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -322,10 +322,6 @@ combined-stream@~0.0.4:
   dependencies:
     delayed-stream "0.0.5"
 
-commander@2:
-  version "2.11.0"
-  resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563"
-
 concat-map@0.0.1:
   version "0.0.1"
   resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
@@ -365,215 +361,9 @@ cycle@1.0.x:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/cycle/-/cycle-1.0.3.tgz#21e80b2be8580f98b468f379430662b046c34ad2"
 
-d3-array@1, d3-array@1.2.0, d3-array@^1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-1.2.0.tgz#147d269720e174c4057a7f42be8b0f3f2ba53108"
-
-d3-axis@1.0.8:
-  version "1.0.8"
-  resolved "https://registry.yarnpkg.com/d3-axis/-/d3-axis-1.0.8.tgz#31a705a0b535e65759de14173a31933137f18efa"
-
-d3-brush@1.0.4:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/d3-brush/-/d3-brush-1.0.4.tgz#00c2f238019f24f6c0a194a26d41a1530ffe7bc4"
-  dependencies:
-    d3-dispatch "1"
-    d3-drag "1"
-    d3-interpolate "1"
-    d3-selection "1"
-    d3-transition "1"
-
-d3-chord@1.0.4:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/d3-chord/-/d3-chord-1.0.4.tgz#7dec4f0ba886f713fe111c45f763414f6f74ca2c"
-  dependencies:
-    d3-array "1"
-    d3-path "1"
-
-d3-collection@1, d3-collection@1.0.4:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/d3-collection/-/d3-collection-1.0.4.tgz#342dfd12837c90974f33f1cc0a785aea570dcdc2"
-
-d3-color@1, d3-color@1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-1.0.3.tgz#bc7643fca8e53a8347e2fbdaffa236796b58509b"
-
-d3-dispatch@1, d3-dispatch@1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-1.0.3.tgz#46e1491eaa9b58c358fce5be4e8bed626e7871f8"
-
-d3-drag@1, d3-drag@1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-1.1.1.tgz#b5155304433b18ba38726b2184d0098e820dc64b"
-  dependencies:
-    d3-dispatch "1"
-    d3-selection "1"
-
-d3-dsv@1, d3-dsv@1.0.7:
-  version "1.0.7"
-  resolved "https://registry.yarnpkg.com/d3-dsv/-/d3-dsv-1.0.7.tgz#137076663f398428fc3d031ae65370522492b78f"
-  dependencies:
-    commander "2"
-    iconv-lite "0.4"
-    rw "1"
-
-d3-ease@1, d3-ease@1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-1.0.3.tgz#68bfbc349338a380c44d8acc4fbc3304aa2d8c0e"
-
-d3-force@1.0.6:
-  version "1.0.6"
-  resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-1.0.6.tgz#ea7e1b7730e2664cd314f594d6718c57cc132b79"
-  dependencies:
-    d3-collection "1"
-    d3-dispatch "1"
-    d3-quadtree "1"
-    d3-timer "1"
-
-d3-format@1, d3-format@1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.2.0.tgz#6b480baa886885d4651dc248a8f4ac9da16db07a"
-
-d3-geo@1.6.4:
-  version "1.6.4"
-  resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-1.6.4.tgz#f20e1e461cb1845f5a8be55ab6f876542a7e3199"
-  dependencies:
-    d3-array "1"
-
-d3-hierarchy@1.1.5:
-  version "1.1.5"
-  resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-1.1.5.tgz#a1c845c42f84a206bcf1c01c01098ea4ddaa7a26"
-
-d3-interpolate@1, d3-interpolate@1.1.5:
-  version "1.1.5"
-  resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-1.1.5.tgz#69e099ff39214716e563c9aec3ea9d1ea4b8a79f"
-  dependencies:
-    d3-color "1"
-
-d3-path@1, d3-path@1.0.5:
-  version "1.0.5"
-  resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.5.tgz#241eb1849bd9e9e8021c0d0a799f8a0e8e441764"
-
-d3-polygon@1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/d3-polygon/-/d3-polygon-1.0.3.tgz#16888e9026460933f2b179652ad378224d382c62"
-
-d3-quadtree@1, d3-quadtree@1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/d3-quadtree/-/d3-quadtree-1.0.3.tgz#ac7987e3e23fe805a990f28e1b50d38fcb822438"
-
-d3-queue@3.0.7:
-  version "3.0.7"
-  resolved "https://registry.yarnpkg.com/d3-queue/-/d3-queue-3.0.7.tgz#c93a2e54b417c0959129d7d73f6cf7d4292e7618"
-
-d3-random@1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/d3-random/-/d3-random-1.1.0.tgz#6642e506c6fa3a648595d2b2469788a8d12529d3"
-
-d3-request@1.0.6:
-  version "1.0.6"
-  resolved "https://registry.yarnpkg.com/d3-request/-/d3-request-1.0.6.tgz#a1044a9ef4ec28c824171c9379fae6d79474b19f"
-  dependencies:
-    d3-collection "1"
-    d3-dispatch "1"
-    d3-dsv "1"
-    xmlhttprequest "1"
-
-d3-scale@1.0.6:
-  version "1.0.6"
-  resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-1.0.6.tgz#bce19da80d3a0cf422c9543ae3322086220b34ed"
-  dependencies:
-    d3-array "^1.2.0"
-    d3-collection "1"
-    d3-color "1"
-    d3-format "1"
-    d3-interpolate "1"
-    d3-time "1"
-    d3-time-format "2"
-
-d3-selection@1, d3-selection@1.1.0, d3-selection@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-1.1.0.tgz#1998684896488f839ca0372123da34f1d318809c"
-
-d3-shape@1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.2.0.tgz#45d01538f064bafd05ea3d6d2cb748fd8c41f777"
-  dependencies:
-    d3-path "1"
-
-d3-time-format@2, d3-time-format@2.0.5:
-  version "2.0.5"
-  resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-2.0.5.tgz#9d7780204f7c9119c9170b1a56db4de9a8af972e"
-  dependencies:
-    d3-time "1"
-
-d3-time@1, d3-time@1.0.7:
-  version "1.0.7"
-  resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-1.0.7.tgz#94caf6edbb7879bb809d0d1f7572bc48482f7270"
-
-d3-timer@1, d3-timer@1.0.7:
-  version "1.0.7"
-  resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-1.0.7.tgz#df9650ca587f6c96607ff4e60cc38229e8dd8531"
-
-d3-transition@1, d3-transition@1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-1.1.0.tgz#cfc85c74e5239324290546623572990560c3966f"
-  dependencies:
-    d3-color "1"
-    d3-dispatch "1"
-    d3-ease "1"
-    d3-interpolate "1"
-    d3-selection "^1.1.0"
-    d3-timer "1"
-
-d3-voronoi@1.1.2:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/d3-voronoi/-/d3-voronoi-1.1.2.tgz#1687667e8f13a2d158c80c1480c5a29cb0d8973c"
-
-d3-zoom@1.5.0:
-  version "1.5.0"
-  resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-1.5.0.tgz#8417de9a077f98f9ce83b1998efb8ee12b4db26e"
-  dependencies:
-    d3-dispatch "1"
-    d3-drag "1"
-    d3-interpolate "1"
-    d3-selection "1"
-    d3-transition "1"
-
-d3@^4.10.2:
-  version "4.10.2"
-  resolved "https://registry.yarnpkg.com/d3/-/d3-4.10.2.tgz#d401b2bc0372a77e6822f278c0e4b4090206babd"
-  dependencies:
-    d3-array "1.2.0"
-    d3-axis "1.0.8"
-    d3-brush "1.0.4"
-    d3-chord "1.0.4"
-    d3-collection "1.0.4"
-    d3-color "1.0.3"
-    d3-dispatch "1.0.3"
-    d3-drag "1.1.1"
-    d3-dsv "1.0.7"
-    d3-ease "1.0.3"
-    d3-force "1.0.6"
-    d3-format "1.2.0"
-    d3-geo "1.6.4"
-    d3-hierarchy "1.1.5"
-    d3-interpolate "1.1.5"
-    d3-path "1.0.5"
-    d3-polygon "1.0.3"
-    d3-quadtree "1.0.3"
-    d3-queue "3.0.7"
-    d3-random "1.1.0"
-    d3-request "1.0.6"
-    d3-scale "1.0.6"
-    d3-selection "1.1.0"
-    d3-shape "1.2.0"
-    d3-time "1.0.7"
-    d3-time-format "2.0.5"
-    d3-timer "1.0.7"
-    d3-transition "1.1.0"
-    d3-voronoi "1.1.2"
-    d3-zoom "1.5.0"
+d3@3.5.17:
+  version "3.5.17"
+  resolved "https://registry.yarnpkg.com/d3/-/d3-3.5.17.tgz#bc46748004378b21a360c9fc7cf5231790762fb8"
 
 dashdash@^1.12.0:
   version "1.14.1"
@@ -1117,10 +907,6 @@ i@0.3.x:
   version "0.3.5"
   resolved "https://registry.yarnpkg.com/i/-/i-0.3.5.tgz#1d2b854158ec8169113c6cb7f6b6801e99e211d5"
 
-iconv-lite@0.4:
-  version "0.4.18"
-  resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.18.tgz#23d8656b16aae6742ac29732ea8f0336a4789cf2"
-
 iconv-lite@~0.2.11:
   version "0.2.11"
   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.2.11.tgz#1ce60a3a57864a292d1321ff4609ca4bb965adc8"
@@ -1382,10 +1168,20 @@ moment-timezone@^0.4.0:
   dependencies:
     moment ">= 2.6.0"
 
+moment-timezone@^0.5.14:
+  version "0.5.14"
+  resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.14.tgz#4eb38ff9538b80108ba467a458f3ed4268ccfcb1"
+  dependencies:
+    moment ">= 2.9.0"
+
 "moment@>= 2.6.0", moment@^2.10:
   version "2.18.1"
   resolved "https://registry.yarnpkg.com/moment/-/moment-2.18.1.tgz#c36193dd3ce1c2eed2adb7c802dbbc77a81b1c0f"
 
+"moment@>= 2.9.0", moment@^2.19.4:
+  version "2.19.4"
+  resolved "https://registry.yarnpkg.com/moment/-/moment-2.19.4.tgz#17e5e2c6ead8819c8ecfad83a0acccb312e94682"
+
 ms@2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
@@ -1904,10 +1700,6 @@ run-series@^1.0.2:
   version "1.1.4"
   resolved "https://registry.yarnpkg.com/run-series/-/run-series-1.1.4.tgz#89a73ddc5e75c9ef8ab6320c0a1600d6a41179b9"
 
-rw@1:
-  version "1.3.3"
-  resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4"
-
 safe-buffer@^5.0.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
   version "5.1.1"
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
@@ -2225,10 +2017,6 @@ wrappy@1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
 
-xmlhttprequest@1:
-  version "1.8.0"
-  resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc"
-
 xtend@~2.1.1:
   version "2.1.2"
   resolved "https://registry.yarnpkg.com/xtend/-/xtend-2.1.2.tgz#6efecc2a4dad8e6962c4901b337ce7ba87b5d28b"