libpappsomspp-0.11.27/.gitignore 000664 001750 001750 00000000310 15205554740 017754 0 ustar 00rusconi rusconi 000000 000000 *-swp
*.swp
.ycm_extra_conf.py
compile_commands.json
Session.vim
CMakeLists.txt.user
.cache
build
cbuild
dbuild/
wbuild/
.kdev4/
*.kdev4
.zed
_clang-format
__pycache__
CMakeLists.txt.user
.qt/
libpappsomspp-0.11.27/src/ 000775 001750 001750 00000000000 15250227266 016561 5 ustar 00rusconi rusconi 000000 000000 libpappsomspp-0.11.27/src/pappsomspp/ 000775 001750 001750 00000000000 15250227266 020763 5 ustar 00rusconi rusconi 000000 000000 libpappsomspp-0.11.27/src/pappsomspp/gui/ 000775 001750 001750 00000000000 15250227266 021547 5 ustar 00rusconi rusconi 000000 000000 libpappsomspp-0.11.27/src/pappsomspp/gui/plotwidget/ 000775 001750 001750 00000000000 15250227266 023731 5 ustar 00rusconi rusconi 000000 000000 libpappsomspp-0.11.27/src/pappsomspp/gui/plotwidget/New Folder/ 000775 001750 001750 00000000000 15245512717 025660 5 ustar 00rusconi rusconi 000000 000000 libpappsomspp-0.11.27/src/pappsomspp/gui/plotwidget/basecolormapplotwidget.cpp 000664 001750 001750 00000130362 15250226472 031212 0 ustar 00rusconi rusconi 000000 000000 /* This code comes right from the msXpertSuite software project.
*
* msXpertSuite - mass spectrometry software suite
* -----------------------------------------------
* Copyright(C) 2009,...,2018 Filippo Rusconi
*
* http://www.msxpertsuite.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* END software license
*/
/////////////////////// StdLib includes
#include
/////////////////////// Qt includes
#include
/////////////////////// Local includes
#include "basecolormapplotwidget.h"
#include "../../core/trace/maptrace.h"
#include "pappsomspp/core/pappsoexception.h"
namespace pappso
{
BaseColorMapPlotWidget::BaseColorMapPlotWidget(QWidget *parent,
const QString &x_axis_label,
const QString &y_axis_label)
: BasePlotWidget(parent, x_axis_label, y_axis_label)
{
// Do not call createAllAncillaryItems() in this base class because all the
// items will have been created *before* the addition of plots and then the
// rendering order will hide them to the viewer, since the rendering order is
// according to the order in which the items have been created.
//
// The fact that the ancillary items are created before trace plots is not a
// problem because the trace plots are sparse and do not effectively hide the
// data.
//
// But, in the color map plot widgets, we cannot afford to create the
// ancillary items *before* the plot itself because then, the rendering of the
// plot (created after) would screen off the ancillary items (created before).
//
// So, the createAllAncillaryItems() function needs to be called in the
// derived classes at the most appropriate moment in the setting up of the
// widget.
//
// In the present case, the function needs to be called right after addition
// of the color map plot.
}
BaseColorMapPlotWidget::BaseColorMapPlotWidget(QWidget *parent) : BasePlotWidget(parent, "x", "y")
{
// Do not call createAllAncillaryItems() in this base class because all the
// items will have been created *before* the addition of plots and then the
// rendering order will hide them to the viewer, since the rendering order is
// according to the order in which the items have been created.
//
// The fact that the ancillary items are created before trace plots is not a
// problem because the trace plots are sparse and do not effectively hide the
// data.
//
// But, in the color map plot widgets, we cannot afford to create the
// ancillary items *before* the plot itself because then, the rendering of the
// plot (created after) would screen off the ancillary items (created before).
//
// So, the createAllAncillaryItems() function needs to be called in the
// derived classes at the most appropriate moment in the setting up of the
// widget.
//
// In the present case, the function needs to be called right after addition
// of the color map plot.
}
//! Destruct \c this BaseColorMapPlotWidget instance.
/*!
The destruction involves clearing the history, deleting all the axis range
history items for x and y axes.
*/
BaseColorMapPlotWidget::~BaseColorMapPlotWidget()
{
if(mpa_origColorMapData != nullptr)
delete mpa_origColorMapData;
if(mpa_origColorMapPlotConfig != nullptr)
delete mpa_origColorMapPlotConfig;
}
void
BaseColorMapPlotWidget::setColorMapPlotConfig(const ColorMapPlotConfig &color_map_config)
{
m_colorMapPlotConfig = color_map_config;
}
const ColorMapPlotConfig *
BaseColorMapPlotWidget::getOrigColorMapPlotConfig()
{
return mpa_origColorMapPlotConfig;
}
const ColorMapPlotConfig &
BaseColorMapPlotWidget::getColorMapPlotConfig()
{
return m_colorMapPlotConfig;
}
QCPColorMap *
BaseColorMapPlotWidget::addColorMap(
std::shared_ptr> double_map_trace_map_sp,
const ColorMapPlotConfig color_map_plot_config,
const QColor &color)
{
// qDebug() << "Adding color map with config:" <<
// color_map_plot_config.toString();
if(!color.isValid())
throw PappsoException(QString("The color to be used for the plot graph is invalid."));
QCPColorMap *color_map_p = new QCPColorMap(xAxis, yAxis);
color_map_p->setLayer("plotsLayer");
// Do not forget to copy the config!
m_colorMapPlotConfig = color_map_plot_config;
// Immediately create a copy of the original data for backup.
mpa_origColorMapPlotConfig = new ColorMapPlotConfig(color_map_plot_config);
#if 0
// This is the code on the QCustomPlot documentation and it works fine.
QCPColorMap *color_map_p = new QCPColorMap(xAxis, yAxis);
color_map_p->data()->setSize(50, 50);
color_map_p->data()->setRange(QCPRange(0, 2), QCPRange(0, 2));
for(int x = 0; x < 50; ++x)
for(int y = 0; y < 50; ++y)
color_map_p->data()->setCell(x, y, qCos(x / 10.0) + qSin(y / 10.0));
color_map_p->setGradient(QCPColorGradient::gpPolar);
color_map_p->rescaleDataRange(true);
rescaleAxes();
replot();
#endif
// Only now can afford to call createAllAncillaryItems() in this derived class
// because the color map has been created already. The rendering order will
// thus not hide the ancillary items, since they have been created after the
// color map plot (since the rendering order is according to the
// order in which the items have been created). See contructor note.
createAllAncillaryItems();
// Connect the signal of selection change so that we can re-emit it for the
// widget that is using *this widget.
connect(
color_map_p,
static_cast(&QCPAbstractPlottable::selectionChanged),
[this, color_map_p]() {
emit plottableSelectionChangedSignal(color_map_p, color_map_p->selected());
});
// qDebug() << "Configuring the color map with this config:"
//<< color_map_plot_config.toString();
color_map_p->data()->setSize(color_map_plot_config.keyCellCount,
color_map_plot_config.mzCellCount);
color_map_p->data()->setRange(
QCPRange(color_map_plot_config.minKeyValue, color_map_plot_config.maxKeyValue),
QCPRange(color_map_plot_config.minMzValue, color_map_plot_config.maxMzValue));
color_map_p->data()->fill(0.0);
// We have now to fill the color map.
for(auto &&pair : *double_map_trace_map_sp)
{
// The first value is the key and the second value is the MapTrace into
// which we need to iterated and for each point (double mz, double
// intensity) create a map cell.
double dt_or_rt_key = pair.first;
MapTrace map_trace = pair.second;
for(auto &&data_point_pair : map_trace)
{
double mz = data_point_pair.first;
double intensity = data_point_pair.second;
// We are filling dynamically the color map. If a cell had already
// something in, then we need to take that into account. This is
// because we let QCustomPlot handle the fuzzy transition between
// color map plot cells.
double prev_intensity = color_map_p->data()->data(dt_or_rt_key, mz);
double new_intensity = prev_intensity + intensity;
// Record the min/max cell intensity value (origM(in/ax)ZValue). We
// will need that later. Also update the lastM(in/ax)ZValue because
// when doing this kind of data conversion it is assume that the user
// actually changes the data.
m_colorMapPlotConfig.setOrigAndLastMinZValue(
std::min(m_colorMapPlotConfig.origMinZValue, new_intensity));
m_colorMapPlotConfig.setOrigAndLastMaxZValue(
std::max(m_colorMapPlotConfig.origMaxZValue, new_intensity));
// qDebug() << "Setting tri-point:" << dt_or_rt_key << "," << mz <<
// ","
//<< new_intensity;
color_map_p->data()->setData(dt_or_rt_key, mz, new_intensity);
}
}
// At this point we have finished filling-up the color map.
// The gpThermal is certainly one of the best.
color_map_p->setGradient(QCPColorGradient::gpThermal);
color_map_p->rescaleDataRange(true);
color_map_p->rescaleAxes();
resetAxesRangeHistory();
// The pen of the color map itself is of no use. Instead the user will see the
// color of the axes' labels.
QPen pen = xAxis->basePen();
pen.setColor(color);
xAxis->setBasePen(pen);
xAxis->setLabelColor(color);
xAxis->setTickLabelColor(color);
yAxis->setBasePen(pen);
yAxis->setLabelColor(color);
yAxis->setTickLabelColor(color);
// And now set the color map's pen to the same color, even if we do not use
// it, we need it for coloring the plots that might be integrated from this
// color map.
color_map_p->setPen(pen);
// Copy the original color map's data into a backup copy.
mpa_origColorMapData = new QCPColorMapData(*(color_map_p->data()));
replot();
return color_map_p;
}
QCPColorMap *
BaseColorMapPlotWidget::addColorMap(const TimsFrame &tims_frame,
const ColorMapPlotConfig color_map_plot_config,
const QColor &color)
{
qDebug();
if(!color.isValid())
throw PappsoException(QString("The color to be used for the plot graph is invalid."));
QCPColorMap *color_map_p = new QCPColorMap(xAxis, yAxis);
color_map_p->setLayer("plotsLayer");
// Do not forget to copy the config!
m_colorMapPlotConfig = color_map_plot_config;
// Immediately create a copy of the original data for backup.
mpa_origColorMapPlotConfig = new ColorMapPlotConfig(color_map_plot_config);
qDebug();
#if 0
// This is the code on the QCustomPlot documentation and it works fine.
QCPColorMap *color_map_p = new QCPColorMap(xAxis, yAxis);
color_map_p->data()->setSize(50, 50);
color_map_p->data()->setRange(QCPRange(0, 2), QCPRange(0, 2));
for(int x = 0; x < 50; ++x)
for(int y = 0; y < 50; ++y)
color_map_p->data()->setCell(x, y, qCos(x / 10.0) + qSin(y / 10.0));
color_map_p->setGradient(QCPColorGradient::gpPolar);
color_map_p->rescaleDataRange(true);
rescaleAxes();
replot();
#endif
// Only now can afford to call createAllAncillaryItems() in this derived class
// because the color map has been created already. The rendering order will
// thus not hide the ancillary items, since they have been created after the
// color map plot (since the rendering order is according to the
// order in which the items have been created). See contructor note.
createAllAncillaryItems();
qDebug();
// Connect the signal of selection change so that we can re-emit it for the
// widget that is using *this widget.
connect(
color_map_p,
static_cast(&QCPAbstractPlottable::selectionChanged),
[this, color_map_p]() {
emit plottableSelectionChangedSignal(color_map_p, color_map_p->selected());
});
// qDebug() << "Configuring the color map with this config:"
//<< color_map_plot_config.toString();
color_map_p->data()->setSize(color_map_plot_config.keyCellCount,
color_map_plot_config.mzCellCount);
color_map_p->data()->setRange(
QCPRange(color_map_plot_config.minKeyValue, color_map_plot_config.maxKeyValue),
QCPRange(color_map_plot_config.minMzValue, color_map_plot_config.maxMzValue));
color_map_p->data()->fill(0.0);
// double max_intensity = 0;
qDebug();
// We have now to fill the color map.
std::size_t number_of_scans = tims_frame.getTotalNumberOfScans();
for(std::size_t i = 0; i < number_of_scans; i++)
{
std::vector tof_index_vector = tims_frame.getScanTofIndexList(i);
std::vector intensity_index_vector = tims_frame.getScanIntensityList(i);
// The first value is the key and the second value is the MapTrace into
// which we need to iterated and for each point (double mz, double
// intensity) create a map cell.
double dt_or_rt_key = i;
std::size_t vector_index = 0;
for(quint32 mzindex : tof_index_vector)
{
double mz = mzindex;
double intensity = intensity_index_vector.at(vector_index);
// max_intensity = std::max(max_intensity, intensity);
// We are filling dynamically the color map. If a cell had already
// something in, then we need to take that into account. This is
// because we let QCustomPlot handle the fuzzy transition between
// color map plot cells.
double prev_intensity = color_map_p->data()->data(dt_or_rt_key, mz);
double new_intensity = prev_intensity + intensity;
// qDebug() << "mz=" << mz << " int=" << intensity;
// Record the min/max cell intensity value (origM(in/ax)ZValue). We
// will need that later. Also update the lastM(in/ax)ZValue because
// when doing this kind of data conversion it is assume that the user
// actually changes the data.
m_colorMapPlotConfig.setOrigAndLastMinZValue(
std::min(m_colorMapPlotConfig.origMinZValue, new_intensity));
m_colorMapPlotConfig.setOrigAndLastMaxZValue(
std::max(m_colorMapPlotConfig.origMaxZValue, new_intensity));
// qDebug() << "Setting tri-point:" << dt_or_rt_key << "," << mz <<
// ","
//<< new_intensity;
color_map_p->data()->setCell(dt_or_rt_key, mz, new_intensity);
// qDebug() << "dt_or_rt_key=" << dt_or_rt_key << " mz=" << mz
// << " new_intensity=" << new_intensity;
vector_index++;
}
}
// At this point we have finished filling-up the color map.
// The gpThermal is certainly one of the best.
color_map_p->setGradient(QCPColorGradient::gpThermal);
color_map_p->data()->recalculateDataBounds();
color_map_p->rescaleDataRange(true);
color_map_p->rescaleAxes();
resetAxesRangeHistory();
// The pen of the color map itself is of no use. Instead the user will see the
// color of the axes' labels.
qDebug();
QPen pen = xAxis->basePen();
pen.setColor(color);
xAxis->setBasePen(pen);
xAxis->setLabelColor(color);
xAxis->setTickLabelColor(color);
yAxis->setBasePen(pen);
yAxis->setLabelColor(color);
yAxis->setTickLabelColor(color);
// And now set the color map's pen to the same color, even if we do not use
// it, we need it for coloring the plots that might be integrated from this
// color map.
color_map_p->setPen(pen);
// Copy the original color map's data into a backup copy.
mpa_origColorMapData = new QCPColorMapData(*(color_map_p->data()));
color_map_p->setInterpolate(false);
color_map_p->setTightBoundary(false);
replot();
qDebug() << color_map_p->data()->keyRange();
qDebug() << color_map_p->data()->valueRange();
qDebug() << color_map_p->data()->dataBounds();
qDebug();
return color_map_p;
}
void
BaseColorMapPlotWidget::transposeAxes()
{
// qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()" ;
QCPColorMap *color_map_p = static_cast(plottable(0));
QCPColorMapData *origData = color_map_p->data();
int keySize = origData->keySize();
int valueSize = origData->valueSize();
// qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
//<< "Orig data size:" << keySize << valueSize;
QCPRange keyRange = origData->keyRange();
QCPRange valueRange = origData->valueRange();
// qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
//<< "Value at cell 80,650:" << origData->cell(80,650);
// Transposed map.
QCPColorMapData *newData = new QCPColorMapData(valueSize, keySize, valueRange, keyRange);
for(int iter = 0; iter < keySize; ++iter)
{
for(int jter = 0; jter < valueSize; ++jter)
{
double cellData = origData->cell(iter, jter);
newData->setCell(jter, iter, cellData);
}
}
// qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
//<< "New data size:" << newData->keySize() << newData->valueSize();
// At this point the transposition has been done.
color_map_p->data()->clear();
color_map_p->rescaleDataRange(true);
// Now we need to invert the labels and data kinds.
Enums::DataKind temp_data_kind = m_colorMapPlotConfig.xAxisDataKind;
m_colorMapPlotConfig.xAxisDataKind = m_colorMapPlotConfig.yAxisDataKind;
m_colorMapPlotConfig.yAxisDataKind = temp_data_kind;
QString temp_axis_label = xAxis->label();
xAxis->setLabel(yAxis->label());
yAxis->setLabel(temp_axis_label);
// Will take ownership of the newData.
color_map_p->setData(newData);
// qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
//<< "Value at cell 80,650:" << newData->cell(80,650)
//<< "Value at cell 650, 80:" << newData->cell(650,80);
// QCPAxis *p_keyAxis = mp_colorMap->keyAxis();
// QCPAxis *p_valueAxis = mp_colorMap->valueAxis();
// mp_colorMap->setKeyAxis(p_valueAxis);
// mp_colorMap->setValueAxis(p_keyAxis);
color_map_p->rescaleAxes();
replot();
}
void
BaseColorMapPlotWidget::zAxisScaleToLog10()
{
// The user wants to rescale the intensity values of the color map according
// to: new_int = log10(orig_int).
// qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()" ;
if(m_colorMapPlotConfig.zAxisScale == Enums::AxisScale::log10)
{
qDebug() << "Asking to change z axis scale to log10 while it is already "
"like so.";
return;
}
// qDebug() << "m_colorMapPlotConfig:" << m_colorMapPlotConfig.toString();
QCPColorMap *color_map_p = static_cast(plottable(0));
QCPColorMapData *map_data = color_map_p->data();
int keySize = map_data->keySize();
int valueSize = map_data->valueSize();
QCPRange keyRange = map_data->keyRange();
QCPRange valueRange = map_data->valueRange();
// Make a copy of the current config so that we can modify
// the xxxZvalue values.
ColorMapPlotConfig new_color_map_plot_config(m_colorMapPlotConfig);
// But we need to reset these two values to be able to update them using
// std::min() and std::max() below.
new_color_map_plot_config.setOrigAndLastMinZValue(std::numeric_limits::max());
new_color_map_plot_config.setOrigAndLastMaxZValue(std::numeric_limits::min());
// qDebug() << "new_color_map_plot_config"
//<< new_color_map_plot_config.toString();
// Log-ified heat map.
QCPColorMapData *new_map_data = new QCPColorMapData(keySize, valueSize, keyRange, valueRange);
// qDebug() << "Starting iteration in the color map.";
for(int iter = 0; iter < keySize; ++iter)
{
for(int jter = 0; jter < valueSize; ++jter)
{
double cell_data = map_data->cell(iter, jter);
double new_cell_data = 0;
if(!cell_data)
// The log10 would be -inf, but then we'd have a huge data range and
// the color map would look totally blue... that is like 0 intensity
// all over.
new_cell_data = -1;
else
new_cell_data = std::log10(cell_data);
// Store the new values here. Should we change the last or orig or
// both ?
new_color_map_plot_config.lastMinZValue =
//(new_cell_data < new_color_map_plot_config.minZValue
//? new_cell_data
//: new_color_map_plot_config.minZValue);
std::min(new_color_map_plot_config.lastMinZValue, new_cell_data);
new_color_map_plot_config.lastMaxZValue =
//(new_cell_data > new_color_map_plot_config.maxZValue
//? new_cell_data
//: new_color_map_plot_config.maxZValue);
std::max(new_color_map_plot_config.lastMaxZValue, new_cell_data);
// qDebug() << "cell_data:" << cell_data
//<< "new_cell_data:" << new_cell_data
//<< "new_color_map_plot_config.minZValue:"
//<< new_color_map_plot_config.minZValue
//<< "new_color_map_plot_config.maxZValue:"
//<< new_color_map_plot_config.maxZValue;
new_map_data->setCell(iter, jter, new_cell_data);
}
}
// qDebug() << "Finished iteration in the color map.";
color_map_p->data()->clear();
// Will take ownership of the new_map_data.
color_map_p->setData(new_map_data);
color_map_p->data()->recalculateDataBounds();
color_map_p->rescaleDataRange(true);
// At this point the new color map data have taken their place, we can update
// the config. This, way any new filtering can take advantage of the new
// values and compute the threshold correctly.
m_colorMapPlotConfig = new_color_map_plot_config;
// Now we need to document the change.
m_colorMapPlotConfig.zAxisScale = Enums::AxisScale::log10;
// qDebug() << "new_color_map_plot_config"
//<< new_color_map_plot_config.toString();
// qDebug() << "m_colorMapPlotConfig:" << m_colorMapPlotConfig.toString();
// We should not do this, as the user might have zoomed to a region of
// interest.
// color_map_p->rescaleAxes();
replot();
}
void
BaseColorMapPlotWidget::zAxisFilterLowPassPercentage(double threshold_percentage)
{
// This filter allows all the values smaller than a threshold to remain
// unchanged. Instead, all the values above the threshold will be reset to
// that threshold.
//
// The effect of this filter is to enhance the high-intensity signal.
QCPColorMap *color_map_p = static_cast(plottable(0));
QCPColorMapData *map_data = color_map_p->data();
int keySize = map_data->keySize();
int valueSize = map_data->valueSize();
QCPRange keyRange = map_data->keyRange();
QCPRange valueRange = map_data->valueRange();
double minZValue = m_colorMapPlotConfig.lastMinZValue;
double maxZValue = m_colorMapPlotConfig.lastMaxZValue;
double amplitude = maxZValue - minZValue;
double amplitude_fraction = amplitude * threshold_percentage / 100;
double threshold = minZValue + amplitude_fraction;
// qDebug() << "Before filtering minZValue:" << minZValue
//<< "maxZValue:" << maxZValue << "fraction:" << fraction
//<< "threshold:" << threshold
//<< "new threshold percentage:" << new_threshold_percentage;
// Make a copy of the current config so that we can modify
// the xxxZvalue values.
ColorMapPlotConfig new_color_map_plot_config(m_colorMapPlotConfig);
// But we need to reset these two values to be able to update them using
// std::min() and std::max() below.
new_color_map_plot_config.lastMinZValue = std::numeric_limits::max();
new_color_map_plot_config.lastMaxZValue = std::numeric_limits::min();
// Filtered
QCPColorMapData *new_map_data = new QCPColorMapData(keySize, valueSize, keyRange, valueRange);
for(int iter = 0; iter < keySize; ++iter)
{
for(int jter = 0; jter < valueSize; ++jter)
{
double cell_data = map_data->cell(iter, jter);
double new_cell_data = 0;
if(cell_data < threshold)
// Keep the value, we are in low-pass
new_cell_data = cell_data;
else
new_cell_data = threshold;
// Store the new values here.
new_color_map_plot_config.lastMinZValue =
//(new_cell_data < new_color_map_plot_config.minZValue
//? new_cell_data
//: new_color_map_plot_config.minZValue);
std::min(new_color_map_plot_config.lastMinZValue, new_cell_data);
new_color_map_plot_config.lastMaxZValue =
//(new_cell_data > new_color_map_plot_config.maxZValue
//? new_cell_data
//: new_color_map_plot_config.maxZValue);
std::max(new_color_map_plot_config.lastMaxZValue, new_cell_data);
// qDebug() << "cell_data:" << cell_data
//<< "new_cell_data:" << new_cell_data
//<< "new_color_map_plot_config.minZValue:"
//<< new_color_map_plot_config.minZValue
//<< "new_color_map_plot_config.maxZValue:"
//<< new_color_map_plot_config.maxZValue;
new_map_data->setCell(iter, jter, new_cell_data);
}
}
color_map_p->data()->clear();
// Will take ownership of the new_map_data.
color_map_p->setData(new_map_data);
color_map_p->data()->recalculateDataBounds();
color_map_p->rescaleDataRange(true);
// At this point the new color map data have taken their place, we can update
// the config. This, way any new filtering can take advantage of the new
// values and compute the threshold correctly.
m_colorMapPlotConfig = new_color_map_plot_config;
// qDebug() << "Member colormap plot config is now, after filter was applied:"
//<< m_colorMapPlotConfig.toString();
// We should not do this, as the user might have zoomed to a region of
// interest.
// color_map_p->rescaleAxes();
replot();
}
void
BaseColorMapPlotWidget::zAxisFilterLowPassThreshold(double threshold)
{
// This filter allows all the values smaller than a threshold to remain
// unchanged. Instead, all the values above the threshold will be reset to
// that threshold.
QCPColorMap *color_map_p = static_cast(plottable(0));
QCPColorMapData *map_data = color_map_p->data();
int keySize = map_data->keySize();
int valueSize = map_data->valueSize();
QCPRange keyRange = map_data->keyRange();
QCPRange valueRange = map_data->valueRange();
// qDebug() << "Before filtering minZValue:" << minZValue
//<< "maxZValue:" << maxZValue << "fraction:" << fraction
//<< "threshold:" << threshold
//<< "new threshold percentage:" << new_threshold_percentage;
// Make a copy of the current config so that we can modify
// the xxxZvalue values.
ColorMapPlotConfig new_color_map_plot_config(m_colorMapPlotConfig);
// But we need to reset these two values to be able to update them using
// std::min() and std::max() below.
new_color_map_plot_config.lastMinZValue = std::numeric_limits::max();
new_color_map_plot_config.lastMaxZValue = std::numeric_limits::min();
// Filtered
QCPColorMapData *new_map_data = new QCPColorMapData(keySize, valueSize, keyRange, valueRange);
for(int iter = 0; iter < keySize; ++iter)
{
for(int jter = 0; jter < valueSize; ++jter)
{
double cell_data = map_data->cell(iter, jter);
double new_cell_data = 0;
if(cell_data < threshold)
// Keep the value, we are in low-pass
new_cell_data = cell_data;
else
new_cell_data = threshold;
// Store the new values here.
new_color_map_plot_config.lastMinZValue =
//(new_cell_data < new_color_map_plot_config.minZValue
//? new_cell_data
//: new_color_map_plot_config.minZValue);
std::min(new_color_map_plot_config.lastMinZValue, new_cell_data);
new_color_map_plot_config.lastMaxZValue =
//(new_cell_data > new_color_map_plot_config.maxZValue
//? new_cell_data
//: new_color_map_plot_config.maxZValue);
std::max(new_color_map_plot_config.lastMaxZValue, new_cell_data);
// qDebug() << "cell_data:" << cell_data
//<< "new_cell_data:" << new_cell_data
//<< "new_color_map_plot_config.minZValue:"
//<< new_color_map_plot_config.minZValue
//<< "new_color_map_plot_config.maxZValue:"
//<< new_color_map_plot_config.maxZValue;
new_map_data->setCell(iter, jter, new_cell_data);
}
}
color_map_p->data()->clear();
// Will take ownership of the new_map_data.
color_map_p->setData(new_map_data);
color_map_p->data()->recalculateDataBounds();
color_map_p->rescaleDataRange(true);
// At this point the new color map data have taken their place, we can update
// the config. This, way any new filtering can take advantage of the new
// values and compute the threshold correctly.
m_colorMapPlotConfig = new_color_map_plot_config;
// qDebug() << "Member colormap plot config is now, after filter was applied:"
//<< m_colorMapPlotConfig.toString();
// We should not do this, as the user might have zoomed to a region of
// interest.
// color_map_p->rescaleAxes();
replot();
}
void
BaseColorMapPlotWidget::zAxisFilterHighPassPercentage(double threshold_percentage)
{
// This filter allows all the value greater than a threshold to remain
// unchanged. Instead, all the values below the threshold will be reset to
// that threshold value.
//
// The effect of this filter is to reduce the low-intensity signal: reduce
// noise.
QCPColorMap *color_map_p = static_cast(plottable(0));
QCPColorMapData *map_data = color_map_p->data();
int keySize = map_data->keySize();
int valueSize = map_data->valueSize();
QCPRange keyRange = map_data->keyRange();
QCPRange valueRange = map_data->valueRange();
double minZValue = m_colorMapPlotConfig.lastMinZValue;
double maxZValue = m_colorMapPlotConfig.lastMaxZValue;
double amplitude = maxZValue - minZValue;
double amplitude_fraction = amplitude * threshold_percentage / 100;
double threshold = minZValue + amplitude_fraction;
// qDebug() << "Before filtering minZValue:" << minZValue
//<< "maxZValue:" << maxZValue << "fraction:" << fraction
//<< "threshold:" << threshold
//<< "new threshold percentage:" << new_threshold_percentage;
// Make a copy of the current config so that we can modify
// the xxxZvalue values.
ColorMapPlotConfig new_color_map_plot_config(m_colorMapPlotConfig);
// But we need to reset these two values to be able to update them using
// std::min() and std::max() below.
new_color_map_plot_config.lastMinZValue = std::numeric_limits::max();
new_color_map_plot_config.lastMaxZValue = std::numeric_limits::min();
// Filtered
QCPColorMapData *new_map_data = new QCPColorMapData(keySize, valueSize, keyRange, valueRange);
for(int iter = 0; iter < keySize; ++iter)
{
for(int jter = 0; jter < valueSize; ++jter)
{
double cell_data = map_data->cell(iter, jter);
double new_cell_data = 0;
if(cell_data > threshold)
// Keep the value, we are in high-pass
new_cell_data = cell_data;
else
new_cell_data = threshold;
// Store the new values here.
new_color_map_plot_config.lastMinZValue =
//(new_cell_data < new_color_map_plot_config.minZValue
//? new_cell_data
//: new_color_map_plot_config.minZValue);
std::min(new_color_map_plot_config.lastMinZValue, new_cell_data);
new_color_map_plot_config.lastMaxZValue =
//(new_cell_data > new_color_map_plot_config.maxZValue
//? new_cell_data
//: new_color_map_plot_config.maxZValue);
std::max(new_color_map_plot_config.lastMaxZValue, new_cell_data);
// qDebug() << "cell_data:" << cell_data
//<< "new_cell_data:" << new_cell_data
//<< "new_color_map_plot_config.minZValue:"
//<< new_color_map_plot_config.minZValue
//<< "new_color_map_plot_config.maxZValue:"
//<< new_color_map_plot_config.maxZValue;
new_map_data->setCell(iter, jter, new_cell_data);
}
}
color_map_p->data()->clear();
// Will take ownership of the new_map_data.
color_map_p->setData(new_map_data);
color_map_p->data()->recalculateDataBounds();
color_map_p->rescaleDataRange(true);
// At this point the new color map data have taken their place, we can update
// the config. This, way any new filtering can take advantage of the new
// values and compute the threshold correctly.
m_colorMapPlotConfig = new_color_map_plot_config;
// qDebug() << "Member colormap plot config is now, after filter was applied:"
//<< m_colorMapPlotConfig.toString();
// We should not do this, as the user might have zoomed to a region of
// interest.
// color_map_p->rescaleAxes();
replot();
}
void
BaseColorMapPlotWidget::zAxisDataResetToOriginal()
{
// The user might have changed to the axis scale to log10, for example.
// While doing this, the original data were still available in
// mpa_origColorMapData,with mpa_origColorMapPlotConfig. We need to reset the
// current data to the original data.
//
// Same thing for filters that might have been applied to the data.
QCPColorMap *color_map_p = static_cast(plottable(0));
color_map_p->data()->clear();
if(mpa_origColorMapData == nullptr)
throw(PappsoException("Not possible that the mpa_origColorMapData pointer be null."));
// We do no want that the color_map_p takes ownership of the data, because
// these must remain there always, so pass true, to say that we want to copy
// the data not transfer the pointer.
color_map_p->setData(mpa_origColorMapData, true);
color_map_p->data()->recalculateDataBounds();
color_map_p->rescaleDataRange(true);
// We should not do this, as the user might have zoomed to a region of
// interest.
// color_map_p->rescaleAxes();
// Reset the current plot config to what it was originally. The member
// m_colorMapPlotConfig.zAxisScale is now Enums::AxisScale::orig.
m_colorMapPlotConfig = *mpa_origColorMapPlotConfig;
replot();
}
Enums::DataKind
BaseColorMapPlotWidget::xAxisDataKind() const
{
return m_colorMapPlotConfig.xAxisDataKind;
}
Enums::DataKind
BaseColorMapPlotWidget::yAxisDataKind() const
{
return m_colorMapPlotConfig.yAxisDataKind;
}
Enums::AxisScale
BaseColorMapPlotWidget::axisScale(Enums::Axis axis) const
{
if(axis == Enums::Axis::x)
return m_colorMapPlotConfig.xAxisScale;
else if(axis == Enums::Axis::y)
return m_colorMapPlotConfig.yAxisScale;
else if(axis == Enums::Axis::z)
return m_colorMapPlotConfig.zAxisScale;
else
throw PappsoException(
QString("basecolormapplotwidget.cpp: The axis cannot be different than "
"x, y or z."));
return Enums::AxisScale::unset;
}
Enums::AxisScale
BaseColorMapPlotWidget::xAxisScale() const
{
return m_colorMapPlotConfig.xAxisScale;
}
Enums::AxisScale
BaseColorMapPlotWidget::yAxisScale() const
{
return m_colorMapPlotConfig.yAxisScale;
}
Enums::AxisScale
BaseColorMapPlotWidget::zAxisScale() const
{
return m_colorMapPlotConfig.zAxisScale;
}
void
BaseColorMapPlotWidget::setPlottingColor(QCPAbstractPlottable *plottable_p, const QColor &new_color)
{
Q_UNUSED(plottable_p);
// The pen of the color map itself is of no use. Instead the user will see the
// color of the axes' labels.
QPen pen = xAxis->basePen();
pen.setColor(new_color);
xAxis->setBasePen(pen);
xAxis->setLabelColor(new_color);
xAxis->setTickLabelColor(new_color);
yAxis->setBasePen(pen);
yAxis->setLabelColor(new_color);
yAxis->setTickLabelColor(new_color);
// And now set the color map's pen to the same color, even if we do not use
// it, we need it for coloring the plots that might be integrated from this
// color map.
QCPColorMap *color_map_p = static_cast(plottable(0));
color_map_p->setPen(pen);
replot();
}
QColor
BaseColorMapPlotWidget::getPlottingColor(int index) const
{
Q_UNUSED(index);
QPen pen = xAxis->basePen();
return pen.color();
}
void
BaseColorMapPlotWidget::currentXaxisRangeIndices(int &lower, int &upper)
{
// We want to limit the ranges to the visible data range in the plot widget.
QCPColorMap *color_map_p = static_cast(plottable(0));
QCPColorMapData *map_data = color_map_p->data();
bool found_range = false;
// Get the full data set DT values range because if the context contains no
// values for the currently displayed ranges, then we fall back to them;
QCPRange full_data_range = color_map_p->getKeyRange(found_range);
if(!found_range)
{
qDebug() << "The range was not found";
return;
}
// qDebug() << "Full key data range:" << full_data_range.lower << "-"
//<< full_data_range.upper;
// But what we actually want is the currently visible axes ranges. And these
// are stored in the context.
double visible_data_range_lower = m_context.m_xRange.lower;
double visible_data_range_upper = m_context.m_xRange.upper;
// qDebug() << "Visible key data range:" << visible_data_range_lower << "-"
//<< visible_data_range_upper;
// Note that if there has been *no* panning, rescale, nothing, with the color
// map, then the context has no idea of the ranges. So we need to check that.
// If that is the case, then we use the full key range as the full plot is
// displayed full scale upon its first showing.
if(!visible_data_range_lower || !visible_data_range_upper)
{
visible_data_range_lower = full_data_range.lower;
visible_data_range_upper = full_data_range.upper;
}
// qDebug() << "Visible key range:" << visible_data_range_lower << "-"
//<< visible_data_range_upper;
// And now convert the double value ranges into cell indices, which is what we
// are being asked for.
map_data->coordToCell(visible_data_range_lower, 0, &lower, nullptr);
map_data->coordToCell(visible_data_range_upper, 0, &upper, nullptr);
// qDebug() << "Cell indices for currently visible key range:" << lower << "-"
//<< upper;
}
void
BaseColorMapPlotWidget::currentYaxisRangeIndices(int &lower, int &upper)
{
// We want to limit the ranges to the visible data range in the plot widget.
QCPColorMap *color_map_p = static_cast(plottable(0));
QCPColorMapData *map_data = color_map_p->data();
bool found_range = false;
// Get the full data set MZ values range because if the context contains no
// values for the currently displayed ranges, then we fall back to them;
QCPRange full_data_range = color_map_p->getValueRange(found_range);
if(!found_range)
{
qDebug() << "The range was not found";
return;
}
// qDebug() << "Full value data range:" << full_data_range.lower << "-"
//<< full_data_range.upper;
// But what we actually want is the currently visible axes ranges. And these
// are stored in the context.
double visible_data_range_lower = m_context.m_yRange.lower;
double visible_data_range_upper = m_context.m_yRange.upper;
// qDebug() << "Visible value data range:" << visible_data_range_lower << "-"
//<< visible_data_range_upper;
// Note that if there has been *no* panning, rescale, nothing, with the color
// map, then the context has no idea of the ranges. So we need to check that.
// If that is the case, then we use the full key range as the full plot is
// displayed full scale upon its first showing.
if(!visible_data_range_lower || !visible_data_range_upper)
{
visible_data_range_lower = full_data_range.lower;
visible_data_range_upper = full_data_range.upper;
}
// qDebug() << "Final visible value data range:" << visible_data_range_lower
//<< "-" << visible_data_range_upper;
// And now convert the double value ranges into cell indices, which is what we
// are being asked for.
map_data->coordToCell(0, visible_data_range_lower, nullptr, &lower);
map_data->coordToCell(0, visible_data_range_upper, nullptr, &upper);
// qDebug() << "Cell indices for currently visible value range:" << lower <<
// "-"
//<< upper;
}
void
BaseColorMapPlotWidget::dataTo3ColString(QString &data_string)
{
// We want to export the data to a string in the x y z format, with
// x=key (cell's x coordinate)
// y=value (cell's y coordinate)
// z=intensity (cell value)
QCPColorMap *color_map_p = static_cast(plottable(0));
QCPColorMapData *map_data = color_map_p->data();
int key_index_lower_range;
int key_index_upper_range;
currentXaxisRangeIndices(key_index_lower_range, key_index_upper_range);
// qDebug() << "Cell indices for currently visible key range:"
//<< key_index_lower_range << "-" << key_index_upper_range;
int value_index_lower_range;
int value_index_upper_range;
currentYaxisRangeIndices(value_index_lower_range, value_index_upper_range);
// qDebug() << "Cell indices for currently visible value range:"
//<< value_index_lower_range << "-" << value_index_upper_range;
data_string.clear();
QString debug_string;
// Iterate in the matrix' key axis (DT, for example)
for(int key_iter = key_index_lower_range; key_iter < key_index_upper_range; ++key_iter)
{
// Iterate in the matrix' value axis (MZ, for example)
for(int value_iter = value_index_lower_range; value_iter < value_index_upper_range;
++value_iter)
{
// This would be the DT value (x axis)
double key;
// This would be the MZ value (y axis)
double value;
map_data->cellToCoord(key_iter, value_iter, &key, &value);
data_string += QString("%1 %2 %3\n")
.arg(key, 0, 'f', 6, ' ')
.arg(value, 0, 'f', 6, ' ')
// The intensity without decimals
.arg(map_data->cell(key_iter, value_iter), 0, 'f', 0, ' ');
}
}
// qDebug() << "The completed data string has size: " << data_string.size();
}
void
BaseColorMapPlotWidget::dataToMatrixString(QString &data_string, bool detailed)
{
// We want to export the data in the form of a matrix, exactly as the data
// appear within the colormap, unless the color is replaced with the intensity
// value.
// We want to limit the export to the visible data range in the plot widget.
QCPColorMap *color_map_p = static_cast(plottable(0));
QCPColorMapData *map_data = color_map_p->data();
int key_index_lower_range;
int key_index_upper_range;
currentXaxisRangeIndices(key_index_lower_range, key_index_upper_range);
// qDebug() << "Cell indices for currently visible key range:"
//<< key_index_lower_range << "-" << key_index_upper_range;
int value_index_lower_range;
int value_index_upper_range;
currentYaxisRangeIndices(value_index_lower_range, value_index_upper_range);
// qDebug() << "Cell indices for currently visible value range:"
//<< value_index_lower_range << "-" << value_index_upper_range;
data_string.clear();
// At this point, we can write the header of the key data (that is the dt
// key values).
for(int key_iter = key_index_lower_range; key_iter < key_index_upper_range; ++key_iter)
{
double current_key_value;
map_data->cellToCoord(key_iter, 0, ¤t_key_value, nullptr);
data_string += QString("%1 ").arg(current_key_value, 0, 'f', 6, ' ');
}
// Finally call the newline
data_string += "\n";
// Now fill in the matrix, from top to down, that is from higher m/z values to
// lower values.
// The matrix we are exporting looks like this:
// |
// |
// |
// |
// |
// |
// m/z |
// |
// |
// |
// |
// |______________________________
// dt
// Because we want the matrix to be presented the same, we need to fill in the
// matrix from top to bottom starting from higher m/z values.
for(int value_iter = value_index_upper_range; value_iter >= value_index_lower_range; --value_iter)
{
for(int key_iter = key_index_lower_range; key_iter < key_index_upper_range; ++key_iter)
{
double intensity = map_data->cell(key_iter, value_iter);
// Only to report debug messages
double key_double;
double value_double;
map_data->cellToCoord(key_iter, value_iter, &key_double, &value_double);
// qDebug() << "Currently iterated cell: " << key_iter << ","
//<< value_iter << "with values:" << key_double << ","
//<< value_double << "with intensity:" << intensity;
// The intensity without decimals
if(detailed)
data_string += QString("%1/%2/%3 ")
.arg(key_double, 0, 'f', 6, ' ')
.arg(value_double, 0, 'f', 6, ' ')
.arg(intensity);
else
data_string += QString("%1 ").arg(intensity, 0, 'f', 0, ' ');
}
data_string += "\n";
}
// qDebug().noquote() << "The matrix: " << data_string;
// qDebug() << "The completed data string has size: " <<
// data_string.size();
data_string += "\n";
}
} // namespace pappso
libpappsomspp-0.11.27/src/pappsomspp/gui/plotwidget/basecolormapplotwidget.h 000664 001750 001750 00000007535 15250226472 030664 0 ustar 00rusconi rusconi 000000 000000 /* This code comes right from the msXpertSuite software project.
*
* msXpertSuite - mass spectrometry software suite
* -----------------------------------------------
* Copyright(C) 2009,...,2018 Filippo Rusconi
*
* http://www.msxpertsuite.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* END software license
*/
#pragma once
/////////////////////// StdLib includes
/////////////////////// Qt includes
#include
#include
#include
#include
#include
#include
/////////////////////// QCustomPlot
#include
#include
/////////////////////// Local includes
#include "pappsomspp/export-import-config.h"
#include "baseplotwidget.h"
#include "colormapplotconfig.h"
#include "../../core/trace/trace.h"
#include "../../core/vendors/tims/timsframe.h"
namespace pappso
{
class BaseColorMapPlotWidget;
typedef std::shared_ptr BaseColorMapPlotWidgetSPtr;
typedef std::shared_ptr
BaseColorMapPlotWidgetCstSPtr;
class PMSPP_LIB_DECL BaseColorMapPlotWidget : public BasePlotWidget
{
Q_OBJECT;
public:
explicit BaseColorMapPlotWidget(QWidget *parent);
explicit BaseColorMapPlotWidget(QWidget *parent,
const QString &x_axis_label,
const QString &y_axis_label);
virtual ~BaseColorMapPlotWidget();
virtual void
setColorMapPlotConfig(const ColorMapPlotConfig &color_map_config);
virtual const ColorMapPlotConfig &getColorMapPlotConfig();
const ColorMapPlotConfig *getOrigColorMapPlotConfig();
virtual QCPColorMap *addColorMap(
std::shared_ptr> double_map_trace_map_sp,
const ColorMapPlotConfig color_map_plot_config,
const QColor &color);
virtual QCPColorMap *
addColorMap(const TimsFrame &tims_frame,
const ColorMapPlotConfig color_map_plot_config,
const QColor &color);
virtual void transposeAxes();
// Change the scale of the intensity to log10 (color, z virtual axis)
virtual void zAxisScaleToLog10();
virtual void zAxisFilterLowPassPercentage(double threshold_percentage);
/** @brief fix maximum value for the intensity
*/
virtual void zAxisFilterLowPassThreshold(double threshold);
virtual void zAxisFilterHighPassPercentage(double threshold_percentage);
virtual void zAxisDataResetToOriginal();
Enums::DataKind xAxisDataKind() const;
Enums::DataKind yAxisDataKind() const;
Enums::AxisScale axisScale(Enums::Axis axis) const;
Enums::AxisScale xAxisScale() const;
Enums::AxisScale yAxisScale() const;
Enums::AxisScale zAxisScale() const;
virtual void setPlottingColor(QCPAbstractPlottable *plottable_p,
const QColor &new_color) override;
virtual QColor getPlottingColor(int index = 0) const override;
void dataTo3ColString(QString &data_string);
void dataToMatrixString(QString &data_string, bool detailed = false);
void currentXaxisRangeIndices(int &lower, int &upper);
void currentYaxisRangeIndices(int &lower, int &upper);
signals:
protected:
QCPColorMapData *mpa_origColorMapData = nullptr;
ColorMapPlotConfig m_colorMapPlotConfig;
ColorMapPlotConfig *mpa_origColorMapPlotConfig = nullptr;
};
} // namespace pappso
libpappsomspp-0.11.27/src/pappsomspp/gui/plotwidget/baseplotcontext.cpp 000664 001750 001750 00000106174 15250226472 027662 0 ustar 00rusconi rusconi 000000 000000 // Copyright 2021 Filippo Rusconi
// GPL3+
#include "baseplotcontext.h"
#include "pappsomspp/core/processing/combiners/integrationscope.h"
#include "pappsomspp/core/processing/combiners/integrationscoperect.h"
#include "pappsomspp/core/processing/combiners/integrationscoperhomb.h"
namespace pappso
{
std::map qtMouseButtonMap{
{Qt::NoButton, "NoButton"},
{Qt::LeftButton, "LeftButton"},
{Qt::RightButton, "RightButton"},
{Qt::MiddleButton, "MiddleButton"}};
std::map qtMouseButtonsMap{
{Qt::NoButton, "NoButton"},
{Qt::AllButtons, "AllButtons"},
{Qt::LeftButton, "LeftButton"},
{Qt::RightButton, "RightButton"},
{Qt::MiddleButton, "MiddleButton"},
{Qt::LeftButton | Qt::RightButton, "LeftRightButtons"},
{Qt::LeftButton | Qt::MiddleButton, "LeftMiddleButtons"},
{Qt::RightButton | Qt::MiddleButton, "RightMiddleButtons"},
};
std::map qtKeyboardModifierMap{
{Qt::NoModifier, "No modifier"},
{Qt::ShiftModifier, "A Shift key"},
{Qt::ControlModifier, "A Ctrl key"},
{Qt::AltModifier, "An Alt key"},
{Qt::MetaModifier, "A Meta key"},
{Qt::KeypadModifier, "A keypad button"},
{Qt::GroupSwitchModifier, "A Mode_switch key"}};
BasePlotContext::BasePlotContext()
{
}
BasePlotContext::BasePlotContext(const BasePlotContext &other)
{
// qDebug() << "Constructing BasePlotContext by copy.";
initialize(other);
#if 0
m_dataKind = other.m_dataKind;
m_isMouseDragging = other.m_isMouseDragging;
m_wasMouseDragging = other.m_wasMouseDragging;
m_dragDirections = other.m_dragDirections;
m_isKeyBoardDragging = other.m_isKeyBoardDragging;
m_isLeftPseudoButtonKeyPressed = other.m_isLeftPseudoButtonKeyPressed;
m_isRightPseudoButtonKeyPressed = other.m_isRightPseudoButtonKeyPressed;
m_wasKeyBoardDragging = other.m_wasKeyBoardDragging;
m_startDragPoint = other.m_startDragPoint;
m_currentDragPoint = other.m_currentDragPoint;
m_lastCursorHoveredPoint = other.m_lastCursorHoveredPoint;
m_selectionPolygon = other.m_selectionPolygon;
if(mpa_integrationScope != nullptr)
delete mpa_integrationScope;
if(other.mpa_integrationScope != nullptr)
mpa_integrationScope = other.mpa_integrationScope->clone();
m_integrationScopeRhombWidth = other.m_integrationScopeRhombWidth;
m_integrationScopeRhombHeight = other.m_integrationScopeRhombHeight;
// The effective range of the axes.
m_xRange = other.m_xRange;
m_yRange = other.m_yRange;
// Tell if the mouse move was started onto either axis, because that will
// condition if some calculations needs to be performed or not (for example,
// if the mouse cursor motion was started on an axis, there is no point to
// perform deconvolutions).
m_wasClickOnXAxis = other.m_wasClickOnXAxis;
m_wasClickOnYAxis = other.m_wasClickOnYAxis;
m_isMeasuringDistance = other.m_isMeasuringDistance;
// The user-selected region over the plot.
// Note that we cannot use QCPRange structures because these are normalized by
// QCustomPlot in such a manner that lower is actually < upper. But we need
// for a number of our calculations (specifically for the deconvolutions) to
// actually have the lower value be start drag point.x even if the drag
// direction was from right to left.
m_xRegionRangeStart = other.m_xRegionRangeStart;
m_xRegionRangeStop = other.m_xRegionRangeStop;
m_yRegionRangeStart = other.m_yRegionRangeStart;
m_yRegionRangeStop = other.m_yRegionRangeStop;
m_xDelta = other.m_xDelta;
m_yDelta = other.m_yDelta;
m_pressedKeyCode = other.m_pressedKeyCode;
m_pressedKeyCodes = other.m_pressedKeyCodes;
m_pressedKeyText = other.m_pressedKeyText;
m_releasedKeyCode = other.m_releasedKeyCode;
m_releasedKeyCodes = other.m_releasedKeyCodes;
m_releasedKeyText = other.m_releasedKeyText;
m_keyboardModifiers = other.m_keyboardModifiers;
m_lastPressedMouseButton = other.m_lastPressedMouseButton;
m_lastReleasedMouseButton = other.m_lastReleasedMouseButton;
m_pressedMouseButtons = other.m_pressedMouseButtons;
m_mouseButtonsAtMousePress = other.m_mouseButtonsAtMousePress;
m_mouseButtonsAtMouseRelease = other.m_mouseButtonsAtMouseRelease;
#endif
}
BasePlotContext::~BasePlotContext()
{
if(mpa_integrationScope != nullptr)
delete mpa_integrationScope;
}
BasePlotContext *
BasePlotContext::clone()
{
return new BasePlotContext(*this);
}
void
BasePlotContext::initialize(const BasePlotContext &other)
{
m_dataKind = other.m_dataKind;
m_isMouseDragging = other.m_isMouseDragging;
m_wasMouseDragging = other.m_wasMouseDragging;
m_dragDirections = other.m_dragDirections;
m_isKeyBoardDragging = other.m_isKeyBoardDragging;
m_isLeftPseudoButtonKeyPressed = other.m_isLeftPseudoButtonKeyPressed;
m_isRightPseudoButtonKeyPressed = other.m_isRightPseudoButtonKeyPressed;
m_wasKeyBoardDragging = other.m_wasKeyBoardDragging;
m_startDragPoint = other.m_startDragPoint;
m_currentDragPoint = other.m_currentDragPoint;
m_lastCursorHoveredPoint = other.m_lastCursorHoveredPoint;
m_selectionPolygon = other.m_selectionPolygon;
if(mpa_integrationScope != nullptr)
delete mpa_integrationScope;
if(other.mpa_integrationScope != nullptr)
mpa_integrationScope = other.mpa_integrationScope->clone();
m_integrationScopeRhombWidth = other.m_integrationScopeRhombWidth;
m_integrationScopeRhombHeight = other.m_integrationScopeRhombHeight;
// The effective range of the axes.
m_xRange = other.m_xRange;
m_yRange = other.m_yRange;
// Tell if the mouse move was started onto either axis, because that will
// condition if some calculations needs to be performed or not (for example,
// if the mouse cursor motion was started on an axis, there is no point to
// perform deconvolutions).
m_wasClickOnXAxis = other.m_wasClickOnXAxis;
m_wasClickOnYAxis = other.m_wasClickOnYAxis;
m_isMeasuringDistance = other.m_isMeasuringDistance;
// The user-selected region over the plot.
// Note that we cannot use QCPRange structures because these are normalized by
// QCustomPlot in such a manner that lower is actually < upper. But we need
// for a number of our calculations (specifically for the deconvolutions) to
// actually have the lower value be start drag point.x even if the drag
// direction was from right to left.
m_xRegionRangeStart = other.m_xRegionRangeStart;
m_xRegionRangeStop = other.m_xRegionRangeStop;
m_yRegionRangeStart = other.m_yRegionRangeStart;
m_yRegionRangeStop = other.m_yRegionRangeStop;
m_xDelta = other.m_xDelta;
m_yDelta = other.m_yDelta;
m_pressedKeyCode = other.m_pressedKeyCode;
m_pressedKeyCodes = other.m_pressedKeyCodes;
m_pressedKeyText = other.m_pressedKeyText;
m_releasedKeyCode = other.m_releasedKeyCode;
m_releasedKeyCodes = other.m_releasedKeyCodes;
m_releasedKeyText = other.m_releasedKeyText;
m_keyboardModifiers = other.m_keyboardModifiers;
m_lastPressedMouseButton = other.m_lastPressedMouseButton;
m_lastReleasedMouseButton = other.m_lastReleasedMouseButton;
m_pressedMouseButtons = other.m_pressedMouseButtons;
m_mouseButtonsAtMousePress = other.m_mouseButtonsAtMousePress;
m_mouseButtonsAtMouseRelease = other.m_mouseButtonsAtMouseRelease;
}
void
BasePlotContext::updateIntegrationScope()
{
// qDebug();
// By essence, IntegrationScope is 1D scope. The point of the scope is the
// left bottom point, and then we document the width.
double x_range_start = std::min(m_currentDragPoint.x(), m_startDragPoint.x());
double x_range_end = std::max(m_currentDragPoint.x(), m_startDragPoint.x());
double y_position = m_startDragPoint.y();
QPointF point(x_range_start, y_position);
double width = x_range_end - x_range_start;
// qDebug() << "Going to create an integration scope with point:" << point
// << "and width:" << width;
// Because the nature of the integration scope might change inside
// a given context, we need to delete and reallocate.
if(mpa_integrationScope != nullptr)
delete mpa_integrationScope;
mpa_integrationScope = new IntegrationScope(point, width);
// qDebug() << "Created integration scope:" <<
// msp_integrationScope->toString();
}
void
BasePlotContext::updateIntegrationScopeRect()
{
// qDebug();
// By essence, IntegrationScopeRect is a squared rectangle scope. The point of
// the scope is the left bottom point, and then we document the width and the
// height.
/* Like this:
*
+---------------------------+ -
| | |
| | |
| | m_height
| | |
| | |
P---------------------------+ -
|--------- m_width ---------|
*/
// We need to find the point that is actually the left bottom point.
QPointF point;
double width = 0;
double height = 0;
if(static_cast(m_dragDirections) &
static_cast(DragDirections::LEFT_TO_RIGHT) &&
static_cast(m_dragDirections) &
static_cast(DragDirections::BOTTOM_TO_TOP))
{
point.rx() = m_startDragPoint.x();
point.ry() = m_startDragPoint.y();
width = m_currentDragPoint.x() - point.rx();
height = m_currentDragPoint.y() - point.ry();
// qDebug() << "left to right - bottom to top";
}
if(static_cast(m_dragDirections) &
static_cast(DragDirections::RIGHT_TO_LEFT) &&
static_cast(m_dragDirections) &
static_cast(DragDirections::BOTTOM_TO_TOP))
{
point.rx() = m_currentDragPoint.x();
point.ry() = m_currentDragPoint.y();
width = m_startDragPoint.x() - m_currentDragPoint.x();
height = m_startDragPoint.y() - m_currentDragPoint.y();
// qDebug() << "right to left - bottom to top";
}
if(static_cast(m_dragDirections) &
static_cast(DragDirections::LEFT_TO_RIGHT) &&
static_cast(m_dragDirections) &
static_cast(DragDirections::TOP_TO_BOTTOM))
{
point.rx() = m_startDragPoint.x();
point.ry() = m_currentDragPoint.y();
width = m_currentDragPoint.x() - m_startDragPoint.x();
height = m_startDragPoint.y() - m_currentDragPoint.y();
// qDebug() << "left to right - top to bottom";
}
if(static_cast(m_dragDirections) &
static_cast(DragDirections::RIGHT_TO_LEFT) &&
static_cast(m_dragDirections) &
static_cast(DragDirections::TOP_TO_BOTTOM))
{
point.rx() = m_currentDragPoint.x();
point.ry() = m_currentDragPoint.y();
width = m_startDragPoint.x() - m_currentDragPoint.x();
height = m_startDragPoint.y() - m_currentDragPoint.y();
// qDebug() << "right to left - top to bottom";
}
// qDebug() << "The data used to update the integration scope:";
// qDebug() << "Point:" << point << "width:" << width << "height:" << height;
//
// qDebug() << "The integration scope before update:" << mpa_integrationScope;
//
// qDebug() << "Will update IntegrationScopeRect with:" << point << "width"
// << width << "height" << height;
// Because the nature of the integration scope might change inside
// a given context, we need to delete and reallocate.
if(mpa_integrationScope != nullptr)
delete mpa_integrationScope;
mpa_integrationScope = new IntegrationScopeRect(point, width, height);
// if(typeid(*mpa_integrationScope) == typeid(IntegrationScopeInterface))
// qDebug() << "The pointer is of type IntegrationScopeInterface";
// if(typeid(*mpa_integrationScope) == typeid(IntegrationScope))
// qDebug() << "The pointer is of type IntegrationScope";
// if(typeid(*mpa_integrationScope) == typeid(IntegrationScopeRect))
// qDebug() << "The pointer is of type IntegrationScopeRect";
// if(typeid(*mpa_integrationScope) == typeid(IntegrationScopeRhomb))
// qDebug() << "The pointer is of type IntegrationScopeRhomb";
//
// qDebug() << "The integration scope right after update:"
// << mpa_integrationScope;
//
// if(!mpa_integrationScope->getPoint(point))
// qFatal("Could not get point.");
// qDebug() << "The point:" << point;
// if(!mpa_integrationScope->getWidth(width))
// qFatal("Oh no!!!! width");
// if(!mpa_integrationScope->getWidth(height))
// qFatal("Oh no!!!! height");
}
void
BasePlotContext::updateIntegrationScopeRhombHorizontal()
{
// qDebug() << toString();
/*
4+----------+3
| |
| |
| |
| |
| |
| |
| |
1+----------+2
----width---
*/
// As visible here, the fixed size of the rhomboid (using the S key in the
// plot widget) is the horizontal side.
// The points are numbered in a counterclockwise manner, starting from the
// starting drag point. The width side is right of the start drag point if
// the user drags from left to right and left of the start drag point if
// the user drags from left to right. In the figure above, the user
// has dragged the mouse from point 1 and to the right and upwards.
// Thus the width side is right of point 1. Because the numbering
// is counterclockwise, that point happens to be numbered 2.
// If the user had draggged the mouse starting at point 3 and to the left
// and to the bottom, then point 3 above would be point 1, point 4
// would be point 2 because the width side is left of the start
// drag point; point 1 would be point 3 and finally the last point
// would be at point 2.
// Sanity check
if(m_integrationScopeRhombWidth == 0)
qFatal(
"The m_integrationScopeRhombWidth of the fixed rhomboid side cannot be "
"0.");
QPointF point;
std::vector points;
// Fill-in the points in the vector in the order they are created
// while drawing the rhomboid shape. Thus, the first point (start of the
// mouse click & drag operation is always the same.
point.rx() = m_startDragPoint.x();
point.ry() = m_startDragPoint.y();
points.push_back(point);
// qDebug() << "Start point:" << point;
if(static_cast(m_dragDirections) &
static_cast(DragDirections::LEFT_TO_RIGHT) &&
static_cast(m_dragDirections) &
static_cast(DragDirections::BOTTOM_TO_TOP))
{
// Second point.
point.rx() = m_startDragPoint.x() + m_integrationScopeRhombWidth;
point.ry() = m_startDragPoint.y();
points.push_back(point);
// qDebug() << "Second point:" << point;
// Third point.
point.rx() = m_currentDragPoint.rx() + m_integrationScopeRhombWidth;
point.ry() = m_currentDragPoint.ry();
points.push_back(point);
// qDebug() << "Third point:" << point;
// Fourth point.
point.rx() = m_currentDragPoint.rx();
point.ry() = m_currentDragPoint.ry();
points.push_back(point);
// qDebug() << "Last point:" << point;
}
if(static_cast(m_dragDirections) &
static_cast(DragDirections::RIGHT_TO_LEFT) &&
static_cast(m_dragDirections) &
static_cast(DragDirections::BOTTOM_TO_TOP))
{
// Second point.
point.rx() = m_currentDragPoint.rx();
point.ry() = m_currentDragPoint.ry();
points.push_back(point);
// qDebug() << "Second point:" << point;
// Third point.
point.rx() = m_currentDragPoint.rx() - m_integrationScopeRhombWidth;
point.ry() = m_currentDragPoint.ry();
points.push_back(point);
// qDebug() << "Third point:" << point;
// Fourth point.
point.rx() = m_startDragPoint.rx() - m_integrationScopeRhombWidth;
point.ry() = m_startDragPoint.ry();
points.push_back(point);
// qDebug() << "Last point:" << point;
}
if(static_cast(m_dragDirections) &
static_cast(DragDirections::LEFT_TO_RIGHT) &&
static_cast(m_dragDirections) &
static_cast(DragDirections::TOP_TO_BOTTOM))
{
// Second point.
point.rx() = m_currentDragPoint.rx();
point.ry() = m_currentDragPoint.ry();
points.push_back(point);
// qDebug() << "Second point:" << point;
// Third point.
point.rx() = m_currentDragPoint.rx() + m_integrationScopeRhombWidth;
point.ry() = m_currentDragPoint.ry();
points.push_back(point);
// qDebug() << "Third point:" << point;
// Fourth point.
point.rx() = m_startDragPoint.x() + m_integrationScopeRhombWidth;
point.ry() = m_startDragPoint.y();
points.push_back(point);
// qDebug() << "Last point:" << point;
}
if(static_cast(m_dragDirections) &
static_cast(DragDirections::RIGHT_TO_LEFT) &&
static_cast(m_dragDirections) &
static_cast(DragDirections::TOP_TO_BOTTOM))
{
// Second point.
point.rx() = m_startDragPoint.x() - m_integrationScopeRhombWidth;
point.ry() = m_startDragPoint.y();
points.push_back(point);
// qDebug() << "Second point:" << point;
// Third point.
point.rx() = m_currentDragPoint.rx() - m_integrationScopeRhombWidth;
point.ry() = m_currentDragPoint.ry();
points.push_back(point);
// qDebug() << "Third point:" << point;
// Fourth point.
point.rx() = m_currentDragPoint.rx();
point.ry() = m_currentDragPoint.ry();
points.push_back(point);
// qDebug() << "Last point:" << point;
}
// Because the nature of the integration scope might change inside
// a given context, we need to delete and reallocate.
if(mpa_integrationScope != nullptr)
delete mpa_integrationScope;
mpa_integrationScope = new IntegrationScopeRhomb(points);
// qDebug() << "Created an integration scope horizontal rhomboid with"
// << points.size() << "points:" << msp_integrationScope->toString();
}
void
BasePlotContext::updateIntegrationScopeRhombVertical()
{
// qDebug() << toString();
/*
* +3
* . |
* . |
* . |
* . +2
* . .
* . .
* . .
* 4+ .
* | | .
* height | | .
* | | .
* 1+
*
*/
// As visible here, the fixed size of the rhomboid (using the S key in the
// plot widget) is the vertical side.
// The points are numbered in a counterclockwise manner, starting from the
// starting drag point. The height side is below the start drag point if
// the user drags from top to bottom and above the start drag point if
// the user drags from bottom to top. In the figure above, the user
// has dragged the mouse from point 1 and to the right and upwards.
// Thus the height side is above the point 1. Because the numbering
// is counterclockwise, that point happens to be numbered 4.
// If the user had draggged the mouse starting at point 3 and to the left
// and to the bottom, then point 3 above would be point 1, point 4
// would be ponit 2, point 1 would be point 3 and finally, because
// the dragging is from top to bottom, the last point would be at point 2
// above, because the height side of the rhomboid is below the start
// drag point.
// Sanity check
if(m_integrationScopeRhombHeight == 0)
qFatal("The height of the fixed rhomboid side cannot be 0.");
QPointF point;
std::vector points;
// Fill-in the points in the vector in the order they are created
// while drawing the rhomboid shape. Thus, the first point (start of the
// mouse click & drag operation is always the same, the leftmost bottom point
// of the drawing above (point 1).
point.rx() = m_startDragPoint.x();
point.ry() = m_startDragPoint.y();
points.push_back(point);
// qDebug() << "Start point:" << point;
if(static_cast(m_dragDirections) &
static_cast(DragDirections::LEFT_TO_RIGHT) &&
static_cast(m_dragDirections) &
static_cast(DragDirections::BOTTOM_TO_TOP))
{
// Second point.
point.rx() = m_currentDragPoint.rx();
point.ry() = m_currentDragPoint.ry();
points.push_back(point);
// qDebug() << "Second point:" << point;
// Third point.
point.rx() = m_currentDragPoint.rx();
point.ry() = m_currentDragPoint.ry() + m_integrationScopeRhombHeight;
points.push_back(point);
// qDebug() << "Third point:" << point;
// Fourth point.
point.rx() = m_startDragPoint.x();
point.ry() = m_startDragPoint.y() + m_integrationScopeRhombHeight;
points.push_back(point);
// qDebug() << "Last point:" << point;
}
if(static_cast(m_dragDirections) &
static_cast(DragDirections::RIGHT_TO_LEFT) &&
static_cast(m_dragDirections) &
static_cast(DragDirections::BOTTOM_TO_TOP))
{
// Second point.
point.rx() = m_startDragPoint.rx();
point.ry() = m_startDragPoint.ry() + m_integrationScopeRhombHeight;
points.push_back(point);
// qDebug() << "Second point:" << point;
// Third point.
point.rx() = m_currentDragPoint.rx();
point.ry() = m_currentDragPoint.ry() + m_integrationScopeRhombHeight;
points.push_back(point);
// qDebug() << "Third point:" << point;
// Fourth point.
point.rx() = m_currentDragPoint.x();
point.ry() = m_currentDragPoint.y();
points.push_back(point);
// qDebug() << "Last point:" << point;
}
if(static_cast(m_dragDirections) &
static_cast(DragDirections::LEFT_TO_RIGHT) &&
static_cast(m_dragDirections) &
static_cast(DragDirections::TOP_TO_BOTTOM))
{
// Second point.
point.rx() = m_startDragPoint.x();
point.ry() = m_startDragPoint.y() - m_integrationScopeRhombHeight;
points.push_back(point);
// qDebug() << "Second point:" << point;
// Third point.
point.rx() = m_currentDragPoint.rx();
point.ry() = m_currentDragPoint.ry() - m_integrationScopeRhombHeight;
points.push_back(point);
// qDebug() << "Third point:" << point;
// Fourth point.
point.rx() = m_currentDragPoint.rx();
point.ry() = m_currentDragPoint.ry();
points.push_back(point);
// qDebug() << "Last point:" << point;
}
if(static_cast(m_dragDirections) &
static_cast(DragDirections::RIGHT_TO_LEFT) &&
static_cast(m_dragDirections) &
static_cast(DragDirections::TOP_TO_BOTTOM))
{
// Second point.
point.rx() = m_currentDragPoint.rx();
point.ry() = m_currentDragPoint.ry();
points.push_back(point);
// qDebug() << "Second point:" << point;
// Third point.
point.rx() = m_currentDragPoint.rx();
point.ry() = m_currentDragPoint.ry() - m_integrationScopeRhombHeight;
points.push_back(point);
// qDebug() << "Third point:" << point;
// Fourth point.
point.rx() = m_startDragPoint.rx();
point.ry() = m_startDragPoint.ry() - m_integrationScopeRhombHeight;
points.push_back(point);
// qDebug() << "Last point:" << point;
}
// Because the nature of the integration scope might change inside
// a given context, we need to delete and reallocate.
if(mpa_integrationScope != nullptr)
delete mpa_integrationScope;
mpa_integrationScope = new IntegrationScopeRhomb(points);
// qDebug() << "Created an integration scope vertical rhomboid with"
// << points.size() << "points:" << msp_integrationScope->toString();
}
void
BasePlotContext::updateIntegrationScopeRhomb()
{
// qDebug() << toString();
// By essence, IntegrationScopeRhomb is a rhomboid polygon. Just set the
// points. There are two kinds of rhomboid integration scopes: horizontal and
// vertical.
/*
+----------+
| |
| |
| |
| |
| |
| |
| |
+----------+
----width---
*/
// As visible here, the fixed size of the rhomboid (using the S key in the
// plot widget) is the *horizontal* side (that is, the rhomboid has a non-0
// width)..
// However, it might be useful to be able to draw rhomboid integration scopes
// like this, that would correspond to the rhomboid above after a transpose
// operation.
/*
+
. |
. |
. |
. +
. .
. .
. .
+ .
| | .
height | | .
| | .
+
*/
// As visible here, the fixed size of the rhomboid (using the S key in the
// plot widget) is the vertical side (that is, the rhomboid has a non-0
// height).
// The general rule is thus that when the m_integrationScopeRhombWidth is
// not-0, then the first shape is considered, while when the
// m_integrationScopeRhombHeight is non-0, then the second shape is
// considered.
// This function is called when the user has dragged the cursor (left or right
// button, not for or for integration, respectively) with the 'Alt' modifier
// key pressed, so that they want to perform a rhomboid integration scope
// calculation.
// Of course, the integration scope in the context might not be a rhomboid
// scope, because we might enter this function as a very firt switch from
// scope or scopeRect to scopeRhomb. The only indication we have to direct the
// creation of a horizontal or vertical rhomboid is the
// m_integrationScopeRhombWidth/m_integrationScopeRhombHeight recorded in the
// plot widget that owns this plot context.
// qDebug() << "In updateIntegrationScopeRhomb, m_integrationScopeRhombWidth:"
// << m_integrationScopeRhombWidth
// << "and m_integrationScopeRhombHeight:"
// << m_integrationScopeRhombHeight;
if(!m_integrationScopeRhombWidth && !m_integrationScopeRhombHeight)
qFatal(
"Both m_integrationScopeRhombWidth and m_integrationScopeRhombHeight of "
"rhomboid integration scope cannot be 0.");
if(m_integrationScopeRhombWidth != 0)
return updateIntegrationScopeRhombHorizontal();
else if(m_integrationScopeRhombHeight != 0)
return updateIntegrationScopeRhombVertical();
}
BasePlotContext &
BasePlotContext::operator=(const BasePlotContext &other)
{
if(this == &other)
return *this;
m_dataKind = other.m_dataKind;
m_isMouseDragging = other.m_isMouseDragging;
m_wasMouseDragging = other.m_wasMouseDragging;
m_isKeyBoardDragging = other.m_isKeyBoardDragging;
m_isLeftPseudoButtonKeyPressed = other.m_isLeftPseudoButtonKeyPressed;
m_isRightPseudoButtonKeyPressed = other.m_isRightPseudoButtonKeyPressed;
m_wasKeyBoardDragging = other.m_wasKeyBoardDragging;
m_startDragPoint = other.m_startDragPoint;
m_currentDragPoint = other.m_currentDragPoint;
m_lastCursorHoveredPoint = other.m_lastCursorHoveredPoint;
m_selectionPolygon = other.m_selectionPolygon;
mpa_integrationScope = other.mpa_integrationScope->clone();
m_integrationScopeRhombWidth = other.m_integrationScopeRhombWidth;
m_integrationScopeRhombHeight = other.m_integrationScopeRhombHeight;
// The effective range of the axes.
m_xRange = other.m_xRange;
m_yRange = other.m_yRange;
// Tell if the mouse move was started onto either axis, because that will
// condition if some calculations needs to be performed or not (for example,
// if the mouse cursor motion was started on an axis, there is no point to
// perform deconvolutions).
m_wasClickOnXAxis = other.m_wasClickOnXAxis;
m_wasClickOnYAxis = other.m_wasClickOnYAxis;
m_isMeasuringDistance = other.m_isMeasuringDistance;
// The user-selected region over the plot.
// Note that we cannot use QCPRange structures because these are normalized by
// QCustomPlot in such a manner that lower is actually < upper. But we need
// for a number of our calculations (specifically for the deconvolutions) to
// actually have the lower value be start drag point.x even if the drag
// direction was from right to left.
m_xRegionRangeStart = other.m_xRegionRangeStart;
m_xRegionRangeStop = other.m_xRegionRangeStop;
m_yRegionRangeStart = other.m_yRegionRangeStart;
m_yRegionRangeStop = other.m_yRegionRangeStop;
m_xDelta = other.m_xDelta;
m_yDelta = other.m_yDelta;
m_pressedKeyCode = other.m_pressedKeyCode;
m_pressedKeyText = other.m_pressedKeyText;
m_releasedKeyCode = other.m_releasedKeyCode;
m_releasedKeyText = other.m_releasedKeyText;
m_keyboardModifiers = other.m_keyboardModifiers;
m_lastPressedMouseButton = other.m_lastPressedMouseButton;
m_lastReleasedMouseButton = other.m_lastReleasedMouseButton;
m_pressedMouseButtons = other.m_pressedMouseButtons;
m_mouseButtonsAtMousePress = other.m_mouseButtonsAtMousePress;
m_mouseButtonsAtMouseRelease = other.m_mouseButtonsAtMouseRelease;
return *this;
}
DragDirections
BasePlotContext::recordDragDirections()
{
int drag_directions = static_cast(DragDirections::NOT_SET);
if(m_currentDragPoint.x() > m_startDragPoint.x())
drag_directions |= static_cast(DragDirections::LEFT_TO_RIGHT);
else
drag_directions |= static_cast(DragDirections::RIGHT_TO_LEFT);
if(m_currentDragPoint.y() > m_startDragPoint.y())
drag_directions |= static_cast(DragDirections::BOTTOM_TO_TOP);
else
drag_directions |= static_cast(DragDirections::TOP_TO_BOTTOM);
// qDebug() << "DragDirections:" << drag_directions;
m_dragDirections = static_cast(drag_directions);
return static_cast(drag_directions);
}
QString
BasePlotContext::toString() const
{
QString text("Context:");
text += QString(" data kind: %1").arg(static_cast(m_dataKind));
text += QString(" -- isMouseDragging: %1 -- wasMouseDragging: %2")
.arg(m_isMouseDragging ? "true" : "false")
.arg(m_wasMouseDragging ? "true" : "false");
text += QString(" -- startDragPoint : (%1, %2)")
.arg(m_startDragPoint.x())
.arg(m_startDragPoint.y());
text += QString(" -- currentDragPoint : (%1, %2)")
.arg(m_currentDragPoint.x())
.arg(m_currentDragPoint.y());
text += QString(" -- lastCursorHoveredPoint : (%1, %2)")
.arg(m_lastCursorHoveredPoint.x())
.arg(m_lastCursorHoveredPoint.y());
text += dragDirectionsToString();
if(mpa_integrationScope != nullptr)
{
// The integration scope
text += " -- Integration scope: ";
text += mpa_integrationScope->toString();
text += " -- ";
}
text += QString(" -- xRange: (%1, %2)").arg(m_xRange.lower).arg(m_xRange.upper);
text +=
QString(" -- yRange: (%1, %2)").arg(m_yRange.lower).arg(m_yRange.upper);
text += QString(" -- wasClickOnXAxis: %1")
.arg(m_wasClickOnXAxis ? "true" : "false");
text += QString(" -- wasClickOnYAxis: %1")
.arg(m_wasClickOnYAxis ? "true" : "false");
text += QString(" -- isMeasuringDistance: %1")
.arg(m_isMeasuringDistance ? "true" : "false");
text += QString(" -- xRegionRangeStart: %1 -- xRegionRangeEnd: %2")
.arg(m_xRegionRangeStart)
.arg(m_xRegionRangeStop);
text += QString(" -- yRegionRangeStart: %1 -- yRegionRangeEnd: %2")
.arg(m_yRegionRangeStart)
.arg(m_yRegionRangeStop);
text += QString(" -- xDelta: %1 -- yDelta: %2").arg(m_xDelta).arg(m_yDelta);
text += QString(" -- pressedKeyCode: %1").arg(m_pressedKeyCode);
if(m_pressedKeyCodes.size())
{
for(int key : m_pressedKeyCodes)
text += QString(" -- pressedKeyCodes key: %1").arg(key);
}
text += QString(" -- releasedKeyCode: %1").arg(m_releasedKeyCode);
if(m_releasedKeyCodes.size())
{
for(int key : m_releasedKeyCodes)
text += QString(" -- releasedKeyCodes key: %1").arg(key);
}
// Qt::NoModifier0x00000000No modifier key is pressed.
// Qt::ShiftModifier0x02000000A Shift key on the keyboard is pressed.
// Qt::ControlModifier0x04000000A Ctrl key on the keyboard is pressed.
// Qt::AltModifier0x08000000An Alt key on the keyboard is pressed.
// Qt::MetaModifier0x10000000A Meta key on the keyboard is pressed.
// Qt::KeypadModifier0x20000000A keypad button is pressed.
// Qt::GroupSwitchModifier0x40000000X11 only (unless activated on Windows by a
// command line argument).
// A Mode_switch key on the keyboard is
// pressed.
text += QString(" -- keyboardModifiers: ");
if(m_keyboardModifiers == Qt::NoModifier)
text += QString("%1 - ").arg(qtKeyboardModifierMap[Qt::NoModifier]);
if(static_cast(m_keyboardModifiers) & Qt::ShiftModifier)
text += QString("%1 - ").arg(qtKeyboardModifierMap[Qt::ShiftModifier]);
if(static_cast(m_keyboardModifiers) & Qt::ControlModifier)
text += QString("%1 - ").arg(qtKeyboardModifierMap[Qt::ControlModifier]);
if(static_cast(m_keyboardModifiers) & Qt::AltModifier)
text += QString("%1 - ").arg(qtKeyboardModifierMap[Qt::AltModifier]);
if(static_cast(m_keyboardModifiers) & Qt::MetaModifier)
text += QString("%1 - ").arg(qtKeyboardModifierMap[Qt::MetaModifier]);
if(static_cast(m_keyboardModifiers) & Qt::KeypadModifier)
text += QString("%1 - ").arg(qtKeyboardModifierMap[Qt::KeypadModifier]);
if(static_cast(m_keyboardModifiers) & Qt::GroupSwitchModifier)
text +=
QString("%1 - ").arg(qtKeyboardModifierMap[Qt::GroupSwitchModifier]);
text += QString(" -- lastPressedMouseButton: %1")
.arg(qtMouseButtonsMap[m_lastPressedMouseButton]);
text += QString(" -- lastReleasedMouseButton: %1")
.arg(qtMouseButtonsMap[m_lastReleasedMouseButton]);
text += QString(" -- pressedMouseButtons: %1")
.arg(qtMouseButtonsMap[m_pressedMouseButtons]);
text += QString(" -- mouseButtonsAtMousePress: %1")
.arg(qtMouseButtonsMap[m_mouseButtonsAtMousePress]);
text += QString(" -- mouseButtonsAtMouseRelease: %1")
.arg(qtMouseButtonsMap[m_mouseButtonsAtMouseRelease]);
return text;
}
QString
BasePlotContext::dragDirectionsToString() const
{
QString text;
// Document how the mouse cursor is being dragged.
if(m_isMouseDragging)
{
if(static_cast(m_dragDirections) &
static_cast(DragDirections::LEFT_TO_RIGHT))
text += " -- dragging from left to right";
else if(static_cast(m_dragDirections) &
static_cast(DragDirections::RIGHT_TO_LEFT))
text += " -- dragging from right to left";
if(static_cast(m_dragDirections) &
static_cast(DragDirections::TOP_TO_BOTTOM))
text += " -- dragging from top to bottom";
if(static_cast(m_dragDirections) &
static_cast(DragDirections::BOTTOM_TO_TOP))
text += " -- dragging from bottom to top";
}
return text;
}
} // namespace pappso
libpappsomspp-0.11.27/src/pappsomspp/gui/plotwidget/baseplotcontext.h 000664 001750 001750 00000007355 15250226472 027330 0 ustar 00rusconi rusconi 000000 000000 // Copyright 2021 Filippo Rusconi
// GPL3+
#pragma once
/////////////////////// StdLib includes
/////////////////////// Qt includes
#include
#include
/////////////////////// Local includes
#include "../../core/types.h"
#include "pappsomspp/export-import-config.h"
#include "pappsomspp/core/processing/combiners/integrationscopebase.h"
#include "pappsomspp/core/processing/combiners/selectionpolygon.h"
////////////////////// Other includes
#include "qcustomplot.h"
namespace pappso
{
Q_NAMESPACE
enum class DragDirections
{
NOT_SET = 0x0000,
LEFT_TO_RIGHT = 1 << 0,
RIGHT_TO_LEFT = 1 << 1,
TOP_TO_BOTTOM = 1 << 2,
BOTTOM_TO_TOP = 1 << 3
};
Q_ENUM_NS(DragDirections)
extern std::map qtMouseButtonMap;
extern std::map qtMouseButtonsMap;
extern std::map qtKeyboardModifierMap;
class PMSPP_LIB_DECL BasePlotContext
{
public:
explicit BasePlotContext();
virtual ~BasePlotContext();
BasePlotContext *clone();
void initialize(const BasePlotContext &other);
BasePlotContext(const BasePlotContext &other);
BasePlotContext &operator=(const BasePlotContext &other);
Enums::DataKind m_dataKind = Enums::DataKind::unset;
bool m_isMouseDragging = false;
bool m_wasMouseDragging = false;
bool m_isKeyBoardDragging = false;
bool m_isLeftPseudoButtonKeyPressed = false;
bool m_isRightPseudoButtonKeyPressed = false;
bool m_wasKeyBoardDragging = false;
QPointF m_startDragPoint;
QPointF m_currentDragPoint;
QPointF m_lastCursorHoveredPoint;
DragDirections m_dragDirections = DragDirections::NOT_SET;
IntegrationScopeBase *mpa_integrationScope = nullptr;
SelectionPolygon m_selectionPolygon;
double m_integrationScopeRhombWidth = 0;
double m_integrationScopeRhombHeight = 0;
// The effective range of the axes.
QCPRange m_xRange;
QCPRange m_yRange;
// Tell if the mouse move was started onto either axis, because that will
// condition if some calculations needs to be performed or not (for example,
// if the mouse cursor motion was started on an axis, there is no point to
// perform deconvolutions).
bool m_wasClickOnXAxis = false;
bool m_wasClickOnYAxis = false;
bool m_isMeasuringDistance = false;
// The user-selected region over the plot.
// Note that we cannot use QCPRange structures because these are normalized by
// QCustomPlot in such a manner that lower is actually < upper. But we need
// for a number of our calculations (specifically for the deconvolutions) to
// actually have the lower value be start drag point.x even if the drag
// direction was from right to left.
double m_xRegionRangeStart = std::numeric_limits::min();
double m_xRegionRangeStop = std::numeric_limits::min();
double m_yRegionRangeStart = std::numeric_limits::min();
double m_yRegionRangeStop = std::numeric_limits::min();
double m_xDelta = 0;
double m_yDelta = 0;
int m_pressedKeyCode;
QSet m_pressedKeyCodes;
QString m_pressedKeyText;
int m_releasedKeyCode;
QSet m_releasedKeyCodes;
QString m_releasedKeyText;
Qt::KeyboardModifiers m_keyboardModifiers;
Qt::MouseButtons m_lastPressedMouseButton;
Qt::MouseButtons m_lastReleasedMouseButton;
Qt::MouseButtons m_pressedMouseButtons;
Qt::MouseButtons m_mouseButtonsAtMousePress;
Qt::MouseButtons m_mouseButtonsAtMouseRelease;
void updateIntegrationScope();
void updateIntegrationScopeRect();
void updateIntegrationScopeRhomb();
void updateIntegrationScopeRhombHorizontal();
void updateIntegrationScopeRhombVertical();
DragDirections recordDragDirections();
QString toString() const;
QString dragDirectionsToString() const;
};
} // namespace pappso
libpappsomspp-0.11.27/src/pappsomspp/gui/plotwidget/baseplotwidget.cpp 000664 001750 001750 00000322005 15250226472 027452 0 ustar 00rusconi rusconi 000000 000000 /* This code comes right from the msXpertSuite software project.
*
* msXpertSuite - mass spectrometry software suite
* -----------------------------------------------
* Copyright(C) 2009,...,2018 Filippo Rusconi
*
* http://www.msxpertsuite.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* END software license
*/
/////////////////////// StdLib includes
#include
/////////////////////// Qt includes
#include
/////////////////////// Local includes
#include "../../core/types.h"
#include "pappsomspp/core/utils.h"
#include "baseplotwidget.h"
#include "pappsomspp/core/pappsoexception.h"
#include "pappsomspp/core/exception/exceptionnotpossible.h"
int basePlotContextMetaTypeId =
qRegisterMetaType("pappso::BasePlotContext");
int basePlotContextPtrMetaTypeId =
qRegisterMetaType("pappso::BasePlotContext *");
namespace pappso
{
BasePlotWidget::BasePlotWidget(QWidget *parent): QCustomPlot(parent)
{
if(parent == nullptr)
qFatal("Programming error.");
// Default settings for the pen used to graph the data.
m_pen.setStyle(Qt::SolidLine);
m_pen.setBrush(Qt::black);
m_pen.setWidth(1);
// qDebug() << "Created new BasePlotWidget with" << layerCount()
//<< "layers before setting up widget.";
// qDebug().noquote() << "All layer names:\n" << allLayerNamesToString();
// As of today 20210313, the QCustomPlot is created with the following 6
// layers:
//
// All layers' name:
//
// Layer index 0 name: background
// Layer index 1 name: grid
// Layer index 2 name: main
// Layer index 3 name: axes
// Layer index 4 name: legend
// Layer index 5 name: overlay
if(!setupWidget())
qFatal("Programming error.");
// Do not call createAllAncillaryItems() in this base class because all the
// items will have been created *before* the addition of plots and then the
// rendering order will hide them to the viewer, since the rendering order is
// according to the order in which the items have been created.
//
// The fact that the ancillary items are created before trace plots is not a
// problem because the trace plots are sparse and do not effectively hide the
// data.
//
// But, in the color map plot widgets, we cannot afford to create the
// ancillary items *before* the plot itself because then, the rendering of the
// plot (created after) would screen off the ancillary items (created before).
//
// So, the createAllAncillaryItems() function needs to be called in the
// derived classes at the most appropriate moment in the setting up of the
// widget.
//
// All this is only a workaround of a bug in QCustomPlot. See
// https://www.qcustomplot.com/index.php/support/forum/2283.
//
// I initially wanted to have a plots layer on top of the default background
// layer and a items layer on top of it. But that setting prevented the
// selection of graphs.
// qDebug() << "Created new BasePlotWidget with" << layerCount()
//<< "layers after setting up widget.";
// qDebug().noquote() << "All layer names:\n" << allLayerNamesToString();
show();
}
BasePlotWidget::BasePlotWidget(QWidget *parent,
const QString &x_axis_label,
const QString &y_axis_label)
: QCustomPlot(parent), m_axisLabelX(x_axis_label), m_axisLabelY(y_axis_label)
{
// qDebug();
if(parent == nullptr)
qFatal("Programming error.");
// Default settings for the pen used to graph the data.
m_pen.setStyle(Qt::SolidLine);
m_pen.setBrush(Qt::black);
m_pen.setWidth(1);
xAxis->setLabel(x_axis_label);
yAxis->setLabel(y_axis_label);
// qDebug() << "Created new BasePlotWidget with" << layerCount()
//<< "layers before setting up widget.";
// qDebug().noquote() << "All layer names:\n" << allLayerNamesToString();
// As of today 20210313, the QCustomPlot is created with the following 6
// layers:
//
// All layers' name:
//
// Layer index 0 name: background
// Layer index 1 name: grid
// Layer index 2 name: main
// Layer index 3 name: axes
// Layer index 4 name: legend
// Layer index 5 name: overlay
if(!setupWidget())
qFatal("Programming error.");
// qDebug() << "Created new BasePlotWidget with" << layerCount()
//<< "layers after setting up widget.";
// qDebug().noquote() << "All layer names:\n" << allLayerNamesToString();
show();
}
//! Destruct \c this BasePlotWidget instance.
/*!
The destruction involves clearing the history, deleting all the axis range
history items for x and y axes.
*/
BasePlotWidget::~BasePlotWidget()
{
// qDebug() << "In the destructor of plot widget:" << this;
m_xAxisRangeHistory.clear();
m_yAxisRangeHistory.clear();
// Note that the QCustomPlot xxxItem objects are allocated with (this) which
// means their destruction is automatically handled upon *this' destruction.
}
QString
BasePlotWidget::allLayerNamesToString() const
{
QString text;
for(int iter = 0; iter < layerCount(); ++iter)
{
text +=
QString("Layer index %1: %2\n").arg(iter).arg(layer(iter)->name());
}
return text;
}
QString
BasePlotWidget::layerableLayerName(QCPLayerable *layerable_p) const
{
if(layerable_p == nullptr)
qFatal("Programming error.");
QCPLayer *layer_p = layerable_p->layer();
return layer_p->name();
}
int
BasePlotWidget::layerableLayerIndex(QCPLayerable *layerable_p) const
{
if(layerable_p == nullptr)
qFatal("Programming error.");
QCPLayer *layer_p = layerable_p->layer();
for(int iter = 0; iter < layerCount(); ++iter)
{
if(layer(iter) == layer_p)
return iter;
}
return -1;
}
void
BasePlotWidget::createAllAncillaryItems()
{
// Make a copy of the pen to just change its color and set that color to
// the tracer line.
QPen pen = m_pen;
// Create the lines that will act as tracers for position and selection of
// regions.
//
// We have the cross hair that serves as the cursor. That crosshair cursor is
// made of a vertical line (green, because when click-dragging the mouse it
// becomes the tracer that is being anchored at the region start. The second
// line i horizontal and is always black.
pen.setColor(QColor("steelblue"));
// The set of tracers (horizontal and vertical) that track the position of the
// mouse cursor.
mp_vPosTracerItem = new QCPItemLine(this);
mp_vPosTracerItem->setLayer("plotsLayer");
mp_vPosTracerItem->setPen(pen);
mp_vPosTracerItem->start->setType(QCPItemPosition::ptPlotCoords);
mp_vPosTracerItem->end->setType(QCPItemPosition::ptPlotCoords);
mp_vPosTracerItem->start->setCoords(0, 0);
mp_vPosTracerItem->end->setCoords(0, 0);
mp_hPosTracerItem = new QCPItemLine(this);
mp_hPosTracerItem->setLayer("plotsLayer");
mp_hPosTracerItem->setPen(pen);
mp_hPosTracerItem->start->setType(QCPItemPosition::ptPlotCoords);
mp_hPosTracerItem->end->setType(QCPItemPosition::ptPlotCoords);
mp_hPosTracerItem->start->setCoords(0, 0);
mp_hPosTracerItem->end->setCoords(0, 0);
// The set of tracers (horizontal only) that track the region
// spanning/selection regions.
//
// The start vertical tracer is colored in greeen.
pen.setColor(QColor("green"));
mp_vStartTracerItem = new QCPItemLine(this);
mp_vStartTracerItem->setLayer("plotsLayer");
mp_vStartTracerItem->setPen(pen);
mp_vStartTracerItem->start->setType(QCPItemPosition::ptPlotCoords);
mp_vStartTracerItem->end->setType(QCPItemPosition::ptPlotCoords);
mp_vStartTracerItem->start->setCoords(0, 0);
mp_vStartTracerItem->end->setCoords(0, 0);
// The end vertical tracer is colored in red.
pen.setColor(QColor("red"));
mp_vEndTracerItem = new QCPItemLine(this);
mp_vEndTracerItem->setLayer("plotsLayer");
mp_vEndTracerItem->setPen(pen);
mp_vEndTracerItem->start->setType(QCPItemPosition::ptPlotCoords);
mp_vEndTracerItem->end->setType(QCPItemPosition::ptPlotCoords);
mp_vEndTracerItem->start->setCoords(0, 0);
mp_vEndTracerItem->end->setCoords(0, 0);
// When the user click-drags the mouse, the X distance between the drag start
// point and the drag end point (current point) is the xDelta.
mp_xDeltaTextItem = new QCPItemText(this);
mp_xDeltaTextItem->setLayer("plotsLayer");
mp_xDeltaTextItem->setColor(QColor("steelblue"));
mp_xDeltaTextItem->setPositionAlignment(Qt::AlignBottom | Qt::AlignCenter);
mp_xDeltaTextItem->position->setType(QCPItemPosition::ptPlotCoords);
mp_xDeltaTextItem->setVisible(false);
// Same for the y delta
mp_yDeltaTextItem = new QCPItemText(this);
mp_yDeltaTextItem->setLayer("plotsLayer");
mp_yDeltaTextItem->setColor(QColor("steelblue"));
mp_yDeltaTextItem->setPositionAlignment(Qt::AlignBottom | Qt::AlignCenter);
mp_yDeltaTextItem->position->setType(QCPItemPosition::ptPlotCoords);
mp_yDeltaTextItem->setVisible(false);
// Make sure we prepare the four lines that will be needed to
// draw the selection rectangle.
pen = m_pen;
pen.setColor("steelblue");
mp_selectionRectangeLine1 = new QCPItemLine(this);
mp_selectionRectangeLine1->setLayer("plotsLayer");
mp_selectionRectangeLine1->setPen(pen);
mp_selectionRectangeLine1->start->setType(QCPItemPosition::ptPlotCoords);
mp_selectionRectangeLine1->end->setType(QCPItemPosition::ptPlotCoords);
mp_selectionRectangeLine1->start->setCoords(0, 0);
mp_selectionRectangeLine1->end->setCoords(0, 0);
mp_selectionRectangeLine1->setVisible(false);
mp_selectionRectangeLine2 = new QCPItemLine(this);
mp_selectionRectangeLine2->setLayer("plotsLayer");
mp_selectionRectangeLine2->setPen(pen);
mp_selectionRectangeLine2->start->setType(QCPItemPosition::ptPlotCoords);
mp_selectionRectangeLine2->end->setType(QCPItemPosition::ptPlotCoords);
mp_selectionRectangeLine2->start->setCoords(0, 0);
mp_selectionRectangeLine2->end->setCoords(0, 0);
mp_selectionRectangeLine2->setVisible(false);
mp_selectionRectangeLine3 = new QCPItemLine(this);
mp_selectionRectangeLine3->setLayer("plotsLayer");
mp_selectionRectangeLine3->setPen(pen);
mp_selectionRectangeLine3->start->setType(QCPItemPosition::ptPlotCoords);
mp_selectionRectangeLine3->end->setType(QCPItemPosition::ptPlotCoords);
mp_selectionRectangeLine3->start->setCoords(0, 0);
mp_selectionRectangeLine3->end->setCoords(0, 0);
mp_selectionRectangeLine3->setVisible(false);
mp_selectionRectangeLine4 = new QCPItemLine(this);
mp_selectionRectangeLine4->setLayer("plotsLayer");
mp_selectionRectangeLine4->setPen(pen);
mp_selectionRectangeLine4->start->setType(QCPItemPosition::ptPlotCoords);
mp_selectionRectangeLine4->end->setType(QCPItemPosition::ptPlotCoords);
mp_selectionRectangeLine4->start->setCoords(0, 0);
mp_selectionRectangeLine4->end->setCoords(0, 0);
mp_selectionRectangeLine4->setVisible(false);
}
bool
BasePlotWidget::setupWidget()
{
// qDebug();
// By default the widget comes with a graph. Remove it.
if(graphCount())
{
// QCPLayer *layer_p = graph(0)->layer();
// qDebug() << "The graph was on layer:" << layer_p->name();
// As of today 20210313, the graph is created on the currentLayer(), that
// is "main".
removeGraph(0);
}
// The general idea is that we do want custom layers for the trace|colormap
// plots.
// qDebug().noquote() << "Right before creating the new layer, layers:\n"
//<< allLayerNamesToString();
// Add the layer that will store all the plots and all the ancillary items.
addLayer(
"plotsLayer", layer("background"), QCustomPlot::LayerInsertMode::limAbove);
// Add the layer that will store the labels.
addLayer("labelsLayer", layer("background"), QCustomPlot::LayerInsertMode::limAbove);
// qDebug().noquote() << "Added new plotsLayer, layers:\n"
//<< allLayerNamesToString();
// This is required so that we get the keyboard events.
setFocusPolicy(Qt::StrongFocus);
setInteractions(QCP::iRangeZoom | QCP::iSelectPlottables | QCP::iMultiSelect);
// We want to capture the signals emitted by the QCustomPlot base class.
connect(
this, &QCustomPlot::mouseMove, this, &BasePlotWidget::mouseMoveHandler);
connect(
this, &QCustomPlot::mousePress, this, &BasePlotWidget::mousePressHandler);
connect(this,
&QCustomPlot::mouseRelease,
this,
&BasePlotWidget::mouseReleaseHandler);
connect(
this, &QCustomPlot::mouseWheel, this, &BasePlotWidget::mouseWheelHandler);
connect(this,
&QCustomPlot::axisDoubleClick,
this,
&BasePlotWidget::axisDoubleClickHandler);
connect(this, &QCustomPlot::beforeReplot, this, [&]() { emit beforeReplotSignal(); });
connect(this, &QCustomPlot::afterLayout, this, [&]() { emit afterLayoutSignal(); });
connect(this, &QCustomPlot::afterReplot, this, [&]() { emit afterReplotSignal(); });
return true;
}
void
BasePlotWidget::setPen(const QPen &pen)
{
m_pen = pen;
}
const QPen &
BasePlotWidget::getPen() const
{
return m_pen;
}
void
BasePlotWidget::setPlottingColor(QCPAbstractPlottable *plottable_p,
const QColor &new_color)
{
if(plottable_p == nullptr)
qFatal("Pointer cannot be nullptr.");
// First this single-graph widget
QPen pen;
pen = plottable_p->pen();
pen.setColor(new_color);
plottable_p->setPen(pen);
replot();
}
void
BasePlotWidget::setPlottingColor(int index, const QColor &new_color)
{
if(!new_color.isValid())
return;
QCPGraph *graph_p = graph(index);
if(graph_p == nullptr)
qFatal("Programming error.");
return setPlottingColor(graph_p, new_color);
}
QColor
BasePlotWidget::getPlottingColor(QCPAbstractPlottable *plottable_p) const
{
if(plottable_p == nullptr)
qFatal("Programming error.");
return plottable_p->pen().color();
}
QColor
BasePlotWidget::getPlottingColor(int index) const
{
QCPGraph *graph_p = graph(index);
if(graph_p == nullptr)
qFatal("Programming error.");
return getPlottingColor(graph_p);
}
void
BasePlotWidget::setAxisLabelX(const QString &label)
{
xAxis->setLabel(label);
}
void
BasePlotWidget::setAxisLabelY(const QString &label)
{
yAxis->setLabel(label);
}
// AXES RANGE HISTORY-related functions
void
BasePlotWidget::resetAxesRangeHistory()
{
m_xAxisRangeHistory.clear();
m_yAxisRangeHistory.clear();
m_xAxisRangeHistory.push_back(new QCPRange(xAxis->range()));
m_yAxisRangeHistory.push_back(new QCPRange(yAxis->range()));
// qDebug() << "size of history:" << m_xAxisRangeHistory.size()
//<< "setting index to 0";
// qDebug() << "resetting axes history to values:" << xAxis->range().lower
//<< "--" << xAxis->range().upper << "and" << yAxis->range().lower
//<< "--" << yAxis->range().upper;
m_lastAxisRangeHistoryIndex = 0;
}
//! Create new axis range history items and append them to the history.
/*!
The plot widget is queried to get the current x/y-axis ranges and the
current ranges are appended to the history for x-axis and for y-axis.
*/
void
BasePlotWidget::updateAxesRangeHistory()
{
m_xAxisRangeHistory.push_back(new QCPRange(xAxis->range()));
m_yAxisRangeHistory.push_back(new QCPRange(yAxis->range()));
m_lastAxisRangeHistoryIndex = m_xAxisRangeHistory.size() - 1;
// qDebug() << "axes history size:" << m_xAxisRangeHistory.size()
//<< "current index:" << m_lastAxisRangeHistoryIndex
//<< xAxis->range().lower << "--" << xAxis->range().upper << "and"
//<< yAxis->range().lower << "--" << yAxis->range().upper;
}
//! Go up one history element in the axis history.
/*!
If possible, back up one history item in the axis histories and update the
plot's x/y-axis ranges to match that history item.
*/
void
BasePlotWidget::restorePreviousAxesRangeHistory()
{
// qDebug() << "axes history size:" << m_xAxisRangeHistory.size()
//<< "current index:" << m_lastAxisRangeHistoryIndex;
if(m_lastAxisRangeHistoryIndex == 0)
{
// qDebug() << "current index is 0 returning doing nothing";
return;
}
// qDebug() << "Setting index to:" << m_lastAxisRangeHistoryIndex - 1
//<< "and restoring axes history to that index";
restoreAxesRangeHistory(--m_lastAxisRangeHistoryIndex);
}
//! Get the axis histories at index \p index and update the plot ranges.
/*!
\param index index at which to select the axis history item.
\sa updateAxesRangeHistory().
*/
void
BasePlotWidget::restoreAxesRangeHistory(std::size_t index)
{
// qDebug() << "Axes history size:" << m_xAxisRangeHistory.size()
//<< "current index:" << m_lastAxisRangeHistoryIndex
//<< "asking to restore index:" << index;
if(index >= m_xAxisRangeHistory.size())
{
// qDebug() << "index >= history size. Returning.";
return;
}
// We want to go back to the range history item at index, which means we want
// to pop back all the items between index+1 and size-1.
while(m_xAxisRangeHistory.size() > index + 1)
m_xAxisRangeHistory.pop_back();
if(m_xAxisRangeHistory.size() - 1 != index)
qFatal("Programming error.");
xAxis->setRange(*(m_xAxisRangeHistory.at(index)));
yAxis->setRange(*(m_yAxisRangeHistory.at(index)));
hideAllPlotItems();
mp_vPosTracerItem->setVisible(false);
mp_hPosTracerItem->setVisible(false);
mp_vStartTracerItem->setVisible(false);
mp_vEndTracerItem->setVisible(false);
// The start tracer will keep beeing represented at the last position and last
// size even if we call this function repetitively. So actually do not show,
// it will reappare as soon as the mouse is moved.
// if(m_shouldTracersBeVisible)
//{
// mp_vStartTracerItem->setVisible(true);
//}
replot();
updateContextXandYAxisRanges();
// qDebug() << "restored axes history to index:" << index
//<< "with values:" << xAxis->range().lower << "--"
//<< xAxis->range().upper << "and" << yAxis->range().lower << "--"
//<< yAxis->range().upper;
emit plotRangesChangedSignal((QMouseEvent *)nullptr, m_context);
}
// AXES RANGE HISTORY-related functions
/// KEYBOARD-related EVENTS
void
BasePlotWidget::keyPressEvent(QKeyEvent *event)
{
// qDebug() << "ENTER";
// We need this because some keys modify our behaviour.
m_context.m_pressedKeyCode = event->key();
m_context.m_pressedKeyCodes.insert(event->key());
m_context.m_releasedKeyCodes.remove(event->key());
m_context.m_pressedKeyText = event->text();
// qDebug() << "Pressed key code:" << m_context.m_pressedKeyCode;
// qDebug() << "Pressed key codes:" << m_context.m_pressedKeyCodes;
// qDebug() << "Pressed key text:" << m_context.m_pressedKeyText;
m_context.m_keyboardModifiers = QGuiApplication::queryKeyboardModifiers();
if(event->key() == Qt::Key_Left || event->key() == Qt::Key_Right ||
event->key() == Qt::Key_Up || event->key() == Qt::Key_Down)
{
return directionKeyPressEvent(event);
}
else if(event->key() == m_leftMousePseudoButtonKey ||
event->key() == m_rightMousePseudoButtonKey)
{
return mousePseudoButtonKeyPressEvent(event);
}
// Do not do anything here, because this function is used by derived classes
// that will emit the signal below. Otherwise there are going to be multiple
// signals sent.
// qDebug() << "Going to emit keyPressEventSignal(m_context);";
// emit keyPressEventSignal(m_context);
}
//! Handle specific key codes and trigger respective actions.
void
BasePlotWidget::keyReleaseEvent(QKeyEvent *event)
{
m_context.m_releasedKeyCode = event->key();
m_context.m_releasedKeyCodes.insert(event->key());
m_context.m_pressedKeyCodes.remove(event->key());
m_context.m_releasedKeyText = event->text();
// qDebug() << "Released code:" << m_context.m_releasedKeyCode;
// qDebug() << "Released codes:" << m_context.m_releasedKeyCodes;
// qDebug() << "Released key:" << m_context.m_releasedKeyText;
// The keyboard key is being released, set the key code to 0.
m_context.m_pressedKeyCode = 0;
m_context.m_pressedKeyText = "";
m_context.m_keyboardModifiers = QGuiApplication::queryKeyboardModifiers();
// Now test if the key that was released is one of the housekeeping keys.
if(event->key() == Qt::Key_Backspace)
{
// qDebug();
// The user wants to iterate back in the x/y axis range history.
restorePreviousAxesRangeHistory();
event->accept();
}
else if(event->key() == Qt::Key_Space)
{
return spaceKeyReleaseEvent(event);
}
else if(event->key() == Qt::Key_Delete)
{
// The user wants to delete a graph. What graph is to be determined
// programmatically:
// If there is a single graph, then that is the graph to be removed.
// If there are more than one graph, then only the ones that are selected
// are to be removed.
// Note that the user of this widget might want to provide the user with
// the ability to specify if all the children graph needs to be removed
// also. This can be coded in key modifiers. So provide the context.
int graph_count = plottableCount();
if(!graph_count)
{
// qDebug() << "Not a single graph in the plot widget. Doing
// nothing.";
event->accept();
return;
}
if(graph_count == 1)
{
// qDebug() << "A single graph is in the plot widget. Emitting a graph
// " "destruction requested signal for it:"
//<< graph();
emit plottableDestructionRequestedSignal(this, graph(), m_context);
}
else
{
// At this point we know there are more than one graph in the plot
// widget. We need to get the selected one (if any).
QList selected_graph_list;
selected_graph_list = selectedGraphs();
if(!selected_graph_list.size())
{
event->accept();
return;
}
// qDebug() << "Number of selected graphs to be destrobyed:"
//<< selected_graph_list.size();
for(int iter = 0; iter < selected_graph_list.size(); ++iter)
{
// qDebug()
//<< "Emitting a graph destruction requested signal for graph:"
//<< selected_graph_list.at(iter);
emit plottableDestructionRequestedSignal(
this, selected_graph_list.at(iter), m_context);
// We do not do this, because we want the slot called by the
// signal above to handle that removal. Remember that it is not
// possible to delete graphs manually.
//
// removeGraph(selected_graph_list.at(iter));
}
event->accept();
}
}
// End of
// else if(event->key() == Qt::Key_Delete)
else if(event->key() == Qt::Key_T)
{
// The user wants to toggle the visibiity of the tracers.
m_shouldTracersBeVisible = !m_shouldTracersBeVisible;
if(!m_shouldTracersBeVisible)
hideTracers();
else
showTracers();
event->accept();
}
else if(event->key() == Qt::Key_Left || event->key() == Qt::Key_Right ||
event->key() == Qt::Key_Up || event->key() == Qt::Key_Down)
{
return directionKeyReleaseEvent(event);
}
else if(event->key() == m_leftMousePseudoButtonKey ||
event->key() == m_rightMousePseudoButtonKey)
{
return mousePseudoButtonKeyReleaseEvent(event);
}
else if(event->key() == Qt::Key_S)
{
// The user is defining the size of the rhomboid fixed side. That could be
// either a vertical side (less intuitive) or a horizontal size (more
// intuitive, first exclusive implementation). But, in order to be able to
// perform identical integrations starting from non-transposed color maps
// and transposed color maps, the ability to define a vertical fixed size
// side of the rhomboid integration scope has become necessary.
// Check if the vertical displacement is significant (>= 10% of the color
// map height.
if(isVerticalDisplacementAboveThreshold())
{
// The user is dragging the cursor vertically in a sufficient delta to
// consider that they are willing to define a vertical fixed size
// of the rhomboid integration scope.
m_context.m_integrationScopeRhombWidth = 0;
m_context.m_integrationScopeRhombHeight = abs(
m_context.m_currentDragPoint.y() - m_context.m_startDragPoint.y());
// qDebug() << "Set m_context.m_integrationScopePolyHeight to"
// << m_context.m_integrationScopeRhombHeight
// << "upon release of S key";
}
else
{
// The user is dragging the cursor horiontally to define a horizontal
// fixed size of the rhomboid integration scope.
m_context.m_integrationScopeRhombWidth = abs(
m_context.m_currentDragPoint.x() - m_context.m_startDragPoint.x());
m_context.m_integrationScopeRhombHeight = 0;
// qDebug() << "Set m_context.m_integrationScopePolyWidth to"
// << m_context.m_integrationScopeRhombWidth
// << "upon release of S key";
}
}
// At this point emit the signal, since we did not treat it. Maybe the
// consumer widget wants to know that the keyboard key was released.
emit keyReleaseEventSignal(event, m_context);
}
void
BasePlotWidget::spaceKeyReleaseEvent([[maybe_unused]] QKeyEvent *event)
{
// qDebug();
}
void
BasePlotWidget::directionKeyPressEvent(QKeyEvent *event)
{
// qDebug() << "event key:" << event->key();
// The user is trying to move the positional cursor/markers. There are
// multiple way they can do that:
//
// 1.a. Hitting the arrow left/right keys alone will search for next pixel.
// 1.b. Hitting the arrow left/right keys with Alt modifier will search for
// a multiple of pixels that might be equivalent to one 20th of the pixel
// width of the plot widget. 1.c Hitting the left/right keys with Alt and
// Shift modifiers will search for a multiple of pixels that might be the
// equivalent to half of the pixel width.
//
// 2. Hitting the Control modifier will move the cursor to the next data
// point of the graph.
int pixel_increment = 0;
if(m_context.m_keyboardModifiers == Qt::NoModifier)
pixel_increment = 1;
else if(m_context.m_keyboardModifiers == Qt::AltModifier)
pixel_increment = 50;
// The user is moving the positional markers. This is equivalent to a
// non-dragging cursor movement to the next pixel. Note that the origin is
// located at the top left, so key down increments and key up decrements.
if(event->key() == Qt::Key_Left)
horizontalMoveMouseCursorCountPixels(-pixel_increment);
else if(event->key() == Qt::Key_Right)
horizontalMoveMouseCursorCountPixels(pixel_increment);
else if(event->key() == Qt::Key_Up)
verticalMoveMouseCursorCountPixels(-pixel_increment);
else if(event->key() == Qt::Key_Down)
verticalMoveMouseCursorCountPixels(pixel_increment);
event->accept();
}
void
BasePlotWidget::directionKeyReleaseEvent(QKeyEvent *event)
{
// qDebug() << "event key:" << event->key();
event->accept();
}
void
BasePlotWidget::mousePseudoButtonKeyPressEvent(
[[maybe_unused]] QKeyEvent *event)
{
// qDebug();
}
void
BasePlotWidget::mousePseudoButtonKeyReleaseEvent(QKeyEvent *event)
{
QPointF pixel_coordinates(
xAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.x()),
yAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.y()));
Qt::MouseButton button = Qt::NoButton;
QEvent::Type q_event_type = QEvent::MouseButtonPress;
if(event->key() == m_leftMousePseudoButtonKey)
{
// Toggles the left mouse button on/off
button = Qt::LeftButton;
m_context.m_isLeftPseudoButtonKeyPressed =
!m_context.m_isLeftPseudoButtonKeyPressed;
if(m_context.m_isLeftPseudoButtonKeyPressed)
q_event_type = QEvent::MouseButtonPress;
else
q_event_type = QEvent::MouseButtonRelease;
}
else if(event->key() == m_rightMousePseudoButtonKey)
{
// Toggles the right mouse button.
button = Qt::RightButton;
m_context.m_isRightPseudoButtonKeyPressed =
!m_context.m_isRightPseudoButtonKeyPressed;
if(m_context.m_isRightPseudoButtonKeyPressed)
q_event_type = QEvent::MouseButtonPress;
else
q_event_type = QEvent::MouseButtonRelease;
}
// qDebug() << "pressed/released pseudo button:" << button
//<< "q_event_type:" << q_event_type;
// Synthesize a QMouseEvent and use it.
QMouseEvent *mouse_event_p =
new QMouseEvent(q_event_type,
pixel_coordinates,
mapToGlobal(pixel_coordinates.toPoint()),
mapToGlobal(pixel_coordinates.toPoint()),
button,
button,
m_context.m_keyboardModifiers,
Qt::MouseEventSynthesizedByApplication);
if(q_event_type == QEvent::MouseButtonPress)
mousePressHandler(mouse_event_p);
else
mouseReleaseHandler(mouse_event_p);
delete mouse_event_p;
// event->accept();
}
/// KEYBOARD-related EVENTS
/// MOUSE-related EVENTS
void
BasePlotWidget::mouseMoveHandler(QMouseEvent *event)
{
// If we have no focus, then get it. See setFocus() to understand why asking
// for focus is cosly and thus why we want to make this decision first.
if(!hasFocus())
setFocus();
// qDebug() << (graph() != nullptr);
// if(graph(0) != nullptr)
// { // check if the widget contains some graphs
// The event->button() must be by Qt instructions considered to be 0.
// Whatever happens, we want to store the plot coordinates of the current
// mouse cursor position (will be useful later for countless needs).
QPointF mousePoint = event->position();
// qDebug() << "local mousePoint position in pixels:" << mousePoint;
m_context.m_lastCursorHoveredPoint.setX(xAxis->pixelToCoord(mousePoint.x()));
m_context.m_lastCursorHoveredPoint.setY(yAxis->pixelToCoord(mousePoint.y()));
// qDebug() << "lastCursorHoveredPoint coord:"
//<< m_context.m_lastCursorHoveredPoint;
// Now, depending on the button(s) (if any) that are pressed or not, we
// have a different processing.
// qDebug();
if(m_context.m_pressedMouseButtons & Qt::LeftButton ||
m_context.m_pressedMouseButtons & Qt::RightButton)
{
mouseMoveHandlerDraggingCursor(event);
// qDebug() << "Emitting mouseMoveDraggingCursorSignal";
emit mouseMoveDraggingCursorSignal(event, m_context);
}
else
mouseMoveHandlerNotDraggingCursor(event);
// }
// qDebug();
event->accept();
}
void
BasePlotWidget::mouseMoveHandlerNotDraggingCursor(QMouseEvent *event)
{
Q_UNUSED(event)
// qDebug();
m_context.m_isMouseDragging = false;
// qDebug();
// We are not dragging the mouse (no button pressed), simply let this
// widget's consumer know the position of the cursor and update the markers.
// The consumer of this widget will update mouse cursor position at
// m_context.m_lastCursorHoveredPoint if so needed.
emit lastCursorHoveredPointSignal(m_context.m_lastCursorHoveredPoint);
// qDebug();
// We are not dragging, so we do not show the region end tracer we only
// show the anchoring start trace that might be of use if the user starts
// using the arrow keys to move the cursor.
if(mp_vEndTracerItem != nullptr)
mp_vEndTracerItem->setVisible(false);
// qDebug();
// Only bother with the tracers if the user wants them to be visible.
// Their crossing point must be exactly at the last cursor-hovered point.
if(m_shouldTracersBeVisible)
{
// We are not dragging, so only show the position markers (v and h);
// qDebug();
if(mp_hPosTracerItem != nullptr)
{
// Horizontal position tracer.
mp_hPosTracerItem->setVisible(true);
mp_hPosTracerItem->start->setCoords(
xAxis->range().lower, m_context.m_lastCursorHoveredPoint.y());
mp_hPosTracerItem->end->setCoords(
xAxis->range().upper, m_context.m_lastCursorHoveredPoint.y());
}
// qDebug();
// Vertical position tracer.
if(mp_vPosTracerItem != nullptr)
{
mp_vPosTracerItem->setVisible(true);
mp_vPosTracerItem->setVisible(true);
mp_vPosTracerItem->start->setCoords(
m_context.m_lastCursorHoveredPoint.x(), yAxis->range().upper);
mp_vPosTracerItem->end->setCoords(
m_context.m_lastCursorHoveredPoint.x(), yAxis->range().lower);
}
// qDebug();
replot();
}
return;
}
void
BasePlotWidget::mouseMoveHandlerDraggingCursor(QMouseEvent *event)
{
// qDebug();
m_context.m_isMouseDragging = true;
// Now store the mouse position data into the the current drag point
// member datum, that will be used in countless occasions later.
m_context.m_currentDragPoint = m_context.m_lastCursorHoveredPoint;
m_context.m_keyboardModifiers = QGuiApplication::queryKeyboardModifiers();
// When we drag (either keyboard or mouse), we hide the position markers
// (black) and we show the start and end vertical markers for the region.
// Then, we draw the horizontal region range marker that delimits
// horizontally the dragged-over region.
if(mp_hPosTracerItem != nullptr)
mp_hPosTracerItem->setVisible(false);
if(mp_vPosTracerItem != nullptr)
mp_vPosTracerItem->setVisible(false);
// Only bother with the tracers if the user wants them to be visible.
if(m_shouldTracersBeVisible && (mp_vEndTracerItem != nullptr))
{
// The vertical end tracer position must be refreshed.
mp_vEndTracerItem->start->setCoords(m_context.m_currentDragPoint.x(),
yAxis->range().upper);
mp_vEndTracerItem->end->setCoords(m_context.m_currentDragPoint.x(),
yAxis->range().lower);
mp_vEndTracerItem->setVisible(true);
}
// Whatever the button, when we are dealing with the axes, we do not
// want to show any of the tracers.
if(m_context.m_wasClickOnXAxis || m_context.m_wasClickOnYAxis)
{
if(mp_hPosTracerItem != nullptr)
mp_hPosTracerItem->setVisible(false);
if(mp_vPosTracerItem != nullptr)
mp_vPosTracerItem->setVisible(false);
if(mp_vStartTracerItem != nullptr)
mp_vStartTracerItem->setVisible(false);
if(mp_vEndTracerItem != nullptr)
mp_vEndTracerItem->setVisible(false);
}
else
{
// qDebug() << "Not moving the mouse cursor over any of the axes.";
// Since we are not dragging the mouse cursor over the axes, make sure
// we store the drag directions in the context, as this might be
// useful for later operations.
// qDebug() << "Recording the drag direction(s).";
m_context.recordDragDirections();
// qDebug() << "Drag direction(s): " <<
// m_context.dragDirectionsToString();
}
// Because when we drag the mouse button (whatever the button) we need to
// know what is the drag delta (distance between start point and current
// point of the drag operation) on both axes, ask that these x|y deltas be
// computed.
calculateDragDeltas();
// Now deal with the BUTTON-SPECIFIC CODE.
if(m_context.m_mouseButtonsAtMousePress & Qt::LeftButton)
{
return mouseMoveHandlerLeftButtonDraggingCursor(event);
}
else if(m_context.m_mouseButtonsAtMousePress & Qt::RightButton)
{
return mouseMoveHandlerRightButtonDraggingCursor(event);
}
}
void
BasePlotWidget::mouseMoveHandlerLeftButtonDraggingCursor(QMouseEvent *event)
{
Q_UNUSED(event)
// qDebug() << "The left button is dragging.";
// Set the context.m_isMeasuringDistance to false, which later might be set
// to true if effectively we are measuring a distance. This is required
// because the derived widget classes might want to know if they have to
// perform some action on the basis that context is measuring a distance,
// for example the mass spectrum-specific widget might want to compute
// deconvolutions.
m_context.m_isMeasuringDistance = false;
// Let's first check if the mouse drag operation originated on either
// axis. In that case, the user is performing axis reframing or rescaling.
if(m_context.m_wasClickOnXAxis || m_context.m_wasClickOnYAxis)
{
// qDebug() << "Click was on one of the axes.";
if(m_context.m_keyboardModifiers & Qt::ControlModifier)
{
// The user is asking a rescale of the plot.
// We know that we do not want the tracers when we perform axis
// rescaling operations.
if(mp_hPosTracerItem != nullptr)
mp_hPosTracerItem->setVisible(false);
if(mp_vPosTracerItem != nullptr)
mp_vPosTracerItem->setVisible(false);
if(mp_vStartTracerItem != nullptr)
mp_vStartTracerItem->setVisible(false);
if(mp_vEndTracerItem != nullptr)
mp_vEndTracerItem->setVisible(false);
// This operation is particularly intensive, thus we want to
// reduce the number of calculations by skipping this calculation
// a number of times. The user can ask for this feature by
// clicking the 'Q' letter.
if(m_context.m_pressedKeyCode == Qt::Key_Q)
{
if(m_mouseMoveHandlerSkipCount < m_mouseMoveHandlerSkipAmount)
{
m_mouseMoveHandlerSkipCount++;
return;
}
else
{
m_mouseMoveHandlerSkipCount = 0;
}
}
// qDebug() << "Asking that the axes be rescaled.";
axisRescale();
}
else
{
// The user was simply dragging the axis. Just pan, that is slide
// the plot in the same direction as the mouse movement and with the
// same amplitude.
// qDebug() << "Asking that the axes be panned.";
axisPan();
}
return;
}
// At this point we understand that the user was not performing any
// panning/rescaling operation by clicking on any one of the axes.. Go on
// with other possibilities.
// Let's check if the user is actually drawing a rectangle (covering a
// real area) or is drawing a line.
// qDebug() << "The mouse dragging did not originate on an axis.";
if(isVerticalDisplacementAboveThreshold())
{
// qDebug() << "Apparently the selection is two-dimensional.";
// When we draw a two-dimensional integration scope, the tracers are of no
// use.
if(mp_hPosTracerItem != nullptr)
mp_hPosTracerItem->setVisible(false);
if(mp_vPosTracerItem != nullptr)
mp_vPosTracerItem->setVisible(false);
if(mp_vStartTracerItem != nullptr)
mp_vStartTracerItem->setVisible(false);
if(mp_vEndTracerItem != nullptr)
mp_vEndTracerItem->setVisible(false);
// Draw the rectangle, false, not as line segment and
// false, not for integration
drawSelectionRectangleAndPrepareZoom(false /*as_line_segment*/,
false /* for_integration*/);
// Draw the selection width/height text
drawXScopeSpanFeatures();
drawYScopeSpanFeatures();
}
else
{
// qDebug() << "Apparently we are measuring a delta.";
// Draw the rectangle, true, as line segment and
// false, not for integration
drawSelectionRectangleAndPrepareZoom(true, false);
// The pure position tracers should be hidden.
if(mp_hPosTracerItem != nullptr)
mp_hPosTracerItem->setVisible(true);
if(mp_vPosTracerItem != nullptr)
mp_vPosTracerItem->setVisible(true);
// Then, make sure the region range vertical tracers are visible.
if(mp_vStartTracerItem != nullptr)
mp_vStartTracerItem->setVisible(true);
if(mp_vEndTracerItem != nullptr)
mp_vEndTracerItem->setVisible(true);
// Draw the selection width text
drawXScopeSpanFeatures();
}
}
void
BasePlotWidget::mouseMoveHandlerRightButtonDraggingCursor(QMouseEvent *event)
{
Q_UNUSED(event)
// qDebug() << "The right button is dragging.";
// Set the context.m_isMeasuringDistance to false, which later might be set
// to true if effectively we are measuring a distance. This is required
// because the derived widgets might want to know if they have to perform
// some action on the basis that context is measuring a distance, for
// example the mass spectrum-specific widget might want to compute
// deconvolutions.
m_context.m_isMeasuringDistance = false;
if(isVerticalDisplacementAboveThreshold())
{
// qDebug() << "Apparently the selection has height.";
// When we draw a rectangle the tracers are of no use.
if(mp_hPosTracerItem != nullptr)
mp_hPosTracerItem->setVisible(false);
if(mp_vPosTracerItem != nullptr)
mp_vPosTracerItem->setVisible(false);
if(mp_vStartTracerItem != nullptr)
mp_vStartTracerItem->setVisible(false);
if(mp_vEndTracerItem != nullptr)
mp_vEndTracerItem->setVisible(false);
// Draw the rectangle, false for as_line_segment and true for
// integration.
drawSelectionRectangleAndPrepareZoom(false, true);
// Draw the selection width/height text
drawXScopeSpanFeatures();
drawYScopeSpanFeatures();
}
else
{
// qDebug() << "Apparently the selection is a not a rectangle.";
// Draw the rectangle, true as line segment and
// true for integration
drawSelectionRectangleAndPrepareZoom(true, true);
// Draw the selection width text
drawXScopeSpanFeatures();
}
}
void
BasePlotWidget::mousePressHandler(QMouseEvent *event)
{
// qDebug() << "Entering";
// When the user clicks this widget it has to take focus.
setFocus();
QPointF mousePoint = event->position();
m_context.m_lastPressedMouseButton = event->button();
m_context.m_mouseButtonsAtMousePress = event->buttons();
// The pressedMouseButtons must continually inform on the status of
// pressed buttons so add the pressed button.
m_context.m_pressedMouseButtons |= event->button();
// qDebug().noquote() << m_context.toString();
// In all the processing of the events, we need to know if the user is
// clicking somewhere with the intent to change the plot ranges (reframing
// or rescaling the plot).
//
// Reframing the plot means that the new x and y axes ranges are modified
// so that they match the region that the user has encompassed by left
// clicking the mouse and dragging it over the plot. That is we reframe
// the plot so that it contains only the "selected" region.
//
// Rescaling the plot means the the new x|y axis range is modified such
// that the lower axis range is constant and the upper axis range is moved
// either left or right by the same amont as the x|y delta encompassed by
// the user moving the mouse. The axis is thus either compressed (mouse
// movement is leftwards) or un-compressed (mouse movement is rightwards).
// There are two ways to perform axis range modifications:
//
// 1. By clicking on any of the axes
// 2. By clicking on the plot region but using keyboard key modifiers,
// like Alt and Ctrl.
//
// We need to know both cases separately which is why we need to perform a
// number of tests below.
// Let's check if the click is on the axes, either X or Y, because that
// will allow us to take proper actions.
if(isClickOntoXAxis(mousePoint))
{
// The X axis was clicked upon, we need to document that:
// qDebug() << __FILE__ << __LINE__
//<< "Layout element is axisRect and actually on an X axis part.";
m_context.m_wasClickOnXAxis = true;
// int currentInteractions = interactions();
// currentInteractions |= QCP::iRangeDrag;
// setInteractions((QCP::Interaction)currentInteractions);
// axisRect()->setRangeDrag(xAxis->orientation());
}
else
m_context.m_wasClickOnXAxis = false;
if(isClickOntoYAxis(mousePoint))
{
// The Y axis was clicked upon, we need to document that:
// qDebug() << __FILE__ << __LINE__
//<< "Layout element is axisRect and actually on an Y axis part.";
m_context.m_wasClickOnYAxis = true;
// int currentInteractions = interactions();
// currentInteractions |= QCP::iRangeDrag;
// setInteractions((QCP::Interaction)currentInteractions);
// axisRect()->setRangeDrag(yAxis->orientation());
}
else
m_context.m_wasClickOnYAxis = false;
// At this point, let's see if we need to remove the QCP::iRangeDrag bit:
if(!m_context.m_wasClickOnXAxis && !m_context.m_wasClickOnYAxis)
{
// qDebug() << __FILE__ << __LINE__
// << "Click outside of axes.";
// int currentInteractions = interactions();
// currentInteractions = currentInteractions & ~QCP::iRangeDrag;
// setInteractions((QCP::Interaction)currentInteractions);
}
m_context.m_startDragPoint.setX(xAxis->pixelToCoord(mousePoint.x()));
m_context.m_startDragPoint.setY(yAxis->pixelToCoord(mousePoint.y()));
// Now install the vertical start tracer at the last cursor hovered
// position.
if((m_shouldTracersBeVisible) && (mp_vStartTracerItem != nullptr))
mp_vStartTracerItem->setVisible(true);
if(mp_vStartTracerItem != nullptr)
{
mp_vStartTracerItem->start->setCoords(
m_context.m_lastCursorHoveredPoint.x(), yAxis->range().upper);
mp_vStartTracerItem->end->setCoords(
m_context.m_lastCursorHoveredPoint.x(), yAxis->range().lower);
}
replot();
emit mousePressEventSignal(event, m_context);
// qDebug() << "Exiting after having emitted mousePressEventSignal with base
// context:"
// << m_context.toString();
}
void
BasePlotWidget::mouseReleaseHandler(QMouseEvent *event)
{
// qDebug() << "Entering";
// Now the real code of this function.
m_context.m_lastReleasedMouseButton = event->button();
// The event->buttons() is the description of the buttons that are pressed
// at the moment the handler is invoked, that is now. If left and right were
// pressed, and left was released, event->buttons() would be right.
m_context.m_mouseButtonsAtMouseRelease = event->buttons();
// The pressedMouseButtons must continually inform on the status of pressed
// buttons so remove the released button.
m_context.m_pressedMouseButtons ^= event->button();
// qDebug().noquote() << m_context.toString();
// We'll need to know if modifiers were pressed a the moment the user
// released the mouse button.
m_context.m_keyboardModifiers = QGuiApplication::keyboardModifiers();
if(!m_context.m_isMouseDragging)
{
// Let the user know that the mouse was *not* being dragged.
m_context.m_wasMouseDragging = false;
event->accept();
return;
}
// Let the user know that the mouse was being dragged.
m_context.m_wasMouseDragging = true;
// We cannot hide all items in one go because we rely on their visibility
// to know what kind of dragging operation we need to perform (line-only
// X-based zoom or rectangle-based X- and Y-based zoom, for example). The
// only thing we know is that we can make the text invisible.
// Same for the x delta text item
mp_xDeltaTextItem->setVisible(false);
mp_yDeltaTextItem->setVisible(false);
// We do not show the end vertical region range marker.
mp_vEndTracerItem->setVisible(false);
// Horizontal position tracer.
mp_hPosTracerItem->setVisible(true);
mp_hPosTracerItem->start->setCoords(xAxis->range().lower,
m_context.m_lastCursorHoveredPoint.y());
mp_hPosTracerItem->end->setCoords(xAxis->range().upper,
m_context.m_lastCursorHoveredPoint.y());
// Vertical position tracer.
mp_vPosTracerItem->setVisible(true);
mp_vPosTracerItem->setVisible(true);
mp_vPosTracerItem->start->setCoords(m_context.m_lastCursorHoveredPoint.x(),
yAxis->range().upper);
mp_vPosTracerItem->end->setCoords(m_context.m_lastCursorHoveredPoint.x(),
yAxis->range().lower);
// Force replot now because later that call might not be performed.
replot();
// If we were using the "quantum" display for the rescale of the axes
// using the Ctrl-modified left button click drag in the axes, then reset
// the count to 0.
m_mouseMoveHandlerSkipCount = 0;
// By definition we are stopping the drag operation by releasing the mouse
// button. Whatever that mouse button was pressed before and if there was
// one pressed before. We cannot set that boolean value to false before
// this place, because we call a number of routines above that need to know
// that dragging was occurring. Like mouseReleaseHandledEvent(event) for
// example.
m_context.m_isMouseDragging = false;
// Now that we have computed the useful ranges, we need to check what to do
// depending on the button that was pressed.
if(m_context.m_lastReleasedMouseButton == Qt::LeftButton)
{
return mouseReleaseHandlerLeftButton(event);
}
else if(m_context.m_lastReleasedMouseButton == Qt::RightButton)
{
return mouseReleaseHandlerRightButton(event);
}
// FIXME: should we really accept ? No, since we pass the event on.
// event->accept();
// Before returning, emit the signal for the user of
// this class consumption.
// qDebug() << "Emitting mouseReleaseEventSignal.";
emit mouseReleaseEventSignal(event, m_context);
// qDebug() << "Exiting after having emitted mouseReleaseEventSignal with base
// context:"
// << m_context.toString();
return;
}
void
BasePlotWidget::mouseReleaseHandlerLeftButton(QMouseEvent *event)
{
Q_UNUSED(event)
// qDebug();
if(m_context.m_wasClickOnXAxis || m_context.m_wasClickOnYAxis)
{
// When the mouse move handler pans the plot, we cannot store each axes
// range history element that would mean store a huge amount of such
// elements, as many element as there are mouse move event handled by
// the Qt event queue. But we can store an axis range history element
// for the last situation of the mouse move: when the button is
// released:
updateAxesRangeHistory();
// qDebug() << "emit plotRangesChangedSignal(m_context);"
emit plotRangesChangedSignal((QMouseEvent *)nullptr, m_context);
replot();
// Nothing else to do.
return;
}
// There are two possibilities:
//
// 1. The full integration scope (four lines) were currently drawn, which
// means the user was willing to perform a zoom operation.
//
// 2. Only the first top line was drawn, which means the user was dragging
// the cursor horizontally. That might have two ends, as shown below.
// So, first check what is drawn of the selection polygon.
SelectionDrawingLines selection_drawing_lines =
whatIsVisibleOfTheSelectionRectangle();
// Now that we know what was currently drawn of the selection polygon, we
// can remove it. true to reset the values to 0.
hideSelectionRectangle(true);
// Force replot now because later that call might not be performed.
replot();
if(selection_drawing_lines == SelectionDrawingLines::FULL_POLYGON)
{
// qDebug() << "Yes, the full polygon was visible";
// If we were dragging with the left button pressed and could draw a
// rectangle, then we were preparing a zoom operation. Let's bring that
// operation to its accomplishment.
axisZoom();
return;
}
else if(selection_drawing_lines == SelectionDrawingLines::TOP_LINE)
{
// qDebug() << "No, only the top line of the full polygon was visible";
// The user was dragging the left mouse cursor and that may mean they
// were measuring a distance or willing to perform a special zoom
// operation if the Ctrl key was down.
// If the user started by clicking in the plot region, dragged the mouse
// cursor with the left button and pressed the Ctrl modifier, then that
// means that they wanted to do a rescale over the x-axis in the form of
// a reframing.
if(m_context.m_keyboardModifiers & Qt::ControlModifier)
{
return axisReframe();
}
}
// else
// qDebug() << "Another possibility.";
}
void
BasePlotWidget::mouseReleaseHandlerRightButton(QMouseEvent *event)
{
Q_UNUSED(event)
// qDebug();
// The right button is used for the integrations. Not for axis range
// operations. So all we have to do is remove the various graphics items and
// send a signal with the context that contains all the data required by the
// user to perform the integrations over the right plot regions.
// Whatever we were doing we need to make the selection line invisible:
if(mp_xDeltaTextItem->visible())
mp_xDeltaTextItem->setVisible(false);
if(mp_yDeltaTextItem->visible())
mp_yDeltaTextItem->setVisible(false);
// Also make the vertical end tracer invisible.
mp_vEndTracerItem->setVisible(false);
// Once the integration is asked for, then the selection rectangle if of no
// more use.
hideSelectionRectangle();
// Force replot now because later that call might not be performed.
replot();
// Note that we only request an integration if the x-axis delta is enough.
double x_delta_pixel =
fabs(xAxis->coordToPixel(m_context.m_currentDragPoint.x()) -
xAxis->coordToPixel(m_context.m_startDragPoint.x()));
if(x_delta_pixel > 3)
{
// qDebug() << "Emitting integrationRequestedSignal(m_context)";
emit integrationRequestedSignal(m_context);
}
// else
// qDebug() << "Not asking for integration.";
}
void
BasePlotWidget::mouseWheelHandler([[maybe_unused]] QWheelEvent *event)
{
// We should record the new range values each time the wheel is used to
// zoom/unzoom.
m_context.m_xRange = QCPRange(xAxis->range());
m_context.m_yRange = QCPRange(yAxis->range());
// qDebug() << "New x range: " << m_context.m_xRange;
// qDebug() << "New y range: " << m_context.m_yRange;
updateAxesRangeHistory();
emit plotRangesChangedWheelEventSignal(event, m_context);
emit mouseWheelEventSignal(event, m_context);
event->accept();
}
void
BasePlotWidget::axisDoubleClickHandler(
QCPAxis *axis,
[[maybe_unused]] QCPAxis::SelectablePart part,
QMouseEvent *event)
{
// qDebug();
m_context.m_keyboardModifiers = QGuiApplication::queryKeyboardModifiers();
if(m_context.m_keyboardModifiers & Qt::ControlModifier)
{
// qDebug();
// If the Ctrl modifiers is active, then both axes are to be reset. Also
// the histories are reset also.
rescaleAxes();
resetAxesRangeHistory();
}
else
{
// qDebug();
// Only the axis passed as parameter is to be rescaled.
// Reset the range of that axis to the max view possible.
axis->rescale();
updateAxesRangeHistory();
event->accept();
}
// The double-click event does not cancel the mouse press event. That is, if
// left-double-clicking, at the end of the operation the button still
// "pressed". We need to remove manually the button from the pressed buttons
// context member.
m_context.m_pressedMouseButtons ^= event->button();
updateContextXandYAxisRanges();
emit plotRangesChangedSignal(event, m_context);
replot();
}
bool
BasePlotWidget::isClickOntoXAxis(const QPointF &mousePoint)
{
QCPLayoutElement *layoutElement = layoutElementAt(mousePoint);
if(layoutElement &&
layoutElement == dynamic_cast(axisRect()))
{
// The graph is *inside* the axisRect that is the outermost envelope of
// the graph. Thus, if we want to know if the click was indeed on an
// axis, we need to check what selectable part of the the axisRect we
// were clicking:
QCPAxis::SelectablePart selectablePart;
selectablePart = xAxis->getPartAt(mousePoint);
if(selectablePart == QCPAxis::spAxisLabel ||
selectablePart == QCPAxis::spAxis ||
selectablePart == QCPAxis::spTickLabels)
return true;
}
return false;
}
bool
BasePlotWidget::isClickOntoYAxis(const QPointF &mousePoint)
{
QCPLayoutElement *layoutElement = layoutElementAt(mousePoint);
if(layoutElement &&
layoutElement == dynamic_cast(axisRect()))
{
// The graph is *inside* the axisRect that is the outermost envelope of
// the graph. Thus, if we want to know if the click was indeed on an
// axis, we need to check what selectable part of the the axisRect we
// were clicking:
QCPAxis::SelectablePart selectablePart;
selectablePart = yAxis->getPartAt(mousePoint);
if(selectablePart == QCPAxis::spAxisLabel ||
selectablePart == QCPAxis::spAxis ||
selectablePart == QCPAxis::spTickLabels)
return true;
}
return false;
}
/// MOUSE-related EVENTS
/// MOUSE MOVEMENTS mouse/keyboard-triggered
int
BasePlotWidget::dragDirection()
{
// The user is dragging the mouse, probably to rescale the axes, but we need
// to sort out in which direction the drag is happening.
// This function should be called after calculateDragDeltas, so that
// m_context has the proper x/y delta values that we'll compare.
// Note that we cannot compare simply x or y deltas because the y axis might
// have a different scale that the x axis. So we first need to convert the
// positions to pixels.
double x_delta_pixel =
fabs(xAxis->coordToPixel(m_context.m_currentDragPoint.x()) -
xAxis->coordToPixel(m_context.m_startDragPoint.x()));
double y_delta_pixel =
fabs(yAxis->coordToPixel(m_context.m_currentDragPoint.y()) -
yAxis->coordToPixel(m_context.m_startDragPoint.y()));
if(x_delta_pixel > y_delta_pixel)
return Qt::Horizontal;
return Qt::Vertical;
}
void
BasePlotWidget::moveMouseCursorGraphCoordToGlobal(QPointF graph_coordinates)
{
// First convert the graph coordinates to pixel coordinates.
QPointF pixels_coordinates(xAxis->coordToPixel(graph_coordinates.x()),
yAxis->coordToPixel(graph_coordinates.y()));
moveMouseCursorPixelCoordToGlobal(pixels_coordinates.toPoint());
}
void
BasePlotWidget::moveMouseCursorPixelCoordToGlobal(QPointF pixel_coordinates)
{
// qDebug() << "Calling set pos with new cursor position.";
QCursor::setPos(mapToGlobal(pixel_coordinates.toPoint()));
}
void
BasePlotWidget::horizontalMoveMouseCursorCountPixels(int pixel_count)
{
QPointF graph_coord = horizontalGetGraphCoordNewPointCountPixels(pixel_count);
QPointF pixel_coord(xAxis->coordToPixel(graph_coord.x()),
yAxis->coordToPixel(graph_coord.y()));
// Now we need ton convert the new coordinates to the global position system
// and to move the cursor to that new position. That will create an event to
// move the mouse cursor.
moveMouseCursorPixelCoordToGlobal(pixel_coord.toPoint());
}
QPointF
BasePlotWidget::horizontalGetGraphCoordNewPointCountPixels(int pixel_count)
{
QPointF pixel_coordinates(
xAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.x()) + pixel_count,
yAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.y()));
// Now convert back to local coordinates.
QPointF graph_coordinates(xAxis->pixelToCoord(pixel_coordinates.x()),
yAxis->pixelToCoord(pixel_coordinates.y()));
return graph_coordinates;
}
void
BasePlotWidget::verticalMoveMouseCursorCountPixels(int pixel_count)
{
QPointF graph_coord = verticalGetGraphCoordNewPointCountPixels(pixel_count);
QPointF pixel_coord(xAxis->coordToPixel(graph_coord.x()),
yAxis->coordToPixel(graph_coord.y()));
// Now we need ton convert the new coordinates to the global position system
// and to move the cursor to that new position. That will create an event to
// move the mouse cursor.
moveMouseCursorPixelCoordToGlobal(pixel_coord.toPoint());
}
QPointF
BasePlotWidget::verticalGetGraphCoordNewPointCountPixels(int pixel_count)
{
QPointF pixel_coordinates(
xAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.x()),
yAxis->coordToPixel(m_context.m_lastCursorHoveredPoint.y()) + pixel_count);
// Now convert back to local coordinates.
QPointF graph_coordinates(xAxis->pixelToCoord(pixel_coordinates.x()),
yAxis->pixelToCoord(pixel_coordinates.y()));
return graph_coordinates;
}
/// MOUSE MOVEMENTS mouse/keyboard-triggered
/// RANGE-related functions
QCPRange
BasePlotWidget::getRangeX(bool &found_range, int index) const
{
QCPGraph *graph_p = graph(index);
if(graph_p == nullptr)
qFatal("Programming error.");
return graph_p->getKeyRange(found_range);
}
QCPRange
BasePlotWidget::getRangeY(bool &found_range, int index) const
{
QCPGraph *graph_p = graph(index);
if(graph_p == nullptr)
qFatal("Programming error.");
return graph_p->getValueRange(found_range);
}
QCPRange
BasePlotWidget::getRange(Enums::Axis axis,
RangeType range_type,
bool &found_range) const
{
// Iterate in all the graphs in this widget and return a QCPRange that has
// its lower member as the greatest lower value of all
// its upper member as the smallest upper value of all
if(!graphCount())
{
found_range = false;
return QCPRange(0, 1);
}
if(graphCount() == 1)
return graph()->getKeyRange(found_range);
bool found_at_least_one_range = false;
// Create an invalid range.
QCPRange result_range(QCPRange::minRange + 1, QCPRange::maxRange + 1);
for(int iter = 0; iter < graphCount(); ++iter)
{
QCPRange temp_range;
bool found_range_for_iter = false;
QCPGraph *graph_p = graph(iter);
// Depending on the axis param, select the key or value range.
if(axis == Enums::Axis::x)
temp_range = graph_p->getKeyRange(found_range_for_iter);
else if(axis == Enums::Axis::y)
temp_range = graph_p->getValueRange(found_range_for_iter);
else
qFatal("Cannot reach this point. Programming error.");
// Was a range found for the iterated graph ? If not skip this
// iteration.
if(!found_range_for_iter)
continue;
// While the innermost_range is invalid, we need to seed it with a good
// one. So check this.
if(!QCPRange::validRange(result_range))
qFatal("The obtained range is invalid !");
// At this point we know the obtained range is OK.
result_range = temp_range;
// We found at least one valid range!
found_at_least_one_range = true;
// At this point we have two valid ranges to compare. Depending on
// range_type, we need to perform distinct comparisons.
if(range_type == RangeType::innermost)
{
if(temp_range.lower > result_range.lower)
result_range.lower = temp_range.lower;
if(temp_range.upper < result_range.upper)
result_range.upper = temp_range.upper;
}
else if(range_type == RangeType::outermost)
{
if(temp_range.lower < result_range.lower)
result_range.lower = temp_range.lower;
if(temp_range.upper > result_range.upper)
result_range.upper = temp_range.upper;
}
else
qFatal("Cannot reach this point. Programming error.");
// Continue to next graph, if any.
}
// End of
// for(int iter = 0; iter < graphCount(); ++iter)
// Let the caller know if we found at least one range.
found_range = found_at_least_one_range;
return result_range;
}
QCPRange
BasePlotWidget::getInnermostRangeX(bool &found_range) const
{
return getRange(Enums::Axis::x, RangeType::innermost, found_range);
}
QCPRange
BasePlotWidget::getOutermostRangeX(bool &found_range) const
{
return getRange(Enums::Axis::x, RangeType::outermost, found_range);
}
QCPRange
BasePlotWidget::getInnermostRangeY(bool &found_range) const
{
return getRange(Enums::Axis::y, RangeType::innermost, found_range);
}
QCPRange
BasePlotWidget::getOutermostRangeY(bool &found_range) const
{
return getRange(Enums::Axis::y, RangeType::outermost, found_range);
}
/// RANGE-related functions
/// PLOTTING / REPLOTTING functions
void
BasePlotWidget::axisRescale()
{
// Get the current x lower/upper range, that is, leftmost/rightmost x
// coordinate.
double xLower = xAxis->range().lower;
double xUpper = xAxis->range().upper;
// Get the current y lower/upper range, that is, bottommost/topmost y
// coordinate.
double yLower = yAxis->range().lower;
double yUpper = yAxis->range().upper;
// This function is called only when the user has clicked on the x/y axis or
// when the user has dragged the left mouse button with the Ctrl key
// modifier. The m_context.m_wasClickOnXAxis is then simulated in the mouse
// move handler. So we need to test which axis was clicked-on.
if(m_context.m_wasClickOnXAxis)
{
// We are changing the range of the X axis.
// If xDelta is < 0, then we were dragging from right to left, we are
// compressing the view on the x axis, by adding new data to the right
// hand size of the graph. So we add xDelta to the upper bound of the
// range. Otherwise we are uncompressing the view on the x axis and
// remove the xDelta from the upper bound of the range. This is why we
// have the
// '-'
// and not '+' below;
xAxis->setRange(xLower, xUpper - m_context.m_xDelta);
}
// End of
// if(m_context.m_wasClickOnXAxis)
else // that is, if(m_context.m_wasClickOnYAxis)
{
// We are changing the range of the Y axis.
// See above for an explanation of the computation (the - sign below).
yAxis->setRange(yLower, yUpper - m_context.m_yDelta);
}
// End of
// else // that is, if(m_context.m_wasClickOnYAxis)
// Update the context with the current axes ranges
updateContextXandYAxisRanges();
emit plotRangesChangedSignal((QMouseEvent *)nullptr, m_context);
replot();
}
void
BasePlotWidget::axisReframe()
{
// double sorted_start_drag_point_x =
// std::min(m_context.m_startDragPoint.x(),
// m_context.m_currentDragPoint.x());
// xAxis->setRange(sorted_start_drag_point_x,
// sorted_start_drag_point_x + fabs(m_context.m_xDelta));
xAxis->setRange(QCPRange(m_context.m_xRegionRangeStart, m_context.m_xRegionRangeStop));
// Note that the y axis should be rescaled from current lower value to new
// upper value matching the y-axis position of the cursor when the mouse
// button was released.
yAxis->setRange(xAxis->range().lower,
std::max(m_context.m_yRegionRangeStart, m_context.m_yRegionRangeStop));
// qDebug() << "xaxis:" << xAxis->range().lower << "-" <<
// xAxis->range().upper
//<< "yaxis:" << yAxis->range().lower << "-" << yAxis->range().upper;
updateContextXandYAxisRanges();
updateAxesRangeHistory();
emit plotRangesChangedSignal((QMouseEvent *)nullptr, m_context);
replot();
}
void
BasePlotWidget::axisZoom()
{
// Use the m_context.m_xRegionRangeStart/End values, but we need to sort the
// values before using them, because now we want to really have the lower x
// value. Simply craft a QCPRange that will swap the values if lower is not
// < than upper QCustomPlot calls this normalization).
xAxis->setRange(QCPRange(m_context.m_xRegionRangeStart, m_context.m_xRegionRangeStop));
yAxis->setRange(QCPRange(m_context.m_yRegionRangeStart, m_context.m_yRegionRangeStop));
updateContextXandYAxisRanges();
updateAxesRangeHistory();
emit plotRangesChangedSignal((QMouseEvent *)nullptr, m_context);
replot();
}
void
BasePlotWidget::axisPan()
{
// Sanity check
if(!m_context.m_wasClickOnXAxis && !m_context.m_wasClickOnYAxis)
qFatal(
"This function can only be called if the mouse click was on one of the "
"axes");
if(m_context.m_wasClickOnXAxis)
{
xAxis->setRange(m_context.m_xRange.lower - m_context.m_xDelta,
m_context.m_xRange.upper - m_context.m_xDelta);
}
if(m_context.m_wasClickOnYAxis)
{
yAxis->setRange(m_context.m_yRange.lower - m_context.m_yDelta,
m_context.m_yRange.upper - m_context.m_yDelta);
}
updateContextXandYAxisRanges();
// qDebug() << "The updated context:" << m_context.toString();
// We cannot store the new ranges in the history, because the pan operation
// involved a huge quantity of micro-movements elicited upon each mouse move
// cursor event so we would have a huge history.
// updateAxesRangeHistory();
// Now that the context has the right range values, we can emit the
// signal that will be used by this plot widget users, typically to
// abide by the x/y range lock required by the user.
emit plotRangesChangedSignal((QMouseEvent *)nullptr, m_context);
replot();
}
void
BasePlotWidget::replotWithAxesRanges(QCPRange xAxisRange,
QCPRange yAxisRange,
Enums::Axis axis)
{
// qDebug() << "With axis:" << (int)axis;
if(static_cast(axis) & static_cast(Enums::Axis::x))
{
xAxis->setRange(xAxisRange.lower, xAxisRange.upper);
}
if(static_cast(axis) & static_cast(Enums::Axis::y))
{
yAxis->setRange(yAxisRange.lower, yAxisRange.upper);
}
// We do not want to update the history, because there would be way too
// much history items, since this function is called upon mouse moving
// handling and not only during mouse release events.
// updateAxesRangeHistory();
replot();
}
void
BasePlotWidget::replotWithAxisRangeX(double lower, double upper)
{
// qDebug();
xAxis->setRange(lower, upper);
replot();
}
void
BasePlotWidget::replotWithAxisRangeY(double lower, double upper)
{
// qDebug();
yAxis->setRange(lower, upper);
replot();
}
/// PLOTTING / REPLOTTING functions
/// PLOT ITEMS : TRACER TEXT ITEMS...
//! Hide the selection line, the xDelta text and the zoom rectangle items.
void
BasePlotWidget::hideAllPlotItems()
{
mp_xDeltaTextItem->setVisible(false);
mp_yDeltaTextItem->setVisible(false);
// mp_zoomRectItem->setVisible(false);
hideSelectionRectangle();
// Force a replot to make sure the action is immediately visible by the
// user, even without moving the mouse.
replot();
}
//! Show the traces (vertical and horizontal).
void
BasePlotWidget::showTracers()
{
m_shouldTracersBeVisible = true;
mp_vPosTracerItem->setVisible(true);
mp_hPosTracerItem->setVisible(true);
mp_vStartTracerItem->setVisible(true);
mp_vEndTracerItem->setVisible(true);
// Force a replot to make sure the action is immediately visible by the
// user, even without moving the mouse.
replot();
}
//! Hide the traces (vertical and horizontal).
void
BasePlotWidget::hideTracers()
{
m_shouldTracersBeVisible = false;
mp_hPosTracerItem->setVisible(false);
mp_vPosTracerItem->setVisible(false);
mp_vStartTracerItem->setVisible(false);
mp_vEndTracerItem->setVisible(false);
// Force a replot to make sure the action is immediately visible by the
// user, even without moving the mouse.
replot();
}
void
BasePlotWidget::drawSelectionRectangleAndPrepareZoom(bool as_line_segment,
bool for_integration)
{
// The user has dragged the mouse left button on the graph, which means he
// is willing to draw a selection rectangle, either for zooming-in or for
// integration.
if(mp_xDeltaTextItem != nullptr)
mp_xDeltaTextItem->setVisible(false);
if(mp_yDeltaTextItem != nullptr)
mp_yDeltaTextItem->setVisible(false);
// Ensure the right selection rectangle is drawn.
updateIntegrationScopeDrawing(as_line_segment, for_integration);
// Note that if we draw a zoom rectangle, then we are certainly not
// measuring anything. So set the boolean value to false so that the user of
// this widget or derived classes know that there is nothing to perform upon
// (like deconvolution, for example).
m_context.m_isMeasuringDistance = false;
// Also remove the delta value from the pipeline by sending a simple
// distance without measurement signal.
emit xAxisMeasurementSignal(m_context, false);
replot();
}
void
BasePlotWidget::drawXScopeSpanFeatures()
{
// Depending on the kind of integration scope, we will have to display
// differently calculated values. We want to provide the user with
// the horizontal span of the integration scope. There are different
// situations.
// 1. The scope is mono-dimensional across the x axis: the span
// is thus simply the width.
// 2. The scope is bi-dimensional and is a rectangle: the span is
// thus simply the width.
// 3. The socpe is bi-dimensional and is a rhomboid: the span is
// the width.
// In the first and second cases above, the width is equal to the
// m_context.m_xDelta.
// In the case of the rhomboid, the span is not m_context.m_xDelta,
// it is more than that if the rhomboid is horizontal because it is
// the m_context.m_xDelta plus the rhomboid's horizontal size.
// FIXME: is this still true?
//
// We do not want to show the position markers because the only horiontal
// line to be visible must be contained between the start and end vertical
// tracer items.
mp_hPosTracerItem->setVisible(false);
mp_vPosTracerItem->setVisible(false);
// We want to draw the text in the middle position of the leftmost-rightmost
// point, even with rhomboid scopes.
QPointF leftmost_point;
if(!m_context.mpa_integrationScope->getLeftMostPoint(leftmost_point))
qFatal("Could not get the left-most point.");
double width;
if(!m_context.mpa_integrationScope->getWidth(width))
qFatal("Could not get width.");
// qDebug() << "width:" << width;
double x_axis_center_position = leftmost_point.x() + width / 2;
// We want the text to print inside the rectangle, always at the current
// drag point so the eye can follow the delta value while looking where to
// drag the mouse. To position the text inside the rectangle, we need to
// know what is the drag direction.
// What is the distance between the rectangle line at current drag point and
// the text itself. Think of this as a margin distance between the
// point of interest and the actual position of the text.
int pixels_away_from_line = 15;
QPointF reference_point_for_y_axis_label_position;
// ATTENTION: the pixel coordinates for the vertical direction go in reverse
// order with respect to the y axis values !!! That is, pixel(0,0) is top
// left of the graph.
if(static_cast(m_context.m_dragDirections) &
static_cast(DragDirections::BOTTOM_TO_TOP))
{
// We need to print outside the rectangle, that is pixels_away_from_line
// pixels to the top, so with pixel y value decremented of that
// pixels_above_line value (one would have expected to increment that
// value, along the y axis, but the coordinates in pixel go in reverse
// order).
pixels_away_from_line *= -1;
if(!m_context.mpa_integrationScope->getTopMostPoint(
reference_point_for_y_axis_label_position))
qFatal("Failed to get top most point.");
}
else
{
if(!m_context.mpa_integrationScope->getBottomMostPoint(
reference_point_for_y_axis_label_position))
qFatal("Failed to get bottom most point.");
}
// double y_axis_pixel_coordinate =
// yAxis->coordToPixel(m_context.m_currentDragPoint.y());
double y_axis_pixel_coordinate =
yAxis->coordToPixel(reference_point_for_y_axis_label_position.y());
// Now that we have the coordinate in pixel units, we can correct
// it by the value of the margin we want to give.
double y_axis_modified_pixel_coordinate =
y_axis_pixel_coordinate + pixels_away_from_line;
// Set aside a point instance to store the pixel coordinates of the text.
QPointF pixel_coordinates;
pixel_coordinates.setX(x_axis_center_position);
pixel_coordinates.setY(y_axis_modified_pixel_coordinate);
// Now convert back to graph coordinates.
QPointF graph_coordinates(xAxis->pixelToCoord(pixel_coordinates.x()),
yAxis->pixelToCoord(pixel_coordinates.y()));
// qDebug() << "Should print the label at point:" << graph_coordinates;
if(mp_xDeltaTextItem != nullptr)
{
mp_xDeltaTextItem->position->setCoords(x_axis_center_position,
graph_coordinates.y());
// Dynamically set the number of decimals to ensure we can read
// a meaning full delta value even if it is very very very small.
// That is, allow one to read 0.00333, 0.000333, 1.333 and so on.
// The computation below only works properly when the passed
// value is fabs() (not negative !!!).
int decimals = Utils::zeroDecimalsInValue(width) + 3;
QString label_text = QString("full x span %1 -- x drag delta %2")
.arg(width, 0, 'f', decimals)
.arg(fabs(m_context.m_xDelta), 0, 'f', decimals);
mp_xDeltaTextItem->setText(label_text);
mp_xDeltaTextItem->setFont(QFont(font().family(), 9));
mp_xDeltaTextItem->setVisible(true);
}
// Set the boolean to true so that derived widgets know that something is
// being measured, and they can act accordingly, for example by computing
// deconvolutions in a mass spectrum.
m_context.m_isMeasuringDistance = true;
replot();
// Let the caller know that we were measuring something.
emit xAxisMeasurementSignal(m_context, true);
return;
}
void
BasePlotWidget::drawYScopeSpanFeatures()
{
// See drawXScopeSpanFeatures() for explanations.
// Check right away if there is height!
double height;
if(!m_context.mpa_integrationScope->getHeight(height))
qFatal("Could not get height.");
// If there is no height, we have nothing to do here.
if(!height)
return;
// qDebug() << "height:" << height;
// FIXME: is this still true?
//
// We do not want to show the position markers because the only horiontal
// line to be visible must be contained between the start and end vertical
// tracer items.
mp_hPosTracerItem->setVisible(false);
mp_vPosTracerItem->setVisible(false);
// First the easy part: the vertical position: centered on the
// scope Y span.
QPointF bottom_most_point;
if(!m_context.mpa_integrationScope->getBottomMostPoint(bottom_most_point))
qFatal("Could not get the bottom-most bottom point.");
double y_axis_center_position = bottom_most_point.y() + height / 2;
// We want to draw the text outside the rectangle (if normal rectangle)
// at a small distance from the vertical limit of the scope at the
// position of the current drag point. We need to check the horizontal
// drag direction to put the text at the right place (left of
// current drag point if dragging right to left, for example).
// What is the distance between the rectangle line at current drag point and
// the text itself.
int pixels_away_from_line = 15;
double x_axis_coordinate;
double x_axis_pixel_coordinate;
if(static_cast(m_context.m_dragDirections) &
static_cast(DragDirections::RIGHT_TO_LEFT))
{
QPointF left_most_point;
if(!m_context.mpa_integrationScope->getLeftMostPoint(left_most_point))
qFatal("Failed to get left most point.");
x_axis_coordinate = left_most_point.x();
pixels_away_from_line *= -1;
}
else
{
QPointF right_most_point;
if(!m_context.mpa_integrationScope->getRightMostPoint(right_most_point))
qFatal("Failed to get right most point.");
x_axis_coordinate = right_most_point.x();
}
x_axis_pixel_coordinate = xAxis->coordToPixel(x_axis_coordinate);
double x_axis_modified_pixel_coordinate =
x_axis_pixel_coordinate + pixels_away_from_line;
// Set aside a point instance to store the pixel coordinates of the text.
QPointF pixel_coordinates;
pixel_coordinates.setX(x_axis_modified_pixel_coordinate);
pixel_coordinates.setY(y_axis_center_position);
// Now convert back to graph coordinates.
QPointF graph_coordinates(xAxis->pixelToCoord(pixel_coordinates.x()),
yAxis->pixelToCoord(pixel_coordinates.y()));
mp_yDeltaTextItem->position->setCoords(graph_coordinates.x(),
y_axis_center_position);
int decimals = Utils::zeroDecimalsInValue(height) + 3;
QString label_text = QString("full y span %1 -- y drag delta %2")
.arg(height, 0, 'f', decimals)
.arg(fabs(m_context.m_yDelta), 0, 'f', decimals);
mp_yDeltaTextItem->setText(label_text);
mp_yDeltaTextItem->setFont(QFont(font().family(), 9));
mp_yDeltaTextItem->setVisible(true);
mp_yDeltaTextItem->setRotation(90);
// Set the boolean to true so that derived widgets know that something is
// being measured, and they can act accordingly, for example by computing
// deconvolutions in a mass spectrum.
m_context.m_isMeasuringDistance = true;
replot();
// Let the caller know that we were measuring something.
emit xAxisMeasurementSignal(m_context, true);
}
void
BasePlotWidget::calculateDragDeltas()
{
// We compute signed differentials. If the user does not want the sign,
// fabs(double) is their friend.
// Compute the xAxis differential:
m_context.m_xDelta =
m_context.m_currentDragPoint.x() - m_context.m_startDragPoint.x();
// Same with the Y-axis range:
m_context.m_yDelta =
m_context.m_currentDragPoint.y() - m_context.m_startDragPoint.y();
return;
}
bool
BasePlotWidget::isVerticalDisplacementAboveThreshold()
{
// First get the height of the plot.
double plotHeight = yAxis->range().upper - yAxis->range().lower;
double heightDiff =
fabs(m_context.m_startDragPoint.y() - m_context.m_currentDragPoint.y());
double heightDiffRatio = (heightDiff / plotHeight) * 100;
if(heightDiffRatio > 10)
{
return true;
}
return false;
}
void
BasePlotWidget::updateIntegrationScope(bool for_integration)
{
// if(for_integration)
// qDebug() << "for_integration:" << for_integration;
// By essence, the one-dimension IntegrationScope is characterized
// by the left-most point and the width. Using these two data bits
// it is possible to compute the x value of the right-most point.
double x_range_start =
std::min(m_context.m_currentDragPoint.x(), m_context.m_startDragPoint.x());
double x_range_end =
std::max(m_context.m_currentDragPoint.x(), m_context.m_startDragPoint.x());
// qDebug() << "x_range_start:" << x_range_start << "-" << "x_range_end:" <<
// x_range_end;
double y_position = m_context.m_startDragPoint.y();
m_context.updateIntegrationScope();
// Top line
mp_selectionRectangeLine1->start->setCoords(
QPointF(x_range_start, y_position));
mp_selectionRectangeLine1->end->setCoords(QPointF(x_range_end, y_position));
// Only if we are drawing a selection rectangle for integration, do we set
// arrow heads to the line.
if(for_integration)
{
mp_selectionRectangeLine1->setHead(QCPLineEnding::esSpikeArrow);
mp_selectionRectangeLine1->setTail(QCPLineEnding::esSpikeArrow);
}
else
{
mp_selectionRectangeLine1->setHead(QCPLineEnding::esNone);
mp_selectionRectangeLine1->setTail(QCPLineEnding::esNone);
}
mp_selectionRectangeLine1->setVisible(true);
// Right line: does not exist, start and end are the same end point of the
// top line.
mp_selectionRectangeLine2->start->setCoords(QPointF(x_range_end, y_position));
mp_selectionRectangeLine2->end->setCoords(QPointF(x_range_end, y_position));
mp_selectionRectangeLine2->setVisible(false);
// Bottom line: identical to the top line, but invisible
mp_selectionRectangeLine3->start->setCoords(
QPointF(x_range_start, y_position));
mp_selectionRectangeLine3->end->setCoords(QPointF(x_range_end, y_position));
mp_selectionRectangeLine3->setVisible(false);
// Left line: does not exist: start and end are the same end point of the
// top line.
mp_selectionRectangeLine4->start->setCoords(QPointF(x_range_end, y_position));
mp_selectionRectangeLine4->end->setCoords(QPointF(x_range_end, y_position));
mp_selectionRectangeLine4->setVisible(false);
}
void
BasePlotWidget::updateIntegrationScopeRect(bool for_integration)
{
// qDebug();
// if(for_integration)
// qDebug() << "for_integration:" << for_integration;
// We are handling a conventional rectangle. Just create four points
// from top left to bottom right. But we want the top left point to be
// effectively the top left point and the bottom point to be the bottom
// point. So we need to try all four direction combinations, left to right
// or converse versus top to bottom or converse.
m_context.updateIntegrationScopeRect();
// Now that the integration scope has been updated as a rectangle,
// use these newly set data to actually draw the integration
// scope lines.
QPointF bottom_left_point;
if(!m_context.mpa_integrationScope->getPoint(bottom_left_point))
qFatal("Failed to get point.");
// qDebug() << "Starting point is left bottom point:" << bottom_left_point;
double width;
if(!m_context.mpa_integrationScope->getWidth(width))
qFatal("Failed to get width.");
// qDebug() << "Width:" << width;
double height;
if(!m_context.mpa_integrationScope->getHeight(height))
qFatal("Failed to get height.");
// qDebug() << "Height:" << height;
QPointF bottom_right_point(bottom_left_point.x() + width,
bottom_left_point.y());
// qDebug() << "bottom_right_point:" << bottom_right_point;
QPointF top_right_point(bottom_left_point.x() + width,
bottom_left_point.y() + height);
// qDebug() << "top_right_point:" << top_right_point;
QPointF top_left_point(bottom_left_point.x(), bottom_left_point.y() + height);
// qDebug() << "top_left_point:" << top_left_point;
// Start by drawing the bottom line because the IntegrationScopeRect has the
// left bottom point and the width and the height to fully characterize it.
// Bottom line (left to right)
mp_selectionRectangeLine3->start->setCoords(bottom_left_point);
mp_selectionRectangeLine3->end->setCoords(bottom_right_point);
mp_selectionRectangeLine3->setVisible(true);
// Right line (bottom to top)
mp_selectionRectangeLine2->start->setCoords(bottom_right_point);
mp_selectionRectangeLine2->end->setCoords(top_right_point);
mp_selectionRectangeLine2->setVisible(true);
// Top line (right to left)
mp_selectionRectangeLine1->start->setCoords(top_right_point);
mp_selectionRectangeLine1->end->setCoords(top_left_point);
mp_selectionRectangeLine1->setVisible(true);
// Left line (top to bottom)
mp_selectionRectangeLine4->start->setCoords(top_left_point);
mp_selectionRectangeLine4->end->setCoords(bottom_left_point);
mp_selectionRectangeLine4->setVisible(true);
// Only if we are drawing a selection rectangle for integration, do we
// set arrow heads to the line.
if(for_integration)
{
mp_selectionRectangeLine1->setHead(QCPLineEnding::esSpikeArrow);
mp_selectionRectangeLine1->setTail(QCPLineEnding::esSpikeArrow);
}
else
{
mp_selectionRectangeLine1->setHead(QCPLineEnding::esNone);
mp_selectionRectangeLine1->setTail(QCPLineEnding::esNone);
}
}
void
BasePlotWidget::updateIntegrationScopeHorizontalRhomb(bool for_integration)
{
// We are handling a rhomboid scope, that is, a rectangle that
// is tilted either to the left or to the right.
// There are two kinds of rhomboid integration scopes: horizontal and
// vertical.
/*
* +----------+
* | |
* | |
* | |
* | |
* | |
* | |
* | |
* +----------+
* ----width---
*/
// As visible here, the fixed size of the rhomboid (using the S key in the
// plot widget) is the *horizontal* side (this is the plot context's
// m_integrationScopeRhombWidth).
IntegrationScopeFeatures scope_features;
// Top horizontal line
QPointF point_1;
scope_features = m_context.mpa_integrationScope->getLeftMostTopPoint(point_1);
// When the user rotates the horizontal rhomboid, at some point, if the
// current drag point has the same y axis value as the start drag point, then
// we say that the rhomboid is flattened on the x axis. In this case, we do
// not draw anything as this is a purely unusable situation.
if(scope_features & IntegrationScopeFeatures::FLAT_ON_X_AXIS)
{
// qDebug() << "The horizontal rhomboid is flattened on the x axis.";
mp_selectionRectangeLine1->setVisible(false);
mp_selectionRectangeLine2->setVisible(false);
mp_selectionRectangeLine3->setVisible(false);
mp_selectionRectangeLine4->setVisible(false);
return;
}
if(scope_features & IntegrationScopeFeatures::RHOMBOID_VERTICAL)
qFatal("The rhomboid should be horizontal!");
// At this point we can draw the rhomboid fine.
if(!m_context.mpa_integrationScope->getLeftMostTopPoint(point_1))
qFatal("Failed to getLeftMostTopPoint.");
QPointF point_2;
if(!m_context.mpa_integrationScope->getRightMostTopPoint(point_2))
qFatal("Failed to getRightMostTopPoint.");
// qDebug() << "For top line, two points:" << point_1 << "--" << point_2;
mp_selectionRectangeLine1->start->setCoords(point_1);
mp_selectionRectangeLine1->end->setCoords(point_2);
// Only if we are drawing a selection rectangle for integration, do we set
// arrow heads to the line.
if(for_integration)
{
mp_selectionRectangeLine1->setHead(QCPLineEnding::esSpikeArrow);
mp_selectionRectangeLine1->setTail(QCPLineEnding::esSpikeArrow);
}
else
{
mp_selectionRectangeLine1->setHead(QCPLineEnding::esNone);
mp_selectionRectangeLine1->setTail(QCPLineEnding::esNone);
}
mp_selectionRectangeLine1->setVisible(true);
// Right line
if(!m_context.mpa_integrationScope->getRightMostBottomPoint(point_1))
qFatal("Failed to getRightMostBottomPoint.");
mp_selectionRectangeLine2->start->setCoords(point_2);
mp_selectionRectangeLine2->end->setCoords(point_1);
mp_selectionRectangeLine2->setVisible(true);
// qDebug() << "For right line, two points:" << point_2 << "--" << point_1;
// Bottom horizontal line
if(!m_context.mpa_integrationScope->getLeftMostBottomPoint(point_2))
qFatal("Failed to getLeftMostBottomPoint.");
mp_selectionRectangeLine3->start->setCoords(point_1);
mp_selectionRectangeLine3->end->setCoords(point_2);
mp_selectionRectangeLine3->setVisible(true);
// qDebug() << "For bottom line, two points:" << point_1 << "--" << point_2;
// Left line
if(!m_context.mpa_integrationScope->getLeftMostTopPoint(point_1))
qFatal("Failed to getLeftMostTopPoint.");
mp_selectionRectangeLine4->end->setCoords(point_2);
mp_selectionRectangeLine4->start->setCoords(point_1);
mp_selectionRectangeLine4->setVisible(true);
// qDebug() << "For left line, two points:" << point_2 << "--" << point_1;
}
void
BasePlotWidget::updateIntegrationScopeVerticalRhomb(bool for_integration)
{
// We are handling a rhomboid scope, that is, a rectangle that
// is tilted either to the left or to the right.
// There are two kinds of rhomboid integration scopes: horizontal and
// vertical.
/*
* +3
* . |
* . |
* . |
* . +2
* . .
* . .
* . .
* 4+ .
* | | .
* height | | .
* | | .
* 1+
*
*/
// As visible here, the fixed size of the rhomboid (using the S key in the
// plot widget) is the *vertical* side (this is the plot context's
// m_integrationScopeRhombHeight).
IntegrationScopeFeatures scope_features;
// Left vertical line
QPointF point_1;
scope_features = m_context.mpa_integrationScope->getLeftMostTopPoint(point_1);
// When the user rotates the vertical rhomboid, at some point, if the current
// drag point is on the same x axis value as the start drag point, then we say
// that the rhomboid is flattened on the y axis. In this case, we do not draw
// anything as this is a purely unusable situation.
if(scope_features & IntegrationScopeFeatures::FLAT_ON_Y_AXIS)
{
// qDebug() << "The vertical rhomboid is flattened on the y axis.";
mp_selectionRectangeLine1->setVisible(false);
mp_selectionRectangeLine2->setVisible(false);
mp_selectionRectangeLine3->setVisible(false);
mp_selectionRectangeLine4->setVisible(false);
return;
}
if(scope_features & IntegrationScopeFeatures::RHOMBOID_HORIZONTAL)
qFatal("The rhomboid should be vertical!");
// At this point we can draw the rhomboid fine.
QPointF point_2;
if(!m_context.mpa_integrationScope->getLeftMostBottomPoint(point_2))
qFatal("Failed to getLeftMostBottomPoint.");
// qDebug() << "For left vertical line, two points:" << point_1 << "--"
// << point_2;
mp_selectionRectangeLine1->start->setCoords(point_1);
mp_selectionRectangeLine1->end->setCoords(point_2);
// Only if we are drawing a selection rectangle for integration, do we set
// arrow heads to the line.
if(for_integration)
{
mp_selectionRectangeLine1->setHead(QCPLineEnding::esSpikeArrow);
mp_selectionRectangeLine1->setTail(QCPLineEnding::esSpikeArrow);
}
else
{
mp_selectionRectangeLine1->setHead(QCPLineEnding::esNone);
mp_selectionRectangeLine1->setTail(QCPLineEnding::esNone);
}
mp_selectionRectangeLine1->setVisible(true);
// Lower oblique line
if(!m_context.mpa_integrationScope->getRightMostBottomPoint(point_1))
qFatal("Failed to getRightMostBottomPoint.");
mp_selectionRectangeLine2->start->setCoords(point_2);
mp_selectionRectangeLine2->end->setCoords(point_1);
mp_selectionRectangeLine2->setVisible(true);
// qDebug() << "For lower oblique line, two points:" << point_2 << "--"
// << point_1;
// Right vertical line
if(!m_context.mpa_integrationScope->getRightMostTopPoint(point_2))
qFatal("Failed to getRightMostTopPoint.");
mp_selectionRectangeLine3->start->setCoords(point_1);
mp_selectionRectangeLine3->end->setCoords(point_2);
mp_selectionRectangeLine3->setVisible(true);
// qDebug() << "For right vertical line, two points:" << point_1 << "--"
// << point_2;
// Upper oblique line
if(!m_context.mpa_integrationScope->getLeftMostTopPoint(point_1))
qFatal("Failed to get the LeftMostTopPoint.");
mp_selectionRectangeLine4->end->setCoords(point_2);
mp_selectionRectangeLine4->start->setCoords(point_1);
mp_selectionRectangeLine4->setVisible(true);
// qDebug() << "For upper oblique line, two points:" << point_2 << "--"
// << point_1;
}
void
BasePlotWidget::updateIntegrationScopeRhomb(bool for_integration)
{
// qDebug();
// if(for_integration)
// qDebug() << "for_integration:" << for_integration;
// We are handling a skewed rectangle (rhomboid), that is a rectangle that
// is tilted either to the left or to the right.
// There are two kinds of rhomboid integration scopes:
/*
4+----------+3
| |
| |
| |
| |
| |
| |
| |
1+----------+2
----width---
*/
// As visible here, the fixed size of the rhomboid (using the S key in the
// plot widget) is the *horizontal* side (this is the plot context's
// m_integrationScopeRhombWidth).
// and
/*
* +3
* . |
* . |
* . |
* . +2
* . .
* . .
* . .
* 4+ .
* | | .
* height | | .
* | | .
* 1+
*
*/
// As visible here, the fixed size of the rhomboid (using the S key in the
// plot widget) is the *vertical* side (this is the plot context's
// m_integrationScopeRhombHeight).
// qDebug() << "Before calling updateIntegrationScopeRhomb(), "
// "m_integrationScopeRhombWidth:"
// << m_context.m_integrationScopeRhombWidth
// << "and m_integrationScopeRhombHeight:"
// << m_context.m_integrationScopeRhombHeight;
m_context.updateIntegrationScopeRhomb();
// qDebug() << "After, m_integrationScopeRhombWidth:"
// << m_context.m_integrationScopeRhombWidth
// << "and m_integrationScopeRhombHeight:"
// << m_context.m_integrationScopeRhombHeight;
// Now that the integration scope has been updated as a rhomboid,
// use these newly set data to actually draw the integration
// scope lines.
// We thus need to first establish if we have a horiontal or a vertical
// rhomboid scope. This information is located in
// m_context.m_integrationScopeRhombWidth and
// m_context.m_integrationScopeRhombHeight. If width > 0, height *has to be
// 0*, which indicates a horizontal rhomb.Conversely, if height is > 0, then
// the rhomb is vertical.
if(m_context.m_integrationScopeRhombWidth > 0)
// We are dealing with a horizontal scope.
updateIntegrationScopeHorizontalRhomb(for_integration);
else if(m_context.m_integrationScopeRhombHeight > 0)
// We are dealing with a vertical scope.
updateIntegrationScopeVerticalRhomb(for_integration);
else
qFatal("Cannot be both the width or height of rhomboid scope be 0.");
}
void
BasePlotWidget::updateIntegrationScopeDrawing(bool as_line_segment,
bool for_integration)
{
// qDebug() << "as_line_segment:" << as_line_segment;
// qDebug() << "for_integration:" << for_integration;
// We now need to construct the selection rectangle, either for zoom or for
// integration.
// There are two situations :
//
// 1. if the rectangle should look like a line segment
//
// 2. if the rectangle should actually look like a rectangle. In this case,
// there are two sub-situations:
//
// a. if the Alt modifier key is down, then the rectangle is rhomboid.
//
// b. otherwise the rectangle is conventional.
if(as_line_segment)
{
// qDebug() << "Updating the integration scope to an IntegrationScope.";
updateIntegrationScope(for_integration);
}
else
{
if(!(m_context.m_keyboardModifiers & Qt::AltModifier))
{
// qDebug()
// << "Updating the integration scope to an IntegrationScopeRect.";
updateIntegrationScopeRect(for_integration);
}
else if(m_context.m_keyboardModifiers & Qt::AltModifier)
{
// The user might use the Alt modifier, but if no rhomboid side has
// been defined using the S key, then we do not do any rhomboid
// selection because we do not know the side size of that rhomboid.
if(!m_context.m_integrationScopeRhombHeight &&
!m_context.m_integrationScopeRhombWidth)
updateIntegrationScopeRect(for_integration);
else
// qDebug()
// << "Updating the integration scope to an
// IntegrationScopeRhomb.";
updateIntegrationScopeRhomb(for_integration);
}
}
// Depending on the kind of IntegrationScope, (normal, rect or rhomb)
// we have to measure things in different ways. We now set in the context
// a number of parameters that will be used by its user.
QPointF point;
double height;
std::vector points;
// Integration scope values are sorted:
// Line scope: point is left and width is right.x - left.x
// Rect scope: point is bottom left.
// Rhomb scope: points 1->4 are bottom left->bottom right->top right->top left
// width is 2.x - 1.x.
if(m_context.mpa_integrationScope->getPoints(points))
{
// We have defined a IntegrationScopeRhomb.
if(!m_context.mpa_integrationScope->getLeftMostPoint(point))
qFatal("Failed to get LeftMost point.");
m_context.m_xRegionRangeStart = point.x();
if(!m_context.mpa_integrationScope->getRightMostPoint(point))
qFatal("Failed to get RightMost point.");
m_context.m_xRegionRangeStop = point.x();
}
else if(m_context.mpa_integrationScope->getHeight(height))
{
// We have defined a IntegrationScopeRect.
if(!m_context.mpa_integrationScope->getPoint(point))
qFatal("Failed to get point.");
m_context.m_xRegionRangeStart = point.x();
double width;
if(!m_context.mpa_integrationScope->getWidth(width))
qFatal("Failed to get width.");
m_context.m_xRegionRangeStop = m_context.m_xRegionRangeStart + width;
m_context.m_yRegionRangeStart = point.y();
m_context.m_yRegionRangeStop = point.y() + height;
}
else
{
// We have defined a IntegrationScope.
if(!m_context.mpa_integrationScope->getPoint(point))
qFatal("Failed to get point.");
m_context.m_xRegionRangeStart = point.x();
double width;
if(!m_context.mpa_integrationScope->getWidth(width))
qFatal("Failed to get width.");
m_context.m_xRegionRangeStop = m_context.m_xRegionRangeStart + width;
}
// At this point, draw the text describing the widths.
// We want the x-delta on the bottom of the rectangle, inside it
// and the y-delta on the vertical side of the rectangle, inside it.
// Draw the selection width text
drawXScopeSpanFeatures();
}
void
BasePlotWidget::hideSelectionRectangle(bool reset_values)
{
mp_selectionRectangeLine1->setVisible(false);
mp_selectionRectangeLine2->setVisible(false);
mp_selectionRectangeLine3->setVisible(false);
mp_selectionRectangeLine4->setVisible(false);
if(reset_values)
{
resetSelectionRectangle();
}
}
void
BasePlotWidget::resetSelectionRectangle()
{
static_cast(m_context.mpa_integrationScope)->reset();
}
SelectionDrawingLines
BasePlotWidget::whatIsVisibleOfTheSelectionRectangle()
{
// There are four lines that make the selection polygon. We want to know
// which lines are visible.
int current_selection_polygon =
static_cast(SelectionDrawingLines::NOT_SET);
if(mp_selectionRectangeLine1->visible())
{
current_selection_polygon |=
static_cast(SelectionDrawingLines::TOP_LINE);
// qDebug() << "current_selection_polygon:" <<
// current_selection_polygon;
}
if(mp_selectionRectangeLine2->visible())
{
current_selection_polygon |=
static_cast(SelectionDrawingLines::RIGHT_LINE);
// qDebug() << "current_selection_polygon:" <<
// current_selection_polygon;
}
if(mp_selectionRectangeLine3->visible())
{
current_selection_polygon |=
static_cast(SelectionDrawingLines::BOTTOM_LINE);
// qDebug() << "current_selection_polygon:" <<
// current_selection_polygon;
}
if(mp_selectionRectangeLine4->visible())
{
current_selection_polygon |=
static_cast(SelectionDrawingLines::LEFT_LINE);
// qDebug() << "current_selection_polygon:" <<
// current_selection_polygon;
}
// qDebug() << "returning visibility:" << current_selection_polygon;
return static_cast(current_selection_polygon);
}
bool
BasePlotWidget::isSelectionRectangleVisible()
{
// Sanity check
int check = 0;
check += mp_selectionRectangeLine1->visible();
check += mp_selectionRectangeLine2->visible();
check += mp_selectionRectangeLine3->visible();
check += mp_selectionRectangeLine4->visible();
if(check > 0)
return true;
return false;
}
void
BasePlotWidget::setFocus()
{
// qDebug() << "Setting focus to the QCustomPlot:" << this;
QCustomPlot::setFocus();
// qDebug() << "Emitting setFocusSignal().";
emit setFocusSignal();
}
//! Redraw the background of the \p focusedPlotWidget plot widget.
void
BasePlotWidget::redrawPlotBackground(QWidget *focusedPlotWidget)
{
if(focusedPlotWidget == nullptr)
throw ExceptionNotPossible(
"baseplotwidget.cpp @ redrawPlotBackground(QWidget *focusedPlotWidget "
"-- "
"ERROR focusedPlotWidget cannot be nullptr.");
if(dynamic_cast(this) != focusedPlotWidget)
{
// The focused widget is not *this widget. We should make sure that
// we were not the one that had the focus, because in this case we
// need to redraw an unfocused background.
axisRect()->setBackground(m_unfocusedBrush);
}
else
{
axisRect()->setBackground(m_focusedBrush);
}
replot();
}
void
BasePlotWidget::updateContextXandYAxisRanges()
{
m_context.m_xRange = QCPRange(xAxis->range().lower, xAxis->range().upper);
m_context.m_yRange = QCPRange(yAxis->range().lower, yAxis->range().upper);
// qDebug() << "The new updated context: " << m_context.toString();
}
const BasePlotContext &
BasePlotWidget::getContext() const
{
return m_context;
}
} // namespace pappso
libpappsomspp-0.11.27/src/pappsomspp/gui/plotwidget/baseplotwidget.h 000664 001750 001750 00000031553 15250226472 027124 0 ustar 00rusconi rusconi 000000 000000 /* This code comes right from the msXpertSuite software project.
*
* msXpertSuite - mass spectrometry software suite
* -----------------------------------------------
* Copyright(C) 2009,...,2018 Filippo Rusconi
*
* http://www.msxpertsuite.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* END software license
*/
#pragma once
/////////////////////// StdLib includes
#include
/////////////////////// Qt includes
#include
#include
#include
#include
#include
#include
/////////////////////// QCustomPlot
#include
/////////////////////// Local includes
#include "pappsomspp/export-import-config.h"
#include "../../core/types.h"
#include "pappsomspp/core/processing/combiners/selectionpolygon.h"
#include "baseplotcontext.h"
namespace pappso
{
enum class SelectionDrawingLines
{
NOT_SET = 0x0000,
TOP_LINE = 1 << 0,
BOTTOM_LINE = 1 << 1,
HORIZONTAL_LINES = (TOP_LINE | BOTTOM_LINE),
RIGHT_LINE = 1 << 2,
LEFT_LINE = 1 << 3,
VERTICAL_LINES = (RIGHT_LINE | LEFT_LINE),
FULL_POLYGON = (HORIZONTAL_LINES | VERTICAL_LINES)
};
enum class RangeType
{
outermost = 1,
innermost = 2,
};
class BasePlotWidget;
typedef std::shared_ptr BasePlotWidgetSPtr;
typedef std::shared_ptr BasePlotWidgetCstSPtr;
class PMSPP_LIB_DECL BasePlotWidget : public QCustomPlot
{
Q_OBJECT
public:
explicit BasePlotWidget(QWidget *parent);
explicit BasePlotWidget(QWidget *parent,
const QString &x_axis_label,
const QString &y_axis_label);
virtual ~BasePlotWidget();
virtual bool setupWidget();
virtual void setPen(const QPen &pen);
virtual const QPen &getPen() const;
virtual void setPlottingColor(QCPAbstractPlottable *plottable_p,
const QColor &new_color);
virtual void setPlottingColor(int index, const QColor &new_color);
virtual QColor getPlottingColor(QCPAbstractPlottable *plottable_p) const;
virtual QColor getPlottingColor(int index = 0) const;
virtual void setAxisLabelX(const QString &label);
virtual void setAxisLabelY(const QString &label);
// AXES RANGE HISTORY-related functions
virtual void resetAxesRangeHistory();
virtual void updateAxesRangeHistory();
virtual void restorePreviousAxesRangeHistory();
virtual void restoreAxesRangeHistory(std::size_t index);
// AXES RANGE HISTORY-related functions
/// KEYBOARD-related EVENTS
virtual void keyPressEvent(QKeyEvent *event);
virtual void keyReleaseEvent(QKeyEvent *event);
virtual void spaceKeyReleaseEvent(QKeyEvent *event);
virtual void directionKeyPressEvent(QKeyEvent *event);
virtual void directionKeyReleaseEvent(QKeyEvent *event);
virtual void mousePseudoButtonKeyPressEvent(QKeyEvent *event);
virtual void mousePseudoButtonKeyReleaseEvent(QKeyEvent *event);
/// KEYBOARD-related EVENTS
/// MOUSE-related EVENTS
virtual void mousePressHandler(QMouseEvent *event);
virtual void mouseReleaseHandler(QMouseEvent *event);
virtual void mouseReleaseHandlerLeftButton(QMouseEvent *event);
virtual void mouseReleaseHandlerRightButton(QMouseEvent *event);
virtual void mouseWheelHandler(QWheelEvent *event);
virtual void mouseMoveHandler(QMouseEvent *event);
virtual void mouseMoveHandlerNotDraggingCursor(QMouseEvent *event);
virtual void mouseMoveHandlerDraggingCursor(QMouseEvent *event);
virtual void mouseMoveHandlerLeftButtonDraggingCursor(QMouseEvent *event);
virtual void mouseMoveHandlerRightButtonDraggingCursor(QMouseEvent *event);
virtual void axisDoubleClickHandler(QCPAxis *axis,
QCPAxis::SelectablePart part,
QMouseEvent *event);
bool isClickOntoXAxis(const QPointF &mousePoint);
bool isClickOntoYAxis(const QPointF &mousePoint);
/// MOUSE-related EVENTS
/// MOUSE MOVEMENTS mouse/keyboard-triggered
int dragDirection();
virtual void moveMouseCursorGraphCoordToGlobal(QPointF plot_coordinates);
virtual void moveMouseCursorPixelCoordToGlobal(QPointF local_coordinates);
virtual void horizontalMoveMouseCursorCountPixels(int pixel_count);
virtual QPointF horizontalGetGraphCoordNewPointCountPixels(int pixel_count);
virtual void verticalMoveMouseCursorCountPixels(int pixel_count);
virtual QPointF verticalGetGraphCoordNewPointCountPixels(int pixel_count);
/// MOUSE MOVEMENTS mouse/keyboard-triggered
/// RANGE-related functions
virtual QCPRange getRangeX(bool &found_range, int index) const;
virtual QCPRange getRangeY(bool &found_range, int index) const;
QCPRange getRange(Enums::Axis axis, RangeType range_type, bool &found_range) const;
virtual QCPRange getInnermostRangeX(bool &found_range) const;
virtual QCPRange getOutermostRangeX(bool &found_range) const;
virtual QCPRange getInnermostRangeY(bool &found_range) const;
virtual QCPRange getOutermostRangeY(bool &found_range) const;
void yMinMaxOnXAxisCurrentRange(double &min,
double &max,
QCPAbstractPlottable *plottable_p = nullptr);
void yMinMaxOnXAxisCurrentRange(double &min, double &max, int index);
/// RANGE-related functions
/// PLOTTING / REPLOTTING functions
virtual void axisRescale();
virtual void axisReframe();
virtual void axisZoom();
virtual void axisPan();
virtual void
replotWithAxesRanges(QCPRange xAxisRange, QCPRange yAxisRange, Enums::Axis axis);
virtual void replotWithAxisRangeX(double lower, double upper);
virtual void replotWithAxisRangeY(double lower, double upper);
/// PLOTTING / REPLOTTING functions
/// PLOT ITEMS : TRACER TEXT ITEMS...
virtual void hideAllPlotItems();
virtual void showTracers();
virtual void hideTracers();
virtual void drawXScopeSpanFeatures();
virtual void drawYScopeSpanFeatures();
virtual void calculateDragDeltas();
virtual bool isVerticalDisplacementAboveThreshold();
virtual void
drawSelectionRectangleAndPrepareZoom(bool as_line_segment = false,
bool for_integration = false);
virtual void updateIntegrationScopeDrawing(bool as_line_segment = false,
bool for_integration = false);
virtual void resetSelectionRectangle();
virtual void hideSelectionRectangle(bool reset_values = false);
virtual bool isSelectionRectangleVisible();
virtual SelectionDrawingLines whatIsVisibleOfTheSelectionRectangle();
/// PLOT ITEMS : TRACER TEXT ITEMS...
virtual void setFocus();
virtual void redrawPlotBackground(QWidget *focusedPlotWidget);
virtual void updateContextXandYAxisRanges();
virtual const BasePlotContext &getContext() const;
////////////////////////// SIGNALS /////////////////////////////////
////////////////////////// SIGNALS /////////////////////////////////
////////////////////////// SIGNALS /////////////////////////////////
signals:
void setFocusSignal();
void lastCursorHoveredPointSignal(const QPointF &pointf);
void plotRangesChangedSignal(QMouseEvent *event, const pappso::BasePlotContext &context);
void plotRangesChangedWheelEventSignal(QWheelEvent *event,
const pappso::BasePlotContext &context);
void xAxisMeasurementSignal(const pappso::BasePlotContext &context, bool with_delta);
void keyPressEventSignal(QKeyEvent *event, const pappso::BasePlotContext &context);
void keyReleaseEventSignal(QKeyEvent *event, const pappso::BasePlotContext &context);
void mousePressEventSignal(QMouseEvent *event, const pappso::BasePlotContext &context);
void mouseReleaseEventSignal(QMouseEvent *event, const pappso::BasePlotContext &context);
void mouseMoveDraggingCursorSignal(QMouseEvent *event, const pappso::BasePlotContext &context);
void mouseWheelEventSignal(QWheelEvent *event, const pappso::BasePlotContext &context);
void plottableSelectionChangedSignal(QCPAbstractPlottable *plottable_p,
bool selected);
void integrationRequestedSignal(const BasePlotContext &context);
void plottableDestructionRequestedSignal(BasePlotWidget *base_plot_widget_p,
QCPAbstractPlottable *plottable_p,
const pappso::BasePlotContext &context);
void beforeReplotSignal();
void afterLayoutSignal();
void afterReplotSignal();
protected:
//! Name of the plot widget.
QString m_name = "NOT_SET";
//! Description of the plot widget.
QString m_desc = "NOT_SET";
//! The name of the data file from which the mass data were read.
QString m_fileName;
QString m_axisLabelX;
QString m_axisLabelY;
BasePlotContext m_context;
int m_leftMousePseudoButtonKey = Qt::Key_Less;
int m_rightMousePseudoButtonKey = Qt::Key_Greater;
//! Rectangle defining the borders of zoomed-in/out data.
// QCPItemRect *mp_zoomRectItem = nullptr;
// The four lines that are needed to craft the selection rectangle.
QCPItemLine *mp_selectionRectangeLine1 = nullptr;
QCPItemLine *mp_selectionRectangeLine2 = nullptr;
QCPItemLine *mp_selectionRectangeLine3 = nullptr;
QCPItemLine *mp_selectionRectangeLine4 = nullptr;
//! Text describing the x-axis delta value during a drag operation.
QCPItemText *mp_xDeltaTextItem = nullptr;
QCPItemText *mp_yDeltaTextItem = nullptr;
//! Tells if the tracers should be visible.
bool m_shouldTracersBeVisible = true;
//! Horizontal position tracer
QCPItemLine *mp_hPosTracerItem = nullptr;
//! Vertical position tracer
QCPItemLine *mp_vPosTracerItem = nullptr;
//! Vertical selection start tracer (typically in green).
QCPItemLine *mp_vStartTracerItem = nullptr;
//! Vertical selection end tracer (typically in red).
QCPItemLine *mp_vEndTracerItem = nullptr /*only vertical*/;
//! Index of the last axis range history item.
/*!
Each time the user modifies the ranges (x/y axis) during panning or
zooming of the graph, the new axis ranges are stored in a axis ranges
history list. This index allows to point to the last range of that
history.
*/
std::size_t m_lastAxisRangeHistoryIndex = 0;
//! List of x axis ranges occurring during the panning zooming actions.
std::vector m_xAxisRangeHistory;
//! List of y axis ranges occurring during the panning zooming actions.
std::vector m_yAxisRangeHistory;
//! How many mouse move events must be skipped */
/*!
when the data are so massive that the graph panning becomes sluggish. By
default, the value is 10 events to be skipped before accounting one. The
"fat data" mouse movement handler mechanism is actuated by using a
keyboard key combination. There is no automatic shift between normal
processing and "fat data" processing.
*/
int m_mouseMoveHandlerSkipAmount = 10;
//! Counter to handle the "fat data" mouse move event handling.
/*!
\sa m_mouseMoveHandlerSkipAmount.
*/
int m_mouseMoveHandlerSkipCount = 0;
// QColor m_unfocusedColor = QColor(Qt::lightGray);
// QColor m_unfocusedColor = QColor(230, 230, 230, 255);
//! Color used for the background of unfocused plot.
QColor m_unfocusedColor = QColor("lightgray");
//! Color used for the background of unfocused plot.
QBrush m_unfocusedBrush = QBrush(m_unfocusedColor);
//! Color used for the background of focused plot.
QColor m_focusedColor = QColor(Qt::transparent);
//! Color used for the background of focused plot.
QBrush m_focusedBrush = QBrush(m_focusedColor);
//! Pen used to draw the graph and textual elements in the plot widget.
QPen m_pen;
virtual void createAllAncillaryItems();
virtual void updateIntegrationScope(bool for_integration = false);
virtual void updateIntegrationScopeRect(bool for_integration = false);
virtual void updateIntegrationScopeHorizontalRhomb(bool for_integration = false);
virtual void updateIntegrationScopeVerticalRhomb(bool for_integration = false);
virtual void updateIntegrationScopeRhomb(bool for_integration = false);
virtual QString allLayerNamesToString() const;
virtual QString layerableLayerName(QCPLayerable *layerable_p) const;
virtual int layerableLayerIndex(QCPLayerable *layerable_p) const;
};
} // namespace pappso
Q_DECLARE_METATYPE(pappso::BasePlotContext);
extern int basePlotContextMetaTypeId;
Q_DECLARE_METATYPE(pappso::BasePlotContext *);
extern int basePlotContextPtrMetaTypeId;
libpappsomspp-0.11.27/src/pappsomspp/gui/plotwidget/basetraceplotwidget.cpp 000664 001750 001750 00000077642 15250226472 030507 0 ustar 00rusconi rusconi 000000 000000 /* This code comes right from the msXpertSuite software project.
*
* msXpertSuite - mass spectrometry software suite
* -----------------------------------------------
* Copyright(C) 2009,...,2018 Filippo Rusconi
*
* http://www.msxpertsuite.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* END software license
*/
/////////////////////// StdLib includes
#include
/////////////////////// Qt includes
#include
/////////////////////// Local includes
#include "basetraceplotwidget.h"
#include "pappsomspp/core/exception/exceptionnotpossible.h"
#include "pappsomspp/core/pappsoexception.h"
namespace pappso
{
BaseTracePlotWidget::BaseTracePlotWidget(QWidget *parent)
: BasePlotWidget(parent)
{
// We can afford to call createAllAncillaryItems() in this derived class
// because all the items will have been created *before* the addition of plots
// and then the rendering order will hide them to the viewer, since the
// rendering order is according to the order in which the items have been
// created.
//
// The fact that the ancillary items are created before trace plots is not a
// problem because the trace plots are sparse and do not effectively hide the
// data.
//
// But, in the color map plot widgets, we cannot afford to create the
// ancillary items *before* the plot itself because then, the rendering of the
// plot (created after) would screen off the ancillary items (created before).
//
// So, the createAllAncillaryItems() function needs to be called in the
// derived classes at the most appropriate moment in the setting up of the
// widget.
createAllAncillaryItems();
}
BaseTracePlotWidget::BaseTracePlotWidget(QWidget *parent,
const QString &x_axis_label,
const QString &y_axis_label)
: BasePlotWidget(parent, x_axis_label, y_axis_label)
{
// We can afford to call createAllAncillaryItems() in this derived class
// because all the items will have been created *before* the addition of plots
// and then the rendering order will hide them to the viewer, since the
// rendering order is according to the order in which the items have been
// created.
//
// The fact that the ancillary items are created before trace plots is not a
// problem because the trace plots are sparse and do not effectively hide the
// data.
//
// But, in the color map plot widgets, we cannot afford to create the
// ancillary items *before* the plot itself because then, the rendering of the
// plot (created after) would screen off the ancillary items (created before).
//
// So, the createAllAncillaryItems() function needs to be called in the
// derived classes at the most appropriate moment in the setting up of the
// widget.
createAllAncillaryItems();
}
//! Destruct \c this BaseTracePlotWidget instance.
/*!
The destruction involves clearing the history, deleting all the axis range
history items for x and y axes.
*/
BaseTracePlotWidget::~BaseTracePlotWidget()
{
}
void
BaseTracePlotWidget::setGraphData(int graph_index,
const std::vector &keys,
const std::vector &values)
{
QCPGraph *graph_p = graph(graph_index);
if(graph_p == nullptr)
qFatal("Programming error.");
return setGraphData(graph_p, keys, values);
}
void
BaseTracePlotWidget::setGraphData(QCPGraph *graph_p,
const std::vector &keys,
const std::vector &values)
{
if(graph_p == nullptr)
qFatal("Pointer cannot be nullptr.");
// Version that is now deprecated (20200924)
// graph_p->setData(QVector::fromStdVector(keys),
// QVector::fromStdVector(values));
QVector key_qvector;
QVector value_qvector;
#if 0
// Now replace the graph's data. Note that the data are
// inherently sorted (true below).
// The begin() -- end() ranges constructor did not work as of
// Qt 5.14.2 this day: 20200721
key_qvector =
QVector(keys.begin(),
keys.end());
value_qvector =
QVector(values.begin(),
values.end());
#endif
for(auto &value : keys)
key_qvector.push_back(value);
for(auto &value : values)
value_qvector.push_back(value);
graph_p->setData(key_qvector, value_qvector, true);
graph_p->setPen(m_pen);
rescaleAxes();
resetAxesRangeHistory();
replot();
}
void
BaseTracePlotWidget::clearGraphData(int graph_index)
{
QCPGraph *graph_p = graph(graph_index);
if(graph_p == nullptr)
qFatal("Programming error.");
graph_p->data().clear();
rescaleAxes();
resetAxesRangeHistory();
replot();
}
QCPGraph *
BaseTracePlotWidget::addGraphPlot(const pappso::Trace &trace, const QColor &color)
{
// qDebug();
if(!color.isValid())
throw PappsoException(
QString("The color to be used for the plot graph is invalid."));
// This seems to be unpleasant.
// setFocus();
QCPGraph *graph_p = addGraph();
graph_p->setLayer("plotsLayer");
// Now depracated as of 20200924
// graph_p->setData(QVector::fromStdVector(trace.xValues()),
// QVector::fromStdVector(trace.yValues()));
QVector key_qvector;
QVector value_qvector;
#if 0
// Now replace the graph's data. Note that the data are
// inherently sorted (true below).
// The begin() -- end() ranges constructor did not work as of
// Qt 5.14.2 this day: 20200721
key_qvector =
QVector(trace.xValues().begin(),
.trace.xValues()end());
value_qvector =
QVector(trace.yValues().begin(),
trace.yValues().end());
#endif
for(auto &value : trace.xValues())
{
key_qvector.push_back(value);
}
for(auto &value : trace.yValues())
{
value_qvector.push_back(value);
}
#if 0
qDebug() << "The size of the x values for trace is:" << key_qvector.size()
<< "and for y values is:" << value_qvector.size();
QString text;
for(qsizetype iter = 0; iter < key_qvector.size(); ++iter)
text += QString("(%1,%2)\n")
.arg(key_qvector.at(iter), 0, 'f', 6)
.arg(value_qvector.at(iter), 0, 'f', 6);
qDebug().noquote() << text;
#endif
graph_p->setData(key_qvector, value_qvector, true);
QPen pen = graph_p->pen();
pen.setColor(color);
graph_p->setPen(pen);
// Connect the signal of selection change so that we can re-emit it for the
// widget that is using *this widget.
connect(graph_p,
static_cast(
&QCPAbstractPlottable::selectionChanged),
[this, graph_p]() {
emit plottableSelectionChangedSignal(graph_p, graph_p->selected());
});
// Rescaling the axes is actually unpleasant if there are more than one
// graph in the plot widget and that we are adding one. So only, rescale if
// the number of graphs is == 1, that is we are adding the first one.
if(graphCount() == 1)
{
rescaleAxes();
resetAxesRangeHistory();
}
replot();
return graph_p;
}
QCPBars *
BaseTracePlotWidget::addBarsPlot(const pappso::Trace &bars, const QColor &color)
{
qDebug();
if(!color.isValid())
throw PappsoException(
QString("The color to be used for the plot graph is invalid."));
// This seems to be unpleasant.
// setFocus();
QCPBars *bars_p = new QCPBars(xAxis, yAxis);
bars_p->setWidthType(QCPBars::WidthType::wtPlotCoords);
bars_p->setWidth(0.0005);
bars_p->setLayer("plotsLayer");
// Now depracated as of 20200924
// graph_p->setData(QVector::fromStdVector(trace.xValues()),
// QVector::fromStdVector(trace.yValues()));
std::vector x_values = bars.xValues();
QVector key_qvector;
key_qvector.assign(x_values.begin(), x_values.end());
std::vector y_values = bars.yValues();
QVector value_qvector;
value_qvector.assign(y_values.begin(), y_values.end());
//qDebug() << "The size of the x values for trace is:" << key_qvector.size()
//<< "and for y values is:" << value_qvector.size();
#if 0
qDebug() << "The size of the x values for trace is:" << key_qvector.size()
<< "and for y values is:" << value_qvector.size();
QString text;
for(qsizetype iter = 0; iter < key_qvector.size(); ++iter)
text += QString("(%1,%2)\n")
.arg(key_qvector.at(iter), 0, 'f', 6)
.arg(value_qvector.at(iter), 0, 'f', 6);
qDebug().noquote() << text;
#endif
bars_p->setData(key_qvector, value_qvector, true);
QPen pen = bars_p->pen();
pen.setColor(color);
bars_p->setPen(pen);
// Connect the signal of selection change so that we can re-emit it for the
// widget that is using *this widget.
connect(bars_p,
static_cast(
&QCPAbstractPlottable::selectionChanged),
[this, bars_p]() {
emit plottableSelectionChangedSignal(bars_p, bars_p->selected());
});
// Rescaling the axes is actually unpleasant if there are more than one
// graph in the plot widget and that we are adding one. So only, rescale if
// the number of graphs is == 1, that is we are adding the first one.
if(graphCount() == 1)
{
rescaleAxes();
resetAxesRangeHistory();
}
replot();
return bars_p;
}
//! Find a minimal integration range starting at an existing data point
/*!
If the user clicks onto a plot at a location that is not a true data point,
get a data range that begins at the preceding data point and that ends at
the clicked location point.
*/
bool
BaseTracePlotWidget::findIntegrationLowerRangeForKey(int index,
double key,
QCPRange &range)
{
// Given a key double value, we want to know what is the range that will
// frame correctly the key double value if that key value is not exactly
// the one of a point of the trace.
// First of all get the keys of the graph.
QCPGraph *theGraph = graph(index);
if(theGraph == nullptr)
throw ExceptionNotPossible(
"basetraceplotwidget.cpp @ indIntegrationLowerRangeForKey() -- ERROR "
"theGraph cannot be nullptr.");
// QCPGraphDataContainer is a typedef QCPDataContainer and
// QCPDataContainer< DataType > is a Class Template. So in this context,
// DataType is QCPGraphData.
// QCPGraphData is the data point, that is the (key,value) pair.
QSharedPointer graph_data_container_p =
theGraph->data();
QCPDataRange dataRange = graph_data_container_p->dataRange();
if(!dataRange.isValid())
return false;
if(!dataRange.size())
return false;
if(dataRange.size() > 1)
{
double firstKey = graph_data_container_p->at(dataRange.begin())->key;
double lastKey = graph_data_container_p->at(dataRange.end())->key;
// There is one check to be done: the user might erroneously set the mouse
// cursor beyond the last point of the graph. If that is the case, then
// upper key needs to be that very point. All we need to do is return the
// lower key, that is the pre-last key of the keys list. No need to
// iterate in the keys list.
if(key > lastKey)
{
// No need to search for the key in the keys, just get the lower key
// immediately, that is, the key that is one slot left the last key.
range.lower = graph_data_container_p->at(dataRange.end() - 2)->key;
range.upper = graph_data_container_p->at(dataRange.end() - 1)->key;
return true;
}
// Likewise, if the cursor is set left of the first plot point, then that
// will be the lower range point. All we need is to provide the upper
// range point as the second point of the plot.
if(key < firstKey)
{
range.lower = firstKey;
range.upper = graph_data_container_p->at(dataRange.begin() + 1)->key;
return true;
}
// Finally the generic case where the user point to any point *in* the
// graph.
range.lower =
graph_data_container_p->findBegin(key, /*expandedRange*/ true)->key;
range.upper =
std::prev(graph_data_container_p->findEnd(key, /*expandedRange*/ true))
->key;
return true;
}
return false;
}
std::vector
BaseTracePlotWidget::getValuesX(int graph_index) const
{
std::vector keys;
QCPGraph *graph_p = graph(graph_index);
if(graph_p == nullptr)
qFatal("Programming error.");
QSharedPointer graph_data_container_p =
graph_p->data();
// Iterate in the keys
auto beginIt = graph_data_container_p->begin();
auto endIt = graph_data_container_p->end();
for(auto iter = beginIt; iter != endIt; ++iter)
keys.push_back(iter->key);
return keys;
}
std::vector
BaseTracePlotWidget::getValuesY(int graph_index) const
{
std::vector values;
QCPGraph *graph_p = graph(graph_index);
if(graph_p == nullptr)
qFatal("Programming error.");
QSharedPointer graph_data_container_p =
graph_p->data();
// Iterate in the values
auto beginIt = graph_data_container_p->begin();
auto endIt = graph_data_container_p->end();
for(auto iter = beginIt; iter != endIt; ++iter)
values.push_back(iter->key);
return values;
}
QCPRange
BaseTracePlotWidget::getValueRangeOnKeyRange(QCPAbstractPlottable *plottable_p,
bool &ok)
{
// The X axis range is set. But we want to find for that X axis range the
// min and max Y values. This function is useful when the user asks that
// while changing the X axis range, the trace be always in full scale on the
// Y axis.
QCPRange key_range(xAxis->range().lower, xAxis->range().upper);
if(plottable_p != nullptr)
{
return plottable_p->getValueRange(ok, QCP::SignDomain::sdBoth, key_range);
}
else
{
// How many graphs are currently plotted in this plot widget ?
int graph_count = graphCount();
// Iterate in each graph and get the y max value. Then compare with the
// largest one and update if necessary. Store the pointer to the graph
// that has a larger y value. At the end of the iteration, it will be
// the winner.
double temp_min_value = std::numeric_limits::max();
double temp_max_value = std::numeric_limits::min();
bool found_range = false;
for(int iter = 0; iter < graph_count; ++iter)
{
QCPGraph *plottable_p = graph(iter);
QCPRange value_range =
plottable_p->getValueRange(ok, QCP::SignDomain::sdBoth, key_range);
if(ok)
found_range = true;
if(value_range.lower < temp_min_value)
temp_min_value = value_range.lower;
if(value_range.upper > temp_max_value)
temp_max_value = value_range.upper;
}
// At this point return the range.
ok = found_range;
return QCPRange(temp_min_value, temp_max_value);
}
}
QCPRange
BaseTracePlotWidget::getValueRangeOnKeyRange(int index, bool &ok)
{
// The X axis range is set. But we want to find for that X axis range the
// min and max Y values. This function is useful when the user asks that
// while changing the X axis range, the trace be always in full scale on the
// Y axis.
QCPAbstractPlottable *plottable_p = plottable(index);
if(plottable_p == nullptr)
qFatal("Programming error.");
return getValueRangeOnKeyRange(plottable_p, ok);
}
double
BaseTracePlotWidget::getYatX(double x, QCPGraph *graph_p)
{
if(graph_p == nullptr)
qFatal("Programming error.");
QCPItemTracer *tracer_p = new QCPItemTracer(this);
tracer_p->setGraph(graph_p);
tracer_p->setInterpolating(true);
tracer_p->setGraphKey(x);
tracer_p->updatePosition();
double value = tracer_p->position->value();
tracer_p->setGraph(nullptr);
// Essential to do this because otherwise crash when closing the app.
removeItem(tracer_p);
return value;
}
double
BaseTracePlotWidget::getYatX(double x, int index)
{
QCPGraph *graph_p = graph(index);
if(graph_p == nullptr)
qFatal("Programming error.");
return getYatX(x, graph_p);
}
void
BaseTracePlotWidget::axisDoubleClickHandler(
QCPAxis *axis,
[[maybe_unused]] QCPAxis::SelectablePart part,
QMouseEvent *event)
{
// qDebug();
m_context.m_keyboardModifiers = QGuiApplication::queryKeyboardModifiers();
if(m_context.m_keyboardModifiers & Qt::ControlModifier)
{
// qDebug();
// If the Ctrl modifiers is active, then both axes are to be reset. Also
// the histories are reset also.
rescaleAxes();
resetAxesRangeHistory();
}
else
{
// qDebug();
// Only the axis passed as parameter is to be rescaled.
// Reset the range of that axis to the max view possible, but for the y
// axis check if the Shift keyboard key is pressed. If so the full scale
// should be calculated only on the data in the current x range.
if(axis->orientation() == Qt::Vertical)
{
if(m_context.m_keyboardModifiers & Qt::ShiftModifier)
{
// In this case, we want to make a rescale of the Y axis such
// that it displays full scale the data in the current X axis
// range only.
bool ok = false;
QCPRange value_range = getValueRangeOnKeyRange(nullptr, ok);
yAxis->setRange(value_range);
}
else
axis->rescale();
}
else
axis->rescale();
updateAxesRangeHistory();
event->accept();
}
// The double-click event does not cancel the mouse press event. That is, if
// left-double-clicking, at the end of the operation the button still
// "pressed". We need to remove manually the button from the pressed buttons
// context member.
m_context.m_pressedMouseButtons ^= event->button();
updateContextXandYAxisRanges();
emit plotRangesChangedSignal((QMouseEvent *)nullptr, m_context);
replot();
}
void
BaseTracePlotWidget::axisRescale()
{
double xLower = xAxis->range().lower;
double xUpper = xAxis->range().upper;
// Get the current y lower/upper range.
double yLower = yAxis->range().lower;
double yUpper = yAxis->range().upper;
// This function is called only when the user has clicked on the x/y axis or
// when the user has dragged the left mouse button with the Ctrl key
// modifier. The m_context.m_wasClickOnXAxis is then simulated in the mouse
// move handler. So we need to test which axis was clicked-on.
if(m_context.m_wasClickOnXAxis)
{
// We are changing the range of the X axis.
// What is the x delta ?
double xDelta =
m_context.m_currentDragPoint.x() - m_context.m_startDragPoint.x();
// If xDelta is < 0, the we were dragging from right to left, we are
// compressing the view on the x axis, by adding new data to the right
// hand size of the graph. So we add xDelta to the upper bound of the
// range. Otherwise we are uncompressing the view on the x axis and
// remove the xDelta from the upper bound of the range. This is why we
// have the
// '-'
// and not '+' below;
// qDebug() << "Setting xaxis:" << xLower << "--" << xUpper - xDelta;
xAxis->setRange(xLower, xUpper - xDelta);
// Old version
// if(xDelta < 0)
//{
//// The dragging operation was from right to left, we are enlarging
//// the range (thus, we are unzooming the view, since the widget
//// always has the same size).
// xAxis->setRange(xLower, xUpper + fabs(xDelta));
//}
// else
//{
//// The dragging operation was from left to right, we are reducing
//// the range (thus, we are zooming the view, since the widget
//// always has the same size).
// xAxis->setRange(xLower, xUpper - fabs(xDelta));
//}
// We may either leave the scale of the Y axis as is (default) or
// the user may want an automatic scale of the Y axis such that the
// data displayed in the new X axis range are full scale on the Y
// axis. For this, the Shift modifier key should be pressed.
if(m_context.m_keyboardModifiers & Qt::ShiftModifier)
{
// In this case, we want to make a rescale of the Y axis such that
// it displays full scale the data in the current X axis range only.
bool ok = false;
QCPRange value_range = getValueRangeOnKeyRange(nullptr, ok);
yAxis->setRange(value_range);
}
// else, do leave the Y axis range unchanged.
}
// End of
// if(m_context.m_wasClickOnXAxis)
else // that is, if(m_context.m_wasClickOnYAxis)
{
// We are changing the range of the Y axis.
// What is the y delta ?
double yDelta =
m_context.m_currentDragPoint.y() - m_context.m_startDragPoint.y();
// See above for an explanation of the computation.
yAxis->setRange(yLower, yUpper - yDelta);
// Old version
// if(yDelta < 0)
//{
//// The dragging operation was from top to bottom, we are enlarging
//// the range (thus, we are unzooming the view, since the widget
//// always has the same size).
// yAxis->setRange(yLower, yUpper + fabs(yDelta));
//}
// else
//{
//// The dragging operation was from bottom to top, we are reducing
//// the range (thus, we are zooming the view, since the widget
//// always has the same size).
// yAxis->setRange(yLower, yUpper - fabs(yDelta));
//}
}
// End of
// else // that is, if(m_context.m_wasClickOnYAxis)
// Update the context with the current axes ranges
updateContextXandYAxisRanges();
emit plotRangesChangedSignal((QMouseEvent *)nullptr, m_context);
replot();
}
void
BaseTracePlotWidget::axisReframe()
{
// qDebug();
// double sorted_start_drag_point_x =
// std::min(m_context.m_startDragPoint.x(), m_context.m_currentDragPoint.x());
// xAxis->setRange(sorted_start_drag_point_x,
// sorted_start_drag_point_x + fabs(m_context.m_xDelta));
xAxis->setRange(
QCPRange(m_context.m_xRegionRangeStart, m_context.m_xRegionRangeStop));
// Note that the y axis should be rescaled from current lower value to new
// upper value matching the y-axis position of the cursor when the mouse
// button was released.
yAxis->setRange(xAxis->range().lower,
std::max(m_context.m_yRegionRangeStart,
m_context.m_yRegionRangeStop));
// qDebug() << "xaxis:" << xAxis->range().lower << "-" <<
// xAxis->range().upper
//<< "yaxis:" << yAxis->range().lower << "-" << yAxis->range().upper;
// If the shift modifier key is pressed, then the user want the y axis
// to be full scale.
if(m_context.m_keyboardModifiers & Qt::ShiftModifier)
{
bool ok = false;
QCPRange value_range = getValueRangeOnKeyRange(nullptr, ok);
yAxis->setRange(value_range);
}
// else do nothing, let the y axis range as is.
updateContextXandYAxisRanges();
updateAxesRangeHistory();
emit plotRangesChangedSignal((QMouseEvent *)nullptr, m_context);
replot();
}
void
BaseTracePlotWidget::axisZoom()
{
// Use the m_context.m_xRegionRangeStart/End values, but we need to sort the
// values before using them, because now we want to really have the lower x
// value. Simply craft a QCPRange that will swap the values if lower is not
// < than upper QCustomPlot calls this normalization).
xAxis->setRange(
QCPRange(m_context.m_xRegionRangeStart, m_context.m_xRegionRangeStop));
// If the shift modifier key is pressed, then the user want the y axis
// to be full scale.
if(m_context.m_keyboardModifiers & Qt::ShiftModifier)
{
bool ok = false;
QCPRange value_range = getValueRangeOnKeyRange(nullptr, ok);
yAxis->setRange(value_range);
}
else
yAxis->setRange(
QCPRange(m_context.m_yRegionRangeStart, m_context.m_yRegionRangeStop));
updateContextXandYAxisRanges();
updateAxesRangeHistory();
emit plotRangesChangedSignal((QMouseEvent *)nullptr, m_context);
replot();
}
void
BaseTracePlotWidget::axisPan()
{
// qDebug();
// Sanity check
if(!m_context.m_wasClickOnXAxis && !m_context.m_wasClickOnYAxis)
qFatal(
"This function can only be called if the mouse click was on one of the "
"axes");
if(m_context.m_wasClickOnXAxis)
{
xAxis->setRange(m_context.m_xRange.lower - m_context.m_xDelta,
m_context.m_xRange.upper - m_context.m_xDelta);
// If the shift modifier key is pressed, then the user want the y axis
// to be full scale.
if(m_context.m_keyboardModifiers & Qt::ShiftModifier)
{
bool ok = false;
QCPRange value_range = getValueRangeOnKeyRange(nullptr, ok);
yAxis->setRange(value_range);
}
// else nothing to do we do not change the y axis scale.
}
if(m_context.m_wasClickOnYAxis)
{
yAxis->setRange(m_context.m_yRange.lower - m_context.m_yDelta,
m_context.m_yRange.upper - m_context.m_yDelta);
}
updateContextXandYAxisRanges();
// qDebug() << "The updated context:" << m_context.toString();
// We cannot store the new ranges in the history, because the pan operation
// involved a huge quantity of micro-movements elicited upon each mouse move
// cursor event so we would have a huge history.
// updateAxesRangeHistory();
// Now that the contex has the right range values, we can emit the
// signal that will be used by this plot widget users, typically to
// abide by the x/y range lock required by the user.
emit plotRangesChangedSignal((QMouseEvent *)nullptr, m_context);
replot();
}
pappso::Trace
BaseTracePlotWidget::toTrace(int index, bool only_visible_range) const
{
QCPGraph *graph_p = graph(index);
return toTrace(graph_p, only_visible_range);
}
pappso::Trace
BaseTracePlotWidget::toTrace(const QCPGraph *graph_p, bool only_visible_range) const
{
if(graph_p == nullptr)
qFatal("Programming error. Pointer cannot be nullptr.");
pappso::Trace trace;
if(only_visible_range)
{
// qDebug() << "Only visible range.";
QCPRange x_axis_range = xAxis->range();
// qDebug() << "Returning for visible range:" << x_axis_range;
return toTrace(x_axis_range, graph_p);
}
else
{
qDebug() << "Failed to get key range. Will return full key range.";
}
QSharedPointer graph_data_container_p = graph_p->data();
// Iterate in the *all* the trace keys
auto beginIt = graph_data_container_p->begin();
auto endIt = graph_data_container_p->end();
for(auto iter = beginIt; iter != endIt; ++iter)
trace.push_back(pappso::DataPoint(iter->key, iter->value));
return trace;
}
pappso::Trace
BaseTracePlotWidget::toTrace(const QCPRange &x_axis_range, int index) const
{
QCPGraph *graph_p = graph(index);
if(graph_p == nullptr)
qFatal("Programming error.");
return toTrace(x_axis_range, graph_p);
}
pappso::Trace
BaseTracePlotWidget::toTrace(const QCPRange &x_axis_range,
const QCPGraph *graph_p) const
{
// qDebug() << "to Trace only key visible range:" << x_axis_range;
// Make a Trace with the data in the range.
Trace data_trace;
QSharedPointer graph_data_container_sp;
graph_data_container_sp = graph_p->data();
// Grab the iterator to the start to the x axis range
// If expandedRange is true, the data point just
// below sortKey will be considered, otherwise the one just above.
auto beginIt = graph_data_container_sp->findBegin(x_axis_range.lower,
/*expandedRange*/ false);
// Grab the iterator to the end of the axis range
// If expandedRange is true, the data point just
// above sortKey will be considered, otherwise the
// one just below.
auto endIt = graph_data_container_sp->findEnd(x_axis_range.upper,
/*expandedRange*/ false);
for(auto iter = beginIt; iter != endIt; ++iter)
data_trace.push_back(DataPoint(iter->key, iter->value));
return data_trace;
}
pappso::Trace
BaseTracePlotWidget::toTrace(const QCPRange &x_axis_range, const QCPBars *bars_p) const
{
// qDebug() << "to Trace only key visible range:" << x_axis_range;
// Make a Trace with the data in the range.
Trace data_trace;
QSharedPointer bars_data_container_sp;
bars_data_container_sp = bars_p->data();
// Grab the iterator to the start to the x axis range
// If expandedRange is true, the data point just
// below sortKey will be considered, otherwise the one just above.
auto beginIt = bars_data_container_sp->findBegin(x_axis_range.lower,
/*expandedRange*/ false);
// Grab the iterator to the end of the axis range
// If expandedRange is true, the data point just
// above sortKey will be considered, otherwise the
// one just below.
auto endIt = bars_data_container_sp->findEnd(x_axis_range.upper,
/*expandedRange*/ false);
for(auto iter = beginIt; iter != endIt; ++iter)
data_trace.push_back(DataPoint(iter->key, iter->value));
return data_trace;
}
Trace
BaseTracePlotWidget::toTrace(const QCPRange &x_axis_range,
const QCPAbstractPlottable *plottable_p) const
{
// We need to check what kind of plot is behind plottable_p.
const QCPBars *bars_plot_p = dynamic_cast(plottable_p);
if(bars_plot_p != nullptr)
return toTrace(x_axis_range, bars_plot_p);
const QCPGraph *graph_plot_p = dynamic_cast(plottable_p);
if(graph_plot_p != nullptr)
return toTrace(x_axis_range, graph_plot_p);
return Trace();
}
Trace
BaseTracePlotWidget::toTrace(const QCPAbstractPlottable *plottable_p) const
{
// We need to check what kind of plot is behind plottable_p.
const QCPBars *bars_plot_p = dynamic_cast(plottable_p);
if(bars_plot_p != nullptr)
{
QSharedPointer bars_data_container_sp;
bars_data_container_sp = bars_plot_p->data();
auto begin_iterator = bars_data_container_sp->begin();
auto end_iterator = bars_data_container_sp->end();
int data_point_count = std::distance(begin_iterator, end_iterator);
if(!data_point_count)
return Trace();
return toTrace(QCPRange(begin_iterator->key, std::prev(end_iterator)->key), bars_plot_p);
}
const QCPGraph *graph_plot_p = dynamic_cast(plottable_p);
if(graph_plot_p != nullptr)
{
QSharedPointer graph_data_container_sp;
graph_data_container_sp = graph_plot_p->data();
auto begin_iterator = graph_data_container_sp->begin();
auto end_iterator = graph_data_container_sp->end();
int data_point_count = std::distance(begin_iterator, end_iterator);
if(!data_point_count)
return Trace();
return toTrace(QCPRange(begin_iterator->key, std::prev(end_iterator)->key), graph_plot_p);
}
return Trace();
}
} // namespace pappso
libpappsomspp-0.11.27/src/pappsomspp/gui/plotwidget/basetraceplotwidget.h 000664 001750 001750 00000007765 15250226472 030153 0 ustar 00rusconi rusconi 000000 000000 /* This code comes right from the msXpertSuite software project.
*
* msXpertSuite - mass spectrometry software suite
* -----------------------------------------------
* Copyright(C) 2009,...,2018 Filippo Rusconi
*
* http://www.msxpertsuite.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* END software license
*/
#pragma once
/////////////////////// StdLib includes
/////////////////////// Qt includes
#include
#include
#include
#include
#include
#include
/////////////////////// QCustomPlot
#include
/////////////////////// Local includes
#include "pappsomspp/export-import-config.h"
#include "baseplotwidget.h"
#include "../../core/trace/trace.h"
namespace pappso
{
class BaseTracePlotWidget;
typedef std::shared_ptr BaseTracePlotWidgetSPtr;
typedef std::shared_ptr BaseTracePlotWidgetCstSPtr;
class PMSPP_LIB_DECL BaseTracePlotWidget: public BasePlotWidget
{
Q_OBJECT
public:
enum class PlotType : int8_t
{
NONE = 0x00,
GRAPH,
BARS,
};
explicit BaseTracePlotWidget(QWidget *parent = 0);
explicit BaseTracePlotWidget(QWidget *parent,
const QString &x_axis_label,
const QString &y_axis_label);
virtual ~BaseTracePlotWidget();
virtual void setGraphData(int graph_index,
const std::vector &keys,
const std::vector &values);
virtual void setGraphData(QCPGraph *graph_p,
const std::vector &keys,
const std::vector &values);
virtual void clearGraphData(int graph_index);
virtual void axisDoubleClickHandler(QCPAxis *axis,
QCPAxis::SelectablePart part,
QMouseEvent *event) override;
// All these need to be overridden because of some special treatment in case
// of Trace plots (graphs, specifically, and not color maps, for example).
virtual void axisRescale() override;
virtual void axisReframe() override;
virtual void axisZoom() override;
virtual void axisPan() override;
virtual QCPGraph *addGraphPlot(const Trace &trace, const QColor &color);
virtual QCPBars *addBarsPlot(const Trace &bars, const QColor &color);
virtual bool
findIntegrationLowerRangeForKey(int index, double key, QCPRange &range);
std::vector getValuesX(int index) const;
std::vector getValuesY(int index) const;
QCPRange getValueRangeOnKeyRange(QCPAbstractPlottable *plottable_p, bool &ok);
QCPRange getValueRangeOnKeyRange(int index, bool &ok);
double getYatX(double x, QCPGraph *graph_p);
// index is 0 by default, which means that if there is a single graph, then,
// that will be it.
double getYatX(double x, int index = 0);
Trace toTrace(int index, bool only_visible_range = false) const;
Trace toTrace(const QCPGraph *graph_p, bool only_visible_range = false) const;
Trace toTrace(const QCPRange &x_axis_range, int index) const;
Trace toTrace(const QCPRange &x_axis_range, const QCPGraph *graph_p) const;
Trace toTrace(const QCPRange &x_axis_range, const QCPBars *bars_p) const;
Trace toTrace(const QCPRange &x_axis_range, const QCPAbstractPlottable *plottable_p) const;
Trace toTrace(const QCPAbstractPlottable *plottable_p) const;
};
} // namespace pappso
libpappsomspp-0.11.27/src/pappsomspp/gui/plotwidget/colormapplotconfig.cpp 000664 001750 001750 00000010463 15250226472 030340 0 ustar 00rusconi rusconi 000000 000000 // Copyright Filippo Rusconi, GPLv3+
/////////////////////// StdLib includes
/////////////////////// Qt includes
/////////////////////// Local includes
#include "colormapplotconfig.h"
namespace pappso
{
ColorMapPlotConfig::ColorMapPlotConfig()
{
}
ColorMapPlotConfig::ColorMapPlotConfig(Enums::DataKind x_axis_data_kind,
Enums::DataKind y_axis_data_kind,
Enums::AxisScale x_axis_scale,
Enums::AxisScale y_axis_scale,
Enums::AxisScale z_axis_scale,
std::size_t key_cell_count,
std::size_t mz_cell_count,
double min_key_value,
double max_key_value,
double min_mz_value,
double max_mz_value,
double orig_min_z_value,
double orig_max_z_value)
: xAxisDataKind(x_axis_data_kind),
yAxisDataKind(y_axis_data_kind),
xAxisScale(x_axis_scale),
yAxisScale(y_axis_scale),
zAxisScale(z_axis_scale),
keyCellCount(key_cell_count),
mzCellCount(mz_cell_count),
minKeyValue(min_key_value),
maxKeyValue(max_key_value),
minMzValue(min_mz_value),
maxMzValue(max_mz_value),
// Initialize both orig and last to the same value.
origMinZValue(orig_min_z_value),
lastMinZValue(orig_min_z_value),
// Initialize both orig and last to the same value.
origMaxZValue(orig_max_z_value),
lastMaxZValue(orig_max_z_value)
{
}
ColorMapPlotConfig::ColorMapPlotConfig(const ColorMapPlotConfig &other)
{
xAxisDataKind = other.xAxisDataKind;
yAxisDataKind = other.yAxisDataKind;
xAxisScale = other.xAxisScale;
yAxisScale = other.yAxisScale;
zAxisScale = other.zAxisScale;
keyCellCount = other.keyCellCount;
mzCellCount = other.mzCellCount;
minKeyValue = other.minKeyValue;
maxKeyValue = other.maxKeyValue;
minMzValue = other.minMzValue;
maxMzValue = other.maxMzValue;
origMinZValue = other.origMinZValue;
lastMinZValue = other.lastMinZValue;
origMaxZValue = other.origMaxZValue;
lastMaxZValue = other.lastMaxZValue;
}
ColorMapPlotConfig &
ColorMapPlotConfig::operator=(const ColorMapPlotConfig &other)
{
if(this == &other)
return *this;
xAxisDataKind = other.xAxisDataKind;
yAxisDataKind = other.yAxisDataKind;
xAxisScale = other.xAxisScale;
yAxisScale = other.yAxisScale;
zAxisScale = other.zAxisScale;
keyCellCount = other.keyCellCount;
mzCellCount = other.mzCellCount;
minKeyValue = other.minKeyValue;
maxKeyValue = other.maxKeyValue;
minMzValue = other.minMzValue;
maxMzValue = other.maxMzValue;
origMinZValue = other.origMinZValue;
lastMinZValue = other.lastMinZValue;
origMaxZValue = other.origMaxZValue;
lastMaxZValue = other.lastMaxZValue;
return *this;
}
void
ColorMapPlotConfig::setOrigMinZValue(double value)
{
origMinZValue = value;
}
void
ColorMapPlotConfig::setOrigAndLastMinZValue(double value)
{
origMinZValue = value;
lastMinZValue = value;
}
void
ColorMapPlotConfig::setOrigMaxZValue(double value)
{
origMaxZValue = value;
}
void
ColorMapPlotConfig::setOrigAndLastMaxZValue(double value)
{
origMaxZValue = value;
lastMaxZValue = value;
}
QString
ColorMapPlotConfig::toString() const
{
QString text = QString("xAxisDataKind: %1 - yAxisDataKind: %2")
.arg(static_cast(xAxisDataKind))
.arg(static_cast(yAxisDataKind));
text += QString("xAxisScale: %1 - yAxisScale: %2 - zAxisScale: %3 - ")
.arg(static_cast(xAxisScale))
.arg(static_cast(yAxisScale))
.arg(static_cast(zAxisScale));
text += QString("keyCellCount: %1 - mzCellCount: %2 - ").arg(mzCellCount).arg(minKeyValue);
text += QString(
"minKeyValue: %8 - maxKeyValue: %9 - minMzValue: %10 - maxMzValue: "
"%11 - lastMinZValue: %12 - lastMaxZValue: %13")
.arg(keyCellCount)
.arg(maxKeyValue)
.arg(minMzValue)
.arg(maxMzValue)
.arg(lastMinZValue)
.arg(lastMaxZValue);
return text;
}
} // namespace pappso
libpappsomspp-0.11.27/src/pappsomspp/gui/plotwidget/colormapplotconfig.h 000664 001750 001750 00000004424 15250226472 030005 0 ustar 00rusconi rusconi 000000 000000 // Copyright Filippo Rusconi, GPLv3+
/////////////////////// StdLib includes
#include
/////////////////////// Qt includes
#include
/////////////////////// Local includes
#include "../../core/types.h"
#include "pappsomspp/core/utils.h"
#include "pappsomspp/export-import-config.h"
#pragma once
namespace pappso
{
struct PMSPP_LIB_DECL ColorMapPlotConfig
{
Enums::DataKind xAxisDataKind = Enums::DataKind::unset;
Enums::DataKind yAxisDataKind = Enums::DataKind::unset;
Enums::AxisScale xAxisScale = Enums::AxisScale::orig;
Enums::AxisScale yAxisScale = Enums::AxisScale::orig;
Enums::AxisScale zAxisScale = Enums::AxisScale::orig;
std::size_t keyCellCount = 0;
std::size_t mzCellCount = 0;
double lastMinZFilterThresholdPercentage = 0.0;
double lastMaxZFilterThresholdPercentage = 0.0;
double minKeyValue = std::numeric_limits::max();
double maxKeyValue = std::numeric_limits::min();
double minMzValue = std::numeric_limits::max();
double maxMzValue = std::numeric_limits::max();
double origMinZValue = std::numeric_limits::max();
double lastMinZValue = std::numeric_limits::max();
double origMaxZValue = std::numeric_limits::min();
double lastMaxZValue = std::numeric_limits::min();
ColorMapPlotConfig();
ColorMapPlotConfig(const ColorMapPlotConfig &other);
ColorMapPlotConfig(Enums::DataKind x_axis_data_kind,
Enums::DataKind y_axis_data_kind,
Enums::AxisScale x_axis_scale,
Enums::AxisScale y_axis_scale,
Enums::AxisScale z_axis_scale,
std::size_t key_cell_count,
std::size_t mz_cell_count,
double min_key_value,
double max_key_value,
double min_mz_value,
double max_mz_value,
double orig_min_z_value,
double orig_max_z_value);
ColorMapPlotConfig &operator=(const ColorMapPlotConfig &other);
void setOrigMinZValue(double value);
void setOrigAndLastMinZValue(double value);
void setOrigMaxZValue(double value);
void setOrigAndLastMaxZValue(double value);
QString toString() const;
};
} // namespace pappso
libpappsomspp-0.11.27/src/pappsomspp/gui/plotwidget/driftspecmassspeccolormapplotwidget.cpp 000664 001750 001750 00000006307 15250226472 034023 0 ustar 00rusconi rusconi 000000 000000 /* This code comes right from the msXpertSuite software project.
*
* msXpertSuite - mass spectrometry software suite
* -----------------------------------------------
* Copyright(C) 2009,...,2018 Filippo Rusconi
*
* http://www.msxpertsuite.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* END software license
*/
/////////////////////// StdLib includes
#include
/////////////////////// Qt includes
#include
/////////////////////// Local includes
#include "driftspecmassspeccolormapplotwidget.h"
#include "pappsomspp/core/pappsoexception.h"
namespace pappso
{
DriftSpecMassSpecColorMapPlotWidget::DriftSpecMassSpecColorMapPlotWidget(
QWidget *parent, const QString &x_axis_label, const QString &y_axis_label)
: BaseColorMapPlotWidget(parent, x_axis_label, y_axis_label)
{
}
DriftSpecMassSpecColorMapPlotWidget::~DriftSpecMassSpecColorMapPlotWidget()
{
}
//! Set the \c m_pressedKeyCode to the key code in \p event.
void
DriftSpecMassSpecColorMapPlotWidget::keyPressEvent(QKeyEvent *event)
{
BasePlotWidget::keyPressEvent(event);
emit keyPressEventSignal(event, m_context);
}
//! Handle specific key codes and trigger respective actions.
void
DriftSpecMassSpecColorMapPlotWidget::keyReleaseEvent(QKeyEvent *event)
{
BasePlotWidget::keyReleaseEvent(event);
emit keyReleaseEventSignal(event, m_context);
}
//! Handle mouse movements, in particular record all the last visited points.
/*!
This function is reponsible for storing at each time the last visited point
in the graph. Here, point is intended as any x/y coordinate in the plot
widget viewport, not a graph point.
The stored values are then the basis for a large set of calculations
throughout all the plot widget.
\param pointer to QMouseEvent from which to retrieve the coordinates of the
visited viewport points.
*/
void
DriftSpecMassSpecColorMapPlotWidget::mouseMoveHandler(QMouseEvent *event)
{
BasePlotWidget::mouseMoveHandler(event);
}
void
DriftSpecMassSpecColorMapPlotWidget::mouseMoveHandlerNotDraggingCursor(QMouseEvent *event)
{
BasePlotWidget::mouseMoveHandlerNotDraggingCursor(event);
}
void
DriftSpecMassSpecColorMapPlotWidget::mouseMoveHandlerDraggingCursor(QMouseEvent *event)
{
BasePlotWidget::mouseMoveHandlerDraggingCursor(event);
}
//! Record the clicks of the mouse.
void
DriftSpecMassSpecColorMapPlotWidget::mousePressHandler(QMouseEvent *event)
{
BasePlotWidget::mousePressHandler(event);
}
//! React to the release of the mouse buttons.
void
DriftSpecMassSpecColorMapPlotWidget::mouseReleaseHandler(QMouseEvent *event)
{
BasePlotWidget::mouseReleaseHandler(event);
}
} // namespace pappso
libpappsomspp-0.11.27/src/pappsomspp/gui/plotwidget/driftspecmassspeccolormapplotwidget.h 000664 001750 001750 00000004460 15250226472 033466 0 ustar 00rusconi rusconi 000000 000000 /* This code comes right from the msXpertSuite software project.
*
* msXpertSuite - mass spectrometry software suite
* -----------------------------------------------
* Copyright(C) 2009,...,2018 Filippo Rusconi
*
* http://www.msxpertsuite.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* END software license
*/
#pragma once
/////////////////////// StdLib includes
/////////////////////// Qt includes
#include
#include
#include
#include
#include
#include
/////////////////////// QCustomPlot
#include
/////////////////////// Local includes
#include "pappsomspp/export-import-config.h"
#include "basecolormapplotwidget.h"
namespace pappso
{
class PMSPP_LIB_DECL DriftSpecMassSpecColorMapPlotWidget
: public BaseColorMapPlotWidget
{
Q_OBJECT;
public:
explicit DriftSpecMassSpecColorMapPlotWidget(QWidget *parent,
const QString &x_axis_label,
const QString &y_axis_label);
virtual ~DriftSpecMassSpecColorMapPlotWidget();
virtual void keyPressEvent(QKeyEvent *event) override;
virtual void keyReleaseEvent(QKeyEvent *event) override;
virtual void mouseMoveHandler(QMouseEvent *event) override;
virtual void mousePressHandler(QMouseEvent *event) override;
virtual void mouseReleaseHandler(QMouseEvent *event) override;
virtual void mouseMoveHandlerNotDraggingCursor(QMouseEvent *event) override;
virtual void mouseMoveHandlerDraggingCursor(QMouseEvent *event) override;
signals:
// Here we have signals that are specific of the mass spectrum-oriented
// version of the plot widget.
protected:
};
} // namespace pappso
libpappsomspp-0.11.27/src/pappsomspp/gui/plotwidget/driftspectraceplotwidget.cpp 000664 001750 001750 00000006523 15250226472 031546 0 ustar 00rusconi rusconi 000000 000000 /* This code comes right from the msXpertSuite software project.
*
* msXpertSuite - mass spectrometry software suite
* -----------------------------------------------
* Copyright(C) 2009,...,2018 Filippo Rusconi
*
* http://www.msxpertsuite.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* END software license
*/
/////////////////////// StdLib includes
#include
/////////////////////// Qt includes
#include
/////////////////////// Local includes
#include "driftspectraceplotwidget.h"
#include "pappsomspp/core/pappsoexception.h"
namespace pappso
{
DriftSpecTracePlotWidget::DriftSpecTracePlotWidget(QWidget *parent,
const QString &x_axis_label,
const QString &y_axis_label)
: BaseTracePlotWidget(parent, x_axis_label, y_axis_label)
{
// Set the context to be of kind Enums::DataKind::dt
m_context.m_dataKind = Enums::DataKind::dt;
// qDebug() << "Data kind:" << static_cast(m_context.m_dataKind);
}
DriftSpecTracePlotWidget::~DriftSpecTracePlotWidget()
{
}
//! Set the \c m_pressedKeyCode to the key code in \p event.
void
DriftSpecTracePlotWidget::keyPressEvent(QKeyEvent *event)
{
BasePlotWidget::keyPressEvent(event);
emit keyPressEventSignal(event, m_context);
}
//! Handle specific key codes and trigger respective actions.
void
DriftSpecTracePlotWidget::keyReleaseEvent(QKeyEvent *event)
{
BasePlotWidget::keyReleaseEvent(event);
emit keyReleaseEventSignal(event, m_context);
}
//! Handle mouse movements, in particular record all the last visited points.
/*!
This function is reponsible for storing at each time the last visited point
in the graph. Here, point is intended as any x/y coordinate in the plot
widget viewport, not a graph point.
The stored values are then the basis for a large set of calculations
throughout all the plot widget.
\param pointer to QMouseEvent from which to retrieve the coordinates of the
visited viewport points.
*/
void
DriftSpecTracePlotWidget::mouseMoveHandler(QMouseEvent *event)
{
BasePlotWidget::mouseMoveHandler(event);
}
void
DriftSpecTracePlotWidget::mouseMoveHandlerNotDraggingCursor(QMouseEvent *event)
{
BasePlotWidget::mouseMoveHandlerNotDraggingCursor(event);
}
void
DriftSpecTracePlotWidget::mouseMoveHandlerDraggingCursor(QMouseEvent *event)
{
BasePlotWidget::mouseMoveHandlerDraggingCursor(event);
}
//! Record the clicks of the mouse.
void
DriftSpecTracePlotWidget::mousePressHandler(QMouseEvent *event)
{
BasePlotWidget::mousePressHandler(event);
}
//! React to the release of the mouse buttons.
void
DriftSpecTracePlotWidget::mouseReleaseHandler(QMouseEvent *event)
{
BasePlotWidget::mouseReleaseHandler(event);
}
} // namespace pappso
libpappsomspp-0.11.27/src/pappsomspp/gui/plotwidget/driftspectraceplotwidget.h 000664 001750 001750 00000004622 15250226472 031211 0 ustar 00rusconi rusconi 000000 000000 /* This code comes right from the msXpertSuite software project.
*
* msXpertSuite - mass spectrometry software suite
* -----------------------------------------------
* Copyright(C) 2009,...,2018 Filippo Rusconi
*
* http://www.msxpertsuite.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* END software license
*/
#pragma once
/////////////////////// StdLib includes
/////////////////////// Qt includes
#include
#include
#include
#include
#include
#include
/////////////////////// QCustomPlot
#include
/////////////////////// Local includes
#include "pappsomspp/export-import-config.h"
#include "basetraceplotwidget.h"
namespace pappso
{
class PMSPP_LIB_DECL DriftSpecTracePlotWidget : public BaseTracePlotWidget
{
Q_OBJECT;
public:
explicit DriftSpecTracePlotWidget(QWidget *parent,
const QString &x_axis_label,
const QString &y_axis_label);
virtual ~DriftSpecTracePlotWidget();
/******* Mouse and keyboard event handlers *******/
/******* Mouse and keyboard event handlers *******/
virtual void keyPressEvent(QKeyEvent *event) override;
virtual void keyReleaseEvent(QKeyEvent *event) override;
virtual void mouseMoveHandler(QMouseEvent *event) override;
virtual void mousePressHandler(QMouseEvent *event) override;
virtual void mouseReleaseHandler(QMouseEvent *event) override;
virtual void mouseMoveHandlerNotDraggingCursor(QMouseEvent *event) override;
virtual void mouseMoveHandlerDraggingCursor(QMouseEvent *event) override;
/******* Mouse and keyboard event handlers *******/
signals:
// Here we have signals that are specific of the mass spectrum-oriented
// version of the plot widget.
protected:
};
} // namespace pappso
libpappsomspp-0.11.27/src/pappsomspp/gui/plotwidget/massspectraceplotcontext.cpp 000664 001750 001750 00000004423 15250226472 031577 0 ustar 00rusconi rusconi 000000 000000 // Copyright 2021 Filippo Rusconi
// GPLv3+
/////////////////////// StdLib includes
/////////////////////// Qt includes
#include
/////////////////////// Local includes
#include "massspectraceplotcontext.h"
namespace pappso
{
MassSpecTracePlotContext::MassSpecTracePlotContext() : BasePlotContext()
{
}
MassSpecTracePlotContext::MassSpecTracePlotContext(const MassSpecTracePlotContext &other)
: BasePlotContext(other),
m_lastZ(other.m_lastZ),
m_lastMz(other.m_lastMz),
m_lastTicIntensity(other.m_lastTicIntensity),
m_lastMr(other.m_lastMr),
m_lastResolvingPower(other.m_lastResolvingPower)
{
}
MassSpecTracePlotContext::~MassSpecTracePlotContext()
{
}
MassSpecTracePlotContext *
MassSpecTracePlotContext::clone()
{
return new MassSpecTracePlotContext(*this);
}
void
MassSpecTracePlotContext::initialize(const BasePlotContext &other)
{
BasePlotContext::initialize(other);
}
void
MassSpecTracePlotContext::initialize(const MassSpecTracePlotContext &other)
{
BasePlotContext::initialize(other);
m_lastZ = other.m_lastZ;
m_lastMz = other.m_lastMz;
m_lastTicIntensity = other.m_lastTicIntensity;
m_lastMr = other.m_lastMr;
m_lastResolvingPower = other.m_lastResolvingPower;
}
MassSpecTracePlotContext &
MassSpecTracePlotContext::operator=(const BasePlotContext &other)
{
BasePlotContext::initialize(other);
return *this;
}
MassSpecTracePlotContext &
MassSpecTracePlotContext::operator=(const MassSpecTracePlotContext &other)
{
initialize(other);
return *this;
}
void
MassSpecTracePlotContext::resetDeconvolutionData()
{
m_lastZ = std::numeric_limits::max();
m_lastMz = qQNaN();
m_lastMr = qQNaN();
}
QString
MassSpecTracePlotContext::toString() const
{
QString text("Base context:\n");
text += BasePlotContext::toString();
text += "\n";
text += "Mass spectrum trace plot context\n";
text += QString("last z: %1").arg(m_lastZ);
text += QString(" -- last m/z: %1").arg(m_lastMz, 0, 'f', 6);
text += QString(" -- last TIC intensity: %1").arg(m_lastTicIntensity, 0, 'f', 0);
text += QString(" -- last Mr: %1").arg(m_lastMr, 0, 'f', 6);
text += QString(" -- last resolving power: %1").arg(m_lastResolvingPower, 0, 'f', 0);
text += "\n";
return text;
}
} // namespace pappso
libpappsomspp-0.11.27/src/pappsomspp/gui/plotwidget/massspectraceplotcontext.h 000664 001750 001750 00000002242 15250226472 031241 0 ustar 00rusconi rusconi 000000 000000 // Copyright 2021 Filippo Rusconi
// GPLv3+
#pragma once
/////////////////////// StdLib includes
/////////////////////// Qt includes
#include
#include