diff --git a/.vscode/settings.json b/.vscode/settings.json
index 1d109b3056c36df9b04dccc0160d07304777ba1a..9f25ba9ffc4ef50e7318f7376545c5a0bf91e6be 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -101,13 +101,18 @@
         "*.ipp": "cpp",
         "qmap": "cpp",
         "hash_map": "cpp",
-        "unordered_set": "cpp"
+        "unordered_set": "cpp",
+        "qtimer": "cpp",
+        "*.inc": "cpp",
+        "qlist": "cpp"
     },
     "editor.defaultFormatter": "chiehyu.vscode-astyle",
     "[xml]": {
         "editor.defaultFormatter": "redhat.vscode-xml"
     },
     "cSpell.words": [
-        "Mavlink"
+        "Mavlink",
+        "Plottables",
+        "replot"
     ]
 }
\ No newline at end of file
diff --git a/Components/SubscriptionsPanel/subscriptionspanel.cpp b/Components/SubscriptionsPanel/subscriptionspanel.cpp
index 17870ac0051c17bae2ac544a1c78114387d0cb0f..05d3e0051e91c07d3866ae9366e9f88273f4e9c4 100644
--- a/Components/SubscriptionsPanel/subscriptionspanel.cpp
+++ b/Components/SubscriptionsPanel/subscriptionspanel.cpp
@@ -7,16 +7,14 @@
 #include "ui_subscriptionspanel.h"
 
 SubscriptionsPanel::SubscriptionsPanel(
-    const QList<TopicAndFieldFilter>* subscriptionsList)
+    const QList<TopicAndFieldFilter>& filters)
     : QWidget(nullptr), ui(new Ui::SubscriptionsPanel)
 {
     ui->setupUi(this);
     this->setAttribute(Qt::WA_DeleteOnClose, true);
 
-    for (auto s : *subscriptionsList)
-    {
-        addSubscription(s);
-    }
+    for (auto filter : filters)
+        addSubscription(filter);
 
     connect(ui->button_addTopic, &QPushButton::clicked, this,
             &SubscriptionsPanel::onAddTopicClicked);
diff --git a/Components/SubscriptionsPanel/subscriptionspanel.h b/Components/SubscriptionsPanel/subscriptionspanel.h
index a38bf4d4d688db0d685db5d9b02e8d8fc0de8cda..e84eac2abefeb0821ee9ec395162905fde0393b5 100644
--- a/Components/SubscriptionsPanel/subscriptionspanel.h
+++ b/Components/SubscriptionsPanel/subscriptionspanel.h
@@ -17,8 +17,7 @@ class SubscriptionsPanel : public QWidget
     Q_OBJECT
 
 public:
-    explicit SubscriptionsPanel(
-        const QList<TopicAndFieldFilter>* subscriptionsList);
+    explicit SubscriptionsPanel(const QList<TopicAndFieldFilter>& filters);
     ~SubscriptionsPanel();
 
     void addSubscription(const TopicAndFieldFilter& filter);
diff --git a/Modules/Graph/Graph.cpp b/Modules/Graph/Graph.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..37da61de630bbf0a58b49d6fa598ef40036821c2
--- /dev/null
+++ b/Modules/Graph/Graph.cpp
@@ -0,0 +1,271 @@
+#include "Graph.h"
+
+#include <Components/ContextMenuSeparator/contextmenuseparator.h>
+#include <Components/SubscriptionsPanel/subscriptionspanel.h>
+#include <Core/modulemessagesbroker.h>
+
+#include <QDebug>
+#include <QTimer>
+#include <algorithm>
+
+Graph::Graph(QWidget* parent) : DefaultModule(parent)
+{
+    setupUi();
+    customContextMenuSetup();
+
+    connect(&updaterTimer, &QTimer::timeout, this, &Graph::onUpdateTimerTick);
+    updaterTimer.setSingleShot(false);
+    updaterTimer.start(updatePeriod);
+
+    getCore()->getModuleMessagesBroker()->subscribe(
+        {"*"}, this, [this](const ModuleMessage& msg) { onMsgReceived(msg); });
+}
+
+Graph::~Graph() {}
+
+QWidget* Graph::toWidget() { return this; }
+
+XmlObject Graph::toXmlObject()
+{
+    XmlObject obj(getName(ModuleId::GRAPH));
+
+    obj.addAttribute("stopped", stopped ? 1 : 0);
+    obj.addAttribute("following", following ? 1 : 0);
+    obj.addAttribute("y_lower_range", (float)plot->yAxis->range().lower);
+    obj.addAttribute("y_upper_range", (float)plot->yAxis->range().upper);
+    obj.addAttribute("x_lower_range", (float)plot->xAxis->range().lower);
+    obj.addAttribute("x_upper_range", (float)plot->xAxis->range().upper);
+
+    for (int i = 0; i < filters.count(); i++)
+    {
+        XmlObject element("subscription");
+        element.addAttribute("filter", filters[i].toString());
+        obj.addChild(element);
+    }
+
+    return obj;
+}
+
+void Graph::fromXmlObject(const XmlObject& obj)
+{
+    if (obj.getObjectName() == getName(ModuleId::GRAPH))
+    {
+        int tmp;
+        obj.getAttribute("stopped", tmp);
+        stopped = (tmp == 1);
+        obj.getAttribute("following", tmp);
+        following = (tmp == 1);
+
+        float lowerRange, upperRange;
+        obj.getAttribute("y_lower_range", lowerRange);
+        obj.getAttribute("y_upper_range", upperRange);
+        plot->yAxis->setRange(lowerRange, upperRange);
+        obj.getAttribute("x_lower_range", lowerRange);
+        obj.getAttribute("x_upper_range", upperRange);
+        plot->xAxis->setRange(lowerRange, upperRange);
+
+        for (int i = 0; i < obj.childCount(); i++)
+        {
+            XmlObject child = obj.childAt(i);
+
+            if (child.getObjectName() == "subscription")
+            {
+                auto filter = TopicAndFieldFilter::fromStringUnsafe(
+                    child.getAttribute("filter"));
+                onSubscriptionAdded(filter);
+            }
+        }
+    }
+}
+
+void Graph::onSubscribeClicked()
+{
+    SubscriptionsPanel* panel = new SubscriptionsPanel(filters);
+    panel->setWindowTitle("Graph subscriptions");
+    connect(panel, &SubscriptionsPanel::SubscriptionAdded, this,
+            &Graph::onSubscriptionAdded);
+    connect(panel, &SubscriptionsPanel::SubscriptionRemoved, this,
+            &Graph::onSubscriptionRemoved);
+    panel->show();
+}
+
+void Graph::onClearClicked()
+{
+    for (auto graph : graphs)
+        graph->data()->clear();
+
+    // plot->replot();
+}
+
+void Graph::onStopClicked(bool checked)
+{
+    stopped = checked;
+
+    if (stopped)
+        updaterTimer.stop();
+    else
+        updaterTimer.start(updatePeriod);
+}
+
+void Graph::onFollowClicked(bool checked) { following = checked; }
+
+void Graph::onSubscriptionAdded(const TopicAndFieldFilter& filter)
+{
+    if (!filters.contains(filter))
+    {
+        QCPGraph* graph = plot->addGraph();
+        graph->setPen(
+            QPen(QColor(rand() % 255, rand() % 255, rand() % 255), 2));
+        graph->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssNone, 2));
+        graph->setSelectable(QCP::stNone);
+        graph->setName(filter.toString());
+
+        // Add the filter and the graph along with new buffers
+        filters.append(filter);
+        graphs.append(graph);
+        buffersX.append(QVector<double>());
+        buffersY.append(QVector<double>());
+    }
+}
+
+void Graph::onSubscriptionRemoved(const TopicAndFieldFilter& filter)
+{
+    if (filters.contains(filter))
+    {
+        int index = filters.indexOf(filter);
+
+        filters.removeAt(index);
+        graphs.removeAt(index);
+        buffersX.removeAt(index);
+        buffersY.removeAt(index);
+    }
+}
+
+void Graph::onUpdateTimerTick()
+{
+    bool newData = false;
+    double maxX  = 0;
+
+    // Check if new data items are available and redraw
+    for (int i = 0; i < filters.size(); i++)
+    {
+        QCPGraph* graph          = graphs[i];
+        QVector<double>& bufferX = buffersX[i];
+        QVector<double>& bufferY = buffersY[i];
+
+        if (bufferX.size() > 0)
+        {
+            newData = true;
+
+            graph->addData(bufferX, bufferY);
+
+            for (auto tmp : bufferX)
+                if (tmp > maxX)
+                    maxX = tmp;
+
+            bufferX.clear();
+            bufferY.clear();
+
+            // Check if there are too many data points
+            graph->data()->removeBefore(maxX - MAX_DATA_AGE);
+        }
+    }
+
+    if (newData)
+    {
+        plot->replot();
+
+        if (following)
+        {
+            double size = plot->xAxis->range().size();
+            plot->xAxis->setRange(maxX, size, Qt::AlignmentFlag::AlignRight);
+        }
+    }
+}
+
+void Graph::onMsgReceived(const ModuleMessage& msg)
+{
+    if (stopped)
+        return;
+
+    MessageField value;
+
+    for (auto filter : filters)
+    {
+        if (filter.matchMessage(msg, value))
+        {
+            int index = filters.indexOf(filter);
+
+            QVector<double>& bufferX = buffersX[index];
+            QVector<double>& bufferY = buffersY[index];
+
+            double x = msg.getField("timestamp").getUInteger(0) / 1e6;
+            double y = value.getDouble(0);
+
+            bufferX.append(x);
+            bufferY.append(y);
+        }
+    }
+}
+
+void Graph::setupUi()
+{
+    QHBoxLayout* outerLayout = new QHBoxLayout;
+    outerLayout->setContentsMargins(0, 0, 0, 0);
+
+    plot = new QCustomPlot;
+    outerLayout->addWidget(plot);
+
+    setLayout(outerLayout);
+
+    plot->legend->setVisible(true);
+
+    // Allow user to drag axis ranges with mouse, zoom with mouse wheel and
+    // select graphs by clicking:
+    plot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom |
+                          QCP::iSelectPlottables);
+}
+
+void Graph::customContextMenuSetup()
+{
+    setContextMenuPolicy(Qt::CustomContextMenu);
+
+    // Redirect event from the plot to the widget
+    plot->setContextMenuPolicy(Qt::CustomContextMenu);
+    connect(plot, &QCustomPlot::customContextMenuRequested, this,
+            &Graph::onCustomContextMenuRequested);
+}
+
+void Graph::onCustomContextMenuRequested(const QPoint& pos)
+{
+    QMenu menu;
+
+    // Subscribe -> Edit graph series
+    QAction* subscribe = new QAction("Subscribe");
+    connect(subscribe, &QAction::triggered, this, &Graph::onSubscribeClicked);
+    menu.addAction(subscribe);
+
+    // Clear -> Removes all data currently on the plot
+    QAction* clear = new QAction("Clear");
+    connect(clear, &QAction::triggered, this, &Graph::onClearClicked);
+    menu.addAction(clear);
+
+    // Stop -> Pauses the graph
+    QAction* stop = new QAction("Stop");
+    stop->setCheckable(true);
+    stop->setChecked(stopped);
+    connect(stop, &QAction::triggered, this, &Graph::onStopClicked);
+    menu.addAction(stop);
+
+    // Follow -> Automatically move the graph to follow new data
+    QAction* follow = new QAction("Follow");
+    follow->setCheckable(true);
+    follow->setChecked(following);
+    connect(follow, &QAction::triggered, this, &Graph::onFollowClicked);
+    menu.addAction(follow);
+
+    // Add a separator
+    menu.addSeparator();
+
+    emit getModuleEventsHandler()->contextMenuRequest(menu, mapToGlobal(pos));
+}
\ No newline at end of file
diff --git a/Modules/Graph/Graph.h b/Modules/Graph/Graph.h
new file mode 100644
index 0000000000000000000000000000000000000000..e595a701c2d5688c1dca609e221790ac37dc8938
--- /dev/null
+++ b/Modules/Graph/Graph.h
@@ -0,0 +1,54 @@
+#pragma once
+
+#include <Core/Message/modulemessage.h>
+#include <Core/Message/topicandfieldfilter.h>
+#include <Core/QCustomPlot/QCustomPlot.h>
+#include <Core/module.h>
+#include <Modules/DefaultModule/defaultmodule.h>
+
+#include <QList>
+#include <QTimer>
+#include <QWidget>
+
+class Graph : public DefaultModule
+{
+    Q_OBJECT
+
+    static constexpr int MAX_DATA_AGE = 30 * 60;  // [s]
+
+public:
+    explicit Graph(QWidget* parent = nullptr);
+    ~Graph();
+
+    QWidget* toWidget() override;
+    XmlObject toXmlObject() override;
+    void fromXmlObject(const XmlObject& xmlObject) override;
+
+private slots:
+    void onSubscribeClicked();
+    void onClearClicked();
+    void onStopClicked(bool checked);
+    void onFollowClicked(bool checked);
+    void onSubscriptionAdded(const TopicAndFieldFilter& filter);
+    void onSubscriptionRemoved(const TopicAndFieldFilter& filter);
+    void onUpdateTimerTick();
+    void onMsgReceived(const ModuleMessage& msg);
+
+private:
+    void setupUi();
+    void customContextMenuSetup();
+    void onCustomContextMenuRequested(const QPoint& pos) override;
+
+    QCustomPlot* plot;
+
+    QList<TopicAndFieldFilter> filters;
+    QList<QCPGraph*> graphs;
+    QList<QVector<double>> buffersX;
+    QList<QVector<double>> buffersY;
+
+    QTimer updaterTimer;
+    int updatePeriod = 1000 / 10;
+
+    bool stopped   = false;
+    bool following = true;
+};
diff --git a/Modules/Graph/graphmodule.cpp b/Modules/Graph/graphmodule.cpp
deleted file mode 100644
index e92d80cbfb4235d67d923688901f8f2c36ed6cf4..0000000000000000000000000000000000000000
--- a/Modules/Graph/graphmodule.cpp
+++ /dev/null
@@ -1,331 +0,0 @@
-#include "graphmodule.h"
-
-#include <QDebug>
-#include <QTimer>
-
-#include "Components/ContextMenuSeparator/contextmenuseparator.h"
-#include "Components/SubscriptionsPanel/subscriptionspanel.h"
-#include "Core/modulemessagesbroker.h"
-#include "ui_graphmodule.h"
-
-GraphModule::GraphModule(QWidget* parent)
-    : DefaultModule(parent), ui(new Ui::GraphModule)
-{
-    ui->setupUi(this);
-    defaultContextMenuSetup();
-    buildCentralGraphView();
-    ui->centralLayout->addWidget(&graphCentralView);
-    connect(&updaterTimer, &QTimer::timeout, this,
-            &GraphModule::onUpdateTimerTick);
-    updaterTimer.setSingleShot(false);
-    updaterTimer.start(updatePeriod);
-
-    getCore()->getModuleMessagesBroker()->subscribe(
-        {"*"}, this, [this](const ModuleMessage& msg) { onMsgReceived(msg); });
-}
-
-GraphModule::~GraphModule()
-{
-    // QCPGraph are destroyed automatically
-    delete ui;
-}
-
-void GraphModule::buildCentralGraphView()
-{
-    setTheme();
-
-    graphCentralView.yAxis2->setVisible(true);
-    graphCentralView.yAxis2->setTicks(false);
-    graphCentralView.yAxis2->setTickLabels(false);
-
-    // graphCentralView.xAxis2->setVisible(true);
-    // textTicker = QSharedPointer<QCPAxisTickerText>(new QCPAxisTickerText());
-    // cPlot.xAxis2->setTicker(textTicker);
-    // graphCentralView.xAxis2->setTickLabels(true);
-
-    //    QSharedPointer<QCPAxisTickerTime> dateTimeTicker(new
-    //    QCPAxisTickerTime); graphCentralView.xAxis->setTicker(dateTimeTicker);
-    // QDateTime rangeLowerBound = QDateTime::currentDateTime().addDays(-2);
-    //    double now = QDateTime::currentDateTime().toTime_t();
-    //    QDateTime rangeUpperBound = QDateTime::currentDateTime().addDays(+2);
-    //    graphCentralView.xAxis->setRange(now,
-    //    QCPAxisTickerDateTime::(rangeUpperBound));
-    //    dateTimeTicker->setTimeFormat(dateFormat);
-
-    graphCentralView.legend->setVisible(true);
-
-    // make left and bottom axes always transfer their ranges to right and top
-    // axes:
-    connect(graphCentralView.xAxis, SIGNAL(rangeChanged(QCPRange)),
-            graphCentralView.xAxis2, SLOT(setRange(QCPRange)));
-    connect(graphCentralView.yAxis, SIGNAL(rangeChanged(QCPRange)),
-            graphCentralView.yAxis2, SLOT(setRange(QCPRange)));
-
-    // Allow user to drag axis ranges with mouse, zoom with mouse wheel and
-    // select graphs by clicking:
-    graphCentralView.setInteractions(QCP::iRangeDrag | QCP::iRangeZoom |
-                                     QCP::iSelectPlottables);
-
-    // Enable context menu
-    graphCentralView.setContextMenuPolicy(Qt::CustomContextMenu);
-    // connect(&graphCentralView,SIGNAL(customContextMenuRequested(QPoint)),this,SLOT(onCustomContextMenuRequested(QPoint)));
-    connect(&graphCentralView, &QCustomPlot::customContextMenuRequested, this,
-            &GraphModule::onCustomContextMenuRequested);
-    // connect(customPlot,SIGNAL(selectionChangedByUser()),this,SLOT(selectedGraphChange()));
-}
-
-void GraphModule::setTheme()
-{
-    //    QColor accentColor;
-    //    QColor themeColor;
-    //    QColor themeBackgroundColor;
-    //    QColor traceColor;
-
-    //    accentColor = QColor(78, 156, 207);
-    //    traceColor = accentColor;
-    //    themeColor = QColor(Qt::gray);
-    //    themeBackgroundColor = QColor(50, 50, 50);
-
-    //    foreach(QCPAxisRect* rect, graphCentralView.axisRects()){
-    //        foreach(QCPAxis* axis, rect->axes()){
-    //            axis->setLabelColor(themeColor);
-    //            axis->setBasePen(QPen(themeColor, 1));
-    //            axis->setTickPen(QPen(themeColor, 1));
-    //            axis->setSubTickPen(QPen(themeColor, 1));
-    //            axis->setTickLabelColor(themeColor);
-    //            axis->grid()->setPen(QPen(themeColor, 0.5, Qt::DotLine));
-    //            axis->grid()->setSubGridVisible(false);
-
-    //            axis->setSelectedTickLabelColor(accentColor);
-    //            axis->setSelectedLabelColor(accentColor);
-    //            axis->setSelectedBasePen(QPen(accentColor, 1));
-    //            axis->setSelectedSubTickPen(QPen(accentColor, 1));
-    //            axis->setSelectedTickPen(QPen(accentColor, 1));
-    //        }
-    //    }
-    //    graphCentralView.setBackground(QBrush(themeBackgroundColor));
-    //    replotAll();
-}
-
-void GraphModule::onUpdateTimerTick() { replotAll(); }
-
-QCPGraph* GraphModule::instantiateNewGraph()
-{
-    QCPGraph* graph = graphCentralView.addGraph();
-    int r           = rand() % 255;
-    int g           = rand() % 255;
-    int b           = rand() % 255;
-    graph->setPen(QPen(QColor(r, g, b), 2));
-    graph->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, 3));
-    graph->setSelectable(QCP::stSingleData);
-    return graph;
-}
-
-QWidget* GraphModule::toWidget() { return this; }
-
-XmlObject GraphModule::toXmlObject()
-{
-    XmlObject obj(getName(ModuleId::GRAPH));
-    for (int i = 0; i < subsFilters.count(); i++)
-    {
-        XmlObject subscription("Subscription");
-        subscription.addAttribute("Value", subsFilters[i].toString());
-        obj.addChild(subscription);
-    }
-    return obj;
-}
-
-void GraphModule::fromXmlObject(const XmlObject& xmlObject)
-{
-    if (xmlObject.getObjectName() == getName(ModuleId::GRAPH))
-    {
-        for (int i = 0; i < xmlObject.childCount(); i++)
-        {
-            XmlObject child = xmlObject.childAt(i);
-            if (child.getObjectName() == "Subscription")
-            {
-                auto subscription = TopicAndFieldFilter::fromStringUnsafe(
-                    child.getAttribute("Value"));
-                onSubscriptionAdded(subscription);
-            }
-        }
-    }
-}
-
-void GraphModule::onSubscribeClicked()
-{
-    SubscriptionsPanel* sPanel = new SubscriptionsPanel(&subsFilters);
-    sPanel->setWindowTitle("Graph Subscriptions");
-    connect(sPanel, &SubscriptionsPanel::SubscriptionAdded, this,
-            &GraphModule::onSubscriptionAdded);
-    connect(sPanel, &SubscriptionsPanel::SubscriptionRemoved, this,
-            &GraphModule::onSubscriptionRemoved);
-    sPanel->show();
-}
-
-void GraphModule::onSubscriptionAdded(const TopicAndFieldFilter& filter)
-{
-
-    QCPGraph* graph = instantiateNewGraph();
-    graph->setName(filter.toString());
-    subsFilters.append(filter);
-    subsGraphs.append(graph);
-
-    // Da rimuovere per performance
-    graphCentralView.replot();
-}
-
-void GraphModule::onSubscriptionRemoved(const TopicAndFieldFilter& filter)
-{
-    int i = subsFilters.indexOf(filter);
-
-    if (i != -1)
-    {
-        subsFilters.removeAt(i);
-        graphCentralView.removeGraph(subsGraphs[i]);
-        subsGraphs.removeAt(i);
-    }
-
-    // Da rimuovere per performance
-    graphCentralView.replot();
-}
-
-void GraphModule::onMsgReceived(const ModuleMessage& msg)
-{
-    if (stopPlot)
-        return;
-
-    MessageField value;
-    for (int i = 0; i < subsFilters.size(); i++)
-    {
-        if (subsFilters[i].matchMessage(msg, value))
-        {
-            QCPGraph* graph = subsGraphs[i];
-
-            double x = 0, y = 0;
-            x = msg.getField("timestamp").getUInteger(0) / 1e6;
-            y = value.getDouble(0.0);
-            if (graph != nullptr)
-            {
-                graph->addData(x, y);
-                // Da rimuovere per performance
-                // replotAll();
-            }
-        }
-    }
-}
-
-void GraphModule::setFollowedGraphIndex(bool checked)
-{
-    if (checked)
-    {
-        QCPGraph* selectedGraph = getSelectedGraph();
-
-        if (selectedGraph)
-        {
-
-            for (int i = 0; i < graphCentralView.graphCount(); i++)
-            {
-                if (selectedGraph == graphCentralView.graph(i))
-                    followedGraphIndex = i;
-            }
-        }
-        else if (graphCentralView.graphCount() > 0)
-        {
-            // If no graph is selected, select the first graph
-            followedGraphIndex = 0;
-        }
-    }
-    else
-    {
-        followedGraphIndex = -1;
-    }
-}
-
-void GraphModule::onClearClicked()
-{
-    for (QCPGraph* g : subsGraphs)
-    {
-        g->data()->clear();
-    }
-
-    replotAll();
-}
-
-void GraphModule::onStopClicked(bool checked)
-{
-    stopPlot = checked;
-
-    if (stopPlot)
-        updaterTimer.stop();
-    else
-    {
-        updaterTimer.start(updatePeriod);
-    }
-}
-
-QCPGraph* GraphModule::getSelectedGraph()
-{
-    QList<QCPGraph*> selection;
-    // if(graph)
-    selection = graphCentralView.selectedGraphs();
-    if (selection.size() > 0)
-    {
-        return selection.first();
-    }
-    return nullptr;
-}
-
-void GraphModule::centerView(const QCPGraph* graph)
-{
-    if (!graph->data()->isEmpty())
-    {
-        double lastKey   = (graph->data()->constEnd() - 1)->key;
-        double lastValue = (graph->data()->constEnd() - 1)->value;
-        double size_x    = graphCentralView.xAxis->range().size();
-        double size_y    = graphCentralView.yAxis->range().size();
-        graphCentralView.xAxis->setRange(lastKey, size_x,
-                                         Qt::AlignmentFlag::AlignRight);
-        graphCentralView.yAxis->setRange(lastValue, size_y,
-                                         Qt::AlignmentFlag::AlignCenter);
-    }
-}
-
-void GraphModule::replotAll()
-{
-    if (followedGraphIndex < graphCentralView.graphCount() &&
-        followedGraphIndex >= 0)
-    {
-        centerView(graphCentralView.graph(followedGraphIndex));
-    }
-    graphCentralView.replot();
-}
-
-void GraphModule::addCustomActionsToMenu()
-{
-    QAction* subscribe = new QAction("Subscribe");
-    connect(subscribe, &QAction::triggered, this,
-            &GraphModule::onSubscribeClicked);
-
-    QAction* clear = new QAction("Clear");
-    connect(clear, &QAction::triggered, this, &GraphModule::onClearClicked);
-
-    QAction* stop = new QAction("Stop");
-    stop->setCheckable(true);
-    stop->setChecked(stopPlot);
-    connect(stop, &QAction::triggered, this, &GraphModule::onStopClicked);
-
-    QAction* follow = new QAction("Follow");
-    follow->setCheckable(true);
-    if (followedGraphIndex >= 0)
-    {
-        follow->setChecked(true);
-    }
-    connect(follow, &QAction::triggered, this,
-            &GraphModule::setFollowedGraphIndex);
-
-    addActionToMenu(follow);
-    addActionToMenu(subscribe);
-    addActionToMenu(clear);
-    addActionToMenu(stop);
-}
diff --git a/Modules/Graph/graphmodule.h b/Modules/Graph/graphmodule.h
deleted file mode 100644
index 773e781252d418f4befaf23d8425af6c1bfe57a5..0000000000000000000000000000000000000000
--- a/Modules/Graph/graphmodule.h
+++ /dev/null
@@ -1,67 +0,0 @@
-#ifndef GRAPHMODULE_H
-#define GRAPHMODULE_H
-
-#include <Core/QCustomPlot/QCustomPlot.h>
-
-#include <QList>
-#include <QTimer>
-#include <QWidget>
-
-#include "Core/Message/modulemessage.h"
-#include "Core/Message/topicandfieldfilter.h"
-#include "Core/module.h"
-#include "Modules/DefaultModule/defaultmodule.h"
-
-namespace Ui
-{
-class GraphModule;
-}
-
-class GraphModule : public DefaultModule
-{
-    Q_OBJECT
-
-public:
-    explicit GraphModule(QWidget* parent = nullptr);
-    ~GraphModule();
-
-    QWidget* toWidget() override;
-    XmlObject toXmlObject() override;
-    void fromXmlObject(const XmlObject& xmlObject) override;
-
-    void centerView(const QCPGraph* graph);
-    void replotAll();
-
-public slots:
-    void onSubscribeClicked();
-    void onSubscriptionAdded(const TopicAndFieldFilter& filter);
-    void onSubscriptionRemoved(const TopicAndFieldFilter& filter);
-    void onMsgReceived(const ModuleMessage& msg);
-    void setFollowedGraphIndex(bool checked);
-    void onClearClicked();
-    void onStopClicked(bool checked);
-
-protected:
-    void buildCentralGraphView();
-    QCPGraph* instantiateNewGraph();
-    void addCustomActionsToMenu() override;
-    QCPGraph* getSelectedGraph();
-    void setTheme();
-    void onUpdateTimerTick();
-
-private:
-    Ui::GraphModule* ui;
-    QCustomPlot graphCentralView;
-
-    QList<TopicAndFieldFilter> subsFilters;
-    QList<QCPGraph*> subsGraphs;
-
-    int followedGraphIndex = -1;
-    QString dateFormat     = "%h:%m:%s (%z)";  //"HH:mm:ss\n(zzz)";
-    // QSharedPointer<QCPAxisTickerDateTime> dateTicker;
-    QTimer updaterTimer;
-    int updatePeriod = 1000;  // [ms]
-    bool stopPlot    = false;
-};
-
-#endif  // GRAPHMODULE_H
diff --git a/Modules/Graph/graphmodule.ui b/Modules/Graph/graphmodule.ui
deleted file mode 100644
index a3246f844523b0e2185072726e35d39a0cae2f72..0000000000000000000000000000000000000000
--- a/Modules/Graph/graphmodule.ui
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>GraphModule</class>
- <widget class="QWidget" name="GraphModule">
-  <property name="geometry">
-   <rect>
-    <x>0</x>
-    <y>0</y>
-    <width>400</width>
-    <height>300</height>
-   </rect>
-  </property>
-  <property name="windowTitle">
-   <string>Form</string>
-  </property>
-  <layout class="QVBoxLayout" name="verticalLayout_2">
-   <property name="leftMargin">
-    <number>0</number>
-   </property>
-   <property name="topMargin">
-    <number>0</number>
-   </property>
-   <property name="rightMargin">
-    <number>0</number>
-   </property>
-   <property name="bottomMargin">
-    <number>0</number>
-   </property>
-   <item>
-    <layout class="QVBoxLayout" name="centralLayout"/>
-   </item>
-  </layout>
- </widget>
- <resources/>
- <connections/>
-</ui>
diff --git a/Modules/IncomingMessagesViewer/incomingmessagesviewermodule.cpp b/Modules/IncomingMessagesViewer/incomingmessagesviewermodule.cpp
index afeb21faf01ab4fd6d8b26f0e1a5a85902dcf456..47300263e322d61e5a7c51cdebd0a91d65b467b2 100644
--- a/Modules/IncomingMessagesViewer/incomingmessagesviewermodule.cpp
+++ b/Modules/IncomingMessagesViewer/incomingmessagesviewermodule.cpp
@@ -127,7 +127,7 @@ void IncomingMessagesViewerModule::addCustomActionsToMenu()
     connect(manage, &QAction::triggered, this,
             [this]()
             {
-                auto* panel = new SubscriptionsPanel(&filters);
+                auto* panel = new SubscriptionsPanel(filters);
                 panel->setWindowTitle("IncomingMessagesViewer subscriptions");
                 connect(panel, &SubscriptionsPanel::SubscriptionAdded, this,
                         &IncomingMessagesViewerModule::addSubscription);
diff --git a/Modules/moduleslist.cpp b/Modules/moduleslist.cpp
index d50ed9862862ba32ca33e817385dd14cc655130e..0fb40f009506af33133b947edb6d85ac0a57fd2a 100644
--- a/Modules/moduleslist.cpp
+++ b/Modules/moduleslist.cpp
@@ -1,11 +1,11 @@
 #include "moduleslist.h"
 
 #include <Components/ModulesPicker/modulespicker.h>
-#include <Modules/CommandPad/commandpad.h>
+#include <Modules/CommandPad/CommandPad.h>
 #include <Modules/CompactCommandPad/CompactCommandPad.h>
 #include <Modules/Empty/emptymodule.h>
 #include <Modules/FileStream/filestreammodule.h>
-#include <Modules/Graph/graphmodule.h>
+#include <Modules/Graph/Graph.h>
 #include <Modules/IncomingMessagesViewer/incomingmessagesviewermodule.h>
 #include <Modules/Mavlink/mavlinkmodule.h>
 #include <Modules/Mavlink/mavlinkrocketmsgtestingmodule.h>
@@ -77,13 +77,11 @@ void ModulesList::createModuleList()
     addModuleInfo(testModule);
 #endif
 
-#ifdef GRAPHMODULE_H
     ModuleInfo graphModule(ModuleId::GRAPH, "Graph",
                            ModuleCategory::DATAVISUAL);
-    graphModule.setFactory([]() { return new GraphModule(); });
+    graphModule.setFactory([]() { return new Graph(); });
     graphModule.addModuleSourceFiles("Modules/Graph/");
     addModuleInfo(graphModule);
-#endif
 
 #ifdef OUTCOMINGMESSAGESVIEWERMODULE_H
     ModuleInfo outMsgViewer(ModuleId::OUTCOMINGMESSAGEVIEWER,
diff --git a/SkywardHub.pro b/SkywardHub.pro
index fb60c7a1b4b7e824b21ed2a69262ce3529846737..53d3865d59dd7fffa7b2cb2904c026e04711a8f0 100644
--- a/SkywardHub.pro
+++ b/SkywardHub.pro
@@ -38,7 +38,7 @@ SOURCES += \
     Modules/DefaultModule/defaultmodule.cpp \
     Modules/Empty/emptymodule.cpp \
     Modules/FileStream/filestreammodule.cpp \
-    Modules/Graph/graphmodule.cpp \
+    Modules/Graph/Graph.cpp \
     Modules/IncomingMessagesViewer/incomingmessagesviewermodule.cpp \
     Modules/MainWindow/skywardhubmainwindow.cpp \
     Modules/MainWindow/window.cpp \
@@ -95,7 +95,7 @@ HEADERS += \
     Modules/DefaultModule/defaultmodule.h \
     Modules/Empty/emptymodule.h \
     Modules/FileStream/filestreammodule.h \
-    Modules/Graph/graphmodule.h \
+    Modules/Graph/Graph.h \
     Modules/IncomingMessagesViewer/incomingmessagesviewermodule.h \
     Modules/MainWindow/skywardhubmainwindow.h \
     Modules/MainWindow/window.h \
@@ -130,7 +130,6 @@ FORMS += \
     Components/SubscriptionsPanel/subscriptionspanel.ui \
     Modules/Empty/emptymodule.ui \
     Modules/FileStream/filestreammodule.ui \
-    Modules/Graph/graphmodule.ui \
     Modules/MainWindow/skywardhubmainwindow.ui \
     Modules/MainWindow/window.ui \
     Modules/Mavlink/mavlinkrocketmsgtestingmodule.ui \