diff --git a/GUI/coregui/CMakeLists.txt b/GUI/coregui/CMakeLists.txt
index c1383c8a3883ea07928be4ffb8ba321d09dddf71..5548ba346542671447cefc5a003a25d5347f566d 100644
--- a/GUI/coregui/CMakeLists.txt
+++ b/GUI/coregui/CMakeLists.txt
@@ -38,6 +38,7 @@ set(include_dirs
     ${CMAKE_CURRENT_SOURCE_DIR}/Views/AccordionWidget
     ${CMAKE_CURRENT_SOURCE_DIR}/Views/ImportDataWidgets
     ${CMAKE_CURRENT_SOURCE_DIR}/Views/CommonWidgets
+    ${CMAKE_CURRENT_SOURCE_DIR}/Views/SpecularDataWidgets
 )
 
 if(BORNAGAIN_OPENGL)
diff --git a/GUI/coregui/Models/ItemCatalogue.cpp b/GUI/coregui/Models/ItemCatalogue.cpp
index bb6192b14242dc4c5e39e22681e6c28786baf839..efcaef3c9128410dbbbfcb95184a43d4b8fe2a5c 100644
--- a/GUI/coregui/Models/ItemCatalogue.cpp
+++ b/GUI/coregui/Models/ItemCatalogue.cpp
@@ -53,6 +53,7 @@
 #include "ResolutionFunctionItems.h"
 #include "RotationItems.h"
 #include "SimulationOptionsItem.h"
+#include "SpecularDataItem.h"
 #include "SphericalDetectorItem.h"
 #include "TransformationItem.h"
 #include "VectorItem.h"
@@ -164,6 +165,7 @@ ItemCatalogue::ItemCatalogue()
     add(Constants::JobItemType, create_new<JobItem>);
 
     add(Constants::IntensityDataType, create_new<IntensityDataItem>);
+    add(Constants::SpecularDataType, create_new<SpecularDataItem>);
 
     add(Constants::BasicAxisType, create_new<BasicAxisItem>);
     add(Constants::AmplitudeAxisType, create_new<AmplitudeAxisItem>);
