Plot1D.cpp 9.68 KB
Newer Older
1
//  ************************************************************************************************
2
//
3
//  BornAgain: simulate and fit reflection and scattering
4
//
5
//! @file      GUI/View/PlotComparison/Plot1D.cpp
6
//! @brief     Implements class Plot1D
7
8
9
10
11
12
//!
//! @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)
//
13
//  ************************************************************************************************
14

15
#include "GUI/View/PlotComparison/Plot1D.h"
Wuttke, Joachim's avatar
Wuttke, Joachim committed
16
17
18
19
#include "GUI/Model/Data/Data1DViewItem.h"
#include "GUI/Model/Data/DataItem.h"
#include "GUI/Model/Data/DataProperties.h"
#include "GUI/Model/Data/DataPropertyContainer.h"
Wuttke, Joachim's avatar
Wuttke, Joachim committed
20
#include "GUI/Model/Device/AxesItems.h"
Wuttke, Joachim's avatar
Wuttke, Joachim committed
21
22
#include "GUI/View/PlotUtil/PlotConstants.h"
#include "GUI/View/PlotUtil/PlotEventInfo.h"
Wuttke, Joachim's avatar
Wuttke, Joachim committed
23
#include "GUI/View/PlotUtil/RangeUtils.h"
Wuttke, Joachim's avatar
Wuttke, Joachim committed
24
#include "GUI/View/Tool/UpdateTimer.h"
25

Wuttke, Joachim's avatar
Wuttke, Joachim committed
26
namespace {
27

28
29
const int replot_update_interval = 10;

Wuttke, Joachim's avatar
Wuttke, Joachim committed
30
int bin(double x, const QCPGraph* graph);
31

Pospelov, Gennady's avatar
Pospelov, Gennady committed
32
} // namespace
33

34
Plot1D::Plot1D(QWidget* parent)
35
36
37
    : ScientificPlot(parent, PLOT_TYPE::Plot1D)
    , m_custom_plot(new QCustomPlot)
    , m_update_timer(new UpdateTimer(replot_update_interval, this))
38
39
    , m_block_update(false)
{
40
    auto* vlayout = new QVBoxLayout(this);
41
42
43
    vlayout->setContentsMargins(0, 0, 0, 0);
    vlayout->setSpacing(0);
    vlayout->addWidget(m_custom_plot);
44
    m_custom_plot->setAttribute(Qt::WA_NoMousePropagation, false);
45
46
    setLayout(vlayout);

47
    m_custom_plot->xAxis->setTickLabelFont(
Wuttke, Joachim's avatar
Wuttke, Joachim committed
48
        QFont(QFont().family(), GUI::Constants::plot_tick_label_size()));
49
    m_custom_plot->yAxis->setTickLabelFont(
Wuttke, Joachim's avatar
Wuttke, Joachim committed
50
        QFont(QFont().family(), GUI::Constants::plot_tick_label_size()));
51

52
53
54
    setMouseTrackingEnabled(true);
}

55
56
PlotEventInfo Plot1D::eventInfo(double xpos, double ypos) const
{
57
    PlotEventInfo result(plotType());
58
    if (!viewItem())
59
60
61
62
63
64
        return result;

    result.setX(xpos);
    result.setValue(ypos);

    result.setInAxesRange(axesRangeContains(xpos, ypos));
Wuttke, Joachim's avatar
Wuttke, Joachim committed
65
    result.setNx(bin(result.x(), m_custom_plot->graph()));
66
67
68
69

    return result;
}

70
71
void Plot1D::setLog(bool log)
{
72
73
    GUI::View::RangeUtils::setLogz(m_custom_plot->yAxis, log);
    GUI::View::RangeUtils::setLogz(m_custom_plot->yAxis2, log);
74
75
}

76
77
void Plot1D::resetView()
{
78
    viewItem()->resetView();
79
80
}

81
82
void Plot1D::onPropertyChanged(const QString& property_name)
{
83
84
85
    if (m_block_update)
        return;

86
    if (Data1DViewItem::isAxesUnitsPropertyName(property_name)) {
87
        setAxesRangeFromItem(viewItem());
88
        updateAllGraphs();
89
90
91
92
        replot();
    }
}

93
94
void Plot1D::onXaxisRangeChanged(QCPRange newRange)
{
95
    m_block_update = true;
96
97
    viewItem()->setLowerX(newRange.lower);
    viewItem()->setUpperX(newRange.upper);
98
99
100
    m_block_update = false;
}

101
102
void Plot1D::onYaxisRangeChanged(QCPRange newRange)
{
103
    m_block_update = true;
104
105
    viewItem()->setLowerY(newRange.lower);
    viewItem()->setUpperY(newRange.upper);
106
107
108
    m_block_update = false;
}

109
110
void Plot1D::onTimeToReplot()
{
111
112
    m_custom_plot->replot();
}
113

