8 #include <QActionGroup> 9 #include <QApplication> 12 #include <QDragEnterEvent> 13 #include <QDragMoveEvent> 14 #include <QFileDialog> 16 #include <QMessageBox> 20 #include <QPushButton> 21 #include <QWheelEvent> 23 #include <QSvgGenerator> 29 #include <QtXml/QDomElement> 61 QDateTime dt = QDateTime::fromMSecsSinceEpoch((qint64)(v * 1000));
62 if (dt.date().year() == 1970 && dt.date().month() == 1 && dt.date().day() == 1)
64 return dt.toString(
"hh:mm:ss.z");
66 return dt.toString(
"hh:mm:ss.z\nyyyy MMM dd");
70 const double MAX_DOUBLE = std::numeric_limits<double>::max() / 2;
76 , _mapped_data(datamap)
78 , _use_date_time_scale(false)
87 qwtPlot()->setAcceptDrops(
true);
102 connect(
this, &PlotWidgetBase::widgetResized,
this, [
this]() {
146 QIcon iconDeleteList;
149 connect(
_action_edit, &QAction::triggered,
this, [=]() {
151 editor_dialog->exec();
152 editor_dialog->deleteLater();
156 connect(_action_formula, &QAction::triggered,
this, [=]() {
158 int res = editor_dialog->exec();
159 editor_dialog->deleteLater();
160 if (res == QDialog::Accepted)
167 connect(_action_split_horizontal, &QAction::triggered,
this,
187 connect(_action_zoomOutHorizontally, &QAction::triggered,
this, [
this]() {
194 connect(_action_zoomOutVertically, &QAction::triggered,
this, [
this]() {
201 font.setPointSize(10);
217 _flip_x =
new QAction(
"&Flip Horizontal Axis",
this);
221 _flip_y =
new QAction(
"&Flip Vertical Axis",
this);
238 QString theme = settings.value(
"StyleSheet::theme",
"light").toString();
247 LoadSvg(
":/resources/svg/zoom_horizontal.svg", theme));
281 QClipboard* clipboard = QGuiApplication::clipboard();
282 QString clipboard_text = clipboard->text();
284 bool valid_clipbaord = (!clipboard_text.isEmpty() &&
285 doc.setContent(clipboard_text) &&
286 doc.firstChildElement().tagName() ==
"PlotWidgetClipBoard");
293 menu.exec(
qwtPlot()->canvas()->mapToGlobal(pos));
299 std::string
name = curve_name.toStdString();
304 auto ret = dialog.exec();
307 name = curve_name.toStdString();
308 name_x = dialog.
nameX().toStdString();
309 name_y = dialog.
nameY().toStdString();
311 if (
ret != QDialog::Accepted)
318 if (name.empty() || curve_it)
320 int ret = QMessageBox::warning(
qwtPlot(),
"Missing name",
321 "The name of the curve is missing or exist already. " 322 "Try again or abort.",
323 QMessageBox::Abort | QMessageBox::Retry,
324 QMessageBox::NoButton);
325 if (ret == QMessageBox::Abort || ret == QMessageBox::NoButton)
336 throw std::runtime_error(
"Creation of XY plot failed");
343 throw std::runtime_error(
"Creation of XY plot failed");
353 const auto qname = QString::fromStdString(name);
362 curve->setData(plot_qwt);
364 catch (std::exception& ex)
366 QMessageBox::warning(
qwtPlot(),
"Exception!", ex.what());
371 curve->setPen(color);
382 marker->setSymbol(sym);
385 curve_info.
curve = curve;
386 curve_info.
marker = marker;
395 PlotWidgetBase::CurveInfo* info =
nullptr;
400 info = PlotWidgetBase::addCurve(name, it1->second, color);
406 info = PlotWidgetBase::addCurve(name, it2->second, color);
409 if (info && info->curve)
411 if (
auto timeseries = dynamic_cast<QwtTimeseries*>(info->curve->data()))
421 PlotWidgetBase::removeCurve(title);
427 bool deleted =
false;
432 bool remove_curve_xy = curve_xy && (curve_xy->
dataX()->
plotName() == src_name ||
435 if (it->src_name == src_name || remove_curve_xy)
439 it->marker->detach();
463 PlotWidgetBase::removeAllCurves();
472 const QMimeData* mimeData =
event->mimeData();
473 QStringList mimeFormats = mimeData->formats();
479 auto&
format = mimeFormats.first();
480 QByteArray encoded = mimeData->data(
format);
481 QDataStream stream(&encoded, QIODevice::ReadOnly);
483 while (!stream.atEnd())
486 stream >> curve_name;
487 auto name = curve_name.toStdString();
488 if (!curve_name.isEmpty())
505 if (
format ==
"curveslist/add_curve")
508 event->acceptProposedAction();
511 if (
format ==
"curveslist/new_XY_axis")
515 qDebug() <<
"FATAL: Dragging " <<
_dragging.
curves.size() <<
" curves";
521 event->acceptProposedAction();
533 bool curves_changed =
false;
539 size_t scatter_count = 0;
545 if (scatter_count > 0 && !scatter_curves)
549 QMessageBox::warning(
qwtPlot(),
"Warning",
550 "You can not drag XY (scatter) data and timeseries into the " 563 QMessageBox::warning(
qwtPlot(),
"Warning",
564 tr(
"This is a [XY plot], you can not drop a timeseries here.\n" 565 "To convert this widget into a [timeseries plot], " 566 "you must first remove all its curves."));
570 QMessageBox::warning(
qwtPlot(),
"Warning",
571 tr(
"This is a [timeseries plot], you can not " 572 "drop XY scatter data here.\n" 573 "To convert this widget into a [XY plot], " 574 "you must first remove all its curves."));
586 bool added =
addCurve(curve_name.toStdString()) !=
nullptr;
587 curves_changed = curves_changed || added;
596 QMessageBox::warning(
qwtPlot(),
"Warning",
597 tr(
"This is a [timeseries plot], you can not " 598 "drop XY scatter data here.\n" 599 "To convert this widget into a [XY plot], " 600 "you must first remove all its curves."));
607 curves_changed =
true;
616 bool autozoom_curve_added = settings.value(
"Preferences::autozoom_curve_added",
true).toBool();
617 if(autozoom_curve_added || noCurves)
636 QDomElement plot_el = doc.createElement(
"plot");
638 QDomElement range_el = doc.createElement(
"range");
640 range_el.setAttribute(
"bottom", QString::number(rect.bottom(),
'f', 6));
641 range_el.setAttribute(
"top", QString::number(rect.top(),
'f', 6));
642 range_el.setAttribute(
"left", QString::number(rect.left(),
'f', 6));
643 range_el.setAttribute(
"right", QString::number(rect.right(),
'f', 6));
644 plot_el.appendChild(range_el);
646 QDomElement limitY_el = doc.createElement(
"limitY");
655 plot_el.appendChild(limitY_el);
659 plot_el.setAttribute(
"style",
"Lines");
661 else if (
curveStyle() == PlotWidgetBase::LINES_AND_DOTS)
663 plot_el.setAttribute(
"style",
"LinesAndDots");
665 else if (
curveStyle() == PlotWidgetBase::DOTS)
667 plot_el.setAttribute(
"style",
"Dots");
669 else if (
curveStyle() == PlotWidgetBase::STICKS)
671 plot_el.setAttribute(
"style",
"Sticks");
676 auto&
name = it.src_name;
678 QDomElement curve_el = doc.createElement(
"curve");
679 curve_el.setAttribute(
"name", QString::fromStdString(
name));
680 curve_el.setAttribute(
"color", curve->
pen().color().name());
682 plot_el.appendChild(curve_el);
686 if (
auto xy = dynamic_cast<PointSeriesXY*>(curve->
data()))
688 curve_el.setAttribute(
"curve_x", QString::fromStdString(xy->dataX()->plotName()));
689 curve_el.setAttribute(
"curve_y", QString::fromStdString(xy->dataY()->plotName()));
695 if (ts && ts->transform())
697 QDomElement transform_el = doc.createElement(
"transform");
698 transform_el.setAttribute(
"name", ts->transformName());
699 transform_el.setAttribute(
"alias", ts->alias());
700 ts->
transform()->xmlSaveState(doc, transform_el);
701 curve_el.appendChild(transform_el);
706 plot_el.setAttribute(
"mode",
isXYPlot() ?
"XYPlot" :
"TimeSeries");
708 plot_el.setAttribute(
"flip_x",
isXYPlot() &&
_flip_x->isChecked() ?
"true" :
"false");
709 plot_el.setAttribute(
"flip_y",
_flip_y->isChecked() ?
"true" :
"false");
714 plot_el.setAttribute(
"background_colormap",
_background_item->colormapName());
722 std::set<std::string> added_curve_names;
724 QString mode = plot_widget.attribute(
"mode");
727 _flip_x->setChecked(plot_widget.attribute(
"flip_x") ==
"true");
728 _flip_y->setChecked(plot_widget.attribute(
"flip_y") ==
"true");
730 QDomElement limitY_el = plot_widget.firstChildElement(
"limitY");
735 if (!limitY_el.isNull())
737 if (limitY_el.hasAttribute(
"min"))
741 if (limitY_el.hasAttribute(
"max"))
747 static bool warning_message_shown =
false;
758 QStringList missing_curves;
759 for (QDomElement curve_element = plot_widget.firstChildElement(
"curve");
760 !curve_element.isNull(); curve_element = curve_element.nextSiblingElement(
"curve"))
763 curve_element.hasAttribute(
"curve_x") && curve_element.hasAttribute(
"curve_y");
765 bool is_scatter_xy = !is_timeseries && !is_merged_xy;
767 QString curve_name = curve_element.attribute(
"name");
768 std::string curve_name_std = curve_name.toStdString();
769 QColor
color(curve_element.attribute(
"color"));
772 if (is_timeseries || is_scatter_xy)
777 missing_curves.append(curve_name);
786 auto& curve = curve_info->curve;
787 curve->setPen(
color, 1.3);
788 added_curve_names.insert(curve_name_std);
791 QDomElement transform_el = curve_element.firstChildElement(
"transform");
792 if (ts && transform_el.isNull() ==
false)
795 ts->transform()->xmlLoadState(transform_el);
796 ts->updateCache(
true);
797 auto alias = transform_el.attribute(
"alias");
799 curve->setTitle(alias);
806 std::string curve_x = curve_element.attribute(
"curve_x").toStdString();
807 std::string curve_y = curve_element.attribute(
"curve_y").toStdString();
811 missing_curves.append(curve_name);
815 auto curve_it =
addCurveXY(curve_x, curve_y, curve_name);
820 curve_it->curve->setPen(
color, 1.3);
821 curve_it->marker->setSymbol(
823 added_curve_names.insert(curve_name_std);
828 if (missing_curves.size() > 0 && !warning_message_shown)
830 QMessageBox::warning(
qwtPlot(),
"Warning",
831 tr(
"Can't find one or more curves.\n" 832 "This message will be shown only once.\n%1")
833 .
arg(missing_curves.join(
",\n")));
834 warning_message_shown =
true;
841 QDomElement rectangle = plot_widget.firstChildElement(
"range");
843 if (!rectangle.isNull() && autozoom)
846 rect.setBottom(rectangle.attribute(
"bottom").toDouble());
847 rect.setTop(rectangle.attribute(
"top").toDouble());
848 rect.setLeft(rectangle.attribute(
"left").toDouble());
849 rect.setRight(rectangle.attribute(
"right").toDouble());
853 if (plot_widget.hasAttribute(
"style"))
855 QString style = plot_widget.attribute(
"style");
856 if (style ==
"Lines")
860 else if (style ==
"LinesAndDots")
864 else if (style ==
"Dots")
868 else if (style ==
"Sticks")
874 QString bg_data = plot_widget.attribute(
"background_data");
875 QString bg_colormap = plot_widget.attribute(
"background_colormap");
877 if (!bg_data.isEmpty() && !bg_colormap.isEmpty())
882 QMessageBox::warning(
qwtPlot(),
"Warning",
883 tr(
"Can't restore the background color.\n" 884 "Series [%1] not found.")
892 QMessageBox::warning(
qwtPlot(),
"Warning",
893 tr(
"Can't restore the background color.\n" 894 "ColorMap [%1] not found.")
901 std::make_unique<BackgroundColorItem>(plot_it->second, bg_colormap);
921 const double canvas_ratio = std::abs(canvas_rect.width() / canvas_rect.height());
922 const double max_ratio = std::abs(max_rect.width() / max_rect.height());
924 QRectF rect = max_rect;
926 if (max_ratio < canvas_ratio)
928 double new_width = (-max_rect.height() * canvas_ratio);
929 rect.setWidth(new_width);
933 double new_height = (-max_rect.width() / canvas_ratio);
934 rect.setHeight(new_height);
937 rect.moveCenter(max_rect.center());
980 if (it.curve->isVisible())
985 const auto& curve_name = it.src_name;
990 if (
auto ts = dynamic_cast<TransformedTimeseries*>(it.curve->data()))
992 ts->updateCache(
true);
1038 if (
auto series = dynamic_cast<QwtTimeseries*>(it.curve->data()))
1040 auto pointXY = series->sampleFromTime(abs_time);
1043 it.marker->setValue(pointXY.value());
1060 if (fabs(prev_offset - offset) > std::numeric_limits<double>::epsilon())
1064 if (
auto series = dynamic_cast<QwtTimeseries*>(it.curve->data()))
1072 double delta = prev_offset - offset;
1073 rect.moveLeft(rect.left() + delta);
1105 auto [bottom,
top] = PlotWidgetBase::getVisualizationRangeY(range_X);
1159 if (it.curve->title() == curve_name)
1161 auto& curve = it.curve;
1162 if (curve->pen().color() != new_color)
1164 curve->setPen(new_color, 1.3);
1183 canvas_rect = canvas_rect.normalized();
1199 QString prev_colormap;
1226 auto ret = dialog.exec();
1227 if (
ret == QDialog::Accepted)
1238 _background_item = std::make_unique<BackgroundColorItem>(plot_it->second, colormap);
1282 connect(
this, &PlotWidgetBase::curveListChanged,
this,
1289 if (current_rect == rect)
1304 QRectF rect(0, 1, 1, -1);
1320 act.setLeft(rangeX.min);
1321 act.setRight(rangeX.max);
1331 rect.setBottom(rangeY.min);
1332 rect.setTop(rangeY.max);
1342 PlotWidgetBase::setModeXY(enable);
1349 font_footer.setPointSize(10);
1367 QByteArray xml_text =
1368 settings.value(
"AddCustomPlotDialog.savedXML", QByteArray()).toByteArray();
1369 if (!xml_text.isEmpty())
1379 QFileDialog saveDialog(
qwtPlot());
1380 saveDialog.setAcceptMode(QFileDialog::AcceptSave);
1382 QStringList filters;
1383 filters <<
"png (*.png)" 1384 <<
"jpg (*.jpg *.jpeg)" 1387 saveDialog.setNameFilters(filters);
1390 if (saveDialog.result() == QDialog::Accepted && !saveDialog.selectedFiles().empty())
1392 fileName = saveDialog.selectedFiles().first();
1394 if (fileName.isEmpty())
1399 bool is_svg =
false;
1400 QFileInfo fileinfo(fileName);
1401 if (fileinfo.suffix().isEmpty())
1403 auto filter = saveDialog.selectedNameFilter();
1404 if (filter == filters[0])
1406 fileName.append(
".png");
1408 else if (filter == filters[1])
1410 fileName.append(
".jpg");
1412 else if (filter == filters[2])
1414 fileName.append(
".svg");
1420 if (tracker_enabled)
1426 QRect documentRect(0, 0, 1200, 900);
1431 QSvgGenerator generator;
1432 generator.setFileName(fileName);
1433 generator.setResolution(80);
1434 generator.setViewBox(documentRect);
1435 QPainter painter(&generator);
1440 QPixmap pixmap(1200, 900);
1441 QPainter painter(&pixmap);
1443 pixmap.save(fileName);
1446 if (tracker_enabled)
1469 if (tracker_enabled)
1476 qDebug() << documentRect;
1479 QPixmap pixmap(documentRect.width(), documentRect.height());
1480 QPainter painter(&pixmap);
1483 QClipboard* clipboard = QGuiApplication::clipboard();
1484 clipboard->setPixmap(pixmap);
1486 if (tracker_enabled)
1496 auto root = doc.createElement(
"PlotWidgetClipBoard");
1498 doc.appendChild(root);
1499 root.appendChild(el);
1501 QClipboard* clipboard = QGuiApplication::clipboard();
1502 clipboard->setText(doc.toString());
1507 QClipboard* clipboard = QGuiApplication::clipboard();
1508 QString clipboard_text = clipboard->text();
1511 bool valid = doc.setContent(clipboard_text);
1516 auto root = doc.firstChildElement();
1517 if (root.tagName() !=
"PlotWidgetClipBoard")
1523 auto el = root.firstChildElement();
1531 if (PlotWidgetBase::eventFilter(obj, event))
1536 if (event->type() == QEvent::Destroy)
1551 QString theme = settings.value(
"Preferences::theme",
"light").toString();
1552 auto pixmap =
LoadSvg(
":/resources/svg/move_view.svg", theme);
1553 QApplication::setOverrideCursor(QCursor(pixmap.scaled(24, 24)));
1591 switch (event->type())
1593 case QEvent::MouseButtonPress: {
1599 QMouseEvent* mouse_event =
static_cast<QMouseEvent*
>(event);
1601 if (mouse_event->button() == Qt::LeftButton)
1603 const QPoint press_point = mouse_event->pos();
1604 if (mouse_event->modifiers() == Qt::ShiftModifier)
1611 else if (mouse_event->modifiers() == Qt::ControlModifier)
1617 else if (mouse_event->buttons() == Qt::MiddleButton &&
1618 mouse_event->modifiers() == Qt::NoModifier)
1623 else if (mouse_event->button() == Qt::RightButton)
1625 if (mouse_event->modifiers() == Qt::NoModifier)
1634 case QEvent::MouseMove: {
1640 QMouseEvent* mouse_event =
static_cast<QMouseEvent*
>(event);
1642 if (mouse_event->buttons() == Qt::LeftButton &&
1643 mouse_event->modifiers() == Qt::ShiftModifier)
1645 const QPoint point = mouse_event->pos();
1654 case QEvent::Leave: {
1659 case QEvent::MouseButtonRelease: {
1662 QApplication::restoreOverrideCursor();
1677 double min = std::numeric_limits<double>::max();
1678 double max = std::numeric_limits<double>::lowest();
1682 if (data.
size() > 0)
1684 double A = data.
front().x;
1685 double B = data.
back().x;
1686 min = std::min(A, min);
1687 max = std::max(B, max);
1707 catch (std::runtime_error& ex)
1711 QMessageBox msgBox(
qwtPlot());
1712 msgBox.setWindowTitle(
"Warnings");
1713 msgBox.setText(tr(
"The creation of the XY plot failed with the following " 1716 msgBox.addButton(
"Continue", QMessageBox::AcceptRole);
1719 throw std::runtime_error(
"Creation of XY plot failed");
1727 const QString& transform_ID)
double invTransform(double p) const
QString selectedColorMap() const
void enableX(bool)
Enable or disable vertical grid lines.
A plot item, that represents a series of points.
void setFont(const QFont &)
virtual QwtText label(double v) const
Convert a value into its representing label.
void enableYMin(bool)
Enable or disable minor horizontal grid lines.
std::map< QString, ColorMap::Ptr > & ColorMapLibrary()
void setTimeOffset(double offset)
virtual void render(QwtPlot *, QPainter *, const QRectF &plotRect) const
double invTransform(QwtAxisId, double pos) const
auto arg(const Char *name, const T &arg) -> detail::named_arg< Char, T >
QwtSeriesData< T > * data()
A class for drawing symbols.
Renderer for exporting a plot to a document, a printer or anything else, that is supported by QPainte...
const QPixmap & LoadSvg(QString filename, QString style_name="light")
void enableXMin(bool)
Enable or disable minor vertical grid lines.
void setPen(const QColor &, qreal width=0.0, Qt::PenStyle=Qt::SolidLine)
void enableY(bool)
Enable or disable horizontal grid lines.
TimeseriesMap numeric
Numerical timeseries.
virtual size_t size() const
void setAxisScaleDraw(QwtAxisId, QwtScaleDraw *)
Set a scale draw.
NLOHMANN_BASIC_JSON_TPL_DECLARATION void swap(nlohmann::NLOHMANN_BASIC_JSON_TPL &j1, nlohmann::NLOHMANN_BASIC_JSON_TPL &j2) noexcept(//NOLINT(readability-inconsistent-declaration-parameter-name) is_nothrow_move_constructible< nlohmann::NLOHMANN_BASIC_JSON_TPL >::value &&//NOLINT(misc-redundant-expression) is_nothrow_move_assignable< nlohmann::NLOHMANN_BASIC_JSON_TPL >::value)
exchanges the values of two JSON objects
const Point & front() const
A class which draws a coordinate grid.
A class representing a text.
SnippetsMap GetSnippetsFromXML(const QString &xml_text)
void setFooter(const QString &)
void setTitle(QString title)
const PlotData * dataX() const
const PlotData * dataY() const
void setEnabled(bool enable)
int QwtAxisId
Axis identifier.
virtual void updateCache(bool reset_old_data)
virtual void setVisible(bool)
void attach(QwtPlot *plot)
Attach the item to a plot.
QString suggestedName() const
const std::string & plotName() const
void setAxisScale(QwtAxisId, double min, double max, double stepSize=0)
Disable autoscaling and specify a fixed scale for a selected axis.
const Point & back() const
virtual QwtScaleMap canvasMap(QwtAxisId) const
A class for drawing scales.
span_constexpr std::size_t size(span< T, Extent > const &spn)
void setPosition(const QPointF &pos)
static const char * output
void setParameter(Parameter par)
void updateAxes()
Rebuild the axes scales.
std::basic_string< Char > format(const text_style &ts, const S &format_str, const Args &... args)
A class for drawing markers.
const QwtScaleDraw * axisScaleDraw(QwtAxisId) const
Return the scale draw of a specified axis.