diff --git a/GUI/coregui/Models/SpecularDataItem.cpp b/GUI/coregui/Models/SpecularDataItem.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..dc13a2a943cb511898dfaf82215aefe2b958739f
--- /dev/null
+++ b/GUI/coregui/Models/SpecularDataItem.cpp
@@ -0,0 +1,295 @@
+// ************************************************************************** //
+//
+//  BornAgain: simulate and fit scattering at grazing incidence
+//
+//! @file      GUI/coregui/Models/SpecularDataItem.cpp
+//! @brief     Implements class SpecularDataItem
+//!
+//! @homepage  http://www.bornagainproject.org
+//! @license   GNU General Public License v3 or higher (see COPYING)
+//! @copyright Forschungszentrum Jülich GmbH 2018
+//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
+//
+// ************************************************************************** //
+
+#include "SpecularDataItem.h"
+#include "AxesItems.h"
+#include "BornAgainNamespace.h"
+#include "ComboProperty.h"
+#include "GUIHelpers.h"
+#include "BornAgainNamespace.h"
+
+const QString SpecularDataItem::P_AXES_UNITS = "Axes Units";
+const QString SpecularDataItem::P_TITLE = "Title";
+const QString SpecularDataItem::P_XAXIS = "x-axis";
+const QString SpecularDataItem::P_YAXIS = "y-axis";
+const QString SpecularDataItem::P_FILE_NAME = "FileName";
+
+SpecularDataItem::SpecularDataItem() : SessionItem(Constants::SpecularDataType)
+{
+    ComboProperty units = ComboProperty() << Constants::UnitsNbins;
+    addProperty(P_AXES_UNITS, units.variant());
+
+    addProperty(P_TITLE, QString())->setVisible(false);
+
+    SessionItem* item = addGroupProperty(P_XAXIS, Constants::BasicAxisType);
+    item->getItem(BasicAxisItem::P_NBINS)->setVisible(false);
+
+    item = addGroupProperty(P_YAXIS, Constants::AmplitudeAxisType);
+    item->getItem(BasicAxisItem::P_NBINS)->setVisible(false);
+    item->getItem(BasicAxisItem::P_TITLE)->setVisible(true);
+
+    item = item->getItem(AmplitudeAxisItem::P_IS_VISIBLE);
+    item->setValue(true);
+    item->setVisible(false);
+
+    setXaxisTitle("X [nbins]");
+    setYaxisTitle("Signal [a.u.]");
+
+    // name of the file used to serialize given SpecularDataItem
+    addProperty(P_FILE_NAME, QStringLiteral("undefined"))->setVisible(false);
+
+    mapper()->setOnPropertyChange([this](const QString& name)
+    {
+        if(name == P_FILE_NAME)
+            setLastModified(QDateTime::currentDateTime());
+    });
+
+    mapper()->setOnValueChange([this]()
+    {
+        // OutputData was modified
+        setLastModified(QDateTime::currentDateTime());
+    });
+}
+
+void SpecularDataItem::setOutputData(OutputData<double>* data)
+{
+    Q_ASSERT(data);
+    m_data.reset(data);
+
+    updateAxesZoomLevel();
+    updateAxesLabels();
+    computeDataRange();
+
+    emitDataChanged();
+}
+
+//! Sets the raw data vector from external source
+
+void SpecularDataItem::setRawDataVector(const OutputData<double>* data)
+{
+    if (!m_data->hasSameDimensions(*data)) {
+        throw GUIHelpers::Error("SpecularDataItem::setRawDataVector() -> Error. "
+                                "Different dimensions of data.");
+    }
+    m_data->setRawDataVector(data->getRawDataVector());
+    emitDataChanged();
+}
+
+int SpecularDataItem::getNbins() const
+{
+    return xAxisItem()->getItemValue(BasicAxisItem::P_NBINS).toInt();
+}
+
+double SpecularDataItem::getLowerX() const
+{
+    return getItem(P_XAXIS)->getItemValue(BasicAxisItem::P_MIN).toDouble();
+}
+
+double SpecularDataItem::getUpperX() const
+{
+    return getItem(P_XAXIS)->getItemValue(BasicAxisItem::P_MAX).toDouble();
+}
+
+double SpecularDataItem::getXmin() const
+{
+    const double defaultXmin(0.0);
+    return m_data ? m_data->getAxis(BornAgain::X_AXIS_INDEX).getMin() : defaultXmin;
+}
+
+double SpecularDataItem::getXmax() const
+{
+    const double defaultXmax(1.0);
+    return m_data ? m_data->getAxis(BornAgain::X_AXIS_INDEX).getMax() : defaultXmax;
+}
+
+double SpecularDataItem::getLowerY() const
+{
+    return getItem(P_YAXIS)->getItemValue(BasicAxisItem::P_MIN).toDouble();
+}
+
+double SpecularDataItem::getUpperY() const
+{
+    return getItem(P_YAXIS)->getItemValue(BasicAxisItem::P_MAX).toDouble();
+}
+
+double SpecularDataItem::getYmin() const
+{
+    const double defaultYmin(0.0);
+    auto limits = dataRange();
+    return m_data ? limits.first : defaultYmin;
+}
+
+double SpecularDataItem::getYmax() const
+{
+    const double defaultYmax(1.0);
+    auto limits = dataRange();
+    return m_data ? limits.second : defaultYmax;
+}
+
+bool SpecularDataItem::isLog() const
+{
+    return getItem(P_YAXIS)->getItemValue(AmplitudeAxisItem::P_IS_LOGSCALE).toBool();
+}
+
+QString SpecularDataItem::getXaxisTitle() const
+{
+    return getItem(P_XAXIS)->getItemValue(BasicAxisItem::P_TITLE).toString();
+}
+
+QString SpecularDataItem::getYaxisTitle() const
+{
+    return getItem(P_YAXIS)->getItemValue(BasicAxisItem::P_TITLE).toString();
+}
+
+QString SpecularDataItem::selectedAxesUnits() const
+{
+    ComboProperty combo = getItemValue(SpecularDataItem::P_AXES_UNITS).value<ComboProperty>();
+    return combo.getValue();
+}
+
+QString SpecularDataItem::fileName(const QString& projectDir) const
+{
+    QString filename = getItemValue(SpecularDataItem::P_FILE_NAME).toString();
+    return projectDir.isEmpty() ? filename : projectDir + QStringLiteral("/") + filename;
+}
+
+void SpecularDataItem::setLowerX(double xmin)
+{
+    getItem(P_XAXIS)->setItemValue(BasicAxisItem::P_MIN, xmin);
+}
+
+void SpecularDataItem::setUpperX(double xmax)
+{
+    getItem(P_XAXIS)->setItemValue(BasicAxisItem::P_MAX, xmax);
+}
+
+void SpecularDataItem::setLowerY(double ymin)
+{
+    getItem(P_YAXIS)->setItemValue(AmplitudeAxisItem::P_MIN, ymin);
+}
+
+void SpecularDataItem::setUpperY(double ymax)
+{
+    getItem(P_YAXIS)->setItemValue(AmplitudeAxisItem::P_MAX, ymax);
+}
+
+void SpecularDataItem::setLog(bool log_flag)
+{
+    getItem(P_YAXIS)->setItemValue(AmplitudeAxisItem::P_IS_LOGSCALE, log_flag);
+}
+
+void SpecularDataItem::setXaxisTitle(QString xtitle)
+{
+    getItem(P_XAXIS)->setItemValue(BasicAxisItem::P_TITLE, xtitle);
+}
+
+void SpecularDataItem::setYaxisTitle(QString ytitle)
+{
+    getItem(P_YAXIS)->setItemValue(AmplitudeAxisItem::P_TITLE, ytitle);
+}
+
+//! set zoom range of x,y axes to axes of input data
+void SpecularDataItem::setAxesRangeToData()
+{
+    setLowerX(getXmin());
+    setUpperX(getXmax());
+    setLowerY(getYmin());
+    setUpperY(getYmax());
+}
+
+//! Sets zoom range of X,Y axes, if it was not yet defined.
+
+void SpecularDataItem::updateAxesZoomLevel()
+{
+    // set zoom range of x-axis to min, max values if it was not set already
+    if (getUpperX() < getLowerX()) {
+        setLowerX(getXmin());
+        setUpperX(getXmax());
+    }
+
+    // set zoom range of y-axis to min, max values if it was not set already
+    if (getUpperY() < getLowerY()) {
+        setLowerY(getYmin());
+        setUpperY(getYmax());
+    }
+
+    const int nx = static_cast<int>(m_data->getAxis(BornAgain::X_AXIS_INDEX).size());
+    xAxisItem()->setItemValue(BasicAxisItem::P_NBINS, nx);
+}
+
+//! Init axes labels, if it was not done already.
+
+void SpecularDataItem::updateAxesLabels()
+{
+    if (getXaxisTitle().isEmpty())
+        setXaxisTitle(QString::fromStdString(m_data->getAxis(BornAgain::X_AXIS_INDEX).getName()));
+}
+
+void SpecularDataItem::computeDataRange()
+{
+    QPair<double, double> minmax = dataRange();
+    setLowerY(minmax.first);
+    setUpperY(minmax.second);
+}
+
+//! Init ymin, ymax to match the intensity values range.
+QPair<double, double> SpecularDataItem::dataRange() const
+{
+    const OutputData<double>* data = getOutputData();
+    double min(*std::min_element(data->begin(), data->end()));
+    double max(*std::max_element(data->begin(), data->end()));
+    if (isLog()) {
+        min /= 2.0;
+        max *= 2.0;
+    } else {
+        max = max * 1.1;
+    }
+
+    return QPair<double, double>(min, max);
+}
+
+const BasicAxisItem* SpecularDataItem::xAxisItem() const
+{
+    return dynamic_cast<const BasicAxisItem*>(getItem(P_XAXIS));
+}
+
+BasicAxisItem* SpecularDataItem::xAxisItem()
+{
+    return const_cast<BasicAxisItem*>(static_cast<const SpecularDataItem*>(this)->xAxisItem());
+}
+
+const AmplitudeAxisItem* SpecularDataItem::yAxisItem() const
+{
+    auto result = dynamic_cast<const AmplitudeAxisItem*>(getItem(P_YAXIS));
+    Q_ASSERT(result);
+    return result;
+}
+
+//! Set axes viewport to original data.
+
+void SpecularDataItem::resetView()
+{
+    setAxesRangeToData();
+    computeDataRange();
+}
+
+QDateTime SpecularDataItem::lastModified() const
+{
+    return m_last_modified;
+}
+
+void SpecularDataItem::setLastModified(const QDateTime &dtime)
+{
+    m_last_modified = dtime;
+}
diff --git a/GUI/coregui/Models/SpecularDataItem.h b/GUI/coregui/Models/SpecularDataItem.h
new file mode 100644
index 0000000000000000000000000000000000000000..2912ac3012e0a72e35373f660d93181cdcc9571c
--- /dev/null
+++ b/GUI/coregui/Models/SpecularDataItem.h
@@ -0,0 +1,101 @@
+// ************************************************************************** //
+//
+//  BornAgain: simulate and fit scattering at grazing incidence
+//
+//! @file      GUI/coregui/Models/SpecularDataItem.h
+//! @brief     Defines class SpecularDataItem
+//!
+//! @homepage  http://www.bornagainproject.org
+//! @license   GNU General Public License v3 or higher (see COPYING)
+//! @copyright Forschungszentrum Jülich GmbH 2018
+//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
+//
+// ************************************************************************** //
+
+#ifndef SPECULARDATAITEM_H
+#define SPECULARDATAITEM_H
+
+#include "SessionItem.h"
+#include "OutputData.h"
+#include <QDateTime>
+
+class AmplitudeAxisItem;
+class BasicAxisItem;
+class MaskContainerItem;
+class ProjectionContainerItem;
+
+class BA_CORE_API_ SpecularDataItem : public SessionItem
+{
+public:
+    static const QString P_TITLE;
+    static const QString P_AXES_UNITS;
+    static const QString P_XAXIS;
+    static const QString P_YAXIS;
+    static const QString P_FILE_NAME;
+
+    SpecularDataItem();
+
+    OutputData<double>* getOutputData() { return m_data.get(); }
+    const OutputData<double>* getOutputData() const { return m_data.get(); }
+    void setOutputData(OutputData<double>* data);
+    void setRawDataVector(const OutputData<double>* data);
+
+    //! Number of bins in data
+    int getNbins() const;
+
+    //! returns lower and upper zoom ranges of x-axis
+    double getLowerX() const;
+    double getUpperX() const;
+
+    //! returns min and max range of x-axis as given by IntensityData
+    double getXmin() const;
+    double getXmax() const;
+
+    //! returns lower and upper zoom ranges of y-axis
+    double getLowerY() const;
+    double getUpperY() const;
+
+    //! returns min and max range of y-axis as given by IntensityData
+    double getYmin() const;
+    double getYmax() const;
+
+    bool isLog() const;
+    QString getXaxisTitle() const;
+    QString getYaxisTitle() const;
+
+    QString selectedAxesUnits() const;
+
+    QString fileName(const QString& projectDir = QString()) const;
+
+    void updateDataRange();
+    void computeDataRange();
+    QPair<double, double> dataRange() const;
+
+    const BasicAxisItem* xAxisItem() const;
+    BasicAxisItem* xAxisItem();
+    const AmplitudeAxisItem* yAxisItem() const;
+
+    void resetView();
+
+    QDateTime lastModified() const;
+    void setLastModified(const QDateTime& dtime);
+
+public slots:
+    void setLowerX(double xmin);
+    void setUpperX(double xmax);
+    void setLowerY(double ymin);
+    void setUpperY(double ymax);
+    void setLog(bool log_flag);
+    void setXaxisTitle(QString xtitle);
+    void setYaxisTitle(QString ytitle);
+    void setAxesRangeToData();
+
+private:
+    void updateAxesZoomLevel();
+    void updateAxesLabels();
+
+    std::unique_ptr<OutputData<double>> m_data; //!< simulation results
+    QDateTime m_last_modified;
+};
+
+#endif // SPECULARDATAITEM_H
diff --git a/GUI/coregui/Models/item_constants.h b/GUI/coregui/Models/item_constants.h
index 6fc68656a053b1c99df102eebcd922a126bf3f5d..6fae1f0802f3b29a0b9b47a8a0609f44c5e5d473 100644
--- a/GUI/coregui/Models/item_constants.h
+++ b/GUI/coregui/Models/item_constants.h
@@ -135,6 +135,7 @@ const ModelType FitSuiteType = "FitSuite";
 
 const ModelType JobItemType = "JobItem";
 const ModelType IntensityDataType = "IntensityData";