114
115
void Plot1D::subscribeToItem()
{
116
117
    initPlots();
    refreshPlotData();
118

119
    viewItem()->mapper()->setOnPropertyChange(
120
121
        [this](const QString& name) { onPropertyChanged(name); }, this);

122
    viewItem()->mapper()->setOnChildPropertyChange(
123
        [this](SessionItem* item, const QString name) {
124
            if (dynamic_cast<BasicAxisItem*>(item))
125
126
127
128
                modifyAxesProperties(item->itemName(), name);
        },
        this);

Pospelov, Gennady's avatar
Pospelov, Gennady committed
129
    std::for_each(m_graph_map.begin(), m_graph_map.end(), [caller = this](auto pair) {
Mikhail Svechnikov's avatar
Mikhail Svechnikov committed
130
131
132
133
134
#ifdef USE_MAPPERS
        auto property_item = pair.first;
        property_item->dataItem()->mapper()->setOnValueChange(
            [caller]() { caller->refreshPlotData(); }, caller);
#else
Mikhail Svechnikov's avatar
Mikhail Svechnikov committed
135
        DataItem* dataItem = pair.first->dataItem();
136
        connect(dataItem, &DataItem::datafieldChanged, caller,
Mikhail Svechnikov's avatar
cleanup    
Mikhail Svechnikov committed
137
                [caller]() { caller->refreshPlotData(); });
Mikhail Svechnikov's avatar
Mikhail Svechnikov committed
138
#endif
139
    });
140
141
142
    setConnected(true);
}

143
144
void Plot1D::unsubscribeFromItem()
{
145
    m_custom_plot->clearGraphs();
Pospelov, Gennady's avatar
Pospelov, Gennady committed
146
    std::for_each(m_graph_map.begin(), m_graph_map.end(), [caller = this](auto pair) {
Mikhail Svechnikov's avatar
Mikhail Svechnikov committed
147
148
149
#ifdef USE_MAPPERS
        pair.first->dataItem()->mapper()->unsubscribe(caller);
#else
Mikhail Svechnikov's avatar
Mikhail Svechnikov committed
150
        disconnect(pair.first->dataItem(), nullptr, caller, nullptr);
Mikhail Svechnikov's avatar
Mikhail Svechnikov committed
151
#endif
152
    });
153
    m_graph_map.clear();
154
155
156
    setConnected(false);
}

157
158
void Plot1D::initPlots()
{
159
    auto property_items = viewItem()->propertyContainerItem()->propertyItems();
Mikhail Svechnikov's avatar
Mikhail Svechnikov committed
160

Pospelov, Gennady's avatar
Pospelov, Gennady committed
161
    std::for_each(property_items.begin(), property_items.end(), [this](Data1DProperties* item) {
162
        auto* graph = m_custom_plot->addGraph();
163
        graph->setLineStyle(item->line());
Pospelov, Gennady's avatar
Pospelov, Gennady committed
164
        graph->setPen(QPen(item->color()));
Mikhail Svechnikov's avatar
Mikhail Svechnikov committed
165
        graph->setScatterStyle(item->scatter());
Pospelov, Gennady's avatar
Pospelov, Gennady committed
166
167
        m_graph_map[item] = graph;
    });
168
169
}

170
171
void Plot1D::setConnected(bool isConnected)
{
172
173
174
175
    setAxesRangeConnected(isConnected);
    setUpdateTimerConnected(isConnected);
}

176
177
void Plot1D::setAxesRangeConnected(bool isConnected)
{
178
179
180
    if (isConnected) {
        connect(m_custom_plot->xAxis,
                static_cast<void (QCPAxis::*)(const QCPRange&)>(&QCPAxis::rangeChanged), this,
181
                &Plot1D::onXaxisRangeChanged, Qt::UniqueConnection);
182
183
184

        connect(m_custom_plot->yAxis,
                static_cast<void (QCPAxis::*)(const QCPRange&)>(&QCPAxis::rangeChanged), this,
185
                &Plot1D::onYaxisRangeChanged, Qt::UniqueConnection);
186
187
188
    } else {
        disconnect(m_custom_plot->xAxis,
                   static_cast<void (QCPAxis::*)(const QCPRange&)>(&QCPAxis::rangeChanged), this,
189
                   &Plot1D::onXaxisRangeChanged);
190
191
192

        disconnect(m_custom_plot->yAxis,
                   static_cast<void (QCPAxis::*)(const QCPRange&)>(&QCPAxis::rangeChanged), this,
193
                   &Plot1D::onYaxisRangeChanged);
194
195
196
    }
}

