2 #include <QActionGroup> 3 #include <QApplication> 6 #include <QDragEnterEvent> 7 #include <QDragMoveEvent> 10 #include <QMessageBox> 14 #include <QPushButton> 15 #include <QWheelEvent> 17 #include <QSvgGenerator> 23 #include <QtXml/QDomElement> 50 bool remember_color = settings.value(
"Preferences::remember_color",
true).toBool();
51 if (data && remember_color && data->
getColorHint() != Qt::black)
56 bool use_plot_color_index = settings.value(
"Preferences::use_plot_color_index",
false).toBool();
59 if (!use_plot_color_index)
68 color = QColor(
"#1f77b4");
71 color = QColor(
"#d62728");
74 color = QColor(
"#1ac938");
77 color = QColor(
"#ff7f0e");
81 color = QColor(
"#f14cc1");
84 color = QColor(
"#9467bd");
87 color = QColor(
"#17becf");
90 color = QColor(
"#bcbd22");
105 QDateTime dt = QDateTime::fromMSecsSinceEpoch((qint64)(v * 1000));
106 if (dt.date().year() == 1970 && dt.date().month() == 1 && dt.date().day() == 1)
108 return dt.toString(
"hh:mm:ss.z");
110 return dt.toString(
"hh:mm:ss.z\nyyyy MMM dd");
145 this->setAcceptDrops(
true);
147 this->setMinimumWidth(100);
148 this->setMinimumHeight(100);
150 this->sizePolicy().setHorizontalPolicy(QSizePolicy::Expanding);
151 this->sizePolicy().setVerticalPolicy(QSizePolicy::Expanding);
155 canvas->setFrameStyle(QFrame::NoFrame);
157 canvas->setFrameStyle( QFrame::Box | QFrame::Plain );
158 canvas->setLineWidth( 1 );
159 canvas->setPalette( Qt::white );
212 this->
canvas()->setMouseTracking(
true);
223 bottomAxis->installEventFilter(
this);
224 leftAxis->installEventFilter(
this);
234 QIcon iconDeleteList;
241 editor_dialog->exec();
242 editor_dialog->deleteLater();
246 connect(_action_formula, &QAction::triggered,
this,
250 int res = editor_dialog->exec();
251 editor_dialog->deleteLater();
252 if( res == QDialog::Accepted){
275 connect(_action_zoomOutHorizontally, &QAction::triggered,
this, [
this]() {
282 connect(_action_zoomOutVertically, &QAction::triggered,
this, [
this]() {
289 font.setPointSize(10);
313 QString theme = settings.value(
"StyleSheet::theme",
"light").toString();
348 QClipboard *clipboard = QGuiApplication::clipboard();
349 QString clipboard_text = clipboard->text();
351 bool valid_clipbaord =
352 ( !clipboard_text.isEmpty() &&
353 doc.setContent(clipboard_text) &&
354 doc.firstChildElement().tagName() ==
"PlotWidgetClipBoard");
361 menu.exec(
canvas()->mapToGlobal(pos));
377 const auto qname = QString::fromStdString(name);
395 curve->setData(plot_qwt);
397 catch (std::exception& ex)
399 QMessageBox::warning(
this,
"Exception!", ex.what());
403 if( color == Qt::transparent ){
418 marker->setSymbol(sym);
421 curve_info.
curve = curve;
422 curve_info.
marker = marker;
431 std::string
name = curve_name.toStdString();
437 bool ok = (dialog.exec() == QDialog::Accepted);
439 name = curve_name.toStdString();
440 name_x = dialog.
nameX().toStdString();
441 name_y = dialog.
nameY().toStdString();
450 if (name.empty() || curve_it )
452 int ret = QMessageBox::warning(
this,
"Missing name",
453 "The name of the curve is missing or exist already. Try again or abort.",
454 QMessageBox::Abort | QMessageBox::Retry, QMessageBox::Retry);
455 if (ret == QMessageBox::Abort)
466 throw std::runtime_error(
"Creation of XY plot failed");
473 throw std::runtime_error(
"Creation of XY plot failed");
483 const auto qname = QString::fromStdString(name);
492 curve->setData(plot_qwt);
494 catch (std::exception& ex)
496 QMessageBox::warning(
this,
"Exception!", ex.what());
513 marker->setSymbol(sym);
516 curve_info.
curve = curve;
517 curve_info.
marker = marker;
529 return info.curve->title() ==
title;
535 it->marker->detach();
546 bool deleted =
false;
551 bool remove_curve_xy =
552 curve_xy && (curve_xy->
dataX()->
name() == src_name || curve_xy->
dataY()->
name() == src_name);
554 if (it->src_name == src_name || remove_curve_xy)
559 it->marker->detach();
595 return info.curve->title() ==
title;
602 return info.src_name == title.toStdString();
619 return info.curve->title() ==
title;
626 return info.src_name == title.toStdString();
638 const QMimeData* mimeData =
event->mimeData();
639 QStringList mimeFormats = mimeData->formats();
643 for (
const QString&
format : mimeFormats)
645 QByteArray encoded = mimeData->data(
format);
646 QDataStream stream(&encoded, QIODevice::ReadOnly);
648 while (!stream.atEnd())
651 stream >> curve_name;
652 if (!curve_name.isEmpty())
658 if (
format ==
"curveslist/add_curve")
661 event->acceptProposedAction();
663 if (
format ==
"curveslist/new_XY_axis")
667 qDebug() <<
"FATAL: Dragging " <<
_dragging.
curves.size() <<
" curves";
673 event->acceptProposedAction();
690 bool curves_changed =
false;
698 QMessageBox::warning(
this,
"Warning",
699 tr(
"This is a XY plot, you can not drop normal time series here.\n" 700 "Clear all curves to reset it to normal mode."));
710 bool added =
addCurve(curve_name.toStdString()) !=
nullptr;
711 curves_changed = curves_changed || added;
720 QMessageBox::warning(
this,
"Warning",
721 tr(
"To convert this widget into a XY plot, " 722 "you must first remove all the time series."));
730 curves_changed =
true;
767 QDomElement plot_el = doc.createElement(
"plot");
769 QDomElement range_el = doc.createElement(
"range");
771 range_el.setAttribute(
"bottom", QString::number(rect.bottom(),
'f', 6));
772 range_el.setAttribute(
"top", QString::number(rect.top(),
'f', 6));
773 range_el.setAttribute(
"left", QString::number(rect.left(),
'f', 6));
774 range_el.setAttribute(
"right", QString::number(rect.right(),
'f', 6));
775 plot_el.appendChild(range_el);
777 QDomElement limitY_el = doc.createElement(
"limitY");
786 plot_el.appendChild(limitY_el);
790 plot_el.setAttribute(
"style",
"Lines");
794 plot_el.setAttribute(
"style",
"LinesAndDots");
798 plot_el.setAttribute(
"style",
"Dots");
803 auto&
name = it.src_name;
805 QDomElement curve_el = doc.createElement(
"curve");
806 curve_el.setAttribute(
"name", QString::fromStdString(
name));
807 curve_el.setAttribute(
"color", curve->
pen().color().name());
809 plot_el.appendChild(curve_el);
814 curve_el.setAttribute(
"curve_x", QString::fromStdString(curve_xy->
dataX()->
name()));
815 curve_el.setAttribute(
"curve_y", QString::fromStdString(curve_xy->
dataY()->
name()));
819 if(ts && ts->transform())
821 QDomElement transform_el = doc.createElement(
"transform");
822 transform_el.setAttribute(
"name", ts->transformName() );
823 transform_el.setAttribute(
"alias", ts->transform()->alias() );
824 ts->
transform()->xmlSaveState(doc, transform_el);
825 curve_el.appendChild(transform_el);
830 plot_el.setAttribute(
"mode",
isXYPlot() ?
"XYPlot" :
"TimeSeries");
837 std::set<std::string> added_curve_names;
839 QString mode = plot_widget.attribute(
"mode");
842 QDomElement limitY_el = plot_widget.firstChildElement(
"limitY");
847 if (!limitY_el.isNull())
849 if (limitY_el.hasAttribute(
"min"))
853 if (limitY_el.hasAttribute(
"max"))
859 static bool warning_message_shown =
false;
870 for (QDomElement curve_element = plot_widget.firstChildElement(
"curve"); !curve_element.isNull();
871 curve_element = curve_element.nextSiblingElement(
"curve"))
873 QString curve_name = curve_element.attribute(
"name");
874 std::string curve_name_std = curve_name.toStdString();
875 QColor color( curve_element.attribute(
"color"));
886 auto curve_it =
addCurve(curve_name_std, color);
891 auto &curve = curve_it->curve;
892 curve->setPen(color, 1.3);
893 added_curve_names.insert(curve_name_std);
896 QDomElement transform_el = curve_element.firstChildElement(
"transform");
897 if( transform_el.isNull() == false )
900 ts->transform()->xmlLoadState(transform_el);
901 ts->updateCache(
true);
902 auto alias = transform_el.attribute(
"alias");
903 ts->transform()->setAlias( alias );
904 curve->setTitle( alias );
910 std::string curve_x = curve_element.attribute(
"curve_x").toStdString();
911 std::string curve_y = curve_element.attribute(
"curve_y").toStdString();
920 auto curve_it =
addCurveXY(curve_x, curve_y, curve_name);
925 curve_it->curve->setPen(color, 1.3);
926 added_curve_names.insert(curve_name_std);
930 if (error && !warning_message_shown)
932 QMessageBox::warning(
this,
"Warning",
933 tr(
"Can't find one or more curves.\n" 934 "This message will be shown only once."));
935 warning_message_shown =
true;
944 QDomElement rectangle = plot_widget.firstChildElement(
"range");
946 if (!rectangle.isNull())
949 rect.setBottom(rectangle.attribute(
"bottom").toDouble());
950 rect.setTop(rectangle.attribute(
"top").toDouble());
951 rect.setLeft(rectangle.attribute(
"left").toDouble());
952 rect.setRight(rectangle.attribute(
"right").toDouble());
956 if (plot_widget.hasAttribute(
"style"))
958 QString style = plot_widget.attribute(
"style");
959 if (style ==
"Lines")
963 else if (style ==
"LinesAndDots")
967 else if (style ==
"Dots")
993 max_rect.setLeft(rangeX.min);
994 max_rect.setRight(rangeX.max);
997 max_rect.setBottom(rangeY.min);
998 max_rect.setTop(rangeY.max);
1002 const QRectF canvas_rect =
canvas()->contentsRect();
1003 const double canvas_ratio = fabs(canvas_rect.width() / canvas_rect.height());
1004 const double data_ratio = fabs(max_rect.width() / max_rect.height());
1005 if (data_ratio < canvas_ratio)
1008 double new_width = fabs(max_rect.height() * canvas_ratio);
1009 double increment = new_width - max_rect.width();
1010 max_rect.setWidth(new_width);
1011 max_rect.moveLeft(max_rect.left() - 0.5 * increment);
1016 double new_height = -(max_rect.width() / canvas_ratio);
1017 double increment = fabs(new_height - max_rect.height());
1018 max_rect.setHeight(new_height);
1019 max_rect.moveTop(max_rect.top() + 0.5 * increment);
1039 QRectF canvas_rect =
canvas()->contentsRect();
1040 canvas_rect = canvas_rect.normalized();
1041 const double x1 = xMap.
invTransform(canvas_rect.left());
1042 const double x2 = xMap.
invTransform(canvas_rect.right());
1043 const double y1 = yMap.
invTransform(canvas_rect.bottom());
1044 const double y2 = yMap.
invTransform(canvas_rect.top());
1046 const double data_ratio = (x2 - x1) / (y2 - y1);
1047 const double canvas_ratio = canvas_rect.width() / canvas_rect.height();
1049 QRectF rect(QPointF(x1, y2), QPointF(x2, y1));
1051 if (data_ratio < canvas_ratio)
1053 double new_width = fabs(rect.height() * canvas_ratio);
1054 double increment = new_width - rect.width();
1055 rect.setWidth(new_width);
1056 rect.moveLeft(rect.left() - 0.5 * increment);
1060 double new_height = -(rect.width() / canvas_ratio);
1061 double increment = fabs(new_height - rect.height());
1062 rect.setHeight(new_height);
1063 rect.moveTop(rect.top() + 0.5 * increment);
1109 if (current_rect == rect)
1143 if (it.curve->isVisible()){
1147 const auto& curve_name = it.src_name;
1152 const auto&
data = data_it->second;
1153 QString transform_name;
1160 it.curve->setData(data_series);
1164 if (_curve_list.size() == 0 || visible == 0)
1209 it.marker->setValue(pointXY.value());
1225 if (fabs(prev_offset - offset) > std::numeric_limits<double>::epsilon())
1232 if (!
isXYPlot() && !_curve_list.empty())
1235 double delta = prev_offset - offset;
1236 rect.moveLeft(rect.left() + delta);
1270 if (!it.curve->isVisible())
1278 left =
std::min(max_range_X->min, left);
1279 right =
std::max(max_range_X->max, right);
1288 double margin = 0.0;
1289 if (fabs(right - left) > std::numeric_limits<double>::epsilon())
1293 right = right + margin;
1294 left = left - margin;
1307 if (!it.curve->isVisible())
1322 left = std::nextafter(left, right);
1323 right = std::nextafter(right, left);
1325 auto range_Y = series->getVisualizationRangeY({
left, right });
1328 qDebug() <<
" invalid range_Y in PlotWidget::maximumRangeY";
1331 if (top < range_Y->
max){
1334 if (bottom > range_Y->min){
1335 bottom = range_Y->min;
1339 double margin = 0.1;
1347 if (top - bottom > std::numeric_limits<double>::epsilon())
1349 margin = (top - bottom) * 0.025;
1359 top = bottom + margin;
1367 bottom = top - margin;
1371 if (!lower_limit && !upper_limit)
1377 return Range({ bottom, top });
1393 std::map<QString, QColor> color_by_name;
1397 color_by_name.insert( {it.curve->title().text(), it.curve->pen().color()} );
1399 return color_by_name;
1406 if (it.curve->title() == curve_name)
1408 auto& curve = it.curve;
1409 if (curve->pen().color() != new_color)
1411 curve->setPen(new_color, 1.3);
1424 auto& curve = it.curve;
1452 if (current_rect == rect)
1471 QRectF rect(0, 1, 1, -1);
1486 act.setLeft(rangeX.min);
1487 act.setRight(rangeX.max);
1497 rect.setBottom(rangeY.min);
1498 rect.setTop(rangeY.max);
1549 font_footer.setPointSize(10);
1566 QByteArray xml_text = settings.value(
"AddCustomPlotDialog.savedXML", QByteArray()).toByteArray();
1567 if (!xml_text.isEmpty())
1575 std::string error_message;
1628 QFileDialog saveDialog(
this);
1629 saveDialog.setAcceptMode(QFileDialog::AcceptSave);
1631 QStringList filters;
1632 filters <<
"png (*.png)" 1633 <<
"jpg (*.jpg *.jpeg)" 1636 saveDialog.setNameFilters(filters);
1639 if (saveDialog.result() == QDialog::Accepted && !saveDialog.selectedFiles().empty())
1641 fileName = saveDialog.selectedFiles().first();
1643 if (fileName.isEmpty())
1648 bool is_svg =
false;
1649 QFileInfo fileinfo(fileName);
1650 if (fileinfo.suffix().isEmpty())
1652 auto filter = saveDialog.selectedNameFilter();
1653 if (filter == filters[0])
1655 fileName.append(
".png");
1657 else if (filter == filters[1])
1659 fileName.append(
".jpg");
1661 else if (filter == filters[2])
1663 fileName.append(
".svg");
1669 if (tracker_enabled)
1675 QRect documentRect(0, 0, 1200, 900);
1680 QSvgGenerator generator;
1681 generator.setFileName(fileName);
1682 generator.setResolution(80);
1683 generator.setViewBox(documentRect);
1684 QPainter painter(&generator);
1685 rend.
render(
this, &painter, documentRect);
1689 QPixmap pixmap(1200, 900);
1690 QPainter painter(&pixmap);
1691 rend.
render(
this, &painter, documentRect);
1692 pixmap.save(fileName);
1695 if (tracker_enabled)
1718 if (tracker_enabled)
1724 auto documentRect = this->
canvas()->rect();
1725 qDebug() << documentRect;
1728 QPixmap pixmap(documentRect.width(), documentRect.height());
1729 QPainter painter(&pixmap);
1730 rend.
render(
this, &painter, documentRect);
1732 QClipboard* clipboard = QGuiApplication::clipboard();
1733 clipboard->setPixmap(pixmap);
1735 if (tracker_enabled)
1745 auto root = doc.createElement(
"PlotWidgetClipBoard");
1747 doc.appendChild(root);
1748 root.appendChild(el);
1750 QClipboard *clipboard = QGuiApplication::clipboard();
1751 clipboard->setText( doc.toString() );
1756 QClipboard *clipboard = QGuiApplication::clipboard();
1757 QString clipboard_text = clipboard->text();
1760 bool valid = doc.setContent(clipboard_text);
1764 auto root = doc.firstChildElement();
1765 if( root.tagName() !=
"PlotWidgetClipBoard")
1770 auto el = root.firstChildElement();
1772 clipboard->setText(
"");
1784 if (event->type() == QEvent::Wheel)
1786 auto wheel_event =
dynamic_cast<QWheelEvent*
>(
event);
1787 if (obj == bottomAxis)
1814 QString theme = settings.value(
"Preferences::theme",
"light").toString();
1815 QPixmap pixmap(tr(
":/style_%1/move.png").
arg(theme));
1816 QApplication::setOverrideCursor(QCursor(pixmap.scaled(24, 24)));
1821 switch (event->type())
1823 case QEvent::Wheel: {
1824 auto mouse_event =
dynamic_cast<QWheelEvent*
>(
event);
1826 bool ctrl_modifier = mouse_event->modifiers() == Qt::ControlModifier;
1834 int new_size = prev_size;
1835 if (mouse_event->delta() > 0)
1837 new_size =
std::min(13, prev_size+1);
1839 if (mouse_event->delta() < 0)
1841 new_size =
std::max(7, prev_size-1);
1843 if( new_size != prev_size)
1854 case QEvent::MouseButtonPress: {
1860 QMouseEvent* mouse_event =
static_cast<QMouseEvent*
>(
event);
1862 if (mouse_event->button() == Qt::LeftButton)
1864 const QPoint press_point = mouse_event->pos();
1865 if (mouse_event->modifiers() == Qt::ShiftModifier)
1871 else if (mouse_event->modifiers() == Qt::ControlModifier)
1882 if (clicked_item == it.curve)
1884 it.curve->
setVisible(!it.curve->isVisible());
1895 else if (mouse_event->buttons() == Qt::MidButton && mouse_event->modifiers() == Qt::NoModifier)
1900 else if (mouse_event->button() == Qt::RightButton)
1902 if (mouse_event->modifiers() == Qt::NoModifier)
1911 case QEvent::MouseMove: {
1917 QMouseEvent* mouse_event =
static_cast<QMouseEvent*
>(
event);
1919 if (mouse_event->buttons() == Qt::LeftButton && mouse_event->modifiers() == Qt::ShiftModifier)
1921 const QPoint point = mouse_event->pos();
1929 case QEvent::Leave: {
1936 case QEvent::MouseButtonRelease: {
1939 QApplication::restoreOverrideCursor();
1945 case QEvent::Enter: {
1973 if (data.
size() > 0)
1975 double A = data.
front().
x;
1976 double B = data.
back().
x;
1996 catch (std::runtime_error& ex)
2000 QMessageBox msgBox(
this);
2001 msgBox.setWindowTitle(
"Warnings");
2002 msgBox.setText(tr(
"The creation of the XY plot failed with the following message:\n %1").
arg(ex.what()));
2003 msgBox.addButton(
"Continue", QMessageBox::AcceptRole);
2006 throw std::runtime_error(
"Creation of XY plot failed");
2034 font.setPointSize(size);
2070 static int replot_count = 0;
const Point & back() const
virtual RangeOpt getVisualizationRangeX()
const QwtPlotItem * processMousePressEvent(QMouseEvent *mouse_event)
void enableX(bool)
Enable or disable vertical grid lines.
A plot item, that represents a series of points.
FMT_INLINE std::basic_string< Char > format(const S &format_str, Args &&...args)
void setFont(const QFont &)
virtual QwtText label(double v) const
Convert a value into its representing label.
void setCanvasBackground(const QBrush &)
Change the background of the plotting area.
const Point & front() const
void setAxisEnabled(int axis, bool on)
En/Disable an axis.
QIcon LoadSvgIcon(QString filename, QString style_name="light")
void enableYMin(bool)
Enable or disable minor horizontal grid lines.
void setAxisScaleDraw(int axisId, QwtScaleDraw *)
Set a scale draw.
std::unordered_map< std::string, PlotData > numeric
void setCanvas(QWidget *)
Set the drawing canvas of the plot widget.
void setFont(const QFont &)
Y axis right of the canvas.
static l_noret error(LoadState *S, const char *why)
virtual QRect geometry(const QRectF &canvasRect) const
void setColorHint(QColor color)
virtual void replot()
Redraw the plot.
double invTransform(int axisId, int pos) const
QwtSeriesData< T > * data()
A class for drawing symbols.
Y axis left of the canvas.
Renderer for exporting a plot to a document, a printer or anything else, that is supported by QPainte...
void setAlignmentInCanvas(Qt::Alignment)
Set the alignmnet.
const QwtScaleWidget * axisWidget(int axisId) const
virtual bool updateCache(bool reset_old_data)=0
void enableXMin(bool)
Enable or disable minor vertical grid lines.
QBrush canvasBackground() const
void setPen(const QColor &, qreal width=0.0, Qt::PenStyle=Qt::SolidLine)
void enableY(bool)
Enable or disable horizontal grid lines.
QColor getColorHint() const
virtual void widgetWheelEvent(QWheelEvent *event) override
void setEnabled(bool)
En/disable the magnifier.
virtual void updateLayout()
Adjust plot content to its current size.
void setAxisAutoScale(int axisId, bool on=true)
Enable autoscaling for a specified axis.
void keepAspectRatio(bool doKeep)
void setDefaultMode(AxisMode mode)
void setZoomInKey(int key, Qt::KeyboardModifiers=Qt::NoModifier)
A class which draws a coordinate grid.
A class representing a text.
virtual QwtScaleMap canvasMap(int axisId) const
void setAlignCanvasToScales(bool)
Set the align-canvas-to-axis-scales flag for all axes.
SnippetsMap GetSnippetsFromXML(const QString &xml_text)
void rescaled(QRectF new_size)
const PlotData * dataX() const
void setFooter(const QString &)
void panned(int dx, int dy)
void setTimeOffset(double offset)
const PlotData * dataY() const
double invTransform(double p) const
void setEnabled(bool enable)
void setZoomOutKey(int key, Qt::KeyboardModifiers=Qt::NoModifier)
virtual size_t size() const
void setEnabled(bool)
En/disable the picker.
void setAxisScale(int axisId, double min, double max, double stepSize=0)
Disable autoscaling and specify a fixed scale for a selected axis.
detail::named_arg< Char, T > arg(const Char *name, const T &arg)
virtual nonstd::optional< QPointF > sampleFromTime(double t)=0
virtual void setVisible(bool)
virtual void setZoomBase(bool doReplot=true)
const QwtScaleDraw * axisScaleDraw(int axisId) const
Return the scale draw of a specified axis.
void attach(QwtPlot *plot)
Attach the item to a plot.
void setAxisLimits(int axis, double lower, double upper)
QwtPlotPanner provides panning of a plot canvas.
virtual bool event(QEvent *) QWT_OVERRIDE
Adds handling of layout requests.
const std::string & name() const
virtual void resizeEvent(QResizeEvent *) QWT_OVERRIDE
QwtScaleEngine * axisScaleEngine(int axisId)
QString suggestedName() const
void setMousePattern(MousePatternCode, Qt::MouseButton button, Qt::KeyboardModifiers=Qt::NoModifier)
A class for drawing scales.
void setPosition(const QPointF &pos)
void setMouseButton(Qt::MouseButton, Qt::KeyboardModifiers=Qt::NoModifier)
virtual void render(QwtPlot *, QPainter *, const QRectF &plotRect) const
void setParameter(Parameter par)
void setRubberBandPen(const QPen &)
void setMouseButton(Qt::MouseButton, Qt::KeyboardModifiers=Qt::NoModifier)
void setAttribute(Attribute, bool on=true)
void zoomed(const QRectF &rect)
void updateAxes()
Rebuild the axes scales.
void setEnabled(bool)
En/disable the panner.
void setTrackerPen(const QPen &)
QwtPlotLayout * plotLayout()
A class for drawing markers.