plotwidget.cpp
Go to the documentation of this file.
1 /*
2  * This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5  */
6 
7 #include <QAction>
8 #include <QActionGroup>
9 #include <QApplication>
10 #include <QDebug>
11 #include <QDrag>
12 #include <QDragEnterEvent>
13 #include <QDragMoveEvent>
14 #include <QFileDialog>
15 #include <QFileInfo>
16 #include <QMessageBox>
17 #include <QMenu>
18 #include <QMimeData>
19 #include <QPainter>
20 #include <QPushButton>
21 #include <QWheelEvent>
22 #include <QSettings>
23 #include <QSvgGenerator>
24 #include <QClipboard>
25 #include <iostream>
26 #include <limits>
27 #include <set>
28 #include <memory>
29 #include <QtXml/QDomElement>
30 #include "qwt_scale_widget.h"
31 #include "qwt_plot_canvas.h"
32 #include "qwt_plot_opengl_canvas.h"
33 #include "qwt_scale_engine.h"
34 #include "qwt_scale_map.h"
35 #include "qwt_plot_layout.h"
36 #include "qwt_scale_draw.h"
37 #include "qwt_text.h"
38 #include "plotwidget.h"
39 #include "qwt_plot_renderer.h"
40 #include "qwt_series_data.h"
41 #include "qwt_date_scale_draw.h"
42 #include "suggest_dialog.h"
44 #include "plotwidget_editor.h"
45 #include "plotwidget_transforms.h"
46 
47 #include "plotzoomer.h"
48 #include "plotmagnifier.h"
49 #include "plotlegend.h"
50 
51 #include "PlotJuggler/svg_util.h"
52 #include "point_series_xy.h"
53 #include "colormap_selector.h"
54 
55 #include "statistics_dialog.h"
56 
58 {
59  virtual QwtText label(double v) const
60  {
61  QDateTime dt = QDateTime::fromMSecsSinceEpoch((qint64)(v * 1000));
62  if (dt.date().year() == 1970 && dt.date().month() == 1 && dt.date().day() == 1)
63  {
64  return dt.toString("hh:mm:ss.z");
65  }
66  return dt.toString("hh:mm:ss.z\nyyyy MMM dd");
67  }
68 };
69 
70 const double MAX_DOUBLE = std::numeric_limits<double>::max() / 2;
71 
72 static bool if_xy_plot_failed_show_dialog = true;
73 
74 PlotWidget::PlotWidget(PlotDataMapRef& datamap, QWidget* parent)
75  : PlotWidgetBase(parent)
76  , _mapped_data(datamap)
77  , _tracker(nullptr)
78  , _use_date_time_scale(false)
79  , _dragging({ DragInfo::NONE, {}, nullptr })
80  , _time_offset(0.0)
81  , _transform_select_dialog(nullptr)
82  , _context_menu_enabled(true)
83 {
84  connect(this, &PlotWidget::curveListChanged, this,
85  [this]() { this->updateMaximumZoomArea(); });
86 
87  qwtPlot()->setAcceptDrops(true);
88 
89  //--------------------------
90  _tracker = (new CurveTracker(qwtPlot()));
91 
92  _grid = new QwtPlotGrid();
93  _grid->setPen(QPen(Qt::gray, 0.0, Qt::DotLine));
94 
95  connect(this, &PlotWidgetBase::viewResized, this, &PlotWidget::on_externallyResized);
96 
97  connect(this, &PlotWidgetBase::dragEnterSignal, this, &PlotWidget::onDragEnterEvent);
98  connect(this, &PlotWidgetBase::dragLeaveSignal, this, &PlotWidget::onDragLeaveEvent);
99 
100  connect(this, &PlotWidgetBase::dropSignal, this, &PlotWidget::onDropEvent);
101 
102  connect(this, &PlotWidgetBase::widgetResized, this, [this]() {
103  if (isXYPlot() && keepRatioXY())
104  {
106  }
107  });
108 
109  //-------------------------
110 
111  buildActions();
112 
115 
116  // QwtScaleWidget* bottomAxis = qwtPlot()->axisWidget( QwtPlot::xBottom );
117  // QwtScaleWidget* leftAxis = qwtPlot()->axisWidget( QwtPlot::yLeft );
118 
119  // bottomAxis->installEventFilter(this);
120  // leftAxis->installEventFilter(this);
121  // qwtPlot()->canvas()->installEventFilter(this);
122 }
123 
125 {
127  delete _action_split_vertical;
129  delete _action_zoomOutMaximum;
132  delete _action_saveToFile;
133  delete _action_copy;
134  delete _action_paste;
137 }
138 
140 {
141  _context_menu_enabled = enabled;
142 }
143 
145 {
146  QIcon iconDeleteList;
147 
148  _action_edit = new QAction("&Edit curves...", this);
149  connect(_action_edit, &QAction::triggered, this, [=]() {
150  auto editor_dialog = new PlotwidgetEditor(this, qwtPlot());
151  editor_dialog->exec();
152  editor_dialog->deleteLater();
153  });
154 
155  _action_formula = new QAction("&Apply filter to data...", this);
156  connect(_action_formula, &QAction::triggered, this, [=]() {
157  auto editor_dialog = new DialogTransformEditor(this);
158  int res = editor_dialog->exec();
159  editor_dialog->deleteLater();
160  if (res == QDialog::Accepted)
161  {
162  emit undoableChange();
163  }
164  });
165 
166  _action_split_horizontal = new QAction("&Split Horizontally", this);
167  connect(_action_split_horizontal, &QAction::triggered, this,
169 
170  _action_split_vertical = new QAction("&Split Vertically", this);
171  connect(_action_split_vertical, &QAction::triggered, this, &PlotWidget::splitVertical);
172 
173  _action_removeAllCurves = new QAction("&Remove ALL curves", this);
174  connect(_action_removeAllCurves, &QAction::triggered, this,
176  connect(_action_removeAllCurves, &QAction::triggered, this,
178 
179  _action_zoomOutMaximum = new QAction("&Zoom Out", this);
180  connect(_action_zoomOutMaximum, &QAction::triggered, this, [this]() {
181  zoomOut(true);
182  replot();
183  emit undoableChange();
184  });
185 
186  _action_zoomOutHorizontally = new QAction("&Zoom Out Horizontally", this);
187  connect(_action_zoomOutHorizontally, &QAction::triggered, this, [this]() {
189  replot();
190  emit undoableChange();
191  });
192 
193  _action_zoomOutVertically = new QAction("&Zoom Out Vertically", this);
194  connect(_action_zoomOutVertically, &QAction::triggered, this, [this]() {
196  replot();
197  emit undoableChange();
198  });
199 
200  QFont font;
201  font.setPointSize(10);
202 
203  _action_saveToFile = new QAction("&Save plot to file", this);
204  connect(_action_saveToFile, &QAction::triggered, this, &PlotWidget::on_savePlotToFile);
205 
206  _action_copy = new QAction("&Copy", this);
207  connect(_action_copy, &QAction::triggered, this, &PlotWidget::on_copyAction_triggered);
208 
209  _action_paste = new QAction("&Paste", this);
210  connect(_action_paste, &QAction::triggered, this,
212 
213  _action_image_to_clipboard = new QAction("&Copy image to clipboard", this);
214  connect(_action_image_to_clipboard, &QAction::triggered, this,
216 
217  _flip_x = new QAction("&Flip Horizontal Axis", this);
218  _flip_x->setCheckable(true);
219  connect(_flip_x, &QAction::changed, this, &PlotWidget::onFlipAxis);
220 
221  _flip_y = new QAction("&Flip Vertical Axis", this);
222  _flip_y->setCheckable(true);
223  connect(_flip_y, &QAction::changed, this, &PlotWidget::onFlipAxis);
224 
225  _action_data_statistics = new QAction("&Show data statistics", this);
226  connect(_action_data_statistics, &QAction::triggered, this,
228 }
229 
231 {
232  if (_context_menu_enabled == false)
233  {
234  return;
235  }
236 
237  QSettings settings;
238  QString theme = settings.value("StyleSheet::theme", "light").toString();
239 
240  _action_removeAllCurves->setIcon(LoadSvg(":/resources/svg/trash.svg", theme));
241  _action_edit->setIcon(LoadSvg(":/resources/svg/pencil-edit.svg", theme));
242  _action_formula->setIcon(LoadSvg(":/resources/svg/Fx.svg", theme));
243  _action_split_horizontal->setIcon(LoadSvg(":/resources/svg/add_column.svg", theme));
244  _action_split_vertical->setIcon(LoadSvg(":/resources/svg/add_row.svg", theme));
245  _action_zoomOutMaximum->setIcon(LoadSvg(":/resources/svg/zoom_max.svg", theme));
247  LoadSvg(":/resources/svg/zoom_horizontal.svg", theme));
248  _action_zoomOutVertically->setIcon(LoadSvg(":/resources/svg/zoom_vertical.svg", theme));
249  _action_copy->setIcon(LoadSvg(":/resources/svg/copy.svg", theme));
250  _action_paste->setIcon(LoadSvg(":/resources/svg/paste.svg", theme));
251  _action_saveToFile->setIcon(LoadSvg(":/resources/svg/save.svg", theme));
252  _action_image_to_clipboard->setIcon(LoadSvg(":/resources/svg/plot_image.svg", theme));
253 
254  QMenu menu(qwtPlot());
255 
256  menu.addAction(_action_edit);
257  menu.addAction(_action_formula);
258  menu.addSeparator();
259  menu.addAction(_action_split_horizontal);
260  menu.addAction(_action_split_vertical);
261  menu.addSeparator();
262  menu.addAction(_action_zoomOutMaximum);
263  menu.addAction(_action_zoomOutHorizontally);
264  menu.addAction(_action_zoomOutVertically);
265  menu.addSeparator();
266  menu.addAction(_action_removeAllCurves);
267  menu.addSeparator();
268  if (isXYPlot())
269  {
270  menu.addAction(_flip_x);
271  }
272  menu.addAction(_flip_y);
273  menu.addSeparator();
274  menu.addAction(_action_copy);
275  menu.addAction(_action_paste);
276  menu.addAction(_action_image_to_clipboard);
277  menu.addAction(_action_saveToFile);
278  menu.addAction(_action_data_statistics);
279 
280  // check the clipboard
281  QClipboard* clipboard = QGuiApplication::clipboard();
282  QString clipboard_text = clipboard->text();
283  QDomDocument doc;
284  bool valid_clipbaord = (!clipboard_text.isEmpty() && // not empty
285  doc.setContent(clipboard_text) && // valid xml
286  doc.firstChildElement().tagName() == "PlotWidgetClipBoard");
287 
288  _action_paste->setEnabled(valid_clipbaord);
289 
290  _action_removeAllCurves->setEnabled(!curveList().empty());
291  _action_formula->setEnabled(!curveList().empty() && !isXYPlot());
292 
293  menu.exec(qwtPlot()->canvas()->mapToGlobal(pos));
294 }
295 
296 PlotWidget::CurveInfo* PlotWidget::addCurveXY(std::string name_x, std::string name_y,
297  QString curve_name)
298 {
299  std::string name = curve_name.toStdString();
300 
301  while (name.empty())
302  {
303  SuggestDialog dialog(name_x, name_y, qwtPlot());
304  auto ret = dialog.exec();
305 
306  curve_name = dialog.suggestedName();
307  name = curve_name.toStdString();
308  name_x = dialog.nameX().toStdString();
309  name_y = dialog.nameY().toStdString();
310 
311  if (ret != QDialog::Accepted)
312  {
313  return nullptr;
314  }
315 
316  auto curve_it = curveFromTitle(curve_name);
317 
318  if (name.empty() || curve_it)
319  {
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)
326  {
327  return nullptr;
328  }
329  name.clear();
330  }
331  }
332 
333  auto it = _mapped_data.numeric.find(name_x);
334  if (it == _mapped_data.numeric.end())
335  {
336  throw std::runtime_error("Creation of XY plot failed");
337  }
338  PlotData& data_x = it->second;
339 
340  it = _mapped_data.numeric.find(name_y);
341  if (it == _mapped_data.numeric.end())
342  {
343  throw std::runtime_error("Creation of XY plot failed");
344  }
345  PlotData& data_y = it->second;
346 
347  auto curve_it = curveFromTitle(curve_name);
348  if (curve_it)
349  {
350  return nullptr;
351  }
352 
353  const auto qname = QString::fromStdString(name);
354  auto curve = new QwtPlotCurve(qname);
355 
356  try
357  {
358  auto plot_qwt = createCurveXY(&data_x, &data_y);
359 
360  curve->setPaintAttribute(QwtPlotCurve::ClipPolygons, true);
361  curve->setPaintAttribute(QwtPlotCurve::FilterPointsAggressive, true);
362  curve->setData(plot_qwt);
363  }
364  catch (std::exception& ex)
365  {
366  QMessageBox::warning(qwtPlot(), "Exception!", ex.what());
367  return nullptr;
368  }
369 
370  QColor color = getColorHint(nullptr);
371  curve->setPen(color);
372  setStyle(curve, curveStyle());
373 
374  curve->setRenderHint(QwtPlotItem::RenderAntialiased, true);
375 
376  curve->attach(qwtPlot());
377 
378  auto marker = new QwtPlotMarker;
379  marker->attach(qwtPlot());
380  marker->setVisible(isXYPlot());
381  QwtSymbol* sym = new QwtSymbol(QwtSymbol::Ellipse, color, QPen(Qt::black), QSize(8, 8));
382  marker->setSymbol(sym);
383 
384  CurveInfo curve_info;
385  curve_info.curve = curve;
386  curve_info.marker = marker;
387  curve_info.src_name = name;
388  curveList().push_back(curve_info);
389 
390  return &(curveList().back());
391 }
392 
393 PlotWidgetBase::CurveInfo* PlotWidget::addCurve(const std::string& name, QColor color)
394 {
395  PlotWidgetBase::CurveInfo* info = nullptr;
396 
397  auto it1 = _mapped_data.numeric.find(name);
398  if (it1 != _mapped_data.numeric.end())
399  {
400  info = PlotWidgetBase::addCurve(name, it1->second, color);
401  }
402 
403  auto it2 = _mapped_data.scatter_xy.find(name);
404  if (it2 != _mapped_data.scatter_xy.end())
405  {
406  info = PlotWidgetBase::addCurve(name, it2->second, color);
407  }
408 
409  if (info && info->curve)
410  {
411  if (auto timeseries = dynamic_cast<QwtTimeseries*>(info->curve->data()))
412  {
413  timeseries->setTimeOffset(_time_offset);
414  }
415  }
416  return info;
417 }
418 
419 void PlotWidget::removeCurve(const QString& title)
420 {
421  PlotWidgetBase::removeCurve(title);
422  _tracker->redraw();
423 }
424 
425 void PlotWidget::onDataSourceRemoved(const std::string& src_name)
426 {
427  bool deleted = false;
428 
429  for (auto it = curveList().begin(); it != curveList().end();)
430  {
431  PointSeriesXY* curve_xy = dynamic_cast<PointSeriesXY*>(it->curve->data());
432  bool remove_curve_xy = curve_xy && (curve_xy->dataX()->plotName() == src_name ||
433  curve_xy->dataY()->plotName() == src_name);
434 
435  if (it->src_name == src_name || remove_curve_xy)
436  {
437  deleted = true;
438  it->curve->detach();
439  it->marker->detach();
440  it = curveList().erase(it);
441  }
442  else
443  {
444  it++;
445  }
446  }
447 
448  if (deleted)
449  {
450  _tracker->redraw();
451  emit curveListChanged();
452  }
453  if (_background_item &&
454  _background_item->dataName() == QString::fromStdString(src_name))
455  {
456  _background_item->detach();
457  _background_item.reset();
458  }
459 }
460 
462 {
463  PlotWidgetBase::removeAllCurves();
464  setModeXY(false);
465  _tracker->redraw();
466  _flip_x->setChecked(false);
467  _flip_y->setChecked(false);
468 }
469 
470 void PlotWidget::onDragEnterEvent(QDragEnterEvent* event)
471 {
472  const QMimeData* mimeData = event->mimeData();
473  QStringList mimeFormats = mimeData->formats();
474 
476  _dragging.curves.clear();
477  _dragging.source = event->source();
478 
479  auto& format = mimeFormats.first();
480  QByteArray encoded = mimeData->data(format);
481  QDataStream stream(&encoded, QIODevice::ReadOnly);
482 
483  while (!stream.atEnd())
484  {
485  QString curve_name;
486  stream >> curve_name;
487  auto name = curve_name.toStdString();
488  if (!curve_name.isEmpty())
489  {
490  _dragging.curves.push_back(curve_name);
491  }
492  if (_mapped_data.numeric.count(name) == 0 && _mapped_data.scatter_xy.count(name) == 0)
493  {
494  event->ignore();
495  return;
496  }
497  }
498 
499  if (_dragging.curves.empty())
500  {
501  event->ignore();
502  return;
503  }
504 
505  if (format == "curveslist/add_curve")
506  {
508  event->acceptProposedAction();
509  }
510 
511  if (format == "curveslist/new_XY_axis")
512  {
513  if (_dragging.curves.size() != 2)
514  {
515  qDebug() << "FATAL: Dragging " << _dragging.curves.size() << " curves";
516  return;
517  }
518  if (curveList().empty() || isXYPlot())
519  {
521  event->acceptProposedAction();
522  }
523  }
524 }
525 
526 void PlotWidget::onDragLeaveEvent(QDragLeaveEvent* event){
528  _dragging.curves.clear();
529 }
530 
531 void PlotWidget::onDropEvent(QDropEvent*)
532 {
533  bool curves_changed = false;
534 
535  bool noCurves = curveList().empty();
536 
538  {
539  size_t scatter_count = 0;
540  for (const auto& curve_name : _dragging.curves)
541  {
542  scatter_count += _mapped_data.scatter_xy.count(curve_name.toStdString());
543  }
544  bool scatter_curves = (scatter_count == _dragging.curves.size());
545  if (scatter_count > 0 && !scatter_curves)
546  {
548  _dragging.curves.clear();
549  QMessageBox::warning(qwtPlot(), "Warning",
550  "You can not drag XY (scatter) data and timeseries into the "
551  "same plot");
552  return;
553  }
554 
555  // if there aren't other curves, you can change the mode
556  if (curveList().empty())
557  {
558  setModeXY(scatter_curves);
559  }
560 
561  if (isXYPlot() && !scatter_curves)
562  {
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."));
567  }
568  if (!isXYPlot() && scatter_curves)
569  {
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."));
575  }
576 
577  if (isXYPlot() != scatter_curves)
578  {
580  _dragging.curves.clear();
581  return;
582  }
583 
584  for (const auto& curve_name : _dragging.curves)
585  {
586  bool added = addCurve(curve_name.toStdString()) != nullptr;
587  curves_changed = curves_changed || added;
588  }
589  }
590  else if (_dragging.mode == DragInfo::NEW_XY && _dragging.curves.size() == 2)
591  {
592  if (!curveList().empty() && !isXYPlot())
593  {
595  _dragging.curves.clear();
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."));
601  return;
602  }
603 
604  setModeXY(true);
605  addCurveXY(_dragging.curves[0].toStdString(), _dragging.curves[1].toStdString());
606 
607  curves_changed = true;
608  }
609 
610  if (curves_changed)
611  {
612  emit curvesDropped();
613  emit curveListChanged();
614 
615  QSettings settings;
616  bool autozoom_curve_added = settings.value("Preferences::autozoom_curve_added",true).toBool();
617  if(autozoom_curve_added || noCurves)
618  {
619  zoomOut(autozoom_curve_added);
620  }
621  else{
622  replot();
623  }
624  }
626  _dragging.curves.clear();
627 }
628 
629 void PlotWidget::on_panned(int, int)
630 {
632 }
633 
634 QDomElement PlotWidget::xmlSaveState(QDomDocument& doc) const
635 {
636  QDomElement plot_el = doc.createElement("plot");
637 
638  QDomElement range_el = doc.createElement("range");
639  QRectF rect = currentBoundingRect();
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);
645 
646  QDomElement limitY_el = doc.createElement("limitY");
648  {
649  limitY_el.setAttribute("min", QString::number(_custom_Y_limits.min));
650  }
652  {
653  limitY_el.setAttribute("max", QString::number(_custom_Y_limits.max));
654  }
655  plot_el.appendChild(limitY_el);
656 
657  if (curveStyle() == PlotWidgetBase::LINES)
658  {
659  plot_el.setAttribute("style", "Lines");
660  }
661  else if (curveStyle() == PlotWidgetBase::LINES_AND_DOTS)
662  {
663  plot_el.setAttribute("style", "LinesAndDots");
664  }
665  else if (curveStyle() == PlotWidgetBase::DOTS)
666  {
667  plot_el.setAttribute("style", "Dots");
668  }
669  else if (curveStyle() == PlotWidgetBase::STICKS)
670  {
671  plot_el.setAttribute("style", "Sticks");
672  }
673 
674  for (auto& it : curveList())
675  {
676  auto& name = it.src_name;
677  QwtPlotCurve* curve = it.curve;
678  QDomElement curve_el = doc.createElement("curve");
679  curve_el.setAttribute("name", QString::fromStdString(name));
680  curve_el.setAttribute("color", curve->pen().color().name());
681 
682  plot_el.appendChild(curve_el);
683 
684  if (isXYPlot())
685  {
686  if (auto xy = dynamic_cast<PointSeriesXY*>(curve->data()))
687  {
688  curve_el.setAttribute("curve_x", QString::fromStdString(xy->dataX()->plotName()));
689  curve_el.setAttribute("curve_y", QString::fromStdString(xy->dataY()->plotName()));
690  }
691  }
692  else
693  {
694  auto ts = dynamic_cast<TransformedTimeseries*>(curve->data());
695  if (ts && ts->transform())
696  {
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);
702  }
703  }
704  }
705 
706  plot_el.setAttribute("mode", isXYPlot() ? "XYPlot" : "TimeSeries");
707 
708  plot_el.setAttribute("flip_x", isXYPlot() && _flip_x->isChecked() ? "true" : "false");
709  plot_el.setAttribute("flip_y", _flip_y->isChecked() ? "true" : "false");
710 
711  if (_background_item)
712  {
713  plot_el.setAttribute("background_data", _background_item->dataName());
714  plot_el.setAttribute("background_colormap", _background_item->colormapName());
715  }
716 
717  return plot_el;
718 }
719 
720 bool PlotWidget::xmlLoadState(QDomElement& plot_widget, bool autozoom)
721 {
722  std::set<std::string> added_curve_names;
723 
724  QString mode = plot_widget.attribute("mode");
725  setModeXY(mode == "XYPlot");
726 
727  _flip_x->setChecked(plot_widget.attribute("flip_x") == "true");
728  _flip_y->setChecked(plot_widget.attribute("flip_y") == "true");
729 
730  QDomElement limitY_el = plot_widget.firstChildElement("limitY");
731 
734 
735  if (!limitY_el.isNull())
736  {
737  if (limitY_el.hasAttribute("min"))
738  {
739  _custom_Y_limits.min = limitY_el.attribute("min").toDouble();
740  }
741  if (limitY_el.hasAttribute("max"))
742  {
743  _custom_Y_limits.max = limitY_el.attribute("max").toDouble();
744  }
745  }
746 
747  static bool warning_message_shown = false;
748 
749  // removeAllCurves simplified
750  for (auto& it : curveList())
751  {
752  it.curve->detach();
753  it.marker->detach();
754  }
755  curveList().clear();
756 
757  // insert curves
758  QStringList missing_curves;
759  for (QDomElement curve_element = plot_widget.firstChildElement("curve");
760  !curve_element.isNull(); curve_element = curve_element.nextSiblingElement("curve"))
761  {
762  bool is_merged_xy =
763  curve_element.hasAttribute("curve_x") && curve_element.hasAttribute("curve_y");
764  bool is_timeseries = !isXYPlot();
765  bool is_scatter_xy = !is_timeseries && !is_merged_xy;
766 
767  QString curve_name = curve_element.attribute("name");
768  std::string curve_name_std = curve_name.toStdString();
769  QColor color(curve_element.attribute("color"));
770 
771  //-----------------
772  if (is_timeseries || is_scatter_xy)
773  {
774  if ((is_timeseries && _mapped_data.numeric.count(curve_name_std) == 0) ||
775  (!is_timeseries && _mapped_data.scatter_xy.count(curve_name_std) == 0))
776  {
777  missing_curves.append(curve_name);
778  }
779  else
780  {
781  auto curve_info = addCurve(curve_name_std, color);
782  if (!curve_info)
783  {
784  continue;
785  }
786  auto& curve = curve_info->curve;
787  curve->setPen(color, 1.3);
788  added_curve_names.insert(curve_name_std);
789 
790  auto ts = dynamic_cast<TransformedTimeseries*>(curve->data());
791  QDomElement transform_el = curve_element.firstChildElement("transform");
792  if (ts && transform_el.isNull() == false)
793  {
794  ts->setTransform(transform_el.attribute("name"));
795  ts->transform()->xmlLoadState(transform_el);
796  ts->updateCache(true);
797  auto alias = transform_el.attribute("alias");
798  ts->setAlias(alias);
799  curve->setTitle(alias);
800  }
801  }
802  }
803  //-----------------
804  if (is_merged_xy)
805  {
806  std::string curve_x = curve_element.attribute("curve_x").toStdString();
807  std::string curve_y = curve_element.attribute("curve_y").toStdString();
808  if (_mapped_data.numeric.find(curve_x) == _mapped_data.numeric.end() ||
809  _mapped_data.numeric.find(curve_y) == _mapped_data.numeric.end())
810  {
811  missing_curves.append(curve_name);
812  }
813  else
814  {
815  auto curve_it = addCurveXY(curve_x, curve_y, curve_name);
816  if (!curve_it)
817  {
818  continue;
819  }
820  curve_it->curve->setPen(color, 1.3);
821  curve_it->marker->setSymbol(
822  new QwtSymbol(QwtSymbol::Ellipse, color, QPen(Qt::black), QSize(8, 8)));
823  added_curve_names.insert(curve_name_std);
824  }
825  }
826  }
827 
828  if (missing_curves.size() > 0 && !warning_message_shown)
829  {
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;
835  }
836 
837  emit curveListChanged();
838 
839  //-----------------------------------------
840 
841  QDomElement rectangle = plot_widget.firstChildElement("range");
842 
843  if (!rectangle.isNull() && autozoom)
844  {
845  QRectF rect;
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());
850  this->setZoomRectangle(rect, false);
851  }
852 
853  if (plot_widget.hasAttribute("style"))
854  {
855  QString style = plot_widget.attribute("style");
856  if (style == "Lines")
857  {
858  changeCurvesStyle(PlotWidgetBase::LINES);
859  }
860  else if (style == "LinesAndDots")
861  {
862  changeCurvesStyle(PlotWidgetBase::LINES_AND_DOTS);
863  }
864  else if (style == "Dots")
865  {
866  changeCurvesStyle(PlotWidgetBase::DOTS);
867  }
868  else if (style == "Sticks")
869  {
870  changeCurvesStyle(PlotWidgetBase::STICKS);
871  }
872  }
873 
874  QString bg_data = plot_widget.attribute("background_data");
875  QString bg_colormap = plot_widget.attribute("background_colormap");
876 
877  if (!bg_data.isEmpty() && !bg_colormap.isEmpty())
878  {
879  auto plot_it = datamap().numeric.find(bg_data.toStdString());
880  if (plot_it == datamap().numeric.end())
881  {
882  QMessageBox::warning(qwtPlot(), "Warning",
883  tr("Can't restore the background color.\n"
884  "Series [%1] not found.")
885  .arg(bg_data));
886  }
887  else
888  {
889  auto color_it = ColorMapLibrary().find(bg_colormap);
890  if (color_it == ColorMapLibrary().end())
891  {
892  QMessageBox::warning(qwtPlot(), "Warning",
893  tr("Can't restore the background color.\n"
894  "ColorMap [%1] not found.")
895  .arg(bg_colormap));
896  }
897  else
898  {
899  // everything fine.
901  std::make_unique<BackgroundColorItem>(plot_it->second, bg_colormap);
902  _background_item->setTimeOffset(&_time_offset);
903  _background_item->attach(qwtPlot());
904  }
905  }
906  }
907 
908  if(autozoom)
909  {
911  }
912  replot();
913  return true;
914 }
915 
917 {
918  QRectF canvas_rect = qwtPlot()->canvas()->contentsRect();
919 
920  auto max_rect = maxZoomRect();
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());
923 
924  QRectF rect = max_rect;
925 
926  if (max_ratio < canvas_ratio)
927  {
928  double new_width = (-max_rect.height() * canvas_ratio);
929  rect.setWidth(new_width);
930  }
931  else
932  {
933  double new_height = (-max_rect.width() / canvas_ratio);
934  rect.setHeight(new_height);
935  }
936 
937  rect.moveCenter(max_rect.center());
938 
939  setAxisScale(QwtPlot::yLeft, rect.bottom(), rect.top());
940  setAxisScale(QwtPlot::xBottom, rect.left(), rect.right());
941  qwtPlot()->updateAxes();
942  replot();
943 }
944 
945 void PlotWidget::setZoomRectangle(QRectF rect, bool emit_signal)
946 {
947  if (isXYPlot() && keepRatioXY())
948  {
950  }
951  else
952  {
953  setAxisScale(QwtPlot::yLeft, rect.bottom(), rect.top());
954  setAxisScale(QwtPlot::xBottom, rect.left(), rect.right());
955  qwtPlot()->updateAxes();
956  }
957 
958  if (emit_signal)
959  {
960  if (isXYPlot())
961  {
962  emit undoableChange();
963  }
964  else
965  {
966  emit rectChanged(this, rect);
967  }
968  }
970 }
971 
973 {
974  // TODO: this needs MUCH more testing
975 
976  int visible = 0;
977 
978  for (auto& it : curveList())
979  {
980  if (it.curve->isVisible())
981  {
982  visible++;
983  }
984 
985  const auto& curve_name = it.src_name;
986 
987  auto data_it = _mapped_data.numeric.find(curve_name);
988  if (data_it != _mapped_data.numeric.end())
989  {
990  if (auto ts = dynamic_cast<TransformedTimeseries*>(it.curve->data()))
991  {
992  ts->updateCache(true);
993  }
994  }
995  }
996 
997  if (curveList().size() == 0 || visible == 0)
998  {
1000  }
1001 }
1002 
1003 void PlotWidget::activateLegend(bool activate)
1004 {
1005  legend()->setVisible(activate);
1006 }
1007 
1008 void PlotWidget::activateGrid(bool activate)
1009 {
1010  _grid->enableX(activate);
1011  _grid->enableXMin(activate);
1012  _grid->enableY(activate);
1013  _grid->enableYMin(activate);
1014  _grid->attach(qwtPlot());
1015 }
1016 
1018 {
1019  _tracker->setParameter(val);
1020 }
1021 
1023 {
1024  _tracker->setEnabled(enable && !isXYPlot());
1025 }
1026 
1028 {
1029  return _tracker->isEnabled();
1030 }
1031 
1032 void PlotWidget::setTrackerPosition(double abs_time)
1033 {
1034  if (isXYPlot())
1035  {
1036  for (auto& it : curveList())
1037  {
1038  if (auto series = dynamic_cast<QwtTimeseries*>(it.curve->data()))
1039  {
1040  auto pointXY = series->sampleFromTime(abs_time);
1041  if (pointXY)
1042  {
1043  it.marker->setValue(pointXY.value());
1044  }
1045  }
1046  }
1047  }
1048  else
1049  {
1050  double relative_time = abs_time - _time_offset;
1051  _tracker->setPosition(QPointF(relative_time, 0.0));
1052  }
1053 }
1054 
1056 {
1057  auto prev_offset = _time_offset;
1058  _time_offset = offset;
1059 
1060  if (fabs(prev_offset - offset) > std::numeric_limits<double>::epsilon())
1061  {
1062  for (auto& it : curveList())
1063  {
1064  if (auto series = dynamic_cast<QwtTimeseries*>(it.curve->data()))
1065  {
1066  series->setTimeOffset(_time_offset);
1067  }
1068  }
1069  if (!isXYPlot() && !curveList().empty())
1070  {
1071  QRectF rect = currentBoundingRect();
1072  double delta = prev_offset - offset;
1073  rect.moveLeft(rect.left() + delta);
1074  setZoomRectangle(rect, false);
1075  }
1076  }
1078 }
1079 
1081 {
1083  bool is_timescale =
1084  dynamic_cast<TimeScaleDraw*>(qwtPlot()->axisScaleDraw(QwtPlot::xBottom)) != nullptr;
1085 
1086  if (enable && !isXYPlot())
1087  {
1088  if (!is_timescale)
1089  {
1091  }
1092  }
1093  else
1094  {
1095  if (is_timescale)
1096  {
1098  }
1099  }
1100 }
1101 
1102 // TODO report failure for empty dataset
1104 {
1105  auto [bottom, top] = PlotWidgetBase::getVisualizationRangeY(range_X);
1106 
1107  const bool lower_limit = _custom_Y_limits.min > -MAX_DOUBLE;
1108  const bool upper_limit = _custom_Y_limits.max < MAX_DOUBLE;
1109 
1110  if (lower_limit)
1111  {
1112  bottom = _custom_Y_limits.min;
1113  if (top < bottom)
1114  {
1115  top = bottom;
1116  }
1117  }
1118 
1119  if (upper_limit)
1120  {
1122  if (top < bottom)
1123  {
1124  bottom = top;
1125  }
1126  }
1127 
1128  return Range({ bottom, top });
1129 }
1130 
1131 void PlotWidget::updateCurves(bool reset_older_data)
1132 {
1133  for (auto& it : curveList())
1134  {
1135  auto series = dynamic_cast<QwtSeriesWrapper*>(it.curve->data());
1136  series->updateCache(reset_older_data);
1137  }
1139 
1140  updateStatistics(true);
1141 }
1142 
1143 void PlotWidget::updateStatistics(bool forceUpdate)
1144 {
1145  if (_statistics_dialog)
1146  {
1147  if (_statistics_dialog->calcVisibleRange() || forceUpdate)
1148  {
1149  auto rect = currentBoundingRect();
1150  _statistics_dialog->update({ rect.left(), rect.right() });
1151  }
1152  }
1153 }
1154 
1155 void PlotWidget::on_changeCurveColor(const QString& curve_name, QColor new_color)
1156 {
1157  for (auto& it : curveList())
1158  {
1159  if (it.curve->title() == curve_name)
1160  {
1161  auto& curve = it.curve;
1162  if (curve->pen().color() != new_color)
1163  {
1164  curve->setPen(new_color, 1.3);
1165  }
1166  replot();
1167  break;
1168  }
1169  }
1170 }
1171 
1173 {
1174  if (isXYPlot())
1175  {
1177  }
1178  else
1179  {
1180  QRectF canvas_rect = qwtPlot()->canvas()->contentsRect();
1181  const QwtScaleMap xMap = qwtPlot()->canvasMap(QwtPlot::xBottom);
1182  const QwtScaleMap yMap = qwtPlot()->canvasMap(QwtPlot::yLeft);
1183  canvas_rect = canvas_rect.normalized();
1184  double x1 = xMap.invTransform(canvas_rect.left());
1185  double x2 = xMap.invTransform(canvas_rect.right());
1186  double y1 = yMap.invTransform(canvas_rect.bottom());
1187  double y2 = yMap.invTransform(canvas_rect.top());
1188  // flip will be done inside the function setAxisScale()
1189  setAxisScale(QwtPlot::yLeft, y1, y2);
1190  setAxisScale(QwtPlot::xBottom, x1, x2);
1191  qwtPlot()->updateAxes();
1192  replot();
1193  }
1194  emit undoableChange();
1195 }
1196 
1198 {
1199  QString prev_colormap;
1200  if (name.isEmpty())
1201  {
1202  if (_background_item)
1203  {
1204  name = _background_item->dataName();
1205  prev_colormap = _background_item->colormapName();
1206  }
1207  else
1208  {
1209  return;
1210  }
1211  }
1212 
1213  auto plot_it = datamap().numeric.find(name.toStdString());
1214  if (plot_it == datamap().numeric.end())
1215  {
1216  if (_background_item)
1217  {
1218  _background_item->detach();
1219  _background_item.reset();
1220  replot();
1221  }
1222  return;
1223  }
1224 
1225  ColormapSelectorDialog dialog(name, prev_colormap, this);
1226  auto ret = dialog.exec();
1227  if (ret == QDialog::Accepted)
1228  {
1229  if (_background_item)
1230  {
1231  _background_item->detach();
1232  _background_item.reset();
1233  }
1234 
1235  QString colormap = dialog.selectedColorMap();
1236  if (!colormap.isEmpty() && ColorMapLibrary().count(colormap) != 0)
1237  {
1238  _background_item = std::make_unique<BackgroundColorItem>(plot_it->second, colormap);
1239  _background_item->setTimeOffset(&_time_offset);
1240  _background_item->attach(qwtPlot());
1241  }
1242  replot();
1243  }
1244 }
1245 
1247 {
1248  _statistics_window_title = title;
1249 
1250  if (_statistics_dialog)
1251  {
1253  }
1254 }
1255 
1257 {
1258  if (!_statistics_dialog)
1259  {
1261  }
1262 
1264 
1265  auto rect = currentBoundingRect();
1266  _statistics_dialog->update({ rect.left(), rect.right() });
1267  _statistics_dialog->show();
1268  _statistics_dialog->raise();
1269  _statistics_dialog->activateWindow();
1270 
1271  _statistics_dialog->setAttribute(Qt::WA_DeleteOnClose);
1272 
1273  auto setToNull = [this]() { _statistics_dialog = nullptr; };
1274 
1276  [this](PlotWidget*, QRectF rect) {
1277  _statistics_dialog->update({ rect.left(), rect.right() });
1278  });
1279 
1280  connect(_statistics_dialog, &QDialog::rejected, this, setToNull);
1281 
1282  connect(this, &PlotWidgetBase::curveListChanged, this,
1283  [this]() { updateStatistics(); });
1284 }
1285 
1286 void PlotWidget::on_externallyResized(const QRectF& rect)
1287 {
1288  QRectF current_rect = currentBoundingRect();
1289  if (current_rect == rect)
1290  {
1291  return;
1292  }
1293 
1294  if (!isXYPlot() && isZoomLinkEnabled())
1295  {
1296  emit rectChanged(this, rect);
1297  }
1298 }
1299 
1300 void PlotWidget::zoomOut(bool emit_signal)
1301 {
1302  if (curveList().size() == 0)
1303  {
1304  QRectF rect(0, 1, 1, -1);
1305  this->setZoomRectangle(rect, false);
1306  return;
1307  }
1309 
1310  setZoomRectangle(maxZoomRect(), emit_signal);
1311  replot();
1312 }
1313 
1315 {
1317  QRectF act = currentBoundingRect();
1318  auto rangeX = getVisualizationRangeX();
1319 
1320  act.setLeft(rangeX.min);
1321  act.setRight(rangeX.max);
1322  setZoomRectangle(act, emit_signal);
1323 }
1324 
1326 {
1328  QRectF rect = currentBoundingRect();
1329  auto rangeY = getVisualizationRangeY({ rect.left(), rect.right() });
1330 
1331  rect.setBottom(rangeY.min);
1332  rect.setTop(rangeY.max);
1333  this->setZoomRectangle(rect, emit_signal);
1334 }
1335 
1337 {
1338  if (enable == isXYPlot())
1339  {
1340  return;
1341  }
1342  PlotWidgetBase::setModeXY(enable);
1343 
1344  enableTracker(!enable);
1345 
1346  if (enable)
1347  {
1348  QFont font_footer;
1349  font_footer.setPointSize(10);
1350  QwtText text("XY Plot");
1351  text.setFont(font_footer);
1352  qwtPlot()->setFooter(text);
1353  }
1354  else
1355  {
1356  qwtPlot()->setFooter("");
1357  }
1358 
1359  zoomOut(true);
1361  replot();
1362 }
1363 
1365 {
1366  QSettings settings;
1367  QByteArray xml_text =
1368  settings.value("AddCustomPlotDialog.savedXML", QByteArray()).toByteArray();
1369  if (!xml_text.isEmpty())
1370  {
1371  _snippets = GetSnippetsFromXML(xml_text);
1372  }
1373 }
1374 
1376 {
1377  QString fileName;
1378 
1379  QFileDialog saveDialog(qwtPlot());
1380  saveDialog.setAcceptMode(QFileDialog::AcceptSave);
1381 
1382  QStringList filters;
1383  filters << "png (*.png)"
1384  << "jpg (*.jpg *.jpeg)"
1385  << "svg (*.svg)";
1386 
1387  saveDialog.setNameFilters(filters);
1388  saveDialog.exec();
1389 
1390  if (saveDialog.result() == QDialog::Accepted && !saveDialog.selectedFiles().empty())
1391  {
1392  fileName = saveDialog.selectedFiles().first();
1393 
1394  if (fileName.isEmpty())
1395  {
1396  return;
1397  }
1398 
1399  bool is_svg = false;
1400  QFileInfo fileinfo(fileName);
1401  if (fileinfo.suffix().isEmpty())
1402  {
1403  auto filter = saveDialog.selectedNameFilter();
1404  if (filter == filters[0])
1405  {
1406  fileName.append(".png");
1407  }
1408  else if (filter == filters[1])
1409  {
1410  fileName.append(".jpg");
1411  }
1412  else if (filter == filters[2])
1413  {
1414  fileName.append(".svg");
1415  is_svg = true;
1416  }
1417  }
1418 
1419  bool tracker_enabled = _tracker->isEnabled();
1420  if (tracker_enabled)
1421  {
1422  this->enableTracker(false);
1423  replot();
1424  }
1425 
1426  QRect documentRect(0, 0, 1200, 900);
1427  QwtPlotRenderer rend;
1428 
1429  if (is_svg)
1430  {
1431  QSvgGenerator generator;
1432  generator.setFileName(fileName);
1433  generator.setResolution(80);
1434  generator.setViewBox(documentRect);
1435  QPainter painter(&generator);
1436  rend.render(qwtPlot(), &painter, documentRect);
1437  }
1438  else
1439  {
1440  QPixmap pixmap(1200, 900);
1441  QPainter painter(&pixmap);
1442  rend.render(qwtPlot(), &painter, documentRect);
1443  pixmap.save(fileName);
1444  }
1445 
1446  if (tracker_enabled)
1447  {
1448  this->enableTracker(true);
1449  replot();
1450  }
1451  }
1452 }
1453 
1455 {
1456  _custom_Y_limits = range;
1458  replot();
1459 }
1460 
1462 {
1463  return _custom_Y_limits;
1464 }
1465 
1467 {
1468  bool tracker_enabled = _tracker->isEnabled();
1469  if (tracker_enabled)
1470  {
1471  this->enableTracker(false);
1472  replot();
1473  }
1474 
1475  auto documentRect = qwtPlot()->canvas()->rect();
1476  qDebug() << documentRect;
1477 
1478  QwtPlotRenderer rend;
1479  QPixmap pixmap(documentRect.width(), documentRect.height());
1480  QPainter painter(&pixmap);
1481  rend.render(qwtPlot(), &painter, documentRect);
1482 
1483  QClipboard* clipboard = QGuiApplication::clipboard();
1484  clipboard->setPixmap(pixmap);
1485 
1486  if (tracker_enabled)
1487  {
1488  this->enableTracker(true);
1489  replot();
1490  }
1491 }
1492 
1494 {
1495  QDomDocument doc;
1496  auto root = doc.createElement("PlotWidgetClipBoard");
1497  auto el = xmlSaveState(doc);
1498  doc.appendChild(root);
1499  root.appendChild(el);
1500 
1501  QClipboard* clipboard = QGuiApplication::clipboard();
1502  clipboard->setText(doc.toString());
1503 }
1504 
1506 {
1507  QClipboard* clipboard = QGuiApplication::clipboard();
1508  QString clipboard_text = clipboard->text();
1509 
1510  QDomDocument doc;
1511  bool valid = doc.setContent(clipboard_text);
1512  if (!valid)
1513  {
1514  return;
1515  }
1516  auto root = doc.firstChildElement();
1517  if (root.tagName() != "PlotWidgetClipBoard")
1518  {
1519  return;
1520  }
1521  else
1522  {
1523  auto el = root.firstChildElement();
1524  xmlLoadState(el);
1525  emit undoableChange();
1526  }
1527 }
1528 
1529 bool PlotWidget::eventFilter(QObject* obj, QEvent* event)
1530 {
1531  if (PlotWidgetBase::eventFilter(obj, event))
1532  {
1533  return true;
1534  }
1535 
1536  if (event->type() == QEvent::Destroy)
1537  {
1538  return false;
1539  }
1540 
1541  if (obj == qwtPlot()->canvas())
1542  {
1543  return canvasEventFilter(event);
1544  }
1545  return false;
1546 }
1547 
1549 {
1550  QSettings settings;
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)));
1554 }
1555 
1556 void PlotWidget::setAxisScale(QwtAxisId axisId, double min, double max)
1557 {
1558  if (min > max)
1559  {
1560  std::swap(min, max);
1561  }
1562  if (axisId == QwtPlot::xBottom && _flip_x->isChecked())
1563  {
1564  qwtPlot()->setAxisScale(QwtPlot::xBottom, max, min);
1565  }
1566  else if (axisId == QwtPlot::yLeft && _flip_y->isChecked())
1567  {
1568  qwtPlot()->setAxisScale(QwtPlot::yLeft, max, min);
1569  }
1570  else
1571  {
1572  qwtPlot()->setAxisScale(axisId, min, max);
1573  }
1574 }
1575 
1577 {
1578  // for (const auto& it : curveList())
1579  // {
1580  // auto series = dynamic_cast<QwtSeriesWrapper*>(it.curve->data());
1581  // if (series->plotData()->attribute(PJ::DISABLE_LINKED_ZOOM).toBool())
1582  // {
1583  // return false;
1584  // }
1585  // }
1586  return true;
1587 }
1588 
1590 {
1591  switch (event->type())
1592  {
1593  case QEvent::MouseButtonPress: {
1594  if (_dragging.mode != DragInfo::NONE)
1595  {
1596  return true; // don't pass to canvas().
1597  }
1598 
1599  QMouseEvent* mouse_event = static_cast<QMouseEvent*>(event);
1600 
1601  if (mouse_event->button() == Qt::LeftButton)
1602  {
1603  const QPoint press_point = mouse_event->pos();
1604  if (mouse_event->modifiers() == Qt::ShiftModifier) // time tracker
1605  {
1606  QPointF pointF(qwtPlot()->invTransform(QwtPlot::xBottom, press_point.x()),
1607  qwtPlot()->invTransform(QwtPlot::yLeft, press_point.y()));
1608  emit trackerMoved(pointF);
1609  return true; // don't pass to canvas().
1610  }
1611  else if (mouse_event->modifiers() == Qt::ControlModifier) // panner
1612  {
1614  }
1615  return false; // send to canvas()
1616  }
1617  else if (mouse_event->buttons() == Qt::MiddleButton &&
1618  mouse_event->modifiers() == Qt::NoModifier)
1619  {
1621  return false;
1622  }
1623  else if (mouse_event->button() == Qt::RightButton)
1624  {
1625  if (mouse_event->modifiers() == Qt::NoModifier) // show menu
1626  {
1627  canvasContextMenuTriggered(mouse_event->pos());
1628  return true; // don't pass to canvas().
1629  }
1630  }
1631  }
1632  break;
1633  //---------------------------------
1634  case QEvent::MouseMove: {
1635  if (_dragging.mode != DragInfo::NONE)
1636  {
1637  return true; // don't pass to canvas().
1638  }
1639 
1640  QMouseEvent* mouse_event = static_cast<QMouseEvent*>(event);
1641 
1642  if (mouse_event->buttons() == Qt::LeftButton &&
1643  mouse_event->modifiers() == Qt::ShiftModifier)
1644  {
1645  const QPoint point = mouse_event->pos();
1646  QPointF pointF(qwtPlot()->invTransform(QwtPlot::xBottom, point.x()),
1647  qwtPlot()->invTransform(QwtPlot::yLeft, point.y()));
1648  emit trackerMoved(pointF);
1649  return true;
1650  }
1651  }
1652  break;
1653 
1654  case QEvent::Leave: {
1656  _dragging.curves.clear();
1657  }
1658  break;
1659  case QEvent::MouseButtonRelease: {
1660  if (_dragging.mode == DragInfo::NONE)
1661  {
1662  QApplication::restoreOverrideCursor();
1663  return false;
1664  }
1665  }
1666  break;
1667 
1668  } // end switch
1669 
1670  return false;
1671 }
1672 
1674 {
1675  if (!curveList().empty())
1676  {
1677  double min = std::numeric_limits<double>::max();
1678  double max = std::numeric_limits<double>::lowest();
1679  for (auto& it : _mapped_data.numeric)
1680  {
1681  const PlotData& data = it.second;
1682  if (data.size() > 0)
1683  {
1684  double A = data.front().x;
1685  double B = data.back().x;
1686  min = std::min(A, min);
1687  max = std::max(B, max);
1688  }
1689  }
1691  }
1692  else
1693  {
1694  setAxisScale(QwtPlot::xBottom, 0.0, 1.0);
1695  }
1696 }
1697 
1699  const PlotData* data_y)
1700 {
1701  PointSeriesXY* output = nullptr;
1702 
1703  try
1704  {
1705  output = new PointSeriesXY(data_x, data_y);
1706  }
1707  catch (std::runtime_error& ex)
1708  {
1710  {
1711  QMessageBox msgBox(qwtPlot());
1712  msgBox.setWindowTitle("Warnings");
1713  msgBox.setText(tr("The creation of the XY plot failed with the following "
1714  "message:\n %1")
1715  .arg(ex.what()));
1716  msgBox.addButton("Continue", QMessageBox::AcceptRole);
1717  msgBox.exec();
1718  }
1719  throw std::runtime_error("Creation of XY plot failed");
1720  }
1721 
1722  output->setTimeOffset(_time_offset);
1723  return output;
1724 }
1725 
1727  const QString& transform_ID)
1728 {
1730  output->setTransform(transform_ID);
1731  output->setTimeOffset(_time_offset);
1732  output->updateCache(true);
1733  return output;
1734 }
double invTransform(double p) const
void onShowDataStatistics()
QString selectedColorMap() const
const QPen & pen() const
CurveTracker * _tracker
Definition: plotwidget.h:184
void on_savePlotToFile()
PlotLegend * legend()
PlotWidget(PlotDataMapRef &datamap, QWidget *parent)
Definition: plotwidget.cpp:74
void on_panned(int dx, int dy)
Definition: plotwidget.cpp:629
Enable antialiasing.
void enableX(bool)
Enable or disable vertical grid lines.
double max
Definition: plotdatabase.h:32
A plot item, that represents a series of points.
CurveInfo * addCurveXY(std::string name_x, std::string name_y, QString curve_name="")
Definition: plotwidget.cpp:296
void updateStatistics(bool forceUpdate=false)
void setFont(const QFont &)
Definition: qwt_text.cpp:329
#define nullptr
Definition: backward.hpp:386
QAction * _action_removeAllCurves
Definition: plotwidget.h:166
void onDragEnterEvent(QDragEnterEvent *event)
Definition: plotwidget.cpp:470
virtual QwtText label(double v) const
Convert a value into its representing label.
Definition: plotwidget.cpp:59
void rectChanged(PlotWidget *self, QRectF rect)
bool xmlLoadState(QDomElement &element, bool autozoom=true)
Definition: plotwidget.cpp:720
QAction * _flip_y
Definition: plotwidget.h:182
DragInfo _dragging
Definition: plotwidget.h:207
void on_pasteAction_triggered()
void on_changeDateTimeScale(bool enable)
QAction * _action_data_statistics
Definition: plotwidget.h:171
void onFlipAxis()
void enableYMin(bool)
Enable or disable minor horizontal grid lines.
PlotDataMapRef & _mapped_data
Definition: plotwidget.h:86
void on_externallyResized(const QRectF &new_rect)
void undoableChange()
std::map< QString, ColorMap::Ptr > & ColorMapLibrary()
Definition: color_map.cpp:48
QAction * _flip_x
Definition: plotwidget.h:181
void setTimeOffset(double offset)
SnippetsMap _snippets
Definition: plotwidget.h:226
QString _statistics_window_title
Definition: plotwidget.h:187
Range getVisualizationRangeY(Range range_X) const override
void setAxisScale(QwtAxisId axisId, double min, double max)
bool _use_date_time_scale
Definition: plotwidget.h:191
std::unique_ptr< BackgroundColorItem > _background_item
Definition: plotwidget.h:189
virtual PJ::Range getVisualizationRangeX() const
virtual void render(QwtPlot *, QPainter *, const QRectF &plotRect) const
double invTransform(QwtAxisId, double pos) const
double _time_offset
Definition: plotwidget.h:220
QwtSeriesWrapper * createTimeSeries(const PlotData *data, const QString &transform_ID={}) override
void activateLegend(bool activate)
auto arg(const Char *name, const T &arg) -> detail::named_arg< Char, T >
Definition: core.h:1736
QwtSeriesData< T > * data()
A class for drawing symbols.
Definition: qwt_symbol.h:31
QDomElement xmlSaveState(QDomDocument &doc) const
Definition: plotwidget.cpp:634
TransformFunction::Ptr transform()
const double MAX_DOUBLE
Definition: plotwidget.cpp:70
Renderer for exporting a plot to a document, a printer or anything else, that is supported by QPainte...
bool isTrackerEnabled() const
void setStatisticsTitle(QString title)
QRectF maxZoomRect() const
void changeCurvesStyle(CurveStyle style)
QAction * _action_edit
Definition: plotwidget.h:167
const QPixmap & LoadSvg(QString filename, QString style_name="light")
Definition: svg_util.h:26
QAction * _action_formula
Definition: plotwidget.h:168
void splitVertical()
void trackerMoved(QPointF pos)
void enableXMin(bool)
Enable or disable minor vertical grid lines.
void reloadPlotData()
Definition: plotwidget.cpp:972
void setPen(const QColor &, qreal width=0.0, Qt::PenStyle=Qt::SolidLine)
void enableY(bool)
Enable or disable horizontal grid lines.
Range _custom_Y_limits
Definition: plotwidget.h:222
TimeseriesMap numeric
Numerical timeseries.
Definition: plotdata.h:38
virtual size_t size() const
Definition: plotdatabase.h:182
void zoomOut(bool emit_signal)
const std::list< CurveInfo > & curveList() const
void curvesDropped()
QAction * _action_zoomOutVertically
Definition: plotwidget.h:175
QAction * _action_zoomOutMaximum
Definition: plotwidget.h:173
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
Definition: json.hpp:21884
const Point & front() const
Definition: plotdatabase.h:244
QAction * _action_copy
Definition: plotwidget.h:177
QwtSeriesWrapper * createCurveXY(const PlotData *data_x, const PlotData *data_y)
Ellipse or circle.
Definition: qwt_symbol.h:44
void overrideCursonMove()
QAction * _action_split_vertical
Definition: plotwidget.h:170
void removeCurve(const QString &title) override
Definition: plotwidget.cpp:419
static void setStyle(QwtPlotCurve *curve, CurveStyle style)
virtual void updateCache(bool reset_old_data) override
void setCustomAxisLimits(Range range)
A class which draws a coordinate grid.
Definition: qwt_plot_grid.h:33
bool _context_menu_enabled
Definition: plotwidget.h:228
A class representing a text.
Definition: qwt_text.h:51
QString nameY() const
SnippetsMap GetSnippetsFromXML(const QString &xml_text)
QString nameX() const
bool canvasEventFilter(QEvent *event)
static bool if_xy_plot_failed_show_dialog
Definition: plotwidget.cpp:72
bool isEnabled() const
void update(Range range)
QAction * _action_image_to_clipboard
Definition: plotwidget.h:179
void configureTracker(CurveTracker::Parameter val)
void setFooter(const QString &)
Definition: qwt_plot.cpp:372
void on_zoomOutVertical_triggered(bool emit_signal=true)
void setTitle(QString title)
CurveStyle curveStyle() const
const PlotData * dataX() const
QwtPlotGrid * _grid
Definition: plotwidget.h:185
void rescaleEqualAxisScaling()
Definition: plotwidget.cpp:916
A scale map.
Definition: qwt_scale_map.h:26
const PlotData * dataY() const
void setEnabled(bool enable)
void on_copyToClipboard()
void removeAllCurves() override
Definition: plotwidget.cpp:461
void curveListChanged()
void on_zoomOutHorizontal_triggered(bool emit_signal=true)
bool eventFilter(QObject *obj, QEvent *event) override
int QwtAxisId
Axis identifier.
Definition: qwt_axis_id.h:26
color
Definition: color.h:23
bool keepRatioXY() const
virtual void updateCache(bool reset_old_data)
ScatterXYMap scatter_xy
Definition: plotdata.h:35
TransformSelector * _transform_select_dialog
Definition: plotwidget.h:224
int top(lua_State *L)
Definition: sol.hpp:11684
virtual void setVisible(bool)
void setModeXY(bool enable) override
void onDropEvent(QDropEvent *event)
Definition: plotwidget.cpp:531
void buildActions()
Definition: plotwidget.cpp:144
virtual ~PlotWidget() override
Definition: plotwidget.cpp:124
void setTransform(QString transform_ID)
QWidget * canvas()
Definition: qwt_plot.cpp:463
QRectF currentBoundingRect() const
void splitHorizontal()
void setTrackerPosition(double abs_time)
CurveInfo * curveFromTitle(const QString &title)
void attach(QwtPlot *plot)
Attach the item to a plot.
QAction * _action_saveToFile
Definition: plotwidget.h:176
void on_changeTimeOffset(double offset)
std::enable_if_t< all< Args... >::value, enable_t > enable
Definition: sol.hpp:2244
void onBackgroundColorRequest(QString name)
Range customAxisLimit() const
QString suggestedName() const
void setContextMenuEnabled(bool enabled)
Definition: plotwidget.cpp:139
const std::string & plotName() const
Definition: plotdatabase.h:167
void setAxisScale(QwtAxisId, double min, double max, double stepSize=0)
Disable autoscaling and specify a fixed scale for a selected axis.
double min
Definition: plotdatabase.h:31
std::vector< QString > curves
Definition: plotwidget.h:203
const Point & back() const
Definition: plotdatabase.h:249
virtual QwtScaleMap canvasMap(QwtAxisId) const
Definition: qwt_plot.cpp:800
QAction * _action_paste
Definition: plotwidget.h:178
A class for drawing scales.
span_constexpr std::size_t size(span< T, Extent > const &spn)
Definition: span.hpp:1485
void setDefaultRangeX()
void setPosition(const QPointF &pos)
void on_copyAction_triggered()
void onDataSourceRemoved(const std::string &src_name)
Definition: plotwidget.cpp:425
static const char * output
Definition: luac.c:38
bool isZoomLinkEnabled() const
void setZoomRectangle(QRectF rect, bool emit_signal)
Definition: plotwidget.cpp:945
void updateAvailableTransformers()
void activateGrid(bool activate)
enum PlotWidget::DragInfo::@32 mode
QColor getColorHint(PlotDataXY *data)
void setParameter(Parameter par)
PlotDataMapRef & datamap()
Definition: plotwidget.h:64
StatisticsDialog * _statistics_dialog
Definition: plotwidget.h:193
QAction * _action_zoomOutHorizontally
Definition: plotwidget.h:174
void enableTracker(bool enable)
void onDragLeaveEvent(QDragLeaveEvent *event)
Definition: plotwidget.cpp:526
void updateAxes()
Rebuild the axes scales.
QAction * _action_split_horizontal
Definition: plotwidget.h:169
void updateCurves(bool reset_older_data)
Definition: format.h:895
CurveInfo * addCurve(const std::string &name, QColor color=Qt::transparent)
Definition: plotwidget.cpp:393
std::basic_string< Char > format(const text_style &ts, const S &format_str, const Args &... args)
Definition: color.h:583
A class for drawing markers.
const QwtScaleDraw * axisScaleDraw(QwtAxisId) const
Return the scale draw of a specified axis.
void canvasContextMenuTriggered(const QPoint &pos)
Definition: plotwidget.cpp:230
void on_changeCurveColor(const QString &curve_name, QColor new_color)


plotjuggler
Author(s): Davide Faconti
autogenerated on Mon Jun 19 2023 03:01:38