+const ModelType SpecularDataType = "SpecularData";
 
 const ModelType BasicAxisType = "BasicAxis";
 const ModelType AmplitudeAxisType = "AmplitudeAxis";
diff --git a/GUI/coregui/Views/IntensityDataWidgets/IntensityDataCanvas.cpp b/GUI/coregui/Views/IntensityDataWidgets/IntensityDataCanvas.cpp
index c72a033346632b6c52d3990a04a3f0261315c45e..e0ad5b9fdb333fd7260f8750965dd5562309f360 100644
--- a/GUI/coregui/Views/IntensityDataWidgets/IntensityDataCanvas.cpp
+++ b/GUI/coregui/Views/IntensityDataWidgets/IntensityDataCanvas.cpp
@@ -77,7 +77,7 @@ void IntensityDataCanvas::onSavePlotAction()
 {
     QString dirname = AppSvc::projectManager()->userExportDir();
     SavePlotAssistant saveAssistant;
-    saveAssistant.savePlot(dirname, m_colorMap->customPlot(), intensityDataItem());
+    saveAssistant.savePlot(dirname, m_colorMap->customPlot(), intensityDataItem()->getOutputData());
 }
 
 void IntensityDataCanvas::onMousePress(QMouseEvent* event)
diff --git a/GUI/coregui/Views/IntensityDataWidgets/SavePlotAssistant.cpp b/GUI/coregui/Views/IntensityDataWidgets/SavePlotAssistant.cpp
index 5a2b0734f9e825f15e60cb9ff68c15e400d8482f..4891648f2457ba4c3927704287d5a09907e102d1 100644
--- a/GUI/coregui/Views/IntensityDataWidgets/SavePlotAssistant.cpp
+++ b/GUI/coregui/Views/IntensityDataWidgets/SavePlotAssistant.cpp
@@ -15,7 +15,6 @@
 #include "SavePlotAssistant.h"
 #include "ColorMap.h"
 #include "IntensityDataIOFactory.h"
-#include "IntensityDataItem.h"
 #include <QFileDialog>
 #include <QMessageBox>
 
@@ -51,8 +50,8 @@ SavePlotAssistant::Format::Format(const QString &file_extention, const QString &
 
 }
 