197
198
void Plot1D::setUpdateTimerConnected(bool isConnected)
{
199
    if (isConnected)
Pospelov, Gennady's avatar
Pospelov, Gennady committed
200
201
        connect(m_update_timer, &UpdateTimer::timeToUpdate, this, &Plot1D::onTimeToReplot,
                Qt::UniqueConnection);
202
    else
Pospelov, Gennady's avatar
Pospelov, Gennady committed
203
        disconnect(m_update_timer, &UpdateTimer::timeToUpdate, this, &Plot1D::onTimeToReplot);
204
205
}

206
207
void Plot1D::refreshPlotData()
{
208
209
    if (m_block_update)
        return;
210
211
212

    m_block_update = true;

213
    auto* view_item = viewItem();
214
    ASSERT(view_item);
215
216
217

    setAxesRangeFromItem(view_item);
    setAxesLabelsFromItem(view_item);
218
    updateAllGraphs();
219
220
221
222
223
224

    replot();

    m_block_update = false;
}

225
226
void Plot1D::setAxesRangeFromItem(Data1DViewItem* item)
{
227
228
229
230
231
232
233
234
235
236
    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);
}

237
238
void Plot1D::setAxesLabelsFromItem(Data1DViewItem* item)
{
239
240
241
242
    setLabel(item->xAxisItem(), m_custom_plot->xAxis, item->getXaxisTitle());
    setLabel(item->yAxisItem(), m_custom_plot->yAxis, item->getYaxisTitle());
}

243
244
void Plot1D::setLabel(const BasicAxisItem* item, QCPAxis* axis, QString label)
{
245
    ASSERT(item && axis);
246
    if (item->isTitleVisible())
247
        axis->setLabel(label);
248
249
250
251
    else
        axis->setLabel(QString());
}

252
253
void Plot1D::updateAllGraphs()
{
254
    auto property_items = viewItem()->propertyContainerItem()->propertyItems();
255
    std::for_each(property_items.begin(), property_items.end(),
256
257
258
                  [this](Data1DProperties* item) { updateGraph(item); });
}

259
260
void Plot1D::updateGraph(Data1DProperties* item)
{
261
    auto data_points = viewItem()->graphData(item);
262

263
    auto* graph = m_graph_map.at(item);
Wuttke, Joachim's avatar
Wuttke, Joachim committed
264
    graph->setData(data_points.first, data_points.second, /*alreadySorted =*/true);
265
266
}

267
268
Data1DViewItem* Plot1D::viewItem()
{
Pospelov, Gennady's avatar
Pospelov, Gennady committed
269
    return const_cast<Data1DViewItem*>(static_cast<const Plot1D*>(this)->viewItem());
270
271
}

272
273
const Data1DViewItem* Plot1D::viewItem() const
{
274
    const auto* const result = dynamic_cast<const Data1DViewItem*>(currentItem());
275
    ASSERT(result);
276
277
278
    return result;
}

279
280
void Plot1D::modifyAxesProperties(const QString& axisName, const QString& propertyName)
{
281
282
283
    if (m_block_update)
        return;

t.knopff's avatar
t.knopff committed
284
    if (BasicAxisItem::isTitlePropertyName(propertyName)
285
        || BasicAxisItem::isTitleVisiblePropertyName(propertyName)) {
286
        setAxesLabelsFromItem(viewItem());
287
288
289
        replot();
    }

290
    if (Data1DViewItem::isXAxisPropertyName(axisName)) {
291
        if (BasicAxisItem::isBoundsPropertiesName(propertyName)) {
292
            setAxesRangeConnected(false);
293
            m_custom_plot->xAxis->setRange(viewItem()->getLowerX(), viewItem()->getUpperX());
294
295
296
            setAxesRangeConnected(true);
            replot();
        }
297
    } else if (Data1DViewItem::isYAxisPropertyName(axisName)) {
298
        if (BasicAxisItem::isBoundsPropertiesName(propertyName)) {
299
            setAxesRangeConnected(false);
300
            m_custom_plot->yAxis->setRange(viewItem()->getLowerY(), viewItem()->getUpperY());
301
302
            setAxesRangeConnected(true);
            replot();
303
        } else if (AmplitudeAxisItem::isLogScalePropertyName(propertyName)) {
304
            setLog(viewItem()->isLog());
305
306
307
308
309
            replot();
        }
    }
}

310
311
void Plot1D::replot()
{
312
313
314
    m_update_timer->scheduleUpdate();
}

Wuttke, Joachim's avatar
Wuttke, Joachim committed
315
namespace {
316

317
318
int bin(double x, const QCPGraph* graph)
{
319
320
321
322
323
324
325
    const int key_start = graph->findBegin(x);
    const int key_end = graph->findBegin(x, false); // false = do not expand range
    if (key_end == key_start || key_end == graph->dataCount())
        return key_start;
    return (x - graph->dataSortKey(key_start)) <= (graph->dataSortKey(key_end) - x) ? key_start
                                                                                    : key_end;
}
326

Pospelov, Gennady's avatar
Pospelov, Gennady committed
327
} // namespace