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)
81 , _transform_select_dialog(
nullptr)
82 , _context_menu_enabled(
true)
85 [
this]() { this->updateMaximumZoomArea(); });
87 qwtPlot()->setAcceptDrops(
true);
93 _grid->setPen(QPen(Qt::gray, 0.0, Qt::DotLine));
102 connect(
this, &PlotWidgetBase::widgetResized,
this, [
this]() {
103 if (isXYPlot() && keepRatioXY())
105 rescaleEqualAxisScaling();
146 QIcon iconDeleteList;
149 connect(
_action_edit, &QAction::triggered,
this, [=]() {
151 editor_dialog->exec();
152 editor_dialog->deleteLater();
158 int res = editor_dialog->exec();
159 editor_dialog->deleteLater();
160 if (res == QDialog::Accepted)
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");
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();
534 bool curves_changed =
false;
540 size_t scatter_count = 0;
546 if (scatter_count > 0 && !scatter_curves)
550 QMessageBox::warning(
qwtPlot(),
"Warning",
551 "You can not drag XY (scatter) data and timeseries into the "
564 QMessageBox::warning(
qwtPlot(),
"Warning",
565 tr(
"This is a [XY plot], you can not drop a timeseries here.\n"
566 "To convert this widget into a [timeseries plot], "
567 "you must first remove all its curves."));
571 QMessageBox::warning(
qwtPlot(),
"Warning",
572 tr(
"This is a [timeseries plot], you can not "
573 "drop XY scatter data here.\n"
574 "To convert this widget into a [XY plot], "
575 "you must first remove all its curves."));
587 bool added =
addCurve(curve_name.toStdString()) !=
nullptr;
588 curves_changed = curves_changed || added;
597 QMessageBox::warning(
qwtPlot(),
"Warning",
598 tr(
"This is a [timeseries plot], you can not "
599 "drop XY scatter data here.\n"
600 "To convert this widget into a [XY plot], "
601 "you must first remove all its curves."));
608 curves_changed =
true;
617 bool autozoom_curve_added =
618 settings.value(
"Preferences::autozoom_curve_added",
true).toBool();
619 if (autozoom_curve_added || noCurves)
639 QDomElement plot_el = doc.createElement(
"plot");
641 QDomElement range_el = doc.createElement(
"range");
643 range_el.setAttribute(
"bottom", QString::number(rect.bottom(),
'f', 6));
644 range_el.setAttribute(
"top", QString::number(rect.top(),
'f', 6));
645 range_el.setAttribute(
"left", QString::number(rect.left(),
'f', 6));
646 range_el.setAttribute(
"right", QString::number(rect.right(),
'f', 6));
647 plot_el.appendChild(range_el);
649 QDomElement limitY_el = doc.createElement(
"limitY");
658 plot_el.appendChild(limitY_el);
662 plot_el.setAttribute(
"style",
"Lines");
664 else if (
curveStyle() == PlotWidgetBase::LINES_AND_DOTS)
666 plot_el.setAttribute(
"style",
"LinesAndDots");
668 else if (
curveStyle() == PlotWidgetBase::DOTS)
670 plot_el.setAttribute(
"style",
"Dots");
672 else if (
curveStyle() == PlotWidgetBase::STICKS)
674 plot_el.setAttribute(
"style",
"Sticks");
676 else if (
curveStyle() == PlotWidgetBase::STEPS)
678 plot_el.setAttribute(
"style",
"Steps");
680 else if (
curveStyle() == PlotWidgetBase::STEPSINV)
682 plot_el.setAttribute(
"style",
"StepsInv");
687 auto& name = it.src_name;
689 QDomElement curve_el = doc.createElement(
"curve");
690 curve_el.setAttribute(
"name", QString::fromStdString(name));
691 curve_el.setAttribute(
"color", curve->
pen().color().name());
693 plot_el.appendChild(curve_el);
699 curve_el.setAttribute(
"curve_x", QString::fromStdString(xy->dataX()->plotName()));
700 curve_el.setAttribute(
"curve_y", QString::fromStdString(xy->dataY()->plotName()));
706 if (ts && ts->transform())
708 QDomElement transform_el = doc.createElement(
"transform");
709 transform_el.setAttribute(
"name", ts->transformName());
710 transform_el.setAttribute(
"alias", ts->alias());
711 ts->
transform()->xmlSaveState(doc, transform_el);
712 curve_el.appendChild(transform_el);
717 plot_el.setAttribute(
"mode",
isXYPlot() ?
"XYPlot" :
"TimeSeries");
719 plot_el.setAttribute(
"flip_x",
isXYPlot() &&
_flip_x->isChecked() ?
"true" :
"false");
720 plot_el.setAttribute(
"flip_y",
_flip_y->isChecked() ?
"true" :
"false");
725 plot_el.setAttribute(
"background_colormap",
_background_item->colormapName());
733 std::set<std::string> added_curve_names;
735 QString mode = plot_widget.attribute(
"mode");
738 _flip_x->setChecked(plot_widget.attribute(
"flip_x") ==
"true");
739 _flip_y->setChecked(plot_widget.attribute(
"flip_y") ==
"true");
741 QDomElement limitY_el = plot_widget.firstChildElement(
"limitY");
746 if (!limitY_el.isNull())
748 if (limitY_el.hasAttribute(
"min"))
752 if (limitY_el.hasAttribute(
"max"))
758 static bool warning_message_shown =
false;
769 QStringList missing_curves;
770 for (QDomElement curve_element = plot_widget.firstChildElement(
"curve");
771 !curve_element.isNull(); curve_element = curve_element.nextSiblingElement(
"curve"))
774 curve_element.hasAttribute(
"curve_x") && curve_element.hasAttribute(
"curve_y");
776 bool is_scatter_xy = !is_timeseries && !is_merged_xy;
778 QString curve_name = curve_element.attribute(
"name");
779 std::string curve_name_std = curve_name.toStdString();
780 QColor
color(curve_element.attribute(
"color"));
783 if (is_timeseries || is_scatter_xy)
788 missing_curves.append(curve_name);
797 auto& curve = curve_info->curve;
798 curve->setPen(
color, 1.3);
799 added_curve_names.insert(curve_name_std);
802 QDomElement transform_el = curve_element.firstChildElement(
"transform");
803 if (ts && transform_el.isNull() ==
false)
806 ts->transform()->xmlLoadState(transform_el);
807 ts->updateCache(
true);
808 auto alias = transform_el.attribute(
"alias");
810 curve->setTitle(alias);
817 std::string curve_x = curve_element.attribute(
"curve_x").toStdString();
818 std::string curve_y = curve_element.attribute(
"curve_y").toStdString();
822 missing_curves.append(curve_name);
826 auto curve_it =
addCurveXY(curve_x, curve_y, curve_name);
831 curve_it->curve->setPen(
color, 1.3);
832 curve_it->marker->setSymbol(
834 added_curve_names.insert(curve_name_std);
839 if (missing_curves.size() > 0 && !warning_message_shown)
841 QMessageBox::warning(
qwtPlot(),
"Warning",
842 tr(
"Can't find one or more curves.\n"
843 "This message will be shown only once.\n%1")
844 .
arg(missing_curves.join(
",\n")));
845 warning_message_shown =
true;
852 QDomElement rectangle = plot_widget.firstChildElement(
"range");
854 if (!rectangle.isNull() && autozoom)
857 rect.setBottom(rectangle.attribute(
"bottom").toDouble());
858 rect.setTop(rectangle.attribute(
"top").toDouble());
859 rect.setLeft(rectangle.attribute(
"left").toDouble());
860 rect.setRight(rectangle.attribute(
"right").toDouble());
864 if (plot_widget.hasAttribute(
"style"))
866 QString style = plot_widget.attribute(
"style");
867 if (style ==
"Lines")
871 else if (style ==
"LinesAndDots")
875 else if (style ==
"Dots")
879 else if (style ==
"Sticks")
883 else if (style ==
"Steps")
887 else if (style ==
"StepsInv")
893 QString bg_data = plot_widget.attribute(
"background_data");
894 QString bg_colormap = plot_widget.attribute(
"background_colormap");
896 if (!bg_data.isEmpty() && !bg_colormap.isEmpty())
899 if (plot_it ==
datamap().numeric.end())
901 QMessageBox::warning(
qwtPlot(),
"Warning",
902 tr(
"Can't restore the background color.\n"
903 "Series [%1] not found.")
911 QMessageBox::warning(
qwtPlot(),
"Warning",
912 tr(
"Can't restore the background color.\n"
913 "ColorMap [%1] not found.")
920 std::make_unique<BackgroundColorItem>(plot_it->second, bg_colormap);
940 const double canvas_ratio = std::abs(canvas_rect.width() / canvas_rect.height());
941 const double max_ratio = std::abs(max_rect.width() / max_rect.height());
943 QRectF rect = max_rect;
945 if (max_ratio < canvas_ratio)
947 double new_width = (-max_rect.height() * canvas_ratio);
948 rect.setWidth(new_width);
952 double new_height = (-max_rect.width() / canvas_ratio);
953 rect.setHeight(new_height);
956 rect.moveCenter(max_rect.center());
999 if (it.curve->isVisible())
1004 const auto& curve_name = it.src_name;
1011 ts->updateCache(
true);
1057 if (
auto series =
dynamic_cast<QwtTimeseries*
>(it.curve->data()))
1059 auto pointXY = series->sampleFromTime(abs_time);
1062 it.marker->setValue(pointXY.value());
1079 if (fabs(prev_offset - offset) > std::numeric_limits<double>::epsilon())
1083 if (
auto series =
dynamic_cast<QwtTimeseries*
>(it.curve->data()))
1091 double delta = prev_offset - offset;
1092 rect.moveLeft(rect.left() + delta);
1124 auto [bottom,
top] = PlotWidgetBase::getVisualizationRangeY(range_X);
1178 if (it.curve->title() == curve_name)
1180 auto& curve = it.curve;
1181 if (curve->pen().color() != new_color)
1183 curve->setPen(new_color, 1.3);
1202 canvas_rect = canvas_rect.normalized();
1218 QString prev_colormap;
1233 if (plot_it ==
datamap().numeric.end())
1245 auto ret = dialog.exec();
1246 if (
ret == QDialog::Accepted)
1257 _background_item = std::make_unique<BackgroundColorItem>(plot_it->second, colormap);
1301 connect(
this, &PlotWidgetBase::curveListChanged,
this,
1308 if (current_rect == rect)
1323 QRectF rect(0, 1, 1, -1);
1339 act.setLeft(rangeX.min);
1340 act.setRight(rangeX.max);
1350 rect.setBottom(rangeY.min);
1351 rect.setTop(rangeY.max);
1361 PlotWidgetBase::setModeXY(
enable);
1368 font_footer.setPointSize(10);
1370 text.setFont(font_footer);
1386 QByteArray xml_text =
1387 settings.value(
"AddCustomPlotDialog.savedXML", QByteArray()).toByteArray();
1388 if (!xml_text.isEmpty())
1398 QFileDialog saveDialog(
qwtPlot());
1399 saveDialog.setAcceptMode(QFileDialog::AcceptSave);
1401 QStringList filters;
1402 filters <<
"png (*.png)"
1403 <<
"jpg (*.jpg *.jpeg)"
1406 saveDialog.setNameFilters(filters);
1409 if (saveDialog.result() == QDialog::Accepted && !saveDialog.selectedFiles().empty())
1411 fileName = saveDialog.selectedFiles().first();
1413 if (fileName.isEmpty())
1418 bool is_svg =
false;
1419 QFileInfo fileinfo(fileName);
1420 if (fileinfo.suffix().isEmpty())
1422 auto filter = saveDialog.selectedNameFilter();
1423 if (filter == filters[0])
1425 fileName.append(
".png");
1427 else if (filter == filters[1])
1429 fileName.append(
".jpg");
1431 else if (filter == filters[2])
1433 fileName.append(
".svg");
1439 if (tracker_enabled)
1445 QRect documentRect(0, 0, 1200, 900);
1450 QSvgGenerator generator;
1451 generator.setFileName(fileName);
1452 generator.setResolution(80);
1453 generator.setViewBox(documentRect);
1454 QPainter painter(&generator);
1459 QPixmap pixmap(1200, 900);
1460 QPainter painter(&pixmap);
1462 pixmap.save(fileName);
1465 if (tracker_enabled)
1488 if (tracker_enabled)
1495 qDebug() << documentRect;
1498 QPixmap pixmap(documentRect.width(), documentRect.height());
1499 QPainter painter(&pixmap);
1502 QClipboard* clipboard = QGuiApplication::clipboard();
1503 clipboard->setPixmap(pixmap);
1505 if (tracker_enabled)
1515 auto root = doc.createElement(
"PlotWidgetClipBoard");
1517 doc.appendChild(root);
1518 root.appendChild(el);
1520 QClipboard* clipboard = QGuiApplication::clipboard();
1521 clipboard->setText(doc.toString());
1526 QClipboard* clipboard = QGuiApplication::clipboard();
1527 QString clipboard_text = clipboard->text();
1530 bool valid = doc.setContent(clipboard_text);
1535 auto root = doc.firstChildElement();
1536 if (root.tagName() !=
"PlotWidgetClipBoard")
1542 auto el = root.firstChildElement();
1550 if (PlotWidgetBase::eventFilter(obj, event))
1555 if (event->type() == QEvent::Destroy)
1560 if (obj ==
qwtPlot()->canvas())
1570 QString theme = settings.value(
"Preferences::theme",
"light").toString();
1571 auto pixmap =
LoadSvg(
":/resources/svg/move_view.svg", theme);
1572 QApplication::setOverrideCursor(QCursor(pixmap.scaled(24, 24)));
1610 switch (event->type())
1612 case QEvent::MouseButtonPress: {
1618 QMouseEvent* mouse_event =
static_cast<QMouseEvent*
>(event);
1620 if (mouse_event->button() == Qt::LeftButton)
1622 const QPoint press_point = mouse_event->pos();
1623 if (mouse_event->modifiers() == Qt::ShiftModifier)
1630 else if (mouse_event->modifiers() == Qt::ControlModifier)
1636 else if (mouse_event->buttons() == Qt::MiddleButton &&
1637 mouse_event->modifiers() == Qt::NoModifier)
1642 else if (mouse_event->button() == Qt::RightButton)
1644 if (mouse_event->modifiers() == Qt::NoModifier)
1653 case QEvent::MouseMove: {
1659 QMouseEvent* mouse_event =
static_cast<QMouseEvent*
>(event);
1661 if (mouse_event->buttons() == Qt::LeftButton &&
1662 mouse_event->modifiers() == Qt::ShiftModifier)
1664 const QPoint point = mouse_event->pos();
1673 case QEvent::Leave: {
1678 case QEvent::MouseButtonRelease: {
1681 QApplication::restoreOverrideCursor();
1696 double min = std::numeric_limits<double>::max();
1697 double max = std::numeric_limits<double>::lowest();
1701 if (
data.size() > 0)
1703 double A =
data.front().x;
1704 double B =
data.back().x;
1705 min = std::min(A, min);
1706 max = std::max(
B, max);
1726 catch (std::runtime_error& ex)
1730 QMessageBox msgBox(
qwtPlot());
1731 msgBox.setWindowTitle(
"Warnings");
1732 msgBox.setText(tr(
"The creation of the XY plot failed with the following "
1735 msgBox.addButton(
"Continue", QMessageBox::AcceptRole);
1738 throw std::runtime_error(
"Creation of XY plot failed");
1746 const QString& transform_ID)
1749 output->setTransform(transform_ID);
1751 output->updateCache(
true);