-void SavePlotAssistant::savePlot(const QString &dirname, QCustomPlot *plot,
-                                 IntensityDataItem *item)
+void SavePlotAssistant::savePlot(const QString& dirname, QCustomPlot* plot,
+                                 OutputData<double>* output_data)
 
 {
     QString selectedFilter("*.png");
@@ -64,7 +63,7 @@ void SavePlotAssistant::savePlot(const QString &dirname, QCustomPlot *plot,
 
     if(!nameToSave.isEmpty()) {
         try {
-            saveToFile(nameToSave, plot, item);
+            saveToFile(nameToSave, plot, output_data);
         } catch(const std::exception &ex) {
             QString message = "Attempt to save file with the name '";
             message.append(nameToSave);
@@ -76,7 +75,8 @@ void SavePlotAssistant::savePlot(const QString &dirname, QCustomPlot *plot,
 
 }
 
-void SavePlotAssistant::saveToFile(const QString &fileName, QCustomPlot *plot, IntensityDataItem *item)
+void SavePlotAssistant::saveToFile(const QString& fileName, QCustomPlot* plot,
+                                   OutputData<double>* output_data)
 {
     if(isPngFile(fileName)) {
         plot->savePng(fileName);
@@ -91,8 +91,8 @@ void SavePlotAssistant::saveToFile(const QString &fileName, QCustomPlot *plot, I
     }
 
     else {
-        IntensityDataIOFactory::writeOutputData(*item->getOutputData(),
-                                                fileName.toStdString());
+        Q_ASSERT(output_data);
+        IntensityDataIOFactory::writeOutputData(*output_data, fileName.toStdString());
     }
 }
 
diff --git a/GUI/coregui/Views/IntensityDataWidgets/SavePlotAssistant.h b/GUI/coregui/Views/IntensityDataWidgets/SavePlotAssistant.h
index eb09ead99710df4ead533484604d0cf118d075ab..6504987bc2dfa4368de1a72915bdbb089f9ba6a5 100644
--- a/GUI/coregui/Views/IntensityDataWidgets/SavePlotAssistant.h
+++ b/GUI/coregui/Views/IntensityDataWidgets/SavePlotAssistant.h
@@ -20,7 +20,7 @@
 #include <QVector>
 
 class QCustomPlot;
-class IntensityDataItem;
+template <class T> class OutputData;
 
 //! Assistant class which contains all logic for saving IntensityData to various formats
 //! from IntensityDataPlotWidget.
@@ -36,10 +36,10 @@ public:
         QString m_filter;
     };
 
-    void savePlot(const QString &dirname, QCustomPlot *plot, IntensityDataItem *item);
+    void savePlot(const QString& dirname, QCustomPlot* plot, OutputData<double>* output_data);
 
 private:
-    void saveToFile(const QString &dirname, QCustomPlot *plot, IntensityDataItem *item);
+    void saveToFile(const QString& dirname, QCustomPlot* plot, OutputData<double>* output_data);
     QString getFilterString() const;
     QString composeFileName(const QString &fileName, const QString &filterName) const;
     bool isValidExtension(const QString &fileName) const;
diff --git a/GUI/coregui/Views/MaskWidgets/MaskEditorCanvas.cpp b/GUI/coregui/Views/MaskWidgets/MaskEditorCanvas.cpp
index 58449ba4ad901e08f502db3a91366fd48656633b..2037688765985a2002bb2376879bd5a58d6853de 100644
--- a/GUI/coregui/Views/MaskWidgets/MaskEditorCanvas.cpp
+++ b/GUI/coregui/Views/MaskWidgets/MaskEditorCanvas.cpp
@@ -96,7 +96,8 @@ void MaskEditorCanvas::onSavePlotRequest()
     QString dirname = AppSvc::projectManager()->userExportDir();
 
     SavePlotAssistant saveAssistant;
-    saveAssistant.savePlot(dirname, m_scene->colorMap()->customPlot(), m_intensityDataItem);
+    saveAssistant.savePlot(dirname, m_scene->colorMap()->customPlot(),
+                           m_intensityDataItem->getOutputData());
 }
 
 void MaskEditorCanvas::onResetViewRequest()
diff --git a/GUI/coregui/Views/SessionModelView.cpp b/GUI/coregui/Views/SessionModelView.cpp
index b3468e642872d057ab64bbc8a5153b4fadba27be..4663862a98fd44730d0459d27d50d302cdaded4e 100644
--- a/GUI/coregui/Views/SessionModelView.cpp
+++ b/GUI/coregui/Views/SessionModelView.cpp
@@ -27,7 +27,7 @@
 #include <QVBoxLayout>
 
 namespace {
-const bool show_test_view = false;
+const bool show_test_view = true;
 }
 
 SessionModelView::SessionModelView(MainWindow *mainWindow)
diff --git a/GUI/coregui/Views/SpecularDataWidgets/SpecularDataCanvas.cpp b/GUI/coregui/Views/SpecularDataWidgets/SpecularDataCanvas.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ed5261c21eb3844beabacb56e78bf5f01f1e7bdd
--- /dev/null
+++ b/GUI/coregui/Views/SpecularDataWidgets/SpecularDataCanvas.cpp
@@ -0,0 +1,98 @@
+// ************************************************************************** //
+//
+//  BornAgain: simulate and fit scattering at grazing incidence
+//
+//! @file      GUI/coregui/Views/SpecularDataWidgets/SpecularDataCanvas.cpp
+//! @brief     Implements class SpecularDataCanvas
+//!
+//! @homepage  http://www.bornagainproject.org
+//! @license   GNU General Public License v3 or higher (see COPYING)
+//! @copyright Forschungszentrum Jülich GmbH 2018
+//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
+//
+// ************************************************************************** //
+
+#include "AppSvc.h"
+#include "projectmanager.h"
+#include "SavePlotAssistant.h"
+#include "SpecularDataCanvas.h"
+#include "SpecularDataItem.h"
+#include "SpecularPlotCanvas.h"
+#include "plot_constants.h"
+#include "qcustomplot.h"
+
+SpecularDataCanvas::SpecularDataCanvas(QWidget* parent)
+    : SessionItemWidget(parent)
+    , m_plot_canvas(new SpecularPlotCanvas)
+    , m_reset_view_action(nullptr)
+    , m_save_plot_action(nullptr)
+
+{
+    QVBoxLayout* vlayout = new QVBoxLayout(this);
+    vlayout->setMargin(0);
+    vlayout->setSpacing(0);
+    vlayout->setContentsMargins(0, 0, 0, 0);
+    vlayout->addWidget(m_plot_canvas);
+    setLayout(vlayout);
+    setStyleSheet("background-color:white;");
+
+    initActions();
+
+    connect(m_plot_canvas->customPlot(), &QCustomPlot::mousePress, this,
+            &SpecularDataCanvas::onMousePress, Qt::UniqueConnection);
+}
+
+void SpecularDataCanvas::setItem(SessionItem* intensityItem)
+{
+    SessionItemWidget::setItem(intensityItem);
+    m_plot_canvas->setItem(intensityItem);
+}
+
+QSize SpecularDataCanvas::sizeHint() const { return QSize(500, 400); }
+
+QSize SpecularDataCanvas::minimumSizeHint() const { return QSize(128, 128); }
+
+QList<QAction*> SpecularDataCanvas::actionList()
+{
+    return QList<QAction*>() << m_reset_view_action << m_save_plot_action;
+}
+
+void SpecularDataCanvas::onResetViewAction() { specularDataItem()->resetView(); }
+
+void SpecularDataCanvas::onSavePlotAction()
+{
+    QString dirname = AppSvc::projectManager()->userExportDir();
+    SavePlotAssistant saveAssistant;
+    saveAssistant.savePlot(dirname, m_plot_canvas->customPlot(),
+                           specularDataItem()->getOutputData());
+}
+
+void SpecularDataCanvas::onMousePress(QMouseEvent* event)
+{
+    if (event->button() == Qt::RightButton)
+        emit customContextMenuRequested(event->globalPos());
+}
+
+SpecularDataItem* SpecularDataCanvas::specularDataItem()
+{
+    SpecularDataItem* result = dynamic_cast<SpecularDataItem*>(currentItem());
+    Q_ASSERT(result);
+    return result;
+}
+
+// TODO: try to reuse IntensityDataCanvas::initActions somehow
+void SpecularDataCanvas::initActions()
+{
+    m_reset_view_action = new QAction(this);
+    m_reset_view_action->setText("Reset");
+    m_reset_view_action->setIcon(QIcon(":/images/toolbar16light_refresh.svg"));
+    m_reset_view_action->setToolTip("Reset view\n"
+                                    "x,y axes range will be set to default");
+    connect(m_reset_view_action, &QAction::triggered, this, &SpecularDataCanvas::onResetViewAction);
+
+    m_save_plot_action = new QAction(this);
+    m_save_plot_action->setText("Save");
+    m_save_plot_action->setIcon(QIcon(":/images/toolbar16light_save.svg"));
+    m_save_plot_action->setToolTip("Save plot");
+    connect(m_save_plot_action, &QAction::triggered, this, &SpecularDataCanvas::onSavePlotAction);
+}
diff --git a/GUI/coregui/Views/SpecularDataWidgets/SpecularDataCanvas.h b/GUI/coregui/Views/SpecularDataWidgets/SpecularDataCanvas.h
new file mode 100644
index 0000000000000000000000000000000000000000..31d963cb617b5d23fa3f40c68e22b05a8e2a28af
--- /dev/null
+++ b/GUI/coregui/Views/SpecularDataWidgets/SpecularDataCanvas.h
@@ -0,0 +1,52 @@
+// ************************************************************************** //
+//
+//  BornAgain: simulate and fit scattering at grazing incidence
+//
+//! @file      GUI/coregui/Views/SpecularDataWidgets/SpecularDataCanvas.h
+//! @brief     Defines class SpecularDataCanvas
+//!
+//! @homepage  http://www.bornagainproject.org
+//! @license   GNU General Public License v3 or higher (see COPYING)
+//! @copyright Forschungszentrum Jülich GmbH 2018
+//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
+//
+// ************************************************************************** //
+
+#ifndef SPECULARDATACANVAS_H
+#define SPECULARDATACANVAS_H
+
+#include "SessionItemWidget.h"
+#include "WinDllMacros.h"
+#include <QWidget>
+
+class SpecularDataItem;
+class SpecularPlotCanvas;
+
+class BA_CORE_API_ SpecularDataCanvas : public SessionItemWidget
+{
+    Q_OBJECT
+public:
+    explicit SpecularDataCanvas(QWidget* parent = nullptr);
+
+    void setItem(SessionItem* intensityItem) override;
+
+    QSize sizeHint() const override;
+    QSize minimumSizeHint() const override;
+
+    QList<QAction*> actionList() override;
+
+public slots:
+    void onResetViewAction();
+    void onSavePlotAction();
+    void onMousePress(QMouseEvent* event);
+
+private:
+    SpecularDataItem* specularDataItem();
+    void initActions();
+
+    SpecularPlotCanvas* m_plot_canvas;
+    QAction* m_reset_view_action;
+    QAction* m_save_plot_action;
+};
+
+#endif // SPECULARDATACANVAS_H
diff --git a/GUI/coregui/Views/SpecularDataWidgets/SpecularDataWidget.cpp b/GUI/coregui/Views/SpecularDataWidgets/SpecularDataWidget.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..bf45393d6af01fddd9195077f860ffaf8cd84acb
--- /dev/null
+++ b/GUI/coregui/Views/SpecularDataWidgets/SpecularDataWidget.cpp
@@ -0,0 +1,80 @@
+// ************************************************************************** //
+//
+//  BornAgain: simulate and fit scattering at grazing incidence
+//
+//! @file      GUI/coregui/Views/SpecularDataWidgets/SpecularDataWidget.cpp
+//! @brief     Implements class SpecularDataWidget
+//!
+//! @homepage  http://www.bornagainproject.org
+//! @license   GNU General Public License v3 or higher (see COPYING)
+//! @copyright Forschungszentrum Jülich GmbH 2018
+//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
+//
+// ************************************************************************** //
+
+#include "SpecularDataWidget.h"
+#include "SpecularDataItem.h"
+#include "SpecularDataCanvas.h"
+#include "JobItem.h"
+#include "IntensityDataItemUtils.h"
+#include "IntensityDataPropertyWidget.h"
+#include <QBoxLayout>
+#include <QMenu>
+
+SpecularDataWidget::SpecularDataWidget(QWidget* parent)
+    : SessionItemWidget(parent)
+    , m_intensity_canvas(new SpecularDataCanvas)
+    , m_property_widget(new IntensityDataPropertyWidget)
+{
+    setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+
+    auto hlayout = new QHBoxLayout;
+    hlayout->setMargin(0);
+    hlayout->setSpacing(0);
+    hlayout->addWidget(m_intensity_canvas);
+    hlayout->addWidget(m_property_widget);
+
+    auto mainLayout = new QVBoxLayout;
+    mainLayout->setMargin(0);
+    mainLayout->setSpacing(0);
+    mainLayout->addLayout(hlayout);
+
+    setLayout(mainLayout);
+
+    connect(m_intensity_canvas, &SpecularDataCanvas::customContextMenuRequested, this,
+            &SpecularDataWidget::onContextMenuRequest);
+
+    m_property_widget->setVisible(false);
+}
+
+void SpecularDataWidget::setItem(SessionItem* jobItem)
+{
+    SessionItemWidget::setItem(jobItem);
+    m_intensity_canvas->setItem(specularDataItem());
+    m_property_widget->setItem(specularDataItem());
+}
+
+QList<QAction*> SpecularDataWidget::actionList()
+{
+    return m_intensity_canvas->actionList() + m_property_widget->actionList();
+}
+
+void SpecularDataWidget::onContextMenuRequest(const QPoint& point)
+{
+    QMenu menu;
+    for (auto action : actionList())
+        menu.addAction(action);
+    menu.exec(point);
+}
+
+SpecularDataItem* SpecularDataWidget::specularDataItem()
+{
+    // temporarily commented out
+    // return IntensityDataItemUtils::intensityDataItem(currentItem());
+
+    // temporary solution, should be removed after starting to use
+    // JobItem
+    SpecularDataItem* result = dynamic_cast<SpecularDataItem*>(currentItem());
+    Q_ASSERT(result);
+    return result;
+}
diff --git a/GUI/coregui/Views/SpecularDataWidgets/SpecularDataWidget.h b/GUI/coregui/Views/SpecularDataWidgets/SpecularDataWidget.h
new file mode 100644
index 0000000000000000000000000000000000000000..7ee6a42ca64392f325ac10cc1b8d2a7cf3ee0aa0
--- /dev/null
+++ b/GUI/coregui/Views/SpecularDataWidgets/SpecularDataWidget.h
@@ -0,0 +1,45 @@
+// ************************************************************************** //
+//
+//  BornAgain: simulate and fit scattering at grazing incidence
+//
+//! @file      GUI/coregui/Views/SpecularDataWidgets/SpecularDataWidget.h
+//! @brief     Defines class SpecularDataWidget
+//!
+//! @homepage  http://www.bornagainproject.org
+//! @license   GNU General Public License v3 or higher (see COPYING)
+//! @copyright Forschungszentrum Jülich GmbH 2018
+//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
+//
+// ************************************************************************** //
+
+#ifndef SPECULARDATAWIDGET_H
+#define SPECULARDATAWIDGET_H
+
+#include "SessionItemWidget.h"
+
+class SpecularDataCanvas;
+class SpecularDataItem;
+class IntensityDataPropertyWidget;
+
+class SpecularDataWidget : public SessionItemWidget
+{
+    Q_OBJECT
+
+public:
+    SpecularDataWidget(QWidget* parent = nullptr);
+
+    void setItem(SessionItem* jobItem);
+
+    QList<QAction*> actionList();
+
+private slots:
+    void onContextMenuRequest(const QPoint& point);
+
+private:
+    SpecularDataItem* specularDataItem();
+
+    SpecularDataCanvas* m_intensity_canvas;
+    IntensityDataPropertyWidget* m_property_widget;
+};
+
+#endif // SPECULARDATAWIDGET_H
diff --git a/GUI/coregui/Views/SpecularDataWidgets/SpecularPlot.cpp b/GUI/coregui/Views/SpecularDataWidgets/SpecularPlot.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5e8a4c928b7fccc574a85b22cc15f90a6eb71473
--- /dev/null
+++ b/GUI/coregui/Views/SpecularDataWidgets/SpecularPlot.cpp
@@ -0,0 +1,275 @@
+// ************************************************************************** //
+//
+//  BornAgain: simulate and fit scattering at grazing incidence
+//
+//! @file      GUI/coregui/Views/SpecularDataWidgets/SpecularPlot.cpp
+//! @brief     Implements class SpecularPlot
+//!
+//! @homepage  http://www.bornagainproject.org
+//! @license   GNU General Public License v3 or higher (see COPYING)
+//! @copyright Forschungszentrum Jülich GmbH 2018
+//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
+//
+// ************************************************************************** //
+
+#include "SpecularPlot.h"
+#include "AxesItems.h"
+#include "ColorMapUtils.h"
+#include "SpecularPlotEvent.h"
+#include "MathConstants.h"
+#include "SpecularDataItem.h"
+#include "UpdateTimer.h"
+#include "plot_constants.h"
+
+namespace {
+const int replot_update_interval = 10;
+}
+
+SpecularPlot::SpecularPlot(QWidget* parent)
+    : SessionItemWidget(parent)
+    , m_custom_plot(new QCustomPlot)
+    , m_update_timer(new UpdateTimer(replot_update_interval, this))
+    , m_plot_event(new SpecularPlotEvent(this))
+    , m_block_update(true)
+{
+    initPlot();
+
+    QVBoxLayout* vlayout = new QVBoxLayout(this);
+    vlayout->setMargin(0);
+    vlayout->setSpacing(0);
+    vlayout->addWidget(m_custom_plot);
+    setLayout(vlayout);
+
+    setMouseTrackingEnabled(true);
+}
+
+void SpecularPlot::setMouseTrackingEnabled(bool enable)
+{
+    m_plot_event->setMouseTrackingEnabled(enable);
+}
+
+void SpecularPlot::setLog(bool log)
+{
+    ColorMapUtils::setLogz(m_custom_plot->yAxis, log);
+}
+
+void SpecularPlot::resetView()
+{
+    specularItem()->resetView();
+}
+
+void SpecularPlot::onPropertyChanged(const QString& property_name)
+{
+    if (m_block_update)
+        return;
+
+    if (property_name == SpecularDataItem::P_AXES_UNITS) {
+        setAxesRangeFromItem(specularItem());
+        replot();
+    }
+}
+
+void SpecularPlot::onXaxisRangeChanged(QCPRange newRange)
+{
+    m_block_update = true;
+    specularItem()->setLowerX(newRange.lower);
+    specularItem()->setUpperX(newRange.upper);
+    m_block_update = false;
+}
+
+void SpecularPlot::onYaxisRangeChanged(QCPRange newRange)
+{
+    m_block_update = true;
+    specularItem()->setLowerY(newRange.lower);
+    specularItem()->setUpperY(newRange.upper);
+    m_block_update = false;
+}
+
+void SpecularPlot::onTimeToReplot()
+{
+    m_custom_plot->replot();
+}
+
+void SpecularPlot::subscribeToItem()
+{
+    setPlotFromItem(specularItem());
+
+    specularItem()->mapper()->setOnPropertyChange(
+        [this](const QString& name) { onPropertyChanged(name); }, this);
+
+    specularItem()->mapper()->setOnChildPropertyChange(
+        [this](SessionItem* item, const QString name) {
+            if(item->modelType() == Constants::BasicAxisType ||
+               item->modelType() == Constants::AmplitudeAxisType)
+                modifyAxesProperties(item->itemName(), name);
+        },
+        this);
+
+    specularItem()->mapper()->setOnValueChange([this]() { setPlotFromItem(this->specularItem()); },
+                                               this);
+
+    setConnected(true);
+}
+
+void SpecularPlot::unsubscribeFromItem()
+{
+    setConnected(false);
+}
+
+void SpecularPlot::initPlot()
+{
+    m_custom_plot->addGraph();
+
+    QPen pen(QColor(0, 0, 255, 200));
+    m_custom_plot->graph()->setLineStyle(QCPGraph::lsLine);
+    m_custom_plot->graph()->setPen(pen);
+
+    m_custom_plot->xAxis->setTickLabelFont(
+        QFont(QFont().family(), Constants::plot_tick_label_size));
+    m_custom_plot->yAxis->setTickLabelFont(
+        QFont(QFont().family(), Constants::plot_tick_label_size));
+
+    m_custom_plot->xAxis->setLabelFont(QFont(QFont().family(), Constants::plot_axes_label_size));
+    m_custom_plot->yAxis->setLabelFont(QFont(QFont().family(), Constants::plot_axes_label_size));
+
+    ColorMapUtils::setDefaultMargins(m_custom_plot);
+}
+
+void SpecularPlot::setConnected(bool isConnected)
+{
+    setAxesRangeConnected(isConnected);
+    setUpdateTimerConnected(isConnected);
+}
+
+void SpecularPlot::setAxesRangeConnected(bool isConnected)
+{
+    if (isConnected) {
+        connect(m_custom_plot->xAxis, SIGNAL(rangeChanged(QCPRange)), this,
+                SLOT(onXaxisRangeChanged(QCPRange)), Qt::UniqueConnection);
+
+        connect(m_custom_plot->yAxis, SIGNAL(rangeChanged(QCPRange)), this,
+                SLOT(onYaxisRangeChanged(QCPRange)), Qt::UniqueConnection);
+
+    } else {
+        disconnect(m_custom_plot->xAxis, SIGNAL(rangeChanged(QCPRange)), this,
+                   SLOT(onXaxisRangeChanged(QCPRange)));
+
+        disconnect(m_custom_plot->yAxis, SIGNAL(rangeChanged(QCPRange)), this,
+                   SLOT(onYaxisRangeChanged(QCPRange)));
+    }
+}
+
+void SpecularPlot::setUpdateTimerConnected(bool isConnected)
+{
+    if (isConnected)
+        connect(m_update_timer, SIGNAL(timeToUpdate()), this, SLOT(onTimeToReplot()),
+                Qt::UniqueConnection);
+    else
+        disconnect(m_update_timer, SIGNAL(timeToUpdate()), this, SLOT(onTimeToReplot()));
+}
+
+void SpecularPlot::setPlotFromItem(SpecularDataItem* specularItem)
+{
+    Q_ASSERT(specularItem);
+
+    m_block_update = true;
+
+    setAxesRangeFromItem(specularItem);
+    setAxesLabelsFromItem(specularItem);
+    setDataFromItem(specularItem);
+
+    replot();
+
+    m_block_update = false;
+}
+
+void SpecularPlot::setAxesRangeFromItem(SpecularDataItem* item)
+{
+    m_custom_plot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom);
+    m_custom_plot->axisRect()->setupFullAxesBox(true);
+
+    setAxesRangeConnected(false);
+    m_custom_plot->xAxis->setRange(item->getLowerX(), item->getUpperX());
+    m_custom_plot->yAxis->setRange(item->getLowerY(), item->getUpperY());
+    setLog(item->isLog());
+    setAxesRangeConnected(true);
+}
+
+void SpecularPlot::setAxesLabelsFromItem(SpecularDataItem* item)
+{
+    setLabel(item->xAxisItem(), m_custom_plot->xAxis, item->getXaxisTitle());
+    setLabel(item->yAxisItem(), m_custom_plot->yAxis, item->getYaxisTitle());
+}
+
+void SpecularPlot::setLabel(const BasicAxisItem* item, QCPAxis* axis, QString label)
+{
+    Q_ASSERT(item && axis);
+    if(item->getItemValue(BasicAxisItem::P_TITLE_IS_VISIBLE).toBool())
+        axis->setLabel(std::move(label));
+    else
+        axis->setLabel(QString());
+}
+
+void SpecularPlot::setDataFromItem(SpecularDataItem* item)
+{
+    Q_ASSERT(item);
+    auto data = item->getOutputData();
+    Q_ASSERT(data);
+    if (!data)
+        return;
+
+    for (size_t i = 0, size = data->getAllocatedSize(); i < size; ++i) {
+        double x = data->getAxisValue(i, 0);
+        double y = data->operator[](i);
+        m_custom_plot->graph()->addData(x, y);
+    }
+}
+
+SpecularDataItem* SpecularPlot::specularItem()
+{
+    return const_cast<SpecularDataItem*>(
+        static_cast<const SpecularPlot*>(this)->specularItem());
+}
+
+const SpecularDataItem* SpecularPlot::specularItem() const
+{
+    const auto result = dynamic_cast<const SpecularDataItem*>(currentItem());
+    Q_ASSERT(result);
+    return result;
+}
+
+void SpecularPlot::modifyAxesProperties(const QString& axisName, const QString& propertyName)
+{
+    if (m_block_update)
+        return;
+
+    if (propertyName == BasicAxisItem::P_TITLE  ||
+        propertyName == BasicAxisItem::P_TITLE_IS_VISIBLE) {
+        setAxesLabelsFromItem(specularItem());
+        replot();
+    }
+
+    if (axisName == SpecularDataItem::P_XAXIS) {
+        if (propertyName == BasicAxisItem::P_MIN || propertyName == BasicAxisItem::P_MAX) {
+            setAxesRangeConnected(false);
+            m_custom_plot->xAxis->setRange(specularItem()->getLowerX(), specularItem()->getUpperX());
+            setAxesRangeConnected(true);
+            replot();
+        }
+    } else if (axisName == SpecularDataItem::P_YAXIS) {
+        if (propertyName == BasicAxisItem::P_MIN || propertyName == BasicAxisItem::P_MAX) {
+            setAxesRangeConnected(false);
+            m_custom_plot->yAxis->setRange(specularItem()->getLowerY(), specularItem()->getUpperY());
+            setAxesRangeConnected(true);
+            replot();
+        } else if (propertyName == AmplitudeAxisItem::P_IS_LOGSCALE) {
+            setLog(specularItem()->isLog());
+            replot();
+        }
+    }
+}
+
+void SpecularPlot::replot()
+{
+    m_update_timer->scheduleUpdate();
+}
diff --git a/GUI/coregui/Views/SpecularDataWidgets/SpecularPlot.h b/GUI/coregui/Views/SpecularDataWidgets/SpecularPlot.h
new file mode 100644
index 0000000000000000000000000000000000000000..b6dd5a2851e1a831f34eaeafe6303d422cdca6d3
--- /dev/null
+++ b/GUI/coregui/Views/SpecularDataWidgets/SpecularPlot.h
@@ -0,0 +1,114 @@
+// ************************************************************************** //
+//
+//  BornAgain: simulate and fit scattering at grazing incidence
+//
+//! @file      GUI/coregui/Views/SpecularDataWidgets/SpecularPlot.h
+//! @brief     Defines class SpecularPlot
+//!
+//! @homepage  http://www.bornagainproject.org
+//! @license   GNU General Public License v3 or higher (see COPYING)
+//! @copyright Forschungszentrum Jülich GmbH 2018
+//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
+//
+// ************************************************************************** //
+
+#ifndef SPECULARPLOT_H
+#define SPECULARPLOT_H
+
+#include "SessionItemWidget.h"
+#include "ColorMapBin.h"
+#include "qcustomplot.h"
+#include <memory>
+
+class BasicAxisItem;
+class SpecularDataItem;
+class SpecularPlotEvent;
+class UpdateTimer;
+
+//! The SpecularPlot class presents 1D intensity data from SpecularDataItem.
+
+//! Provides minimal functionality for data plotting and axes interaction. Should be a component
+//! for more complicated plotting widgets. Corresponds to ColorMap for 2D intensity data.
+
+class BA_CORE_API_ SpecularPlot : public SessionItemWidget
+{
+    Q_OBJECT
+
+public:
+    explicit SpecularPlot(QWidget* parent = 0);
+
+    QSize sizeHint() const override { return QSize(500, 400); }
+    QSize minimumSizeHint() const override { return QSize(128, 128); }
+
+    QCustomPlot* customPlot() { return m_custom_plot; }
+    const QCustomPlot* customPlot() const { return m_custom_plot; }
+
+    //! to track move events (used when showing profile histograms and printing status string)
+    void setMouseTrackingEnabled(bool enable);
+
+    //! sets logarithmic scale
+    void setLog(bool log);
+
+    //! reset all axes min,max to initial value
+    void resetView();
+
+private slots:
+    //! updates plot depending on  IntensityDataItem properties
+    void onPropertyChanged(const QString& property_name);
+
+    //! Propagate xmin, xmax back to IntensityDataItem
+    void onXaxisRangeChanged(QCPRange newRange);
+
+    //! Propagate ymin, ymax back to IntensityDataItem
+    void onYaxisRangeChanged(QCPRange newRange);
+
+    //! Replots SpecularPlot.
+    void onTimeToReplot();
+
+protected:
+    void subscribeToItem() override;
+    void unsubscribeFromItem() override;
+
+private:
+    //! creates and initializes the color map
+    void initPlot();
+
+    void setConnected(bool isConnected);
+
+    //! Connects/disconnects signals related to SpecularPlot's X,Y axes rectangle change.
+    void setAxesRangeConnected(bool isConnected);
+
+    void setUpdateTimerConnected(bool isConnected);
+
+    //! Sets initial state of SpecularPlot to match given intensity item.
+    void setPlotFromItem(SpecularDataItem* intensityItem);
+
+    //! Sets (xmin,xmax) and (ymin,ymax) of SpecularPlot from specular item.
+    //! Also sets logarithmic scale on y-axis if necessary.
+    void setAxesRangeFromItem(SpecularDataItem* item);
+
+    //! Sets X,Y axes labels from item
+    void setAxesLabelsFromItem(SpecularDataItem* item);
+
+    //! Sets label to axis
+    void setLabel(const BasicAxisItem* item, QCPAxis* axis, QString label);
+
+    //! Sets the intensity values to SpecularPlot.
+    void setDataFromItem(SpecularDataItem* item);
+
+    SpecularDataItem* specularItem();
+    const SpecularDataItem* specularItem() const;
+
+    void modifyAxesProperties(const QString& axisName, const QString& propertyName);
+
+    //! Schedule replot for later execution by onTimeReplot() slot.
+    void replot();
+
+    QCustomPlot* m_custom_plot;
+    UpdateTimer* m_update_timer;
+    SpecularPlotEvent* m_plot_event;
+
+    bool m_block_update;
+};
+
+#endif // SPECULARPLOT_H
diff --git a/GUI/coregui/Views/SpecularDataWidgets/SpecularPlotCanvas.cpp b/GUI/coregui/Views/SpecularDataWidgets/SpecularPlotCanvas.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0af71f7600967d57fb76c5b78eb0d8fec4db9fd3
--- /dev/null
+++ b/GUI/coregui/Views/SpecularDataWidgets/SpecularPlotCanvas.cpp
@@ -0,0 +1,47 @@
+// ************************************************************************** //
+//
+//  BornAgain: simulate and fit scattering at grazing incidence
+//
+//! @file      GUI/coregui/Views/SpecularDataWidgets/SpecularPlotCanvas.cpp
+//! @brief     Declares class SpecularPlotCanvas
+//!
+//! @homepage  http://www.bornagainproject.org
+//! @license   GNU General Public License v3 or higher (see COPYING)
+//! @copyright Forschungszentrum Jülich GmbH 2018
+//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
+//
+// ************************************************************************** //
+
+#include "SpecularPlotCanvas.h"
+#include "SpecularPlot.h"
+#include "SpecularDataItem.h"
+#include <QVBoxLayout>
+
+SpecularPlotCanvas::SpecularPlotCanvas(QWidget *parent)
+    : SessionItemWidget(parent)
+    , m_plot(new SpecularPlot)
+{
+    QVBoxLayout* layout = new QVBoxLayout;
+    layout->setMargin(0);
+    layout->setSpacing(0);
+
+    layout->addWidget(m_plot);
+
+    setLayout(layout);
+}
+
+void SpecularPlotCanvas::setItem(SessionItem* specularDataItem)
+{
+    SessionItemWidget::setItem(specularDataItem);
+    m_plot->setItem(dynamic_cast<SpecularDataItem*>(specularDataItem));
+}
+
+SpecularPlot* SpecularPlotCanvas::specularPlot()
+{
+    return m_plot;
+}
+
+QCustomPlot* SpecularPlotCanvas::customPlot()
+{
+    return m_plot->customPlot();
+}
diff --git a/GUI/coregui/Views/SpecularDataWidgets/SpecularPlotCanvas.h b/GUI/coregui/Views/SpecularDataWidgets/SpecularPlotCanvas.h
new file mode 100644
index 0000000000000000000000000000000000000000..e66606ad0c849e7189566a5195a1e39be6cb7eb8
--- /dev/null
+++ b/GUI/coregui/Views/SpecularDataWidgets/SpecularPlotCanvas.h
@@ -0,0 +1,42 @@
+// ************************************************************************** //
+//
+//  BornAgain: simulate and fit scattering at grazing incidence
+//
+//! @file      GUI/coregui/Views/SpecularDataWidgets/SpecularPlotCanvas.h
+//! @brief     Defines class ColorMapCanvas
+//!
+//! @homepage  http://www.bornagainproject.org
+//! @license   GNU General Public License v3 or higher (see COPYING)
+//! @copyright Forschungszentrum Jülich GmbH 2018
+//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
+//
+// ************************************************************************** //
+
+#ifndef SPECULARPLOTCANVAS_H
+#define SPECULARPLOTCANVAS_H
+
+#include "SessionItemWidget.h"
+
+class QCustomPlot;
+class SpecularPlot;
+
+//! The SpecularPlotCanvas class contains SpecularPlot for specular data presentation, and provides
+//! status string appearance.
+
+class BA_CORE_API_ SpecularPlotCanvas : public SessionItemWidget
+{
+    Q_OBJECT
+
+public:
+    explicit SpecularPlotCanvas(QWidget* parent = 0);
+
+    void setItem(SessionItem* specularDataItem) override;
+
+    SpecularPlot* specularPlot();
+    QCustomPlot* customPlot();
+
+private:
+    SpecularPlot* m_plot;
+};
+
+#endif // SPECULARPLOTCANVAS_H
diff --git a/GUI/coregui/Views/SpecularDataWidgets/SpecularPlotEvent.cpp b/GUI/coregui/Views/SpecularDataWidgets/SpecularPlotEvent.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9f6b2714a7e1f01e41df657eb756c4513da58eeb
--- /dev/null
+++ b/GUI/coregui/Views/SpecularDataWidgets/SpecularPlotEvent.cpp
@@ -0,0 +1,56 @@
+// ************************************************************************** //
+//
+//  BornAgain: simulate and fit scattering at grazing incidence
+//
+//! @file      GUI/coregui/Views/SpecularDataWidgets/SpecularPlotEvent.cpp
+//! @brief     Implements class SpecularPlotEvent
+//!
+//! @homepage  http://www.bornagainproject.org
+//! @license   GNU General Public License v3 or higher (see COPYING)
+//! @copyright Forschungszentrum Jülich GmbH 2018
+//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
+//
+// ************************************************************************** //
+
+#include "SpecularPlotEvent.h"
+#include "SpecularPlot.h"
+#include <QMouseEvent>
+
+SpecularPlotEvent::SpecularPlotEvent(SpecularPlot* plot) : QObject(plot), m_plot(plot) {}
+
+//! Sets tracking of the mouse for parent COlorMap
+
+void SpecularPlotEvent::setMouseTrackingEnabled(bool enable)
+{
+    m_plot->setMouseTracking(enable);
+    customPlot()->setMouseTracking(enable);
+
+    if (enable)
+        connect(customPlot(), &QCustomPlot::mouseMove, this,
+                &SpecularPlotEvent::onCustomMouseMove, Qt::UniqueConnection);
+    else
+        disconnect(customPlot(), &QCustomPlot::mouseMove, this,
+                   &SpecularPlotEvent::onCustomMouseMove);
+}
+
+void SpecularPlotEvent::onCustomMouseMove(QMouseEvent* event)
+{
+    Q_UNUSED(event);
+    // this method should track mouse position in plot
+    // left unimplemented for now
+}
+
+SpecularPlot* SpecularPlotEvent::specularPlot()
+{
+    return m_plot;
+}
+
+const SpecularPlot* SpecularPlotEvent::specularPlot() const
+{
+    return m_plot;
+}
+
+QCustomPlot* SpecularPlotEvent::customPlot()
+{
+    return m_plot->customPlot();
+}
diff --git a/GUI/coregui/Views/SpecularDataWidgets/SpecularPlotEvent.h b/GUI/coregui/Views/SpecularDataWidgets/SpecularPlotEvent.h
new file mode 100644
index 0000000000000000000000000000000000000000..c99a07582dd562682390de2e9a02c4ed76b639c9
--- /dev/null
+++ b/GUI/coregui/Views/SpecularDataWidgets/SpecularPlotEvent.h
@@ -0,0 +1,51 @@
+// ************************************************************************** //
+//
+//  BornAgain: simulate and fit scattering at grazing incidence
+//
+//! @file      GUI/coregui/Views/SpecularDataWidgets/SpecularPlotEvent.h
+//! @brief     Defines class SpecularPlotEvent
+//!
+//! @homepage  http://www.bornagainproject.org
+//! @license   GNU General Public License v3 or higher (see COPYING)
+//! @copyright Forschungszentrum Jülich GmbH 2018
+//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
+//
+// ************************************************************************** //
+
+#ifndef SPECULARPLOTEVENT_H
+#define SPECULARPLOTEVENT_H
+
+#include "WinDllMacros.h"
+#include <QObject>
+
+class SpecularPlot;
+class QMouseEvent;
+class QCustomPlot;
+
+//! Helps SpecularPlot to handle mouse events. Particularly, it constructs a valid status string.
+//! Can be extended to play a role of event filter.
+
+class BA_CORE_API_ SpecularPlotEvent : public QObject
+{
+    Q_OBJECT
+
+public:
+    explicit SpecularPlotEvent(SpecularPlot* colorMap);
+
+    void setMouseTrackingEnabled(bool enable);
+
+public slots:
+    //! Constructs status string on mouse move event coming from QCustomPlot. String is emitted
+    //! if mouse is in axes's viewport rectangle. Once mouse goes out of it, an
+    //! empty string is emitted once again.
+    void onCustomMouseMove(QMouseEvent* event);
+
+private:
+    SpecularPlot* specularPlot();
+    const SpecularPlot* specularPlot() const;
+    QCustomPlot* customPlot();
+
+    SpecularPlot* m_plot;
+};
+
+#endif // SPECULARPLOTEVENT_H
diff --git a/GUI/coregui/Views/TestView.cpp b/GUI/coregui/Views/TestView.cpp
index fcdbc0990f968239392b5e8e92ab3d67b3ccf624..69f62308b9d239b284243ae9b67dc24e6f93387c 100644
--- a/GUI/coregui/Views/TestView.cpp
+++ b/GUI/coregui/Views/TestView.cpp
@@ -21,6 +21,8 @@
 #include "MinimizerSettingsWidget.h"
 #include "ApplicationModels.h"
 #include "SampleModel.h"
+#include "SpecularDataItem.h"
+#include "SpecularDataWidget.h"
 #include "TestComponentView.h"
 #include "mainwindow.h"
 #include <QTreeView>
@@ -32,16 +34,25 @@
 #include <QCheckBox>
 #include <QLineEdit>
 
+namespace {
+// These functions are required for testing purposes only
+// They must be removed after completion of
+// SpecularDataWidget
+double getTestValue(size_t bin);
+SpecularDataItem* fillTestItem(SessionItem* item);
+}
+
 TestView::TestView(MainWindow *mainWindow)
     : QWidget(mainWindow)
     , m_mainWindow(mainWindow)
 {
-    test_ComponentProxyModel();
+//    test_ComponentProxyModel();
 //    test_MaterialEditor();
 //    test_MinimizerSettings();
 //    test_AccordionWidget();
 //    test_RunFitWidget();
 //    test_ba3d();
+    test_specular_data_widget();
 }
 
 void TestView::test_ComponentProxyModel()
@@ -169,3 +180,39 @@ void TestView::test_ba3d()
     setLayout(layout);
 
 }
+
+void TestView::test_specular_data_widget()
+{
+    SessionModel* tempModel = new SessionModel("Test", this);
+    auto data = fillTestItem(tempModel->insertNewItem(Constants::SpecularDataType));
+
+    QVBoxLayout *layout = new QVBoxLayout;
+    layout->setMargin(0);
+    layout->setSpacing(0);
+    auto widget = new SpecularDataWidget(this);
+    widget->setItem(data);
+    layout->addWidget(widget);
+    setLayout(layout);
+}
+
+namespace {
+double getTestValue(size_t bin)
+{
+    const double factor = M_PI / (180.0 * 100.0);
+    const double angle = bin * factor;
+    return (std::cos(angle * 1000.0) + 1.5) * std::exp(-(bin / 100.0));
+}
+
+SpecularDataItem* fillTestItem(SessionItem* item)
+{
+    SpecularDataItem* result = dynamic_cast<SpecularDataItem*>(item);
+    Q_ASSERT(result);
+    auto outputData = std::make_unique<OutputData<double>>();
+    outputData->addAxis(FixedBinAxis("Angle [deg]", 1000, 0.0, 10.0));
+    for (size_t i = 0; i < 1000; ++i)
+        outputData->operator[](i) = getTestValue(i);
+
+    result->setOutputData(outputData.release());
+    return result;
+}
+}
diff --git a/GUI/coregui/Views/TestView.h b/GUI/coregui/Views/TestView.h
index 41e5a044f50e481e605e9e552e89f13e437fbea2..47e24725f165a34485580f8ba3846846d3e837f1 100644
--- a/GUI/coregui/Views/TestView.h
+++ b/GUI/coregui/Views/TestView.h
@@ -32,6 +32,7 @@ private:
     void test_MinimizerSettings();
     void test_AccordionWidget();
     void test_ba3d();
+    void test_specular_data_widget();
 
     MainWindow* m_mainWindow;
 };